zope2.13-2.13.21/0000755000175000017500000000000012214017707012140 5ustar arnauarnauzope2.13-2.13.21/COPYRIGHT0000644000175000017500000001220612214017707013434 0ustar arnauarnauzope.filerepresentation by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.testing by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.interface by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.traversing by Zope Foundation and Contributors is licensed under ZPL 2.1. MultiMapping by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.size by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.contenttype by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.browserpage by Zope Foundation and Contributors is licensed under ZPL 2.1. DateTime by Unknown is licensed under Unknown. zope.contentprovider by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.component by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.sendmail by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.dottedname by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.lifecycleevent by Zope Foundation and Contributors is licensed under ZPL 2.1. Products.ZCTextIndex by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.annotation by Zope Corporation and Contributors is licensed under ZPL 2.1. Persistence by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.viewlet by Zope Foundation and Contributors is licensed under ZPL 2.1. Products.MIMETools by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.i18nmessageid by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.publisher by Zope Foundation and Contributors is licensed under ZPL 2.1. Missing by Zope Foundation and Contributors is licensed under ZPL 2.1. Products.OFSP by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.sequencesort by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.testbrowser by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.event by Zope Foundation and Contributors is licensed under ZPL 2.1. Products.PythonScripts by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.browser by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.structuredtext by Zope Foundation and Contributors is licensed under ZPL 2.1. zExceptions by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.browsermenu by Zope Corporation and Contributors is licensed under UNKNOWN. zope.tal by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.exceptions by Zope Foundation and Contributors is licensed under ZPL 2.1. Products.MailHost by Zope Foundation and Contributors is licensed under ZPL 2.1. Products.BTreeFolder2 by Zope Foundation and Contributors is licensed under ZPL 2.1. ZopeUndo by Zope Corporation and Contributors is licensed under ZPL 2.1. ZConfig by Zope Foundation and Contributors is licensed under ZPL 2.1. Record by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.tales by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.schema by Zope Foundation and Contributors is licensed under ZPL 2.1. ExtensionClass by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.processlifetime by Zope Corporation and Contributors is licensed under ZPL 2.1. zc.lockfile by Jim Fulton is licensed under ZPL 2.1. Acquisition by Zope Foundation and Contributors is licensed under ZPL 2.1. AccessControl by Zope Foundation and Contributors is licensed under ZPL 2.1. Zope2 by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.proxy by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.broken by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.site by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.container by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.pagetemplate by Zope Foundation and Contributors is licensed under ZPL 2.1. zdaemon by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.browserresource by Zope Corporation and Contributors is licensed under UNKNOWN. zope.deferredimport by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.security by Zope Corporation and Contributors is licensed under ZPL 2.1. zope.configuration by Zope Foundation and Contributors is licensed under ZPL 2.1. Products.StandardCacheManagers by Zope Foundation and Contributors is licensed under ZPL 2.1. Products.ZCatalog by Zope Foundation and Contributors is licensed under ZPL 2.1. RestrictedPython by Zope Foundation and Contributors is licensed under ZPL 2.1. ZODB3 by Zope Foundation and Contributors is licensed under ZPL 2.1. DocumentTemplate by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.ptresource by Zope Corporation and Contributors is licensed under UNKNOWN. zLOG by Zope Corporation and Contributors is licensed under ZPL 2.1. transaction by Zope Corporation is licensed under ZPL 2.1. tempstorage by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.i18n by Zope Foundation and Contributors is licensed under ZPL 2.1. zope.location by Zope Corporation and Contributors is licensed under ZPL 2.1. Products.ExternalMethod by Zope Foundation and Contributors is licensed under ZPL 2.1. zope2.13-2.13.21/source/0000755000175000017500000000000012214017707013440 5ustar arnauarnauzope2.13-2.13.21/source/DateTime/0000755000175000017500000000000012214017435015132 5ustar arnauarnauzope2.13-2.13.21/source/DateTime/setup.py0000644000175000017500000000301712214017435016645 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import os from setuptools import setup, find_packages setup(name='DateTime', version = '2.12.7', url='http://pypi.python.org/pypi/DateTime', license='ZPL 2.1', description="""\ This package provides a DateTime data type, as known from Zope 2. Unless you need to communicate with Zope 2 APIs, you're probably better off using Python's bult-in datetime module.""", author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=open( os.path.join('src', 'DateTime', 'DateTime.txt')).read() + \ '\n\n' + open('CHANGES.txt').read(), packages=find_packages('src'), package_dir={'': 'src'}, install_requires=['zope.interface', 'pytz', ], include_package_data=True, test_suite='DateTime.tests.testDateTime.test_suite', zip_safe=False, ) zope2.13-2.13.21/source/DateTime/PKG-INFO0000644000175000017500000007676712214017435016256 0ustar arnauarnauMetadata-Version: 1.0 Name: DateTime Version: 2.12.7 Summary: This package provides a DateTime data type, as known from Zope 2. Unless you need to communicate with Zope 2 APIs, you're probably better off using Python's bult-in datetime module. Home-page: http://pypi.python.org/pypi/DateTime Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: The DateTime package ==================== Encapsulation of date/time values. Function Timezones() -------------------- Returns the list of recognized timezone names: >>> from DateTime import Timezones >>> zones = set(Timezones()) Almost all of the standard pytz timezones are included, with the exception of some commonly-used but ambiguous abbreviations, where historical Zope usage conflicts with the name used by pytz: >>> import pytz >>> [x for x in pytz.all_timezones if x not in zones] ['CET', 'EET', 'EST', 'MET', 'MST', 'WET'] Class DateTime -------------- DateTime objects represent instants in time and provide interfaces for controlling its representation without affecting the absolute value of the object. DateTime objects may be created from a wide variety of string or numeric data, or may be computed from other DateTime objects. DateTimes support the ability to convert their representations to many major timezones, as well as the ablility to create a DateTime object in the context of a given timezone. DateTime objects provide partial numerical behavior: * Two date-time objects can be subtracted to obtain a time, in days between the two. * A date-time object and a positive or negative number may be added to obtain a new date-time object that is the given number of days later than the input date-time object. * A positive or negative number and a date-time object may be added to obtain a new date-time object that is the given number of days later than the input date-time object. * A positive or negative number may be subtracted from a date-time object to obtain a new date-time object that is the given number of days earlier than the input date-time object. DateTime objects may be converted to integer, long, or float numbers of days since January 1, 1901, using the standard int, long, and float functions (Compatibility Note: int, long and float return the number of days since 1901 in GMT rather than local machine timezone). DateTime objects also provide access to their value in a float format usable with the python time module, provided that the value of the object falls in the range of the epoch-based time module. A DateTime object should be considered immutable; all conversion and numeric operations return a new DateTime object rather than modify the current object. A DateTime object always maintains its value as an absolute UTC time, and is represented in the context of some timezone based on the arguments used to create the object. A DateTime object's methods return values based on the timezone context. Note that in all cases the local machine timezone is used for representation if no timezone is specified. Constructor for DateTime ------------------------ DateTime() returns a new date-time object. DateTimes may be created with from zero to seven arguments: * If the function is called with no arguments, then the current date/ time is returned, represented in the timezone of the local machine. * If the function is invoked with a single string argument which is a recognized timezone name, an object representing the current time is returned, represented in the specified timezone. * If the function is invoked with a single string argument representing a valid date/time, an object representing that date/ time will be returned. As a general rule, any date-time representation that is recognized and unambigous to a resident of North America is acceptable. (The reason for this qualification is that in North America, a date like: 2/1/1994 is interpreted as February 1, 1994, while in some parts of the world, it is interpreted as January 2, 1994.) A date/ time string consists of two components, a date component and an optional time component, separated by one or more spaces. If the time component is omited, 12:00am is assumed. Any recognized timezone name specified as the final element of the date/time string will be used for computing the date/time value. (If you create a DateTime with the string "Mar 9, 1997 1:45pm US/Pacific", the value will essentially be the same as if you had captured time.time() at the specified date and time on a machine in that timezone). o Returns current date/time, represented in US/Eastern: >>> from DateTime import DateTime >>> e = DateTime('US/Eastern') >>> e.timezone() 'US/Eastern' o Returns specified time, represented in local machine zone: >>> x = DateTime('1997/3/9 1:45pm') >>> x.parts() # doctest: +ELLIPSIS (1997, 3, 9, 13, 45, 0.0, ...) o Specified time in local machine zone, verbose format: >>> y = DateTime('Mar 9, 1997 13:45:00') >>> y.parts() # doctest: +ELLIPSIS (1997, 3, 9, 13, 45, 0.0, ...) >>> y == x True The date component consists of year, month, and day values. The year value must be a one-, two-, or four-digit integer. If a one- or two-digit year is used, the year is assumed to be in the twentieth century. The month may an integer, from 1 to 12, a month name, or a month abreviation, where a period may optionally follow the abreviation. The day must be an integer from 1 to the number of days in the month. The year, month, and day values may be separated by periods, hyphens, forward, shashes, or spaces. Extra spaces are permitted around the delimiters. Year, month, and day values may be given in any order as long as it is possible to distinguish the components. If all three components are numbers that are less than 13, then a a month-day-year ordering is assumed. The time component consists of hour, minute, and second values separated by colons. The hour value must be an integer between 0 and 23 inclusively. The minute value must be an integer between 0 and 59 inclusively. The second value may be an integer value between 0 and 59.999 inclusively. The second value or both the minute and second values may be ommitted. The time may be followed by am or pm in upper or lower case, in which case a 12-hour clock is assumed. * If the DateTime function is invoked with a single Numeric argument, the number is assumed to be either a floating point value such as that returned by time.time() , or a number of days after January 1, 1901 00:00:00 UTC. A DateTime object is returned that represents either the gmt value of the time.time() float represented in the local machine's timezone, or that number of days after January 1, 1901. Note that the number of days after 1901 need to be expressed from the viewpoint of the local machine's timezone. A negative argument will yield a date-time value before 1901. * If the function is invoked with two numeric arguments, then the first is taken to be an integer year and the second argument is taken to be an offset in days from the beginning of the year, in the context of the local machine timezone. The date-time value returned is the given offset number of days from the beginning of the given year, represented in the timezone of the local machine. The offset may be positive or negative. Two-digit years are assumed to be in the twentieth century. * If the function is invoked with two arguments, the first a float representing a number of seconds past the epoch in gmt (such as those returned by time.time()) and the second a string naming a recognized timezone, a DateTime with a value of that gmt time will be returned, represented in the given timezone. >>> import time >>> t = time.time() Time t represented as US/Eastern: >>> now_east = DateTime(t, 'US/Eastern') Time t represented as US/Pacific: >>> now_west = DateTime(t, 'US/Pacific') Only their representations are different: >>> now_east == now_west True * If the function is invoked with three or more numeric arguments, then the first is taken to be an integer year, the second is taken to be an integer month, and the third is taken to be an integer day. If the combination of values is not valid, then a DateTimeError is raised. One- or two-digit years up to 69 are assumed to be in the 21st century, whereas values 70-99 are assumed to be 20th century. The fourth, fifth, and sixth arguments are floating point, positive or negative offsets in units of hours, minutes, and days, and default to zero if not given. An optional string may be given as the final argument to indicate timezone (the effect of this is as if you had taken the value of time.time() at that time on a machine in the specified timezone). If a string argument passed to the DateTime constructor cannot be parsed, it will raise DateTime.SyntaxError. Invalid date, time, or timezone components will raise a DateTime.DateTimeError. The module function Timezones() will return a list of the timezones recognized by the DateTime module. Recognition of timezone names is case-insensitive. Instance Methods for DateTime (IDateTime interface) --------------------------------------------------- Conversion and comparison methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``timeTime()`` returns the date/time as a floating-point number in UTC, in the format used by the python time module. Note that it is possible to create date /time values with DateTime that have no meaningful value to the time module, and in such cases a DateTimeError is raised. A DateTime object's value must generally be between Jan 1, 1970 (or your local machine epoch) and Jan 2038 to produce a valid time.time() style value. >>> dt = DateTime('Mar 9, 1997 13:45:00 US/Eastern') >>> dt.timeTime() 857933100.0 >>> DateTime('2040/01/01 UTC').timeTime() 2208988800.0 >>> DateTime('1900/01/01 UTC').timeTime() -2208988800.0 * ``toZone(z)`` returns a DateTime with the value as the current object, represented in the indicated timezone: >>> dt.toZone('UTC') DateTime('1997/03/09 18:45:00 UTC') >>> dt.toZone('UTC') == dt True * ``isFuture()`` returns true if this object represents a date/time later than the time of the call: >>> dt.isFuture() False >>> DateTime('Jan 1 3000').isFuture() # not time-machine safe! True * ``isPast()`` returns true if this object represents a date/time earlier than the time of the call: >>> dt.isPast() True >>> DateTime('Jan 1 3000').isPast() # not time-machine safe! False * ``isCurrentYear()`` returns true if this object represents a date/time that falls within the current year, in the context of this object's timezone representation: >>> dt.isCurrentYear() False >>> DateTime().isCurrentYear() True * ``isCurrentMonth()`` returns true if this object represents a date/time that falls within the current month, in the context of this object's timezone representation: >>> dt.isCurrentMonth() False >>> DateTime().isCurrentMonth() True * ``isCurrentDay()`` returns true if this object represents a date/time that falls within the current day, in the context of this object's timezone representation: >>> dt.isCurrentDay() False >>> DateTime().isCurrentDay() True * ``isCurrentHour()`` returns true if this object represents a date/time that falls within the current hour, in the context of this object's timezone representation: >>> dt.isCurrentHour() False >>> DateTime().isCurrentHour() True * ``isCurrentMinute()`` returns true if this object represents a date/time that falls within the current minute, in the context of this object's timezone representation: >>> dt.isCurrentMinute() False >>> DateTime().isCurrentMinute() True * ``isLeapYear()`` returns true if the current year (in the context of the object's timezone) is a leap year: >>> dt.isLeapYear() False >>> DateTime('Mar 8 2004').isLeapYear() True * ``earliestTime()`` returns a new DateTime object that represents the earliest possible time (in whole seconds) that still falls within the current object's day, in the object's timezone context: >>> dt.earliestTime() DateTime('1997/03/09 00:00:00 US/Eastern') * ``latestTime()`` return a new DateTime object that represents the latest possible time (in whole seconds) that still falls within the current object's day, in the object's timezone context >>> dt.latestTime() DateTime('1997/03/09 23:59:59 US/Eastern') Component access ~~~~~~~~~~~~~~~~ * ``parts()`` returns a tuple containing the calendar year, month, day, hour, minute second and timezone of the object >>> dt.parts() (1997, 3, 9, 13, 45, 0.0, 'US/Eastern') * ``timezone()`` returns the timezone in which the object is represented: >>> dt.timezone() in Timezones() True * ``tzoffset()`` returns the timezone offset for the objects timezone: >>> dt.tzoffset() -18000 * ``year()`` returns the calendar year of the object: >>> dt.year() 1997 * ``month()`` retursn the month of the object as an integer: >>> dt.month() 3 * ``Month()`` returns the full month name: >>> dt.Month() 'March' * ``aMonth()`` returns the abreviated month name: >>> dt.aMonth() 'Mar' * ``pMonth()`` returns the abreviated (with period) month name: >>> dt.pMonth() 'Mar.' * ``day()`` returns the integer day: >>> dt.day() 9 * ``Day()`` returns the full name of the day of the week: >>> dt.Day() 'Sunday' * ``dayOfYear()`` returns the day of the year, in context of the timezone representation of the object: >>> dt.dayOfYear() 68 * ``aDay()`` returns the abreviated name of the day of the week: >>> dt.aDay() 'Sun' * ``pDay()`` returns the abreviated (with period) name of the day of the week: >>> dt.pDay() 'Sun.' * ``dow()`` returns the integer day of the week, where Sunday is 0: >>> dt.dow() 0 * ``dow_1()`` returns the integer day of the week, where sunday is 1: >>> dt.dow_1() 1 * ``h_12()`` returns the 12-hour clock representation of the hour: >>> dt.h_12() 1 * ``h_24()`` returns the 24-hour clock representation of the hour: >>> dt.h_24() 13 * ``ampm()`` returns the appropriate time modifier (am or pm): >>> dt.ampm() 'pm' * ``hour()`` returns the 24-hour clock representation of the hour: >>> dt.hour() 13 * ``minute()`` returns the minute: >>> dt.minute() 45 * ``second()`` returns the second: >>> dt.second() 0.0 * ``millis()`` returns the milliseconds since the epoch in GMT. >>> dt.millis() 857933100000L strftime() ~~~~~~~~~~ See ``tests/testDateTime.py``. General formats from previous DateTime ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``Date()`` return the date string for the object: >>> dt.Date() '1997/03/09' * ``Time()`` returns the time string for an object to the nearest second: >>> dt.Time() '13:45:00' * ``TimeMinutes()`` returns the time string for an object not showing seconds: >>> dt.TimeMinutes() '13:45' * ``AMPM()`` returns the time string for an object to the nearest second: >>> dt.AMPM() '01:45:00 pm' * ``AMPMMinutes()`` returns the time string for an object not showing seconds: >>> dt.AMPMMinutes() '01:45 pm' * ``PreciseTime()`` returns the time string for the object: >>> dt.PreciseTime() '13:45:00.000' * ``PreciseAMPM()`` returns the time string for the object: >>> dt.PreciseAMPM() '01:45:00.000 pm' * ``yy()`` returns the calendar year as a 2 digit string >>> dt.yy() '97' * ``mm()`` returns the month as a 2 digit string >>> dt.mm() '03' * ``dd()`` returns the day as a 2 digit string: >>> dt.dd() '09' * ``rfc822()`` returns the date in RFC 822 format: >>> dt.rfc822() 'Sun, 09 Mar 1997 13:45:00 -0500' New formats ~~~~~~~~~~~ * ``fCommon()`` returns a string representing the object's value in the format: March 9, 1997 1:45 pm: >>> dt.fCommon() 'March 9, 1997 1:45 pm' * ``fCommonZ()`` returns a string representing the object's value in the format: March 9, 1997 1:45 pm US/Eastern: >>> dt.fCommonZ() 'March 9, 1997 1:45 pm US/Eastern' * ``aCommon()`` returns a string representing the object's value in the format: Mar 9, 1997 1:45 pm: >>> dt.aCommon() 'Mar 9, 1997 1:45 pm' * ``aCommonZ()`` return a string representing the object's value in the format: Mar 9, 1997 1:45 pm US/Eastern: >>> dt.aCommonZ() 'Mar 9, 1997 1:45 pm US/Eastern' * ``pCommon()`` returns a string representing the object's value in the format Mar. 9, 1997 1:45 pm: >>> dt.pCommon() 'Mar. 9, 1997 1:45 pm' * ``pCommonZ()`` returns a string representing the object's value in the format: Mar. 9, 1997 1:45 pm US/Eastern: >>> dt.pCommonZ() 'Mar. 9, 1997 1:45 pm US/Eastern' * ``ISO()`` returns a string with the date/time in ISO format. Note: this is not ISO 8601-format! See the ISO8601 and HTML4 methods below for ISO 8601-compliant output. Dates are output as: YYYY-MM-DD HH:MM:SS >>> dt.ISO() '1997-03-09 13:45:00' * ``ISO8601()`` returns the object in ISO 8601-compatible format containing the date, time with seconds-precision and the time zone identifier - see http://www.w3.org/TR/NOTE-datetime. Dates are output as: YYYY-MM-DDTHH:MM:SSTZD (T is a literal character, TZD is Time Zone Designator, format +HH:MM or -HH:MM). The ``HTML4()`` method below offers the same formatting, but converts to UTC before returning the value and sets the TZD"Z" >>> dt.ISO8601() '1997-03-09T13:45:00-05:00' * ``HTML4()`` returns the object in the format used in the HTML4.0 specification, one of the standard forms in ISO8601. See http://www.w3.org/TR/NOTE-datetime. Dates are output as: YYYY-MM-DDTHH:MM:SSZ (T, Z are literal characters, the time is in UTC.): >>> dt.HTML4() '1997-03-09T18:45:00Z' * ``JulianDay()`` returns the Julian day according to http://www.tondering.dk/claus/cal/node3.html#sec-calcjd >>> dt.JulianDay() 2450517 * ``week()`` returns the week number according to ISO see http://www.tondering.dk/claus/cal/node6.html#SECTION00670000000000000000 >>> dt.week() 10 Deprecated API ~~~~~~~~~~~~~~ * DayOfWeek(): see Day() * Day_(): see pDay() * Mon(): see aMonth() * Mon_(): see pMonth General Services Provided by DateTime ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DateTimes can be repr()'ed; the result will be a string indicating how to make a DateTime object like this: >>> `dt` "DateTime('1997/03/09 13:45:00 US/Eastern')" When we convert them into a string, we get a nicer string that could actually be shown to a user: >>> str(dt) '1997/03/09 13:45:00 US/Eastern' The hash value of a DateTime is based on the date and time and is equal for different representations of the DateTime: >>> hash(dt) 3618678 >>> hash(dt.toZone('UTC')) 3618678 A DateTime can be compared with another DateTime or float via ``cmp()``. NOTE: __cmp__ support is provided for backward compatibility only, and mixing DateTimes with ExtensionClasses could cause __cmp__ to break. You should use the methods lessThan, greaterThan, lessThanEqualTo, greaterThanEqualTo, equalTo and notEqualTo to avoid potential problems later! >>> cmp(dt, dt) 0 >>> cmp(dt, dt.toZone('UTC')) 0 >>> cmp(dt, dt.timeTime()) 0 >>> cmp(dt, DateTime('2000/01/01')) -1 >>> cmp(dt, DateTime('1900/01/01')) 1 DateTime objects can be compared to other DateTime objects OR floating point numbers such as the ones which are returned by the python time module. On comparison for equality, True is returned if the object represents a date/time equal to the specified DateTime or time module style time: >>> dt == dt True >>> dt == dt.toZone('UTC') True >>> dt == dt.timeTime() True >>> dt == DateTime() False >>> dt.equalTo(dt) True >>> dt.equalTo(dt.toZone('UTC')) True >>> dt.equalTo(dt.timeTime()) True >>> dt.equalTo(DateTime()) False Same goes for inequalities: >>> dt != dt False >>> dt != dt.toZone('UTC') False >>> dt != dt.timeTime() False >>> dt != DateTime() True >>> dt.notEqualTo(dt) False >>> dt.notEqualTo(dt.toZone('UTC')) False >>> dt.notEqualTo(dt.timeTime()) False >>> dt.notEqualTo(DateTime()) True >>> dt > dt False >>> DateTime() > dt True >>> dt > DateTime().timeTime() False >>> DateTime().timeTime() > dt True >>> dt.greaterThan(dt) False >>> DateTime().greaterThan(dt) True >>> dt.greaterThan(DateTime().timeTime()) False >>> dt >= dt True >>> DateTime() >= dt True >>> dt >= DateTime().timeTime() False >>> DateTime().timeTime() >= dt True >>> dt.greaterThanEqualTo(dt) True >>> DateTime().greaterThanEqualTo(dt) True >>> dt.greaterThanEqualTo(DateTime().timeTime()) False >>> dt < dt False >>> DateTime() < dt False >>> dt < DateTime().timeTime() True >>> DateTime().timeTime() < dt False >>> dt.lessThan(dt) False >>> DateTime().lessThan(dt) False >>> dt.lessThan(DateTime().timeTime()) True >>> dt <= dt True >>> DateTime() <= dt False >>> dt <= DateTime().timeTime() True >>> DateTime().timeTime() <= dt False >>> dt.lessThanEqualTo(dt) True >>> DateTime().lessThanEqualTo(dt) False >>> dt.lessThanEqualTo(DateTime().timeTime()) True Numeric Services Provided by DateTime ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A DateTime may be added to a number and a number may be added to a DateTime: >>> dt + 5 DateTime('1997/03/14 13:45:00 US/Eastern') >>> 5 + dt DateTime('1997/03/14 13:45:00 US/Eastern') Two DateTimes cannot be added: >>> dt + dt Traceback (most recent call last): ... DateTimeError: Cannot add two DateTimes Either a DateTime or a number may be subtracted from a DateTime, however, a DateTime may not be subtracted from a number: >>> DateTime('1997/03/10 13:45 US/Eastern') - dt 1.0 >>> dt - 1 DateTime('1997/03/08 13:45:00 US/Eastern') >>> 1 - dt Traceback (most recent call last): ... TypeError: unsupported operand type(s) for -: 'int' and 'instance' DateTimes can also be converted to integers (number of seconds since the epoch), longs (not too long ;)) and floats: >>> int(dt) 857933100 >>> long(dt) 857933100L >>> float(dt) 857933100.0 Changelog ========= 2.12.7 (2012-08-11) ------------------- - Added forward compatibility with DateTime 3 pickle format. DateTime instances constructed under version 3 can be read and unpickled by this version. The pickled data is converted to the current versions format (old-style class / no slots). Once converted it will be stored again in the old format. This should allow for a transparent upgrade/downgrade path between DateTime 2 and 3. 2.12.6 (2010-10-17) ------------------- - Changed ``testDayOfWeek`` test to be independent of OS locale. 2.12.5 (2010-07-29) ------------------- - Launchpad #143269: Corrected the documentation for year value behavior when constructing a DateTime object with three numeric arguments. - Launchpad #142521: Removed confusing special case in DateTime.__str__ where DateTime instances for midnight (e.g. '2010-07-27 00:00:00 US/Eastern') values would render only their date and nothing else. 2.12.4 (2010-07-12) ------------------- - Fixed mapping of EDT (was -> 'GMT-0400', now 'GMT-4'). 2.12.3 (2010-07-09) ------------------- - Added EDT timezone support. Addresses bug #599856. [vangheem] 2.12.2 (2010-05-05) ------------------- - Launchpad #572715: Relaxed pin on pytz, after applying a patch from Marius Gedminus which fixes the apparent API breakage. 2.12.1 (2010-04-30) ------------------- - Removed an undeclared testing dependency on zope.testing.doctest in favor of the standard libraries doctest module. - Added a maximum version requirement on pytz <= 2010b. Later versions produce test failures related to timezone changes. 2.12.0 (2009-03-04) ------------------- - Launchpad #290254: Forward-ported fix for '_micros'-less pickles from the Zope 2.11 branch version. 2.11.2 (2009-02-02) ------------------- - Include *all* pytz zone names, not just "common" ones. - Fix one fragile doctest, band-aid another. - Fix for launchpad #267545: DateTime(DateTime()) should preserve the correct hour. 2.11.1 (2008-08-05) ------------------- - DateTime conversion of datetime objects with non-pytz tzinfo. Timezones() returns a copy of the timezone list (allows tests to run). - Merged the slinkp-datetime-200007 branch: fix the DateTime(anotherDateTime) constructor to preserve timezones. 2.11.0b1 (2008-01-06) --------------------- - Split off from the Zope2 main source code tree. Platform: UNKNOWN zope2.13-2.13.21/source/DateTime/pip-egg-info/0000755000175000017500000000000012214017435017413 5ustar arnauarnauzope2.13-2.13.21/source/DateTime/pip-egg-info/DateTime.egg-info/0000755000175000017500000000000012214017435022601 5ustar arnauarnauzope2.13-2.13.21/source/DateTime/pip-egg-info/DateTime.egg-info/PKG-INFO0000644000175000017500000007676712214017435023725 0ustar arnauarnauMetadata-Version: 1.0 Name: DateTime Version: 2.12.7 Summary: This package provides a DateTime data type, as known from Zope 2. Unless you need to communicate with Zope 2 APIs, you're probably better off using Python's bult-in datetime module. Home-page: http://pypi.python.org/pypi/DateTime Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: The DateTime package ==================== Encapsulation of date/time values. Function Timezones() -------------------- Returns the list of recognized timezone names: >>> from DateTime import Timezones >>> zones = set(Timezones()) Almost all of the standard pytz timezones are included, with the exception of some commonly-used but ambiguous abbreviations, where historical Zope usage conflicts with the name used by pytz: >>> import pytz >>> [x for x in pytz.all_timezones if x not in zones] ['CET', 'EET', 'EST', 'MET', 'MST', 'WET'] Class DateTime -------------- DateTime objects represent instants in time and provide interfaces for controlling its representation without affecting the absolute value of the object. DateTime objects may be created from a wide variety of string or numeric data, or may be computed from other DateTime objects. DateTimes support the ability to convert their representations to many major timezones, as well as the ablility to create a DateTime object in the context of a given timezone. DateTime objects provide partial numerical behavior: * Two date-time objects can be subtracted to obtain a time, in days between the two. * A date-time object and a positive or negative number may be added to obtain a new date-time object that is the given number of days later than the input date-time object. * A positive or negative number and a date-time object may be added to obtain a new date-time object that is the given number of days later than the input date-time object. * A positive or negative number may be subtracted from a date-time object to obtain a new date-time object that is the given number of days earlier than the input date-time object. DateTime objects may be converted to integer, long, or float numbers of days since January 1, 1901, using the standard int, long, and float functions (Compatibility Note: int, long and float return the number of days since 1901 in GMT rather than local machine timezone). DateTime objects also provide access to their value in a float format usable with the python time module, provided that the value of the object falls in the range of the epoch-based time module. A DateTime object should be considered immutable; all conversion and numeric operations return a new DateTime object rather than modify the current object. A DateTime object always maintains its value as an absolute UTC time, and is represented in the context of some timezone based on the arguments used to create the object. A DateTime object's methods return values based on the timezone context. Note that in all cases the local machine timezone is used for representation if no timezone is specified. Constructor for DateTime ------------------------ DateTime() returns a new date-time object. DateTimes may be created with from zero to seven arguments: * If the function is called with no arguments, then the current date/ time is returned, represented in the timezone of the local machine. * If the function is invoked with a single string argument which is a recognized timezone name, an object representing the current time is returned, represented in the specified timezone. * If the function is invoked with a single string argument representing a valid date/time, an object representing that date/ time will be returned. As a general rule, any date-time representation that is recognized and unambigous to a resident of North America is acceptable. (The reason for this qualification is that in North America, a date like: 2/1/1994 is interpreted as February 1, 1994, while in some parts of the world, it is interpreted as January 2, 1994.) A date/ time string consists of two components, a date component and an optional time component, separated by one or more spaces. If the time component is omited, 12:00am is assumed. Any recognized timezone name specified as the final element of the date/time string will be used for computing the date/time value. (If you create a DateTime with the string "Mar 9, 1997 1:45pm US/Pacific", the value will essentially be the same as if you had captured time.time() at the specified date and time on a machine in that timezone). o Returns current date/time, represented in US/Eastern: >>> from DateTime import DateTime >>> e = DateTime('US/Eastern') >>> e.timezone() 'US/Eastern' o Returns specified time, represented in local machine zone: >>> x = DateTime('1997/3/9 1:45pm') >>> x.parts() # doctest: +ELLIPSIS (1997, 3, 9, 13, 45, 0.0, ...) o Specified time in local machine zone, verbose format: >>> y = DateTime('Mar 9, 1997 13:45:00') >>> y.parts() # doctest: +ELLIPSIS (1997, 3, 9, 13, 45, 0.0, ...) >>> y == x True The date component consists of year, month, and day values. The year value must be a one-, two-, or four-digit integer. If a one- or two-digit year is used, the year is assumed to be in the twentieth century. The month may an integer, from 1 to 12, a month name, or a month abreviation, where a period may optionally follow the abreviation. The day must be an integer from 1 to the number of days in the month. The year, month, and day values may be separated by periods, hyphens, forward, shashes, or spaces. Extra spaces are permitted around the delimiters. Year, month, and day values may be given in any order as long as it is possible to distinguish the components. If all three components are numbers that are less than 13, then a a month-day-year ordering is assumed. The time component consists of hour, minute, and second values separated by colons. The hour value must be an integer between 0 and 23 inclusively. The minute value must be an integer between 0 and 59 inclusively. The second value may be an integer value between 0 and 59.999 inclusively. The second value or both the minute and second values may be ommitted. The time may be followed by am or pm in upper or lower case, in which case a 12-hour clock is assumed. * If the DateTime function is invoked with a single Numeric argument, the number is assumed to be either a floating point value such as that returned by time.time() , or a number of days after January 1, 1901 00:00:00 UTC. A DateTime object is returned that represents either the gmt value of the time.time() float represented in the local machine's timezone, or that number of days after January 1, 1901. Note that the number of days after 1901 need to be expressed from the viewpoint of the local machine's timezone. A negative argument will yield a date-time value before 1901. * If the function is invoked with two numeric arguments, then the first is taken to be an integer year and the second argument is taken to be an offset in days from the beginning of the year, in the context of the local machine timezone. The date-time value returned is the given offset number of days from the beginning of the given year, represented in the timezone of the local machine. The offset may be positive or negative. Two-digit years are assumed to be in the twentieth century. * If the function is invoked with two arguments, the first a float representing a number of seconds past the epoch in gmt (such as those returned by time.time()) and the second a string naming a recognized timezone, a DateTime with a value of that gmt time will be returned, represented in the given timezone. >>> import time >>> t = time.time() Time t represented as US/Eastern: >>> now_east = DateTime(t, 'US/Eastern') Time t represented as US/Pacific: >>> now_west = DateTime(t, 'US/Pacific') Only their representations are different: >>> now_east == now_west True * If the function is invoked with three or more numeric arguments, then the first is taken to be an integer year, the second is taken to be an integer month, and the third is taken to be an integer day. If the combination of values is not valid, then a DateTimeError is raised. One- or two-digit years up to 69 are assumed to be in the 21st century, whereas values 70-99 are assumed to be 20th century. The fourth, fifth, and sixth arguments are floating point, positive or negative offsets in units of hours, minutes, and days, and default to zero if not given. An optional string may be given as the final argument to indicate timezone (the effect of this is as if you had taken the value of time.time() at that time on a machine in the specified timezone). If a string argument passed to the DateTime constructor cannot be parsed, it will raise DateTime.SyntaxError. Invalid date, time, or timezone components will raise a DateTime.DateTimeError. The module function Timezones() will return a list of the timezones recognized by the DateTime module. Recognition of timezone names is case-insensitive. Instance Methods for DateTime (IDateTime interface) --------------------------------------------------- Conversion and comparison methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``timeTime()`` returns the date/time as a floating-point number in UTC, in the format used by the python time module. Note that it is possible to create date /time values with DateTime that have no meaningful value to the time module, and in such cases a DateTimeError is raised. A DateTime object's value must generally be between Jan 1, 1970 (or your local machine epoch) and Jan 2038 to produce a valid time.time() style value. >>> dt = DateTime('Mar 9, 1997 13:45:00 US/Eastern') >>> dt.timeTime() 857933100.0 >>> DateTime('2040/01/01 UTC').timeTime() 2208988800.0 >>> DateTime('1900/01/01 UTC').timeTime() -2208988800.0 * ``toZone(z)`` returns a DateTime with the value as the current object, represented in the indicated timezone: >>> dt.toZone('UTC') DateTime('1997/03/09 18:45:00 UTC') >>> dt.toZone('UTC') == dt True * ``isFuture()`` returns true if this object represents a date/time later than the time of the call: >>> dt.isFuture() False >>> DateTime('Jan 1 3000').isFuture() # not time-machine safe! True * ``isPast()`` returns true if this object represents a date/time earlier than the time of the call: >>> dt.isPast() True >>> DateTime('Jan 1 3000').isPast() # not time-machine safe! False * ``isCurrentYear()`` returns true if this object represents a date/time that falls within the current year, in the context of this object's timezone representation: >>> dt.isCurrentYear() False >>> DateTime().isCurrentYear() True * ``isCurrentMonth()`` returns true if this object represents a date/time that falls within the current month, in the context of this object's timezone representation: >>> dt.isCurrentMonth() False >>> DateTime().isCurrentMonth() True * ``isCurrentDay()`` returns true if this object represents a date/time that falls within the current day, in the context of this object's timezone representation: >>> dt.isCurrentDay() False >>> DateTime().isCurrentDay() True * ``isCurrentHour()`` returns true if this object represents a date/time that falls within the current hour, in the context of this object's timezone representation: >>> dt.isCurrentHour() False >>> DateTime().isCurrentHour() True * ``isCurrentMinute()`` returns true if this object represents a date/time that falls within the current minute, in the context of this object's timezone representation: >>> dt.isCurrentMinute() False >>> DateTime().isCurrentMinute() True * ``isLeapYear()`` returns true if the current year (in the context of the object's timezone) is a leap year: >>> dt.isLeapYear() False >>> DateTime('Mar 8 2004').isLeapYear() True * ``earliestTime()`` returns a new DateTime object that represents the earliest possible time (in whole seconds) that still falls within the current object's day, in the object's timezone context: >>> dt.earliestTime() DateTime('1997/03/09 00:00:00 US/Eastern') * ``latestTime()`` return a new DateTime object that represents the latest possible time (in whole seconds) that still falls within the current object's day, in the object's timezone context >>> dt.latestTime() DateTime('1997/03/09 23:59:59 US/Eastern') Component access ~~~~~~~~~~~~~~~~ * ``parts()`` returns a tuple containing the calendar year, month, day, hour, minute second and timezone of the object >>> dt.parts() (1997, 3, 9, 13, 45, 0.0, 'US/Eastern') * ``timezone()`` returns the timezone in which the object is represented: >>> dt.timezone() in Timezones() True * ``tzoffset()`` returns the timezone offset for the objects timezone: >>> dt.tzoffset() -18000 * ``year()`` returns the calendar year of the object: >>> dt.year() 1997 * ``month()`` retursn the month of the object as an integer: >>> dt.month() 3 * ``Month()`` returns the full month name: >>> dt.Month() 'March' * ``aMonth()`` returns the abreviated month name: >>> dt.aMonth() 'Mar' * ``pMonth()`` returns the abreviated (with period) month name: >>> dt.pMonth() 'Mar.' * ``day()`` returns the integer day: >>> dt.day() 9 * ``Day()`` returns the full name of the day of the week: >>> dt.Day() 'Sunday' * ``dayOfYear()`` returns the day of the year, in context of the timezone representation of the object: >>> dt.dayOfYear() 68 * ``aDay()`` returns the abreviated name of the day of the week: >>> dt.aDay() 'Sun' * ``pDay()`` returns the abreviated (with period) name of the day of the week: >>> dt.pDay() 'Sun.' * ``dow()`` returns the integer day of the week, where Sunday is 0: >>> dt.dow() 0 * ``dow_1()`` returns the integer day of the week, where sunday is 1: >>> dt.dow_1() 1 * ``h_12()`` returns the 12-hour clock representation of the hour: >>> dt.h_12() 1 * ``h_24()`` returns the 24-hour clock representation of the hour: >>> dt.h_24() 13 * ``ampm()`` returns the appropriate time modifier (am or pm): >>> dt.ampm() 'pm' * ``hour()`` returns the 24-hour clock representation of the hour: >>> dt.hour() 13 * ``minute()`` returns the minute: >>> dt.minute() 45 * ``second()`` returns the second: >>> dt.second() 0.0 * ``millis()`` returns the milliseconds since the epoch in GMT. >>> dt.millis() 857933100000L strftime() ~~~~~~~~~~ See ``tests/testDateTime.py``. General formats from previous DateTime ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``Date()`` return the date string for the object: >>> dt.Date() '1997/03/09' * ``Time()`` returns the time string for an object to the nearest second: >>> dt.Time() '13:45:00' * ``TimeMinutes()`` returns the time string for an object not showing seconds: >>> dt.TimeMinutes() '13:45' * ``AMPM()`` returns the time string for an object to the nearest second: >>> dt.AMPM() '01:45:00 pm' * ``AMPMMinutes()`` returns the time string for an object not showing seconds: >>> dt.AMPMMinutes() '01:45 pm' * ``PreciseTime()`` returns the time string for the object: >>> dt.PreciseTime() '13:45:00.000' * ``PreciseAMPM()`` returns the time string for the object: >>> dt.PreciseAMPM() '01:45:00.000 pm' * ``yy()`` returns the calendar year as a 2 digit string >>> dt.yy() '97' * ``mm()`` returns the month as a 2 digit string >>> dt.mm() '03' * ``dd()`` returns the day as a 2 digit string: >>> dt.dd() '09' * ``rfc822()`` returns the date in RFC 822 format: >>> dt.rfc822() 'Sun, 09 Mar 1997 13:45:00 -0500' New formats ~~~~~~~~~~~ * ``fCommon()`` returns a string representing the object's value in the format: March 9, 1997 1:45 pm: >>> dt.fCommon() 'March 9, 1997 1:45 pm' * ``fCommonZ()`` returns a string representing the object's value in the format: March 9, 1997 1:45 pm US/Eastern: >>> dt.fCommonZ() 'March 9, 1997 1:45 pm US/Eastern' * ``aCommon()`` returns a string representing the object's value in the format: Mar 9, 1997 1:45 pm: >>> dt.aCommon() 'Mar 9, 1997 1:45 pm' * ``aCommonZ()`` return a string representing the object's value in the format: Mar 9, 1997 1:45 pm US/Eastern: >>> dt.aCommonZ() 'Mar 9, 1997 1:45 pm US/Eastern' * ``pCommon()`` returns a string representing the object's value in the format Mar. 9, 1997 1:45 pm: >>> dt.pCommon() 'Mar. 9, 1997 1:45 pm' * ``pCommonZ()`` returns a string representing the object's value in the format: Mar. 9, 1997 1:45 pm US/Eastern: >>> dt.pCommonZ() 'Mar. 9, 1997 1:45 pm US/Eastern' * ``ISO()`` returns a string with the date/time in ISO format. Note: this is not ISO 8601-format! See the ISO8601 and HTML4 methods below for ISO 8601-compliant output. Dates are output as: YYYY-MM-DD HH:MM:SS >>> dt.ISO() '1997-03-09 13:45:00' * ``ISO8601()`` returns the object in ISO 8601-compatible format containing the date, time with seconds-precision and the time zone identifier - see http://www.w3.org/TR/NOTE-datetime. Dates are output as: YYYY-MM-DDTHH:MM:SSTZD (T is a literal character, TZD is Time Zone Designator, format +HH:MM or -HH:MM). The ``HTML4()`` method below offers the same formatting, but converts to UTC before returning the value and sets the TZD"Z" >>> dt.ISO8601() '1997-03-09T13:45:00-05:00' * ``HTML4()`` returns the object in the format used in the HTML4.0 specification, one of the standard forms in ISO8601. See http://www.w3.org/TR/NOTE-datetime. Dates are output as: YYYY-MM-DDTHH:MM:SSZ (T, Z are literal characters, the time is in UTC.): >>> dt.HTML4() '1997-03-09T18:45:00Z' * ``JulianDay()`` returns the Julian day according to http://www.tondering.dk/claus/cal/node3.html#sec-calcjd >>> dt.JulianDay() 2450517 * ``week()`` returns the week number according to ISO see http://www.tondering.dk/claus/cal/node6.html#SECTION00670000000000000000 >>> dt.week() 10 Deprecated API ~~~~~~~~~~~~~~ * DayOfWeek(): see Day() * Day_(): see pDay() * Mon(): see aMonth() * Mon_(): see pMonth General Services Provided by DateTime ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DateTimes can be repr()'ed; the result will be a string indicating how to make a DateTime object like this: >>> `dt` "DateTime('1997/03/09 13:45:00 US/Eastern')" When we convert them into a string, we get a nicer string that could actually be shown to a user: >>> str(dt) '1997/03/09 13:45:00 US/Eastern' The hash value of a DateTime is based on the date and time and is equal for different representations of the DateTime: >>> hash(dt) 3618678 >>> hash(dt.toZone('UTC')) 3618678 A DateTime can be compared with another DateTime or float via ``cmp()``. NOTE: __cmp__ support is provided for backward compatibility only, and mixing DateTimes with ExtensionClasses could cause __cmp__ to break. You should use the methods lessThan, greaterThan, lessThanEqualTo, greaterThanEqualTo, equalTo and notEqualTo to avoid potential problems later! >>> cmp(dt, dt) 0 >>> cmp(dt, dt.toZone('UTC')) 0 >>> cmp(dt, dt.timeTime()) 0 >>> cmp(dt, DateTime('2000/01/01')) -1 >>> cmp(dt, DateTime('1900/01/01')) 1 DateTime objects can be compared to other DateTime objects OR floating point numbers such as the ones which are returned by the python time module. On comparison for equality, True is returned if the object represents a date/time equal to the specified DateTime or time module style time: >>> dt == dt True >>> dt == dt.toZone('UTC') True >>> dt == dt.timeTime() True >>> dt == DateTime() False >>> dt.equalTo(dt) True >>> dt.equalTo(dt.toZone('UTC')) True >>> dt.equalTo(dt.timeTime()) True >>> dt.equalTo(DateTime()) False Same goes for inequalities: >>> dt != dt False >>> dt != dt.toZone('UTC') False >>> dt != dt.timeTime() False >>> dt != DateTime() True >>> dt.notEqualTo(dt) False >>> dt.notEqualTo(dt.toZone('UTC')) False >>> dt.notEqualTo(dt.timeTime()) False >>> dt.notEqualTo(DateTime()) True >>> dt > dt False >>> DateTime() > dt True >>> dt > DateTime().timeTime() False >>> DateTime().timeTime() > dt True >>> dt.greaterThan(dt) False >>> DateTime().greaterThan(dt) True >>> dt.greaterThan(DateTime().timeTime()) False >>> dt >= dt True >>> DateTime() >= dt True >>> dt >= DateTime().timeTime() False >>> DateTime().timeTime() >= dt True >>> dt.greaterThanEqualTo(dt) True >>> DateTime().greaterThanEqualTo(dt) True >>> dt.greaterThanEqualTo(DateTime().timeTime()) False >>> dt < dt False >>> DateTime() < dt False >>> dt < DateTime().timeTime() True >>> DateTime().timeTime() < dt False >>> dt.lessThan(dt) False >>> DateTime().lessThan(dt) False >>> dt.lessThan(DateTime().timeTime()) True >>> dt <= dt True >>> DateTime() <= dt False >>> dt <= DateTime().timeTime() True >>> DateTime().timeTime() <= dt False >>> dt.lessThanEqualTo(dt) True >>> DateTime().lessThanEqualTo(dt) False >>> dt.lessThanEqualTo(DateTime().timeTime()) True Numeric Services Provided by DateTime ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A DateTime may be added to a number and a number may be added to a DateTime: >>> dt + 5 DateTime('1997/03/14 13:45:00 US/Eastern') >>> 5 + dt DateTime('1997/03/14 13:45:00 US/Eastern') Two DateTimes cannot be added: >>> dt + dt Traceback (most recent call last): ... DateTimeError: Cannot add two DateTimes Either a DateTime or a number may be subtracted from a DateTime, however, a DateTime may not be subtracted from a number: >>> DateTime('1997/03/10 13:45 US/Eastern') - dt 1.0 >>> dt - 1 DateTime('1997/03/08 13:45:00 US/Eastern') >>> 1 - dt Traceback (most recent call last): ... TypeError: unsupported operand type(s) for -: 'int' and 'instance' DateTimes can also be converted to integers (number of seconds since the epoch), longs (not too long ;)) and floats: >>> int(dt) 857933100 >>> long(dt) 857933100L >>> float(dt) 857933100.0 Changelog ========= 2.12.7 (2012-08-11) ------------------- - Added forward compatibility with DateTime 3 pickle format. DateTime instances constructed under version 3 can be read and unpickled by this version. The pickled data is converted to the current versions format (old-style class / no slots). Once converted it will be stored again in the old format. This should allow for a transparent upgrade/downgrade path between DateTime 2 and 3. 2.12.6 (2010-10-17) ------------------- - Changed ``testDayOfWeek`` test to be independent of OS locale. 2.12.5 (2010-07-29) ------------------- - Launchpad #143269: Corrected the documentation for year value behavior when constructing a DateTime object with three numeric arguments. - Launchpad #142521: Removed confusing special case in DateTime.__str__ where DateTime instances for midnight (e.g. '2010-07-27 00:00:00 US/Eastern') values would render only their date and nothing else. 2.12.4 (2010-07-12) ------------------- - Fixed mapping of EDT (was -> 'GMT-0400', now 'GMT-4'). 2.12.3 (2010-07-09) ------------------- - Added EDT timezone support. Addresses bug #599856. [vangheem] 2.12.2 (2010-05-05) ------------------- - Launchpad #572715: Relaxed pin on pytz, after applying a patch from Marius Gedminus which fixes the apparent API breakage. 2.12.1 (2010-04-30) ------------------- - Removed an undeclared testing dependency on zope.testing.doctest in favor of the standard libraries doctest module. - Added a maximum version requirement on pytz <= 2010b. Later versions produce test failures related to timezone changes. 2.12.0 (2009-03-04) ------------------- - Launchpad #290254: Forward-ported fix for '_micros'-less pickles from the Zope 2.11 branch version. 2.11.2 (2009-02-02) ------------------- - Include *all* pytz zone names, not just "common" ones. - Fix one fragile doctest, band-aid another. - Fix for launchpad #267545: DateTime(DateTime()) should preserve the correct hour. 2.11.1 (2008-08-05) ------------------- - DateTime conversion of datetime objects with non-pytz tzinfo. Timezones() returns a copy of the timezone list (allows tests to run). - Merged the slinkp-datetime-200007 branch: fix the DateTime(anotherDateTime) constructor to preserve timezones. 2.11.0b1 (2008-01-06) --------------------- - Split off from the Zope2 main source code tree. Platform: UNKNOWN zope2.13-2.13.21/source/DateTime/pip-egg-info/DateTime.egg-info/dependency_links.txt0000644000175000017500000000000112214017435026647 0ustar arnauarnau zope2.13-2.13.21/source/DateTime/pip-egg-info/DateTime.egg-info/requires.txt0000644000175000017500000000002312214017435025174 0ustar arnauarnauzope.interface pytzzope2.13-2.13.21/source/DateTime/pip-egg-info/DateTime.egg-info/top_level.txt0000644000175000017500000000001112214017435025323 0ustar arnauarnauDateTime zope2.13-2.13.21/source/DateTime/pip-egg-info/DateTime.egg-info/SOURCES.txt0000644000175000017500000000121712214017435024466 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt MANIFEST.in README.txt setup.cfg pip-egg-info/DateTime.egg-info/PKG-INFO pip-egg-info/DateTime.egg-info/SOURCES.txt pip-egg-info/DateTime.egg-info/dependency_links.txt pip-egg-info/DateTime.egg-info/not-zip-safe pip-egg-info/DateTime.egg-info/requires.txt pip-egg-info/DateTime.egg-info/top_level.txt src/DateTime/DateTime.py src/DateTime/DateTime.txt src/DateTime/DateTimeZone.py src/DateTime/__init__.py src/DateTime/interfaces.py src/DateTime/pytz.txt src/DateTime/pytz_support.py src/DateTime/tests/__init__.py src/DateTime/tests/julian_testdata.txt src/DateTime/tests/legacy.py src/DateTime/tests/testDateTime.pyzope2.13-2.13.21/source/DateTime/pip-egg-info/DateTime.egg-info/not-zip-safe0000644000175000017500000000000112214017435025027 0ustar arnauarnau zope2.13-2.13.21/source/DateTime/LICENSE.txt0000644000175000017500000000402612214017435016757 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/DateTime/README.txt0000644000175000017500000000005312214017435016626 0ustar arnauarnauPlease refer to src/DateTime/DateTime.txt. zope2.13-2.13.21/source/DateTime/setup.cfg0000644000175000017500000000007312214017435016753 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/DateTime/MANIFEST.in0000644000175000017500000000020412214017435016664 0ustar arnauarnauinclude *.txt recursive-include src/DateTime * global-exclude *.dll global-exclude *.pyc global-exclude *.pyo global-exclude *.so zope2.13-2.13.21/source/DateTime/COPYRIGHT.txt0000644000175000017500000000004012214017435017235 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/DateTime/buildout.cfg0000644000175000017500000000030012214017435017433 0ustar arnauarnau[buildout] develop = . parts = interpreter test unzip = true [interpreter] recipe = zc.recipe.egg interpreter = python eggs = DateTime [test] recipe = zc.recipe.testrunner eggs = DateTime zope2.13-2.13.21/source/DateTime/bootstrap.py0000644000175000017500000000733612214017435017532 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id$ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/DateTime/CHANGES.txt0000644000175000017500000000454012214017435016746 0ustar arnauarnauChangelog ========= 2.12.7 (2012-08-11) ------------------- - Added forward compatibility with DateTime 3 pickle format. DateTime instances constructed under version 3 can be read and unpickled by this version. The pickled data is converted to the current versions format (old-style class / no slots). Once converted it will be stored again in the old format. This should allow for a transparent upgrade/downgrade path between DateTime 2 and 3. 2.12.6 (2010-10-17) ------------------- - Changed ``testDayOfWeek`` test to be independent of OS locale. 2.12.5 (2010-07-29) ------------------- - Launchpad #143269: Corrected the documentation for year value behavior when constructing a DateTime object with three numeric arguments. - Launchpad #142521: Removed confusing special case in DateTime.__str__ where DateTime instances for midnight (e.g. '2010-07-27 00:00:00 US/Eastern') values would render only their date and nothing else. 2.12.4 (2010-07-12) ------------------- - Fixed mapping of EDT (was -> 'GMT-0400', now 'GMT-4'). 2.12.3 (2010-07-09) ------------------- - Added EDT timezone support. Addresses bug #599856. [vangheem] 2.12.2 (2010-05-05) ------------------- - Launchpad #572715: Relaxed pin on pytz, after applying a patch from Marius Gedminus which fixes the apparent API breakage. 2.12.1 (2010-04-30) ------------------- - Removed an undeclared testing dependency on zope.testing.doctest in favor of the standard libraries doctest module. - Added a maximum version requirement on pytz <= 2010b. Later versions produce test failures related to timezone changes. 2.12.0 (2009-03-04) ------------------- - Launchpad #290254: Forward-ported fix for '_micros'-less pickles from the Zope 2.11 branch version. 2.11.2 (2009-02-02) ------------------- - Include *all* pytz zone names, not just "common" ones. - Fix one fragile doctest, band-aid another. - Fix for launchpad #267545: DateTime(DateTime()) should preserve the correct hour. 2.11.1 (2008-08-05) ------------------- - DateTime conversion of datetime objects with non-pytz tzinfo. Timezones() returns a copy of the timezone list (allows tests to run). - Merged the slinkp-datetime-200007 branch: fix the DateTime(anotherDateTime) constructor to preserve timezones. 2.11.0b1 (2008-01-06) --------------------- - Split off from the Zope2 main source code tree. zope2.13-2.13.21/source/DateTime/src/0000755000175000017500000000000012214017435015721 5ustar arnauarnauzope2.13-2.13.21/source/DateTime/src/DateTime/0000755000175000017500000000000012214017435017415 5ustar arnauarnauzope2.13-2.13.21/source/DateTime/src/DateTime/pytz_support.py0000644000175000017500000002554112214017435022600 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Pytz timezone support. """ import pytz import pytz.reference from pytz.tzinfo import StaticTzInfo, memorized_timedelta from datetime import datetime, timedelta import re from interfaces import DateTimeError EPOCH = datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc) _numeric_timezone_data = { 'GMT': ('GMT', 0, 1, [], '', [(0, 0, 0)], 'GMT\000'), 'GMT+0': ('GMT+0', 0, 1, [], '', [(0, 0, 0)], 'GMT+0000\000'), 'GMT+1': ('GMT+1', 0, 1, [], '', [(3600, 0, 0)], 'GMT+0100\000'), 'GMT+2': ('GMT+2', 0, 1, [], '', [(7200, 0, 0)], 'GMT+0200\000'), 'GMT+3': ('GMT+3', 0, 1, [], '', [(10800, 0, 0)], 'GMT+0300\000'), 'GMT+4': ('GMT+4', 0, 1, [], '', [(14400, 0, 0)], 'GMT+0400\000'), 'GMT+5': ('GMT+5', 0, 1, [], '', [(18000, 0, 0)], 'GMT+0500\000'), 'GMT+6': ('GMT+6', 0, 1, [], '', [(21600, 0, 0)], 'GMT+0600\000'), 'GMT+7': ('GMT+7', 0, 1, [], '', [(25200, 0, 0)], 'GMT+0700\000'), 'GMT+8': ('GMT+8', 0, 1, [], '', [(28800, 0, 0)], 'GMT+0800\000'), 'GMT+9': ('GMT+9', 0, 1, [], '', [(32400, 0, 0)], 'GMT+0900\000'), 'GMT+10': ('GMT+10', 0, 1, [], '', [(36000, 0, 0)], 'GMT+1000\000'), 'GMT+11': ('GMT+11', 0, 1, [], '', [(39600, 0, 0)], 'GMT+1100\000'), 'GMT+12': ('GMT+12', 0, 1, [], '', [(43200, 0, 0)], 'GMT+1200\000'), 'GMT+13': ('GMT+13', 0, 1, [], '', [(46800, 0, 0)], 'GMT+1300\000'), 'GMT-1': ('GMT-1', 0, 1, [], '', [(-3600, 0, 0)], 'GMT-0100\000'), 'GMT-2': ('GMT-2', 0, 1, [], '', [(-7200, 0, 0)], 'GMT-0200\000'), 'GMT-3': ('GMT-3', 0, 1, [], '', [(-10800, 0, 0)], 'GMT-0300\000'), 'GMT-4': ('GMT-4', 0, 1, [], '', [(-14400, 0, 0)], 'GMT-0400\000'), 'GMT-5': ('GMT-5', 0, 1, [], '', [(-18000, 0, 0)], 'GMT-0500\000'), 'GMT-6': ('GMT-6', 0, 1, [], '', [(-21600, 0, 0)], 'GMT-0600\000'), 'GMT-7': ('GMT-7', 0, 1, [], '', [(-25200, 0, 0)], 'GMT-0700\000'), 'GMT-8': ('GMT-8', 0, 1, [], '', [(-28800, 0, 0)], 'GMT-0800\000'), 'GMT-9': ('GMT-9', 0, 1, [], '', [(-32400, 0, 0)], 'GMT-0900\000'), 'GMT-10': ('GMT-10', 0, 1, [], '', [(-36000, 0, 0)], 'GMT-1000\000'), 'GMT-11': ('GMT-11', 0, 1, [], '', [(-39600, 0, 0)], 'GMT-1100\000'), 'GMT-12': ('GMT-12', 0, 1, [], '', [(-43200, 0, 0)], 'GMT-1200\000'), 'GMT+0130': ('GMT+0130', 0, 1, [], '', [(5400, 0, 0)], 'GMT+0130\000'), 'GMT+0230': ('GMT+0230', 0, 1, [], '', [(9000, 0, 0)], 'GMT+0230\000'), 'GMT+0330': ('GMT+0330', 0, 1, [], '', [(12600, 0, 0)], 'GMT+0330\000'), 'GMT+0430': ('GMT+0430', 0, 1, [], '', [(16200, 0, 0)], 'GMT+0430\000'), 'GMT+0530': ('GMT+0530', 0, 1, [], '', [(19800, 0, 0)], 'GMT+0530\000'), 'GMT+0630': ('GMT+0630', 0, 1, [], '', [(23400, 0, 0)], 'GMT+0630\000'), 'GMT+0730': ('GMT+0730', 0, 1, [], '', [(27000, 0, 0)], 'GMT+0730\000'), 'GMT+0830': ('GMT+0830', 0, 1, [], '', [(30600, 0, 0)], 'GMT+0830\000'), 'GMT+0930': ('GMT+0930', 0, 1, [], '', [(34200, 0, 0)], 'GMT+0930\000'), 'GMT+1030': ('GMT+1030', 0, 1, [], '', [(37800, 0, 0)], 'GMT+1030\000'), 'GMT+1130': ('GMT+1130', 0, 1, [], '', [(41400, 0, 0)], 'GMT+1130\000'), 'GMT+1230': ('GMT+1230', 0, 1, [], '', [(45000, 0, 0)], 'GMT+1230\000'), 'GMT-0130': ('GMT-0130', 0, 1, [], '', [(-5400, 0, 0)], 'GMT-0130\000'), 'GMT-0230': ('GMT-0230', 0, 1, [], '', [(-9000, 0, 0)], 'GMT-0230\000'), 'GMT-0330': ('GMT-0330', 0, 1, [], '', [(-12600, 0, 0)], 'GMT-0330\000'), 'GMT-0430': ('GMT-0430', 0, 1, [], '', [(-16200, 0, 0)], 'GMT-0430\000'), 'GMT-0530': ('GMT-0530', 0, 1, [], '', [(-19800, 0, 0)], 'GMT-0530\000'), 'GMT-0630': ('GMT-0630', 0, 1, [], '', [(-23400, 0, 0)], 'GMT-0630\000'), 'GMT-0730': ('GMT-0730', 0, 1, [], '', [(-27000, 0, 0)], 'GMT-0730\000'), 'GMT-0830': ('GMT-0830', 0, 1, [], '', [(-30600, 0, 0)], 'GMT-0830\000'), 'GMT-0930': ('GMT-0930', 0, 1, [], '', [(-34200, 0, 0)], 'GMT-0930\000'), 'GMT-1030': ('GMT-1030', 0, 1, [], '', [(-37800, 0, 0)], 'GMT-1030\000'), 'GMT-1130': ('GMT-1130', 0, 1, [], '', [(-41400, 0, 0)], 'GMT-1130\000'), 'GMT-1230': ('GMT-1230', 0, 1, [], '', [(-45000, 0, 0)], 'GMT-1230\000'), } # These are the timezones not in pytz.common_timezones _old_zlst = [ 'AST', 'AT', 'BST', 'BT', 'CCT', 'CET', 'CST', 'Cuba', 'EADT', 'EAST', 'EEST', 'EET', 'EST', 'Egypt', 'FST', 'FWT', 'GB-Eire', 'GMT+0100', 'GMT+0130', 'GMT+0200', 'GMT+0230', 'GMT+0300', 'GMT+0330', 'GMT+0400', 'GMT+0430', 'GMT+0500', 'GMT+0530', 'GMT+0600', 'GMT+0630', 'GMT+0700', 'GMT+0730', 'GMT+0800', 'GMT+0830', 'GMT+0900', 'GMT+0930', 'GMT+1', 'GMT+1000', 'GMT+1030', 'GMT+1100', 'GMT+1130', 'GMT+1200', 'GMT+1230', 'GMT+1300', 'GMT-0100', 'GMT-0130', 'GMT-0200', 'GMT-0300', 'GMT-0400', 'GMT-0500', 'GMT-0600', 'GMT-0630', 'GMT-0700', 'GMT-0730', 'GMT-0800', 'GMT-0830', 'GMT-0900', 'GMT-0930', 'GMT-1000', 'GMT-1030', 'GMT-1100', 'GMT-1130', 'GMT-1200', 'GMT-1230', 'GST', 'Greenwich', 'Hongkong', 'IDLE', 'IDLW', 'Iceland', 'Iran', 'Israel', 'JST', 'Jamaica', 'Japan', 'MEST', 'MET', 'MEWT', 'MST', 'NT', 'NZDT', 'NZST', 'NZT', 'PST', 'Poland', 'SST', 'SWT', 'Singapore', 'Turkey', 'UCT', 'UT', 'Universal', 'WADT', 'WAST', 'WAT', 'WET', 'ZP4', 'ZP5', 'ZP6', ] _old_zmap={ 'aest':'GMT+10', 'aedt':'GMT+11', 'aus eastern standard time':'GMT+10', 'sydney standard time':'GMT+10', 'tasmania standard time':'GMT+10', 'e. australia standard time':'GMT+10', 'aus central standard time':'GMT+0930', 'cen. australia standard time':'GMT+0930', 'w. australia standard time':'GMT+8', 'central europe standard time':'GMT+1', 'eastern standard time':'US/Eastern', 'us eastern standard time':'US/Eastern', 'central standard time':'US/Central', 'mountain standard time':'US/Mountain', 'pacific standard time':'US/Pacific', 'mst':'US/Mountain','pst':'US/Pacific', 'cst':'US/Central','est':'US/Eastern', 'gmt+0000':'GMT+0', 'gmt+0':'GMT+0', 'gmt+0100':'GMT+1', 'gmt+0200':'GMT+2', 'gmt+0300':'GMT+3', 'gmt+0400':'GMT+4', 'gmt+0500':'GMT+5', 'gmt+0600':'GMT+6', 'gmt+0700':'GMT+7', 'gmt+0800':'GMT+8', 'gmt+0900':'GMT+9', 'gmt+1000':'GMT+10','gmt+1100':'GMT+11','gmt+1200':'GMT+12', 'gmt+1300':'GMT+13', 'gmt-0100':'GMT-1', 'gmt-0200':'GMT-2', 'gmt-0300':'GMT-3', 'gmt-0400':'GMT-4', 'gmt-0500':'GMT-5', 'gmt-0600':'GMT-6', 'gmt-0700':'GMT-7', 'gmt-0800':'GMT-8', 'gmt-0900':'GMT-9', 'gmt-1000':'GMT-10','gmt-1100':'GMT-11','gmt-1200':'GMT-12', 'gmt+1': 'GMT+1', 'gmt+2': 'GMT+2', 'gmt+3': 'GMT+3', 'gmt+4': 'GMT+4', 'gmt+5': 'GMT+5', 'gmt+6': 'GMT+6', 'gmt+7': 'GMT+7', 'gmt+8': 'GMT+8', 'gmt+9': 'GMT+9', 'gmt+10':'GMT+10','gmt+11':'GMT+11','gmt+12':'GMT+12', 'gmt+13':'GMT+13', 'gmt-1': 'GMT-1', 'gmt-2': 'GMT-2', 'gmt-3': 'GMT-3', 'gmt-4': 'GMT-4', 'gmt-5': 'GMT-5', 'gmt-6': 'GMT-6', 'gmt-7': 'GMT-7', 'gmt-8': 'GMT-8', 'gmt-9': 'GMT-9', 'gmt-10':'GMT-10','gmt-11':'GMT-11','gmt-12':'GMT-12', 'gmt+130':'GMT+0130', 'gmt+0130':'GMT+0130', 'gmt+230':'GMT+0230', 'gmt+0230':'GMT+0230', 'gmt+330':'GMT+0330', 'gmt+0330':'GMT+0330', 'gmt+430':'GMT+0430', 'gmt+0430':'GMT+0430', 'gmt+530':'GMT+0530', 'gmt+0530':'GMT+0530', 'gmt+630':'GMT+0630', 'gmt+0630':'GMT+0630', 'gmt+730':'GMT+0730', 'gmt+0730':'GMT+0730', 'gmt+830':'GMT+0830', 'gmt+0830':'GMT+0830', 'gmt+930':'GMT+0930', 'gmt+0930':'GMT+0930', 'gmt+1030':'GMT+1030', 'gmt+1130':'GMT+1130', 'gmt+1230':'GMT+1230', 'gmt-130':'GMT-0130', 'gmt-0130':'GMT-0130', 'gmt-230':'GMT-0230', 'gmt-0230':'GMT-0230', 'gmt-330':'GMT-0330', 'gmt-0330':'GMT-0330', 'gmt-430':'GMT-0430', 'gmt-0430':'GMT-0430', 'gmt-530':'GMT-0530', 'gmt-0530':'GMT-0530', 'gmt-630':'GMT-0630', 'gmt-0630':'GMT-0630', 'gmt-730':'GMT-0730', 'gmt-0730':'GMT-0730', 'gmt-830':'GMT-0830', 'gmt-0830':'GMT-0830', 'gmt-930':'GMT-0930', 'gmt-0930':'GMT-0930', 'gmt-1030':'GMT-1030', 'gmt-1130':'GMT-1130', 'gmt-1230':'GMT-1230', 'ut':'Universal', 'bst':'GMT+1', 'mest':'GMT+2', 'sst':'GMT+2', 'fst':'GMT+2', 'wadt':'GMT+8', 'eadt':'GMT+11', 'nzdt':'GMT+13', 'wet':'GMT', 'wat':'GMT-1', 'at':'GMT-2', 'ast':'GMT-4', 'nt':'GMT-11', 'idlw':'GMT-12', 'cet':'GMT+1', 'cest':'GMT+2', 'met':'GMT+1', 'mewt':'GMT+1', 'swt':'GMT+1', 'fwt':'GMT+1', 'eet':'GMT+2', 'eest':'GMT+3', 'bt':'GMT+3', 'zp4':'GMT+4', 'zp5':'GMT+5', 'zp6':'GMT+6', 'wast':'GMT+7', 'cct':'GMT+8', 'jst':'GMT+9', 'east':'GMT+10', 'gst':'GMT+10', 'nzt':'GMT+12', 'nzst':'GMT+12', 'idle':'GMT+12', 'ret':'GMT+4', 'ist': 'GMT+0530', 'edt' : 'GMT-4' } def _static_timezone_factory(data): zone = data[0] cls = type(zone, (StaticTzInfo,), dict( zone=zone, _utcoffset=memorized_timedelta(data[5][0][0]), _tzname=data[6][:-1] )) # strip the trailing null return cls() _numeric_timezones = dict((key, _static_timezone_factory(data)) for key, data in _numeric_timezone_data.items()) class Timezone: """ Timezone information returned by PytzCache.__getitem__ Adapts datetime.tzinfo object to DateTime._timezone interface """ def __init__(self, tzinfo): self.tzinfo = tzinfo def info(self, t=None): if t is None: dt = datetime.utcnow().replace(tzinfo=pytz.utc) else: dt = EPOCH + timedelta(0, t) # can't use utcfromtimestamp past 2038 # need to normalize tzinfo for the datetime to deal with # daylight savings time. normalized_dt = self.tzinfo.normalize(dt.astimezone(self.tzinfo)) normalized_tzinfo = normalized_dt.tzinfo offset = normalized_tzinfo.utcoffset(normalized_dt) secs = offset.days * 24 * 60 * 60 + offset.seconds dst = normalized_tzinfo.dst(normalized_dt) if dst == timedelta(0): is_dst = 0 else: is_dst = 1 return secs, is_dst, normalized_tzinfo.tzname(normalized_dt) class PytzCache: """ Reimplementation of the DateTime._cache class that uses for timezone info """ _zlst = pytz.common_timezones + _old_zlst # used by DateTime.TimeZones _zmap = dict((name.lower(), name) for name in pytz.all_timezones) _zmap.update(_old_zmap) # These must take priority _zidx = _zmap.keys() def __getitem__(self, key): name = self._zmap.get(key.lower(), key) # fallback to key try: return Timezone(pytz.timezone(name)) except pytz.UnknownTimeZoneError: try: return Timezone(_numeric_timezones[name]) except KeyError: raise DateTimeError,'Unrecognized timezone: %s' % key zope2.13-2.13.21/source/DateTime/src/DateTime/DateTimeZone.py0000644000175000017500000020566412214017435022334 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## # This data is used only for testing legacy compatibility with pytz _data={ 'GMT': ('GMT', 0, 1, [], '', [(0, 0, 0)], 'GMT\000'), 'GMT+0': ('GMT+0', 0, 1, [], '', [(0, 0, 0)], 'GMT+0000\000'), 'GMT+1': ('GMT+1', 0, 1, [], '', [(3600, 0, 0)], 'GMT+0100\000'), 'GMT+2': ('GMT+2', 0, 1, [], '', [(7200, 0, 0)], 'GMT+0200\000'), 'GMT+3': ('GMT+3', 0, 1, [], '', [(10800, 0, 0)], 'GMT+0300\000'), 'GMT+4': ('GMT+4', 0, 1, [], '', [(14400, 0, 0)], 'GMT+0400\000'), 'GMT+5': ('GMT+5', 0, 1, [], '', [(18000, 0, 0)], 'GMT+0500\000'), 'GMT+6': ('GMT+6', 0, 1, [], '', [(21600, 0, 0)], 'GMT+0600\000'), 'GMT+7': ('GMT+7', 0, 1, [], '', [(25200, 0, 0)], 'GMT+0700\000'), 'GMT+8': ('GMT+8', 0, 1, [], '', [(28800, 0, 0)], 'GMT+0800\000'), 'GMT+9': ('GMT+9', 0, 1, [], '', [(32400, 0, 0)], 'GMT+0900\000'), 'GMT+10': ('GMT+10', 0, 1, [], '', [(36000, 0, 0)], 'GMT+1000\000'), 'GMT+11': ('GMT+11', 0, 1, [], '', [(39600, 0, 0)], 'GMT+1100\000'), 'GMT+12': ('GMT+12', 0, 1, [], '', [(43200, 0, 0)], 'GMT+1200\000'), 'GMT+13': ('GMT+13', 0, 1, [], '', [(46800, 0, 0)], 'GMT+1300\000'), 'GMT-1': ('GMT-1', 0, 1, [], '', [(-3600, 0, 0)], 'GMT-0100\000'), 'GMT-2': ('GMT-2', 0, 1, [], '', [(-7200, 0, 0)], 'GMT-0200\000'), 'GMT-3': ('GMT-3', 0, 1, [], '', [(-10800, 0, 0)], 'GMT-0300\000'), 'GMT-4': ('GMT-4', 0, 1, [], '', [(-14400, 0, 0)], 'GMT-0400\000'), 'GMT-5': ('GMT-5', 0, 1, [], '', [(-18000, 0, 0)], 'GMT-0500\000'), 'GMT-6': ('GMT-6', 0, 1, [], '', [(-21600, 0, 0)], 'GMT-0600\000'), 'GMT-7': ('GMT-7', 0, 1, [], '', [(-25200, 0, 0)], 'GMT-0700\000'), 'GMT-8': ('GMT-8', 0, 1, [], '', [(-28800, 0, 0)], 'GMT-0800\000'), 'GMT-9': ('GMT-9', 0, 1, [], '', [(-32400, 0, 0)], 'GMT-0900\000'), 'GMT-10': ('GMT-10', 0, 1, [], '', [(-36000, 0, 0)], 'GMT-1000\000'), 'GMT-11': ('GMT-11', 0, 1, [], '', [(-39600, 0, 0)], 'GMT-1100\000'), 'GMT-12': ('GMT-12', 0, 1, [], '', [(-43200, 0, 0)], 'GMT-1200\000'), 'GMT+0130': ('GMT+0130', 0, 1, [], '', [(5400, 0, 0)], 'GMT+0130\000'), 'GMT+0230': ('GMT+0230', 0, 1, [], '', [(9000, 0, 0)], 'GMT+0230\000'), 'GMT+0330': ('GMT+0330', 0, 1, [], '', [(12600, 0, 0)], 'GMT+0330\000'), 'GMT+0430': ('GMT+0430', 0, 1, [], '', [(16200, 0, 0)], 'GMT+0430\000'), 'GMT+0530': ('GMT+0530', 0, 1, [], '', [(19800, 0, 0)], 'GMT+0530\000'), 'GMT+0630': ('GMT+0630', 0, 1, [], '', [(23400, 0, 0)], 'GMT+0630\000'), 'GMT+0730': ('GMT+0730', 0, 1, [], '', [(27000, 0, 0)], 'GMT+0730\000'), 'GMT+0830': ('GMT+0830', 0, 1, [], '', [(30600, 0, 0)], 'GMT+0830\000'), 'GMT+0930': ('GMT+0930', 0, 1, [], '', [(34200, 0, 0)], 'GMT+0930\000'), 'GMT+1030': ('GMT+1030', 0, 1, [], '', [(37800, 0, 0)], 'GMT+1030\000'), 'GMT+1130': ('GMT+1130', 0, 1, [], '', [(41400, 0, 0)], 'GMT+1130\000'), 'GMT+1230': ('GMT+1230', 0, 1, [], '', [(45000, 0, 0)], 'GMT+1230\000'), 'GMT-0130': ('GMT-0130', 0, 1, [], '', [(-5400, 0, 0)], 'GMT-0130\000'), 'GMT-0230': ('GMT-0230', 0, 1, [], '', [(-9000, 0, 0)], 'GMT-0230\000'), 'GMT-0330': ('GMT-0330', 0, 1, [], '', [(-12600, 0, 0)], 'GMT-0330\000'), 'GMT-0430': ('GMT-0430', 0, 1, [], '', [(-16200, 0, 0)], 'GMT-0430\000'), 'GMT-0530': ('GMT-0530', 0, 1, [], '', [(-19800, 0, 0)], 'GMT-0530\000'), 'GMT-0630': ('GMT-0630', 0, 1, [], '', [(-23400, 0, 0)], 'GMT-0630\000'), 'GMT-0730': ('GMT-0730', 0, 1, [], '', [(-27000, 0, 0)], 'GMT-0730\000'), 'GMT-0830': ('GMT-0830', 0, 1, [], '', [(-30600, 0, 0)], 'GMT-0830\000'), 'GMT-0930': ('GMT-0930', 0, 1, [], '', [(-34200, 0, 0)], 'GMT-0930\000'), 'GMT-1030': ('GMT-1030', 0, 1, [], '', [(-37800, 0, 0)], 'GMT-1030\000'), 'GMT-1130': ('GMT-1130', 0, 1, [], '', [(-41400, 0, 0)], 'GMT-1130\000'), 'GMT-1230': ('GMT-1230', 0, 1, [], '', [(-45000, 0, 0)], 'GMT-1230\000'), 'US/Indiana-Starke': ('US/Indiana-Starke', 56, 4, [-1633276800, -1615136400, -1601827200, -1583686800, -880214400, -765392400, -84384000, -68662800, -52934400, -37213200, -21484800, -5763600, 9964800, 25686000, 41414400, 57740400, 73468800, 89190000, 104918400, 120639600, 126691200, 152089200, 162374400, 183538800, 199267200, 215593200, 230716800, 247042800, 262771200, 278492400, 294220800, 309942000, 325670400, 341391600, 357120000, 372841200, 388569600, 404895600, 420019200, 436345200, 452073600, 467794800, 483523200, 499244400, 514972800, 530694000, 544608000, 562143600, 576057600, 594198000, 607507200, 625647600, 638956800, 657097200, 671011200, 688546800], '\000\001\000\001\002\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\003', [(-18000, 1, 0), (-21600, 0, 4), (-18000, 1, 8), (-18000, 0, 12)], 'CDT\000CST\000CWT\000EST\000'), 'Japan': ('Japan', 0, 1, [], '', [(32400, 0, 0)], 'JST\000'), 'Cuba': ('Cuba', 118, 2, [290581200, 308721600, 322030800, 340171200, 358318800, 371620800, 389768400, 403070400, 421218000, 434520000, 453272400, 466574400, 484722000, 498024000, 516171600, 529473600, 547621200, 560923200, 579070800, 592372800, 611125200, 623822400, 642574800, 655876800, 674024400, 687326400, 705474000, 718776000, 736923600, 750225600, 768373200, 781675200, 800427600, 813124800, 831877200, 845179200, 863326800, 876628800, 894776400, 908078400, 926226000, 939528000, 958280400, 970977600, 989730000, 1003032000, 1021179600, 1034481600, 1052629200, 1065931200, 1084078800, 1097380800, 1115528400, 1128830400, 1147582800, 1160280000, 1179032400, 1192334400, 1210482000, 1223784000, 1241931600, 1255233600, 1273381200, 1286683200, 1304830800, 1318132800, 1336885200, 1350187200, 1368334800, 1381636800, 1399784400, 1413086400, 1431234000, 1444536000, 1462683600, 1475985600, 1494738000, 1507435200, 1526187600, 1539489600, 1557637200, 1570939200, 1589086800, 1602388800, 1620536400, 1633838400, 1651986000, 1665288000, 1684040400, 1696737600, 1715490000, 1728792000, 1746939600, 1760241600, 1778389200, 1791691200, 1809838800, 1823140800, 1841893200, 1854590400, 1873342800, 1886644800, 1904792400, 1918094400, 1936242000, 1949544000, 1967691600, 1980993600, 1999141200, 2012443200, 2031195600, 2043892800, 2062645200, 2075947200, 2094094800, 2107396800, 2125544400, 2138846400], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-14400, 1, 0), (-18000, 0, 4)], 'CDT\000CST\000'), 'Singapore': ('Singapore', 0, 1, [], '', [(28800, 0, 0)], 'SST\000'), 'Chile/Continental': ('Chile/Continental', 121, 2, [245217600, 258519600, 276667200, 289969200, 308721600, 321418800, 340171200, 352868400, 371620800, 384922800, 403070400, 416372400, 434520000, 447822000, 466574400, 479271600, 498024000, 510721200, 529473600, 542170800, 560923200, 574225200, 592372800, 605674800, 623822400, 637124400, 655876800, 668574000, 687326400, 700023600, 718776000, 732078000, 750225600, 763527600, 781675200, 794977200, 813124800, 826426800, 845179200, 857876400, 876628800, 889326000, 908078400, 921380400, 939528000, 952830000, 970977600, 984279600, 1003032000, 1015729200, 1034481600, 1047178800, 1065931200, 1079233200, 1097380800, 1110682800, 1128830400, 1142132400, 1160280000, 1173582000, 1192334400, 1205031600, 1223784000, 1236481200, 1255233600, 1268535600, 1286683200, 1299985200, 1318132800, 1331434800, 1350187200, 1362884400, 1381636800, 1394334000, 1413086400, 1425783600, 1444536000, 1457838000, 1475985600, 1489287600, 1507435200, 1520737200, 1539489600, 1552186800, 1570939200, 1583636400, 1602388800, 1615690800, 1633838400, 1647140400, 1665288000, 1678590000, 1696737600, 1710039600, 1728792000, 1741489200, 1760241600, 1772938800, 1791691200, 1804993200, 1823140800, 1836442800, 1854590400, 1867892400, 1886644800, 1899342000, 1918094400, 1930791600, 1949544000, 1962846000, 1980993600, 1994295600, 2012443200, 2025745200, 2043892800, 2057194800, 2075947200, 2088644400, 2107396800, 2120094000, 2138846400], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000', [(-10800, 1, 0), (-14400, 0, 4)], 'CDT\000CST\000'), 'Brazil/West': ('Brazil/West', 101, 2, [561967200, 571640400, 593416800, 603090000, 625557600, 634539600, 656920800, 665989200, 688370400, 697438800, 719820000, 729493200, 751269600, 760942800, 782719200, 792392400, 814860000, 823842000, 846223200, 855291600, 877672800, 887432400, 909122400, 918795600, 940572000, 950245200, 972712800, 981694800, 1004076000, 1013144400, 1035525600, 1044594000, 1066975200, 1076734800, 1098424800, 1108098000, 1129874400, 1139547600, 1162015200, 1170997200, 1193378400, 1202446800, 1224828000, 1234587600, 1256277600, 1265950800, 1287727200, 1297400400, 1319176800, 1328850000, 1351231200, 1360299600, 1382680800, 1391749200, 1414130400, 1423890000, 1445580000, 1455253200, 1477029600, 1486702800, 1509170400, 1518152400, 1540533600, 1549602000, 1571983200, 1581051600, 1603432800, 1613106000, 1634882400, 1644555600, 1666332000, 1676005200, 1698472800, 1707454800, 1729836000, 1738904400, 1761285600, 1771045200, 1792735200, 1802408400, 1824184800, 1833858000, 1856325600, 1865307600, 1887688800, 1896757200, 1919138400, 1928206800, 1950588000, 1960347600, 1982037600, 1991710800, 2013487200, 2023160400, 2045628000, 2054610000, 2076991200, 2086059600, 2108440800, 2118200400, 2139890400], '\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-14400, 0, 0), (-10800, 1, 4)], 'WST\000WDT\000'), 'Mexico/BajaSur': ('Mexico/BajaSur', 0, 1, [], '', [(-25200, 0, 0)], 'MST\000'), 'Israel': ('Israel', 42, 2, [609890400, 622587600, 640735200, 653432400, 670975200, 683672400, 704239200, 716936400, 735084000, 747781200, 765324000, 778021200, 798588000, 811285200, 829432800, 842130000, 862696800, 875394000, 892936800, 905634000, 923781600, 936478800, 957045600, 969742800, 987285600, 999982800, 1018130400, 1030827600, 1051394400, 1064091600, 1082239200, 1094936400, 1114898400, 1127595600, 1145743200, 1158440400, 1176588000, 1189285200, 1209247200, 1221944400, 1240092000, 1252789200], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(10800, 1, 0), (7200, 0, 4)], 'IDT\000IST\000'), 'Canada/Newfoundland': ('Canada/Newfoundland', 138, 2, [-21493800, -5772600, 9955800, 25677000, 41405400, 57731400, 73459800, 89181000, 104909400, 120630600, 136359000, 152080200, 167808600, 183529800, 199258200, 215584200, 230707800, 247033800, 262762200, 278483400, 294211800, 309933000, 325661400, 341382600, 357111000, 372832200, 388560600, 404886600, 420010200, 436336200, 452064600, 467785800, 483514200, 499235400, 514963800, 530685000, 544599000, 562134600, 576048600, 594189000, 607498200, 625638600, 638947800, 657088200, 671002200, 688537800, 702451800, 719987400, 733901400, 752041800, 765351000, 783491400, 796800600, 814941000, 828855000, 846390600, 860304600, 877840200, 891754200, 909289800, 923203800, 941344200, 954653400, 972793800, 986103000, 1004243400, 1018157400, 1035693000, 1049607000, 1067142600, 1081056600, 1099197000, 1112506200, 1130646600, 1143955800, 1162096200, 1175405400, 1193545800, 1207459800, 1224995400, 1238909400, 1256445000, 1270359000, 1288499400, 1301808600, 1319949000, 1333258200, 1351398600, 1365312600, 1382848200, 1396762200, 1414297800, 1428211800, 1445747400, 1459661400, 1477801800, 1491111000, 1509251400, 1522560600, 1540701000, 1554615000, 1572150600, 1586064600, 1603600200, 1617514200, 1635654600, 1648963800, 1667104200, 1680413400, 1698553800, 1712467800, 1730003400, 1743917400, 1761453000, 1775367000, 1792902600, 1806816600, 1824957000, 1838266200, 1856406600, 1869715800, 1887856200, 1901770200, 1919305800, 1933219800, 1950755400, 1964669400, 1982809800, 1996119000, 2014259400, 2027568600, 2045709000, 2059018200, 2077158600, 2091072600, 2108608200, 2122522200, 2140057800], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-9000, 1, 0), (-12600, 0, 4)], 'NDT\000NST\000'), 'GB-Eire': ('GB-Eire', 241, 4, [-1697238000, -1680476400, -1664146800, -1650150000, -1633906800, -1617490800, -1601852400, -1586041200, -1570402800, -1552172400, -1538348400, -1522537200, -1507503600, -1490569200, -1473634800, -1458342000, -1441321200, -1428879600, -1410735600, -1396220400, -1379286000, -1364770800, -1347836400, -1333321200, -1316386800, -1301266800, -1284332400, -1269817200, -1252882800, -1238367600, -1221433200, -1206918000, -1189983600, -1175468400, -1158534000, -1144018800, -1127084400, -1111964400, -1095030000, -1080514800, -1063580400, -1049065200, -1032130800, -1017615600, -1000681200, -986166000, -969231600, -950482800, -942015600, -904518000, -896050800, -875487600, -864601200, -844038000, -832546800, -812588400, -798073200, -781052400, -772066800, -764809200, -748479600, -733359600, -719449200, -717030000, -706748400, -699490800, -687999600, -668041200, -654735600, -636591600, -622076400, -605746800, -590626800, -574297200, -558572400, -542242800, -527122800, -512607600, -496278000, -481158000, -464223600, -449708400, -432774000, -417654000, -401324400, -386204400, -369270000, -354754800, -337820400, -323305200, -306975600, -291855600, -276735600, -257986800, -245286000, -226537200, -213231600, -195087600, -182386800, -163638000, -150937200, -132188400, -119487600, -100738800, -88038000, -68684400, -59007600, -37238400, 57715200, 69814800, 89168400, 101264400, 120618000, 132714000, 152067600, 164163600, 183517200, 196218000, 214966800, 227667600, 246416400, 259117200, 278470800, 290566800, 309920400, 322016400, 341370000, 354675600, 372819600, 386125200, 404269200, 417574800, 435718800, 449024400, 467773200, 481078800, 499222800, 512528400, 530672400, 543978000, 562122000, 575427600, 593571600, 606877200, 625626000, 638326800, 657075600, 670381200, 688525200, 701830800, 719974800, 733280400, 751424400, 764730000, 782874000, 796179600, 814928400, 828234000, 846378000, 859683600, 877827600, 891133200, 909277200, 922582800, 940726800, 954032400, 972781200, 985482000, 1004230800, 1017536400, 1035680400, 1048986000, 1067130000, 1080435600, 1098579600, 1111885200, 1130029200, 1143334800, 1162083600, 1174784400, 1193533200, 1206838800, 1224982800, 1238288400, 1256432400, 1269738000, 1287882000, 1301187600, 1319331600, 1332637200, 1351386000, 1364691600, 1382835600, 1396141200, 1414285200, 1427590800, 1445734800, 1459040400, 1477184400, 1490490000, 1509238800, 1521939600, 1540688400, 1553994000, 1572138000, 1585443600, 1603587600, 1616893200, 1635037200, 1648342800, 1666486800, 1679792400, 1698541200, 1711846800, 1729990800, 1743296400, 1761440400, 1774746000, 1792890000, 1806195600, 1824339600, 1837645200, 1856394000, 1869094800, 1887843600, 1901149200, 1919293200, 1932598800, 1950742800, 1964048400, 1982192400, 1995498000, 2013642000, 2026947600, 2045696400, 2058397200, 2077146000, 2090451600, 2108595600, 2121901200, 2140045200], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\002\000\002\000\002\000\002\000\002\000\001\000\001\000\002\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\003\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(3600, 1, 0), (0, 0, 4), (7200, 1, 8), (3600, 0, 0)], 'BST\000GMT\000DST\000'), 'Hongkong': ('Hongkong', 0, 1, [], '', [(28800, 0, 0)], 'HKT\000'), 'Turkey': ('Turkey', 104, 2, [512517600, 528238800, 543967200, 559688400, 575416800, 591138000, 606866400, 622587600, 638316000, 654642000, 670370400, 686091600, 701820000, 717541200, 733269600, 748990800, 764719200, 780440400, 796168800, 811890000, 828223200, 843944400, 859672800, 875394000, 891122400, 906843600, 922572000, 938293200, 954021600, 969742800, 985471200, 1001797200, 1017525600, 1033246800, 1048975200, 1064696400, 1080424800, 1096146000, 1111874400, 1127595600, 1143324000, 1159045200, 1174773600, 1191099600, 1206828000, 1222549200, 1238277600, 1253998800, 1269727200, 1285448400, 1301176800, 1316898000, 1332626400, 1348952400, 1364680800, 1380402000, 1396130400, 1411851600, 1427580000, 1443301200, 1459029600, 1474750800, 1490479200, 1506200400, 1521928800, 1538254800, 1553983200, 1569704400, 1585432800, 1601154000, 1616882400, 1632603600, 1648332000, 1664053200, 1679781600, 1695502800, 1711836000, 1727557200, 1743285600, 1759006800, 1774735200, 1790456400, 1806184800, 1821906000, 1837634400, 1853355600, 1869084000, 1885410000, 1901138400, 1916859600, 1932588000, 1948309200, 1964037600, 1979758800, 1995487200, 2011208400, 2026936800, 2042658000, 2058386400, 2074712400, 2090440800, 2106162000, 2121890400, 2137611600], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(14400, 1, 0), (10800, 0, 8)], 'EET DST\000EET\000'), 'US/Samoa': ('US/Samoa', 2, 3, [-86878800, 439038000], '\001\002', [(-39600, 0, 0), (-39600, 0, 4), (-39600, 0, 8)], 'NST\000BST\000SST\000'), 'Iran': ('Iran', 100, 2, [575418600, 590535000, 606868200, 621984600, 638317800, 653434200, 670372200, 684883800, 701821800, 716938200, 733271400, 748387800, 764721000, 779837400, 796170600, 811287000, 828225000, 842736600, 859674600, 874791000, 891124200, 906240600, 922573800, 937690200, 954023400, 969139800, 985473000, 1000589400, 1017527400, 1032039000, 1048977000, 1064093400, 1080426600, 1095543000, 1111876200, 1126992600, 1143325800, 1158442200, 1174775400, 1189891800, 1206829800, 1221946200, 1238279400, 1253395800, 1269729000, 1284845400, 1301178600, 1316295000, 1332628200, 1347744600, 1364682600, 1379194200, 1396132200, 1411248600, 1427581800, 1442698200, 1459031400, 1474147800, 1490481000, 1505597400, 1521930600, 1537047000, 1553985000, 1568496600, 1585434600, 1600551000, 1616884200, 1632000600, 1648333800, 1663450200, 1679783400, 1694899800, 1711837800, 1726349400, 1743287400, 1758403800, 1774737000, 1789853400, 1806186600, 1821303000, 1837636200, 1852752600, 1869085800, 1884202200, 1901140200, 1915651800, 1932589800, 1947706200, 1964039400, 1979155800, 1995489000, 2010605400, 2026938600, 2042055000, 2058388200, 2073504600, 2090442600, 2105559000, 2121892200, 2137008600], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(16200, 1, 0), (12600, 0, 4)], 'IDT\000IST\000'), 'US/Pacific': ('US/Pacific', 148, 3, [-1633269600, -1615129200, -1601820000, -1583679600, -880207200, -765385200, -84376800, -68655600, -52927200, -37206000, -21477600, -5756400, 9972000, 25693200, 41421600, 57747600, 73476000, 89197200, 104925600, 120646800, 126698400, 152096400, 162381600, 183546000, 199274400, 215600400, 230724000, 247050000, 262778400, 278499600, 294228000, 309949200, 325677600, 341398800, 357127200, 372848400, 388576800, 404902800, 420026400, 436352400, 452080800, 467802000, 483530400, 499251600, 514980000, 530701200, 544615200, 562150800, 576064800, 594205200, 607514400, 625654800, 638964000, 657104400, 671018400, 688554000, 702468000, 720003600, 733917600, 752058000, 765367200, 783507600, 796816800, 814957200, 828871200, 846406800, 860320800, 877856400, 891770400, 909306000, 923220000, 941360400, 954669600, 972810000, 986119200, 1004259600, 1018173600, 1035709200, 1049623200, 1067158800, 1081072800, 1099213200, 1112522400, 1130662800, 1143972000, 1162112400, 1175421600, 1193562000, 1207476000, 1225011600, 1238925600, 1256461200, 1270375200, 1288515600, 1301824800, 1319965200, 1333274400, 1351414800, 1365328800, 1382864400, 1396778400, 1414314000, 1428228000, 1445763600, 1459677600, 1477818000, 1491127200, 1509267600, 1522576800, 1540717200, 1554631200, 1572166800, 1586080800, 1603616400, 1617530400, 1635670800, 1648980000, 1667120400, 1680429600, 1698570000, 1712484000, 1730019600, 1743933600, 1761469200, 1775383200, 1792918800, 1806832800, 1824973200, 1838282400, 1856422800, 1869732000, 1887872400, 1901786400, 1919322000, 1933236000, 1950771600, 1964685600, 1982826000, 1996135200, 2014275600, 2027584800, 2045725200, 2059034400, 2077174800, 2091088800, 2108624400, 2122538400, 2140074000], '\000\001\000\001\002\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-25200, 1, 0), (-28800, 0, 4), (-25200, 1, 8)], 'PDT\000PST\000PWT\000'), 'Brazil/East': ('Brazil/East', 101, 2, [561963600, 571636800, 593413200, 603086400, 625554000, 634536000, 656917200, 665985600, 688366800, 697435200, 719816400, 729489600, 751266000, 760939200, 782715600, 792388800, 814856400, 823838400, 846219600, 855288000, 877669200, 887428800, 909118800, 918792000, 940568400, 950241600, 972709200, 981691200, 1004072400, 1013140800, 1035522000, 1044590400, 1066971600, 1076731200, 1098421200, 1108094400, 1129870800, 1139544000, 1162011600, 1170993600, 1193374800, 1202443200, 1224824400, 1234584000, 1256274000, 1265947200, 1287723600, 1297396800, 1319173200, 1328846400, 1351227600, 1360296000, 1382677200, 1391745600, 1414126800, 1423886400, 1445576400, 1455249600, 1477026000, 1486699200, 1509166800, 1518148800, 1540530000, 1549598400, 1571979600, 1581048000, 1603429200, 1613102400, 1634878800, 1644552000, 1666328400, 1676001600, 1698469200, 1707451200, 1729832400, 1738900800, 1761282000, 1771041600, 1792731600, 1802404800, 1824181200, 1833854400, 1856322000, 1865304000, 1887685200, 1896753600, 1919134800, 1928203200, 1950584400, 1960344000, 1982034000, 1991707200, 2013483600, 2023156800, 2045624400, 2054606400, 2076987600, 2086056000, 2108437200, 2118196800, 2139886800], '\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-10800, 0, 0), (-7200, 1, 4)], 'EST\000EDT\000'), 'Chile/EasterIsland': ('Chile/EasterIsland', 121, 2, [245224800, 258526800, 276674400, 289976400, 308728800, 321426000, 340178400, 352875600, 371628000, 384930000, 403077600, 416379600, 434527200, 447829200, 466581600, 479278800, 498031200, 510728400, 529480800, 542178000, 560930400, 574232400, 592380000, 605682000, 623829600, 637131600, 655884000, 668581200, 687333600, 700030800, 718783200, 732085200, 750232800, 763534800, 781682400, 794984400, 813132000, 826434000, 845186400, 857883600, 876636000, 889333200, 908085600, 921387600, 939535200, 952837200, 970984800, 984286800, 1003039200, 1015736400, 1034488800, 1047186000, 1065938400, 1079240400, 1097388000, 1110690000, 1128837600, 1142139600, 1160287200, 1173589200, 1192341600, 1205038800, 1223791200, 1236488400, 1255240800, 1268542800, 1286690400, 1299992400, 1318140000, 1331442000, 1350194400, 1362891600, 1381644000, 1394341200, 1413093600, 1425790800, 1444543200, 1457845200, 1475992800, 1489294800, 1507442400, 1520744400, 1539496800, 1552194000, 1570946400, 1583643600, 1602396000, 1615698000, 1633845600, 1647147600, 1665295200, 1678597200, 1696744800, 1710046800, 1728799200, 1741496400, 1760248800, 1772946000, 1791698400, 1805000400, 1823148000, 1836450000, 1854597600, 1867899600, 1886652000, 1899349200, 1918101600, 1930798800, 1949551200, 1962853200, 1981000800, 1994302800, 2012450400, 2025752400, 2043900000, 2057202000, 2075954400, 2088651600, 2107404000, 2120101200, 2138853600], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000', [(-18000, 1, 0), (-21600, 0, 4)], 'EDT\000EST\000'), 'Brazil/DeNoronha': ('Brazil/DeNoronha', 101, 2, [561960000, 571633200, 593409600, 603082800, 625550400, 634532400, 656913600, 665982000, 688363200, 697431600, 719812800, 729486000, 751262400, 760935600, 782712000, 792385200, 814852800, 823834800, 846216000, 855284400, 877665600, 887425200, 909115200, 918788400, 940564800, 950238000, 972705600, 981687600, 1004068800, 1013137200, 1035518400, 1044586800, 1066968000, 1076727600, 1098417600, 1108090800, 1129867200, 1139540400, 1162008000, 1170990000, 1193371200, 1202439600, 1224820800, 1234580400, 1256270400, 1265943600, 1287720000, 1297393200, 1319169600, 1328842800, 1351224000, 1360292400, 1382673600, 1391742000, 1414123200, 1423882800, 1445572800, 1455246000, 1477022400, 1486695600, 1509163200, 1518145200, 1540526400, 1549594800, 1571976000, 1581044400, 1603425600, 1613098800, 1634875200, 1644548400, 1666324800, 1675998000, 1698465600, 1707447600, 1729828800, 1738897200, 1761278400, 1771038000, 1792728000, 1802401200, 1824177600, 1833850800, 1856318400, 1865300400, 1887681600, 1896750000, 1919131200, 1928199600, 1950580800, 1960340400, 1982030400, 1991703600, 2013480000, 2023153200, 2045620800, 2054602800, 2076984000, 2086052400, 2108433600, 2118193200, 2139883200], '\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-7200, 0, 0), (-3600, 1, 4)], 'FST\000FDT\000'), 'US/Aleutian': ('US/Aleutian', 149, 5, [-1633262400, -1615122000, -1601812800, -1583672400, -880200000, -765378000, -84369600, -68648400, -52920000, -37198800, -21470400, -5749200, 9979200, 25700400, 41428800, 57754800, 73483200, 89204400, 104932800, 120654000, 126705600, 152103600, 162388800, 183553200, 199281600, 215607600, 230731200, 247057200, 262785600, 278506800, 294235200, 309956400, 325684800, 341406000, 357134400, 372855600, 388584000, 404910000, 420033600, 436359600, 439034400, 452088000, 467809200, 483537600, 499258800, 514987200, 530708400, 544622400, 562158000, 576072000, 594212400, 607521600, 625662000, 638971200, 657111600, 671025600, 688561200, 702475200, 720010800, 733924800, 752065200, 765374400, 783514800, 796824000, 814964400, 828878400, 846414000, 860328000, 877863600, 891777600, 909313200, 923227200, 941367600, 954676800, 972817200, 986126400, 1004266800, 1018180800, 1035716400, 1049630400, 1067166000, 1081080000, 1099220400, 1112529600, 1130670000, 1143979200, 1162119600, 1175428800, 1193569200, 1207483200, 1225018800, 1238932800, 1256468400, 1270382400, 1288522800, 1301832000, 1319972400, 1333281600, 1351422000, 1365336000, 1382871600, 1396785600, 1414321200, 1428235200, 1445770800, 1459684800, 1477825200, 1491134400, 1509274800, 1522584000, 1540724400, 1554638400, 1572174000, 1586088000, 1603623600, 1617537600, 1635678000, 1648987200, 1667127600, 1680436800, 1698577200, 1712491200, 1730026800, 1743940800, 1761476400, 1775390400, 1792926000, 1806840000, 1824980400, 1838289600, 1856430000, 1869739200, 1887879600, 1901793600, 1919329200, 1933243200, 1950778800, 1964692800, 1982833200, 1996142400, 2014282800, 2027592000, 2045732400, 2059041600, 2077182000, 2091096000, 2108631600, 2122545600, 2140081200], '\000\001\000\001\002\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003\004\003', [(-32400, 1, 0), (-36000, 0, 5), (-32400, 1, 10), (-36000, 0, 15), (-32400, 1, 20)], 'AHDT\000AHST\000AHWT\000HAST\000HADT\000'), 'Canada/Atlantic': ('Canada/Atlantic', 138, 2, [-21492000, -5770800, 9957600, 25678800, 41407200, 57733200, 73461600, 89182800, 104911200, 120632400, 136360800, 152082000, 167810400, 183531600, 199260000, 215586000, 230709600, 247035600, 262764000, 278485200, 294213600, 309934800, 325663200, 341384400, 357112800, 372834000, 388562400, 404888400, 420012000, 436338000, 452066400, 467787600, 483516000, 499237200, 514965600, 530686800, 544600800, 562136400, 576050400, 594190800, 607500000, 625640400, 638949600, 657090000, 671004000, 688539600, 702453600, 719989200, 733903200, 752043600, 765352800, 783493200, 796802400, 814942800, 828856800, 846392400, 860306400, 877842000, 891756000, 909291600, 923205600, 941346000, 954655200, 972795600, 986104800, 1004245200, 1018159200, 1035694800, 1049608800, 1067144400, 1081058400, 1099198800, 1112508000, 1130648400, 1143957600, 1162098000, 1175407200, 1193547600, 1207461600, 1224997200, 1238911200, 1256446800, 1270360800, 1288501200, 1301810400, 1319950800, 1333260000, 1351400400, 1365314400, 1382850000, 1396764000, 1414299600, 1428213600, 1445749200, 1459663200, 1477803600, 1491112800, 1509253200, 1522562400, 1540702800, 1554616800, 1572152400, 1586066400, 1603602000, 1617516000, 1635656400, 1648965600, 1667106000, 1680415200, 1698555600, 1712469600, 1730005200, 1743919200, 1761454800, 1775368800, 1792904400, 1806818400, 1824958800, 1838268000, 1856408400, 1869717600, 1887858000, 1901772000, 1919307600, 1933221600, 1950757200, 1964671200, 1982811600, 1996120800, 2014261200, 2027570400, 2045710800, 2059020000, 2077160400, 2091074400, 2108610000, 2122524000, 2140059600], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-10800, 1, 0), (-14400, 0, 4)], 'ADT\000AST\000'), 'Mexico/General': ('Mexico/General', 0, 1, [], '', [(-21600, 0, 0)], 'CST\000'), 'Greenwich': ('Greenwich', 0, 1, [], '', [(0, 0, 0)], 'GMT\000'), 'Egypt': ('Egypt', 152, 2, [-305164800, -291949200, -273628800, -260413200, -242092800, -228877200, -210556800, -197341200, -178934400, -165718800, -147398400, -134182800, -115862400, -102646800, -84326400, -71110800, -52704000, -39488400, -21168000, -7952400, 10368000, 23583600, 41904000, 55119600, 73526400, 86742000, 105062400, 118278000, 136598400, 149814000, 168134400, 181350000, 199756800, 212972400, 231292800, 244508400, 262828800, 276044400, 294364800, 307580400, 325987200, 339202800, 420595200, 433810800, 452217600, 465433200, 483753600, 496969200, 515289600, 528505200, 546825600, 560041200, 578448000, 591663600, 609984000, 623199600, 641520000, 654735600, 673056000, 686271600, 704678400, 717894000, 736214400, 749430000, 767750400, 780966000, 799286400, 812502000, 830908800, 844124400, 862444800, 875660400, 893980800, 907196400, 925516800, 938732400, 957139200, 970354800, 988675200, 1001890800, 1020211200, 1033426800, 1051747200, 1064962800, 1083369600, 1096585200, 1114905600, 1128121200, 1146441600, 1159657200, 1177977600, 1191193200, 1209600000, 1222815600, 1241136000, 1254351600, 1272672000, 1285887600, 1304208000, 1317423600, 1335830400, 1349046000, 1367366400, 1380582000, 1398902400, 1412118000, 1430438400, 1443654000, 1462060800, 1475276400, 1493596800, 1506812400, 1525132800, 1538348400, 1556668800, 1569884400, 1588291200, 1601506800, 1619827200, 1633042800, 1651363200, 1664578800, 1682899200, 1696114800, 1714521600, 1727737200, 1746057600, 1759273200, 1777593600, 1790809200, 1809129600, 1822345200, 1840752000, 1853967600, 1872288000, 1885503600, 1903824000, 1917039600, 1935360000, 1948575600, 1966982400, 1980198000, 1998518400, 2011734000, 2030054400, 2043270000, 2061590400, 2074806000, 2093212800, 2106428400, 2124748800, 2137964400], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(10800, 1, 0), (7200, 0, 8)], 'EET DST\000EET\000'), 'US/Eastern': ('US/Eastern', 148, 3, [-1633280400, -1615140000, -1601830800, -1583690400, -880218000, -765396000, -84387600, -68666400, -52938000, -37216800, -21488400, -5767200, 9961200, 25682400, 41410800, 57736800, 73465200, 89186400, 104914800, 120636000, 126687600, 152085600, 162370800, 183535200, 199263600, 215589600, 230713200, 247039200, 262767600, 278488800, 294217200, 309938400, 325666800, 341388000, 357116400, 372837600, 388566000, 404892000, 420015600, 436341600, 452070000, 467791200, 483519600, 499240800, 514969200, 530690400, 544604400, 562140000, 576054000, 594194400, 607503600, 625644000, 638953200, 657093600, 671007600, 688543200, 702457200, 719992800, 733906800, 752047200, 765356400, 783496800, 796806000, 814946400, 828860400, 846396000, 860310000, 877845600, 891759600, 909295200, 923209200, 941349600, 954658800, 972799200, 986108400, 1004248800, 1018162800, 1035698400, 1049612400, 1067148000, 1081062000, 1099202400, 1112511600, 1130652000, 1143961200, 1162101600, 1175410800, 1193551200, 1207465200, 1225000800, 1238914800, 1256450400, 1270364400, 1288504800, 1301814000, 1319954400, 1333263600, 1351404000, 1365318000, 1382853600, 1396767600, 1414303200, 1428217200, 1445752800, 1459666800, 1477807200, 1491116400, 1509256800, 1522566000, 1540706400, 1554620400, 1572156000, 1586070000, 1603605600, 1617519600, 1635660000, 1648969200, 1667109600, 1680418800, 1698559200, 1712473200, 1730008800, 1743922800, 1761458400, 1775372400, 1792908000, 1806822000, 1824962400, 1838271600, 1856412000, 1869721200, 1887861600, 1901775600, 1919311200, 1933225200, 1950760800, 1964674800, 1982815200, 1996124400, 2014264800, 2027574000, 2045714400, 2059023600, 2077164000, 2091078000, 2108613600, 2122527600, 2140063200], '\000\001\000\001\002\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-14400, 1, 0), (-18000, 0, 4), (-14400, 1, 8)], 'EDT\000EST\000EWT\000'), 'Brazil/Acre': ('Brazil/Acre', 101, 2, [561970800, 571644000, 593420400, 603093600, 625561200, 634543200, 656924400, 665992800, 688374000, 697442400, 719823600, 729496800, 751273200, 760946400, 782722800, 792396000, 814863600, 823845600, 846226800, 855295200, 877676400, 887436000, 909126000, 918799200, 940575600, 950248800, 972716400, 981698400, 1004079600, 1013148000, 1035529200, 1044597600, 1066978800, 1076738400, 1098428400, 1108101600, 1129878000, 1139551200, 1162018800, 1171000800, 1193382000, 1202450400, 1224831600, 1234591200, 1256281200, 1265954400, 1287730800, 1297404000, 1319180400, 1328853600, 1351234800, 1360303200, 1382684400, 1391752800, 1414134000, 1423893600, 1445583600, 1455256800, 1477033200, 1486706400, 1509174000, 1518156000, 1540537200, 1549605600, 1571986800, 1581055200, 1603436400, 1613109600, 1634886000, 1644559200, 1666335600, 1676008800, 1698476400, 1707458400, 1729839600, 1738908000, 1761289200, 1771048800, 1792738800, 1802412000, 1824188400, 1833861600, 1856329200, 1865311200, 1887692400, 1896760800, 1919142000, 1928210400, 1950591600, 1960351200, 1982041200, 1991714400, 2013490800, 2023164000, 2045631600, 2054613600, 2076994800, 2086063200, 2108444400, 2118204000, 2139894000], '\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-18000, 0, 0), (-14400, 1, 4)], 'AST\000ADT\000'), 'US/Mountain': ('US/Mountain', 148, 3, [-1633273200, -1615132800, -1601823600, -1583683200, -880210800, -765388800, -84380400, -68659200, -52930800, -37209600, -21481200, -5760000, 9968400, 25689600, 41418000, 57744000, 73472400, 89193600, 104922000, 120643200, 126694800, 152092800, 162378000, 183542400, 199270800, 215596800, 230720400, 247046400, 262774800, 278496000, 294224400, 309945600, 325674000, 341395200, 357123600, 372844800, 388573200, 404899200, 420022800, 436348800, 452077200, 467798400, 483526800, 499248000, 514976400, 530697600, 544611600, 562147200, 576061200, 594201600, 607510800, 625651200, 638960400, 657100800, 671014800, 688550400, 702464400, 720000000, 733914000, 752054400, 765363600, 783504000, 796813200, 814953600, 828867600, 846403200, 860317200, 877852800, 891766800, 909302400, 923216400, 941356800, 954666000, 972806400, 986115600, 1004256000, 1018170000, 1035705600, 1049619600, 1067155200, 1081069200, 1099209600, 1112518800, 1130659200, 1143968400, 1162108800, 1175418000, 1193558400, 1207472400, 1225008000, 1238922000, 1256457600, 1270371600, 1288512000, 1301821200, 1319961600, 1333270800, 1351411200, 1365325200, 1382860800, 1396774800, 1414310400, 1428224400, 1445760000, 1459674000, 1477814400, 1491123600, 1509264000, 1522573200, 1540713600, 1554627600, 1572163200, 1586077200, 1603612800, 1617526800, 1635667200, 1648976400, 1667116800, 1680426000, 1698566400, 1712480400, 1730016000, 1743930000, 1761465600, 1775379600, 1792915200, 1806829200, 1824969600, 1838278800, 1856419200, 1869728400, 1887868800, 1901782800, 1919318400, 1933232400, 1950768000, 1964682000, 1982822400, 1996131600, 2014272000, 2027581200, 2045721600, 2059030800, 2077171200, 2091085200, 2108620800, 2122534800, 2140070400], '\000\001\000\001\002\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-21600, 1, 0), (-25200, 0, 4), (-21600, 1, 8)], 'MDT\000MST\000MWT\000'), 'US/Central': ('US/Central', 148, 3, [-1633276800, -1615136400, -1601827200, -1583686800, -880214400, -765392400, -84384000, -68662800, -52934400, -37213200, -21484800, -5763600, 9964800, 25686000, 41414400, 57740400, 73468800, 89190000, 104918400, 120639600, 126691200, 152089200, 162374400, 183538800, 199267200, 215593200, 230716800, 247042800, 262771200, 278492400, 294220800, 309942000, 325670400, 341391600, 357120000, 372841200, 388569600, 404895600, 420019200, 436345200, 452073600, 467794800, 483523200, 499244400, 514972800, 530694000, 544608000, 562143600, 576057600, 594198000, 607507200, 625647600, 638956800, 657097200, 671011200, 688546800, 702460800, 719996400, 733910400, 752050800, 765360000, 783500400, 796809600, 814950000, 828864000, 846399600, 860313600, 877849200, 891763200, 909298800, 923212800, 941353200, 954662400, 972802800, 986112000, 1004252400, 1018166400, 1035702000, 1049616000, 1067151600, 1081065600, 1099206000, 1112515200, 1130655600, 1143964800, 1162105200, 1175414400, 1193554800, 1207468800, 1225004400, 1238918400, 1256454000, 1270368000, 1288508400, 1301817600, 1319958000, 1333267200, 1351407600, 1365321600, 1382857200, 1396771200, 1414306800, 1428220800, 1445756400, 1459670400, 1477810800, 1491120000, 1509260400, 1522569600, 1540710000, 1554624000, 1572159600, 1586073600, 1603609200, 1617523200, 1635663600, 1648972800, 1667113200, 1680422400, 1698562800, 1712476800, 1730012400, 1743926400, 1761462000, 1775376000, 1792911600, 1806825600, 1824966000, 1838275200, 1856415600, 1869724800, 1887865200, 1901779200, 1919314800, 1933228800, 1950764400, 1964678400, 1982818800, 1996128000, 2014268400, 2027577600, 2045718000, 2059027200, 2077167600, 2091081600, 2108617200, 2122531200, 2140066800], '\000\001\000\001\002\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-18000, 1, 0), (-21600, 0, 4), (-18000, 1, 8)], 'CDT\000CST\000CWT\000'), 'US/Hawaii': ('US/Hawaii', 9, 4, [-1633260600, -1615120200, -1601811000, -1583670600, -1157283000, -1157200200, -880198200, -765376200, -712150200], '\000\001\000\001\000\001\002\001\003', [(-34200, 1, 0), (-37800, 0, 4), (-34200, 1, 8), (-36000, 0, 4)], 'HDT\000HST\000HWT\000'), 'US/Arizona': ('US/Arizona', 6, 3, [-1633273200, -1615132800, -1601823600, -1583683200, -880210800, -765388800], '\000\001\000\001\002\001', [(-21600, 1, 0), (-25200, 0, 4), (-21600, 1, 8)], 'MDT\000MST\000MWT\000'), 'Jamaica': ('Jamaica', 148, 3, [-1633280400, -1615140000, -1601830800, -1583690400, -880218000, -765396000, -84387600, -68666400, -52938000, -37216800, -21488400, -5767200, 9961200, 25682400, 41410800, 57736800, 73465200, 89186400, 104914800, 120636000, 126687600, 152085600, 162370800, 183535200, 199263600, 215589600, 230713200, 247039200, 262767600, 278488800, 294217200, 309938400, 325666800, 341388000, 357116400, 372837600, 388566000, 404892000, 420015600, 436341600, 452070000, 467791200, 483519600, 499240800, 514969200, 530690400, 544604400, 562140000, 576054000, 594194400, 607503600, 625644000, 638953200, 657093600, 671007600, 688543200, 702457200, 719992800, 733906800, 752047200, 765356400, 783496800, 796806000, 814946400, 828860400, 846396000, 860310000, 877845600, 891759600, 909295200, 923209200, 941349600, 954658800, 972799200, 986108400, 1004248800, 1018162800, 1035698400, 1049612400, 1067148000, 1081062000, 1099202400, 1112511600, 1130652000, 1143961200, 1162101600, 1175410800, 1193551200, 1207465200, 1225000800, 1238914800, 1256450400, 1270364400, 1288504800, 1301814000, 1319954400, 1333263600, 1351404000, 1365318000, 1382853600, 1396767600, 1414303200, 1428217200, 1445752800, 1459666800, 1477807200, 1491116400, 1509256800, 1522566000, 1540706400, 1554620400, 1572156000, 1586070000, 1603605600, 1617519600, 1635660000, 1648969200, 1667109600, 1680418800, 1698559200, 1712473200, 1730008800, 1743922800, 1761458400, 1775372400, 1792908000, 1806822000, 1824962400, 1838271600, 1856412000, 1869721200, 1887861600, 1901775600, 1919311200, 1933225200, 1950760800, 1964674800, 1982815200, 1996124400, 2014264800, 2027574000, 2045714400, 2059023600, 2077164000, 2091078000, 2108613600, 2122527600, 2140063200], '\000\001\000\001\002\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-14400, 1, 0), (-18000, 0, 4), (-14400, 1, 8)], 'EDT\000EST\000EWT\000'), 'Poland': ('Poland', 104, 2, [512524800, 528249600, 543974400, 559699200, 575424000, 591148800, 606873600, 622598400, 638323200, 654652800, 670377600, 686102400, 701827200, 717552000, 733276800, 749001600, 764726400, 780451200, 796176000, 811900800, 828230400, 843955200, 859680000, 875404800, 891129600, 906854400, 922579200, 938304000, 954028800, 969753600, 985478400, 1001808000, 1017532800, 1033257600, 1048982400, 1064707200, 1080432000, 1096156800, 1111881600, 1127606400, 1143331200, 1159056000, 1174780800, 1191110400, 1206835200, 1222560000, 1238284800, 1254009600, 1269734400, 1285459200, 1301184000, 1316908800, 1332633600, 1348963200, 1364688000, 1380412800, 1396137600, 1411862400, 1427587200, 1443312000, 1459036800, 1474761600, 1490486400, 1506211200, 1521936000, 1538265600, 1553990400, 1569715200, 1585440000, 1601164800, 1616889600, 1632614400, 1648339200, 1664064000, 1679788800, 1695513600, 1711843200, 1727568000, 1743292800, 1759017600, 1774742400, 1790467200, 1806192000, 1821916800, 1837641600, 1853366400, 1869091200, 1885420800, 1901145600, 1916870400, 1932595200, 1948320000, 1964044800, 1979769600, 1995494400, 2011219200, 2026944000, 2042668800, 2058393600, 2074723200, 2090448000, 2106172800, 2121897600, 2137622400], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(7200, 1, 0), (3600, 0, 8)], 'MET DST\000MET\000'), 'Canada/East-Saskatchewan': ('Canada/East-Saskatchewan', 0, 1, [], '', [(-21600, 0, 0)], 'CST\000'), 'US/Alaska': ('US/Alaska', 148, 3, [-1633266000, -1615125600, -1601816400, -1583676000, -880203600, -765381600, -84373200, -68652000, -52923600, -37202400, -21474000, -5752800, 9975600, 25696800, 41425200, 57751200, 73479600, 89200800, 104929200, 120650400, 126702000, 152100000, 162385200, 183549600, 199278000, 215604000, 230727600, 247053600, 262782000, 278503200, 294231600, 309952800, 325681200, 341402400, 357130800, 372852000, 388580400, 404906400, 420030000, 436356000, 452084400, 467805600, 483534000, 499255200, 514983600, 530704800, 544618800, 562154400, 576068400, 594208800, 607518000, 625658400, 638967600, 657108000, 671022000, 688557600, 702471600, 720007200, 733921200, 752061600, 765370800, 783511200, 796820400, 814960800, 828874800, 846410400, 860324400, 877860000, 891774000, 909309600, 923223600, 941364000, 954673200, 972813600, 986122800, 1004263200, 1018177200, 1035712800, 1049626800, 1067162400, 1081076400, 1099216800, 1112526000, 1130666400, 1143975600, 1162116000, 1175425200, 1193565600, 1207479600, 1225015200, 1238929200, 1256464800, 1270378800, 1288519200, 1301828400, 1319968800, 1333278000, 1351418400, 1365332400, 1382868000, 1396782000, 1414317600, 1428231600, 1445767200, 1459681200, 1477821600, 1491130800, 1509271200, 1522580400, 1540720800, 1554634800, 1572170400, 1586084400, 1603620000, 1617534000, 1635674400, 1648983600, 1667124000, 1680433200, 1698573600, 1712487600, 1730023200, 1743937200, 1761472800, 1775386800, 1792922400, 1806836400, 1824976800, 1838286000, 1856426400, 1869735600, 1887876000, 1901790000, 1919325600, 1933239600, 1950775200, 1964689200, 1982829600, 1996138800, 2014279200, 2027588400, 2045728800, 2059038000, 2077178400, 2091092400, 2108628000, 2122542000, 2140077600], '\000\001\000\001\002\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-28800, 1, 0), (-32400, 0, 5), (-28800, 1, 10)], 'AKDT\000AKST\000AKWT\000'), 'Canada/Mountain': ('Canada/Mountain', 138, 2, [-21481200, -5760000, 9968400, 25689600, 41418000, 57744000, 73472400, 89193600, 104922000, 120643200, 136371600, 152092800, 167821200, 183542400, 199270800, 215596800, 230720400, 247046400, 262774800, 278496000, 294224400, 309945600, 325674000, 341395200, 357123600, 372844800, 388573200, 404899200, 420022800, 436348800, 452077200, 467798400, 483526800, 499248000, 514976400, 530697600, 544611600, 562147200, 576061200, 594201600, 607510800, 625651200, 638960400, 657100800, 671014800, 688550400, 702464400, 720000000, 733914000, 752054400, 765363600, 783504000, 796813200, 814953600, 828867600, 846403200, 860317200, 877852800, 891766800, 909302400, 923216400, 941356800, 954666000, 972806400, 986115600, 1004256000, 1018170000, 1035705600, 1049619600, 1067155200, 1081069200, 1099209600, 1112518800, 1130659200, 1143968400, 1162108800, 1175418000, 1193558400, 1207472400, 1225008000, 1238922000, 1256457600, 1270371600, 1288512000, 1301821200, 1319961600, 1333270800, 1351411200, 1365325200, 1382860800, 1396774800, 1414310400, 1428224400, 1445760000, 1459674000, 1477814400, 1491123600, 1509264000, 1522573200, 1540713600, 1554627600, 1572163200, 1586077200, 1603612800, 1617526800, 1635667200, 1648976400, 1667116800, 1680426000, 1698566400, 1712480400, 1730016000, 1743930000, 1761465600, 1775379600, 1792915200, 1806829200, 1824969600, 1838278800, 1856419200, 1869728400, 1887868800, 1901782800, 1919318400, 1933232400, 1950768000, 1964682000, 1982822400, 1996131600, 2014272000, 2027581200, 2045721600, 2059030800, 2077171200, 2091085200, 2108620800, 2122534800, 2140070400], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-21600, 1, 0), (-25200, 0, 4)], 'MDT\000MST\000'), 'Universal': ('Universal', 0, 1, [], '', [(0, 0, 0)], 'GMT\000'), 'US/East-Indiana': ('US/East-Indiana', 6, 3, [-1633280400, -1615140000, -1601830800, -1583690400, -880218000, -765396000], '\000\001\000\001\002\001', [(-14400, 1, 0), (-18000, 0, 4), (-14400, 1, 8)], 'EDT\000EST\000EWT\000'), 'Canada/Yukon': ('Canada/Yukon', 138, 2, [-21474000, -5752800, 9975600, 25696800, 41425200, 57751200, 73479600, 89200800, 104929200, 120650400, 136378800, 152100000, 167828400, 183549600, 199278000, 215604000, 230727600, 247053600, 262782000, 278503200, 294231600, 309952800, 325681200, 341402400, 357130800, 372852000, 388580400, 404906400, 420030000, 436356000, 452084400, 467805600, 483534000, 499255200, 514983600, 530704800, 544618800, 562154400, 576068400, 594208800, 607518000, 625658400, 638967600, 657108000, 671022000, 688557600, 702471600, 720007200, 733921200, 752061600, 765370800, 783511200, 796820400, 814960800, 828874800, 846410400, 860324400, 877860000, 891774000, 909309600, 923223600, 941364000, 954673200, 972813600, 986122800, 1004263200, 1018177200, 1035712800, 1049626800, 1067162400, 1081076400, 1099216800, 1112526000, 1130666400, 1143975600, 1162116000, 1175425200, 1193565600, 1207479600, 1225015200, 1238929200, 1256464800, 1270378800, 1288519200, 1301828400, 1319968800, 1333278000, 1351418400, 1365332400, 1382868000, 1396782000, 1414317600, 1428231600, 1445767200, 1459681200, 1477821600, 1491130800, 1509271200, 1522580400, 1540720800, 1554634800, 1572170400, 1586084400, 1603620000, 1617534000, 1635674400, 1648983600, 1667124000, 1680433200, 1698573600, 1712487600, 1730023200, 1743937200, 1761472800, 1775386800, 1792922400, 1806836400, 1824976800, 1838286000, 1856426400, 1869735600, 1887876000, 1901790000, 1919325600, 1933239600, 1950775200, 1964689200, 1982829600, 1996138800, 2014279200, 2027588400, 2045728800, 2059038000, 2077178400, 2091092400, 2108628000, 2122542000, 2140077600], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-28800, 1, 0), (-32400, 0, 4)], 'YDT\000YST\000'), 'Iceland': ('Iceland', 0, 1, [], '', [(0, 0, 0)], 'WET\000'), 'Canada/Eastern': ('Canada/Eastern', 138, 2, [-21488400, -5767200, 9961200, 25682400, 41410800, 57736800, 73465200, 89186400, 104914800, 120636000, 136364400, 152085600, 167814000, 183535200, 199263600, 215589600, 230713200, 247039200, 262767600, 278488800, 294217200, 309938400, 325666800, 341388000, 357116400, 372837600, 388566000, 404892000, 420015600, 436341600, 452070000, 467791200, 483519600, 499240800, 514969200, 530690400, 544604400, 562140000, 576054000, 594194400, 607503600, 625644000, 638953200, 657093600, 671007600, 688543200, 702457200, 719992800, 733906800, 752047200, 765356400, 783496800, 796806000, 814946400, 828860400, 846396000, 860310000, 877845600, 891759600, 909295200, 923209200, 941349600, 954658800, 972799200, 986108400, 1004248800, 1018162800, 1035698400, 1049612400, 1067148000, 1081062000, 1099202400, 1112511600, 1130652000, 1143961200, 1162101600, 1175410800, 1193551200, 1207465200, 1225000800, 1238914800, 1256450400, 1270364400, 1288504800, 1301814000, 1319954400, 1333263600, 1351404000, 1365318000, 1382853600, 1396767600, 1414303200, 1428217200, 1445752800, 1459666800, 1477807200, 1491116400, 1509256800, 1522566000, 1540706400, 1554620400, 1572156000, 1586070000, 1603605600, 1617519600, 1635660000, 1648969200, 1667109600, 1680418800, 1698559200, 1712473200, 1730008800, 1743922800, 1761458400, 1775372400, 1792908000, 1806822000, 1824962400, 1838271600, 1856412000, 1869721200, 1887861600, 1901775600, 1919311200, 1933225200, 1950760800, 1964674800, 1982815200, 1996124400, 2014264800, 2027574000, 2045714400, 2059023600, 2077164000, 2091078000, 2108613600, 2122527600, 2140063200], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-14400, 1, 0), (-18000, 0, 4)], 'EDT\000EST\000'), 'Canada/Pacific': ('Canada/Pacific', 138, 2, [-21477600, -5756400, 9972000, 25693200, 41421600, 57747600, 73476000, 89197200, 104925600, 120646800, 136375200, 152096400, 167824800, 183546000, 199274400, 215600400, 230724000, 247050000, 262778400, 278499600, 294228000, 309949200, 325677600, 341398800, 357127200, 372848400, 388576800, 404902800, 420026400, 436352400, 452080800, 467802000, 483530400, 499251600, 514980000, 530701200, 544615200, 562150800, 576064800, 594205200, 607514400, 625654800, 638964000, 657104400, 671018400, 688554000, 702468000, 720003600, 733917600, 752058000, 765367200, 783507600, 796816800, 814957200, 828871200, 846406800, 860320800, 877856400, 891770400, 909306000, 923220000, 941360400, 954669600, 972810000, 986119200, 1004259600, 1018173600, 1035709200, 1049623200, 1067158800, 1081072800, 1099213200, 1112522400, 1130662800, 1143972000, 1162112400, 1175421600, 1193562000, 1207476000, 1225011600, 1238925600, 1256461200, 1270375200, 1288515600, 1301824800, 1319965200, 1333274400, 1351414800, 1365328800, 1382864400, 1396778400, 1414314000, 1428228000, 1445763600, 1459677600, 1477818000, 1491127200, 1509267600, 1522576800, 1540717200, 1554631200, 1572166800, 1586080800, 1603616400, 1617530400, 1635670800, 1648980000, 1667120400, 1680429600, 1698570000, 1712484000, 1730019600, 1743933600, 1761469200, 1775383200, 1792918800, 1806832800, 1824973200, 1838282400, 1856422800, 1869732000, 1887872400, 1901786400, 1919322000, 1933236000, 1950771600, 1964685600, 1982826000, 1996135200, 2014275600, 2027584800, 2045725200, 2059034400, 2077174800, 2091088800, 2108624400, 2122538400, 2140074000], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-25200, 1, 0), (-28800, 0, 4)], 'PDT\000PST\000'), 'Mexico/BajaNorte': ('Mexico/BajaNorte', 102, 2, [544615200, 562150800, 576064800, 594205200, 607514400, 625654800, 638964000, 657104400, 671018400, 688554000, 702468000, 720003600, 733917600, 752058000, 765367200, 783507600, 796816800, 814957200, 828871200, 846406800, 860320800, 877856400, 891770400, 909306000, 923220000, 941360400, 954669600, 972810000, 986119200, 1004259600, 1018173600, 1035709200, 1049623200, 1067158800, 1081072800, 1099213200, 1112522400, 1130662800, 1143972000, 1162112400, 1175421600, 1193562000, 1207476000, 1225011600, 1238925600, 1256461200, 1270375200, 1288515600, 1301824800, 1319965200, 1333274400, 1351414800, 1365328800, 1382864400, 1396778400, 1414314000, 1428228000, 1445763600, 1459677600, 1477818000, 1491127200, 1509267600, 1522576800, 1540717200, 1554631200, 1572166800, 1586080800, 1603616400, 1617530400, 1635670800, 1648980000, 1667120400, 1680429600, 1698570000, 1712484000, 1730019600, 1743933600, 1761469200, 1775383200, 1792918800, 1806832800, 1824973200, 1838282400, 1856422800, 1869732000, 1887872400, 1901786400, 1919322000, 1933236000, 1950771600, 1964685600, 1982826000, 1996135200, 2014275600, 2027584800, 2045725200, 2059034400, 2077174800, 2091088800, 2108624400, 2122538400, 2140074000], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-25200, 1, 0), (-28800, 0, 4)], 'PDT\000PST\000'), 'Canada/Central': ('Canada/Central', 138, 2, [-21484800, -5763600, 9964800, 25686000, 41414400, 57740400, 73468800, 89190000, 104918400, 120639600, 136368000, 152089200, 167817600, 183538800, 199267200, 215593200, 230716800, 247042800, 262771200, 278492400, 294220800, 309942000, 325670400, 341391600, 357120000, 372841200, 388569600, 404895600, 420019200, 436345200, 452073600, 467794800, 483523200, 499244400, 514972800, 530694000, 544608000, 562143600, 576057600, 594198000, 607507200, 625647600, 638956800, 657097200, 671011200, 688546800, 702460800, 719996400, 733910400, 752050800, 765360000, 783500400, 796809600, 814950000, 828864000, 846399600, 860313600, 877849200, 891763200, 909298800, 923212800, 941353200, 954662400, 972802800, 986112000, 1004252400, 1018166400, 1035702000, 1049616000, 1067151600, 1081065600, 1099206000, 1112515200, 1130655600, 1143964800, 1162105200, 1175414400, 1193554800, 1207468800, 1225004400, 1238918400, 1256454000, 1270368000, 1288508400, 1301817600, 1319958000, 1333267200, 1351407600, 1365321600, 1382857200, 1396771200, 1414306800, 1428220800, 1445756400, 1459670400, 1477810800, 1491120000, 1509260400, 1522569600, 1540710000, 1554624000, 1572159600, 1586073600, 1603609200, 1617523200, 1635663600, 1648972800, 1667113200, 1680422400, 1698562800, 1712476800, 1730012400, 1743926400, 1761462000, 1775376000, 1792911600, 1806825600, 1824966000, 1838275200, 1856415600, 1869724800, 1887865200, 1901779200, 1919314800, 1933228800, 1950764400, 1964678400, 1982818800, 1996128000, 2014268400, 2027577600, 2045718000, 2059027200, 2077167600, 2091081600, 2108617200, 2122531200, 2140066800], '\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-18000, 1, 0), (-21600, 0, 4)], 'CDT\000CST\000'), 'US/Michigan': ('US/Michigan', 138, 3, [-1633280400, -1615140000, -1601830800, -1583690400, -880218000, -765396000, -84387600, -68666400, 104914800, 120636000, 126687600, 152085600, 162370800, 183535200, 199263600, 215589600, 230713200, 247039200, 262767600, 278488800, 294217200, 309938400, 325666800, 341388000, 357116400, 372837600, 388566000, 404892000, 420015600, 436341600, 452070000, 467791200, 483519600, 499240800, 514969200, 530690400, 544604400, 562140000, 576054000, 594194400, 607503600, 625644000, 638953200, 657093600, 671007600, 688543200, 702457200, 719992800, 733906800, 752047200, 765356400, 783496800, 796806000, 814946400, 828860400, 846396000, 860310000, 877845600, 891759600, 909295200, 923209200, 941349600, 954658800, 972799200, 986108400, 1004248800, 1018162800, 1035698400, 1049612400, 1067148000, 1081062000, 1099202400, 1112511600, 1130652000, 1143961200, 1162101600, 1175410800, 1193551200, 1207465200, 1225000800, 1238914800, 1256450400, 1270364400, 1288504800, 1301814000, 1319954400, 1333263600, 1351404000, 1365318000, 1382853600, 1396767600, 1414303200, 1428217200, 1445752800, 1459666800, 1477807200, 1491116400, 1509256800, 1522566000, 1540706400, 1554620400, 1572156000, 1586070000, 1603605600, 1617519600, 1635660000, 1648969200, 1667109600, 1680418800, 1698559200, 1712473200, 1730008800, 1743922800, 1761458400, 1775372400, 1792908000, 1806822000, 1824962400, 1838271600, 1856412000, 1869721200, 1887861600, 1901775600, 1919311200, 1933225200, 1950760800, 1964674800, 1982815200, 1996124400, 2014264800, 2027574000, 2045714400, 2059023600, 2077164000, 2091078000, 2108613600, 2122527600, 2140063200], '\000\001\000\001\002\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001\000\001', [(-14400, 1, 0), (-18000, 0, 4), (-14400, 1, 8)], 'EDT\000EST\000EWT\000'), } zope2.13-2.13.21/source/DateTime/src/DateTime/DateTime.txt0000644000175000017500000005362712214017435021667 0ustar arnauarnauThe DateTime package ==================== Encapsulation of date/time values. Function Timezones() -------------------- Returns the list of recognized timezone names: >>> from DateTime import Timezones >>> zones = set(Timezones()) Almost all of the standard pytz timezones are included, with the exception of some commonly-used but ambiguous abbreviations, where historical Zope usage conflicts with the name used by pytz: >>> import pytz >>> [x for x in pytz.all_timezones if x not in zones] ['CET', 'EET', 'EST', 'MET', 'MST', 'WET'] Class DateTime -------------- DateTime objects represent instants in time and provide interfaces for controlling its representation without affecting the absolute value of the object. DateTime objects may be created from a wide variety of string or numeric data, or may be computed from other DateTime objects. DateTimes support the ability to convert their representations to many major timezones, as well as the ablility to create a DateTime object in the context of a given timezone. DateTime objects provide partial numerical behavior: * Two date-time objects can be subtracted to obtain a time, in days between the two. * A date-time object and a positive or negative number may be added to obtain a new date-time object that is the given number of days later than the input date-time object. * A positive or negative number and a date-time object may be added to obtain a new date-time object that is the given number of days later than the input date-time object. * A positive or negative number may be subtracted from a date-time object to obtain a new date-time object that is the given number of days earlier than the input date-time object. DateTime objects may be converted to integer, long, or float numbers of days since January 1, 1901, using the standard int, long, and float functions (Compatibility Note: int, long and float return the number of days since 1901 in GMT rather than local machine timezone). DateTime objects also provide access to their value in a float format usable with the python time module, provided that the value of the object falls in the range of the epoch-based time module. A DateTime object should be considered immutable; all conversion and numeric operations return a new DateTime object rather than modify the current object. A DateTime object always maintains its value as an absolute UTC time, and is represented in the context of some timezone based on the arguments used to create the object. A DateTime object's methods return values based on the timezone context. Note that in all cases the local machine timezone is used for representation if no timezone is specified. Constructor for DateTime ------------------------ DateTime() returns a new date-time object. DateTimes may be created with from zero to seven arguments: * If the function is called with no arguments, then the current date/ time is returned, represented in the timezone of the local machine. * If the function is invoked with a single string argument which is a recognized timezone name, an object representing the current time is returned, represented in the specified timezone. * If the function is invoked with a single string argument representing a valid date/time, an object representing that date/ time will be returned. As a general rule, any date-time representation that is recognized and unambigous to a resident of North America is acceptable. (The reason for this qualification is that in North America, a date like: 2/1/1994 is interpreted as February 1, 1994, while in some parts of the world, it is interpreted as January 2, 1994.) A date/ time string consists of two components, a date component and an optional time component, separated by one or more spaces. If the time component is omited, 12:00am is assumed. Any recognized timezone name specified as the final element of the date/time string will be used for computing the date/time value. (If you create a DateTime with the string "Mar 9, 1997 1:45pm US/Pacific", the value will essentially be the same as if you had captured time.time() at the specified date and time on a machine in that timezone). o Returns current date/time, represented in US/Eastern: >>> from DateTime import DateTime >>> e = DateTime('US/Eastern') >>> e.timezone() 'US/Eastern' o Returns specified time, represented in local machine zone: >>> x = DateTime('1997/3/9 1:45pm') >>> x.parts() # doctest: +ELLIPSIS (1997, 3, 9, 13, 45, 0.0, ...) o Specified time in local machine zone, verbose format: >>> y = DateTime('Mar 9, 1997 13:45:00') >>> y.parts() # doctest: +ELLIPSIS (1997, 3, 9, 13, 45, 0.0, ...) >>> y == x True The date component consists of year, month, and day values. The year value must be a one-, two-, or four-digit integer. If a one- or two-digit year is used, the year is assumed to be in the twentieth century. The month may an integer, from 1 to 12, a month name, or a month abreviation, where a period may optionally follow the abreviation. The day must be an integer from 1 to the number of days in the month. The year, month, and day values may be separated by periods, hyphens, forward, shashes, or spaces. Extra spaces are permitted around the delimiters. Year, month, and day values may be given in any order as long as it is possible to distinguish the components. If all three components are numbers that are less than 13, then a a month-day-year ordering is assumed. The time component consists of hour, minute, and second values separated by colons. The hour value must be an integer between 0 and 23 inclusively. The minute value must be an integer between 0 and 59 inclusively. The second value may be an integer value between 0 and 59.999 inclusively. The second value or both the minute and second values may be ommitted. The time may be followed by am or pm in upper or lower case, in which case a 12-hour clock is assumed. * If the DateTime function is invoked with a single Numeric argument, the number is assumed to be either a floating point value such as that returned by time.time() , or a number of days after January 1, 1901 00:00:00 UTC. A DateTime object is returned that represents either the gmt value of the time.time() float represented in the local machine's timezone, or that number of days after January 1, 1901. Note that the number of days after 1901 need to be expressed from the viewpoint of the local machine's timezone. A negative argument will yield a date-time value before 1901. * If the function is invoked with two numeric arguments, then the first is taken to be an integer year and the second argument is taken to be an offset in days from the beginning of the year, in the context of the local machine timezone. The date-time value returned is the given offset number of days from the beginning of the given year, represented in the timezone of the local machine. The offset may be positive or negative. Two-digit years are assumed to be in the twentieth century. * If the function is invoked with two arguments, the first a float representing a number of seconds past the epoch in gmt (such as those returned by time.time()) and the second a string naming a recognized timezone, a DateTime with a value of that gmt time will be returned, represented in the given timezone. >>> import time >>> t = time.time() Time t represented as US/Eastern: >>> now_east = DateTime(t, 'US/Eastern') Time t represented as US/Pacific: >>> now_west = DateTime(t, 'US/Pacific') Only their representations are different: >>> now_east == now_west True * If the function is invoked with three or more numeric arguments, then the first is taken to be an integer year, the second is taken to be an integer month, and the third is taken to be an integer day. If the combination of values is not valid, then a DateTimeError is raised. One- or two-digit years up to 69 are assumed to be in the 21st century, whereas values 70-99 are assumed to be 20th century. The fourth, fifth, and sixth arguments are floating point, positive or negative offsets in units of hours, minutes, and days, and default to zero if not given. An optional string may be given as the final argument to indicate timezone (the effect of this is as if you had taken the value of time.time() at that time on a machine in the specified timezone). If a string argument passed to the DateTime constructor cannot be parsed, it will raise DateTime.SyntaxError. Invalid date, time, or timezone components will raise a DateTime.DateTimeError. The module function Timezones() will return a list of the timezones recognized by the DateTime module. Recognition of timezone names is case-insensitive. Instance Methods for DateTime (IDateTime interface) --------------------------------------------------- Conversion and comparison methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``timeTime()`` returns the date/time as a floating-point number in UTC, in the format used by the python time module. Note that it is possible to create date /time values with DateTime that have no meaningful value to the time module, and in such cases a DateTimeError is raised. A DateTime object's value must generally be between Jan 1, 1970 (or your local machine epoch) and Jan 2038 to produce a valid time.time() style value. >>> dt = DateTime('Mar 9, 1997 13:45:00 US/Eastern') >>> dt.timeTime() 857933100.0 >>> DateTime('2040/01/01 UTC').timeTime() 2208988800.0 >>> DateTime('1900/01/01 UTC').timeTime() -2208988800.0 * ``toZone(z)`` returns a DateTime with the value as the current object, represented in the indicated timezone: >>> dt.toZone('UTC') DateTime('1997/03/09 18:45:00 UTC') >>> dt.toZone('UTC') == dt True * ``isFuture()`` returns true if this object represents a date/time later than the time of the call: >>> dt.isFuture() False >>> DateTime('Jan 1 3000').isFuture() # not time-machine safe! True * ``isPast()`` returns true if this object represents a date/time earlier than the time of the call: >>> dt.isPast() True >>> DateTime('Jan 1 3000').isPast() # not time-machine safe! False * ``isCurrentYear()`` returns true if this object represents a date/time that falls within the current year, in the context of this object's timezone representation: >>> dt.isCurrentYear() False >>> DateTime().isCurrentYear() True * ``isCurrentMonth()`` returns true if this object represents a date/time that falls within the current month, in the context of this object's timezone representation: >>> dt.isCurrentMonth() False >>> DateTime().isCurrentMonth() True * ``isCurrentDay()`` returns true if this object represents a date/time that falls within the current day, in the context of this object's timezone representation: >>> dt.isCurrentDay() False >>> DateTime().isCurrentDay() True * ``isCurrentHour()`` returns true if this object represents a date/time that falls within the current hour, in the context of this object's timezone representation: >>> dt.isCurrentHour() False >>> DateTime().isCurrentHour() True * ``isCurrentMinute()`` returns true if this object represents a date/time that falls within the current minute, in the context of this object's timezone representation: >>> dt.isCurrentMinute() False >>> DateTime().isCurrentMinute() True * ``isLeapYear()`` returns true if the current year (in the context of the object's timezone) is a leap year: >>> dt.isLeapYear() False >>> DateTime('Mar 8 2004').isLeapYear() True * ``earliestTime()`` returns a new DateTime object that represents the earliest possible time (in whole seconds) that still falls within the current object's day, in the object's timezone context: >>> dt.earliestTime() DateTime('1997/03/09 00:00:00 US/Eastern') * ``latestTime()`` return a new DateTime object that represents the latest possible time (in whole seconds) that still falls within the current object's day, in the object's timezone context >>> dt.latestTime() DateTime('1997/03/09 23:59:59 US/Eastern') Component access ~~~~~~~~~~~~~~~~ * ``parts()`` returns a tuple containing the calendar year, month, day, hour, minute second and timezone of the object >>> dt.parts() (1997, 3, 9, 13, 45, 0.0, 'US/Eastern') * ``timezone()`` returns the timezone in which the object is represented: >>> dt.timezone() in Timezones() True * ``tzoffset()`` returns the timezone offset for the objects timezone: >>> dt.tzoffset() -18000 * ``year()`` returns the calendar year of the object: >>> dt.year() 1997 * ``month()`` retursn the month of the object as an integer: >>> dt.month() 3 * ``Month()`` returns the full month name: >>> dt.Month() 'March' * ``aMonth()`` returns the abreviated month name: >>> dt.aMonth() 'Mar' * ``pMonth()`` returns the abreviated (with period) month name: >>> dt.pMonth() 'Mar.' * ``day()`` returns the integer day: >>> dt.day() 9 * ``Day()`` returns the full name of the day of the week: >>> dt.Day() 'Sunday' * ``dayOfYear()`` returns the day of the year, in context of the timezone representation of the object: >>> dt.dayOfYear() 68 * ``aDay()`` returns the abreviated name of the day of the week: >>> dt.aDay() 'Sun' * ``pDay()`` returns the abreviated (with period) name of the day of the week: >>> dt.pDay() 'Sun.' * ``dow()`` returns the integer day of the week, where Sunday is 0: >>> dt.dow() 0 * ``dow_1()`` returns the integer day of the week, where sunday is 1: >>> dt.dow_1() 1 * ``h_12()`` returns the 12-hour clock representation of the hour: >>> dt.h_12() 1 * ``h_24()`` returns the 24-hour clock representation of the hour: >>> dt.h_24() 13 * ``ampm()`` returns the appropriate time modifier (am or pm): >>> dt.ampm() 'pm' * ``hour()`` returns the 24-hour clock representation of the hour: >>> dt.hour() 13 * ``minute()`` returns the minute: >>> dt.minute() 45 * ``second()`` returns the second: >>> dt.second() 0.0 * ``millis()`` returns the milliseconds since the epoch in GMT. >>> dt.millis() 857933100000L strftime() ~~~~~~~~~~ See ``tests/testDateTime.py``. General formats from previous DateTime ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``Date()`` return the date string for the object: >>> dt.Date() '1997/03/09' * ``Time()`` returns the time string for an object to the nearest second: >>> dt.Time() '13:45:00' * ``TimeMinutes()`` returns the time string for an object not showing seconds: >>> dt.TimeMinutes() '13:45' * ``AMPM()`` returns the time string for an object to the nearest second: >>> dt.AMPM() '01:45:00 pm' * ``AMPMMinutes()`` returns the time string for an object not showing seconds: >>> dt.AMPMMinutes() '01:45 pm' * ``PreciseTime()`` returns the time string for the object: >>> dt.PreciseTime() '13:45:00.000' * ``PreciseAMPM()`` returns the time string for the object: >>> dt.PreciseAMPM() '01:45:00.000 pm' * ``yy()`` returns the calendar year as a 2 digit string >>> dt.yy() '97' * ``mm()`` returns the month as a 2 digit string >>> dt.mm() '03' * ``dd()`` returns the day as a 2 digit string: >>> dt.dd() '09' * ``rfc822()`` returns the date in RFC 822 format: >>> dt.rfc822() 'Sun, 09 Mar 1997 13:45:00 -0500' New formats ~~~~~~~~~~~ * ``fCommon()`` returns a string representing the object's value in the format: March 9, 1997 1:45 pm: >>> dt.fCommon() 'March 9, 1997 1:45 pm' * ``fCommonZ()`` returns a string representing the object's value in the format: March 9, 1997 1:45 pm US/Eastern: >>> dt.fCommonZ() 'March 9, 1997 1:45 pm US/Eastern' * ``aCommon()`` returns a string representing the object's value in the format: Mar 9, 1997 1:45 pm: >>> dt.aCommon() 'Mar 9, 1997 1:45 pm' * ``aCommonZ()`` return a string representing the object's value in the format: Mar 9, 1997 1:45 pm US/Eastern: >>> dt.aCommonZ() 'Mar 9, 1997 1:45 pm US/Eastern' * ``pCommon()`` returns a string representing the object's value in the format Mar. 9, 1997 1:45 pm: >>> dt.pCommon() 'Mar. 9, 1997 1:45 pm' * ``pCommonZ()`` returns a string representing the object's value in the format: Mar. 9, 1997 1:45 pm US/Eastern: >>> dt.pCommonZ() 'Mar. 9, 1997 1:45 pm US/Eastern' * ``ISO()`` returns a string with the date/time in ISO format. Note: this is not ISO 8601-format! See the ISO8601 and HTML4 methods below for ISO 8601-compliant output. Dates are output as: YYYY-MM-DD HH:MM:SS >>> dt.ISO() '1997-03-09 13:45:00' * ``ISO8601()`` returns the object in ISO 8601-compatible format containing the date, time with seconds-precision and the time zone identifier - see http://www.w3.org/TR/NOTE-datetime. Dates are output as: YYYY-MM-DDTHH:MM:SSTZD (T is a literal character, TZD is Time Zone Designator, format +HH:MM or -HH:MM). The ``HTML4()`` method below offers the same formatting, but converts to UTC before returning the value and sets the TZD"Z" >>> dt.ISO8601() '1997-03-09T13:45:00-05:00' * ``HTML4()`` returns the object in the format used in the HTML4.0 specification, one of the standard forms in ISO8601. See http://www.w3.org/TR/NOTE-datetime. Dates are output as: YYYY-MM-DDTHH:MM:SSZ (T, Z are literal characters, the time is in UTC.): >>> dt.HTML4() '1997-03-09T18:45:00Z' * ``JulianDay()`` returns the Julian day according to http://www.tondering.dk/claus/cal/node3.html#sec-calcjd >>> dt.JulianDay() 2450517 * ``week()`` returns the week number according to ISO see http://www.tondering.dk/claus/cal/node6.html#SECTION00670000000000000000 >>> dt.week() 10 Deprecated API ~~~~~~~~~~~~~~ * DayOfWeek(): see Day() * Day_(): see pDay() * Mon(): see aMonth() * Mon_(): see pMonth General Services Provided by DateTime ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DateTimes can be repr()'ed; the result will be a string indicating how to make a DateTime object like this: >>> `dt` "DateTime('1997/03/09 13:45:00 US/Eastern')" When we convert them into a string, we get a nicer string that could actually be shown to a user: >>> str(dt) '1997/03/09 13:45:00 US/Eastern' The hash value of a DateTime is based on the date and time and is equal for different representations of the DateTime: >>> hash(dt) 3618678 >>> hash(dt.toZone('UTC')) 3618678 A DateTime can be compared with another DateTime or float via ``cmp()``. NOTE: __cmp__ support is provided for backward compatibility only, and mixing DateTimes with ExtensionClasses could cause __cmp__ to break. You should use the methods lessThan, greaterThan, lessThanEqualTo, greaterThanEqualTo, equalTo and notEqualTo to avoid potential problems later! >>> cmp(dt, dt) 0 >>> cmp(dt, dt.toZone('UTC')) 0 >>> cmp(dt, dt.timeTime()) 0 >>> cmp(dt, DateTime('2000/01/01')) -1 >>> cmp(dt, DateTime('1900/01/01')) 1 DateTime objects can be compared to other DateTime objects OR floating point numbers such as the ones which are returned by the python time module. On comparison for equality, True is returned if the object represents a date/time equal to the specified DateTime or time module style time: >>> dt == dt True >>> dt == dt.toZone('UTC') True >>> dt == dt.timeTime() True >>> dt == DateTime() False >>> dt.equalTo(dt) True >>> dt.equalTo(dt.toZone('UTC')) True >>> dt.equalTo(dt.timeTime()) True >>> dt.equalTo(DateTime()) False Same goes for inequalities: >>> dt != dt False >>> dt != dt.toZone('UTC') False >>> dt != dt.timeTime() False >>> dt != DateTime() True >>> dt.notEqualTo(dt) False >>> dt.notEqualTo(dt.toZone('UTC')) False >>> dt.notEqualTo(dt.timeTime()) False >>> dt.notEqualTo(DateTime()) True >>> dt > dt False >>> DateTime() > dt True >>> dt > DateTime().timeTime() False >>> DateTime().timeTime() > dt True >>> dt.greaterThan(dt) False >>> DateTime().greaterThan(dt) True >>> dt.greaterThan(DateTime().timeTime()) False >>> dt >= dt True >>> DateTime() >= dt True >>> dt >= DateTime().timeTime() False >>> DateTime().timeTime() >= dt True >>> dt.greaterThanEqualTo(dt) True >>> DateTime().greaterThanEqualTo(dt) True >>> dt.greaterThanEqualTo(DateTime().timeTime()) False >>> dt < dt False >>> DateTime() < dt False >>> dt < DateTime().timeTime() True >>> DateTime().timeTime() < dt False >>> dt.lessThan(dt) False >>> DateTime().lessThan(dt) False >>> dt.lessThan(DateTime().timeTime()) True >>> dt <= dt True >>> DateTime() <= dt False >>> dt <= DateTime().timeTime() True >>> DateTime().timeTime() <= dt False >>> dt.lessThanEqualTo(dt) True >>> DateTime().lessThanEqualTo(dt) False >>> dt.lessThanEqualTo(DateTime().timeTime()) True Numeric Services Provided by DateTime ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A DateTime may be added to a number and a number may be added to a DateTime: >>> dt + 5 DateTime('1997/03/14 13:45:00 US/Eastern') >>> 5 + dt DateTime('1997/03/14 13:45:00 US/Eastern') Two DateTimes cannot be added: >>> dt + dt Traceback (most recent call last): ... DateTimeError: Cannot add two DateTimes Either a DateTime or a number may be subtracted from a DateTime, however, a DateTime may not be subtracted from a number: >>> DateTime('1997/03/10 13:45 US/Eastern') - dt 1.0 >>> dt - 1 DateTime('1997/03/08 13:45:00 US/Eastern') >>> 1 - dt Traceback (most recent call last): ... TypeError: unsupported operand type(s) for -: 'int' and 'instance' DateTimes can also be converted to integers (number of seconds since the epoch), longs (not too long ;)) and floats: >>> int(dt) 857933100 >>> long(dt) 857933100L >>> float(dt) 857933100.0 zope2.13-2.13.21/source/DateTime/src/DateTime/interfaces.py0000644000175000017500000003103412214017435022113 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """DateTime interfaces $Id: interfaces.py 110592 2010-04-07 16:26:18Z tseaver $ """ from zope.interface import Interface class DateTimeError(Exception): pass class SyntaxError(DateTimeError): pass class DateError(DateTimeError): pass class TimeError(DateTimeError): pass class IDateTime(Interface): # Conversion and comparison methods #TODO determine whether this method really is part of the public API def localZone(ltm=None): '''Returns the time zone on the given date. The time zone can change according to daylight savings.''' def timeTime(): """Return the date/time as a floating-point number in UTC, in the format used by the python time module. Note that it is possible to create date/time values with DateTime that have no meaningful value to the time module.""" def toZone(z): """Return a DateTime with the value as the current object, represented in the indicated timezone.""" def isFuture(): """Return true if this object represents a date/time later than the time of the call""" def isPast(): """Return true if this object represents a date/time earlier than the time of the call""" def isCurrentYear(): """Return true if this object represents a date/time that falls within the current year, in the context of this object's timezone representation""" def isCurrentMonth(): """Return true if this object represents a date/time that falls within the current month, in the context of this object's timezone representation""" def isCurrentDay(): """Return true if this object represents a date/time that falls within the current day, in the context of this object's timezone representation""" def isCurrentHour(): """Return true if this object represents a date/time that falls within the current hour, in the context of this object's timezone representation""" def isCurrentMinute(): """Return true if this object represents a date/time that falls within the current minute, in the context of this object's timezone representation""" def isLeapYear(): """Return true if the current year (in the context of the object's timezone) is a leap year""" def earliestTime(): """Return a new DateTime object that represents the earliest possible time (in whole seconds) that still falls within the current object's day, in the object's timezone context""" def latestTime(): """Return a new DateTime object that represents the latest possible time (in whole seconds) that still falls within the current object's day, in the object's timezone context""" def greaterThan(t): """Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time greater than the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer milliseconds.""" __gt__ = greaterThan def greaterThanEqualTo(t): """Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time greater than or equal to the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer milliseconds.""" __ge__ = greaterThanEqualTo def equalTo(t): """Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time equal to the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer milliseconds.""" __eq__ = equalTo def notEqualTo(t): """Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time not equal to the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer milliseconds.""" __ne__ = notEqualTo def lessThan(t): """Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time less than the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer milliseconds.""" __lt__ = lessThan def lessThanEqualTo(t): """Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time less than or equal to the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer milliseconds.""" __le__ = lessThanEqualTo # Component access def parts(): """Return a tuple containing the calendar year, month, day, hour, minute second and timezone of the object""" def timezone(): """Return the timezone in which the object is represented.""" def tzoffset(): """Return the timezone offset for the objects timezone.""" def year(): """Return the calendar year of the object""" def month(): """Return the month of the object as an integer""" def Month(): """Return the full month name""" def aMonth(): """Return the abreviated month name.""" def Mon(): """Compatibility: see aMonth""" def pMonth(): """Return the abreviated (with period) month name.""" def Mon_(): """Compatibility: see pMonth""" def day(): """Return the integer day""" def Day(): """Return the full name of the day of the week""" def DayOfWeek(): """Compatibility: see Day""" def dayOfYear(): """Return the day of the year, in context of the timezone representation of the object""" def aDay(): """Return the abreviated name of the day of the week""" def pDay(): """Return the abreviated (with period) name of the day of the week""" def Day_(): """Compatibility: see pDay""" def dow(): """Return the integer day of the week, where sunday is 0""" def dow_1(): """Return the integer day of the week, where sunday is 1""" def h_12(): """Return the 12-hour clock representation of the hour""" def h_24(): """Return the 24-hour clock representation of the hour""" def ampm(): """Return the appropriate time modifier (am or pm)""" def hour(): """Return the 24-hour clock representation of the hour""" def minute(): """Return the minute""" def second(): """Return the second""" def millis(): """Return the millisecond since the epoch in GMT.""" def strftime(format): """Format the date/time using the *current timezone representation*.""" # General formats from previous DateTime def Date(): """Return the date string for the object.""" def Time(): """Return the time string for an object to the nearest second.""" def TimeMinutes(): """Return the time string for an object not showing seconds.""" def AMPM(): """Return the time string for an object to the nearest second.""" def AMPMMinutes(): """Return the time string for an object not showing seconds.""" def PreciseTime(): """Return the time string for the object.""" def PreciseAMPM(): """Return the time string for the object.""" def yy(): """Return calendar year as a 2 digit string""" def mm(): """Return month as a 2 digit string""" def dd(): """Return day as a 2 digit string""" def rfc822(): """Return the date in RFC 822 format""" # New formats def fCommon(): """Return a string representing the object's value in the format: March 1, 1997 1:45 pm""" def fCommonZ(): """Return a string representing the object's value in the format: March 1, 1997 1:45 pm US/Eastern""" def aCommon(): """Return a string representing the object's value in the format: Mar 1, 1997 1:45 pm""" def aCommonZ(): """Return a string representing the object's value in the format: Mar 1, 1997 1:45 pm US/Eastern""" def pCommon(): """Return a string representing the object's value in the format: Mar. 1, 1997 1:45 pm""" def pCommonZ(): """Return a string representing the object's value in the format: Mar. 1, 1997 1:45 pm US/Eastern""" def ISO(): """Return the object in ISO standard format. Note: this is *not* ISO 8601-format! See the ISO8601 and HTML4 methods below for ISO 8601-compliant output Dates are output as: YYYY-MM-DD HH:MM:SS """ def ISO8601(): """Return the object in ISO 8601-compatible format containing the date, time with seconds-precision and the time zone identifier - see http://www.w3.org/TR/NOTE-datetime Dates are output as: YYYY-MM-DDTHH:MM:SSTZD T is a literal character. TZD is Time Zone Designator, format +HH:MM or -HH:MM The HTML4 method below offers the same formatting, but converts to UTC before returning the value and sets the TZD"Z" """ def HTML4(): """Return the object in the format used in the HTML4.0 specification, one of the standard forms in ISO8601. See http://www.w3.org/TR/NOTE-datetime Dates are output as: YYYY-MM-DDTHH:MM:SSZ T, Z are literal characters. The time is in UTC. """ def JulianDay(): """Return the Julian day according to http://www.tondering.dk/claus/cal/node3.html#sec-calcjd """ def week(): """Return the week number according to ISO see http://www.tondering.dk/claus/cal/node6.html#SECTION00670000000000000000 """ # Python operator and conversion API def __add__(other): """A DateTime may be added to a number and a number may be added to a DateTime; two DateTimes cannot be added.""" __radd__ = __add__ def __sub__(other): """Either a DateTime or a number may be subtracted from a DateTime, however, a DateTime may not be subtracted from a number.""" def __repr__(): """Convert a DateTime to a string that looks like a Python expression.""" def __str__(): """Convert a DateTime to a string.""" def __cmp__(obj): """Compare a DateTime with another DateTime object, or a float such as those returned by time.time(). NOTE: __cmp__ support is provided for backward compatibility only, and mixing DateTimes with ExtensionClasses could cause __cmp__ to break. You should use the methods lessThan, greaterThan, lessThanEqualTo, greaterThanEqualTo, equalTo and notEqualTo to avoid potential problems later!!""" def __hash__(): """Compute a hash value for a DateTime""" def __int__(): """Convert to an integer number of seconds since the epoch (gmt)""" def __long__(): """Convert to a long-int number of seconds since the epoch (gmt)""" def __float__(): """Convert to floating-point number of seconds since the epoch (gmt)""" zope2.13-2.13.21/source/DateTime/src/DateTime/pytz.txt0000644000175000017500000001301412214017435021163 0ustar arnauarnauPytz Support ============ Allows the pytz package to be used for time zone information. The advantage of using pytz is that it has a more complete and up to date time zone and daylight savings time database. Usage ----- You don't have to do anything special to make it work. >>> from DateTime import DateTime, Timezones >>> d = DateTime('March 11, 2007 US/Eastern') Daylight Savings ---------------- In 2007 daylight savings time in the US was changed. The Energy Policy Act of 2005 mandates that DST will start on the second Sunday in March and end on the first Sunday in November. In 2007, the start and stop dates are March 11 and November 4, respectively. These dates are different from previous DST start and stop dates. In 2006, the dates were the first Sunday in April (April 2, 2006) and the last Sunday in October (October 29, 2006). Let's make sure that DateTime can deal with this, since the primary motivation to use pytz for time zone information is the fact that it is kept up to date with daylight savings changes. >>> DateTime('March 11, 2007 US/Eastern').tzoffset() -18000 >>> DateTime('March 12, 2007 US/Eastern').tzoffset() -14400 >>> DateTime('November 4, 2007 US/Eastern').tzoffset() -14400 >>> DateTime('November 5, 2007 US/Eastern').tzoffset() -18000 Let's compare this to 2006. >>> DateTime('April 2, 2006 US/Eastern').tzoffset() -18000 >>> DateTime('April 3, 2006 US/Eastern').tzoffset() -14400 >>> DateTime('October 29, 2006 US/Eastern').tzoffset() -14400 >>> DateTime('October 30, 2006 US/Eastern').tzoffset() -18000 Time Zones --------- DateTime can use pytz's large database of time zones. Here are some examples: >>> d = DateTime('Pacific/Kwajalein') >>> d = DateTime('America/Shiprock') >>> d = DateTime('Africa/Ouagadougou') Of course pytz doesn't know about everything. >>> d = DateTime('July 21, 1969 Moon/Eastern') Traceback (most recent call last): ... SyntaxError: July 21, 1969 Moon/Eastern You can still use zone names that DateTime defines that aren't part of the pytz database. >>> d = DateTime('eet') >>> d = DateTime('iceland') These time zones use DateTimes database. So it's preferable to use the official time zone name. One trickiness is that DateTime supports some zone name abbreviations. Some of these map to pytz names, so these abbreviations will give you time zone date from pytz. Notable among abbreviations that work this way are 'est', 'cst', 'mst', and 'pst'. Let's verify that 'est' picks up the 2007 daylight savings time changes. >>> DateTime('March 11, 2007 est').tzoffset() -18000 >>> DateTime('March 12, 2007 est').tzoffset() -14400 >>> DateTime('November 4, 2007 est').tzoffset() -14400 >>> DateTime('November 5, 2007 est').tzoffset() -18000 You can get a list of time zones supported by calling the Timezones() function. >>> Timezones() #doctest: +ELLIPSIS ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', ...] Note that you can mess with this list without hurting things. >>> t = Timezones() >>> t.remove('US/Eastern') >>> d = DateTime('US/Eastern') Python Versions --------------- Both pytz and DateTime should work under Python 2.3 as well as Python 2.4. Internal Components ------------------- The following are tests of internal components. Cache ~~~~~ The DateTime class uses a new time zone cache. >>> DateTime._tzinfo #doctest: +ELLIPSIS The cache maps time zone names to time zone instances. >>> cache = DateTime._tzinfo >>> tz = cache['GMT+730'] >>> tz = cache['US/Mountain'] The cache also must provide a few attributes for use by the DateTime class. The _zlst attribute is a list of supported time zone names. >>> cache._zlst #doctest: +ELLIPSIS ['Africa/Abidjan'... 'Africa/Accra'... 'IDLE'... 'NZST'... 'NZT'...] The _zidx attribute is a list of lower-case and possibly abbreviated time zone names that can be mapped to offical zone names. >>> cache._zidx #doctest: +ELLIPSIS [... 'australia/yancowinna'... 'gmt+0500'... 'europe/isle_of_man'...] Note that there are more items in _zidx than in _zlst since there are multiple names for some time zones. >>> len(cache._zidx) > len(cache._zlst) True Each entry in _zlst should also be present in _zidx in lower case form. >>> for name in cache._zlst: ... if not name.lower() in cache._zidx: ... print "Error %s not in _zidx" % name.lower() The _zmap attribute maps the names in _zidx to official names in _zlst. >>> cache._zmap['africa/abidjan'] 'Africa/Abidjan' >>> cache._zmap['gmt+1'] 'GMT+1' >>> cache._zmap['gmt+0100'] 'GMT+1' >>> cache._zmap['utc'] 'UTC' Let's make sure that _zmap and _zidx agree. >>> idx = list(cache._zidx) >>> idx.sort() >>> keys = cache._zmap.keys() >>> keys.sort() >>> idx == keys True Timezone objects ~~~~~~~~~~~~~~~~ The timezone instances have only one public method info(). It returns a tuple of (offset, is_dst, name). The method takes a timestamp, which is used to determine dst information. >>> t1 = DateTime('November 4, 00:00 2007 US/Mountain').timeTime() >>> t2 = DateTime('November 4, 02:00 2007 US/Mountain').timeTime() >>> tz.info(t1) (-21600, 1, 'MDT') >>> tz.info(t2) (-25200, 0, 'MST') If you don't pass any arguments to info it provides daylight savings time information as of today. >>> tz.info() in ((-21600, 1, 'MDT'), (-25200, 0, 'MST')) True zope2.13-2.13.21/source/DateTime/src/DateTime/__init__.py0000644000175000017500000000124112214017435021524 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from DateTime import DateTime from DateTime import Timezones zope2.13-2.13.21/source/DateTime/src/DateTime/tests/0000755000175000017500000000000012214017435020557 5ustar arnauarnauzope2.13-2.13.21/source/DateTime/src/DateTime/tests/testDateTime.py0000644000175000017500000007016212214017435023533 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import cPickle import math import os import time import unittest from DateTime.DateTime import _findLocalTimeZoneName, _cache from DateTime import DateTime from datetime import date, datetime, tzinfo, timedelta import pytz import legacy try: __file__ except NameError: import sys f = sys.argv[0] else: f = __file__ DATADIR = os.path.dirname(os.path.abspath(f)) del f ZERO = timedelta(0) class FixedOffset(tzinfo): """Fixed offset in minutes east from UTC.""" def __init__(self, offset, name): self.__offset = timedelta(minutes = offset) self.__name = name def utcoffset(self, dt): return self.__offset def tzname(self, dt): return self.__name def dst(self, dt): return ZERO class DateTimeTests(unittest.TestCase): def _compare(self, dt1, dt2, ms=1): '''Compares the internal representation of dt1 with the representation in dt2. Allows sub-millisecond variations. Primarily for testing.''' if ms: self.assertEqual(dt1.millis(), dt2.millis()) self.assertEqual(math.floor(dt1._t * 1000.0), math.floor(dt2._t * 1000.0)) self.assertEqual(math.floor(dt1._d * 86400000.0), math.floor(dt2._d * 86400000.0)) self.assertEqual(math.floor(dt1.time * 86400000.0), math.floor(dt2.time * 86400000.0)) def testBug1203(self): # 01:59:60 occurred in old DateTime dt = DateTime(7200, 'GMT') self.assert_(str(dt).find('60') < 0, dt) def testDSTInEffect(self): # Checks GMT offset for a DST date in the US/Eastern time zone dt = DateTime(2000, 5, 9, 15, 0, 0, 'US/Eastern') self.assertEqual(dt.toZone('GMT').hour(), 19, (dt, dt.toZone('GMT'))) def testDSTNotInEffect(self): # Checks GMT offset for a non-DST date in the US/Eastern time zone dt = DateTime(2000, 11, 9, 15, 0, 0, 'US/Eastern') self.assertEqual(dt.toZone('GMT').hour(), 20, (dt, dt.toZone('GMT'))) def testAddPrecision(self): # Precision of serial additions dt = DateTime() self.assertEqual(str(dt + 0.10 + 3.14 + 6.76 - 10), str(dt), dt) def testConstructor3(self): # Constructor from date/time string dt = DateTime() dt1s = '%d/%d/%d %d:%d:%f %s' % ( dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute(), dt.second(), dt.timezone()) dt1 = DateTime(dt1s) # Compare representations as it's the # only way to compare the dates to the same accuracy self.assertEqual(repr(dt),repr(dt1)) def testConstructor4(self): # Constructor from time float dt = DateTime() dt1 = DateTime(float(dt)) self._compare(dt,dt1) def testConstructor5(self): # Constructor from time float and timezone dt = DateTime() dt1 = DateTime(float(dt), dt.timezone()) self.assertEqual(str(dt), str(dt1), (dt, dt1)) def testConstructor6(self): # Constructor from year and julian date # This test must normalize the time zone, or it *will* break when # DST changes! dt1 = DateTime(2000, 5.500000578705) dt = DateTime('2000/1/5 12:00:00.050 pm %s' % dt1.localZone()) self._compare(dt, dt1) def testConstructor7(self): # Constructor from parts dt = DateTime() dt1 = DateTime( dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute(), dt.second(), dt.timezone()) # Compare representations as it's the # only way to compare the dates to the same accuracy self.assertEqual(repr(dt), repr(dt1)) def testDayOfWeek(self): # Compare to the datetime.date value to make it locale independent expected = date(2000, 6, 16).strftime('%A') # strftime() used to always be passed a day of week of 0 dt = DateTime('2000/6/16') s = dt.strftime('%A') self.assertEqual(s, expected, (dt, s)) def testOldDate(self): # Fails when an 1800 date is displayed with negative signs dt = DateTime('1830/5/6 12:31:46.213 pm') dt1 = dt.toZone('GMT+6') self.assert_(str(dt1).find('-') < 0, (dt, dt1)) def testSubtraction(self): # Reconstruction of a DateTime from its parts, with subtraction # this also tests the accuracy of addition and reconstruction dt = DateTime() dt1 = dt - 3.141592653 dt2 = DateTime( dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute(), dt.second()) dt3 = dt2 - 3.141592653 self.assertEqual(dt1, dt3, (dt, dt1, dt2, dt3)) def testTZ1add(self): # Time zone manipulation: add to a date dt = DateTime('1997/3/8 1:45am GMT-4') dt1 = DateTime('1997/3/9 1:45pm GMT+8') self.assertEqual(dt + 1.0, dt1, (dt, dt1)) def testTZ1sub(self): # Time zone manipulation: subtract from a date dt = DateTime('1997/3/8 1:45am GMT-4') dt1 = DateTime('1997/3/9 1:45pm GMT+8') self.assertEqual(dt1 - 1.0, dt, (dt, dt1)) def testTZ1diff(self): # Time zone manipulation: diff two dates dt = DateTime('1997/3/8 1:45am GMT-4') dt1 = DateTime('1997/3/9 1:45pm GMT+8') self.assertEqual(dt1 - dt, 1.0, (dt, dt1)) def testCompareMethods(self): # Compare two dates using several methods dt = DateTime('1997/1/1') dt1 = DateTime('1997/2/2') self.failUnless(dt1.greaterThan(dt)) self.failUnless(dt1.greaterThanEqualTo(dt)) self.failUnless(dt.lessThan(dt1)) self.failUnless(dt.lessThanEqualTo(dt1)) self.failUnless(dt.notEqualTo(dt1)) self.failUnless(not dt.equalTo(dt1)) def testCompareOperations(self, dt=None, dt1=None): # Compare two dates using several operations if dt is None: dt = DateTime('1997/1/1') if dt1 is None: dt1 = DateTime('1997/2/2') self.failUnless(dt1 > dt) self.failUnless(dt1 >= dt) self.failUnless(dt < dt1) self.failUnless(dt <= dt1) self.failUnless(dt != dt1) self.failUnless(not (dt == dt1)) def test_compare_old_instances(self): # Compare dates that don't have the _micros attribute yet # (e.g., from old pickles). dt = DateTime('1997/1/1') dt1 = DateTime('1997/2/2') dt._millis = dt._micros / 1000 del dt._micros dt1._millis = dt1._micros / 1000 del dt1._micros self.testCompareOperations(dt, dt1) def test_compare_old_new_instances(self): # Compare a date without _micros attribute (e.g., from an old # pickle) with one that does. dt = DateTime('1997/1/1') dt1 = DateTime('1997/2/2') dt._millis = dt._micros / 1000 del dt._micros self.testCompareOperations(dt, dt1) def test_compare_new_old_instances(self): # Compare a date with _micros attribute with one that does not # (e.g., from an old pickle). dt = DateTime('1997/1/1') dt1 = DateTime('1997/2/2') dt1._millis = dt._micros / 1000 del dt1._micros self.testCompareOperations(dt, dt1) def test_strftime_old_instance(self): # https://bugs.launchpad.net/zope2/+bug/290254 # Ensure that dates without _micros attribute (e.g., from old # pickles) still render correctly in strftime. ISO = '2001-10-10T00:00:00+02:00' dt = DateTime(ISO) dt._millis = dt._micros / 1000 del dt._micros self.assertEqual(dt.strftime('%Y'), '2001') # Now, create one via pickling / unpickling. from cPickle import dumps, loads self.assertEqual(loads(dumps(dt)).strftime('%Y'), '2001') def test_pickle(self): dt = DateTime() data = cPickle.dumps(dt, 1) new = cPickle.loads(data) self.assertEqual(dt.__dict__, new.__dict__) def test_pickle_with_tz(self): dt = DateTime('2002/5/2 8:00am GMT+8') data = cPickle.dumps(dt, 1) new = cPickle.loads(data) self.assertEqual(dt.__dict__, new.__dict__) def test_pickle_with_micros(self): dt = DateTime('2002/5/2 8:00:14.123 GMT+8') data = cPickle.dumps(dt, 1) new = cPickle.loads(data) self.assertEqual(dt.__dict__, new.__dict__) def test_pickle_new(self): dt = DateTime('2002/5/2 8:00am') data = ('ccopy_reg\n_reconstructor\nq\x01(cDateTime.DateTime\n' 'DateTime\nq\x02c__builtin__\nobject\nq\x03NtRq\x04(GA\xcehj' '\xf0\x00\x00\x00I01\nU\x05GMT+2q\x05tb.') new = cPickle.loads(data) self._compare(dt, new) def test_pickle_new_with_tz(self): dt = DateTime('2002/5/2 8:00am GMT+8') data = ('ccopy_reg\n_reconstructor\nq\x01(cDateTime.DateTime\n' 'DateTime\nq\x02c__builtin__\nobject\nq\x03NtRq\x04(GA\xceh@' '\xc0\x00\x00\x00I00\nU\x05GMT+8q\x05tb.') new = cPickle.loads(data) self._compare(dt, new) def test_pickle_new_with_micros(self): dt = DateTime('2002/5/2 8:00:14.123 GMT+8') data = ('ccopy_reg\n_reconstructor\nq\x01(cDateTime.DateTime\n' 'DateTime\nq\x02c__builtin__\nobject\nq\x03NtRq\x04(GA\xceh@' '\xc7\x0f\xbewI00\nU\x05GMT+8q\x05tb.') new = cPickle.loads(data) self._compare(dt, new) def test_pickle_old(self): dt = DateTime('2002/5/2 8:00am GMT+0') data = ('(cDateTime.DateTime\nDateTime\nq\x01Noq\x02}q\x03(U\x05_amonq' '\x04U\x03Mayq\x05U\x05_adayq\x06U\x03Thuq\x07U\x05_pmonq\x08h' '\x05U\x05_hourq\tK\x08U\x05_fmonq\nh\x05U\x05_pdayq\x0bU\x04T' 'hu.q\x0cU\x05_fdayq\rU\x08Thursdayq\x0eU\x03_pmq\x0fU\x02amq' '\x10U\x02_tq\x11GA\xcehy\x00\x00\x00\x00U\x07_minuteq\x12K\x00U' '\x07_microsq\x13L1020326400000000L\nU\x02_dq\x14G@\xe2\x12j\xaa' '\xaa\xaa\xabU\x07_secondq\x15G\x00\x00\x00\x00\x00\x00\x00\x00U' '\x03_tzq\x16U\x05GMT+0q\x17U\x06_monthq\x18K\x05U' '\x0f_timezone_naiveq\x19I00\nU\x04_dayq\x1aK\x02U\x05_yearq' '\x1bM\xd2\x07U\x08_nearsecq\x1cG\x00\x00\x00\x00\x00\x00\x00' '\x00U\x07_pmhourq\x1dK\x08U\n_dayoffsetq\x1eK\x04U\x04timeq' '\x1fG?\xd5UUUV\x00\x00ub.') new = cPickle.loads(data) self.assertEqual(dt.__dict__, new.__dict__) def test_pickle_old_without_micros(self): dt = DateTime('2002/5/2 8:00am GMT+0') data = ('(cDateTime.DateTime\nDateTime\nq\x01Noq\x02}q\x03(U\x05_amonq' '\x04U\x03Mayq\x05U\x05_adayq\x06U\x03Thuq\x07U\x05_pmonq\x08h' '\x05U\x05_hourq\tK\x08U\x05_fmonq\nh\x05U\x05_pdayq\x0bU' '\x04Thu.q\x0cU\x05_fdayq\rU\x08Thursdayq\x0eU\x03_pmq\x0fU' '\x02amq\x10U\x02_tq\x11GA\xcehy\x00\x00\x00\x00U\x07_minuteq' '\x12K\x00U\x02_dq\x13G@\xe2\x12j\xaa\xaa\xaa\xabU\x07_secondq' '\x14G\x00\x00\x00\x00\x00\x00\x00\x00U\x03_tzq\x15U\x05GMT+0q' '\x16U\x06_monthq\x17K\x05U\x0f_timezone_naiveq\x18I00\nU' '\x04_dayq\x19K\x02U\x05_yearq\x1aM\xd2\x07U\x08_nearsecq' '\x1bG\x00\x00\x00\x00\x00\x00\x00\x00U\x07_pmhourq\x1cK\x08U' '\n_dayoffsetq\x1dK\x04U\x04timeq\x1eG?\xd5UUUV\x00\x00ub.') new = cPickle.loads(data) self.assertEqual(dt.__dict__, new.__dict__) def testTZ2(self): # Time zone manipulation test 2 dt = DateTime() dt1 = dt.toZone('GMT') s = dt.second() s1 = dt1.second() self.assertEqual(s, s1, (dt, dt1, s, s1)) def testTZDiffDaylight(self): # Diff dates across daylight savings dates dt = DateTime('2000/6/8 1:45am US/Eastern') dt1 = DateTime('2000/12/8 12:45am US/Eastern') self.assertEqual(dt1 - dt, 183, (dt, dt1, dt1 - dt)) def testY10KDate(self): # Comparison of a Y10K date and a Y2K date dt = DateTime('10213/09/21') dt1 = DateTime(2000, 1, 1) dsec = (dt.millis() - dt1.millis()) / 1000.0 ddays = math.floor((dsec / 86400.0) + 0.5) self.assertEqual(ddays, 3000000L, ddays) def test_tzoffset(self): # Test time-zone given as an offset # GMT dt = DateTime('Tue, 10 Sep 2001 09:41:03 GMT') self.assertEqual(dt.tzoffset(), 0) # Timezone by name, a timezone that hasn't got daylightsaving. dt = DateTime('Tue, 2 Mar 2001 09:41:03 GMT+3') self.assertEqual(dt.tzoffset(), 10800) # Timezone by name, has daylightsaving but is not in effect. dt = DateTime('Tue, 21 Jan 2001 09:41:03 PST') self.assertEqual(dt.tzoffset(), -28800) # Timezone by name, with daylightsaving in effect dt = DateTime('Tue, 24 Aug 2001 09:41:03 PST') self.assertEqual(dt.tzoffset(), -25200) # A negative numerical timezone dt = DateTime('Tue, 24 Jul 2001 09:41:03 -0400') self.assertEqual(dt.tzoffset(), -14400) # A positive numerical timzone dt = DateTime('Tue, 6 Dec 1966 01:41:03 +0200') self.assertEqual(dt.tzoffset(), 7200) # A negative numerical timezone with minutes. dt = DateTime('Tue, 24 Jul 2001 09:41:03 -0637') self.assertEqual(dt.tzoffset(), -23820) # A positive numerical timezone with minutes. dt = DateTime('Tue, 24 Jul 2001 09:41:03 +0425') self.assertEqual(dt.tzoffset(), 15900) def testISO8601(self): # ISO8601 reference dates ref0 = DateTime('2002/5/2 8:00am GMT') ref1 = DateTime('2002/5/2 8:00am US/Eastern') ref2 = DateTime('2006/11/6 10:30 GMT') ref3 = DateTime('2004/06/14 14:30:15 GMT-3') ref4 = DateTime('2006/01/01 GMT') # Basic tests # Though this is timezone naive and according to specification should # be interpreted in the local timezone, to preserve backwards # compatibility with previously expected behaviour. isoDt = DateTime('2002-05-02T08:00:00') self.assertEqual(ref0, isoDt) isoDt = DateTime('2002-05-02T08:00:00Z') self.assertEqual(ref0, isoDt) isoDt = DateTime('2002-05-02T08:00:00+00:00') self.assertEqual(ref0, isoDt) isoDt = DateTime('2002-05-02T08:00:00-04:00') self.assertEqual(ref1, isoDt) isoDt = DateTime('2002-05-02 08:00:00-04:00') self.assertEqual(ref1, isoDt) # Bug 1386: the colon in the timezone offset is optional isoDt = DateTime('2002-05-02T08:00:00-0400') self.assertEqual(ref1, isoDt) # Bug 2191: date reduced formats isoDt = DateTime('2006-01-01') self.assertEqual(ref4, isoDt) isoDt = DateTime('200601-01') self.assertEqual(ref4, isoDt) isoDt = DateTime('20060101') self.assertEqual(ref4, isoDt) isoDt = DateTime('2006-01') self.assertEqual(ref4, isoDt) isoDt = DateTime('200601') self.assertEqual(ref4, isoDt) isoDt = DateTime('2006') self.assertEqual(ref4, isoDt) # Bug 2191: date/time separators are also optional isoDt = DateTime('20020502T08:00:00') self.assertEqual(ref0, isoDt) isoDt = DateTime('2002-05-02T080000') self.assertEqual(ref0, isoDt) isoDt = DateTime('20020502T080000') self.assertEqual(ref0, isoDt) # Bug 2191: timezones with only one digit for hour isoDt = DateTime('20020502T080000+0') self.assertEqual(ref0, isoDt) isoDt = DateTime('20020502 080000-4') self.assertEqual(ref1, isoDt) isoDt = DateTime('20020502T080000-400') self.assertEqual(ref1, isoDt) isoDt = DateTime('20020502T080000-4:00') self.assertEqual(ref1, isoDt) # Bug 2191: optional seconds/minutes isoDt = DateTime('2002-05-02T0800') self.assertEqual(ref0, isoDt) isoDt = DateTime('2002-05-02T08') self.assertEqual(ref0, isoDt) # Bug 2191: week format isoDt = DateTime('2002-W18-4T0800') self.assertEqual(ref0, isoDt) isoDt = DateTime('2002-W184T0800') self.assertEqual(ref0, isoDt) isoDt = DateTime('2002W18-4T0800') self.assertEqual(ref0, isoDt) isoDt = DateTime('2002W184T08') self.assertEqual(ref0, isoDt) isoDt = DateTime('2004-W25-1T14:30:15-03:00') self.assertEqual(ref3, isoDt) isoDt = DateTime('2004-W25T14:30:15-03:00') self.assertEqual(ref3, isoDt) # Bug 2191: day of year format isoDt = DateTime('2002-122T0800') self.assertEqual(ref0, isoDt) isoDt = DateTime('2002122T0800') self.assertEqual(ref0, isoDt) # Bug 2191: hours/minutes fractions isoDt = DateTime('2006-11-06T10.5') self.assertEqual(ref2, isoDt) isoDt = DateTime('2006-11-06T10,5') self.assertEqual(ref2, isoDt) isoDt = DateTime('20040614T1430.25-3') self.assertEqual(ref3, isoDt) isoDt = DateTime('2004-06-14T1430,25-3') self.assertEqual(ref3, isoDt) isoDt = DateTime('2004-06-14T14:30.25-3') self.assertEqual(ref3, isoDt) isoDt = DateTime('20040614T14:30,25-3') self.assertEqual(ref3, isoDt) # ISO8601 standard format iso8601_string = '2002-05-02T08:00:00-04:00' iso8601DT = DateTime(iso8601_string) self.assertEqual(iso8601_string, iso8601DT.ISO8601()) # ISO format with no timezone isoDt = DateTime('2006-01-01 00:00:00') self.assertEqual(ref4, isoDt) def testJulianWeek(self): # Check JulianDayWeek function fn = os.path.join(DATADIR, 'julian_testdata.txt') with open(fn, 'r') as fd: lines = fd.readlines() for line in lines: d = DateTime(line[:10]) result_from_mx = tuple(map(int, line[12:-2].split(','))) self.assertEqual(result_from_mx[1], d.week()) def testCopyConstructor(self): d = DateTime('2004/04/04') self.assertEqual(DateTime(d), d) self.assertEqual(str(DateTime(d)), str(d)) d2 = DateTime('1999/04/12 01:00:00') self.assertEqual(DateTime(d2), d2) self.assertEqual(str(DateTime(d2)), str(d2)) def testCopyConstructorPreservesTimezone(self): # test for https://bugs.launchpad.net/zope2/+bug/200007 # This always worked in the local timezone, so we need at least # two tests with different zones to be sure at least one of them # is not local. d = DateTime('2004/04/04') self.assertEqual(DateTime(d).timezone(), d.timezone()) d2 = DateTime('2008/04/25 12:00:00 EST') self.assertEqual(DateTime(d2).timezone(), d2.timezone()) self.assertEqual(str(DateTime(d2)), str(d2)) d3 = DateTime('2008/04/25 12:00:00 PST') self.assertEqual(DateTime(d3).timezone(), d3.timezone()) self.assertEqual(str(DateTime(d3)), str(d3)) def testRFC822(self): # rfc822 conversion dt = DateTime('2002-05-02T08:00:00+00:00') self.assertEqual(dt.rfc822(), 'Thu, 02 May 2002 08:00:00 +0000') dt = DateTime('2002-05-02T08:00:00+02:00') self.assertEqual(dt.rfc822(), 'Thu, 02 May 2002 08:00:00 +0200') dt = DateTime('2002-05-02T08:00:00-02:00') self.assertEqual(dt.rfc822(), 'Thu, 02 May 2002 08:00:00 -0200') # Checking that conversion from local time is working. dt = DateTime() dts = dt.rfc822().split(' ') times = dts[4].split(':') _isDST = time.localtime(time.time())[8] if _isDST: offset = time.altzone else: offset = time.timezone self.assertEqual(dts[0], dt.aDay() + ',') self.assertEqual(int(dts[1]), dt.day()) self.assertEqual(dts[2], dt.aMonth()) self.assertEqual(int(dts[3]), dt.year()) self.assertEqual(int(times[0]), dt.h_24()) self.assertEqual(int(times[1]), dt.minute()) self.assertEqual(int(times[2]), int(dt.second())) self.assertEqual(dts[5], "%+03d%02d" % divmod((-offset / 60), 60)) def testInternationalDateformat(self): for year in (1990, 2001, 2020): for month in (1, 12): for day in (1, 12, 28, 31): try: d_us = DateTime("%d/%d/%d" % (year, month, day)) except Exception: continue d_int = DateTime("%d.%d.%d" % (day, month, year), datefmt="international") self.assertEqual(d_us, d_int) d_int = DateTime("%d/%d/%d" % (day, month, year), datefmt="international") self.assertEqual(d_us, d_int) def test_calcTimezoneName(self): timezone_dependent_epoch = 2177452800L try: DateTime()._calcTimezoneName(timezone_dependent_epoch, 0) except DateTime.TimeError: self.fail('Zope Collector issue #484 (negative time bug): ' 'TimeError raised') def testStrftimeTZhandling(self): # strftime timezone testing # This is a test for collector issue #1127 format = '%Y-%m-%d %H:%M %Z' dt = DateTime('Wed, 19 Nov 2003 18:32:07 -0215') dt_string = dt.strftime(format) dt_local = dt.toZone(_findLocalTimeZoneName(0)) dt_localstring = dt_local.strftime(format) self.assertEqual(dt_string, dt_localstring) def testStrftimeFarDates(self): # Checks strftime in dates <= 1900 or >= 2038 dt = DateTime('1900/01/30') self.assertEqual(dt.strftime('%d/%m/%Y'), '30/01/1900') dt = DateTime('2040/01/30') self.assertEqual(dt.strftime('%d/%m/%Y'), '30/01/2040') def testZoneInFarDates(self): # Checks time zone in dates <= 1900 or >= 2038 dt1 = DateTime('2040/01/30 14:33 GMT+1') dt2 = DateTime('2040/01/30 11:33 GMT-2') self.assertEqual(dt1.strftime('%d/%m/%Y %H:%M'), dt2.strftime('%d/%m/%Y %H:%M')) def testStrftimeUnicode(self): dt = DateTime('2002-05-02T08:00:00+00:00') ok = dt.strftime('Le %d/%m/%Y a %Hh%M').replace('a', u'\xe0') self.assertEqual(dt.strftime(u'Le %d/%m/%Y \xe0 %Hh%M'), ok) def testTimezoneNaiveHandling(self): # checks that we assign timezone naivity correctly dt = DateTime('2007-10-04T08:00:00+00:00') assert dt.timezoneNaive() is False, 'error with naivity handling in __parse_iso8601' dt = DateTime('2007-10-04T08:00:00Z') assert dt.timezoneNaive() is False, 'error with naivity handling in __parse_iso8601' dt = DateTime('2007-10-04T08:00:00') assert dt.timezoneNaive() is True, 'error with naivity handling in __parse_iso8601' dt = DateTime('2007/10/04 15:12:33.487618 GMT+1') assert dt.timezoneNaive() is False, 'error with naivity handling in _parse' dt = DateTime('2007/10/04 15:12:33.487618') assert dt.timezoneNaive() is True, 'error with naivity handling in _parse' dt = DateTime() assert dt.timezoneNaive() is False, 'error with naivity for current time' s = '2007-10-04T08:00:00' dt = DateTime(s) self.assertEqual(s, dt.ISO8601()) s = '2007-10-04T08:00:00+00:00' dt = DateTime(s) self.assertEqual(s, dt.ISO8601()) def testConversions(self): sdt0 = datetime.now() # this is a timezone naive datetime dt0 = DateTime(sdt0) assert dt0.timezoneNaive() is True, (sdt0, dt0) sdt1 = datetime(2007, 10, 4, 18, 14, 42, 580, pytz.utc) dt1 = DateTime(sdt1) assert dt1.timezoneNaive() is False, (sdt1, dt1) # convert back sdt2 = dt0.asdatetime() self.assertEqual(sdt0, sdt2) sdt3 = dt1.utcdatetime() # this returns a timezone naive datetime self.assertEqual(sdt1.hour, sdt3.hour) dt4 = DateTime('2007-10-04T10:00:00+05:00') sdt4 = datetime(2007, 10, 4, 5, 0) self.assertEqual(dt4.utcdatetime(), sdt4) self.assertEqual(dt4.asdatetime(), sdt4.replace(tzinfo=pytz.utc)) dt5 = DateTime('2007-10-23 10:00:00 US/Eastern') tz = pytz.timezone('US/Eastern') sdt5 = datetime(2007, 10, 23, 10, 0, tzinfo=tz) dt6 = DateTime(sdt5) self.assertEqual(dt5.asdatetime(), sdt5) self.assertEqual(dt6.asdatetime(), sdt5) self.assertEqual(dt5, dt6) self.assertEqual(dt5.asdatetime().tzinfo, tz) self.assertEqual(dt6.asdatetime().tzinfo, tz) def testLegacyTimezones(self): cache = _cache() # The year is important here as timezones change over time t1 = time.mktime(datetime(2002, 1, 1).timetuple()) t2 = time.mktime(datetime(2002, 7, 1).timetuple()) for name in legacy._zlst + legacy._zmap.keys() + legacy._data.keys(): self.failUnless(name.lower() in cache._zidx, 'legacy timezone %s cannot be looked up' % name) failures = [] for name, zone in legacy.timezones.iteritems(): newzone = cache[name] # The name of the new zone might change (eg GMT+6 rather than GMT+0600) if zone.info(t1)[:2] != newzone.info(t1)[:2] or zone.info(t2)[:2] != newzone.info(t2)[:2]: failures.append(name) expected_failures = [ # zone.info(t1) newzone.info(t1) zone.info(t2) newzone.info(t2) 'Jamaica', # (-18000, 0, 'EST') (-18000, 0, 'EST') (-14400, 1, 'EDT') (-18000, 0, 'EST') 'Turkey', # (10800, 0, 'EET') (7200, 0, 'EET') (14400, 1, 'EET DST') (10800, 1, 'EEST') 'Mexico/BajaSur', # (-25200, 0, 'MST') (-25200, 0, 'MST') (-25200, 0, 'MST') (-21600, 1, 'MDT') 'Mexico/General', # (-21600, 0, 'CST') (-21600, 0, 'CST') (-21600, 0, 'CST') (-18000, 1, 'CDT') 'Canada/Yukon', # (-32400, 0, 'YST') (-28800, 0, 'PST') (-28800, 1, 'YDT') (-25200, 1, 'PDT') 'Brazil/West', # (-10800, 1, 'WDT') (-14400, 0, 'AMT') (-14400, 0, 'WST') (-14400, 0, 'AMT') 'Brazil/Acre', # (-14400, 1, 'ADT') (-18000, 0, 'ACT') (-18000, 0, 'AST') (-18000, 0, 'ACT') ] real_failures = list(set(failures).difference(set(expected_failures))) self.failIf(real_failures, '\n'.join(real_failures)) def testBasicTZ(self): #psycopg2 supplies it's own tzinfo instances, with no `zone` attribute tz = FixedOffset(60, 'GMT+1') dt1 = datetime(2008, 8, 5, 12, 0, tzinfo=tz) DT = DateTime(dt1) dt2 = DT.asdatetime() offset1 = dt1.tzinfo.utcoffset(dt1) offset2 = dt2.tzinfo.utcoffset(dt2) self.assertEqual(offset1, offset2) def testEDTTimezone(self): #Should be able to parse EDT timezones: see lp:599856. dt = DateTime("Mon, 28 Jun 2010 10:12:25 EDT") self.assertEqual(dt.Day(), 'Monday') self.assertEqual(dt.day(), 28) self.assertEqual(dt.Month(), 'June') self.assertEqual(dt.timezone(), 'GMT-4') def test_suite(): import doctest return unittest.TestSuite([ unittest.makeSuite(DateTimeTests), doctest.DocFileSuite('DateTime.txt', package='DateTime'), doctest.DocFileSuite('pytz.txt', package='DateTime'), ]) if __name__=="__main__": unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/DateTime/src/DateTime/tests/__init__.py0000644000175000017500000000125312214017435022671 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This file is needed to make this a package. zope2.13-2.13.21/source/DateTime/src/DateTime/tests/julian_testdata.txt0000644000175000017500000000256512214017435024503 0ustar arnauarnau1970-01-01 (1970, 1, 4) 1970-01-02 (1970, 1, 5) 1970-01-30 (1970, 5, 5) 1970-01-31 (1970, 5, 6) 1970-02-01 (1970, 5, 7) 1970-02-02 (1970, 6, 1) 1970-02-28 (1970, 9, 6) 1970-03-01 (1970, 9, 7) 1970-03-30 (1970, 14, 1) 1970-03-31 (1970, 14, 2) 1970-04-01 (1970, 14, 3) 1970-09-30 (1970, 40, 3) 1970-10-01 (1970, 40, 4) 1970-10-02 (1970, 40, 5) 1970-10-03 (1970, 40, 6) 1970-10-04 (1970, 40, 7) 1970-10-05 (1970, 41, 1) 1971-01-02 (1970, 53, 6) 1971-01-03 (1970, 53, 7) 1971-01-04 (1971, 1, 1) 1971-01-05 (1971, 1, 2) 1971-12-31 (1971, 52, 5) 1972-01-01 (1971, 52, 6) 1972-01-02 (1971, 52, 7) 1972-01-03 (1972, 1, 1) 1972-01-04 (1972, 1, 2) 1972-12-30 (1972, 52, 6) 1972-12-31 (1972, 52, 7) 1973-01-01 (1973, 1, 1) 1973-01-02 (1973, 1, 2) 1973-12-29 (1973, 52, 6) 1973-12-30 (1973, 52, 7) 1973-12-31 (1974, 1, 1) 1974-01-01 (1974, 1, 2) 1998-12-30 (1998, 53, 3) 1998-12-31 (1998, 53, 4) 1999-01-01 (1998, 53, 5) 1999-01-02 (1998, 53, 6) 1999-01-03 (1998, 53, 7) 1999-01-04 (1999, 1, 1) 1999-01-05 (1999, 1, 2) 1999-12-30 (1999, 52, 4) 1999-12-31 (1999, 52, 5) 2000-01-01 (1999, 52, 6) 2000-01-02 (1999, 52, 7) 2000-01-03 (2000, 1, 1) 2000-01-04 (2000, 1, 2) 2000-01-05 (2000, 1, 3) 2000-01-06 (2000, 1, 4) 2000-01-07 (2000, 1, 5) 2000-01-08 (2000, 1, 6) 2000-01-09 (2000, 1, 7) 2000-01-10 (2000, 2, 1) 2019-12-28 (2019, 52, 6) 2019-12-29 (2019, 52, 7) 2019-12-30 (2020, 1, 1) 2019-12-31 (2020, 1, 2) zope2.13-2.13.21/source/DateTime/src/DateTime/tests/legacy.py0000644000175000017500000002235712214017435022406 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from DateTime.DateTimeZone import _data from time import time class _timezone: def __init__(self,data): self.name,self.timect,self.typect, \ self.ttrans,self.tindex,self.tinfo,self.az=data def default_index(self): if self.timect == 0: return 0 for i in range(self.typect): if self.tinfo[i][1] == 0: return i return 0 def index(self,t=None): t=t or time() if self.timect==0: idx=(0, 0, 0) elif t < self.ttrans[0]: i=self.default_index() idx=(i, ord(self.tindex[0]),i) elif t >= self.ttrans[-1]: if self.timect > 1: idx=(ord(self.tindex[-1]),ord(self.tindex[-1]), ord(self.tindex[-2])) else: idx=(ord(self.tindex[-1]),ord(self.tindex[-1]), self.default_index()) else: for i in range(self.timect-1): if t < self.ttrans[i+1]: if i==0: idx=(ord(self.tindex[0]),ord(self.tindex[1]), self.default_index()) else: idx=(ord(self.tindex[i]),ord(self.tindex[i+1]), ord(self.tindex[i-1])) break return idx def info(self,t=None): idx=self.index(t)[0] zs =self.az[self.tinfo[idx][2]:] return self.tinfo[idx][0],self.tinfo[idx][1],zs[:zs.find('\000')] _zlst = ['Brazil/Acre','Brazil/DeNoronha','Brazil/East', 'Brazil/West','Canada/Atlantic','Canada/Central', 'Canada/Eastern','Canada/East-Saskatchewan', 'Canada/Mountain','Canada/Newfoundland', 'Canada/Pacific','Canada/Yukon', 'Chile/Continental','Chile/EasterIsland','CST','Cuba', 'Egypt','EST','GB-Eire','Greenwich','Hongkong','Iceland', 'Iran','Israel','Jamaica','Japan','Mexico/BajaNorte', 'Mexico/BajaSur','Mexico/General','MST','Poland','PST', 'Singapore','Turkey','Universal','US/Alaska','US/Aleutian', 'US/Arizona','US/Central','US/Eastern','US/East-Indiana', 'US/Hawaii','US/Indiana-Starke','US/Michigan', 'US/Mountain','US/Pacific','US/Samoa','UTC','UCT','GMT', 'GMT+0100','GMT+0200','GMT+0300','GMT+0400','GMT+0500', 'GMT+0600','GMT+0700','GMT+0800','GMT+0900','GMT+1000', 'GMT+1100','GMT+1200','GMT+1300','GMT-0100','GMT-0200', 'GMT-0300','GMT-0400','GMT-0500','GMT-0600','GMT-0700', 'GMT-0800','GMT-0900','GMT-1000','GMT-1100','GMT-1200', 'GMT+1', 'GMT+0130', 'GMT+0230', 'GMT+0330', 'GMT+0430', 'GMT+0530', 'GMT+0630', 'GMT+0730', 'GMT+0830', 'GMT+0930', 'GMT+1030', 'GMT+1130', 'GMT+1230', 'GMT-0130', 'GMT-0230', 'GMT-0330', 'GMT-0430', 'GMT-0530', 'GMT-0630', 'GMT-0730', 'GMT-0830', 'GMT-0930', 'GMT-1030', 'GMT-1130', 'GMT-1230', 'UT','BST','MEST','SST','FST','WADT','EADT','NZDT', 'WET','WAT','AT','AST','NT','IDLW','CET','MET', 'MEWT','SWT','FWT','EET','EEST','BT','ZP4','ZP5','ZP6', 'WAST','CCT','JST','EAST','GST','NZT','NZST','IDLE'] _zmap = {'aest':'GMT+1000', 'aedt':'GMT+1100', 'aus eastern standard time':'GMT+1000', 'sydney standard time':'GMT+1000', 'tasmania standard time':'GMT+1000', 'e. australia standard time':'GMT+1000', 'aus central standard time':'GMT+0930', 'cen. australia standard time':'GMT+0930', 'w. australia standard time':'GMT+0800', 'brazil/acre':'Brazil/Acre', 'brazil/denoronha':'Brazil/DeNoronha', 'brazil/east':'Brazil/East','brazil/west':'Brazil/West', 'canada/atlantic':'Canada/Atlantic', 'canada/central':'Canada/Central', 'canada/eastern':'Canada/Eastern', 'canada/east-saskatchewan':'Canada/East-Saskatchewan', 'canada/mountain':'Canada/Mountain', 'canada/newfoundland':'Canada/Newfoundland', 'canada/pacific':'Canada/Pacific','canada/yukon':'Canada/Yukon', 'central europe standard time':'GMT+0100', 'chile/continental':'Chile/Continental', 'chile/easterisland':'Chile/EasterIsland', 'cst':'US/Central','cuba':'Cuba','est':'US/Eastern','egypt':'Egypt', 'eastern standard time':'US/Eastern', 'us eastern standard time':'US/Eastern', 'central standard time':'US/Central', 'mountain standard time':'US/Mountain', 'pacific standard time':'US/Pacific', 'gb-eire':'GB-Eire','gmt':'GMT', 'gmt+0000':'GMT+0', 'gmt+0':'GMT+0', 'gmt+0100':'GMT+1', 'gmt+0200':'GMT+2', 'gmt+0300':'GMT+3', 'gmt+0400':'GMT+4', 'gmt+0500':'GMT+5', 'gmt+0600':'GMT+6', 'gmt+0700':'GMT+7', 'gmt+0800':'GMT+8', 'gmt+0900':'GMT+9', 'gmt+1000':'GMT+10','gmt+1100':'GMT+11','gmt+1200':'GMT+12', 'gmt+1300':'GMT+13', 'gmt-0100':'GMT-1', 'gmt-0200':'GMT-2', 'gmt-0300':'GMT-3', 'gmt-0400':'GMT-4', 'gmt-0500':'GMT-5', 'gmt-0600':'GMT-6', 'gmt-0700':'GMT-7', 'gmt-0800':'GMT-8', 'gmt-0900':'GMT-9', 'gmt-1000':'GMT-10','gmt-1100':'GMT-11','gmt-1200':'GMT-12', 'gmt+1': 'GMT+1', 'gmt+2': 'GMT+2', 'gmt+3': 'GMT+3', 'gmt+4': 'GMT+4', 'gmt+5': 'GMT+5', 'gmt+6': 'GMT+6', 'gmt+7': 'GMT+7', 'gmt+8': 'GMT+8', 'gmt+9': 'GMT+9', 'gmt+10':'GMT+10','gmt+11':'GMT+11','gmt+12':'GMT+12', 'gmt+13':'GMT+13', 'gmt-1': 'GMT-1', 'gmt-2': 'GMT-2', 'gmt-3': 'GMT-3', 'gmt-4': 'GMT-4', 'gmt-5': 'GMT-5', 'gmt-6': 'GMT-6', 'gmt-7': 'GMT-7', 'gmt-8': 'GMT-8', 'gmt-9': 'GMT-9', 'gmt-10':'GMT-10','gmt-11':'GMT-11','gmt-12':'GMT-12', 'gmt+130':'GMT+0130', 'gmt+0130':'GMT+0130', 'gmt+230':'GMT+0230', 'gmt+0230':'GMT+0230', 'gmt+330':'GMT+0330', 'gmt+0330':'GMT+0330', 'gmt+430':'GMT+0430', 'gmt+0430':'GMT+0430', 'gmt+530':'GMT+0530', 'gmt+0530':'GMT+0530', 'gmt+630':'GMT+0630', 'gmt+0630':'GMT+0630', 'gmt+730':'GMT+0730', 'gmt+0730':'GMT+0730', 'gmt+830':'GMT+0830', 'gmt+0830':'GMT+0830', 'gmt+930':'GMT+0930', 'gmt+0930':'GMT+0930', 'gmt+1030':'GMT+1030', 'gmt+1130':'GMT+1130', 'gmt+1230':'GMT+1230', 'gmt-130':'GMT-0130', 'gmt-0130':'GMT-0130', 'gmt-230':'GMT-0230', 'gmt-0230':'GMT-0230', 'gmt-330':'GMT-0330', 'gmt-0330':'GMT-0330', 'gmt-430':'GMT-0430', 'gmt-0430':'GMT-0430', 'gmt-530':'GMT-0530', 'gmt-0530':'GMT-0530', 'gmt-630':'GMT-0630', 'gmt-0630':'GMT-0630', 'gmt-730':'GMT-0730', 'gmt-0730':'GMT-0730', 'gmt-830':'GMT-0830', 'gmt-0830':'GMT-0830', 'gmt-930':'GMT-0930', 'gmt-0930':'GMT-0930', 'gmt-1030':'GMT-1030', 'gmt-1130':'GMT-1130', 'gmt-1230':'GMT-1230', 'greenwich':'Greenwich','hongkong':'Hongkong', 'iceland':'Iceland','iran':'Iran','israel':'Israel', 'jamaica':'Jamaica','japan':'Japan', 'mexico/bajanorte':'Mexico/BajaNorte', 'mexico/bajasur':'Mexico/BajaSur','mexico/general':'Mexico/General', 'mst':'US/Mountain','pst':'US/Pacific','poland':'Poland', 'singapore':'Singapore','turkey':'Turkey','universal':'Universal', 'utc':'Universal','uct':'Universal','us/alaska':'US/Alaska', 'us/aleutian':'US/Aleutian','us/arizona':'US/Arizona', 'us/central':'US/Central','us/eastern':'US/Eastern', 'us/east-indiana':'US/East-Indiana','us/hawaii':'US/Hawaii', 'us/indiana-starke':'US/Indiana-Starke','us/michigan':'US/Michigan', 'us/mountain':'US/Mountain','us/pacific':'US/Pacific', 'us/samoa':'US/Samoa', 'ut':'Universal', 'bst':'GMT+1', 'mest':'GMT+2', 'sst':'GMT+2', 'fst':'GMT+2', 'wadt':'GMT+8', 'eadt':'GMT+11', 'nzdt':'GMT+13', 'wet':'GMT', 'wat':'GMT-1', 'at':'GMT-2', 'ast':'GMT-4', 'nt':'GMT-11', 'idlw':'GMT-12', 'cet':'GMT+1', 'cest':'GMT+2', 'met':'GMT+1', 'mewt':'GMT+1', 'swt':'GMT+1', 'fwt':'GMT+1', 'eet':'GMT+2', 'eest':'GMT+3', 'bt':'GMT+3', 'zp4':'GMT+4', 'zp5':'GMT+5', 'zp6':'GMT+6', 'wast':'GMT+7', 'cct':'GMT+8', 'jst':'GMT+9', 'east':'GMT+10', 'gst':'GMT+10', 'nzt':'GMT+12', 'nzst':'GMT+12', 'idle':'GMT+12', 'ret':'GMT+4', 'ist': 'GMT+0530' } timezones = dict((name, _timezone(data)) for name, data in _data.iteritems())zope2.13-2.13.21/source/DateTime/src/DateTime/DateTime.py0000644000175000017500000021211312214017435021463 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Encapsulation of date/time values""" __version__='$Revision: 1.99 $'[11:-2] import copy_reg import re, math, DateTimeZone from time import time, gmtime, localtime from time import daylight, timezone, altzone, strftime from datetime import datetime from interfaces import IDateTime from interfaces import DateTimeError, SyntaxError, DateError, TimeError from zope.interface import implements from pytz_support import PytzCache _cache = PytzCache default_datefmt = None def getDefaultDateFormat(): global default_datefmt if default_datefmt is None: try: from App.config import getConfiguration default_datefmt = getConfiguration().datetime_format return default_datefmt except: return 'us' else: return default_datefmt try: from time import tzname except: tzname=('UNKNOWN','UNKNOWN') # To control rounding errors, we round system time to the nearest # microsecond. Then delicate calculations can rely on that the # maximum precision that needs to be preserved is known. _system_time = time def time(): return round(_system_time(), 6) # Determine machine epoch tm=((0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334), (0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335)) yr,mo,dy,hr,mn,sc=gmtime(0)[:6] i=int(yr-1) to_year =int(i*365+i/4-i/100+i/400-693960.0) to_month=tm[yr%4==0 and (yr%100!=0 or yr%400==0)][mo] EPOCH =(to_year+to_month+dy+(hr/24.0+mn/1440.0+sc/86400.0))*86400 jd1901 =2415385L numericTimeZoneMatch = re.compile(r'[+-][0-9][0-9][0-9][0-9]').match iso8601Match = re.compile(r''' (?P\d\d\d\d) # four digits year (?:-? # one optional dash (?: # followed by: (?P\d\d\d # three digits year day (?!\d)) # when there is no fourth digit | # or: W # one W (?P\d\d) # two digits week (?:-? # one optional dash (?P\d) # one digit week day )? # week day is optional | # or: (?P\d\d)? # two digits month (?:-? # one optional dash (?P\d\d)? # two digits day )? # after day is optional ) # )? # after year is optional (?:[T ] # one T or one whitespace (?P\d\d) # two digits hour (?::? # one optional colon (?P\d\d)? # two digits minute (?::? # one optional colon (?P\d\d)? # two digits second (?:[.,] # one dot or one comma (?P\d+) # n digits fraction )? # after second is optional )? # after minute is optional )? # after hour is optional (?: # timezone: (?PZ) # one Z | # or: (?P[-+]) # one plus or one minus as signal (?P\d # one digit for hour offset... (?:\d(?!\d$) # ...or two, if not the last two digits )?) # second hour offset digit is optional (?::? # one optional colon (?P\d\d) # two digits minute offset )? # after hour offset is optional )? # timezone is optional )? # time is optional (?P.*) # store the extra garbage ''', re.VERBOSE).match def _findLocalTimeZoneName(isDST): if not daylight: # Daylight savings does not occur in this time zone. isDST = 0 try: # Get the name of the current time zone depending # on DST. _localzone = _cache._zmap[tzname[isDST].lower()] except: try: # Generate a GMT-offset zone name. if isDST: localzone = altzone else: localzone = timezone offset=(-localzone/(60*60.0)) majorOffset=int(offset) if majorOffset != 0 : minorOffset=abs(int((offset % majorOffset) * 60.0)) else: minorOffset = 0 m=majorOffset >= 0 and '+' or '' lz='%s%0.02d%0.02d' % (m, majorOffset, minorOffset) _localzone = _cache._zmap[('GMT%s' % lz).lower()] except: _localzone = '' return _localzone # Some utility functions for calculating dates: def _calcSD(t): # Returns timezone-independent days since epoch and the fractional # part of the days. dd = t + EPOCH - 86400.0 d = dd / 86400.0 s = d - math.floor(d) return s, d def _calcDependentSecond(tz, t): # Calculates the timezone-dependent second (integer part only) # from the timezone-independent second. fset = _tzoffset(tz, t) return fset + long(math.floor(t)) + long(EPOCH) - 86400L def _calcDependentSecond2(yr,mo,dy,hr,mn,sc): # Calculates the timezone-dependent second (integer part only) # from the date given. ss = int(hr) * 3600 + int(mn) * 60 + int(sc) x = long(_julianday(yr,mo,dy)-jd1901) * 86400 + ss return x def _calcIndependentSecondEtc(tz, x, ms): # Derive the timezone-independent second from the timezone # dependent second. fsetAtEpoch = _tzoffset(tz, 0.0) nearTime = x - fsetAtEpoch - long(EPOCH) + 86400L + ms # nearTime is now within an hour of being correct. # Recalculate t according to DST. fset = long(_tzoffset(tz, nearTime)) x_adjusted = x - fset + ms d = x_adjusted / 86400.0 t = x_adjusted - long(EPOCH) + 86400L micros = (x + 86400 - fset) * 1000000 + \ long(round(ms * 1000000.0)) - long(EPOCH * 1000000.0) s = d - math.floor(d) return s,d,t,micros def _calcHMS(x, ms): # hours, minutes, seconds from integer and float. hr = x / 3600 x = x - hr * 3600 mn = x / 60 sc = x - mn * 60 + ms return hr,mn,sc def _calcYMDHMS(x, ms): # x is a timezone-dependent integer of seconds. # Produces yr,mo,dy,hr,mn,sc. yr,mo,dy=_calendarday(x / 86400 + jd1901) x = int(x - (x / 86400) * 86400) hr = x / 3600 x = x - hr * 3600 mn = x / 60 sc = x - mn * 60 + ms return yr,mo,dy,hr,mn,sc def _julianday(yr,mo,dy): y,m,d=long(yr),long(mo),long(dy) if m > 12L: y=y+m/12L m=m%12L elif m < 1L: m=-m y=y-m/12L-1L m=12L-m%12L if y > 0L: yr_correct=0L else: yr_correct=3L if m < 3L: y,m=y-1L,m+12L if y*10000L+m*100L+d > 15821014L: b=2L-y/100L+y/400L else: b=0L return (1461L*y-yr_correct)/4L+306001L*(m+1L)/10000L+d+1720994L+b def _calendarday(j): j=long(j) if(j < 2299160L): b=j+1525L else: a=(4L*j-7468861L)/146097L b=j+1526L+a-a/4L c=(20L*b-2442L)/7305L d=1461L*c/4L e=10000L*(b-d)/306001L dy=int(b-d-306001L*e/10000L) mo=(e < 14L) and int(e-1L) or int(e-13L) yr=(mo > 2) and (c-4716L) or (c-4715L) return int(yr),int(mo),int(dy) def _tzoffset(tz, t): """Returns the offset in seconds to GMT from a specific timezone (tz) at a specific time (t). NB! The _tzoffset result is the same same sign as the time zone, i.e. GMT+2 has a 7200 second offset. This is the opposite sign of time.timezone which (confusingly) is -7200 for GMT+2.""" try: return DateTime._tzinfo[tz].info(t)[0] except: if numericTimeZoneMatch(tz) is not None: return int(tz[0:3])*3600+int(tz[0]+tz[3:5])*60 else: return 0 # ?? def _correctYear(year): # Y2K patch. if year >= 0 and year < 100: # 00-69 means 2000-2069, 70-99 means 1970-1999. if year < 70: year = 2000 + year else: year = 1900 + year return year def safegmtime(t): '''gmtime with a safety zone.''' try: t_int = int(t) if isinstance(t_int, long): raise OverflowError # Python 2.3 fix: int can return a long! return gmtime(t_int) except (ValueError, OverflowError): raise TimeError, 'The time %f is beyond the range ' \ 'of this Python implementation.' % float(t) def safelocaltime(t): '''localtime with a safety zone.''' try: t_int = int(t) if isinstance(t_int, long): raise OverflowError # Python 2.3 fix: int can return a long! return localtime(t_int) except (ValueError, OverflowError): raise TimeError, 'The time %f is beyond the range ' \ 'of this Python implementation.' % float(t) def _tzoffset2rfc822zone(seconds): """Takes an offset, such as from _tzoffset(), and returns an rfc822 compliant zone specification. Please note that the result of _tzoffset() is the negative of what time.localzone and time.altzone is.""" return "%+03d%02d" % divmod( (seconds/60), 60) def _tzoffset2iso8601zone(seconds): """Takes an offset, such as from _tzoffset(), and returns an ISO 8601 compliant zone specification. Please note that the result of _tzoffset() is the negative of what time.localzone and time.altzone is.""" return "%+03d:%02d" % divmod( (seconds/60), 60) class DateTime: """DateTime objects represent instants in time and provide interfaces for controlling its representation without affecting the absolute value of the object. DateTime objects may be created from a wide variety of string or numeric data, or may be computed from other DateTime objects. DateTimes support the ability to convert their representations to many major timezones, as well as the ablility to create a DateTime object in the context of a given timezone. DateTime objects provide partial numerical behavior: - Two date-time objects can be subtracted to obtain a time, in days between the two. - A date-time object and a positive or negative number may be added to obtain a new date-time object that is the given number of days later than the input date-time object. - A positive or negative number and a date-time object may be added to obtain a new date-time object that is the given number of days later than the input date-time object. - A positive or negative number may be subtracted from a date-time object to obtain a new date-time object that is the given number of days earlier than the input date-time object. DateTime objects may be converted to integer, long, or float numbers of days since January 1, 1901, using the standard int, long, and float functions (Compatibility Note: int, long and float return the number of days since 1901 in GMT rather than local machine timezone). DateTime objects also provide access to their value in a float format usable with the python time module, provided that the value of the object falls in the range of the epoch-based time module, and as a datetime.datetime object. A DateTime object should be considered immutable; all conversion and numeric operations return a new DateTime object rather than modify the current object.""" implements(IDateTime) # For security machinery: __roles__=None __allow_access_to_unprotected_subobjects__=1 # Make class-specific exceptions available as attributes. DateError = DateError TimeError = TimeError DateTimeError = DateTimeError SyntaxError = SyntaxError def __init__(self,*args, **kw): """Return a new date-time object""" try: return self._parse_args(*args, **kw) except (DateError, TimeError, DateTimeError): raise except: raise SyntaxError('Unable to parse %s, %s' % (args, kw)) def __setstate__(self, state): self.__dict__.clear() # why doesn't Python's unpickler do this? if isinstance(state, tuple): # Add support for parsing the DateTime 3 format self._parse_args(state[0], state[2]) self._timezone_naive = state[1] self._micros = long(state[0] * 1000000) return self.__dict__.update(state) if '_micros' not in state: self._micros = self._upgrade_old() def _parse_args(self, *args, **kw): """Return a new date-time object. A DateTime object always maintains its value as an absolute UTC time, and is represented in the context of some timezone based on the arguments used to create the object. A DateTime object's methods return values based on the timezone context. Note that in all cases the local machine timezone is used for representation if no timezone is specified. DateTimes may be created with from zero to seven arguments. - If the function is called with no arguments or with None, then the current date/time is returned, represented in the timezone of the local machine. - If the function is invoked with a single string argument which is a recognized timezone name, an object representing the current time is returned, represented in the specified timezone. - If the function is invoked with a single string argument representing a valid date/time, an object representing that date/time will be returned. As a general rule, any date-time representation that is recognized and unambigous to a resident of North America is acceptable. The reason for this qualification is that in North America, a date like: 2/1/1994 is interpreted as February 1, 1994, while in some parts of the world, it is interpreted as January 2, 1994. A date/time string consists of two components, a date component and an optional time component, separated by one or more spaces. If the time component is omited, 12:00am is assumed. Any recognized timezone name specified as the final element of the date/time string will be used for computing the date/time value. If you create a DateTime with the string 'Mar 9, 1997 1:45pm US/Pacific', the value will essentially be the same as if you had captured time.time() at the specified date and time on a machine in that timezone:
            e=DateTime('US/Eastern')
            # returns current date/time, represented in US/Eastern.

            x=DateTime('1997/3/9 1:45pm')
            # returns specified time, represented in local machine zone.

            y=DateTime('Mar 9, 1997 13:45:00')
            # y is equal to x
            
The date component consists of year, month, and day values. The year value must be a one-, two-, or four-digit integer. If a one- or two-digit year is used, the year is assumed to be in the twentieth century. The month may be an integer, from 1 to 12, a month name, or a month abreviation, where a period may optionally follow the abreviation. The day must be an integer from 1 to the number of days in the month. The year, month, and day values may be separated by periods, hyphens, forward, shashes, or spaces. Extra spaces are permitted around the delimiters. Year, month, and day values may be given in any order as long as it is possible to distinguish the components. If all three components are numbers that are less than 13, then a a month-day-year ordering is assumed. The time component consists of hour, minute, and second values separated by colons. The hour value must be an integer between 0 and 23 inclusively. The minute value must be an integer between 0 and 59 inclusively. The second value may be an integer value between 0 and 59.999 inclusively. The second value or both the minute and second values may be ommitted. The time may be followed by am or pm in upper or lower case, in which case a 12-hour clock is assumed. New in Zope 2.4: The DateTime constructor automatically detects and handles ISO8601 compliant dates (YYYY-MM-DDThh:ss:mmTZD). New in Zope 2.9.6: The existing ISO8601 parser was extended to support almost the whole ISO8601 specification. New formats includes:
            y=DateTime('1993-045')
            # returns the 45th day from 1993, which is 14th February

            w=DateTime('1993-W06-7')
            # returns the 7th day from the 6th week from 1993, which
            # is also 14th February
            
See http://en.wikipedia.org/wiki/ISO_8601 for full specs. Note that the Zope DateTime parser assumes timezone naive ISO strings to be in UTC rather than local time as specified. - If the DateTime function is invoked with a single Numeric argument, the number is assumed to be a floating point value such as that returned by time.time(). A DateTime object is returned that represents the GMT value of the time.time() float represented in the local machine's timezone. - If the DateTime function is invoked with a single argument that is a DateTime instane, a copy of the passed object will be created. - New in 2.11: The DateTime function may now be invoked with a single argument that is a datetime.datetime instance. DateTimes may be converted back to datetime.datetime objects with asdatetime(). DateTime instances may be converted to a timezone naive datetime.datetime in UTC with utcdatetime(). - If the function is invoked with two numeric arguments, then the first is taken to be an integer year and the second argument is taken to be an offset in days from the beginning of the year, in the context of the local machine timezone. The date-time value returned is the given offset number of days from the beginning of the given year, represented in the timezone of the local machine. The offset may be positive or negative. Two-digit years are assumed to be in the twentieth century. - If the function is invoked with two arguments, the first a float representing a number of seconds past the epoch in gmt (such as those returned by time.time()) and the second a string naming a recognized timezone, a DateTime with a value of that gmt time will be returned, represented in the given timezone.
            import time
            t=time.time()

            now_east=DateTime(t,'US/Eastern')
            # Time t represented as US/Eastern

            now_west=DateTime(t,'US/Pacific')
            # Time t represented as US/Pacific

            # now_east == now_west
            # only their representations are different
            
- If the function is invoked with three or more numeric arguments, then the first is taken to be an integer year, the second is taken to be an integer month, and the third is taken to be an integer day. If the combination of values is not valid, then a DateError is raised. Two-digit years are assumed to be in the twentieth century. The fourth, fifth, and sixth arguments specify a time in hours, minutes, and seconds; hours and minutes should be positive integers and seconds is a positive floating point value, all of these default to zero if not given. An optional string may be given as the final argument to indicate timezone (the effect of this is as if you had taken the value of time.time() at that time on a machine in the specified timezone). New in Zope 2.7: A new keyword parameter "datefmt" can be passed to the constructor. If set to "international", the constructor is forced to treat ambigious dates as "days before month before year". This useful if you need to parse non-US dates in a reliable way In any case that a floating point number of seconds is given or derived, it's rounded to the nearest millisecond. If a string argument passed to the DateTime constructor cannot be parsed, it will raise DateTime.SyntaxError. Invalid date components will raise a DateError, while invalid time or timezone components will raise a DateTimeError. The module function Timezones() will return a list of the (common) timezones recognized by the DateTime module. Recognition of timezone names is case-insensitive. """ datefmt = kw.get('datefmt', getDefaultDateFormat()) d=t=s=None ac=len(args) microsecs = None if ac==10: # Internal format called only by DateTime yr,mo,dy,hr,mn,sc,tz,t,d,s=args elif ac == 11: # Internal format that includes milliseconds (from the epoch) yr,mo,dy,hr,mn,sc,tz,t,d,s,millisecs=args microsecs = millisecs * 1000 elif ac == 12: # Internal format that includes microseconds (from the epoch) and a # flag indicating whether this was constructed in a timezone naive # manner yr,mo,dy,hr,mn,sc,tz,t,d,s,microsecs,tznaive=args if tznaive is not None: # preserve this information self._timezone_naive = tznaive elif not args or (ac and args[0]==None): # Current time, to be displayed in local timezone t = time() lt = safelocaltime(t) tz = self.localZone(lt) ms = (t - math.floor(t)) s,d = _calcSD(t) yr,mo,dy,hr,mn,sc=lt[:6] sc=sc+ms self._timezone_naive = False elif ac==1: arg=args[0] if arg=='': raise SyntaxError, arg if isinstance(arg, DateTime): """Construct a new DateTime instance from a given DateTime instance. """ t = arg.timeTime() s,d = _calcSD(t) yr,mo,dy,hr,mn,sc,tz = arg.parts() elif isinstance(arg, datetime): yr,mo,dy,hr,mn,sc,numerictz,tznaive=self._parse_iso8601_preserving_tznaive(arg.isoformat()) if arg.tzinfo is None: self._timezone_naive = True tz = None else: self._timezone_naive = False # if we have a pytz tzinfo, use the `zone` attribute as a key tz = getattr(arg.tzinfo, 'zone', numerictz) ms = sc - math.floor(sc) x = _calcDependentSecond2(yr,mo,dy,hr,mn,sc) if tz: try: zone = self._tzinfo[tz] except DateTimeError: try: zone = self._tzinfo[numerictz] except DateTimeError: raise DateTimeError, \ 'Unknown time zone in date: %s' % arg tz = zone.tzinfo.zone else: tz = self._calcTimezoneName(x, ms) s,d,t,microsecs = _calcIndependentSecondEtc(tz, x, ms) elif isinstance(arg, (unicode, str)) and arg.lower() in self._tzinfo._zidx: # Current time, to be displayed in specified timezone t,tz=time(),self._tzinfo._zmap[arg.lower()] ms=(t-math.floor(t)) # Use integer arithmetic as much as possible. s,d = _calcSD(t) x = _calcDependentSecond(tz, t) yr,mo,dy,hr,mn,sc = _calcYMDHMS(x, ms) elif isinstance(arg, (unicode, str)): # Date/time string iso8601 = iso8601Match(arg.strip()) fields_iso8601 = iso8601 and iso8601.groupdict() or {} if fields_iso8601 and not fields_iso8601.get('garbage'): yr,mo,dy,hr,mn,sc,tz,tznaive=self._parse_iso8601_preserving_tznaive(arg) self._timezone_naive = tznaive else: yr,mo,dy,hr,mn,sc,tz=self._parse(arg, datefmt) if not self._validDate(yr,mo,dy): raise DateError, 'Invalid date: %s' % arg if not self._validTime(hr,mn,int(sc)): raise TimeError, 'Invalid time: %s' % arg ms = sc - math.floor(sc) x = _calcDependentSecond2(yr,mo,dy,hr,mn,sc) if tz: try: tz=self._tzinfo._zmap[tz.lower()] except KeyError: if numericTimeZoneMatch(tz) is None: raise DateTimeError, \ 'Unknown time zone in date: %s' % arg else: tz = self._calcTimezoneName(x, ms) s,d,t,microsecs = _calcIndependentSecondEtc(tz, x, ms) else: # Seconds from epoch, gmt t = arg lt = safelocaltime(t) tz = self.localZone(lt) ms=(t-math.floor(t)) s,d = _calcSD(t) yr,mo,dy,hr,mn,sc=lt[:6] sc=sc+ms elif ac==2: if isinstance(args[1], str): # Seconds from epoch (gmt) and timezone t,tz=args ms = (t - math.floor(t)) tz=self._tzinfo._zmap[tz.lower()] # Use integer arithmetic as much as possible. s,d = _calcSD(t) x = _calcDependentSecond(tz, t) yr,mo,dy,hr,mn,sc = _calcYMDHMS(x, ms) else: # Year, julian expressed in local zone t = time() lt = safelocaltime(t) tz = self.localZone(lt) yr,jul=args yr = _correctYear(yr) d=(_julianday(yr,1,0)-jd1901)+jul x_float = d * 86400.0 x_floor = math.floor(x_float) ms = x_float - x_floor x = long(x_floor) yr,mo,dy,hr,mn,sc = _calcYMDHMS(x, ms) s,d,t,microsecs = _calcIndependentSecondEtc(tz, x, ms) else: # Explicit format yr,mo,dy=args[:3] hr,mn,sc,tz=0,0,0,0 yr = _correctYear(yr) if not self._validDate(yr,mo,dy): raise DateError, 'Invalid date: %s' % (args,) args=args[3:] if args: hr,args=args[0],args[1:] if args: mn,args=args[0],args[1:] if args: sc,args=args[0],args[1:] if args: tz,args=args[0],args[1:] if args: raise DateTimeError,'Too many arguments' if not self._validTime(hr,mn,sc): raise TimeError, 'Invalid time: %s' % `args` leap = (yr % 4 == 0) and (yr % 100 != 0 or yr % 400 == 0) x = _calcDependentSecond2(yr,mo,dy,hr,mn,sc) ms = sc - math.floor(sc) if tz: try: tz=self._tzinfo._zmap[tz.lower()] except KeyError: if numericTimeZoneMatch(tz) is None: raise DateTimeError, \ 'Unknown time zone: %s' % tz else: # Get local time zone name tz = self._calcTimezoneName(x, ms) s,d,t,microsecs = _calcIndependentSecondEtc(tz, x, ms) if hr>12: self._pmhour=hr-12 self._pm='pm' else: self._pmhour=hr or 12 self._pm= (hr==12) and 'pm' or 'am' self._dayoffset=dx=int((_julianday(yr,mo,dy)+2L)%7) self._fmon,self._amon,self._pmon= \ self._months[mo],self._months_a[mo],self._months_p[mo] self._fday,self._aday,self._pday= \ self._days[dx],self._days_a[dx],self._days_p[dx] # Round to nearest microsecond in platform-independent way. You # cannot rely on C sprintf (Python '%') formatting to round # consistently; doing it ourselves ensures that all but truly # horrid C sprintf implementations will yield the same result # x-platform, provided the format asks for exactly 6 digits after # the decimal point. sc = round(sc, 6) if sc >= 60.0: # can happen if, e.g., orig sc was 59.9999999 sc = 59.999999 self._nearsec=math.floor(sc) self._year,self._month,self._day =yr,mo,dy self._hour,self._minute,self._second =hr,mn,sc self.time,self._d,self._t,self._tz =s,d,t,tz if microsecs is None: microsecs = long(math.floor(t * 1000000.0)) self._micros = microsecs # self._micros is the time since the epoch # in long integer microseconds. int_pattern =re.compile(r'([0-9]+)') #AJ flt_pattern =re.compile(r':([0-9]+\.[0-9]+)') #AJ name_pattern =re.compile(r'([a-zA-Z]+)', re.I) #AJ space_chars =' \t\n' delimiters ='-/.:,+' _month_len =((0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31), (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)) _until_month=((0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334), (0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335)) _months =['','January','February','March','April','May','June','July', 'August', 'September', 'October', 'November', 'December'] _months_a =['','Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] _months_p =['','Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'June', 'July', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.'] _monthmap ={'january': 1, 'jan': 1, 'february': 2, 'feb': 2, 'march': 3, 'mar': 3, 'april': 4, 'apr': 4, 'may': 5, 'june': 6, 'jun': 6, 'july': 7, 'jul': 7, 'august': 8, 'aug': 8, 'september': 9, 'sep': 9, 'sept': 9, 'october': 10, 'oct': 10, 'november': 11, 'nov': 11, 'december': 12, 'dec': 12} _days =['Sunday','Monday','Tuesday','Wednesday', 'Thursday','Friday','Saturday'] _days_a =['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ] _days_p =['Sun.', 'Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.'] _daymap ={'sunday': 1, 'sun': 1, 'monday': 2, 'mon': 2, 'tuesday': 3, 'tues': 3, 'tue': 3, 'wednesday': 4, 'wed': 4, 'thursday': 5, 'thurs': 5, 'thur': 5, 'thu': 5, 'friday': 6, 'fri': 6, 'saturday': 7, 'sat': 7} _localzone0 = _findLocalTimeZoneName(0) _localzone1 = _findLocalTimeZoneName(1) _multipleZones = (_localzone0 != _localzone1) # For backward compatibility only: _isDST = localtime(time())[8] _localzone = _isDST and _localzone1 or _localzone0 _tzinfo = PytzCache() def localZone(self, ltm=None): '''Returns the time zone on the given date. The time zone can change according to daylight savings.''' if not DateTime._multipleZones: return DateTime._localzone0 if ltm == None: ltm = localtime(time()) isDST = ltm[8] lz = isDST and DateTime._localzone1 or DateTime._localzone0 return lz def _calcTimezoneName(self, x, ms): # Derive the name of the local time zone at the given # timezone-dependent second. if not DateTime._multipleZones: return DateTime._localzone0 fsetAtEpoch = _tzoffset(DateTime._localzone0, 0.0) nearTime = x - fsetAtEpoch - long(EPOCH) + 86400L + ms # nearTime is within an hour of being correct. try: ltm = safelocaltime(nearTime) except: # We are beyond the range of Python's date support. # Hopefully we can assume that daylight savings schedules # repeat every 28 years. Calculate the name of the # time zone using a supported range of years. yr,mo,dy,hr,mn,sc = _calcYMDHMS(x, 0) yr = ((yr - 1970) % 28) + 1970 x = _calcDependentSecond2(yr,mo,dy,hr,mn,sc) nearTime = x - fsetAtEpoch - long(EPOCH) + 86400L + ms # nearTime might still be negative if we are east of Greenwich. # But we can asume on 1969/12/31 were no timezone changes. nearTime = max(0, nearTime) ltm = safelocaltime(nearTime) tz = self.localZone(ltm) return tz def _parse(self,st, datefmt=getDefaultDateFormat()): # Parse date-time components from a string month=year=tz=tm=None spaces =self.space_chars intpat =self.int_pattern fltpat =self.flt_pattern wordpat =self.name_pattern delimiters =self.delimiters MonthNumbers =self._monthmap DayOfWeekNames=self._daymap ValidZones =self._tzinfo._zidx TimeModifiers =['am','pm'] # Find timezone first, since it should always be the last # element, and may contain a slash, confusing the parser. st= st.strip() sp=st.split() tz=sp[-1] if tz and (tz.lower() in ValidZones): self._timezone_naive = False st=' '.join(sp[:-1]) else: self._timezone_naive = True tz = None # Decide later, since the default time zone # could depend on the date. ints,dels=[],[] i,l=0,len(st) while i < l: while i < l and st[i] in spaces : i=i+1 if i < l and st[i] in delimiters: d=st[i] i=i+1 else: d='' while i < l and st[i] in spaces : i=i+1 # The float pattern needs to look back 1 character, because it # actually looks for a preceding colon like ':33.33'. This is # needed to avoid accidentally matching the date part of a # dot-separated date string such as '1999.12.31'. if i > 0: b=i-1 else: b=i ts_results = fltpat.match(st, b) if ts_results: s=ts_results.group(1) i=i+len(s) ints.append(float(s)) continue #AJ ts_results = intpat.match(st, i) if ts_results: s=ts_results.group(0) ls=len(s) i=i+ls if (ls==4 and d and d in '+-' and (len(ints) + (not not month) >= 3)): tz='%s%s' % (d,s) else: v=int(s) ints.append(v) continue ts_results = wordpat.match(st, i) if ts_results: o,s=ts_results.group(0),ts_results.group(0).lower() i=i+len(s) if i < l and st[i]=='.': i=i+1 # Check for month name: if MonthNumbers.has_key(s): v=MonthNumbers[s] if month is None: month=v else: raise SyntaxError, st continue # Check for time modifier: if s in TimeModifiers: if tm is None: tm=s else: raise SyntaxError, st continue # Check for and skip day of week: if DayOfWeekNames.has_key(s): continue raise SyntaxError, st day=None if ints[-1] > 60 and d not in ['.',':','/'] and len(ints) > 2: year=ints[-1] del ints[-1] if month: day=ints[0] del ints[:1] else: month=ints[0] day=ints[1] del ints[:2] elif month: if len(ints) > 1: if ints[0] > 31: year=ints[0] day=ints[1] else: year=ints[1] day=ints[0] del ints[:2] elif len(ints) > 2: if ints[0] > 31: year=ints[0] if ints[1] > 12: day=ints[1] month=ints[2] else: day=ints[2] month=ints[1] if ints[1] > 31: year=ints[1] if ints[0] > 12 and ints[2] <= 12: day=ints[0] month=ints[2] elif ints[2] > 12 and ints[0] <= 12: day=ints[2] month=ints[0] elif ints[2] > 31: year=ints[2] if ints[0] > 12: day=ints[0] month=ints[1] else: if datefmt=="us": day=ints[1] month=ints[0] else: day=ints[0] month=ints[1] elif ints[0] <= 12: month=ints[0] day=ints[1] year=ints[2] del ints[:3] if day is None: # Use today's date. year,month,day = localtime(time())[:3] year = _correctYear(year) if year < 1000: raise SyntaxError, st leap = year%4==0 and (year%100!=0 or year%400==0) try: if not day or day > self._month_len[leap][month]: raise DateError, st except IndexError: raise DateError, st tod=0 if ints: i=ints[0] # Modify hour to reflect am/pm if tm and (tm=='pm') and i<12: i=i+12 if tm and (tm=='am') and i==12: i=0 if i > 24: raise TimeError, st tod = tod + int(i) * 3600 del ints[0] if ints: i=ints[0] if i > 60: raise TimeError, st tod = tod + int(i) * 60 del ints[0] if ints: i=ints[0] if i > 60: raise TimeError, st tod = tod + i del ints[0] if ints: raise SyntaxError,st tod_int = int(math.floor(tod)) ms = tod - tod_int hr,mn,sc = _calcHMS(tod_int, ms) if not tz: # Figure out what time zone it is in the local area # on the given date. x = _calcDependentSecond2(year,month,day,hr,mn,sc) tz = self._calcTimezoneName(x, ms) return year,month,day,hr,mn,sc,tz # Internal methods def __getinitargs__(self): return (None,) def _validDate(self,y,m,d): if m<1 or m>12 or y<0 or d<1 or d>31: return 0 return d<=self._month_len[(y%4==0 and (y%100!=0 or y%400==0))][m] def _validTime(self,h,m,s): return h>=0 and h<=23 and m>=0 and m<=59 and s>=0 and s < 60 def __getattr__(self, name): if '%' in name: return strftimeFormatter(self, name) raise AttributeError, name # Conversion and comparison methods def timeTime(self): """Return the date/time as a floating-point number in UTC, in the format used by the python time module. Note that it is possible to create date/time values with DateTime that have no meaningful value to the time module. """ return self._t def toZone(self, z): """Return a DateTime with the value as the current object, represented in the indicated timezone. """ t,tz=self._t,self._tzinfo._zmap[z.lower()] micros = self.micros() tznaive = False # you're performing a timzone change, can't be naive try: # Try to use time module for speed. yr,mo,dy,hr,mn,sc=safegmtime(t+_tzoffset(tz, t))[:6] sc=self._second return self.__class__(yr,mo,dy,hr,mn,sc,tz,t, self._d,self.time,micros,tznaive) except: # gmtime can't perform the calculation in the given range. # Calculate the difference between the two time zones. tzdiff = _tzoffset(tz, t) - _tzoffset(self._tz, t) if tzdiff == 0: return self sc = self._second ms = sc - math.floor(sc) x = _calcDependentSecond2(self._year, self._month, self._day, self._hour, self._minute, sc) x_new = x + tzdiff yr,mo,dy,hr,mn,sc = _calcYMDHMS(x_new, ms) return self.__class__(yr,mo,dy,hr,mn,sc,tz,t, self._d,self.time,micros,tznaive) def isFuture(self): """Return true if this object represents a date/time later than the time of the call. """ return (self._t > time()) def isPast(self): """Return true if this object represents a date/time earlier than the time of the call. """ return (self._t < time()) def isCurrentYear(self): """Return true if this object represents a date/time that falls within the current year, in the context of this object\'s timezone representation. """ t=time() return safegmtime(t+_tzoffset(self._tz, t))[0]==self._year def isCurrentMonth(self): """Return true if this object represents a date/time that falls within the current month, in the context of this object\'s timezone representation. """ t=time() gmt=safegmtime(t+_tzoffset(self._tz, t)) return gmt[0]==self._year and gmt[1]==self._month def isCurrentDay(self): """Return true if this object represents a date/time that falls within the current day, in the context of this object\'s timezone representation. """ t=time() gmt=safegmtime(t+_tzoffset(self._tz, t)) return gmt[0]==self._year and gmt[1]==self._month and gmt[2]==self._day def isCurrentHour(self): """Return true if this object represents a date/time that falls within the current hour, in the context of this object\'s timezone representation. """ t=time() gmt=safegmtime(t+_tzoffset(self._tz, t)) return (gmt[0]==self._year and gmt[1]==self._month and gmt[2]==self._day and gmt[3]==self._hour) def isCurrentMinute(self): """Return true if this object represents a date/time that falls within the current minute, in the context of this object\'s timezone representation. """ t=time() gmt=safegmtime(t+_tzoffset(self._tz, t)) return (gmt[0]==self._year and gmt[1]==self._month and gmt[2]==self._day and gmt[3]==self._hour and gmt[4]==self._minute) def earliestTime(self): """Return a new DateTime object that represents the earliest possible time (in whole seconds) that still falls within the current object\'s day, in the object\'s timezone context. """ return self.__class__(self._year,self._month,self._day,0,0,0,self._tz) def latestTime(self): """Return a new DateTime object that represents the latest possible time (in whole seconds) that still falls within the current object\'s day, in the object\'s timezone context. """ return self.__class__(self._year,self._month,self._day, 23,59,59,self._tz) def greaterThan(self,t): """Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time greater than the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer microseconds. """ # Optimized for sorting speed try: return (self._micros > t._micros) except AttributeError: try: self._micros except AttributeError: self._upgrade_old() return (self._t > t) __gt__ = greaterThan def greaterThanEqualTo(self,t): """Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time greater than or equal to the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer microseconds. """ # Optimized for sorting speed try: return (self._micros >= t._micros) except AttributeError: try: self._micros except AttributeError: self._upgrade_old() return (self._t >= t) __ge__ = greaterThanEqualTo def equalTo(self,t): """Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time equal to the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer microseconds. """ # Optimized for sorting speed try: return (self._micros == t._micros) except AttributeError: try: self._micros except AttributeError: self._upgrade_old() return (self._t == t) __eq__ = equalTo def notEqualTo(self,t): """Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time not equal to the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer microseconds. """ # Optimized for sorting speed try: return (self._micros != t._micros) except AttributeError: try: self._micros except AttributeError: self._upgrade_old() return (self._t != t) __ne__ = notEqualTo def lessThan(self,t): """Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time less than the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer microseconds. """ # Optimized for sorting speed try: return (self._micros < t._micros) except AttributeError: try: self._micros except AttributeError: self._upgrade_old() return (self._t < t) __lt__ = lessThan def lessThanEqualTo(self,t): """Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time less than or equal to the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer microseconds. """ # Optimized for sorting speed try: return (self._micros <= t._micros) except AttributeError: try: self._micros except AttributeError: self._upgrade_old() return (self._t <= t) __le__ = lessThanEqualTo def isLeapYear(self): """Return true if the current year (in the context of the object\'s timezone) is a leap year. """ return self._year%4==0 and (self._year%100!=0 or self._year%400==0) def dayOfYear(self): """Return the day of the year, in context of the timezone representation of the object. """ d=int(self._d+(_tzoffset(self._tz, self._t)/86400.0)) return int((d+jd1901)-_julianday(self._year,1,0)) # Component access def parts(self): """Return a tuple containing the calendar year, month, day, hour, minute second and timezone of the object. """ return self._year, self._month, self._day, self._hour, \ self._minute, self._second, self._tz def timezone(self): """Return the timezone in which the object is represented.""" return self._tz def tzoffset(self): """Return the timezone offset for the objects timezone.""" return _tzoffset(self._tz, self._t) def year(self): """Return the calendar year of the object.""" return self._year def month(self): """Return the month of the object as an integer.""" return self._month def Month(self): """Return the full month name.""" return self._fmon def aMonth(self): """Return the abreviated month name.""" return self._amon def Mon(self): """Compatibility: see aMonth.""" return self._amon def pMonth(self): """Return the abreviated (with period) month name.""" return self._pmon def Mon_(self): """Compatibility: see pMonth.""" return self._pmon def day(self): """Return the integer day.""" return self._day def Day(self): """Return the full name of the day of the week.""" return self._fday def DayOfWeek(self): """Compatibility: see Day.""" return self._fday def aDay(self): """Return the abreviated name of the day of the week.""" return self._aday def pDay(self): """Return the abreviated (with period) name of the day of the week.""" return self._pday def Day_(self): """Compatibility: see pDay.""" return self._pday def dow(self): """Return the integer day of the week, where sunday is 0.""" return self._dayoffset def dow_1(self): """Return the integer day of the week, where sunday is 1.""" return self._dayoffset+1 def h_12(self): """Return the 12-hour clock representation of the hour.""" return self._pmhour def h_24(self): """Return the 24-hour clock representation of the hour.""" return self._hour def ampm(self): """Return the appropriate time modifier (am or pm).""" return self._pm def hour(self): """Return the 24-hour clock representation of the hour.""" return self._hour def minute(self): """Return the minute.""" return self._minute def second(self): """Return the second.""" return self._second def millis(self): """Return the millisecond since the epoch in GMT.""" try: micros = self._micros except AttributeError: micros = self._upgrade_old() return micros / 1000 def micros(self): """Return the microsecond since the epoch in GMT.""" try: return self._micros except AttributeError: return self._upgrade_old() def timezoneNaive(self): """The python datetime module introduces the idea of distinguishing between timezone aware and timezone naive datetime values. For lossless conversion to and from datetime.datetime record if we record this information using True / False. DateTime makes no distinction, when we don't have any information we return None here. """ try: return self._timezone_naive except AttributeError: return None def _upgrade_old(self): """Upgrades a previously pickled DateTime object.""" micros = long(math.floor(self._t * 1000000.0)) #self._micros = micros # don't upgrade instances in place return micros def strftime(self, format): """Format the date/time using the *current timezone representation*.""" x = _calcDependentSecond2(self._year, self._month, self._day, self._hour, self._minute, self._second) ltz = self._calcTimezoneName(x, 0) tzdiff = _tzoffset(ltz, self._t) - _tzoffset(self._tz, self._t) zself = self + tzdiff/86400.0 microseconds = int((zself._second - zself._nearsec) * 1000000) # Note: in older versions strftime() accepted also unicode strings # as format strings (just because time.strftime() did not perform # any type checking). So we convert unicode strings to utf8, # pass them to strftime and convert them back to unicode if necessary. format_is_unicode = False if isinstance(format, unicode): format = format.encode('utf-8') format_is_unicode = True ds = datetime(zself._year, zself._month, zself._day, zself._hour, zself._minute, int(zself._nearsec), microseconds).strftime(format) return format_is_unicode and unicode(ds, 'utf-8') or ds # General formats from previous DateTime def Date(self): """Return the date string for the object.""" return "%s/%2.2d/%2.2d" % (self._year, self._month, self._day) def Time(self): """Return the time string for an object to the nearest second.""" return '%2.2d:%2.2d:%2.2d' % (self._hour,self._minute,self._nearsec) def TimeMinutes(self): """Return the time string for an object not showing seconds.""" return '%2.2d:%2.2d' % (self._hour,self._minute) def AMPM(self): """Return the time string for an object to the nearest second.""" return '%2.2d:%2.2d:%2.2d %s' % ( self._pmhour,self._minute,self._nearsec,self._pm) def AMPMMinutes(self): """Return the time string for an object not showing seconds.""" return '%2.2d:%2.2d %s' % (self._pmhour,self._minute,self._pm) def PreciseTime(self): """Return the time string for the object.""" return '%2.2d:%2.2d:%06.3f' % (self._hour,self._minute,self._second) def PreciseAMPM(self): """Return the time string for the object.""" return '%2.2d:%2.2d:%06.3f %s' % ( self._pmhour,self._minute,self._second,self._pm) def yy(self): """Return calendar year as a 2 digit string.""" return str(self._year)[-2:] def mm(self): """Return month as a 2 digit string.""" return '%02d' % self._month def dd(self): """Return day as a 2 digit string.""" return '%02d' % self._day def rfc822(self): """Return the date in RFC 822 format.""" tzoffset = _tzoffset2rfc822zone(_tzoffset(self._tz, self._t)) return '%s, %2.2d %s %d %2.2d:%2.2d:%2.2d %s' % ( self._aday,self._day,self._amon,self._year, self._hour,self._minute,self._nearsec,tzoffset) # New formats def fCommon(self): """Return a string representing the object\'s value in the format: March 1, 1997 1:45 pm. """ return '%s %s, %4.4d %s:%2.2d %s' % ( self._fmon,self._day,self._year,self._pmhour, self._minute,self._pm) def fCommonZ(self): """Return a string representing the object\'s value in the format: March 1, 1997 1:45 pm US/Eastern. """ return '%s %s, %4.4d %d:%2.2d %s %s' % ( self._fmon,self._day,self._year,self._pmhour, self._minute,self._pm,self._tz) def aCommon(self): """Return a string representing the object\'s value in the format: Mar 1, 1997 1:45 pm. """ return '%s %s, %4.4d %s:%2.2d %s' % ( self._amon,self._day,self._year,self._pmhour, self._minute,self._pm) def aCommonZ(self): """Return a string representing the object\'s value in the format: Mar 1, 1997 1:45 pm US/Eastern. """ return '%s %s, %4.4d %d:%2.2d %s %s' % ( self._amon,self._day,self._year,self._pmhour, self._minute,self._pm,self._tz) def pCommon(self): """Return a string representing the object\'s value in the format: Mar. 1, 1997 1:45 pm. """ return '%s %s, %4.4d %s:%2.2d %s' % ( self._pmon,self._day,self._year,self._pmhour, self._minute,self._pm) def pCommonZ(self): """Return a string representing the object\'s value in the format: Mar. 1, 1997 1:45 pm US/Eastern. """ return '%s %s, %4.4d %d:%2.2d %s %s' % ( self._pmon,self._day,self._year,self._pmhour, self._minute,self._pm,self._tz) def ISO(self): """Return the object in ISO standard format. Note: this is *not* ISO 8601-format! See the ISO8601 and HTML4 methods below for ISO 8601-compliant output. Dates are output as: YYYY-MM-DD HH:MM:SS """ return "%.4d-%.2d-%.2d %.2d:%.2d:%.2d" % ( self._year, self._month, self._day, self._hour, self._minute, self._second) def ISO8601(self): """Return the object in ISO 8601-compatible format containing the date, time with seconds-precision and the time zone identifier. See: http://www.w3.org/TR/NOTE-datetime Dates are output as: YYYY-MM-DDTHH:MM:SSTZD T is a literal character. TZD is Time Zone Designator, format +HH:MM or -HH:MM If the instance is timezone naive (it was not specified with a timezone when it was constructed) then the timezone is ommitted. The HTML4 method below offers the same formatting, but converts to UTC before returning the value and sets the TZD "Z". """ if self.timezoneNaive(): return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d" % ( self._year, self._month, self._day, self._hour, self._minute, self._second) tzoffset = _tzoffset2iso8601zone(_tzoffset(self._tz, self._t)) return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d%s" % ( self._year, self._month, self._day, self._hour, self._minute, self._second, tzoffset) def HTML4(self): """Return the object in the format used in the HTML4.0 specification, one of the standard forms in ISO8601. See: http://www.w3.org/TR/NOTE-datetime Dates are output as: YYYY-MM-DDTHH:MM:SSZ T, Z are literal characters. The time is in UTC. """ newdate = self.toZone('UTC') return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2dZ" % ( newdate._year, newdate._month, newdate._day, newdate._hour, newdate._minute, newdate._second) def asdatetime(self): """Return a standard libary datetime.datetime """ tznaive = self.timezoneNaive() if tznaive: tzinfo = None else: tzinfo = self._tzinfo[self._tz].tzinfo second = int(self._second) microsec = self.micros() % 1000000 dt = datetime(self._year, self._month, self._day, self._hour, self._minute, second, microsec, tzinfo) return dt def utcdatetime(self): """Convert the time to UTC then return a timezone naive datetime object""" utc = self.toZone('UTC') second = int(utc._second) microsec = utc.micros() % 1000000 dt = datetime(utc._year, utc._month, utc._day, utc._hour, utc._minute, second, microsec) return dt def __add__(self,other): """A DateTime may be added to a number and a number may be added to a DateTime; two DateTimes cannot be added. """ if hasattr(other,'_t'): raise DateTimeError,'Cannot add two DateTimes' o=float(other) tz = self._tz #t = (self._t + (o*86400.0)) omicros = round(o*86400000000) tmicros = self.micros() + omicros #d = (self._d + o) t = tmicros / 1000000.0 d = (tmicros + long(EPOCH*1000000)) / 86400000000.0 s = d - math.floor(d) ms = t - math.floor(t) x = _calcDependentSecond(tz, t) yr,mo,dy,hr,mn,sc = _calcYMDHMS(x, ms) return self.__class__(yr,mo,dy,hr,mn,sc,self._tz,t,d,s, None, self.timezoneNaive()) __radd__=__add__ def __sub__(self,other): """Either a DateTime or a number may be subtracted from a DateTime, however, a DateTime may not be subtracted from a number. """ if hasattr(other, '_d'): return (self.micros() - other.micros()) / 86400000000.0 else: return self.__add__(-(other)) def __repr__(self): """Convert a DateTime to a string that looks like a Python expression. """ return '%s(\'%s\')' % (self.__class__.__name__,str(self)) def __str__(self): """Convert a DateTime to a string.""" y,m,d = self._year,self._month,self._day h,mn,s,t = self._hour,self._minute,self._second,self._tz if s == int(s): # A whole number of seconds -- suppress milliseconds. return '%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d %s' % ( y, m, d, h, mn, s, t) else: # s is already rounded to the nearest microsecond, and # it's not a whole number of seconds. Be sure to print # 2 digits before the decimal point. return '%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%06.6f %s' % ( y, m, d, h, mn, s, t) def __cmp__(self,obj): """Compare a DateTime with another DateTime object, or a float such as those returned by time.time(). NOTE: __cmp__ support is provided for backward compatibility only, and mixing DateTimes with ExtensionClasses could cause __cmp__ to break. You should use the methods lessThan, greaterThan, lessThanEqualTo, greaterThanEqualTo, equalTo and notEqualTo to avoid potential problems later! """ # Optimized for sorting speed. try: return cmp(self._micros, obj._micros) except AttributeError: try: self._micros except AttributeError: self._upgrade_old() return cmp(self._t,obj) def __hash__(self): """Compute a hash value for a DateTime.""" return int(((self._year%100*12+self._month)*31+ self._day+self.time)*100) def __int__(self): """Convert to an integer number of seconds since the epoch (gmt).""" return int(self.micros() / 1000000) def __long__(self): """Convert to a long-int number of seconds since the epoch (gmt).""" return long(self.micros() / 1000000) def __float__(self): """Convert to floating-point number of seconds since the epoch (gmt).""" return float(self._t) def _parse_iso8601(self,s): # preserve the previously implied contract # who know where this could be used... return _parse_iso8601_preserving_tznaive(s)[:7] def _parse_iso8601_preserving_tznaive(self,s): try: return self.__parse_iso8601(s) except IndexError: raise SyntaxError, ( 'Not an ISO 8601 compliant date string: "%s"' % s) def __parse_iso8601(self,s): """Parse an ISO 8601 compliant date. See: http://en.wikipedia.org/wiki/ISO_8601 """ month = day = week_day = 1 year = hour = minute = seconds = hour_off = min_off = 0 tznaive = True iso8601 = iso8601Match(s.strip()) fields = iso8601 and iso8601.groupdict() or {} if not iso8601 or fields.get('garbage'): raise IndexError if fields['year']: year = int(fields['year']) if fields['month']: month = int(fields['month']) if fields['day']: day = int(fields['day']) if fields['year_day']: d = DateTime('%s-01-01' % year) + int(fields['year_day']) - 1 month = d.month() day = d.day() if fields['week']: week = int(fields['week']) if fields['week_day']: week_day = int(fields['week_day']) d = DateTime('%s-01-04' % year) d = d - (d.dow()+6) % 7 + week * 7 + week_day - 8 month = d.month() day = d.day() if fields['hour']: hour = int(fields['hour']) if fields['minute']: minute = int(fields['minute']) elif fields['fraction']: minute = 60.0 * float('0.%s' % fields['fraction']) seconds, minute = math.modf(minute) minute = int(minute) seconds = 60.0 * seconds # Avoid reprocess when handling seconds, bellow fields['fraction'] = None if fields['second']: seconds = int(fields['second']) if fields['fraction']: seconds = seconds + float('0.%s' % fields['fraction']) elif fields['fraction']: seconds = 60.0 * float('0.%s' % fields['fraction']) if fields['hour_off']: hour_off = int(fields['hour_off']) if fields['signal'] == '-': hour_off *= -1 if fields['min_off']: min_off = int(fields['min_off']) if fields['signal'] or fields['Z']: tznaive = False else: tznaive = True # Differ from the specification here. To preserve backwards # compatibility assume a default timezone == UTC. tz = 'GMT%+03d%02d' % (hour_off, min_off) return year, month, day, hour, minute, seconds, tz, tznaive def JulianDay(self): """Return the Julian day. See: http://www.tondering.dk/claus/cal/node3.html#sec-calcjd """ a = (14 - self._month)/12 #integer division, discard remainder y = self._year + 4800 - a m = self._month + (12*a) - 3 return self._day + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045 def week(self): """Return the week number according to ISO. See: http://www.tondering.dk/claus/cal/node6.html#SECTION00670000000000000000 """ J = self.JulianDay() d4 = (J + 31741 - (J % 7)) % 146097 % 36524 % 1461 L = d4/1460 d1 = (( d4 - L) % 365) + L return d1/7 + 1 def encode(self, out): """Encode value for XML-RPC.""" out.write('') out.write(self.ISO8601()) out.write('\n') class strftimeFormatter: def __init__(self, dt, format): self._dt=dt self._f=format def __call__(self): return self._dt.strftime(self._f) # Module methods def Timezones(): """Return the list of recognized timezone names""" return sorted(list(PytzCache._zmap.values())) # Patch the copy_reg module, so we can deal with DateTime 3 new-style # classes being recreated as old-style classes orig_reconstructor = copy_reg._reconstructor def _dt_reconstructor(cls, base, state): if cls is DateTime: return cls(state) return orig_reconstructor(cls, base, state) copy_reg._reconstructor = _dt_reconstructor zope2.13-2.13.21/source/DateTime/src/DateTime.egg-info/0000755000175000017500000000000012214017435021107 5ustar arnauarnauzope2.13-2.13.21/source/DateTime/src/DateTime.egg-info/PKG-INFO0000644000175000017500000007676712214017435022233 0ustar arnauarnauMetadata-Version: 1.0 Name: DateTime Version: 2.12.7 Summary: This package provides a DateTime data type, as known from Zope 2. Unless you need to communicate with Zope 2 APIs, you're probably better off using Python's bult-in datetime module. Home-page: http://pypi.python.org/pypi/DateTime Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: The DateTime package ==================== Encapsulation of date/time values. Function Timezones() -------------------- Returns the list of recognized timezone names: >>> from DateTime import Timezones >>> zones = set(Timezones()) Almost all of the standard pytz timezones are included, with the exception of some commonly-used but ambiguous abbreviations, where historical Zope usage conflicts with the name used by pytz: >>> import pytz >>> [x for x in pytz.all_timezones if x not in zones] ['CET', 'EET', 'EST', 'MET', 'MST', 'WET'] Class DateTime -------------- DateTime objects represent instants in time and provide interfaces for controlling its representation without affecting the absolute value of the object. DateTime objects may be created from a wide variety of string or numeric data, or may be computed from other DateTime objects. DateTimes support the ability to convert their representations to many major timezones, as well as the ablility to create a DateTime object in the context of a given timezone. DateTime objects provide partial numerical behavior: * Two date-time objects can be subtracted to obtain a time, in days between the two. * A date-time object and a positive or negative number may be added to obtain a new date-time object that is the given number of days later than the input date-time object. * A positive or negative number and a date-time object may be added to obtain a new date-time object that is the given number of days later than the input date-time object. * A positive or negative number may be subtracted from a date-time object to obtain a new date-time object that is the given number of days earlier than the input date-time object. DateTime objects may be converted to integer, long, or float numbers of days since January 1, 1901, using the standard int, long, and float functions (Compatibility Note: int, long and float return the number of days since 1901 in GMT rather than local machine timezone). DateTime objects also provide access to their value in a float format usable with the python time module, provided that the value of the object falls in the range of the epoch-based time module. A DateTime object should be considered immutable; all conversion and numeric operations return a new DateTime object rather than modify the current object. A DateTime object always maintains its value as an absolute UTC time, and is represented in the context of some timezone based on the arguments used to create the object. A DateTime object's methods return values based on the timezone context. Note that in all cases the local machine timezone is used for representation if no timezone is specified. Constructor for DateTime ------------------------ DateTime() returns a new date-time object. DateTimes may be created with from zero to seven arguments: * If the function is called with no arguments, then the current date/ time is returned, represented in the timezone of the local machine. * If the function is invoked with a single string argument which is a recognized timezone name, an object representing the current time is returned, represented in the specified timezone. * If the function is invoked with a single string argument representing a valid date/time, an object representing that date/ time will be returned. As a general rule, any date-time representation that is recognized and unambigous to a resident of North America is acceptable. (The reason for this qualification is that in North America, a date like: 2/1/1994 is interpreted as February 1, 1994, while in some parts of the world, it is interpreted as January 2, 1994.) A date/ time string consists of two components, a date component and an optional time component, separated by one or more spaces. If the time component is omited, 12:00am is assumed. Any recognized timezone name specified as the final element of the date/time string will be used for computing the date/time value. (If you create a DateTime with the string "Mar 9, 1997 1:45pm US/Pacific", the value will essentially be the same as if you had captured time.time() at the specified date and time on a machine in that timezone). o Returns current date/time, represented in US/Eastern: >>> from DateTime import DateTime >>> e = DateTime('US/Eastern') >>> e.timezone() 'US/Eastern' o Returns specified time, represented in local machine zone: >>> x = DateTime('1997/3/9 1:45pm') >>> x.parts() # doctest: +ELLIPSIS (1997, 3, 9, 13, 45, 0.0, ...) o Specified time in local machine zone, verbose format: >>> y = DateTime('Mar 9, 1997 13:45:00') >>> y.parts() # doctest: +ELLIPSIS (1997, 3, 9, 13, 45, 0.0, ...) >>> y == x True The date component consists of year, month, and day values. The year value must be a one-, two-, or four-digit integer. If a one- or two-digit year is used, the year is assumed to be in the twentieth century. The month may an integer, from 1 to 12, a month name, or a month abreviation, where a period may optionally follow the abreviation. The day must be an integer from 1 to the number of days in the month. The year, month, and day values may be separated by periods, hyphens, forward, shashes, or spaces. Extra spaces are permitted around the delimiters. Year, month, and day values may be given in any order as long as it is possible to distinguish the components. If all three components are numbers that are less than 13, then a a month-day-year ordering is assumed. The time component consists of hour, minute, and second values separated by colons. The hour value must be an integer between 0 and 23 inclusively. The minute value must be an integer between 0 and 59 inclusively. The second value may be an integer value between 0 and 59.999 inclusively. The second value or both the minute and second values may be ommitted. The time may be followed by am or pm in upper or lower case, in which case a 12-hour clock is assumed. * If the DateTime function is invoked with a single Numeric argument, the number is assumed to be either a floating point value such as that returned by time.time() , or a number of days after January 1, 1901 00:00:00 UTC. A DateTime object is returned that represents either the gmt value of the time.time() float represented in the local machine's timezone, or that number of days after January 1, 1901. Note that the number of days after 1901 need to be expressed from the viewpoint of the local machine's timezone. A negative argument will yield a date-time value before 1901. * If the function is invoked with two numeric arguments, then the first is taken to be an integer year and the second argument is taken to be an offset in days from the beginning of the year, in the context of the local machine timezone. The date-time value returned is the given offset number of days from the beginning of the given year, represented in the timezone of the local machine. The offset may be positive or negative. Two-digit years are assumed to be in the twentieth century. * If the function is invoked with two arguments, the first a float representing a number of seconds past the epoch in gmt (such as those returned by time.time()) and the second a string naming a recognized timezone, a DateTime with a value of that gmt time will be returned, represented in the given timezone. >>> import time >>> t = time.time() Time t represented as US/Eastern: >>> now_east = DateTime(t, 'US/Eastern') Time t represented as US/Pacific: >>> now_west = DateTime(t, 'US/Pacific') Only their representations are different: >>> now_east == now_west True * If the function is invoked with three or more numeric arguments, then the first is taken to be an integer year, the second is taken to be an integer month, and the third is taken to be an integer day. If the combination of values is not valid, then a DateTimeError is raised. One- or two-digit years up to 69 are assumed to be in the 21st century, whereas values 70-99 are assumed to be 20th century. The fourth, fifth, and sixth arguments are floating point, positive or negative offsets in units of hours, minutes, and days, and default to zero if not given. An optional string may be given as the final argument to indicate timezone (the effect of this is as if you had taken the value of time.time() at that time on a machine in the specified timezone). If a string argument passed to the DateTime constructor cannot be parsed, it will raise DateTime.SyntaxError. Invalid date, time, or timezone components will raise a DateTime.DateTimeError. The module function Timezones() will return a list of the timezones recognized by the DateTime module. Recognition of timezone names is case-insensitive. Instance Methods for DateTime (IDateTime interface) --------------------------------------------------- Conversion and comparison methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``timeTime()`` returns the date/time as a floating-point number in UTC, in the format used by the python time module. Note that it is possible to create date /time values with DateTime that have no meaningful value to the time module, and in such cases a DateTimeError is raised. A DateTime object's value must generally be between Jan 1, 1970 (or your local machine epoch) and Jan 2038 to produce a valid time.time() style value. >>> dt = DateTime('Mar 9, 1997 13:45:00 US/Eastern') >>> dt.timeTime() 857933100.0 >>> DateTime('2040/01/01 UTC').timeTime() 2208988800.0 >>> DateTime('1900/01/01 UTC').timeTime() -2208988800.0 * ``toZone(z)`` returns a DateTime with the value as the current object, represented in the indicated timezone: >>> dt.toZone('UTC') DateTime('1997/03/09 18:45:00 UTC') >>> dt.toZone('UTC') == dt True * ``isFuture()`` returns true if this object represents a date/time later than the time of the call: >>> dt.isFuture() False >>> DateTime('Jan 1 3000').isFuture() # not time-machine safe! True * ``isPast()`` returns true if this object represents a date/time earlier than the time of the call: >>> dt.isPast() True >>> DateTime('Jan 1 3000').isPast() # not time-machine safe! False * ``isCurrentYear()`` returns true if this object represents a date/time that falls within the current year, in the context of this object's timezone representation: >>> dt.isCurrentYear() False >>> DateTime().isCurrentYear() True * ``isCurrentMonth()`` returns true if this object represents a date/time that falls within the current month, in the context of this object's timezone representation: >>> dt.isCurrentMonth() False >>> DateTime().isCurrentMonth() True * ``isCurrentDay()`` returns true if this object represents a date/time that falls within the current day, in the context of this object's timezone representation: >>> dt.isCurrentDay() False >>> DateTime().isCurrentDay() True * ``isCurrentHour()`` returns true if this object represents a date/time that falls within the current hour, in the context of this object's timezone representation: >>> dt.isCurrentHour() False >>> DateTime().isCurrentHour() True * ``isCurrentMinute()`` returns true if this object represents a date/time that falls within the current minute, in the context of this object's timezone representation: >>> dt.isCurrentMinute() False >>> DateTime().isCurrentMinute() True * ``isLeapYear()`` returns true if the current year (in the context of the object's timezone) is a leap year: >>> dt.isLeapYear() False >>> DateTime('Mar 8 2004').isLeapYear() True * ``earliestTime()`` returns a new DateTime object that represents the earliest possible time (in whole seconds) that still falls within the current object's day, in the object's timezone context: >>> dt.earliestTime() DateTime('1997/03/09 00:00:00 US/Eastern') * ``latestTime()`` return a new DateTime object that represents the latest possible time (in whole seconds) that still falls within the current object's day, in the object's timezone context >>> dt.latestTime() DateTime('1997/03/09 23:59:59 US/Eastern') Component access ~~~~~~~~~~~~~~~~ * ``parts()`` returns a tuple containing the calendar year, month, day, hour, minute second and timezone of the object >>> dt.parts() (1997, 3, 9, 13, 45, 0.0, 'US/Eastern') * ``timezone()`` returns the timezone in which the object is represented: >>> dt.timezone() in Timezones() True * ``tzoffset()`` returns the timezone offset for the objects timezone: >>> dt.tzoffset() -18000 * ``year()`` returns the calendar year of the object: >>> dt.year() 1997 * ``month()`` retursn the month of the object as an integer: >>> dt.month() 3 * ``Month()`` returns the full month name: >>> dt.Month() 'March' * ``aMonth()`` returns the abreviated month name: >>> dt.aMonth() 'Mar' * ``pMonth()`` returns the abreviated (with period) month name: >>> dt.pMonth() 'Mar.' * ``day()`` returns the integer day: >>> dt.day() 9 * ``Day()`` returns the full name of the day of the week: >>> dt.Day() 'Sunday' * ``dayOfYear()`` returns the day of the year, in context of the timezone representation of the object: >>> dt.dayOfYear() 68 * ``aDay()`` returns the abreviated name of the day of the week: >>> dt.aDay() 'Sun' * ``pDay()`` returns the abreviated (with period) name of the day of the week: >>> dt.pDay() 'Sun.' * ``dow()`` returns the integer day of the week, where Sunday is 0: >>> dt.dow() 0 * ``dow_1()`` returns the integer day of the week, where sunday is 1: >>> dt.dow_1() 1 * ``h_12()`` returns the 12-hour clock representation of the hour: >>> dt.h_12() 1 * ``h_24()`` returns the 24-hour clock representation of the hour: >>> dt.h_24() 13 * ``ampm()`` returns the appropriate time modifier (am or pm): >>> dt.ampm() 'pm' * ``hour()`` returns the 24-hour clock representation of the hour: >>> dt.hour() 13 * ``minute()`` returns the minute: >>> dt.minute() 45 * ``second()`` returns the second: >>> dt.second() 0.0 * ``millis()`` returns the milliseconds since the epoch in GMT. >>> dt.millis() 857933100000L strftime() ~~~~~~~~~~ See ``tests/testDateTime.py``. General formats from previous DateTime ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ``Date()`` return the date string for the object: >>> dt.Date() '1997/03/09' * ``Time()`` returns the time string for an object to the nearest second: >>> dt.Time() '13:45:00' * ``TimeMinutes()`` returns the time string for an object not showing seconds: >>> dt.TimeMinutes() '13:45' * ``AMPM()`` returns the time string for an object to the nearest second: >>> dt.AMPM() '01:45:00 pm' * ``AMPMMinutes()`` returns the time string for an object not showing seconds: >>> dt.AMPMMinutes() '01:45 pm' * ``PreciseTime()`` returns the time string for the object: >>> dt.PreciseTime() '13:45:00.000' * ``PreciseAMPM()`` returns the time string for the object: >>> dt.PreciseAMPM() '01:45:00.000 pm' * ``yy()`` returns the calendar year as a 2 digit string >>> dt.yy() '97' * ``mm()`` returns the month as a 2 digit string >>> dt.mm() '03' * ``dd()`` returns the day as a 2 digit string: >>> dt.dd() '09' * ``rfc822()`` returns the date in RFC 822 format: >>> dt.rfc822() 'Sun, 09 Mar 1997 13:45:00 -0500' New formats ~~~~~~~~~~~ * ``fCommon()`` returns a string representing the object's value in the format: March 9, 1997 1:45 pm: >>> dt.fCommon() 'March 9, 1997 1:45 pm' * ``fCommonZ()`` returns a string representing the object's value in the format: March 9, 1997 1:45 pm US/Eastern: >>> dt.fCommonZ() 'March 9, 1997 1:45 pm US/Eastern' * ``aCommon()`` returns a string representing the object's value in the format: Mar 9, 1997 1:45 pm: >>> dt.aCommon() 'Mar 9, 1997 1:45 pm' * ``aCommonZ()`` return a string representing the object's value in the format: Mar 9, 1997 1:45 pm US/Eastern: >>> dt.aCommonZ() 'Mar 9, 1997 1:45 pm US/Eastern' * ``pCommon()`` returns a string representing the object's value in the format Mar. 9, 1997 1:45 pm: >>> dt.pCommon() 'Mar. 9, 1997 1:45 pm' * ``pCommonZ()`` returns a string representing the object's value in the format: Mar. 9, 1997 1:45 pm US/Eastern: >>> dt.pCommonZ() 'Mar. 9, 1997 1:45 pm US/Eastern' * ``ISO()`` returns a string with the date/time in ISO format. Note: this is not ISO 8601-format! See the ISO8601 and HTML4 methods below for ISO 8601-compliant output. Dates are output as: YYYY-MM-DD HH:MM:SS >>> dt.ISO() '1997-03-09 13:45:00' * ``ISO8601()`` returns the object in ISO 8601-compatible format containing the date, time with seconds-precision and the time zone identifier - see http://www.w3.org/TR/NOTE-datetime. Dates are output as: YYYY-MM-DDTHH:MM:SSTZD (T is a literal character, TZD is Time Zone Designator, format +HH:MM or -HH:MM). The ``HTML4()`` method below offers the same formatting, but converts to UTC before returning the value and sets the TZD"Z" >>> dt.ISO8601() '1997-03-09T13:45:00-05:00' * ``HTML4()`` returns the object in the format used in the HTML4.0 specification, one of the standard forms in ISO8601. See http://www.w3.org/TR/NOTE-datetime. Dates are output as: YYYY-MM-DDTHH:MM:SSZ (T, Z are literal characters, the time is in UTC.): >>> dt.HTML4() '1997-03-09T18:45:00Z' * ``JulianDay()`` returns the Julian day according to http://www.tondering.dk/claus/cal/node3.html#sec-calcjd >>> dt.JulianDay() 2450517 * ``week()`` returns the week number according to ISO see http://www.tondering.dk/claus/cal/node6.html#SECTION00670000000000000000 >>> dt.week() 10 Deprecated API ~~~~~~~~~~~~~~ * DayOfWeek(): see Day() * Day_(): see pDay() * Mon(): see aMonth() * Mon_(): see pMonth General Services Provided by DateTime ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DateTimes can be repr()'ed; the result will be a string indicating how to make a DateTime object like this: >>> `dt` "DateTime('1997/03/09 13:45:00 US/Eastern')" When we convert them into a string, we get a nicer string that could actually be shown to a user: >>> str(dt) '1997/03/09 13:45:00 US/Eastern' The hash value of a DateTime is based on the date and time and is equal for different representations of the DateTime: >>> hash(dt) 3618678 >>> hash(dt.toZone('UTC')) 3618678 A DateTime can be compared with another DateTime or float via ``cmp()``. NOTE: __cmp__ support is provided for backward compatibility only, and mixing DateTimes with ExtensionClasses could cause __cmp__ to break. You should use the methods lessThan, greaterThan, lessThanEqualTo, greaterThanEqualTo, equalTo and notEqualTo to avoid potential problems later! >>> cmp(dt, dt) 0 >>> cmp(dt, dt.toZone('UTC')) 0 >>> cmp(dt, dt.timeTime()) 0 >>> cmp(dt, DateTime('2000/01/01')) -1 >>> cmp(dt, DateTime('1900/01/01')) 1 DateTime objects can be compared to other DateTime objects OR floating point numbers such as the ones which are returned by the python time module. On comparison for equality, True is returned if the object represents a date/time equal to the specified DateTime or time module style time: >>> dt == dt True >>> dt == dt.toZone('UTC') True >>> dt == dt.timeTime() True >>> dt == DateTime() False >>> dt.equalTo(dt) True >>> dt.equalTo(dt.toZone('UTC')) True >>> dt.equalTo(dt.timeTime()) True >>> dt.equalTo(DateTime()) False Same goes for inequalities: >>> dt != dt False >>> dt != dt.toZone('UTC') False >>> dt != dt.timeTime() False >>> dt != DateTime() True >>> dt.notEqualTo(dt) False >>> dt.notEqualTo(dt.toZone('UTC')) False >>> dt.notEqualTo(dt.timeTime()) False >>> dt.notEqualTo(DateTime()) True >>> dt > dt False >>> DateTime() > dt True >>> dt > DateTime().timeTime() False >>> DateTime().timeTime() > dt True >>> dt.greaterThan(dt) False >>> DateTime().greaterThan(dt) True >>> dt.greaterThan(DateTime().timeTime()) False >>> dt >= dt True >>> DateTime() >= dt True >>> dt >= DateTime().timeTime() False >>> DateTime().timeTime() >= dt True >>> dt.greaterThanEqualTo(dt) True >>> DateTime().greaterThanEqualTo(dt) True >>> dt.greaterThanEqualTo(DateTime().timeTime()) False >>> dt < dt False >>> DateTime() < dt False >>> dt < DateTime().timeTime() True >>> DateTime().timeTime() < dt False >>> dt.lessThan(dt) False >>> DateTime().lessThan(dt) False >>> dt.lessThan(DateTime().timeTime()) True >>> dt <= dt True >>> DateTime() <= dt False >>> dt <= DateTime().timeTime() True >>> DateTime().timeTime() <= dt False >>> dt.lessThanEqualTo(dt) True >>> DateTime().lessThanEqualTo(dt) False >>> dt.lessThanEqualTo(DateTime().timeTime()) True Numeric Services Provided by DateTime ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A DateTime may be added to a number and a number may be added to a DateTime: >>> dt + 5 DateTime('1997/03/14 13:45:00 US/Eastern') >>> 5 + dt DateTime('1997/03/14 13:45:00 US/Eastern') Two DateTimes cannot be added: >>> dt + dt Traceback (most recent call last): ... DateTimeError: Cannot add two DateTimes Either a DateTime or a number may be subtracted from a DateTime, however, a DateTime may not be subtracted from a number: >>> DateTime('1997/03/10 13:45 US/Eastern') - dt 1.0 >>> dt - 1 DateTime('1997/03/08 13:45:00 US/Eastern') >>> 1 - dt Traceback (most recent call last): ... TypeError: unsupported operand type(s) for -: 'int' and 'instance' DateTimes can also be converted to integers (number of seconds since the epoch), longs (not too long ;)) and floats: >>> int(dt) 857933100 >>> long(dt) 857933100L >>> float(dt) 857933100.0 Changelog ========= 2.12.7 (2012-08-11) ------------------- - Added forward compatibility with DateTime 3 pickle format. DateTime instances constructed under version 3 can be read and unpickled by this version. The pickled data is converted to the current versions format (old-style class / no slots). Once converted it will be stored again in the old format. This should allow for a transparent upgrade/downgrade path between DateTime 2 and 3. 2.12.6 (2010-10-17) ------------------- - Changed ``testDayOfWeek`` test to be independent of OS locale. 2.12.5 (2010-07-29) ------------------- - Launchpad #143269: Corrected the documentation for year value behavior when constructing a DateTime object with three numeric arguments. - Launchpad #142521: Removed confusing special case in DateTime.__str__ where DateTime instances for midnight (e.g. '2010-07-27 00:00:00 US/Eastern') values would render only their date and nothing else. 2.12.4 (2010-07-12) ------------------- - Fixed mapping of EDT (was -> 'GMT-0400', now 'GMT-4'). 2.12.3 (2010-07-09) ------------------- - Added EDT timezone support. Addresses bug #599856. [vangheem] 2.12.2 (2010-05-05) ------------------- - Launchpad #572715: Relaxed pin on pytz, after applying a patch from Marius Gedminus which fixes the apparent API breakage. 2.12.1 (2010-04-30) ------------------- - Removed an undeclared testing dependency on zope.testing.doctest in favor of the standard libraries doctest module. - Added a maximum version requirement on pytz <= 2010b. Later versions produce test failures related to timezone changes. 2.12.0 (2009-03-04) ------------------- - Launchpad #290254: Forward-ported fix for '_micros'-less pickles from the Zope 2.11 branch version. 2.11.2 (2009-02-02) ------------------- - Include *all* pytz zone names, not just "common" ones. - Fix one fragile doctest, band-aid another. - Fix for launchpad #267545: DateTime(DateTime()) should preserve the correct hour. 2.11.1 (2008-08-05) ------------------- - DateTime conversion of datetime objects with non-pytz tzinfo. Timezones() returns a copy of the timezone list (allows tests to run). - Merged the slinkp-datetime-200007 branch: fix the DateTime(anotherDateTime) constructor to preserve timezones. 2.11.0b1 (2008-01-06) --------------------- - Split off from the Zope2 main source code tree. Platform: UNKNOWN zope2.13-2.13.21/source/DateTime/src/DateTime.egg-info/dependency_links.txt0000644000175000017500000000000112214017435025155 0ustar arnauarnau zope2.13-2.13.21/source/DateTime/src/DateTime.egg-info/requires.txt0000644000175000017500000000002312214017435023502 0ustar arnauarnauzope.interface pytzzope2.13-2.13.21/source/DateTime/src/DateTime.egg-info/top_level.txt0000644000175000017500000000001112214017435023631 0ustar arnauarnauDateTime zope2.13-2.13.21/source/DateTime/src/DateTime.egg-info/SOURCES.txt0000644000175000017500000000116212214017435022773 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt MANIFEST.in README.txt bootstrap.py buildout.cfg setup.py src/DateTime/DateTime.py src/DateTime/DateTime.txt src/DateTime/DateTimeZone.py src/DateTime/__init__.py src/DateTime/interfaces.py src/DateTime/pytz.txt src/DateTime/pytz_support.py src/DateTime.egg-info/PKG-INFO src/DateTime.egg-info/SOURCES.txt src/DateTime.egg-info/dependency_links.txt src/DateTime.egg-info/not-zip-safe src/DateTime.egg-info/requires.txt src/DateTime.egg-info/top_level.txt src/DateTime/tests/__init__.py src/DateTime/tests/julian_testdata.txt src/DateTime/tests/legacy.py src/DateTime/tests/testDateTime.pyzope2.13-2.13.21/source/DateTime/src/DateTime.egg-info/not-zip-safe0000644000175000017500000000000112214017435023335 0ustar arnauarnau zope2.13-2.13.21/source/zope.size/0000755000175000017500000000000012214017636015367 5ustar arnauarnauzope2.13-2.13.21/source/zope.size/setup.py0000644000175000017500000000276712214017636017115 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Setup for zope.size package $Id: setup.py 96376 2009-02-10 10:55:21Z nadako $ """ import os from setuptools import setup, find_packages setup(name='zope.size', version = '3.4.1', url='http://pypi.python.org/pypi/zope.size', license='ZPL 2.1', description='Interfaces and simple adapter that give the size of an object', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', long_description=\ open('README.txt').read() + \ '\n\n' + \ open('CHANGES.txt').read(), packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope'], tests_require = [], install_requires=['setuptools', 'zope.interface', 'zope.i18nmessageid'], include_package_data = True, zip_safe = False, ) zope2.13-2.13.21/source/zope.size/PKG-INFO0000644000175000017500000000225112214017636016464 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.size Version: 3.4.1 Summary: Interfaces and simple adapter that give the size of an object Home-page: http://pypi.python.org/pypi/zope.size Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: This package provides a definition of simple interface that allows to retrieve the size of the object for displaying and for sorting. The default adapter is also provided. It expects objects to have the ``getSize`` method that returns size in bytes, however, it won't crash if an object doesn't have one and will show size as ``not available`` instead. ======= CHANGES ======= 3.4.1 (2009-09-10) ------------------ - Added support to bootstrap on Jython. - Added docstrings - Beautify package's README and include CHANGES into the description. - Changed package's url to PyPI instead of Subversion. 3.4.0 (2006-09-29) ------------------ - First release as a separate egg Platform: UNKNOWN zope2.13-2.13.21/source/zope.size/pip-egg-info/0000755000175000017500000000000012214017636017650 5ustar arnauarnauzope2.13-2.13.21/source/zope.size/pip-egg-info/zope.size.egg-info/0000755000175000017500000000000012214017636023270 5ustar arnauarnauzope2.13-2.13.21/source/zope.size/pip-egg-info/zope.size.egg-info/PKG-INFO0000644000175000017500000000225112214017636024365 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.size Version: 3.4.1 Summary: Interfaces and simple adapter that give the size of an object Home-page: http://pypi.python.org/pypi/zope.size Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: This package provides a definition of simple interface that allows to retrieve the size of the object for displaying and for sorting. The default adapter is also provided. It expects objects to have the ``getSize`` method that returns size in bytes, however, it won't crash if an object doesn't have one and will show size as ``not available`` instead. ======= CHANGES ======= 3.4.1 (2009-09-10) ------------------ - Added support to bootstrap on Jython. - Added docstrings - Beautify package's README and include CHANGES into the description. - Changed package's url to PyPI instead of Subversion. 3.4.0 (2006-09-29) ------------------ - First release as a separate egg Platform: UNKNOWN zope2.13-2.13.21/source/zope.size/pip-egg-info/zope.size.egg-info/dependency_links.txt0000644000175000017500000000000112214017636027336 0ustar arnauarnau zope2.13-2.13.21/source/zope.size/pip-egg-info/zope.size.egg-info/requires.txt0000644000175000017500000000005412214017636025667 0ustar arnauarnausetuptools zope.interface zope.i18nmessageidzope2.13-2.13.21/source/zope.size/pip-egg-info/zope.size.egg-info/namespace_packages.txt0000644000175000017500000000000512214017636027616 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.size/pip-egg-info/zope.size.egg-info/top_level.txt0000644000175000017500000000000512214017636026015 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.size/pip-egg-info/zope.size.egg-info/SOURCES.txt0000644000175000017500000000067712214017636025166 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.size.egg-info/PKG-INFO pip-egg-info/zope.size.egg-info/SOURCES.txt pip-egg-info/zope.size.egg-info/dependency_links.txt pip-egg-info/zope.size.egg-info/namespace_packages.txt pip-egg-info/zope.size.egg-info/not-zip-safe pip-egg-info/zope.size.egg-info/requires.txt pip-egg-info/zope.size.egg-info/top_level.txt src/zope/__init__.py src/zope/size/__init__.py src/zope/size/interfaces.py src/zope/size/tests.pyzope2.13-2.13.21/source/zope.size/pip-egg-info/zope.size.egg-info/not-zip-safe0000644000175000017500000000000112214017636025516 0ustar arnauarnau zope2.13-2.13.21/source/zope.size/README.txt0000644000175000017500000000054212214017636017066 0ustar arnauarnauThis package provides a definition of simple interface that allows to retrieve the size of the object for displaying and for sorting. The default adapter is also provided. It expects objects to have the ``getSize`` method that returns size in bytes, however, it won't crash if an object doesn't have one and will show size as ``not available`` instead. zope2.13-2.13.21/source/zope.size/setup.cfg0000644000175000017500000000007312214017636017210 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.size/buildout.cfg0000644000175000017500000000013312214017636017674 0ustar arnauarnau[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = zope.size zope2.13-2.13.21/source/zope.size/bootstrap.py0000644000175000017500000000426212214017636017762 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 90507 2008-08-27 23:46:35Z georgyberdyshev $ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources is_jython = sys.platform.startswith('java') if is_jython: import subprocess cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set if is_jython: assert subprocess.Popen( [sys.executable] + ['-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout'], env = dict(os.environ, PYTHONPATH = ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.size/CHANGES.txt0000644000175000017500000000050512214017636017200 0ustar arnauarnau======= CHANGES ======= 3.4.1 (2009-09-10) ------------------ - Added support to bootstrap on Jython. - Added docstrings - Beautify package's README and include CHANGES into the description. - Changed package's url to PyPI instead of Subversion. 3.4.0 (2006-09-29) ------------------ - First release as a separate egg zope2.13-2.13.21/source/zope.size/src/0000755000175000017500000000000012214017636016156 5ustar arnauarnauzope2.13-2.13.21/source/zope.size/src/zope/0000755000175000017500000000000012214017636017133 5ustar arnauarnauzope2.13-2.13.21/source/zope.size/src/zope/__init__.py0000644000175000017500000000031012214017636021236 0ustar arnauarnau# this is a namespace package try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: import pkgutil __path__ = pkgutil.extend_path(__path__, __name__) zope2.13-2.13.21/source/zope.size/src/zope/size/0000755000175000017500000000000012214017636020105 5ustar arnauarnauzope2.13-2.13.21/source/zope.size/src/zope/size/configure.zcml0000644000175000017500000000032512214017636022755 0ustar arnauarnau zope2.13-2.13.21/source/zope.size/src/zope/size/DEPENDENCIES.cfg0000644000175000017500000000004112214017636022407 0ustar arnauarnauzope.interface zope.i18nmessageidzope2.13-2.13.21/source/zope.size/src/zope/size/interfaces.py0000644000175000017500000000264212214017636022606 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interfaces that give the size of an object. $Id: interfaces.py 89174 2008-08-01 22:34:06Z ccomb $ """ from zope.interface import Interface # basic units: # 'byte' # 'item' for example, number of subobjects for a folder # None for unsized things # 'line' for source-code like things class ISized(Interface): def sizeForSorting(): """Returns a tuple (basic_unit, amount) Used for sorting among different kinds of sized objects. 'amount' need only be sortable among things that share the same basic unit.""" def sizeForDisplay(): """Returns a string giving the size. The output string may be a zope.i18nmessageid.message.Message with an embedded mapping, so it should be translated with zope.i18n.translate() """ zope2.13-2.13.21/source/zope.size/src/zope/size/__init__.py0000644000175000017500000000363612214017636022226 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Adapters that give the size of an object. $Id: __init__.py 89174 2008-08-01 22:34:06Z ccomb $ """ from zope.interface import implements from zope.size.interfaces import ISized from zope.i18nmessageid import MessageFactory _ = MessageFactory('zope') class DefaultSized(object): """ A default ISized adapter """ implements(ISized) def __init__(self, obj): try: size = int(obj.getSize()) except (AttributeError, ValueError, TypeError): self._sortingSize = None, None else: self._sortingSize = 'byte', size def sizeForSorting(self): """See ISized""" return self._sortingSize def sizeForDisplay(self): """See ISized""" units, size = self._sortingSize if units == 'byte': return byteDisplay(size) return _('not-available', 'n/a') def byteDisplay(size): """ Returns a size with the correct unit (KB, MB), given the size in bytes. The output should be given to zope.i18n.translate() """ if size == 0: return _('0 KB') if size <= 1024: return _('1 KB') if size > 1048576: return _('${size} MB', mapping={'size': '%0.02f' % (size / 1048576.0)}) return _('${size} KB', mapping={'size': '%d' % (size / 1024.0)}) zope2.13-2.13.21/source/zope.size/src/zope/size/tests.py0000644000175000017500000000654012214017636021626 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test ISized Adapter $Id: tests.py 70826 2006-10-20 03:41:16Z baijum $ """ import unittest from zope.size.interfaces import ISized class DummyObject(object): def __init__(self, size): self._size = size def getSize(self): return self._size class Test(unittest.TestCase): def testImplementsISized(self): from zope.size import DefaultSized sized = DefaultSized(object()) self.assert_(ISized.providedBy(sized)) def testSizeWithBytes(self): from zope.size import DefaultSized obj = DummyObject(1023) sized = DefaultSized(obj) self.assertEqual(sized.sizeForSorting(), ('byte', 1023)) self.assertEqual(sized.sizeForDisplay(), u'1 KB') def testSizeWithNone(self): from zope.size import DefaultSized obj = DummyObject(None) sized = DefaultSized(obj) self.assertEqual(sized.sizeForSorting(), (None, None)) self.assertEqual(sized.sizeForDisplay(), u'not-available') def testSizeNotAvailable(self): from zope.size import DefaultSized sized = DefaultSized(object()) self.assertEqual(sized.sizeForSorting(), (None, None)) self.assertEqual(sized.sizeForDisplay(), u'not-available') def testVariousSizes(self): from zope.size import DefaultSized sized = DefaultSized(DummyObject(0)) self.assertEqual(sized.sizeForSorting(), ('byte', 0)) self.assertEqual(sized.sizeForDisplay(), u'0 KB') sized = DefaultSized(DummyObject(1)) self.assertEqual(sized.sizeForSorting(), ('byte', 1)) self.assertEqual(sized.sizeForDisplay(), u'1 KB') sized = DefaultSized(DummyObject(2048)) self.assertEqual(sized.sizeForSorting(), ('byte', 2048)) self.assertEqual(sized.sizeForDisplay(), u'${size} KB') self.assertEqual(sized.sizeForDisplay().mapping, {'size': '2'}) sized = DefaultSized(DummyObject(2000000)) self.assertEqual(sized.sizeForSorting(), ('byte', 2000000)) self.assertEqual(sized.sizeForDisplay(), u'${size} MB') self.assertEqual(sized.sizeForDisplay().mapping, {'size': '1.91'}) def test_byteDisplay(self): from zope.size import byteDisplay self.assertEqual(byteDisplay(0), u'0 KB') self.assertEqual(byteDisplay(1), u'1 KB') self.assertEqual(byteDisplay(2048), u'${size} KB') self.assertEqual(byteDisplay(2048).mapping, {'size': '2'}) self.assertEqual(byteDisplay(2000000), u'${size} MB') self.assertEqual(byteDisplay(2000000).mapping, {'size': '1.91'}) def test_suite(): loader = unittest.TestLoader() return loader.loadTestsFromTestCase(Test) if __name__=='__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/zope.size/src/zope.size.egg-info/0000755000175000017500000000000012214017636021576 5ustar arnauarnauzope2.13-2.13.21/source/zope.size/src/zope.size.egg-info/PKG-INFO0000644000175000017500000000225112214017636022673 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.size Version: 3.4.1 Summary: Interfaces and simple adapter that give the size of an object Home-page: http://pypi.python.org/pypi/zope.size Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: This package provides a definition of simple interface that allows to retrieve the size of the object for displaying and for sorting. The default adapter is also provided. It expects objects to have the ``getSize`` method that returns size in bytes, however, it won't crash if an object doesn't have one and will show size as ``not available`` instead. ======= CHANGES ======= 3.4.1 (2009-09-10) ------------------ - Added support to bootstrap on Jython. - Added docstrings - Beautify package's README and include CHANGES into the description. - Changed package's url to PyPI instead of Subversion. 3.4.0 (2006-09-29) ------------------ - First release as a separate egg Platform: UNKNOWN zope2.13-2.13.21/source/zope.size/src/zope.size.egg-info/dependency_links.txt0000644000175000017500000000000112214017636025644 0ustar arnauarnau zope2.13-2.13.21/source/zope.size/src/zope.size.egg-info/requires.txt0000644000175000017500000000005412214017636024175 0ustar arnauarnausetuptools zope.interface zope.i18nmessageidzope2.13-2.13.21/source/zope.size/src/zope.size.egg-info/namespace_packages.txt0000644000175000017500000000000512214017636026124 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.size/src/zope.size.egg-info/top_level.txt0000644000175000017500000000000512214017636024323 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.size/src/zope.size.egg-info/SOURCES.txt0000644000175000017500000000074112214017636023464 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.size.egg-info/PKG-INFO src/zope.size.egg-info/SOURCES.txt src/zope.size.egg-info/dependency_links.txt src/zope.size.egg-info/namespace_packages.txt src/zope.size.egg-info/not-zip-safe src/zope.size.egg-info/requires.txt src/zope.size.egg-info/top_level.txt src/zope/size/DEPENDENCIES.cfg src/zope/size/__init__.py src/zope/size/configure.zcml src/zope/size/interfaces.py src/zope/size/tests.pyzope2.13-2.13.21/source/zope.size/src/zope.size.egg-info/not-zip-safe0000644000175000017500000000000112214017636024024 0ustar arnauarnau zope2.13-2.13.21/source/zope.annotation/0000755000175000017500000000000012214017705016564 5ustar arnauarnauzope2.13-2.13.21/source/zope.annotation/setup.py0000644000175000017500000000413712214017705020303 0ustar arnauarnau############################################################################## # # Copyright (c) 2006-2007 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Setup for zope.annotation package $Id: setup.py 103612 2009-09-07 16:06:08Z nadako $ """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup( name='zope.annotation', version='3.5.0', url='http://pypi.python.org/pypi/zope.annotation', license='ZPL 2.1', description='Object annotation mechanism', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Software Development', ], long_description= \ read('src', 'zope', 'annotation', 'README.txt') + '\n\n' + read('CHANGES.txt'), packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope',], install_requires=['setuptools', 'zope.interface', 'zope.component', 'zope.location', 'zope.proxy', 'ZODB3', ], extras_require=dict( test=['zope.testing'], ), include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/zope.annotation/PKG-INFO0000644000175000017500000002013212214017705017657 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.annotation Version: 3.5.0 Summary: Object annotation mechanism Home-page: http://pypi.python.org/pypi/zope.annotation Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ================== Object Annotations ================== This package provides a mechanism to store additional information about objects without need to modify object class. Annotation factories -------------------- There is more to document about annotations, but we'll just sketch out a scenario on how to use the annotation factory for now. This is one of the easiest ways to use annotations -- basically you can see them as persistent, writable adapters. First, let's make a persistent object we can create annotations for: >>> from zope import interface >>> class IFoo(interface.Interface): ... pass >>> from zope.annotation.interfaces import IAttributeAnnotatable >>> from persistent import Persistent >>> class Foo(Persistent): ... interface.implements(IFoo, IAttributeAnnotatable) We directly say that Foo implements IAttributeAnnotatable here. In practice this is often done in ZCML, using the `implements` subdirective of the `content` or `class` directive. Now let's create an annotation for this: >>> class IBar(interface.Interface): ... a = interface.Attribute('A') ... b = interface.Attribute('B') >>> from zope import component >>> class Bar(Persistent): ... interface.implements(IBar) ... component.adapts(IFoo) ... def __init__(self): ... self.a = 1 ... self.b = 2 Note that the annotation implementation does not expect any arguments to its `__init__`. Otherwise it's basically an adapter. Now, we'll register the annotation as an adapter. To do this we use the `factory` function provided by `zope.annotation`: >>> from zope.annotation import factory >>> component.provideAdapter(factory(Bar)) Note that we do not need to specify what the adapter provides or what it adapts - we already do this on the annotation class itself. Now let's make an instance of `Foo`, and make an annotation for it. >>> foo = Foo() >>> bar = IBar(foo) >>> bar.a 1 >>> bar.b 2 We'll change `a` and get the annotation again. Our change is still there: >>> bar.a = 3 >>> IBar(foo).a 3 Of course it's still different for another instance of `Foo`: >>> foo2 = Foo() >>> IBar(foo2).a 1 What if our annotation does not provide what it adapts with `component.adapts`? It will complain: >>> class IQux(interface.Interface): ... pass >>> class Qux(Persistent): ... interface.implements(IQux) >>> component.provideAdapter(factory(Qux)) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: Missing 'zope.component.adapts' on annotation It's possible to provide an annotation with an explicit key. (If the key is not supplied, the key is deduced from the annotation's dotted name, provided it is a class.) >>> class IHoi(interface.Interface): ... pass >>> class Hoi(Persistent): ... interface.implements(IHoi) ... component.adapts(IFoo) >>> component.provideAdapter(factory(Hoi, 'my.unique.key')) >>> isinstance(IHoi(foo), Hoi) True Location -------- Annotation factories are put into the location hierarchy with their parent pointing to the annotated object and the name to the dotted name of the annotation's class (or the name the adapter was registered under): >>> foo3 = Foo() >>> new_hoi = IHoi(foo3) >>> new_hoi.__parent__ >>> new_hoi.__name__ 'my.unique.key' >>> import zope.location.interfaces >>> zope.location.interfaces.ILocation.providedBy(new_hoi) True Please notice, that our Hoi object does not implement ILocation, so a location proxy will be used. This has to be re-established every time we retrieve the object (Guard against former bug: proxy wasn't established when the annotation existed already.) >>> old_hoi = IHoi(foo3) >>> old_hoi.__parent__ >>> old_hoi.__name__ 'my.unique.key' >>> zope.location.interfaces.ILocation.providedBy(old_hoi) True LocationProxies --------------- Suppose your annotation proxy provides ILocation. >>> class IPolloi(interface.Interface): ... pass >>> class Polloi(Persistent): ... interface.implements(IPolloi, zope.location.interfaces.ILocation) ... component.adapts(IFoo) ... __name__ = __parent__ = 0 >>> component.provideAdapter(factory(Polloi, 'my.other.key')) Sometimes you're adapting an object wrapped in a LocationProxy. >>> foo4 = Foo() >>> import zope.location.location >>> wrapped_foo4 = zope.location.location.LocationProxy(foo4, None, 'foo4') >>> located_polloi = IPolloi(wrapped_foo4) At first glance it looks as if located_polloi is located under wrapped_foo4. >>> located_polloi.__parent__ is wrapped_foo4 True >>> located_polloi.__name__ 'my.other.key' but that's because we received a LocationProxy >>> print type(located_polloi).__name__ LocationProxy If we unwrap located_polloi and look at it directly, we'll see it stores a reference to the real Foo object >>> from zope.proxy import removeAllProxies >>> removeAllProxies(located_polloi).__parent__ is foo4 True >>> removeAllProxies(located_polloi).__name__ 'my.other.key' ======= CHANGES ======= 3.5.0 (2009-09-07) ------------------ - Add ZODB3 to install_requires, because it's a true requirement of this package, not just a testing requirement, as BTrees are in use. - Fix one test that was inactive because it's function was overriden by a mistake. 3.4.2 (2009-03-09) ------------------ - Clean up package description and documentation a bit. - Change mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired. - Remove old zpkg-related files. 3.4.1 (2008-08-26) ------------------ - Annotation factories take care not to store proxies in the database, so adapting an object wrapped in a ``LocationProxy`` works correctly. Fixes https://bugs.launchpad.net/zope3/+bug/261620 3.4.0 (2007-08-29) ------------------ - Annotation factories are no longer containing the factored object. Instead the objects are located using ``zope.location``. This removes a dependency to ``zope.app.container``. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Software Development zope2.13-2.13.21/source/zope.annotation/pip-egg-info/0000755000175000017500000000000012214017705021045 5ustar arnauarnauzope2.13-2.13.21/source/zope.annotation/pip-egg-info/zope.annotation.egg-info/0000755000175000017500000000000012214017705025665 5ustar arnauarnauzope2.13-2.13.21/source/zope.annotation/pip-egg-info/zope.annotation.egg-info/PKG-INFO0000644000175000017500000002042412214017705026764 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.annotation Version: 3.5.0 Summary: Object annotation mechanism Home-page: http://pypi.python.org/pypi/zope.annotation Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ================== Object Annotations ================== This package provides a mechanism to store additional information about objects without need to modify object class. Annotation factories -------------------- There is more to document about annotations, but we'll just sketch out a scenario on how to use the annotation factory for now. This is one of the easiest ways to use annotations -- basically you can see them as persistent, writable adapters. First, let's make a persistent object we can create annotations for: >>> from zope import interface >>> class IFoo(interface.Interface): ... pass >>> from zope.annotation.interfaces import IAttributeAnnotatable >>> from persistent import Persistent >>> class Foo(Persistent): ... interface.implements(IFoo, IAttributeAnnotatable) We directly say that Foo implements IAttributeAnnotatable here. In practice this is often done in ZCML, using the `implements` subdirective of the `content` or `class` directive. Now let's create an annotation for this: >>> class IBar(interface.Interface): ... a = interface.Attribute('A') ... b = interface.Attribute('B') >>> from zope import component >>> class Bar(Persistent): ... interface.implements(IBar) ... component.adapts(IFoo) ... def __init__(self): ... self.a = 1 ... self.b = 2 Note that the annotation implementation does not expect any arguments to its `__init__`. Otherwise it's basically an adapter. Now, we'll register the annotation as an adapter. To do this we use the `factory` function provided by `zope.annotation`: >>> from zope.annotation import factory >>> component.provideAdapter(factory(Bar)) Note that we do not need to specify what the adapter provides or what it adapts - we already do this on the annotation class itself. Now let's make an instance of `Foo`, and make an annotation for it. >>> foo = Foo() >>> bar = IBar(foo) >>> bar.a 1 >>> bar.b 2 We'll change `a` and get the annotation again. Our change is still there: >>> bar.a = 3 >>> IBar(foo).a 3 Of course it's still different for another instance of `Foo`: >>> foo2 = Foo() >>> IBar(foo2).a 1 What if our annotation does not provide what it adapts with `component.adapts`? It will complain: >>> class IQux(interface.Interface): ... pass >>> class Qux(Persistent): ... interface.implements(IQux) >>> component.provideAdapter(factory(Qux)) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: Missing 'zope.component.adapts' on annotation It's possible to provide an annotation with an explicit key. (If the key is not supplied, the key is deduced from the annotation's dotted name, provided it is a class.) >>> class IHoi(interface.Interface): ... pass >>> class Hoi(Persistent): ... interface.implements(IHoi) ... component.adapts(IFoo) >>> component.provideAdapter(factory(Hoi, 'my.unique.key')) >>> isinstance(IHoi(foo), Hoi) True Location -------- Annotation factories are put into the location hierarchy with their parent pointing to the annotated object and the name to the dotted name of the annotation's class (or the name the adapter was registered under): >>> foo3 = Foo() >>> new_hoi = IHoi(foo3) >>> new_hoi.__parent__ >>> new_hoi.__name__ 'my.unique.key' >>> import zope.location.interfaces >>> zope.location.interfaces.ILocation.providedBy(new_hoi) True Please notice, that our Hoi object does not implement ILocation, so a location proxy will be used. This has to be re-established every time we retrieve the object (Guard against former bug: proxy wasn't established when the annotation existed already.) >>> old_hoi = IHoi(foo3) >>> old_hoi.__parent__ >>> old_hoi.__name__ 'my.unique.key' >>> zope.location.interfaces.ILocation.providedBy(old_hoi) True LocationProxies --------------- Suppose your annotation proxy provides ILocation. >>> class IPolloi(interface.Interface): ... pass >>> class Polloi(Persistent): ... interface.implements(IPolloi, zope.location.interfaces.ILocation) ... component.adapts(IFoo) ... __name__ = __parent__ = 0 >>> component.provideAdapter(factory(Polloi, 'my.other.key')) Sometimes you're adapting an object wrapped in a LocationProxy. >>> foo4 = Foo() >>> import zope.location.location >>> wrapped_foo4 = zope.location.location.LocationProxy(foo4, None, 'foo4') >>> located_polloi = IPolloi(wrapped_foo4) At first glance it looks as if located_polloi is located under wrapped_foo4. >>> located_polloi.__parent__ is wrapped_foo4 True >>> located_polloi.__name__ 'my.other.key' but that's because we received a LocationProxy >>> print type(located_polloi).__name__ LocationProxy If we unwrap located_polloi and look at it directly, we'll see it stores a reference to the real Foo object >>> from zope.proxy import removeAllProxies >>> removeAllProxies(located_polloi).__parent__ is foo4 True >>> removeAllProxies(located_polloi).__name__ 'my.other.key' ======= CHANGES ======= 3.5.0 (2009-09-07) ------------------ - Add ZODB3 to install_requires, because it's a true requirement of this package, not just a testing requirement, as BTrees are in use. - Fix one test that was inactive because it's function was overriden by a mistake. 3.4.2 (2009-03-09) ------------------ - Clean up package description and documentation a bit. - Change mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired. - Remove old zpkg-related files. 3.4.1 (2008-08-26) ------------------ - Annotation factories take care not to store proxies in the database, so adapting an object wrapped in a ``LocationProxy`` works correctly. Fixes https://bugs.launchpad.net/zope3/+bug/261620 3.4.0 (2007-08-29) ------------------ - Annotation factories are no longer containing the factored object. Instead the objects are located using ``zope.location``. This removes a dependency to ``zope.app.container``. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Software Development zope2.13-2.13.21/source/zope.annotation/pip-egg-info/zope.annotation.egg-info/dependency_links.txt0000644000175000017500000000000112214017705031733 0ustar arnauarnau zope2.13-2.13.21/source/zope.annotation/pip-egg-info/zope.annotation.egg-info/requires.txt0000644000175000017500000000013412214017705030263 0ustar arnauarnausetuptools zope.interface zope.component zope.location zope.proxy ZODB3 [test] zope.testingzope2.13-2.13.21/source/zope.annotation/pip-egg-info/zope.annotation.egg-info/namespace_packages.txt0000644000175000017500000000000512214017705032213 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.annotation/pip-egg-info/zope.annotation.egg-info/top_level.txt0000644000175000017500000000000512214017705030412 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.annotation/pip-egg-info/zope.annotation.egg-info/SOURCES.txt0000644000175000017500000000124412214017705027552 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.annotation.egg-info/PKG-INFO pip-egg-info/zope.annotation.egg-info/SOURCES.txt pip-egg-info/zope.annotation.egg-info/dependency_links.txt pip-egg-info/zope.annotation.egg-info/namespace_packages.txt pip-egg-info/zope.annotation.egg-info/not-zip-safe pip-egg-info/zope.annotation.egg-info/requires.txt pip-egg-info/zope.annotation.egg-info/top_level.txt src/zope/__init__.py src/zope/annotation/__init__.py src/zope/annotation/attribute.py src/zope/annotation/factory.py src/zope/annotation/interfaces.py src/zope/annotation/tests/__init__.py src/zope/annotation/tests/annotations.py src/zope/annotation/tests/test_attributeannotations.pyzope2.13-2.13.21/source/zope.annotation/pip-egg-info/zope.annotation.egg-info/not-zip-safe0000644000175000017500000000000112214017705030113 0ustar arnauarnau zope2.13-2.13.21/source/zope.annotation/README.txt0000644000175000017500000000004412214017705020260 0ustar arnauarnauSee src/zope/annotation/README.txt. zope2.13-2.13.21/source/zope.annotation/setup.cfg0000644000175000017500000000007312214017705020405 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.annotation/buildout.cfg0000644000175000017500000000034612214017705021077 0ustar arnauarnau[buildout] develop = . parts = test pydev [test] recipe = zc.recipe.testrunner eggs = zope.annotation [test] [ctags] recipe = z3c.recipe.tag:tags eggs = zope.annotation [pydev] recipe = pb.recipes.pydev eggs = zope.annotation zope2.13-2.13.21/source/zope.annotation/bootstrap.py0000644000175000017500000000337212214017705021160 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 79338 2007-08-29 09:42:56Z baijum $ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.annotation/CHANGES.txt0000644000175000017500000000172012214017705020375 0ustar arnauarnau======= CHANGES ======= 3.5.0 (2009-09-07) ------------------ - Add ZODB3 to install_requires, because it's a true requirement of this package, not just a testing requirement, as BTrees are in use. - Fix one test that was inactive because it's function was overriden by a mistake. 3.4.2 (2009-03-09) ------------------ - Clean up package description and documentation a bit. - Change mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired. - Remove old zpkg-related files. 3.4.1 (2008-08-26) ------------------ - Annotation factories take care not to store proxies in the database, so adapting an object wrapped in a ``LocationProxy`` works correctly. Fixes https://bugs.launchpad.net/zope3/+bug/261620 3.4.0 (2007-08-29) ------------------ - Annotation factories are no longer containing the factored object. Instead the objects are located using ``zope.location``. This removes a dependency to ``zope.app.container``. zope2.13-2.13.21/source/zope.annotation/src/0000755000175000017500000000000012214017705017353 5ustar arnauarnauzope2.13-2.13.21/source/zope.annotation/src/zope/0000755000175000017500000000000012214017705020330 5ustar arnauarnauzope2.13-2.13.21/source/zope.annotation/src/zope/__init__.py0000644000175000017500000000007012214017705022436 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.annotation/src/zope/annotation/0000755000175000017500000000000012214017705022502 5ustar arnauarnauzope2.13-2.13.21/source/zope.annotation/src/zope/annotation/configure.zcml0000644000175000017500000000057612214017705025362 0ustar arnauarnau zope2.13-2.13.21/source/zope.annotation/src/zope/annotation/factory.py0000644000175000017500000000432712214017705024531 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Annotation factory helper $Id: factory.py 90369 2008-08-26 22:03:33Z mgedmin $ """ import zope.component import zope.interface import zope.location.location import zope.location.interfaces import zope.proxy import zope.annotation.interfaces def factory(factory, key=None): """Adapter factory to help create annotations easily. """ # if no key is provided, # we'll determine the unique key based on the factory's dotted name if key is None: key = factory.__module__ + '.' + factory.__name__ adapts = zope.component.adaptedBy(factory) if adapts is None: raise TypeError("Missing 'zope.component.adapts' on annotation") @zope.component.adapter(list(adapts)[0]) @zope.interface.implementer(list(zope.component.implementedBy(factory))[0]) def getAnnotation(context): annotations = zope.annotation.interfaces.IAnnotations(context) try: result = annotations[key] except KeyError: result = factory() annotations[key] = result if zope.location.interfaces.ILocation.providedBy(result): zope.location.location.locate(result, zope.proxy.removeAllProxies(context), key) if not (zope.location.interfaces.ILocation.providedBy(result) and result.__parent__ is context and result.__name__ == key): result = zope.location.location.LocationProxy(result, context, key) return result # Convention to make adapter introspectable, used by apidoc getAnnotation.factory = factory return getAnnotation zope2.13-2.13.21/source/zope.annotation/src/zope/annotation/interfaces.py0000644000175000017500000000537612214017705025212 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Annotations store arbitrary application data under package-unique keys. $Id: interfaces.py 71513 2006-12-10 22:50:23Z ctheune $ """ __docformat__ = 'restructuredtext' from zope.interface import Interface class IAnnotatable(Interface): """Marker interface for objects that support storing annotations. This interface says "There exists an adapter to an IAnnotations for an object that implements `IAnnotatable`". Classes should not directly declare that they implement this interface. Instead they should implement an interface derived from this one, which details how the annotations are to be stored, such as `IAttributeAnnotatable`. """ class IAnnotations(IAnnotatable): """Stores arbitrary application data under package-unique keys. By "package-unique keys", we mean keys that are are unique by virtue of including the dotted name of a package as a prefix. A package name is used to limit the authority for picking names for a package to the people using that package. For example, when implementing annotations for storing Zope Dublin-Core meta-data, we use the key:: "zope.app.dublincore.ZopeDublinCore" """ def __nonzero__(): """Test whether there are any annotations """ def __getitem__(key): """Return the annotation stored under key. Raises KeyError if key not found. """ def get(key, default=None): """Return the annotation stored under key, or default if not found. """ def __setitem__(key, value): """Store annotation under key. In order to avoid key collisions, users of this interface must use their dotted package name as part of the key name. """ def __delitem__(key): """Removes the annotation stored under key. Raises a KeyError if the key is not found. """ class IAttributeAnnotatable(IAnnotatable): """Marker indicating that annotations can be stored on an attribute. This is a marker interface giving permission for an `IAnnotations` adapter to store data in an attribute named `__annotations__`. """ zope2.13-2.13.21/source/zope.annotation/src/zope/annotation/attribute.py0000644000175000017500000000476012214017705025066 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Attribute Annotations implementation $Id: attribute.py 72995 2007-03-05 21:36:39Z jacobholm $ """ __docformat__ = 'restructuredtext' from UserDict import DictMixin from BTrees.OOBTree import OOBTree from zope import component, interface from zope.annotation import interfaces class AttributeAnnotations(DictMixin): """Store annotations on an object Store annotations in the `__annotations__` attribute on a `IAttributeAnnotatable` object. """ interface.implements(interfaces.IAnnotations) component.adapts(interfaces.IAttributeAnnotatable) def __init__(self, obj, context=None): self.obj = obj def __nonzero__(self): return bool(getattr(self.obj, '__annotations__', 0)) def get(self, key, default=None): """See zope.annotation.interfaces.IAnnotations""" annotations = getattr(self.obj, '__annotations__', None) if not annotations: return default return annotations.get(key, default) def __getitem__(self, key): annotations = getattr(self.obj, '__annotations__', None) if annotations is None: raise KeyError(key) return annotations[key] def keys(self): annotations = getattr(self.obj, '__annotations__', None) if annotations is None: return [] return annotations.keys() def __setitem__(self, key, value): """See zope.annotation.interfaces.IAnnotations""" try: annotations = self.obj.__annotations__ except AttributeError: annotations = self.obj.__annotations__ = OOBTree() annotations[key] = value def __delitem__(self, key): """See zope.app.interfaces.annotation.IAnnotations""" try: annotation = self.obj.__annotations__ except AttributeError: raise KeyError(key) del annotation[key] zope2.13-2.13.21/source/zope.annotation/src/zope/annotation/__init__.py0000644000175000017500000000157012214017705024616 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Annotation (meta data) support $Id: __init__.py 66606 2006-04-06 19:04:54Z philikon $ """ from zope.annotation.interfaces import IAttributeAnnotatable from zope.annotation.interfaces import IAnnotations from zope.annotation.factory import factory zope2.13-2.13.21/source/zope.annotation/src/zope/annotation/README.txt0000644000175000017500000001202312214017705024176 0ustar arnauarnau================== Object Annotations ================== This package provides a mechanism to store additional information about objects without need to modify object class. Annotation factories -------------------- There is more to document about annotations, but we'll just sketch out a scenario on how to use the annotation factory for now. This is one of the easiest ways to use annotations -- basically you can see them as persistent, writable adapters. First, let's make a persistent object we can create annotations for: >>> from zope import interface >>> class IFoo(interface.Interface): ... pass >>> from zope.annotation.interfaces import IAttributeAnnotatable >>> from persistent import Persistent >>> class Foo(Persistent): ... interface.implements(IFoo, IAttributeAnnotatable) We directly say that Foo implements IAttributeAnnotatable here. In practice this is often done in ZCML, using the `implements` subdirective of the `content` or `class` directive. Now let's create an annotation for this: >>> class IBar(interface.Interface): ... a = interface.Attribute('A') ... b = interface.Attribute('B') >>> from zope import component >>> class Bar(Persistent): ... interface.implements(IBar) ... component.adapts(IFoo) ... def __init__(self): ... self.a = 1 ... self.b = 2 Note that the annotation implementation does not expect any arguments to its `__init__`. Otherwise it's basically an adapter. Now, we'll register the annotation as an adapter. To do this we use the `factory` function provided by `zope.annotation`: >>> from zope.annotation import factory >>> component.provideAdapter(factory(Bar)) Note that we do not need to specify what the adapter provides or what it adapts - we already do this on the annotation class itself. Now let's make an instance of `Foo`, and make an annotation for it. >>> foo = Foo() >>> bar = IBar(foo) >>> bar.a 1 >>> bar.b 2 We'll change `a` and get the annotation again. Our change is still there: >>> bar.a = 3 >>> IBar(foo).a 3 Of course it's still different for another instance of `Foo`: >>> foo2 = Foo() >>> IBar(foo2).a 1 What if our annotation does not provide what it adapts with `component.adapts`? It will complain: >>> class IQux(interface.Interface): ... pass >>> class Qux(Persistent): ... interface.implements(IQux) >>> component.provideAdapter(factory(Qux)) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: Missing 'zope.component.adapts' on annotation It's possible to provide an annotation with an explicit key. (If the key is not supplied, the key is deduced from the annotation's dotted name, provided it is a class.) >>> class IHoi(interface.Interface): ... pass >>> class Hoi(Persistent): ... interface.implements(IHoi) ... component.adapts(IFoo) >>> component.provideAdapter(factory(Hoi, 'my.unique.key')) >>> isinstance(IHoi(foo), Hoi) True Location -------- Annotation factories are put into the location hierarchy with their parent pointing to the annotated object and the name to the dotted name of the annotation's class (or the name the adapter was registered under): >>> foo3 = Foo() >>> new_hoi = IHoi(foo3) >>> new_hoi.__parent__ >>> new_hoi.__name__ 'my.unique.key' >>> import zope.location.interfaces >>> zope.location.interfaces.ILocation.providedBy(new_hoi) True Please notice, that our Hoi object does not implement ILocation, so a location proxy will be used. This has to be re-established every time we retrieve the object (Guard against former bug: proxy wasn't established when the annotation existed already.) >>> old_hoi = IHoi(foo3) >>> old_hoi.__parent__ >>> old_hoi.__name__ 'my.unique.key' >>> zope.location.interfaces.ILocation.providedBy(old_hoi) True LocationProxies --------------- Suppose your annotation proxy provides ILocation. >>> class IPolloi(interface.Interface): ... pass >>> class Polloi(Persistent): ... interface.implements(IPolloi, zope.location.interfaces.ILocation) ... component.adapts(IFoo) ... __name__ = __parent__ = 0 >>> component.provideAdapter(factory(Polloi, 'my.other.key')) Sometimes you're adapting an object wrapped in a LocationProxy. >>> foo4 = Foo() >>> import zope.location.location >>> wrapped_foo4 = zope.location.location.LocationProxy(foo4, None, 'foo4') >>> located_polloi = IPolloi(wrapped_foo4) At first glance it looks as if located_polloi is located under wrapped_foo4. >>> located_polloi.__parent__ is wrapped_foo4 True >>> located_polloi.__name__ 'my.other.key' but that's because we received a LocationProxy >>> print type(located_polloi).__name__ LocationProxy If we unwrap located_polloi and look at it directly, we'll see it stores a reference to the real Foo object >>> from zope.proxy import removeAllProxies >>> removeAllProxies(located_polloi).__parent__ is foo4 True >>> removeAllProxies(located_polloi).__name__ 'my.other.key' zope2.13-2.13.21/source/zope.annotation/src/zope/annotation/tests/0000755000175000017500000000000012214017705023644 5ustar arnauarnauzope2.13-2.13.21/source/zope.annotation/src/zope/annotation/tests/annotations.py0000644000175000017500000000522712214017705026561 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """General Annotations Tests All objects implementing 'IAnnotations' should pass these tests. They might be used as base tests for real implementations. $Id: annotations.py 103612 2009-09-07 16:06:08Z nadako $ """ import unittest from zope.interface.verify import verifyObject from zope.annotation.interfaces import IAnnotations class AnnotationsTest(unittest.TestCase): """Test the IAnnotations interface. The test case class expects the 'IAnnotations' implementer to be in 'self.annotations'. """ def setUp(self): self.obj = {1:2, 3:4} def test_nonzero(self): self.failIf(self.annotations) self.annotations['unittest'] = self.obj self.failUnless(self.annotations) del self.annotations['unittest'] self.failIf(self.annotations) def testInterfaceVerifies(self): verifyObject(IAnnotations, self.annotations) def testStorage(self): # test __getitem__ self.annotations['unittest'] = self.obj res = self.annotations['unittest'] self.failUnlessEqual(self.obj, res) def testGetitemException(self): # test __getitem__ raises exception on unknown key self.assertRaises(KeyError, self.annotations.__getitem__,'randomkey') def testGet(self): # test get self.annotations['unittest'] = self.obj res = self.annotations.get('unittest') self.failUnlessEqual(self.obj, res) def testGetNoSet(self): # test get with no set res = self.annotations.get('randomkey') self.failUnlessEqual(None, res) def testGetDefault(self): # test get returns default res = self.annotations.get('randomkey', 'default') self.failUnlessEqual('default', res) def testDel(self): self.annotations['unittest'] = self.obj del self.annotations['unittest'] self.failUnlessEqual(None, self.annotations.get('unittest')) def testDelRaisesKeyError(self): self.assertRaises(KeyError, self.annotations.__delitem__, 'unittest') zope2.13-2.13.21/source/zope.annotation/src/zope/annotation/tests/test_attributeannotations.py0000644000175000017500000000342512214017705031542 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests the 'AttributeAnnotations' adapter. Also test the annotation factory. $Id: test_attributeannotations.py 70846 2006-10-20 13:52:31Z ctheune $ """ import unittest, doctest from zope.testing import cleanup from zope.interface import implements from zope import component from zope.annotation.tests.annotations import AnnotationsTest from zope.annotation.attribute import AttributeAnnotations from zope.annotation.interfaces import IAttributeAnnotatable class Dummy(object): implements(IAttributeAnnotatable) class AttributeAnnotationsTest(AnnotationsTest, cleanup.CleanUp): def setUp(self): self.annotations = AttributeAnnotations(Dummy()) super(AttributeAnnotationsTest, self).setUp() def setUp(test=None): cleanup.setUp() component.provideAdapter(AttributeAnnotations) def tearDown(test=None): cleanup.tearDown() def test_suite(): return unittest.TestSuite(( unittest.makeSuite(AttributeAnnotationsTest), doctest.DocFileSuite('../README.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS) )) if __name__=='__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.annotation/src/zope/annotation/tests/__init__.py0000644000175000017500000000001712214017705025753 0ustar arnauarnau# Import this. zope2.13-2.13.21/source/zope.annotation/src/zope.annotation.egg-info/0000755000175000017500000000000012214017705024173 5ustar arnauarnauzope2.13-2.13.21/source/zope.annotation/src/zope.annotation.egg-info/PKG-INFO0000644000175000017500000002013212214017705025266 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.annotation Version: 3.5.0 Summary: Object annotation mechanism Home-page: http://pypi.python.org/pypi/zope.annotation Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ================== Object Annotations ================== This package provides a mechanism to store additional information about objects without need to modify object class. Annotation factories -------------------- There is more to document about annotations, but we'll just sketch out a scenario on how to use the annotation factory for now. This is one of the easiest ways to use annotations -- basically you can see them as persistent, writable adapters. First, let's make a persistent object we can create annotations for: >>> from zope import interface >>> class IFoo(interface.Interface): ... pass >>> from zope.annotation.interfaces import IAttributeAnnotatable >>> from persistent import Persistent >>> class Foo(Persistent): ... interface.implements(IFoo, IAttributeAnnotatable) We directly say that Foo implements IAttributeAnnotatable here. In practice this is often done in ZCML, using the `implements` subdirective of the `content` or `class` directive. Now let's create an annotation for this: >>> class IBar(interface.Interface): ... a = interface.Attribute('A') ... b = interface.Attribute('B') >>> from zope import component >>> class Bar(Persistent): ... interface.implements(IBar) ... component.adapts(IFoo) ... def __init__(self): ... self.a = 1 ... self.b = 2 Note that the annotation implementation does not expect any arguments to its `__init__`. Otherwise it's basically an adapter. Now, we'll register the annotation as an adapter. To do this we use the `factory` function provided by `zope.annotation`: >>> from zope.annotation import factory >>> component.provideAdapter(factory(Bar)) Note that we do not need to specify what the adapter provides or what it adapts - we already do this on the annotation class itself. Now let's make an instance of `Foo`, and make an annotation for it. >>> foo = Foo() >>> bar = IBar(foo) >>> bar.a 1 >>> bar.b 2 We'll change `a` and get the annotation again. Our change is still there: >>> bar.a = 3 >>> IBar(foo).a 3 Of course it's still different for another instance of `Foo`: >>> foo2 = Foo() >>> IBar(foo2).a 1 What if our annotation does not provide what it adapts with `component.adapts`? It will complain: >>> class IQux(interface.Interface): ... pass >>> class Qux(Persistent): ... interface.implements(IQux) >>> component.provideAdapter(factory(Qux)) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: Missing 'zope.component.adapts' on annotation It's possible to provide an annotation with an explicit key. (If the key is not supplied, the key is deduced from the annotation's dotted name, provided it is a class.) >>> class IHoi(interface.Interface): ... pass >>> class Hoi(Persistent): ... interface.implements(IHoi) ... component.adapts(IFoo) >>> component.provideAdapter(factory(Hoi, 'my.unique.key')) >>> isinstance(IHoi(foo), Hoi) True Location -------- Annotation factories are put into the location hierarchy with their parent pointing to the annotated object and the name to the dotted name of the annotation's class (or the name the adapter was registered under): >>> foo3 = Foo() >>> new_hoi = IHoi(foo3) >>> new_hoi.__parent__ >>> new_hoi.__name__ 'my.unique.key' >>> import zope.location.interfaces >>> zope.location.interfaces.ILocation.providedBy(new_hoi) True Please notice, that our Hoi object does not implement ILocation, so a location proxy will be used. This has to be re-established every time we retrieve the object (Guard against former bug: proxy wasn't established when the annotation existed already.) >>> old_hoi = IHoi(foo3) >>> old_hoi.__parent__ >>> old_hoi.__name__ 'my.unique.key' >>> zope.location.interfaces.ILocation.providedBy(old_hoi) True LocationProxies --------------- Suppose your annotation proxy provides ILocation. >>> class IPolloi(interface.Interface): ... pass >>> class Polloi(Persistent): ... interface.implements(IPolloi, zope.location.interfaces.ILocation) ... component.adapts(IFoo) ... __name__ = __parent__ = 0 >>> component.provideAdapter(factory(Polloi, 'my.other.key')) Sometimes you're adapting an object wrapped in a LocationProxy. >>> foo4 = Foo() >>> import zope.location.location >>> wrapped_foo4 = zope.location.location.LocationProxy(foo4, None, 'foo4') >>> located_polloi = IPolloi(wrapped_foo4) At first glance it looks as if located_polloi is located under wrapped_foo4. >>> located_polloi.__parent__ is wrapped_foo4 True >>> located_polloi.__name__ 'my.other.key' but that's because we received a LocationProxy >>> print type(located_polloi).__name__ LocationProxy If we unwrap located_polloi and look at it directly, we'll see it stores a reference to the real Foo object >>> from zope.proxy import removeAllProxies >>> removeAllProxies(located_polloi).__parent__ is foo4 True >>> removeAllProxies(located_polloi).__name__ 'my.other.key' ======= CHANGES ======= 3.5.0 (2009-09-07) ------------------ - Add ZODB3 to install_requires, because it's a true requirement of this package, not just a testing requirement, as BTrees are in use. - Fix one test that was inactive because it's function was overriden by a mistake. 3.4.2 (2009-03-09) ------------------ - Clean up package description and documentation a bit. - Change mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired. - Remove old zpkg-related files. 3.4.1 (2008-08-26) ------------------ - Annotation factories take care not to store proxies in the database, so adapting an object wrapped in a ``LocationProxy`` works correctly. Fixes https://bugs.launchpad.net/zope3/+bug/261620 3.4.0 (2007-08-29) ------------------ - Annotation factories are no longer containing the factored object. Instead the objects are located using ``zope.location``. This removes a dependency to ``zope.app.container``. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Software Development zope2.13-2.13.21/source/zope.annotation/src/zope.annotation.egg-info/dependency_links.txt0000644000175000017500000000000112214017705030241 0ustar arnauarnau zope2.13-2.13.21/source/zope.annotation/src/zope.annotation.egg-info/requires.txt0000644000175000017500000000013412214017705026571 0ustar arnauarnausetuptools zope.interface zope.component zope.location zope.proxy ZODB3 [test] zope.testingzope2.13-2.13.21/source/zope.annotation/src/zope.annotation.egg-info/namespace_packages.txt0000644000175000017500000000000512214017705030521 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.annotation/src/zope.annotation.egg-info/top_level.txt0000644000175000017500000000000512214017705026720 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.annotation/src/zope.annotation.egg-info/SOURCES.txt0000644000175000017500000000131412214017705026056 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.annotation.egg-info/PKG-INFO src/zope.annotation.egg-info/SOURCES.txt src/zope.annotation.egg-info/dependency_links.txt src/zope.annotation.egg-info/namespace_packages.txt src/zope.annotation.egg-info/not-zip-safe src/zope.annotation.egg-info/requires.txt src/zope.annotation.egg-info/top_level.txt src/zope/annotation/README.txt src/zope/annotation/__init__.py src/zope/annotation/attribute.py src/zope/annotation/configure.zcml src/zope/annotation/factory.py src/zope/annotation/interfaces.py src/zope/annotation/tests/__init__.py src/zope/annotation/tests/annotations.py src/zope/annotation/tests/test_attributeannotations.pyzope2.13-2.13.21/source/zope.annotation/src/zope.annotation.egg-info/not-zip-safe0000644000175000017500000000000112214017705026421 0ustar arnauarnau zope2.13-2.13.21/source/ZopeUndo/0000755000175000017500000000000012214017466015205 5ustar arnauarnauzope2.13-2.13.21/source/ZopeUndo/setup.py0000644000175000017500000000224412214017466016721 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from setuptools import setup, find_packages setup(name='ZopeUndo', version = '2.12.0', url='http://pypi.python.org/pypi/ZopeUndo', license='ZPL 2.1', description="ZODB undo support for Zope2.", author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', long_description=open('README.txt').read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), package_dir={'': 'src'}, include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/ZopeUndo/PKG-INFO0000644000175000017500000000143512214017466016305 0ustar arnauarnauMetadata-Version: 1.0 Name: ZopeUndo Version: 2.12.0 Summary: ZODB undo support for Zope2. Home-page: http://pypi.python.org/pypi/ZopeUndo Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== This package is used to support the Prefix object that Zope 2 uses for the undo log. It is a separate package only to aid configuration management. This package is included in Zope 2. It can be used in a ZEO server to allow it to support Zope 2's undo log , without pulling in all of Zope 2. Changelog ========= 2.12.0 (2010-04-05) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/ZopeUndo/pip-egg-info/0000755000175000017500000000000012214017466017466 5ustar arnauarnauzope2.13-2.13.21/source/ZopeUndo/pip-egg-info/ZopeUndo.egg-info/0000755000175000017500000000000012214017466022723 5ustar arnauarnauzope2.13-2.13.21/source/ZopeUndo/pip-egg-info/ZopeUndo.egg-info/PKG-INFO0000644000175000017500000000143512214017466024023 0ustar arnauarnauMetadata-Version: 1.0 Name: ZopeUndo Version: 2.12.0 Summary: ZODB undo support for Zope2. Home-page: http://pypi.python.org/pypi/ZopeUndo Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== This package is used to support the Prefix object that Zope 2 uses for the undo log. It is a separate package only to aid configuration management. This package is included in Zope 2. It can be used in a ZEO server to allow it to support Zope 2's undo log , without pulling in all of Zope 2. Changelog ========= 2.12.0 (2010-04-05) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/ZopeUndo/pip-egg-info/ZopeUndo.egg-info/dependency_links.txt0000644000175000017500000000000112214017466026771 0ustar arnauarnau zope2.13-2.13.21/source/ZopeUndo/pip-egg-info/ZopeUndo.egg-info/top_level.txt0000644000175000017500000000001112214017466025445 0ustar arnauarnauZopeUndo zope2.13-2.13.21/source/ZopeUndo/pip-egg-info/ZopeUndo.egg-info/SOURCES.txt0000644000175000017500000000054412214017466024612 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/ZopeUndo.egg-info/PKG-INFO pip-egg-info/ZopeUndo.egg-info/SOURCES.txt pip-egg-info/ZopeUndo.egg-info/dependency_links.txt pip-egg-info/ZopeUndo.egg-info/not-zip-safe pip-egg-info/ZopeUndo.egg-info/top_level.txt src/ZopeUndo/Prefix.py src/ZopeUndo/__init__.py src/ZopeUndo/tests/__init__.py src/ZopeUndo/tests/testPrefix.pyzope2.13-2.13.21/source/ZopeUndo/pip-egg-info/ZopeUndo.egg-info/not-zip-safe0000644000175000017500000000000112214017466025151 0ustar arnauarnau zope2.13-2.13.21/source/ZopeUndo/README.txt0000644000175000017500000000047012214017466016704 0ustar arnauarnauOverview ======== This package is used to support the Prefix object that Zope 2 uses for the undo log. It is a separate package only to aid configuration management. This package is included in Zope 2. It can be used in a ZEO server to allow it to support Zope 2's undo log , without pulling in all of Zope 2. zope2.13-2.13.21/source/ZopeUndo/setup.cfg0000644000175000017500000000007312214017466017026 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/ZopeUndo/buildout.cfg0000644000175000017500000000026112214017466017514 0ustar arnauarnau[buildout] develop = . parts = interpreter test [interpreter] recipe = zc.recipe.egg interpreter = python eggs = ZopeUndo [test] recipe = zc.recipe.testrunner eggs = ZopeUndo zope2.13-2.13.21/source/ZopeUndo/bootstrap.py0000644000175000017500000000742112214017466017600 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 108946 2010-02-12 02:40:18Z yusei $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/ZopeUndo/CHANGES.txt0000644000175000017500000000013612214017466017016 0ustar arnauarnauChangelog ========= 2.12.0 (2010-04-05) ------------------- - Released as separate package. zope2.13-2.13.21/source/ZopeUndo/src/0000755000175000017500000000000012214017466015774 5ustar arnauarnauzope2.13-2.13.21/source/ZopeUndo/src/ZopeUndo/0000755000175000017500000000000012214017466017537 5ustar arnauarnauzope2.13-2.13.21/source/ZopeUndo/src/ZopeUndo/Prefix.py0000644000175000017500000000300512214017466021344 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class Prefix: """A Prefix() is equal to any path it is a prefix of. This class can be compared to a string. The comparison will return True if all path elements of the Prefix are found at the beginning of the string being compared. Two Prefixes can not be compared. """ __no_side_effects__ = 1 def __init__(self, path): path_list = path.split('/') self.length = len(path_list) self.path = path_list def __cmp__(self, o): other_path = o.split('/') if other_path and ' ' in other_path[-1]: # don't include logged username in comparison pos = other_path[-1].rfind(' ') other_path[-1] = other_path[-1][:pos] return cmp(other_path[:self.length], self.path) def __repr__(self): # makes failing tests easier to read return "Prefix('%s')" % '/'.join(self.path) zope2.13-2.13.21/source/ZopeUndo/src/ZopeUndo/__init__.py0000644000175000017500000000120112214017466021642 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## zope2.13-2.13.21/source/ZopeUndo/src/ZopeUndo/tests/0000755000175000017500000000000012214017466020701 5ustar arnauarnauzope2.13-2.13.21/source/ZopeUndo/src/ZopeUndo/tests/testPrefix.py0000644000175000017500000000326412214017466023415 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from ZopeUndo.Prefix import Prefix import unittest class PrefixTest(unittest.TestCase): def test(self): p1 = Prefix("/a/b") for equal in ("/a/b", "/a/b/c", "/a/b/c/d"): self.assertEqual(p1, equal) for notEqual in ("", "/a/c", "/a/bbb", "///"): self.assertNotEqual(p1, notEqual) p2 = Prefix("") for equal in ("", "/", "/def", "/a/b", "/a/b/c", "/a/b/c/d"): self.assertEqual(p2, equal) def test_username_info(self): # Zope Collector 1810; user paths have username appended p1 = Prefix('/a/b') for equal in ('/a/b spam', '/a/b/c spam', '/a/b/c/b spam'): self.assertEqual(p1, equal) for notEqual in (" spam", "/a/c spam", "/a/bbb spam", "/// spam"): self.assertNotEqual(p1, notEqual) p2 = Prefix("") for equal in (" eggs", "/ eggs", "/def eggs", "/a/b eggs", "/a/b/c eggs", "/a/b/c/d eggs"): self.assertEqual(p2, equal) def test_suite(): return unittest.makeSuite(PrefixTest) zope2.13-2.13.21/source/ZopeUndo/src/ZopeUndo/tests/__init__.py0000644000175000017500000000120112214017466023004 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## zope2.13-2.13.21/source/ZopeUndo/src/ZopeUndo.egg-info/0000755000175000017500000000000012214017466021231 5ustar arnauarnauzope2.13-2.13.21/source/ZopeUndo/src/ZopeUndo.egg-info/PKG-INFO0000644000175000017500000000143512214017466022331 0ustar arnauarnauMetadata-Version: 1.0 Name: ZopeUndo Version: 2.12.0 Summary: ZODB undo support for Zope2. Home-page: http://pypi.python.org/pypi/ZopeUndo Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== This package is used to support the Prefix object that Zope 2 uses for the undo log. It is a separate package only to aid configuration management. This package is included in Zope 2. It can be used in a ZEO server to allow it to support Zope 2's undo log , without pulling in all of Zope 2. Changelog ========= 2.12.0 (2010-04-05) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/ZopeUndo/src/ZopeUndo.egg-info/dependency_links.txt0000644000175000017500000000000112214017466025277 0ustar arnauarnau zope2.13-2.13.21/source/ZopeUndo/src/ZopeUndo.egg-info/top_level.txt0000644000175000017500000000001112214017466023753 0ustar arnauarnauZopeUndo zope2.13-2.13.21/source/ZopeUndo/src/ZopeUndo.egg-info/SOURCES.txt0000644000175000017500000000053412214017466023117 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py src/ZopeUndo/Prefix.py src/ZopeUndo/__init__.py src/ZopeUndo.egg-info/PKG-INFO src/ZopeUndo.egg-info/SOURCES.txt src/ZopeUndo.egg-info/dependency_links.txt src/ZopeUndo.egg-info/not-zip-safe src/ZopeUndo.egg-info/top_level.txt src/ZopeUndo/tests/__init__.py src/ZopeUndo/tests/testPrefix.pyzope2.13-2.13.21/source/ZopeUndo/src/ZopeUndo.egg-info/not-zip-safe0000644000175000017500000000000112214017466023457 0ustar arnauarnau zope2.13-2.13.21/source/zc.lockfile/0000755000175000017500000000000012214017700015634 5ustar arnauarnauzope2.13-2.13.21/source/zc.lockfile/setup.py0000644000175000017500000000374612214017700017360 0ustar arnauarnau############################################################################## # # Copyright (c) Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## name, version = "zc.lockfile", '1.0.2' import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() long_description=( read('README.txt') + '\n' + 'Detailed Documentation\n' '**********************\n' + '\n' + read('src', 'zc', 'lockfile', 'README.txt') + '\n' + read('CHANGES.txt') + '\n' + 'Download\n' '**********************\n' ) open('doc.txt', 'w').write(long_description) setup( name = name, version=version, author = "Jim Fulton", author_email = "jim@zope.com", description = "Basic inter-process locks", long_description=long_description, license = "ZPL 2.1", keywords = "lock", url='http://www.python.org/pypi/'+name, packages = find_packages('src'), package_dir = {'': 'src'}, namespace_packages = ['zc'], install_requires = 'setuptools', extras_require=dict( test=[ 'zope.testing', ]), include_package_data = True, zip_safe=False, classifiers = [ 'Programming Language :: Python', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', ], ) zope2.13-2.13.21/source/zc.lockfile/PKG-INFO0000644000175000017500000000734012214017700016735 0ustar arnauarnauMetadata-Version: 1.0 Name: zc.lockfile Version: 1.0.2 Summary: Basic inter-process locks Home-page: http://www.python.org/pypi/zc.lockfile Author: Jim Fulton Author-email: jim@zope.com License: ZPL 2.1 Description: ************************* Basic inter-process locks ************************* The zc.lockfile package provides a basic portable implementation of interprocess locks using lock files. The purpose if not specifically to lock files, but to simply provide locks with an implementation based on file-locking primitives. Of course, these locks could be used to mediate access to *other* files. For example, the ZODB file storage implementation uses file locks to mediate access to file-storage database files. The database files and lock file files are separate files. .. contents:: Detailed Documentation ********************** Lock file support ================= The ZODB lock_file module provides support for creating file system locks. These are locks that are implemented with lock files and OS-provided locking facilities. To create a lock, instantiate a LockFile object with a file name: >>> import zc.lockfile >>> lock = zc.lockfile.LockFile('lock') If we try to lock the same name, we'll get a lock error: >>> import zope.testing.loggingsupport >>> handler = zope.testing.loggingsupport.InstalledHandler('zc.lockfile') >>> try: ... zc.lockfile.LockFile('lock') ... except zc.lockfile.LockError: ... print "Can't lock file" Can't lock file >>> for record in handler.records: # doctest: +ELLIPSIS ... print record.levelname, record.getMessage() ERROR Error locking file lock; pid=... To release the lock, use it's close method: >>> lock.close() The lock file is not removed. It is left behind: >>> import os >>> os.path.exists('lock') True Of course, now that we've released the lock, we can create it again: >>> lock = zc.lockfile.LockFile('lock') >>> lock.close() .. Cleanup >>> import os >>> os.remove('lock') Change History *************** 1.0.2 (2012-12-02) ================== - Fixed: the fix included in 1.0.1 caused multiple pids to be written to the lock file 1.0.1 (2012-11-30) ================== - Fixed: when there was lock contention, the pid in the lock file was lost. Thanks to Daniel Moisset reporting the problem and providing a fix with tests. - Added test extra to declare test dependency on ``zope.testing``. - Using Python's ``doctest`` module instead of depreacted ``zope.testing.doctest``. 1.0.0 (2008-10-18) ================== - Fixed a small bug in error logging. 1.0.0b1 (2007-07-18) ==================== Initial release Download ********************** Keywords: lock Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows zope2.13-2.13.21/source/zc.lockfile/pip-egg-info/0000755000175000017500000000000012214017700020115 5ustar arnauarnauzope2.13-2.13.21/source/zc.lockfile/pip-egg-info/zc.lockfile.egg-info/0000755000175000017500000000000012214017700024012 5ustar arnauarnauzope2.13-2.13.21/source/zc.lockfile/pip-egg-info/zc.lockfile.egg-info/PKG-INFO0000644000175000017500000000734012214017700025113 0ustar arnauarnauMetadata-Version: 1.1 Name: zc.lockfile Version: 1.0.2 Summary: Basic inter-process locks Home-page: http://www.python.org/pypi/zc.lockfile Author: Jim Fulton Author-email: jim@zope.com License: ZPL 2.1 Description: ************************* Basic inter-process locks ************************* The zc.lockfile package provides a basic portable implementation of interprocess locks using lock files. The purpose if not specifically to lock files, but to simply provide locks with an implementation based on file-locking primitives. Of course, these locks could be used to mediate access to *other* files. For example, the ZODB file storage implementation uses file locks to mediate access to file-storage database files. The database files and lock file files are separate files. .. contents:: Detailed Documentation ********************** Lock file support ================= The ZODB lock_file module provides support for creating file system locks. These are locks that are implemented with lock files and OS-provided locking facilities. To create a lock, instantiate a LockFile object with a file name: >>> import zc.lockfile >>> lock = zc.lockfile.LockFile('lock') If we try to lock the same name, we'll get a lock error: >>> import zope.testing.loggingsupport >>> handler = zope.testing.loggingsupport.InstalledHandler('zc.lockfile') >>> try: ... zc.lockfile.LockFile('lock') ... except zc.lockfile.LockError: ... print "Can't lock file" Can't lock file >>> for record in handler.records: # doctest: +ELLIPSIS ... print record.levelname, record.getMessage() ERROR Error locking file lock; pid=... To release the lock, use it's close method: >>> lock.close() The lock file is not removed. It is left behind: >>> import os >>> os.path.exists('lock') True Of course, now that we've released the lock, we can create it again: >>> lock = zc.lockfile.LockFile('lock') >>> lock.close() .. Cleanup >>> import os >>> os.remove('lock') Change History *************** 1.0.2 (2012-12-02) ================== - Fixed: the fix included in 1.0.1 caused multiple pids to be written to the lock file 1.0.1 (2012-11-30) ================== - Fixed: when there was lock contention, the pid in the lock file was lost. Thanks to Daniel Moisset reporting the problem and providing a fix with tests. - Added test extra to declare test dependency on ``zope.testing``. - Using Python's ``doctest`` module instead of depreacted ``zope.testing.doctest``. 1.0.0 (2008-10-18) ================== - Fixed a small bug in error logging. 1.0.0b1 (2007-07-18) ==================== Initial release Download ********************** Keywords: lock Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows zope2.13-2.13.21/source/zc.lockfile/pip-egg-info/zc.lockfile.egg-info/dependency_links.txt0000644000175000017500000000000112214017700030060 0ustar arnauarnau zope2.13-2.13.21/source/zc.lockfile/pip-egg-info/zc.lockfile.egg-info/requires.txt0000644000175000017500000000003712214017700026412 0ustar arnauarnausetuptools [test] zope.testingzope2.13-2.13.21/source/zc.lockfile/pip-egg-info/zc.lockfile.egg-info/namespace_packages.txt0000644000175000017500000000000312214017700030336 0ustar arnauarnauzc zope2.13-2.13.21/source/zc.lockfile/pip-egg-info/zc.lockfile.egg-info/top_level.txt0000644000175000017500000000000312214017700026535 0ustar arnauarnauzc zope2.13-2.13.21/source/zc.lockfile/pip-egg-info/zc.lockfile.egg-info/SOURCES.txt0000644000175000017500000000066312214017700025703 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zc.lockfile.egg-info/PKG-INFO pip-egg-info/zc.lockfile.egg-info/SOURCES.txt pip-egg-info/zc.lockfile.egg-info/dependency_links.txt pip-egg-info/zc.lockfile.egg-info/namespace_packages.txt pip-egg-info/zc.lockfile.egg-info/not-zip-safe pip-egg-info/zc.lockfile.egg-info/requires.txt pip-egg-info/zc.lockfile.egg-info/top_level.txt src/zc/__init__.py src/zc/lockfile/__init__.py src/zc/lockfile/tests.pyzope2.13-2.13.21/source/zc.lockfile/pip-egg-info/zc.lockfile.egg-info/not-zip-safe0000644000175000017500000000000112214017700026240 0ustar arnauarnau zope2.13-2.13.21/source/zc.lockfile/doc.txt0000644000175000017500000000473112214017700017147 0ustar arnauarnau************************* Basic inter-process locks ************************* The zc.lockfile package provides a basic portable implementation of interprocess locks using lock files. The purpose if not specifically to lock files, but to simply provide locks with an implementation based on file-locking primitives. Of course, these locks could be used to mediate access to *other* files. For example, the ZODB file storage implementation uses file locks to mediate access to file-storage database files. The database files and lock file files are separate files. .. contents:: Detailed Documentation ********************** Lock file support ================= The ZODB lock_file module provides support for creating file system locks. These are locks that are implemented with lock files and OS-provided locking facilities. To create a lock, instantiate a LockFile object with a file name: >>> import zc.lockfile >>> lock = zc.lockfile.LockFile('lock') If we try to lock the same name, we'll get a lock error: >>> import zope.testing.loggingsupport >>> handler = zope.testing.loggingsupport.InstalledHandler('zc.lockfile') >>> try: ... zc.lockfile.LockFile('lock') ... except zc.lockfile.LockError: ... print "Can't lock file" Can't lock file >>> for record in handler.records: # doctest: +ELLIPSIS ... print record.levelname, record.getMessage() ERROR Error locking file lock; pid=... To release the lock, use it's close method: >>> lock.close() The lock file is not removed. It is left behind: >>> import os >>> os.path.exists('lock') True Of course, now that we've released the lock, we can create it again: >>> lock = zc.lockfile.LockFile('lock') >>> lock.close() .. Cleanup >>> import os >>> os.remove('lock') Change History *************** 1.0.2 (2012-12-02) ================== - Fixed: the fix included in 1.0.1 caused multiple pids to be written to the lock file 1.0.1 (2012-11-30) ================== - Fixed: when there was lock contention, the pid in the lock file was lost. Thanks to Daniel Moisset reporting the problem and providing a fix with tests. - Added test extra to declare test dependency on ``zope.testing``. - Using Python's ``doctest`` module instead of depreacted ``zope.testing.doctest``. 1.0.0 (2008-10-18) ================== - Fixed a small bug in error logging. 1.0.0b1 (2007-07-18) ==================== Initial release Download ********************** zope2.13-2.13.21/source/zc.lockfile/LICENSE.txt0000644000175000017500000000402612214017700017461 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zc.lockfile/README.txt0000644000175000017500000000110712214017700017331 0ustar arnauarnau************************* Basic inter-process locks ************************* The zc.lockfile package provides a basic portable implementation of interprocess locks using lock files. The purpose if not specifically to lock files, but to simply provide locks with an implementation based on file-locking primitives. Of course, these locks could be used to mediate access to *other* files. For example, the ZODB file storage implementation uses file locks to mediate access to file-storage database files. The database files and lock file files are separate files. .. contents:: zope2.13-2.13.21/source/zc.lockfile/setup.cfg0000644000175000017500000000007312214017700017455 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zc.lockfile/COPYRIGHT.txt0000644000175000017500000000004012214017700017737 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zc.lockfile/buildout.cfg0000644000175000017500000000025412214017700020145 0ustar arnauarnau[buildout] develop = . parts = py [test] recipe = zc.recipe.testrunner ==1.3.0 eggs = zc.lockfile [test] [py] recipe = zc.recipe.egg eggs = ${test:eggs} interpreter = py zope2.13-2.13.21/source/zc.lockfile/bootstrap.py0000644000175000017500000002352212214017700020227 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess from optparse import OptionParser if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: quote = str # See zc.buildout.easy_install._has_broken_dash_S for motivation and comments. stdout, stderr = subprocess.Popen( [sys.executable, '-Sc', 'try:\n' ' import ConfigParser\n' 'except ImportError:\n' ' print 1\n' 'else:\n' ' print 0\n'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() has_broken_dash_S = bool(int(stdout.strip())) # In order to be more robust in the face of system Pythons, we want to # run without site-packages loaded. This is somewhat tricky, in # particular because Python 2.6's distutils imports site, so starting # with the -S flag is not sufficient. However, we'll start with that: if not has_broken_dash_S and 'site' in sys.modules: # We will restart with python -S. args = sys.argv[:] args[0:0] = [sys.executable, '-S'] args = map(quote, args) os.execv(sys.executable, args) # Now we are running with -S. We'll get the clean sys.path, import site # because distutils will do it later, and then reset the path and clean # out any namespace packages from site-packages that might have been # loaded by .pth files. clean_path = sys.path[:] import site sys.path[:] = clean_path for k, v in sys.modules.items(): if k in ('setuptools', 'pkg_resources') or ( hasattr(v, '__path__') and len(v.__path__)==1 and not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))): # This is a namespace package. Remove it. sys.modules.pop(k) is_jython = sys.platform.startswith('java') setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py' distribute_source = 'http://python-distribute.org/distribute_setup.py' # parsing arguments def normalize_to_url(option, opt_str, value, parser): if value: if '://' not in value: # It doesn't smell like a URL. value = 'file://%s' % ( urllib.pathname2url( os.path.abspath(os.path.expanduser(value))),) if opt_str == '--download-base' and not value.endswith('/'): # Download base needs a trailing slash to make the world happy. value += '/' else: value = None name = opt_str[2:].replace('-', '_') setattr(parser.values, name, value) usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --setup-source and --download-base to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="use_distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("--setup-source", action="callback", dest="setup_source", callback=normalize_to_url, nargs=1, type="string", help=("Specify a URL or file location for the setup file. " "If you use Setuptools, this will default to " + setuptools_source + "; if you use Distribute, this " "will default to " + distribute_source +".")) parser.add_option("--download-base", action="callback", dest="download_base", callback=normalize_to_url, nargs=1, type="string", help=("Specify a URL or directory for downloading " "zc.buildout and either Setuptools or Distribute. " "Defaults to PyPI.")) parser.add_option("--eggs", help=("Specify a directory for storing eggs. Defaults to " "a temporary directory that is deleted when the " "bootstrap script completes.")) parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, help=("Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas.")) parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout's main function if options.config_file is not None: args += ['-c', options.config_file] if options.eggs: eggs_dir = os.path.abspath(os.path.expanduser(options.eggs)) else: eggs_dir = tempfile.mkdtemp() if options.setup_source is None: if options.use_distribute: options.setup_source = distribute_source else: options.setup_source = setuptools_source if options.accept_buildout_test_releases: args.append('buildout:accept-buildout-test-releases=true') args.append('bootstrap') try: import pkg_resources import setuptools # A flag. Sometimes pkg_resources is installed alone. if not hasattr(pkg_resources, '_distribute'): raise ImportError except ImportError: ez_code = urllib2.urlopen( options.setup_source).read().replace('\r\n', '\n') ez = {} exec ez_code in ez setup_args = dict(to_dir=eggs_dir, download_delay=0) if options.download_base: setup_args['download_base'] = options.download_base if options.use_distribute: setup_args['no_fake'] = True ez['use_setuptools'](**setup_args) if 'pkg_resources' in sys.modules: reload(sys.modules['pkg_resources']) import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) cmd = [quote(sys.executable), '-c', quote('from setuptools.command.easy_install import main; main()'), '-mqNxd', quote(eggs_dir)] if not has_broken_dash_S: cmd.insert(1, '-S') find_links = options.download_base if not find_links: find_links = os.environ.get('bootstrap-testing-find-links') if find_links: cmd.extend(['-f', quote(find_links)]) if options.use_distribute: setup_requirement = 'distribute' else: setup_requirement = 'setuptools' ws = pkg_resources.working_set setup_requirement_path = ws.find( pkg_resources.Requirement.parse(setup_requirement)).location env = dict( os.environ, PYTHONPATH=setup_requirement_path) requirement = 'zc.buildout' version = options.version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): for part in parsed_version: if (part[:1] == '*') and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex( search_path=[setup_requirement_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = '=='.join((requirement, version)) cmd.append(requirement) if is_jython: import subprocess exitcode = subprocess.Popen(cmd, env=env).wait() else: # Windows prefers this, apparently; otherwise we would prefer subprocess exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env])) if exitcode != 0: sys.stdout.flush() sys.stderr.flush() print ("An error occurred when trying to install zc.buildout. " "Look above this message for any errors that " "were output by easy_install.") sys.exit(exitcode) ws.add_entry(eggs_dir) ws.require(requirement) import zc.buildout.buildout zc.buildout.buildout.main(args) if not options.eggs: # clean up temporary egg directory shutil.rmtree(eggs_dir) zope2.13-2.13.21/source/zc.lockfile/CHANGES.txt0000644000175000017500000000122012214017700017440 0ustar arnauarnauChange History *************** 1.0.2 (2012-12-02) ================== - Fixed: the fix included in 1.0.1 caused multiple pids to be written to the lock file 1.0.1 (2012-11-30) ================== - Fixed: when there was lock contention, the pid in the lock file was lost. Thanks to Daniel Moisset reporting the problem and providing a fix with tests. - Added test extra to declare test dependency on ``zope.testing``. - Using Python's ``doctest`` module instead of depreacted ``zope.testing.doctest``. 1.0.0 (2008-10-18) ================== - Fixed a small bug in error logging. 1.0.0b1 (2007-07-18) ==================== Initial release zope2.13-2.13.21/source/zc.lockfile/src/0000755000175000017500000000000012214017700016423 5ustar arnauarnauzope2.13-2.13.21/source/zc.lockfile/src/zc/0000755000175000017500000000000012214017700017037 5ustar arnauarnauzope2.13-2.13.21/source/zc.lockfile/src/zc/__init__.py0000644000175000017500000000007012214017700021145 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zc.lockfile/src/zc/lockfile/0000755000175000017500000000000012214017700020627 5ustar arnauarnauzope2.13-2.13.21/source/zc.lockfile/src/zc/lockfile/__init__.py0000644000175000017500000000565712214017700022755 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import os import errno import logging logger = logging.getLogger("zc.lockfile") class LockError(Exception): """Couldn't get a lock """ try: import fcntl except ImportError: try: import msvcrt except ImportError: def _lock_file(file): raise TypeError('No file-locking support on this platform') def _unlock_file(file): raise TypeError('No file-locking support on this platform') else: # Windows def _lock_file(file): # Lock just the first byte try: msvcrt.locking(file.fileno(), msvcrt.LK_NBLCK, 1) except IOError: raise LockError("Couldn't lock %r" % file.name) def _unlock_file(file): try: file.seek(0) msvcrt.locking(file.fileno(), msvcrt.LK_UNLCK, 1) except IOError: raise LockError("Couldn't unlock %r" % file.name) else: # Unix _flags = fcntl.LOCK_EX | fcntl.LOCK_NB def _lock_file(file): try: fcntl.flock(file.fileno(), _flags) except IOError: raise LockError("Couldn't lock %r" % file.name) def _unlock_file(file): # File is automatically unlocked on close pass class LockFile: _fp = None def __init__(self, path): self._path = path try: # Try to open for writing without truncation: fp = open(path, 'r+') except IOError: # If the file doesn't exist, we'll get an IO error, try a+ # Note that there may be a race here. Multiple processes # could fail on the r+ open and open the file a+, but only # one will get the the lock and write a pid. fp = open(path, 'a+') try: _lock_file(fp) except: fp.seek(1) pid = fp.read().strip()[:20] fp.close() if not pid: pid = 'UNKNOWN' logger.exception("Error locking file %s; pid=%s", path, pid) raise self._fp = fp fp.write(" %s\n" % os.getpid()) fp.truncate() fp.flush() def close(self): if self._fp is not None: _unlock_file(self._fp) self._fp.close() self._fp = None zope2.13-2.13.21/source/zc.lockfile/src/zc/lockfile/README.txt0000644000175000017500000000226012214017700022325 0ustar arnauarnauLock file support ================= The ZODB lock_file module provides support for creating file system locks. These are locks that are implemented with lock files and OS-provided locking facilities. To create a lock, instantiate a LockFile object with a file name: >>> import zc.lockfile >>> lock = zc.lockfile.LockFile('lock') If we try to lock the same name, we'll get a lock error: >>> import zope.testing.loggingsupport >>> handler = zope.testing.loggingsupport.InstalledHandler('zc.lockfile') >>> try: ... zc.lockfile.LockFile('lock') ... except zc.lockfile.LockError: ... print "Can't lock file" Can't lock file >>> for record in handler.records: # doctest: +ELLIPSIS ... print record.levelname, record.getMessage() ERROR Error locking file lock; pid=... To release the lock, use it's close method: >>> lock.close() The lock file is not removed. It is left behind: >>> import os >>> os.path.exists('lock') True Of course, now that we've released the lock, we can create it again: >>> lock = zc.lockfile.LockFile('lock') >>> lock.close() .. Cleanup >>> import os >>> os.remove('lock') zope2.13-2.13.21/source/zc.lockfile/src/zc/lockfile/tests.py0000644000175000017500000000502312214017700022343 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.testing import setupstack import os, sys, unittest, doctest import zc.lockfile, time, threading def inc(): while 1: try: lock = zc.lockfile.LockFile('f.lock') except zc.lockfile.LockError: continue else: break f = open('f', 'r+b') v = int(f.readline().strip()) time.sleep(0.01) v += 1 f.seek(0) f.write('%d\n' % v) f.close() lock.close() def many_threads_read_and_write(): r""" >>> open('f', 'w+b').write('0\n') >>> open('f.lock', 'w+b').write('0\n') >>> n = 50 >>> threads = [threading.Thread(target=inc) for i in range(n)] >>> _ = [thread.start() for thread in threads] >>> _ = [thread.join() for thread in threads] >>> saved = int(open('f', 'rb').readline().strip()) >>> saved == n True >>> os.remove('f') We should only have one pid in the lock file: >>> f = open('f.lock') >>> len(f.read().strip().split()) 1 >>> f.close() >>> os.remove('f.lock') """ def pid_in_lockfile(): r""" >>> import os, zc.lockfile >>> pid = os.getpid() >>> lock = zc.lockfile.LockFile("f.lock") >>> f = open("f.lock") >>> f.seek(1) >>> f.read().strip() == str(pid) True >>> f.close() Make sure that locking twice does not overwrite the old pid: >>> lock = zc.lockfile.LockFile("f.lock") Traceback (most recent call last): ... LockError: Couldn't lock 'f.lock' >>> f = open("f.lock") >>> f.seek(1) >>> f.read().strip() == str(pid) True >>> f.close() >>> lock.close() """ def test_suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocFileSuite( 'README.txt', setUp=setupstack.setUpDirectory, tearDown=setupstack.tearDown)) suite.addTest(doctest.DocTestSuite( setUp=setupstack.setUpDirectory, tearDown=setupstack.tearDown)) return suite zope2.13-2.13.21/source/zc.lockfile/src/zc.lockfile.egg-info/0000755000175000017500000000000012214017700022320 5ustar arnauarnauzope2.13-2.13.21/source/zc.lockfile/src/zc.lockfile.egg-info/PKG-INFO0000644000175000017500000000734012214017700023421 0ustar arnauarnauMetadata-Version: 1.0 Name: zc.lockfile Version: 1.0.2 Summary: Basic inter-process locks Home-page: http://www.python.org/pypi/zc.lockfile Author: Jim Fulton Author-email: jim@zope.com License: ZPL 2.1 Description: ************************* Basic inter-process locks ************************* The zc.lockfile package provides a basic portable implementation of interprocess locks using lock files. The purpose if not specifically to lock files, but to simply provide locks with an implementation based on file-locking primitives. Of course, these locks could be used to mediate access to *other* files. For example, the ZODB file storage implementation uses file locks to mediate access to file-storage database files. The database files and lock file files are separate files. .. contents:: Detailed Documentation ********************** Lock file support ================= The ZODB lock_file module provides support for creating file system locks. These are locks that are implemented with lock files and OS-provided locking facilities. To create a lock, instantiate a LockFile object with a file name: >>> import zc.lockfile >>> lock = zc.lockfile.LockFile('lock') If we try to lock the same name, we'll get a lock error: >>> import zope.testing.loggingsupport >>> handler = zope.testing.loggingsupport.InstalledHandler('zc.lockfile') >>> try: ... zc.lockfile.LockFile('lock') ... except zc.lockfile.LockError: ... print "Can't lock file" Can't lock file >>> for record in handler.records: # doctest: +ELLIPSIS ... print record.levelname, record.getMessage() ERROR Error locking file lock; pid=... To release the lock, use it's close method: >>> lock.close() The lock file is not removed. It is left behind: >>> import os >>> os.path.exists('lock') True Of course, now that we've released the lock, we can create it again: >>> lock = zc.lockfile.LockFile('lock') >>> lock.close() .. Cleanup >>> import os >>> os.remove('lock') Change History *************** 1.0.2 (2012-12-02) ================== - Fixed: the fix included in 1.0.1 caused multiple pids to be written to the lock file 1.0.1 (2012-11-30) ================== - Fixed: when there was lock contention, the pid in the lock file was lost. Thanks to Daniel Moisset reporting the problem and providing a fix with tests. - Added test extra to declare test dependency on ``zope.testing``. - Using Python's ``doctest`` module instead of depreacted ``zope.testing.doctest``. 1.0.0 (2008-10-18) ================== - Fixed a small bug in error logging. 1.0.0b1 (2007-07-18) ==================== Initial release Download ********************** Keywords: lock Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows zope2.13-2.13.21/source/zc.lockfile/src/zc.lockfile.egg-info/dependency_links.txt0000644000175000017500000000000112214017700026366 0ustar arnauarnau zope2.13-2.13.21/source/zc.lockfile/src/zc.lockfile.egg-info/requires.txt0000644000175000017500000000003712214017700024720 0ustar arnauarnausetuptools [test] zope.testingzope2.13-2.13.21/source/zc.lockfile/src/zc.lockfile.egg-info/namespace_packages.txt0000644000175000017500000000000312214017700026644 0ustar arnauarnauzc zope2.13-2.13.21/source/zc.lockfile/src/zc.lockfile.egg-info/top_level.txt0000644000175000017500000000000312214017700025043 0ustar arnauarnauzc zope2.13-2.13.21/source/zc.lockfile/src/zc.lockfile.egg-info/SOURCES.txt0000644000175000017500000000071612214017700024210 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zc/__init__.py src/zc.lockfile.egg-info/PKG-INFO src/zc.lockfile.egg-info/SOURCES.txt src/zc.lockfile.egg-info/dependency_links.txt src/zc.lockfile.egg-info/namespace_packages.txt src/zc.lockfile.egg-info/not-zip-safe src/zc.lockfile.egg-info/requires.txt src/zc.lockfile.egg-info/top_level.txt src/zc/lockfile/README.txt src/zc/lockfile/__init__.py src/zc/lockfile/tests.pyzope2.13-2.13.21/source/zc.lockfile/src/zc.lockfile.egg-info/not-zip-safe0000644000175000017500000000000112214017700024546 0ustar arnauarnau zope2.13-2.13.21/source/zope.contenttype/0000755000175000017500000000000012214017553016767 5ustar arnauarnauzope2.13-2.13.21/source/zope.contenttype/setup.py0000644000175000017500000000416412214017553020506 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.contenttype package """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() long_description = ( read('README.txt') + '\n' + read('CHANGES.txt') + '\n' + 'Download\n' '********\n' ) setup( name='zope.contenttype', version='3.5.5', url='http://pypi.python.org/pypi/zope.contenttype', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', license='ZPL 2.1', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Software Development', ], description='Zope contenttype', long_description=long_description, packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope',], install_requires=['setuptools'], test_suite='zope.contenttype', include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/zope.contenttype/PKG-INFO0000644000175000017500000000530312214017553020065 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.contenttype Version: 3.5.5 Summary: Zope contenttype Home-page: http://pypi.python.org/pypi/zope.contenttype Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: zope.contenttype **************** A utility module for content-type handling. .. contents:: Change History ============== 3.5.5 (2011-07-27) ------------------ * Properly restore the HTML snippet detection, by looking at the entire string and not just its start. 3.5.4 (2011-07-26) ------------------ * Restore detection of HTML snippets from 3.4 series. 3.5.3 (2011-03-18) ------------------ * Added new mime types for web fonts, cache manifest and new media formats. 3.5.2 (2011-02-11) ------------------ * LP #717289: added 'video/x-m4v' mimetype for the '.m4v' extension. 3.5.1 (2010-03-23) ------------------ * LP #242321: fix IndexError raised when testing strings consisting solely of leading whitespace. * Updated mime-type for .js to be application/javascript. 3.5.0 (2009-10-22) ------------------ * Moved the implementation of zope.publisher.contenttype to zope.contenttype.parse, moved tests along. 3.4.3 (2009-12-28) ------------------ * Updated mime-type for .js to be application/javascript. 3.4.2 (2009-05-28) ------------------ * Added MS Office 12 types based on: http://www.therightstuff.de/2006/12/16/Office+2007+File+Icons+For+Windows+SharePoint+Services+20+And+SharePoint+Portal+Server+2003.aspx 3.4.1 (2009-02-04) ------------------ * Improved text_type(). Based on the patch from http://www.zope.org/Collectors/Zope/2355/ * Add missing setuptools dependency to setup.py. * Added reference documentation. 3.4.0 (2007-09-13) ------------------ * First stable release as an independent package. Download ******** Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Software Development zope2.13-2.13.21/source/zope.contenttype/pip-egg-info/0000755000175000017500000000000012214017554021251 5ustar arnauarnauzope2.13-2.13.21/source/zope.contenttype/pip-egg-info/zope.contenttype.egg-info/0000755000175000017500000000000012214017554026273 5ustar arnauarnauzope2.13-2.13.21/source/zope.contenttype/pip-egg-info/zope.contenttype.egg-info/PKG-INFO0000644000175000017500000000530312214017554027371 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.contenttype Version: 3.5.5 Summary: Zope contenttype Home-page: http://pypi.python.org/pypi/zope.contenttype Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: zope.contenttype **************** A utility module for content-type handling. .. contents:: Change History ============== 3.5.5 (2011-07-27) ------------------ * Properly restore the HTML snippet detection, by looking at the entire string and not just its start. 3.5.4 (2011-07-26) ------------------ * Restore detection of HTML snippets from 3.4 series. 3.5.3 (2011-03-18) ------------------ * Added new mime types for web fonts, cache manifest and new media formats. 3.5.2 (2011-02-11) ------------------ * LP #717289: added 'video/x-m4v' mimetype for the '.m4v' extension. 3.5.1 (2010-03-23) ------------------ * LP #242321: fix IndexError raised when testing strings consisting solely of leading whitespace. * Updated mime-type for .js to be application/javascript. 3.5.0 (2009-10-22) ------------------ * Moved the implementation of zope.publisher.contenttype to zope.contenttype.parse, moved tests along. 3.4.3 (2009-12-28) ------------------ * Updated mime-type for .js to be application/javascript. 3.4.2 (2009-05-28) ------------------ * Added MS Office 12 types based on: http://www.therightstuff.de/2006/12/16/Office+2007+File+Icons+For+Windows+SharePoint+Services+20+And+SharePoint+Portal+Server+2003.aspx 3.4.1 (2009-02-04) ------------------ * Improved text_type(). Based on the patch from http://www.zope.org/Collectors/Zope/2355/ * Add missing setuptools dependency to setup.py. * Added reference documentation. 3.4.0 (2007-09-13) ------------------ * First stable release as an independent package. Download ******** Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Software Development zope2.13-2.13.21/source/zope.contenttype/pip-egg-info/zope.contenttype.egg-info/dependency_links.txt0000644000175000017500000000000112214017554032341 0ustar arnauarnau zope2.13-2.13.21/source/zope.contenttype/pip-egg-info/zope.contenttype.egg-info/requires.txt0000644000175000017500000000001212214017554030664 0ustar arnauarnausetuptools././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.contenttype/pip-egg-info/zope.contenttype.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.contenttype/pip-egg-info/zope.contenttype.egg-info/namespace_packages.t0000644000175000017500000000000512214017554032245 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.contenttype/pip-egg-info/zope.contenttype.egg-info/top_level.txt0000644000175000017500000000000512214017554031020 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.contenttype/pip-egg-info/zope.contenttype.egg-info/SOURCES.txt0000644000175000017500000000114112214017554030154 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.contenttype.egg-info/PKG-INFO pip-egg-info/zope.contenttype.egg-info/SOURCES.txt pip-egg-info/zope.contenttype.egg-info/dependency_links.txt pip-egg-info/zope.contenttype.egg-info/namespace_packages.txt pip-egg-info/zope.contenttype.egg-info/not-zip-safe pip-egg-info/zope.contenttype.egg-info/requires.txt pip-egg-info/zope.contenttype.egg-info/top_level.txt src/zope/__init__.py src/zope/contenttype/__init__.py src/zope/contenttype/parse.py src/zope/contenttype/tests/__init__.py src/zope/contenttype/tests/testContentTypes.py src/zope/contenttype/tests/test_parse.pyzope2.13-2.13.21/source/zope.contenttype/pip-egg-info/zope.contenttype.egg-info/not-zip-safe0000644000175000017500000000000112214017554030521 0ustar arnauarnau zope2.13-2.13.21/source/zope.contenttype/LICENSE.txt0000644000175000017500000000402612214017553020614 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.contenttype/README.txt0000644000175000017500000000013612214017553020465 0ustar arnauarnauzope.contenttype **************** A utility module for content-type handling. .. contents:: zope2.13-2.13.21/source/zope.contenttype/setup.cfg0000644000175000017500000000007312214017553020610 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.contenttype/COPYRIGHT.txt0000644000175000017500000000004012214017553021072 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.contenttype/buildout.cfg0000644000175000017500000000022712214017553021300 0ustar arnauarnau[buildout] develop = . parts = test find-links = http://download.zope.org/distribution/ [test] recipe = zc.recipe.testrunner eggs = zope.contenttype zope2.13-2.13.21/source/zope.contenttype/bootstrap.py0000644000175000017500000002352212214017553021362 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess from optparse import OptionParser if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: quote = str # See zc.buildout.easy_install._has_broken_dash_S for motivation and comments. stdout, stderr = subprocess.Popen( [sys.executable, '-Sc', 'try:\n' ' import ConfigParser\n' 'except ImportError:\n' ' print 1\n' 'else:\n' ' print 0\n'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() has_broken_dash_S = bool(int(stdout.strip())) # In order to be more robust in the face of system Pythons, we want to # run without site-packages loaded. This is somewhat tricky, in # particular because Python 2.6's distutils imports site, so starting # with the -S flag is not sufficient. However, we'll start with that: if not has_broken_dash_S and 'site' in sys.modules: # We will restart with python -S. args = sys.argv[:] args[0:0] = [sys.executable, '-S'] args = map(quote, args) os.execv(sys.executable, args) # Now we are running with -S. We'll get the clean sys.path, import site # because distutils will do it later, and then reset the path and clean # out any namespace packages from site-packages that might have been # loaded by .pth files. clean_path = sys.path[:] import site sys.path[:] = clean_path for k, v in sys.modules.items(): if k in ('setuptools', 'pkg_resources') or ( hasattr(v, '__path__') and len(v.__path__)==1 and not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))): # This is a namespace package. Remove it. sys.modules.pop(k) is_jython = sys.platform.startswith('java') setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py' distribute_source = 'http://python-distribute.org/distribute_setup.py' # parsing arguments def normalize_to_url(option, opt_str, value, parser): if value: if '://' not in value: # It doesn't smell like a URL. value = 'file://%s' % ( urllib.pathname2url( os.path.abspath(os.path.expanduser(value))),) if opt_str == '--download-base' and not value.endswith('/'): # Download base needs a trailing slash to make the world happy. value += '/' else: value = None name = opt_str[2:].replace('-', '_') setattr(parser.values, name, value) usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --setup-source and --download-base to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="use_distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("--setup-source", action="callback", dest="setup_source", callback=normalize_to_url, nargs=1, type="string", help=("Specify a URL or file location for the setup file. " "If you use Setuptools, this will default to " + setuptools_source + "; if you use Distribute, this " "will default to " + distribute_source +".")) parser.add_option("--download-base", action="callback", dest="download_base", callback=normalize_to_url, nargs=1, type="string", help=("Specify a URL or directory for downloading " "zc.buildout and either Setuptools or Distribute. " "Defaults to PyPI.")) parser.add_option("--eggs", help=("Specify a directory for storing eggs. Defaults to " "a temporary directory that is deleted when the " "bootstrap script completes.")) parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, help=("Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas.")) parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout's main function if options.config_file is not None: args += ['-c', options.config_file] if options.eggs: eggs_dir = os.path.abspath(os.path.expanduser(options.eggs)) else: eggs_dir = tempfile.mkdtemp() if options.setup_source is None: if options.use_distribute: options.setup_source = distribute_source else: options.setup_source = setuptools_source if options.accept_buildout_test_releases: args.append('buildout:accept-buildout-test-releases=true') args.append('bootstrap') try: import pkg_resources import setuptools # A flag. Sometimes pkg_resources is installed alone. if not hasattr(pkg_resources, '_distribute'): raise ImportError except ImportError: ez_code = urllib2.urlopen( options.setup_source).read().replace('\r\n', '\n') ez = {} exec ez_code in ez setup_args = dict(to_dir=eggs_dir, download_delay=0) if options.download_base: setup_args['download_base'] = options.download_base if options.use_distribute: setup_args['no_fake'] = True ez['use_setuptools'](**setup_args) if 'pkg_resources' in sys.modules: reload(sys.modules['pkg_resources']) import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) cmd = [quote(sys.executable), '-c', quote('from setuptools.command.easy_install import main; main()'), '-mqNxd', quote(eggs_dir)] if not has_broken_dash_S: cmd.insert(1, '-S') find_links = options.download_base if not find_links: find_links = os.environ.get('bootstrap-testing-find-links') if find_links: cmd.extend(['-f', quote(find_links)]) if options.use_distribute: setup_requirement = 'distribute' else: setup_requirement = 'setuptools' ws = pkg_resources.working_set setup_requirement_path = ws.find( pkg_resources.Requirement.parse(setup_requirement)).location env = dict( os.environ, PYTHONPATH=setup_requirement_path) requirement = 'zc.buildout' version = options.version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): for part in parsed_version: if (part[:1] == '*') and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex( search_path=[setup_requirement_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = '=='.join((requirement, version)) cmd.append(requirement) if is_jython: import subprocess exitcode = subprocess.Popen(cmd, env=env).wait() else: # Windows prefers this, apparently; otherwise we would prefer subprocess exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env])) if exitcode != 0: sys.stdout.flush() sys.stderr.flush() print ("An error occurred when trying to install zc.buildout. " "Look above this message for any errors that " "were output by easy_install.") sys.exit(exitcode) ws.add_entry(eggs_dir) ws.require(requirement) import zc.buildout.buildout zc.buildout.buildout.main(args) if not options.eggs: # clean up temporary egg directory shutil.rmtree(eggs_dir) zope2.13-2.13.21/source/zope.contenttype/CHANGES.txt0000644000175000017500000000266412214017553020610 0ustar arnauarnauChange History ============== 3.5.5 (2011-07-27) ------------------ * Properly restore the HTML snippet detection, by looking at the entire string and not just its start. 3.5.4 (2011-07-26) ------------------ * Restore detection of HTML snippets from 3.4 series. 3.5.3 (2011-03-18) ------------------ * Added new mime types for web fonts, cache manifest and new media formats. 3.5.2 (2011-02-11) ------------------ * LP #717289: added 'video/x-m4v' mimetype for the '.m4v' extension. 3.5.1 (2010-03-23) ------------------ * LP #242321: fix IndexError raised when testing strings consisting solely of leading whitespace. * Updated mime-type for .js to be application/javascript. 3.5.0 (2009-10-22) ------------------ * Moved the implementation of zope.publisher.contenttype to zope.contenttype.parse, moved tests along. 3.4.3 (2009-12-28) ------------------ * Updated mime-type for .js to be application/javascript. 3.4.2 (2009-05-28) ------------------ * Added MS Office 12 types based on: http://www.therightstuff.de/2006/12/16/Office+2007+File+Icons+For+Windows+SharePoint+Services+20+And+SharePoint+Portal+Server+2003.aspx 3.4.1 (2009-02-04) ------------------ * Improved text_type(). Based on the patch from http://www.zope.org/Collectors/Zope/2355/ * Add missing setuptools dependency to setup.py. * Added reference documentation. 3.4.0 (2007-09-13) ------------------ * First stable release as an independent package. zope2.13-2.13.21/source/zope.contenttype/src/0000755000175000017500000000000012214017553017556 5ustar arnauarnauzope2.13-2.13.21/source/zope.contenttype/src/zope/0000755000175000017500000000000012214017553020533 5ustar arnauarnauzope2.13-2.13.21/source/zope.contenttype/src/zope/__init__.py0000644000175000017500000000007012214017553022641 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.contenttype/src/zope/contenttype/0000755000175000017500000000000012214017553023107 5ustar arnauarnauzope2.13-2.13.21/source/zope.contenttype/src/zope/contenttype/mime.types0000644000175000017500000000500112214017553025120 0ustar arnauarnauapplication-x-cdf cdf application/msword doc dot wiz application/pdf pdf application/pkcs7-mime p7c application/vnd.mozilla.xul+xml xul application/vnd.ms-excel xlb xls application/vnd.ms-fontobject eot application/vnd.ms-powerpoint pot ppa pps ppt pwz application/javascript js application/x-font-woff woff application/x-pkcs12 p12 pfx application/x-pn-realaudio ram audio/mpeg mp3 audio/x-pn-realaudio ra font/opentype otf font/truetype ttf image/pict pct pic pict image/svg+xml svg svgz image/vnd.microsoft.icon ico image/webp webp message/rfc822 eml nws mht mhtml text/cache-manifest manifest text/css css text/plain bat c h pl ksh text/x-vcard vcf text/xml xml xsl video/mp4 mp4 video/ogg ogv video/mpeg m1v mpa video/webm webm video/x-m4v m4v application/vnd.ms-word.document.macroEnabled.12 docm application/vnd.openxmlformats-officedocument.wordprocessingml.document docx application/vnd.ms-word.template.macroEnabled.12 dotm application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx application/vnd.ms-powerpoint.template.macroEnabled.12 potm application/vnd.openxmlformats-officedocument.presentationml.template potx application/vnd.ms-powerpoint.addin.macroEnabled.12 ppam application/vnd.ms-powerpoint.slideshow.macroEnabled.12 ppsm application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx application/vnd.ms-powerpoint.presentation.macroEnabled.12 pptm application/vnd.openxmlformats-officedocument.presentationml.presentation pptx application/vnd.ms-excel.addin.macroEnabled.12 xlam application/vnd.ms-excel.sheet.binary.macroEnabled.12 xlsb application/vnd.ms-excel.sheet.macroEnabled.12 xlsm application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx application/vnd.ms-excel.template.macroEnabled.12 xltm application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx zope2.13-2.13.21/source/zope.contenttype/src/zope/contenttype/parse.py0000644000175000017500000000771012214017553024600 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """MIME Content-Type parsing helper functions. This supports parsing RFC 1341 Content-Type values, including quoted-string values as defined in RFC 822. """ __docformat__ = "reStructuredText" import re # TODO: This still needs to support comments in structured fields as # specified in RFC 2822. def parse(string): major, minor, params = parseOrdered(string) d = {} for (name, value) in params: d[name] = value return major, minor, d def parseOrdered(string): if ";" in string: type, params = string.split(";", 1) params = _parse_params(params) else: type = string params = [] if "/" not in type: raise ValueError("content type missing major/minor parts: %r" % type) type = type.strip() major, minor = type.lower().split("/", 1) return _check_token(major.strip()), _check_token(minor.strip()), params def _parse_params(string): result = [] string = string.strip() while string: if not "=" in string: raise ValueError("parameter values are not optional") name, rest = string.split("=", 1) name = _check_token(name.strip().lower()) rest = rest.strip() # rest is: value *[";" parameter] if rest[:1] == '"': # quoted-string, defined in RFC 822. m = _quoted_string_match(rest) if m is None: raise ValueError("invalid quoted-string in %r" % rest) value = m.group() rest = rest[m.end():].strip() #import pdb; pdb.set_trace() if rest[:1] not in ("", ";"): raise ValueError( "invalid token following quoted-string: %r" % rest) rest = rest[1:] value = _unescape(value) elif ";" in rest: value, rest = rest.split(";") value = _check_token(value.strip()) else: value = _check_token(rest.strip()) rest = "" result.append((name, value)) string = rest.strip() return result def _quoted_string_match(string): # This support RFC 822 quoted-string values. global _quoted_string_match _quoted_string_match = re.compile( '"(?:\\\\.|[^"\n\r\\\\])*"', re.DOTALL).match return _quoted_string_match(string) def _check_token(string): if _token_match(string) is None: raise ValueError('"%s" is not a valid token' % string) return string def _token_match(string): global _token_match _token_match = re.compile("[^][ \t\n\r()<>@,;:\"/?=\\\\]+$").match return _token_match(string) def _unescape(string): assert string[0] == '"' assert string[-1] == '"' string = string[1:-1] if "\\" in string: string = re.sub(r"\\(.)", r"\1", string) return string def join((major, minor, params)): pstr = "" try: params.items except AttributeError: pass else: params = params.items() # ensure a predictable order: params.sort() for name, value in params: pstr += ";%s=%s" % (name, _escape(value)) return "%s/%s%s" % (major, minor, pstr) def _escape(string): try: return _check_token(string) except ValueError: # '\\' must be first for c in '\\"\n\r': string = string.replace(c, "\\" + c) return '"%s"' % string zope2.13-2.13.21/source/zope.contenttype/src/zope/contenttype/__init__.py0000644000175000017500000000727112214017553025227 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """A utility module for content-type handling. """ import re import os.path import mimetypes find_binary = re.compile('[\0-\7]').search def text_type(s): """Given an unnamed piece of text, try to guess its content type. Detects HTML, XML, and plain text. Returns a MIME type string such as 'text/html'. """ # at least the maximum length of any tags we look for max_tags = 14 s2 = s.strip()[:max_tags].lower() if len(s2) == max_tags: if s2.startswith(''): return 'text/html' if s2.startswith(' ? other () chars\t") self.assertEqual( self._callFUT('a/b;c= " This [] has <> ? other () chars\t" '), ("a", "b", p)) self.assertEqual( self._callFUT('a/b;c=""'), ("a", "b", self.oneParam("c", ""))) self.assertEqual( self._callFUT(r'a/b;c="\\\""'), ("a", "b", self.oneParam("c", r'\"'))) class ParseTestCase(ParseOrderedTestCase): empty_params = {} def _callFUT(self, *args, **kw): from zope.contenttype.parse import parse return parse(*args, **kw) def oneParam(self, name, value): return {name: value} def test_multiple_parameters(self): self.assertEqual( self._callFUT("text/plain;charset=utf-8;format=flowed"), ("text", "plain", {"charset": "utf-8", "format": "flowed"})) self.assertEqual( self._callFUT('text/plain;charset=utf-8;format="flowed"'), ("text", "plain", {"charset": "utf-8", "format": "flowed"})) class JoinTestCase(unittest.TestCase): def _callFUT(self, *args, **kw): from zope.contenttype.parse import join return join(*args, **kw) def test_without_params(self): self.assertEqual(self._callFUT(("text", "plain", [])), "text/plain") self.assertEqual(self._callFUT(("text", "plain", {})), "text/plain") def test_single_token_param(self): self.assertEqual( self._callFUT(("text", "plain", [("charset", "UTF-8")])), "text/plain;charset=UTF-8") self.assertEqual( self._callFUT(("text", "plain", {"charset": "UTF-8"})), "text/plain;charset=UTF-8") def test_multi_params_list_maintains_order(self): # multiple parameters given as a list maintain order: self.assertEqual( self._callFUT(("text", "plain", [("charset", "UTF-8"), ("format", "flowed")])), "text/plain;charset=UTF-8;format=flowed") self.assertEqual( self._callFUT(("text", "plain", [("format", "flowed"), ("charset", "UTF-8")])), "text/plain;format=flowed;charset=UTF-8") def test_multi_params_dict_sorted_order(self): # multiple parameters given as a dict are sorted by param name: self.assertEqual( self._callFUT(("text", "plain", {"charset": "UTF-8", "format": "flowed"})), "text/plain;charset=UTF-8;format=flowed") def test_params_list_quoted(self): # parameter values are quoted automatically: self.assertEqual(self._callFUT(("a", "b", [("c", "")])), 'a/b;c=""') self.assertEqual(self._callFUT(("a", "b", [("c", "ab cd")])), 'a/b;c="ab cd"') self.assertEqual(self._callFUT(("a", "b", [("c", " \t")])), 'a/b;c=" \t"') self.assertEqual(self._callFUT(("a", "b", [("c", '"')])), r'a/b;c="\""') self.assertEqual(self._callFUT(("a", "b", [("c", "\n")])), 'a/b;c="\\\n"') def test_params_dict_quoted(self): # parameter values are quoted automatically: self.assertEqual(self._callFUT(("a", "b", {"c": ""})), 'a/b;c=""') self.assertEqual(self._callFUT(("a", "b", {"c": "ab cd"})), 'a/b;c="ab cd"') self.assertEqual(self._callFUT(("a", "b", {"c": " \t"})), 'a/b;c=" \t"') self.assertEqual(self._callFUT(("a", "b", {"c": '"'})), r'a/b;c="\""') self.assertEqual(self._callFUT(("a", "b", {"c": "\n"})), 'a/b;c="\\\n"') def test_suite(): return unittest.TestSuite(( unittest.makeSuite(ParseOrderedTestCase), unittest.makeSuite(ParseTestCase), unittest.makeSuite(JoinTestCase), )) zope2.13-2.13.21/source/zope.contenttype/src/zope/contenttype/tests/testContentTypes.py0000644000175000017500000000771612214017553030175 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the contenttypes extension mechanism. """ import unittest class ContentTypesTestCase(unittest.TestCase): def setUp(self): import mimetypes mimetypes.init() self._old_state = mimetypes.__dict__.copy() def tearDown(self): import mimetypes mimetypes.__dict__.clear() mimetypes.__dict__.update(self._old_state) def _check_types_count(self, delta): import mimetypes self.assertEqual(len(mimetypes.types_map), len(self._old_state["types_map"]) + delta) def _getFilename(self, name): import os.path here = os.path.dirname(os.path.abspath(__file__)) return os.path.join(here, name) def test_add_one_file(self): from zope.contenttype import add_files from zope.contenttype import guess_content_type filename = self._getFilename('mime.types-1') add_files([filename]) ctype, encoding = guess_content_type("foo.ztmt-1") self.assert_(encoding is None) self.assertEqual(ctype, "text/x-vnd.zope.test-mime-type-1") ctype, encoding = guess_content_type("foo.ztmt-1.gz") self.assertEqual(encoding, "gzip") self.assertEqual(ctype, "text/x-vnd.zope.test-mime-type-1") self._check_types_count(1) def test_add_two_files(self): from zope.contenttype import add_files from zope.contenttype import guess_content_type filename1 = self._getFilename('mime.types-1') filename2 = self._getFilename('mime.types-2') add_files([filename1, filename2]) ctype, encoding = guess_content_type("foo.ztmt-1") self.assert_(encoding is None) self.assertEqual(ctype, "text/x-vnd.zope.test-mime-type-1") ctype, encoding = guess_content_type("foo.ztmt-2") self.assert_(encoding is None) self.assertEqual(ctype, "text/x-vnd.zope.test-mime-type-2") self._check_types_count(2) def test_text_type(self): HTML = 'hello world' from zope.contenttype import text_type self.assertEqual(text_type(HTML), 'text/html') self.assertEqual(text_type(''), 'text/xml') self.assertEqual(text_type(''), 'text/xml') self.assertEqual(text_type('foo bar'), 'text/plain') self.assertEqual(text_type(''), 'text/html') self.assertEqual(text_type('\n\n\n'), 'text/html') # we can also parse text snippets self.assertEqual(text_type('

Hello

'), 'text/html') longtext = 'abc ' * 100 self.assertEqual(text_type('

%s

' % longtext), 'text/html') # See https://bugs.launchpad.net/bugs/487998 self.assertEqual(text_type(' ' * 14 + HTML), 'text/html') self.assertEqual(text_type(' ' * 14 + 'abc'), 'text/plain') self.assertEqual(text_type(' ' * 14), 'text/plain') def test_suite(): return unittest.makeSuite(ContentTypesTestCase) zope2.13-2.13.21/source/zope.contenttype/src/zope/contenttype/tests/__init__.py0000644000175000017500000000000012214017553026350 0ustar arnauarnauzope2.13-2.13.21/source/zope.contenttype/src/zope/contenttype/tests/mime.types-20000644000175000017500000000031312214017553026422 0ustar arnauarnau# This is a sample mime.types file. # It contains a single bogus MIME type and extention for testing # purposes. It is not loaded during normal Zope operation. text/x-vnd.zope.test-mime-type-2 ztmt-2 zope2.13-2.13.21/source/zope.contenttype/src/zope/contenttype/tests/mime.types-10000644000175000017500000000031312214017553026421 0ustar arnauarnau# This is a sample mime.types file. # It contains a single bogus MIME type and extention for testing # purposes. It is not loaded during normal Zope operation. text/x-vnd.zope.test-mime-type-1 ztmt-1 zope2.13-2.13.21/source/zope.contenttype/src/zope.contenttype.egg-info/0000755000175000017500000000000012214017553024600 5ustar arnauarnauzope2.13-2.13.21/source/zope.contenttype/src/zope.contenttype.egg-info/PKG-INFO0000644000175000017500000000530312214017553025676 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.contenttype Version: 3.5.5 Summary: Zope contenttype Home-page: http://pypi.python.org/pypi/zope.contenttype Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: zope.contenttype **************** A utility module for content-type handling. .. contents:: Change History ============== 3.5.5 (2011-07-27) ------------------ * Properly restore the HTML snippet detection, by looking at the entire string and not just its start. 3.5.4 (2011-07-26) ------------------ * Restore detection of HTML snippets from 3.4 series. 3.5.3 (2011-03-18) ------------------ * Added new mime types for web fonts, cache manifest and new media formats. 3.5.2 (2011-02-11) ------------------ * LP #717289: added 'video/x-m4v' mimetype for the '.m4v' extension. 3.5.1 (2010-03-23) ------------------ * LP #242321: fix IndexError raised when testing strings consisting solely of leading whitespace. * Updated mime-type for .js to be application/javascript. 3.5.0 (2009-10-22) ------------------ * Moved the implementation of zope.publisher.contenttype to zope.contenttype.parse, moved tests along. 3.4.3 (2009-12-28) ------------------ * Updated mime-type for .js to be application/javascript. 3.4.2 (2009-05-28) ------------------ * Added MS Office 12 types based on: http://www.therightstuff.de/2006/12/16/Office+2007+File+Icons+For+Windows+SharePoint+Services+20+And+SharePoint+Portal+Server+2003.aspx 3.4.1 (2009-02-04) ------------------ * Improved text_type(). Based on the patch from http://www.zope.org/Collectors/Zope/2355/ * Add missing setuptools dependency to setup.py. * Added reference documentation. 3.4.0 (2007-09-13) ------------------ * First stable release as an independent package. Download ******** Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Software Development zope2.13-2.13.21/source/zope.contenttype/src/zope.contenttype.egg-info/dependency_links.txt0000644000175000017500000000000112214017553030646 0ustar arnauarnau zope2.13-2.13.21/source/zope.contenttype/src/zope.contenttype.egg-info/requires.txt0000644000175000017500000000001212214017553027171 0ustar arnauarnausetuptoolszope2.13-2.13.21/source/zope.contenttype/src/zope.contenttype.egg-info/namespace_packages.txt0000644000175000017500000000000512214017553031126 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.contenttype/src/zope.contenttype.egg-info/top_level.txt0000644000175000017500000000000512214017553027325 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.contenttype/src/zope.contenttype.egg-info/SOURCES.txt0000644000175000017500000000132112214017553026461 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.contenttype.egg-info/PKG-INFO src/zope.contenttype.egg-info/SOURCES.txt src/zope.contenttype.egg-info/dependency_links.txt src/zope.contenttype.egg-info/namespace_packages.txt src/zope.contenttype.egg-info/not-zip-safe src/zope.contenttype.egg-info/requires.txt src/zope.contenttype.egg-info/top_level.txt src/zope/contenttype/__init__.py src/zope/contenttype/mime.types src/zope/contenttype/parse.py src/zope/contenttype/tests/__init__.py src/zope/contenttype/tests/mime.types-1 src/zope/contenttype/tests/mime.types-2 src/zope/contenttype/tests/testContentTypes.py src/zope/contenttype/tests/test_parse.pyzope2.13-2.13.21/source/zope.contenttype/src/zope.contenttype.egg-info/not-zip-safe0000644000175000017500000000000112214017553027026 0ustar arnauarnau zope2.13-2.13.21/source/zope.tales/0000755000175000017500000000000012214017647015527 5ustar arnauarnauzope2.13-2.13.21/source/zope.tales/setup.py0000644000175000017500000000464212214017647017247 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.tales package $Id: setup.py 128536 2012-12-06 17:16:18Z menesis $ """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.tales', version='3.5.3', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', description='Zope Template Application Language Expression Syntax ' '(TALES)', long_description=( read('README.txt') + '\n\n' + read('CHANGES.txt') ), keywords = "zope template xml tales", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.tales', license='ZPL 2.1', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope'], extras_require = dict( test=['zope.testing', ]), install_requires=[ 'setuptools', 'zope.interface', 'zope.tal'], include_package_data = True, zip_safe = False, ) zope2.13-2.13.21/source/zope.tales/PKG-INFO0000644000175000017500000000425512214017647016632 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.tales Version: 3.5.3 Summary: Zope Template Application Language Expression Syntax (TALES) Home-page: http://pypi.python.org/pypi/zope.tales Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== Template Attribute Language - Expression Syntax See http://wiki.zope.org/ZPT/TALESSpecification13 CHANGES ======= 3.5.3 (2012-12-06) ------------------ - Fixed URL for TALES 1.3 spec in README. https://bugs.launchpad.net/bugs/1004025 3.5.2 (2012-05-23) ------------------ - Subexpressions of a 'string:' expression can be only path expressions. https://bugs.launchpad.net/zope.tales/+bug/1002242 3.5.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctestunit' in favor of stdlib's 'doctest. 3.5.0 (2010-01-01) ------------------ - Ported the lazy expression from Products.PageTemplates. 3.4.0 (2007-10-03) ------------------ - Updated package setup. - Initial release outside the Zope 3 trunk. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the zope.tales package shipped as part of the Zope 3.2.0 release. - Documentation / test fixes. 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the zope.tales package shipped as part of the Zope X3.0.0 release. Keywords: zope template xml tales Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.tales/pip-egg-info/0000755000175000017500000000000012214017647020010 5ustar arnauarnauzope2.13-2.13.21/source/zope.tales/pip-egg-info/zope.tales.egg-info/0000755000175000017500000000000012214017647023566 5ustar arnauarnauzope2.13-2.13.21/source/zope.tales/pip-egg-info/zope.tales.egg-info/PKG-INFO0000644000175000017500000000425512214017647024671 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.tales Version: 3.5.3 Summary: Zope Template Application Language Expression Syntax (TALES) Home-page: http://pypi.python.org/pypi/zope.tales Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== Template Attribute Language - Expression Syntax See http://wiki.zope.org/ZPT/TALESSpecification13 CHANGES ======= 3.5.3 (2012-12-06) ------------------ - Fixed URL for TALES 1.3 spec in README. https://bugs.launchpad.net/bugs/1004025 3.5.2 (2012-05-23) ------------------ - Subexpressions of a 'string:' expression can be only path expressions. https://bugs.launchpad.net/zope.tales/+bug/1002242 3.5.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctestunit' in favor of stdlib's 'doctest. 3.5.0 (2010-01-01) ------------------ - Ported the lazy expression from Products.PageTemplates. 3.4.0 (2007-10-03) ------------------ - Updated package setup. - Initial release outside the Zope 3 trunk. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the zope.tales package shipped as part of the Zope 3.2.0 release. - Documentation / test fixes. 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the zope.tales package shipped as part of the Zope X3.0.0 release. Keywords: zope template xml tales Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.tales/pip-egg-info/zope.tales.egg-info/dependency_links.txt0000644000175000017500000000000112214017647027634 0ustar arnauarnau zope2.13-2.13.21/source/zope.tales/pip-egg-info/zope.tales.egg-info/requires.txt0000644000175000017500000000006712214017647026171 0ustar arnauarnausetuptools zope.interface zope.tal [test] zope.testingzope2.13-2.13.21/source/zope.tales/pip-egg-info/zope.tales.egg-info/namespace_packages.txt0000644000175000017500000000000512214017647030114 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.tales/pip-egg-info/zope.tales.egg-info/top_level.txt0000644000175000017500000000000512214017647026313 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.tales/pip-egg-info/zope.tales.egg-info/SOURCES.txt0000644000175000017500000000132412214017647025452 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.tales.egg-info/PKG-INFO pip-egg-info/zope.tales.egg-info/SOURCES.txt pip-egg-info/zope.tales.egg-info/dependency_links.txt pip-egg-info/zope.tales.egg-info/namespace_packages.txt pip-egg-info/zope.tales.egg-info/not-zip-safe pip-egg-info/zope.tales.egg-info/requires.txt pip-egg-info/zope.tales.egg-info/top_level.txt src/zope/__init__.py src/zope/tales/__init__.py src/zope/tales/engine.py src/zope/tales/expressions.py src/zope/tales/interfaces.py src/zope/tales/pythonexpr.py src/zope/tales/tales.py src/zope/tales/tests/__init__.py src/zope/tales/tests/simpleexpr.py src/zope/tales/tests/test_expressions.py src/zope/tales/tests/test_tales.py src/zope/tales/tests/test_traverser.pyzope2.13-2.13.21/source/zope.tales/pip-egg-info/zope.tales.egg-info/not-zip-safe0000644000175000017500000000000112214017647026014 0ustar arnauarnau zope2.13-2.13.21/source/zope.tales/LICENSE.txt0000644000175000017500000000402612214017647017354 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.tales/README.txt0000644000175000017500000000016612214017647017230 0ustar arnauarnauOverview ======== Template Attribute Language - Expression Syntax See http://wiki.zope.org/ZPT/TALESSpecification13 zope2.13-2.13.21/source/zope.tales/setup.cfg0000644000175000017500000000007312214017647017350 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.tales/COPYRIGHT.txt0000644000175000017500000000004012214017647017632 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.tales/buildout.cfg0000644000175000017500000000013412214017647020035 0ustar arnauarnau[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = zope.tales zope2.13-2.13.21/source/zope.tales/bootstrap.py0000644000175000017500000000742212214017647020123 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 111793 2010-04-30 22:15:26Z hannosch $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.tales/CHANGES.txt0000644000175000017500000000165512214017647017347 0ustar arnauarnauCHANGES ======= 3.5.3 (2012-12-06) ------------------ - Fixed URL for TALES 1.3 spec in README. https://bugs.launchpad.net/bugs/1004025 3.5.2 (2012-05-23) ------------------ - Subexpressions of a 'string:' expression can be only path expressions. https://bugs.launchpad.net/zope.tales/+bug/1002242 3.5.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctestunit' in favor of stdlib's 'doctest. 3.5.0 (2010-01-01) ------------------ - Ported the lazy expression from Products.PageTemplates. 3.4.0 (2007-10-03) ------------------ - Updated package setup. - Initial release outside the Zope 3 trunk. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the zope.tales package shipped as part of the Zope 3.2.0 release. - Documentation / test fixes. 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the zope.tales package shipped as part of the Zope X3.0.0 release. zope2.13-2.13.21/source/zope.tales/src/0000755000175000017500000000000012214017647016316 5ustar arnauarnauzope2.13-2.13.21/source/zope.tales/src/zope.tales.egg-info/0000755000175000017500000000000012214017647022074 5ustar arnauarnauzope2.13-2.13.21/source/zope.tales/src/zope.tales.egg-info/PKG-INFO0000644000175000017500000000425512214017647023177 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.tales Version: 3.5.3 Summary: Zope Template Application Language Expression Syntax (TALES) Home-page: http://pypi.python.org/pypi/zope.tales Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== Template Attribute Language - Expression Syntax See http://wiki.zope.org/ZPT/TALESSpecification13 CHANGES ======= 3.5.3 (2012-12-06) ------------------ - Fixed URL for TALES 1.3 spec in README. https://bugs.launchpad.net/bugs/1004025 3.5.2 (2012-05-23) ------------------ - Subexpressions of a 'string:' expression can be only path expressions. https://bugs.launchpad.net/zope.tales/+bug/1002242 3.5.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctestunit' in favor of stdlib's 'doctest. 3.5.0 (2010-01-01) ------------------ - Ported the lazy expression from Products.PageTemplates. 3.4.0 (2007-10-03) ------------------ - Updated package setup. - Initial release outside the Zope 3 trunk. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the zope.tales package shipped as part of the Zope 3.2.0 release. - Documentation / test fixes. 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the zope.tales package shipped as part of the Zope X3.0.0 release. Keywords: zope template xml tales Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.tales/src/zope.tales.egg-info/dependency_links.txt0000644000175000017500000000000112214017647026142 0ustar arnauarnau zope2.13-2.13.21/source/zope.tales/src/zope.tales.egg-info/requires.txt0000644000175000017500000000006712214017647024477 0ustar arnauarnausetuptools zope.interface zope.tal [test] zope.testingzope2.13-2.13.21/source/zope.tales/src/zope.tales.egg-info/namespace_packages.txt0000644000175000017500000000000512214017647026422 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.tales/src/zope.tales.egg-info/top_level.txt0000644000175000017500000000000512214017647024621 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.tales/src/zope.tales.egg-info/SOURCES.txt0000644000175000017500000000132412214017647023760 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.tales.egg-info/PKG-INFO src/zope.tales.egg-info/SOURCES.txt src/zope.tales.egg-info/dependency_links.txt src/zope.tales.egg-info/namespace_packages.txt src/zope.tales.egg-info/not-zip-safe src/zope.tales.egg-info/requires.txt src/zope.tales.egg-info/top_level.txt src/zope/tales/__init__.py src/zope/tales/engine.py src/zope/tales/expressions.py src/zope/tales/interfaces.py src/zope/tales/pythonexpr.py src/zope/tales/tales.py src/zope/tales/tests/__init__.py src/zope/tales/tests/simpleexpr.py src/zope/tales/tests/test_expressions.py src/zope/tales/tests/test_tales.py src/zope/tales/tests/test_traverser.pyzope2.13-2.13.21/source/zope.tales/src/zope.tales.egg-info/not-zip-safe0000644000175000017500000000000112214017647024322 0ustar arnauarnau zope2.13-2.13.21/source/zope.tales/src/zope/0000755000175000017500000000000012214017647017273 5ustar arnauarnauzope2.13-2.13.21/source/zope.tales/src/zope/__init__.py0000644000175000017500000000007012214017647021401 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.tales/src/zope/tales/0000755000175000017500000000000012214017647020403 5ustar arnauarnauzope2.13-2.13.21/source/zope.tales/src/zope/tales/expressions.py0000644000175000017500000002630412214017647023344 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Basic Page Template expression types. $Id: expressions.py 126816 2012-06-11 19:00:21Z tseaver $ """ import re, types from zope.interface import implements from zope.tales.tales import _valid_name, _parse_expr, NAME_RE, Undefined from zope.tales.interfaces import ITALESExpression, ITALESFunctionNamespace Undefs = (Undefined, AttributeError, LookupError, TypeError) _marker = object() namespace_re = re.compile(r'(\w+):(.+)') def simpleTraverse(object, path_items, econtext): """Traverses a sequence of names, first trying attributes then items. """ for name in path_items: next = getattr(object, name, _marker) if next is not _marker: object = next elif hasattr(object, '__getitem__'): object = object[name] else: # Allow AttributeError to propagate object = getattr(object, name) return object class SubPathExpr(object): def __init__(self, path, traverser, engine): self._traverser = traverser self._engine = engine # Parse path compiledpath = [] currentpath = [] for element in str(path).strip().split('/'): if not element: raise engine.getCompilerError()( 'Path element may not be empty in %r' % path) if element.startswith('?'): if currentpath: compiledpath.append(tuple(currentpath)) currentpath = [] if not _valid_name(element[1:]): raise engine.getCompilerError()( 'Invalid variable name "%s"' % element[1:]) compiledpath.append(element[1:]) else: match = namespace_re.match(element) if match: if currentpath: compiledpath.append(tuple(currentpath)) currentpath = [] namespace, functionname = match.groups() if not _valid_name(namespace): raise engine.getCompilerError()( 'Invalid namespace name "%s"' % namespace) try: compiledpath.append( self._engine.getFunctionNamespace(namespace)) except KeyError: raise engine.getCompilerError()( 'Unknown namespace "%s"' % namespace) currentpath.append(functionname) else: currentpath.append(element) if currentpath: compiledpath.append(tuple(currentpath)) first = compiledpath[0] base = first[0] if callable(first): # check for initial function raise engine.getCompilerError()( 'Namespace function specified in first subpath element') elif isinstance(first, basestring): # check for initial ? raise engine.getCompilerError()( 'Dynamic name specified in first subpath element') if base and not _valid_name(base): raise engine.getCompilerError()( 'Invalid variable name "%s"' % element) self._base = base compiledpath[0] = first[1:] self._compiled_path = tuple(compiledpath) def _eval(self, econtext, isinstance=isinstance): vars = econtext.vars compiled_path = self._compiled_path base = self._base if base == 'CONTEXTS' or not base: # Special base name ob = econtext.contexts else: ob = vars[base] if isinstance(ob, DeferWrapper): ob = ob() for element in compiled_path: if isinstance(element, tuple): ob = self._traverser(ob, element, econtext) elif isinstance(element, basestring): val = vars[element] # If the value isn't a string, assume it's a sequence # of path names. if isinstance(val, basestring): val = (val,) ob = self._traverser(ob, val, econtext) elif callable(element): ob = element(ob) # TODO: Once we have n-ary adapters, use them. if ITALESFunctionNamespace.providedBy(ob): ob.setEngine(econtext) else: raise ValueError(repr(element)) return ob class PathExpr(object): """One or more subpath expressions, separated by '|'.""" implements(ITALESExpression) # _default_type_names contains the expression type names this # class is usually registered for. _default_type_names = ( 'standard', 'path', 'exists', 'nocall', ) def __init__(self, name, expr, engine, traverser=simpleTraverse): self._s = expr self._name = name self._hybrid = False paths = expr.split('|') self._subexprs = [] add = self._subexprs.append for i in range(len(paths)): path = paths[i].lstrip() if _parse_expr(path): # This part is the start of another expression type, # so glue it back together and compile it. add(engine.compile('|'.join(paths[i:]).lstrip())) self._hybrid = True break add(SubPathExpr(path, traverser, engine)._eval) def _exists(self, econtext): for expr in self._subexprs: try: expr(econtext) except Undefs: pass else: return 1 return 0 def _eval(self, econtext): for expr in self._subexprs[:-1]: # Try all but the last subexpression, skipping undefined ones. try: ob = expr(econtext) except Undefs: pass else: break else: # On the last subexpression allow exceptions through, and # don't autocall if the expression was not a subpath. ob = self._subexprs[-1](econtext) if self._hybrid: return ob if self._name == 'nocall': return ob # Call the object if it is callable. Note that checking for # callable() isn't safe because the object might be security # proxied (and security proxies report themselves callable, no # matter what the underlying object is). We therefore check # for the __call__ attribute, but not with hasattr as that # eats babies, err, exceptions. In addition to that, we # support calling old style classes which don't have a # __call__. if (getattr(ob, '__call__', _marker) is not _marker or isinstance(ob, types.ClassType)): return ob() return ob def __call__(self, econtext): if self._name == 'exists': return self._exists(econtext) return self._eval(econtext) def __str__(self): return '%s expression (%s)' % (self._name, `self._s`) def __repr__(self): return '' % (self._name, `self._s`) _interp = re.compile( r'\$(%(n)s)|\${(%(n)s(?:/[^}|]*)*(?:\|%(n)s(?:/[^}|]*)*)*)}' % {'n': NAME_RE}) class StringExpr(object): implements(ITALESExpression) def __init__(self, name, expr, engine): self._s = expr if '%' in expr: expr = expr.replace('%', '%%') self._vars = vars = [] if '$' in expr: # Use whatever expr type is registered as "path". path_type = engine.getTypes()['path'] parts = [] for exp in expr.split('$$'): if parts: parts.append('$') m = _interp.search(exp) while m is not None: parts.append(exp[:m.start()]) parts.append('%s') vars.append(path_type( 'path', m.group(1) or m.group(2), engine)) exp = exp[m.end():] m = _interp.search(exp) if '$' in exp: raise engine.getCompilerError()( '$ must be doubled or followed by a simple path') parts.append(exp) expr = ''.join(parts) self._expr = expr def __call__(self, econtext): vvals = [] for var in self._vars: v = var(econtext) vvals.append(v) return self._expr % tuple(vvals) def __str__(self): return 'string expression (%s)' % `self._s` def __repr__(self): return '' % `self._s` class NotExpr(object): implements(ITALESExpression) def __init__(self, name, expr, engine): self._s = expr = expr.lstrip() self._c = engine.compile(expr) def __call__(self, econtext): return int(not econtext.evaluateBoolean(self._c)) def __repr__(self): return '' % `self._s` class DeferWrapper(object): def __init__(self, expr, econtext): self._expr = expr self._econtext = econtext def __str__(self): return str(self()) def __call__(self): return self._expr(self._econtext) class DeferExpr(object): implements(ITALESExpression) def __init__(self, name, expr, compiler): self._s = expr = expr.lstrip() self._c = compiler.compile(expr) def __call__(self, econtext): return DeferWrapper(self._c, econtext) def __repr__(self): return '' % `self._s` class LazyWrapper(DeferWrapper): """Wrapper for lazy: expression """ def __init__(self, expr, econtext): DeferWrapper.__init__(self, expr, econtext) self._result = _marker def __call__(self): r = self._result if r is _marker: self._result = r = self._expr(self._econtext) return r class LazyExpr(DeferExpr): """lazy: expression handler for lazy initialization of expressions """ def __call__(self, econtext): return LazyWrapper(self._c, econtext) def __repr__(self): return 'lazy:%s' % `self._s` class SimpleModuleImporter(object): """Minimal module importer with no security.""" def __getitem__(self, module): mod = self._get_toplevel_module(module) path = module.split('.') for name in path[1:]: mod = getattr(mod, name) return mod def _get_toplevel_module(self, module): # This can be overridden to add security proxies. return __import__(module) zope2.13-2.13.21/source/zope.tales/src/zope/tales/interfaces.py0000644000175000017500000000704112214017647023102 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interface that describes the 'macros' attribute of a PageTemplate. $Id: interfaces.py 126816 2012-06-11 19:00:21Z tseaver $ """ from zope.interface import Interface try: from zope import tal except ImportError: tal = None class ITALESFunctionNamespace(Interface): """Function namespaces can be used in TALES path expressions to extract information in non-default ways.""" def setEngine(engine): """Sets the engine that is used to evaluate TALES expressions.""" class ITALESExpression(Interface): """TALES expression These are expression handlers that handle a specific type of expression in TALES, e.g. path or string expression. """ def __call__(econtext): """Evaluate expression according to the given execution context 'econtext' and return computed value. """ if tal is not None: from zope.tal.interfaces import ITALIterator class ITALESIterator(ITALIterator): """TAL Iterator provided by TALES Values of this iterator are assigned to items in the repeat namespace. For example, with a TAL statement like: tal:repeat="item items", an iterator will be assigned to "repeat/item". The iterator provides a number of handy methods useful in writing TAL loops. The results are undefined of calling any of the methods except 'length' before the first iteration. """ def index(): """Return the position (starting with "0") within the iteration """ def number(): """Return the position (starting with "1") within the iteration """ def even(): """Return whether the current position is even """ def odd(): """Return whether the current position is odd """ def parity(): """Return 'odd' or 'even' depending on the position's parity Useful for assigning CSS class names to table rows. """ def start(): """Return whether the current position is the first position """ def end(): """Return whether the current position is the last position """ def letter(): """Return the position (starting with "a") within the iteration """ def Letter(): """Return the position (starting with "A") within the iteration """ def roman(): """Return the position (starting with "i") within the iteration """ def Roman(): """Return the position (starting with "I") within the iteration """ def item(): """Return the item at the current position """ def length(): """Return the length of the sequence Note that this may fail if the TAL iterator was created on a Python iterator. """ zope2.13-2.13.21/source/zope.tales/src/zope/tales/tales.py0000644000175000017500000005050612214017647022073 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """TALES An implementation of a TAL expression engine $Id: tales.py 126816 2012-06-11 19:00:21Z tseaver $ """ __docformat__ = "reStructuredText" import re from zope.interface import implements try: from zope import tal except ImportError: tal = None if tal: from zope.tal.interfaces import ITALExpressionEngine from zope.tal.interfaces import ITALExpressionCompiler from zope.tal.interfaces import ITALExpressionErrorInfo from zope.tales.interfaces import ITALESIterator NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*" _parse_expr = re.compile(r"(%s):" % NAME_RE).match _valid_name = re.compile('%s$' % NAME_RE).match class TALESError(Exception): """Error during TALES evaluation""" class Undefined(TALESError): '''Exception raised on traversal of an undefined path''' class CompilerError(Exception): '''TALES Compiler Error''' class RegistrationError(Exception): '''Expression type or base name registration Error''' _default = object() class Iterator(object): """TALES Iterator """ if tal: implements(ITALESIterator) def __init__(self, name, seq, context): """Construct an iterator Iterators are defined for a name, a sequence, or an iterator and a context, where a context simply has a setLocal method: >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) A local variable is not set until the iterator is used: >>> int("foo" in context.vars) 0 We can create an iterator on an empty sequence: >>> it = Iterator('foo', (), context) An iterator works as well: >>> it = Iterator('foo', {"apple":1, "pear":1, "orange":1}, context) >>> it.next() True >>> it = Iterator('foo', {}, context) >>> it.next() False >>> it = Iterator('foo', iter((1, 2, 3)), context) >>> it.next() True >>> it.next() True """ self._seq = seq self._iter = i = iter(seq) self._nextIndex = 0 self._name = name self._setLocal = context.setLocal # This is tricky. We want to know if we are on the last item, # but we can't know that without trying to get it. :( self._last = False try: self._next = i.next() except StopIteration: self._done = True else: self._done = False def next(self): """Advance the iterator, if possible. >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> bool(it.next()) True >>> context.vars['foo'] 'apple' >>> bool(it.next()) True >>> context.vars['foo'] 'pear' >>> bool(it.next()) True >>> context.vars['foo'] 'orange' >>> bool(it.next()) False >>> it = Iterator('foo', {"apple":1, "pear":1, "orange":1}, context) >>> bool(it.next()) True >>> bool(it.next()) True >>> bool(it.next()) True >>> bool(it.next()) False >>> it = Iterator('foo', (), context) >>> bool(it.next()) False >>> it = Iterator('foo', {}, context) >>> bool(it.next()) False If we can advance, set a local variable to the new value. """ # Note that these are *NOT* Python iterators! if self._done: return False self._item = v = self._next try: self._next = self._iter.next() except StopIteration: self._done = True self._last = True self._nextIndex += 1 self._setLocal(self._name, v) return True def index(self): """Get the iterator index >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> int(bool(it.next())) 1 >>> it.index() 0 >>> int(bool(it.next())) 1 >>> it.index() 1 >>> int(bool(it.next())) 1 >>> it.index() 2 """ index = self._nextIndex - 1 if index < 0: raise TypeError("No iteration position") return index def number(self): """Get the iterator position >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> int(bool(it.next())) 1 >>> it.number() 1 >>> int(bool(it.next())) 1 >>> it.number() 2 >>> int(bool(it.next())) 1 >>> it.number() 3 """ return self._nextIndex def even(self): """Test whether the position is even >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> it.next() True >>> it.even() True >>> it.next() True >>> it.even() False >>> it.next() True >>> it.even() True """ return not ((self._nextIndex - 1) % 2) def odd(self): """Test whether the position is odd >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> it.next() True >>> it.odd() False >>> it.next() True >>> it.odd() True >>> it.next() True >>> it.odd() False """ return bool((self._nextIndex - 1) % 2) def parity(self): """Return 'odd' or 'even' depending on the position's parity >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> it.next() True >>> it.parity() 'odd' >>> it.next() True >>> it.parity() 'even' >>> it.next() True >>> it.parity() 'odd' """ if self._nextIndex % 2: return 'odd' return 'even' def letter(self, base=ord('a'), radix=26): """Get the iterator position as a lower-case letter >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> it.next() True >>> it.letter() 'a' >>> it.next() True >>> it.letter() 'b' >>> it.next() True >>> it.letter() 'c' """ index = self._nextIndex - 1 if index < 0: raise TypeError("No iteration position") s = '' while 1: index, off = divmod(index, radix) s = chr(base + off) + s if not index: return s def Letter(self): """Get the iterator position as an upper-case letter >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> it.next() True >>> it.Letter() 'A' >>> it.next() True >>> it.Letter() 'B' >>> it.next() True >>> it.Letter() 'C' """ return self.letter(base=ord('A')) def Roman(self, rnvalues=( (1000,'M'),(900,'CM'),(500,'D'),(400,'CD'), (100,'C'),(90,'XC'),(50,'L'),(40,'XL'), (10,'X'),(9,'IX'),(5,'V'),(4,'IV'),(1,'I')) ): """Get the iterator position as an upper-case roman numeral >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> it.next() True >>> it.Roman() 'I' >>> it.next() True >>> it.Roman() 'II' >>> it.next() True >>> it.Roman() 'III' """ n = self._nextIndex s = '' for v, r in rnvalues: rct, n = divmod(n, v) s = s + r * rct return s def roman(self): """Get the iterator position as a lower-case roman numeral >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> it.next() True >>> it.roman() 'i' >>> it.next() True >>> it.roman() 'ii' >>> it.next() True >>> it.roman() 'iii' """ return self.Roman().lower() def start(self): """Test whether the position is the first position >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> it.next() True >>> it.start() True >>> it.next() True >>> it.start() False >>> it.next() True >>> it.start() False >>> it = Iterator('foo', {}, context) >>> it.start() False >>> it.next() False >>> it.start() False """ return self._nextIndex == 1 def end(self): """Test whether the position is the last position >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> it.next() True >>> it.end() False >>> it.next() True >>> it.end() False >>> it.next() True >>> it.end() True >>> it = Iterator('foo', {}, context) >>> it.end() False >>> it.next() False >>> it.end() False """ return self._last def item(self): """Get the iterator value >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> it.next() True >>> it.item() 'apple' >>> it.next() True >>> it.item() 'pear' >>> it.next() True >>> it.item() 'orange' >>> it = Iterator('foo', {1:2}, context) >>> it.next() True >>> it.item() 1 """ if self._nextIndex == 0: raise TypeError("No iteration position") return self._item def length(self): """Get the length of the iterator sequence >>> context = Context(ExpressionEngine(), {}) >>> it = Iterator('foo', ("apple", "pear", "orange"), context) >>> it.length() 3 You can even get the length of a mapping: >>> it = Iterator('foo', {"apple":1, "pear":2, "orange":3}, context) >>> it.length() 3 But you can't get the length of an iterable which doesn't support len(): >>> class MyIter(object): ... def __init__(self, seq): ... self._next = iter(seq).next ... def __iter__(self): ... return self ... def next(self): ... return self._next() >>> it = Iterator('foo', MyIter({"apple":1, "pear":2}), context) >>> it.length() Traceback (most recent call last): ... TypeError: len() of unsized object """ return len(self._seq) class ErrorInfo(object): """Information about an exception passed to an on-error handler.""" if tal: implements(ITALExpressionErrorInfo) def __init__(self, err, position=(None, None)): if isinstance(err, Exception): self.type = err.__class__ self.value = err else: self.type = err self.value = None self.lineno = position[0] self.offset = position[1] class ExpressionEngine(object): '''Expression Engine An instance of this class keeps a mutable collection of expression type handlers. It can compile expression strings by delegating to these handlers. It can provide an expression Context, which is capable of holding state and evaluating compiled expressions. ''' if tal: implements(ITALExpressionCompiler) def __init__(self): self.types = {} self.base_names = {} self.namespaces = {} self.iteratorFactory = Iterator def registerFunctionNamespace(self, namespacename, namespacecallable): """Register a function namespace namespace - a string containing the name of the namespace to be registered namespacecallable - a callable object which takes the following parameter: context - the object on which the functions provided by this namespace will be called This callable should return an object which can be traversed to get the functions provided by the this namespace. example: class stringFuncs(object): def __init__(self,context): self.context = str(context) def upper(self): return self.context.upper() def lower(self): return self.context.lower() engine.registerFunctionNamespace('string',stringFuncs) """ self.namespaces[namespacename] = namespacecallable def getFunctionNamespace(self, namespacename): """ Returns the function namespace """ return self.namespaces[namespacename] def registerType(self, name, handler): if not _valid_name(name): raise RegistrationError('Invalid expression type name "%s".' % name) types = self.types if name in types: raise RegistrationError( 'Multiple registrations for Expression type "%s".' % name) types[name] = handler def getTypes(self): return self.types def registerBaseName(self, name, object): if not _valid_name(name): raise RegistrationError('Invalid base name "%s".' % name) base_names = self.base_names if name in base_names: raise RegistrationError( 'Multiple registrations for base name "%s".' % name) base_names[name] = object def getBaseNames(self): return self.base_names def compile(self, expression): m = _parse_expr(expression) if m: type = m.group(1) expr = expression[m.end():] else: type = "standard" expr = expression try: handler = self.types[type] except KeyError: raise CompilerError('Unrecognized expression type "%s".' % type) return handler(type, expr, self) def getContext(self, contexts=None, **kwcontexts): if contexts is not None: if kwcontexts: kwcontexts.update(contexts) else: kwcontexts = contexts return Context(self, kwcontexts) def getCompilerError(self): return CompilerError class Context(object): '''Expression Context An instance of this class holds context information that it can use to evaluate compiled expressions. ''' if tal: implements(ITALExpressionEngine) position = (None, None) source_file = None def __init__(self, engine, contexts): self._engine = engine self.contexts = contexts self.setContext('nothing', None) self.setContext('default', _default) self.repeat_vars = rv = {} # Wrap this, as it is visible to restricted code self.setContext('repeat', rv) self.setContext('loop', rv) # alias self.vars = vars = contexts.copy() self._vars_stack = [vars] # Keep track of what needs to be popped as each scope ends. self._scope_stack = [] def setContext(self, name, value): # Hook to allow subclasses to do things like adding security proxies self.contexts[name] = value def beginScope(self): self.vars = vars = self.vars.copy() self._vars_stack.append(vars) self._scope_stack.append([]) def endScope(self): self._vars_stack.pop() self.vars = self._vars_stack[-1] scope = self._scope_stack.pop() # Pop repeat variables, if any i = len(scope) while i: i = i - 1 name, value = scope[i] if value is None: del self.repeat_vars[name] else: self.repeat_vars[name] = value def setLocal(self, name, value): self.vars[name] = value def setGlobal(self, name, value): for vars in self._vars_stack: vars[name] = value def getValue(self, name, default=None): value = default for vars in self._vars_stack: value = vars.get(name, default) if value is not default: break return value def setRepeat(self, name, expr): expr = self.evaluate(expr) if not expr: return self._engine.iteratorFactory(name, (), self) it = self._engine.iteratorFactory(name, expr, self) old_value = self.repeat_vars.get(name) self._scope_stack[-1].append((name, old_value)) self.repeat_vars[name] = it return it def evaluate(self, expression): if isinstance(expression, str): expression = self._engine.compile(expression) __traceback_supplement__ = ( TALESTracebackSupplement, self, expression) return expression(self) evaluateValue = evaluate def evaluateBoolean(self, expr): return not not self.evaluate(expr) def evaluateText(self, expr): text = self.evaluate(expr) if text is self.getDefault() or text is None: return text if isinstance(text, basestring): # text could already be something text-ish, e.g. a Message object return text return unicode(text) def evaluateStructure(self, expr): return self.evaluate(expr) evaluateStructure = evaluate def evaluateMacro(self, expr): # TODO: Should return None or a macro definition return self.evaluate(expr) evaluateMacro = evaluate def createErrorInfo(self, err, position): return ErrorInfo(err, position) def getDefault(self): return _default def setSourceFile(self, source_file): self.source_file = source_file def setPosition(self, position): self.position = position def translate(self, msgid, domain=None, mapping=None, default=None): # custom Context implementations are supposed to customize # this to call whichever translation routine they want to use return unicode(msgid) class TALESTracebackSupplement(object): """Implementation of zope.exceptions.ITracebackSupplement""" def __init__(self, context, expression): self.context = context self.source_url = context.source_file self.line = context.position[0] self.column = context.position[1] self.expression = repr(expression) def getInfo(self, as_html=0): import pprint data = self.context.contexts.copy() if 'modules' in data: del data['modules'] # the list is really long and boring s = pprint.pformat(data) if not as_html: return ' - Names:\n %s' % s.replace('\n', '\n ') else: from cgi import escape return 'Names:
%s
' % (escape(s)) return None zope2.13-2.13.21/source/zope.tales/src/zope/tales/__init__.py0000644000175000017500000000136212214017647022516 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Template Attribute Language - Expression Syntax $Id: __init__.py 126816 2012-06-11 19:00:21Z tseaver $ """ zope2.13-2.13.21/source/zope.tales/src/zope/tales/engine.py0000644000175000017500000000302312214017647022220 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Expression engine configuration and registration. Each expression engine can have its own expression types and base names. $Id: engine.py 126816 2012-06-11 19:00:21Z tseaver $ """ from zope.tales.tales import ExpressionEngine from zope.tales.expressions import PathExpr from zope.tales.expressions import StringExpr from zope.tales.expressions import NotExpr from zope.tales.expressions import DeferExpr from zope.tales.expressions import LazyExpr from zope.tales.expressions import SimpleModuleImporter from zope.tales.pythonexpr import PythonExpr def Engine(): e = ExpressionEngine() reg = e.registerType for pt in PathExpr._default_type_names: reg(pt, PathExpr) reg('string', StringExpr) reg('python', PythonExpr) reg('not', NotExpr) reg('defer', DeferExpr) reg('lazy', LazyExpr) e.registerBaseName('modules', SimpleModuleImporter()) return e Engine = Engine() zope2.13-2.13.21/source/zope.tales/src/zope/tales/pythonexpr.py0000644000175000017500000000542412214017647023202 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Generic Python Expression Handler $Id: pythonexpr.py 126816 2012-06-11 19:00:21Z tseaver $ """ class PythonExpr(object): def __init__(self, name, expr, engine): text = '\n'.join(expr.splitlines()) # normalize line endings text = '(' + text + ')' # Put text in parens so newlines don't matter self.text = text try: code = self._compile(text, '') except SyntaxError, e: raise engine.getCompilerError()(str(e)) self._code = code self._varnames = code.co_names def _compile(self, text, filename): return compile(text, filename, 'eval') def _bind_used_names(self, econtext, builtins): # Construct a dictionary of globals with which the Python # expression should be evaluated. names = {} vars = econtext.vars marker = self if not isinstance(builtins, dict): builtins = builtins.__dict__ for vname in self._varnames: val = vars.get(vname, marker) if val is not marker: names[vname] = val elif vname not in builtins: # Fall back to using expression types as variable values. val = econtext._engine.getTypes().get(vname, marker) if val is not marker: val = ExprTypeProxy(vname, val, econtext) names[vname] = val names['__builtins__'] = builtins return names def __call__(self, econtext): __traceback_info__ = self.text vars = self._bind_used_names(econtext, __builtins__) return eval(self._code, vars) def __str__(self): return 'Python expression "%s"' % self.text def __repr__(self): return '' % self.text class ExprTypeProxy(object): '''Class that proxies access to an expression type handler''' def __init__(self, name, handler, econtext): self._name = name self._handler = handler self._econtext = econtext def __call__(self, text): return self._handler(self._name, text, self._econtext._engine)(self._econtext) zope2.13-2.13.21/source/zope.tales/src/zope/tales/tests/0000755000175000017500000000000012214017647021545 5ustar arnauarnauzope2.13-2.13.21/source/zope.tales/src/zope/tales/tests/test_expressions.py0000644000175000017500000003170112214017647025542 0ustar arnauarnau# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Default TALES expression implementations tests. $Id: test_expressions.py 126816 2012-06-11 19:00:21Z tseaver $ """ import unittest from zope.tales.engine import Engine from zope.tales.interfaces import ITALESFunctionNamespace from zope.tales.tales import Undefined from zope.interface import implements class Data(object): def __init__(self, **kw): self.__dict__.update(kw) def __repr__(self): return self.name __str__ = __repr__ class ErrorGenerator: def __getitem__(self, name): import __builtin__ if name == 'Undefined': e = Undefined else: e = getattr(__builtin__, name, None) if e is None: e = SystemError raise e('mess') class ExpressionTestBase(unittest.TestCase): def setUp(self): # Test expression compilation d = Data( name = 'xander', y = Data( name = 'yikes', z = Data(name = 'zope') ) ) at = Data( name = 'yikes', _d = d ) self.context = Data( vars = dict( x = d, y = Data(z = 3), b = 'boot', B = 2, adapterTest = at, dynamic = 'z', eightBits = 'déjà vu', ErrorGenerator = ErrorGenerator(), ) ) self.engine = Engine class ExpressionTests(ExpressionTestBase): def testSimple(self): expr = self.engine.compile('x') context=self.context self.assertEqual(expr(context), context.vars['x']) def testPath(self): expr = self.engine.compile('x/y') context=self.context self.assertEqual(expr(context), context.vars['x'].y) def testLongPath(self): expr = self.engine.compile('x/y/z') context=self.context self.assertEqual(expr(context), context.vars['x'].y.z) def testOrPath(self): expr = self.engine.compile('path:a|b|c/d/e') context=self.context self.assertEqual(expr(context), 'boot') for e in 'Undefined', 'AttributeError', 'LookupError', 'TypeError': expr = self.engine.compile('path:ErrorGenerator/%s|b|c/d/e' % e) context=self.context self.assertEqual(expr(context), 'boot') def testDynamic(self): expr = self.engine.compile('x/y/?dynamic') context=self.context self.assertEqual(expr(context),context.vars['x'].y.z) def testBadInitalDynamic(self): from zope.tales.tales import CompilerError try: self.engine.compile('?x') except CompilerError,e: self.assertEqual(e.args[0], 'Dynamic name specified in first subpath element') else: self.fail('Engine accepted first subpath element as dynamic') def testOldStyleClassIsCalled(self): class AnOldStyleClass: pass self.context.vars['oldstyleclass'] = AnOldStyleClass expr = self.engine.compile('oldstyleclass') self.assert_(isinstance(expr(self.context), AnOldStyleClass)) def testString(self): expr = self.engine.compile('string:Fred') context=self.context result = expr(context) self.assertEqual(result, 'Fred') self.failUnless(isinstance(result, str)) def testStringSub(self): expr = self.engine.compile('string:A$B') context=self.context self.assertEqual(expr(context), 'A2') def testStringSub_w_python(self): CompilerError = self.engine.getCompilerError() self.assertRaises(CompilerError, self.engine.compile, 'string:${python:1}') def testStringSubComplex(self): expr = self.engine.compile('string:a ${x/y} b ${y/z} c') context=self.context self.assertEqual(expr(context), 'a yikes b 3 c') def testStringSubComplex_w_miss_and_python(self): # See https://bugs.launchpad.net/zope.tales/+bug/1002242 CompilerError = self.engine.getCompilerError() self.assertRaises(CompilerError, self.engine.compile, 'string:${nothig/nothing|python:1}') def testString8Bits(self): # Simple eight bit string interpolation should just work. expr = self.engine.compile('string:a ${eightBits}') context=self.context self.assertEqual(expr(context), 'a déjà vu') def testStringUnicode(self): # Unicode string expressions should return unicode strings expr = self.engine.compile(u'string:Fred') context=self.context result = expr(context) self.assertEqual(result, u'Fred') self.failUnless(isinstance(result, unicode)) def testStringFailureWhenMixed(self): # Mixed Unicode and 8bit string interpolation fails with a # UnicodeDecodeError, assuming there is no default encoding expr = self.engine.compile(u'string:a ${eightBits}') self.assertRaises(UnicodeDecodeError, expr, self.context) def testPython(self): expr = self.engine.compile('python: 2 + 2') context=self.context self.assertEqual(expr(context), 4) def testPythonCallableIsntCalled(self): self.context.vars['acallable'] = lambda: 23 expr = self.engine.compile('python: acallable') self.assertEqual(expr(self.context), self.context.vars['acallable']) def testPythonNewline(self): expr = self.engine.compile('python: 2 \n+\n 2\n') context=self.context self.assertEqual(expr(context), 4) def testPythonDosNewline(self): expr = self.engine.compile('python: 2 \r\n+\r\n 2\r\n') context=self.context self.assertEqual(expr(context), 4) def testPythonErrorRaisesCompilerError(self): self.assertRaises(self.engine.getCompilerError(), self.engine.compile, 'python: splat.0') def testHybridPathExpressions(self): def eval(expr): e = self.engine.compile(expr) return e(self.context) self.context.vars['one'] = 1 self.context.vars['acallable'] = lambda: 23 self.assertEqual(eval('foo | python:1+1'), 2) self.assertEqual(eval('foo | python:acallable'), self.context.vars['acallable']) self.assertEqual(eval('foo | string:x'), 'x') self.assertEqual(eval('foo | string:$one'), '1') self.assert_(eval('foo | exists:x')) def testEmptyPathSegmentRaisesCompilerError(self): CompilerError = self.engine.getCompilerError() def check(expr): self.assertRaises(CompilerError, self.engine.compile, expr) # path expressions on their own: check('/ab/cd | c/d | e/f') check('ab//cd | c/d | e/f') check('ab/cd/ | c/d | e/f') check('ab/cd | /c/d | e/f') check('ab/cd | c//d | e/f') check('ab/cd | c/d/ | e/f') check('ab/cd | c/d | /e/f') check('ab/cd | c/d | e//f') check('ab/cd | c/d | e/f/') # path expressions embedded in string: expressions: check('string:${/ab/cd}') check('string:${ab//cd}') check('string:${ab/cd/}') check('string:foo${/ab/cd | c/d | e/f}bar') check('string:foo${ab//cd | c/d | e/f}bar') check('string:foo${ab/cd/ | c/d | e/f}bar') check('string:foo${ab/cd | /c/d | e/f}bar') check('string:foo${ab/cd | c//d | e/f}bar') check('string:foo${ab/cd | c/d/ | e/f}bar') check('string:foo${ab/cd | c/d | /e/f}bar') check('string:foo${ab/cd | c/d | e//f}bar') check('string:foo${ab/cd | c/d | e/f/}bar') def test_defer_expression_returns_wrapper(self): from zope.tales.expressions import DeferWrapper expr = self.engine.compile('defer: b') context=self.context self.failUnless(isinstance(expr(context), DeferWrapper)) def test_lazy_expression_returns_wrapper(self): from zope.tales.expressions import LazyWrapper expr = self.engine.compile('lazy: b') context=self.context self.failUnless(isinstance(expr(context), LazyWrapper)) class FunctionTests(ExpressionTestBase): def setUp(self): ExpressionTestBase.setUp(self) # a test namespace class TestNameSpace(object): implements(ITALESFunctionNamespace) def __init__(self, context): self.context = context def setEngine(self, engine): self._engine = engine def engine(self): return self._engine def upper(self): return str(self.context).upper() def __getitem__(self,key): if key=='jump': return self.context._d raise KeyError,key self.TestNameSpace = TestNameSpace self.engine.registerFunctionNamespace('namespace', self.TestNameSpace) ## framework-ish tests def testSetEngine(self): expr = self.engine.compile('adapterTest/namespace:engine') self.assertEqual(expr(self.context), self.context) def testGetFunctionNamespace(self): self.assertEqual( self.engine.getFunctionNamespace('namespace'), self.TestNameSpace ) def testGetFunctionNamespaceBadNamespace(self): self.assertRaises(KeyError, self.engine.getFunctionNamespace, 'badnamespace') ## compile time tests def testBadNamespace(self): # namespace doesn't exist from zope.tales.tales import CompilerError try: self.engine.compile('adapterTest/badnamespace:title') except CompilerError,e: self.assertEqual(e.args[0],'Unknown namespace "badnamespace"') else: self.fail('Engine accepted unknown namespace') def testBadInitialNamespace(self): # first segment in a path must not have modifier from zope.tales.tales import CompilerError self.assertRaises(CompilerError, self.engine.compile, 'namespace:title') # In an ideal world there would be another test here to test # that a nicer error was raised when you tried to use # something like: # standard:namespace:upper # ...as a path. # However, the compilation stage of PathExpr currently # allows any expression type to be nested, so something like: # standard:standard:context/attribute # ...will work fine. # When that is changed so that only expression types which # should be nested are nestable, then the additional test # should be added here. def testInvalidNamespaceName(self): from zope.tales.tales import CompilerError try: self.engine.compile('adapterTest/1foo:bar') except CompilerError,e: self.assertEqual(e.args[0], 'Invalid namespace name "1foo"') else: self.fail('Engine accepted invalid namespace name') def testBadFunction(self): # namespace is fine, adapter is not defined try: expr = self.engine.compile('adapterTest/namespace:title') expr(self.context) except KeyError,e: self.assertEquals(e.args[0],'title') else: self.fail('Engine accepted unknown function') ## runtime tests def testNormalFunction(self): expr = self.engine.compile('adapterTest/namespace:upper') self.assertEqual(expr(self.context), 'YIKES') def testFunctionOnFunction(self): expr = self.engine.compile('adapterTest/namespace:jump/namespace:upper') self.assertEqual(expr(self.context), 'XANDER') def testPathOnFunction(self): expr = self.engine.compile('adapterTest/namespace:jump/y/z') context = self.context self.assertEqual(expr(context), context.vars['x'].y.z) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(ExpressionTests), unittest.makeSuite(FunctionTests), )) if __name__ == '__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/zope.tales/src/zope/tales/tests/test_traverser.py0000644000175000017500000000771112214017647025201 0ustar arnauarnau""" Tests for zope.tales.expressions.simpleTraverse $Id: test_traverser.py 40270 2005-11-20 13:23:13Z shh $ """ from unittest import TestCase, TestSuite, makeSuite, main from zope.tales.expressions import simpleTraverse class AttrTraversable(object): """Traversable by attribute access""" attr = 'foo' class ItemTraversable(object): """Traversable by item access""" def __getitem__(self, name): if name == 'attr': return 'foo' raise KeyError, name class AllTraversable(AttrTraversable, ItemTraversable): """Traversable by attribute and item access""" pass _marker = object() def getitem(ob, name, default=_marker): """Helper a la getattr(ob, name, default).""" try: item = ob[name] except KeyError: if default is not _marker: return default raise KeyError, name else: return item class TraverserTests(TestCase): def testGetItem(self): # getitem helper should behave like __getitem__ ob = {'attr': 'foo'} self.assertEqual(getitem(ob, 'attr', None), 'foo') self.assertEqual(getitem(ob, 'attr'), 'foo') self.assertEqual(getitem(ob, 'missing_attr', None), None) self.assertRaises(KeyError, getitem, ob, 'missing_attr') self.assertRaises(TypeError, getitem, object(), 'attr') def testAttrTraversable(self): # An object without __getitem__ should raise AttributeError # for missing attribute. ob = AttrTraversable() self.assertEqual(getattr(ob, 'attr', None), 'foo') self.assertRaises(AttributeError, getattr, ob, 'missing_attr') def testItemTraversable(self): # An object with __getitem__ (but without attr) should raise # KeyError for missing attribute. ob = ItemTraversable() self.assertEqual(getitem(ob, 'attr', None), 'foo') self.assertRaises(KeyError, getitem, ob, 'missing_attr') def testAllTraversable(self): # An object with attr and __getitem__ should raise either # exception, depending on method of access. ob = AllTraversable() self.assertEqual(getattr(ob, 'attr', None), 'foo') self.assertRaises(AttributeError, getattr, ob, 'missing_attr') self.assertEqual(getitem(ob, 'attr', None), 'foo') self.assertRaises(KeyError, getitem, ob, 'missing_attr') def testTraverseEmptyPath(self): # simpleTraverse should return the original object if the path is emtpy ob = object() self.assertEqual(simpleTraverse(ob, [], None), ob) def testTraverseByAttr(self): # simpleTraverse should find attr through attribute access ob = AttrTraversable() self.assertEqual(simpleTraverse(ob, ['attr'], None), 'foo') def testTraverseByMissingAttr(self): # simpleTraverse should raise AttributeError ob = AttrTraversable() # Here lurks the bug (unexpected NamError raised) self.assertRaises(AttributeError, simpleTraverse, ob, ['missing_attr'], None) def testTraverseByItem(self): # simpleTraverse should find attr through item access ob = ItemTraversable() self.assertEqual(simpleTraverse(ob, ['attr'], None), 'foo') def testTraverseByMissingItem(self): # simpleTraverse should raise KeyError ob = ItemTraversable() self.assertRaises(KeyError, simpleTraverse, ob, ['missing_attr'], None) def testTraverseByAll(self): # simpleTraverse should find attr through attribute access ob = AllTraversable() self.assertEqual(simpleTraverse(ob, ['attr'], None), 'foo') def testTraverseByMissingAll(self): # simpleTraverse should raise KeyError (because ob implements __getitem__) ob = AllTraversable() self.assertRaises(KeyError, simpleTraverse, ob, ['missing_attr'], None) def test_suite(): return TestSuite(( makeSuite(TraverserTests), )) if __name__ == '__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.tales/src/zope/tales/tests/__init__.py0000644000175000017500000000007512214017647023660 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.tales/src/zope/tales/tests/simpleexpr.py0000644000175000017500000000207612214017647024314 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Simple TALES Expression $Id: simpleexpr.py 126816 2012-06-11 19:00:21Z tseaver $ """ class SimpleExpr(object): '''Simple example of an expression type handler for testing ''' def __init__(self, name, expr, engine): self._name = name self._expr = expr def __call__(self, econtext): return self._name, self._expr def __repr__(self): return '' % (self._name, `self._expr`) zope2.13-2.13.21/source/zope.tales/src/zope/tales/tests/test_tales.py0000644000175000017500000001367712214017647024304 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """TALES Tests $Id: test_tales.py 126816 2012-06-11 19:00:21Z tseaver $ """ import unittest import re from zope.tales import tales from zope.tales.tests.simpleexpr import SimpleExpr from doctest import DocTestSuite from zope.testing import renormalizing class TALESTests(unittest.TestCase): def testIterator0(self): # Test sample Iterator class context = Harness(self) it = tales.Iterator('name', (), context) self.assert_( not it.next(), "Empty iterator") context._complete_() def testIterator1(self): # Test sample Iterator class context = Harness(self) it = tales.Iterator('name', (1,), context) context._assert_('setLocal', 'name', 1) self.assert_( it.next() and not it.next(), "Single-element iterator") context._complete_() def testIterator2(self): # Test sample Iterator class context = Harness(self) it = tales.Iterator('text', 'text', context) for c in 'text': context._assert_('setLocal', 'text', c) for c in 'text': self.assert_(it.next(), "Multi-element iterator") self.assert_( not it.next(), "Multi-element iterator") context._complete_() def testRegisterType(self): # Test expression type registration e = tales.ExpressionEngine() e.registerType('simple', SimpleExpr) self.assert_( e.getTypes()['simple'] == SimpleExpr) def testRegisterTypeUnique(self): # Test expression type registration uniqueness e = tales.ExpressionEngine() e.registerType('simple', SimpleExpr) try: e.registerType('simple', SimpleExpr) except tales.RegistrationError: pass else: self.assert_( 0, "Duplicate registration accepted.") def testRegisterTypeNameConstraints(self): # Test constraints on expression type names e = tales.ExpressionEngine() for name in '1A', 'A!', 'AB ': try: e.registerType(name, SimpleExpr) except tales.RegistrationError: pass else: self.assert_( 0, 'Invalid type name "%s" accepted.' % name) def testCompile(self): # Test expression compilation e = tales.ExpressionEngine() e.registerType('simple', SimpleExpr) ce = e.compile('simple:x') self.assert_( ce(None) == ('simple', 'x'), ( 'Improperly compiled expression %s.' % `ce`)) def testGetContext(self): # Test Context creation tales.ExpressionEngine().getContext() tales.ExpressionEngine().getContext(v=1) tales.ExpressionEngine().getContext(x=1, y=2) def getContext(self, **kws): e = tales.ExpressionEngine() e.registerType('simple', SimpleExpr) return apply(e.getContext, (), kws) def testContext0(self): # Test use of Context se = self.getContext().evaluate('simple:x') self.assert_( se == ('simple', 'x'), ( 'Improperly evaluated expression %s.' % `se`)) def testVariables(self): # Test variables ctxt = self.getContext() ctxt.beginScope() ctxt.setLocal('v1', 1) ctxt.setLocal('v2', 2) c = ctxt.vars self.assert_( c['v1'] == 1, 'Variable "v1"') ctxt.beginScope() ctxt.setLocal('v1', 3) ctxt.setGlobal('g', 1) c = ctxt.vars self.assert_( c['v1'] == 3, 'Inner scope') self.assert_( c['v2'] == 2, 'Outer scope') self.assert_( c['g'] == 1, 'Global') ctxt.endScope() c = ctxt.vars self.assert_( c['v1'] == 1, "Uncovered local") self.assert_( c['g'] == 1, "Global from inner scope") ctxt.endScope() class Harness(object): def __init__(self, testcase): self._callstack = [] self._testcase = testcase def _assert_(self, name, *args, **kwargs): self._callstack.append((name, args, kwargs)) def _complete_(self): self._testcase.assert_(len(self._callstack) == 0, "Harness methods called") def __getattr__(self, name): return HarnessMethod(self, name) class HarnessMethod(object): def __init__(self, harness, name): self._harness = harness self._name = name def __call__(self, *args, **kwargs): name = self._name self = self._harness cs = self._callstack self._testcase.assert_( len(cs), 'Unexpected harness method call "%s".' % name ) self._testcase.assert_( cs[0][0] == name, 'Harness method name "%s" called, "%s" expected.' % (name, cs[0][0]) ) name, aargs, akwargs = self._callstack.pop(0) self._testcase.assert_(aargs == args, "Harness method arguments") self._testcase.assert_(akwargs == kwargs, "Harness method keyword args") def test_suite(): checker = renormalizing.RENormalizing([ (re.compile(r"object of type 'MyIter' has no len\(\)"), r"len() of unsized object"), ]) suite = unittest.makeSuite(TALESTests) suite.addTest(DocTestSuite("zope.tales.tales",checker=checker)) return suite if __name__ == '__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/zope.browser/0000755000175000017500000000000012214017525016075 5ustar arnauarnauzope2.13-2.13.21/source/zope.browser/setup.py0000644000175000017500000000371512214017525017615 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Setup for zope.browser package $Id:$ """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.browser', version = '1.3', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', description='Shared Zope Toolkit browser components', long_description=( read('README.txt') + '\n\n.. contents::\n\n' + read('src', 'zope', 'browser', 'README.txt') + '\n\n' + read('CHANGES.txt') ), license='ZPL 2.1', keywords = "zope browser component", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.browser', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope',], install_requires=[ 'setuptools', 'zope.interface', ], include_package_data = True, zip_safe = False, ) zope2.13-2.13.21/source/zope.browser/PKG-INFO0000644000175000017500000001133612214017525017176 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.browser Version: 1.3 Summary: Shared Zope Toolkit browser components Home-page: http://pypi.python.org/pypi/zope.browser Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: zope.browser ============ This package provides shared browser components for the Zope Toolkit. .. contents:: IView ----- Views adapt both a context and a request. There is not much we can test except that ``IView`` is importable and an interface: >>> from zope.interface import Interface >>> from zope.browser.interfaces import IView >>> Interface.providedBy(IView) True IBrowserView ------------- Browser views are views specialized for requests from a browser (e.g., as distinct from WebDAV, FTP, XML-RPC, etc.). There is not much we can test except that ``IBrowserView`` is importable and an interface derived from ``IView``: >>> from zope.interface import Interface >>> from zope.browser.interfaces import IBrowserView >>> Interface.providedBy(IBrowserView) True >>> IBrowserView.extends(IView) True IAdding ------- Adding views manage how newly-created items get added to containers. There is not much we can test except that ``IAdding`` is importable and an interface derived from ``IBrowserView``: >>> from zope.interface import Interface >>> from zope.browser.interfaces import IAdding >>> Interface.providedBy(IBrowserView) True >>> IAdding.extends(IBrowserView) True ITerms ------ The ``ITerms`` interface is used as a base for ``ISource`` widget implementations. This interfaces get used by ``zope.app.form`` and was initially defined in ``zope.app.form.browser.interfaces``, which made it impossible to use for other packages like ``z3c.form`` wihtout depending on ``zope.app.form``. Moving such base components / interfaces to ``zope.browser`` makes it possible to share them without undesirable dependencies. There is not much we can test except that ITerms is importable and an interface: >>> from zope.interface import Interface >>> from zope.browser.interfaces import ITerms >>> Interface.providedBy(ITerms) True ISystemErrorView ---------------- Views providing this interface can classify their contexts as system errors. These errors can be handled in a special way (e. g. more detailed logging). There is not much we can test except that ISystemErrorView is importable and an interface: >>> from zope.interface import Interface >>> from zope.browser.interfaces import ISystemErrorView >>> Interface.providedBy(ISystemErrorView) True Changelog ========= 1.3 (2010-04-30) ---------------- - Removed test extra and zope.testing dependency. 1.2 (2009-05-18) ---------------- - Moved ``ISystemErrorView`` interface here from ``zope.app.exception`` to break undesirable dependencies. - Fixed home page and author's e-mail address. - Added doctests to long_description. 1.1 (2009-05-13) ---------------- - Moved ``IAdding`` interface here from ``zope.app.container.interfaces`` to break undesirable dependencies. 1.0 (2009-05-13) ---------------- - Moved ``IView`` and ``IBrowserView`` interfaces here from ``zope.publisher.interfaces`` to break undesirable dependencies. 0.5.0 (2008-12-11) ------------------ - Moved ``ITerms`` interface here from ``zope.app.form.browser.interfaces`` to break undesirable dependencies. Keywords: zope browser component Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.browser/pip-egg-info/0000755000175000017500000000000012214017525020356 5ustar arnauarnauzope2.13-2.13.21/source/zope.browser/pip-egg-info/zope.browser.egg-info/0000755000175000017500000000000012214017525024507 5ustar arnauarnauzope2.13-2.13.21/source/zope.browser/pip-egg-info/zope.browser.egg-info/PKG-INFO0000644000175000017500000001133612214017525025610 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.browser Version: 1.3 Summary: Shared Zope Toolkit browser components Home-page: http://pypi.python.org/pypi/zope.browser Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: zope.browser ============ This package provides shared browser components for the Zope Toolkit. .. contents:: IView ----- Views adapt both a context and a request. There is not much we can test except that ``IView`` is importable and an interface: >>> from zope.interface import Interface >>> from zope.browser.interfaces import IView >>> Interface.providedBy(IView) True IBrowserView ------------- Browser views are views specialized for requests from a browser (e.g., as distinct from WebDAV, FTP, XML-RPC, etc.). There is not much we can test except that ``IBrowserView`` is importable and an interface derived from ``IView``: >>> from zope.interface import Interface >>> from zope.browser.interfaces import IBrowserView >>> Interface.providedBy(IBrowserView) True >>> IBrowserView.extends(IView) True IAdding ------- Adding views manage how newly-created items get added to containers. There is not much we can test except that ``IAdding`` is importable and an interface derived from ``IBrowserView``: >>> from zope.interface import Interface >>> from zope.browser.interfaces import IAdding >>> Interface.providedBy(IBrowserView) True >>> IAdding.extends(IBrowserView) True ITerms ------ The ``ITerms`` interface is used as a base for ``ISource`` widget implementations. This interfaces get used by ``zope.app.form`` and was initially defined in ``zope.app.form.browser.interfaces``, which made it impossible to use for other packages like ``z3c.form`` wihtout depending on ``zope.app.form``. Moving such base components / interfaces to ``zope.browser`` makes it possible to share them without undesirable dependencies. There is not much we can test except that ITerms is importable and an interface: >>> from zope.interface import Interface >>> from zope.browser.interfaces import ITerms >>> Interface.providedBy(ITerms) True ISystemErrorView ---------------- Views providing this interface can classify their contexts as system errors. These errors can be handled in a special way (e. g. more detailed logging). There is not much we can test except that ISystemErrorView is importable and an interface: >>> from zope.interface import Interface >>> from zope.browser.interfaces import ISystemErrorView >>> Interface.providedBy(ISystemErrorView) True Changelog ========= 1.3 (2010-04-30) ---------------- - Removed test extra and zope.testing dependency. 1.2 (2009-05-18) ---------------- - Moved ``ISystemErrorView`` interface here from ``zope.app.exception`` to break undesirable dependencies. - Fixed home page and author's e-mail address. - Added doctests to long_description. 1.1 (2009-05-13) ---------------- - Moved ``IAdding`` interface here from ``zope.app.container.interfaces`` to break undesirable dependencies. 1.0 (2009-05-13) ---------------- - Moved ``IView`` and ``IBrowserView`` interfaces here from ``zope.publisher.interfaces`` to break undesirable dependencies. 0.5.0 (2008-12-11) ------------------ - Moved ``ITerms`` interface here from ``zope.app.form.browser.interfaces`` to break undesirable dependencies. Keywords: zope browser component Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.browser/pip-egg-info/zope.browser.egg-info/dependency_links.txt0000644000175000017500000000000112214017525030555 0ustar arnauarnau zope2.13-2.13.21/source/zope.browser/pip-egg-info/zope.browser.egg-info/requires.txt0000644000175000017500000000003112214017525027101 0ustar arnauarnausetuptools zope.interfacezope2.13-2.13.21/source/zope.browser/pip-egg-info/zope.browser.egg-info/namespace_packages.txt0000644000175000017500000000000512214017525031035 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.browser/pip-egg-info/zope.browser.egg-info/top_level.txt0000644000175000017500000000000512214017525027234 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.browser/pip-egg-info/zope.browser.egg-info/SOURCES.txt0000644000175000017500000000073512214017525026400 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.browser.egg-info/PKG-INFO pip-egg-info/zope.browser.egg-info/SOURCES.txt pip-egg-info/zope.browser.egg-info/dependency_links.txt pip-egg-info/zope.browser.egg-info/namespace_packages.txt pip-egg-info/zope.browser.egg-info/not-zip-safe pip-egg-info/zope.browser.egg-info/requires.txt pip-egg-info/zope.browser.egg-info/top_level.txt src/zope/__init__.py src/zope/browser/__init__.py src/zope/browser/interfaces.py src/zope/browser/tests.pyzope2.13-2.13.21/source/zope.browser/pip-egg-info/zope.browser.egg-info/not-zip-safe0000644000175000017500000000000112214017525026735 0ustar arnauarnau zope2.13-2.13.21/source/zope.browser/README.txt0000644000175000017500000000014112214017525017567 0ustar arnauarnauzope.browser ============ This package provides shared browser components for the Zope Toolkit. zope2.13-2.13.21/source/zope.browser/setup.cfg0000644000175000017500000000007312214017525017716 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.browser/buildout.cfg0000644000175000017500000000025312214017525020405 0ustar arnauarnau[buildout] develop = . parts = test py [test] recipe = zc.recipe.testrunner eggs = zope.browser [test] [py] recipe = zc.recipe.egg eggs = zope.browser interpreter = py zope2.13-2.13.21/source/zope.browser/bootstrap.py0000644000175000017500000000733612214017525020475 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id$ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.browser/CHANGES.txt0000644000175000017500000000144112214017525017706 0ustar arnauarnauChangelog ========= 1.3 (2010-04-30) ---------------- - Removed test extra and zope.testing dependency. 1.2 (2009-05-18) ---------------- - Moved ``ISystemErrorView`` interface here from ``zope.app.exception`` to break undesirable dependencies. - Fixed home page and author's e-mail address. - Added doctests to long_description. 1.1 (2009-05-13) ---------------- - Moved ``IAdding`` interface here from ``zope.app.container.interfaces`` to break undesirable dependencies. 1.0 (2009-05-13) ---------------- - Moved ``IView`` and ``IBrowserView`` interfaces here from ``zope.publisher.interfaces`` to break undesirable dependencies. 0.5.0 (2008-12-11) ------------------ - Moved ``ITerms`` interface here from ``zope.app.form.browser.interfaces`` to break undesirable dependencies. zope2.13-2.13.21/source/zope.browser/src/0000755000175000017500000000000012214017525016664 5ustar arnauarnauzope2.13-2.13.21/source/zope.browser/src/zope/0000755000175000017500000000000012214017525017641 5ustar arnauarnauzope2.13-2.13.21/source/zope.browser/src/zope/__init__.py0000644000175000017500000000031012214017525021744 0ustar arnauarnau# this is a namespace package try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: import pkgutil __path__ = pkgutil.extend_path(__path__, __name__) zope2.13-2.13.21/source/zope.browser/src/zope/browser/0000755000175000017500000000000012214017525021324 5ustar arnauarnauzope2.13-2.13.21/source/zope.browser/src/zope/browser/interfaces.py0000644000175000017500000000657612214017525024037 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2009 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Shared dependency less Zope3 brwoser components. """ __docformat__ = 'restructuredtext' from zope.interface import Attribute from zope.interface import Interface class IView(Interface): """ Views are multi-adapters for context and request objects. """ context = Attribute("The context object the view renders") request = Attribute("The request object driving the view") class IBrowserView(IView): """ Views which are specialized for requests from a browser o Such views are distinct from those geerated via WebDAV, FTP, XML-RPC, etc.. """ class IAdding(IBrowserView): """ Multi-adapter interface for views which add items to containers. o The 'context' of the view must implement ``zope.container.IContainer``. """ def add(content): """Add content object to context. Add using the name in `contentName`. Return the added object in the context of its container. If `contentName` is already used in container, raise ``zope.container.interfaces.DuplicateIDError``. """ contentName = Attribute( """The content name, usually set by the Adder traverser. If the content name hasn't been defined yet, returns ``None``. Some creation views might use this to optionally display the name on forms. """ ) def nextURL(): """Return the URL that the creation view should redirect to. This is called by the creation view after calling add. It is the adder's responsibility, not the creation view's to decide what page to display after content is added. """ def nameAllowed(): """Return whether names can be input by the user. """ def addingInfo(): """Return add menu data as a sequence of mappings. Each mapping contains 'action', 'title', and possibly other keys. The result is sorted by title. """ def isSingleMenuItem(): """Return whether there is single menu item or not.""" def hasCustomAddView(): "This should be called only if there is `singleMenuItem` else return 0" class ITerms(Interface): """ Adapter providing lookups for vocabulary terms. """ def getTerm(value): """Return an ITitledTokenizedTerm object for the given value LookupError is raised if the value isn't in the source """ def getValue(token): """Return a value for a given identifier token LookupError is raised if there isn't a value in the source. """ class ISystemErrorView(Interface): """Error views that can classify their contexts as system errors """ def isSystemError(): """Return a boolean indicating whether the error is a system errror """ zope2.13-2.13.21/source/zope.browser/src/zope/browser/__init__.py0000644000175000017500000000002112214017525023426 0ustar arnauarnau# make a package zope2.13-2.13.21/source/zope.browser/src/zope/browser/README.txt0000644000175000017500000000424612214017525023030 0ustar arnauarnauIView ----- Views adapt both a context and a request. There is not much we can test except that ``IView`` is importable and an interface: >>> from zope.interface import Interface >>> from zope.browser.interfaces import IView >>> Interface.providedBy(IView) True IBrowserView ------------- Browser views are views specialized for requests from a browser (e.g., as distinct from WebDAV, FTP, XML-RPC, etc.). There is not much we can test except that ``IBrowserView`` is importable and an interface derived from ``IView``: >>> from zope.interface import Interface >>> from zope.browser.interfaces import IBrowserView >>> Interface.providedBy(IBrowserView) True >>> IBrowserView.extends(IView) True IAdding ------- Adding views manage how newly-created items get added to containers. There is not much we can test except that ``IAdding`` is importable and an interface derived from ``IBrowserView``: >>> from zope.interface import Interface >>> from zope.browser.interfaces import IAdding >>> Interface.providedBy(IBrowserView) True >>> IAdding.extends(IBrowserView) True ITerms ------ The ``ITerms`` interface is used as a base for ``ISource`` widget implementations. This interfaces get used by ``zope.app.form`` and was initially defined in ``zope.app.form.browser.interfaces``, which made it impossible to use for other packages like ``z3c.form`` wihtout depending on ``zope.app.form``. Moving such base components / interfaces to ``zope.browser`` makes it possible to share them without undesirable dependencies. There is not much we can test except that ITerms is importable and an interface: >>> from zope.interface import Interface >>> from zope.browser.interfaces import ITerms >>> Interface.providedBy(ITerms) True ISystemErrorView ---------------- Views providing this interface can classify their contexts as system errors. These errors can be handled in a special way (e. g. more detailed logging). There is not much we can test except that ISystemErrorView is importable and an interface: >>> from zope.interface import Interface >>> from zope.browser.interfaces import ISystemErrorView >>> Interface.providedBy(ISystemErrorView) True zope2.13-2.13.21/source/zope.browser/src/zope/browser/tests.py0000644000175000017500000000172612214017525023046 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ $Id:$ """ __docformat__ = "reStructuredText" import doctest import unittest def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('README.txt', optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS, ), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.browser/src/zope.browser.egg-info/0000755000175000017500000000000012214017525023015 5ustar arnauarnauzope2.13-2.13.21/source/zope.browser/src/zope.browser.egg-info/PKG-INFO0000644000175000017500000001133612214017525024116 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.browser Version: 1.3 Summary: Shared Zope Toolkit browser components Home-page: http://pypi.python.org/pypi/zope.browser Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: zope.browser ============ This package provides shared browser components for the Zope Toolkit. .. contents:: IView ----- Views adapt both a context and a request. There is not much we can test except that ``IView`` is importable and an interface: >>> from zope.interface import Interface >>> from zope.browser.interfaces import IView >>> Interface.providedBy(IView) True IBrowserView ------------- Browser views are views specialized for requests from a browser (e.g., as distinct from WebDAV, FTP, XML-RPC, etc.). There is not much we can test except that ``IBrowserView`` is importable and an interface derived from ``IView``: >>> from zope.interface import Interface >>> from zope.browser.interfaces import IBrowserView >>> Interface.providedBy(IBrowserView) True >>> IBrowserView.extends(IView) True IAdding ------- Adding views manage how newly-created items get added to containers. There is not much we can test except that ``IAdding`` is importable and an interface derived from ``IBrowserView``: >>> from zope.interface import Interface >>> from zope.browser.interfaces import IAdding >>> Interface.providedBy(IBrowserView) True >>> IAdding.extends(IBrowserView) True ITerms ------ The ``ITerms`` interface is used as a base for ``ISource`` widget implementations. This interfaces get used by ``zope.app.form`` and was initially defined in ``zope.app.form.browser.interfaces``, which made it impossible to use for other packages like ``z3c.form`` wihtout depending on ``zope.app.form``. Moving such base components / interfaces to ``zope.browser`` makes it possible to share them without undesirable dependencies. There is not much we can test except that ITerms is importable and an interface: >>> from zope.interface import Interface >>> from zope.browser.interfaces import ITerms >>> Interface.providedBy(ITerms) True ISystemErrorView ---------------- Views providing this interface can classify their contexts as system errors. These errors can be handled in a special way (e. g. more detailed logging). There is not much we can test except that ISystemErrorView is importable and an interface: >>> from zope.interface import Interface >>> from zope.browser.interfaces import ISystemErrorView >>> Interface.providedBy(ISystemErrorView) True Changelog ========= 1.3 (2010-04-30) ---------------- - Removed test extra and zope.testing dependency. 1.2 (2009-05-18) ---------------- - Moved ``ISystemErrorView`` interface here from ``zope.app.exception`` to break undesirable dependencies. - Fixed home page and author's e-mail address. - Added doctests to long_description. 1.1 (2009-05-13) ---------------- - Moved ``IAdding`` interface here from ``zope.app.container.interfaces`` to break undesirable dependencies. 1.0 (2009-05-13) ---------------- - Moved ``IView`` and ``IBrowserView`` interfaces here from ``zope.publisher.interfaces`` to break undesirable dependencies. 0.5.0 (2008-12-11) ------------------ - Moved ``ITerms`` interface here from ``zope.app.form.browser.interfaces`` to break undesirable dependencies. Keywords: zope browser component Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.browser/src/zope.browser.egg-info/dependency_links.txt0000644000175000017500000000000112214017525027063 0ustar arnauarnau zope2.13-2.13.21/source/zope.browser/src/zope.browser.egg-info/requires.txt0000644000175000017500000000003112214017525025407 0ustar arnauarnausetuptools zope.interfacezope2.13-2.13.21/source/zope.browser/src/zope.browser.egg-info/namespace_packages.txt0000644000175000017500000000000512214017525027343 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.browser/src/zope.browser.egg-info/top_level.txt0000644000175000017500000000000512214017525025542 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.browser/src/zope.browser.egg-info/SOURCES.txt0000644000175000017500000000073712214017525024710 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.browser.egg-info/PKG-INFO src/zope.browser.egg-info/SOURCES.txt src/zope.browser.egg-info/dependency_links.txt src/zope.browser.egg-info/namespace_packages.txt src/zope.browser.egg-info/not-zip-safe src/zope.browser.egg-info/requires.txt src/zope.browser.egg-info/top_level.txt src/zope/browser/README.txt src/zope/browser/__init__.py src/zope/browser/interfaces.py src/zope/browser/tests.pyzope2.13-2.13.21/source/zope.browser/src/zope.browser.egg-info/not-zip-safe0000644000175000017500000000000112214017525025243 0ustar arnauarnau zope2.13-2.13.21/source/zope.processlifetime/0000755000175000017500000000000012214017606017607 5ustar arnauarnauzope2.13-2.13.21/source/zope.processlifetime/setup.py0000644000175000017500000000375212214017606021330 0ustar arnauarnau############################################################################## # # Copyright (c) 2009 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Setup for zope.processlifetime package """ import os from setuptools import find_packages from setuptools import setup def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup( name='zope.processlifetime', version = '1.0', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', description="Zope process lifetime events", long_description=(read('README.txt') + '\n\n' + read('CHANGES.txt')), license='ZPL 2.1', keywords="zope process lifetime events", classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3', 'Framework :: Zope2', ], url='http://pypi.python.org/pypi/zope.processlifetime', packages=find_packages('src'), package_dir={'': 'src'}, test_suite='zope.processlifetime.tests', namespace_packages=['zope'], install_requires=['setuptools', 'zope.interface', ], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/zope.processlifetime/PKG-INFO0000644000175000017500000000246612214017606020714 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.processlifetime Version: 1.0 Summary: Zope process lifetime events Home-page: http://pypi.python.org/pypi/zope.processlifetime Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: zope.processlifetime README =========================== This package provides interfaces / implementations for events relative to the lifetime of a server process (startup, database opening, etc.) It is derived from Zope 3.4's 'zope.app.appsetup'. zope.processlifetime Changelog ============================== 1.0 (2009-05-13) ---------------- - Split out event interfaces / implementations from ``zope.app.appsetup`` version 3.10.2. Keywords: zope process lifetime events Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 Classifier: Framework :: Zope2 zope2.13-2.13.21/source/zope.processlifetime/pip-egg-info/0000755000175000017500000000000012214017606022070 5ustar arnauarnauzope2.13-2.13.21/source/zope.processlifetime/pip-egg-info/zope.processlifetime.egg-info/0000755000175000017500000000000012214017606027733 5ustar arnauarnauzope2.13-2.13.21/source/zope.processlifetime/pip-egg-info/zope.processlifetime.egg-info/PKG-INFO0000644000175000017500000000247012214017606031033 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.processlifetime Version: 1.0 Summary: Zope process lifetime events Home-page: http://pypi.python.org/pypi/zope.processlifetime Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: zope.processlifetime README =========================== This package provides interfaces / implementations for events relative to the lifetime of a server process (startup, database opening, etc.) It is derived from Zope 3.4's 'zope.app.appsetup'. zope.processlifetime Changelog ============================== 1.0 (2009-05-13) ---------------- - Split out event interfaces / implementations from ``zope.app.appsetup`` version 3.10.2. Keywords: zope process lifetime events Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 Classifier: Framework :: Zope2 ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.processlifetime/pip-egg-info/zope.processlifetime.egg-info/dependency_links.txtzope2.13-2.13.21/source/zope.processlifetime/pip-egg-info/zope.processlifetime.egg-info/dependency_l0000644000175000017500000000000112214017606032276 0ustar arnauarnau zope2.13-2.13.21/source/zope.processlifetime/pip-egg-info/zope.processlifetime.egg-info/requires.txt0000644000175000017500000000003112214017606032325 0ustar arnauarnausetuptools zope.interface././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.processlifetime/pip-egg-info/zope.processlifetime.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.processlifetime/pip-egg-info/zope.processlifetime.egg-info/namespace_pa0000644000175000017500000000000512214017606032265 0ustar arnauarnauzope ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.processlifetime/pip-egg-info/zope.processlifetime.egg-info/top_level.txtzope2.13-2.13.21/source/zope.processlifetime/pip-egg-info/zope.processlifetime.egg-info/top_level.tx0000644000175000017500000000000512214017606032274 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.processlifetime/pip-egg-info/zope.processlifetime.egg-info/SOURCES.txt0000644000175000017500000000100612214017606031614 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.processlifetime.egg-info/PKG-INFO pip-egg-info/zope.processlifetime.egg-info/SOURCES.txt pip-egg-info/zope.processlifetime.egg-info/dependency_links.txt pip-egg-info/zope.processlifetime.egg-info/namespace_packages.txt pip-egg-info/zope.processlifetime.egg-info/not-zip-safe pip-egg-info/zope.processlifetime.egg-info/requires.txt pip-egg-info/zope.processlifetime.egg-info/top_level.txt src/zope/__init__.py src/zope/processlifetime/__init__.py src/zope/processlifetime/tests.pyzope2.13-2.13.21/source/zope.processlifetime/pip-egg-info/zope.processlifetime.egg-info/not-zip-safe0000644000175000017500000000000112214017606032161 0ustar arnauarnau zope2.13-2.13.21/source/zope.processlifetime/README.txt0000644000175000017500000000037212214017606021307 0ustar arnauarnauzope.processlifetime README =========================== This package provides interfaces / implementations for events relative to the lifetime of a server process (startup, database opening, etc.) It is derived from Zope 3.4's 'zope.app.appsetup'. zope2.13-2.13.21/source/zope.processlifetime/setup.cfg0000644000175000017500000000007312214017606021430 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.processlifetime/buildout.cfg0000644000175000017500000000040512214017606022116 0ustar arnauarnau[buildout] develop = . parts = test python tags [test] recipe = zc.recipe.testrunner eggs = zope.processlifetime [python] recipe = zc.recipe.egg eggs = zope.processlifetime interpreter = python [tags] recipe = z3c.recipe.tag:tags eggs = zope.processlifetime zope2.13-2.13.21/source/zope.processlifetime/bootstrap.py0000644000175000017500000000337012214017606022201 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 73531 2007-03-25 07:22:18Z dobe $ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.processlifetime/CHANGES.txt0000644000175000017500000000027612214017606021425 0ustar arnauarnauzope.processlifetime Changelog ============================== 1.0 (2009-05-13) ---------------- - Split out event interfaces / implementations from ``zope.app.appsetup`` version 3.10.2. zope2.13-2.13.21/source/zope.processlifetime/src/0000755000175000017500000000000012214017606020376 5ustar arnauarnauzope2.13-2.13.21/source/zope.processlifetime/src/zope/0000755000175000017500000000000012214017606021353 5ustar arnauarnauzope2.13-2.13.21/source/zope.processlifetime/src/zope/processlifetime/0000755000175000017500000000000012214017606024550 5ustar arnauarnauzope2.13-2.13.21/source/zope.processlifetime/src/zope/processlifetime/__init__.py0000644000175000017500000000275312214017606026670 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2009 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Events in the lifetime of a server process. """ from zope.interface import Attribute from zope.interface import Interface from zope.interface import implements class IDatabaseOpened(Interface): """The main database has been opened. """ database = Attribute("The main database.") class DatabaseOpened(object): implements(IDatabaseOpened) def __init__(self, database): self.database = database class IDatabaseOpenedWithRoot(Interface): """The main database has been opened. """ database = Attribute("The main database.") class DatabaseOpenedWithRoot(object): implements(IDatabaseOpenedWithRoot) def __init__(self, database): self.database = database class IProcessStarting(Interface): """The application server process is starting. """ class ProcessStarting(object): implements(IProcessStarting) zope2.13-2.13.21/source/zope.processlifetime/src/zope/processlifetime/tests.py0000644000175000017500000000542712214017606026274 0ustar arnauarnau############################################################################## # # Copyright (c) 2009 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest class DatabaseOpenedTests(unittest.TestCase): def _getTargetClass(self): from zope.processlifetime import DatabaseOpened return DatabaseOpened def test_class_conforms_to_IDatabaseOpened(self): from zope.interface.verify import verifyClass from zope.processlifetime import IDatabaseOpened verifyClass(IDatabaseOpened, self._getTargetClass()) def test_instance_conforms_to_IDatabaseOpened(self): from zope.interface.verify import verifyObject from zope.processlifetime import IDatabaseOpened verifyObject(IDatabaseOpened, self._getTargetClass()(object())) class DatabaseOpenedWithRootTests(unittest.TestCase): def _getTargetClass(self): from zope.processlifetime import DatabaseOpenedWithRoot return DatabaseOpenedWithRoot def test_class_conforms_to_IDatabaseOpenedWithRoot(self): from zope.interface.verify import verifyClass from zope.processlifetime import IDatabaseOpenedWithRoot verifyClass(IDatabaseOpenedWithRoot, self._getTargetClass()) def test_instance_conforms_to_IDatabaseOpenedWithRoot(self): from zope.interface.verify import verifyObject from zope.processlifetime import IDatabaseOpenedWithRoot verifyObject(IDatabaseOpenedWithRoot, self._getTargetClass()(object())) class ProcessStartingTests(unittest.TestCase): def _getTargetClass(self): from zope.processlifetime import ProcessStarting return ProcessStarting def test_class_conforms_to_IProcessStarting(self): from zope.interface.verify import verifyClass from zope.processlifetime import IProcessStarting verifyClass(IProcessStarting, self._getTargetClass()) def test_instance_conforms_to_IProcessStarting(self): from zope.interface.verify import verifyObject from zope.processlifetime import IProcessStarting verifyObject(IProcessStarting, self._getTargetClass()()) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(DatabaseOpenedTests), unittest.makeSuite(DatabaseOpenedWithRootTests), unittest.makeSuite(ProcessStartingTests), )) zope2.13-2.13.21/source/zope.processlifetime/src/zope/__init__.py0000644000175000017500000000031112214017606023457 0ustar arnauarnau# this is a namespace package try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: import pkgutil __path__ = pkgutil.extend_path(__path__, __name__) zope2.13-2.13.21/source/zope.processlifetime/src/zope.processlifetime.egg-info/0000755000175000017500000000000012214017606026241 5ustar arnauarnauzope2.13-2.13.21/source/zope.processlifetime/src/zope.processlifetime.egg-info/PKG-INFO0000644000175000017500000000246612214017606027346 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.processlifetime Version: 1.0 Summary: Zope process lifetime events Home-page: http://pypi.python.org/pypi/zope.processlifetime Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: zope.processlifetime README =========================== This package provides interfaces / implementations for events relative to the lifetime of a server process (startup, database opening, etc.) It is derived from Zope 3.4's 'zope.app.appsetup'. zope.processlifetime Changelog ============================== 1.0 (2009-05-13) ---------------- - Split out event interfaces / implementations from ``zope.app.appsetup`` version 3.10.2. Keywords: zope process lifetime events Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 Classifier: Framework :: Zope2 zope2.13-2.13.21/source/zope.processlifetime/src/zope.processlifetime.egg-info/dependency_links.txt0000644000175000017500000000000112214017606032307 0ustar arnauarnau zope2.13-2.13.21/source/zope.processlifetime/src/zope.processlifetime.egg-info/requires.txt0000644000175000017500000000003112214017606030633 0ustar arnauarnausetuptools zope.interface././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.processlifetime/src/zope.processlifetime.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.processlifetime/src/zope.processlifetime.egg-info/namespace_packages.tx0000644000175000017500000000000512214017606032403 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.processlifetime/src/zope.processlifetime.egg-info/top_level.txt0000644000175000017500000000000512214017606030766 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.processlifetime/src/zope.processlifetime.egg-info/SOURCES.txt0000644000175000017500000000075412214017606030133 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.processlifetime.egg-info/PKG-INFO src/zope.processlifetime.egg-info/SOURCES.txt src/zope.processlifetime.egg-info/dependency_links.txt src/zope.processlifetime.egg-info/namespace_packages.txt src/zope.processlifetime.egg-info/not-zip-safe src/zope.processlifetime.egg-info/requires.txt src/zope.processlifetime.egg-info/top_level.txt src/zope/processlifetime/__init__.py src/zope/processlifetime/tests.pyzope2.13-2.13.21/source/zope.processlifetime/src/zope.processlifetime.egg-info/not-zip-safe0000644000175000017500000000000112214017606030467 0ustar arnauarnau zope2.13-2.13.21/source/ZODB3/0000755000175000017500000000000012214017465014262 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/setup.py0000644000175000017500000001644512214017464016005 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Zope Object Database: object database and persistence The Zope Object Database provides an object-oriented database for Python that provides a high-degree of transparency. Applications can take advantage of object database features with few, if any, changes to application logic. ZODB includes features such as a plugable storage interface, rich transaction support, and undo. """ VERSION = "3.10.5" from ez_setup import use_setuptools use_setuptools() from setuptools import setup, find_packages from setuptools.extension import Extension import os import sys if sys.version_info < (2, 5): print "This version of ZODB requires Python 2.5 or higher" sys.exit(0) # The (non-obvious!) choices for the Trove Development Status line: # Development Status :: 5 - Production/Stable # Development Status :: 4 - Beta # Development Status :: 3 - Alpha classifiers = """\ Intended Audience :: Developers License :: OSI Approved :: Zope Public License Programming Language :: Python Topic :: Database Topic :: Software Development :: Libraries :: Python Modules Operating System :: Microsoft :: Windows Operating System :: Unix Framework :: ZODB """ # Include directories for C extensions include = ['src'] # Set up dependencies for the BTrees package base_btrees_depends = [ "src/BTrees/BTreeItemsTemplate.c", "src/BTrees/BTreeModuleTemplate.c", "src/BTrees/BTreeTemplate.c", "src/BTrees/BucketTemplate.c", "src/BTrees/MergeTemplate.c", "src/BTrees/SetOpTemplate.c", "src/BTrees/SetTemplate.c", "src/BTrees/TreeSetTemplate.c", "src/BTrees/sorters.c", "src/persistent/cPersistence.h", ] _flavors = {"O": "object", "I": "int", "F": "float", 'L': 'int'} KEY_H = "src/BTrees/%skeymacros.h" VALUE_H = "src/BTrees/%svaluemacros.h" def BTreeExtension(flavor): key = flavor[0] value = flavor[1] name = "BTrees._%sBTree" % flavor sources = ["src/BTrees/_%sBTree.c" % flavor] kwargs = {"include_dirs": include} if flavor != "fs": kwargs["depends"] = (base_btrees_depends + [KEY_H % _flavors[key], VALUE_H % _flavors[value]]) else: kwargs["depends"] = base_btrees_depends if key != "O": kwargs["define_macros"] = [('EXCLUDE_INTSET_SUPPORT', None)] return Extension(name, sources, **kwargs) exts = [BTreeExtension(flavor) for flavor in ("OO", "IO", "OI", "II", "IF", "fs", "LO", "OL", "LL", "LF", )] cPersistence = Extension(name = 'persistent.cPersistence', include_dirs = include, sources= ['src/persistent/cPersistence.c', 'src/persistent/ring.c'], depends = ['src/persistent/cPersistence.h', 'src/persistent/ring.h', 'src/persistent/ring.c'] ) cPickleCache = Extension(name = 'persistent.cPickleCache', include_dirs = include, sources= ['src/persistent/cPickleCache.c', 'src/persistent/ring.c'], depends = ['src/persistent/cPersistence.h', 'src/persistent/ring.h', 'src/persistent/ring.c'] ) TimeStamp = Extension(name = 'persistent.TimeStamp', include_dirs = include, sources= ['src/persistent/TimeStamp.c'] ) exts += [cPersistence, cPickleCache, TimeStamp, ] def _modname(path, base, name=''): if path == base: return name dirname, basename = os.path.split(path) return _modname(dirname, base, basename + '.' + name) def alltests(): import logging import pkg_resources import unittest import ZEO.ClientStorage class NullHandler(logging.Handler): level = 50 def emit(self, record): pass logging.getLogger().addHandler(NullHandler()) suite = unittest.TestSuite() base = pkg_resources.working_set.find( pkg_resources.Requirement.parse('ZODB3')).location for dirpath, dirnames, filenames in os.walk(base): if os.path.basename(dirpath) == 'tests': for filename in filenames: if filename != 'testZEO.py': continue if filename.endswith('.py') and filename.startswith('test'): mod = __import__( _modname(dirpath, base, os.path.splitext(filename)[0]), {}, {}, ['*']) suite.addTest(mod.test_suite()) elif 'tests.py' in filenames: continue mod = __import__(_modname(dirpath, base, 'tests'), {}, {}, ['*']) suite.addTest(mod.test_suite()) return suite doclines = __doc__.split("\n") def read_file(*path): base_dir = os.path.dirname(__file__) file_path = (base_dir, ) + tuple(path) return file(os.path.join(*file_path)).read() long_description = str( ("\n".join(doclines[2:]) + "\n\n" + ".. contents::\n\n" + read_file("README.txt") + "\n\n" + read_file("src", "CHANGES.txt") ).decode('latin-1').replace(u'L\xf6wis', '|Lowis|') )+ '''\n\n.. |Lowis| unicode:: L \\xf6 wis\n''' setup(name="ZODB3", version=VERSION, maintainer="Zope Foundation and Contributors", maintainer_email="zodb-dev@zope.org", packages = find_packages('src'), package_dir = {'': 'src'}, ext_modules = exts, headers = ['src/persistent/cPersistence.h', 'src/persistent/py24compat.h', 'src/persistent/ring.h'], license = "ZPL 2.1", platforms = ["any"], description = doclines[0], classifiers = filter(None, classifiers.split("\n")), long_description = long_description, test_suite="__main__.alltests", # to support "setup.py test" tests_require = ['zope.testing', 'manuel'], extras_require = dict(test=['zope.testing', 'manuel']), install_requires = [ 'transaction >=1.1.0', 'zc.lockfile', 'ZConfig', 'zdaemon', 'zope.event', 'zope.interface', ], zip_safe = False, entry_points = """ [console_scripts] fsdump = ZODB.FileStorage.fsdump:main fsoids = ZODB.scripts.fsoids:main fsrefs = ZODB.scripts.fsrefs:main fstail = ZODB.scripts.fstail:Main repozo = ZODB.scripts.repozo:main zeopack = ZEO.scripts.zeopack:main runzeo = ZEO.runzeo:main zeopasswd = ZEO.zeopasswd:main zeoctl = ZEO.zeoctl:main """, include_package_data = True, ) zope2.13-2.13.21/source/ZODB3/PKG-INFO0000644000175000017500000006077712214017464015377 0ustar arnauarnauMetadata-Version: 1.0 Name: ZODB3 Version: 3.10.5 Summary: Zope Object Database: object database and persistence Home-page: UNKNOWN Author: Zope Foundation and Contributors Author-email: zodb-dev@zope.org License: ZPL 2.1 Description: The Zope Object Database provides an object-oriented database for Python that provides a high-degree of transparency. Applications can take advantage of object database features with few, if any, changes to application logic. ZODB includes features such as a plugable storage interface, rich transaction support, and undo. .. contents:: ==== ZODB ==== Introduction ============ The ZODB package provides a set of tools for using the Zope Object Database (ZODB). The components you get with the ZODB release are as follows: - Core ZODB, including the persistence machinery - Standard storages such as FileStorage - The persistent BTrees modules - ZEO, for scalability needs - documentation (needs a lot more work) Our primary development platforms are Linux, Mac OS X, and Windows XP. The test suite should pass without error on all of these platforms, although it can take a long time on Windows -- longer if you use ZoneAlarm. Many particularly slow tests are skipped unless you pass --all as an argument to test.py. Compatibility ============= ZODB 3.10 requires Python 2.5 or later. Note -- When using ZEO and upgrading from Python 2.4, you need to upgrade clients and servers at the same time, or upgrade clients first and then servers. Clients running Python 2.5 or 2.6 will work with servers running Python 2.4. Clients running Python 2.4 won't work properly with servers running Python 2.5 or later due to changes in the way Python implements exceptions. ZODB ZEO clients from ZODB 3.2 on can talk to ZODB 3.10 servers. ZODB ZEO 3.10 Clients can talk to ZODB 3.8, 3.9, and 3.10 ZEO servers. Note -- ZEO 3.10 servers don't support undo for older clients. Prerequisites ============= You must have Python installed. If you're using a system Python install, make sure development support is installed too. You also need the transaction, zc.lockfile, ZConfig, zdaemon, zope.event, zope.interface, zope.proxy and zope.testing packages. If you don't have them and you can connect to the Python Package Index, then these will be installed for you if you don't have them. Installation ============ ZODB is released as a distutils package. The easiest ways to build and install it are to use `easy_install `_, or `zc.buildout `_. To install by hand, first install the dependencies, ZConfig, zdaemon, zope.interface, zope.proxy and zope.testing. These can be found in the `Python Package Index `_. To run the tests, use the test setup command:: python setup.py test It will download dependencies if needed. If this happens, ou may get an import error when the test command gets to looking for tests. Try running the test command a second time and you should see the tests run. :: python setup.py test To install, use the install command:: python setup.py install Testing for Developers ====================== The ZODB checkouts are `buildouts `_. When working from a ZODB checkout, first run the bootstrap.py script to initialize the buildout: % python bootstrap.py and then use the buildout script to build ZODB and gather the dependencies: % bin/buildout This creates a test script: % bin/test -v This command will run all the tests, printing a single dot for each test. When it finishes, it will print a test summary. The exact number of tests can vary depending on platform and available third-party libraries.:: Ran 1182 tests in 241.269s OK The test script has many more options. Use the ``-h`` or ``--help`` options to see a file list of options. The default test suite omits several tests that depend on third-party software or that take a long time to run. To run all the available tests use the ``--all`` option. Running all the tests takes much longer.:: Ran 1561 tests in 1461.557s OK Maintenance scripts ------------------- Several scripts are provided with the ZODB and can help for analyzing, debugging, checking for consistency, summarizing content, reporting space used by objects, doing backups, artificial load testing, etc. Look at the ZODB/script directory for more informations. History ======= The historical version numbering schemes for ZODB and ZEO are complicated. Starting with ZODB 3.4, the ZODB and ZEO version numbers are the same. In the ZODB 3.1 through 3.3 lines, the ZEO version number was "one smaller" than the ZODB version number; e.g., ZODB 3.2.7 included ZEO 2.2.7. ZODB and ZEO were distinct releases prior to ZODB 3.1, and had independent version numbers. Historically, ZODB was distributed as a part of the Zope application server. Jim Fulton's paper at the Python conference in 2000 described a version of ZODB he called ZODB 3, based on an earlier persistent object system called BoboPOS. The earliest versions of ZODB 3 were released with Zope 2.0. Andrew Kuchling extracted ZODB from Zope 2.4.1 and packaged it for use by standalone Python programs. He called this version "StandaloneZODB". Andrew's guide to using ZODB is included in the Doc directory. This version of ZODB was hosted at http://sf.net/projects/zodb. It supported Python 1.5.2, and might still be of interest to users of this very old Python version. Zope Corporation released a version of ZODB called "StandaloneZODB 1.0" in Feb. 2002. This release was based on Andrew's packaging, but built from the same CVS repository as Zope. It is roughly equivalent to the ZODB in Zope 2.5. Why not call the current release StandaloneZODB? The name StandaloneZODB is a bit of a mouthful. The standalone part of the name suggests that the Zope version is the real version and that this is an afterthought, which isn't the case. So we're calling this release "ZODB". We also worked on a ZODB4 package for a while and made a couple of alpha releases. We've now abandoned that effort, because we didn't have the resources to pursue ot while also maintaining ZODB(3). License ======= ZODB is distributed under the Zope Public License, an OSI-approved open source license. Please see the LICENSE.txt file for terms and conditions. The ZODB/ZEO Programming Guide included in the documentation is a modified version of Andrew Kuchling's original guide, provided under the terms of the GNU Free Documentation License. More information ================ We maintain a Wiki page about all things ZODB, including status on future directions for ZODB. Please see http://wiki.zope.org/ZODB/FrontPage and feel free to contribute your comments. There is a Mailman mailing list in place to discuss all issues related to ZODB. You can send questions to zodb-dev@zope.org or subscribe at http://lists.zope.org/mailman/listinfo/zodb-dev and view its archives at http://lists.zope.org/pipermail/zodb-dev Note that Zope Corp mailing lists have a subscriber-only posting policy. Andrew's ZODB Programmers Guide is made available in several forms, including DVI and HTML. To view it online, point your browser at the file Doc/guide/zodb/index.html Bugs and Patches ================ Bug reports and patches should be added to the Launchpad: https://launchpad.net/zodb .. Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 End: ================ Change History ================ 3.10.5 (2011-11-19) =================== Bugs Fixed ---------- - Conflict resolution failed when state included cross-database persistent references with classes that couldn't be imported. 3.10.4 (2011-11-17) =================== Bugs Fixed ---------- - Conflict resolution failed when state included persistent references with classes that couldn't be imported. 3.10.3 (2011-04-12) =================== Bugs Fixed ---------- - "activity monitor not updated for subconnections when connection returned to pool" https://bugs.launchpad.net/zodb/+bug/737198 - "Blob temp file get's removed before it should", https://bugs.launchpad.net/zodb/+bug/595378 A way this to happen is that a transaction is aborted after the commit process has started. I don't know how this would happen in the wild. In 3.10.3, the ZEO tpc_abort call to the server is changed to be synchronous, which should address this case. Maybe there's another case. Performance enhancements ------------------------ - Improved ZEO client cache implementation to make it less likely to evict objects that are being used. - Small (possibly negligable) reduction in CPU in ZEO storage servers to service object loads and in networking code. 3.10.2 (2011-02-12) =================== Bugs Fixed ---------- - 3.10 introduced an optimization to try to address BTree conflict errors arrising for basing BTree keys on object ids. The optimization caused object ids allocated in aborted transactions to be reused. Unfortunately, this optimzation led to some rather severe failures in some applications. The symptom is a conflict error in which one of the serials mentioned is zero. This optimization has been removed. See (for example): https://bugs.launchpad.net/zodb/+bug/665452 - ZEO server transaction timeouts weren't logged as critical. https://bugs.launchpad.net/zodb/+bug/670986 3.10.1 (2010-10-27) =================== Bugs Fixed ---------- - When a transaction rolled back a savepoint after adding objects and subsequently added more objects and committed, an error could be raised "ValueError: A different object already has the same oid" causing the transaction to fail. Worse, this could leave a database in a state where subsequent transactions in the same process would fail. https://bugs.launchpad.net/zodb/+bug/665452 - Unix domain sockets didn't work for ZEO (since the addition of IPv6 support). https://bugs.launchpad.net/zodb/+bug/663259 - Removed a missfeature that can cause performance problems when using an external garbage collector with ZEO. When objects were deleted from a storage, invalidations were sent to clients. This makes no sense. It's wildly unlikely that the other connections/clients have copies of the garbage. In normal storage garbage collection, we don't send invalidations. There's no reason to send them when an external garbage collector is used. - ZEO client cache simulation misshandled invalidations causing incorrect statistics and errors. 3.10.0 (2010-10-08) =================== New Features ------------ - There are a number of performance enhancements for ZEO storage servers. - FileStorage indexes use a new format. They are saved and loaded much faster and take less space. Old indexes can still be read, but new indexes won't be readable by older versions of ZODB. - The API for undoing multiple transactions has changed. To undo multiple transactions in a single transaction, pass a list of transaction identifiers to a database's undoMultiple method. Calling a database's undo method multiple times in the same transaction now raises an exception. - The ZEO protocol for undo has changed. The only user-visible consequence of this is that when ZODB 3.10 ZEO servers won't support undo for older clients. - The storage API (IStorage) has been tightened. Now, storages should raise a StorageTransactionError when invalid transactions are passed to tpc_begin, tpc_vote, or tpc_finish. - ZEO clients (``ClientStorage`` instances) now work in forked processes, including those created via ``multiprocessing.Process`` instances. - Broken objects now provide the IBroken interface. - As a convenience, you can now pass an integer port as an address to the ZEO ClientStorage constructor. - As a convenience, there's a new ``client`` function in the ZEO package for constructing a ClientStorage instance. It takes the same arguments as the ClientStorage constructor. - DemoStorages now accept constructor athuments, close_base_on_close and close_changes_on_close, to control whether underlying storages are closed when the DemoStorage is closed. https://bugs.launchpad.net/zodb/+bug/118512 - Removed the dependency on zope.proxy. - Removed support for the _p_independent mini framework, which was made moot by the introduction of multi-version concurrency control several years ago. - Added support for the transaction retry convenience (transaction-manager attempts method) introduced in the ``transaction`` 1.1.0 release. - Enhanced the database opening conveniences: - You can now pass storage keyword arguments to ZODB.DB and ZODB.connection. - You can now pass None (rather than a storage or file name) to get a database with a mapping storage. - Databases now warn when committing very large records (> 16MB). This is to try to warn people of likely design mistakes. There is a new option (large_record_size/large-record-size) to control the record size at which the warning is issued. - Added support for wrapper storages that transform pickle data. Applications for this include compression and encryption. An example wrapper storage implementation, ZODB.tests.hexstorage, was included for testing. It is important that storage implementations not assume that storages contain pickles. Renamed IStorageDB to IStorageWrapper and expanded it to provide methods for transforming and untransforming data records. Storages implementations should use these methods to get pickle data from stored records. - Deprecated ZODB.interfaces.StorageStopIteration. Storage iterator implementations should just raise StopIteration, which means they can now be implemented as generators. - The filestorage packer configuration option noe accepts values of the form ``modname:expression``, allowing the use of packer factories with options. - Added a new API that allows applications to make sure that current data are read. For example, with:: self._p_jar.readCurrent(ob) A conflict error will be raised if the version of ob read by the transaction isn't current when the transaction is committed. Normally, ZODB only assures that objects read are consistent, but not necessarily up to date. Checking whether an object is up to date is important when information read from one object is used to update another. BTrees are an important case of reading one object to update another. Internal nodes are read to decide which leave notes are updated when a BTree is updated. BTrees now use this new API to make sure that internal nodes are up to date on updates. - When transactions are aborted, new object ids allocated during the transaction are saved and used in subsequent transactions. This can help in situations where object ids are used as BTree keys and the sequential allocation of object ids leads to conflict errors. - ZEO servers now support a server_status method for for getting information on the number of clients, lock requests and general statistics. - ZEO clients now support a client_label constructor argument and client-label configuration-file option to specify a label for a client in server logs. This makes it easier to identify specific clients corresponding to server log entries, especially when there are multiple clients originating from the same machine. - Improved ZEO server commit lock logging. Now, locking activity is logged at the debug level until the number of waiting lock requests gets above 3. Log at the critical level when the number of waiting lock requests gets above 9. - The file-storage backup script, repozo, will now create a backup index file if an output file name is given via the --output/-o option. - Added a '--kill-old-on-full' argument to the repozo backup options: if passed, remove any older full or incremental backup files from the repository after doing a full backup. (https://bugs.launchpad.net/zope2/+bug/143158) - The mkzeoinst script has been moved to a separate project: http://pypi.python.org/pypi/zope.mkzeoinstance and is no-longer included with ZODB. - Removed untested unsupported dbmstorage fossile. - ZEO servers no longer log their pids in every log message. It's just not interesting. :) Bugs fixed ---------- - When a pool timeout was specified for a database and old connections were removed due to timing out, an error occured due to a bug in the connection cleanup logic. - When multi-database connections were no longer used and cleaned up, their subconnections weren't cleaned up properly. - ZEO didn't work with IPv6 addrsses. Added IPv6 support contributed by Martin v. |Lowis|. - A file storage bug could cause ZEO clients to have incorrect information about current object revisions after reconnecting to a database server. - Updated the 'repozo --kill-old-on-full' option to remove any '.index' files corresponding to backups being removed. - ZEO extension methods failed when a client reconnected to a storage. (https://bugs.launchpad.net/zodb/+bug/143344) - Clarified the return Value for lastTransaction in the case when there aren't any transactions. Now a string of 8 nulls (aka "z64") is specified. - Setting _p_changed on a blob wo actually writing anything caused an error. (https://bugs.launchpad.net/zodb/+bug/440234) - The verbose mode of the fstest was broken. (https://bugs.launchpad.net/zodb/+bug/475996) - Object ids created in a savepoint that is rolled back wren't being reused. (https://bugs.launchpad.net/zodb/+bug/588389) - Database connections didn't invalidate cache entries when conflict errors were raised in response to checkCurrentSerialInTransaction errors. Normally, this shouldn't be a problem, since there should be pending invalidations for these oids which will cause the object to be invalidated. There have been issues with ZEO persistent cache management that have caused out of date data to remain in the cache. (It's possible that the last of these were addressed in the 3.10.0b5.) Invalidating read data when there is a conflict error provides some extra insurance. - The interface, ZODB.interfaces.IStorage was incorrect. The store method should never return a sequence of oid and serial pairs. - When a demo storage push method was used to create a new demo storage and the new storage was closed, the original was (incorrectly) closed. - There were numerous bugs in the ZEO cache tracing and analysis code. Cache simulation, while not perfect, seems to be much more accurate now than it was before. The ZEO cache trace statistics and simulation scripts have been given more descriptive names and moved to the ZEO scripts package. - BTree sets and tree sets didn't correctly check values passed to update or to constructors, causing Python to exit under certain circumstances. - Fixed bug in copying a BTrees.Length instance. (https://bugs.launchpad.net/zodb/+bug/516653) - Fixed a serious bug that caused cache failures when run with Python optimization turned on. https://bugs.launchpad.net/zodb/+bug/544305 - When using using a ClientStorage in a Storage server, there was a threading bug that caused clients to get disconnected. - On Mac OS X, clients that connected and disconnected quickly could cause a ZEO server to stop accepting connections, due to a failure to catch errors in the initial part of the connection process. The failure to properly handle exceptions while accepting connections is potentially problematic on other platforms. Fixes: https://bugs.launchpad.net/zodb/+bug/135108 - Object state management wasn't done correctly when classes implemented custom _p_deavtivate methods. (https://bugs.launchpad.net/zodb/+bug/185066) .. |Lowis| unicode:: L \xf6 wis Platform: any Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Topic :: Database Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: Unix Classifier: Framework :: ZODB zope2.13-2.13.21/source/ZODB3/HISTORY.txt0000644000175000017500000046253112214017464016176 0ustar arnauarnau 3.9.7 (2010-09-28) ================== Bugs Fixed ---------- - Changes in way that garbage collection treats dictionaries in Python 2.7 broke the object/connection cache implementation. (https://bugs.launchpad.net/zodb/+bug/641481) Python 2.7 wasn't officially supported, but we were releasing binaries for it, so ... - Logrotation/repoening via a SIGUSR2 signal wasn't implemented. (https://bugs.launchpad.net/zodb/+bug/143600) - When using multi-databases, cache-management operations on a connection, cacheMinimize and cacheGC, weren't applied to subconnections. 3.9.6 (2010-09-21) ================== Bugs Fixed ---------- - Updating blobs in save points could cause spurious "invalidations out of order" errors. https://bugs.launchpad.net/zodb/+bug/509801 (Thanks to Christian Zagrodnick for chasing this down.) - If a ZEO client process was restarted while invalidating a ZEO cache entry, the cache could be left in a stage when there is data marked current that should be invalidated, leading to persistent conflict errors. - Corrupted or invalid cache files prevented ZEO clients from starting. Now, bad cache files are moved aside. - Invalidations of object records in ZEO caches, where the invalidation transaction ids matched the cached transaction ids should have been ignored. - Shutting down a process while committing a transaction or processing invalidations from the server could cause ZEO persistent client caches to have invalid data. This, in turn caused stale data to remain in the cache until it was updated. - Conflict errors didn't invalidate ZEO cache entries. - When objects were added in savepoints and either the savepoint was rolled back (https://bugs.launchpad.net/zodb/+bug/143560) or the transaction was aborted (https://mail.zope.org/pipermail/zodb-dev/2010-June/013488.html) The objects' _p_oid and _p_jar variables weren't cleared, leading to surprizing errors. - Objects added in transactions that were later aborted could have _p_changed still set (https://bugs.launchpad.net/zodb/+bug/615758). - ZEO extension methods failed when a client reconnected to a storage. (https://bugs.launchpad.net/zodb/+bug/143344) - On Mac OS X, clients that connected and disconnected quickly could cause a ZEO server to stop accepting connections, due to a failure to catch errors in the initial part of the connection process. The failure to properly handle exceptions while accepting connections is potentially problematic on other platforms. Fixes: https://bugs.launchpad.net/zodb/+bug/135108 - Passing keys or values outside the range of 32-bit ints on 64-bit platforms led to undetected overflow errors. Now these cases cause Type errors to be raised. https://bugs.launchpad.net/zodb/+bug/143237 - BTree sets and tree sets didn't correctly check values passed to update or to constructors, causing Python to exit under certain circumstances. - The verbose mode of the fstest was broken. (https://bugs.launchpad.net/zodb/+bug/475996) 3.9.5 (2010-04-23) ================== Bugs Fixed ---------- - Fixed bug in cPickleCache's byte size estimation logic. (https://bugs.launchpad.net/zodb/+bug/533015) - Fixed a serious bug that caused cache failures when run with Python optimization turned on. https://bugs.launchpad.net/zodb/+bug/544305 - Fixed a bug that caused savepoint rollback to not properly set object state when objects implemented _p_invalidate methods that reloaded ther state (unghostifiable objects). https://bugs.launchpad.net/zodb/+bug/428039 - cross-database wekrefs weren't handled correctly. https://bugs.launchpad.net/zodb/+bug/435547 - The mkzeoinst script was fixed to tell people to install and use the mkzeoinstance script. :) 3.9.4 (2009-12-14) ================== Bugs Fixed ---------- - A ZEO threading bug could cause transactions to read inconsistent data. (This sometimes caused an AssertionError in Connection._setstate_noncurrent.) - DemoStorage.loadBefore sometimes returned invalid data which would trigger AssertionErrors in ZODB.Connection. - History support was broken when using stprages that work with ZODB 3.8 and 3.9. - zope.testing was an unnecessary non-testing dependency. - Internal ZEO errors were logged at the INFO level, rather than at the error level. - The FileStorage backup and restore script, repozo, gave a deprecation warning under Python 2.6. - C Header files weren't installed correctly. - The undo implementation was incorrect in ways that could cause subtle missbehaviors. 3.9.3 (2009-10-23) ================== Bugs Fixed ---------- - 2 BTree bugs, introduced by a bug fix in 3.9.0c2, sometimes caused deletion of keys to be improperly handled, resulting in data being available via iteraation but not item access. 3.9.2 (2009-10-13) ================== Bugs Fixed ---------- - ZEO manages a separate thread for client network IO. It created this thread on import, which caused problems for applications that implemented daemon behavior by forking. Now, the client thread isn't created until needed. - File-storage pack clean-up tasks that can take a long time unnecessarily blocked other activity. - In certain rare situations, ZEO client connections would hang during the initial connection setup. 3.9.1 (2009-10-01) ================== Bugs Fixed ---------- - Conflict errors committing blobs caused ZEO servers to stop committing transactions. 3.9.0 (2009-09-08) ================== New Features (in more or less reverse chronological order) ---------------------------------------------------------- - The Database class now has an ``xrefs`` keyword argument and a corresponding allow-implicit-cross-references configuration option. which default to true. When set to false, cross-database references are disallowed. - Added support for RelStorage. - As a convenience, the connection root method for returning the root object can now *also* be used as an object with attributes mapped to the root-object keys. - Databases have a new method, ``transaction``, that can be used with the Python (2.5 and later) ``with`` statement:: db = ZODB.DB(...) with db.transaction() as conn: # ... do stuff with conn This uses a private transaction manager for the connection. If control exits the block without an error, the transaction is committed, otherwise, it is aborted. - Convenience functions ZODB.connection and ZEO.connection provide a convenient way to open a connection to a database. They open a database and return a connection to it. When the connection is closed, the database is closed as well. - The ZODB.config databaseFrom... methods now support multi-databases. If multiple zodb sections are used to define multiple databases, the databases are connected in a multi-database arrangement and the first of the defined databases is returned. - The zeopack script has gotten a number of improvements: - Simplified command-line interface. (The old interface is still supported, except that support for ZEO version 1 servers has been dropped.) - Multiple storages can be packed in sequence. - This simplifies pack scheduling on servers serving multiple databases. - All storages are packed to the same time. - You can now specify a time of day to pack to. - The script will now time out if it can't connect to s storage in 60 seconds. - The connection now estimates the object size based on its pickle size and informs the cache about size changes. The database got additional configurations options (`cache-size-bytes` and `historical-cache-size-bytes`) to limit the cache size based on the estimated total size of cached objects. The default values are 0 which has the interpretation "do not limit based on the total estimated size". There are corresponding methods to read and set the new configuration parameters. - Connections now have a public ``opened`` attribute that is true when the connection is open, and false otherwise. When true, it is the seconds since the epoch (time.time()) when the connection was opened. This is a renaming of the previous ``_opened`` private variable. - FileStorage now supports blobs directly. - You can now control whether FileStorages keep .old files when packing. - POSKeyErrors are no longer logged by ZEO servers, because they are really client errors. - A new storage interface, IExternalGC, to support external garbage collection, http://wiki.zope.org/ZODB/ExternalGC, has been defined and implemented for FileStorage and ClientStorage. - As a small convenience (mainly for tests), you can now specify initial data as a string argument to the Blob constructor. - ZEO Servers now provide an option, invalidation-age, that allows quick verification of ZEO clients have been disconnected for less than a given time even if the number of transactions the client hasn't seen exceeds the invalidation queue size. This is only recommended if the storage being served supports efficient iteration from a point near the end of the transaction history. - The FileStorage iterator now handles large files better. When iterating from a starting transaction near the end of the file, the iterator will scan backward from the end of the file to find the starting point. This enhancement makes it practical to take advantage of the new storage server invalidation-age option. - Previously, database connections were managed as a stack. This tended to cause the same connection(s) to be used over and over. For example, the most used connection would typically be the only connection used. In some rare situations, extra connections could be opened and end up on the top of the stack, causing extreme memory wastage. Now, when connections are placed on the stack, they sink below existing connections that have more active objects. - There is a new pool-timeout database configuration option to specify that connections unused after the given time interval should be garbage collection. This will provide a means of dealing with extra connections that are created in rare circumstances and that would consume an unreasonable amount of memory. - The Blob open method now supports a new mode, 'c', to open committed data for reading as an ordinary file, rather than as a blob file. The ordinary file may be used outside the current transaction and even after the blob's database connection has been closed. - ClientStorage now provides blob cache management. When using non-shared blob directories, you can set a target cache size and the cache will periodically be reduced try to keep it below the target size. The client blob directory layout has changed. If you have existing non-shared blob directories, you will have to remove them. - ZODB 3.9 ZEO clients can connect to ZODB 3.8 servers. ZODB ZEO clients from ZODB 3.2 on can connect to ZODB 3.9 servers. - When a ZEO cache is stale and would need verification, a ZEO.interfaces.StaleCache event is published (to zope.event). Applications may handle this event and take action such as exiting the application without verifying the cache or starting cold. - There's a new convenience function, ZEO.DB, for creating databases using ZEO Client Storages. Just call ZEO.DB with the same arguments you would otherwise pass to ZEO.ClientStorage.ClientStorage:: import ZEO db = ZEO.DB(('some_host', 8200)) - Object saves are a little faster - When configuring storages in a storage server, the storage name now defaults to "1". In the overwhelmingly common case that a single storage, the name can now be omitted. - FileStorage now provides optional garbage collection. A 'gc' keyword option can be passed to the pack method. A false value prevents garbage collection. - The FileStorage constructor now provides a boolean pack_gc option, which defaults to True, to control whether garbage collection is performed when packing by default. This can be overridden with the gc option to the pack method. The ZConfig configuration for FileStorage now includes a pack-gc option, corresponding to the pack_gc constructor argument. - The FileStorage constructor now has a packer keyword argument that allows an alternative packer to be supplied. The ZConfig configuration for FileStorage now includes a packer option, corresponding to the packer constructor argument. - MappingStorage now supports multi-version concurrency control and iteration and provides a better storage implementation example. - DemoStorage has a number of new features: - The ability to use a separate storage, such as a file storage to store changes - Blob support - Multi-version concurrency control and iteration - Explicit support for demo-storage stacking via push and pop methods. - Wen calling ZODB.DB to create a database, you can now pass a file name, rather than a storage to use a file storage. - Added support for copying and recovery of blob storages: - Added a helper function, ZODB.blob.is_blob_record for testing whether a data record is for a blob. This can be used when iterating over a storage to detect blob records so that blob data can be copied. In the future, we may want to build this into a blob-aware iteration interface, so that records get blob file attributes automatically. - Added the IBlobStorageRestoreable interfaces for blob storages that support recovery via a restoreBlob method. - Updated ZODB.blob.BlobStorage to implement IBlobStorageRestoreable and to have a copyTransactionsFrom method that also copies blob data. - New `ClientStorage` configuration option `drop_cache_rather_verify`. If this option is true then the ZEO client cache is dropped instead of the long (unoptimized) verification. For large caches, setting this option can avoid effective down times in the order of hours when the connection to the ZEO server was interrupted for a longer time. - Cleaned-up the storage iteration API and provided an iterator implementation for ZEO. - Versions are no-longer supported. - Document conflict resolution (see ZODB/ConflictResolution.txt). - Support multi-database references in conflict resolution. - Make it possible to examine oid and (in some situations) database name of persistent object references during conflict resolution. - Moved the 'transaction' module out of ZODB. ZODB depends upon this module, but it must be installed separately. - ZODB installation now requires setuptools. - Added `offset` information to output of `fstail` script. Added test harness for this script. - Added support for read-only, historical connections based on datetimes or serials (TIDs). See src/ZODB/historical_connections.txt. - Removed the ThreadedAsync module. - Now depend on zc.lockfile Bugs Fixed ---------- - CVE-2009-2701: Fixed a vulnerability in ZEO storage servers when blobs are available. Someone with write access to a ZEO server configured to support blobs could read any file on the system readable by the server process and remove any file removable by the server process. - BTrees (and TreeSets) kept references to internal keys. https://bugs.launchpad.net/zope3/+bug/294788 - BTree Sets and TreeSets don't support the standard set add method. (Now either add or the original insert method can be used to add an object to a BTree-based set.) - The runzeo script didn't work without a configuration file. (https://bugs.launchpad.net/zodb/+bug/410571) - Officially deprecated PersistentDict (https://bugs.launchpad.net/zodb/+bug/400775) - Calling __setstate__ on a persistent object could under certain uncommon cause the process to crash. (https://bugs.launchpad.net/zodb/+bug/262158) - When committing transactions involving blobs to ClientStorages with non-shared blob directories, a failure could occur in tpc_finish if there was insufficient disk space to copy the blob file or if the file wasn't available. https://bugs.launchpad.net/zodb/+bug/224169 - Savepoint blob data wasn't properly isolated. If multiple simultaneous savepoints in separate transactions modified the same blob, data from one savepoint would overwrite data for another. - Savepoint blob data wasn't cleaned up after a transaction abort. https://bugs.launchpad.net/zodb/+bug/323067 - Opening a blob with modes 'r+' or 'a' would fail when the blob had no committed changes. - PersistentList's sort method did not allow passing of keyword parameters. Changed its sort parameter list to match that of its (Python 2.4+) UserList base class. - Certain ZEO server errors could cause a client to get into a state where it couldn't commit transactions. https://bugs.launchpad.net/zodb/+bug/374737 - Fixed vulnerabilities in the ZEO network protocol that allow: - CVE-2009-0668 Arbitrary Python code execution in ZODB ZEO storage servers - CVE-2009-0669 Authentication bypass in ZODB ZEO storage servers The vulnerabilities only apply if you are using ZEO to share a database among multiple applications or application instances and if untrusted clients are able to connect to your ZEO servers. - Fixed the setup test command. It previously depended on private functions in zope.testing.testrunner that don't exist any more. - ZEO client threads were unnamed, making it hard to debug thread management. - ZEO protocol 2 support was broken. This caused very old clients to be unable to use new servers. - zeopack was less flexible than it was before. -h should default to local host. - The "lawn" layout was being selected by default if the root of the blob directory happened to contain a hidden file or directory such as ".svn". Now hidden files and directories are ignored when choosing the default layout. - BlobStorage was not compatible with MVCC storages because the wrappers were being removed by each database connection. Fixed. - Saving indexes for large file storages failed (with the error: RuntimeError: maximum recursion depth exceeded). This can cause a FileStorage to fail to start because it gets an error trying to save its index. - Sizes of new objects weren't added to the object cache size estimation, causing the object-cache size limiting feature to let the cache grow too large when many objects were added. - Deleted records weren't removed when packing file storages. - Fixed analyze.py and added test. - fixed Python 2.6 compatibility issue with ZEO/zeoserverlog.py - using hashlib.sha1 if available in order to avoid DeprecationWarning under Python 2.6 - made runzeo -h work - The monitor server didn't correctly report the actual number of clients. - Packing could return spurious errors due to errors notifying disconnected clients of new database size statistics. - Undo sometimes failed for FileStorages configured to support blobs. - Starting ClientStorages sometimes failed with non-new but empty cache files. - The history method on ZEO clients failed. - Fix for bug #251037: Make packing of blob storages non-blocking. - Fix for bug #220856: Completed implementation of ZEO authentication. - Fix for bug #184057: Make initialisation of small ZEO client file cache sizes not fail. - Fix for bug #184054: MappingStorage used to raise a KeyError during `load` instead of a POSKeyError. - Fixed bug in Connection.TmpStore: load() would not defer to the backend storage for loading blobs. - Fix for bug #181712: Make ClientStorage update `lastTransaction` directly after connecting to a server, even when no cache verification is necessary. - Fixed bug in blob filesystem helper: the `isSecure` check was inverted. - Fixed bug in transaction buffer: a tuple was unpacked incorrectly in `clear`. - Bugfix the situation in which comparing persistent objects (for instance, as members in BTree set or keys of BTree) might cause data inconsistency during conflict resolution. - Fixed bug 153316: persistent and BTrees were using `int` for memory sizes which caused errors on x86_64 Intel Xeon machines (using 64-bit Linux). - Fixed small bug that the Connection.isReadOnly method didn't work after a savepoint. - Bug #98275: Made ZEO cache more tolerant when invalidating current versions of objects. - Fixed a serious bug that could cause client I/O to stop (hang). This was accompanied by a critical log message along the lines of: "RuntimeError: dictionary changed size during iteration". - Fixed bug #127182: Blobs were subclassable which was not desired. - Fixed bug #126007: tpc_abort had untested code path that was broken. - Fixed bug #129921: getSize() function in BlobStorage could not deal with garbage files - Fixed bug in which MVCC would not work for blobs. - Fixed bug in ClientCache that occurred with objects larger than the total cache size. - When an error occured attempting to lock a file and logging of said error was enabled. - FileStorages previously saved indexes after a certain number of writes. This was done during the last phase of two-phase commit, which made this critical phase more subject to errors than it should have been. Also, for large databases, saves were done so infrequently as to be useless. The feature was removed to reduce the chance for errors during the last phase of two-phase commit. - File storages previously kept an internal object id to transaction id mapping as an optimization. This mapping caused excessive memory usage and failures during the last phase of two-phase commit. This optimization has been removed. - Refactored handling of invalidations on ZEO clients to fix a possible ordering problem for invalidation messages. - On many systems, it was impossible to create more than 32K blobs. Added a new blob-directory layout to work around this limitation. - Fixed bug that could lead to memory errors due to the use of a Python dictionary for a mapping that can grow large. - Fixed bug #251037: Made packing of blob storages non-blocking. - Fixed a bug that could cause InvalidObjectReference errors for objects that were explicitly added to a database if the object was modified after a savepoint that added the object. - Fixed several bugs that caused ZEO cache corruption when connecting to servers. These bugs affected both persistent and non-persistent caches. - Improved the the ZEO client shutdown support to try to avoid spurious errors on exit, especially for scripts, such as zeopack. - Packing failed for databases containing cross-database references. - Cross-database references to databases with empty names weren't constructed properly. - The zeo client cache used an excessive amount of memory, causing applications with large caches to exhaust available memory. - Fixed a number of bugs in the handling of persistent ZEO caches: - Cache records are written in several steps. If a process exits after writing begins and before it is finishes, the cache will be corrupt on restart. The way records are written was changed to make cache record updates atomic. - There was no lock file to prevent opening a cache multiple times at once, which would lead to corruption. Persistent caches now use lock files, in the same way that file storages do. - A bug in the cache-opening logic led to cache failure in the unlikely event that a cache has no free blocks. - When using ZEO Client Storages, Errors occured when trying to store objects too big to fit in the ZEO cache file. - Fixed bug in blob filesystem helper: the `isSecure` check was inverted. - Fixed bug in transaction buffer: a tuple was unpacked incorrectly in `clear`. - Fixed bug in Connection.TmpStore: load() would not defer to the back-end storage for loading blobs. - Fixed bug #190884: Wrong reference to `POSKeyError` caused NameError. - Completed implementation of ZEO authentication. This fixes issue 220856. What's new in ZODB 3.8.0 ======================== General ------- - (unreleased) Fixed setup.py use of setuptools vs distutils, so .c and .h files are included in the bdist_egg. - The ZODB Storage APIs have been documented and cleaned up. - ZODB versions are now officially deprecated and support for them will be removed in ZODB 3.9. (They have been widely recognized as deprecated for quite a while.) - Changed the automatic garbage collection when opening a connection to only apply the garbage collections on those connections in the pool that are closed. (This fixed issue 113923.) ZEO --- - (3.8a1) ZEO's strategoes for avoiding client cache verification were improved in the case that servers are restarted. Before, if transactions were committed after the restart, clients that were up to date or nearly up to date at the time of the restart and then connected had to verify their caches. Now, it is far more likely that a client that reconnects soon after a server restart won't have to verify its cache. - (3.8a1) Fixed a serious bug that could cause clients that disconnect from and reconnect to a server to get bad invalidation data if the server serves multiple storages with active writes. - (3.8a1) It is now theoretically possible to use a ClientStorage in a storage server. This might make it possible to offload read load from a storage server at the cost of increasing write latency. This should increase write throughput by offloading reads from the final storage server. This feature is somewhat experimental. It has tests, but hasn't been used in production. Transactions ------------ - (3.8a1) Add a doom() and isDoomed() interface to the transaction module. First step towards the resolution of http://www.zope.org/Collectors/Zope3-dev/655 A doomed transaction behaves exactly the same way as an active transaction but raises an error on any attempt to commit it, thus forcing an abort. Doom is useful in places where abort is unsafe and an exception cannot be raised. This occurs when the programmer wants the code following the doom to run but not commit. It is unsafe to abort in these circumstances as a following get() may implicitly open a new transaction. Any attempt to commit a doomed transaction will raise a DoomedTransaction exception. - (3.8a1) Clean up the ZODB imports in transaction. Clean up weird import dance with ZODB. This is unnecessary since the transaction module stopped being imported in ZODB/__init__.py in rev 39622. - (3.8a1) Support for subtransactions has been removed in favor of save points. Blobs ----- - (3.8b1) Updated the Blob implementation in a number of ways. Some of these are backward incompatible with 3.8a1: o The Blob class now lives in ZODB.blob o The blob openDetached method has been replaced by the committed method. - (3.8a1) Added new blob feature. See the ZODB/Blobs directory for documentation. ZODB now handles (reasonably) large binary objects efficiently. Useful to use from a few kilobytes to at least multiple hundred megabytes. BTrees ------ - (3.8a1) Added support for 64-bit integer BTrees as separate types. (For now, we're retaining compile-time support for making the regular integer BTrees 64-bit.) - (3.8a1) Normalize names in modules so that BTrees, Buckets, Sets, and TreeSets can all be accessed with those names in the modules (e.g., BTrees.IOBTree.BTree). This is in addition to the older names (e.g., BTrees.IOBTree.IOBTree). This allows easier drop-in replacement, which can especially be simplify code for packages that want to support both 32-bit and 64-bit BTrees. - (3.8a1) Describe the interfaces for each module and actually declare the interfaces for each. - (3.8a1) Fix module references so klass.__module__ points to the Python wrapper module, not the C extension. - (3.8a1) introduce module families, to group all 32-bit and all 64-bit modules. What's new in ZODB3 3.7.0 ========================== Release date: 2007-04-20 Packaging --------- - (3.7.0b3) ZODB is now packaged without it's dependencies ZODB no longer includes copies of dependencies such as ZConfig, zope.interface and so on. It now treats these as dependencies. If ZODB is installed with easy_install or zc.buildout, the dependencies will be installed automatically. - (3.7.0b3) ZODB is now a buildout ZODB checkouts are now built and tested using zc.buildout. - (3.7b4) Added logic to avoid spurious errors from the logging system on exit. - (3.7b2) Removed the "sync" mode for ClientStorage. Previously, a ClientStorage could be in either "sync" mode or "async" mode. Now there is just "async" mode. There is now a dedicicated asyncore main loop dedicated to ZEO clients. Applications no-longer need to run an asyncore main loop to cause client storages to run in async mode. Even if an application runs an asyncore main loop, it is independent of the loop used by client storages. This addresses a test failure on Mac OS X, http://www.zope.org/Collectors/Zope3-dev/650, that I believe was due to a bug in sync mode. Some asyncore-based code was being called from multiple threads that didn't expect to be. Converting to always-async mode revealed some bugs that weren't caught before because the tests ran in sync mode. These problems could explain some problems we've seen at times with clients taking a long time to reconnect after a disconnect. Added a partial heart beat to try to detect lost connections that aren't otherwise caught, http://mail.zope.org/pipermail/zodb-dev/2005-June/008951.html, by perioidically writing to all connections during periods of inactivity. Connection management --------------------- - (3.7a1) When more than ``pool_size`` connections have been closed, ``DB`` forgets the excess (over ``pool_size``) connections closed first. Python's cyclic garbage collection can take "a long time" to reclaim them (and may in fact never reclaim them if application code keeps strong references to them), but such forgotten connections can never be opened again, so their caches are now cleared at the time ``DB`` forgets them. Most applications won't notice a difference, but applications that open many connections, and/or store many large objects in connection caches, and/or store limited resources (such as RDB connections) in connection caches may benefit. BTrees ------ - Support for 64-bit integer keys and values has been provided as a compile-time option for the "I" BTrees (e.g. IIBTree). Documentation ------------- - (3.7a1) Thanks to Stephan Richter for converting many of the doctest files to ReST format. These are now chapters in the Zope 3 apidoc too. IPersistent ----------- - (3.7a1) The documentation for ``_p_oid`` now specifies the concrete type of oids (in short, an oid is either None or a non-empty string). Testing ------- - (3.7b2) Fixed test-runner output truncation. A bug was fixed in the test runner that caused result summaries to be omitted when running on Windows. Tools ----- - (3.7a1) The changeover from zLOG to the logging module means that some tools need to perform minimal logging configuration themselves. Changed the zeoup script to do so and thus enable it to emit error messages. BTrees ------ - (3.7a1) Suppressed warnings about signedness of characters when compiling under GCC 4.0.x. See http://www.zope.org/Collectors/Zope/2027. Connection ---------- - (3.7a1) An optimization for loading non-current data (MVCC) was inadvertently disabled in ``_setstate()``; this has been repaired. persistent ---------- - (3.7a1) Suppressed warnings about signedness of characters when compiling under GCC 4.0.x. See http://www.zope.org/Collectors/Zope/2027. - (3.7a1) PersistentMapping was inadvertently pickling volatile attributes (http://www.zope.org/Collectors/Zope/2052). After Commit hooks ------------------ - (3.7a1) Transaction objects have a new method, ``addAfterCommitHook(hook, *args, **kws)``. Hook functions registered with a transaction are called after the transaction commits or aborts. For example, one might want to launch non transactional or asynchrnonous code after a successful, or aborted, commit. See ``test_afterCommitHook()`` in ``transaction/tests/test_transaction.py`` for a tutorial doctest, and the ``ITransaction`` interface for details. What's new in ZODB3 3.6.2? ========================== Release date: 15-July-2006 DemoStorage ----------- - (3.6.2) DemoStorage was unable to wrap base storages who did not have an '_oid' attribute: most notably, ZEO.ClientStorage (http://www.zope.org/Collectors/Zope/2016). Following is combined news from internal releases (to support ongoing Zope2 / Zope3 development). These are the dates of the internal releases: - 3.6.1 27-Mar-2006 - 3.6.0 05-Jan-2006 - 3.6b6 01-Jan-2006 - 3.6b5 18-Dec-2005 - 3.6b4 04-Dec-2005 - 3.6b3 06-Nov-2005 - 3.6b2 25-Oct-2005 - 3.6b1 24-Oct-2005 - 3.6a4 07-Oct-2005 - 3.6a3 07-Sep-2005 - 3.6a2 06-Sep-2005 - 3.6a1 04-Sep-2005 Removal of Features Deprecated in ZODB 3.4 ------------------------------------------ (3.6b2) ZODB 3.6 no longer contains features officially deprecated in the ZODB 3.4 release. These include: - ``get_transaction()``. Use ``transaction.get()`` instead. ``transaction.commit()`` is a shortcut spelling of ``transaction.get().commit()``, and ``transaction.abort()`` of ``transaction.get().abort()``. Note that importing ZODB no longer installs ``get_transaction`` as a name in Python's ``__builtin__`` module either. - The ``begin()`` method of ``Transaction`` objects. Use the ``begin()`` method of a transaction manager instead. ``transaction.begin()`` is a shortcut spelling to call the default transaction manager's ``begin()`` method. - The ``dt`` argument to ``Connection.cacheMinimize()``. - The ``Connection.cacheFullSweep()`` method. Use ``cacheMinimize()`` instead. - The ``Connection.getTransaction()`` method. Pass a transaction manager to ``DB.open()`` instead. - The ``Connection.getLocalTransaction()`` method. Pass a transaction manager to ``DB.open()`` instead. - The ``cache_deactivate_after`` and ``version_cache_deactivate_after`` arguments to the ``DB`` constructor. - The ``temporary``, ``force``, and ``waitflag`` arguments to ``DB.open()``. ``DB.open()`` no longer blocks (there's no longer a fixed limit on the number of open connections). - The ``transaction`` and ``txn_mgr``arguments to ``DB.open()``. Use the ``transaction_manager`` argument instead. - The ``getCacheDeactivateAfter``, ``setCacheDeactivateAfter``, ``getVersionCacheDeactivateAfter`` and ``setVersionCacheDeactivateAfter`` methods of ``DB``. Persistent ---------- - (3.6.1) Suppressed warnings about signedness of characters when compiling under GCC 4.0.x. See http://www.zope.org/Collectors/Zope/2027. - (3.6a4) ZODB 3.6 introduces a change to the basic behavior of Persistent objects in a particular end case. Before ZODB 3.6, setting ``obj._p_changed`` to a true value when ``obj`` was a ghost was ignored: ``obj`` remained a ghost, and getting ``obj._p_changed`` continued to return ``None``. Starting with ZODB 3.6, ``obj`` is activated instead (unghostified), and its state is changed from the ghost state to the changed state. The new behavior is less surprising and more robust. - (3.6b5) The documentation for ``_p_oid`` now specifies the concrete type of oids (in short, an oid is either None or a non-empty string). Commit hooks ------------ - (3.6a1) The ``beforeCommitHook()`` method has been replaced by the new ``addBeforeCommitHook()`` method, with a more-robust signature. ``beforeCommitHook()`` is now deprecated, and will be removed in ZODB 3.8. Thanks to Julien Anguenot for contributing code and tests. Connection management --------------------- - (3.6b6) When more than ``pool_size`` connections have been closed, ``DB`` forgets the excess (over ``pool_size``) connections closed first. Python's cyclic garbage collection can take "a long time" to reclaim them (and may in fact never reclaim them if application code keeps strong references to them), but such forgotten connections can never be opened again, so their caches are now cleared at the time ``DB`` forgets them. Most applications won't notice a difference, but applications that open many connections, and/or store many large objects in connection caches, and/or store limited resources (such as RDB connections) in connection caches may benefit. ZEO --- - (3.6a4) Collector 1900. In some cases of pickle exceptions raised by low-level ZEO communication code, callers of ``marshal.encode()`` could attempt to catch an exception that didn't actually exist, leading to an erroneous ``AttributeError`` exception. Thanks to Tres Seaver for the diagnosis. BaseStorage ----------- - (3.6a4) Nothing done by ``tpc_abort()`` should raise an exception. However, if something does (an error case), ``BaseStorage.tpc_abort()`` left the commit lock in the acquired state, causing any later attempt to commit changes hang. Multidatabase ------------- - (3.6b1) The ``database_name`` for a database in a multidatabase collection can now be specified in a config file's ```` section, as the value of the optional new ``database_name`` key. The ``.databases`` attribute cannot be specified in a config file, but can be passed as the optional new ``databases`` argument to the ``open()`` method of a ZConfig factory for type ``ZODBDatabase``. For backward compatibility, Zope 2.9 continues to allow using the name in its ```` config section as the database name (note that ```` is defined by Zope, not by ZODB -- it's a Zope-specific extension of ZODB's ```` section). PersistentMapping ----------------- - (3.6.1) PersistentMapping was inadvertently pickling volatile attributes (http://www.zope.org/Collectors/Zope/2052). - (3.6b4) ``PersistentMapping`` makes changes by a ``pop()`` method call persistent now (http://www.zope.org/Collectors/Zope/2036). - (3.6a1) The ``PersistentMapping`` class has an ``__iter__()`` method now, so that objects of this type work well with Python's iteration protocol. For example, if ``x`` is a ``PersistentMapping`` (or Python dictionary, or BTree, or ``PersistentDict``, ...), then ``for key in x:`` iterates over the keys of ``x``, ``list(x)`` creates a list containing ``x``'s keys, ``iter(x)`` creates an iterator for ``x``'s keys, and so on. Tools ----- - (3.6b5) The changeover from zLOG to the logging module means that some tools need to perform minimal logging configuration themselves. Changed the zeoup script to do so and thus enable it to emit error messages. BTrees ------ - (3.6.1) Suppressed warnings about signedness of characters when compiling under GCC 4.0.x. See http://www.zope.org/Collectors/Zope/2027. - (3.6a1) BTrees and Buckets now implement the ``setdefault()`` and ``pop()`` methods. These are exactly like Python's dictionary methods of the same names, except that ``setdefault()`` requires both arguments (and Python is likely to change to require both arguments too -- defaulting the ``default`` argument to ``None`` has no viable use cases). Thanks to Ruslan Spivak for contributing code, tests, and documentation. - (3.6a1) Collector 1873. It wasn't possible to construct a BTree or Bucket from, or apply their update() methods to, a PersistentMapping or PersistentDict. This works now. ZopeUndo -------- - (3.6a4) Collector 1810. A previous bugfix (#1726) broke listing undoable transactions for users defined in a non-root acl_users folder. Zope logs a acl_users path together with a username (separated by a space) and this previous fix failed to take this into account. Connection ---------- - (3.6b5) An optimization for loading non-current data (MVCC) was inadvertently disabled in ``_setstate()``; this has been repaired. Documentation ------------- - (3.6b3) Thanks to Stephan Richter for converting many of the doctest files to ReST format. These are now chapters in the Zope 3 apidoc too. - (3.6b4) Several misspellings of "occurred" were repaired. Development ----------- - (3.6a1) The source code for the old ExtensionClass-based Persistence package moved, from ZODB to the Zope 2.9 development tree. ZODB 3.5 makes no use of Persistence, and, indeed, the Persistence package could not be compiled from a ZODB release, since some of the C header files needed appear only in Zope. - (3.6a3) Re-added the ``zeoctl`` module, for the same reasons ``mkzeoinst`` was re-added (see below). - (3.6a2) The ``mkzeoinst`` module was re-added to ZEO, because Zope3 has a script that expects to import it from there. ZODB's ``mkzeoinst`` script was rewritten to invoke the ``mkzeoinst`` module. ``transact`` ------------ - (3.6b4) Collector 1959: The undocumented ``transact`` module no longer worked. It remains undocumented and untested, but thanks to Janko Hauser it's possible that it works again ;-). What's new in ZODB3 3.5.1? ========================== Release date: 26-Sep-2005 Following is combined news from internal releases (to support ongoing Zope3 development). These are the dates of the internal releases: - 3.5.1b2 07-Sep-2005 - 3.5.1b1 06-Sep-2005 Build ----- - (3.5.1b2) Re-added the ``zeoctl`` module, for the same reasons ``mkzeoinst`` was re-added (see below). - (3.5.1b1) The ``mkzeoinst`` module was re-added to ZEO, because Zope3 has a script that expects to import it from there. ZODB's ``mkzeoinst`` script was rewritten to invoke the ``mkzeoinst`` module. ZopeUndo -------- - (3.5.1) Collector 1810. A previous bugfix (#1726) broke listing undoable transactions for users defined in a non-root acl_users folder. Zope logs a acl_users path together with a username (separated by a space) and this previous fix failed to take this into account. What's new in ZODB3 3.5.0? ========================== Release date: 31-Aug-2005 Following is combined news from internal releases (to support ongoing Zope3 development). These are the dates of the internal releases: - 3.5a7 11-Aug-2005 - 3.5a6 04-Aug-2005 - 3.5a5 19-Jul-2005 - 3.5a4 14-Jul-2005 - 3.5a3 17-Jun-2005 - 3.5a2 16-Jun-2005 - 3.5a1 10-Jun-2005 Savepoints ---------- - (3.5.0) As for deprecated subtransaction commits, the intent was that making a savepoint would invoke incremental garbage collection on Connection memory caches, to try to reduce the number of objects in cache to the configured cache size. Due to an oversight, this didn't happen, and stopped happening for subtransaction commits too. Making a savepoint (or doing a subtransaction commit) does invoke cache gc now. - (3.5a3) When a savepoint is made, the states of objects modified so far are saved to a temporary storage (an instance of class ``TmpStore``, although that's an internal implementation detail). That storage needs to implement the full storage API too, but was missing the ``loadBefore()`` method needed for MVCC to retrieve non-current revisions of objects. This could cause spurious errors if a transaction with a pending savepoint needed to fetch an older revision of some object. - (3.5a4) The ``ISavepoint`` interface docs said you could roll back to a given savepoint any number of times (until the transaction ends, or until you roll back to an earlier savepoint's state), but the implementation marked a savepoint as invalid after its first use. The implementation has been repaired, to match the docs. ZEO client cache ---------------- - (3.5a6) Two memory leaks in the ZEO client cache were repaired, a major one involving ``ZEO.cache.Entry`` objects, and a minor one involving empty lists. Subtransactions are deprecated ------------------------------ - (3.5a4) Subtransactions are deprecated, and will be removed in ZODB 3.7. Use savepoints instead. Savepoints are more powerful, and code using subtransactions does not mix well with code using savepoints (a subtransaction commit forces all current savepoints to become unusable, so code using subtransactions can hurt newer code trying to use savepoints). In general, a subtransaction commit done just to free memory can be changed from:: transaction.commit(1) to:: transaction.savepoint(True) That is, make a savepoint, and forget it. As shown, it's best to pass ``True`` for the optional ``optimistic`` argument in this case: because there's no possibility of asking for a rollback later, there's no need to insist that all data managers support rollback. In rarer cases, a subtransaction commit is followed later by a subtransaction abort. In that case, change the initial:: transaction.commit(1) to:: sp = transaction.savepoint() and in place of the subtransaction abort:: transaction.abort(1) roll back the savepoint instead:: sp.rollback() - (3.5a4) Internal uses of subtransactions (transaction ``commit()`` or ``abort()`` passing a true argument) were rewritten to use savepoints instead. Multi-database -------------- - (3.5a1) Preliminary support for persistent cross-database references has been added. See ``ZODB/cross-database-references.txt`` for an introduction. Tools ----- - (3.5a6, 3.5a7) Collector #1847. The ZEO client cache tracing and simulation tools weren't updated to work with ZODB 3.3, and the introduction of MVCC required major reworking of the tracing and simulation code. These tools are in a working state again, although so far lightly tested on just a few applications. In ``doc/ZEO/``, see the heavily revised ``trace.txt`` and ``cache.txt``. - (3.5a5) Collector #1846: If an uncommitted transaction was found, fsrecover.py fell into an infinite loop. Windows ------- - (3.5a6) As developed in a long thread starting at http://mail.zope.org/pipermail/zope/2005-July/160433.html there appears to be a race bug in the Microsoft Windows socket implementation, rarely visible in ZEO when multiple processes try to create an "asyncore trigger" simultaneously. Windows-specific code in ``ZEO/zrpc/trigger.py`` changed to work around this bug when it occurs. ThreadedAsync.LoopCallback -------------------------- - (3.5a5) This once again physically replaces Python's ``asyncore.loop`` function with its own loop function, because it turns out Zope relied on the seemingly unused ``LoopCallback.exit_status`` global, which was removed in the change described below. Python's ``asyncore.loop`` is again not invoked, so any breakpoints or debugging prints added to that are again "lost". - (3.5a4) This replaces Python's ``asyncore.loop`` function with its own, in order to get notified when ``loop()`` is first called. The signature of ``asyncore.loop`` changed in Python 2.4, but ``LoopCallback.loop``'s signature didn't change to match. The code here was repaired to be compatible with both old and new signatures, and also repaired to invoke Python's ``asyncore.loop()`` instead of replacing it entirely (so, for example, debugging prints added to Python's ``asyncore.loop`` won't be lost anymore). FileStorage ----------- - (3.5a4) Collector #1830. In some error cases when reading a FileStorage index, the code referenced an undefined global. - (3.5a4) Collector #1822. The ``undoLog()`` and ``undoInfo()`` methods were changed in 3.4a9 to return the documented results. Alas, some pieces of (non-ZODB) code relied on the actual behavior. When the ``first`` and ``last`` arguments are both >= 0, these methods now treat them as if they were Python slice indices, including the `first` index but excluding the ``last`` index. This matches former behavior, although it contradicts older ZODB UML documentation. The documentation in ``ZODB.interfaces.IStorageUndoable`` was changed to match the new intent. - (3.5a2) The ``_readnext()`` method now returns the transaction size as the value of the "size" key. Thanks to Dieter Maurer for the patch, from http://mail.zope.org/pipermail/zodb-dev/2003-October/006157.html. "This is very valuable when you want to spot strange transaction sizes via Zope's 'Undo' tab". BTrees ------ - (3.5.a5) Collector 1843. When a non-integer was passed to a method like ``keys()`` of a Bucket or Set with integer keys, an internal error code was overlooked, leading to everything from "delayed errors" to segfaults. Such cases raise TypeError now, as intended. - (3.5a4) Collector 1831. The BTree ``minKey()`` and ``maxKey()`` methods gave a misleading message if no key satisfying the constraints existed in a non-empty tree. - (3.5a4) Collector 1829. Clarified that the ``minKey()`` and ``maxKey()`` methods raise an exception if no key exists satsifying the constraints. - (3.5a4) The ancient ``convert.py`` script was removed. It was intended to convert "old" BTrees to "new" BTrees, but the "old" BTree implementation was removed from ZODB years ago. What's new in ZODB3 3.4.1? ========================== Release date: 09-Aug-2005 Following are dates of internal releases (to support ongoing Zope 2 development) since ZODB 3.4's last public release: - 3.4.1b5 08-Aug-2005 - 3.4.1b4 07-Aug-2005 - 3.4.1b3 04-Aug-2005 - 3.4.1b2 02-Aug-2005 - 3.4.1b1 26-Jul-2005 - 3.4.1a6 19-Jul-2005 - 3.4.1a5 12-Jul-2005 - 3.4.1a4 08-Jul-2005 - 3.4.1a3 02-Jul-2005 - 3.4.1a2 29-Jun-2005 - 3.4.1a1 27-Jun-2005 Savepoints ---------- - (3.4.1a1) When a savepoint is made, the states of objects modified so far are saved to a temporary storage (an instance of class ``TmpStore``, although that's an internal implementation detail). That storage needs to implement the full storage API too, but was missing the ``loadBefore()`` method needed for MVCC to retrieve non-current revisions of objects. This could cause spurious errors if a transaction with a pending savepoint needed to fetch an older revision of some object. - (3.4.1a5) The ``ISavepoint`` interface docs said you could roll back to a given savepoint any number of times (until the transaction ends, or until you roll back to an earlier savepoint's state), but the implementation marked a savepoint as invalid after its first use. The implementation has been repaired, to match the docs. - (3.4.1b4) Collector 1860: use an optimistic savepoint in ExportImport (there's no possiblity of rollback here, so no need to insist that the data manager support rollbacks). ZEO client cache ---------------- - (3.4.1b3) Two memory leaks in the ZEO client cache were repaired, a major one involving ``ZEO.cache.Entry`` objects, and a minor one involving empty lists. Subtransactions --------------- - (3.4.1a5) Internal uses of subtransactions (transaction ``commit()`` or ``abort()`` passing a true argument) were rewritten to use savepoints instead. Application code is strongly encouraged to do this too: subtransactions are weaker, will be deprecated soon, and do not mix well with savepoints (when you do a subtransaction commit, all current savepoints are made unusable). In general, a subtransaction commit done just to free memory can be changed from:: transaction.commit(1) to:: transaction.savepoint(True) That is, make a savepoint, and forget it. As shown, it's best to pass ``True`` for the optional ``optimistic`` argument in this case: because there's no possibility of asking for a rollback later, there's no need to insist that all data managers support rollback. In rarer cases, a subtransaction commit is followed later by a subtransaction abort. In that case, change the initial:: transaction.commit(1) to:: sp = transaction.savepoint() and in place of the subtransaction abort:: transaction.abort(1) roll back the savepoint instead:: sp.rollback() FileStorage ----------- - (3.4.1a3) Collector #1830. In some error cases when reading a FileStorage index, the code referenced an undefined global. - (3.4.1a2) Collector #1822. The ``undoLog()`` and ``undoInfo()`` methods were changed in 3.4a9 to return the documented results. Alas, some pieces of (non-ZODB) code relied on the actual behavior. When the `first` and `last` arguments are both >= 0, these methods now treat them as if they were Python slice indices, including the `first` index but excluding the `last` index. This matches former behavior, although it contradicts older ZODB UML documentation. The documentation in ``ZODB.interfaces.IStorageUndoable`` was changed to match the new intent. - (3.4.1a1) The ``UndoSearch._readnext()`` method now returns the transaction size as the value of the "size" key. Thanks to Dieter Maurer for the patch, from http://mail.zope.org/pipermail/zodb-dev/2003-October/006157.html. "This is very valuable when you want to spot strange transaction sizes via Zope's 'Undo' tab". ThreadedAsync.LoopCallback -------------------------- - (3.4.1a6) This once again physically replaces Python's ``asyncore.loop`` function with its own loop function, because it turns out Zope relied on the seemingly unused ``LoopCallback.exit_status`` global, which was removed in the change described below. Python's ``asyncore.loop`` is again not invoked, so any breakpoints or debugging prints added to that are again "lost". - (3.4.1a1) This replaces Python's ``asyncore.loop`` function with its own, in order to get notified when ``loop()`` is first called. The signature of ``asyncore.loop`` changed in Python 2.4, but ``LoopCallback.loop``'s signature didn't change to match. The code here was repaired to be compatible with both old and new signatures, and also repaired to invoke Python's ``asyncore.loop()`` instead of replacing it entirely (so, for example, debugging prints added to Python's ``asyncore.loop`` won't be lost anymore). Windows ------- - (3.4.1b2) As developed in a long thread starting at http://mail.zope.org/pipermail/zope/2005-July/160433.html there appears to be a race bug in the Microsoft Windows socket implementation, rarely visible in ZEO when multiple processes try to create an "asyncore trigger" simultaneously. Windows-specific code in ``ZEO/zrpc/trigger.py`` changed to work around this bug when it occurs. Tools ----- - (3.4.1b1 thru 3.4.1b5) Collector #1847. The ZEO client cache tracing and simulation tools weren't updated to work with ZODB 3.3, and the introduction of MVCC required major reworking of the tracing and simulation code. These tools are in a working state again, although so far lightly tested on just a few applications. In ``doc/ZEO/``, see the heavily revised ``trace.txt`` and ``cache.txt``. - (3.4.1a6) Collector #1846: If an uncommitted transaction was found, fsrecover.py fell into an infinite loop. DemoStorage ----------- - (3.4.1a1) The implementation of ``undoLog()`` was wrong in several ways; repaired. BTrees ------ - (3.4.1a6) Collector 1843. When a non-integer was passed to a method like ``keys()`` of a Bucket or Set with integer keys, an internal error code was overlooked, leading to everything from "delayed errors" to segfaults. Such cases raise TypeError now, as intended. - (3.4.1a4) Collector 1831. The BTree ``minKey()`` and ``maxKey()`` methods gave a misleading message if no key satisfying the constraints existed in a non-empty tree. - (3.4.1a3) Collector 1829. Clarified that the ``minKey()`` and ``maxKey()`` methods raise an exception if no key exists satsifying the constraints. What's new in ZODB3 3.4? ======================== Release date: 09-Jun-2005 Following is combined news from the "internal releases" (to support ongoing Zope 2.8 and Zope3 development) since the last public ZODB 3.4 release. These are the dates of the internal releases: - 3.4c2 06-Jun-2005 - 3.4c1 03-Jun-2005 - 3.4b3 27-May-2005 - 3.4b2 26-May-2005 Connection, DB -------------- - (3.4b3) ``.transaction_manager`` is now a public attribute of IDataManager, and is the instance of ITransactionManager used by the data manager as its transaction manager. There was previously no way to ask a data manager which transaction manager it was using. It's intended that ``transaction_manager`` be treated as read-only. - (3.4b3) For sanity, the ``txn_mgr`` argument to ``DB.open()``, ``Connection.__init__()``, and ``Connection._setDB()`` has been renamed to ``transaction_manager``. ``txn_mgr`` is still accepted, but is deprecated and will be removed in ZODB 3.6. Any code that was using the private ``._txn_mgr`` attribute of ``Connection`` will break immediately. Development ----------- - (3.4b2) ZODB's ``test.py`` is now a small driver for the shared ``zope.testing.testrunner``. See the latter's documentation for command-line arguments. Error reporting --------------- - (3.4c1) In the unlikely event that ``referencesf()`` reports an unpickling error (for example, a corrupt database can cause this), the message it produces no longer contains unprintable characters. Tests ----- - (3.4c2) ``checkCrossDBInvalidations`` suffered spurious failures too often on slow and/or busy machines. The test is willing to wait longer for success now. What's new in ZODB3 3.4b1? ========================== Release date: 19-May-2005 What follows is combined news from the "internal releases" (to support ongoing Zope 2.8 and Zope3 development) since the last public ZODB 3.4 release. These are the dates of the internal releases: - 3.4b1 19-May-2005 - 3.4a9 12-May-2005 - 3.4a8 09-May-2005 - 3.4a7 06-May-2005 - 3.4a6 05-May-2005 - 3.4a5 25-Apr-2005 - 3.4a4 23-Apr-2005 - 3.4a3 13-Apr-2005 - 3.4a2 03-Apr-2005 transaction ----------- - (3.4a7) If the first activity seen by a new ``ThreadTransactionManager`` was an explicit ``begin()`` call, then synchronizers registered after that (but still during the first transaction) were not communicated to the transaction object. As a result, the ``afterCompletion()`` methods of registered synchronizers weren't called when the first transaction ended. - (3.4a6) Doing a subtransaction commit erroneously processed invalidations, which could lead to an inconsistent view of the database. For example, let T be the transaction of which the subtransaction commit was a part. If T read a persistent object O's state before the subtransaction commit, did not commit new state of its own for O during its subtransaction commit, and O was modified before the subtransaction commit by a different transaction, then the subtransaction commit processed an invalidation for O, and the state T read for O originally was discarded in T. If T went on to access O again, it saw the newly committed (by a different transaction) state for O:: o_attr = O.some_attribute get_transaction().commit(True) assert o_attr == O.some_attribute could fail, and despite that T never modifed O. - (3.4a4) Transactions now support savepoints. Savepoints allow changes to be periodically checkpointed within a transaction. You can then rollback to a previously created savepoint. See ``transaction/savepoint.txt``. - (3.4a6) A ``getBeforeCommitHooks()`` method was added. It returns an iterable producing the registered beforeCommit hooks. - (3.4a6) The ``ISynchronizer`` interface has a new ``newTransaction()`` method. This is invoked whenever a transaction manager's ``begin()`` method is called. (Note that a transaction object's (as opposed to a transaction manager's) ``begin()`` method is deprecated, and ``newTransaction()`` is not called when using the deprecated method.) - (3.4a6) Relatedly, ``Connection`` implements ``ISynchronizer``, and ``Connection``'s ``afterCompletion()`` and ``newTransaction()`` methods now call ``sync()`` on the underlying storage (if the underlying storage has such a method), in addition to processing invalidations. The practical implication is that storage synchronization will be done automatically now, whenever a transaction is explicitly started, and after top-level transaction commit or abort. As a result, ``Connection.sync()`` should virtually never be needed anymore, and will eventually be deprecated. - (3.4a3) Transaction objects have a new method, ``beforeCommitHook(hook, *args, **kws)``. Hook functions registered with a transaction are called at the start of a top-level commit, before any of the work is begun, so a hook function can perform any database operations it likes. See ``test_beforeCommitHook()`` in ``transaction/tests/test_transaction.py`` for a tutorial doctest, and the ``ITransaction`` interface for details. Thanks to Florent Guillaume for contributing code and tests. - (3.4a3) Clarifications were made to transaction interfaces. Support for ZODB4 savepoint-aware data managers has been dropped ---------------------------------------------------------------- - (3.4a4) In adding savepoint support, we dropped the attempted support for ZODB4 data managers that support savepoints. We don't think that this will affect anyone. ZEO --- - (3.4a4) The ZODB and ZEO version numbers are now the same. Concretely:: import ZODB, ZEO assert ZODB.__version__ == ZEO.version no longer fails. If interested, see the README file for details about earlier version numbering schemes. - (3.4b1) ZConfig version 2.3 adds new socket address types, for smoother default behavior across platforms. The hostname portion of socket-binding-address defaults to an empty string, which acts like INADDR_ANY on Windows and Linux (bind to any interface). The hostname portion of socket-connection-address defaults to "127.0.0.1" (aka "localhost"). In config files, the types of ``zeo`` section keys ``address`` and ``monitor-address`` changed to socket-binding-address, and the type of the ``zeoclient`` section key ``server`` changed to socket-connection-address. - (3.4a4) The default logging setup in ``runzeo.py`` was broken. It was changed so that running ``runzeo.py`` from a command line now, and without using a config file, prints output to the console much as ZODB 3.2 did. ZEO on Windows -------------- Thanks to Mark Hammond for these ``runzeo.py`` enhancements on Windows: - (3.4b1) Collector 1788: Repair one of the new features below. - (3.4a4) A pid file (containing the process id as a decimal string) is created now for a ZEO server started via ``runzeo.py``. External programs can read the pid from this file and derive a "signal name" used in a new signal-emulation scheme for Windows. This is only necessary on Windows, but the pid file is created on all platforms that implement ``os.getpid()``, as long as the ``pid-filename`` option is set, or environment variable ``INSTANCE_HOME`` is defined. The ``pid-filename`` option can be set in a ZEO config file, or passed as the new ``--pid-file`` argument to ``runzeo.py``. - (3.4a4) If available, ``runzeo.py`` now uses Zope's new 'Signal' mechanism for Windows, to implement clean shutdown and log rotation handlers for Windows. Note that the Python in use on the ZEO server must also have the Python Win32 extensions installed for this to be useful. Tools ----- - (3.4a4) ``fsdump.py`` now displays the size (in bytes) of data records. This actually went in several months go, but wasn't noted here at the time. Thanks to Dmitry Vasiliev for contributing code and tests. FileStorage ----------- - (3.4a9) The ``undoLog()`` and ``undoInfo()`` methods almost always returned a wrong number of results, one too many if ``last < 0`` (the default is such a case), or one too few if ``last >= 0``. These have been repaired, new tests were added, and these methods are now documented in ``ZODB.interfaces.IStorageUndoable``. - (3.4a2) A ``pdb.set_trace()`` call was mistakenly left in method ``FileStorage.modifiedInVersion()``. ZConfig ------- - (3.4b1) The "standalone" release of ZODB now includes ZConfig version 2.3. DemoStorage ----------- - (3.4a4) Appropriate implementations of the storage API's ``registerDB()`` and ``new_oid()`` methods were added, delegating to the base storage. This was needed to support wrapping a ZEO client storage as a ``DemoStorage`` base storage, as some new Zope tests want to do. BaseStorage ----------- - (3.4a4) ``new_oid()``'s undocumented ``last=`` argument was removed. It was used only for internal recursion, and injured code sanity elsewhere because not all storages included it in their ``new_oid()``'s signature. Straightening this out required adding ``last=`` everywhere, or removing it everywhere. Since recursion isn't actually needed, and there was no other use for ``last=``, removing it everywhere was the obvious choice. Tests ----- - (3.4a3) The various flavors of the ``check2ZODBThreads`` and ``check7ZODBThreads`` tests are much less likely to suffer sproadic failures now. - (3.4a2) The test ``checkOldStyleRoot`` failed in Zope3, because of an obscure dependence on the ``Persistence`` package (which Zope3 doesn't use). ZApplication ------------ - (3.4a8) The file ``ZApplication.py`` was moved, from ZODB to Zope(2). ZODB and Zope3 don't use it, but Zope2 does. - (3.4a7) The ``__call__`` method didn't work if a non-None ``connection`` string argument was passed. Thanks to Stefan Holek for noticing. What's new in ZODB3 3.4a1? ========================== Release date: 01-Apr-2005 transaction ----------- - ``get_transaction()`` is officially deprecated now, and will be removed in ZODB 3.6. Use the ``transaction`` package instead. For example, instead of:: import ZODB ... get_transaction().commit() do:: import transaction ... transaction.commit() DB -- - There is no longer a hard limit on the number of connections that ``DB.open()`` will create. In other words, ``DB.open()`` never blocks anymore waiting for an earlier connection to close, and ``DB.open()`` always returns a connection now (while it wasn't documented, it was possible for ``DB.open()`` to return ``None`` before). ``pool_size`` continues to default to 7, but its meaning has changed: if more than ``pool_size`` connections are obtained from ``DB.open()`` and not closed, a warning is logged; if more than twice ``pool_size``, a critical problem is logged. ``pool_size`` should be set to the maximum number of connections from the ``DB`` instance you expect to have open simultaneously. In addition, if a connection obtained from ``DB.open()`` becomes unreachable without having been explicitly closed, when Python's garbage collection reclaims that connection it no longer counts against the ``pool_size`` thresholds for logging messages. The following optional arguments to ``DB.open()`` are deprecated: ``transaction``, ``waitflag``, ``force`` and ``temporary``. If one is specified, its value is ignored, and ``DeprecationWarning`` is raised. In ZODB 3.6, these optional arguments will be removed. - Lightweight support for "multi-databases" is implemented. These are collections of named DB objects and associated open Connections, such that the Connection for any DB in the collection can be obtained from a Connection from any other DB in the collection. See the new test file ZODB/tests/multidb.txt for a tutorial doctest. Thanks to Christian Theune for his work on this during the PyCon 2005 ZODB sprint. ZEO compatibility ----------------- There are severe restrictions on using ZEO servers and clients at or after ZODB 3.3 with ZEO servers and clients from ZODB versions before 3.3. See the reworked ``Compatibility`` section in ``README.txt`` for details. If possible, it will be easiest to move clients and servers to 3.3+ simultaneously. With care, it's possible to use a 3.3+ ZEO server with pre-3.3 ZEO clients, but not possible to use a pre-3.3 ZEO server with 3.3+ ZEO clients. BTrees ------ - A new family of BTree types, in the ``IFBTree`` module, map signed integers (32 bits) to C floats (also 32 bits). The intended use is to help construct search indices, where, e.g., integer word or document identifiers map to scores of some kind. This is easier than trying to work with scaled integer scores in an ``IIBTree``, and Zope3 has moved to ``IFBTrees`` for these purposes in its search code. FileStorage ----------- - Addded a record iteration protocol to FileStorage. You can use the record iterator to iterate over all current revisions of data pickles in the storage. In order to support calling via ZEO, we don't implement this as an actual iterator. An example of using the record iterator protocol is as follows:: storage = FileStorage('anexisting.fs') next_oid = None while True: oid, tid, data, next_oid = storage.record_iternext(next_oid) # do something with oid, tid and data if next_oid is None: break The behavior of the iteration protocol is now to iterate over all current records in the database in ascending oid order, although this is not a promise to do so in the future. Tools ----- New tool fsoids.py, for heavy debugging of FileStorages; shows all uses of specified oids in the entire database (e.g., suppose oid 0x345620 is missing -- did it ever exist? if so, when? who referenced it? when was the last transaction that modified an object that referenced it? which objects did it reference? what kind of object was it?). ZODB/test/testfsoids.py is a tutorial doctest. fsIndex ------- Efficient, general implementations of ``minKey()`` and ``maxKey()`` methods were added. ``fsIndex`` is a special hybrid kind of BTree used to implement FileStorage indices. Thanks to Chris McDonough for code and tests. What's new in ZODB3 3.3.1? ========================== Release date: DD-MMM-2005 Tests ----- The various flavors of the ``check2ZODBThreads`` and ``check7ZODBThreads`` tests are much less likely to suffer sproadic failures now. What's new in ZODB3 3.3.1c1? ============================ Release date: 01-Apr-2005 BTrees ------ Collector #1734: BTrees conflict resolution leads to index inconsistencies. Silent data loss could occur due to BTree conflict resolution when one transaction T1 added a new key to a BTree containing at least three buckets, and a concurrent transaction T2 deleted all keys in the bucket to which the new key was added. Conflict resolution then created a bucket containing the newly added key, but the bucket remained isolated, disconnected from the BTree. In other words, the committed BTree didn't contain the new key added by T1. Conflict resolution doesn't have enough information to repair this, so ``ConflictError`` is now raised in such cases. ZEO --- Repaired subtle race conditions in establishing ZEO connections, both client- and server-side. These account for intermittent cases where ZEO failed to make a connection (or reconnection), accompanied by a log message showing an error caught in ``asyncore`` and having a traceback ending with: ``UnpicklingError: invalid load key, 'Z'.`` or: ``ZRPCError: bad handshake '(K\x00K\x00U\x0fgetAuthProtocol)t.'`` or: ``error: (9, 'Bad file descriptor')`` or an ``AttributeError``. These were exacerbated when running the test suite, because of an unintended busy loop in the test scaffolding, which could starve the thread trying to make a connection. The ZEO reconnection tests may run much faster now, depending on platform, and should suffer far fewer (if any) intermittent "timed out waiting for storage to connect" failures. ZEO protocol and compatibility ------------------------------ ZODB 3.3 introduced multiversion concurrency control (MVCC), which required changes to the ZEO protocol. The first 3.3 release should have increased the internal ZEO protocol version number (used by ZEO protocol negotiation when a client connects), but neglected to. This has been repaired. Compatibility between pre-3.3 and post-3.3 ZEO clients and servers remains very limited. See the newly updated ``Compatibility`` section in ``README.txt`` for details. FileStorage ----------- - The ``.store()`` and ``.restore()`` methods didn't update the storage's belief about the largest oid in use when passed an oid larger than the largest oid the storage already knew about. Because ``.restore()`` in particular is used by ``copyTransactionsFrom()``, and by the first stage of ZRS recovery, a large database could be created that believed the only oid in use was oid 0 (the special oid reserved for the root object). In rare cases, it could go on from there assigning duplicate oids to new objects, starting over from oid 1 again. This has been repaired. A new ``set_max_oid()`` method was added to the ``BaseStorage`` class so that derived storages can update the largest oid in use in a threadsafe way. - A FileStorage's index file tried to maintain the index's largest oid as a separate piece of data, incrementally updated over the storage's lifetime. This scheme was more complicated than necessary, so was also more brittle and slower than necessary. It indirectly participated in a rare but critical bug: when a FileStorage was created via ``copyTransactionsFrom()``, the "maximum oid" saved in the index file was always 0. Use that FileStorage, and it could then create "new" oids starting over at 0 again, despite that those oids were already in use by old objects in the database. Packing a FileStorage has no reason to try to update the maximum oid in the index file either, so this kind of damage could (and did) persist even across packing. The index file's maximum-oid data is ignored now, but is still written out so that ``.index`` files can be read by older versions of ZODB. Finding the true maximum oid is done now by exploiting that the main index is really a kind of BTree (long ago, this wasn't true), and finding the largest key in a BTree is inexpensive. - A FileStorage's index file could be updated on disk even if the storage was opened in read-only mode. That bug has been repaired. - An efficient ``maxKey()`` implementation was added to class ``fsIndex``. Pickle (in-memory Connection) Cache ----------------------------------- You probably never saw this exception: ``ValueError: Can not re-register object under a different oid`` It's been changed to say what it meant: ``ValueError: A different object already has the same oid`` This happens if an attempt is made to add distinct objects to the cache that have the same oid (object identifier). ZODB should never do this, but it's possible for application code to force such an attempt. PersistentMapping and PersistentList ------------------------------------ Backward compatibility code has been added so that the sanest of the ZODB 3.2 dotted paths for ``PersistentMapping`` and ``PersistentList`` resolve. These are still preferred: - ``from persistent.list import PersistentList`` - ``from persistent.mapping import PersistentMapping`` but these work again too: - ``from ZODB.PersistentList import PersistentList`` - ``from ZODB.PersistentMapping import PersistentMapping`` BTrees ------ The BTrees interface file neglected to document the optional ``excludemin`` and ``excludemax`` arguments to the ``keys()``, ``values()`` and ``items()`` methods. Appropriate changes were merged in from the ZODB4 BTrees interface file. Tools ----- - ``mkzeoinst.py``'s default port number changed from to 9999 to 8100, to match the example in Zope's ``zope.conf``. fsIndex ------- An efficient ``maxKey()`` method was implemented for the ``fsIndex`` class. This makes it possible to determine the largest oid in a ``FileStorage`` index efficiently, directly, and reliably, replacing a more delicate scheme that tried to keep track of this by saving an oid high water mark in the index file and incrementally updating it. What's new in ZODB3 3.3.1a1? ============================ Release date: 11-Jan-2005 ZEO client cache ---------------- - Collector 1536: The ``cache-size`` configuration option for ZEO clients was being ignored. Worse, the client cache size was only one megabyte, much smaller than the advertised default of 20MB. Note that the default is carried over from a time when gigabyte disks were expensive and rare; 20MB is also too small on most modern machines. - Fixed a nasty bug in cache verification. A persistent ZEO cache uses a disk file, and, when active, has some in-memory data structures too to speed operation. Invalidations processed as part of startup cache verification were reflected in the in-memory data structures, but not correctly in the disk file. So if an object revision was invalidated as part of verification, the object wasn't loaded again before the connection was closed, and the object revision remained in the cache file until the connection was closed, then the next time the cache file was opened it could believe that the stale object revision in the file was actually current. - Fixed a bug wherein an object removed from the client cache didn't properly mark the file slice it occupied as being available for reuse. ZEO --- Collector 1503: excessive logging. It was possible for a ZEO client to log "waiting for cache verification to finish" messages at a very high rate, producing gigabytes of such messages in short order. ``ClientStorage._wait_sync()`` was changed to log no more than one such message per 5 minutes. persistent ---------- Collector #1350: ZODB has a default one-thread-per-connection model, and two threads should never do operations on a single connection simultaneously. However, ZODB can't detect violations, and this happened in an early stage of Zope 2.8 development. The low-level ``ghostify()`` and ``unghostify()`` routines in ``cPerisistence.c`` were changed to give some help in detecting this when it happens. In a debug build, both abort the process if thread interference is detected. This is extreme, but impossible to overlook. In a release build, ``unghostify()`` raises ``SystemError`` if thread damage is detected; ``ghostify()`` ignores the problem in a release build (``ghostify()`` is supposed to be so simple that it "can't fail"). ConflictError ------------- New in 3.3, a ``ConflictError`` exception may attempt to insert the path to the object's class in its message. However, a ZEO server may not have access to application class implementations, and then the attempt by the server to raise ``ConflictError`` could raise ``ImportError`` instead while trying to determine the object's class path. This was confusing. The code has been changed to obtain the class path from the object's pickle, without trying to import application modules or classes. FileStorage ----------- Collector 1581: When an attempt to pack a corrupted ``Data.fs`` file was made, it was possible for the pack routine to die with a reference to an undefined global while it was trying to raise ``CorruptedError``. It raises ``CorruptedError``, as it always intended, in these cases now. Install ------- The C header file ``ring.h`` is now installed. Tools ----- - ``BTrees.check.display()`` now displays the oids (if any) of the BTree's or TreeSet's constituent objects. What's new in ZODB3 3.3? ======================== Release date: 06-Oct-2004 ZEO --- The encoding of RPC calls between server and client was being done with protocol 0 ("text mode") pickles, which could require sending four times as many bytes as necessary. Protocol 1 pickles are used now. Thanks to Andreas Jung for the diagnosis and cure. ZODB/component.xml ------------------ ``cache-size`` parameters were changed from type ``integer`` to type ``byte-size``. This allows you to specify, for example, "``cache-size 20MB``" to get a 20 megabyte cache. transaction ----------- The deprecation warning for ``Transaction.begin()`` was changed to point to the caller, instead of to ``Transaction.begin()`` itself. Connection ---------- Restored Connection's private ``_opened`` attribute. This was still referenced by ``DB.connectionDebugInfo()``, and Zope 2 calls the latter. FileStorage ----------- Collector #1517: History tab for ZPT does not work. ``FileStorage.history()`` was reading the user, description, and extension fields out of the object pickle, due to starting the read at a wrong location. Looked like cut-and-paste repetition of the same bug in ``FileStorage.FileIterator`` noted in the news for 3.3c1. What's new in ZODB3 3.3 release candidate 1? ============================================ Release date: 14-Sep-2004 Connection ---------- ZODB intends to raise ``ConnnectionStateError`` if an attempt is made to close a connection while modifications are pending (the connection is involved in a transaction that hasn't been ``abort()``'ed or ``commit()``'ed). It was missing the case where the only pending modifications were made in subtransactions. This has been fixed. If an attempt to close a connection with pending subtransactions is made now:: ConnnectionStateError: Cannot close a connection with a pending subtransaction is raised. transaction ----------- - Transactions have new, backward-incompatible behavior in one respect: if a ``Transaction.commit()``, ``Transaction.commit(False)``, or ``Transaction.commit(True)`` raised an exception, prior behavior was that the transaction effectively aborted, and a new transaction began. A primary bad consequence was that, if in a sequence of subtransaction commits, one of the commits failed but the exception was suppressed, all changes up to and including the failing commit were lost, but later subtransaction commits in the sequence got no indication that something had gone wrong, nor did the final (top level) commit. This could easily lead to inconsistent data being committed, from the application's point of view. The new behavior is that a failing commit "sticks" until explicitly cleared. Now if an exception is raised by a ``commit()`` call (whether subtransaction or top level) on a Transaction object ``T``: - Pending changes are aborted, exactly as they were for a failing commit before. - But ``T`` remains the current transaction object (if ``tm`` is ``T``'s transaction manger, ``tm.get()`` continues to return ``T``). - All subsequent attempts to do ``T.commit()``, ``T.join()``, or ``T.register()`` raise the new ``TransactionFailedError`` exception. Note that if you try to modify a persistent object, that object's resource manager (usually a ``Connection`` object) will attempt to ``join()`` the failed transaction, and ``TransactionFailedError`` will be raised right away. So after a transaction or subtransaction commit fails, that must be explicitly cleared now, either by invoking ``abort()`` on the transaction object, or by invoking ``begin()`` on its transaction manager. - Some explanations of new transaction features in the 3.3a3 news were incorrect, and this news file has been retroactively edited to repair that. See news for 3.3a3 below. - If ReadConflictError was raised by an attempt to load an object with a ``_p_independent()`` method that returned false, attempting to commit the transaction failed to (re)raise ReadConflictError for that object. Note that ZODB intends to prevent committing a transaction in which a ReadConflictError occurred; this was an obscure case it missed. - Growing pains: ZODB 3.2 had a bug wherein ``Transaction.begin()`` didn't abort the current transaction if the only pending changes were in a subtransaction. In ZODB 3.3, it's intended that a transaction manager be used to effect ``begin()`` (instead of invoking ``Transaction.begin()``), and calling ``begin()`` on a transaction manager didn't have this old bug. However, ``Transaction.begin()`` still exists in 3.3, and it had a worse bug: it never aborted the transaction (not even if changes were pending outside of subtransactions). ``Transaction.begin()`` has been changed to abort the transaction. ``Transaction.begin()`` is also deprecated. Don't use it. Use ``begin()`` on the relevant transaction manager instead. For example, >>> import transaction >>> txn = transaction.begin() # start a txn using the default TM if using the default ``ThreadTransactionManager`` (see news for 3.3a3 below). In 3.3, it's intended that a single ``Transaction`` object is used for exactly one transaction. So, unlike as in 3.2, when somtimes ``Transaction`` objects were reused across transactions, but sometimes weren't, when you do ``Transaction.begin()`` in 3.3 a brand new transaction object is created. That's why this use is deprecated. Code of the form: >>> txn = transaction.get() >>> ... >>> txn.begin() >>> ... >>> txn.commit() can't work as intended in 3.3, because ``txn`` is no longer the current ``Transaction`` object the instant ``txn.begin()`` returns. BTrees ------ The BTrees __init__.py file is now just a comment. It had been trying to set up support for (long gone) "int sets", and to import an old version of Zope's Interface package, which doesn't even ship with ZODB. The latter in particular created problems, at least clashing with PythonCAD's Interface package. POSException ------------ Collector #1488 (TemporaryStorage -- going backward in time). This confusion was really due to that the detail on a ConflictError exception didn't make sense. It called the current revision "was", and the old revision "now". The detail is much more informative now. For example, if the exception said:: ConflictError: database conflict error (oid 0xcb22, serial was 0x03441422948b4399, now 0x034414228c3728d5) before, it now says:: ConflictError: database conflict error (oid 0xcb22, serial this txn started with 0x034414228c3728d5 2002-04-14 20:50:32.863000, serial currently committed 0x03441422948b4399 2002-04-14 20:50:34.815000) ConflictError ------------- The undocumented ``get_old_serial()`` and ``get_new_serial()`` methods were swapped (the first returned the new serial, and the second returned the old serial). Tools ----- ``FileStorage.FileIterator`` was confused about how to read a transaction's user and description fields, which caused several tools to display binary gibberish for these values. ``ZODB.utils.oid_repr()`` changed to add a leading "0x", and to strip leading zeroes. This is used, e.g., in the detail of a ``POSKeyError`` exception, to identify the missing oid. Before, the output was ambiguous. For example, oid 17 was displayed as 0000000000000011. As a Python integer, that's octal 9. Or was it meant to be decimal 11? Or was it meant to be hex? Now it displays as 0x11. fsrefs.py: When run with ``-v``, produced tracebacks for objects whose creation was merely undone. This was confusing. Tracebacks are now produced only if there's "a real" problem loading an oid. If the current revision of object O refers to an object P whose creation has been undone, this is now identified as a distinct case. Captured and ignored most attempts to stop it via Ctrl+C. Repaired. Now makes two passes, so that an accurate report can be given of all invalid references. ``analyze.py`` produced spurious "len of unsized object" messages when finding a data record for an object uncreation or version abort. These no longer appear. ``fsdump.py``'s ``get_pickle_metadata()`` function (which is used by several tools) was confused about what to do when the ZODB pickle started with a pickle ``GLOBAL`` opcode. It actually loaded the class then, which it intends never to do, leading to stray messages on stdout when the class wasn't available, and leading to a strange return value even when it was available (the repr of the type object was returned as "the module name", and an empty string was returned as "the class name"). This has been repaired. What's new in ZODB3 3.3 beta 2 ============================== Release date: 13-Aug-2004 Transaction Managers -------------------- Zope3-dev Collector #139: Memory leak involving buckets and connections The transaction manager internals effectively made every Connection object immortal, except for those explicitly closed. Since typical practice is not to close connections explicitly (and closing a DB happens not to close the connections to it -- although that may change), this caused massive memory leaks when many connections were opened. The transaction manager internals were reworked to use weak references instead, so that connection memory (and other registered synch objects) now get cleaned up when nothing other than the transaction manager knows about them. Storages -------- Collector #1327: FileStorage init confused by time travel If the system clock "went backwards" a long time between the times a FileStorage was closed and reopened, new transaction ids could be smaller than transaction ids already in the storage, violating a key invariant. Now transaction ids are guaranteed to be increasing even when this happens. If time appears to have run backwards at all when a FileStorage is opened, a new message saying so is logged at warning level; if time appears to have run backwards at least 30 minutes, the message is logged at critical level (and you should investigate to find and repair the true cause). Tools ----- repozo.py: Thanks to a suggestion from Toby Dickenson, backups (whether incremental or full) are first written to a temp file now, which is fsync'ed at the end, and only after that succeeds is the file renamed to YYYY-MM-DD-HH-MM-SS.ext form. In case of a system crash during a repozo backup, this at least makes it much less likely that a backup file with incomplete or incorrect data will be left behind. fsrefs.py: Fleshed out the module docstring, and repaired a bug wherein spurious error msgs could be produced after reporting a problem with an unloadable object. Test suite ---------- Collector #1397: testTimeStamp fails on FreeBSD The BSD distributions are unique in that their mktime() implementation usually ignores the input tm_isdst value. Test checkFullTimeStamp() was sensitive to this platform quirk. Reworked the way some of the ZEO tests use threads, so that unittest is more likely to notice the real cause of a failure (which usually occurs in a thread), and less likely to latch on to spurious problems resulting from the real failure. What's new in ZODB3 3.3 beta 1 ============================== Release date: 07-Jun-2004 3.3b1 is the first ZODB release built using the new zpkg tools: http://zope.org/Members/fdrake/zpkgtools/ This appears to have worked very well. The structure of the tarball release differs from previous releases because of it, and the set of installed files includes some that were not installed in previous releases. That shouldn't create problems, so let us know if it does! We'll fine-tune this for the next release. BTrees ------ Fixed bug indexing BTreeItems objects with negative indexes. This caused reverse iteration to return each item twice. Thanks to Casey Duncan for the fix. ZODB ---- Methods removed from the database (ZODB.DB.DB) class: cacheStatistics(), cacheMeanAge(), cacheMeanDeac(), and cacheMeanDeal(). These were undocumented, untested, and unused. The first always returned an empty tuple, and the rest always returned None. When trying to do recovery to a time earlier than that of the most recent full backup, repozo.py failed to find the appropriate files, erroneously claiming "No files in repository before ". This has been repaired. Collector #1330: repozo.py -R can create corrupt .fs. When looking for the backup files needed to recreate a Data.fs file, repozo could (unintentionally) include its meta .dat files in the list, or random files of any kind created by the user in the backup directory. These would then get copied verbatim into the reconstructed file, filling parts with junk. Repaired by filtering the file list to include only files with the data extensions repozo.py creates (.fs, .fsz, .deltafs, and .deltafsz). Thanks to James Henderson for the diagnosis. fsrecover.py couldn't work, because it referenced attributes that no longer existed after the MVCC changes. Repaired that, and added new tests to ensure it continues working. Collector #1309: The reference counts reported by DB.cacheExtremeDetails() for ghosts were one too small. Thanks to Dieter Maurer for the diagnosis. Collector #1208: Infinite loop in cPickleCache. If a persistent object had a __del__ method (probably not a good idea regardless, but we don't prevent it) that referenced an attribute of self, the code to deactivate objects in the cache could get into an infinite loop: ghostifying the object could lead to calling its __del__ method, the latter would load the object into cache again to satsify the attribute reference, the cache would again decide that the object should be ghostified, and so on. The infinite loop no longer occurs, but note that objects of this kind still aren't sensible (they're effectively immortal). Thanks to Toby Dickenson for suggesting a nice cure. What's new in ZODB3 3.3 alpha 3 =============================== Release date: 16-Apr-2004 transaction ----------- There is a new transaction package, which provides new interfaces for application code and for the interaction between transactions and resource managers. The top-level transaction package has functions ``commit()``, ``abort()``, ``get()``, and ``begin()``. They should be used instead of the magic ``get_transaction()`` builtin, which will be deprecated. For example: >>> get_transaction().commit() should now be written as >>> import transaction >>> transaction.commit() The new API provides explicit transaction manager objects. A transaction manager (TM) is responsible for associating resource managers with a "current" transaction. The default TM, implemented by class ``ThreadedTransactionManager``, assigns each thread its own current transaction. This default TM is available as ``transaction.manager``. The ``TransactionManager`` class assigns all threads to the same transaction, and is an explicit replacement for the ``Connection.setLocalTransaction()`` method: A transaction manager instance can be passed as the transaction_manager argument to ``DB.open()``. If you do, the connection will use the specified transaction manager instead of the default TM. The current transaction is obtained by calling ``get()`` on a TM. For example: >>> tm = transaction.TransactionManager() >>> cn = db.open(transaction_manager=tm) [...] >>> tm.get().commit() The ``setLocalTransaction()`` and ``getTransaction()`` methods of Connection are deprecated. Use an explicit TM passed via ``transaction_manager=`` to ``DB.open()`` instead. The ``setLocalTransaction()`` method still works, but it returns a TM instead of a Transaction. A TM creates Transaction objects, which are used for exactly one transaction. Transaction objects still have ``commit()``, ``abort()``, ``note()``, ``setUser()``, and ``setExtendedInfo()`` methods. Resource managers, e.g. Connection or RDB adapter, should use a Transaction's ``join()`` method instead of its ``register()`` method. An object that calls ``join()`` manages its own resources. An object that calls ``register()`` expects the TM to manage the objects. Data managers written against the ZODB 4 transaction API are now supported in ZODB 3. persistent ---------- A database can now contain persistent weak references. An object that is only reachable from persistent weak references will be removed by pack(). The persistence API now distinguishes between deactivation and invalidation. This change is intended to support objects that can't be ghosts, like persistent classes. Deactivation occurs when a user calls _p_deactivate() or when the cache evicts objects because it is full. Invalidation occurs when a transaction updates the object. An object that can't be a ghost must load new state when it is invalidated, but can ignore deactivation. Persistent objects can implement a __getnewargs__() method that will be used to provide arguments that should be passed to __new__() when instances (including ghosts) are created. An object that implements __getnewargs__() must be loaded from storage even to create a ghost. There is new support for writing hooks like __getattr__ and __getattribute__. The new hooks require that user code call special persistence methods like _p_getattr() inside their hook. See the ZODB programming guide for details. The format of serialized persistent references has changed; that is, the on-disk format for references has changed. The old format is still supported, but earlier versions of ZODB will not be able to read the new format. ZODB ---- Closing a ZODB Connection while it is registered with a transaction, e.g. has pending modifications, will raise a ConnnectionStateError. Trying to load objects from or store objects to a closed connection will also raise a ConnnectionStateError. ZODB connections are synchronized on commit, even when they didn't modify objects. This feature assumes that the thread that opened the connection is also the thread that uses it. If not, this feature will cause problems. It can be disabled by passing synch=False to open(). New broken object support. New add() method on Connection. User code should not assign the _p_jar attribute of a new persistent object directly; a deprecation warning is issued in this case. Added a get() method to Connection as a preferred synonym for __getitem__(). Several methods and/or specific optional arguments of methods have been deprecated. The cache_deactivate_after argument used by DB() and Connection() is deprecated. The DB methods getCacheDeactivateAfter(), getVersionCacheDeactivateAfter(), setCacheDeactivateAfter(), and setVersionCacheDeactivateAfter() are also deprecated. The old-style undo() method was removed from the storage API, and transactionalUndo() was renamed to undo(). The BDBStorages are no longer distributed with ZODB. Fixed a serious bug in the new pack implementation. If pack was called on the storage and passed a time earlier than a previous pack time, data could be lost. In other words, if there are any two pack calls, where the time argument passed to the second call was earlier than the first call, data loss could occur. The bug was fixed by causing the second call to raise a StorageError before performing any work. Fixed a rare bug in pack: if a pack started during a small window of time near the end of a concurrent transaction's commit, it was possible for the pack attempt to raise a spurious CorruptedError: ... transaction with checkpoint flag set exception. This did no damage to the database, or to the transaction in progress, but no pack was performed then. By popular demand, FileStorage.pack() no longer propagates a FileStorageError: The database has already been packed to a later time or no changes have been made since the last pack exception. Instead that message is logged (at INFO level), and the pack attempt simply returns then (no pack is performed). ZEO --- Fixed a bug that prevented the -m / --monitor argument from working. zdaemon ------- Added a -m / --mask option that controls the umask of the subprocess. zLOG ---- The zLOG backend has been removed. zLOG is now just a facade over the standard Python logging package. Environment variables like STUPID_LOG_FILE are no longer honored. To configure logging, you need to follow the directions in the logging package documentation. The process is currently more complicated than configured zLOG. See test.py for an example. ZConfig ------- This release of ZODB contains ZConfig 2.1. More documentation has been written. Make sure keys specified as attributes of the element are converted by the appropriate key type, and are re-checked for derived sections. Refactored the ZConfig.components.logger schema components so that a schema can import just one of the "eventlog" or "logger" sections if desired. This can be helpful to avoid naming conflicts. Added a reopen() method to the logger factories. Always use an absolute pathname when opening a FileHandler. Miscellaneous ------------- The layout of the ZODB source release has changed. All the source code is contained in a src subdirectory. The primary motivation for this change was to avoid confusion caused by installing ZODB and then testing it interactively from the source directory; the interpreter would find the uncompiled ZODB package in the source directory and report an import error. A reference-counting bug was fixed, in the logic calling a modified persistent object's data manager's register() method. The primary symptom was rare assertion failures in Python's cyclic garbage collection. The Connection class's onCommitAction() method was removed. Some of the doc strings in ZODB are now written for processing by epydoc. Several new test suites were written using doctest instead of the standard unittest TestCase framework. MappingStorage now implements getTid(). ThreadedAsync: Provide a way to shutdown the servers using an exit status. The mkzeoinstance script looks for a ZODB installation, not a Zope installation. The received wisdom is that running a ZEO server without access to the appserver code avoids many mysterious problems. What's new in ZODB3 3.3 alpha 2 =============================== Release date: 06-Jan-2004 This release contains a major overhaul of the persistence machinery, including some user-visible changes. The Persistent base class is now a new-style class instead of an ExtensionClass. The change enables the use of features like properties with persistent object classes. The Persistent base class is now contained in the persistent package. The Persistence package is included for backwards compatibility. The Persistence package is used by Zope to provide special ExtensionClass-compatibility features like a non-C3 MRO and an __of__ method. ExtensionClass is not included with this release of ZODB3. If you use the Persistence package, it will print a warning and import Persistent from persistent. In short, the new persistent package is recommended for non-Zope applications. The following dotted class names are now preferred over earlier names: - persistent.Persistent - persistent.list.PersistentList - persistent.mapping.PersistentMapping - persistent.TimeStamp The in-memory, per-connection object cache (pickle cache) was changed to participate in garbage collection. This should reduce the number of memory leaks, although we are still tracking a few problems. Multi-version concurrency control --------------------------------- ZODB now supports multi-version concurrency control (MVCC) for storages that support multiple revisions. FileStorage and BDBFullStorage both support MVCC. In short, MVCC means that read conflicts should almost never occur. When an object is modified in one transaction, other concurrent transactions read old revisions of the object to preserve consistency. In earlier versions of ZODB, any access of the modified object would raise a ReadConflictError. The ZODB internals changed significantly to accommodate MVCC. There are relatively few user visible changes, aside from the lack of read conflicts. It is possible to disable the MVCC feature using the mvcc keyword argument to the DB open() method, ex.: db.open(mvcc=False). ZEO --- Changed the ZEO server and control process to work with a single configuration file; this is now the default way to configure these processes. (It's still possible to use separate configuration files.) The ZEO configuration file can now include a "runner" section used by the control process and ignored by the ZEO server process itself. If present, the control process can use the same configuration file. Fixed a performance problem in the logging code for the ZEO protocol. The logging code could call repr() on arbitrarily long lists, even though it only logged the first 60 bytes; worse, it called repr() even if logging was currently disabled. Fixed to call repr() on individual elements until the limit is reached. Fixed a bug in zrpc (when using authentication) where the MAC header wasn't being read for large messages, generating errors while unpickling commands sent over the wire. Also fixed the zeopasswd.py script, added testcases and provided a more complete commandline interface. Fixed a misuse of the _map variable in zrpc Connectio objects, which are also asyncore.dispatcher objects. This allows ZEO to work with CVS Python (2.4). _map is used to indicate whether the dispatcher users the default socket_map or a custom socket_map. A recent change to asyncore caused it to use _map in its add_channel() and del_channel() methods, which presumes to be a bug fix (may get ported to 2.3). That causes our dubious use of _map to be a problem, because we also put the Connections in the global socket_map. The new asyncore won't remove it from the global socket map, because it has a custom _map. The prefix used for log messages from runzeo.py was changed from RUNSVR to RUNZEO. Miscellaneous ------------- ReadConflictError objects now have an ignore() method. Normally, a transaction that causes a read conflict can't be committed. If the exception is caught and its ignore() method called, the transaction can be committed. Application code may need this in advanced applications. What's new in ZODB3 3.3 alpha 1 =============================== Release date: 17-Jul-2003 New features of Persistence --------------------------- The Persistent base class is a regular Python type implemented in C. It should be possible to create new-style classes that inherit from Persistent, and, thus, use all the new Python features introduced in Python 2.2 and 2.3. The __changed__() method on Persistent objects is no longer supported. New features in BTrees ---------------------- BTree, Bucket, TreeSet and Set objects are now iterable objects, playing nicely with the iteration protocol introduced in Python 2.2, and can be used in any context that accepts an iterable object. As for Python dicts, the iterator constructed for BTrees and Buckets iterates over the keys. >>> from BTrees.OOBTree import OOBTree >>> b = OOBTree({"one": 1, "two": 2, "three": 3, "four": 4}) >>> for key in b: # iterates over the keys ... print key four one three two >>> list(enumerate(b)) [(0, 'four'), (1, 'one'), (2, 'three'), (3, 'two')] >>> i = iter(b) >>> i.next() 'four' >>> i.next() 'one' >>> i.next() 'three' >>> i.next() 'two' >>> As for Python dicts in 2.2, BTree and Bucket objects have new .iterkeys(), .iteritems(), and .itervalues() methods. TreeSet and Set objects have a new .iterkeys() method. Unlike as for Python dicts, these new methods accept optional min and max arguments to effect range searches. While Bucket.keys() produces a list, Bucket.iterkeys() produces an iterator, and similarly for Bucket values() versus itervalues(), Bucket items() versus iteritems(), and Set keys() versus iterkeys(). The iter{keys,values,items} methods of BTrees and the iterkeys() method of Treesets also produce iterators, while their keys() (etc) methods continue to produce BTreeItems objects (a form of "lazy" iterator that predates Python 2.2's iteration protocol). >>> sum(b.itervalues()) 10 >>> zip(b.itervalues(), b.iterkeys()) [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')] >>> BTree, Bucket, TreeSet and Set objects also implement the __contains__ method new in Python 2.2, which means that testing for key membership can be done directly now via the "in" and "not in" operators: >>> "won" in b False >>> "won" not in b True >>> "one" in b True >>> All old and new range-search methods now accept keyword arguments, and new optional excludemin and excludemax keyword arguments. The new keyword arguments allow doing a range search that's exclusive at one or both ends (doesn't include min, and/or doesn't include max). >>> list(b.keys()) ['four', 'one', 'three', 'two'] >>> list(b.keys(max='three')) ['four', 'one', 'three'] >>> list(b.keys(max='three', excludemax=True)) ['four', 'one'] >>> Other improvements ------------------ The exceptions generated by write conflicts now contain the name of the conflicted object's class. This feature requires support for the storage. All the standard storages support it. What's new in ZODB3 3.2 ======================== Release date: 08-Oct-2003 Nothing has changed since release candidate 1. What's new in ZODB3 3.2 release candidate 1 =========================================== Release date: 01-Oct-2003 Added a summary to the Doc directory. There are several new documents in the 3.2 release, including "Using zdctl and zdrun to manage server processes" and "Running a ZEO Server HOWTO." Fixed ZEO's protocol negotiation mechanism so that a client ZODB 3.1 can talk to a ZODB 3.2 server. Fixed a memory leak in the ZEO server. The server was leaking a few KB of memory per connection. Fixed a memory leak in the ZODB object cache (cPickleCache). The cache did not release two references to its Connection, causing a large cycle of objects to leak when a database was closed. Fixed a bug in the ZEO code that caused it to leak socket objects on Windows. Specifically, fix the trigger mechanism so that both sockets created for a trigger are closed. Fixed a bug in the ZEO storage server that caused it to leave temp files behind. The CommitLog class contains a temp file, but it was not closing the file. Changed the order of setuid() and setgid() calls in zdrun, so that setgid() is called first. Added a timeout to the ZEO test suite that prevents hangs. The test suite creates ZEO servers with randomly assigned ports. If the port happens to be in use, the test suite would hang because the ZEO client would never stop trying to connect. The fix will cause the test to fail after a minute, but should prevent the test runner from hanging. The logging package was updated to include the latest version of the logging package from Python CVS. Note that this package is only installed for Python 2.2. In later versions of Python, it is available in the Python standard library. The ZEO1 directory was removed from the source distribution. ZEO1 is not supported, and we never intended to include it in the release. What's new in ZODB3 3.2 beta 3 ============================== Release date: 23-Sep-2003 Note: The changes listed for this release include changes also made in ZODB 3.1.x releases and ported to the 3.2 release. This version of ZODB 3.2 is not compatible with Python 2.1. Early versions were explicitly designed to be compatible with Zope 2.6. That plan has been dropped, because Zope 2.7 is already in beta release. Several of the classes in ZEO and ZODB now inherit from object, making them new-style classes. The primary motivation for the change was to make it easier to debug memory leaks. We don't expect any behavior to change as a result. A new feature to allow removal of connection pools for versions was ported from Zope 2.6. This feature is needed by Zope to avoid denial of service attacks that allow a client to create an arbitrary number of version pools. Fixed several critical ZEO bugs. - If several client transactions were blocked waiting for the storage and one of the blocked clients disconnected, the server would attempt to restart one of the other waiting clients. Since the disconnected client did not have the storage lock, this could lead to deadlock. It could also cause the assertion "self._client is None" to fail. - If a storage server fails or times out between the vote and the finish, the ZEO cache could get populated with objects that didn't make it to the storage server. - If a client loses its connection to the server near the end of a transaction, it is now guaranteed to get a ClientDisconnected error even if it reconnects before the transaction finishes. This is necessary because the server will always abort the transaction. In some cases, the client would never see an error for the aborted transaction. - In tpc_finish(), reordered the calls so that the server's tpc_finish() is called (and must succeed) before we update the ZEO client cache. - The storage name is now prepended to the sort key, to ensure a unique global sort order if storages are named uniquely. This can prevent deadlock in some unusual cases. Fixed several serious flaws in the implementation of the ZEO authentication protocol. - The smac layer would accept a message without a MAC even after the session key was established. - The client never initialized its session key, so it never checked incoming messages or created MACs for outgoing messags. - The smac layer used a single HMAC instance for sending and receiving messages. This approach could only work if client and server were guaranteed to process all messages in the same total order, which could only happen in simple scenarios like unit tests. Fixed a bug in ExtensionClass when comparing ExtensionClass instances. The code could raise RuntimeWarning under Python 2.3, and produce incorrect results on 64-bit platforms. Fixed bug in BDBStorage that could lead to DBRunRecoveryErrors when a transaction was aborted after performing operations like commit version or undo that create new references to existing pickles. Fixed a bug in Connection.py that caused it to fail with an AttributeError if close() was called after the database was closed. The test suite leaves fewer log files behind, although it still leaves a lot of junk. The test.py script puts each tests temp files in a separate directory, so it is easier to see which tests are causing problems. Unfortunately, it is still to tedious to figure out why the identified tests are leaving files behind. This release contains the latest and greatest version of the BDBStorage. This storage has still not seen testing in a production environment, but it represents the current best design and most recent code culled from various branches where development has occurred. The Tools directory contains a number of small improvements, a few new tools, and README.txt that catalogs the tools. Many of the tools are installed by setup.py; those scripts will now have a #! line set automatically on Unix. Fixed bugs in Tools/repozo.py, including a timing-dependent one that could cause the following invocation of repozo to do a full backup when an incremental backup would have sufficed. A pair of new scripts from Jim Fulton can be used to synthesize workloads and measure ZEO performance: see zodbload.py and zeoserverlog.py in the Tools directory. Note that these require Zope. Tools/checkbtrees.py was strengthened in two ways: - In addition to running the _check() method on each BTree B found, BTrees.check.check(B) is also run. The check() function was written after checkbtrees.py, and identifies kinds of damage B._check() cannot find. - Cycles in the object graph no longer lead to unbounded output. Note that preventing this requires remembering the oid of each persistent object found, which increases the memory needed by the script. What's new in ZODB3 3.2 beta 2 ============================== Release date: 16-Jun-2003 Fixed critical race conditions in ZEO's cache consistency code that could cause invalidations to be lost or stale data to be written to the cache. These bugs can lead to data loss or data corruption. These bugs are relatively unlikely to be provoked in sites with few conflicts, but the possibility of failure existed any time an object was loaded and stored concurrently. Fixed a bug in conflict resolution that failed to ghostify an object if it was involved in a conflict. (This code may be redundant, but it has been fixed regardless.) The ZEO server was fixed so that it does not perform any I/O until all of a transactions' invalidations are queued. If it performs I/O in the middle of sending invalidations, it would be possible to overlap a load from a client with the invalidation being sent to it. The ZEO cache now handles invalidations atomically. This is the same sort of bug that is described in the 3.1.2b1 section below, but it affects the ZEO cache. Fixed several serious bugs in fsrecover that caused it to fail catastrophically in certain cases because it thought it had found a checkpoint (status "c") record when it was in the middle of the file. Two new features snuck into this beta release. The ZODB.transact module provides a helper function that converts a regular function or method into a transactional one. The ZEO client cache now supports Adaptable Persistence (APE). The cache used to expect that all OIDs were eight bytes long. What's new in ZODB3 3.2 beta 1 ============================== Release date: 30-May-2003 ZODB ---- Invalidations are now processed atomically. Each transaction will see all the changes caused by an earlier transaction or none of them. Before this patch, it was possible for a transaction to see invalid data because it saw only a subset of the invalidations. This is the most likely cause of reported BTrees corruption, where keys were stored in the wrong bucket. When a BTree bucket splits, the bucket and the bucket's parent are both modified. If a transaction sees the invalidation for the bucket but not the parent, the BTree in memory will be internally inconsistent and keys can be put in the wrong bucket. The atomic invalidation fix prevents this problem. A number of minor reference count fixes in the object cache were fixed. That's the cPickleCache.c file. It was possible for a transaction that failed in tpc_finish() to lose the traceback that caused the failure. The transaction code was fixed to report the original error as well as any errors that occur while trying to recover from the original error. The "other" argument to copyTransactionsFrom() only needs to have an .iterator() method. For convenience, change FileStorage's and BDBFullStorage's iterator to have this method, which just returns self. Mount points are now visible from mounted objects. Fixed memory leak involving database connections and caches. When a connection or database was closed, the cache and database leaked, because of a circular reference involving the cache. Fixed the cache to explicitly clear out its contents when its connection is closed. The ZODB cache has fewer methods. It used to expose methods that could mutate the dictionary, which allowed users to violate internal invariants. ZConfig ------- It is now possible to configure ZODB databases and storages and ZEO servers using ZConfig. ZEO & zdaemon ------------- ZEO now supports authenticated client connections. The default authentication protocol uses a hash-based challenge-response protocol to prove identity and establish a session key for message authentication. The architecture is pluggable to allow third-parties to developer better authentication protocols. There is a new HOWTO for running a ZEO server. The draft in this release is incomplete, but provides more guidance than previous releases. See the file Doc/ZEO/howto.txt. The ZEO storage server's transaction timeout feature was refactored and made slightly more rebust. A new ZEO utility script, ZEO/mkzeoinst.py, was added. This creates a standard directory structure and writes a configuration file with mostly default values, and a bootstrap script that can be used to manage and monitor the server using zdctl.py (see below). Much work was done to improve zdaemon's zdctl.py and zdrun.py scripts. (In the alpha 1 release, zdrun.py was called zdaemon.py, but installing it in /bin caused much breakage due to the name conflict with the zdaemon package.) Together with the new mkzeoinst.py script, this makes controlling a ZEO server a breeze. A ZEO client will not read from its cache during cache verification. This fix was necessary to prevent the client from reading inconsistent data. The isReadOnly() method of a ZEO client was fixed to return the false when the client is connected to a read-only fallback server. The sync() method of ClientStorage and the pending() method of a zrpc connection now do both input and output. The short_repr() function used to generate log messages was fixed so that it does not blow up creating a repr of very long tuples. Storages -------- FileStorage has a new pack() implementation that fixes several reported problems that could lead to data loss. Two small bugs were fixed in DemoStorage. undoLog() did not handle its arguments correctly and pack() could accidentally delete objects created in versions. Fixed trivial bug in fsrecover that prevented it from working at all. FileStorage will use fsync() on Windows starting with Python 2.2.3. FileStorage's commit version was fixed. It used to stop after the first object, leaving all the other objects in the version. BTrees ------ Trying to store an object of a non-integer type into an IIBTree or OIBTree could leave the bucket in a variety of insane states. For example, trying b[obj] = "I'm a string, not an integer" where b is an OIBTree. This manifested as a refcount leak in the test suite, but could have been much worse (most likely in real life is that a seemingly arbitrary existing key would "go missing"). When deleting the first child of a BTree node with more than one child, a reference to the second child leaked. This could cause the entire bucket chain to leak (not be collected as garbage despite not being referenced anymore). Other minor BTree leak scenarios were also fixed. Tools ----- New tool zeoqueue.py for parsing ZEO log files, looking for blocked transactions. New tool repozo.py (originally by Anthony Baxter) for performing incremental backups of Data.fs files. The fsrecover.py script now does a better job of recovering from errors the occur in the middle of a transaction record. Fixed several bugs that caused partial or total failures in earlier versions. What's new in ZODB3 3.2 alpha 1 =============================== Release date: 17-Jan-2003 Most of the changes in this release are performance and stability improvements to ZEO. A major packaging change is that there won't be a separate ZEO release. The new ZConfig is a noteworthy addtion (see below). ZODB ---- An experimental new transaction API was added. The Connection class has a new method, setLocalTransaction(). ZODB applications can call this method to bind transactions to connections rather than threads. This is especially useful for GUI applications, which often have only one thread but multiple independent activities within that thread (generally one per window). Thanks to Christian Reis for championing this feature. Applications that take advantage of this feature should not use the get_transaction() function. Until now, ZODB itself sometimes assumed get_transaction() was the only way to get the transaction. Minor corrections have been added. The ZODB test suite, on the other hand, can continue to use get_transaction(), since it is free to assume that transactions are bound to threads. ZEO --- There is a new recommended script for starting a storage server. We recommend using ZEO/runzeo.py instead of ZEO/start.py. The start.py script is still available in this release, but it will no longer be maintained and will eventually be removed. There is a new zdaemon implementation. This version is a separate script that runs an arbitrary daemon. To run the ZEO server as a daemon, you would run "zdrun.py runzeo.py". There is also a simple shell, zdctl.py, that can be used to manage a daemon. Try "zdctl.py -p runzeo.py". There is a new version of the ZEO protocol in this release and a first stab at protocol negotiation. (It's a first stab because the protocol checking supporting in ZODB 3.1 was too primitive to support anything better.) A ZODB 3.2 ZEO client can talk to an old server, but a ZODB 3.2 server can't talk to an old client. It's safe to upgrade all the clients first and upgrade the server last. The ZEO client cache format changed, so you'll need to delete persistent caches before restarting clients. The ZEO cache verification protocol was revised to require many fewer messages in cases where a client or server restarts quickly. The performance of full cache verification has improved dramatically. Measurements from Jim were somewhere in 2x-5x. The implementation was fixed to use the very-fast getSerial() method on the storage instead of the comparatively slow load(). The ZEO server has an optional timeout feature that will abort a connection that does not commit within a certain amount of time. The timeout works by closing the socket the client is using, causing both client and server to abort the transaction and continue. This is a drastic step, but can be useful to prevent a hung client or other bug from blocking a server indefinitely. A bug was fixed in the ZEO protocol that allowed clients to read stale cache data while cache verification was being performed. The fixed version prevents the client from using the storage until after verification completes. The ZEO server has an experimental monitoring interface that reports usage statistics for the storage server including number of connected clients and number of transactions active and committed. It can be enabled by passing the -m flag to runsvr.py. The ZEO ClientStorage no longer supports the environment variables CLIENT_HOME, INSTANCE_HOME, or ZEO_CLIENT. The ZEO1 package is still included with this release, but there is no longer an option to install it. BTrees ------ The BTrees package now has a check module that inspects a BTree to check internal invariants. Bugs in older versions of the code code leave a BTree in an inconsistent state. Calling BTrees.check.check() on a BTree object should verify its consistency. (See the NEWS section for 3.1 beta 1 below to for the old BTrees bugs.) Fixed a rare conflict resolution problem in the BTrees that could cause an segfault when the conflict resolution resulted in any empty bucket. Installation ------------ The distutils setup now installs several Python scripts. The runzeo.py and zdrun.py scripts mentioned above and several fsXXX.py scripts from the Tools directory. The test.py script does not run all the ZEO tests by default, because the ZEO tests take a long time to run. Use --all to run all the tests. Otherwise a subset of the tests, mostly using MappingStorage, are run. Storages -------- There are two new storages based on Sleepycat's BerkeleyDB in the BDBStorage package. Barry will have to write more here, because I don't know how different they are from the old bsddb3Storage storages. See Doc/BDBStorage.txt for more information. It now takes less time to open an existing FileStorage. The FileStorage uses a BTree-based index that is faster to pickle and unpickle. It also saves the index periodically so that subsequent opens will go fast even if the storage was not closed cleanly. Misc ---- The new ZConfig package, which will be used by Zope and ZODB, is included. ZConfig provides a configuration syntax, similar to Apache's syntax. The package can be used to configure the ZEO server and ZODB databases. See the module ZODB.config for functions to open the database from configuration. See ZConfig/doc for more info. The zLOG package now uses the logging package by Vinay Sajip, which will be included in Python 2.3. The Sync extension was removed from ExtensionClass, because it was not used by ZODB. What's new in ZODB3 3.1.4? ========================== Release date: 11-Sep-2003 A new feature to allow removal of connection pools for versions was ported from Zope 2.6. This feature is needed by Zope to avoid denial of service attacks that allow a client to create an arbitrary number of version pools. A pair of new scripts from Jim Fulton can be used to synthesize workloads and measure ZEO performance: see zodbload.py and zeoserverlog.py in the Tools directory. Note that these require Zope. Tools/checkbtrees.py was strengthened in two ways: - In addition to running the _check() method on each BTree B found, BTrees.check.check(B) is also run. The check() function was written after checkbtrees.py, and identifies kinds of damage B._check() cannot find. - Cycles in the object graph no longer lead to unbounded output. Note that preventing this requires remembering the oid of each persistent object found, which increases the memory needed by the script. What's new in ZODB3 3.1.3? ========================== Release date: 18-Aug-2003 Fixed several critical ZEO bugs. - If a storage server fails or times out between the vote and the finish, the ZEO cache could get populated with objects that didn't make it to the storage server. - If a client loses its connection to the server near the end of a transaction, it is now guaranteed to get a ClientDisconnected error even if it reconnects before the transaction finishes. This is necessary because the server will always abort the transaction. In some cases, the client would never see an error for the aborted transaction. - In tpc_finish(), reordered the calls so that the server's tpc_finish() is called (and must succeed) before we update the ZEO client cache. - The storage name is now prepended to the sort key, to ensure a unique global sort order if storages are named uniquely. This can prevent deadlock in some unusual cases. A variety of fixes and improvements to Berkeley storage (aka BDBStorage) were back-ported from ZODB 4. This release now contains the most current version of the Berkeley storage code. Many tests have been back-ported, but not all. Modified the Windows tests to wait longer at the end of ZEO tests for the server to shut down. Before Python 2.3, there is no waitpid() on Windows, and, thus, no way to know if the server has shut down. The change makes the Windows ZEO tests much less likely to fail or hang, at the cost of increasing the time needed to run the tests. Fixed a bug in ExtensionClass when comparing ExtensionClass instances. The code could raise RuntimeWarning under Python 2.3, and produce incorrect results on 64-bit platforms. Fixed bugs in Tools/repozo.py, including a timing-dependent one that could cause the following invocation of repozo to do a full backup when an incremental backup would have sufficed. Added Tools/README.txt that explains what each of the scripts in the Tools directory does. There were many small changes and improvements to the test suite. What's new in ZODB3 3.1.2 final? ================================ Fixed bug in FileStorage pack that caused it to fail if it encountered an old undo record (status "u"). Fixed several bugs in FileStorage pack that could cause OverflowErrors for storages > 2 GB. Fixed memory leak in TimeStamp.laterThan() that only occurred when it had to create a new TimeStamp. Fixed two BTree bugs that were fixed on the head a while ago: - bug in fsBTree that would cause byValue searches to end early. (fsBTrees are never used this way, but it was still a bug.) - bug that lead to segfault if BTree was mutated via deletion while it was being iterated over. What's new in ZODB3 3.1.2 beta 2? ================================= Fixed critical race conditions in ZEO's cache consistency code that could cause invalidations to be lost or stale data to be written to the cache. These bugs can lead to data loss or data corruption. These bugs are relatively unlikely to be provoked in sites with few conflicts, but the possibility of failure existed any time an object was loaded and stored concurrently. Fixed a bug in conflict resolution that failed to ghostify an object if it was involved in a conflict. (This code may be redundant, but it has been fixed regardless.) The ZEO server was fixed so that it does not perform any I/O until all of a transactions' invalidations are queued. If it performs I/O in the middle of sending invalidations, it would be possible to overlap a load from a client with the invalidation being sent to it. The ZEO cache now handles invalidations atomically. This is the same sort of bug that is described in the 3.1.2b1 section below, but it affects the ZEO cache. Fixed several serious bugs in fsrecover that caused it to fail catastrophically in certain cases because it thought it had found a checkpoint (status "c") record when it was in the middle of the file. What's new in ZODB3 3.1.2 beta 1? ================================= ZODB ---- Invalidations are now processed atomically. Each transaction will see all the changes caused by an earlier transaction or none of them. Before this patch, it was possible for a transaction to see invalid data because it saw only a subset of the invalidations. This is the most likely cause of reported BTrees corruption, where keys were stored in the wrong bucket. When a BTree bucket splits, the bucket and the bucket's parent are both modified. If a transaction sees the invalidation for the bucket but not the parent, the BTree in memory will be internally inconsistent and keys can be put in the wrong bucket. The atomic invalidation fix prevents this problem. A number of minor reference count fixes in the object cache were fixed. That's the cPickleCache.c file. It was possible for a transaction that failed in tpc_finish() to lose the traceback that caused the failure. The transaction code was fixed to report the original error as well as any errors that occur while trying to recover from the original error. ZEO --- A ZEO client will not read from its cache during cache verification. This fix was necessary to prevent the client from reading inconsistent data. The isReadOnly() method of a ZEO client was fixed to return the false when the client is connected to a read-only fallback server. The sync() method of ClientStorage and the pending() method of a zrpc connection now do both input and output. The short_repr() function used to generate log messages was fixed so that it does not blow up creating a repr of very long tuples. Storages -------- FileStorage has a new pack() implementation that fixes several reported problems that could lead to data loss. Two small bugs were fixed in DemoStorage. undoLog() did not handle its arguments correctly and pack() could accidentally delete objects created in versions. Fixed trivial bug in fsrecover that prevented it from working at all. FileStorage will use fsync() on Windows starting with Python 2.2.3. FileStorage's commit version was fixed. It used to stop after the first object, leaving all the other objects in the version. BTrees ------ Trying to store an object of a non-integer type into an IIBTree or OIBTree could leave the bucket in a variety of insane states. For example, trying b[obj] = "I'm a string, not an integer" where b is an OIBTree. This manifested as a refcount leak in the test suite, but could have been much worse (most likely in real life is that a seemingly arbitrary existing key would "go missing"). When deleting the first child of a BTree node with more than one child, a reference to the second child leaked. This could cause the entire bucket chain to leak (not be collected as garbage despite not being referenced anymore). Other minor BTree leak scenarios were also fixed. Other ----- Comparing a Missing.Value object to a C type that provide its own comparison operation could lead to a segfault when the Missing.Value was on the right-hand side of the comparison operator. The Missing class was fixed so that its coercion and comparison operations are safe. Tools ----- Four tools are now installed by setup.py: fsdump.py, fstest.py, repozo.py, and zeopack.py. What's new in ZODB3 3.1.1 final? ================================ Release date: 11-Feb-2003 Tools ----- Updated repozo.py tool What's new in ZODB3 3.1.1 beta 2? ================================= Release date: 03-Feb-2003 The Transaction "hosed" feature is disabled in this release. If a transaction fails during the tpc_finish() it is not possible, in general, to know whether the storage is in a consistent state. For example, a ZEO server may commit the data and then fail before sending confirmation of the commit to the client. If multiple storages are involved in a transaction, the problem is exacerbated: One storage may commit the data while another fails to commit. In previous versions of ZODB, the database would set a global "hosed" flag that prevented any other transaction from committing until an administrator could check the status of the various failed storages and ensure that the database is in a consistent state. This approach favors data consistency over availability. The new approach is to log a panic but continue. In practice, availability seems to be more important than consistency. The failure mode is exceedingly rare in either case. The BTrees-based fsIndex for FileStorage is enabled. This version of the index is faster to load and store via pickle and uses less memory to store keys. We had intended to enable this feature in an earlier release, but failed to actually do it; thus, it's getting enabled as a bug fix now. Two rare bugs were fixed in BTrees conflict resolution. The most probable symptom of the bug would have been a segfault. The bugs were found via synthetic stress tests rather than bug reports. A value-based consistency checker for BTrees was added. See the module BTrees.check for the checker and other utilities for working with BTrees. A new script called repozo.py was added. This script, originally written by Anthony Baxter, provides an incremental backup scheme for FileStorage based storages. zeopack.py has been fixed to use a read-only connection. Various small autopack-related race conditions have been fixed in the Berkeley storage implementations. There have been some table changes to the Berkeley storages so any storage you created in 3.1.1b1 may not work. Part of these changes was to add a storage version number to the schema so these types of incompatible changes can be avoided in the future. Removed the chance of bogus warnings in the FileStorage iterator. ZEO --- The ZEO version number was bumped to 2.0.2 on account of the following minor feature additions. The performance of full cache verification has improved dramatically. Measurements from Jim were somewhere in 2x-5x. The implementation was fixed to use the very-fast getSerial() method on the storage instead of the comparatively slow load(). The ZEO server has an optional timeout feature that will abort a connection that does not commit within a certain amount of time. The timeout works by closing the socket the client is using, causing both client and server to abort the transaction and continue. This is a drastic step, but can be useful to prevent a hung client or other bug from blocking a server indefinitely. If a client was disconnected during a transaction, the tpc_abort() call did not properly reset the internal state about the transaction. The bug caused the next transaction to fail in its tpc_finish(). Also, any ClientDisconnected exceptions raised during tpc_abort() are ignored. ZEO logging has been improved by adding more logging for important events, and changing the logging level for existing messages to a more appropriate level (usually lower). What's new in ZODB3 3.1.1 beta 1? ================================= Release date: 10-Dev-2002 It was possible for earlier versions of ZODB to deadlock when using multiple storages. If multiple transactions committed concurrently and both transactions involved two or more shared storages, deadlock was possible. This problem has been fixed by introducing a sortKey() method to the transaction and storage APIs that is used to define an ordering on transaction participants. This solution will prevent deadlocks provided that all transaction participants that use locks define a valid sortKey() method. A warning is raised if a participant does not define sortKey(). For backwards compatibility, BaseStorage provides a sortKey() that uses __name__. Added code to ThreadedAsync/LoopCallback.py to work around a bug in asyncore.py: a handled signal can cause unwanted reads to happen. A bug in FileStorage related to object uncreation was fixed. If an a transaction that created an object was undone, FileStorage could write a bogus data record header that could lead to strange errors if the object was loaded. An attempt to load an uncreated object now raises KeyError, as expected. The restore() implementation in FileStorage wrote incorrect backpointers for a few corner cases involving versions and undo. It also failed if the backpointer pointed to a record that was before the pack time. These specific bugs have been fixed and new test cases were added to cover them. A bug was fixed in conflict resolution that raised a NameError when a class involved in a conflict could not be loaded. The bug did not affect correctness, but prevent ZODB from caching the fact that the class was unloadable. A related bug prevented spurious AttributeErrors when a class could not be loaded. It was also fixed. The script Tools/zeopack.py was fixed to work with ZEO 2. It was untested and had two silly bugs. Some C extensions included standard header files before including Python.h, which is not allowed. They now include Python.h first, which eliminates compiler warnings in certain configurations. The BerkeleyDB based storages have been merged from the trunk, providing a much more robust version of the storages. They are not backwards compatible with the old storages, but the decision was made to update them in this micro release because the old storages did not work for all practical purposes. For details, see Doc/BDBStorage.txt. What's new in ZODB3 3.1 final? =============================== Release date: 28-Oct-2002 If an error occurs during conflict resolution, the store will silently catch the error, log it, and continue as if the conflict was unresolvable. ZODB used to behave this way, and the change to catch only ConflictError was causing problems in deployed systems. There are a lot of legitimate errors that should be caught, but it's too close to the final release to make the substantial changes needed to correct this. What's new in ZODB3 3.1 beta 3? =============================== Release date: 21-Oct-2002 A small extension was made to the iterator protocol. The Record objects, which are returned by the per-transaction iterators, contain a new `data_txn` attribute. It is None, unless the data contained in the record is a logical copy of an earlier transaction's data. For example, when transactional undo modifies an object, it creates a logical copy of the earlier transaction's data. Note that this provide a stronger statement about consistency than whether the data in two records is the same; it's possible for two different updates to an object to coincidentally have the same data. The restore() method was extended to take the data_txn attribute mentioned above as an argument. FileStorage uses the new argument to write a backpointer if possible. A few bugs were fixed. The setattr slot of the cPersistence C API was being initialized to NULL. The proper initialization was restored, preventing crashes in some applications with C extensions that used persistence. The return value of TimeStamp's __cmp__ method was clipped to return only 1, 0, -1. The restore() method was fixed to write a valid backpointer if the update being restored is in a version. Several bugs and improvements were made to zdaemon, which can be used to run the ZEO server. The parent now forwards signals to the child as intended. Pidfile handling was improved and the trailing newline was omitted. What's new in ZODB3 3.1 beta 2? =============================== Release date: 4-Oct-2002 A few bugs have been fixed, some that were found with the help of Neal Norwitz's PyChecker. The zeoup.py tool has been fixed to allow connecting to a read-only storage, when the --nowrite option is given. Casey Duncan fixed a few bugs in the recent changes to undoLog(). The fstest.py script no longer checks that each object modified in a transaction has a serial number that matches the transaction id. This invariant is no longer maintained; several new features in the 3.1 release depend on it. The ZopeUndo package was added. If ZODB3 is being used to run a ZEO server that will be used with Zope, it is usually best if the server and the Zope client don't share any software. The Zope undo framework, however, requires that a Prefix object be passed between client and server. To support this use, ZopeUndo was created to hold the Prefix object. Many bugs were fixed in ZEO, and a couple of features added. See `ZEO-NEWS.txt` for details. The ZODB guide included in the Doc directory has been updated. It is still incomplete, but most of the references to old ZODB packages have been removed. There is a new section that briefly explains how to use BTrees. The zeoup.py tool connects using a read-only connection when --nowrite is specifified. This feature is useful for checking on read-only ZEO servers. What's new in ZODB3 3.1 beta 1? =============================== Release date: 12-Sep-2002 We've changed the name and version number of the project, but it's still the same old ZODB. There have been a lot of changes since the last release. New ZODB cache -------------- Toby Dickenson implemented a new Connection cache for ZODB. The cache is responsible for pointer swizzling (translating between oids and Python objects) and for keeping recently used objects in memory. The new cache is a big improvement over the old cache. It strictly honors its size limit, where size is specified in number of objects, and it evicts objects in least recently used (LRU) order. Users should take care when setting the cache size, which has a default value of 400 objects. The old version of the cache often held many more objects than its specified size. An application may not perform as well with a small cache size, because the cache no longer exceeds the limit. Storages -------- The index used by FileStorage was reimplemented using a custom BTrees object. The index maps oids to file offsets, and is kept in memory at all times. The new index uses about 1/4 the memory of the old, dictionary-based index. See the module ZODB.fsIndex for details. A security flaw was corrected in transactionalUndo(). The transaction ids returned by undoLog() and used for transactionalUndo() contained a file offset. An attacker could construct a pickle with a bogus transaction record in its binary data, deduce the position of the pickle in the file from the undo log, then submit an undo with a bogus file position that caused the pickle to get written as a regular data record. The implementation was fixed so that file offsets are not included in the transaction ids. Several storages now have an explicit read-only mode. For example, passing the keyword argument read_only=1 to FileStorage will make it read-only. If a write operation is performed on a read-only storage, a ReadOnlyError will be raised. The storage API was extended with new methods that support the Zope Replication Service (ZRS), a proprietary Zope Corp product. We expect these methods to be generally useful. The methods are: - restore(oid, serialno, data, version, transaction) Perform a store without doing consistency checks. A client can use this method to provide a new current revision of an object. The ``serialno`` argument is the new serialno to use for the object, not the serialno of the previous revision. - lastTransaction() Returns the transaction id of the last committed transaction. - lastSerial(oid) Return the current serialno for ``oid`` or None. - iterator(start=None, stop=None) The iterator method isn't new, but the optional ``start`` and ``stop`` arguments are. These arguments can be used to specify the range of the iterator -- an inclusive range [start, stop]. FileStorage is now more cautious about creating a new file when it believes a file does not exist. This change is a workaround for bug in Python versions upto and including 2.1.3. If the interpreter was builtin without large file support but the platform had it, os.path.exists() would return false for large files. The fix is to try to open the file first, and decide whether to create a new file based on errno. The undoLog() and undoInfo() methods of FileStorage can run concurrently with other methods. The internal storage lock is released periodically to give other threads a chance to run. This should increase responsiveness of ZEO clients when used with ZEO 2. New serial numbers are assigned consistently for abortVersion() and commitVersion(). When a version is committed, the non-version data gets a new serial number. When a version is aborted, the serial number for non-version data does not change. This means that the abortVersion() transaction record has the unique property that its transaction id is not the serial number of the data records. Berkeley Storages ----------------- Berkeley storage constructors now take an optional `config` argument, which is an instance whose attributes can be used to configure such BerkeleyDB policies as an automatic checkpointing interval, lock table sizing, and the log directory. See bsddb3Storage/BerkeleyBase.py for details. A getSize() method has been added to all Berkeley storages. Berkeley storages open their environments with the DB_THREAD flag. Some performance optimizations have been implemented in Full storage, including the addition of a helper C extension when used with Python 2.2. More performance improvements will be added for the ZODB 3.1 final release. A new experimental Autopack storage was added which keeps only a certain amount of old revision information. The concepts in this storage will be folded into Full and Autopack will likely go away in ZODB 3.1 final. ZODB 3.1 final will also have much improved Minimal and Full storages, which eliminate Berkeley lock exhaustion problems, reduce memory use, and improve performance. It is recommended that you use BerkeleyDB 4.0.14 and PyBSDDB 3.4.0 with the Berkeley storages. See bsddb3Storage/README.txt for details. BTrees ------ BTrees no longer ignore exceptions raised when two keys are compared. Tim Peters fixed several endcase bugs in the BTrees code. Most importantly, after a mix of inserts and deletes in a BTree or TreeSet, it was possible (but unlikely) for the internal state of the object to become inconsistent. Symptoms then varied; most often this manifested as a mysterious failure to find a key that you knew was present, or that tree.keys() would yield an object that disgreed with the tree about how many keys there were. If you suspect such a problem, BTrees and TreeSets now support a ._check() method, which does a thorough job of examining the internal tree pointers for consistency. It raises AssertionError if it finds any problems, else returns None. If ._check() raises an exception, the object is damaged, and rebuilding the object is the best solution. All known ways for a BTree or TreeSet object to become internally inconsistent have been repaired. Other fixes include: - Many fixes for range search endcases, including the "range search bug:" If the smallest key S in a bucket in a BTree was deleted, doing a range search on the BTree with S on the high end could claim that the range was empty even when it wasn't. - Zope Collector #419: repaired off-by-1 errors and IndexErrors when slicing BTree-based data structures. For example, an_IIBTree.items()[0:0] had length 1 (should be empty) if the tree wasn't empty. - The BTree module functions weightedIntersection() and weightedUnion() now treat negative weights as documented. It's hard to explain what their effects were before this fix, as the sign bits were getting confused with an internal distinction between whether the result should be a set or a mapping. ZEO ---- For news about ZEO2, see the file ZEO-NEWS.txt. This version of ZODB ships with two different versions of ZEO. It includes ZEO 2.0 beta 1, the recommended new version. (ZEO 2 will reach final release before ZODB3.) The ZEO 2.0 protocol is not compatible with ZEO 1.0, so we have also included ZEO 1.0 to support people already using ZEO 1.0. Other features -------------- When a ConflictError is raised, the exception object now has a sensible structure, thanks to a patch from Greg Ward. The exception now uses the following standard attributes: oid, class_name, message, serials. See the ZODB.POSException.ConflictError doc string for details. It is now easier to customize the registration of persistent objects with a transaction. The low-level persistence mechanism in cPersistence.c registers with the object's jar instead of with the current transaction. The jar (Connection) then registers with the transaction. This redirection would allow specialized Connections to change the default policy on how the transaction manager is selected without hacking the Transaction module. Empty transactions can be committed without interacting with the storage. It is possible for registration to occur unintentionally and for a persistent object to compensate by making itself as unchanged. When this happens, it's possible to commit a transaction with no modified objects. The change allows such transactions to finish even on a read-only storage. Two new tools were added to the Tools directory. The ``analyze.py`` script, based on a tool by Matt Kromer, prints a summary of space usage in a FileStorage Data.fs. The ``checkbtrees.py`` script scans a FileStorage Data.fs. When it finds a BTrees object, it loads the object and calls the ``_check`` method. It prints warning messages for any corrupt BTrees objects found. Documentation ------------- The user's guide included with this release is still woefully out of date. Other bugs fixed ---------------- If an exception occurs inside an _p_deactivate() method, a traceback is printed on stderr. Previous versions of ZODB silently cleared the exception. ExtensionClass and ZODB now work correctly with a Python debug build. All C code has been fixed to use a consistent set of functions from the Python memory API. This allows ZODB to be used in conjunction with pymalloc, the default allocator in Python 2.3. zdaemon, which can be used to run a ZEO server, more clearly reports the exit status of its child processes. The ZEO server will reinitialize zLOG when it receives a SIGHUP. This allows log file rotation without restarting the server. What's new in StandaloneZODB 1.0 final? ======================================= Release date: 08-Feb-2002 All copyright notices have been updated to reflect the fact that the ZPL 2.0 covers this release. Added a cleanroom PersistentList.py implementation, which multiply inherits from UserDict and Persistent. Some improvements in setup.py and test.py for sites that don't have the Berkeley libraries installed. A new program, zeoup.py was added which simply verifies that a ZEO server is reachable. Also, a new program zeopack.py was added which connects to a ZEO server and packs it. What's new in StandaloneZODB 1.0 c1? ==================================== Release Date: 25-Jan-2002 This was the first public release of the StandaloneZODB from Zope Corporation. Everything's new! :) zope2.13-2.13.21/source/ZODB3/COPYING0000644000175000017500000000013312214017464015311 0ustar arnauarnauSee: - the copyright notice in: COPYRIGHT.txt - The Zope Public License in LICENSE.txt zope2.13-2.13.21/source/ZODB3/pip-egg-info/0000755000175000017500000000000012214017465016543 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/pip-egg-info/ZODB3.egg-info/0000755000175000017500000000000012214017465021056 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/pip-egg-info/ZODB3.egg-info/PKG-INFO0000644000175000017500000006077712214017465022174 0ustar arnauarnauMetadata-Version: 1.1 Name: ZODB3 Version: 3.10.5 Summary: Zope Object Database: object database and persistence Home-page: UNKNOWN Author: Zope Foundation and Contributors Author-email: zodb-dev@zope.org License: ZPL 2.1 Description: The Zope Object Database provides an object-oriented database for Python that provides a high-degree of transparency. Applications can take advantage of object database features with few, if any, changes to application logic. ZODB includes features such as a plugable storage interface, rich transaction support, and undo. .. contents:: ==== ZODB ==== Introduction ============ The ZODB package provides a set of tools for using the Zope Object Database (ZODB). The components you get with the ZODB release are as follows: - Core ZODB, including the persistence machinery - Standard storages such as FileStorage - The persistent BTrees modules - ZEO, for scalability needs - documentation (needs a lot more work) Our primary development platforms are Linux, Mac OS X, and Windows XP. The test suite should pass without error on all of these platforms, although it can take a long time on Windows -- longer if you use ZoneAlarm. Many particularly slow tests are skipped unless you pass --all as an argument to test.py. Compatibility ============= ZODB 3.10 requires Python 2.5 or later. Note -- When using ZEO and upgrading from Python 2.4, you need to upgrade clients and servers at the same time, or upgrade clients first and then servers. Clients running Python 2.5 or 2.6 will work with servers running Python 2.4. Clients running Python 2.4 won't work properly with servers running Python 2.5 or later due to changes in the way Python implements exceptions. ZODB ZEO clients from ZODB 3.2 on can talk to ZODB 3.10 servers. ZODB ZEO 3.10 Clients can talk to ZODB 3.8, 3.9, and 3.10 ZEO servers. Note -- ZEO 3.10 servers don't support undo for older clients. Prerequisites ============= You must have Python installed. If you're using a system Python install, make sure development support is installed too. You also need the transaction, zc.lockfile, ZConfig, zdaemon, zope.event, zope.interface, zope.proxy and zope.testing packages. If you don't have them and you can connect to the Python Package Index, then these will be installed for you if you don't have them. Installation ============ ZODB is released as a distutils package. The easiest ways to build and install it are to use `easy_install `_, or `zc.buildout `_. To install by hand, first install the dependencies, ZConfig, zdaemon, zope.interface, zope.proxy and zope.testing. These can be found in the `Python Package Index `_. To run the tests, use the test setup command:: python setup.py test It will download dependencies if needed. If this happens, ou may get an import error when the test command gets to looking for tests. Try running the test command a second time and you should see the tests run. :: python setup.py test To install, use the install command:: python setup.py install Testing for Developers ====================== The ZODB checkouts are `buildouts `_. When working from a ZODB checkout, first run the bootstrap.py script to initialize the buildout: % python bootstrap.py and then use the buildout script to build ZODB and gather the dependencies: % bin/buildout This creates a test script: % bin/test -v This command will run all the tests, printing a single dot for each test. When it finishes, it will print a test summary. The exact number of tests can vary depending on platform and available third-party libraries.:: Ran 1182 tests in 241.269s OK The test script has many more options. Use the ``-h`` or ``--help`` options to see a file list of options. The default test suite omits several tests that depend on third-party software or that take a long time to run. To run all the available tests use the ``--all`` option. Running all the tests takes much longer.:: Ran 1561 tests in 1461.557s OK Maintenance scripts ------------------- Several scripts are provided with the ZODB and can help for analyzing, debugging, checking for consistency, summarizing content, reporting space used by objects, doing backups, artificial load testing, etc. Look at the ZODB/script directory for more informations. History ======= The historical version numbering schemes for ZODB and ZEO are complicated. Starting with ZODB 3.4, the ZODB and ZEO version numbers are the same. In the ZODB 3.1 through 3.3 lines, the ZEO version number was "one smaller" than the ZODB version number; e.g., ZODB 3.2.7 included ZEO 2.2.7. ZODB and ZEO were distinct releases prior to ZODB 3.1, and had independent version numbers. Historically, ZODB was distributed as a part of the Zope application server. Jim Fulton's paper at the Python conference in 2000 described a version of ZODB he called ZODB 3, based on an earlier persistent object system called BoboPOS. The earliest versions of ZODB 3 were released with Zope 2.0. Andrew Kuchling extracted ZODB from Zope 2.4.1 and packaged it for use by standalone Python programs. He called this version "StandaloneZODB". Andrew's guide to using ZODB is included in the Doc directory. This version of ZODB was hosted at http://sf.net/projects/zodb. It supported Python 1.5.2, and might still be of interest to users of this very old Python version. Zope Corporation released a version of ZODB called "StandaloneZODB 1.0" in Feb. 2002. This release was based on Andrew's packaging, but built from the same CVS repository as Zope. It is roughly equivalent to the ZODB in Zope 2.5. Why not call the current release StandaloneZODB? The name StandaloneZODB is a bit of a mouthful. The standalone part of the name suggests that the Zope version is the real version and that this is an afterthought, which isn't the case. So we're calling this release "ZODB". We also worked on a ZODB4 package for a while and made a couple of alpha releases. We've now abandoned that effort, because we didn't have the resources to pursue ot while also maintaining ZODB(3). License ======= ZODB is distributed under the Zope Public License, an OSI-approved open source license. Please see the LICENSE.txt file for terms and conditions. The ZODB/ZEO Programming Guide included in the documentation is a modified version of Andrew Kuchling's original guide, provided under the terms of the GNU Free Documentation License. More information ================ We maintain a Wiki page about all things ZODB, including status on future directions for ZODB. Please see http://wiki.zope.org/ZODB/FrontPage and feel free to contribute your comments. There is a Mailman mailing list in place to discuss all issues related to ZODB. You can send questions to zodb-dev@zope.org or subscribe at http://lists.zope.org/mailman/listinfo/zodb-dev and view its archives at http://lists.zope.org/pipermail/zodb-dev Note that Zope Corp mailing lists have a subscriber-only posting policy. Andrew's ZODB Programmers Guide is made available in several forms, including DVI and HTML. To view it online, point your browser at the file Doc/guide/zodb/index.html Bugs and Patches ================ Bug reports and patches should be added to the Launchpad: https://launchpad.net/zodb .. Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 End: ================ Change History ================ 3.10.5 (2011-11-19) =================== Bugs Fixed ---------- - Conflict resolution failed when state included cross-database persistent references with classes that couldn't be imported. 3.10.4 (2011-11-17) =================== Bugs Fixed ---------- - Conflict resolution failed when state included persistent references with classes that couldn't be imported. 3.10.3 (2011-04-12) =================== Bugs Fixed ---------- - "activity monitor not updated for subconnections when connection returned to pool" https://bugs.launchpad.net/zodb/+bug/737198 - "Blob temp file get's removed before it should", https://bugs.launchpad.net/zodb/+bug/595378 A way this to happen is that a transaction is aborted after the commit process has started. I don't know how this would happen in the wild. In 3.10.3, the ZEO tpc_abort call to the server is changed to be synchronous, which should address this case. Maybe there's another case. Performance enhancements ------------------------ - Improved ZEO client cache implementation to make it less likely to evict objects that are being used. - Small (possibly negligable) reduction in CPU in ZEO storage servers to service object loads and in networking code. 3.10.2 (2011-02-12) =================== Bugs Fixed ---------- - 3.10 introduced an optimization to try to address BTree conflict errors arrising for basing BTree keys on object ids. The optimization caused object ids allocated in aborted transactions to be reused. Unfortunately, this optimzation led to some rather severe failures in some applications. The symptom is a conflict error in which one of the serials mentioned is zero. This optimization has been removed. See (for example): https://bugs.launchpad.net/zodb/+bug/665452 - ZEO server transaction timeouts weren't logged as critical. https://bugs.launchpad.net/zodb/+bug/670986 3.10.1 (2010-10-27) =================== Bugs Fixed ---------- - When a transaction rolled back a savepoint after adding objects and subsequently added more objects and committed, an error could be raised "ValueError: A different object already has the same oid" causing the transaction to fail. Worse, this could leave a database in a state where subsequent transactions in the same process would fail. https://bugs.launchpad.net/zodb/+bug/665452 - Unix domain sockets didn't work for ZEO (since the addition of IPv6 support). https://bugs.launchpad.net/zodb/+bug/663259 - Removed a missfeature that can cause performance problems when using an external garbage collector with ZEO. When objects were deleted from a storage, invalidations were sent to clients. This makes no sense. It's wildly unlikely that the other connections/clients have copies of the garbage. In normal storage garbage collection, we don't send invalidations. There's no reason to send them when an external garbage collector is used. - ZEO client cache simulation misshandled invalidations causing incorrect statistics and errors. 3.10.0 (2010-10-08) =================== New Features ------------ - There are a number of performance enhancements for ZEO storage servers. - FileStorage indexes use a new format. They are saved and loaded much faster and take less space. Old indexes can still be read, but new indexes won't be readable by older versions of ZODB. - The API for undoing multiple transactions has changed. To undo multiple transactions in a single transaction, pass a list of transaction identifiers to a database's undoMultiple method. Calling a database's undo method multiple times in the same transaction now raises an exception. - The ZEO protocol for undo has changed. The only user-visible consequence of this is that when ZODB 3.10 ZEO servers won't support undo for older clients. - The storage API (IStorage) has been tightened. Now, storages should raise a StorageTransactionError when invalid transactions are passed to tpc_begin, tpc_vote, or tpc_finish. - ZEO clients (``ClientStorage`` instances) now work in forked processes, including those created via ``multiprocessing.Process`` instances. - Broken objects now provide the IBroken interface. - As a convenience, you can now pass an integer port as an address to the ZEO ClientStorage constructor. - As a convenience, there's a new ``client`` function in the ZEO package for constructing a ClientStorage instance. It takes the same arguments as the ClientStorage constructor. - DemoStorages now accept constructor athuments, close_base_on_close and close_changes_on_close, to control whether underlying storages are closed when the DemoStorage is closed. https://bugs.launchpad.net/zodb/+bug/118512 - Removed the dependency on zope.proxy. - Removed support for the _p_independent mini framework, which was made moot by the introduction of multi-version concurrency control several years ago. - Added support for the transaction retry convenience (transaction-manager attempts method) introduced in the ``transaction`` 1.1.0 release. - Enhanced the database opening conveniences: - You can now pass storage keyword arguments to ZODB.DB and ZODB.connection. - You can now pass None (rather than a storage or file name) to get a database with a mapping storage. - Databases now warn when committing very large records (> 16MB). This is to try to warn people of likely design mistakes. There is a new option (large_record_size/large-record-size) to control the record size at which the warning is issued. - Added support for wrapper storages that transform pickle data. Applications for this include compression and encryption. An example wrapper storage implementation, ZODB.tests.hexstorage, was included for testing. It is important that storage implementations not assume that storages contain pickles. Renamed IStorageDB to IStorageWrapper and expanded it to provide methods for transforming and untransforming data records. Storages implementations should use these methods to get pickle data from stored records. - Deprecated ZODB.interfaces.StorageStopIteration. Storage iterator implementations should just raise StopIteration, which means they can now be implemented as generators. - The filestorage packer configuration option noe accepts values of the form ``modname:expression``, allowing the use of packer factories with options. - Added a new API that allows applications to make sure that current data are read. For example, with:: self._p_jar.readCurrent(ob) A conflict error will be raised if the version of ob read by the transaction isn't current when the transaction is committed. Normally, ZODB only assures that objects read are consistent, but not necessarily up to date. Checking whether an object is up to date is important when information read from one object is used to update another. BTrees are an important case of reading one object to update another. Internal nodes are read to decide which leave notes are updated when a BTree is updated. BTrees now use this new API to make sure that internal nodes are up to date on updates. - When transactions are aborted, new object ids allocated during the transaction are saved and used in subsequent transactions. This can help in situations where object ids are used as BTree keys and the sequential allocation of object ids leads to conflict errors. - ZEO servers now support a server_status method for for getting information on the number of clients, lock requests and general statistics. - ZEO clients now support a client_label constructor argument and client-label configuration-file option to specify a label for a client in server logs. This makes it easier to identify specific clients corresponding to server log entries, especially when there are multiple clients originating from the same machine. - Improved ZEO server commit lock logging. Now, locking activity is logged at the debug level until the number of waiting lock requests gets above 3. Log at the critical level when the number of waiting lock requests gets above 9. - The file-storage backup script, repozo, will now create a backup index file if an output file name is given via the --output/-o option. - Added a '--kill-old-on-full' argument to the repozo backup options: if passed, remove any older full or incremental backup files from the repository after doing a full backup. (https://bugs.launchpad.net/zope2/+bug/143158) - The mkzeoinst script has been moved to a separate project: http://pypi.python.org/pypi/zope.mkzeoinstance and is no-longer included with ZODB. - Removed untested unsupported dbmstorage fossile. - ZEO servers no longer log their pids in every log message. It's just not interesting. :) Bugs fixed ---------- - When a pool timeout was specified for a database and old connections were removed due to timing out, an error occured due to a bug in the connection cleanup logic. - When multi-database connections were no longer used and cleaned up, their subconnections weren't cleaned up properly. - ZEO didn't work with IPv6 addrsses. Added IPv6 support contributed by Martin v. |Lowis|. - A file storage bug could cause ZEO clients to have incorrect information about current object revisions after reconnecting to a database server. - Updated the 'repozo --kill-old-on-full' option to remove any '.index' files corresponding to backups being removed. - ZEO extension methods failed when a client reconnected to a storage. (https://bugs.launchpad.net/zodb/+bug/143344) - Clarified the return Value for lastTransaction in the case when there aren't any transactions. Now a string of 8 nulls (aka "z64") is specified. - Setting _p_changed on a blob wo actually writing anything caused an error. (https://bugs.launchpad.net/zodb/+bug/440234) - The verbose mode of the fstest was broken. (https://bugs.launchpad.net/zodb/+bug/475996) - Object ids created in a savepoint that is rolled back wren't being reused. (https://bugs.launchpad.net/zodb/+bug/588389) - Database connections didn't invalidate cache entries when conflict errors were raised in response to checkCurrentSerialInTransaction errors. Normally, this shouldn't be a problem, since there should be pending invalidations for these oids which will cause the object to be invalidated. There have been issues with ZEO persistent cache management that have caused out of date data to remain in the cache. (It's possible that the last of these were addressed in the 3.10.0b5.) Invalidating read data when there is a conflict error provides some extra insurance. - The interface, ZODB.interfaces.IStorage was incorrect. The store method should never return a sequence of oid and serial pairs. - When a demo storage push method was used to create a new demo storage and the new storage was closed, the original was (incorrectly) closed. - There were numerous bugs in the ZEO cache tracing and analysis code. Cache simulation, while not perfect, seems to be much more accurate now than it was before. The ZEO cache trace statistics and simulation scripts have been given more descriptive names and moved to the ZEO scripts package. - BTree sets and tree sets didn't correctly check values passed to update or to constructors, causing Python to exit under certain circumstances. - Fixed bug in copying a BTrees.Length instance. (https://bugs.launchpad.net/zodb/+bug/516653) - Fixed a serious bug that caused cache failures when run with Python optimization turned on. https://bugs.launchpad.net/zodb/+bug/544305 - When using using a ClientStorage in a Storage server, there was a threading bug that caused clients to get disconnected. - On Mac OS X, clients that connected and disconnected quickly could cause a ZEO server to stop accepting connections, due to a failure to catch errors in the initial part of the connection process. The failure to properly handle exceptions while accepting connections is potentially problematic on other platforms. Fixes: https://bugs.launchpad.net/zodb/+bug/135108 - Object state management wasn't done correctly when classes implemented custom _p_deavtivate methods. (https://bugs.launchpad.net/zodb/+bug/185066) .. |Lowis| unicode:: L \xf6 wis Platform: any Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Topic :: Database Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: Unix Classifier: Framework :: ZODB zope2.13-2.13.21/source/ZODB3/pip-egg-info/ZODB3.egg-info/dependency_links.txt0000644000175000017500000000000112214017465025124 0ustar arnauarnau zope2.13-2.13.21/source/ZODB3/pip-egg-info/ZODB3.egg-info/requires.txt0000644000175000017500000000014512214017465023456 0ustar arnauarnautransaction >=1.1.0 zc.lockfile ZConfig zdaemon zope.event zope.interface [test] zope.testing manuelzope2.13-2.13.21/source/ZODB3/pip-egg-info/ZODB3.egg-info/entry_points.txt0000644000175000017500000000056712214017465024364 0ustar arnauarnau [console_scripts] fsdump = ZODB.FileStorage.fsdump:main fsoids = ZODB.scripts.fsoids:main fsrefs = ZODB.scripts.fsrefs:main fstail = ZODB.scripts.fstail:Main repozo = ZODB.scripts.repozo:main zeopack = ZEO.scripts.zeopack:main runzeo = ZEO.runzeo:main zeopasswd = ZEO.zeopasswd:main zeoctl = ZEO.zeoctl:main zope2.13-2.13.21/source/ZODB3/pip-egg-info/ZODB3.egg-info/top_level.txt0000644000175000017500000000003312214017465023604 0ustar arnauarnauZODB BTrees persistent ZEO zope2.13-2.13.21/source/ZODB3/pip-egg-info/ZODB3.egg-info/SOURCES.txt0000644000175000017500000001467512214017465022757 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/ZODB3.egg-info/PKG-INFO pip-egg-info/ZODB3.egg-info/SOURCES.txt pip-egg-info/ZODB3.egg-info/dependency_links.txt pip-egg-info/ZODB3.egg-info/entry_points.txt pip-egg-info/ZODB3.egg-info/not-zip-safe pip-egg-info/ZODB3.egg-info/requires.txt pip-egg-info/ZODB3.egg-info/top_level.txt src/BTrees/IFBTree.py src/BTrees/IIBTree.py src/BTrees/IOBTree.py src/BTrees/Interfaces.py src/BTrees/LFBTree.py src/BTrees/LLBTree.py src/BTrees/LOBTree.py src/BTrees/Length.py src/BTrees/OIBTree.py src/BTrees/OLBTree.py src/BTrees/OOBTree.py src/BTrees/_IFBTree.c src/BTrees/_IIBTree.c src/BTrees/_IOBTree.c src/BTrees/_LFBTree.c src/BTrees/_LLBTree.c src/BTrees/_LOBTree.c src/BTrees/_OIBTree.c src/BTrees/_OLBTree.c src/BTrees/_OOBTree.c src/BTrees/__init__.py src/BTrees/_fsBTree.c src/BTrees/check.py src/BTrees/fsBTree.py src/BTrees/tests/__init__.py src/BTrees/tests/testBTrees.py src/BTrees/tests/testBTreesUnicode.py src/BTrees/tests/testConflict.py src/BTrees/tests/testLength.py src/BTrees/tests/testSetOps.py src/BTrees/tests/test_btreesubclass.py src/BTrees/tests/test_check.py src/BTrees/tests/test_compare.py src/BTrees/tests/test_fsBTree.py src/ZEO/ClientStorage.py src/ZEO/Exceptions.py src/ZEO/ServerStub.py src/ZEO/StorageServer.py src/ZEO/TransactionBuffer.py src/ZEO/__init__.py src/ZEO/cache.py src/ZEO/hash.py src/ZEO/interfaces.py src/ZEO/monitor.py src/ZEO/runzeo.py src/ZEO/util.py src/ZEO/zeoctl.py src/ZEO/zeopasswd.py src/ZEO/auth/__init__.py src/ZEO/auth/auth_digest.py src/ZEO/auth/base.py src/ZEO/auth/hmac.py src/ZEO/scripts/__init__.py src/ZEO/scripts/cache_simul.py src/ZEO/scripts/cache_stats.py src/ZEO/scripts/parsezeolog.py src/ZEO/scripts/tests.py src/ZEO/scripts/timeout.py src/ZEO/scripts/zeopack.py src/ZEO/scripts/zeoqueue.py src/ZEO/scripts/zeoreplay.py src/ZEO/scripts/zeoserverlog.py src/ZEO/scripts/zeoup.py src/ZEO/tests/Cache.py src/ZEO/tests/CommitLockTests.py src/ZEO/tests/ConnectionTests.py src/ZEO/tests/InvalidationTests.py src/ZEO/tests/IterationTests.py src/ZEO/tests/TestThread.py src/ZEO/tests/ThreadTests.py src/ZEO/tests/__init__.py src/ZEO/tests/auth_plaintext.py src/ZEO/tests/forker.py src/ZEO/tests/servertesting.py src/ZEO/tests/speed.py src/ZEO/tests/stress.py src/ZEO/tests/testAuth.py src/ZEO/tests/testConnection.py src/ZEO/tests/testConversionSupport.py src/ZEO/tests/testMonitor.py src/ZEO/tests/testTransactionBuffer.py src/ZEO/tests/testZEO.py src/ZEO/tests/testZEO2.py src/ZEO/tests/testZEOOptions.py src/ZEO/tests/test_cache.py src/ZEO/tests/zeoserver.py src/ZEO/zrpc/__init__.py src/ZEO/zrpc/_hmac.py src/ZEO/zrpc/client.py src/ZEO/zrpc/connection.py src/ZEO/zrpc/error.py src/ZEO/zrpc/log.py src/ZEO/zrpc/marshal.py src/ZEO/zrpc/server.py src/ZEO/zrpc/smac.py src/ZEO/zrpc/trigger.py src/ZODB/ActivityMonitor.py src/ZODB/BaseStorage.py src/ZODB/ConflictResolution.py src/ZODB/Connection.py src/ZODB/DB.py src/ZODB/DemoStorage.py src/ZODB/ExportImport.py src/ZODB/MappingStorage.py src/ZODB/POSException.py src/ZODB/UndoLogCompatible.py src/ZODB/__init__.py src/ZODB/blob.py src/ZODB/broken.py src/ZODB/config.py src/ZODB/conversionhack.py src/ZODB/fsIndex.py src/ZODB/fsrecover.py src/ZODB/fstools.py src/ZODB/interfaces.py src/ZODB/loglevels.py src/ZODB/persistentclass.py src/ZODB/serialize.py src/ZODB/transact.py src/ZODB/utils.py src/ZODB/FileStorage/FileStorage.py src/ZODB/FileStorage/__init__.py src/ZODB/FileStorage/format.py src/ZODB/FileStorage/fsdump.py src/ZODB/FileStorage/fsoids.py src/ZODB/FileStorage/fspack.py src/ZODB/FileStorage/interfaces.py src/ZODB/FileStorage/tests.py src/ZODB/scripts/__init__.py src/ZODB/scripts/analyze.py src/ZODB/scripts/checkbtrees.py src/ZODB/scripts/fsoids.py src/ZODB/scripts/fsrefs.py src/ZODB/scripts/fsstats.py src/ZODB/scripts/fstail.py src/ZODB/scripts/fstest.py src/ZODB/scripts/migrate.py src/ZODB/scripts/migrateblobs.py src/ZODB/scripts/netspace.py src/ZODB/scripts/referrers.py src/ZODB/scripts/repozo.py src/ZODB/scripts/space.py src/ZODB/scripts/zodbload.py src/ZODB/scripts/tests/__init__.py src/ZODB/scripts/tests/test_doc.py src/ZODB/scripts/tests/test_fstest.py src/ZODB/scripts/tests/test_repozo.py src/ZODB/tests/BasicStorage.py src/ZODB/tests/ConflictResolution.py src/ZODB/tests/Corruption.py src/ZODB/tests/HistoryStorage.py src/ZODB/tests/IteratorStorage.py src/ZODB/tests/MTStorage.py src/ZODB/tests/MVCCMappingStorage.py src/ZODB/tests/MinPO.py src/ZODB/tests/PackableStorage.py src/ZODB/tests/PersistentStorage.py src/ZODB/tests/ReadOnlyStorage.py src/ZODB/tests/RecoveryStorage.py src/ZODB/tests/RevisionStorage.py src/ZODB/tests/StorageTestBase.py src/ZODB/tests/Synchronization.py src/ZODB/tests/TransactionalUndoStorage.py src/ZODB/tests/__init__.py src/ZODB/tests/dangle.py src/ZODB/tests/hexstorage.py src/ZODB/tests/loggingsupport.py src/ZODB/tests/sampledm.py src/ZODB/tests/speed.py src/ZODB/tests/testActivityMonitor.py src/ZODB/tests/testBroken.py src/ZODB/tests/testCache.py src/ZODB/tests/testConfig.py src/ZODB/tests/testConnection.py src/ZODB/tests/testConnectionSavepoint.py src/ZODB/tests/testDB.py src/ZODB/tests/testDemoStorage.py src/ZODB/tests/testFileStorage.py src/ZODB/tests/testMVCCMappingStorage.py src/ZODB/tests/testMappingStorage.py src/ZODB/tests/testPersistentList.py src/ZODB/tests/testPersistentMapping.py src/ZODB/tests/testRecover.py src/ZODB/tests/testSerialize.py src/ZODB/tests/testTimeStamp.py src/ZODB/tests/testUtils.py src/ZODB/tests/testZODB.py src/ZODB/tests/test_cache.py src/ZODB/tests/test_datamanageradapter.py src/ZODB/tests/test_doctest_files.py src/ZODB/tests/test_fsdump.py src/ZODB/tests/test_storage.py src/ZODB/tests/testblob.py src/ZODB/tests/testconflictresolution.py src/ZODB/tests/testcrossdatabasereferences.py src/ZODB/tests/testfsIndex.py src/ZODB/tests/testfsoids.py src/ZODB/tests/testhistoricalconnections.py src/ZODB/tests/testmvcc.py src/ZODB/tests/testpersistentclass.py src/ZODB/tests/util.py src/ZODB/tests/warnhook.py src/persistent/TimeStamp.c src/persistent/__init__.py src/persistent/cPersistence.c src/persistent/cPickleCache.c src/persistent/dict.py src/persistent/interfaces.py src/persistent/list.py src/persistent/mapping.py src/persistent/ring.c src/persistent/wref.py src/persistent/tests/__init__.py src/persistent/tests/testPersistent.py src/persistent/tests/test_PickleCache.py src/persistent/tests/test_list.py src/persistent/tests/test_mapping.py src/persistent/tests/test_overriding_attrs.py src/persistent/tests/test_persistent.py src/persistent/tests/test_pickle.py src/persistent/tests/test_wref.py src/persistent/tests/utils.pyzope2.13-2.13.21/source/ZODB3/pip-egg-info/ZODB3.egg-info/not-zip-safe0000644000175000017500000000000112214017465023304 0ustar arnauarnau zope2.13-2.13.21/source/ZODB3/LICENSE.txt0000644000175000017500000000402612214017464016106 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/ZODB3/README.txt0000644000175000017500000001565012214017464015766 0ustar arnauarnau==== ZODB ==== Introduction ============ The ZODB package provides a set of tools for using the Zope Object Database (ZODB). The components you get with the ZODB release are as follows: - Core ZODB, including the persistence machinery - Standard storages such as FileStorage - The persistent BTrees modules - ZEO, for scalability needs - documentation (needs a lot more work) Our primary development platforms are Linux, Mac OS X, and Windows XP. The test suite should pass without error on all of these platforms, although it can take a long time on Windows -- longer if you use ZoneAlarm. Many particularly slow tests are skipped unless you pass --all as an argument to test.py. Compatibility ============= ZODB 3.10 requires Python 2.5 or later. Note -- When using ZEO and upgrading from Python 2.4, you need to upgrade clients and servers at the same time, or upgrade clients first and then servers. Clients running Python 2.5 or 2.6 will work with servers running Python 2.4. Clients running Python 2.4 won't work properly with servers running Python 2.5 or later due to changes in the way Python implements exceptions. ZODB ZEO clients from ZODB 3.2 on can talk to ZODB 3.10 servers. ZODB ZEO 3.10 Clients can talk to ZODB 3.8, 3.9, and 3.10 ZEO servers. Note -- ZEO 3.10 servers don't support undo for older clients. Prerequisites ============= You must have Python installed. If you're using a system Python install, make sure development support is installed too. You also need the transaction, zc.lockfile, ZConfig, zdaemon, zope.event, zope.interface, zope.proxy and zope.testing packages. If you don't have them and you can connect to the Python Package Index, then these will be installed for you if you don't have them. Installation ============ ZODB is released as a distutils package. The easiest ways to build and install it are to use `easy_install `_, or `zc.buildout `_. To install by hand, first install the dependencies, ZConfig, zdaemon, zope.interface, zope.proxy and zope.testing. These can be found in the `Python Package Index `_. To run the tests, use the test setup command:: python setup.py test It will download dependencies if needed. If this happens, ou may get an import error when the test command gets to looking for tests. Try running the test command a second time and you should see the tests run. :: python setup.py test To install, use the install command:: python setup.py install Testing for Developers ====================== The ZODB checkouts are `buildouts `_. When working from a ZODB checkout, first run the bootstrap.py script to initialize the buildout: % python bootstrap.py and then use the buildout script to build ZODB and gather the dependencies: % bin/buildout This creates a test script: % bin/test -v This command will run all the tests, printing a single dot for each test. When it finishes, it will print a test summary. The exact number of tests can vary depending on platform and available third-party libraries.:: Ran 1182 tests in 241.269s OK The test script has many more options. Use the ``-h`` or ``--help`` options to see a file list of options. The default test suite omits several tests that depend on third-party software or that take a long time to run. To run all the available tests use the ``--all`` option. Running all the tests takes much longer.:: Ran 1561 tests in 1461.557s OK Maintenance scripts ------------------- Several scripts are provided with the ZODB and can help for analyzing, debugging, checking for consistency, summarizing content, reporting space used by objects, doing backups, artificial load testing, etc. Look at the ZODB/script directory for more informations. History ======= The historical version numbering schemes for ZODB and ZEO are complicated. Starting with ZODB 3.4, the ZODB and ZEO version numbers are the same. In the ZODB 3.1 through 3.3 lines, the ZEO version number was "one smaller" than the ZODB version number; e.g., ZODB 3.2.7 included ZEO 2.2.7. ZODB and ZEO were distinct releases prior to ZODB 3.1, and had independent version numbers. Historically, ZODB was distributed as a part of the Zope application server. Jim Fulton's paper at the Python conference in 2000 described a version of ZODB he called ZODB 3, based on an earlier persistent object system called BoboPOS. The earliest versions of ZODB 3 were released with Zope 2.0. Andrew Kuchling extracted ZODB from Zope 2.4.1 and packaged it for use by standalone Python programs. He called this version "StandaloneZODB". Andrew's guide to using ZODB is included in the Doc directory. This version of ZODB was hosted at http://sf.net/projects/zodb. It supported Python 1.5.2, and might still be of interest to users of this very old Python version. Zope Corporation released a version of ZODB called "StandaloneZODB 1.0" in Feb. 2002. This release was based on Andrew's packaging, but built from the same CVS repository as Zope. It is roughly equivalent to the ZODB in Zope 2.5. Why not call the current release StandaloneZODB? The name StandaloneZODB is a bit of a mouthful. The standalone part of the name suggests that the Zope version is the real version and that this is an afterthought, which isn't the case. So we're calling this release "ZODB". We also worked on a ZODB4 package for a while and made a couple of alpha releases. We've now abandoned that effort, because we didn't have the resources to pursue ot while also maintaining ZODB(3). License ======= ZODB is distributed under the Zope Public License, an OSI-approved open source license. Please see the LICENSE.txt file for terms and conditions. The ZODB/ZEO Programming Guide included in the documentation is a modified version of Andrew Kuchling's original guide, provided under the terms of the GNU Free Documentation License. More information ================ We maintain a Wiki page about all things ZODB, including status on future directions for ZODB. Please see http://wiki.zope.org/ZODB/FrontPage and feel free to contribute your comments. There is a Mailman mailing list in place to discuss all issues related to ZODB. You can send questions to zodb-dev@zope.org or subscribe at http://lists.zope.org/mailman/listinfo/zodb-dev and view its archives at http://lists.zope.org/pipermail/zodb-dev Note that Zope Corp mailing lists have a subscriber-only posting policy. Andrew's ZODB Programmers Guide is made available in several forms, including DVI and HTML. To view it online, point your browser at the file Doc/guide/zodb/index.html Bugs and Patches ================ Bug reports and patches should be added to the Launchpad: https://launchpad.net/zodb .. Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 End: zope2.13-2.13.21/source/ZODB3/setup.cfg0000644000175000017500000000007312214017464016102 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/ZODB3/log.ini0000644000175000017500000000111212214017464015536 0ustar arnauarnau# This file configures the logging module for the test harness: # critical errors are logged to testing.log; everything else is # ignored. # Documentation for the file format is at # http://www.red-dove.com/python_logging.html#config [logger_root] level=CRITICAL handlers=normal [handler_normal] class=FileHandler level=NOTSET formatter=common args=('testing.log', 'a') filename=testing.log mode=a [formatter_common] format=------ %(asctime)s %(levelname)s %(name)s %(message)s datefmt=%Y-%m-%dT%H:%M:%S [loggers] keys=root [handlers] keys=normal [formatters] keys=common zope2.13-2.13.21/source/ZODB3/doc/0000755000175000017500000000000012214017464015026 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/doc/zeo-client-cache.txt0000644000175000017500000000360212214017464020702 0ustar arnauarnauZEO Client Cache The client cache provides a disk based cache for each ZEO client. The client cache allows reads to be done from local disk rather than by remote access to the storage server. The cache may be persistent or transient. If the cache is persistent, then the cache file is retained for use after process restarts. A non- persistent cache uses a temporary file. The client cache is managed in a single file, of the specified size. The life of the cache is as follows: - The cache file is opened (if it already exists), or created and set to the specified size. - Cache records are written to the cache file, as transactions commit locally, and as data are loaded from the server. - Writes are to "the current file position". This is a pointer that travels around the file, circularly. After a record is written, the pointer advances to just beyond it. Objects starting at the current file position are evicted, as needed, to make room for the next record written. A distinct index file is not created, although indexing structures are maintained in memory while a ClientStorage is running. When a persistent client cache file is reopened, these indexing structures are recreated by analyzing the file contents. Persistent cache files are created in the directory named in the ``var`` argument to the ClientStorage, or if ``var`` is None, in the current working directory. Persistent cache files have names of the form:: client-storage.zec where: client -- the client name, as given by the ClientStorage's ``client`` argument storage -- the storage name, as given by the ClientStorage's ``storage`` argument; this is typically a string denoting a small integer, "1" by default For example, the cache file for client '8881' and storage 'spam' is named "8881-spam.zec". zope2.13-2.13.21/source/ZODB3/doc/zodb-guide.txt0000644000175000017500000000017412214017464017622 0ustar arnauarnauThe ZODB/ZEO Programming Guide has been moved into it's own package (zodbguide) and published at http://docs.zope.org/zodb. zope2.13-2.13.21/source/ZODB3/doc/storage.pdf0000644000175000017500000006136212214017464017175 0ustar arnauarnau%PDF-1.4 3 0 obj << /Length 1933 /Filter /FlateDecode >> stream xÚÍXYÛÈ~Ÿ_¡G 9}ñÊ›íµA¼›d,ö"[Rg(RËóÚ_Ÿª®"E? ì£ú«£¿®®–\ ø#W©‚¿bU¼yü¤]Fy#†ÔY”JЦKfäo¶¶EÏËd$­ÝÒDÚ$¸.422©¯Šb-Iy{äE@w¨Epd×6¬\­(.€'‘ÊÕÄ—±AˆXJüijƒkWa""­õ*”i”(ãåÿRl½‚“^ø¼NE`Ë}ÓÖíîDcl3ØÎ£H ®§Â!Xjû±sMÉ0«WÜUÔ:k2DGz†H<ħÎV¶såK¿Y+ŒàvìØƒ¿¯¥ŽƒwìŽ2B"ÌÃÇçy¯MG‹$±' Å/bãEÎÄ 'Ùp!û䀉ášRP}d"¹0ä¥XêM;æ³taÌŸUÁ5ò}ŸÏrw}æiT£Ùg¼ç¢÷Pä]:ip ‡ 3á\³-‚ÚÜ‹/,» Â$õ¦asÖÅ÷‚å…†ù»A8ËÝ O£ÃAøk;L—ÃïA_ÄׄðWF¥á›L£”†BëÒÌ;a©7­œ#²0õ^D¤`ËïFd!w/"Ó4©¡ˆüà 6sTÂÎwáÍù€`Ü=" â ø¼4ðN,XêMû¦X\y'Ie*ù_Y ”©’>_ó¨EÆQ¢õ¿½siÅü¦‚zH'*[^Y¯¬L,o¬Ë‹Fø‚BÓ“äòvƒl/Ë\èÇ#¼¯êø VÇ ”¼Í.ìÆ¦¡2¦®*Ù˜Žõ$`h‰oÿuKu³€5ÙCÑ ®ì±VÍ úË=--ظbh®tÉ»5U†çwV/¤¢vW£(Tˆ¾P––ô*ƒ\ Ç‘³öÅ@-(G€*ØÙÖTÌ÷4æ±?*5Á¦è]É‹¾³®þØÙ¢¢iЍGêh-†æ *umÅ Ú-ã9Ÿ êuï0dÁ‰{Çóƒkà<õüœòÌÉU–^üüª½Ú}zµ¥Ö¡¨jÇÿ :¥0ì+Vøô¡H|Ož ¦1ßqÍ„¹éÝ0Á rÌühe’œ¦Y€c ^1öv;Ö—-TÙ5«öP¸Éübv£®£‹Qæÿºú!»dê_BŸ­Ó‡±hxcé[Âb‡Îã´¨À F&~Œø1bÒyqÏ yqâ0Évö7ðxtÝdÀÌ0[>Ypö’G(QÐ’pZ¶htKà”ŠŒÝTðÖlZßH)Ú0ðÍõnSsg„ÓXÓ<¼Üá‚"À =’°qœ†7«ÂÚÌÿ:9ètteQ×'’èý­3Kxì§³VMâ];îö$D*´Èÿ^#ä"¤¶ìœÚ þÈ”€Û> nÊó“ÿ5…g¯2Å7ë:º“(¡`H )‹¹ÏâàcßswŠÒІÏà·r‹6*(qŽÎ?“¡‹'¿}Îl32(ü®Ã(2x€>|¨çg _۽дc¬#]‰mwð„Dôæ|AhÇ}ðÙÇ“?ï-¯"³A‹û»ŒW,Õyóý¼8ÁðN@a¿¤Z,ƒ8TÔÞvíäæÅdíŽ;gS}À0rsZ¼©Æé¾wÜ3yÌÏt/mß/mÂ+€Q;Û·cw®.þâ5*endstream endobj 2 0 obj << /Type /Page /Contents 3 0 R /Resources 1 0 R /MediaBox [0 0 612 792] /Parent 22 0 R /Annots [ 11 0 R 14 0 R 15 0 R 16 0 R 17 0 R 18 0 R 19 0 R ] >> endobj 11 0 obj << /Type /Annot /Border [0 0 0] /Rect [455.098 504.184 540 512.662] /S /URI /URI (http://www.zope.com/) >> endobj 14 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 382.05 126.236 390.941] /Subtype /Link /A << /S /GoTo /D (page001) >> >> endobj 15 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 372.007 144.169 378.916] /Subtype /Link /A << /S /GoTo /D (page001) >> >> endobj 16 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 348.177 160.806 357.068] /Subtype /Link /A << /S /GoTo /D (page002) >> >> endobj 17 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 326.259 240.805 335.15] /Subtype /Link /A << /S /GoTo /D (page005) >> >> endobj 18 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 304.341 223.092 313.233] /Subtype /Link /A << /S /GoTo /D (page005) >> >> endobj 19 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 282.423 211.804 291.315] /Subtype /Link /A << /S /GoTo /D (page005) >> >> endobj 4 0 obj << /D [2 0 R /XYZ 72 744.907 null] >> endobj 1 0 obj << /Font << /F29 6 0 R /F30 8 0 R /F32 10 0 R /F34 13 0 R /F38 21 0 R >> /ProcSet [ /PDF /Text ] >> endobj 27 0 obj << /Length 3152 /Filter /FlateDecode >> stream xÚ¥ZKܸ¾Ï¯èÛª·VÔ[»‡`½Îöa“Àƒˆã[âL3VK²DyÒÿ>U¬¢-y×Èƈ¤ª‹ÅbÕWùõãÃ÷¿Dá¡ð‹4:<>²ð‰À¢ìðX}ðtcT3èc˜x_Ž"ñÔñ©'› ™gÚ–V®mÓÖÚ\téOqVxo ½ÐQ6-/ŒMÙ^œfOmOïÕÉ^åˆzUì¢iy‘ͳÜž´xfÚ«¬Ôú¤Ç‹:Úð;9õÌE êøññÝ!8œ„ð‹$±G÷ÈBïE×5e…€‘åO»/<˶Óëw‘gŽÂ»(w}[%,mO KƒêA©§zšë¦Rx6¦¾ÓÄý¥Yæ=^4ËÒ«Ï£îd²¡'jHm#Ï5Ë#¯íØ·O{'lÛ¾ÒDy^Oax¨}|^ÇÚè9ál0dzÖÍ3Ï­ØpûýàO|3? cË÷_}óSPrU ÊŠScÏ€#ÐÈyuË};>_hR·xüçS?6 mˆ½lY¢ J𠝔u Ú†ÝÁ`óÙ`ÃBøB Eùš 0þiÆE¾R&=G©ð~v׎û’)Y Z>=&SÂI‰w€ƒ3ÙÑ) c?+;µì™U$ˆ<‘UzN“Ö(pýßAÀ}íEÕ h…ö„×e b° f)Gs±W”ƒBhbÅfÚ)3ÿ”Žd÷•†F=lDª4ÃDìxQâaàü›Nµd;0~Aý·ý'¼Ë­ ¶£´u×(³"EQN[àÂJh|š&܈2{Î59KÊIÀ—þyQ ­3AŽÖ¸¬ïÀ\öî—í&xÁu‹ °.;0T‚$©^¡–!xímŠ¨Ï¼Í QÓO¶§¾ÊO“/¯+¾èAŸËY#A‡´{yýÀ*ëk;볪[wüÇüÕˆÏ#!8[»®í ½yêÛ+ÓöòYÑä*£»Ÿ² _±dê%‹T9cÌÛ=WÖ¼…’fì™±½sÚ ¹Ùfx!K5’KM{’2XAô€àat9Ö’­’FžñöV0!º}‹nÕÍ\vY“éÃ`xîëÛŒ‡NU:Qrô©þ˜ð…|ÿKXDìGqа§ˆr?…=Ex<‰ ¼÷À}…e¹÷bmÿtŒ`©T[ð.aæéâ/ªQ½äéaÕðÃÊÈò1Pïçÿ°O£z{^Ń ØE~ù·`KCãN—ŸjëaqP8f1,Ãyñ M1† Ѓ¡©fFúŠ*µhК ¥Þü f²×`̼Ü!-ZÁ“C Œ¦[÷͎“iÃ.£ügÄKFQ~ÚóŸÛAN—|á³Úz´az«B²ýIÎ$X¯Xq—£é.ñ‡¥úŠåØ÷ª)‘ÙmÙ™å2þþü¯Þ¬•f©F‡ê!8¼{ü¨È/0|Q‡ëCâCò˳úáýÃß'F'âtZ°zÙ÷R¨"õ1m†¤a[¥†…Ÿç9k ˆmb”Œ)L61ÙÔ¤ý£âÄõ‡-³ØÏ2·»™ÑÕ‘øÓ®_á“­ù@L3ã°Ã Θ&.±ûx<üÝšê*œ5!¶¦uqê "Ë`òÚºÉóë %0§î"]èÀzLKgiýö`"‚44OGß³ê&¿3Lý8D!Á†DÌNnÁr2dŸë¶üÄëñkŒ‚Ó\›Ã“ úË>z2d¸‚ÇÓÆÏØŸCá…—FŒHô3fÐÕà“Ã3Ú’3K˜ßí¶Úd ÝvG 2?¾E(-\ÞY,ƒmÐià­å_¸kƒ…Õ! ²*þ aÉòyQÐ6ü®·Ê]m2Ümâ`Á1.d“5TfÕÏjÿÈ,¯M1Ùþî-+ÂÕªUŒãtiÝhwQ£Wg9Ä×׫ª4(¡¾¹,h4î7‹°£žPOÊÖªs@3\Äš~À‰ÐÕ;€ƒý:X€ÃÂGpÕC¢&rïyÄðȦN•˜X@hmÆkÆílôUÁ^;œÆToÃò™_CRQÑj(¬~jo 3.ÄaíízRæ u£láórØC (‚µ¬ÉWÓ@*^]h3^Ï”0cæñ–‡úþþV‡]Á]`•lh—¶ÔpWN/ë{‹9BÏ ùöÒB?ÈÞAñm’E_^œ-¡:¾èòBuáŦ‘°¥¤y¥(Œµ¡uJWêQÑ{jTÜAzûM˜þÝñ”BºüÝŽ=a:-¢ ÔbÓþC‹IxÅ\„ƒ<-ÍÑh¥l;”ð6“.’/'q¨ó»ÄÁ&Ò8L·ÚYÛ«¹–áᢿálf 5“…ogz¤¿$“Ü‹x$ßÏÇZõ\"?v~ŒÂí^M&âÅÕÄ‘µˆ8œë S}Áï­çâ*7 ¦¬Æè±øÔ%öÔÈshñ|Û‰ÈEèGi:«v‚@,úAAæýDßOQ'v=@;²þ ]”ñÉ(> —˜3,õÚpT„™ÓØd$ øÝª€Y 6õ£€‘$äßÚUo!1‡Ím¨Î\ þ`0‡Î a@ÿº¢g&èzìº*^µ>bkÑzI(fN¸5 ÄÒ°·LmNK20ÉòôeaqM $HK¸â ay™)æÎÎÕg$¾ƒ©Ìϧ„2XnüqÏ1 üTD+ BoñwÜ5ŠlÛ!ÝiB~.Š¥Û·ÄÇ`z ‘ÉTâ*º#û"““¨É] 2áfóŠB];s[rß>}(!ÿŸÄ# S[íZQmû¬ä½÷öäýv_*[þãÜØYüUÒ̦ðe;œ†ç.IÏs¯D‹Ç€¡9žl>&Ãà@ÜqÉ<êæ1 C wÌ×T\°LKpïä`³¦eûqÊ+æ®c¯JÜy§ð ¡>sæ»S9‡"÷ƒŸ1<³?T;;^§3ªžW¾AÞ0í7•Ï¿áØtˆ8ÕbPUÙ=bYàÍ#p†az.$¨o»vpÝL0vù+ôQ#å¦eW5ÈAÚöFÆýIXy¹@2ƒ_pB—”-K#$Aˆî(8j ïûè²­I¦–Ò$s¬òµUÕ5ÚFQ•Ò>q›¶pÛ²?,Šò‘mÍ)ö‹Ü…â_‘çÞ¡|˜>ô¼r}¶J—Ò,»ësÎD#ʧÜ=+WkMýOߔيÞäçúMl3¹µüÑWä—Ž;bÚ+×D€l^7[I§‚ƒó>,.]c—ŠŠ¹í°þ%§G[¡I’¹øv™£Ûå-±)i?õòw#ût¥P9É›WŽÚFb»èdÅõNjû#º;xä>(ÙL•Ö.§^P=sWÇ®â¯Ãð†²¿;cV²¯µêw"4díA>U<-~ðú |¦†ˆƒkµØ¢F0`ŸôY‹ˆ´#ï—ÀeusÏ&›Îo;3îÖCûÜ­¡’¶áâl+Œ©Ê\Ûþ.1¯ÝÜ/ø¥ý@»ü­`Kºxòo£~ØqðÔ‡ œi¸õ»Stµl\5óQq×¶yPÅ8‡ú¯uÞóG\|*JÌÒ²óŒ?—ŸD!ÀeÖi¹µevAû±låƒ+ÿ‰ŠÿײïdSRï p\-êò÷ss±–½~âN †AGøIyœï7ÑNs÷†óƒ{™øÛ°í'í¶ý×§þ“£R}ï§-!ÿ Zxà!ŸHÀAÒoíqšïvÌIf&6¤‡Å꿣ġ/DÈŸÎP56J'[úöÏi(úÿyÙD(endstream endobj 26 0 obj << /Type /Page /Contents 27 0 R /Resources 25 0 R /MediaBox [0 0 612 792] /Parent 22 0 R >> endobj 23 0 obj << /D [26 0 R /XYZ 72 744.907 null] >> endobj 25 0 obj << /Font << /F32 10 0 R /F38 21 0 R /F29 6 0 R /F42 29 0 R /F43 31 0 R /F14 34 0 R /F34 13 0 R >> /ProcSet [ /PDF /Text ] >> endobj 37 0 obj << /Length 2970 /Filter /FlateDecode >> stream xÚíZÝã¶ß¿Âoõ1ÃO}´OÉ!W$( ´·i$yez­ž-9’|Ûë_ßr(Q–¼»—»—}X,)Qäpfø›ß ýíÃÝ×o…^å,OÔêa¿B±L«U*8ã*]=ì~^ÿÂEzÿëÃ_¿Õr©Yž‰w#Þ4õþX•ýwmÛ´~¨Š†fœIYu÷iøº-ªÎî|ûé`kú(?29ÓYBŸu¶­Šã|fi˜È Ú5–f¯›Þ7NE_|³?XzÖtô¶µå½XÛšº´ˆŸárÚÚÖ·÷ 5ší¿lÙÏE•R3!R£©vsAA­œu1ÿ>ÖûFóºÚÃrýJµÿö]ÕÔiÊ÷74Ÿ§,ÏU¬{•Œº‡¶Ó½kÝÚ\fXš<¿7Ô‘W‹A¨{Á×a¡Š–)ü¿÷¬½øô¼v³ÓLÐ.ÓiFk|ËÀÔîã™4J03h¨€ÙúñròF†uʦîðÃÕF錉T€ÚËñßxcïªý½4ë½m™ ^RœÈ©‚‹ÿ¶'ð¬sÿñ>1Ëævfü$k¿ë›¶x´mQwEÙƒ7Œ.4ÓäóN\ßâf‡¥æ«è”¥êÓN^yiGeö°ÐÍ¿¤#i$Gý.-ÝPM 5 RÃÍ×_8Ú*´(¥ÖëfßÛšžþQwÙ–Ç¢ëÂÿ¿êiTE/C›´ ú_Õ½mk0гN@?_ ŠéÂg(åH¢xÍ¡~WÞiiÞg“‰‘pŒŽUý8*~¦¬DÀ×VaZû 7þÄ‚‰5SJÓØ²8ÉJF+ °’ˆ\ðLú#ÔZ7󂥄‹.œŒiø‚‚ïf#Ü|c3uvE_P3‚zÒƒþ–|7C€óeF96¿)gZçløA$pë©Qy›/g0åé«õ,e»½—|}¡ùŸªþЄÀ7È-8‚Y‡þÒÇ–¸çôü`Ë÷¼`÷ÅÓõÃÁÃr¾î`ºãη›úøÑ·¶Öÿ¿ûò!.€ p,sÃLŽÜûºyZÍŠiTì%†ÙÐt¾Uk.Y>EàǦ v1J{À-(e!°ß'ØáŸ‡êx=º«뢿´Ô=6Í{:,·'uAO¾Òz±O5¬,B£kB°ˆCKi»?.a_*X’ä‚Ìäæ üb[Äàw“&e b_ŒñÖ5ïÁvà=Æ$çüÀð läþPÑqK”ð€ïüèdœöF7—…ÏÁbBBïûÞ–ð.ˆ-Ò½-vÃì3+mh[±ëtö¸w(u÷Ýê…¯÷ jËB‰Uyºûíîç_ùjwÇW?Üq¦€x=A›™ÌW§;\#§ÞñîÝÝ߆™64Õ&šë[äÐt1šÁ÷aÈs†Q†e‰™ú„€9¬ƒ ƒ î–‚dš°LÈå©nÄÈ¿6`Ò$~`áÔË‹<ÃVn¹¢;èsGäp®²!¢ÔO ÂÏü‚žõ om&£ê”Au8gU者著`·ð@øgÛxFhýão  œøžH°{‡'v§Có‡{<+~\ÙÚÂ…÷òPÐã­µô¨YÀöÇ!ps)ÇÀ}©w°ËRlʤž„ÖÚ>]»wÎ!˜­t®0°Žsû‰6ãLÞµ#‰òv–š$“Y‡Š£üÍ 9æôüþôÓO z€ü*‡`Š$¤‡þ\ÞÐC¢ ø"zfº¥?À¥$M¿äÄ‘‹¾Ìvú—h‹6LêçI ¸˜TâSUª_P©Ò.{ý:§º¥Tí«ºê ä*†ÍOV,áìþR— ô8e*M_`†)gW‰é2ÞÎÝ„ ÿ`»:Ž`‡â|¶uÇæÂm g&‰#×hõ«¸¤† =—ñ ì‰8¬ðºñâê}óä(Žò^ÏBòæ:®.âZ~™ (îqç¼CÜߟ›®«¶Hѱ·µ{—¡Nf¾œ!ÌzSŒ¸v:³óyh±•w—¶Ø-1gÊ_”L°–Výw…ùDOƒú†8íå|nÚþ™tS¤©cAC¡°¶7œ#‘L&¤ú@B®ã)5fëAÇjGáߜۦoÊf1;•‰a<דÐQlAdªÍâ=úèËØ²€¾ãiδ/@Œm˜)ļ9Z08ŠÂšáxÍð†Î=´¶ôÚ{ |’lÅÀ[‘Y¥± °§é©g)N0ä7€qå1c op÷ˆ«™Q@ǻԵÕ D˜6, † //*——o œ°;¦[Øï†„ŸÀŸWh÷#Húì®ÅpgWØB¨ßÙˆdUÓ©ò'=®³õ˜Z¯ 7jp“ÞàîÊ$¨v±ª à™šä'unnÀgž¿¸‡šlsiï°<‹!-T–m¨Vô7¢¹£  ‹t#¸Ñ3Í”™r°w½¿u0×ÇŽâØòh&nÜŠñ”‰¡Œmh©˜ä!u¼ºÆÂE¼‘o,“€àÃ¥I¤¡ JX:Ü E˸°¥Öß,¤´šé,ŸÞg¾qv¾u¹…Óy> Ã"ºÜè™TXÌ$É–Õ€¬Ò×i15A‹xa9Ÿ ŽŸÎ^©ªL§Ã5¥3wº¶¿]ü tJ!5¥í\ù1äŸiþZ‰ÍDY)±…”X“kŠÈ"™,( #Iöú HRÐ [/ž¤®ç×wG.GŸ÷—³ ¨œÑ“GË¢»®õÓí¤ê^`N¾˜¬í¢ÕM½™]é÷™¦¿ÿ'lÿ£„M¤€Ì€/_„°E“Ý$laÌ—%lá¶ÎÖ ÖGêãÝ{ýÌÙ{rrì`¸Ej’‰@•ƹÀ ð*ŸL Îê ´<­ƒÁÛ°`Då¼ÁÙKa|ôÇæFa<› BÄ^pÙ}ÀÕ„pqø§¼žá¢äkÿË÷©Û <™Òà íçs»át4ðÉvœiBRSN\‹ëe®¥ð÷ãï«XÖ&|©Of[:OüçR­…Jpylºå;™,Ë^®ëë} ÑâXý“lä€[ˆI2ƒÓ~´EçËz2ªq`Ç× †PÀ“Öbà+mç‹Zñ¤Ô ÷Ý2 ¡ô®„ÿéº×Ú÷î×^N8‡h2‚\h‡g¾&È^wGq,ºþ]tóëü øöËluùž%þQÙ [åxFlŒ¿»›¾koYþõ›ý6ŒÄmQöae¬×±¾ã·á‚Í95ÈêÎe˜`ûq®¥ASw}Íë\:øý~vÈbÝLv|:R¹2šqˆ./ W™K!æØŒ“¸`!óȰ Xo"VZ2hR« Æú®Äendstream endobj 36 0 obj << /Type /Page /Contents 37 0 R /Resources 35 0 R /MediaBox [0 0 612 792] /Parent 22 0 R >> endobj 38 0 obj << /D [36 0 R /XYZ 72 744.907 null] >> endobj 35 0 obj << /Font << /F14 34 0 R /F42 29 0 R /F32 10 0 R /F38 21 0 R /F43 31 0 R /F29 6 0 R >> /ProcSet [ /PDF /Text ] >> endobj 41 0 obj << /Length 2595 /Filter /FlateDecode >> stream xÚ½Z[Û¸~Ÿ_áGX³¼‰”Ú‡¢ƒ¤À‹ v3-‚&yÐÈôŒZ[òJr&Ó_ßÛD]l9´‰©Û!ù}çι½¿ùÃ_]¥(lu¿[¥¥X®$Á3¹ºß~ŠÊÓáAÕë ¥I´«êõ—ûwðMÒØ¯°y»*¶î@*‰•Ò½ñ™Ri¥=?ù“¦Qž•ö^Uî_ì­*ÏOkÕöªØÙçEk¯ku¬U£Ê¶±÷3ûSª5£g'ááŸ*oí?«þ²=ÕecWɃUÒ˜"˶Ëü¥Y“P†RâÞ@î1ïÇ(ÆI' Uœ‘ %­û§Â­ßþ¦QY¹oÕgŒi© œ« )Jx¼Ú‚Ò86_wl„» ì±þÇû7·è6kÔ‡¶ª³Ç™ ¥1|´#ÎúçÂ8±žX"A¹yoŸ5í}•M–·EUN‘¤H¿ÉÏ8ÆðL'ߎd,›úÍÐÄ8jƒ9Ì»7öWïÜ ö !°{‘W‡CÑ¶Ùøëª(a°/±H£dHHö¿–,ôduË›`’bÄÓÇ ÂÄób±{Tí/ÙAMçæ”‡-óAÀü™˜áCo˜ñ¨}RzÀ¢ROcnU»Ñ£¶¤ÐzÃp ¹·4"$:d­½ÌÊ­e«j°ä6s$ƒ€^bá¦õ³±Èßinæb«ŽªÜ‚/0³òèÎÍ’W§½›çÁ}ŸÙKÍÖÞà˜„»Õ3ýdI̵Y›=SŽñþ¹jsØÒ5”':bæCñïËÌ_œQdÇc]}+\·ÈFÏdFJm †žv»q{(œˆ‡—V5‹[áÝVT]dûéfb†dš›™‹¥2ÇŠÿbcÒ˘ƒK”\ðO»LKœbàm7z÷Py/R«TÉŽ¿®I©ºéP5þ®‹1M­ƒ…Ö-ï^¦0½l!.ÆK½`üŒC4CãßWÙ œaÀºŠ§_,¢UiνCÄOC¥ãÜS 3ïJBEþ¯½êÍ)u $m/óØEW°A¥@¤s´ç²žz6б’…*2ÅV ¸K|&"MŠ(»äî1$'\N?kŠ)’R,/'Ä7gr_$¹ÌyBCÎÖZúSpê໋Ü:obàª^C*†½¥ñÕ¤>ek +)Êdz 7™#ˆc?EÚ€UŠd—Ïr¬SF6p°‡j[ì µ½+ÿêÕÀIb‚Iÿ÷žV§!à?æ­X&Ž$ i4OL°ß鈄ñsÑ>ÙQ@é Ì$…TR.0)¥gòY[Xæ§ìTEÏTjOâÈø ¸‘¹µªÃ±}±Ã¦­­è÷µ&ÊôÌ™t"hÒtªõ @¶€‰ŽÏPc½ É&pe.šßT¶}¯+¥e»?C,dõ‰H/¸çúä̯‡¼>÷+õ©p +Ú˜âMÄ‘s%|R?>wªCù;ß¾QW&PÍéx¬ê¶ ÊŽlÿ·r[ÍU@)ppEJÓÄŒÃ@vH‡ôˆýôpÜ«h±Obáž_¯—¬ÚJ8ÁÚ N©UGx«Ãd8©)yáw[)'Ôm¦¢a’#Þ‘ó £‚FFE/Øê1Ç%É›kŠ^‡~gõy)Ñ!Ö+Å@ý=´óìK‚$#ËìC“tù‰x…LÈÇòqÏ4Ž|Â2`ºŸ¡ðå¤cL/9?§‹3ýAIy‰Aþ\Ã"2ëk^ÐõeX{…÷øÎÐÚÖzƒ^ÈÍÛû3^È šŒ@‚GÄ*?Üü~óé ^moðêÝ F ¦gc¡nu¸×eê®ö7n~íDm¼¬M ìÖtðÂ܇aÈøû MÀðÉÝÌbGáCÂÖè圀¤ r; –T„ŽjŒÇÃÖ¾ßUÞ…AýÆ\âF…ºF\ ­…¦+†%däuÈZQ›@ÖXÈnI7›…uŠN‚p§ÈvZCvð²ž~îl!F`S¿+w3æ®»~”\ÝÒøøñãLtO\ì-Æ£Š·—´^èµÿ\=^Ñåí&&`»|Õ§™S6¨ëú¥cj{IíŒ (Aã„\'Éѯtæ$¥(î´î:I¦³ÞªzFšÎ^¸ýËz“¦ÝÿSž$"B,ä! :MÏV@ÝžÙŸ@‰á ”xšX£·š Â|£š¼.ŽílÇNŒÑ¤kM›pÛÆ$ ´Bd{7uà |³}˜ü€r ú”ãP™ðþ(ôÃÍ c9x¹UúÁ7ç} ¹ý0u›´´Í̾>9‚Ó ‹é»³ {åsLÓÙ™ þ•$^¿ñœƒå"̺»ål•­NûÖ(‚]uͺ°…ktñgr&°W}ð§ ,ŽO2§äÌéßÀDB=Å"ù[Èáv§ø'w9ìn³º~Ö9ïÀY`Ù/î—!Ì–½ã>ëÏEógW¤3Änän7Ó¨„B>të ŽC ·AEt¬+=ù×bkò?¸cí`à’ÆQå 1’_}z®µ…wž©ñ©q#sTG…=ÑÑîTz탫îOà9įcÛø—§C-ñ_X5C2NVíÐÖ±£;·‘;†ËcVgÕúó × ó¥ûÚçËgÐXÏóä/²{1ºvî"ùÇá¡÷\†Î0ƒÈ)—©!C_ží÷ýº _pgõd9s}m iþ0‰ÿj+ý·¦ÙºÜ·¹œÍ_<Ÿ SÎUP„½ð—3$ui”ÎHuÚøös§Sƒø{[¿qˆas}v4N1¹Ç÷}›K´‰ó.„OH8w.ÕÝì‰@"ìßñ³V¶Ej£”ð‡ pÃb§oæú3ý·[ûfQ޾pÍ)m="žu•B:é”0„ 8@¬«Œ§„)•ÐOaY t 9ê„ÁmÓ ÐçOUeÚ\"qPOÛj\IºŠ9›+j".’ùŠdlz!¦¢i`‚tÅ)"Ä֢ܸ=&u5î)§ë ÁGºº7AùÑŒÜé¿ãØ­ÜÊ•_ú„¼ endstream endobj 40 0 obj << /Type /Page /Contents 41 0 R /Resources 39 0 R /MediaBox [0 0 612 792] /Parent 22 0 R >> endobj 42 0 obj << /D [40 0 R /XYZ 72 744.907 null] >> endobj 39 0 obj << /Font << /F32 10 0 R /F38 21 0 R /F42 29 0 R /F34 13 0 R /F43 31 0 R /F29 6 0 R >> /ProcSet [ /PDF /Text ] >> endobj 43 0 obj << /S /GoTo /D (page001) >> endobj 45 0 obj (1 Concepts) endobj 46 0 obj << /S /GoTo /D (page001) >> endobj 48 0 obj (1.1 Versions) endobj 49 0 obj << /S /GoTo /D (page002) >> endobj 51 0 obj (2 Storage Interface) endobj 52 0 obj << /S /GoTo /D (page005) >> endobj 54 0 obj (3 ZODB.BaseStorage Implementation) endobj 55 0 obj << /S /GoTo /D (page005) >> endobj 57 0 obj (4 Notes for Storage Implementors) endobj 58 0 obj << /S /GoTo /D (page005) >> endobj 60 0 obj (5 Distributed Storage Interface) endobj 63 0 obj << /Length 2870 /Filter /FlateDecode >> stream xÚ­ZY“ܸ ~Ÿ_Ñêª4#ñБ¼ÍÚ[ñV*©'Ùªxý –ÔÓŠuôJjÏN~}ÔÑÒx0^ÑõeËò›´.zA«´šV‚@Š$¡‰Pj»ú\öCÛ=óìÙ:1¬¦=~õ¿VÔˆ$rZ´e>êh¡thUÂ÷ÝQ>­%hEÿ´ÿZtûÀ÷ìqÖ•2x›À¾üo±¥¬‘æÛ’⥤_}_Vè·–&ÐТéŸ÷‡$ÿ]Y¬„ÑdÙ`}Á‡@ƒ%xUHŒ±SÿÁ>¤Tâ¥ø'öúâ·kÑdu¶§õfq"bãŽùºì÷Íàn|¾idDà»3´ÇÿÙnt0Qè=œy‹²9µ]ÖÙ°ãÒµè_˼èI§”ªöqÔÊþHFìeàõ4;ÿÎYôÐNó@78z¨Aµ`aR ´ÒFyïÒ6“ ì >ãÔîŠKÛ EN_%>Š$‚Þ½4ã[á©Ù¹k›4-³´¢ym—ÃãñNk×ð6d¢ÝûÛpØqª’"HœMËž¶},']ð-å'ã;d¥•áX ÷m-&ŸÊáL&“‘:a‹…Œ<ýL3cmmlÿâ]`Æ`‹¬D×¶&ƒ¾  ã' ŒB#pÂÀ«žod4ms˜­qbÆkÆòt³¨¨/ k`YýЕÍ#Oå%‹ƒâmÒ»çùyqB›§×j°*J/­*ká.“è˜vÕä!MÏÎý͵>µÑkñï¹5ÝÕÀwo§tg®¦ø†Ð¿ÛΓ–8>¯}H4ÊÐyÆ£& ÿ!Ï©}ïé\fg;3ï ×»xÕ¾IÈÔ ˆ;0bƒlù<èbF€[á\ZBGzq håë9V䨣¼´Éi¨i‡ ì D vý µî×SÏ©kk;ó¤âÚ1íÝ’sŠH‰¡‡Mk7h#·Ô¬l x´àKš¢È)‚|ø[ )…ÃÙ^ÙµëàZè£Òû-þm*µ²m3€ŒžýéŸMÞ’uŸJwóø¾—>ˆŸ3ß`_æõVîc;Y§ƒ.©îçNÚÚËé°…ŽJƒÛDΥѳÐw¶ïX‡f„ʵ³jž½Žü:k/ϳKÿ±F`£ÆÈWÝw„8÷& b¾•nÛ~‹ «H¨X”v Ì„½Äb1• –|ä‡ö‚Q J…ñôЗñI`']5¶Ò†Nj?Ð[`ZúX Ò¯© £ÍP+ýHÈñý.M7MšYü8 ð‚’r‡ç 2Åê™6»ö6ŽÁÀÓ™°ÁašùÐ`Y ÎMixñŠRÆ@×– —C|…FèõøþÛNße}©Šœ<`„"t[ ëœÑˆÄº“À½Hœ}í-e^㨖Bûîb;z£/? À=ß1a EV½D `«¬ÒceïKkïˆý×öwš4ãÆ‹ð!ÿôû½¢¼<¸>³$§-¡ð†Ôw-a¬gÜ]' dpgëC‚9Áܯ¤÷ ºÃÆîá,ÁzesÈÿ"™Ünnq¬i xÄÙu×é35ˆ–ièÒ‚-$R h›SUfÃû®k7^E,…-JpБGÉ’ïý‹¿¶Ù—ä&F„:ø”Ò¾/â~ <)¶´Ð‘ˆåFðŸÃÙ>_I÷!ìtÃVÉ^«y[ía´Û€i,‹ÜV ¶½U~a_Ä7l{,ÄD3cI–Cµ#'ö “€ç×Ìæå¸Ò¾NhPÚãKê‚C UÓ>f[ð"Hh·’Ç#-ͱ$¿/šß9:ˆP–ûjR¶&«®– c/RrüKþ¾ $ §" ·ƒK„~®è[,ýùFΈ—–Šz¦b’ˆÀÄãíÅŸ6Α€¸“°¢Y=І>ú7®qg:mÆ ¥áÇK´ú÷ßßÝ‹{`D™áübEWNÛQíl,œ=íHO»/\Th!XeCÅE¶béš‹’\ÉÂêb8·,Ìõ%Ì(Çêpþ‰'¤¶¿$B$2QVk…9@/'škOËýñAË(ö>L)"ÿ.aKáõ‘Íb÷{ZsÚ+ŸJýðñ²Ä¶ë7ä–÷/Úà·óÛÜ”ØôÙMÓlÃ×|¬þ‰DÑe½[9Ñëù]/XHÄ:yb“J®A g¾„k5”—ŠûÓËF:쀀 ì·çŸNm”ŸâÉzû0˜oJïÌ!ûÂÀz! ŒH‹}e´×þb‡_[±ƒ9Kmpm“óê¶æÉ=úlqz°ÐÕÙ_®&mxUÑq ‚xm6NçÀ–#6 zÎ$ÇâœV®:|[g.›œP¹Ì¯i5^(I ;‡N{cS*ýÂ’¡"wÔ7ýtƒÕžH‰È1Ñ™zi&Uý±—@:Zà§ -9[àBÙ¿!W†æš¬Ëµ§_§`dÜ”ÍØúüªÍW²6êt?VMnÝÖØÃCÀÑÓÑ%C¯¶EÒ~\EuÑÐÝU6áRühwK]"gnÛsÃZÌmWlµ¡Îm"`ua"À‹'MÊnqØÅ¯Ž³°Máê_äwïî0œø»RÑ£]Vßýv÷鳿ËïüÝOw>Ыx÷mŠîõcnWwï~E@Æarÿ/ðtbu (Gì´A ¶XÿÃ÷ÚÛendstream endobj 62 0 obj << /Type /Page /Contents 63 0 R /Resources 61 0 R /MediaBox [0 0 612 792] /Parent 22 0 R >> endobj 24 0 obj << /D [62 0 R /XYZ 72 744.907 null] >> endobj 61 0 obj << /Font << /F32 10 0 R /F38 21 0 R /F43 31 0 R /F42 29 0 R /F34 13 0 R /F29 6 0 R >> /ProcSet [ /PDF /Text ] >> endobj 64 0 obj << /Type /Encoding /Differences [ 0 /minus/periodcentered/multiply/asteriskmath/divide/diamondmath/plusminus/minusplus/circleplus/circleminus/circlemultiply/circledivide/circledot/circlecopyrt/openbullet/bullet/equivasymptotic/equivalence/reflexsubset/reflexsuperset/lessequal/greaterequal/precedesequal/followsequal/similar/approxequal/propersubset/propersuperset/lessmuch/greatermuch/precedes/follows/arrowleft/arrowright/arrowup/arrowdown/arrowboth/arrownortheast/arrowsoutheast/similarequal/arrowdblleft/arrowdblright/arrowdblup/arrowdbldown/arrowdblboth/arrownorthwest/arrowsouthwest/proportional/prime/infinity/element/owner/triangle/triangleinv/negationslash/mapsto/universal/existential/logicalnot/emptyset/Rfractur/Ifractur/latticetop/perpendicular/aleph/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/union/intersection/unionmulti/logicaland/logicalor/turnstileleft/turnstileright/floorleft/floorright/ceilingleft/ceilingright/braceleft/braceright/angbracketleft/angbracketright/bar/bardbl/arrowbothv/arrowdblbothv/backslash/wreathproduct/radical/coproduct/nabla/integral/unionsq/intersectionsq/subsetsqequal/supersetsqequal/section/dagger/daggerdbl/paragraph/club/diamond/heart/spade/arrowleft 129/.notdef 161/minus/periodcentered/multiply/asteriskmath/divide/diamondmath/plusminus/minusplus/circleplus/circleminus 171/.notdef 173/circlemultiply/circledivide/circledot/circlecopyrt/openbullet/bullet/equivasymptotic/equivalence/reflexsubset/reflexsuperset/lessequal/greaterequal/precedesequal/followsequal/similar/approxequal/propersubset/propersuperset/lessmuch/greatermuch/precedes/follows/arrowleft/spade 197/.notdef] >> endobj 33 0 obj << /Length1 772 /Length2 576 /Length3 532 /Length 1127 /Filter /FlateDecode >> stream xÚSU ÖuLÉOJuËÏ+Ñ5Ô3´Rpö Ž44P0Ô3àRUu.JM,ÉÌÏsI,IµR0´´4Tp,MW04U00·22°25çRUpÎ/¨,ÊLÏ(QÐpÖ)2WpÌM-ÊLNÌSðM,ÉHÍš‘œ˜£œŸœ™ZR©§à˜“£ÒQ¬”ZœZT–š¢Çeh¨’™\¢”šž™Ç¥r‘g^Z¾‚9D8¥´&U–ZT t”‚Бš @'¦äçåT*¤¤¦qéûåíJº„ŽB7Ü­4'Ç/1d<8”0äs3s*¡*òs JKR‹|óSR‹òЕ†§B盚’Yš‹.ëY’˜“™ì˜—ž“ª kh¢g`l ‘È,vˬHM È,IÎPHKÌ)N‹§æ¥ ;|`‡èGºúD„kCã,˜™WRYª`€P æ"øÀP*ʬPˆ6Ð300*B+Í2×¼äü”̼t#S3…Ä¢¢ÄJ.` òLª 2óRR+R+€.Ö×ËË/jQM­BZ~(ZÉI? ´©% q.L89åWTëY*èZš 644S077­EUš—YXšêé¢`j```añYriQQj^ 8 ÆOËljjEj2×ÍkùÉÖ-YÓ·µ­¬s]|a«>çÏk_Þd?±£nvfJm°é¼@Åô’%¯>ÚÚwX<û¢„W²õTá¢-’½~=q_ ¯ÙÚµ`YÄ„Óýz7‚Å+›»¦ñþÓVåy¸0lÆœÖGÒVû‹ÏêTÖ¹ùE¹þϼ”NQ‹÷}¿w[H+h’–’”ùÍìwÅÄ+ï>¿,ÿiGýôã¶ÉïÎÞòñ /vëR¿˜fÇô%ñۮش²‹µŸ9¼òâQ¹DÊÿžýÑod;”ÚU? ^Vñµ«Nºúú©vñK¯{~­ñçäÚ/ëtôî…Ã-Çé÷7¸ï“õ‘9ñØ8ã·Ô m¿i"é÷Œ™6=Û!y:ëIèÆõ†íÿ_°K-­û±,1{Îö)².oª —ï¶ý*Þ[«ç½mFäû%»s_Û-j(lå¦sÿÏùœ~gغŒ|K·~›¶#£ïµ¾øÓ·&g®]p_ò¸!—GrnM`ìv®^ÿD·l½ŸÞë>Z`.x‹“Yh—ý.Ž#ÁÇ8©¯Øw6O~¡—5“{Þ„U7¶ð807ì™õ…ûk4鹇Wñ»5þô öŒïùfÕŸ”ÛV¼ RÅ—÷mõ‰_A¢ëX¦¼OïjW;[Ã(Ï´ÿÇê¼uï,¥n˜ q(ï»°õÆA®æ…Ëü+Ì»·3z^›"_Õöûÿ‘Ù“O:†~ýUûI¯H$P†kR¦½ÏíÏ-‚©¢áúº×y'y=øØ'sµñó‰BˉêÿcÙWdtDÇ?û:`‡‘µªñ½w¦[K½ð_°È€BÀ5jÀ°0 9'5±¨$?7±(› _Úx¦endstream endobj 34 0 obj << /Type /Font /Subtype /Type1 /Encoding 64 0 R /FirstChar 15 /LastChar 15 /Widths 65 0 R /BaseFont /YELXPW+CMSY10 /FontDescriptor 32 0 R >> endobj 32 0 obj << /Ascent 750 /CapHeight 683 /Descent -194 /FontName /YELXPW+CMSY10 /ItalicAngle -14 /StemV 85 /XHeight 431 /FontBBox [-29 -960 1116 775] /Flags 4 /CharSet (/bullet) /FontFile 33 0 R >> endobj 65 0 obj [500 ] endobj 66 0 obj << /Type /Encoding /Differences [ 0 /.notdef 1/dotaccent/fi/fl/fraction/hungarumlaut/Lslash/lslash/ogonek/ring 10/.notdef 11/breve/minus 13/.notdef 14/Zcaron/zcaron/caron/dotlessi/dotlessj/ff/ffi/ffl 22/.notdef 30/grave/quotesingle/space/exclam/quotedbl/numbersign/dollar/percent/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/backslash/bracketright/asciicircum/underscore/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/braceleft/bar/braceright/asciitilde 127/.notdef 128/Euro 129/.notdef 130/quotesinglbase/florin/quotedblbase/ellipsis/dagger/daggerdbl/circumflex/perthousand/Scaron/guilsinglleft/OE 141/.notdef 147/quotedblleft/quotedblright/bullet/endash/emdash/tilde/trademark/scaron/guilsinglright/oe 157/.notdef 159/Ydieresis 160/.notdef 161/exclamdown/cent/sterling/currency/yen/brokenbar/section/dieresis/copyright/ordfeminine/guillemotleft/logicalnot/hyphen/registered/macron/degree/plusminus/twosuperior/threesuperior/acute/mu/paragraph/periodcentered/cedilla/onesuperior/ordmasculine/guillemotright/onequarter/onehalf/threequarters/questiondown/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex/Idieresis/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis/multiply/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn/germandbls/agrave/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla/egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis/eth/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide/oslash/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis] >> endobj 31 0 obj << /Type /Font /Subtype /Type1 /Encoding 66 0 R /BaseFont /Courier-Bold >> endobj 29 0 obj << /Type /Font /Subtype /Type1 /Encoding 66 0 R /BaseFont /Courier >> endobj 21 0 obj << /Type /Font /Subtype /Type1 /Encoding 66 0 R /BaseFont /Times-Italic >> endobj 13 0 obj << /Type /Font /Subtype /Type1 /Encoding 66 0 R /BaseFont /Times-Bold >> endobj 10 0 obj << /Type /Font /Subtype /Type1 /Encoding 66 0 R /BaseFont /Times-Roman >> endobj 8 0 obj << /Type /Font /Subtype /Type1 /Encoding 66 0 R /BaseFont /Helvetica-Oblique >> endobj 6 0 obj << /Type /Font /Subtype /Type1 /Encoding 66 0 R /BaseFont /Helvetica >> endobj 22 0 obj << /Type /Pages /Count 5 /Kids [2 0 R 26 0 R 36 0 R 40 0 R 62 0 R] >> endobj 67 0 obj << /Type /Outlines /First 44 0 R /Last 59 0 R /Count 5 >> endobj 59 0 obj << /Title 60 0 R /A 58 0 R /Parent 67 0 R /Prev 56 0 R >> endobj 56 0 obj << /Title 57 0 R /A 55 0 R /Parent 67 0 R /Prev 53 0 R /Next 59 0 R >> endobj 53 0 obj << /Title 54 0 R /A 52 0 R /Parent 67 0 R /Prev 50 0 R /Next 56 0 R >> endobj 50 0 obj << /Title 51 0 R /A 49 0 R /Parent 67 0 R /Prev 44 0 R /Next 53 0 R >> endobj 47 0 obj << /Title 48 0 R /A 46 0 R /Parent 44 0 R >> endobj 44 0 obj << /Title 45 0 R /A 43 0 R /Parent 67 0 R /Next 50 0 R /First 47 0 R /Last 47 0 R /Count -1 >> endobj 68 0 obj << /Names [(page001) 4 0 R (page002) 23 0 R (page003) 38 0 R (page004) 42 0 R (page005) 24 0 R] /Limits [(page001) (page005)] >> endobj 69 0 obj << /Kids [68 0 R] >> endobj 70 0 obj << /Dests 69 0 R >> endobj 71 0 obj << /Type /Catalog /Pages 22 0 R /Outlines 67 0 R /Names 70 0 R /PageMode /UseOutlines /PTEX.Fullbanner (This is pdfTeX, Version 3.14159-1.10b) >> endobj 72 0 obj << /Producer (pdfTeX-1.10b) /Author (Zope Corporation) /Title (ZODB Storage API) /Creator (TeX) /CreationDate (D:20040620214400) >> endobj xref 0 73 0000000005 65535 f 0000003156 00000 n 0000002020 00000 n 0000000009 00000 n 0000003103 00000 n 0000000007 00000 f 0000022505 00000 n 0000000009 00000 f 0000022410 00000 n 0000000012 00000 f 0000022320 00000 n 0000002186 00000 n 0000000020 00000 f 0000022231 00000 n 0000002309 00000 n 0000002440 00000 n 0000002576 00000 n 0000002708 00000 n 0000002839 00000 n 0000002971 00000 n 0000000028 00000 f 0000022140 00000 n 0000022592 00000 n 0000006610 00000 n 0000016685 00000 n 0000006665 00000 n 0000006502 00000 n 0000003271 00000 n 0000000030 00000 f 0000022054 00000 n 0000000000 00000 f 0000021963 00000 n 0000019927 00000 n 0000018525 00000 n 0000019770 00000 n 0000010018 00000 n 0000009855 00000 n 0000006806 00000 n 0000009963 00000 n 0000012984 00000 n 0000012821 00000 n 0000010147 00000 n 0000012929 00000 n 0000013113 00000 n 0000023148 00000 n 0000013157 00000 n 0000013186 00000 n 0000023087 00000 n 0000013230 00000 n 0000013261 00000 n 0000023000 00000 n 0000013305 00000 n 0000013343 00000 n 0000022913 00000 n 0000013387 00000 n 0000013439 00000 n 0000022826 00000 n 0000013483 00000 n 0000013534 00000 n 0000022752 00000 n 0000013578 00000 n 0000016740 00000 n 0000016577 00000 n 0000013628 00000 n 0000016869 00000 n 0000020130 00000 n 0000020153 00000 n 0000022678 00000 n 0000023259 00000 n 0000023404 00000 n 0000023441 00000 n 0000023477 00000 n 0000023639 00000 n trailer << /Size 73 /Root 71 0 R /Info 72 0 R >> startxref 23789 %%EOF zope2.13-2.13.21/source/ZODB3/doc/zeo.txt0000644000175000017500000004033412214017464016370 0ustar arnauarnau========================== Running a ZEO Server HOWTO ========================== Introduction ------------ ZEO (Zope Enterprise Objects) is a client-server system for sharing a single storage among many clients. Normally, a ZODB storage can only be used by a single process. When you use ZEO, the storage is opened in the ZEO server process. Client programs connect to this process using a ZEO ClientStorage. ZEO provides a consistent view of the database to all clients. The ZEO client and server communicate using a custom RPC protocol layered on top of TCP. There are several configuration options that affect the behavior of a ZEO server. This section describes how a few of these features working. Subsequent sections describe how to configure every option. Client cache ~~~~~~~~~~~~ Each ZEO client keeps an on-disk cache of recently used objects to avoid fetching those objects from the server each time they are requested. It is usually faster to read the objects from disk than it is to fetch them over the network. The cache can also provide read-only copies of objects during server outages. The cache may be persistent or transient. If the cache is persistent, then the cache files are retained for use after process restarts. A non-persistent cache uses temporary files that are removed when the client storage is closed. The client cache size is configured when the ClientStorage is created. The default size is 20MB, but the right size depends entirely on the particular database. Setting the cache size too small can hurt performance, but in most cases making it too big just wastes disk space. The document "Client cache tracing" describes how to collect a cache trace that can be used to determine a good cache size. ZEO uses invalidations for cache consistency. Every time an object is modified, the server sends a message to each client informing it of the change. The client will discard the object from its cache when it receives an invalidation. These invalidations are often batched. Each time a client connects to a server, it must verify that its cache contents are still valid. (It did not receive any invalidation messages while it was disconnected.) There are several mechanisms used to perform cache verification. In the worst case, the client sends the server a list of all objects in its cache along with their timestamps; the server sends back an invalidation message for each stale object. The cost of verification is one drawback to making the cache too large. Note that every time a client crashes or disconnects, it must verify its cache. Every time a server crashes, all of its clients must verify their caches. The cache verification process is optimized in two ways to eliminate costs when restarting clients and servers. Each client keeps the timestamp of the last invalidation message it has seen. When it connects to the server, it checks to see if any invalidation messages were sent after that timestamp. If not, then the cache is up-to-date and no further verification occurs. The other optimization is the invalidation queue, described below. Invalidation queue ~~~~~~~~~~~~~~~~~~ The ZEO server keeps a queue of recent invalidation messages in memory. When a client connects to the server, it sends the timestamp of the most recent invalidation message it has received. If that message is still in the invalidation queue, then the server sends the client all the missing invalidations. This is often cheaper than perform full cache verification. The default size of the invalidation queue is 100. If the invalidation queue is larger, it will be more likely that a client that reconnects will be able to verify its cache using the queue. On the other hand, a large queue uses more memory on the server to store the message. Invalidation messages tend to be small, perhaps a few hundred bytes each on average; it depends on the number of objects modified by a transaction. Transaction timeouts ~~~~~~~~~~~~~~~~~~~~ A ZEO server can be configured to timeout a transaction if it takes too long to complete. Only a single transaction can commit at a time; so if one transaction takes too long, all other clients will be delayed waiting for it. In the extreme, a client can hang during the commit process. If the client hangs, the server will be unable to commit other transactions until it restarts. A well-behaved client will not hang, but the server can be configured with a transaction timeout to guard against bugs that cause a client to hang. If any transaction exceeds the timeout threshold, the client's connection to the server will be closed and the transaction aborted. Once the transaction is aborted, the server can start processing other client's requests. Most transactions should take very little time to commit. The timer begins for a transaction after all the data has been sent to the server. At this point, the cost of commit should be dominated by the cost of writing data to disk; it should be unusual for a commit to take longer than 1 second. A transaction timeout of 30 seconds should tolerate heavy load and slow communications between client and server, while guarding against hung servers. When a transaction times out, the client can be left in an awkward position. If the timeout occurs during the second phase of the two phase commit, the client will log a panic message. This should only cause problems if the client transaction involved multiple storages. If it did, it is possible that some storages committed the client changes and others did not. Connection management ~~~~~~~~~~~~~~~~~~~~~ A ZEO client manages its connection to the ZEO server. If it loses the connection, it attempts to reconnect. While it is disconnected, it can satisfy some reads by using its cache. The client can be configured to wait for a connection when it is created or to return immediately and provide data from its persistent cache. It usually simplifies programming to have the client wait for a connection on startup. When the client is disconnected, it polls periodically to see if the server is available. The rate at which it polls is configurable. The client can be configured with multiple server addresses. In this case, it assumes that each server has identical content and will use any server that is available. It is possible to configure the client to accept a read-only connection to one of these servers if no read-write connection is available. If it has a read-only connection, it will continue to poll for a read-write connection. This feature supports the Zope Replication Services product, http://www.zope.com/Products/ZopeProducts/ZRS. In general, it could be used to with a system that arranges to provide hot backups of servers in the case of failure. If a single address resolves to multiple IPv4 or IPv6 addresses, the client will connect to an arbitrary of these addresses. Authentication ~~~~~~~~~~~~~~ ZEO supports optional authentication of client and server using a password scheme similar to HTTP digest authentication (RFC 2069). It is a simple challenge-response protocol that does not send passwords in the clear, but does not offer strong security. The RFC discusses many of the limitations of this kind of protocol. Note that this feature provides authentication only. It does not provide encryption or confidentiality. The challenge-response also produces a session key that is used to generate message authentication codes for each ZEO message. This should prevent session hijacking. Guard the password database as if it contained plaintext passwords. It stores the hash of a username and password. This does not expose the plaintext password, but it is sensitive nonetheless. An attacker with the hash can impersonate the real user. This is a limitation of the simple digest scheme. The authentication framework allows third-party developers to provide new authentication modules. Installing software ------------------- ZEO is distributed as part of the ZODB3 package and with Zope, starting with Zope 2.7. You can download it from http://pypi.python.org/pypi/ZODB3. Configuring server ------------------ The script runzeo.py runs the ZEO server. The server can be configured using command-line arguments or a config file. This document only describes the config file. Run runzeo.py -h to see the list of command-line arguments. The runzeo.py script imports the ZEO package. ZEO must either be installed in Python's site-packages directory or be in a directory on PYTHONPATH. The configuration file specifies the underlying storage the server uses, the address it binds, and a few other optional parameters. An example is:: address zeo.example.com:8090 monitor-address zeo.example.com:8091 path /var/tmp/Data.fs path /var/tmp/zeo.log format %(asctime)s %(message)s This file configures a server to use a FileStorage from /var/tmp/Data.fs. The server listens on port 8090 of zeo.example.com. It also starts a monitor server that lists in port 8091. The ZEO server writes its log file to /var/tmp/zeo.log and uses a custom format for each line. Assuming the example configuration it stored in zeo.config, you can run a server by typing:: python /usr/local/bin/runzeo.py -C zeo.config A configuration file consists of a section and a storage section, where the storage section can use any of the valid ZODB storage types. It may also contain an eventlog configuration. See the document "Configuring a ZODB database" for more information about configuring storages and eventlogs. The zeo section must list the address. All the other keys are optional. address The address at which the server should listen. This can be in the form 'host:port' to signify a TCP/IP connection or a pathname string to signify a Unix domain socket connection (at least one '/' is required). A hostname may be a DNS name or a dotted IP address. If the hostname is omitted, the platform's default behavior is used when binding the listening socket ('' is passed to socket.bind() as the hostname portion of the address). read-only Flag indicating whether the server should operate in read-only mode. Defaults to false. Note that even if the server is operating in writable mode, individual storages may still be read-only. But if the server is in read-only mode, no write operations are allowed, even if the storages are writable. Note that pack() is considered a read-only operation. invalidation-queue-size The storage server keeps a queue of the objects modified by the last N transactions, where N == invalidation_queue_size. This queue is used to speed client cache verification when a client disconnects for a short period of time. monitor-address The address at which the monitor server should listen. If specified, a monitor server is started. The monitor server provides server statistics in a simple text format. This can be in the form 'host:port' to signify a TCP/IP connection or a pathname string to signify a Unix domain socket connection (at least one '/' is required). A hostname may be a DNS name or a dotted IP address. If the hostname is omitted, the platform's default behavior is used when binding the listening socket ('' is passed to socket.bind() as the hostname portion of the address). transaction-timeout The maximum amount of time to wait for a transaction to commit after acquiring the storage lock, specified in seconds. If the transaction takes too long, the client connection will be closed and the transaction aborted. authentication-protocol The name of the protocol used for authentication. The only protocol provided with ZEO is "digest," but extensions may provide other protocols. authentication-database The path of the database containing authentication credentials. authentication-realm The authentication realm of the server. Some authentication schemes use a realm to identify the logic set of usernames that are accepted by this server. Configuring clients ------------------- The ZEO client can also be configured using ZConfig. The ZODB.config module provides several function for opening a storage based on its configuration. - ZODB.config.storageFromString() - ZODB.config.storageFromFile() - ZODB.config.storageFromURL() The ZEO client configuration requires the server address be specified. Everything else is optional. An example configuration is:: server zeo.example.com:8090 The other configuration options are listed below. storage The name of the storage that the client wants to use. If the ZEO server serves more than one storage, the client selects the storage it wants to use by name. The default name is '1', which is also the default name for the ZEO server. cache-size The maximum size of the client cache, in bytes. name The storage name. If unspecified, the address of the server will be used as the name. client Enables persistent cache files. The string passed here is used to construct the cache filenames. If it is not specified, the client creates a temporary cache that will only be used by the current object. var The directory where persistent cache files are stored. By default cache files, if they are persistent, are stored in the current directory. min-disconnect-poll The minimum delay in seconds between attempts to connect to the server, in seconds. Defaults to 5 seconds. max-disconnect-poll The maximum delay in seconds between attempts to connect to the server, in seconds. Defaults to 300 seconds. wait A boolean indicating whether the constructor should wait for the client to connect to the server and verify the cache before returning. The default is true. read-only A flag indicating whether this should be a read-only storage, defaulting to false (i.e. writing is allowed by default). read-only-fallback A flag indicating whether a read-only remote storage should be acceptable as a fallback when no writable storages are available. Defaults to false. At most one of read_only and read_only_fallback should be true. realm The authentication realm of the server. Some authentication schemes use a realm to identify the logic set of usernames that are accepted by this server. A ZEO client can also be created by calling the ClientStorage constructor explicitly. For example:: from ZEO.ClientStorage import ClientStorage storage = ClientStorage(("zeo.example.com", 8090)) Running the ZEO server as a daemon ---------------------------------- In an operational setting, you will want to run the ZEO server a daemon process that is restarted when it dies. The zdaemon package provides two tools for running daemons: zdrun.py and zdctl.py. You can find zdaemon and it's documentation at http://pypi.python.org/pypi/zdaemon. Rotating log files ~~~~~~~~~~~~~~~~~~ ZEO will re-initialize its logging subsystem when it receives a SIGUSR2 signal. If you are using the standard event logger, you should first rename the log file and then send the signal to the server. The server will continue writing to the renamed log file until it receives the signal. After it receives the signal, the server will create a new file with the old name and write to it. Tools ----- There are a few scripts that may help running a ZEO server. The zeopack.py script connects to a server and packs the storage. It can be run as a cron job. The zeoup.py script attempts to connect to a ZEO server and verify that is is functioning. The zeopasswd.py script manages a ZEO servers password database. Diagnosing problems ------------------- If an exception occurs on the server, the server will log a traceback and send an exception to the client. The traceback on the client will show a ZEO protocol library as the source of the error. If you need to diagnose the problem, you will have to look in the server log for the rest of the traceback. zope2.13-2.13.21/source/ZODB3/doc/HOWTO-Blobs-NFS.txt0000644000175000017500000000545012214017464020156 0ustar arnauarnau=========================================== How to use NFS to make Blobs more efficient =========================================== :Author: Christian Theune Overview ======== When handling blobs, the biggest goal is to avoid writing operations that require the blob data to be transferred using up IO resources. When bringing a blob into the system, at least one O(N) operation has to happen, e.g. when the blob is uploaded via a network server. The blob should be extracted as a file on the final storage volume as early as possible, avoiding further copies. In a ZEO setup, all data is stored on a networked server and passed to it using zrpc. This is a major problem for handling blobs, because it will lock all transactions from committing when storing a single large blob. As a default, this mechanism works but is not recommended for high-volume installations. Shared filesystem ================= The solution for the transfer problem is to setup various storage parameters so that blobs are always handled on a single volume that is shared via network between ZEO servers and clients. Step 1: Setup a writable shared filesystem for ZEO server and client -------------------------------------------------------------------- On the ZEO server, create two directories on the volume that will be used by this setup (assume the volume is accessible via $SERVER/): - $SERVER/blobs - $SERVER/tmp Then export the $SERVER directory using a shared network filesystem like NFS. Make sure it's writable by the ZEO clients. Assume the exported directory is available on the client as $CLIENT. Step 2: Application temporary directories ----------------------------------------- Applications (i.e. Zope) will put uploaded data in a temporary directory first. Adjust your TMPDIR, TMP or TEMP environment variable to point to the shared filesystem: $ export TMPDIR=$CLIENT/tmp Step 3: ZEO client caches ------------------------- Edit the file `zope.conf` on the ZEO client and adjust the configuration of the `zeoclient` storage with two new variables:: blob-dir = $CLIENT/blobs blob-cache-writable = yes Step 4: ZEO server ------------------ Edit the file `zeo.conf` on the ZEO server to configure the blob directory. Assuming the published storage of the ZEO server is a file storage, then the configuration should look like this:: path $INSTANCE/var/Data.fs blob-dir $SERVER/blobs (Remember to manually replace $SERVER and $CLIENT with the exported directory as accessible by either the ZEO server or the ZEO client.) Conclusion ---------- At this point, after restarting your ZEO server and clients, the blob directory will be shared and a minimum amount of IO will occur when working with blobs. zope2.13-2.13.21/source/ZODB3/doc/zeo-client-cache-tracing.txt0000644000175000017500000001661312214017464022335 0ustar arnauarnauZEO Client Cache Tracing ======================== An important question for ZEO users is: how large should the ZEO client cache be? ZEO 2 (as of ZEO 2.0b2) has a new feature that lets you collect a trace of cache activity and tools to analyze this trace, enabling you to make an informed decision about the cache size. Don't confuse the ZEO client cache with the Zope object cache. The ZEO client cache is only used when an object is not in the Zope object cache; the ZEO client cache avoids roundtrips to the ZEO server. Enabling Cache Tracing ---------------------- To enable cache tracing, you must use a persistent cache (specify a ``client`` name), and set the environment variable ZEO_CACHE_TRACE to a non-empty value. The path to the trace file is derived from the path to the persistent cache file by appending ".trace". If the file doesn't exist, ZEO will try to create it. If the file does exist, it's opened for appending (previous trace information is not overwritten). If there are problems with the file, a warning message is logged. To start or stop tracing, the ZEO client process (typically a Zope application server) must be restarted. The trace file can grow pretty quickly; on a moderately loaded server, we observed it growing by 7 MB per hour. The file consists of binary records, each 34 bytes long if 8-byte oids are in use; a detailed description of the record lay-out is given in stats.py. No sensitive data is logged: data record sizes (but not data records), and binary object and transaction ids are logged, but no object pickles, object types or names, user names, transaction comments, access paths, or machine information (such as machine name or IP address) are logged. Analyzing a Cache Trace ----------------------- The stats.py command-line tool is the first-line tool to analyze a cache trace. Its default output consists of two parts: a one-line summary of essential statistics for each segment of 15 minutes, interspersed with lines indicating client restarts, followed by a more detailed summary of overall statistics. The most important statistic is the "hit rate", a percentage indicating how many requests to load an object could be satisfied from the cache. Hit rates around 70% are good. 90% is excellent. If you see a hit rate under 60% you can probably improve the cache performance (and hence your Zope application server's performance) by increasing the ZEO cache size. This is normally configured using key ``cache_size`` in the ``zeoclient`` section of your configuration file. The default cache size is 20 MB, which is small. The stats.py tool shows its command line syntax when invoked without arguments. The tracefile argument can be a gzipped file if it has a .gz extension. It will be read from stdin (assuming uncompressed data) if the tracefile argument is '-'. Simulating Different Cache Sizes -------------------------------- Based on a cache trace file, you can make a prediction of how well the cache might do with a different cache size. The simul.py tool runs a simulation of the ZEO client cache implementation based upon the events read from a trace file. A new simulation is started each time the trace file records a client restart event; if a trace file contains more than one restart event, a separate line is printed for each simulation, and a line with overall statistics is added at the end. Example, assuming the trace file is in /tmp/cachetrace.log:: $ python simul.py -s 4 /tmp/cachetrace.log CircularCacheSimulation, cache size 4,194,304 bytes START TIME DURATION LOADS HITS INVALS WRITES HITRATE EVICTS INUSE Jul 22 22:22 39:09 3218856 1429329 24046 41517 44.4% 40776 99.8 This shows that with a 4 MB cache size, the cache hit rate is 44.4%, the percentage 1429329 (number of cache hits) is of 3218856 (number of load requests). The cache simulated 40776 evictions, to make room for new object states. At the end, 99.8% of the bytes reserved for the cache file were in use to hold object state (the remaining 0.2% consists of "holes", bytes freed by object eviction and not yet reused to hold another object's state). Let's try this again with an 8 MB cache:: $ python simul.py -s 8 /tmp/cachetrace.log CircularCacheSimulation, cache size 8,388,608 bytes START TIME DURATION LOADS HITS INVALS WRITES HITRATE EVICTS INUSE Jul 22 22:22 39:09 3218856 2182722 31315 41517 67.8% 40016 100.0 That's a huge improvement in hit rate, which isn't surprising since these are very small cache sizes. The default cache size is 20 MB, which is still on the small side:: $ python simul.py /tmp/cachetrace.log CircularCacheSimulation, cache size 20,971,520 bytes START TIME DURATION LOADS HITS INVALS WRITES HITRATE EVICTS INUSE Jul 22 22:22 39:09 3218856 2982589 37922 41517 92.7% 37761 99.9 Again a very nice improvement in hit rate, and there's not a lot of room left for improvement. Let's try 100 MB:: $ python simul.py -s 100 /tmp/cachetrace.log CircularCacheSimulation, cache size 104,857,600 bytes START TIME DURATION LOADS HITS INVALS WRITES HITRATE EVICTS INUSE Jul 22 22:22 39:09 3218856 3218741 39572 41517 100.0% 22778 100.0 It's very unusual to see a hit rate so high. The application here frequently modified a very large BTree, so given enough cache space to hold the entire BTree it rarely needed to ask the ZEO server for data: this application reused the same objects over and over. More typical is that a substantial number of objects will be referenced only once. Whenever an object turns out to be loaded only once, it's a pure loss for the cache: the first (and only) load is a cache miss; storing the object evicts other objects, possibly causing more cache misses; and the object is never loaded again. If, for example, a third of the objects are loaded only once, it's quite possible for the theoretical maximum hit rate to be 67%, no matter how large the cache. The simul.py script also contains code to simulate different cache strategies. Since none of these are implemented, and only the default cache strategy's code has been updated to be aware of MVCC, these are not further documented here. Simulation Limitations ---------------------- The cache simulation is an approximation, and actual hit rate may be higher or lower than the simulated result. These are some factors that inhibit exact simulation: - The simulator doesn't try to emulate versions. If the trace file contains loads and stores of objects in versions, the simulator treats them as if they were loads and stores of non-version data. - Each time a load of an object O in the trace file was a cache hit, but the simulated cache has evicted O, the simulated cache has no way to repair its knowledge about O. This is more frequent when simulating caches smaller than the cache used to produce the trace file. When a real cache suffers a cache miss, it asks the ZEO server for the needed information about O, and saves O in the client cache. The simulated cache doesn't have a ZEO server to ask, and O continues to be absent in the simulated cache. Further requests for O will continue to be simulated cache misses, although in a real cache they'll likely be cache hits. On the other hand, the simulated cache doesn't need to evict any objects to make room for O, so it may enjoy further cache hits on objects a real cache would have evicted. zope2.13-2.13.21/source/ZODB3/COPYRIGHT.txt0000644000175000017500000000004012214017464016364 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/ZODB3/ez_setup.py0000644000175000017500000002276412214017464016504 0ustar arnauarnau#!python """Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import sys DEFAULT_VERSION = "0.6c9" DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] md5_data = { 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03', 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a', 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6', 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a', } import sys, os try: from hashlib import md5 except ImportError: from md5 import md5 def _validate_md5(egg_name, data): if egg_name in md5_data: digest = md5(data).hexdigest() if digest != md5_data[egg_name]: print >>sys.stderr, ( "md5 validation of %s failed! (Possible download problem?)" % egg_name ) sys.exit(2) return data def use_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, download_delay=15 ): """Automatically find/download setuptools and make it available on sys.path `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where setuptools will be downloaded, if it is not already available. If `download_delay` is specified, it should be the number of seconds that will be paused before initiating a download, should one be required. If an older version of setuptools is installed, this routine will print a message to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling script. """ was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules def do_download(): egg = download_setuptools(version, download_base, to_dir, download_delay) sys.path.insert(0, egg) import setuptools; setuptools.bootstrap_install_from = egg try: import pkg_resources except ImportError: return do_download() try: pkg_resources.require("setuptools>="+version); return except pkg_resources.VersionConflict, e: if was_imported: print >>sys.stderr, ( "The required version of setuptools (>=%s) is not available, and\n" "can't be installed while this script is running. Please install\n" " a more recent version first, using 'easy_install -U setuptools'." "\n\n(Currently using %r)" ) % (version, e.args[0]) sys.exit(2) else: del pkg_resources, sys.modules['pkg_resources'] # reload ok return do_download() except pkg_resources.DistributionNotFound: return do_download() def download_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay = 15 ): """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. """ import urllib2, shutil egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) url = download_base + egg_name saveto = os.path.join(to_dir, egg_name) src = dst = None if not os.path.exists(saveto): # Avoid repeated downloads try: from distutils import log if delay: log.warn(""" --------------------------------------------------------------------------- This script requires setuptools version %s to run (even to display help). I will attempt to download it for you (from %s), but you may need to enable firewall access for this script first. I will start the download in %d seconds. (Note: if this machine does not have network access, please obtain the file %s and place it in this directory before rerunning this script.) ---------------------------------------------------------------------------""", version, download_base, delay, url ); from time import sleep; sleep(delay) log.warn("Downloading %s", url) src = urllib2.urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. data = _validate_md5(egg_name, src.read()) dst = open(saveto,"wb"); dst.write(data) finally: if src: src.close() if dst: dst.close() return os.path.realpath(saveto) def main(argv, version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" try: import setuptools except ImportError: egg = None try: egg = download_setuptools(version, delay=0) sys.path.insert(0,egg) from setuptools.command.easy_install import main return main(list(argv)+[egg]) # we're done here finally: if egg and os.path.exists(egg): os.unlink(egg) else: if setuptools.__version__ == '0.0.1': print >>sys.stderr, ( "You have an obsolete version of setuptools installed. Please\n" "remove it from your system entirely before rerunning this script." ) sys.exit(2) req = "setuptools>="+version import pkg_resources try: pkg_resources.require(req) except pkg_resources.VersionConflict: try: from setuptools.command.easy_install import main except ImportError: from easy_install import main main(list(argv)+[download_setuptools(delay=0)]) sys.exit(0) # try to force an exit else: if argv: from setuptools.command.easy_install import main main(argv) else: print "Setuptools version",version,"or greater has been installed." print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' def update_md5(filenames): """Update our built-in md5 registry""" import re for name in filenames: base = os.path.basename(name) f = open(name,'rb') md5_data[base] = md5(f.read()).hexdigest() f.close() data = [" %r: %r,\n" % it for it in md5_data.items()] data.sort() repl = "".join(data) import inspect srcfile = inspect.getsourcefile(sys.modules[__name__]) f = open(srcfile, 'rb'); src = f.read(); f.close() match = re.search("\nmd5_data = {\n([^}]+)}", src) if not match: print >>sys.stderr, "Internal error!" sys.exit(2) src = src[:match.start(1)] + repl + src[match.end(1):] f = open(srcfile,'w') f.write(src) f.close() if __name__=='__main__': if len(sys.argv)>2 and sys.argv[1]=='--md5update': update_md5(sys.argv[2:]) else: main(sys.argv[1:]) zope2.13-2.13.21/source/ZODB3/buildout.cfg0000644000175000017500000000057212214017464016575 0ustar arnauarnau[buildout] develop = . parts = test scripts versions = versions [versions] zc.recipe.testrunner = 1.3.0 [test] recipe = zc.recipe.testrunner eggs = ZODB3 [test] initialization = import os, tempfile try: os.mkdir('tmp') except: pass tempfile.tempdir = os.path.abspath('tmp') defaults = ['--all'] [scripts] recipe = zc.recipe.egg eggs = ZODB3 [test] interpreter = py zope2.13-2.13.21/source/ZODB3/bootstrap.py0000644000175000017500000000410412214017464016647 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources is_jython = sys.platform.startswith('java') if is_jython: import subprocess cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set if is_jython: assert subprocess.Popen( [sys.executable] + ['-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout'], env = dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/ZODB3/release.py0000644000175000017500000000446112214017464016260 0ustar arnauarnau#! /usr/bin/env python """Update version numbers and release dates for the next release. usage: release.py version date version should be a string like "3.2.0c1" date should be a string like "23-Sep-2003" The following files are updated: - setup.py - NEWS.txt - doc/guide/zodb.tex - src/ZEO/__init__.py - src/ZEO/version.txt - src/ZODB/__init__.py """ import fileinput import os import re # In file filename, replace the first occurrence of regexp pat with # string repl. def replace(filename, pat, repl): from sys import stderr as e # fileinput hijacks sys.stdout foundone = False for line in fileinput.input([filename], inplace=True, backup="~"): if foundone: print line, else: match = re.search(pat, line) if match is not None: foundone = True new = re.sub(pat, repl, line) print new, print >> e, "In %s, replaced:" % filename print >> e, " ", repr(line) print >> e, " ", repr(new) else: print line, if not foundone: print >> e, "*" * 60, "Oops!" print >> e, " Failed to find %r in %r" % (pat, filename) # Nothing in our codebase cares about ZEO/version.txt. Jeremy said # someone asked for it so that a shell script could read up the ZEO # version easily. # Before ZODB 3.4, the ZEO version was one smaller than the ZODB version; # e.g., ZEO 2.2.7 shipped with ZODB 3.2.7. Now ZEO and ZODB share their # version number. def write_zeoversion(path, version): f = open(path, "w") print >> f, version f.close() def main(args): version, date = args replace("setup.py", r'^VERSION = "\S+"$', 'VERSION = "%s"' % version) replace("src/ZODB/__init__.py", r'__version__ = "\S+"', '__version__ = "%s"' % version) replace("src/ZEO/__init__.py", r'version = "\S+"', 'version = "%s"' % version) write_zeoversion("src/ZEO/version.txt", version) replace("NEWS.txt", r"^Release date: .*", "Release date: %s" % date) replace("doc/guide/zodb.tex", r"release{\S+}", "release{%s}" % version) if __name__ == "__main__": import sys main(sys.argv[1:]) zope2.13-2.13.21/source/ZODB3/src/0000755000175000017500000000000012214017464015050 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZODB3.egg-info/0000755000175000017500000000000012214017464017363 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZODB3.egg-info/PKG-INFO0000644000175000017500000006077712214017464020501 0ustar arnauarnauMetadata-Version: 1.0 Name: ZODB3 Version: 3.10.5 Summary: Zope Object Database: object database and persistence Home-page: UNKNOWN Author: Zope Foundation and Contributors Author-email: zodb-dev@zope.org License: ZPL 2.1 Description: The Zope Object Database provides an object-oriented database for Python that provides a high-degree of transparency. Applications can take advantage of object database features with few, if any, changes to application logic. ZODB includes features such as a plugable storage interface, rich transaction support, and undo. .. contents:: ==== ZODB ==== Introduction ============ The ZODB package provides a set of tools for using the Zope Object Database (ZODB). The components you get with the ZODB release are as follows: - Core ZODB, including the persistence machinery - Standard storages such as FileStorage - The persistent BTrees modules - ZEO, for scalability needs - documentation (needs a lot more work) Our primary development platforms are Linux, Mac OS X, and Windows XP. The test suite should pass without error on all of these platforms, although it can take a long time on Windows -- longer if you use ZoneAlarm. Many particularly slow tests are skipped unless you pass --all as an argument to test.py. Compatibility ============= ZODB 3.10 requires Python 2.5 or later. Note -- When using ZEO and upgrading from Python 2.4, you need to upgrade clients and servers at the same time, or upgrade clients first and then servers. Clients running Python 2.5 or 2.6 will work with servers running Python 2.4. Clients running Python 2.4 won't work properly with servers running Python 2.5 or later due to changes in the way Python implements exceptions. ZODB ZEO clients from ZODB 3.2 on can talk to ZODB 3.10 servers. ZODB ZEO 3.10 Clients can talk to ZODB 3.8, 3.9, and 3.10 ZEO servers. Note -- ZEO 3.10 servers don't support undo for older clients. Prerequisites ============= You must have Python installed. If you're using a system Python install, make sure development support is installed too. You also need the transaction, zc.lockfile, ZConfig, zdaemon, zope.event, zope.interface, zope.proxy and zope.testing packages. If you don't have them and you can connect to the Python Package Index, then these will be installed for you if you don't have them. Installation ============ ZODB is released as a distutils package. The easiest ways to build and install it are to use `easy_install `_, or `zc.buildout `_. To install by hand, first install the dependencies, ZConfig, zdaemon, zope.interface, zope.proxy and zope.testing. These can be found in the `Python Package Index `_. To run the tests, use the test setup command:: python setup.py test It will download dependencies if needed. If this happens, ou may get an import error when the test command gets to looking for tests. Try running the test command a second time and you should see the tests run. :: python setup.py test To install, use the install command:: python setup.py install Testing for Developers ====================== The ZODB checkouts are `buildouts `_. When working from a ZODB checkout, first run the bootstrap.py script to initialize the buildout: % python bootstrap.py and then use the buildout script to build ZODB and gather the dependencies: % bin/buildout This creates a test script: % bin/test -v This command will run all the tests, printing a single dot for each test. When it finishes, it will print a test summary. The exact number of tests can vary depending on platform and available third-party libraries.:: Ran 1182 tests in 241.269s OK The test script has many more options. Use the ``-h`` or ``--help`` options to see a file list of options. The default test suite omits several tests that depend on third-party software or that take a long time to run. To run all the available tests use the ``--all`` option. Running all the tests takes much longer.:: Ran 1561 tests in 1461.557s OK Maintenance scripts ------------------- Several scripts are provided with the ZODB and can help for analyzing, debugging, checking for consistency, summarizing content, reporting space used by objects, doing backups, artificial load testing, etc. Look at the ZODB/script directory for more informations. History ======= The historical version numbering schemes for ZODB and ZEO are complicated. Starting with ZODB 3.4, the ZODB and ZEO version numbers are the same. In the ZODB 3.1 through 3.3 lines, the ZEO version number was "one smaller" than the ZODB version number; e.g., ZODB 3.2.7 included ZEO 2.2.7. ZODB and ZEO were distinct releases prior to ZODB 3.1, and had independent version numbers. Historically, ZODB was distributed as a part of the Zope application server. Jim Fulton's paper at the Python conference in 2000 described a version of ZODB he called ZODB 3, based on an earlier persistent object system called BoboPOS. The earliest versions of ZODB 3 were released with Zope 2.0. Andrew Kuchling extracted ZODB from Zope 2.4.1 and packaged it for use by standalone Python programs. He called this version "StandaloneZODB". Andrew's guide to using ZODB is included in the Doc directory. This version of ZODB was hosted at http://sf.net/projects/zodb. It supported Python 1.5.2, and might still be of interest to users of this very old Python version. Zope Corporation released a version of ZODB called "StandaloneZODB 1.0" in Feb. 2002. This release was based on Andrew's packaging, but built from the same CVS repository as Zope. It is roughly equivalent to the ZODB in Zope 2.5. Why not call the current release StandaloneZODB? The name StandaloneZODB is a bit of a mouthful. The standalone part of the name suggests that the Zope version is the real version and that this is an afterthought, which isn't the case. So we're calling this release "ZODB". We also worked on a ZODB4 package for a while and made a couple of alpha releases. We've now abandoned that effort, because we didn't have the resources to pursue ot while also maintaining ZODB(3). License ======= ZODB is distributed under the Zope Public License, an OSI-approved open source license. Please see the LICENSE.txt file for terms and conditions. The ZODB/ZEO Programming Guide included in the documentation is a modified version of Andrew Kuchling's original guide, provided under the terms of the GNU Free Documentation License. More information ================ We maintain a Wiki page about all things ZODB, including status on future directions for ZODB. Please see http://wiki.zope.org/ZODB/FrontPage and feel free to contribute your comments. There is a Mailman mailing list in place to discuss all issues related to ZODB. You can send questions to zodb-dev@zope.org or subscribe at http://lists.zope.org/mailman/listinfo/zodb-dev and view its archives at http://lists.zope.org/pipermail/zodb-dev Note that Zope Corp mailing lists have a subscriber-only posting policy. Andrew's ZODB Programmers Guide is made available in several forms, including DVI and HTML. To view it online, point your browser at the file Doc/guide/zodb/index.html Bugs and Patches ================ Bug reports and patches should be added to the Launchpad: https://launchpad.net/zodb .. Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 End: ================ Change History ================ 3.10.5 (2011-11-19) =================== Bugs Fixed ---------- - Conflict resolution failed when state included cross-database persistent references with classes that couldn't be imported. 3.10.4 (2011-11-17) =================== Bugs Fixed ---------- - Conflict resolution failed when state included persistent references with classes that couldn't be imported. 3.10.3 (2011-04-12) =================== Bugs Fixed ---------- - "activity monitor not updated for subconnections when connection returned to pool" https://bugs.launchpad.net/zodb/+bug/737198 - "Blob temp file get's removed before it should", https://bugs.launchpad.net/zodb/+bug/595378 A way this to happen is that a transaction is aborted after the commit process has started. I don't know how this would happen in the wild. In 3.10.3, the ZEO tpc_abort call to the server is changed to be synchronous, which should address this case. Maybe there's another case. Performance enhancements ------------------------ - Improved ZEO client cache implementation to make it less likely to evict objects that are being used. - Small (possibly negligable) reduction in CPU in ZEO storage servers to service object loads and in networking code. 3.10.2 (2011-02-12) =================== Bugs Fixed ---------- - 3.10 introduced an optimization to try to address BTree conflict errors arrising for basing BTree keys on object ids. The optimization caused object ids allocated in aborted transactions to be reused. Unfortunately, this optimzation led to some rather severe failures in some applications. The symptom is a conflict error in which one of the serials mentioned is zero. This optimization has been removed. See (for example): https://bugs.launchpad.net/zodb/+bug/665452 - ZEO server transaction timeouts weren't logged as critical. https://bugs.launchpad.net/zodb/+bug/670986 3.10.1 (2010-10-27) =================== Bugs Fixed ---------- - When a transaction rolled back a savepoint after adding objects and subsequently added more objects and committed, an error could be raised "ValueError: A different object already has the same oid" causing the transaction to fail. Worse, this could leave a database in a state where subsequent transactions in the same process would fail. https://bugs.launchpad.net/zodb/+bug/665452 - Unix domain sockets didn't work for ZEO (since the addition of IPv6 support). https://bugs.launchpad.net/zodb/+bug/663259 - Removed a missfeature that can cause performance problems when using an external garbage collector with ZEO. When objects were deleted from a storage, invalidations were sent to clients. This makes no sense. It's wildly unlikely that the other connections/clients have copies of the garbage. In normal storage garbage collection, we don't send invalidations. There's no reason to send them when an external garbage collector is used. - ZEO client cache simulation misshandled invalidations causing incorrect statistics and errors. 3.10.0 (2010-10-08) =================== New Features ------------ - There are a number of performance enhancements for ZEO storage servers. - FileStorage indexes use a new format. They are saved and loaded much faster and take less space. Old indexes can still be read, but new indexes won't be readable by older versions of ZODB. - The API for undoing multiple transactions has changed. To undo multiple transactions in a single transaction, pass a list of transaction identifiers to a database's undoMultiple method. Calling a database's undo method multiple times in the same transaction now raises an exception. - The ZEO protocol for undo has changed. The only user-visible consequence of this is that when ZODB 3.10 ZEO servers won't support undo for older clients. - The storage API (IStorage) has been tightened. Now, storages should raise a StorageTransactionError when invalid transactions are passed to tpc_begin, tpc_vote, or tpc_finish. - ZEO clients (``ClientStorage`` instances) now work in forked processes, including those created via ``multiprocessing.Process`` instances. - Broken objects now provide the IBroken interface. - As a convenience, you can now pass an integer port as an address to the ZEO ClientStorage constructor. - As a convenience, there's a new ``client`` function in the ZEO package for constructing a ClientStorage instance. It takes the same arguments as the ClientStorage constructor. - DemoStorages now accept constructor athuments, close_base_on_close and close_changes_on_close, to control whether underlying storages are closed when the DemoStorage is closed. https://bugs.launchpad.net/zodb/+bug/118512 - Removed the dependency on zope.proxy. - Removed support for the _p_independent mini framework, which was made moot by the introduction of multi-version concurrency control several years ago. - Added support for the transaction retry convenience (transaction-manager attempts method) introduced in the ``transaction`` 1.1.0 release. - Enhanced the database opening conveniences: - You can now pass storage keyword arguments to ZODB.DB and ZODB.connection. - You can now pass None (rather than a storage or file name) to get a database with a mapping storage. - Databases now warn when committing very large records (> 16MB). This is to try to warn people of likely design mistakes. There is a new option (large_record_size/large-record-size) to control the record size at which the warning is issued. - Added support for wrapper storages that transform pickle data. Applications for this include compression and encryption. An example wrapper storage implementation, ZODB.tests.hexstorage, was included for testing. It is important that storage implementations not assume that storages contain pickles. Renamed IStorageDB to IStorageWrapper and expanded it to provide methods for transforming and untransforming data records. Storages implementations should use these methods to get pickle data from stored records. - Deprecated ZODB.interfaces.StorageStopIteration. Storage iterator implementations should just raise StopIteration, which means they can now be implemented as generators. - The filestorage packer configuration option noe accepts values of the form ``modname:expression``, allowing the use of packer factories with options. - Added a new API that allows applications to make sure that current data are read. For example, with:: self._p_jar.readCurrent(ob) A conflict error will be raised if the version of ob read by the transaction isn't current when the transaction is committed. Normally, ZODB only assures that objects read are consistent, but not necessarily up to date. Checking whether an object is up to date is important when information read from one object is used to update another. BTrees are an important case of reading one object to update another. Internal nodes are read to decide which leave notes are updated when a BTree is updated. BTrees now use this new API to make sure that internal nodes are up to date on updates. - When transactions are aborted, new object ids allocated during the transaction are saved and used in subsequent transactions. This can help in situations where object ids are used as BTree keys and the sequential allocation of object ids leads to conflict errors. - ZEO servers now support a server_status method for for getting information on the number of clients, lock requests and general statistics. - ZEO clients now support a client_label constructor argument and client-label configuration-file option to specify a label for a client in server logs. This makes it easier to identify specific clients corresponding to server log entries, especially when there are multiple clients originating from the same machine. - Improved ZEO server commit lock logging. Now, locking activity is logged at the debug level until the number of waiting lock requests gets above 3. Log at the critical level when the number of waiting lock requests gets above 9. - The file-storage backup script, repozo, will now create a backup index file if an output file name is given via the --output/-o option. - Added a '--kill-old-on-full' argument to the repozo backup options: if passed, remove any older full or incremental backup files from the repository after doing a full backup. (https://bugs.launchpad.net/zope2/+bug/143158) - The mkzeoinst script has been moved to a separate project: http://pypi.python.org/pypi/zope.mkzeoinstance and is no-longer included with ZODB. - Removed untested unsupported dbmstorage fossile. - ZEO servers no longer log their pids in every log message. It's just not interesting. :) Bugs fixed ---------- - When a pool timeout was specified for a database and old connections were removed due to timing out, an error occured due to a bug in the connection cleanup logic. - When multi-database connections were no longer used and cleaned up, their subconnections weren't cleaned up properly. - ZEO didn't work with IPv6 addrsses. Added IPv6 support contributed by Martin v. |Lowis|. - A file storage bug could cause ZEO clients to have incorrect information about current object revisions after reconnecting to a database server. - Updated the 'repozo --kill-old-on-full' option to remove any '.index' files corresponding to backups being removed. - ZEO extension methods failed when a client reconnected to a storage. (https://bugs.launchpad.net/zodb/+bug/143344) - Clarified the return Value for lastTransaction in the case when there aren't any transactions. Now a string of 8 nulls (aka "z64") is specified. - Setting _p_changed on a blob wo actually writing anything caused an error. (https://bugs.launchpad.net/zodb/+bug/440234) - The verbose mode of the fstest was broken. (https://bugs.launchpad.net/zodb/+bug/475996) - Object ids created in a savepoint that is rolled back wren't being reused. (https://bugs.launchpad.net/zodb/+bug/588389) - Database connections didn't invalidate cache entries when conflict errors were raised in response to checkCurrentSerialInTransaction errors. Normally, this shouldn't be a problem, since there should be pending invalidations for these oids which will cause the object to be invalidated. There have been issues with ZEO persistent cache management that have caused out of date data to remain in the cache. (It's possible that the last of these were addressed in the 3.10.0b5.) Invalidating read data when there is a conflict error provides some extra insurance. - The interface, ZODB.interfaces.IStorage was incorrect. The store method should never return a sequence of oid and serial pairs. - When a demo storage push method was used to create a new demo storage and the new storage was closed, the original was (incorrectly) closed. - There were numerous bugs in the ZEO cache tracing and analysis code. Cache simulation, while not perfect, seems to be much more accurate now than it was before. The ZEO cache trace statistics and simulation scripts have been given more descriptive names and moved to the ZEO scripts package. - BTree sets and tree sets didn't correctly check values passed to update or to constructors, causing Python to exit under certain circumstances. - Fixed bug in copying a BTrees.Length instance. (https://bugs.launchpad.net/zodb/+bug/516653) - Fixed a serious bug that caused cache failures when run with Python optimization turned on. https://bugs.launchpad.net/zodb/+bug/544305 - When using using a ClientStorage in a Storage server, there was a threading bug that caused clients to get disconnected. - On Mac OS X, clients that connected and disconnected quickly could cause a ZEO server to stop accepting connections, due to a failure to catch errors in the initial part of the connection process. The failure to properly handle exceptions while accepting connections is potentially problematic on other platforms. Fixes: https://bugs.launchpad.net/zodb/+bug/135108 - Object state management wasn't done correctly when classes implemented custom _p_deavtivate methods. (https://bugs.launchpad.net/zodb/+bug/185066) .. |Lowis| unicode:: L \xf6 wis Platform: any Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Topic :: Database Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: Unix Classifier: Framework :: ZODB zope2.13-2.13.21/source/ZODB3/src/ZODB3.egg-info/dependency_links.txt0000644000175000017500000000000112214017464023431 0ustar arnauarnau zope2.13-2.13.21/source/ZODB3/src/ZODB3.egg-info/requires.txt0000644000175000017500000000014512214017464021763 0ustar arnauarnautransaction >=1.1.0 zc.lockfile ZConfig zdaemon zope.event zope.interface [test] zope.testing manuelzope2.13-2.13.21/source/ZODB3/src/ZODB3.egg-info/entry_points.txt0000644000175000017500000000056712214017464022671 0ustar arnauarnau [console_scripts] fsdump = ZODB.FileStorage.fsdump:main fsoids = ZODB.scripts.fsoids:main fsrefs = ZODB.scripts.fsrefs:main fstail = ZODB.scripts.fstail:Main repozo = ZODB.scripts.repozo:main zeopack = ZEO.scripts.zeopack:main runzeo = ZEO.runzeo:main zeopasswd = ZEO.zeopasswd:main zeoctl = ZEO.zeoctl:main zope2.13-2.13.21/source/ZODB3/src/ZODB3.egg-info/top_level.txt0000644000175000017500000000003312214017464022111 0ustar arnauarnauZODB persistent ZEO BTrees zope2.13-2.13.21/source/ZODB3/src/ZODB3.egg-info/SOURCES.txt0000644000175000017500000002120612214017464021250 0ustar arnauarnauCOPYING COPYRIGHT.txt HISTORY.txt LICENSE.txt README.txt bootstrap.py buildout.cfg ez_setup.py log.ini release.py setup.py doc/HOWTO-Blobs-NFS.txt doc/storage.pdf doc/zeo-client-cache-tracing.txt doc/zeo-client-cache.txt doc/zeo.txt doc/zodb-guide.txt src/CHANGES.txt src/BTrees/BTreeItemsTemplate.c src/BTrees/BTreeModuleTemplate.c src/BTrees/BTreeTemplate.c src/BTrees/BucketTemplate.c src/BTrees/Development.txt src/BTrees/IFBTree.py src/BTrees/IIBTree.py src/BTrees/IOBTree.py src/BTrees/Interfaces.py src/BTrees/LFBTree.py src/BTrees/LLBTree.py src/BTrees/LOBTree.py src/BTrees/Length.py src/BTrees/MergeTemplate.c src/BTrees/OIBTree.py src/BTrees/OLBTree.py src/BTrees/OOBTree.py src/BTrees/SetOpTemplate.c src/BTrees/SetTemplate.c src/BTrees/TreeSetTemplate.c src/BTrees/_IFBTree.c src/BTrees/_IIBTree.c src/BTrees/_IOBTree.c src/BTrees/_LFBTree.c src/BTrees/_LLBTree.c src/BTrees/_LOBTree.c src/BTrees/_OIBTree.c src/BTrees/_OLBTree.c src/BTrees/_OOBTree.c src/BTrees/__init__.py src/BTrees/_fsBTree.c src/BTrees/check.py src/BTrees/floatvaluemacros.h src/BTrees/fsBTree.py src/BTrees/intkeymacros.h src/BTrees/intvaluemacros.h src/BTrees/objectkeymacros.h src/BTrees/objectvaluemacros.h src/BTrees/py24compat.h src/BTrees/sorters.c src/BTrees/tests/__init__.py src/BTrees/tests/testBTrees.py src/BTrees/tests/testBTreesUnicode.py src/BTrees/tests/testConflict.py src/BTrees/tests/testLength.py src/BTrees/tests/testSetOps.py src/BTrees/tests/test_btreesubclass.py src/BTrees/tests/test_check.py src/BTrees/tests/test_compare.py src/BTrees/tests/test_fsBTree.py src/ZEO/ClientStorage.py src/ZEO/Exceptions.py src/ZEO/ServerStub.py src/ZEO/StorageServer.py src/ZEO/TransactionBuffer.py src/ZEO/__init__.py src/ZEO/cache.py src/ZEO/component.xml src/ZEO/hash.py src/ZEO/interfaces.py src/ZEO/monitor.py src/ZEO/protocol.txt src/ZEO/runzeo.py src/ZEO/schema.xml src/ZEO/util.py src/ZEO/version.txt src/ZEO/zeoctl.py src/ZEO/zeoctl.xml src/ZEO/zeopasswd.py src/ZEO/auth/__init__.py src/ZEO/auth/auth_digest.py src/ZEO/auth/base.py src/ZEO/auth/hmac.py src/ZEO/scripts/README.txt src/ZEO/scripts/__init__.py src/ZEO/scripts/cache_simul.py src/ZEO/scripts/cache_stats.py src/ZEO/scripts/parsezeolog.py src/ZEO/scripts/tests.py src/ZEO/scripts/timeout.py src/ZEO/scripts/zeopack.py src/ZEO/scripts/zeopack.test src/ZEO/scripts/zeoqueue.py src/ZEO/scripts/zeoreplay.py src/ZEO/scripts/zeoserverlog.py src/ZEO/scripts/zeoup.py src/ZEO/tests/Cache.py src/ZEO/tests/CommitLockTests.py src/ZEO/tests/ConnectionTests.py src/ZEO/tests/InvalidationTests.py src/ZEO/tests/IterationTests.py src/ZEO/tests/TestThread.py src/ZEO/tests/ThreadTests.py src/ZEO/tests/__init__.py src/ZEO/tests/auth_plaintext.py src/ZEO/tests/client-config.test src/ZEO/tests/drop_cache_rather_than_verify.txt src/ZEO/tests/forker.py src/ZEO/tests/invalidation-age.txt src/ZEO/tests/protocols.test src/ZEO/tests/registerDB.test src/ZEO/tests/servertesting.py src/ZEO/tests/speed.py src/ZEO/tests/stress.py src/ZEO/tests/testAuth.py src/ZEO/tests/testConnection.py src/ZEO/tests/testConversionSupport.py src/ZEO/tests/testMonitor.py src/ZEO/tests/testTransactionBuffer.py src/ZEO/tests/testZEO.py src/ZEO/tests/testZEO2.py src/ZEO/tests/testZEOOptions.py src/ZEO/tests/test_cache.py src/ZEO/tests/zdoptions.test src/ZEO/tests/zeo-fan-out.test src/ZEO/tests/zeo_blob_cache.test src/ZEO/tests/zeoserver.py src/ZEO/zrpc/__init__.py src/ZEO/zrpc/_hmac.py src/ZEO/zrpc/client.py src/ZEO/zrpc/connection.py src/ZEO/zrpc/error.py src/ZEO/zrpc/log.py src/ZEO/zrpc/marshal.py src/ZEO/zrpc/server.py src/ZEO/zrpc/smac.py src/ZEO/zrpc/trigger.py src/ZODB/ActivityMonitor.py src/ZODB/BaseStorage.py src/ZODB/ConflictResolution.py src/ZODB/ConflictResolution.txt src/ZODB/Connection.py src/ZODB/DB.py src/ZODB/DemoStorage.py src/ZODB/DemoStorage.test src/ZODB/ExportImport.py src/ZODB/MappingStorage.py src/ZODB/POSException.py src/ZODB/UndoLogCompatible.py src/ZODB/__init__.py src/ZODB/blob.py src/ZODB/broken.py src/ZODB/collaborations.txt src/ZODB/component.xml src/ZODB/config.py src/ZODB/config.xml src/ZODB/conversionhack.py src/ZODB/cross-database-references.txt src/ZODB/fsIndex.py src/ZODB/fsrecover.py src/ZODB/fstools.py src/ZODB/historical_connections.txt src/ZODB/interfaces.py src/ZODB/loglevels.py src/ZODB/persistentclass.py src/ZODB/persistentclass.txt src/ZODB/serialize.py src/ZODB/storage.xml src/ZODB/subtransactions.txt src/ZODB/transact.py src/ZODB/utils.py src/ZODB/utils.txt src/ZODB/FileStorage/FileStorage.py src/ZODB/FileStorage/__init__.py src/ZODB/FileStorage/format.py src/ZODB/FileStorage/fsdump.py src/ZODB/FileStorage/fsoids.py src/ZODB/FileStorage/fspack.py src/ZODB/FileStorage/interfaces.py src/ZODB/FileStorage/iterator.test src/ZODB/FileStorage/tests.py src/ZODB/FileStorage/zconfig.txt src/ZODB/scripts/README.txt src/ZODB/scripts/__init__.py src/ZODB/scripts/analyze.py src/ZODB/scripts/checkbtrees.py src/ZODB/scripts/fsoids.py src/ZODB/scripts/fsrefs.py src/ZODB/scripts/fsstats.py src/ZODB/scripts/fstail.py src/ZODB/scripts/fstest.py src/ZODB/scripts/migrate.py src/ZODB/scripts/migrateblobs.py src/ZODB/scripts/netspace.py src/ZODB/scripts/referrers.py src/ZODB/scripts/repozo.py src/ZODB/scripts/space.py src/ZODB/scripts/zodbload.py src/ZODB/scripts/manual_tests/test-checker.fs src/ZODB/scripts/manual_tests/testfstest.py src/ZODB/scripts/tests/__init__.py src/ZODB/scripts/tests/fstail.txt src/ZODB/scripts/tests/referrers.txt src/ZODB/scripts/tests/test_doc.py src/ZODB/scripts/tests/test_fstest.py src/ZODB/scripts/tests/test_repozo.py src/ZODB/tests/BasicStorage.py src/ZODB/tests/ConflictResolution.py src/ZODB/tests/Corruption.py src/ZODB/tests/HistoryStorage.py src/ZODB/tests/IExternalGC.test src/ZODB/tests/IteratorStorage.py src/ZODB/tests/MTStorage.py src/ZODB/tests/MVCCMappingStorage.py src/ZODB/tests/MinPO.py src/ZODB/tests/PackableStorage.py src/ZODB/tests/PersistentStorage.py src/ZODB/tests/ReadOnlyStorage.py src/ZODB/tests/RecoveryStorage.py src/ZODB/tests/RevisionStorage.py src/ZODB/tests/StorageTestBase.py src/ZODB/tests/Synchronization.py src/ZODB/tests/TransactionalUndoStorage.py src/ZODB/tests/__init__.py src/ZODB/tests/blob_basic.txt src/ZODB/tests/blob_connection.txt src/ZODB/tests/blob_consume.txt src/ZODB/tests/blob_importexport.txt src/ZODB/tests/blob_layout.txt src/ZODB/tests/blob_packing.txt src/ZODB/tests/blob_tempdir.txt src/ZODB/tests/blob_transaction.txt src/ZODB/tests/blobstorage_packing.txt src/ZODB/tests/component.xml src/ZODB/tests/dangle.py src/ZODB/tests/dbopen.txt src/ZODB/tests/hexstorage.py src/ZODB/tests/loggingsupport.py src/ZODB/tests/multidb.txt src/ZODB/tests/sampledm.py src/ZODB/tests/speed.py src/ZODB/tests/synchronizers.txt src/ZODB/tests/testActivityMonitor.py src/ZODB/tests/testBroken.py src/ZODB/tests/testCache.py src/ZODB/tests/testConfig.py src/ZODB/tests/testConnection.py src/ZODB/tests/testConnectionSavepoint.py src/ZODB/tests/testConnectionSavepoint.txt src/ZODB/tests/testDB.py src/ZODB/tests/testDemoStorage.py src/ZODB/tests/testFileStorage.py src/ZODB/tests/testMVCCMappingStorage.py src/ZODB/tests/testMappingStorage.py src/ZODB/tests/testPersistentList.py src/ZODB/tests/testPersistentMapping.py src/ZODB/tests/testRecover.py src/ZODB/tests/testSerialize.py src/ZODB/tests/testTimeStamp.py src/ZODB/tests/testUtils.py src/ZODB/tests/testZODB.py src/ZODB/tests/test_cache.py src/ZODB/tests/test_datamanageradapter.py src/ZODB/tests/test_doctest_files.py src/ZODB/tests/test_fsdump.py src/ZODB/tests/test_storage.py src/ZODB/tests/testblob.py src/ZODB/tests/testconflictresolution.py src/ZODB/tests/testcrossdatabasereferences.py src/ZODB/tests/testfsIndex.py src/ZODB/tests/testfsoids.py src/ZODB/tests/testhistoricalconnections.py src/ZODB/tests/testmvcc.py src/ZODB/tests/testpersistentclass.py src/ZODB/tests/util.py src/ZODB/tests/warnhook.py src/ZODB3.egg-info/PKG-INFO src/ZODB3.egg-info/SOURCES.txt src/ZODB3.egg-info/dependency_links.txt src/ZODB3.egg-info/entry_points.txt src/ZODB3.egg-info/not-zip-safe src/ZODB3.egg-info/requires.txt src/ZODB3.egg-info/top_level.txt src/persistent/README.txt src/persistent/TimeStamp.c src/persistent/__init__.py src/persistent/cPersistence.c src/persistent/cPersistence.h src/persistent/cPickleCache.c src/persistent/dict.py src/persistent/interfaces.py src/persistent/list.py src/persistent/mapping.py src/persistent/py24compat.h src/persistent/ring.c src/persistent/ring.h src/persistent/wref.py src/persistent/tests/__init__.py src/persistent/tests/persistent.txt src/persistent/tests/testPersistent.py src/persistent/tests/test_PickleCache.py src/persistent/tests/test_list.py src/persistent/tests/test_mapping.py src/persistent/tests/test_overriding_attrs.py src/persistent/tests/test_persistent.py src/persistent/tests/test_pickle.py src/persistent/tests/test_wref.py src/persistent/tests/utils.pyzope2.13-2.13.21/source/ZODB3/src/ZODB3.egg-info/not-zip-safe0000644000175000017500000000000112214017464021611 0ustar arnauarnau zope2.13-2.13.21/source/ZODB3/src/persistent/0000755000175000017500000000000012214017464017250 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/persistent/dict.py0000644000175000017500000000135712214017464020553 0ustar arnauarnau############################################################################## # # Copyright Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # persistent.dict is deprecated. Use persistent.mapping from persistent.mapping import PersistentMapping as PersistentDict zope2.13-2.13.21/source/ZODB3/src/persistent/ring.c0000644000175000017500000000347012214017464020357 0ustar arnauarnau/***************************************************************************** Copyright (c) 2003 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #define RING_C "$Id: ring.c 113734 2010-06-21 15:33:46Z ctheune $\n" /* Support routines for the doubly-linked list of cached objects. The cache stores a doubly-linked list of persistent objects, with space for the pointers allocated in the objects themselves. The cache stores the distinguished head of the list, which is not a valid persistent object. The next pointers traverse the ring in order starting with the least recently used object. The prev pointers traverse the ring in order starting with the most recently used object. */ #include "Python.h" #include "ring.h" void ring_add(CPersistentRing *ring, CPersistentRing *elt) { assert(!elt->r_next); elt->r_next = ring; elt->r_prev = ring->r_prev; ring->r_prev->r_next = elt; ring->r_prev = elt; } void ring_del(CPersistentRing *elt) { elt->r_next->r_prev = elt->r_prev; elt->r_prev->r_next = elt->r_next; elt->r_next = NULL; elt->r_prev = NULL; } void ring_move_to_head(CPersistentRing *ring, CPersistentRing *elt) { elt->r_prev->r_next = elt->r_next; elt->r_next->r_prev = elt->r_prev; elt->r_next = ring; elt->r_prev = ring->r_prev; ring->r_prev->r_next = elt; ring->r_prev = elt; } zope2.13-2.13.21/source/ZODB3/src/persistent/list.py0000644000175000017500000000561112214017464020600 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Python implementation of persistent list. $Id: list.py 113734 2010-06-21 15:33:46Z ctheune $""" import persistent from UserList import UserList class PersistentList(UserList, persistent.Persistent): __super_setitem = UserList.__setitem__ __super_delitem = UserList.__delitem__ __super_setslice = UserList.__setslice__ __super_delslice = UserList.__delslice__ __super_iadd = UserList.__iadd__ __super_imul = UserList.__imul__ __super_append = UserList.append __super_insert = UserList.insert __super_pop = UserList.pop __super_remove = UserList.remove __super_reverse = UserList.reverse __super_sort = UserList.sort __super_extend = UserList.extend def __setitem__(self, i, item): self.__super_setitem(i, item) self._p_changed = 1 def __delitem__(self, i): self.__super_delitem(i) self._p_changed = 1 def __setslice__(self, i, j, other): self.__super_setslice(i, j, other) self._p_changed = 1 def __delslice__(self, i, j): self.__super_delslice(i, j) self._p_changed = 1 def __iadd__(self, other): L = self.__super_iadd(other) self._p_changed = 1 return L def __imul__(self, n): L = self.__super_imul(n) self._p_changed = 1 return L def append(self, item): self.__super_append(item) self._p_changed = 1 def insert(self, i, item): self.__super_insert(i, item) self._p_changed = 1 def pop(self, i=-1): rtn = self.__super_pop(i) self._p_changed = 1 return rtn def remove(self, item): self.__super_remove(item) self._p_changed = 1 def reverse(self): self.__super_reverse() self._p_changed = 1 def sort(self, *args, **kwargs): self.__super_sort(*args, **kwargs) self._p_changed = 1 def extend(self, other): self.__super_extend(other) self._p_changed = 1 # This works around a bug in Python 2.1.x (up to 2.1.2 at least) where the # __cmp__ bogusly raises a RuntimeError, and because this is an extension # class, none of the rich comparison stuff works anyway. def __cmp__(self, other): return cmp(self.data, self._UserList__cast(other)) zope2.13-2.13.21/source/ZODB3/src/persistent/mapping.py0000644000175000017500000000672212214017464021264 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Python implementation of persistent base types $Id: mapping.py 113734 2010-06-21 15:33:46Z ctheune $""" import persistent import UserDict class default(object): def __init__(self, func): self.func = func def __get__(self, inst, class_): if inst is None: return self return self.func(inst) class PersistentMapping(UserDict.IterableUserDict, persistent.Persistent): """A persistent wrapper for mapping objects. This class allows wrapping of mapping objects so that object changes are registered. As a side effect, mapping objects may be subclassed. A subclass of PersistentMapping or any code that adds new attributes should not create an attribute named _container. This is reserved for backwards compatibility reasons. """ # UserDict provides all of the mapping behavior. The # PersistentMapping class is responsible marking the persistent # state as changed when a method actually changes the state. At # the mapping API evolves, we may need to add more methods here. __super_delitem = UserDict.IterableUserDict.__delitem__ __super_setitem = UserDict.IterableUserDict.__setitem__ __super_clear = UserDict.IterableUserDict.clear __super_update = UserDict.IterableUserDict.update __super_setdefault = UserDict.IterableUserDict.setdefault __super_pop = UserDict.IterableUserDict.pop __super_popitem = UserDict.IterableUserDict.popitem def __delitem__(self, key): self.__super_delitem(key) self._p_changed = 1 def __setitem__(self, key, v): self.__super_setitem(key, v) self._p_changed = 1 def clear(self): self.__super_clear() self._p_changed = 1 def update(self, b): self.__super_update(b) self._p_changed = 1 def setdefault(self, key, failobj=None): # We could inline all of UserDict's implementation into the # method here, but I'd rather not depend at all on the # implementation in UserDict (simple as it is). if not self.has_key(key): self._p_changed = 1 return self.__super_setdefault(key, failobj) def pop(self, key, *args): self._p_changed = 1 return self.__super_pop(key, *args) def popitem(self): self._p_changed = 1 return self.__super_popitem() # Old implementations used _container rather than data. # Use a descriptor to provide data when we have _container instead @default def data(self): # We don't want to cause a write on read, so wer're careful not to # do anything that would cause us to become marked as changed, however, # if we're modified, then the saved record will have data, not # _container. data = self.__dict__.pop('_container') self.__dict__['data'] = data return data zope2.13-2.13.21/source/ZODB3/src/persistent/cPersistence.c0000644000175000017500000010324412214017464022047 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ static char cPersistence_doc_string[] = "Defines Persistent mixin class for persistent objects.\n" "\n" "$Id: cPersistence.c 116138 2010-09-02 13:55:38Z jim $\n"; #include "cPersistence.h" #include "structmember.h" struct ccobject_head_struct { CACHE_HEAD }; /* These two objects are initialized when the module is loaded */ static PyObject *TimeStamp, *py_simple_new; /* Strings initialized by init_strings() below. */ static PyObject *py_keys, *py_setstate, *py___dict__, *py_timeTime; static PyObject *py__p_changed, *py__p_deactivate; static PyObject *py___getattr__, *py___setattr__, *py___delattr__; static PyObject *py___slotnames__, *copy_reg_slotnames, *__newobj__; static PyObject *py___getnewargs__, *py___getstate__; static int init_strings(void) { #define INIT_STRING(S) \ if (!(py_ ## S = PyString_InternFromString(#S))) \ return -1; INIT_STRING(keys); INIT_STRING(setstate); INIT_STRING(timeTime); INIT_STRING(__dict__); INIT_STRING(_p_changed); INIT_STRING(_p_deactivate); INIT_STRING(__getattr__); INIT_STRING(__setattr__); INIT_STRING(__delattr__); INIT_STRING(__slotnames__); INIT_STRING(__getnewargs__); INIT_STRING(__getstate__); #undef INIT_STRING return 0; } #ifdef Py_DEBUG static void fatal_1350(cPersistentObject *self, const char *caller, const char *detail) { char buf[1000]; PyOS_snprintf(buf, sizeof(buf), "cPersistence.c %s(): object at %p with type %.200s\n" "%s.\n" "The only known cause is multiple threads trying to ghost and\n" "unghost the object simultaneously.\n" "That's not legal, but ZODB can't stop it.\n" "See Collector #1350.\n", caller, self, self->ob_type->tp_name, detail); Py_FatalError(buf); } #endif static void ghostify(cPersistentObject*); /* Load the state of the object, unghostifying it. Upon success, return 1. * If an error occurred, re-ghostify the object and return -1. */ static int unghostify(cPersistentObject *self) { if (self->state < 0 && self->jar) { PyObject *r; /* Is it ever possible to not have a cache? */ if (self->cache) { /* Create a node in the ring for this unghostified object. */ self->cache->non_ghost_count++; self->cache->total_estimated_size += _estimated_size_in_bytes(self->estimated_size); ring_add(&self->cache->ring_home, &self->ring); Py_INCREF(self); } /* set state to CHANGED while setstate() call is in progress to prevent a recursive call to _PyPersist_Load(). */ self->state = cPersistent_CHANGED_STATE; /* Call the object's __setstate__() */ r = PyObject_CallMethod(self->jar, "setstate", "O", (PyObject *)self); if (r == NULL) { ghostify(self); return -1; } self->state = cPersistent_UPTODATE_STATE; Py_DECREF(r); if (self->cache && self->ring.r_next == NULL) { #ifdef Py_DEBUG fatal_1350(self, "unghostify", "is not in the cache despite that we just " "unghostified it"); #else PyErr_Format(PyExc_SystemError, "object at %p with type " "%.200s not in the cache despite that we just " "unghostified it", self, self->ob_type->tp_name); return -1; #endif } } return 1; } /****************************************************************************/ static PyTypeObject Pertype; static void accessed(cPersistentObject *self) { /* Do nothing unless the object is in a cache and not a ghost. */ if (self->cache && self->state >= 0 && self->ring.r_next) ring_move_to_head(&self->cache->ring_home, &self->ring); } static void ghostify(cPersistentObject *self) { PyObject **dictptr; /* are we already a ghost? */ if (self->state == cPersistent_GHOST_STATE) return; /* Is it ever possible to not have a cache? */ if (self->cache == NULL) { self->state = cPersistent_GHOST_STATE; return; } if (self->ring.r_next == NULL) { /* There's no way to raise an error in this routine. */ #ifdef Py_DEBUG fatal_1350(self, "ghostify", "claims to be in a cache but isn't"); #else return; #endif } /* If we're ghostifying an object, we better have some non-ghosts. */ assert(self->cache->non_ghost_count > 0); self->cache->non_ghost_count--; self->cache->total_estimated_size -= _estimated_size_in_bytes(self->estimated_size); ring_del(&self->ring); self->state = cPersistent_GHOST_STATE; dictptr = _PyObject_GetDictPtr((PyObject *)self); if (dictptr && *dictptr) { Py_DECREF(*dictptr); *dictptr = NULL; } /* We remove the reference to the just ghosted object that the ring * holds. Note that the dictionary of oids->objects has an uncounted * reference, so if the ring's reference was the only one, this frees * the ghost object. Note further that the object's dealloc knows to * inform the dictionary that it is going away. */ Py_DECREF(self); } static int changed(cPersistentObject *self) { if ((self->state == cPersistent_UPTODATE_STATE || self->state == cPersistent_STICKY_STATE) && self->jar) { PyObject *meth, *arg, *result; static PyObject *s_register; if (s_register == NULL) s_register = PyString_InternFromString("register"); meth = PyObject_GetAttr((PyObject *)self->jar, s_register); if (meth == NULL) return -1; arg = PyTuple_New(1); if (arg == NULL) { Py_DECREF(meth); return -1; } Py_INCREF(self); PyTuple_SET_ITEM(arg, 0, (PyObject *)self); result = PyEval_CallObject(meth, arg); Py_DECREF(arg); Py_DECREF(meth); if (result == NULL) return -1; Py_DECREF(result); self->state = cPersistent_CHANGED_STATE; } return 0; } static int readCurrent(cPersistentObject *self) { if ((self->state == cPersistent_UPTODATE_STATE || self->state == cPersistent_STICKY_STATE) && self->jar && self->oid) { static PyObject *s_readCurrent=NULL; PyObject *r; if (s_readCurrent == NULL) s_readCurrent = PyString_InternFromString("readCurrent"); r = PyObject_CallMethodObjArgs(self->jar, s_readCurrent, self, NULL); if (r == NULL) return -1; Py_DECREF(r); } return 0; } static PyObject * Per__p_deactivate(cPersistentObject *self) { if (self->state == cPersistent_UPTODATE_STATE && self->jar) { PyObject **dictptr = _PyObject_GetDictPtr((PyObject *)self); if (dictptr && *dictptr) { Py_DECREF(*dictptr); *dictptr = NULL; } /* Note that we need to set to ghost state unless we are called directly. Methods that override this need to do the same! */ ghostify(self); } Py_INCREF(Py_None); return Py_None; } static PyObject * Per__p_activate(cPersistentObject *self) { if (unghostify(self) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static int Per_set_changed(cPersistentObject *self, PyObject *v); static PyObject * Per__p_invalidate(cPersistentObject *self) { signed char old_state = self->state; if (old_state != cPersistent_GHOST_STATE) { if (Per_set_changed(self, NULL) < 0) return NULL; ghostify(self); } Py_INCREF(Py_None); return Py_None; } static PyObject * pickle_slotnames(PyTypeObject *cls) { PyObject *slotnames; slotnames = PyDict_GetItem(cls->tp_dict, py___slotnames__); if (slotnames) { int n = PyObject_Not(slotnames); if (n < 0) return NULL; if (n) slotnames = Py_None; Py_INCREF(slotnames); return slotnames; } slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames, (PyObject*)cls, NULL); if (slotnames && !(slotnames == Py_None || PyList_Check(slotnames))) { PyErr_SetString(PyExc_TypeError, "copy_reg._slotnames didn't return a list or None"); Py_DECREF(slotnames); return NULL; } return slotnames; } static PyObject * pickle_copy_dict(PyObject *state) { PyObject *copy, *key, *value; char *ckey; Py_ssize_t pos = 0; copy = PyDict_New(); if (!copy) return NULL; if (!state) return copy; while (PyDict_Next(state, &pos, &key, &value)) { if (key && PyString_Check(key)) { ckey = PyString_AS_STRING(key); if (*ckey == '_' && (ckey[1] == 'v' || ckey[1] == 'p') && ckey[2] == '_') /* skip volatile and persistent */ continue; } if (PyObject_SetItem(copy, key, value) < 0) goto err; } return copy; err: Py_DECREF(copy); return NULL; } static char pickle___getstate__doc[] = "Get the object serialization state\n" "\n" "If the object has no assigned slots and has no instance dictionary, then \n" "None is returned.\n" "\n" "If the object has no assigned slots and has an instance dictionary, then \n" "the a copy of the instance dictionary is returned. The copy has any items \n" "with names starting with '_v_' or '_p_' ommitted.\n" "\n" "If the object has assigned slots, then a two-element tuple is returned. \n" "The first element is either None or a copy of the instance dictionary, \n" "as described above. The second element is a dictionary with items \n" "for each of the assigned slots.\n" ; static PyObject * pickle___getstate__(PyObject *self) { PyObject *slotnames=NULL, *slots=NULL, *state=NULL; PyObject **dictp; int n=0; slotnames = pickle_slotnames(self->ob_type); if (!slotnames) return NULL; dictp = _PyObject_GetDictPtr(self); if (dictp) state = pickle_copy_dict(*dictp); else { state = Py_None; Py_INCREF(state); } if (slotnames != Py_None) { int i; slots = PyDict_New(); if (!slots) goto end; for (i = 0; i < PyList_GET_SIZE(slotnames); i++) { PyObject *name, *value; char *cname; name = PyList_GET_ITEM(slotnames, i); if (PyString_Check(name)) { cname = PyString_AS_STRING(name); if (*cname == '_' && (cname[1] == 'v' || cname[1] == 'p') && cname[2] == '_') /* skip volatile and persistent */ continue; } /* Unclear: Will this go through our getattr hook? */ value = PyObject_GetAttr(self, name); if (value == NULL) PyErr_Clear(); else { int err = PyDict_SetItem(slots, name, value); Py_DECREF(value); if (err < 0) goto end; n++; } } } if (n) state = Py_BuildValue("(NO)", state, slots); end: Py_XDECREF(slotnames); Py_XDECREF(slots); return state; } static int pickle_setattrs_from_dict(PyObject *self, PyObject *dict) { PyObject *key, *value; Py_ssize_t pos = 0; if (!PyDict_Check(dict)) { PyErr_SetString(PyExc_TypeError, "Expected dictionary"); return -1; } while (PyDict_Next(dict, &pos, &key, &value)) { if (PyObject_SetAttr(self, key, value) < 0) return -1; } return 0; } static char pickle___setstate__doc[] = "Set the object serialization state\n\n" "The state should be in one of 3 forms:\n\n" "- None\n\n" " Ignored\n\n" "- A dictionary\n\n" " In this case, the object's instance dictionary will be cleared and \n" " updated with the new state.\n\n" "- A two-tuple with a string as the first element. \n\n" " In this case, the method named by the string in the first element will\n" " be called with the second element.\n\n" " This form supports migration of data formats.\n\n" "- A two-tuple with None or a Dictionary as the first element and\n" " with a dictionary as the second element.\n\n" " If the first element is not None, then the object's instance dictionary \n" " will be cleared and updated with the value.\n\n" " The items in the second element will be assigned as attributes.\n" ; static PyObject * pickle___setstate__(PyObject *self, PyObject *state) { PyObject *slots=NULL; if (PyTuple_Check(state)) { if (!PyArg_ParseTuple(state, "OO:__setstate__", &state, &slots)) return NULL; } if (state != Py_None) { PyObject **dict; dict = _PyObject_GetDictPtr(self); if (!dict) { PyErr_SetString(PyExc_TypeError, "this object has no instance dictionary"); return NULL; } if (!*dict) { *dict = PyDict_New(); if (!*dict) return NULL; } PyDict_Clear(*dict); if (PyDict_Update(*dict, state) < 0) return NULL; } if (slots && pickle_setattrs_from_dict(self, slots) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static char pickle___reduce__doc[] = "Reduce an object to contituent parts for serialization\n" ; static PyObject * pickle___reduce__(PyObject *self) { PyObject *args=NULL, *bargs=NULL, *state=NULL, *getnewargs=NULL; int l, i; getnewargs = PyObject_GetAttr(self, py___getnewargs__); if (getnewargs) { bargs = PyObject_CallFunctionObjArgs(getnewargs, NULL); Py_DECREF(getnewargs); if (!bargs) return NULL; l = PyTuple_Size(bargs); if (l < 0) goto end; } else { PyErr_Clear(); l = 0; } args = PyTuple_New(l+1); if (args == NULL) goto end; Py_INCREF(self->ob_type); PyTuple_SET_ITEM(args, 0, (PyObject*)(self->ob_type)); for (i = 0; i < l; i++) { Py_INCREF(PyTuple_GET_ITEM(bargs, i)); PyTuple_SET_ITEM(args, i+1, PyTuple_GET_ITEM(bargs, i)); } state = PyObject_CallMethodObjArgs(self, py___getstate__, NULL); if (!state) goto end; state = Py_BuildValue("(OON)", __newobj__, args, state); end: Py_XDECREF(bargs); Py_XDECREF(args); return state; } /* Return the object's state, a dict or None. If the object has no dict, it's state is None. Otherwise, return a dict containing all the attributes that don't start with "_v_". The caller should not modify this dict, as it may be a reference to the object's __dict__. */ static PyObject * Per__getstate__(cPersistentObject *self) { /* TODO: Should it be an error to call __getstate__() on a ghost? */ if (unghostify(self) < 0) return NULL; /* TODO: should we increment stickyness? Tim doesn't understand that question. S*/ return pickle___getstate__((PyObject*)self); } /* The Persistent base type provides a traverse function, but not a clear function. An instance of a Persistent subclass will have its dict cleared through subtype_clear(). There is always a cycle between a persistent object and its cache. When the cycle becomes unreachable, the clear function for the cache will break the cycle. Thus, the persistent object need not have a clear function. It would be complex to write a clear function for the objects, if we needed one, because of the reference count tricks done by the cache. */ static void Per_dealloc(cPersistentObject *self) { if (self->state >= 0) { /* If the cache has been cleared, then a non-ghost object isn't in the ring any longer. */ if (self->ring.r_next != NULL) { /* if we're ghostifying an object, we better have some non-ghosts */ assert(self->cache->non_ghost_count > 0); self->cache->non_ghost_count--; self->cache->total_estimated_size -= _estimated_size_in_bytes(self->estimated_size); ring_del(&self->ring); } } if (self->cache) cPersistenceCAPI->percachedel(self->cache, self->oid); Py_XDECREF(self->cache); Py_XDECREF(self->jar); Py_XDECREF(self->oid); self->ob_type->tp_free(self); } static int Per_traverse(cPersistentObject *self, visitproc visit, void *arg) { int err; #define VISIT(SLOT) \ if (SLOT) { \ err = visit((PyObject *)(SLOT), arg); \ if (err) \ return err; \ } VISIT(self->jar); VISIT(self->oid); VISIT(self->cache); #undef VISIT return 0; } /* convert_name() returns a new reference to a string name or sets an exception and returns NULL. */ static PyObject * convert_name(PyObject *name) { #ifdef Py_USING_UNICODE /* The Unicode to string conversion is done here because the existing tp_setattro slots expect a string object as name and we wouldn't want to break those. */ if (PyUnicode_Check(name)) { name = PyUnicode_AsEncodedString(name, NULL, NULL); } else #endif if (!PyString_Check(name)) { PyErr_SetString(PyExc_TypeError, "attribute name must be a string"); return NULL; } else Py_INCREF(name); return name; } /* Returns true if the object requires unghostification. There are several special attributes that we allow access to without requiring that the object be unghostified: __class__ __del__ __dict__ __of__ __setstate__ */ static int unghost_getattr(const char *s) { if (*s++ != '_') return 1; if (*s == 'p') { s++; if (*s == '_') return 0; /* _p_ */ else return 1; } else if (*s == '_') { s++; switch (*s) { case 'c': return strcmp(s, "class__"); case 'd': s++; if (!strcmp(s, "el__")) return 0; /* __del__ */ if (!strcmp(s, "ict__")) return 0; /* __dict__ */ return 1; case 'o': return strcmp(s, "of__"); case 's': return strcmp(s, "setstate__"); default: return 1; } } return 1; } static PyObject* Per_getattro(cPersistentObject *self, PyObject *name) { PyObject *result = NULL; /* guilty until proved innocent */ char *s; name = convert_name(name); if (!name) goto Done; s = PyString_AS_STRING(name); if (unghost_getattr(s)) { if (unghostify(self) < 0) goto Done; accessed(self); } result = PyObject_GenericGetAttr((PyObject *)self, name); Done: Py_XDECREF(name); return result; } /* Exposed as _p_getattr method. Test whether base getattr should be used */ static PyObject * Per__p_getattr(cPersistentObject *self, PyObject *name) { PyObject *result = NULL; /* guilty until proved innocent */ char *s; name = convert_name(name); if (!name) goto Done; s = PyString_AS_STRING(name); if (*s != '_' || unghost_getattr(s)) { if (unghostify(self) < 0) goto Done; accessed(self); result = Py_False; } else result = Py_True; Py_INCREF(result); Done: Py_XDECREF(name); return result; } /* TODO: we should probably not allow assignment of __class__ and __dict__. */ static int Per_setattro(cPersistentObject *self, PyObject *name, PyObject *v) { int result = -1; /* guilty until proved innocent */ char *s; name = convert_name(name); if (!name) goto Done; s = PyString_AS_STRING(name); if (strncmp(s, "_p_", 3) != 0) { if (unghostify(self) < 0) goto Done; accessed(self); if (strncmp(s, "_v_", 3) != 0 && self->state != cPersistent_CHANGED_STATE) { if (changed(self) < 0) goto Done; } } result = PyObject_GenericSetAttr((PyObject *)self, name, v); Done: Py_XDECREF(name); return result; } static int Per_p_set_or_delattro(cPersistentObject *self, PyObject *name, PyObject *v) { int result = -1; /* guilty until proved innocent */ char *s; name = convert_name(name); if (!name) goto Done; s = PyString_AS_STRING(name); if (strncmp(s, "_p_", 3)) { if (unghostify(self) < 0) goto Done; accessed(self); result = 0; } else { if (PyObject_GenericSetAttr((PyObject *)self, name, v) < 0) goto Done; result = 1; } Done: Py_XDECREF(name); return result; } static PyObject * Per__p_setattr(cPersistentObject *self, PyObject *args) { PyObject *name, *v, *result; int r; if (!PyArg_ParseTuple(args, "OO:_p_setattr", &name, &v)) return NULL; r = Per_p_set_or_delattro(self, name, v); if (r < 0) return NULL; result = r ? Py_True : Py_False; Py_INCREF(result); return result; } static PyObject * Per__p_delattr(cPersistentObject *self, PyObject *name) { int r; PyObject *result; r = Per_p_set_or_delattro(self, name, NULL); if (r < 0) return NULL; result = r ? Py_True : Py_False; Py_INCREF(result); return result; } static PyObject * Per_get_changed(cPersistentObject *self) { if (self->state < 0) { Py_INCREF(Py_None); return Py_None; } return PyBool_FromLong(self->state == cPersistent_CHANGED_STATE); } static int Per_set_changed(cPersistentObject *self, PyObject *v) { int deactivate = 0; int true; if (!v) { /* delattr is used to invalidate an object even if it has changed. */ if (self->state != cPersistent_GHOST_STATE) self->state = cPersistent_UPTODATE_STATE; deactivate = 1; } else if (v == Py_None) deactivate = 1; if (deactivate) { PyObject *res, *meth; meth = PyObject_GetAttr((PyObject *)self, py__p_deactivate); if (meth == NULL) return -1; res = PyObject_CallObject(meth, NULL); if (res) Py_DECREF(res); else { /* an error occured in _p_deactivate(). It's not clear what we should do here. The code is obviously ignoring the exception, but it shouldn't return 0 for a getattr and set an exception. The simplest change is to clear the exception, but that simply masks the error. This prints an error to stderr just like exceptions in __del__(). It would probably be better to log it but that would be painful from C. */ PyErr_WriteUnraisable(meth); } Py_DECREF(meth); return 0; } /* !deactivate. If passed a true argument, mark self as changed (starting * with ZODB 3.6, that includes activating the object if it's a ghost). * If passed a false argument, and the object isn't a ghost, set the * state as up-to-date. */ true = PyObject_IsTrue(v); if (true == -1) return -1; if (true) { if (self->state < 0) { if (unghostify(self) < 0) return -1; } return changed(self); } /* We were passed a false, non-None argument. If we're not a ghost, * mark self as up-to-date. */ if (self->state >= 0) self->state = cPersistent_UPTODATE_STATE; return 0; } static PyObject * Per_get_oid(cPersistentObject *self) { PyObject *oid = self->oid ? self->oid : Py_None; Py_INCREF(oid); return oid; } static int Per_set_oid(cPersistentObject *self, PyObject *v) { if (self->cache) { int result; if (v == NULL) { PyErr_SetString(PyExc_ValueError, "can't delete _p_oid of cached object"); return -1; } if (PyObject_Cmp(self->oid, v, &result) < 0) return -1; if (result) { PyErr_SetString(PyExc_ValueError, "can not change _p_oid of cached object"); return -1; } } Py_XDECREF(self->oid); Py_XINCREF(v); self->oid = v; return 0; } static PyObject * Per_get_jar(cPersistentObject *self) { PyObject *jar = self->jar ? self->jar : Py_None; Py_INCREF(jar); return jar; } static int Per_set_jar(cPersistentObject *self, PyObject *v) { if (self->cache) { int result; if (v == NULL) { PyErr_SetString(PyExc_ValueError, "can't delete _p_jar of cached object"); return -1; } if (PyObject_Cmp(self->jar, v, &result) < 0) return -1; if (result) { PyErr_SetString(PyExc_ValueError, "can not change _p_jar of cached object"); return -1; } } Py_XDECREF(self->jar); Py_XINCREF(v); self->jar = v; return 0; } static PyObject * Per_get_serial(cPersistentObject *self) { return PyString_FromStringAndSize(self->serial, 8); } static int Per_set_serial(cPersistentObject *self, PyObject *v) { if (v) { if (PyString_Check(v) && PyString_GET_SIZE(v) == 8) memcpy(self->serial, PyString_AS_STRING(v), 8); else { PyErr_SetString(PyExc_ValueError, "_p_serial must be an 8-character string"); return -1; } } else memset(self->serial, 0, 8); return 0; } static PyObject * Per_get_mtime(cPersistentObject *self) { PyObject *t, *v; if (unghostify(self) < 0) return NULL; accessed(self); if (memcmp(self->serial, "\0\0\0\0\0\0\0\0", 8) == 0) { Py_INCREF(Py_None); return Py_None; } t = PyObject_CallFunction(TimeStamp, "s#", self->serial, 8); if (!t) return NULL; v = PyObject_CallMethod(t, "timeTime", ""); Py_DECREF(t); return v; } static PyObject * Per_get_state(cPersistentObject *self) { return PyInt_FromLong(self->state); } static PyObject * Per_get_estimated_size(cPersistentObject *self) { return PyInt_FromLong(_estimated_size_in_bytes(self->estimated_size)); } static int Per_set_estimated_size(cPersistentObject *self, PyObject *v) { if (v) { if (PyInt_Check(v)) { long lv = PyInt_AS_LONG(v); if (lv < 0) { PyErr_SetString(PyExc_ValueError, "_p_estimated_size must not be negative"); return -1; } self->estimated_size = _estimated_size_in_24_bits(lv); } else { PyErr_SetString(PyExc_ValueError, "_p_estimated_size must be an integer"); return -1; } } else self->estimated_size = 0; return 0; } static PyGetSetDef Per_getsets[] = { {"_p_changed", (getter)Per_get_changed, (setter)Per_set_changed}, {"_p_jar", (getter)Per_get_jar, (setter)Per_set_jar}, {"_p_mtime", (getter)Per_get_mtime}, {"_p_oid", (getter)Per_get_oid, (setter)Per_set_oid}, {"_p_serial", (getter)Per_get_serial, (setter)Per_set_serial}, {"_p_state", (getter)Per_get_state}, {"_p_estimated_size", (getter)Per_get_estimated_size, (setter)Per_set_estimated_size }, {NULL} }; static struct PyMethodDef Per_methods[] = { {"_p_deactivate", (PyCFunction)Per__p_deactivate, METH_NOARGS, "_p_deactivate() -- Deactivate the object"}, {"_p_activate", (PyCFunction)Per__p_activate, METH_NOARGS, "_p_activate() -- Activate the object"}, {"_p_invalidate", (PyCFunction)Per__p_invalidate, METH_NOARGS, "_p_invalidate() -- Invalidate the object"}, {"_p_getattr", (PyCFunction)Per__p_getattr, METH_O, "_p_getattr(name) -- Test whether the base class must handle the name\n" "\n" "The method unghostifies the object, if necessary.\n" "The method records the object access, if necessary.\n" "\n" "This method should be called by subclass __getattribute__\n" "implementations before doing anything else. If the method\n" "returns True, then __getattribute__ implementations must delegate\n" "to the base class, Persistent.\n" }, {"_p_setattr", (PyCFunction)Per__p_setattr, METH_VARARGS, "_p_setattr(name, value) -- Save persistent meta data\n" "\n" "This method should be called by subclass __setattr__ implementations\n" "before doing anything else. If it returns true, then the attribute\n" "was handled by the base class.\n" "\n" "The method unghostifies the object, if necessary.\n" "The method records the object access, if necessary.\n" }, {"_p_delattr", (PyCFunction)Per__p_delattr, METH_O, "_p_delattr(name) -- Delete persistent meta data\n" "\n" "This method should be called by subclass __delattr__ implementations\n" "before doing anything else. If it returns true, then the attribute\n" "was handled by the base class.\n" "\n" "The method unghostifies the object, if necessary.\n" "The method records the object access, if necessary.\n" }, {"__getstate__", (PyCFunction)Per__getstate__, METH_NOARGS, pickle___getstate__doc }, {"__setstate__", (PyCFunction)pickle___setstate__, METH_O, pickle___setstate__doc}, {"__reduce__", (PyCFunction)pickle___reduce__, METH_NOARGS, pickle___reduce__doc}, {NULL, NULL} /* sentinel */ }; /* This module is compiled as a shared library. Some compilers don't allow addresses of Python objects defined in other libraries to be used in static initializers here. The DEFERRED_ADDRESS macro is used to tag the slots where such addresses appear; the module init function must fill in the tagged slots at runtime. The argument is for documentation -- the macro ignores it. */ #define DEFERRED_ADDRESS(ADDR) 0 static PyTypeObject Pertype = { PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyPersist_MetaType)) 0, /* ob_size */ "persistent.Persistent", /* tp_name */ sizeof(cPersistentObject), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Per_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ (getattrofunc)Per_getattro, /* tp_getattro */ (setattrofunc)Per_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ (traverseproc)Per_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Per_methods, /* tp_methods */ 0, /* tp_members */ Per_getsets, /* tp_getset */ }; /* End of code for Persistent objects */ /* -------------------------------------------------------- */ typedef int (*intfunctionwithpythonarg)(PyObject*); /* Load the object's state if necessary and become sticky */ static int Per_setstate(cPersistentObject *self) { if (unghostify(self) < 0) return -1; self->state = cPersistent_STICKY_STATE; return 0; } static PyObject * simple_new(PyObject *self, PyObject *type_object) { if (!PyType_Check(type_object)) { PyErr_SetString(PyExc_TypeError, "simple_new argument must be a type object."); return NULL; } return PyType_GenericNew((PyTypeObject *)type_object, NULL, NULL); } static PyMethodDef cPersistence_methods[] = { {"simple_new", simple_new, METH_O, "Create an object by simply calling a class's __new__ method without " "arguments."}, {NULL, NULL} }; static cPersistenceCAPIstruct truecPersistenceCAPI = { &Pertype, (getattrofunc)Per_getattro, /*tp_getattr with object key*/ (setattrofunc)Per_setattro, /*tp_setattr with object key*/ changed, accessed, ghostify, (intfunctionwithpythonarg)Per_setstate, NULL, /* The percachedel slot is initialized in cPickleCache.c when the module is loaded. It uses a function in a different shared library. */ readCurrent }; void initcPersistence(void) { PyObject *m, *s; PyObject *copy_reg; if (init_strings() < 0) return; m = Py_InitModule3("cPersistence", cPersistence_methods, cPersistence_doc_string); Pertype.ob_type = &PyType_Type; Pertype.tp_new = PyType_GenericNew; if (PyType_Ready(&Pertype) < 0) return; if (PyModule_AddObject(m, "Persistent", (PyObject *)&Pertype) < 0) return; cPersistenceCAPI = &truecPersistenceCAPI; s = PyCObject_FromVoidPtr(cPersistenceCAPI, NULL); if (!s) return; if (PyModule_AddObject(m, "CAPI", s) < 0) return; if (PyModule_AddIntConstant(m, "GHOST", cPersistent_GHOST_STATE) < 0) return; if (PyModule_AddIntConstant(m, "UPTODATE", cPersistent_UPTODATE_STATE) < 0) return; if (PyModule_AddIntConstant(m, "CHANGED", cPersistent_CHANGED_STATE) < 0) return; if (PyModule_AddIntConstant(m, "STICKY", cPersistent_STICKY_STATE) < 0) return; py_simple_new = PyObject_GetAttrString(m, "simple_new"); if (!py_simple_new) return; copy_reg = PyImport_ImportModule("copy_reg"); if (!copy_reg) return; copy_reg_slotnames = PyObject_GetAttrString(copy_reg, "_slotnames"); if (!copy_reg_slotnames) { Py_DECREF(copy_reg); return; } __newobj__ = PyObject_GetAttrString(copy_reg, "__newobj__"); if (!__newobj__) { Py_DECREF(copy_reg); return; } if (!TimeStamp) { m = PyImport_ImportModule("persistent.TimeStamp"); if (!m) return; TimeStamp = PyObject_GetAttrString(m, "TimeStamp"); Py_DECREF(m); /* fall through to immediate return on error */ } } zope2.13-2.13.21/source/ZODB3/src/persistent/cPersistence.h0000644000175000017500000001177212214017464022060 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #ifndef CPERSISTENCE_H #define CPERSISTENCE_H #include "Python.h" #include "py24compat.h" #include "ring.h" #define CACHE_HEAD \ PyObject_HEAD \ CPersistentRing ring_home; \ int non_ghost_count; \ PY_LONG_LONG total_estimated_size; struct ccobject_head_struct; typedef struct ccobject_head_struct PerCache; /* How big is a persistent object? 12 PyGC_Head is two pointers and an int 8 PyObject_HEAD is an int and a pointer 12 jar, oid, cache pointers 8 ring struct 8 serialno 4 state + extra 4 size info (56) so far 4 dict ptr 4 weaklist ptr ------------------------- 68 only need 62, but obmalloc rounds up to multiple of eight Even a ghost requires 64 bytes. It's possible to make a persistent instance with slots and no dict, which changes the storage needed. */ #define cPersistent_HEAD \ PyObject_HEAD \ PyObject *jar; \ PyObject *oid; \ PerCache *cache; \ CPersistentRing ring; \ char serial[8]; \ signed state:8; \ unsigned estimated_size:24; /* We recently added estimated_size. We originally added it as a new unsigned long field after a signed char state field and a 3-character reserved field. This didn't work because there are packages in the wild that have their own copies of cPersistence.h that didn't see the update. To get around this, we used the reserved space by making estimated_size a 24-bit bit field in the space occupied by the old 3-character reserved field. To fit in 24 bits, we made the units of estimated_size 64-character blocks. This allows is to handle up to a GB. We should never see that, but to be paranoid, we also truncate sizes greater than 1GB. We also set the minimum size to 64 bytes. We use the _estimated_size_in_24_bits and _estimated_size_in_bytes macros both to avoid repetition and to make intent a little clearer. */ #define _estimated_size_in_24_bits(I) ((I) > 1073741696 ? 16777215 : (I)/64+1) #define _estimated_size_in_bytes(I) ((I)*64) #define cPersistent_GHOST_STATE -1 #define cPersistent_UPTODATE_STATE 0 #define cPersistent_CHANGED_STATE 1 #define cPersistent_STICKY_STATE 2 typedef struct { cPersistent_HEAD } cPersistentObject; typedef void (*percachedelfunc)(PerCache *, PyObject *); typedef struct { PyTypeObject *pertype; getattrofunc getattro; setattrofunc setattro; int (*changed)(cPersistentObject*); void (*accessed)(cPersistentObject*); void (*ghostify)(cPersistentObject*); int (*setstate)(PyObject*); percachedelfunc percachedel; int (*readCurrent)(cPersistentObject*); } cPersistenceCAPIstruct; #define cPersistenceType cPersistenceCAPI->pertype #ifndef DONT_USE_CPERSISTENCECAPI static cPersistenceCAPIstruct *cPersistenceCAPI; #endif #define cPersistanceModuleName "cPersistence" #define PER_TypeCheck(O) PyObject_TypeCheck((O), cPersistenceCAPI->pertype) #define PER_USE_OR_RETURN(O,R) {if((O)->state==cPersistent_GHOST_STATE && cPersistenceCAPI->setstate((PyObject*)(O)) < 0) return (R); else if ((O)->state==cPersistent_UPTODATE_STATE) (O)->state=cPersistent_STICKY_STATE;} #define PER_CHANGED(O) (cPersistenceCAPI->changed((cPersistentObject*)(O))) #define PER_READCURRENT(O, E) \ if (cPersistenceCAPI->readCurrent((cPersistentObject*)(O)) < 0) { E; } #define PER_GHOSTIFY(O) (cPersistenceCAPI->ghostify((cPersistentObject*)(O))) /* If the object is sticky, make it non-sticky, so that it can be ghostified. The value is not meaningful */ #define PER_ALLOW_DEACTIVATION(O) ((O)->state==cPersistent_STICKY_STATE && ((O)->state=cPersistent_UPTODATE_STATE)) #define PER_PREVENT_DEACTIVATION(O) ((O)->state==cPersistent_UPTODATE_STATE && ((O)->state=cPersistent_STICKY_STATE)) /* Make a persistent object usable from C by: - Making sure it is not a ghost - Making it sticky. IMPORTANT: If you call this and don't call PER_ALLOW_DEACTIVATION, your object will not be ghostified. PER_USE returns a 1 on success and 0 failure, where failure means error. */ #define PER_USE(O) \ (((O)->state != cPersistent_GHOST_STATE \ || (cPersistenceCAPI->setstate((PyObject*)(O)) >= 0)) \ ? (((O)->state==cPersistent_UPTODATE_STATE) \ ? ((O)->state=cPersistent_STICKY_STATE) : 1) : 0) #define PER_ACCESSED(O) (cPersistenceCAPI->accessed((cPersistentObject*)(O))) #endif zope2.13-2.13.21/source/ZODB3/src/persistent/py24compat.h0000644000175000017500000000046212214017464021425 0ustar arnauarnau/* Backport type definitions from Python 2.5's object.h */ #ifndef PERSISTENT_PY24COMPAT_H #define PERSISTENT_PY24COMPAT_H #if PY_VERSION_HEX < 0x02050000 typedef int Py_ssize_t; #define PY_SSIZE_T_MAX INT_MAX #define PY_SSIZE_T_MIN INT_MIN #endif /* PY_VERSION_HEX */ #endif /* PERSISTENT_PY24COMPAT_H */ zope2.13-2.13.21/source/ZODB3/src/persistent/interfaces.py0000644000175000017500000002377212214017464021760 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Persistence Interfaces $Id: interfaces.py 113734 2010-06-21 15:33:46Z ctheune $ """ from zope.interface import Interface from zope.interface import Attribute class IPersistent(Interface): """Python persistent interface A persistent object can be in one of several states: - Unsaved The object has been created but not saved in a data manager. In this state, the _p_changed attribute is non-None and false and the _p_jar attribute is None. - Saved The object has been saved and has not been changed since it was saved. In this state, the _p_changed attribute is non-None and false and the _p_jar attribute is set to a data manager. - Sticky This state is identical to the saved state except that the object cannot transition to the ghost state. This is a special state used by C methods of persistent objects to make sure that state is not unloaded in the middle of computation. In this state, the _p_changed attribute is non-None and false and the _p_jar attribute is set to a data manager. There is no Python API for detecting whether an object is in the sticky state. - Changed The object has been changed. In this state, the _p_changed attribute is true and the _p_jar attribute is set to a data manager. - Ghost the object is in memory but its state has not been loaded from the database (or its state has been unloaded). In this state, the object doesn't contain any application data. In this state, the _p_changed attribute is None, and the _p_jar attribute is set to the data manager from which the object was obtained. In all the above, _p_oid (the persistent object id) is set when _p_jar first gets set. The following state transitions are possible: - Unsaved -> Saved This transition occurs when an object is saved in the database. This usually happens when an unsaved object is added to (e.g. as an attribute or item of) a saved (or changed) object and the transaction is committed. - Saved -> Changed Sticky -> Changed Ghost -> Changed This transition occurs when someone sets an attribute or sets _p_changed to a true value on a saved, sticky or ghost object. When the transition occurs, the persistent object is required to call the register() method on its data manager, passing itself as the only argument. Prior to ZODB 3.6, setting _p_changed to a true value on a ghost object was ignored (the object remained a ghost, and getting its _p_changed attribute continued to return None). - Saved -> Sticky This transition occurs when C code marks the object as sticky to prevent its deactivation. - Saved -> Ghost This transition occurs when a saved object is deactivated or invalidated. See discussion below. - Sticky -> Saved This transition occurs when C code unmarks the object as sticky to allow its deactivation. - Changed -> Saved This transition occurs when a transaction is committed. After saving the state of a changed object during transaction commit, the data manager sets the object's _p_changed to a non-None false value. - Changed -> Ghost This transition occurs when a transaction is aborted. All changed objects are invalidated by the data manager by an abort. - Ghost -> Saved This transition occurs when an attribute or operation of a ghost is accessed and the object's state is loaded from the database. Note that there is a separate C API that is not included here. The C API requires a specific data layout and defines the sticky state. About Invalidation, Deactivation and the Sticky & Ghost States The sticky state is intended to be a short-lived state, to prevent an object's state from being discarded while we're in C routines. It is an error to invalidate an object in the sticky state. Deactivation is a request that an object discard its state (become a ghost). Deactivation is an optimization, and a request to deactivate may be ignored. There are two equivalent ways to request deactivation: - call _p_deactivate() - set _p_changed to None There are two ways to invalidate an object: call the _p_invalidate() method (preferred) or delete its _p_changed attribute. This cannot be ignored, and is used when semantics require invalidation. Normally, an invalidated object transitions to the ghost state. However, some objects cannot be ghosts. When these objects are invalidated, they immediately reload their state from their data manager, and are then in the saved state. """ _p_jar = Attribute( """The data manager for the object. The data manager implements the IPersistentDataManager interface. If there is no data manager, then this is None. """) _p_oid = Attribute( """The object id. It is up to the data manager to assign this. The special value None is reserved to indicate that an object id has not been assigned. Non-None object ids must be non-empty strings. The 8-byte string '\0'*8 (8 NUL bytes) is reserved to identify the database root object. """) _p_changed = Attribute( """The persistent state of the object. This is one of: None -- The object is a ghost. false but not None -- The object is saved (or has never been saved). true -- The object has been modified since it was last saved. The object state may be changed by assigning or deleting this attribute; however, assigning None is ignored if the object is not in the saved state, and may be ignored even if the object is in the saved state. At and after ZODB 3.6, setting _p_changed to a true value for a ghost object activates the object; prior to 3.6, setting _p_changed to a true value on a ghost object was ignored. Note that an object can transition to the changed state only if it has a data manager. When such a state change occurs, the 'register' method of the data manager must be called, passing the persistent object. Deleting this attribute forces invalidation independent of existing state, although it is an error if the sticky state is current. """) _p_serial = Attribute( """The object serial number. This member is used by the data manager to distiguish distinct revisions of a given persistent object. This is an 8-byte string (not Unicode). """) def __getstate__(): """Get the object data. The state should not include persistent attributes ("_p_name"). The result must be picklable. """ def __setstate__(state): """Set the object data. """ def _p_activate(): """Activate the object. Change the object to the saved state if it is a ghost. """ def _p_deactivate(): """Deactivate the object. Possibly change an object in the saved state to the ghost state. It may not be possible to make some persistent objects ghosts, and, for optimization reasons, the implementation may choose to keep an object in the saved state. """ def _p_invalidate(): """Invalidate the object. Invalidate the object. This causes any data to be thrown away, even if the object is in the changed state. The object is moved to the ghost state; further accesses will cause object data to be reloaded. """ # TODO: document conflict resolution. class IPersistentDataManager(Interface): """Provide services for managing persistent state. This interface is used by a persistent object to interact with its data manager in the context of a transaction. """ def setstate(object): """Load the state for the given object. The object should be in the ghost state. The object's state will be set and the object will end up in the saved state. The object must provide the IPersistent interface. """ def oldstate(obj, tid): """Return copy of 'obj' that was written by transaction 'tid'. The returned object does not have the typical metadata (_p_jar, _p_oid, _p_serial) set. I'm not sure how references to other peristent objects are handled. Parameters obj: a persistent object from this Connection. tid: id of a transaction that wrote an earlier revision. Raises KeyError if tid does not exist or if tid deleted a revision of obj. """ def register(object): """Register an IPersistent with the current transaction. This method must be called when the object transitions to the changed state. A subclass could override this method to customize the default policy of one transaction manager for each thread. """ # Maybe later: ## def mtime(object): ## """Return the modification time of the object. ## The modification time may not be known, in which case None ## is returned. If non-None, the return value is the kind of ## timestamp supplied by Python's time.time(). ## """ zope2.13-2.13.21/source/ZODB3/src/persistent/__init__.py0000644000175000017500000000227712214017464021371 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Provide access to Persistent and PersistentMapping. $Id: __init__.py 113734 2010-06-21 15:33:46Z ctheune $ """ from cPersistence import Persistent, GHOST, UPTODATE, CHANGED, STICKY from cPickleCache import PickleCache from cPersistence import simple_new import copy_reg copy_reg.constructor(simple_new) # Make an interface declaration for Persistent, # if zope.interface is available. try: from zope.interface import classImplements except ImportError: pass else: from persistent.interfaces import IPersistent classImplements(Persistent, IPersistent) zope2.13-2.13.21/source/ZODB3/src/persistent/README.txt0000644000175000017500000000134212214017464020746 0ustar arnauarnau=================== Persistence support =================== (This document is under construction. More basic documentation will eventually appear here.) Overriding `__getattr__`, `__getattribute__`, `__setattr__`, and `__delattr__` ------------------------------------------------------------------------------ Subclasses can override the attribute-management methods. For the `__getattr__` method, the behavior is like that for regular Python classes and for earlier versions of ZODB 3. For `__getattribute__`, __setattr__`, and `__delattr__`, it is necessary to call certain methods defined by `persistent.Persistent`. Detailed examples and documentation is provided in the test module, `persistent.tests.test_overriding_attrs`. zope2.13-2.13.21/source/ZODB3/src/persistent/ring.h0000644000175000017500000000511712214017464020364 0ustar arnauarnau/***************************************************************************** Copyright (c) 2003 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /* Support routines for the doubly-linked list of cached objects. The cache stores a headed, doubly-linked, circular list of persistent objects, with space for the pointers allocated in the objects themselves. The cache stores the distinguished head of the list, which is not a valid persistent object. The other list members are non-ghost persistent objects, linked in LRU (least-recently used) order. The r_next pointers traverse the ring starting with the least recently used object. The r_prev pointers traverse the ring starting with the most recently used object. Obscure: While each object is pointed at twice by list pointers (once by its predecessor's r_next, again by its successor's r_prev), the refcount on the object is bumped only by 1. This leads to some possibly surprising sequences of incref and decref code. Note that since the refcount is bumped at least once, the list does hold a strong reference to each object in it. */ typedef struct CPersistentRing_struct { struct CPersistentRing_struct *r_prev; struct CPersistentRing_struct *r_next; } CPersistentRing; /* The list operations here take constant time independent of the * number of objects in the list: */ /* Add elt as the most recently used object. elt must not already be * in the list, although this isn't checked. */ void ring_add(CPersistentRing *ring, CPersistentRing *elt); /* Remove elt from the list. elt must already be in the list, although * this isn't checked. */ void ring_del(CPersistentRing *elt); /* elt must already be in the list, although this isn't checked. It's * unlinked from its current position, and relinked into the list as the * most recently used object (which is arguably the tail of the list * instead of the head -- but the name of this function could be argued * either way). This is equivalent to * * ring_del(elt); * ring_add(ring, elt); * * but may be a little quicker. */ void ring_move_to_head(CPersistentRing *ring, CPersistentRing *elt); zope2.13-2.13.21/source/ZODB3/src/persistent/cPickleCache.c0000644000175000017500000011264412214017464021722 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /* Objects are stored under three different regimes: Regime 1: Persistent Classes Persistent Classes are part of ZClasses. They are stored in the self->data dictionary, and are never garbage collected. The klass_items() method returns a sequence of (oid,object) tuples for every Persistent Class, which should make it possible to implement garbage collection in Python if necessary. Regime 2: Ghost Objects There is no benefit to keeping a ghost object which has no external references, therefore a weak reference scheme is used to ensure that ghost objects are removed from memory as soon as possible, when the last external reference is lost. Ghost objects are stored in the self->data dictionary. Normally a dictionary keeps a strong reference on its values, however this reference count is 'stolen'. This weak reference scheme leaves a dangling reference, in the dictionary, when the last external reference is lost. To clean up this dangling reference the persistent object dealloc function calls self->cache->_oid_unreferenced(self->oid). The cache looks up the oid in the dictionary, ensures it points to an object whose reference count is zero, then removes it from the dictionary. Before removing the object from the dictionary it must temporarily resurrect the object in much the same way that class instances are resurrected before their __del__ is called. Since ghost objects are stored under a different regime to non-ghost objects, an extra ghostify function in cPersistenceAPI replaces self->state=GHOST_STATE assignments that were common in other persistent classes (such as BTrees). Regime 3: Non-Ghost Objects Non-ghost objects are stored in two data structures: the dictionary mapping oids to objects and a doubly-linked list that encodes the order in which the objects were accessed. The dictionary reference is borrowed, as it is for ghosts. The list reference is a new reference; the list stores recently used objects, even if they are otherwise unreferenced, to avoid loading the object from the database again. The doubly-link-list nodes contain next and previous pointers linking together the cache and all non-ghost persistent objects. The node embedded in the cache is the home position. On every attribute access a non-ghost object will relink itself just behind the home position in the ring. Objects accessed least recently will eventually find themselves positioned after the home position. Occasionally other nodes are temporarily inserted in the ring as position markers. The cache contains a ring_lock flag which must be set and unset before and after doing so. Only if the flag is unset can the cache assume that all nodes are either his own home node, or nodes from persistent objects. This assumption is useful during the garbage collection process. The number of non-ghost objects is counted in self->non_ghost_count. The garbage collection process consists of traversing the ring, and deactivating (that is, turning into a ghost) every object until self->non_ghost_count is down to the target size, or until it reaches the home position again. Note that objects in the sticky or changed states are still kept in the ring, however they can not be deactivated. The garbage collection process must skip such objects, rather than deactivating them. */ static char cPickleCache_doc_string[] = "Defines the PickleCache used by ZODB Connection objects.\n" "\n" "$Id: cPickleCache.c 116786 2010-09-24 15:12:47Z jim $\n"; #define DONT_USE_CPERSISTENCECAPI #include "cPersistence.h" #include "structmember.h" #include #include #undef Py_FindMethod /* Python 2.4 backward compat */ #if PY_MAJOR_VERSION <= 2 && PY_MINOR_VERSION < 5 #define Py_ssize_t int typedef Py_ssize_t (*lenfunc)(PyObject *); #endif /* Python string objects to speed lookups; set by module init. */ static PyObject *py__p_changed; static PyObject *py__p_deactivate; static PyObject *py__p_jar; static PyObject *py__p_oid; static cPersistenceCAPIstruct *capi; /* This object is the pickle cache. The CACHE_HEAD macro guarantees that layout of this struct is the same as the start of ccobject_head in cPersistence.c */ typedef struct { CACHE_HEAD int klass_count; /* count of persistent classes */ PyObject *data; /* oid -> object dict */ PyObject *jar; /* Connection object */ int cache_size; /* target number of items in cache */ PY_LONG_LONG cache_size_bytes; /* target total estimated size of items in cache */ /* Most of the time the ring contains only: * many nodes corresponding to persistent objects * one 'home' node from the cache. In some cases it is handy to temporarily add other types of node into the ring as placeholders. 'ring_lock' is a boolean indicating that someone has already done this. Currently this is only used by the garbage collection code. */ int ring_lock; /* 'cache_drain_resistance' controls how quickly the cache size will drop when it is smaller than the configured size. A value of zero means it will not drop below the configured size (suitable for most caches). Otherwise, it will remove cache_non_ghost_count/cache_drain_resistance items from the cache every time (suitable for rarely used caches, such as those associated with Zope versions. */ int cache_drain_resistance; } ccobject; static int cc_ass_sub(ccobject *self, PyObject *key, PyObject *v); /* ---------------------------------------------------------------- */ #define OBJECT_FROM_RING(SELF, HERE) \ ((cPersistentObject *)(((char *)here) - offsetof(cPersistentObject, ring))) /* Insert self into the ring, following after. */ static void insert_after(CPersistentRing *self, CPersistentRing *after) { assert(self != NULL); assert(after != NULL); self->r_prev = after; self->r_next = after->r_next; after->r_next->r_prev = self; after->r_next = self; } /* Remove self from the ring. */ static void unlink_from_ring(CPersistentRing *self) { assert(self != NULL); self->r_prev->r_next = self->r_next; self->r_next->r_prev = self->r_prev; } static int scan_gc_items(ccobject *self, int target, PY_LONG_LONG target_bytes) { /* This function must only be called with the ring lock held, because it places non-object placeholders in the ring. */ cPersistentObject *object; CPersistentRing *here; CPersistentRing before_original_home; int result = -1; /* guilty until proved innocent */ /* Scan the ring, from least to most recently used, deactivating * up-to-date objects, until we either find the ring_home again or * or we've ghosted enough objects to reach the target size. * Tricky: __getattr__ and __del__ methods can do anything, and in * particular if we ghostify an object with a __del__ method, that method * can load the object again, putting it back into the MRU part of the * ring. Waiting to find ring_home again can thus cause an infinite * loop (Collector #1208). So before_original_home records the MRU * position we start with, and we stop the scan when we reach that. */ insert_after(&before_original_home, self->ring_home.r_prev); here = self->ring_home.r_next; /* least recently used object */ while (here != &before_original_home && (self->non_ghost_count > target || (target_bytes && self->total_estimated_size > target_bytes) ) ) { assert(self->ring_lock); assert(here != &self->ring_home); /* At this point we know that the ring only contains nodes from persistent objects, plus our own home node. We know this because the ring lock is held. We can safely assume the current ring node is a persistent object now we know it is not the home */ object = OBJECT_FROM_RING(self, here); if (object->state == cPersistent_UPTODATE_STATE) { CPersistentRing placeholder; PyObject *method; PyObject *temp; int error_occurred = 0; /* deactivate it. This is the main memory saver. */ /* Add a placeholder, a dummy node in the ring. We need to do this to mark our position in the ring. It is possible that the PyObject_GetAttr() call below will invoke a __getattr__() hook in Python. Also possible that deactivation will lead to a __del__ method call. So another thread might run, and mutate the ring as a side effect of object accesses. There's no predicting then where in the ring here->next will point after that. The placeholder won't move as a side effect of calling Python code. */ insert_after(&placeholder, here); method = PyObject_GetAttr((PyObject *)object, py__p_deactivate); if (method == NULL) error_occurred = 1; else { temp = PyObject_CallObject(method, NULL); Py_DECREF(method); if (temp == NULL) error_occurred = 1; else Py_DECREF(temp); } here = placeholder.r_next; unlink_from_ring(&placeholder); if (error_occurred) goto Done; } else here = here->r_next; } result = 0; Done: unlink_from_ring(&before_original_home); return result; } static PyObject * lockgc(ccobject *self, int target_size, PY_LONG_LONG target_size_bytes) { /* This is thread-safe because of the GIL, and there's nothing * in between checking the ring_lock and acquiring it that calls back * into Python. */ if (self->ring_lock) { Py_INCREF(Py_None); return Py_None; } self->ring_lock = 1; if (scan_gc_items(self, target_size, target_size_bytes) < 0) { self->ring_lock = 0; return NULL; } self->ring_lock = 0; Py_INCREF(Py_None); return Py_None; } static PyObject * cc_incrgc(ccobject *self, PyObject *args) { int obsolete_arg = -999; int starting_size = self->non_ghost_count; int target_size = self->cache_size; PY_LONG_LONG target_size_bytes = self->cache_size_bytes; if (self->cache_drain_resistance >= 1) { /* This cache will gradually drain down to a small size. Check a (small) number of objects proportional to the current size */ int target_size_2 = (starting_size - 1 - starting_size / self->cache_drain_resistance); if (target_size_2 < target_size) target_size = target_size_2; } if (!PyArg_ParseTuple(args, "|i:incrgc", &obsolete_arg)) return NULL; if (obsolete_arg != -999 && (PyErr_Warn(PyExc_DeprecationWarning, "No argument expected") < 0)) return NULL; return lockgc(self, target_size, target_size_bytes); } static PyObject * cc_full_sweep(ccobject *self, PyObject *args) { int dt = -999; /* TODO: This should be deprecated; */ if (!PyArg_ParseTuple(args, "|i:full_sweep", &dt)) return NULL; if (dt == -999) return lockgc(self, 0, 0); else return cc_incrgc(self, args); } static PyObject * cc_minimize(ccobject *self, PyObject *args) { int ignored = -999; if (!PyArg_ParseTuple(args, "|i:minimize", &ignored)) return NULL; if (ignored != -999 && (PyErr_Warn(PyExc_DeprecationWarning, "No argument expected") < 0)) return NULL; return lockgc(self, 0, 0); } static int _invalidate(ccobject *self, PyObject *key) { static PyObject *_p_invalidate = NULL; PyObject *meth, *v; v = PyDict_GetItem(self->data, key); if (v == NULL) return 0; if (_p_invalidate == NULL) { _p_invalidate = PyString_InternFromString("_p_invalidate"); if (_p_invalidate == NULL) { /* It doesn't make any sense to ignore this error, but the caller ignores all errors. TODO: and why does it do that? This should be fixed */ return -1; } } if (v->ob_refcnt <= 1 && PyType_Check(v)) { /* This looks wrong, but it isn't. We use strong references to types because they don't have the ring members. The result is that we *never* remove classes unless they are modified. We can fix this by using wekrefs uniformly. */ self->klass_count--; return PyDict_DelItem(self->data, key); } meth = PyObject_GetAttr(v, _p_invalidate); if (meth == NULL) return -1; v = PyObject_CallObject(meth, NULL); Py_DECREF(meth); if (v == NULL) return -1; Py_DECREF(v); return 0; } static PyObject * cc_invalidate(ccobject *self, PyObject *inv) { PyObject *key, *v; Py_ssize_t i = 0; if (PyDict_Check(inv)) { while (PyDict_Next(inv, &i, &key, &v)) { if (_invalidate(self, key) < 0) return NULL; } PyDict_Clear(inv); } else { if (PyString_Check(inv)) { if (_invalidate(self, inv) < 0) return NULL; } else { int l, r; l = PyObject_Length(inv); if (l < 0) return NULL; for (i=l; --i >= 0; ) { key = PySequence_GetItem(inv, i); if (!key) return NULL; r = _invalidate(self, key); Py_DECREF(key); if (r < 0) return NULL; } /* Dubious: modifying the input may be an unexpected side effect. */ PySequence_DelSlice(inv, 0, l); } } Py_INCREF(Py_None); return Py_None; } static PyObject * cc_get(ccobject *self, PyObject *args) { PyObject *r, *key, *d = NULL; if (!PyArg_ParseTuple(args, "O|O:get", &key, &d)) return NULL; r = PyDict_GetItem(self->data, key); if (!r) { if (d) r = d; else r = Py_None; } Py_INCREF(r); return r; } static PyObject * cc_items(ccobject *self) { return PyObject_CallMethod(self->data, "items", ""); } static PyObject * cc_klass_items(ccobject *self) { PyObject *l,*k,*v; Py_ssize_t p = 0; l = PyList_New(0); if (l == NULL) return NULL; while (PyDict_Next(self->data, &p, &k, &v)) { if(PyType_Check(v)) { v = Py_BuildValue("OO", k, v); if (v == NULL) { Py_DECREF(l); return NULL; } if (PyList_Append(l, v) < 0) { Py_DECREF(v); Py_DECREF(l); return NULL; } Py_DECREF(v); } } return l; } static PyObject * cc_debug_info(ccobject *self) { PyObject *l,*k,*v; Py_ssize_t p = 0; l = PyList_New(0); if (l == NULL) return NULL; while (PyDict_Next(self->data, &p, &k, &v)) { if (v->ob_refcnt <= 0) v = Py_BuildValue("Oi", k, v->ob_refcnt); else if (! PyType_Check(v) && (v->ob_type->tp_basicsize >= sizeof(cPersistentObject)) ) v = Py_BuildValue("Oisi", k, v->ob_refcnt, v->ob_type->tp_name, ((cPersistentObject*)v)->state); else v = Py_BuildValue("Ois", k, v->ob_refcnt, v->ob_type->tp_name); if (v == NULL) goto err; if (PyList_Append(l, v) < 0) goto err; } return l; err: Py_DECREF(l); return NULL; } static PyObject * cc_lru_items(ccobject *self) { PyObject *l; CPersistentRing *here; if (self->ring_lock) { /* When the ring lock is held, we have no way of know which ring nodes belong to persistent objects, and which a placeholders. */ PyErr_SetString(PyExc_ValueError, ".lru_items() is unavailable during garbage collection"); return NULL; } l = PyList_New(0); if (l == NULL) return NULL; here = self->ring_home.r_next; while (here != &self->ring_home) { PyObject *v; cPersistentObject *object = OBJECT_FROM_RING(self, here); if (object == NULL) { Py_DECREF(l); return NULL; } v = Py_BuildValue("OO", object->oid, object); if (v == NULL) { Py_DECREF(l); return NULL; } if (PyList_Append(l, v) < 0) { Py_DECREF(v); Py_DECREF(l); return NULL; } Py_DECREF(v); here = here->r_next; } return l; } static void cc_oid_unreferenced(ccobject *self, PyObject *oid) { /* This is called by the persistent object deallocation function when the reference count on a persistent object reaches zero. We need to fix up our dictionary; its reference is now dangling because we stole its reference count. Be careful to not release the global interpreter lock until this is complete. */ PyObject *v; /* If the cache has been cleared by GC, data will be NULL. */ if (!self->data) return; v = PyDict_GetItem(self->data, oid); assert(v); assert(v->ob_refcnt == 0); /* Need to be very hairy here because a dictionary is about to decref an already deleted object. */ #ifdef Py_TRACE_REFS /* This is called from the deallocation function after the interpreter has untracked the reference. Track it again. */ _Py_NewReference(v); /* Don't increment total refcount as a result of the shenanigans played in this function. The _Py_NewReference() call above creates artificial references to v. */ _Py_RefTotal--; assert(v->ob_type); #else Py_INCREF(v); #endif assert(v->ob_refcnt == 1); /* Incremement the refcount again, because delitem is going to DECREF it. If it's refcount reached zero again, we'd call back to the dealloc function that called us. */ Py_INCREF(v); /* TODO: Should we call _Py_ForgetReference() on error exit? */ if (PyDict_DelItem(self->data, oid) < 0) return; Py_DECREF((ccobject *)((cPersistentObject *)v)->cache); ((cPersistentObject *)v)->cache = NULL; assert(v->ob_refcnt == 1); /* Undo the temporary resurrection. Don't DECREF the object, because this function is called from the object's dealloc function. If the refcnt reaches zero, it will all be invoked recursively. */ _Py_ForgetReference(v); } static PyObject * cc_ringlen(ccobject *self) { CPersistentRing *here; int c = 0; for (here = self->ring_home.r_next; here != &self->ring_home; here = here->r_next) c++; return PyInt_FromLong(c); } static PyObject * cc_update_object_size_estimation(ccobject *self, PyObject *args) { PyObject *oid; cPersistentObject *v; unsigned int new_size; if (!PyArg_ParseTuple(args, "OI:updateObjectSizeEstimation", &oid, &new_size)) return NULL; /* Note: reference borrowed */ v = (cPersistentObject *)PyDict_GetItem(self->data, oid); if (v) { /* we know this object -- update our "total_size_estimation" we must only update when the object is in the ring */ if (v->ring.r_next) { self->total_estimated_size += _estimated_size_in_bytes( (int)(_estimated_size_in_24_bits(new_size)) - (int)(v->estimated_size) ); /* we do this in "Connection" as we need it even when the object is not in the cache (or not the ring) */ /* v->estimated_size = new_size; */ } } Py_RETURN_NONE; } static PyObject* cc_new_ghost(ccobject *self, PyObject *args) { PyObject *tmp, *key, *v; if (!PyArg_ParseTuple(args, "OO:new_ghost", &key, &v)) return NULL; /* Sanity check the value given to make sure it is allowed in the cache */ if (PyType_Check(v)) { /* Its a persistent class, such as a ZClass. Thats ok. */ } else if (v->ob_type->tp_basicsize < sizeof(cPersistentObject)) { /* If it's not an instance of a persistent class, (ie Python classes that derive from persistent.Persistent, BTrees, etc), report an error. TODO: checking sizeof() seems a poor test. */ PyErr_SetString(PyExc_TypeError, "Cache values must be persistent objects."); return NULL; } /* Can't access v->oid directly because the object might be a * persistent class. */ tmp = PyObject_GetAttr(v, py__p_oid); if (tmp == NULL) return NULL; Py_DECREF(tmp); if (tmp != Py_None) { PyErr_SetString(PyExc_AssertionError, "New ghost object must not have an oid"); return NULL; } /* useful sanity check, but not strictly an invariant of this class */ tmp = PyObject_GetAttr(v, py__p_jar); if (tmp == NULL) return NULL; Py_DECREF(tmp); if (tmp != Py_None) { PyErr_SetString(PyExc_AssertionError, "New ghost object must not have a jar"); return NULL; } tmp = PyDict_GetItem(self->data, key); if (tmp) { Py_DECREF(tmp); PyErr_SetString(PyExc_AssertionError, "The given oid is already in the cache"); return NULL; } if (PyType_Check(v)) { if (PyObject_SetAttr(v, py__p_jar, self->jar) < 0) return NULL; if (PyObject_SetAttr(v, py__p_oid, key) < 0) return NULL; if (PyDict_SetItem(self->data, key, v) < 0) return NULL; PyObject_GC_UnTrack((void *)self->data); self->klass_count++; } else { cPersistentObject *p = (cPersistentObject *)v; if(p->cache != NULL) { PyErr_SetString(PyExc_AssertionError, "Already in a cache"); return NULL; } if (PyDict_SetItem(self->data, key, v) < 0) return NULL; /* the dict should have a borrowed reference */ PyObject_GC_UnTrack((void *)self->data); Py_DECREF(v); Py_INCREF(self); p->cache = (PerCache *)self; Py_INCREF(self->jar); p->jar = self->jar; Py_INCREF(key); p->oid = key; p->state = cPersistent_GHOST_STATE; } Py_RETURN_NONE; } static struct PyMethodDef cc_methods[] = { {"items", (PyCFunction)cc_items, METH_NOARGS, "Return list of oid, object pairs for all items in cache."}, {"lru_items", (PyCFunction)cc_lru_items, METH_NOARGS, "List (oid, object) pairs from the lru list, as 2-tuples."}, {"klass_items", (PyCFunction)cc_klass_items, METH_NOARGS, "List (oid, object) pairs of cached persistent classes."}, {"full_sweep", (PyCFunction)cc_full_sweep, METH_VARARGS, "full_sweep() -- Perform a full sweep of the cache."}, {"minimize", (PyCFunction)cc_minimize, METH_VARARGS, "minimize([ignored]) -- Remove as many objects as possible\n\n" "Ghostify all objects that are not modified. Takes an optional\n" "argument, but ignores it."}, {"incrgc", (PyCFunction)cc_incrgc, METH_VARARGS, "incrgc() -- Perform incremental garbage collection\n\n" "This method had been depricated!" "Some other implementations support an optional parameter 'n' which\n" "indicates a repetition count; this value is ignored."}, {"invalidate", (PyCFunction)cc_invalidate, METH_O, "invalidate(oids) -- invalidate one, many, or all ids"}, {"get", (PyCFunction)cc_get, METH_VARARGS, "get(key [, default]) -- get an item, or a default"}, {"ringlen", (PyCFunction)cc_ringlen, METH_NOARGS, "ringlen() -- Returns number of non-ghost items in cache."}, {"debug_info", (PyCFunction)cc_debug_info, METH_NOARGS, "debug_info() -- Returns debugging data about objects in the cache."}, {"update_object_size_estimation", (PyCFunction)cc_update_object_size_estimation, METH_VARARGS, "update_object_size_estimation(oid, new_size) -- " "update the caches size estimation for *oid* " "(if this is known to the cache)."}, {"new_ghost", (PyCFunction)cc_new_ghost, METH_VARARGS, "new_ghost() -- Initialize a ghost and add it to the cache."}, {NULL, NULL} /* sentinel */ }; static int cc_init(ccobject *self, PyObject *args, PyObject *kwds) { int cache_size = 100; PY_LONG_LONG cache_size_bytes = 0; PyObject *jar; if (!PyArg_ParseTuple(args, "O|iL", &jar, &cache_size, &cache_size_bytes)) return -1; self->jar = NULL; self->data = PyDict_New(); if (self->data == NULL) { Py_DECREF(self); return -1; } /* Untrack the dict mapping oids to objects. The dict contains uncounted references to ghost objects, so it isn't safe for GC to visit it. If GC finds an object with more referents that refcounts, it will die with an assertion failure. When the cache participates in GC, it will need to traverse the objects in the doubly-linked list, which will account for all the non-ghost objects. */ PyObject_GC_UnTrack((void *)self->data); self->jar = jar; Py_INCREF(jar); self->cache_size = cache_size; self->cache_size_bytes = cache_size_bytes; self->non_ghost_count = 0; self->total_estimated_size = 0; self->klass_count = 0; self->cache_drain_resistance = 0; self->ring_lock = 0; self->ring_home.r_next = &self->ring_home; self->ring_home.r_prev = &self->ring_home; return 0; } static void cc_dealloc(ccobject *self) { Py_XDECREF(self->data); Py_XDECREF(self->jar); PyObject_GC_Del(self); } static int cc_clear(ccobject *self) { Py_ssize_t pos = 0; PyObject *k, *v; /* Clearing the cache is delicate. A non-ghost object will show up in the ring and in the dict. If we deallocating the dict before clearing the ring, the GC will decref each object in the dict. Since the dict references are uncounted, this will lead to objects having negative refcounts. Freeing the non-ghost objects should eliminate many objects from the cache, but there may still be ghost objects left. It's not safe to decref the dict until it's empty, so we need to manually clear those out of the dict, too. We accomplish that by replacing all the ghost objects with None. */ /* We don't need to lock the ring, because the cache is unreachable. It should be impossible for anyone to be modifying the cache. */ assert(! self->ring_lock); while (self->ring_home.r_next != &self->ring_home) { CPersistentRing *here = self->ring_home.r_next; cPersistentObject *o = OBJECT_FROM_RING(self, here); if (o->cache) { Py_INCREF(o); /* account for uncounted reference */ if (PyDict_DelItem(self->data, o->oid) < 0) return -1; } o->cache = NULL; Py_DECREF(self); self->ring_home.r_next = here->r_next; o->ring.r_prev = NULL; o->ring.r_next = NULL; Py_DECREF(o); here = here->r_next; } Py_XDECREF(self->jar); while (PyDict_Next(self->data, &pos, &k, &v)) { Py_INCREF(v); if (PyDict_SetItem(self->data, k, Py_None) < 0) return -1; } Py_XDECREF(self->data); self->data = NULL; self->jar = NULL; return 0; } static int cc_traverse(ccobject *self, visitproc visit, void *arg) { int err; CPersistentRing *here; /* If we're in the midst of cleaning up old objects, the ring contains * assorted junk we must not pass on to the visit() callback. This * should be rare (our cleanup code would need to have called back * into Python, which in turn triggered Python's gc). When it happens, * simply don't chase any pointers. The cache will appear to be a * source of external references then, and at worst we miss cleaning * up a dead cycle until the next time Python's gc runs. */ if (self->ring_lock) return 0; #define VISIT(SLOT) \ if (SLOT) { \ err = visit((PyObject *)(SLOT), arg); \ if (err) \ return err; \ } VISIT(self->jar); here = self->ring_home.r_next; /* It is possible that an object is traversed after it is cleared. In that case, there is no ring. */ if (!here) return 0; while (here != &self->ring_home) { cPersistentObject *o = OBJECT_FROM_RING(self, here); VISIT(o); here = here->r_next; } #undef VISIT return 0; } static Py_ssize_t cc_length(ccobject *self) { return PyObject_Length(self->data); } static PyObject * cc_subscript(ccobject *self, PyObject *key) { PyObject *r; r = PyDict_GetItem(self->data, key); if (r == NULL) { PyErr_SetObject(PyExc_KeyError, key); return NULL; } Py_INCREF(r); return r; } static int cc_add_item(ccobject *self, PyObject *key, PyObject *v) { int result; PyObject *oid, *object_again, *jar; cPersistentObject *p; /* Sanity check the value given to make sure it is allowed in the cache */ if (PyType_Check(v)) { /* Its a persistent class, such as a ZClass. Thats ok. */ } else if (v->ob_type->tp_basicsize < sizeof(cPersistentObject)) { /* If it's not an instance of a persistent class, (ie Python classes that derive from persistent.Persistent, BTrees, etc), report an error. TODO: checking sizeof() seems a poor test. */ PyErr_SetString(PyExc_TypeError, "Cache values must be persistent objects."); return -1; } /* Can't access v->oid directly because the object might be a * persistent class. */ oid = PyObject_GetAttr(v, py__p_oid); if (oid == NULL) return -1; if (! PyString_Check(oid)) { Py_DECREF(oid); PyErr_Format(PyExc_TypeError, "Cached object oid must be a string, not a %s", oid->ob_type->tp_name); return -1; } /* we know they are both strings. * now check if they are the same string. */ result = PyObject_Compare(key, oid); if (PyErr_Occurred()) { Py_DECREF(oid); return -1; } Py_DECREF(oid); if (result) { PyErr_SetString(PyExc_ValueError, "Cache key does not match oid"); return -1; } /* useful sanity check, but not strictly an invariant of this class */ jar = PyObject_GetAttr(v, py__p_jar); if (jar == NULL) return -1; if (jar==Py_None) { Py_DECREF(jar); PyErr_SetString(PyExc_ValueError, "Cached object jar missing"); return -1; } Py_DECREF(jar); object_again = PyDict_GetItem(self->data, key); if (object_again) { if (object_again != v) { PyErr_SetString(PyExc_ValueError, "A different object already has the same oid"); return -1; } else { /* re-register under the same oid - no work needed */ return 0; } } if (PyType_Check(v)) { if (PyDict_SetItem(self->data, key, v) < 0) return -1; PyObject_GC_UnTrack((void *)self->data); self->klass_count++; return 0; } else { PerCache *cache = ((cPersistentObject *)v)->cache; if (cache) { if (cache != (PerCache *)self) /* This object is already in a different cache. */ PyErr_SetString(PyExc_ValueError, "Cache values may only be in one cache."); return -1; } /* else: This object is already one of ours, which is ok. It would be very strange if someone was trying to register the same object under a different key. */ } if (PyDict_SetItem(self->data, key, v) < 0) return -1; /* the dict should have a borrowed reference */ PyObject_GC_UnTrack((void *)self->data); Py_DECREF(v); p = (cPersistentObject *)v; Py_INCREF(self); p->cache = (PerCache *)self; if (p->state >= 0) { /* insert this non-ghost object into the ring just behind the home position. */ self->non_ghost_count++; ring_add(&self->ring_home, &p->ring); /* this list should have a new reference to the object */ Py_INCREF(v); } return 0; } static int cc_del_item(ccobject *self, PyObject *key) { PyObject *v; cPersistentObject *p; /* unlink this item from the ring */ v = PyDict_GetItem(self->data, key); if (v == NULL) { PyErr_SetObject(PyExc_KeyError, key); return -1; } if (PyType_Check(v)) { self->klass_count--; } else { p = (cPersistentObject *)v; if (p->state >= 0) { self->non_ghost_count--; ring_del(&p->ring); /* The DelItem below will account for the reference held by the list. */ } else { /* This is a ghost object, so we haven't kept a reference count on it. For it have stayed alive this long someone else must be keeping a reference to it. Therefore we need to temporarily give it back a reference count before calling DelItem below */ Py_INCREF(v); } Py_DECREF((PyObject *)p->cache); p->cache = NULL; } if (PyDict_DelItem(self->data, key) < 0) { PyErr_SetString(PyExc_RuntimeError, "unexpectedly couldn't remove key in cc_ass_sub"); return -1; } return 0; } static int cc_ass_sub(ccobject *self, PyObject *key, PyObject *v) { if (!PyString_Check(key)) { PyErr_Format(PyExc_TypeError, "cPickleCache key must be a string, not a %s", key->ob_type->tp_name); return -1; } if (v) return cc_add_item(self, key, v); else return cc_del_item(self, key); } static PyMappingMethods cc_as_mapping = { (lenfunc)cc_length, /*mp_length*/ (binaryfunc)cc_subscript, /*mp_subscript*/ (objobjargproc)cc_ass_sub, /*mp_ass_subscript*/ }; static PyObject * cc_cache_data(ccobject *self, void *context) { return PyDict_Copy(self->data); } static PyGetSetDef cc_getsets[] = { {"cache_data", (getter)cc_cache_data}, {NULL} }; static PyMemberDef cc_members[] = { {"cache_size", T_INT, offsetof(ccobject, cache_size)}, {"cache_size_bytes", T_LONG, offsetof(ccobject, cache_size_bytes)}, {"total_estimated_size", T_LONG, offsetof(ccobject, total_estimated_size), RO}, {"cache_drain_resistance", T_INT, offsetof(ccobject, cache_drain_resistance)}, {"cache_non_ghost_count", T_INT, offsetof(ccobject, non_ghost_count), RO}, {"cache_klass_count", T_INT, offsetof(ccobject, klass_count), RO}, {NULL} }; /* This module is compiled as a shared library. Some compilers don't allow addresses of Python objects defined in other libraries to be used in static initializers here. The DEFERRED_ADDRESS macro is used to tag the slots where such addresses appear; the module init function must fill in the tagged slots at runtime. The argument is for documentation -- the macro ignores it. */ #define DEFERRED_ADDRESS(ADDR) 0 static PyTypeObject Cctype = { PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type)) 0, /* ob_size */ "persistent.PickleCache", /* tp_name */ sizeof(ccobject), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)cc_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ &cc_as_mapping, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ (traverseproc)cc_traverse, /* tp_traverse */ (inquiry)cc_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ cc_methods, /* tp_methods */ cc_members, /* tp_members */ cc_getsets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)cc_init, /* tp_init */ }; void initcPickleCache(void) { PyObject *m; Cctype.ob_type = &PyType_Type; Cctype.tp_new = &PyType_GenericNew; if (PyType_Ready(&Cctype) < 0) { return; } m = Py_InitModule3("cPickleCache", NULL, cPickleCache_doc_string); capi = (cPersistenceCAPIstruct *)PyCObject_Import( "persistent.cPersistence", "CAPI"); if (!capi) return; capi->percachedel = (percachedelfunc)cc_oid_unreferenced; py__p_changed = PyString_InternFromString("_p_changed"); if (!py__p_changed) return; py__p_deactivate = PyString_InternFromString("_p_deactivate"); if (!py__p_deactivate) return; py__p_jar = PyString_InternFromString("_p_jar"); if (!py__p_jar) return; py__p_oid = PyString_InternFromString("_p_oid"); if (!py__p_oid) return; if (PyModule_AddStringConstant(m, "cache_variant", "stiff/c") < 0) return; /* This leaks a reference to Cctype, but it doesn't matter. */ if (PyModule_AddObject(m, "PickleCache", (PyObject *)&Cctype) < 0) return; } zope2.13-2.13.21/source/ZODB3/src/persistent/TimeStamp.c0000644000175000017500000002446212214017464021327 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2004 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #include "Python.h" #include PyObject *TimeStamp_FromDate(int, int, int, int, int, double); PyObject *TimeStamp_FromString(const char *); static char TimeStampModule_doc[] = "A 64-bit TimeStamp used as a ZODB serial number.\n" "\n" "$Id: TimeStamp.c 113734 2010-06-21 15:33:46Z ctheune $\n"; typedef struct { PyObject_HEAD unsigned char data[8]; } TimeStamp; /* The first dimension of the arrays below is non-leapyear / leapyear */ static char month_len[2][12]={ {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} }; static short joff[2][12] = { {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335} }; static double gmoff=0; /* TODO: May be better (faster) to store in a file static. */ #define SCONV ((double)60) / ((double)(1<<16)) / ((double)(1<<16)) static int leap(int year) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } static int days_in_month(int year, int month) { return month_len[leap(year)][month]; } static double TimeStamp_yad(int y) { double d, s; y -= 1900; d = (y - 1) * 365; if (y > 0) { s = 1.0; y -= 1; } else { s = -1.0; y = -y; } return d + s * (y / 4 - y / 100 + (y + 300) / 400); } static double TimeStamp_abst(int y, int mo, int d, int m, int s) { return (TimeStamp_yad(y) + joff[leap(y)][mo] + d) * 86400 + m * 60 + s; } static int TimeStamp_init_gmoff(void) { struct tm *t; time_t z=0; t = gmtime(&z); if (t == NULL) { PyErr_SetString(PyExc_SystemError, "gmtime failed"); return -1; } gmoff = TimeStamp_abst(t->tm_year+1900, t->tm_mon, t->tm_mday - 1, t->tm_hour * 60 + t->tm_min, t->tm_sec); return 0; } static void TimeStamp_dealloc(TimeStamp *ts) { PyObject_Del(ts); } static int TimeStamp_compare(TimeStamp *v, TimeStamp *w) { int cmp = memcmp(v->data, w->data, 8); if (cmp < 0) return -1; if (cmp > 0) return 1; return 0; } static long TimeStamp_hash(TimeStamp *self) { register unsigned char *p = (unsigned char *)self->data; register int len = 8; register long x = *p << 7; while (--len >= 0) x = (1000003*x) ^ *p++; x ^= 8; if (x == -1) x = -2; return x; } typedef struct { /* TODO: reverse-engineer what's in these things and comment them */ int y; int m; int d; int mi; } TimeStampParts; static void TimeStamp_unpack(TimeStamp *self, TimeStampParts *p) { unsigned long v; v = (self->data[0] * 16777216 + self->data[1] * 65536 + self->data[2] * 256 + self->data[3]); p->y = v / 535680 + 1900; p->m = (v % 535680) / 44640 + 1; p->d = (v % 44640) / 1440 + 1; p->mi = v % 1440; } static double TimeStamp_sec(TimeStamp *self) { unsigned int v; v = (self->data[4] * 16777216 + self->data[5] * 65536 + self->data[6] * 256 + self->data[7]); return SCONV * v; } static PyObject * TimeStamp_year(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); return PyInt_FromLong(p.y); } static PyObject * TimeStamp_month(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); return PyInt_FromLong(p.m); } static PyObject * TimeStamp_day(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); return PyInt_FromLong(p.d); } static PyObject * TimeStamp_hour(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); return PyInt_FromLong(p.mi / 60); } static PyObject * TimeStamp_minute(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); return PyInt_FromLong(p.mi % 60); } static PyObject * TimeStamp_second(TimeStamp *self) { return PyFloat_FromDouble(TimeStamp_sec(self)); } static PyObject * TimeStamp_timeTime(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); return PyFloat_FromDouble(TimeStamp_abst(p.y, p.m - 1, p.d - 1, p.mi, 0) + TimeStamp_sec(self) - gmoff); } static PyObject * TimeStamp_raw(TimeStamp *self) { return PyString_FromStringAndSize((const char*)self->data, 8); } static PyObject * TimeStamp_str(TimeStamp *self) { char buf[128]; TimeStampParts p; int len; TimeStamp_unpack(self, &p); len =sprintf(buf, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%09.6f", p.y, p.m, p.d, p.mi / 60, p.mi % 60, TimeStamp_sec(self)); return PyString_FromStringAndSize(buf, len); } static PyObject * TimeStamp_laterThan(TimeStamp *self, PyObject *obj) { TimeStamp *o = NULL; TimeStampParts p; unsigned char new[8]; int i; if (obj->ob_type != self->ob_type) { PyErr_SetString(PyExc_TypeError, "expected TimeStamp object"); return NULL; } o = (TimeStamp *)obj; if (memcmp(self->data, o->data, 8) > 0) { Py_INCREF(self); return (PyObject *)self; } memcpy(new, o->data, 8); for (i = 7; i > 3; i--) { if (new[i] == 255) new[i] = 0; else { new[i]++; return TimeStamp_FromString((const char*)new); } } /* All but the first two bytes are the same. Need to increment the year, month, and day explicitly. */ TimeStamp_unpack(o, &p); if (p.mi >= 1439) { p.mi = 0; if (p.d == month_len[leap(p.y)][p.m - 1]) { p.d = 1; if (p.m == 12) { p.m = 1; p.y++; } else p.m++; } else p.d++; } else p.mi++; return TimeStamp_FromDate(p.y, p.m, p.d, p.mi / 60, p.mi % 60, 0); } static struct PyMethodDef TimeStamp_methods[] = { {"year", (PyCFunction)TimeStamp_year, METH_NOARGS}, {"minute", (PyCFunction)TimeStamp_minute, METH_NOARGS}, {"month", (PyCFunction)TimeStamp_month, METH_NOARGS}, {"day", (PyCFunction)TimeStamp_day, METH_NOARGS}, {"hour", (PyCFunction)TimeStamp_hour, METH_NOARGS}, {"second", (PyCFunction)TimeStamp_second, METH_NOARGS}, {"timeTime",(PyCFunction)TimeStamp_timeTime, METH_NOARGS}, {"laterThan", (PyCFunction)TimeStamp_laterThan, METH_O}, {"raw", (PyCFunction)TimeStamp_raw, METH_NOARGS}, {NULL, NULL}, }; static PyTypeObject TimeStamp_type = { PyObject_HEAD_INIT(NULL) 0, "persistent.TimeStamp", sizeof(TimeStamp), 0, (destructor)TimeStamp_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ (cmpfunc)TimeStamp_compare, /* tp_compare */ (reprfunc)TimeStamp_raw, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)TimeStamp_hash, /* tp_hash */ 0, /* tp_call */ (reprfunc)TimeStamp_str, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ TimeStamp_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ }; PyObject * TimeStamp_FromString(const char *buf) { /* buf must be exactly 8 characters */ TimeStamp *ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type); memcpy(ts->data, buf, 8); return (PyObject *)ts; } #define CHECK_RANGE(VAR, LO, HI) if ((VAR) < (LO) || (VAR) > (HI)) { \ return PyErr_Format(PyExc_ValueError, \ # VAR " must be between %d and %d: %d", \ (LO), (HI), (VAR)); \ } PyObject * TimeStamp_FromDate(int year, int month, int day, int hour, int min, double sec) { TimeStamp *ts = NULL; int d; unsigned int v; if (year < 1900) return PyErr_Format(PyExc_ValueError, "year must be greater than 1900: %d", year); CHECK_RANGE(month, 1, 12); d = days_in_month(year, month - 1); if (day < 1 || day > d) return PyErr_Format(PyExc_ValueError, "day must be between 1 and %d: %d", d, day); CHECK_RANGE(hour, 0, 23); CHECK_RANGE(min, 0, 59); /* Seconds are allowed to be anything, so chill If we did want to be pickly, 60 would be a better choice. if (sec < 0 || sec > 59) return PyErr_Format(PyExc_ValueError, "second must be between 0 and 59: %f", sec); */ ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type); v = (((year - 1900) * 12 + month - 1) * 31 + day - 1); v = (v * 24 + hour) * 60 + min; ts->data[0] = v / 16777216; ts->data[1] = (v % 16777216) / 65536; ts->data[2] = (v % 65536) / 256; ts->data[3] = v % 256; sec /= SCONV; v = (unsigned int)sec; ts->data[4] = v / 16777216; ts->data[5] = (v % 16777216) / 65536; ts->data[6] = (v % 65536) / 256; ts->data[7] = v % 256; return (PyObject *)ts; } PyObject * TimeStamp_TimeStamp(PyObject *obj, PyObject *args) { char *buf = NULL; int len = 0, y, mo, d, h = 0, m = 0; double sec = 0; if (PyArg_ParseTuple(args, "s#:TimeStamp", &buf, &len)) { if (len != 8) { PyErr_SetString(PyExc_ValueError, "8-character string expected"); return NULL; } return TimeStamp_FromString(buf); } PyErr_Clear(); if (!PyArg_ParseTuple(args, "iii|iid", &y, &mo, &d, &h, &m, &sec)) return NULL; return TimeStamp_FromDate(y, mo, d, h, m, sec); } static PyMethodDef TimeStampModule_functions[] = { {"TimeStamp", TimeStamp_TimeStamp, METH_VARARGS}, {NULL, NULL}, }; void initTimeStamp(void) { PyObject *m; if (TimeStamp_init_gmoff() < 0) return; m = Py_InitModule4("TimeStamp", TimeStampModule_functions, TimeStampModule_doc, NULL, PYTHON_API_VERSION); if (m == NULL) return; TimeStamp_type.ob_type = &PyType_Type; TimeStamp_type.tp_getattro = PyObject_GenericGetAttr; } zope2.13-2.13.21/source/ZODB3/src/persistent/wref.py0000644000175000017500000002325412214017464020573 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZODB-based persistent weakrefs $Id: wref.py 113734 2010-06-21 15:33:46Z ctheune $ """ __docformat__ = "reStructuredText" from persistent import Persistent import transaction WeakRefMarker = object() class WeakRef(object): """Persistent weak references Persistent weak references are used much like Python weak references. The major difference is that you can't specify an object to be called when the object is removed from the database. Here's an example. We'll start by creating a persistent object and a reference to it: >>> import persistent, ZODB.tests.MinPO >>> import ZODB.tests.util >>> ob = ZODB.tests.MinPO.MinPO() >>> ref = WeakRef(ob) >>> ref() is ob True The hash of the ref if the same as the hash of the referenced object: >>> hash(ref) == hash(ob) True Two refs to the same object are equal: >>> WeakRef(ob) == ref True >>> ob2 = ZODB.tests.MinPO.MinPO(1) >>> WeakRef(ob2) == ref False Lets save the reference and the referenced object in a database: >>> db = ZODB.tests.util.DB() >>> conn1 = db.open() >>> conn1.root()['ob'] = ob >>> conn1.root()['ref'] = ref >>> transaction.commit() If we open a new connection, we can use the reference: >>> conn2 = db.open() >>> conn2.root()['ref']() is conn2.root()['ob'] True >>> hash(conn2.root()['ref']) == hash(conn2.root()['ob']) True But if we delete the referenced object and pack: >>> del conn2.root()['ob'] >>> transaction.commit() >>> ZODB.tests.util.pack(db) And then look in a new connection: >>> conn3 = db.open() >>> conn3.root()['ob'] Traceback (most recent call last): ... KeyError: 'ob' Trying to dereference the reference returns None: >>> conn3.root()['ref']() Trying to get a hash, raises a type error: >>> hash(conn3.root()['ref']) Traceback (most recent call last): ... TypeError: Weakly-referenced object has gone away Always explicitly close databases: :) >>> db.close() >>> del ob, ref, db, conn1, conn2, conn3 When multiple databases are in use, a weakref in one database may point to an object in a different database. Let's create two new databases to demonstrate this. >>> dbA = ZODB.tests.util.DB( ... database_name = 'dbA', ... ) >>> dbB = ZODB.tests.util.DB( ... database_name = 'dbB', ... databases = dbA.databases, ... ) >>> connA1 = dbA.open() >>> connB1 = connA1.get_connection('dbB') Now create and add a new object and a weak reference, and add them to different databases. >>> ob = ZODB.tests.MinPO.MinPO() >>> ref = WeakRef(ob) >>> connA1.root()['ob'] = ob >>> connA1.add(ob) >>> connB1.root()['ref'] = ref >>> transaction.commit() After a succesful commit, the reference should know the oid, database name and connection of the object. >>> ref.oid == ob._p_oid True >>> ref.database_name == 'dbA' True >>> ref.dm is ob._p_jar is connA1 True If we open new connections, we should be able to use the reference. >>> connA2 = dbA.open() >>> connB2 = connA2.get_connection('dbB') >>> ref2 = connB2.root()['ref'] >>> ob2 = connA2.root()['ob'] >>> ref2() is ob2 True >>> ref2.oid == ob2._p_oid True >>> ref2.database_name == 'dbA' True >>> ref2.dm is ob2._p_jar is connA2 True Always explicitly close databases: :) >>> dbA.close() >>> dbB.close() """ # We set _p_oid to a marker so that the serialization system can # provide special handling of weakrefs. _p_oid = WeakRefMarker def __init__(self, ob): self._v_ob = ob self.oid = ob._p_oid self.dm = ob._p_jar if self.dm is not None: self.database_name = self.dm.db().database_name def __call__(self): try: return self._v_ob except AttributeError: try: self._v_ob = self.dm[self.oid] except (KeyError, AttributeError): return None return self._v_ob def __hash__(self): self = self() if self is None: raise TypeError('Weakly-referenced object has gone away') return hash(self) def __eq__(self, other): self = self() if self is None: raise TypeError('Weakly-referenced object has gone away') other = other() if other is None: raise TypeError('Weakly-referenced object has gone away') return self == other class PersistentWeakKeyDictionary(Persistent): """Persistent weak key dictionary This is akin to WeakKeyDictionaries. Note, however, that removal of items is extremely lazy. See below. We'll start by creating a PersistentWeakKeyDictionary and adding some persistent objects to it. >>> d = PersistentWeakKeyDictionary() >>> import ZODB.tests.util >>> p1 = ZODB.tests.util.P('p1') >>> p2 = ZODB.tests.util.P('p2') >>> p3 = ZODB.tests.util.P('p3') >>> d[p1] = 1 >>> d[p2] = 2 >>> d[p3] = 3 We'll create an extra persistent object that's not in the dict: >>> p4 = ZODB.tests.util.P('p4') Now we'll excercise iteration and item access: >>> l = [(str(k), d[k], d.get(k)) for k in d] >>> l.sort() >>> l [('P(p1)', 1, 1), ('P(p2)', 2, 2), ('P(p3)', 3, 3)] And the containment operator: >>> [p in d for p in [p1, p2, p3, p4]] [True, True, True, False] We can add the dict and the referenced objects to a database: >>> db = ZODB.tests.util.DB() >>> conn1 = db.open() >>> conn1.root()['p1'] = p1 >>> conn1.root()['d'] = d >>> conn1.root()['p2'] = p2 >>> conn1.root()['p3'] = p3 >>> transaction.commit() And things still work, as before: >>> l = [(str(k), d[k], d.get(k)) for k in d] >>> l.sort() >>> l [('P(p1)', 1, 1), ('P(p2)', 2, 2), ('P(p3)', 3, 3)] >>> [p in d for p in [p1, p2, p3, p4]] [True, True, True, False] Likewise, we can read the objects from another connection and things still work. >>> conn2 = db.open() >>> d = conn2.root()['d'] >>> p1 = conn2.root()['p1'] >>> p2 = conn2.root()['p2'] >>> p3 = conn2.root()['p3'] >>> l = [(str(k), d[k], d.get(k)) for k in d] >>> l.sort() >>> l [('P(p1)', 1, 1), ('P(p2)', 2, 2), ('P(p3)', 3, 3)] >>> [p in d for p in [p1, p2, p3, p4]] [True, True, True, False] Now, we'll delete one of the objects from the database, but *not* from the dictionary: >>> del conn2.root()['p2'] >>> transaction.commit() And pack the database, so that the no-longer referenced p2 is actually removed from the database. >>> ZODB.tests.util.pack(db) Now if we access the dictionary in a new connection, it no longer has p2: >>> conn3 = db.open() >>> d = conn3.root()['d'] >>> l = [(str(k), d[k], d.get(k)) for k in d] >>> l.sort() >>> l [('P(p1)', 1, 1), ('P(p3)', 3, 3)] It's worth nothing that that the versions of the dictionary in conn1 and conn2 still have p2, because p2 is still in the caches for those connections. Always explicitly close databases: :) >>> db.close() """ # TODO: It's expensive trying to load dead objects from the database. # It would be helpful if the data manager/connection cached these. def __init__(self, adict=None, **kwargs): self.data = {} if adict is not None: keys = getattr(adict, "keys", None) if keys is None: adict = dict(adict) self.update(adict) if kwargs: self.update(kwargs) def __getstate__(self): state = Persistent.__getstate__(self) state['data'] = state['data'].items() return state def __setstate__(self, state): state['data'] = dict([ (k, v) for (k, v) in state['data'] if k() is not None ]) Persistent.__setstate__(self, state) def __setitem__(self, key, value): self.data[WeakRef(key)] = value def __getitem__(self, key): return self.data[WeakRef(key)] def __delitem__(self, key): del self.data[WeakRef(key)] def get(self, key, default=None): """D.get(k[, d]) -> D[k] if k in D, else d. >>> import ZODB.tests.util >>> key = ZODB.tests.util.P("key") >>> missing = ZODB.tests.util.P("missing") >>> d = PersistentWeakKeyDictionary([(key, 1)]) >>> d.get(key) 1 >>> d.get(missing) >>> d.get(missing, 12) 12 """ return self.data.get(WeakRef(key), default) def __contains__(self, key): return WeakRef(key) in self.data def __iter__(self): for k in self.data: yield k() def update(self, adict): if isinstance(adict, PersistentWeakKeyDictionary): self.data.update(adict.update) else: for k, v in adict.items(): self.data[WeakRef(k)] = v # TODO: May need more methods, and tests. zope2.13-2.13.21/source/ZODB3/src/persistent/tests/0000755000175000017500000000000012214017464020412 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/persistent/tests/test_list.py0000644000175000017500000001462012214017464023001 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for PersistentList """ import unittest l0 = [] l1 = [0] l2 = [0, 1] class OtherList: def __init__(self, initlist): self.__data = initlist def __len__(self): return len(self.__data) def __getitem__(self, i): return self.__data[i] class TestPList(unittest.TestCase): def _getTargetClass(self): from persistent.list import PersistentList return PersistentList def test_volatile_attributes_not_persisted(self): # http://www.zope.org/Collectors/Zope/2052 m = self._getTargetClass()() m.foo = 'bar' m._v_baz = 'qux' state = m.__getstate__() self.failUnless('foo' in state) self.failIf('_v_baz' in state) def testTheWorld(self): # Test constructors pl = self._getTargetClass() u = pl() u0 = pl(l0) u1 = pl(l1) u2 = pl(l2) uu = pl(u) uu0 = pl(u0) uu1 = pl(u1) uu2 = pl(u2) v = pl(tuple(u)) v0 = pl(OtherList(u0)) vv = pl("this is also a sequence") # Test __repr__ eq = self.assertEqual eq(str(u0), str(l0), "str(u0) == str(l0)") eq(repr(u1), repr(l1), "repr(u1) == repr(l1)") eq(`u2`, `l2`, "`u2` == `l2`") # Test __cmp__ and __len__ def mycmp(a, b): r = cmp(a, b) if r < 0: return -1 if r > 0: return 1 return r all = [l0, l1, l2, u, u0, u1, u2, uu, uu0, uu1, uu2] for a in all: for b in all: eq(mycmp(a, b), mycmp(len(a), len(b)), "mycmp(a, b) == mycmp(len(a), len(b))") # Test __getitem__ for i in range(len(u2)): eq(u2[i], i, "u2[i] == i") # Test __setitem__ uu2[0] = 0 uu2[1] = 100 try: uu2[2] = 200 except IndexError: pass else: raise TestFailed("uu2[2] shouldn't be assignable") # Test __delitem__ del uu2[1] del uu2[0] try: del uu2[0] except IndexError: pass else: raise TestFailed("uu2[0] shouldn't be deletable") # Test __getslice__ for i in range(-3, 4): eq(u2[:i], l2[:i], "u2[:i] == l2[:i]") eq(u2[i:], l2[i:], "u2[i:] == l2[i:]") for j in range(-3, 4): eq(u2[i:j], l2[i:j], "u2[i:j] == l2[i:j]") # Test __setslice__ for i in range(-3, 4): u2[:i] = l2[:i] eq(u2, l2, "u2 == l2") u2[i:] = l2[i:] eq(u2, l2, "u2 == l2") for j in range(-3, 4): u2[i:j] = l2[i:j] eq(u2, l2, "u2 == l2") uu2 = u2[:] uu2[:0] = [-2, -1] eq(uu2, [-2, -1, 0, 1], "uu2 == [-2, -1, 0, 1]") uu2[0:] = [] eq(uu2, [], "uu2 == []") # Test __contains__ for i in u2: self.failUnless(i in u2, "i in u2") for i in min(u2)-1, max(u2)+1: self.failUnless(i not in u2, "i not in u2") # Test __delslice__ uu2 = u2[:] del uu2[1:2] del uu2[0:1] eq(uu2, [], "uu2 == []") uu2 = u2[:] del uu2[1:] del uu2[:1] eq(uu2, [], "uu2 == []") # Test __add__, __radd__, __mul__ and __rmul__ #self.failUnless(u1 + [] == [] + u1 == u1, "u1 + [] == [] + u1 == u1") self.failUnless(u1 + [1] == u2, "u1 + [1] == u2") #self.failUnless([-1] + u1 == [-1, 0], "[-1] + u1 == [-1, 0]") self.failUnless(u2 == u2*1 == 1*u2, "u2 == u2*1 == 1*u2") self.failUnless(u2+u2 == u2*2 == 2*u2, "u2+u2 == u2*2 == 2*u2") self.failUnless(u2+u2+u2 == u2*3 == 3*u2, "u2+u2+u2 == u2*3 == 3*u2") # Test append u = u1[:] u.append(1) eq(u, u2, "u == u2") # Test insert u = u2[:] u.insert(0, -1) eq(u, [-1, 0, 1], "u == [-1, 0, 1]") # Test pop u = pl([0, -1, 1]) u.pop() eq(u, [0, -1], "u == [0, -1]") u.pop(0) eq(u, [-1], "u == [-1]") # Test remove u = u2[:] u.remove(1) eq(u, u1, "u == u1") # Test count u = u2*3 eq(u.count(0), 3, "u.count(0) == 3") eq(u.count(1), 3, "u.count(1) == 3") eq(u.count(2), 0, "u.count(2) == 0") # Test index eq(u2.index(0), 0, "u2.index(0) == 0") eq(u2.index(1), 1, "u2.index(1) == 1") try: u2.index(2) except ValueError: pass else: raise TestFailed("expected ValueError") # Test reverse u = u2[:] u.reverse() eq(u, [1, 0], "u == [1, 0]") u.reverse() eq(u, u2, "u == u2") # Test sort u = pl([1, 0]) u.sort() eq(u, u2, "u == u2") # Test keyword arguments to sort u.sort(cmp=lambda x,y: cmp(y, x)) eq(u, [1, 0], "u == [1, 0]") u.sort(key=lambda x:-x) eq(u, [1, 0], "u == [1, 0]") u.sort(reverse=True) eq(u, [1, 0], "u == [1, 0]") # Passing any other keyword arguments results in a TypeError try: u.sort(blah=True) except TypeError: pass else: raise TestFailed("expected TypeError") # Test extend u = u1[:] u.extend(u2) eq(u, u1 + u2, "u == u1 + u2") # Test iadd u = u1[:] u += u2 eq(u, u1 + u2, "u == u1 + u2") # Test imul u = u1[:] u *= 3 eq(u, u1 + u1 + u1, "u == u1 + u1 + u1") def test_suite(): return unittest.makeSuite(TestPList) if __name__ == "__main__": loader = unittest.TestLoader() unittest.main(testLoader=loader) zope2.13-2.13.21/source/ZODB3/src/persistent/tests/test_wref.py0000644000175000017500000000162012214017464022765 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest import os if os.environ.get('USE_ZOPE_TESTING_DOCTEST'): from zope.testing.doctest import DocTestSuite else: from doctest import DocTestSuite def test_suite(): return DocTestSuite('persistent.wref') if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/ZODB3/src/persistent/tests/utils.py0000644000175000017500000000431712214017464022131 0ustar arnauarnau class ResettingJar(object): """Testing stub for _p_jar attribute. """ def __init__(self): from persistent.cPickleCache import PickleCache # XXX stub it! self.cache = PickleCache(self) self.oid = 1 self.registered = {} def add(self, obj): import struct obj._p_oid = struct.pack(">Q", self.oid) self.oid += 1 obj._p_jar = self self.cache[obj._p_oid] = obj def close(self): pass # the following methods must be implemented to be a jar def setklassstate(self): # I don't know what this method does, but the pickle cache # constructor calls it. pass def register(self, obj): self.registered[obj] = 1 def setstate(self, obj): # Trivial setstate() implementation that just re-initializes # the object. This isn't what setstate() is supposed to do, # but it suffices for the tests. obj.__class__.__init__(obj) class RememberingJar(object): """Testing stub for _p_jar attribute. """ def __init__(self): from persistent.cPickleCache import PickleCache # XXX stub it! self.cache = PickleCache(self) self.oid = 1 self.registered = {} def add(self, obj): import struct obj._p_oid = struct.pack(">Q", self.oid) self.oid += 1 obj._p_jar = self self.cache[obj._p_oid] = obj # Remember object's state for later. self.obj = obj self.remembered = obj.__getstate__() def close(self): pass def fake_commit(self): self.remembered = self.obj.__getstate__() self.obj._p_changed = 0 # the following methods must be implemented to be a jar def setklassstate(self): # I don't know what this method does, but the pickle cache # constructor calls it. pass def register(self, obj): self.registered[obj] = 1 def setstate(self, obj): # Trivial setstate() implementation that resets the object's # state as of the time it was added to the jar. # This isn't what setstate() is supposed to do, # but it suffices for the tests. obj.__setstate__(self.remembered) zope2.13-2.13.21/source/ZODB3/src/persistent/tests/__init__.py0000644000175000017500000000001212214017464022514 0ustar arnauarnau# package zope2.13-2.13.21/source/ZODB3/src/persistent/tests/persistent.txt0000644000175000017500000002547612214017464023371 0ustar arnauarnauTests for `persistent.Persistent` ================================= This document is an extended doc test that covers the basics of the Persistent base class. The test expects a class named `P` to be provided in its globals. The `P` class implements the `Persistent` interface. Test framework -------------- The class `P` needs to behave like `ExampleP`. (Note that the code below is *not* part of the tests.) :: class ExampleP(Persistent): def __init__(self): self.x = 0 def inc(self): self.x += 1 The tests use stub data managers. A data manager is responsible for loading and storing the state of a persistent object. It's stored in the ``_p_jar`` attribute of a persistent object. >>> class DM: ... def __init__(self): ... self.called = 0 ... def register(self, ob): ... self.called += 1 ... def setstate(self, ob): ... ob.__setstate__({'x': 42}) >>> class BrokenDM(DM): ... def register(self,ob): ... self.called += 1 ... raise NotImplementedError ... def setstate(self,ob): ... raise NotImplementedError >>> from persistent import Persistent Test Persistent without Data Manager ------------------------------------ First do some simple tests of a Persistent instance that does not have a data manager (``_p_jar``). >>> p = P() >>> p.x 0 >>> p._p_changed False >>> p._p_state 0 >>> p._p_jar >>> p._p_oid Verify that modifications have no effect on ``_p_state`` of ``_p_changed``. >>> p.inc() >>> p.inc() >>> p.x 2 >>> p._p_changed False >>> p._p_state 0 Try all sorts of different ways to change the object's state. >>> p._p_deactivate() >>> p._p_state 0 >>> p._p_changed = True >>> p._p_state 0 >>> del p._p_changed >>> p._p_changed False >>> p._p_state 0 >>> p.x 2 We can store a size estimation in ``_p_estimated_size``. Its default is 0. The size estimation can be used by a cache associated with the data manager to help in the implementation of its replacement strategy or its size bounds. Of course, the estimated size must not be negative. >>> p._p_estimated_size 0 >>> p._p_estimated_size = 1000 >>> p._p_estimated_size 1024 Huh? Why is the estimated size coming out different than what we put in? The reason is that the size isn't stored exactly. For backward compatibility reasons, the size needs to fit in 24 bits, so, internally, it is adjusted somewhat. >>> p._p_estimated_size = -1 Traceback (most recent call last): .... ValueError: _p_estimated_size must not be negative Test Persistent with Data Manager --------------------------------- Next try some tests of an object with a data manager. The `DM` class is a simple testing stub. >>> p = P() >>> dm = DM() >>> p._p_oid = "00000012" >>> p._p_jar = dm >>> p._p_changed 0 >>> dm.called 0 Modifying the object marks it as changed and registers it with the data manager. Subsequent modifications don't have additional side-effects. >>> p.inc() >>> p._p_changed 1 >>> dm.called 1 >>> p.inc() >>> p._p_changed 1 >>> dm.called 1 It's not possible to deactivate a modified object. >>> p._p_deactivate() >>> p._p_changed 1 It is possible to invalidate it. That's the key difference between deactivation and invalidation. >>> p._p_invalidate() >>> p._p_state -1 Now that the object is a ghost, any attempt to modify it will require that it be unghosted first. The test data manager has the odd property that it sets the object's ``x`` attribute to ``42`` when it is unghosted. >>> p.inc() >>> p.x 43 >>> dm.called 2 You can manually reset the changed field to ``False``, although it's not clear why you would want to do that. The object changes to the ``UPTODATE`` state but retains its modifications. >>> p._p_changed = False >>> p._p_state 0 >>> p._p_changed False >>> p.x 43 >>> p.inc() >>> p._p_changed True >>> dm.called 3 ``__getstate__()`` and ``__setstate__()`` ----------------------------------------- The next several tests cover the ``__getstate__()`` and ``__setstate__()`` implementations. >>> p = P() >>> state = p.__getstate__() >>> isinstance(state, dict) True >>> state['x'] 0 >>> p._p_state 0 Calling setstate always leaves the object in the uptodate state? (I'm not entirely clear on this one.) >>> p.__setstate__({'x': 5}) >>> p._p_state 0 Assigning to a volatile attribute has no effect on the object state. >>> p._v_foo = 2 >>> p.__getstate__() {'x': 5} >>> p._p_state 0 The ``_p_serial`` attribute is not affected by calling setstate. >>> p._p_serial = "00000012" >>> p.__setstate__(p.__getstate__()) >>> p._p_serial '00000012' Change Ghost test ----------------- If an object is a ghost and its ``_p_changed`` is set to ``True`` (any true value), it should activate (unghostify) the object. This behavior is new in ZODB 3.6; before then, an attempt to do ``ghost._p_changed = True`` was ignored. >>> p = P() >>> p._p_jar = DM() >>> p._p_oid = 1 >>> p._p_deactivate() >>> p._p_changed # None >>> p._p_state # ghost state -1 >>> p._p_changed = True >>> p._p_changed 1 >>> p._p_state # changed state 1 >>> p.x 42 Activate, deactivate, and invalidate ------------------------------------ Some of these tests are redundant, but are included to make sure there are explicit and simple tests of ``_p_activate()``, ``_p_deactivate()``, and ``_p_invalidate()``. >>> p = P() >>> p._p_oid = 1 >>> p._p_jar = DM() >>> p._p_deactivate() >>> p._p_state -1 >>> p._p_activate() >>> p._p_state 0 >>> p.x 42 >>> p.inc() >>> p.x 43 >>> p._p_state 1 >>> p._p_invalidate() >>> p._p_state -1 >>> p.x 42 Test failures ------------- The following tests cover various errors cases. When an object is modified, it registers with its data manager. If that registration fails, the exception is propagated and the object stays in the up-to-date state. It shouldn't change to the modified state, because it won't be saved when the transaction commits. >>> p = P() >>> p._p_oid = 1 >>> p._p_jar = BrokenDM() >>> p._p_state 0 >>> p._p_jar.called 0 >>> p._p_changed = 1 Traceback (most recent call last): ... NotImplementedError >>> p._p_jar.called 1 >>> p._p_state 0 Make sure that exceptions that occur inside the data manager's ``setstate()`` method propagate out to the caller. >>> p = P() >>> p._p_oid = 1 >>> p._p_jar = BrokenDM() >>> p._p_deactivate() >>> p._p_state -1 >>> p._p_activate() Traceback (most recent call last): ... NotImplementedError >>> p._p_state -1 Special test to cover layout of ``__dict__`` -------------------------------------------- We once had a bug in the `Persistent` class that calculated an incorrect offset for the ``__dict__`` attribute. It assigned ``__dict__`` and ``_p_jar`` to the same location in memory. This is a simple test to make sure they have different locations. >>> p = P() >>> p.inc() >>> p.inc() >>> 'x' in p.__dict__ True >>> p._p_jar Inheritance and metaclasses --------------------------- Simple tests to make sure it's possible to inherit from the `Persistent` base class multiple times. There used to be metaclasses involved in `Persistent` that probably made this a more interesting test. >>> class A(Persistent): ... pass >>> class B(Persistent): ... pass >>> class C(A, B): ... pass >>> class D(object): ... pass >>> class E(D, B): ... pass >>> a = A() >>> b = B() >>> c = C() >>> d = D() >>> e = E() Also make sure that it's possible to define `Persistent` classes that have a custom metaclass. >>> class alternateMeta(type): ... type >>> class alternate(object): ... __metaclass__ = alternateMeta >>> class mixedMeta(alternateMeta, type): ... pass >>> class mixed(alternate, Persistent): ... pass >>> class mixed(Persistent, alternate): ... pass Basic type structure -------------------- >>> Persistent.__dictoffset__ 0 >>> Persistent.__weakrefoffset__ 0 >>> Persistent.__basicsize__ > object.__basicsize__ True >>> P.__dictoffset__ > 0 True >>> P.__weakrefoffset__ > 0 True >>> P.__dictoffset__ < P.__weakrefoffset__ True >>> P.__basicsize__ > Persistent.__basicsize__ True Slots ----- These are some simple tests of classes that have an ``__slots__`` attribute. Some of the classes should have slots, others shouldn't. >>> class noDict(object): ... __slots__ = ['foo'] >>> class p_noDict(Persistent): ... __slots__ = ['foo'] >>> class p_shouldHaveDict(p_noDict): ... pass >>> p_noDict.__dictoffset__ 0 >>> x = p_noDict() >>> x.foo = 1 >>> x.foo 1 >>> x.bar = 1 Traceback (most recent call last): ... AttributeError: 'p_noDict' object has no attribute 'bar' >>> x._v_bar = 1 Traceback (most recent call last): ... AttributeError: 'p_noDict' object has no attribute '_v_bar' >>> x.__dict__ Traceback (most recent call last): ... AttributeError: 'p_noDict' object has no attribute '__dict__' The various _p_ attributes are unaffected by slots. >>> p._p_oid >>> p._p_jar >>> p._p_state 0 If the most-derived class does not specify >>> p_shouldHaveDict.__dictoffset__ > 0 True >>> x = p_shouldHaveDict() >>> isinstance(x.__dict__, dict) True Pickling -------- There's actually a substantial effort involved in making subclasses of `Persistent` work with plain-old pickle. The ZODB serialization layer never calls pickle on an object; it pickles the object's class description and its state as two separate pickles. >>> import pickle >>> p = P() >>> p.inc() >>> p2 = pickle.loads(pickle.dumps(p)) >>> p2.__class__ is P True >>> p2.x == p.x True We should also test that pickle works with custom getstate and setstate. Perhaps even reduce. The problem is that pickling depends on finding the class in a particular module, and classes defined here won't appear in any module. We could require each user of the tests to define a base class, but that might be tedious. Interfaces ---------- Some versions of Zope and ZODB have the `zope.interface` package available. If it is available, then persistent will be associated with several interfaces. It's hard to write a doctest test that runs the tests only if `zope.interface` is available, so this test looks a little unusual. One problem is that the assert statements won't do anything if you run with `-O`. >>> try: ... import zope.interface ... except ImportError: ... pass ... else: ... from persistent.interfaces import IPersistent ... assert IPersistent.implementedBy(Persistent) ... p = Persistent() ... assert IPersistent.providedBy(p) ... assert IPersistent.implementedBy(P) ... p = P() ... assert IPersistent.providedBy(p) zope2.13-2.13.21/source/ZODB3/src/persistent/tests/test_overriding_attrs.py0000644000175000017500000002530412214017464025414 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Overriding attr methods This module tests and documents, through example, overriding attribute access methods. """ from persistent import Persistent # ouch! def _resettingJar(): from persistent.tests.utils import ResettingJar return ResettingJar() def _rememberingJar(): from persistent.tests.utils import RememberingJar return RememberingJar() class SampleOverridingGetattr(Persistent): """Example of overriding __getattr__ """ def __getattr__(self, name): """Get attributes that can't be gotten the usual way The __getattr__ method works pretty much the same for persistent classes as it does for other classes. No special handling is needed. If an object is a ghost, then it will be activated before __getattr__ is called. In this example, our objects returns a tuple with the attribute name, converted to upper case and the value of _p_changed, for any attribute that isn't handled by the default machinery. >>> o = SampleOverridingGetattr() >>> o._p_changed False >>> o._p_oid >>> o._p_jar >>> o.spam ('SPAM', False) >>> o.spam = 1 >>> o.spam 1 We'll save the object, so it can be deactivated: >>> jar = _resettingJar() >>> jar.add(o) >>> o._p_deactivate() >>> o._p_changed And now, if we ask for an attribute it doesn't have, >>> o.eggs ('EGGS', False) And we see that the object was activated before calling the __getattr__ method. """ # Don't pretend we have any special attributes. if name.startswith("__") and name.endswrith("__"): raise AttributeError(name) else: return name.upper(), self._p_changed class SampleOverridingGetattributeSetattrAndDelattr(Persistent): """Example of overriding __getattribute__, __setattr__, and __delattr__ In this example, we'll provide an example that shows how to override the __getattribute__, __setattr__, and __delattr__ methods. We'll create a class that stores it's attributes in a secret dictionary within it's instance dictionary. The class will have the policy that variables with names starting with 'tmp_' will be volatile. """ def __init__(self, **kw): self.__dict__['__secret__'] = kw.copy() def __getattribute__(self, name): """Get an attribute value The __getattribute__ method is called for all attribute accesses. It overrides the attribute access support inherited from Persistent. Our sample class let's us provide initial values as keyword arguments to the constructor: >>> o = SampleOverridingGetattributeSetattrAndDelattr(x=1) >>> o._p_changed 0 >>> o._p_oid >>> o._p_jar >>> o.x 1 >>> o.y Traceback (most recent call last): ... AttributeError: y Next, we'll save the object in a database so that we can deactivate it: >>> jar = _rememberingJar() >>> jar.add(o) >>> o._p_deactivate() >>> o._p_changed And we'll get some data: >>> o.x 1 which activates the object: >>> o._p_changed 0 It works for missing attribes too: >>> o._p_deactivate() >>> o._p_changed >>> o.y Traceback (most recent call last): ... AttributeError: y >>> o._p_changed 0 See the very important note in the comment below! """ ################################################################# # IMPORTANT! READ THIS! 8-> # # We *always* give Persistent a chance first. # Persistent handles certain special attributes, like _p_ # attributes. In particular, the base class handles __dict__ # and __class__. # # We call _p_getattr. If it returns True, then we have to # use Persistent.__getattribute__ to get the value. # ################################################################# if Persistent._p_getattr(self, name): return Persistent.__getattribute__(self, name) # Data should be in our secret dictionary: secret = self.__dict__['__secret__'] if name in secret: return secret[name] # Maybe it's a method: meth = getattr(self.__class__, name, None) if meth is None: raise AttributeError(name) return meth.__get__(self, self.__class__) def __setattr__(self, name, value): """Set an attribute value The __setattr__ method is called for all attribute assignments. It overrides the attribute assignment support inherited from Persistent. Implementors of __setattr__ methods: 1. Must call Persistent._p_setattr first to allow it to handle some attributes and to make sure that the object is activated if necessary, and 2. Must set _p_changed to mark objects as changed. See the comments in the source below. >>> o = SampleOverridingGetattributeSetattrAndDelattr() >>> o._p_changed 0 >>> o._p_oid >>> o._p_jar >>> o.x Traceback (most recent call last): ... AttributeError: x >>> o.x = 1 >>> o.x 1 Because the implementation doesn't store attributes directly in the instance dictionary, we don't have a key for the attribute: >>> 'x' in o.__dict__ False Next, we'll give the object a "remembering" jar so we can deactivate it: >>> jar = _rememberingJar() >>> jar.add(o) >>> o._p_deactivate() >>> o._p_changed We'll modify an attribute >>> o.y = 2 >>> o.y 2 which reactivates it, and markes it as modified, because our implementation marked it as modified: >>> o._p_changed 1 Now, if fake a commit: >>> jar.fake_commit() >>> o._p_changed 0 And deactivate the object: >>> o._p_deactivate() >>> o._p_changed and then set a variable with a name starting with 'tmp_', The object will be activated, but not marked as modified, because our __setattr__ implementation doesn't mark the object as changed if the name starts with 'tmp_': >>> o.tmp_foo = 3 >>> o._p_changed 0 >>> o.tmp_foo 3 """ ################################################################# # IMPORTANT! READ THIS! 8-> # # We *always* give Persistent a chance first. # Persistent handles certain special attributes, like _p_ # attributes. # # We call _p_setattr. If it returns True, then we are done. # It has already set the attribute. # ################################################################# if Persistent._p_setattr(self, name, value): return self.__dict__['__secret__'][name] = value if not name.startswith('tmp_'): self._p_changed = 1 def __delattr__(self, name): """Delete an attribute value The __delattr__ method is called for all attribute deletions. It overrides the attribute deletion support inherited from Persistent. Implementors of __delattr__ methods: 1. Must call Persistent._p_delattr first to allow it to handle some attributes and to make sure that the object is activated if necessary, and 2. Must set _p_changed to mark objects as changed. See the comments in the source below. >>> o = SampleOverridingGetattributeSetattrAndDelattr( ... x=1, y=2, tmp_z=3) >>> o._p_changed 0 >>> o._p_oid >>> o._p_jar >>> o.x 1 >>> del o.x >>> o.x Traceback (most recent call last): ... AttributeError: x Next, we'll save the object in a jar so that we can deactivate it: >>> jar = _rememberingJar() >>> jar.add(o) >>> o._p_deactivate() >>> o._p_changed If we delete an attribute: >>> del o.y The object is activated. It is also marked as changed because our implementation marked it as changed. >>> o._p_changed 1 >>> o.y Traceback (most recent call last): ... AttributeError: y >>> o.tmp_z 3 Now, if fake a commit: >>> jar.fake_commit() >>> o._p_changed 0 And deactivate the object: >>> o._p_deactivate() >>> o._p_changed and then delete a variable with a name starting with 'tmp_', The object will be activated, but not marked as modified, because our __delattr__ implementation doesn't mark the object as changed if the name starts with 'tmp_': >>> del o.tmp_z >>> o._p_changed 0 >>> o.tmp_z Traceback (most recent call last): ... AttributeError: tmp_z """ ################################################################# # IMPORTANT! READ THIS! 8-> # # We *always* give Persistent a chance first. # Persistent handles certain special attributes, like _p_ # attributes. # # We call _p_delattr. If it returns True, then we are done. # It has already deleted the attribute. # ################################################################# if Persistent._p_delattr(self, name): return del self.__dict__['__secret__'][name] if not name.startswith('tmp_'): self._p_changed = 1 def test_suite(): import os if os.environ.get('USE_ZOPE_TESTING_DOCTEST'): from zope.testing.doctest import DocTestSuite else: from doctest import DocTestSuite return DocTestSuite() zope2.13-2.13.21/source/ZODB3/src/persistent/tests/test_pickle.py0000644000175000017500000001436612214017464023304 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from persistent import Persistent import pickle def print_dict(d): d = d.items() d.sort() print '{%s}' % (', '.join( [('%r: %r' % (k, v)) for (k, v) in d] )) def cmpattrs(self, other, *attrs): for attr in attrs: if attr[:3] in ('_v_', '_p_'): continue c = cmp(getattr(self, attr, None), getattr(other, attr, None)) if c: return c return 0 class Simple(Persistent): def __init__(self, name, **kw): self.__name__ = name self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', *(self.__dict__.keys())) def test_basic_pickling(): """ >>> x = Simple('x', aaa=1, bbb='foo') >>> print_dict(x.__getstate__()) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> f, (c,), state = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Simple' >>> print_dict(state) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.__setstate__({'z': 1}) >>> x.__dict__ {'z': 1} """ class Custom(Simple): def __new__(cls, x, y): r = Persistent.__new__(cls) r.x, r.y = x, y return r def __init__(self, x, y): self.a = 42 def __getnewargs__(self): return self.x, self.y def __getstate__(self): return self.a def __setstate__(self, a): self.a = a def test_pickling_w_overrides(): """ >>> x = Custom('x', 'y') >>> x.a = 99 >>> (f, (c, ax, ay), a) = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Custom' >>> ax, ay, a ('x', 'y', 99) >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class Slotted(Persistent): __slots__ = 's1', 's2', '_p_splat', '_v_eek' def __init__(self, s1, s2): self.s1, self.s2 = s1, s2 self._v_eek = 1 self._p_splat = 2 class SubSlotted(Slotted): __slots__ = 's3', 's4' def __init__(self, s1, s2, s3): Slotted.__init__(self, s1, s2) self.s3 = s3 def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4') def test_pickling_w_slots_only(): """ >>> x = SubSlotted('x', 'y', 'z') >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class SubSubSlotted(SubSlotted): def __init__(self, s1, s2, s3, **kw): SubSlotted.__init__(self, s1, s2, s3) self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4', *(self.__dict__.keys())) def test_pickling_w_slots(): """ >>> x = SubSubSlotted('x', 'y', 'z', aaa=1, bbb='foo') >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ def test_pickling_w_slots_w_empty_dict(): """ >>> x = SubSubSlotted('x', 'y', 'z') >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ import os if os.environ.get('USE_ZOPE_TESTING_DOCTEST'): from zope.testing.doctest import DocTestSuite else: from doctest import DocTestSuite import unittest def test_suite(): return unittest.TestSuite(( DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/ZODB3/src/persistent/tests/test_persistent.py0000644000175000017500000000316512214017464024230 0ustar arnauarnau############################################################################## # # Copyright (c) Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from persistent import Persistent, simple_new import os if os.environ.get('USE_ZOPE_TESTING_DOCTEST'): from zope.testing import doctest else: import doctest import unittest class P(Persistent): def __init__(self): self.x = 0 def inc(self): self.x += 1 def cpersistent_setstate_pointer_sanity(): """ >>> Persistent().__setstate__({}) Traceback (most recent call last): ... TypeError: this object has no instance dictionary >>> class C(Persistent): __slots__ = 'x', 'y' >>> C().__setstate__(({}, {})) Traceback (most recent call last): ... TypeError: this object has no instance dictionary """ def cpersistent_simple_new_invalid_argument(): """ >>> simple_new('') Traceback (most recent call last): ... TypeError: simple_new argument must be a type object. """ def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite("persistent.txt", globs={"P": P}), doctest.DocTestSuite(), )) zope2.13-2.13.21/source/ZODB3/src/persistent/tests/test_PickleCache.py0000644000175000017500000000770412214017464024166 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## class DummyConnection: def setklassstate(self, obj): """Method used by PickleCache.""" def test_delitem(): """ >>> from persistent import PickleCache >>> conn = DummyConnection() >>> cache = PickleCache(conn) >>> del cache[''] Traceback (most recent call last): ... KeyError: '' >>> from persistent import Persistent >>> p = Persistent() >>> p._p_oid = 'foo' >>> p._p_jar = conn >>> cache['foo'] = p >>> del cache['foo'] """ def new_ghost(): """ Creating ghosts (from scratch, as opposed to ghostifying a non-ghost) in the curremt implementation is rather tricky. IPeristent doesn't really provide the right interface given that: - _p_deactivate and _p_invalidate are overridable and could assume that the object's state is properly initialized. - Assigning _p_changed to None or deleting it just calls _p_deactivate or _p_invalidate. The current cache implementation is intimately tied up with the persistence implementation and has internal access to the persistence state. The cache implementation can update the persistence state for newly created and ininitialized objects directly. The future persistence and cache implementations will be far more decoupled. The persistence implementation will only manage object state and generate object-usage events. The cache implemnentation(s) will be rersponsible for managing persistence-related (meta-)state, such as _p_state, _p_changed, _p_oid, etc. So in that future implemention, the cache will be more central to managing object persistence information. Caches have a new_ghost method that: - adds an object to the cache, and - initializes its persistence data. >>> import persistent >>> class C(persistent.Persistent): ... pass >>> jar = object() >>> cache = persistent.PickleCache(jar, 10, 100) >>> ob = C.__new__(C) >>> cache.new_ghost('1', ob) >>> ob._p_changed >>> ob._p_jar is jar True >>> ob._p_oid '1' >>> cache.cache_non_ghost_count, cache.total_estimated_size (0, 0) Peristent meta classes work too: >>> import ZODB.persistentclass >>> class PC: ... __metaclass__ = ZODB.persistentclass.PersistentMetaClass >>> PC._p_oid >>> PC._p_jar >>> PC._p_serial >>> PC._p_changed False >>> cache.new_ghost('2', PC) >>> PC._p_oid '2' >>> PC._p_jar is jar True >>> PC._p_serial >>> PC._p_changed False """ def cache_invalidate_and_minimize_used_to_leak_None_ref(): """Persistent weak references >>> import transaction >>> import ZODB.tests.util >>> db = ZODB.tests.util.DB() >>> conn = db.open() >>> conn.root.p = p = conn.root().__class__() >>> transaction.commit() >>> import sys >>> old = sys.getrefcount(None) >>> conn._cache.invalidate(p._p_oid) >>> sys.getrefcount(None) - old 0 >>> _ = conn.root.p.keys() >>> old = sys.getrefcount(None) >>> conn._cache.minimize() >>> sys.getrefcount(None) - old 0 >>> db.close() """ import os if os.environ.get('USE_ZOPE_TESTING_DOCTEST'): from zope.testing.doctest import DocTestSuite else: from doctest import DocTestSuite import unittest def test_suite(): return unittest.TestSuite(( DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/ZODB3/src/persistent/tests/test_mapping.py0000644000175000017500000001354112214017464023462 0ustar arnauarnau############################################################################## # # Copyright (c) Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import unittest from zope.testing import setupstack def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('README.txt'), )) l0 = {} l1 = {0:0} l2 = {0:0, 1:1} class MappingTests(unittest.TestCase): def _getTargetClass(self): from persistent.mapping import PersistentMapping return PersistentMapping def test_volatile_attributes_not_persisted(self): # http://www.zope.org/Collectors/Zope/2052 m = self._getTargetClass()() m.foo = 'bar' m._v_baz = 'qux' state = m.__getstate__() self.failUnless('foo' in state) self.failIf('_v_baz' in state) def testTheWorld(self): # Test constructors pm = self._getTargetClass() u = pm() u0 = pm(l0) u1 = pm(l1) u2 = pm(l2) uu = pm(u) uu0 = pm(u0) uu1 = pm(u1) uu2 = pm(u2) class OtherMapping: def __init__(self, initmapping): self.__data = initmapping def items(self): return self.__data.items() v0 = pm(OtherMapping(u0)) vv = pm([(0, 0), (1, 1)]) # Test __repr__ eq = self.assertEqual eq(str(u0), str(l0), "str(u0) == str(l0)") eq(repr(u1), repr(l1), "repr(u1) == repr(l1)") eq(`u2`, `l2`, "`u2` == `l2`") # Test __cmp__ and __len__ def mycmp(a, b): r = cmp(a, b) if r < 0: return -1 if r > 0: return 1 return r all = [l0, l1, l2, u, u0, u1, u2, uu, uu0, uu1, uu2] for a in all: for b in all: eq(mycmp(a, b), mycmp(len(a), len(b)), "mycmp(a, b) == mycmp(len(a), len(b))") # Test __getitem__ for i in range(len(u2)): eq(u2[i], i, "u2[i] == i") # Test get for i in range(len(u2)): eq(u2.get(i), i, "u2.get(i) == i") eq(u2.get(i, 5), i, "u2.get(i, 5) == i") for i in min(u2)-1, max(u2)+1: eq(u2.get(i), None, "u2.get(i) == None") eq(u2.get(i, 5), 5, "u2.get(i, 5) == 5") # Test __setitem__ uu2[0] = 0 uu2[1] = 100 uu2[2] = 200 # Test __delitem__ del uu2[1] del uu2[0] try: del uu2[0] except KeyError: pass else: raise TestFailed("uu2[0] shouldn't be deletable") # Test __contains__ for i in u2: self.failUnless(i in u2, "i in u2") for i in min(u2)-1, max(u2)+1: self.failUnless(i not in u2, "i not in u2") # Test update l = {"a":"b"} u = pm(l) u.update(u2) for i in u: self.failUnless(i in l or i in u2, "i in l or i in u2") for i in l: self.failUnless(i in u, "i in u") for i in u2: self.failUnless(i in u, "i in u") # Test setdefault x = u2.setdefault(0, 5) eq(x, 0, "u2.setdefault(0, 5) == 0") x = u2.setdefault(5, 5) eq(x, 5, "u2.setdefault(5, 5) == 5") self.failUnless(5 in u2, "5 in u2") # Test pop x = u2.pop(1) eq(x, 1, "u2.pop(1) == 1") self.failUnless(1 not in u2, "1 not in u2") try: u2.pop(1) except KeyError: pass else: raise TestFailed("1 should not be poppable from u2") x = u2.pop(1, 7) eq(x, 7, "u2.pop(1, 7) == 7") # Test popitem items = u2.items() key, value = u2.popitem() self.failUnless((key, value) in items, "key, value in items") self.failUnless(key not in u2, "key not in u2") # Test clear u2.clear() eq(u2, {}, "u2 == {}") def test_legacy_data(): """ We've deprecated PersistentDict. If you import persistent.dict.PersistentDict, you'll get persistent.mapping.PersistentMapping. >>> import persistent.dict, persistent.mapping >>> persistent.dict.PersistentDict is persistent.mapping.PersistentMapping True PersistentMapping uses a data attribute for it's mapping data: >>> m = persistent.mapping.PersistentMapping() >>> m.__dict__ {'data': {}} In the past, it used a _container attribute. For some time, the implementation continued to use a _container attribute in pickles (__get/setstate__) to be compatible with older releases. This isn't really necessary any more. In fact, releases for which this might matter can no longer share databases with current releases. Because releases as recent as 3.9.0b5 still use _container in saved state, we need to accept such state, but we stop producing it. If we reset it's __dict__ with legacy data: >>> m.__dict__.clear() >>> m.__dict__['_container'] = {'a': 1} >>> m.__dict__ {'_container': {'a': 1}} >>> m._p_changed = 0 But when we perform any operations on it, the data will be converted without marking the object as changed: >>> m {'a': 1} >>> m.__dict__ {'data': {'a': 1}} >>> m._p_changed 0 >>> m.__getstate__() {'data': {'a': 1}} """ def test_suite(): return unittest.TestSuite(( doctest.DocTestSuite(), unittest.makeSuite(MappingTests), )) zope2.13-2.13.21/source/ZODB3/src/persistent/tests/testPersistent.py0000644000175000017500000002176312214017464024035 0ustar arnauarnau############################################################################# # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest Picklable = None # avoid global import of Persistent; updated later class PersistenceTest(unittest.TestCase): def _makeOne(self): from persistent import Persistent class P(Persistent): pass return P() def _makeJar(self): from persistent.tests.utils import ResettingJar return ResettingJar() def test_oid_initial_value(self): obj = self._makeOne() self.assertEqual(obj._p_oid, None) def test_oid_mutable_and_deletable_when_no_jar(self): obj = self._makeOne() obj._p_oid = 12 self.assertEqual(obj._p_oid, 12) del obj._p_oid def test_oid_immutable_when_in_jar(self): obj = self._makeOne() jar = self._makeJar() jar.add(obj) # Can't change oid of cache object. def deloid(): del obj._p_oid self.assertRaises(ValueError, deloid) def setoid(): obj._p_oid = 12 self.assertRaises(ValueError, setoid) # The value returned for _p_changed can be one of: # 0 -- it is not changed # 1 -- it is changed # None -- it is a ghost def test_change_via_setattr(self): from persistent import CHANGED obj = self._makeOne() jar = self._makeJar() jar.add(obj) obj.x = 1 self.assertEqual(obj._p_changed, 1) self.assertEqual(obj._p_state, CHANGED) self.assert_(obj in jar.registered) def test_setattr_then_mark_uptodate(self): from persistent import UPTODATE obj = self._makeOne() jar = self._makeJar() jar.add(obj) obj.x = 1 obj._p_changed = 0 self.assertEqual(obj._p_changed, 0) self.assertEqual(obj._p_state, UPTODATE) def test_set_changed_directly(self): from persistent import CHANGED obj = self._makeOne() jar = self._makeJar() jar.add(obj) obj._p_changed = 1 self.assertEqual(obj._p_changed, 1) self.assertEqual(obj._p_state, CHANGED) self.assert_(obj in jar.registered) def test_cant_ghostify_if_changed(self): from persistent import CHANGED obj = self._makeOne() jar = self._makeJar() jar.add(obj) # setting obj._p_changed to None ghostifies if the # object is in the up-to-date state, but not otherwise. obj.x = 1 obj._p_changed = None self.assertEqual(obj._p_changed, 1) self.assertEqual(obj._p_state, CHANGED) def test_can_ghostify_if_uptodate(self): from persistent import GHOST obj = self._makeOne() jar = self._makeJar() jar.add(obj) obj.x = 1 obj._p_changed = 0 obj._p_changed = None self.assertEqual(obj._p_changed, None) self.assertEqual(obj._p_state, GHOST) def test_can_ghostify_if_changed_but_del__p_changed(self): from persistent import GHOST obj = self._makeOne() jar = self._makeJar() jar.add(obj) # You can transition directly from modified to ghost if # you delete the _p_changed attribute. obj.x = 1 del obj._p_changed self.assertEqual(obj._p_changed, None) self.assertEqual(obj._p_state, GHOST) def test__p_state_immutable(self): from persistent import CHANGED from persistent import GHOST from persistent import STICKY from persistent import UPTODATE # make sure we can't write to _p_state; we don't want yet # another way to change state! obj = self._makeOne() def setstate(value): obj._p_state = value self.assertRaises(Exception, setstate, GHOST) self.assertRaises(Exception, setstate, UPTODATE) self.assertRaises(Exception, setstate, CHANGED) self.assertRaises(Exception, setstate, STICKY) def test_invalidate(self): from persistent import GHOST from persistent import UPTODATE obj = self._makeOne() jar = self._makeJar() jar.add(obj) self.assertEqual(obj._p_changed, 0) self.assertEqual(obj._p_state, UPTODATE) obj._p_invalidate() self.assertEqual(obj._p_changed, None) self.assertEqual(obj._p_state, GHOST) def test_invalidate_activate_invalidate(self): from persistent import GHOST obj = self._makeOne() jar = self._makeJar() jar.add(obj) obj._p_invalidate() obj._p_activate() obj.x = 1 obj._p_invalidate() self.assertEqual(obj._p_changed, None) self.assertEqual(obj._p_state, GHOST) def test_initial_serial(self): NOSERIAL = "\000" * 8 obj = self._makeOne() self.assertEqual(obj._p_serial, NOSERIAL) def test_setting_serial_w_invalid_types_raises(self): # Serial must be an 8-digit string obj = self._makeOne() def set(val): obj._p_serial = val self.assertRaises(ValueError, set, 1) self.assertRaises(ValueError, set, "0123") self.assertRaises(ValueError, set, "012345678") self.assertRaises(ValueError, set, u"01234567") def test_del_serial_returns_to_initial(self): NOSERIAL = "\000" * 8 obj = self._makeOne() obj._p_serial = "01234567" del obj._p_serial self.assertEqual(obj._p_serial, NOSERIAL) def test_initial_mtime(self): obj = self._makeOne() self.assertEqual(obj._p_mtime, None) def test_setting_serial_sets_mtime_to_now(self): import time from persistent.TimeStamp import TimeStamp obj = self._makeOne() t = int(time.time()) ts = TimeStamp(*time.gmtime(t)[:6]) # XXX: race? obj._p_serial = repr(ts) # why repr it? self.assertEqual(obj._p_mtime, t) self.assert_(isinstance(obj._p_mtime, float)) def test_pickle_unpickle(self): import pickle from persistent import Persistent # see above: class must be at module scope to be pickled. global Picklable class Picklable(Persistent): pass obj = Picklable() obj.attr = "test" s = pickle.dumps(obj) obj2 = pickle.loads(s) self.assertEqual(obj.attr, obj2.attr) def test___getattr__(self): from persistent import CHANGED from persistent import Persistent class H1(Persistent): def __init__(self): self.n = 0 def __getattr__(self, attr): self.n += 1 return self.n obj = H1() self.assertEqual(obj.larry, 1) self.assertEqual(obj.curly, 2) self.assertEqual(obj.moe, 3) jar = self._makeJar() jar.add(obj) obj._p_deactivate() # The simple Jar used for testing re-initializes the object. self.assertEqual(obj.larry, 1) # The getattr hook modified the object, so it should now be # in the changed state. self.assertEqual(obj._p_changed, 1) self.assertEqual(obj._p_state, CHANGED) self.assertEqual(obj.curly, 2) self.assertEqual(obj.moe, 3) def test___getattribute__(self): from persistent import CHANGED from persistent import Persistent class H2(Persistent): def __init__(self): self.n = 0 def __getattribute__(self, attr): supergetattr = super(H2, self).__getattribute__ try: return supergetattr(attr) except AttributeError: n = supergetattr("n") self.n = n + 1 return n + 1 obj = H2() self.assertEqual(obj.larry, 1) self.assertEqual(obj.curly, 2) self.assertEqual(obj.moe, 3) jar = self._makeJar() jar.add(obj) obj._p_deactivate() # The simple Jar used for testing re-initializes the object. self.assertEqual(obj.larry, 1) # The getattr hook modified the object, so it should now be # in the changed state. self.assertEqual(obj._p_changed, 1) self.assertEqual(obj._p_state, CHANGED) self.assertEqual(obj.curly, 2) self.assertEqual(obj.moe, 3) # TODO: Need to decide how __setattr__ and __delattr__ should work, # then write tests. def test_suite(): return unittest.makeSuite(PersistenceTest) zope2.13-2.13.21/source/ZODB3/src/ZEO/0000755000175000017500000000000012214017464015505 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZEO/monitor.py0000644000175000017500000001256012214017464017552 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Monitor behavior of ZEO server and record statistics. $Id: monitor.py 120291 2011-02-11 23:50:41Z jim $ """ import asyncore import socket import time import types import logging zeo_version = 'unknown' try: import pkg_resources except ImportError: pass else: zeo_dist = pkg_resources.working_set.find( pkg_resources.Requirement.parse('ZODB3') ) if zeo_dist is not None: zeo_version = zeo_dist.version class StorageStats: """Per-storage usage statistics.""" def __init__(self, connections=None): self.connections = connections self.loads = 0 self.stores = 0 self.commits = 0 self.aborts = 0 self.active_txns = 0 self.verifying_clients = 0 self.lock_time = None self.conflicts = 0 self.conflicts_resolved = 0 self.start = time.ctime() @property def clients(self): return len(self.connections) def parse(self, s): # parse the dump format lines = s.split("\n") for line in lines: field, value = line.split(":", 1) if field == "Server started": self.start = value elif field == "Clients": # Hack because we use this both on the server and on # the client where there are no connections. self.connections = [0] * int(value) elif field == "Clients verifying": self.verifying_clients = int(value) elif field == "Active transactions": self.active_txns = int(value) elif field == "Commit lock held for": # This assumes self.lock_time = time.time() - int(value) elif field == "Commits": self.commits = int(value) elif field == "Aborts": self.aborts = int(value) elif field == "Loads": self.loads = int(value) elif field == "Stores": self.stores = int(value) elif field == "Conflicts": self.conflicts = int(value) elif field == "Conflicts resolved": self.conflicts_resolved = int(value) def dump(self, f): print >> f, "Server started:", self.start print >> f, "Clients:", self.clients print >> f, "Clients verifying:", self.verifying_clients print >> f, "Active transactions:", self.active_txns if self.lock_time: howlong = time.time() - self.lock_time print >> f, "Commit lock held for:", int(howlong) print >> f, "Commits:", self.commits print >> f, "Aborts:", self.aborts print >> f, "Loads:", self.loads print >> f, "Stores:", self.stores print >> f, "Conflicts:", self.conflicts print >> f, "Conflicts resolved:", self.conflicts_resolved class StatsClient(asyncore.dispatcher): def __init__(self, sock, addr): asyncore.dispatcher.__init__(self, sock) self.buf = [] self.closed = 0 def close(self): self.closed = 1 # The socket is closed after all the data is written. # See handle_write(). def write(self, s): self.buf.append(s) def writable(self): return len(self.buf) def readable(self): return 0 def handle_write(self): s = "".join(self.buf) self.buf = [] n = self.socket.send(s) if n < len(s): self.buf.append(s[:n]) if self.closed and not self.buf: asyncore.dispatcher.close(self) class StatsServer(asyncore.dispatcher): StatsConnectionClass = StatsClient def __init__(self, addr, stats): asyncore.dispatcher.__init__(self) self.addr = addr self.stats = stats if type(self.addr) == types.TupleType: self.create_socket(socket.AF_INET, socket.SOCK_STREAM) else: self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) self.set_reuse_addr() logger = logging.getLogger('ZEO.monitor') logger.info("listening on %s", repr(self.addr)) self.bind(self.addr) self.listen(5) def writable(self): return 0 def readable(self): return 1 def handle_accept(self): try: sock, addr = self.accept() except socket.error: return f = self.StatsConnectionClass(sock, addr) self.dump(f) f.close() def dump(self, f): print >> f, "ZEO monitor server version %s" % zeo_version print >> f, time.ctime() print >> f L = self.stats.keys() L.sort() for k in L: stats = self.stats[k] print >> f, "Storage:", k stats.dump(f) print >> f zope2.13-2.13.21/source/ZODB3/src/ZEO/auth/0000755000175000017500000000000012214017464016446 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZEO/auth/hmac.py0000644000175000017500000000573012214017464017735 0ustar arnauarnau"""HMAC (Keyed-Hashing for Message Authentication) Python module. Implements the HMAC algorithm as described by RFC 2104. """ def _strxor(s1, s2): """Utility method. XOR the two strings s1 and s2 (must have same length). """ return "".join(map(lambda x, y: chr(ord(x) ^ ord(y)), s1, s2)) # The size of the digests returned by HMAC depends on the underlying # hashing module used. digest_size = None class HMAC: """RFC2104 HMAC class. This supports the API for Cryptographic Hash Functions (PEP 247). """ def __init__(self, key, msg = None, digestmod = None): """Create a new HMAC object. key: key for the keyed hash object. msg: Initial input for the hash, if provided. digestmod: A module supporting PEP 247. Defaults to the md5 module. """ if digestmod is None: import md5 digestmod = md5 self.digestmod = digestmod self.outer = digestmod.new() self.inner = digestmod.new() self.digest_size = digestmod.digest_size blocksize = 64 ipad = "\x36" * blocksize opad = "\x5C" * blocksize if len(key) > blocksize: key = digestmod.new(key).digest() key = key + chr(0) * (blocksize - len(key)) self.outer.update(_strxor(key, opad)) self.inner.update(_strxor(key, ipad)) if msg is not None: self.update(msg) ## def clear(self): ## raise NotImplementedError("clear() method not available in HMAC.") def update(self, msg): """Update this hashing object with the string msg. """ self.inner.update(msg) def copy(self): """Return a separate copy of this hashing object. An update to this copy won't affect the original object. """ other = HMAC("") other.digestmod = self.digestmod other.inner = self.inner.copy() other.outer = self.outer.copy() return other def digest(self): """Return the hash value of this hashing object. This returns a string containing 8-bit data. The object is not altered in any way by this function; you can continue updating the object after calling this function. """ h = self.outer.copy() h.update(self.inner.digest()) return h.digest() def hexdigest(self): """Like digest(), but returns a string of hexadecimal digits instead. """ return "".join([hex(ord(x))[2:].zfill(2) for x in tuple(self.digest())]) def new(key, msg = None, digestmod = None): """Create a new hashing object and return it. key: The starting key for the hash. msg: if available, will immediately be hashed into the object's starting state. You can now feed arbitrary strings into the object using its update() method, and can ask for the hash value at any time by calling its digest() method. """ return HMAC(key, msg, digestmod) zope2.13-2.13.21/source/ZODB3/src/ZEO/auth/__init__.py0000644000175000017500000000230612214017464020560 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## _auth_modules = {} def get_module(name): if name == 'sha': from auth_sha import StorageClass, SHAClient, Database return StorageClass, SHAClient, Database elif name == 'digest': from auth_digest import StorageClass, DigestClient, DigestDatabase return StorageClass, DigestClient, DigestDatabase else: return _auth_modules.get(name) def register_module(name, storage_class, client, db): if _auth_modules.has_key(name): raise TypeError("%s is already registred" % name) _auth_modules[name] = storage_class, client, db zope2.13-2.13.21/source/ZODB3/src/ZEO/auth/base.py0000644000175000017500000001024512214017464017734 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Base classes for defining an authentication protocol. Database -- abstract base class for password database Client -- abstract base class for authentication client """ import os from ZEO.hash import sha1 class Client: # Subclass should override to list the names of methods that # will be called on the server. extensions = [] def __init__(self, stub): self.stub = stub for m in self.extensions: setattr(self.stub, m, self.stub.extensionMethod(m)) def sort(L): """Sort a list in-place and return it.""" L.sort() return L class Database: """Abstracts a password database. This class is used both in the authentication process (via get_password()) and by client scripts that manage the password database file. The password file is a simple, colon-separated text file mapping usernames to password hashes. The hashes are SHA hex digests produced from the password string. """ realm = None def __init__(self, filename, realm=None): """Creates a new Database filename: a string containing the full pathname of the password database file. Must be readable by the user running ZEO. Must be writeable by any client script that accesses the database. realm: the realm name (a string) """ self._users = {} self.filename = filename self.load() if realm: if self.realm and self.realm != realm: raise ValueError("Specified realm %r differs from database " "realm %r" % (realm or '', self.realm)) else: self.realm = realm def save(self, fd=None): filename = self.filename if not fd: fd = open(filename, 'w') if self.realm: print >> fd, "realm", self.realm for username in sort(self._users.keys()): print >> fd, "%s: %s" % (username, self._users[username]) def load(self): filename = self.filename if not filename: return if not os.path.exists(filename): return fd = open(filename) L = fd.readlines() if not L: return if L[0].startswith("realm "): line = L.pop(0).strip() self.realm = line[len("realm "):] for line in L: username, hash = line.strip().split(":", 1) self._users[username] = hash.strip() def _store_password(self, username, password): self._users[username] = self.hash(password) def get_password(self, username): """Returns password hash for specified username. Callers must check for LookupError, which is raised in the case of a non-existent user specified.""" if not self._users.has_key(username): raise LookupError("No such user: %s" % username) return self._users[username] def hash(self, s): return sha1(s).hexdigest() def add_user(self, username, password): if self._users.has_key(username): raise LookupError("User %s already exists" % username) self._store_password(username, password) def del_user(self, username): if not self._users.has_key(username): raise LookupError("No such user: %s" % username) del self._users[username] def change_password(self, username, password): if not self._users.has_key(username): raise LookupError("No such user: %s" % username) self._store_password(username, password) zope2.13-2.13.21/source/ZODB3/src/ZEO/auth/auth_digest.py0000644000175000017500000001235312214017464021324 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Digest authentication for ZEO This authentication mechanism follows the design of HTTP digest authentication (RFC 2069). It is a simple challenge-response protocol that does not send passwords in the clear, but does not offer strong security. The RFC discusses many of the limitations of this kind of protocol. Guard the password database as if it contained plaintext passwords. It stores the hash of a username and password. This does not expose the plaintext password, but it is sensitive nonetheless. An attacker with the hash can impersonate the real user. This is a limitation of the simple digest scheme. HTTP is a stateless protocol, and ZEO is a stateful protocol. The security requirements are quite different as a result. The HTTP protocol uses a nonce as a challenge. The ZEO protocol requires a separate session key that is used for message authentication. We generate a second nonce for this purpose; the hash of nonce and user/realm/password is used as the session key. TODO: I'm not sure if this is a sound approach; SRP would be preferred. """ import os import random import struct import time from ZEO.auth.base import Database, Client from ZEO.StorageServer import ZEOStorage from ZEO.Exceptions import AuthError from ZEO.hash import sha1 def get_random_bytes(n=8): if os.path.exists("/dev/urandom"): f = open("/dev/urandom") s = f.read(n) f.close() else: L = [chr(random.randint(0, 255)) for i in range(n)] s = "".join(L) return s def hexdigest(s): return sha1(s).hexdigest() class DigestDatabase(Database): def __init__(self, filename, realm=None): Database.__init__(self, filename, realm) # Initialize a key used to build the nonce for a challenge. # We need one key for the lifetime of the server, so it # is convenient to store in on the database. self.noncekey = get_random_bytes(8) def _store_password(self, username, password): dig = hexdigest("%s:%s:%s" % (username, self.realm, password)) self._users[username] = dig def session_key(h_up, nonce): # The hash itself is a bit too short to be a session key. # HMAC wants a 64-byte key. We don't want to use h_up # directly because it would never change over time. Instead # use the hash plus part of h_up. return sha1("%s:%s" % (h_up, nonce)).digest() + h_up[:44] class StorageClass(ZEOStorage): def set_database(self, database): assert isinstance(database, DigestDatabase) self.database = database self.noncekey = database.noncekey def _get_time(self): # Return a string representing the current time. t = int(time.time()) return struct.pack("i", t) def _get_nonce(self): # RFC 2069 recommends a nonce of the form # H(client-IP ":" time-stamp ":" private-key) dig = sha1() dig.update(str(self.connection.addr)) dig.update(self._get_time()) dig.update(self.noncekey) return dig.hexdigest() def auth_get_challenge(self): """Return realm, challenge, and nonce.""" self._challenge = self._get_nonce() self._key_nonce = self._get_nonce() return self.auth_realm, self._challenge, self._key_nonce def auth_response(self, resp): # verify client response user, challenge, response = resp # Since zrpc is a stateful protocol, we just store the nonce # we sent to the client. It will need to generate a new # nonce for a new connection anyway. if self._challenge != challenge: raise ValueError("invalid challenge") # lookup user in database h_up = self.database.get_password(user) # regeneration resp from user, password, and nonce check = hexdigest("%s:%s" % (h_up, challenge)) if check == response: self.connection.setSessionKey(session_key(h_up, self._key_nonce)) return self._finish_auth(check == response) extensions = [auth_get_challenge, auth_response] class DigestClient(Client): extensions = ["auth_get_challenge", "auth_response"] def start(self, username, realm, password): _realm, challenge, nonce = self.stub.auth_get_challenge() if _realm != realm: raise AuthError("expected realm %r, got realm %r" % (_realm, realm)) h_up = hexdigest("%s:%s:%s" % (username, realm, password)) resp_dig = hexdigest("%s:%s" % (h_up, challenge)) result = self.stub.auth_response((username, challenge, resp_dig)) if result: return session_key(h_up, nonce) else: return None zope2.13-2.13.21/source/ZODB3/src/ZEO/StorageServer.py0000644000175000017500000015765312214017464020673 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """The StorageServer class and the exception that it may raise. This server acts as a front-end for one or more real storages, like file storage or Berkeley storage. TODO: Need some basic access control-- a declaration of the methods exported for invocation by the server. """ from __future__ import with_statement from ZEO.Exceptions import AuthError from ZEO.monitor import StorageStats, StatsServer from ZEO.zrpc.connection import ManagedServerConnection, Delay, MTDelay, Result from ZEO.zrpc.server import Dispatcher from ZODB.ConflictResolution import ResolvedSerial from ZODB.loglevels import BLATHER from ZODB.POSException import StorageError, StorageTransactionError from ZODB.POSException import TransactionError, ReadOnlyError, ConflictError from ZODB.serialize import referencesf from ZODB.utils import oid_repr, p64, u64, z64 import asyncore import cPickle import itertools import logging import os import sys import tempfile import threading import time import transaction import warnings import ZEO.zrpc.error import ZODB.blob import ZODB.serialize import ZODB.TimeStamp import zope.interface logger = logging.getLogger('ZEO.StorageServer') def log(message, level=logging.INFO, label='', exc_info=False): """Internal helper to log a message.""" if label: message = "(%s) %s" % (label, message) logger.log(level, message, exc_info=exc_info) class StorageServerError(StorageError): """Error reported when an unpicklable exception is raised.""" class ZEOStorage: """Proxy to underlying storage for a single remote client.""" # A list of extension methods. A subclass with extra methods # should override. extensions = [] def __init__(self, server, read_only=0, auth_realm=None): self.server = server # timeout and stats will be initialized in register() self.stats = None self.connection = None self.client = None self.storage = None self.storage_id = "uninitialized" self.transaction = None self.read_only = read_only self.log_label = 'unconnected' self.locked = False # Don't have storage lock self.verifying = 0 self.store_failed = 0 self.authenticated = 0 self.auth_realm = auth_realm self.blob_tempfile = None # The authentication protocol may define extra methods. self._extensions = {} for func in self.extensions: self._extensions[func.func_name] = None self._iterators = {} self._iterator_ids = itertools.count() # Stores the last item that was handed out for a # transaction iterator. self._txn_iterators_last = {} def _finish_auth(self, authenticated): if not self.auth_realm: return 1 self.authenticated = authenticated return authenticated def set_database(self, database): self.database = database def notifyConnected(self, conn): self.connection = conn assert conn.peer_protocol_version is not None if conn.peer_protocol_version < 'Z309': self.client = ClientStub308(conn) conn.register_object(ZEOStorage308Adapter(self)) else: self.client = ClientStub(conn) self.log_label = _addr_label(conn.addr) def notifyDisconnected(self): # When this storage closes, we must ensure that it aborts # any pending transaction. if self.transaction is not None: self.log("disconnected during %s transaction" % (self.locked and 'locked' or 'unlocked')) self.tpc_abort(self.transaction.id) else: self.log("disconnected") self.connection = None def __repr__(self): tid = self.transaction and repr(self.transaction.id) if self.storage: stid = (self.tpc_transaction() and repr(self.tpc_transaction().id)) else: stid = None name = self.__class__.__name__ return "<%s %X trans=%s s_trans=%s>" % (name, id(self), tid, stid) def log(self, msg, level=logging.INFO, exc_info=False): log(msg, level=level, label=self.log_label, exc_info=exc_info) def setup_delegation(self): """Delegate several methods to the storage """ # Called from register storage = self.storage info = self.get_info() if not info['supportsUndo']: self.undoLog = self.undoInfo = lambda *a,**k: () self.getTid = storage.getTid self.load = storage.load self.loadSerial = storage.loadSerial record_iternext = getattr(storage, 'record_iternext', None) if record_iternext is not None: self.record_iternext = record_iternext try: fn = storage.getExtensionMethods except AttributeError: pass # no extension methods else: d = fn() self._extensions.update(d) for name in d: assert not hasattr(self, name) setattr(self, name, getattr(storage, name)) self.lastTransaction = storage.lastTransaction try: self.tpc_transaction = storage.tpc_transaction except AttributeError: if hasattr(storage, '_transaction'): log("Storage %r doesn't have a tpc_transaction method.\n" "See ZEO.interfaces.IServeable." "Falling back to using _transaction attribute, which\n." "is icky.", logging.ERROR) self.tpc_transaction = lambda : storage._transaction else: raise def history(self,tid,size=1): # This caters for storages which still accept # a version parameter. return self.storage.history(tid,size=size) def _check_tid(self, tid, exc=None): if self.read_only: raise ReadOnlyError() if self.transaction is None: caller = sys._getframe().f_back.f_code.co_name self.log("no current transaction: %s()" % caller, level=logging.WARNING) if exc is not None: raise exc(None, tid) else: return 0 if self.transaction.id != tid: caller = sys._getframe().f_back.f_code.co_name self.log("%s(%s) invalid; current transaction = %s" % (caller, repr(tid), repr(self.transaction.id)), logging.WARNING) if exc is not None: raise exc(self.transaction.id, tid) else: return 0 return 1 def getAuthProtocol(self): """Return string specifying name of authentication module to use. The module name should be auth_%s where %s is auth_protocol.""" protocol = self.server.auth_protocol if not protocol or protocol == 'none': return None return protocol def register(self, storage_id, read_only): """Select the storage that this client will use This method must be the first one called by the client. For authenticated storages this method will be called by the client immediately after authentication is finished. """ if self.auth_realm and not self.authenticated: raise AuthError("Client was never authenticated with server!") if self.storage is not None: self.log("duplicate register() call") raise ValueError("duplicate register() call") storage = self.server.storages.get(storage_id) if storage is None: self.log("unknown storage_id: %s" % storage_id) raise ValueError("unknown storage: %s" % storage_id) if not read_only and (self.read_only or storage.isReadOnly()): raise ReadOnlyError() self.read_only = self.read_only or read_only self.storage_id = storage_id self.storage = storage self.setup_delegation() self.stats = self.server.register_connection(storage_id, self) def get_info(self): storage = self.storage supportsUndo = (getattr(storage, 'supportsUndo', lambda : False)() and self.connection.peer_protocol_version >= 'Z310') # Communicate the backend storage interfaces to the client storage_provides = zope.interface.providedBy(storage) interfaces = [] for candidate in storage_provides.__iro__: interfaces.append((candidate.__module__, candidate.__name__)) return {'length': len(storage), 'size': storage.getSize(), 'name': storage.getName(), 'supportsUndo': supportsUndo, 'extensionMethods': self.getExtensionMethods(), 'supports_record_iternext': hasattr(self, 'record_iternext'), 'interfaces': tuple(interfaces), } def get_size_info(self): return {'length': len(self.storage), 'size': self.storage.getSize(), } def getExtensionMethods(self): return self._extensions def loadEx(self, oid): self.stats.loads += 1 return self.storage.load(oid, '') def loadBefore(self, oid, tid): self.stats.loads += 1 return self.storage.loadBefore(oid, tid) def getInvalidations(self, tid): invtid, invlist = self.server.get_invalidations(self.storage_id, tid) if invtid is None: return None self.log("Return %d invalidations up to tid %s" % (len(invlist), u64(invtid))) return invtid, invlist def verify(self, oid, tid): try: t = self.getTid(oid) except KeyError: self.client.invalidateVerify(oid) else: if tid != t: self.client.invalidateVerify(oid) def zeoVerify(self, oid, s): if not self.verifying: self.verifying = 1 self.stats.verifying_clients += 1 try: os = self.getTid(oid) except KeyError: self.client.invalidateVerify((oid, '')) # It's not clear what we should do now. The KeyError # could be caused by an object uncreation, in which case # invalidation is right. It could be an application bug # that left a dangling reference, in which case it's bad. else: if s != os: self.client.invalidateVerify((oid, '')) def endZeoVerify(self): if self.verifying: self.stats.verifying_clients -= 1 self.verifying = 0 self.client.endVerify() def pack(self, time, wait=1): # Yes, you can pack a read-only server or storage! if wait: return run_in_thread(self._pack_impl, time) else: # If the client isn't waiting for a reply, start a thread # and forget about it. t = threading.Thread(target=self._pack_impl, args=(time,)) t.start() return None def _pack_impl(self, time): self.log("pack(time=%s) started..." % repr(time)) self.storage.pack(time, referencesf) self.log("pack(time=%s) complete" % repr(time)) # Broadcast new size statistics self.server.invalidate(0, self.storage_id, None, (), self.get_size_info()) def new_oids(self, n=100): """Return a sequence of n new oids, where n defaults to 100""" n = min(n, 100) if self.read_only: raise ReadOnlyError() if n <= 0: n = 1 return [self.storage.new_oid() for i in range(n)] # undoLog and undoInfo are potentially slow methods def undoInfo(self, first, last, spec): return run_in_thread(self.storage.undoInfo, first, last, spec) def undoLog(self, first, last): return run_in_thread(self.storage.undoLog, first, last) def tpc_begin(self, id, user, description, ext, tid=None, status=" "): if self.read_only: raise ReadOnlyError() if self.transaction is not None: if self.transaction.id == id: self.log("duplicate tpc_begin(%s)" % repr(id)) return else: raise StorageTransactionError("Multiple simultaneous tpc_begin" " requests from one client.") t = transaction.Transaction() t.id = id t.user = user t.description = description t._extension = ext self.serials = [] self.invalidated = [] self.txnlog = CommitLog() self.blob_log = [] self.tid = tid self.status = status self.store_failed = 0 self.stats.active_txns += 1 # Assign the transaction attribute last. This is so we don't # think we've entered TPC until everything is set. Why? # Because if we have an error after this, the server will # think it is in TPC and the client will think it isn't. At # that point, the client will keep trying to enter TPC and # server won't let it. Errors *after* the tpc_begin call will # cause the client to abort the transaction. # (Also see https://bugs.launchpad.net/zodb/+bug/374737.) self.transaction = t def tpc_finish(self, id): if not self._check_tid(id): return assert self.locked, "finished called wo lock" self.stats.commits += 1 self.storage.tpc_finish(self.transaction, self._invalidate) # Note that the tid is still current because we still hold the # commit lock. We'll relinquish it in _clear_transaction. tid = self.storage.lastTransaction() # Return the tid, for cache invalidation optimization return Result(tid, self._clear_transaction) def _invalidate(self, tid): if self.invalidated: self.server.invalidate(self, self.storage_id, tid, self.invalidated, self.get_size_info()) def tpc_abort(self, tid): if not self._check_tid(tid): return self.stats.aborts += 1 self.storage.tpc_abort(self.transaction) self._clear_transaction() def _clear_transaction(self): # Common code at end of tpc_finish() and tpc_abort() if self.locked: self.server.unlock_storage(self) self.locked = 0 if self.transaction is not None: self.server.stop_waiting(self) self.transaction = None self.stats.active_txns -= 1 if self.txnlog is not None: self.txnlog.close() self.txnlog = None for oid, oldserial, data, blobfilename in self.blob_log: ZODB.blob.remove_committed(blobfilename) del self.blob_log def vote(self, tid): self._check_tid(tid, exc=StorageTransactionError) if self.locked or self.server.already_waiting(self): raise StorageTransactionError( 'Already voting (%s)' % (self.locked and 'locked' or 'waiting') ) return self._try_to_vote() def _try_to_vote(self, delay=None): if self.connection is None: return # We're disconnected if delay is not None and delay.sent: # as a consequence of the unlocking strategy, _try_to_vote # may be called multiple times for delayed # transactions. The first call will mark the delay as # sent. We should skip if the delay was already sent. return self.locked, delay = self.server.lock_storage(self, delay) if self.locked: try: self.log( "Preparing to commit transaction: %d objects, %d bytes" % (self.txnlog.stores, self.txnlog.size()), level=BLATHER) if (self.tid is not None) or (self.status != ' '): self.storage.tpc_begin(self.transaction, self.tid, self.status) else: self.storage.tpc_begin(self.transaction) for op, args in self.txnlog: if not getattr(self, op)(*args): break # Blob support while self.blob_log and not self.store_failed: oid, oldserial, data, blobfilename = self.blob_log.pop() self._store(oid, oldserial, data, blobfilename) if not self.store_failed: # Only call tpc_vote of no store call failed, # otherwise the serialnos() call will deliver an # exception that will be handled by the client in # its tpc_vote() method. serials = self.storage.tpc_vote(self.transaction) if serials: self.serials.extend(serials) self.client.serialnos(self.serials) except Exception: self.storage.tpc_abort(self.transaction) self._clear_transaction() if delay is not None: delay.error() else: raise else: if delay is not None: delay.reply(None) else: return None else: return delay def _unlock_callback(self, delay): connection = self.connection if connection is None: self.server.stop_waiting(self) else: connection.call_from_thread(self._try_to_vote, delay) # The public methods of the ZEO client API do not do the real work. # They defer work until after the storage lock has been acquired. # Most of the real implementations are in methods beginning with # an _. def deleteObject(self, oid, serial, id): self._check_tid(id, exc=StorageTransactionError) self.stats.stores += 1 self.txnlog.delete(oid, serial) def storea(self, oid, serial, data, id): self._check_tid(id, exc=StorageTransactionError) self.stats.stores += 1 self.txnlog.store(oid, serial, data) def checkCurrentSerialInTransaction(self, oid, serial, id): self._check_tid(id, exc=StorageTransactionError) self.txnlog.checkread(oid, serial) def restorea(self, oid, serial, data, prev_txn, id): self._check_tid(id, exc=StorageTransactionError) self.stats.stores += 1 self.txnlog.restore(oid, serial, data, prev_txn) def storeBlobStart(self): assert self.blob_tempfile is None self.blob_tempfile = tempfile.mkstemp( dir=self.storage.temporaryDirectory()) def storeBlobChunk(self, chunk): os.write(self.blob_tempfile[0], chunk) def storeBlobEnd(self, oid, serial, data, id): self._check_tid(id, exc=StorageTransactionError) assert self.txnlog is not None # effectively not allowed after undo fd, tempname = self.blob_tempfile self.blob_tempfile = None os.close(fd) self.blob_log.append((oid, serial, data, tempname)) def storeBlobShared(self, oid, serial, data, filename, id): self._check_tid(id, exc=StorageTransactionError) assert self.txnlog is not None # effectively not allowed after undo # Reconstruct the full path from the filename in the OID directory if (os.path.sep in filename or not (filename.endswith('.tmp') or filename[:-1].endswith('.tmp') ) ): logger.critical( "We're under attack! (bad filename to storeBlobShared, %r)", filename) raise ValueError(filename) filename = os.path.join(self.storage.fshelper.getPathForOID(oid), filename) self.blob_log.append((oid, serial, data, filename)) def sendBlob(self, oid, serial): self.client.storeBlob(oid, serial, self.storage.loadBlob(oid, serial)) def undo(*a, **k): raise NotImplementedError def undoa(self, trans_id, tid): self._check_tid(tid, exc=StorageTransactionError) self.txnlog.undo(trans_id) def _op_error(self, oid, err, op): self.store_failed = 1 if isinstance(err, ConflictError): self.stats.conflicts += 1 self.log("conflict error oid=%s msg=%s" % (oid_repr(oid), str(err)), BLATHER) if not isinstance(err, TransactionError): # Unexpected errors are logged and passed to the client self.log("%s error: %s, %s" % ((op,)+ sys.exc_info()[:2]), logging.ERROR, exc_info=True) err = self._marshal_error(err) # The exception is reported back as newserial for this oid self.serials.append((oid, err)) def _delete(self, oid, serial): err = None try: self.storage.deleteObject(oid, serial, self.transaction) except (SystemExit, KeyboardInterrupt): raise except Exception, err: self._op_error(oid, err, 'delete') return err is None def _checkread(self, oid, serial): err = None try: self.storage.checkCurrentSerialInTransaction( oid, serial, self.transaction) except (SystemExit, KeyboardInterrupt): raise except Exception, err: self._op_error(oid, err, 'checkCurrentSerialInTransaction') return err is None def _store(self, oid, serial, data, blobfile=None): err = None try: if blobfile is None: newserial = self.storage.store( oid, serial, data, '', self.transaction) else: newserial = self.storage.storeBlob( oid, serial, data, blobfile, '', self.transaction) except (SystemExit, KeyboardInterrupt): raise except Exception, err: self._op_error(oid, err, 'store') else: if serial != "\0\0\0\0\0\0\0\0": self.invalidated.append(oid) if isinstance(newserial, str): newserial = [(oid, newserial)] for oid, s in newserial or (): if s == ResolvedSerial: self.stats.conflicts_resolved += 1 self.log("conflict resolved oid=%s" % oid_repr(oid), BLATHER) self.serials.append((oid, s)) return err is None def _restore(self, oid, serial, data, prev_txn): err = None try: self.storage.restore(oid, serial, data, '', prev_txn, self.transaction) except (SystemExit, KeyboardInterrupt): raise except Exception, err: self._op_error(oid, err, 'restore') return err is None def _undo(self, trans_id): err = None try: tid, oids = self.storage.undo(trans_id, self.transaction) except (SystemExit, KeyboardInterrupt): raise except Exception, err: self._op_error(z64, err, 'undo') else: self.invalidated.extend(oids) self.serials.extend((oid, ResolvedSerial) for oid in oids) return err is None def _marshal_error(self, error): # Try to pickle the exception. If it can't be pickled, # the RPC response would fail, so use something that can be pickled. pickler = cPickle.Pickler() pickler.fast = 1 try: pickler.dump(error, 1) except: msg = "Couldn't pickle storage exception: %s" % repr(error) self.log(msg, logging.ERROR) error = StorageServerError(msg) return error # IStorageIteration support def iterator_start(self, start, stop): iid = self._iterator_ids.next() self._iterators[iid] = iter(self.storage.iterator(start, stop)) return iid def iterator_next(self, iid): iterator = self._iterators[iid] try: info = iterator.next() except StopIteration: del self._iterators[iid] item = None if iid in self._txn_iterators_last: del self._txn_iterators_last[iid] else: item = (info.tid, info.status, info.user, info.description, info.extension) # Keep a reference to the last iterator result to allow starting a # record iterator off it. self._txn_iterators_last[iid] = info return item def iterator_record_start(self, txn_iid, tid): record_iid = self._iterator_ids.next() txn_info = self._txn_iterators_last[txn_iid] if txn_info.tid != tid: raise Exception( 'Out-of-order request for record iterator for transaction %r' % tid) self._iterators[record_iid] = iter(txn_info) return record_iid def iterator_record_next(self, iid): iterator = self._iterators[iid] try: info = iterator.next() except StopIteration: del self._iterators[iid] item = None else: item = (info.oid, info.tid, info.data, info.data_txn) return item def iterator_gc(self, iids): for iid in iids: self._iterators.pop(iid, None) def server_status(self): return self.server.server_status(self) def set_client_label(self, label): self.log_label = str(label)+' '+_addr_label(self.connection.addr) class StorageServerDB: def __init__(self, server, storage_id): self.server = server self.storage_id = storage_id self.references = ZODB.serialize.referencesf def invalidate(self, tid, oids, version=''): if version: raise StorageServerError("Versions aren't supported.") storage_id = self.storage_id self.server.invalidate(None, storage_id, tid, oids) def invalidateCache(self): self.server._invalidateCache(self.storage_id) transform_record_data = untransform_record_data = lambda self, data: data class StorageServer: """The server side implementation of ZEO. The StorageServer is the 'manager' for incoming connections. Each connection is associated with its own ZEOStorage instance (defined below). The StorageServer may handle multiple storages; each ZEOStorage instance only handles a single storage. """ # Classes we instantiate. A subclass might override. DispatcherClass = Dispatcher ZEOStorageClass = ZEOStorage ManagedServerConnectionClass = ManagedServerConnection def __init__(self, addr, storages, read_only=0, invalidation_queue_size=100, invalidation_age=None, transaction_timeout=None, monitor_address=None, auth_protocol=None, auth_database=None, auth_realm=None): """StorageServer constructor. This is typically invoked from the start.py script. Arguments (the first two are required and positional): addr -- the address at which the server should listen. This can be a tuple (host, port) to signify a TCP/IP connection or a pathname string to signify a Unix domain socket connection. A hostname may be a DNS name or a dotted IP address. storages -- a dictionary giving the storage(s) to handle. The keys are the storage names, the values are the storage instances, typically FileStorage or Berkeley storage instances. By convention, storage names are typically strings representing small integers starting at '1'. read_only -- an optional flag saying whether the server should operate in read-only mode. Defaults to false. Note that even if the server is operating in writable mode, individual storages may still be read-only. But if the server is in read-only mode, no write operations are allowed, even if the storages are writable. Note that pack() is considered a read-only operation. invalidation_queue_size -- The storage server keeps a queue of the objects modified by the last N transactions, where N == invalidation_queue_size. This queue is used to speed client cache verification when a client disconnects for a short period of time. invalidation_age -- If the invalidation queue isn't big enough to support a quick verification, but the last transaction seen by a client is younger than the invalidation age, then invalidations will be computed by iterating over transactions later than the given transaction. transaction_timeout -- The maximum amount of time to wait for a transaction to commit after acquiring the storage lock. If the transaction takes too long, the client connection will be closed and the transaction aborted. monitor_address -- The address at which the monitor server should listen. If specified, a monitor server is started. The monitor server provides server statistics in a simple text format. auth_protocol -- The name of the authentication protocol to use. Examples are "digest" and "srp". auth_database -- The name of the password database filename. It should be in a format compatible with the authentication protocol used; for instance, "sha" and "srp" require different formats. Note that to implement an authentication protocol, a server and client authentication mechanism must be implemented in a auth_* module, which should be stored inside the "auth" subdirectory. This module may also define a DatabaseClass variable that should indicate what database should be used by the authenticator. """ self.addr = addr self.storages = storages msg = ", ".join( ["%s:%s:%s" % (name, storage.isReadOnly() and "RO" or "RW", storage.getName()) for name, storage in storages.items()]) log("%s created %s with storages: %s" % (self.__class__.__name__, read_only and "RO" or "RW", msg)) self._lock = threading.Lock() self._commit_locks = {} self._waiting = dict((name, []) for name in storages) self.read_only = read_only self.auth_protocol = auth_protocol self.auth_database = auth_database self.auth_realm = auth_realm self.database = None if auth_protocol: self._setup_auth(auth_protocol) # A list, by server, of at most invalidation_queue_size invalidations. # The list is kept in sorted order with the most recent # invalidation at the front. The list never has more than # self.invq_bound elements. self.invq_bound = invalidation_queue_size self.invq = {} for name, storage in storages.items(): self._setup_invq(name, storage) storage.registerDB(StorageServerDB(self, name)) self.invalidation_age = invalidation_age self.connections = {} self.dispatcher = self.DispatcherClass(addr, factory=self.new_connection) self.stats = {} self.timeouts = {} for name in self.storages.keys(): self.connections[name] = [] self.stats[name] = StorageStats(self.connections[name]) if transaction_timeout is None: # An object with no-op methods timeout = StubTimeoutThread() else: timeout = TimeoutThread(transaction_timeout) timeout.start() self.timeouts[name] = timeout if monitor_address: warnings.warn( "The monitor server is deprecated. Use the server_status\n" "ZEO method instead.", DeprecationWarning) self.monitor = StatsServer(monitor_address, self.stats) else: self.monitor = None def _setup_invq(self, name, storage): lastInvalidations = getattr(storage, 'lastInvalidations', None) if lastInvalidations is None: # Using None below doesn't look right, but the first # element in invq is never used. See get_invalidations. # (If it was used, it would generate an error, which would # be good. :) Doing this allows clients that were up to # date when a server was restarted to pick up transactions # it subsequently missed. self.invq[name] = [(storage.lastTransaction() or z64, None)] else: self.invq[name] = list(lastInvalidations(self.invq_bound)) self.invq[name].reverse() def _setup_auth(self, protocol): # Can't be done in global scope, because of cyclic references from ZEO.auth import get_module name = self.__class__.__name__ module = get_module(protocol) if not module: log("%s: no such an auth protocol: %s" % (name, protocol)) return storage_class, client, db_class = module if not storage_class or not issubclass(storage_class, ZEOStorage): log(("%s: %s isn't a valid protocol, must have a StorageClass" % (name, protocol))) self.auth_protocol = None return self.ZEOStorageClass = storage_class log("%s: using auth protocol: %s" % (name, protocol)) # We create a Database instance here for use with the authenticator # modules. Having one instance allows it to be shared between multiple # storages, avoiding the need to bloat each with a new authenticator # Database that would contain the same info, and also avoiding any # possibly synchronization issues between them. self.database = db_class(self.auth_database) if self.database.realm != self.auth_realm: raise ValueError("password database realm %r " "does not match storage realm %r" % (self.database.realm, self.auth_realm)) def new_connection(self, sock, addr): """Internal: factory to create a new connection. This is called by the Dispatcher class in ZEO.zrpc.server whenever accept() returns a socket for a new incoming connection. """ if self.auth_protocol and self.database: zstorage = self.ZEOStorageClass(self, self.read_only, auth_realm=self.auth_realm) zstorage.set_database(self.database) else: zstorage = self.ZEOStorageClass(self, self.read_only) c = self.ManagedServerConnectionClass(sock, addr, zstorage, self) log("new connection %s: %s" % (addr, repr(c))) return c def register_connection(self, storage_id, conn): """Internal: register a connection with a particular storage. This is called by ZEOStorage.register(). The dictionary self.connections maps each storage name to a list of current connections for that storage; this information is needed to handle invalidation. This function updates this dictionary. Returns the timeout and stats objects for the appropriate storage. """ self.connections[storage_id].append(conn) return self.stats[storage_id] def _invalidateCache(self, storage_id): """We need to invalidate any caches we have. This basically means telling our clients to invalidate/revalidate their caches. We do this by closing them and making them reconnect. """ # This method can be called from foreign threads. We have to # worry about interaction with the main thread. # 1. We modify self.invq which is read by get_invalidations # below. This is why get_invalidations makes a copy of # self.invq. # 2. We access connections. There are two dangers: # # a. We miss a new connection. This is not a problem because # if a client connects after we get the list of connections, # then it will have to read the invalidation queue, which # has already been reset. # # b. A connection is closes while we are iterating. This # doesn't matter, bacause we can call should_close on a closed # connection. # Rebuild invq self._setup_invq(storage_id, self.storages[storage_id]) # Make a copy since we are going to be mutating the # connections indirectoy by closing them. We don't care about # later transactions since they will have to validate their # caches anyway. for p in self.connections[storage_id][:]: try: p.connection.should_close() p.connection.trigger.pull_trigger() except ZEO.zrpc.error.DisconnectedError: pass def invalidate(self, conn, storage_id, tid, invalidated=(), info=None): """Internal: broadcast info and invalidations to clients. This is called from several ZEOStorage methods. invalidated is a sequence of oids. This can do three different things: - If the invalidated argument is non-empty, it broadcasts invalidateTransaction() messages to all clients of the given storage except the current client (the conn argument). - If the invalidated argument is empty and the info argument is a non-empty dictionary, it broadcasts info() messages to all clients of the given storage, including the current client. - If both the invalidated argument and the info argument are non-empty, it broadcasts invalidateTransaction() messages to all clients except the current, and sends an info() message to the current client. """ # This method can be called from foreign threads. We have to # worry about interaction with the main thread. # 1. We modify self.invq which is read by get_invalidations # below. This is why get_invalidations makes a copy of # self.invq. # 2. We access connections. There are two dangers: # # a. We miss a new connection. This is not a problem because # we are called while the storage lock is held. A new # connection that tries to read data won't read committed # data without first recieving an invalidation. Also, if a # client connects after getting the list of connections, # then it will have to read the invalidation queue, which # has been updated to reflect the invalidations. # # b. A connection is closes while we are iterating. We'll need # to cactch and ignore Disconnected errors. if invalidated: invq = self.invq[storage_id] if len(invq) >= self.invq_bound: invq.pop() invq.insert(0, (tid, invalidated)) for p in self.connections[storage_id]: try: if invalidated and p is not conn: p.client.invalidateTransaction(tid, invalidated) elif info is not None: p.client.info(info) except ZEO.zrpc.error.DisconnectedError: pass def get_invalidations(self, storage_id, tid): """Return a tid and list of all objects invalidation since tid. The tid is the most recent transaction id seen by the client. Returns None if it is unable to provide a complete list of invalidations for tid. In this case, client should do full cache verification. """ # We make a copy of invq because it might be modified by a # foreign (other than main thread) calling invalidate above. invq = self.invq[storage_id][:] oids = set() latest_tid = None if invq and invq[-1][0] <= tid: # We have needed data in the queue for _tid, L in invq: if _tid <= tid: break oids.update(L) latest_tid = invq[0][0] elif (self.invalidation_age and (self.invalidation_age > (time.time()-ZODB.TimeStamp.TimeStamp(tid).timeTime()) ) ): for t in self.storages[storage_id].iterator(p64(u64(tid)+1)): for r in t: oids.add(r.oid) latest_tid = t.tid elif not invq: log("invq empty") else: log("tid to old for invq %s < %s" % (u64(tid), u64(invq[-1][0]))) return latest_tid, list(oids) def close_server(self): """Close the dispatcher so that there are no new connections. This is only called from the test suite, AFAICT. """ self.dispatcher.close() if self.monitor is not None: self.monitor.close() # Force the asyncore mainloop to exit by hackery, i.e. close # every socket in the map. loop() will return when the map is # empty. for s in asyncore.socket_map.values(): try: s.close() except: pass asyncore.socket_map.clear() for storage in self.storages.values(): storage.close() def close_conn(self, conn): """Internal: remove the given connection from self.connections. This is the inverse of register_connection(). """ for cl in self.connections.values(): if conn.obj in cl: cl.remove(conn.obj) def lock_storage(self, zeostore, delay): storage_id = zeostore.storage_id waiting = self._waiting[storage_id] with self._lock: if storage_id in self._commit_locks: # The lock is held by another zeostore locked = self._commit_locks[storage_id] assert locked is not zeostore, (storage_id, delay) if locked.connection is None: locked.log("Still locked after disconnected. Unlocking.", logging.CRITICAL) if locked.transaction: locked.storage.tpc_abort(locked.transaction) del self._commit_locks[storage_id] # yuck: have to manipulate lock to appease with :( self._lock.release() try: return self.lock_storage(zeostore, delay) finally: self._lock.acquire() if delay is None: # New request, queue it assert not [i for i in waiting if i[0] is zeostore ], "already waiting" delay = Delay() waiting.append((zeostore, delay)) zeostore.log("(%r) queue lock: transactions waiting: %s" % (storage_id, len(waiting)), _level_for_waiting(waiting) ) return False, delay else: self._commit_locks[storage_id] = zeostore self.timeouts[storage_id].begin(zeostore) self.stats[storage_id].lock_time = time.time() if delay is not None: # we were waiting, stop waiting[:] = [i for i in waiting if i[0] is not zeostore] zeostore.log("(%r) lock: transactions waiting: %s" % (storage_id, len(waiting)), _level_for_waiting(waiting) ) return True, delay def unlock_storage(self, zeostore): storage_id = zeostore.storage_id waiting = self._waiting[storage_id] with self._lock: assert self._commit_locks[storage_id] is zeostore del self._commit_locks[storage_id] self.timeouts[storage_id].end(zeostore) self.stats[storage_id].lock_time = None callbacks = waiting[:] if callbacks: assert not [i for i in waiting if i[0] is zeostore ], "waiting while unlocking" zeostore.log("(%r) unlock: transactions waiting: %s" % (storage_id, len(callbacks)), _level_for_waiting(callbacks) ) for zeostore, delay in callbacks: try: zeostore._unlock_callback(delay) except (SystemExit, KeyboardInterrupt): raise except Exception: logger.exception("Calling unlock callback") def stop_waiting(self, zeostore): storage_id = zeostore.storage_id waiting = self._waiting[storage_id] with self._lock: new_waiting = [i for i in waiting if i[0] is not zeostore] if len(new_waiting) == len(waiting): return waiting[:] = new_waiting zeostore.log("(%r) dequeue lock: transactions waiting: %s" % (storage_id, len(waiting)), _level_for_waiting(waiting) ) def already_waiting(self, zeostore): storage_id = zeostore.storage_id waiting = self._waiting[storage_id] with self._lock: return bool([i for i in waiting if i[0] is zeostore]) def server_status(self, zeostore): storage_id = zeostore.storage_id status = self.stats[storage_id].__dict__.copy() status['connections'] = len(status['connections']) status['waiting'] = len(self._waiting[storage_id]) status['timeout-thread-is-alive'] = self.timeouts[storage_id].isAlive() return status def _level_for_waiting(waiting): if len(waiting) > 9: return logging.CRITICAL if len(waiting) > 3: return logging.WARNING else: return logging.DEBUG class StubTimeoutThread: def begin(self, client): pass def end(self, client): pass isAlive = lambda self: 'stub' class TimeoutThread(threading.Thread): """Monitors transaction progress and generates timeouts.""" # There is one TimeoutThread per storage, because there's one # transaction lock per storage. def __init__(self, timeout): threading.Thread.__init__(self) self.setDaemon(1) self._timeout = timeout self._client = None self._deadline = None self._cond = threading.Condition() # Protects _client and _deadline def begin(self, client): # Called from the restart code the "main" thread, whenever the # storage lock is being acquired. (Serialized by asyncore.) with self._cond: assert self._client is None self._client = client self._deadline = time.time() + self._timeout self._cond.notify() def end(self, client): # Called from the "main" thread whenever the storage lock is # being released. (Serialized by asyncore.) with self._cond: assert self._client is not None assert self._client is client self._client = None self._deadline = None def run(self): # Code running in the thread. while 1: with self._cond: while self._deadline is None: self._cond.wait() howlong = self._deadline - time.time() if howlong <= 0: # Prevent reporting timeout more than once self._deadline = None client = self._client # For the howlong <= 0 branch below if howlong <= 0: client.log("Transaction timeout after %s seconds" % self._timeout, logging.CRITICAL) try: client.connection.call_from_thread(client.connection.close) except: client.log("Timeout failure", logging.CRITICAL, exc_info=sys.exc_info()) self.end(client) else: time.sleep(howlong) def run_in_thread(method, *args): t = SlowMethodThread(method, args) t.start() return t.delay class SlowMethodThread(threading.Thread): """Thread to run potentially slow storage methods. Clients can use the delay attribute to access the MTDelay object used to send a zrpc response at the right time. """ # Some storage methods can take a long time to complete. If we # run these methods via a standard asyncore read handler, they # will block all other server activity until they complete. To # avoid blocking, we spawn a separate thread, return an MTDelay() # object, and have the thread reply() when it finishes. def __init__(self, method, args): threading.Thread.__init__(self) self._method = method self._args = args self.delay = MTDelay() def run(self): try: result = self._method(*self._args) except (SystemExit, KeyboardInterrupt): raise except Exception: self.delay.error(sys.exc_info()) else: self.delay.reply(result) class ClientStub: def __init__(self, rpc): self.rpc = rpc def beginVerify(self): self.rpc.callAsync('beginVerify') def invalidateVerify(self, args): self.rpc.callAsync('invalidateVerify', args) def endVerify(self): self.rpc.callAsync('endVerify') def invalidateTransaction(self, tid, args): # Note that this method is *always* called from a different # thread than self.rpc's async thread. It is the only method # for which this is true and requires special consideration! # callAsyncNoSend is important here because: # - callAsyncNoPoll isn't appropriate because # the network thread may not wake up for a long time, # delaying invalidations for too long. (This is demonstrateed # by a test failure.) # - callAsync isn't appropriate because (on the server) it tries # to write to the socket. If self.rpc's network thread also # tries to write at the ame time, we can run into problems # because handle_write isn't thread safe. self.rpc.callAsyncNoSend('invalidateTransaction', tid, args) def serialnos(self, arg): self.rpc.callAsyncNoPoll('serialnos', arg) def info(self, arg): self.rpc.callAsyncNoPoll('info', arg) def storeBlob(self, oid, serial, blobfilename): def store(): yield ('receiveBlobStart', (oid, serial)) f = open(blobfilename, 'rb') while 1: chunk = f.read(59000) if not chunk: break yield ('receiveBlobChunk', (oid, serial, chunk, )) f.close() yield ('receiveBlobStop', (oid, serial)) self.rpc.callAsyncIterator(store()) class ClientStub308(ClientStub): def invalidateTransaction(self, tid, args): ClientStub.invalidateTransaction( self, tid, [(arg, '') for arg in args]) def invalidateVerify(self, oid): ClientStub.invalidateVerify(self, (oid, '')) class ZEOStorage308Adapter: def __init__(self, storage): self.storage = storage def __eq__(self, other): return self is other or self.storage is other def getSerial(self, oid): return self.storage.loadEx(oid)[1] # Z200 def history(self, oid, version, size=1): if version: raise ValueError("Versions aren't supported.") return self.storage.history(oid, size=size) def getInvalidations(self, tid): result = self.storage.getInvalidations(tid) if result is not None: result = result[0], [(oid, '') for oid in result[1]] return result def verify(self, oid, version, tid): if version: raise StorageServerError("Versions aren't supported.") return self.storage.verify(oid, tid) def loadEx(self, oid, version=''): if version: raise StorageServerError("Versions aren't supported.") data, serial = self.storage.loadEx(oid) return data, serial, '' def storea(self, oid, serial, data, version, id): if version: raise StorageServerError("Versions aren't supported.") self.storage.storea(oid, serial, data, id) def storeBlobEnd(self, oid, serial, data, version, id): if version: raise StorageServerError("Versions aren't supported.") self.storage.storeBlobEnd(oid, serial, data, id) def storeBlobShared(self, oid, serial, data, filename, version, id): if version: raise StorageServerError("Versions aren't supported.") self.storage.storeBlobShared(oid, serial, data, filename, id) def getInfo(self): result = self.storage.getInfo() result['supportsVersions'] = False return result def zeoVerify(self, oid, s, sv=None): if sv: raise StorageServerError("Versions aren't supported.") self.storage.zeoVerify(oid, s) def modifiedInVersion(self, oid): return '' def versions(self): return () def versionEmpty(self, version): return True def commitVersion(self, *a, **k): raise NotImplementedError abortVersion = commitVersion def zeoLoad(self, oid): # Z200 p, s = self.storage.loadEx(oid) return p, s, '', None, None def __getattr__(self, name): return getattr(self.storage, name) def _addr_label(addr): if isinstance(addr, type("")): return addr else: host, port = addr return str(host) + ":" + str(port) class CommitLog: def __init__(self): self.file = tempfile.TemporaryFile(suffix=".comit-log") self.pickler = cPickle.Pickler(self.file, 1) self.pickler.fast = 1 self.stores = 0 def size(self): return self.file.tell() def delete(self, oid, serial): self.pickler.dump(('_delete', (oid, serial))) self.stores += 1 def checkread(self, oid, serial): self.pickler.dump(('_checkread', (oid, serial))) self.stores += 1 def store(self, oid, serial, data): self.pickler.dump(('_store', (oid, serial, data))) self.stores += 1 def restore(self, oid, serial, data, prev_txn): self.pickler.dump(('_restore', (oid, serial, data, prev_txn))) self.stores += 1 def undo(self, transaction_id): self.pickler.dump(('_undo', (transaction_id, ))) self.stores += 1 def __iter__(self): self.file.seek(0) unpickler = cPickle.Unpickler(self.file) for i in range(self.stores): yield unpickler.load() def close(self): if self.file: self.file.close() self.file = None zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/0000755000175000017500000000000012214017464017174 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/cache_simul.py0000644000175000017500000004742012214017464022031 0ustar arnauarnau#! /usr/bin/env python ############################################################################## # # Copyright (c) 2001-2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Cache simulation. Usage: simul.py [-s size] tracefile Options: -s size: cache size in MB (default 20 MB) -i: summarizing interval in minutes (default 15; max 60) -r: rearrange factor Note: - The simulation isn't perfect. - The simulation will be far off if the trace file was created starting with a non-empty cache """ import bisect import getopt import struct import re import sys import ZEO.cache from ZODB.utils import z64, u64 # we assign ctime locally to facilitate test replacement! from time import ctime def usage(msg): print >> sys.stderr, msg print >> sys.stderr, __doc__ def main(args=None): if args is None: args = sys.argv[1:] # Parse options. MB = 1<<20 cachelimit = 20*MB rearrange = 0.8 simclass = CircularCacheSimulation interval_step = 15 try: opts, args = getopt.getopt(args, "s:i:r:") except getopt.error, msg: usage(msg) return 2 for o, a in opts: if o == '-s': cachelimit = int(float(a)*MB) elif o == '-i': interval_step = int(a) elif o == '-r': rearrange = float(a) else: assert False, (o, a) interval_step *= 60 if interval_step <= 0: interval_step = 60 elif interval_step > 3600: interval_step = 3600 if len(args) != 1: usage("exactly one file argument required") return 2 filename = args[0] # Open file. if filename.endswith(".gz"): # Open gzipped file. try: import gzip except ImportError: print >> sys.stderr, "can't read gzipped files (no module gzip)" return 1 try: f = gzip.open(filename, "rb") except IOError, msg: print >> sys.stderr, "can't open %s: %s" % (filename, msg) return 1 elif filename == "-": # Read from stdin. f = sys.stdin else: # Open regular file. try: f = open(filename, "rb") except IOError, msg: print >> sys.stderr, "can't open %s: %s" % (filename, msg) return 1 # Create simulation object. sim = simclass(cachelimit, rearrange) interval_sim = simclass(cachelimit, rearrange) # Print output header. sim.printheader() # Read trace file, simulating cache behavior. f_read = f.read unpack = struct.unpack FMT = ">iiH8s8s" FMT_SIZE = struct.calcsize(FMT) assert FMT_SIZE == 26 last_interval = None while 1: # Read a record and decode it. r = f_read(FMT_SIZE) if len(r) < FMT_SIZE: break ts, code, oidlen, start_tid, end_tid = unpack(FMT, r) if ts == 0: # Must be a misaligned record caused by a crash; skip 8 bytes # and try again. Why 8? Lost in the mist of history. f.seek(f.tell() - FMT_SIZE + 8) continue oid = f_read(oidlen) if len(oid) < oidlen: break # Decode the code. dlen, version, code = ((code & 0x7fffff00) >> 8, code & 0x80, code & 0x7e) # And pass it to the simulation. this_interval = int(ts)/interval_step if this_interval != last_interval: if last_interval is not None: interval_sim.report() interval_sim.restart() if not interval_sim.warm: sim.restart() last_interval = this_interval sim.event(ts, dlen, version, code, oid, start_tid, end_tid) interval_sim.event(ts, dlen, version, code, oid, start_tid, end_tid) f.close() # Finish simulation. interval_sim.report() sim.finish() class Simulation(object): """Base class for simulations. The driver program calls: event(), printheader(), finish(). The standard event() method calls these additional methods: write(), load(), inval(), report(), restart(); the standard finish() method also calls report(). """ def __init__(self, cachelimit, rearrange): self.cachelimit = cachelimit self.rearrange = rearrange # Initialize global statistics. self.epoch = None self.warm = False self.total_loads = 0 self.total_hits = 0 # subclass must increment self.total_invals = 0 # subclass must increment self.total_writes = 0 if not hasattr(self, "extras"): self.extras = (self.extraname,) self.format = self.format + " %7s" * len(self.extras) # Reset per-run statistics and set up simulation data. self.restart() def restart(self): # Reset per-run statistics. self.loads = 0 self.hits = 0 # subclass must increment self.invals = 0 # subclass must increment self.writes = 0 self.ts0 = None def event(self, ts, dlen, _version, code, oid, start_tid, end_tid): # Record first and last timestamp seen. if self.ts0 is None: self.ts0 = ts if self.epoch is None: self.epoch = ts self.ts1 = ts # Simulate cache behavior. Caution: the codes in the trace file # record whether the actual cache missed or hit on each load, but # that bears no necessary relationship to whether the simulated cache # will hit or miss. Relatedly, if the actual cache needed to store # an object, the simulated cache may not need to (it may already # have the data). action = code & 0x70 if action & 0x20: # Load. self.loads += 1 self.total_loads += 1 # Asserting that dlen is 0 iff it's a load miss. # assert (dlen == 0) == (code in (0x20, 0x24)) self.load(oid, dlen, start_tid, code) elif action & 0x40: # Store. assert dlen self.write(oid, dlen, start_tid, end_tid) elif action & 0x10: # Invalidate. self.inval(oid, start_tid) elif action == 0x00: # Restart. self.restart() else: raise ValueError("unknown trace code 0x%x" % code) def write(self, oid, size, start_tid, end_tid): pass def load(self, oid, size, start_tid, code): # Must increment .hits and .total_hits as appropriate. pass def inval(self, oid, start_tid): # Must increment .invals and .total_invals as appropriate. pass format = "%12s %6s %7s %7s %6s %6s %7s" # Subclass should override extraname to name known instance variables; # if extraname is 'foo', both self.foo and self.total_foo must exist: extraname = "*** please override ***" def printheader(self): print "%s, cache size %s bytes" % (self.__class__.__name__, addcommas(self.cachelimit)) self.extraheader() extranames = tuple([s.upper() for s in self.extras]) args = ("START TIME", "DUR.", "LOADS", "HITS", "INVALS", "WRITES", "HITRATE") + extranames print self.format % args def extraheader(self): pass nreports = 0 def report(self): if not hasattr(self, 'ts1'): return self.nreports += 1 args = (ctime(self.ts0)[4:-8], duration(self.ts1 - self.ts0), self.loads, self.hits, self.invals, self.writes, hitrate(self.loads, self.hits)) args += tuple([getattr(self, name) for name in self.extras]) print self.format % args def finish(self): # Make sure that the last line of output ends with "OVERALL". This # makes it much easier for another program parsing the output to # find summary statistics. print '-'*74 if self.nreports < 2: self.report() else: self.report() args = ( ctime(self.epoch)[4:-8], duration(self.ts1 - self.epoch), self.total_loads, self.total_hits, self.total_invals, self.total_writes, hitrate(self.total_loads, self.total_hits)) args += tuple([getattr(self, "total_" + name) for name in self.extras]) print self.format % args # For use in CircularCacheSimulation. class CircularCacheEntry(object): __slots__ = ( # object key: an (oid, start_tid) pair, where start_tid is the # tid of the transaction that created this revision of oid 'key', # tid of transaction that created the next revision; z64 iff # this is the current revision 'end_tid', # Offset from start of file to the object's data record; this # includes all overhead bytes (status byte, size bytes, etc). 'offset', ) def __init__(self, key, end_tid, offset): self.key = key self.end_tid = end_tid self.offset = offset from ZEO.cache import ZEC_HEADER_SIZE class CircularCacheSimulation(Simulation): """Simulate the ZEO 3.0 cache.""" # The cache is managed as a single file with a pointer that # goes around the file, circularly, forever. New objects # are written at the current pointer, evicting whatever was # there previously. extras = "evicts", "inuse" evicts = 0 def __init__(self, cachelimit, rearrange): from ZEO import cache Simulation.__init__(self, cachelimit, rearrange) self.total_evicts = 0 # number of cache evictions # Current offset in file. self.offset = ZEC_HEADER_SIZE # Map offset in file to (size, CircularCacheEntry) pair, or to # (size, None) if the offset starts a free block. self.filemap = {ZEC_HEADER_SIZE: (self.cachelimit - ZEC_HEADER_SIZE, None)} # Map key to CircularCacheEntry. A key is an (oid, tid) pair. self.key2entry = {} # Map oid to tid of current revision. self.current = {} # Map oid to list of (start_tid, end_tid) pairs in sorted order. # Used to find matching key for load of non-current data. self.noncurrent = {} # The number of overhead bytes needed to store an object pickle # on disk (all bytes beyond those needed for the object pickle). self.overhead = ZEO.cache.allocated_record_overhead # save evictions so we can replay them, if necessary self.evicted = {} def restart(self): Simulation.restart(self) if self.evicts: self.warm = True self.evicts = 0 self.evicted_hit = self.evicted_miss = 0 evicted_hit = evicted_miss = 0 def load(self, oid, size, tid, code): if (code == 0x20) or (code == 0x22): # Trying to load current revision. if oid in self.current: # else it's a cache miss self.hits += 1 self.total_hits += 1 tid = self.current[oid] entry = self.key2entry[(oid, tid)] offset_offset = self.offset - entry.offset if offset_offset < 0: offset_offset += self.cachelimit assert offset_offset >= 0 if offset_offset > self.rearrange * self.cachelimit: # we haven't accessed it in a while. Move it forward size = self.filemap[entry.offset][0] self._remove(*entry.key) self.add(oid, size, tid) elif oid in self.evicted: size, e = self.evicted[oid] self.write(oid, size, e.key[1], z64, 1) self.evicted_hit += 1 else: self.evicted_miss += 1 return # May or may not be trying to load current revision. cur_tid = self.current.get(oid) if cur_tid == tid: self.hits += 1 self.total_hits += 1 return # It's a load for non-current data. Do we know about this oid? L = self.noncurrent.get(oid) if L is None: return # cache miss i = bisect.bisect_left(L, (tid, None)) if i == 0: # This tid is smaller than any we know about -- miss. return lo, hi = L[i-1] assert lo < tid if tid > hi: # No data in the right tid range -- miss. return # Cache hit. self.hits += 1 self.total_hits += 1 # (oid, tid) is in the cache. Remove it: take it out of key2entry, # and in `filemap` mark the space it occupied as being free. The # caller is responsible for removing it from `current` or `noncurrent`. def _remove(self, oid, tid): key = oid, tid e = self.key2entry.pop(key) pos = e.offset size, _e = self.filemap[pos] assert e is _e self.filemap[pos] = size, None def _remove_noncurrent_revisions(self, oid): noncurrent_list = self.noncurrent.get(oid) if noncurrent_list: self.invals += len(noncurrent_list) self.total_invals += len(noncurrent_list) for start_tid, end_tid in noncurrent_list: self._remove(oid, start_tid) del self.noncurrent[oid] def inval(self, oid, tid): if tid == z64: # This is part of startup cache verification: forget everything # about this oid. self._remove_noncurrent_revisions(oid) if oid in self.evicted: del self.evicted[oid] cur_tid = self.current.get(oid) if cur_tid is None: # We don't have current data, so nothing more to do. return # We had current data for oid, but no longer. self.invals += 1 self.total_invals += 1 del self.current[oid] if tid == z64: # Startup cache verification: forget this oid entirely. self._remove(oid, cur_tid) return # Our current data becomes non-current data. # Add the validity range to the list of non-current data for oid. assert cur_tid < tid L = self.noncurrent.setdefault(oid, []) bisect.insort_left(L, (cur_tid, tid)) # Update the end of oid's validity range in its CircularCacheEntry. e = self.key2entry[oid, cur_tid] assert e.end_tid == z64 e.end_tid = tid def write(self, oid, size, start_tid, end_tid, evhit=0): if end_tid == z64: # Storing current revision. if oid in self.current: # we already have it in cache if evhit: import pdb; pdb.set_trace() raise ValueError('WTF') return self.current[oid] = start_tid self.writes += 1 self.total_writes += 1 self.add(oid, size, start_tid) return if evhit: import pdb; pdb.set_trace() raise ValueError('WTF') # Storing non-current revision. L = self.noncurrent.setdefault(oid, []) p = start_tid, end_tid if p in L: return # we already have it in cache bisect.insort_left(L, p) self.writes += 1 self.total_writes += 1 self.add(oid, size, start_tid, end_tid) # Add `oid` to the cache, evicting objects as needed to make room. # This updates `filemap` and `key2entry`; it's the caller's # responsibilty to update `current` or `noncurrent` appropriately. def add(self, oid, size, start_tid, end_tid=z64): key = oid, start_tid assert key not in self.key2entry size += self.overhead avail = self.makeroom(size+1) # see cache.py e = CircularCacheEntry(key, end_tid, self.offset) self.filemap[self.offset] = size, e self.key2entry[key] = e self.offset += size # All the space made available must be accounted for in filemap. excess = avail - size if excess: self.filemap[self.offset] = excess, None # Evict enough objects to make at least `need` contiguous bytes, starting # at `self.offset`, available. Evicted objects are removed from # `filemap`, `key2entry`, `current` and `noncurrent`. The caller is # responsible for adding new entries to `filemap` to account for all # the freed bytes, and for advancing `self.offset`. The number of bytes # freed is the return value, and will be >= need. def makeroom(self, need): if self.offset + need > self.cachelimit: self.offset = ZEC_HEADER_SIZE pos = self.offset while need > 0: assert pos < self.cachelimit size, e = self.filemap.pop(pos) if e: # there is an object here (else it's already free space) self.evicts += 1 self.total_evicts += 1 assert pos == e.offset _e = self.key2entry.pop(e.key) assert e is _e oid, start_tid = e.key if e.end_tid == z64: del self.current[oid] self.evicted[oid] = size-self.overhead, e else: L = self.noncurrent[oid] L.remove((start_tid, e.end_tid)) need -= size pos += size return pos - self.offset # total number of bytes freed def report(self): self.check() free = used = total = 0 for size, e in self.filemap.itervalues(): total += size if e: used += size else: free += size self.inuse = round(100.0 * used / total, 1) self.total_inuse = self.inuse Simulation.report(self) #print self.evicted_hit, self.evicted_miss def check(self): oidcount = 0 pos = ZEC_HEADER_SIZE while pos < self.cachelimit: size, e = self.filemap[pos] if e: oidcount += 1 assert self.key2entry[e.key].offset == pos pos += size assert oidcount == len(self.key2entry) assert pos == self.cachelimit def dump(self): print len(self.filemap) L = list(self.filemap) L.sort() for k in L: v = self.filemap[k] print k, v[0], repr(v[1]) def roundup(size): k = MINSIZE while k < size: k += k return k def hitrate(loads, hits): if loads < 1: return 'n/a' return "%5.1f%%" % (100.0 * hits / loads) def duration(secs): mm, ss = divmod(secs, 60) hh, mm = divmod(mm, 60) if hh: return "%d:%02d:%02d" % (hh, mm, ss) if mm: return "%d:%02d" % (mm, ss) return "%d" % ss nre = re.compile('([=-]?)(\d+)([.]\d*)?').match def addcommas(n): sign, s, d = nre(str(n)).group(1, 2, 3) if d == '.0': d = '' result = s[-3:] s = s[:-3] while s: result = s[-3:]+','+result s = s[:-3] return (sign or '') + result + (d or '') import random def maybe(f, p=0.5): if random.random() < p: f() if __name__ == "__main__": sys.exit(main()) zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/cache_stats.py0000644000175000017500000003110612214017464022030 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Trace file statistics analyzer. Usage: stats.py [-h] [-i interval] [-q] [-s] [-S] [-v] [-X] tracefile -h: print histogram of object load frequencies -i: summarizing interval in minutes (default 15; max 60) -q: quiet; don't print summaries -s: print histogram of object sizes -S: don't print statistics -v: verbose; print each record -X: enable heuristic checking for misaligned records: oids > 2**32 will be rejected; this requires the tracefile to be seekable """ """File format: Each record is 26 bytes, plus a variable number of bytes to store an oid, with the following layout. Numbers are big-endian integers. Offset Size Contents 0 4 timestamp (seconds since 1/1/1970) 4 3 data size, in 256-byte increments, rounded up 7 1 code (see below) 8 2 object id length 10 8 start tid 18 8 end tid 26 variable object id The code at offset 7 packs three fields: Mask bits Contents 0x80 1 set if there was a non-empty version string 0x7e 6 function and outcome code 0x01 1 current cache file (0 or 1) The "current cache file" bit is no longer used; it refers to a 2-file cache scheme used before ZODB 3.3. The function and outcome codes are documented in detail at the end of this file in the 'explain' dictionary. Note that the keys there (and also the arguments to _trace() in ClientStorage.py) are 'code & 0x7e', i.e. the low bit is always zero. """ import sys import time import getopt import struct from types import StringType # we assign ctime locally to facilitate test replacement! from time import ctime def usage(msg): print >> sys.stderr, msg print >> sys.stderr, __doc__ def main(args=None): if args is None: args = sys.argv[1:] # Parse options verbose = False quiet = False dostats = True print_size_histogram = False print_histogram = False interval = 15*60 # Every 15 minutes heuristic = False try: opts, args = getopt.getopt(args, "hi:qsSvX") except getopt.error, msg: usage(msg) return 2 for o, a in opts: if o == '-h': print_histogram = True elif o == "-i": interval = int(60 * float(a)) if interval <= 0: interval = 60 elif interval > 3600: interval = 3600 elif o == "-q": quiet = True verbose = False elif o == "-s": print_size_histogram = True elif o == "-S": dostats = False elif o == "-v": verbose = True elif o == '-X': heuristic = True else: assert False, (o, opts) if len(args) != 1: usage("exactly one file argument required") return 2 filename = args[0] # Open file if filename.endswith(".gz"): # Open gzipped file try: import gzip except ImportError: print >> sys.stderr, "can't read gzipped files (no module gzip)" return 1 try: f = gzip.open(filename, "rb") except IOError, msg: print >> sys.stderr, "can't open %s: %s" % (filename, msg) return 1 elif filename == '-': # Read from stdin f = sys.stdin else: # Open regular file try: f = open(filename, "rb") except IOError, msg: print >> sys.stderr, "can't open %s: %s" % (filename, msg) return 1 rt0 = time.time() bycode = {} # map code to count of occurrences byinterval = {} # map code to count in current interval records = 0 # number of trace records read versions = 0 # number of trace records with versions datarecords = 0 # number of records with dlen set datasize = 0L # sum of dlen across records with dlen set oids = {} # map oid to number of times it was loaded bysize = {} # map data size to number of loads bysizew = {} # map data size to number of writes total_loads = 0 t0 = None # first timestamp seen te = None # most recent timestamp seen h0 = None # timestamp at start of current interval he = None # timestamp at end of current interval thisinterval = None # generally te//interval f_read = f.read unpack = struct.unpack FMT = ">iiH8s8s" FMT_SIZE = struct.calcsize(FMT) assert FMT_SIZE == 26 # Read file, gathering statistics, and printing each record if verbose. print ' '*16, "%7s %7s %7s %7s" % ('loads', 'hits', 'inv(h)', 'writes'), print 'hitrate' try: while 1: r = f_read(FMT_SIZE) if len(r) < FMT_SIZE: break ts, code, oidlen, start_tid, end_tid = unpack(FMT, r) if ts == 0: # Must be a misaligned record caused by a crash. if not quiet: print "Skipping 8 bytes at offset", f.tell() - FMT_SIZE f.seek(f.tell() - FMT_SIZE + 8) continue oid = f_read(oidlen) if len(oid) < oidlen: break records += 1 if t0 is None: t0 = ts thisinterval = t0 // interval h0 = he = ts te = ts if ts // interval != thisinterval: if not quiet: dumpbyinterval(byinterval, h0, he) byinterval = {} thisinterval = ts // interval h0 = ts he = ts dlen, code = (code & 0x7fffff00) >> 8, code & 0xff if dlen: datarecords += 1 datasize += dlen if code & 0x80: version = 'V' versions += 1 else: version = '-' code &= 0x7e bycode[code] = bycode.get(code, 0) + 1 byinterval[code] = byinterval.get(code, 0) + 1 if dlen: if code & 0x70 == 0x20: # All loads bysize[dlen] = d = bysize.get(dlen) or {} d[oid] = d.get(oid, 0) + 1 elif code & 0x70 == 0x50: # All stores bysizew[dlen] = d = bysizew.get(dlen) or {} d[oid] = d.get(oid, 0) + 1 if verbose: print "%s %02x %s %016x %016x %c%s" % ( ctime(ts)[4:-5], code, oid_repr(oid), U64(start_tid), U64(end_tid), version, dlen and (' '+str(dlen)) or "") if code & 0x70 == 0x20: oids[oid] = oids.get(oid, 0) + 1 total_loads += 1 elif code == 0x00: # restart if not quiet: dumpbyinterval(byinterval, h0, he) byinterval = {} thisinterval = ts // interval h0 = he = ts if not quiet: print ctime(ts)[4:-5], print '='*20, "Restart", '='*20 except KeyboardInterrupt: print "\nInterrupted. Stats so far:\n" end_pos = f.tell() f.close() rte = time.time() if not quiet: dumpbyinterval(byinterval, h0, he) # Error if nothing was read if not records: print >> sys.stderr, "No records processed" return 1 # Print statistics if dostats: print print "Read %s trace records (%s bytes) in %.1f seconds" % ( addcommas(records), addcommas(end_pos), rte-rt0) print "Versions: %s records used a version" % addcommas(versions) print "First time: %s" % ctime(t0) print "Last time: %s" % ctime(te) print "Duration: %s seconds" % addcommas(te-t0) print "Data recs: %s (%.1f%%), average size %d bytes" % ( addcommas(datarecords), 100.0 * datarecords / records, datasize / datarecords) print "Hit rate: %.1f%% (load hits / loads)" % hitrate(bycode) print codes = bycode.keys() codes.sort() print "%13s %4s %s" % ("Count", "Code", "Function (action)") for code in codes: print "%13s %02x %s" % ( addcommas(bycode.get(code, 0)), code, explain.get(code) or "*** unknown code ***") # Print histogram. if print_histogram: print print "Histogram of object load frequency" total = len(oids) print "Unique oids: %s" % addcommas(total) print "Total loads: %s" % addcommas(total_loads) s = addcommas(total) width = max(len(s), len("objects")) fmt = "%5d %" + str(width) + "s %5.1f%% %5.1f%% %5.1f%%" hdr = "%5s %" + str(width) + "s %6s %6s %6s" print hdr % ("loads", "objects", "%obj", "%load", "%cum") cum = 0.0 for binsize, count in histogram(oids): obj_percent = 100.0 * count / total load_percent = 100.0 * count * binsize / total_loads cum += load_percent print fmt % (binsize, addcommas(count), obj_percent, load_percent, cum) # Print size histogram. if print_size_histogram: print print "Histograms of object sizes" print dumpbysize(bysizew, "written", "writes") dumpbysize(bysize, "loaded", "loads") def dumpbysize(bysize, how, how2): print print "Unique sizes %s: %s" % (how, addcommas(len(bysize))) print "%10s %6s %6s" % ("size", "objs", how2) sizes = bysize.keys() sizes.sort() for size in sizes: loads = 0 for n in bysize[size].itervalues(): loads += n print "%10s %6d %6d" % (addcommas(size), len(bysize.get(size, "")), loads) def dumpbyinterval(byinterval, h0, he): loads = hits = invals = writes = 0 for code in byinterval: if code & 0x20: n = byinterval[code] loads += n if code in (0x22, 0x26): hits += n elif code & 0x40: writes += byinterval[code] elif code & 0x10: if code != 0x10: invals += byinterval[code] if loads: hr = "%5.1f%%" % (100.0 * hits / loads) else: hr = 'n/a' print "%s-%s %7s %7s %7s %7s %7s" % ( ctime(h0)[4:-8], ctime(he)[14:-8], loads, hits, invals, writes, hr) def hitrate(bycode): loads = hits = 0 for code in bycode: if code & 0x70 == 0x20: n = bycode[code] loads += n if code in (0x22, 0x26): hits += n if loads: return 100.0 * hits / loads else: return 0.0 def histogram(d): bins = {} for v in d.itervalues(): bins[v] = bins.get(v, 0) + 1 L = bins.items() L.sort() return L def U64(s): return struct.unpack(">Q", s)[0] def oid_repr(oid): if isinstance(oid, StringType) and len(oid) == 8: return '%16x' % U64(oid) else: return repr(oid) def addcommas(n): sign, s = '', str(n) if s[0] == '-': sign, s = '-', s[1:] i = len(s) - 3 while i > 0: s = s[:i] + ',' + s[i:] i -= 3 return sign + s explain = { # The first hex digit shows the operation, the second the outcome. # If the second digit is in "02468" then it is a 'miss'. # If it is in "ACE" then it is a 'hit'. 0x00: "_setup_trace (initialization)", 0x10: "invalidate (miss)", 0x1A: "invalidate (hit, version)", 0x1C: "invalidate (hit, saving non-current)", # 0x1E can occur during startup verification. 0x1E: "invalidate (hit, discarding current or non-current)", 0x20: "load (miss)", 0x22: "load (hit)", 0x24: "load (non-current, miss)", 0x26: "load (non-current, hit)", 0x50: "store (version)", 0x52: "store (current, non-version)", 0x54: "store (non-current)", } if __name__ == "__main__": sys.exit(main()) zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/timeout.py0000644000175000017500000000322512214017464021236 0ustar arnauarnau#!/usr/bin/env python2.3 """Transaction timeout test script. This script connects to a storage, begins a transaction, calls store() and tpc_vote(), and then sleeps forever. This should trigger the transaction timeout feature of the server. usage: timeout.py address delay [storage-name] """ import sys import time from ZODB.Transaction import Transaction from ZODB.tests.MinPO import MinPO from ZODB.tests.StorageTestBase import zodb_pickle from ZEO.ClientStorage import ClientStorage ZERO = '\0'*8 def main(): if len(sys.argv) not in (3, 4): sys.stderr.write("Usage: timeout.py address delay [storage-name]\n" % sys.argv[0]) sys.exit(2) hostport = sys.argv[1] delay = float(sys.argv[2]) if sys.argv[3:]: name = sys.argv[3] else: name = "1" if "/" in hostport: address = hostport else: if ":" in hostport: i = hostport.index(":") host, port = hostport[:i], hostport[i+1:] else: host, port = "", hostport port = int(port) address = (host, port) print "Connecting to %s..." % repr(address) storage = ClientStorage(address, name) print "Connected. Now starting a transaction..." oid = storage.new_oid() revid = ZERO data = MinPO("timeout.py") pickled_data = zodb_pickle(data) t = Transaction() t.user = "timeout.py" storage.tpc_begin(t) storage.store(oid, revid, pickled_data, '', t) print "Stored. Now voting..." storage.tpc_vote(t) print "Voted; now sleeping %s..." % delay time.sleep(delay) print "Done." if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/zeopack.py0000644000175000017500000001177412214017464021214 0ustar arnauarnau#!/usr/bin/env python2.3 import logging import optparse import socket import sys import time import traceback import ZEO.ClientStorage usage = """Usage: %prog [options] [servers] Pack one or more storages hosted by ZEO servers. The positional arguments specify 0 or more tcp servers to pack, where each is of the form: host:port[:name] """ WAIT = 10 # wait no more than 10 seconds for client to connect def _main(args=None, prog=None): if args is None: args = sys.argv[1:] parser = optparse.OptionParser(usage, prog=prog) parser.add_option( "-d", "--days", dest="days", type='int', default=0, help=("Pack objects that are older than this number of days") ) parser.add_option( "-t", "--time", dest="time", help=("Time of day to pack to of the form: HH[:MM[:SS]]. " "Defaults to current time.") ) parser.add_option( "-u", "--unix", dest="unix_sockets", action="append", help=("A unix-domain-socket server to connect to, of the form: " "path[:name]") ) parser.remove_option('-h') parser.add_option( "-h", dest="host", help=("Deprecated: " "Used with the -p and -S options, specified the host to " "connect to.") ) parser.add_option( "-p", type="int", dest="port", help=("Deprecated: " "Used with the -h and -S options, specifies " "the port to connect to.") ) parser.add_option( "-S", dest="name", default='1', help=("Deprecated: Used with the -h and -p, options, or with the " "-U option specified the storage name to use. Defaults to 1.") ) parser.add_option( "-U", dest="unix", help=("Deprecated: Used with the -S option, " "Unix-domain socket to connect to.") ) if not args: parser.print_help() return def error(message): sys.stderr.write("Error:\n%s\n" % message) sys.exit(1) options, args = parser.parse_args(args) packt = time.time() if options.time: time_ = map(int, options.time.split(':')) if len(time_) == 1: time_ += (0, 0) elif len(time_) == 2: time_ += (0,) elif len(time_) > 3: error("Invalid time value: %r" % options.time) packt = time.localtime(packt) packt = time.mktime(packt[:3]+tuple(time_)+packt[6:]) packt -= options.days * 86400 servers = [] if options.host: if not options.port: error("If host (-h) is specified then a port (-p) must be " "specified as well.") servers.append(((options.host, options.port), options.name)) elif options.port: servers.append(((socket.gethostname(), options.port), options.name)) if options.unix: servers.append((options.unix, options.name)) for server in args: data = server.split(':') if len(data) in (2, 3): host = data[0] try: port = int(data[1]) except ValueError: error("Invalid port in server specification: %r" % server) addr = host, port if len(data) == 2: name = '1' else: name = data[2] else: error("Invalid server specification: %r" % server) servers.append((addr, name)) for server in options.unix_sockets or (): data = server.split(':') if len(data) == 1: addr = data[0] name = '1' elif len(data) == 2: addr = data[0] name = data[1] else: error("Invalid server specification: %r" % server) servers.append((addr, name)) if not servers: error("No servers specified.") for addr, name in servers: try: cs = ZEO.ClientStorage.ClientStorage( addr, storage=name, wait=False, read_only=1) for i in range(60): if cs.is_connected(): break time.sleep(1) else: sys.stderr.write("Couldn't connect to: %r\n" % ((addr, name), )) cs.close() continue cs.pack(packt, wait=True) cs.close() except: traceback.print_exception(*(sys.exc_info()+(99, sys.stderr))) error("Error packing storage %s in %r" % (name, addr)) def main(*args): root_logger = logging.getLogger() old_level = root_logger.getEffectiveLevel() logging.getLogger().setLevel(logging.WARNING) handler = logging.StreamHandler(sys.stdout) handler.setFormatter(logging.Formatter( "%(name)s %(levelname)s %(message)s")) logging.getLogger().addHandler(handler) try: _main(*args) finally: logging.getLogger().setLevel(old_level) logging.getLogger().removeHandler(handler) if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/__init__.py0000644000175000017500000000000212214017464021275 0ustar arnauarnau# zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/README.txt0000644000175000017500000000361312214017464020675 0ustar arnauarnauThis directory contains a collection of utilities for working with ZEO. Some are more useful than others. If you install ZODB using distutils ("python setup.py install"), some of these will be installed. Unless otherwise noted, these scripts are invoked with the name of the Data.fs file as their only argument. Example: checkbtrees.py data.fs. parsezeolog.py -- parse BLATHER logs from ZEO server This script may be obsolete. It has not been tested against the current log output of the ZEO server. Reports on the time and size of transactions committed by a ZEO server, by inspecting log messages at BLATHER level. timeout.py -- script to test transaction timeout usage: timeout.py address delay [storage-name] This script connects to a storage, begins a transaction, calls store() and tpc_vote(), and then sleeps forever. This should trigger the transaction timeout feature of the server. zeopack.py -- pack a ZEO server The script connects to a server and calls pack() on a specific storage. See the script for usage details. zeoreplay.py -- experimental script to replay transactions from a ZEO log Like parsezeolog.py, this may be obsolete because it was written against an earlier version of the ZEO server. See the script for usage details. zeoup.py usage: zeoup.py [options] The test will connect to a ZEO server, load the root object, and attempt to update the zeoup counter in the root. It will report success if it updates to counter or if it gets a ConflictError. A ConflictError is considered a success, because the client was able to start a transaction. See the script for details about the options. zeoserverlog.py -- analyze ZEO server log for performance statistics See the module docstring for details; there are a large number of options. New in ZODB3 3.1.4. zeoqueue.py -- report number of clients currently waiting in the ZEO queue See the module docstring for details. zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/zeopack.test0000644000175000017500000002115712214017464021537 0ustar arnauarnauzeopack ======= The zeopack script can be used to pack one or more storages. It uses ClientStorage to do this. To test it's behavior, we'll replace the normal ClientStorage with a fake one that echos information we'll want for our test: >>> class ClientStorage: ... connect_wait = 0 ... def __init__(self, *args, **kw): ... if args[0] == 'bad': ... import logging ... logging.getLogger('test.ClientStorage').error( ... "I hate this address, %r", args[0]) ... raise ValueError("Bad address") ... print "ClientStorage(%s %s)" % ( ... repr(args)[1:-1], ... ', '.join("%s=%r" % i for i in sorted(kw.items())), ... ) ... def pack(self, t=None, *args, **kw): ... now = time.localtime(time.time()) ... local_midnight = time.mktime(now[:3]+(0, 0, 0)+now[6:]) ... t -= local_midnight # adjust for tz ... t += 86400*7 # add a week to make sure we're positive ... print "pack(%r,%s %s)" % ( ... t, repr(args)[1:-1], ... ', '.join("%s=%r" % i for i in sorted(kw.items())), ... ) ... def is_connected(self): ... self.connect_wait -= 1 ... print 'is_connected', self.connect_wait < 0 ... return self.connect_wait < 0 ... def close(self): ... print "close()" >>> import ZEO >>> ClientStorage_orig = ZEO.ClientStorage.ClientStorage >>> ZEO.ClientStorage.ClientStorage = ClientStorage Now, we're ready to try the script: >>> from ZEO.scripts.zeopack import main If we call it with no arguments, we get help: >>> import os; os.environ['COLUMNS'] = '80' # for consistent optparse output >>> main([], 'zeopack') Usage: zeopack [options] [servers] Pack one or more storages hosted by ZEO servers. The positional arguments specify 0 or more tcp servers to pack, where each is of the form: host:port[:name] Options: -d DAYS, --days=DAYS Pack objects that are older than this number of days -t TIME, --time=TIME Time of day to pack to of the form: HH[:MM[:SS]]. Defaults to current time. -u UNIX_SOCKETS, --unix=UNIX_SOCKETS A unix-domain-socket server to connect to, of the form: path[:name] -h HOST Deprecated: Used with the -p and -S options, specified the host to connect to. -p PORT Deprecated: Used with the -h and -S options, specifies the port to connect to. -S NAME Deprecated: Used with the -h and -p, options, or with the -U option specified the storage name to use. Defaults to 1. -U UNIX Deprecated: Used with the -S option, Unix-domain socket to connect to. Since packing involves time, we'd better have our way with it. Replace time.time() with a function that always returns the same value. The value is timezone dependent. >>> import time >>> time_orig = time.time >>> time.time = lambda : time.mktime((2009, 3, 24, 10, 55, 17, 1, 83, -1)) >>> sleep_orig = time.sleep >>> def sleep(t): ... print 'sleep(%r)' % t >>> time.sleep = sleep Normally, we pass one or more TCP server specifications: >>> main(["host1:8100", "host1:8100:2"]) ClientStorage(('host1', 8100), read_only=1, storage='1', wait=False) is_connected True pack(644117.0, wait=True) close() ClientStorage(('host1', 8100), read_only=1, storage='2', wait=False) is_connected True pack(644117.0, wait=True) close() We can also pass unix-domain-sockey servers using the -u option: >>> main(["-ufoo", "-ubar:spam", "host1:8100", "host1:8100:2"]) ClientStorage(('host1', 8100), read_only=1, storage='1', wait=False) is_connected True pack(644117.0, wait=True) close() ClientStorage(('host1', 8100), read_only=1, storage='2', wait=False) is_connected True pack(644117.0, wait=True) close() ClientStorage('foo', read_only=1, storage='1', wait=False) is_connected True pack(644117.0, wait=True) close() ClientStorage('bar', read_only=1, storage='spam', wait=False) is_connected True pack(644117.0, wait=True) close() The -d option causes a pack time the given number of days earlier to be used: >>> main(["-ufoo", "-ubar:spam", "-d3", "host1:8100", "host1:8100:2"]) ClientStorage(('host1', 8100), read_only=1, storage='1', wait=False) is_connected True pack(384917.0, wait=True) close() ClientStorage(('host1', 8100), read_only=1, storage='2', wait=False) is_connected True pack(384917.0, wait=True) close() ClientStorage('foo', read_only=1, storage='1', wait=False) is_connected True pack(384917.0, wait=True) close() ClientStorage('bar', read_only=1, storage='spam', wait=False) is_connected True pack(384917.0, wait=True) close() The -t option allows us to control the time of day: >>> main(["-ufoo", "-d3", "-t1:30", "host1:8100:2"]) ClientStorage(('host1', 8100), read_only=1, storage='2', wait=False) is_connected True pack(351000.0, wait=True) close() ClientStorage('foo', read_only=1, storage='1', wait=False) is_connected True pack(351000.0, wait=True) close() Connection timeout ------------------ The zeopack script tells ClientStorage not to wait for connections before returning from the constructor, but will time out after 60 seconds of waiting for a connect. >>> ClientStorage.connect_wait = 3 >>> main(["-d3", "-t1:30", "host1:8100:2"]) ClientStorage(('host1', 8100), read_only=1, storage='2', wait=False) is_connected False sleep(1) is_connected False sleep(1) is_connected False sleep(1) is_connected True pack(351000.0, wait=True) close() >>> def call_main(args): ... import sys ... old_stderr = sys.stderr ... sys.stderr = sys.stdout ... try: ... try: ... main(args) ... except SystemExit, v: ... print "Exited", v ... finally: ... sys.stderr = old_stderr >>> ClientStorage.connect_wait = 999 >>> call_main(["-d3", "-t1:30", "host1:8100", "host1:8100:2"]) ... # doctest: +ELLIPSIS ClientStorage(('host1', 8100), read_only=1, storage='1', wait=False) is_connected False sleep(1) ... is_connected False sleep(1) Couldn't connect to: (('host1', 8100), '1') close() ClientStorage(('host1', 8100), read_only=1, storage='2', wait=False) is_connected False sleep(1) ... is_connected False sleep(1) Couldn't connect to: (('host1', 8100), '2') close() >>> ClientStorage.connect_wait = 0 Legacy support -------------- >>> main(["-d3", "-h", "host1", "-p", "8100", "-S", "2"]) ClientStorage(('host1', 8100), read_only=1, storage='2', wait=False) is_connected True pack(384917.0, wait=True) close() >>> import socket >>> old_gethostname = socket.gethostname >>> socket.gethostname = lambda : 'test.host.com' >>> main(["-d3", "-p", "8100"]) ClientStorage(('test.host.com', 8100), read_only=1, storage='1', wait=False) is_connected True pack(384917.0, wait=True) close() >>> socket.gethostname = old_gethostname >>> main(["-d3", "-U", "foo/bar", "-S", "2"]) ClientStorage('foo/bar', read_only=1, storage='2', wait=False) is_connected True pack(384917.0, wait=True) close() Error handling -------------- >>> call_main(["-d3"]) Error: No servers specified. Exited 1 >>> call_main(["-d3", "a"]) Error: Invalid server specification: 'a' Exited 1 >>> call_main(["-d3", "a:b:c:d"]) Error: Invalid server specification: 'a:b:c:d' Exited 1 >>> call_main(["-d3", "a:b:2"]) Error: Invalid port in server specification: 'a:b:2' Exited 1 >>> call_main(["-d3", "-u", "a:b:2"]) Error: Invalid server specification: 'a:b:2' Exited 1 >>> call_main(["-d3", "-u", "bad"]) # doctest: +ELLIPSIS test.ClientStorage ERROR I hate this address, 'bad' Traceback (most recent call last): ... ValueError: Bad address Error: Error packing storage 1 in 'bad' Exited 1 Note that in the previous example, the first line was output through logging. .. tear down >>> ZEO.ClientStorage.ClientStorage = ClientStorage_orig >>> time.time = time_orig >>> time.sleep = sleep_orig zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/zeoqueue.py0000644000175000017500000002553512214017464021422 0ustar arnauarnau#!/usr/bin/env python2.3 """Report on the number of currently waiting clients in the ZEO queue. Usage: %(PROGRAM)s [options] logfile Options: -h / --help Print this help text and exit. -v / --verbose Verbose output -f file --file file Use the specified file to store the incremental state as a pickle. If not given, %(STATEFILE)s is used. -r / --reset Reset the state of the tool. This blows away any existing state pickle file and then exits -- it does not parse the file. Use this when you rotate log files so that the next run will parse from the beginning of the file. """ import os import re import sys import time import errno import getopt import cPickle as pickle COMMASPACE = ', ' STATEFILE = 'zeoqueue.pck' PROGRAM = sys.argv[0] try: True, False except NameError: True = 1 False = 0 tcre = re.compile(r""" (?P \d{4}- # year \d{2}- # month \d{2}) # day T # separator (?P \d{2}: # hour \d{2}: # minute \d{2}) # second """, re.VERBOSE) ccre = re.compile(r""" zrpc-conn:(?P\d+.\d+.\d+.\d+:\d+)\s+ calling\s+ (?P \w+) # the method \( # args open paren \' # string quote start (?P \S+) # first argument -- usually the tid \' # end of string (?P .*) # rest of line """, re.VERBOSE) wcre = re.compile(r'Clients waiting: (?P\d+)') def parse_time(line): """Return the time portion of a zLOG line in seconds or None.""" mo = tcre.match(line) if mo is None: return None date, time_ = mo.group('ymd', 'hms') date_l = [int(elt) for elt in date.split('-')] time_l = [int(elt) for elt in time_.split(':')] return int(time.mktime(date_l + time_l + [0, 0, 0])) class Txn: """Track status of single transaction.""" def __init__(self, tid): self.tid = tid self.hint = None self.begin = None self.vote = None self.abort = None self.finish = None self.voters = [] def isactive(self): if self.begin and not (self.abort or self.finish): return True else: return False class Status: """Track status of ZEO server by replaying log records. We want to keep track of several events: - The last committed transaction. - The last committed or aborted transaction. - The last transaction that got the lock but didn't finish. - The client address doing the first vote of a transaction. - The number of currently active transactions. - The number of reported queued transactions. - Client restarts. - Number of current connections (but this might not be useful). We can observe these events by reading the following sorts of log entries: 2002-12-16T06:16:05 BLATHER(-100) zrpc:12649 calling tpc_begin('\x03I\x90((\xdbp\xd5', '', 'QueueCatal... 2002-12-16T06:16:06 BLATHER(-100) zrpc:12649 calling vote('\x03I\x90((\xdbp\xd5') 2002-12-16T06:16:06 BLATHER(-100) zrpc:12649 calling tpc_finish('\x03I\x90((\xdbp\xd5') 2002-12-16T10:46:10 INFO(0) ZSS:12649:1 Transaction blocked waiting for storage. Clients waiting: 1. 2002-12-16T06:15:57 BLATHER(-100) zrpc:12649 connect from ('10.0.26.54', 48983): 2002-12-16T10:30:09 INFO(0) ZSS:12649:1 disconnected """ def __init__(self): self.lineno = 0 self.pos = 0 self.reset() def reset(self): self.commit = None self.commit_or_abort = None self.last_unfinished = None self.n_active = 0 self.n_blocked = 0 self.n_conns = 0 self.t_restart = None self.txns = {} def iscomplete(self): # The status report will always be complete if we encounter an # explicit restart. if self.t_restart is not None: return True # If we haven't seen a restart, assume that seeing a finished # transaction is good enough. return self.commit is not None def process_file(self, fp): if self.pos: if VERBOSE: print 'seeking to file position', self.pos fp.seek(self.pos) while True: line = fp.readline() if not line: break self.lineno += 1 self.process(line) self.pos = fp.tell() def process(self, line): if line.find("calling") != -1: self.process_call(line) elif line.find("connect") != -1: self.process_connect(line) # test for "locked" because word may start with "B" or "b" elif line.find("locked") != -1: self.process_block(line) elif line.find("Starting") != -1: self.process_start(line) def process_call(self, line): mo = ccre.search(line) if mo is None: return called_method = mo.group('method') # Exit early if we've got zeoLoad, because it's the most # frequently called method and we don't use it. if called_method == "zeoLoad": return t = parse_time(line) meth = getattr(self, "call_%s" % called_method, None) if meth is None: return client = mo.group('addr') tid = mo.group('tid') rest = mo.group('rest') meth(t, client, tid, rest) def process_connect(self, line): pass def process_block(self, line): mo = wcre.search(line) if mo is None: # assume that this was a restart message for the last blocked # transaction. self.n_blocked = 0 else: self.n_blocked = int(mo.group('num')) def process_start(self, line): if line.find("Starting ZEO server") != -1: self.reset() self.t_restart = parse_time(line) def call_tpc_begin(self, t, client, tid, rest): txn = Txn(tid) txn.begin = t if rest[0] == ',': i = 1 while rest[i].isspace(): i += 1 rest = rest[i:] txn.hint = rest self.txns[tid] = txn self.n_active += 1 self.last_unfinished = txn def call_vote(self, t, client, tid, rest): txn = self.txns.get(tid) if txn is None: print "Oops!" txn = self.txns[tid] = Txn(tid) txn.vote = t txn.voters.append(client) def call_tpc_abort(self, t, client, tid, rest): txn = self.txns.get(tid) if txn is None: print "Oops!" txn = self.txns[tid] = Txn(tid) txn.abort = t txn.voters = [] self.n_active -= 1 if self.commit_or_abort: # delete the old transaction try: del self.txns[self.commit_or_abort.tid] except KeyError: pass self.commit_or_abort = txn def call_tpc_finish(self, t, client, tid, rest): txn = self.txns.get(tid) if txn is None: print "Oops!" txn = self.txns[tid] = Txn(tid) txn.finish = t txn.voters = [] self.n_active -= 1 if self.commit: # delete the old transaction try: del self.txns[self.commit.tid] except KeyError: pass if self.commit_or_abort: # delete the old transaction try: del self.txns[self.commit_or_abort.tid] except KeyError: pass self.commit = self.commit_or_abort = txn def report(self): print "Blocked transactions:", self.n_blocked if not VERBOSE: return if self.t_restart: print "Server started:", time.ctime(self.t_restart) if self.commit is not None: t = self.commit_or_abort.finish if t is None: t = self.commit_or_abort.abort print "Last finished transaction:", time.ctime(t) # the blocked transaction should be the first one that calls vote L = [(txn.begin, txn) for txn in self.txns.values()] L.sort() for x, txn in L: if txn.isactive(): began = txn.begin if txn.voters: print "Blocked client (first vote):", txn.voters[0] print "Blocked transaction began at:", time.ctime(began) print "Hint:", txn.hint print "Idle time: %d sec" % int(time.time() - began) break def usage(code, msg=''): print >> sys.stderr, __doc__ % globals() if msg: print >> sys.stderr, msg sys.exit(code) def main(): global VERBOSE VERBOSE = 0 file = STATEFILE reset = False # -0 is a secret option used for testing purposes only seek = True try: opts, args = getopt.getopt(sys.argv[1:], 'vhf:r0', ['help', 'verbose', 'file=', 'reset']) except getopt.error, msg: usage(1, msg) for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt in ('-v', '--verbose'): VERBOSE += 1 elif opt in ('-f', '--file'): file = arg elif opt in ('-r', '--reset'): reset = True elif opt == '-0': seek = False if reset: # Blow away the existing state file and exit try: os.unlink(file) if VERBOSE: print 'removing pickle state file', file except OSError, e: if e.errno <> errno.ENOENT: raise return if not args: usage(1, 'logfile is required') if len(args) > 1: usage(1, 'too many arguments: %s' % COMMASPACE.join(args)) path = args[0] # Get the previous status object from the pickle file, if it is available # and if the --reset flag wasn't given. status = None try: statefp = open(file, 'rb') try: status = pickle.load(statefp) if VERBOSE: print 'reading status from file', file finally: statefp.close() except IOError, e: if e.errno <> errno.ENOENT: raise if status is None: status = Status() if VERBOSE: print 'using new status' if not seek: status.pos = 0 fp = open(path, 'rb') try: status.process_file(fp) finally: fp.close() # Save state statefp = open(file, 'wb') pickle.dump(status, statefp, 1) statefp.close() # Print the report and return the number of blocked clients in the exit # status code. status.report() sys.exit(status.n_blocked) if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/zeoup.py0000644000175000017500000001001212214017464020702 0ustar arnauarnau#!/usr/bin/env python2.3 """Make sure a ZEO server is running. usage: zeoup.py [options] The test will connect to a ZEO server, load the root object, and attempt to update the zeoup counter in the root. It will report success if it updates the counter or if it gets a ConflictError. A ConflictError is considered a success, because the client was able to start a transaction. Options: -p port -- port to connect to -h host -- host to connect to (default is current host) -S storage -- storage name (default '1') -U path -- Unix-domain socket to connect to --nowrite -- Do not update the zeoup counter. -1 -- Connect to a ZEO 1.0 server. You must specify either -p and -h or -U. """ import getopt import logging import socket import sys import time from persistent.mapping import PersistentMapping import transaction import ZODB from ZODB.POSException import ConflictError from ZODB.tests.MinPO import MinPO from ZEO.ClientStorage import ClientStorage ZEO_VERSION = 2 def setup_logging(): # Set up logging to stderr which will show messages originating # at severity ERROR or higher. root = logging.getLogger() root.setLevel(logging.ERROR) fmt = logging.Formatter( "------\n%(asctime)s %(levelname)s %(name)s %(message)s", "%Y-%m-%dT%H:%M:%S") handler = logging.StreamHandler() handler.setFormatter(fmt) root.addHandler(handler) def check_server(addr, storage, write): t0 = time.time() if ZEO_VERSION == 2: # TODO: should do retries w/ exponential backoff. cs = ClientStorage(addr, storage=storage, wait=0, read_only=(not write)) else: cs = ClientStorage(addr, storage=storage, debug=1, wait_for_server_on_startup=1) # _startup() is an artifact of the way ZEO 1.0 works. The # ClientStorage doesn't get fully initialized until registerDB() # is called. The only thing we care about, though, is that # registerDB() calls _startup(). if write: db = ZODB.DB(cs) cn = db.open() root = cn.root() try: # We store the data in a special `monitor' dict under the root, # where other tools may also store such heartbeat and bookkeeping # type data. monitor = root.get('monitor') if monitor is None: monitor = root['monitor'] = PersistentMapping() obj = monitor['zeoup'] = monitor.get('zeoup', MinPO(0)) obj.value += 1 transaction.commit() except ConflictError: pass cn.close() db.close() else: data, serial = cs.load("\0\0\0\0\0\0\0\0", "") cs.close() t1 = time.time() print "Elapsed time: %.2f" % (t1 - t0) def usage(exit=1): print __doc__ print " ".join(sys.argv) sys.exit(exit) def main(): host = None port = None unix = None write = 1 storage = '1' try: opts, args = getopt.getopt(sys.argv[1:], 'p:h:U:S:1', ['nowrite']) for o, a in opts: if o == '-p': port = int(a) elif o == '-h': host = a elif o == '-U': unix = a elif o == '-S': storage = a elif o == '--nowrite': write = 0 elif o == '-1': ZEO_VERSION = 1 except Exception, err: s = str(err) if s: s = ": " + s print err.__class__.__name__ + s usage() if unix is not None: addr = unix else: if host is None: host = socket.gethostname() if port is None: usage() addr = host, port setup_logging() check_server(addr, storage, write) if __name__ == "__main__": try: main() except SystemExit: raise except Exception, err: s = str(err) if s: s = ": " + s print err.__class__.__name__ + s sys.exit(1) zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/zeoserverlog.py0000644000175000017500000003474712214017464022313 0ustar arnauarnau#!/usr/bin/env python2.3 ############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tools for analyzing ZEO Server logs. This script contains a number of commands, implemented by command functions. To run a command, give the command name and it's arguments as arguments to this script. Commands: blocked_times file threshold Output a summary of episodes where thransactions were blocked when the episode lasted at least threshold seconds. The file may be a file name or - to read from standard input. The file may also be a command: script blocked_times 'bunzip2 = 0 if blocking and waiting == 1: t1 = time(line) t2 = t1 if not blocking and last_blocking: last_wait = 0 t2 = time(line) cid = idre.search(line).group(1) if waiting == 0: d = sub(t1, time(line)) if d >= thresh: print t1, sub(t1, t2), cid, d t1 = t2 = cid = blocking = waiting = last_wait = max_wait = 0 last_blocking = blocking connidre = re.compile(r' zrpc-conn:(\d+.\d+.\d+.\d+:\d+) ') def time_calls(f): f, thresh = f if f == '-': f = sys.stdin else: f = xopen(f) thresh = float(thresh) t1 = None maxd = 0 for line in f: line = line.strip() if ' calling ' in line: t1 = time(line) elif ' returns ' in line and t1 is not None: d = sub(t1, time(line)) if d >= thresh: print t1, d, connidre.search(line).group(1) maxd = max(maxd, d) t1 = None print maxd def xopen(f): if f == '-': return sys.stdin if ' ' in f: return os.popen(f, 'r') return open(f) def time_tpc(f): f, thresh = f if f == '-': f = sys.stdin else: f = xopen(f) thresh = float(thresh) transactions = {} for line in f: line = line.strip() if ' calling vote(' in line: cid = connidre.search(line).group(1) transactions[cid] = time(line), elif ' vote returns None' in line: cid = connidre.search(line).group(1) transactions[cid] += time(line), 'n' elif ' vote() raised' in line: cid = connidre.search(line).group(1) transactions[cid] += time(line), 'e' elif ' vote returns ' in line: # delayed, skip cid = connidre.search(line).group(1) transactions[cid] += time(line), 'd' elif ' calling tpc_abort(' in line: cid = connidre.search(line).group(1) if cid in transactions: t1, t2, vs = transactions[cid] t = time(line) d = sub(t1, t) if d >= thresh: print 'a', t1, cid, sub(t1, t2), vs, sub(t2, t) del transactions[cid] elif ' calling tpc_finish(' in line: if cid in transactions: cid = connidre.search(line).group(1) transactions[cid] += time(line), elif ' tpc_finish returns ' in line: if cid in transactions: t1, t2, vs, t3 = transactions[cid] t = time(line) d = sub(t1, t) if d >= thresh: print 'c', t1, cid, sub(t1, t2), vs, sub(t2, t3), sub(t3, t) del transactions[cid] newobre = re.compile(r"storea\(.*, '\\x00\\x00\\x00\\x00\\x00") def time_trans(f): f, thresh = f if f == '-': f = sys.stdin else: f = xopen(f) thresh = float(thresh) transactions = {} for line in f: line = line.strip() if ' calling tpc_begin(' in line: cid = connidre.search(line).group(1) transactions[cid] = time(line), [0, 0] if ' calling storea(' in line: cid = connidre.search(line).group(1) if cid in transactions: transactions[cid][1][0] += 1 if not newobre.search(line): transactions[cid][1][1] += 1 elif ' calling vote(' in line: cid = connidre.search(line).group(1) if cid in transactions: transactions[cid] += time(line), elif ' vote returns None' in line: cid = connidre.search(line).group(1) if cid in transactions: transactions[cid] += time(line), 'n' elif ' vote() raised' in line: cid = connidre.search(line).group(1) if cid in transactions: transactions[cid] += time(line), 'e' elif ' vote returns ' in line: # delayed, skip cid = connidre.search(line).group(1) if cid in transactions: transactions[cid] += time(line), 'd' elif ' calling tpc_abort(' in line: cid = connidre.search(line).group(1) if cid in transactions: try: t0, (stores, old), t1, t2, vs = transactions[cid] except ValueError: pass else: t = time(line) d = sub(t1, t) if d >= thresh: print t1, cid, "%s/%s" % (stores, old), \ sub(t0, t1), sub(t1, t2), vs, \ sub(t2, t), 'abort' del transactions[cid] elif ' calling tpc_finish(' in line: if cid in transactions: cid = connidre.search(line).group(1) transactions[cid] += time(line), elif ' tpc_finish returns ' in line: if cid in transactions: t0, (stores, old), t1, t2, vs, t3 = transactions[cid] t = time(line) d = sub(t1, t) if d >= thresh: print t1, cid, "%s/%s" % (stores, old), \ sub(t0, t1), sub(t1, t2), vs, \ sub(t2, t3), sub(t3, t) del transactions[cid] def minute(f, slice=16, detail=1, summary=1): f, = f if f == '-': f = sys.stdin else: f = xopen(f) cols = ["time", "reads", "stores", "commits", "aborts", "txns"] fmt = "%18s %6s %6s %7s %6s %6s" print fmt % cols print fmt % ["-"*len(col) for col in cols] mlast = r = s = c = a = cl = None rs = [] ss = [] cs = [] aborts = [] ts = [] cls = [] for line in f: line = line.strip() if (line.find('returns') > 0 or line.find('storea') > 0 or line.find('tpc_abort') > 0 ): client = connidre.search(line).group(1) m = line[:slice] if m != mlast: if mlast: if detail: print fmt % (mlast, len(cl), r, s, c, a, a+c) cls.append(len(cl)) rs.append(r) ss.append(s) cs.append(c) aborts.append(a) ts.append(c+a) mlast = m r = s = c = a = 0 cl = {} if line.find('zeoLoad') > 0: r += 1 cl[client] = 1 elif line.find('storea') > 0: s += 1 cl[client] = 1 elif line.find('tpc_finish') > 0: c += 1 cl[client] = 1 elif line.find('tpc_abort') > 0: a += 1 cl[client] = 1 if mlast: if detail: print fmt % (mlast, len(cl), r, s, c, a, a+c) cls.append(len(cl)) rs.append(r) ss.append(s) cs.append(c) aborts.append(a) ts.append(c+a) if summary: print print 'Summary: \t', '\t'.join(('min', '10%', '25%', 'med', '75%', '90%', 'max', 'mean')) print "n=%6d\t" % len(cls), '-'*62 print 'Clients: \t', '\t'.join(map(str,stats(cls))) print 'Reads: \t', '\t'.join(map(str,stats(rs))) print 'Stores: \t', '\t'.join(map(str,stats(ss))) print 'Commits: \t', '\t'.join(map(str,stats(cs))) print 'Aborts: \t', '\t'.join(map(str,stats(aborts))) print 'Trans: \t', '\t'.join(map(str,stats(ts))) def stats(s): s.sort() min = s[0] max = s[-1] n = len(s) out = [min] ni = n + 1 for p in .1, .25, .5, .75, .90: lp = ni*p l = int(lp) if lp < 1 or lp > n: out.append('-') elif abs(lp-l) < .00001: out.append(s[l-1]) else: out.append(int(s[l-1] + (lp - l) * (s[l] - s[l-1]))) mean = 0.0 for v in s: mean += v out.extend([max, int(mean/n)]) return out def minutes(f): minute(f, 16, detail=0) def hour(f): minute(f, 13) def day(f): minute(f, 10) def hours(f): minute(f, 13, detail=0) def days(f): minute(f, 10, detail=0) new_connection_idre = re.compile(r"new connection \('(\d+.\d+.\d+.\d+)', (\d+)\):") def verify(f): f, = f if f == '-': f = sys.stdin else: f = xopen(f) t1 = None nv = {} for line in f: if line.find('new connection') > 0: m = new_connection_idre.search(line) cid = "%s:%s" % (m.group(1), m.group(2)) nv[cid] = [time(line), 0] elif line.find('calling zeoVerify(') > 0: cid = connidre.search(line).group(1) nv[cid][1] += 1 elif line.find('calling endZeoVerify()') > 0: cid = connidre.search(line).group(1) t1, n = nv[cid] if n: d = sub(t1, time(line)) print cid, t1, n, d, n and (d*1000.0/n) or '-' def recovery(f): f, = f if f == '-': f = sys.stdin else: f = xopen(f) last = '' trans = [] n = 0 for line in f: n += 1 if line.find('RecoveryServer') < 0: continue l = line.find('sending transaction ') if l > 0 and last.find('sending transaction ') > 0: trans.append(line[l+20:].strip()) else: if trans: if len(trans) > 1: print " ... %s similar records skipped ..." % ( len(trans) - 1) print n, last.strip() trans=[] print n, line.strip() last = line if len(trans) > 1: print " ... %s similar records skipped ..." % ( len(trans) - 1) print n, last.strip() if __name__ == '__main__': globals()[sys.argv[1]](sys.argv[2:]) zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/tests.py0000644000175000017500000000202012214017464020702 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest, re, unittest from zope.testing import renormalizing def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite( 'zeopack.test', checker=renormalizing.RENormalizing([ (re.compile('usage: Usage: '), 'Usage: '), # Py 2.4 (re.compile('options:'), 'Options:'), # Py 2.4 ]) ), )) zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/zeoreplay.py0000644000175000017500000002045712214017464021570 0ustar arnauarnau#!/usr/bin/env python2.3 """Parse the BLATHER logging generated by ZEO, and optionally replay it. Usage: zeointervals.py [options] Options: --help / -h Print this message and exit. --replay=storage -r storage Replay the parsed transactions through the new storage --maxtxn=count -m count Parse no more than count transactions. --report / -p Print a report as we're parsing. Unlike parsezeolog.py, this script generates timestamps for each transaction, and sub-command in the transaction. We can use this to compare timings with synthesized data. """ import re import sys import time import getopt import operator # ZEO logs measure wall-clock time so for consistency we need to do the same #from time import clock as now from time import time as now from ZODB.FileStorage import FileStorage #from BDBStorage.BDBFullStorage import BDBFullStorage #from Standby.primary import PrimaryStorage #from Standby.config import RS_PORT from ZODB.Transaction import Transaction from ZODB.utils import p64 datecre = re.compile('(\d\d\d\d-\d\d-\d\d)T(\d\d:\d\d:\d\d)') methcre = re.compile("ZEO Server (\w+)\((.*)\) \('(.*)', (\d+)") class StopParsing(Exception): pass def usage(code, msg=''): print __doc__ if msg: print msg sys.exit(code) def parse_time(line): """Return the time portion of a zLOG line in seconds or None.""" mo = datecre.match(line) if mo is None: return None date, time_ = mo.group(1, 2) date_l = [int(elt) for elt in date.split('-')] time_l = [int(elt) for elt in time_.split(':')] return int(time.mktime(date_l + time_l + [0, 0, 0])) def parse_line(line): """Parse a log entry and return time, method info, and client.""" t = parse_time(line) if t is None: return None, None, None mo = methcre.search(line) if mo is None: return None, None, None meth_name = mo.group(1) meth_args = mo.group(2) meth_args = [s.strip() for s in meth_args.split(',')] m = meth_name, tuple(meth_args) c = mo.group(3), mo.group(4) return t, m, c class StoreStat: def __init__(self, when, oid, size): self.when = when self.oid = oid self.size = size # Crufty def __getitem__(self, i): if i == 0: return self.oid if i == 1: return self.size raise IndexError class TxnStat: def __init__(self): self._begintime = None self._finishtime = None self._aborttime = None self._url = None self._objects = [] def tpc_begin(self, when, args, client): self._begintime = when # args are txnid, user, description (looks like it's always a url) self._url = args[2] def storea(self, when, args, client): oid = int(args[0]) # args[1] is "[numbytes]" size = int(args[1][1:-1]) s = StoreStat(when, oid, size) self._objects.append(s) def tpc_abort(self, when): self._aborttime = when def tpc_finish(self, when): self._finishtime = when # Mapping oid -> revid _revids = {} class ReplayTxn(TxnStat): def __init__(self, storage): self._storage = storage self._replaydelta = 0 TxnStat.__init__(self) def replay(self): ZERO = '\0'*8 t0 = now() t = Transaction() self._storage.tpc_begin(t) for obj in self._objects: oid = obj.oid revid = _revids.get(oid, ZERO) # BAW: simulate a pickle of the given size data = 'x' * obj.size # BAW: ignore versions for now newrevid = self._storage.store(p64(oid), revid, data, '', t) _revids[oid] = newrevid if self._aborttime: self._storage.tpc_abort(t) origdelta = self._aborttime - self._begintime else: self._storage.tpc_vote(t) self._storage.tpc_finish(t) origdelta = self._finishtime - self._begintime t1 = now() # Shows how many seconds behind (positive) or ahead (negative) of the # original reply our local update took self._replaydelta = t1 - t0 - origdelta class ZEOParser: def __init__(self, maxtxns=-1, report=1, storage=None): self.__txns = [] self.__curtxn = {} self.__skipped = 0 self.__maxtxns = maxtxns self.__finishedtxns = 0 self.__report = report self.__storage = storage def parse(self, line): t, m, c = parse_line(line) if t is None: # Skip this line return name = m[0] meth = getattr(self, name, None) if meth is not None: meth(t, m[1], c) def tpc_begin(self, when, args, client): txn = ReplayTxn(self.__storage) self.__curtxn[client] = txn meth = getattr(txn, 'tpc_begin', None) if meth is not None: meth(when, args, client) def storea(self, when, args, client): txn = self.__curtxn.get(client) if txn is None: self.__skipped += 1 return meth = getattr(txn, 'storea', None) if meth is not None: meth(when, args, client) def tpc_finish(self, when, args, client): txn = self.__curtxn.get(client) if txn is None: self.__skipped += 1 return meth = getattr(txn, 'tpc_finish', None) if meth is not None: meth(when) if self.__report: self.report(txn) self.__txns.append(txn) self.__curtxn[client] = None self.__finishedtxns += 1 if self.__maxtxns > 0 and self.__finishedtxns >= self.__maxtxns: raise StopParsing def report(self, txn): """Print a report about the transaction""" if txn._objects: bytes = reduce(operator.add, [size for oid, size in txn._objects]) else: bytes = 0 print '%s %s %4d %10d %s %s' % ( txn._begintime, txn._finishtime - txn._begintime, len(txn._objects), bytes, time.ctime(txn._begintime), txn._url) def replay(self): for txn in self.__txns: txn.replay() # How many fell behind? slower = [] faster = [] for txn in self.__txns: if txn._replaydelta > 0: slower.append(txn) else: faster.append(txn) print len(slower), 'laggards,', len(faster), 'on-time or faster' # Find some averages if slower: sum = reduce(operator.add, [txn._replaydelta for txn in slower], 0) print 'average slower txn was:', float(sum) / len(slower) if faster: sum = reduce(operator.add, [txn._replaydelta for txn in faster], 0) print 'average faster txn was:', float(sum) / len(faster) def main(): try: opts, args = getopt.getopt( sys.argv[1:], 'hr:pm:', ['help', 'replay=', 'report', 'maxtxns=']) except getopt.error, e: usage(1, e) if args: usage(1) replay = 0 maxtxns = -1 report = 0 storagefile = None for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt in ('-r', '--replay'): replay = 1 storagefile = arg elif opt in ('-p', '--report'): report = 1 elif opt in ('-m', '--maxtxns'): try: maxtxns = int(arg) except ValueError: usage(1, 'Bad -m argument: %s' % arg) if replay: storage = FileStorage(storagefile) #storage = BDBFullStorage(storagefile) #storage = PrimaryStorage('yyz', storage, RS_PORT) t0 = now() p = ZEOParser(maxtxns, report, storage) i = 0 while 1: line = sys.stdin.readline() if not line: break i += 1 try: p.parse(line) except StopParsing: break except: print 'input file line:', i raise t1 = now() print 'total parse time:', t1-t0 t2 = now() if replay: p.replay() t3 = now() print 'total replay time:', t3-t2 print 'total time:', t3-t0 if __name__ == '__main__': main() zope2.13-2.13.21/source/ZODB3/src/ZEO/scripts/parsezeolog.py0000644000175000017500000000647612214017464022115 0ustar arnauarnau#!/usr/bin/env python2.3 """Parse the BLATHER logging generated by ZEO2. An example of the log format is: 2002-04-15T13:05:29 BLATHER(-100) ZEO Server storea(3235680, [714], 235339406490168806) ('10.0.26.30', 45514) """ import re import time rx_time = re.compile('(\d\d\d\d-\d\d-\d\d)T(\d\d:\d\d:\d\d)') def parse_time(line): """Return the time portion of a zLOG line in seconds or None.""" mo = rx_time.match(line) if mo is None: return None date, time_ = mo.group(1, 2) date_l = [int(elt) for elt in date.split('-')] time_l = [int(elt) for elt in time_.split(':')] return int(time.mktime(date_l + time_l + [0, 0, 0])) rx_meth = re.compile("zrpc:\d+ calling (\w+)\((.*)") def parse_method(line): pass def parse_line(line): """Parse a log entry and return time, method info, and client.""" t = parse_time(line) if t is None: return None, None mo = rx_meth.search(line) if mo is None: return None, None meth_name = mo.group(1) meth_args = mo.group(2).strip() if meth_args.endswith(')'): meth_args = meth_args[:-1] meth_args = [s.strip() for s in meth_args.split(",")] m = meth_name, tuple(meth_args) return t, m class TStats: counter = 1 def __init__(self): self.id = TStats.counter TStats.counter += 1 fields = ("time", "vote", "done", "user", "path") fmt = "%-24s %5s %5s %-15s %s" hdr = fmt % fields def report(self): """Print a report about the transaction""" t = time.ctime(self.begin) if hasattr(self, "vote"): d_vote = self.vote - self.begin else: d_vote = "*" if hasattr(self, "finish"): d_finish = self.finish - self.begin else: d_finish = "*" print self.fmt % (time.ctime(self.begin), d_vote, d_finish, self.user, self.url) class TransactionParser: def __init__(self): self.txns = {} self.skipped = 0 def parse(self, line): t, m = parse_line(line) if t is None: return name = m[0] meth = getattr(self, name, None) if meth is not None: meth(t, m[1]) def tpc_begin(self, time, args): t = TStats() t.begin = time t.user = args[1] t.url = args[2] t.objects = [] tid = eval(args[0]) self.txns[tid] = t def get_txn(self, args): tid = eval(args[0]) try: return self.txns[tid] except KeyError: print "uknown tid", repr(tid) return None def tpc_finish(self, time, args): t = self.get_txn(args) if t is None: return t.finish = time def vote(self, time, args): t = self.get_txn(args) if t is None: return t.vote = time def get_txns(self): L = [(t.id, t) for t in self.txns.values()] L.sort() return [t for (id, t) in L] if __name__ == "__main__": import fileinput p = TransactionParser() i = 0 for line in fileinput.input(): i += 1 try: p.parse(line) except: print "line", i raise print "Transaction: %d" % len(p.txns) print TStats.hdr for txn in p.get_txns(): txn.report() zope2.13-2.13.21/source/ZODB3/src/ZEO/ClientStorage.py0000644000175000017500000017601412214017464020633 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """The ClientStorage class and the exceptions that it may raise. Public contents of this module: ClientStorage -- the main class, implementing the Storage API """ from persistent.TimeStamp import TimeStamp from ZEO.auth import get_module from ZEO.cache import ClientCache from ZEO.Exceptions import ClientStorageError, ClientDisconnected, AuthError from ZEO import ServerStub from ZEO.TransactionBuffer import TransactionBuffer from ZEO.zrpc.client import ConnectionManager from ZODB import POSException from ZODB import utils import BTrees.IOBTree import cPickle import logging import os import re import socket import stat import sys import tempfile import thread import threading import time import types import weakref import zc.lockfile import ZEO.interfaces import ZODB import ZODB.BaseStorage import ZODB.interfaces import zope.event import zope.interface logger = logging.getLogger(__name__) try: from ZODB.ConflictResolution import ResolvedSerial except ImportError: ResolvedSerial = 'rs' def tid2time(tid): return str(TimeStamp(tid)) def get_timestamp(prev_ts=None): """Internal helper to return a unique TimeStamp instance. If the optional argument is not None, it must be a TimeStamp; the return value is then guaranteed to be at least 1 microsecond later the argument. """ t = time.time() t = TimeStamp(*time.gmtime(t)[:5] + (t % 60,)) if prev_ts is not None: t = t.laterThan(prev_ts) return t class DisconnectedServerStub: """Internal helper class used as a faux RPC stub when disconnected. This raises ClientDisconnected on all attribute accesses. This is a singleton class -- there should be only one instance, the global disconnected_stub, so it can be tested by identity. """ def __getattr__(self, attr): raise ClientDisconnected() # Singleton instance of DisconnectedServerStub disconnected_stub = DisconnectedServerStub() MB = 1024**2 class ClientStorage(object): """A storage class that is a network client to a remote storage. This is a faithful implementation of the Storage API. This class is thread-safe; transactions are serialized in tpc_begin(). """ # ClientStorage does not declare any interfaces here. Interfaces are # declared according to the server's storage once a connection is # established. # Classes we instantiate. A subclass might override. TransactionBufferClass = TransactionBuffer ClientCacheClass = ClientCache ConnectionManagerClass = ConnectionManager StorageServerStubClass = ServerStub.stub def __init__(self, addr, storage='1', cache_size=20 * MB, name='', client=None, var=None, min_disconnect_poll=1, max_disconnect_poll=30, wait_for_server_on_startup=None, # deprecated alias for wait wait=None, wait_timeout=None, read_only=0, read_only_fallback=0, drop_cache_rather_verify=False, username='', password='', realm=None, blob_dir=None, shared_blob_dir=False, blob_cache_size=None, blob_cache_size_check=10, client_label=None, ): """ClientStorage constructor. This is typically invoked from a custom_zodb.py file. All arguments except addr should be keyword arguments. Arguments: addr The server address(es). This is either a list of addresses or a single address. Each address can be a (hostname, port) tuple to signify a TCP/IP connection or a pathname string to signify a Unix domain socket connection. A hostname may be a DNS name or a dotted IP address. Required. storage The storage name, defaulting to '1'. The name must match one of the storage names supported by the server(s) specified by the addr argument. The storage name is displayed in the Zope control panel. cache_size The disk cache size, defaulting to 20 megabytes. This is passed to the ClientCache constructor. name The storage name, defaulting to ''. If this is false, str(addr) is used as the storage name. client A name used to construct persistent cache filenames. Defaults to None, in which case the cache is not persistent. See ClientCache for more info. var When client is not None, this specifies the directory where the persistent cache files are created. It defaults to None, in whichcase the current directory is used. min_disconnect_poll The minimum delay in seconds between attempts to connect to the server, in seconds. Defaults to 5 seconds. max_disconnect_poll The maximum delay in seconds between attempts to connect to the server, in seconds. Defaults to 300 seconds. wait_for_server_on_startup A backwards compatible alias for the wait argument. wait A flag indicating whether to wait until a connection with a server is made, defaulting to true. wait_timeout Maximum time to wait for a connection before giving up. Only meaningful if wait is True. read_only A flag indicating whether this should be a read-only storage, defaulting to false (i.e. writing is allowed by default). read_only_fallback A flag indicating whether a read-only remote storage should be acceptable as a fallback when no writable storages are available. Defaults to false. At most one of read_only and read_only_fallback should be true. username string with username to be used when authenticating. These only need to be provided if you are connecting to an authenticated server storage. password string with plaintext password to be used when authenticated. realm not documented. drop_cache_rather_verify a flag indicating that the cache should be dropped rather than expensively verified. blob_dir directory path for blob data. 'blob data' is data that is retrieved via the loadBlob API. shared_blob_dir Flag whether the blob_dir is a server-shared filesystem that should be used instead of transferring blob data over zrpc. blob_cache_size Maximum size of the ZEO blob cache, in bytes. If not set, then the cache size isn't checked and the blob directory will grow without bound. This option is ignored if shared_blob_dir is true. blob_cache_size_check ZEO check size as percent of blob_cache_size. The ZEO cache size will be checked when this many bytes have been loaded into the cache. Defaults to 10% of the blob cache size. This option is ignored if shared_blob_dir is true. client_label A label to include in server log messages for the client. Note that the authentication protocol is defined by the server and is detected by the ClientStorage upon connecting (see testConnection() and doAuth() for details). """ if isinstance(addr, int): addr = '127.0.0.1', addr self.__name__ = name or str(addr) # Standard convention for storages logger.info( "%s %s (pid=%d) created %s/%s for storage: %r", self.__name__, self.__class__.__name__, os.getpid(), read_only and "RO" or "RW", read_only_fallback and "fallback" or "normal", storage, ) self._drop_cache_rather_verify = drop_cache_rather_verify # wait defaults to True, but wait_for_server_on_startup overrides # if not None if wait_for_server_on_startup is not None: if wait is not None and wait != wait_for_server_on_startup: logger.warning( "%s ClientStorage(): conflicting values for wait and " "wait_for_server_on_startup; wait prevails", self.__name__) else: logger.info( "%s ClientStorage(): wait_for_server_on_startup " "is deprecated; please use wait instead", self.__name__) wait = wait_for_server_on_startup elif wait is None: wait = 1 self._addr = addr # For tests # A ZEO client can run in disconnected mode, using data from # its cache, or in connected mode. Several instance variables # are related to whether the client is connected. # _server: All method calls are invoked through the server # stub. When not connect, set to disconnected_stub an # object that raises ClientDisconnected errors. # _ready: A threading Event that is set only if _server # is set to a real stub. # _connection: The current zrpc connection or None. # _connection is set as soon as a connection is established, # but _server is set only after cache verification has finished # and clients can safely use the server. _pending_server holds # a server stub while it is being verified. self._server = disconnected_stub self._connection = None self._pending_server = None self._ready = threading.Event() # _is_read_only stores the constructor argument self._is_read_only = read_only self._storage = storage self._read_only_fallback = read_only_fallback self._username = username self._password = password self._realm = realm self._iterators = weakref.WeakValueDictionary() self._iterator_ids = set() # Flag tracking disconnections in the middle of a transaction. This # is reset in tpc_begin() and set in notifyDisconnected(). self._midtxn_disconnect = 0 # _server_addr is used by sortKey() self._server_addr = None self._client_label = client_label self._pickler = self._tfile = None self._info = {'length': 0, 'size': 0, 'name': 'ZEO Client', 'supportsUndo': 0, 'interfaces': ()} self._tbuf = self.TransactionBufferClass() self._db = None self._ltid = None # the last committed transaction # _serials: stores (oid, serialno) as returned by server # _seriald: _check_serials() moves from _serials to _seriald, # which maps oid to serialno # TODO: If serial number matches transaction id, then there is # no need to have all this extra infrastructure for handling # serial numbers. The vote call can just return the tid. # If there is a conflict error, we can't have a special method # called just to propagate the error. self._serials = [] self._seriald = {} # A ClientStorage only allows one thread to commit at a time. # Mutual exclusion is achieved using _tpc_cond, which # protects _transaction. A thread that wants to assign to # self._transaction must acquire _tpc_cond first. A thread # that decides it's done with a transaction (whether via success # or failure) must set _transaction to None and do # _tpc_cond.notify() before releasing _tpc_cond. self._tpc_cond = threading.Condition() self._transaction = None # Prevent multiple new_oid calls from going out. The _oids # variable should only be modified while holding the # _oid_lock. self._oid_lock = threading.Lock() self._oids = [] # Object ids retrieved from new_oids() # load() and tpc_finish() must be serialized to guarantee # that cache modifications from each occur atomically. # It also prevents multiple load calls occuring simultaneously, # which simplifies the cache logic. self._load_lock = threading.Lock() # _load_oid and _load_status are protected by _lock self._load_oid = None self._load_status = None # Can't read data in one thread while writing data # (tpc_finish) in another thread. In general, the lock # must prevent access to the cache while _update_cache # is executing. self._lock = threading.Lock() # XXX need to check for POSIX-ness here self.blob_dir = blob_dir self.shared_blob_dir = shared_blob_dir if blob_dir is not None: # Avoid doing this import unless we need it, as it # currently requires pywin32 on Windows. import ZODB.blob if shared_blob_dir: self.fshelper = ZODB.blob.FilesystemHelper(blob_dir) else: if 'zeocache' not in ZODB.blob.LAYOUTS: ZODB.blob.LAYOUTS['zeocache'] = BlobCacheLayout() self.fshelper = ZODB.blob.FilesystemHelper( blob_dir, layout_name='zeocache') self.fshelper.create() self.fshelper.checkSecure() else: self.fshelper = None if client is not None: dir = var or os.getcwd() cache_path = os.path.join(dir, "%s-%s.zec" % (client, storage)) else: cache_path = None self._cache = self.ClientCacheClass(cache_path, size=cache_size) self._blob_cache_size = blob_cache_size self._blob_data_bytes_loaded = 0 if blob_cache_size is not None: assert blob_cache_size_check < 100 self._blob_cache_size_check = ( blob_cache_size * blob_cache_size_check / 100) self._check_blob_size() self._rpc_mgr = self.ConnectionManagerClass(addr, self, tmin=min_disconnect_poll, tmax=max_disconnect_poll) if wait: self._wait(wait_timeout) else: # attempt_connect() will make an attempt that doesn't block # "too long," for a very vague notion of too long. If that # doesn't succeed, call connect() to start a thread. if not self._rpc_mgr.attempt_connect(): self._rpc_mgr.connect() def _wait(self, timeout=None): if timeout is not None: deadline = time.time() + timeout logger.debug("%s Setting deadline to %f", self.__name__, deadline) else: deadline = None # Wait for a connection to be established. self._rpc_mgr.connect(sync=1) # When a synchronous connect() call returns, there is # a valid _connection object but cache validation may # still be going on. This code must wait until validation # finishes, but if the connection isn't a zrpc async # connection it also needs to poll for input. while 1: self._ready.wait(30) if self._ready.isSet(): break if timeout and time.time() > deadline: logger.warning("%s Timed out waiting for connection", self.__name__) break logger.info("%s Waiting for cache verification to finish", self.__name__) def close(self): "Storage API: finalize the storage, releasing external resources." _rpc_mgr = self._rpc_mgr self._rpc_mgr = None if _rpc_mgr is None: return # already closed if self._connection is not None: self._connection.register_object(None) # Don't call me! self._connection = None _rpc_mgr.close() self._tbuf.close() if self._cache is not None: self._cache.close() self._cache = None if self._tfile is not None: self._tfile.close() if self._check_blob_size_thread is not None: self._check_blob_size_thread.join() _check_blob_size_thread = None def _check_blob_size(self, bytes=None): if self._blob_cache_size is None: return if self.shared_blob_dir or not self.blob_dir: return if (bytes is not None) and (bytes < self._blob_cache_size_check): return self._blob_data_bytes_loaded = 0 target = max(self._blob_cache_size - self._blob_cache_size_check, 0) check_blob_size_thread = threading.Thread( target=_check_blob_cache_size, args=(self.blob_dir, target), ) check_blob_size_thread.setDaemon(True) check_blob_size_thread.start() self._check_blob_size_thread = check_blob_size_thread def registerDB(self, db): """Storage API: register a database for invalidation messages. This is called by ZODB.DB (and by some tests). The storage isn't really ready to use until after this call. """ self._db = db def is_connected(self): """Return whether the storage is currently connected to a server.""" # This function is used by clients, so we only report that a # connection exists when the connection is ready to use. return self._ready.isSet() def sync(self): # The separate async thread should keep us up to date pass def doAuth(self, protocol, stub): if not (self._username and self._password): raise AuthError("empty username or password") module = get_module(protocol) if not module: logger.error("%s %s: no such an auth protocol: %s", self.__name__, self.__class__.__name__, protocol) return storage_class, client, db_class = module if not client: logger.error( "%s %s: %s isn't a valid protocol, must have a Client class", self.__name__, self.__class__.__name__, protocol) raise AuthError("invalid protocol") c = client(stub) # Initiate authentication, returns boolean specifying whether OK return c.start(self._username, self._realm, self._password) def testConnection(self, conn): """Internal: test the given connection. This returns: 1 if the connection is an optimal match, 0 if it is a suboptimal but acceptable match. It can also raise DisconnectedError or ReadOnlyError. This is called by ZEO.zrpc.ConnectionManager to decide which connection to use in case there are multiple, and some are read-only and others are read-write. This works by calling register() on the server. In read-only mode, register() is called with the read_only flag set. In writable mode and in read-only fallback mode, register() is called with the read_only flag cleared. In read-only fallback mode only, if the register() call raises ReadOnlyError, it is retried with the read-only flag set, and if this succeeds, this is deemed a suboptimal match. In all other cases, a succeeding register() call is deemed an optimal match, and any exception raised by register() is passed through. """ logger.info("%s Testing connection %r", self.__name__, conn) # TODO: Should we check the protocol version here? conn._is_read_only = self._is_read_only stub = self.StorageServerStubClass(conn) auth = stub.getAuthProtocol() logger.info("%s Server authentication protocol %r", self.__name__, auth) if auth: skey = self.doAuth(auth, stub) if skey: logger.info("%s Client authentication successful", self.__name__) conn.setSessionKey(skey) else: logger.info("%s Authentication failed", self.__name__) raise AuthError("Authentication failed") try: stub.register(str(self._storage), self._is_read_only) return 1 except POSException.ReadOnlyError: if not self._read_only_fallback: raise logger.info("%s Got ReadOnlyError; trying again with read_only=1", self.__name__) stub.register(str(self._storage), read_only=1) conn._is_read_only = True return 0 def notifyConnected(self, conn): """Internal: start using the given connection. This is called by ConnectionManager after it has decided which connection should be used. """ if self._cache is None: # the storage was closed, but the connect thread called # this method before it was stopped. return if self._connection is not None: # If we are upgrading from a read-only fallback connection, # we must close the old connection to prevent it from being # used while the cache is verified against the new connection. self._connection.register_object(None) # Don't call me! self._connection.close() self._connection = None self._ready.clear() reconnect = 1 else: reconnect = 0 self.set_server_addr(conn.get_addr()) self._connection = conn # invalidate our db cache if self._db is not None: self._db.invalidateCache() if reconnect: logger.info("%s Reconnected to storage: %s", self.__name__, self._server_addr) else: logger.info("%s Connected to storage: %s", self.__name__, self._server_addr) stub = self.StorageServerStubClass(conn) if self._client_label and conn.peer_protocol_version >= "Z310": stub.set_client_label(self._client_label) if conn.peer_protocol_version < "Z3101": logger.warning("Old server doesn't suppport " "checkCurrentSerialInTransaction") self.checkCurrentSerialInTransaction = lambda *args: None self._oids = [] self.verify_cache(stub) # It's important to call get_info after calling verify_cache. # If we end up doing a full-verification, we need to wait till # it's done. By doing a synchonous call, we are guarenteed # that the verification will be done because operations are # handled in order. self._info.update(stub.get_info()) self._handle_extensions() for iface in ( ZODB.interfaces.IStorageRestoreable, ZODB.interfaces.IStorageIteration, ZODB.interfaces.IStorageUndoable, ZODB.interfaces.IStorageCurrentRecordIteration, ZODB.interfaces.IBlobStorage, ZODB.interfaces.IExternalGC, ): if (iface.__module__, iface.__name__) in self._info.get( 'interfaces', ()): zope.interface.alsoProvides(self, iface) def _handle_extensions(self): for name in self.getExtensionMethods().keys(): if not hasattr(self, name): def mklambda(mname): return (lambda *args, **kw: self._server.rpc.call(mname, *args, **kw)) setattr(self, name, mklambda(name)) def set_server_addr(self, addr): # Normalize server address and convert to string if isinstance(addr, types.StringType): self._server_addr = addr else: assert isinstance(addr, types.TupleType) # If the server is on a remote host, we need to guarantee # that all clients used the same name for the server. If # they don't, the sortKey() may be different for each client. # The best solution seems to be the official name reported # by gethostbyaddr(). host = addr[0] try: canonical, aliases, addrs = socket.gethostbyaddr(host) except socket.error, err: logger.debug("%s Error resolving host: %s (%s)", self.__name__, host, err) canonical = host self._server_addr = str((canonical, addr[1])) def sortKey(self): # If the client isn't connected to anything, it can't have a # valid sortKey(). Raise an error to stop the transaction early. if self._server_addr is None: raise ClientDisconnected else: return '%s:%s' % (self._storage, self._server_addr) ### Is there a race condition between notifyConnected and ### notifyDisconnected? In Particular, what if we get ### notifyDisconnected in the middle of notifyConnected? ### The danger is that we'll proceed as if we were connected ### without worrying if we were, but this would happen any way if ### notifyDisconnected had to get the instance lock. There's ### nothing to gain by getting the instance lock. def notifyDisconnected(self): """Internal: notify that the server connection was terminated. This is called by ConnectionManager when the connection is closed or when certain problems with the connection occur. """ logger.info("%s Disconnected from storage: %r", self.__name__, self._server_addr) self._connection = None self._ready.clear() self._server = disconnected_stub self._midtxn_disconnect = 1 self._iterator_gc(True) def __len__(self): """Return the size of the storage.""" # TODO: Is this method used? return self._info['length'] def getName(self): """Storage API: return the storage name as a string. The return value consists of two parts: the name as determined by the name and addr argments to the ClientStorage constructor, and the string 'connected' or 'disconnected' in parentheses indicating whether the storage is (currently) connected. """ return "%s (%s)" % ( self.__name__, self.is_connected() and "connected" or "disconnected") def getSize(self): """Storage API: an approximate size of the database, in bytes.""" return self._info['size'] def getExtensionMethods(self): """getExtensionMethods This returns a dictionary whose keys are names of extra methods provided by this storage. Storage proxies (such as ZEO) should call this method to determine the extra methods that they need to proxy in addition to the standard storage methods. Dictionary values should be None; this will be a handy place for extra marshalling information, should we need it """ return self._info.get('extensionMethods', {}) def supportsUndo(self): """Storage API: return whether we support undo.""" return self._info['supportsUndo'] def isReadOnly(self): """Storage API: return whether we are in read-only mode.""" if self._is_read_only: return True else: # If the client is configured for a read-write connection # but has a read-only fallback connection, conn._is_read_only # will be True. If self._connection is None, we'll behave as # read_only try: return self._connection._is_read_only except AttributeError: return True def _check_trans(self, trans): """Internal helper to check a transaction argument for sanity.""" if self._is_read_only: raise POSException.ReadOnlyError() if self._transaction is not trans: raise POSException.StorageTransactionError(self._transaction, trans) def history(self, oid, size=1): """Storage API: return a sequence of HistoryEntry objects. """ return self._server.history(oid, size) def record_iternext(self, next=None): """Storage API: get the next database record. This is part of the conversion-support API. """ return self._server.record_iternext(next) def getTid(self, oid): """Storage API: return current serial number for oid.""" return self._server.getTid(oid) def loadSerial(self, oid, serial): """Storage API: load a historical revision of an object.""" return self._server.loadSerial(oid, serial) def load(self, oid, version=''): """Storage API: return the data for a given object. This returns the pickle data and serial number for the object specified by the given object id, if they exist; otherwise a KeyError is raised. """ self._lock.acquire() # for atomic processing of invalidations try: t = self._cache.load(oid) if t: return t finally: self._lock.release() if self._server is None: raise ClientDisconnected() self._load_lock.acquire() try: self._lock.acquire() try: self._load_oid = oid self._load_status = 1 finally: self._lock.release() data, tid = self._server.loadEx(oid) self._lock.acquire() # for atomic processing of invalidations try: if self._load_status: self._cache.store(oid, tid, None, data) self._load_oid = None finally: self._lock.release() finally: self._load_lock.release() return data, tid def loadBefore(self, oid, tid): self._lock.acquire() try: t = self._cache.loadBefore(oid, tid) if t is not None: return t finally: self._lock.release() t = self._server.loadBefore(oid, tid) if t is None: return None data, start, end = t if end is None: # This method should not be used to get current data. It # doesn't use the _load_lock, so it is possble to overlap # this load with an invalidation for the same object. # If we call again, we're guaranteed to get the # post-invalidation data. But if the data is still # current, we'll still get end == None. # Maybe the best thing to do is to re-run the test with # the load lock in the case. That's slow performance, but # I don't think real application code will ever care about # it. return data, start, end self._lock.acquire() try: self._cache.store(oid, start, end, data) finally: self._lock.release() return data, start, end def new_oid(self): """Storage API: return a new object identifier.""" if self._is_read_only: raise POSException.ReadOnlyError() # avoid multiple oid requests to server at the same time self._oid_lock.acquire() try: if not self._oids: self._oids = self._server.new_oids() self._oids.reverse() return self._oids.pop() finally: self._oid_lock.release() def pack(self, t=None, referencesf=None, wait=1, days=0): """Storage API: pack the storage. Deviations from the Storage API: the referencesf argument is ignored; two additional optional arguments wait and days are provided: wait -- a flag indicating whether to wait for the pack to complete; defaults to true. days -- a number of days to subtract from the pack time; defaults to zero. """ # TODO: Is it okay that read-only connections allow pack()? # rf argument ignored; server will provide its own implementation if t is None: t = time.time() t = t - (days * 86400) return self._server.pack(t, wait) def _check_serials(self): """Internal helper to move data from _serials to _seriald.""" # serials are always going to be the same, the only # question is whether an exception has been raised. if self._serials: l = len(self._serials) r = self._serials[:l] del self._serials[:l] for oid, s in r: if isinstance(s, Exception): self._cache.invalidate(oid, None) raise s self._seriald[oid] = s return r def store(self, oid, serial, data, version, txn): """Storage API: store data for an object.""" assert not version self._check_trans(txn) self._server.storea(oid, serial, data, id(txn)) self._tbuf.store(oid, data) return self._check_serials() def checkCurrentSerialInTransaction(self, oid, serial, transaction): self._check_trans(transaction) self._server.checkCurrentSerialInTransaction(oid, serial, id(transaction)) def storeBlob(self, oid, serial, data, blobfilename, version, txn): """Storage API: store a blob object.""" assert not version # Grab the file right away. That way, if we don't have enough # room for a copy, we'll know now rather than in tpc_finish. # Also, this releaves the client of having to manage the file # (or the directory contianing it). self.fshelper.getPathForOID(oid, create=True) fd, target = self.fshelper.blob_mkstemp(oid, serial) os.close(fd) # It's a bit odd (and impossible on windows) to rename over # an existing file. We'll use the temporary file name as a base. target += '-' ZODB.blob.rename_or_copy_blob(blobfilename, target) os.remove(target[:-1]) serials = self.store(oid, serial, data, '', txn) if self.shared_blob_dir: self._server.storeBlobShared( oid, serial, data, os.path.basename(target), id(txn)) else: self._server.storeBlob(oid, serial, data, target, txn) self._tbuf.storeBlob(oid, target) return serials def receiveBlobStart(self, oid, serial): blob_filename = self.fshelper.getBlobFilename(oid, serial) assert not os.path.exists(blob_filename) lockfilename = os.path.join(os.path.dirname(blob_filename), '.lock') assert os.path.exists(lockfilename) blob_filename += '.dl' assert not os.path.exists(blob_filename) f = open(blob_filename, 'wb') f.close() def receiveBlobChunk(self, oid, serial, chunk): blob_filename = self.fshelper.getBlobFilename(oid, serial)+'.dl' assert os.path.exists(blob_filename) f = open(blob_filename, 'r+b') f.seek(0, 2) f.write(chunk) f.close() self._blob_data_bytes_loaded += len(chunk) self._check_blob_size(self._blob_data_bytes_loaded) def receiveBlobStop(self, oid, serial): blob_filename = self.fshelper.getBlobFilename(oid, serial) os.rename(blob_filename+'.dl', blob_filename) os.chmod(blob_filename, stat.S_IREAD) def deleteObject(self, oid, serial, txn): self._check_trans(txn) self._server.deleteObject(oid, serial, id(txn)) self._tbuf.store(oid, None) def loadBlob(self, oid, serial): # Load a blob. If it isn't present and we have a shared blob # directory, then assume that it doesn't exist on the server # and return None. if self.fshelper is None: raise POSException.Unsupported("No blob cache directory is " "configured.") blob_filename = self.fshelper.getBlobFilename(oid, serial) if self.shared_blob_dir: if os.path.exists(blob_filename): return blob_filename else: # We're using a server shared cache. If the file isn't # here, it's not anywhere. raise POSException.POSKeyError("No blob file", oid, serial) if os.path.exists(blob_filename): return _accessed(blob_filename) # First, we'll create the directory for this oid, if it doesn't exist. self.fshelper.createPathForOID(oid) # OK, it's not here and we (or someone) needs to get it. We # want to avoid getting it multiple times. We want to avoid # getting it multiple times even accross separate client # processes on the same machine. We'll use file locking. lock = _lock_blob(blob_filename) try: # We got the lock, so it's our job to download it. First, # we'll double check that someone didn't download it while we # were getting the lock: if os.path.exists(blob_filename): return _accessed(blob_filename) # Ask the server to send it to us. When this function # returns, it will have been sent. (The recieving will # have been handled by the asyncore thread.) self._server.sendBlob(oid, serial) if os.path.exists(blob_filename): return _accessed(blob_filename) raise POSException.POSKeyError("No blob file", oid, serial) finally: lock.close() def openCommittedBlobFile(self, oid, serial, blob=None): blob_filename = self.loadBlob(oid, serial) try: if blob is None: return open(blob_filename, 'rb') else: return ZODB.blob.BlobFile(blob_filename, 'r', blob) except (IOError): # The file got removed while we were opening. # Fall through and try again with the protection of the lock. pass lock = _lock_blob(blob_filename) try: blob_filename = self.fshelper.getBlobFilename(oid, serial) if not os.path.exists(blob_filename): if self.shared_blob_dir: # We're using a server shared cache. If the file isn't # here, it's not anywhere. raise POSException.POSKeyError("No blob file", oid, serial) self._server.sendBlob(oid, serial) if not os.path.exists(blob_filename): raise POSException.POSKeyError("No blob file", oid, serial) _accessed(blob_filename) if blob is None: return open(blob_filename, 'rb') else: return ZODB.blob.BlobFile(blob_filename, 'r', blob) finally: lock.close() def temporaryDirectory(self): return self.fshelper.temp_dir def tpc_vote(self, txn): """Storage API: vote on a transaction.""" if txn is not self._transaction: raise POSException.StorageTransactionError( "tpc_vote called with wrong transaction") self._server.vote(id(txn)) return self._check_serials() def tpc_transaction(self): return self._transaction def tpc_begin(self, txn, tid=None, status=' '): """Storage API: begin a transaction.""" if self._is_read_only: raise POSException.ReadOnlyError() self._tpc_cond.acquire() self._midtxn_disconnect = 0 while self._transaction is not None: # It is allowable for a client to call two tpc_begins in a # row with the same transaction, and the second of these # must be ignored. if self._transaction == txn: self._tpc_cond.release() raise POSException.StorageTransactionError( "Duplicate tpc_begin calls for same transaction") self._tpc_cond.wait(30) self._transaction = txn self._tpc_cond.release() try: self._server.tpc_begin(id(txn), txn.user, txn.description, txn._extension, tid, status) except: # Client may have disconnected during the tpc_begin(). if self._server is not disconnected_stub: self.end_transaction() raise self._tbuf.clear() self._seriald.clear() del self._serials[:] def end_transaction(self): """Internal helper to end a transaction.""" # the right way to set self._transaction to None # calls notify() on _tpc_cond in case there are waiting threads self._tpc_cond.acquire() self._transaction = None self._tpc_cond.notify() self._tpc_cond.release() def lastTransaction(self): return self._cache.getLastTid() def tpc_abort(self, txn): """Storage API: abort a transaction.""" if txn is not self._transaction: return try: # Caution: Are there any exceptions that should prevent an # abort from occurring? It seems wrong to swallow them # all, yet you want to be sure that other abort logic is # executed regardless. try: self._server.tpc_abort(id(txn)) except ClientDisconnected: logger.debug("%s ClientDisconnected in tpc_abort() ignored", self.__name__) finally: self._tbuf.clear() self._seriald.clear() del self._serials[:] self._iterator_gc() self.end_transaction() def tpc_finish(self, txn, f=None): """Storage API: finish a transaction.""" if txn is not self._transaction: raise POSException.StorageTransactionError( "tpc_finish called with wrong transaction") self._load_lock.acquire() try: if self._midtxn_disconnect: raise ClientDisconnected( 'Calling tpc_finish() on a disconnected transaction') finished = 0 try: self._lock.acquire() # for atomic processing of invalidations try: tid = self._server.tpc_finish(id(txn)) finished = 1 self._update_cache(tid) if f is not None: f(tid) finally: self._lock.release() r = self._check_serials() assert r is None or len(r) == 0, "unhandled serialnos: %s" % r except: if finished: # The server successfully committed. If we get a failure # here, our own state will be in question, so reconnect. self._connection.close() raise self.end_transaction() finally: self._load_lock.release() self._iterator_gc() def _update_cache(self, tid): """Internal helper to handle objects modified by a transaction. This iterates over the objects in the transaction buffer and update or invalidate the cache. """ # Must be called with _lock already acquired. # Not sure why _update_cache() would be called on a closed storage. if self._cache is None: return for oid, _ in self._seriald.iteritems(): self._cache.invalidate(oid, tid) for oid, data in self._tbuf: # If data is None, we just invalidate. if data is not None: s = self._seriald[oid] if s != ResolvedSerial: assert s == tid, (s, tid) self._cache.store(oid, s, None, data) else: # object deletion self._cache.invalidate(oid, tid) if self.fshelper is not None: blobs = self._tbuf.blobs had_blobs = False while blobs: oid, blobfilename = blobs.pop() self._blob_data_bytes_loaded += os.stat(blobfilename).st_size targetpath = self.fshelper.getPathForOID(oid, create=True) target_blob_file_name = self.fshelper.getBlobFilename(oid, tid) lock = _lock_blob(target_blob_file_name) try: ZODB.blob.rename_or_copy_blob( blobfilename, target_blob_file_name, ) finally: lock.close() had_blobs = True if had_blobs: self._check_blob_size(self._blob_data_bytes_loaded) self._cache.setLastTid(tid) self._tbuf.clear() def undo(self, trans_id, txn): """Storage API: undo a transaction. This is executed in a transactional context. It has no effect until the transaction is committed. It can be undone itself. Zope uses this to implement undo unless it is not supported by a storage. """ self._check_trans(txn) self._server.undoa(trans_id, id(txn)) def undoInfo(self, first=0, last=-20, specification=None): """Storage API: return undo information.""" return self._server.undoInfo(first, last, specification) def undoLog(self, first=0, last=-20, filter=None): """Storage API: return a sequence of TransactionDescription objects. The filter argument should be None or left unspecified, since it is impossible to pass the filter function to the server to be executed there. If filter is not None, an empty sequence is returned. """ if filter is not None: return [] return self._server.undoLog(first, last) # Recovery support def copyTransactionsFrom(self, other, verbose=0): """Copy transactions from another storage. This is typically used for converting data from one storage to another. `other` must have an .iterator() method. """ ZODB.BaseStorage.copy(other, self, verbose) def restore(self, oid, serial, data, version, prev_txn, transaction): """Write data already committed in a separate database.""" assert not version self._check_trans(transaction) self._server.restorea(oid, serial, data, prev_txn, id(transaction)) # Don't update the transaction buffer, because current data are # unaffected. return self._check_serials() # Below are methods invoked by the StorageServer def serialnos(self, args): """Server callback to pass a list of changed (oid, serial) pairs.""" self._serials.extend(args) def info(self, dict): """Server callback to update the info dictionary.""" self._info.update(dict) def verify_cache(self, server): """Internal routine called to verify the cache. The return value (indicating which path we took) is used by the test suite. """ self._pending_server = server # setup tempfile to hold zeoVerify results and interim # invalidation results self._tfile = tempfile.TemporaryFile(suffix=".inv") self._pickler = cPickle.Pickler(self._tfile, 1) self._pickler.fast = 1 # Don't use the memo if self._connection.peer_protocol_version < 'Z309': client = ClientStorage308Adapter(self) else: client = self # allow incoming invalidations: self._connection.register_object(client) # If verify_cache() finishes the cache verification process, # it should set self._server. If it goes through full cache # verification, then endVerify() should self._server. server_tid = server.lastTransaction() if not self._cache: logger.info("%s No verification necessary -- empty cache", self.__name__) if server_tid != utils.z64: self._cache.setLastTid(server_tid) self.finish_verification() return "empty cache" cache_tid = self._cache.getLastTid() if cache_tid != utils.z64: if server_tid == cache_tid: logger.info( "%s No verification necessary" " (cache_tid up-to-date %r)", self.__name__, server_tid) self.finish_verification() return "no verification" elif server_tid < cache_tid: message = ("%s Client has seen newer transactions than server!" % self.__name__) logger.critical(message) raise ClientStorageError(message) # log some hints about last transaction logger.info("%s last inval tid: %r %s\n", self.__name__, cache_tid, tid2time(cache_tid)) logger.info("%s last transaction: %r %s", self.__name__, server_tid, server_tid and tid2time(server_tid)) pair = server.getInvalidations(cache_tid) if pair is not None: logger.info("%s Recovering %d invalidations", self.__name__, len(pair[1])) self.finish_verification(pair) return "quick verification" elif server_tid != utils.z64: # Hm, to have gotten here, the cache is non-empty, but # it has no last tid. This doesn't seem like good situation. # We'll have to verify the cache, if we're willing. self._cache.setLastTid(server_tid) zope.event.notify(ZEO.interfaces.StaleCache(self)) # From this point on, we do not have complete information about # the missed transactions. The reason is that cache # verification only checks objects in the client cache and # there may be objects in the object caches that aren't in the # client cach that would need verification too. We avoid that # problem by just invalidating the objects in the object caches. if self._db is not None: self._db.invalidateCache() if self._cache and self._drop_cache_rather_verify: logger.critical("%s dropping stale cache", self.__name__) self._cache.clear() if server_tid: self._cache.setLastTid(server_tid) self.finish_verification() return "cache dropped" logger.info("%s Verifying cache", self.__name__) for oid, tid in self._cache.contents(): server.verify(oid, tid) server.endZeoVerify() return "full verification" def invalidateVerify(self, oid): """Server callback to invalidate an oid pair. This is called as part of cache validation. """ # Invalidation as result of verify_cache(). # Queue an invalidate for the end the verification procedure. if self._pickler is None: # This should never happen. logger.error("%s invalidateVerify with no _pickler", self.__name__) return self._pickler.dump((None, [oid])) def endVerify(self): """Server callback to signal end of cache validation.""" logger.info("%s endVerify finishing", self.__name__) self.finish_verification() logger.info("%s endVerify finished", self.__name__) def finish_verification(self, catch_up=None): self._lock.acquire() try: if catch_up: # process catch-up invalidations self._process_invalidations(*catch_up) if self._pickler is None: return # write end-of-data marker self._pickler.dump((None, None)) self._pickler = None self._tfile.seek(0) unpickler = cPickle.Unpickler(self._tfile) min_tid = self._cache.getLastTid() while 1: tid, oids = unpickler.load() logger.debug('pickled inval %r %r', tid, min_tid) if oids is None: break if ((tid is None) or (min_tid is None) or (tid > min_tid) ): self._process_invalidations(tid, oids) self._tfile.close() self._tfile = None finally: self._lock.release() self._server = self._pending_server self._ready.set() self._pending_server = None def invalidateTransaction(self, tid, oids): """Server callback: Invalidate objects modified by tid.""" self._lock.acquire() try: if self._pickler is not None: logger.debug( "%s Transactional invalidation during cache verification", self.__name__) self._pickler.dump((tid, oids)) else: self._process_invalidations(tid, oids) finally: self._lock.release() def _process_invalidations(self, tid, oids): for oid in oids: if oid == self._load_oid: self._load_status = 0 self._cache.invalidate(oid, tid) self._cache.setLastTid(tid) if self._db is not None: self._db.invalidate(tid, oids) # The following are for compatibility with protocol version 2.0.0 def invalidateTrans(self, oids): return self.invalidateTransaction(None, oids) invalidate = invalidateVerify end = endVerify Invalidate = invalidateTrans # IStorageIteration def iterator(self, start=None, stop=None): """Return an IStorageTransactionInformation iterator.""" # iids are "iterator IDs" that can be used to query an iterator whose # status is held on the server. iid = self._server.iterator_start(start, stop) return self._setup_iterator(TransactionIterator, iid) def _setup_iterator(self, factory, iid, *args): self._iterators[iid] = iterator = factory(self, iid, *args) self._iterator_ids.add(iid) return iterator def _forget_iterator(self, iid): self._iterators.pop(iid, None) self._iterator_ids.remove(iid) def _iterator_gc(self, disconnected=False): if not self._iterator_ids: return if disconnected: for i in self._iterators.values(): i._iid = -1 self._iterators.clear() self._iterator_ids.clear() return iids = self._iterator_ids - set(self._iterators) if iids: try: self._server.iterator_gc(list(iids)) except ClientDisconnected: # If we get disconnected, all of the iterators on the # server are thrown away. We should clear ours too: return self._iterator_gc(True) self._iterator_ids -= iids def server_status(self): return self._server.server_status() class TransactionIterator(object): def __init__(self, storage, iid, *args): self._storage = storage self._iid = iid self._ended = False def __iter__(self): return self def next(self): if self._ended: raise StopIteration() if self._iid < 0: raise ClientDisconnected("Disconnected iterator") tx_data = self._storage._server.iterator_next(self._iid) if tx_data is None: # The iterator is exhausted, and the server has already # disposed it. self._ended = True self._storage._forget_iterator(self._iid) raise StopIteration() return ClientStorageTransactionInformation( self._storage, self, *tx_data) class ClientStorageTransactionInformation(ZODB.BaseStorage.TransactionRecord): def __init__(self, storage, txiter, tid, status, user, description, extension): self._storage = storage self._txiter = txiter self._completed = False self._riid = None self.tid = tid self.status = status self.user = user self.description = description self.extension = extension def __iter__(self): riid = self._storage._server.iterator_record_start(self._txiter._iid, self.tid) return self._storage._setup_iterator(RecordIterator, riid) class RecordIterator(object): def __init__(self, storage, riid): self._riid = riid self._completed = False self._storage = storage def __iter__(self): return self def next(self): if self._completed: # We finished iteration once already and the server can't know # about the iteration anymore. raise StopIteration() item = self._storage._server.iterator_record_next(self._riid) if item is None: # The iterator is exhausted, and the server has already # disposed it. self._completed = True raise StopIteration() return ZODB.BaseStorage.DataRecord(*item) class ClientStorage308Adapter: def __init__(self, client): self.client = client def invalidateTransaction(self, tid, args): self.client.invalidateTransaction(tid, [arg[0] for arg in args]) def invalidateVerify(self, arg): self.client.invalidateVerify(arg[0]) def __getattr__(self, name): return getattr(self.client, name) class BlobCacheLayout(object): size = 997 def oid_to_path(self, oid): return str(utils.u64(oid) % self.size) def getBlobFilePath(self, oid, tid): base, rem = divmod(utils.u64(oid), self.size) return os.path.join( str(rem), "%s.%s%s" % (base, tid.encode('hex'), ZODB.blob.BLOB_SUFFIX) ) def _accessed(filename): try: os.utime(filename, (time.time(), os.stat(filename).st_mtime)) except OSError: pass # We tried. :) return filename cache_file_name = re.compile(r'\d+$').match def _check_blob_cache_size(blob_dir, target): logger = logging.getLogger(__name__+'.check_blob_cache') layout = open(os.path.join(blob_dir, ZODB.blob.LAYOUT_MARKER) ).read().strip() if not layout == 'zeocache': logger.critical("Invalid blob directory layout %s", layout) raise ValueError("Invalid blob directory layout", layout) attempt_path = os.path.join(blob_dir, 'check_size.attempt') try: check_lock = zc.lockfile.LockFile( os.path.join(blob_dir, 'check_size.lock')) except zc.lockfile.LockError: try: time.sleep(1) check_lock = zc.lockfile.LockFile( os.path.join(blob_dir, 'check_size.lock')) except zc.lockfile.LockError: # Someone is already cleaning up, so don't bother logger.debug("%s Another thread is checking the blob cache size.", thread.get_ident()) open(attempt_path, 'w').close() # Mark that we tried return logger.debug("%s Checking blob cache size. (target: %s)", thread.get_ident(), target) try: while 1: size = 0 blob_suffix = ZODB.blob.BLOB_SUFFIX files_by_atime = BTrees.OOBTree.BTree() for dirname in os.listdir(blob_dir): if not cache_file_name(dirname): continue base = os.path.join(blob_dir, dirname) if not os.path.isdir(base): continue for file_name in os.listdir(base): if not file_name.endswith(blob_suffix): continue file_path = os.path.join(base, file_name) if not os.path.isfile(file_path): continue stat = os.stat(file_path) size += stat.st_size t = stat.st_atime if t not in files_by_atime: files_by_atime[t] = [] files_by_atime[t].append(os.path.join(dirname, file_name)) logger.debug("%s blob cache size: %s", thread.get_ident(), size) if size <= target: if os.path.isfile(attempt_path): try: os.remove(attempt_path) except OSError: pass # Sigh, windows continue logger.debug("%s -->", thread.get_ident()) break while size > target and files_by_atime: for file_name in files_by_atime.pop(files_by_atime.minKey()): file_name = os.path.join(blob_dir, file_name) lockfilename = os.path.join(os.path.dirname(file_name), '.lock') try: lock = zc.lockfile.LockFile(lockfilename) except zc.lockfile.LockError: logger.debug("%s Skipping locked %s", thread.get_ident(), os.path.basename(file_name)) continue # In use, skip try: fsize = os.stat(file_name).st_size try: ZODB.blob.remove_committed(file_name) except OSError, v: pass # probably open on windows else: size -= fsize finally: lock.close() if size <= target: break logger.debug("%s reduced blob cache size: %s", thread.get_ident(), size) finally: check_lock.close() def check_blob_size_script(args=None): if args is None: args = sys.argv[1:] blob_dir, target = args _check_blob_cache_size(blob_dir, int(target)) def _lock_blob(path): lockfilename = os.path.join(os.path.dirname(path), '.lock') n = 0 while 1: try: return zc.lockfile.LockFile(lockfilename) except zc.lockfile.LockError: time.sleep(0.01) n += 1 if n > 60000: raise else: break zope2.13-2.13.21/source/ZODB3/src/ZEO/component.xml0000644000175000017500000001150712214017464020235 0ustar arnauarnau The content of a ZEO section describe operational parameters of a ZEO server except for the storage(s) to be served. The address at which the server should listen. This can be in the form 'host:port' to signify a TCP/IP connection or a pathname string to signify a Unix domain socket connection (at least one '/' is required). A hostname may be a DNS name or a dotted IP address. If the hostname is omitted, the platform's default behavior is used when binding the listening socket ('' is passed to socket.bind() as the hostname portion of the address). Flag indicating whether the server should operate in read-only mode. Defaults to false. Note that even if the server is operating in writable mode, individual storages may still be read-only. But if the server is in read-only mode, no write operations are allowed, even if the storages are writable. Note that pack() is considered a read-only operation. The storage server keeps a queue of the objects modified by the last N transactions, where N == invalidation_queue_size. This queue is used to speed client cache verification when a client disconnects for a short period of time. The maximum age of a client for which quick-verification invalidations will be provided by iterating over the served storage. This option should only be used if the served storage supports efficient iteration from a starting point near the end of the transaction history (e.g. end of file). The address at which the monitor server should listen. If specified, a monitor server is started. The monitor server provides server statistics in a simple text format. This can be in the form 'host:port' to signify a TCP/IP connection or a pathname string to signify a Unix domain socket connection (at least one '/' is required). A hostname may be a DNS name or a dotted IP address. If the hostname is omitted, the platform's default behavior is used when binding the listening socket ('' is passed to socket.bind() as the hostname portion of the address). The maximum amount of time to wait for a transaction to commit after acquiring the storage lock, specified in seconds. If the transaction takes too long, the client connection will be closed and the transaction aborted. The name of the protocol used for authentication. The only protocol provided with ZEO is "digest," but extensions may provide other protocols. The path of the database containing authentication credentials. The authentication realm of the server. Some authentication schemes use a realm to identify the logical set of usernames that are accepted by this server. The full path to the file in which to write the ZEO server's Process ID at startup. If omitted, $INSTANCE/var/ZEO.pid is used. $INSTANCE/var/ZEO.pid (or $clienthome/ZEO.pid) indicates that the cache should be dropped rather than verified when the verification optimization is not available (e.g. when the ZEO server restarted). zope2.13-2.13.21/source/ZODB3/src/ZEO/zrpc/0000755000175000017500000000000012214017464016463 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZEO/zrpc/marshal.py0000644000175000017500000000716112214017464020471 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from cPickle import Unpickler, Pickler from cStringIO import StringIO import logging from ZEO.zrpc.error import ZRPCError from ZEO.zrpc.log import log, short_repr def encode(*args): # args: (msgid, flags, name, args) # (We used to have a global pickler, but that's not thread-safe. :-( ) # It's not thread safe if, in the couse of pickling, we call the # Python interpeter, which releases the GIL. # Note that args may contain very large binary pickles already; for # this reason, it's important to use proto 1 (or higher) pickles here # too. For a long time, this used proto 0 pickles, and that can # bloat our pickle to 4x the size (due to high-bit and control bytes # being represented by \xij escapes in proto 0). # Undocumented: cPickle.Pickler accepts a lone protocol argument; # pickle.py does not. pickler = Pickler(1) pickler.fast = 1 return pickler.dump(args, 1) @apply def fast_encode(): # Only use in cases where you *know* the data contains only basic # Python objects pickler = Pickler(1) pickler.fast = 1 dump = pickler.dump def fast_encode(*args): return dump(args, 1) return fast_encode def decode(msg): """Decodes msg and returns its parts""" unpickler = Unpickler(StringIO(msg)) unpickler.find_global = find_global try: return unpickler.load() # msgid, flags, name, args except: log("can't decode message: %s" % short_repr(msg), level=logging.ERROR) raise def server_decode(msg): """Decodes msg and returns its parts""" unpickler = Unpickler(StringIO(msg)) unpickler.find_global = server_find_global try: return unpickler.load() # msgid, flags, name, args except: log("can't decode message: %s" % short_repr(msg), level=logging.ERROR) raise _globals = globals() _silly = ('__doc__',) exception_type_type = type(Exception) def find_global(module, name): """Helper for message unpickler""" try: m = __import__(module, _globals, _globals, _silly) except ImportError, msg: raise ZRPCError("import error %s: %s" % (module, msg)) try: r = getattr(m, name) except AttributeError: raise ZRPCError("module %s has no global %s" % (module, name)) safe = getattr(r, '__no_side_effects__', 0) if safe: return r # TODO: is there a better way to do this? if type(r) == exception_type_type and issubclass(r, Exception): return r raise ZRPCError("Unsafe global: %s.%s" % (module, name)) def server_find_global(module, name): """Helper for message unpickler""" try: if module != 'ZopeUndo.Prefix': raise ImportError m = __import__(module, _globals, _globals, _silly) except ImportError, msg: raise ZRPCError("import error %s: %s" % (module, msg)) try: r = getattr(m, name) except AttributeError: raise ZRPCError("module %s has no global %s" % (module, name)) return r zope2.13-2.13.21/source/ZODB3/src/ZEO/zrpc/error.py0000644000175000017500000000207212214017464020167 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from ZODB import POSException from ZEO.Exceptions import ClientDisconnected class ZRPCError(POSException.StorageError): pass class DisconnectedError(ZRPCError, ClientDisconnected): """The database storage is disconnected from the storage server. The error occurred because a problem in the low-level RPC connection, or because the connection was closed. """ # This subclass is raised when zrpc catches the error. zope2.13-2.13.21/source/ZODB3/src/ZEO/zrpc/trigger.py0000644000175000017500000002142612214017464020505 0ustar arnauarnau############################################################################## # # Copyright (c) 2001-2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from __future__ import with_statement import asyncore import os import socket import thread import errno from ZODB.utils import positive_id # Original comments follow; they're hard to follow in the context of # ZEO's use of triggers. TODO: rewrite from a ZEO perspective. # Wake up a call to select() running in the main thread. # # This is useful in a context where you are using Medusa's I/O # subsystem to deliver data, but the data is generated by another # thread. Normally, if Medusa is in the middle of a call to # select(), new output data generated by another thread will have # to sit until the call to select() either times out or returns. # If the trigger is 'pulled' by another thread, it should immediately # generate a READ event on the trigger object, which will force the # select() invocation to return. # # A common use for this facility: letting Medusa manage I/O for a # large number of connections; but routing each request through a # thread chosen from a fixed-size thread pool. When a thread is # acquired, a transaction is performed, but output data is # accumulated into buffers that will be emptied more efficiently # by Medusa. [picture a server that can process database queries # rapidly, but doesn't want to tie up threads waiting to send data # to low-bandwidth connections] # # The other major feature provided by this class is the ability to # move work back into the main thread: if you call pull_trigger() # with a thunk argument, when select() wakes up and receives the # event it will call your thunk from within that thread. The main # purpose of this is to remove the need to wrap thread locks around # Medusa's data structures, which normally do not need them. [To see # why this is true, imagine this scenario: A thread tries to push some # new data onto a channel's outgoing data queue at the same time that # the main thread is trying to remove some] class _triggerbase(object): """OS-independent base class for OS-dependent trigger class.""" kind = None # subclass must set to "pipe" or "loopback"; used by repr def __init__(self): self._closed = False # `lock` protects the `thunks` list from being traversed and # appended to simultaneously. self.lock = thread.allocate_lock() # List of no-argument callbacks to invoke when the trigger is # pulled. These run in the thread running the asyncore mainloop, # regardless of which thread pulls the trigger. self.thunks = [] def readable(self): return 1 def writable(self): return 0 def handle_connect(self): pass def handle_close(self): self.close() # Override the asyncore close() method, because it doesn't know about # (so can't close) all the gimmicks we have open. Subclass must # supply a _close() method to do platform-specific closing work. _close() # will be called iff we're not already closed. def close(self): if not self._closed: self._closed = True self.del_channel() self._close() # subclass does OS-specific stuff def _close(self): # see close() above; subclass must supply raise NotImplementedError def pull_trigger(self, *thunk): if thunk: with self.lock: self.thunks.append(thunk) try: self._physical_pull() except Exception: if not self._closed: raise # Subclass must supply _physical_pull, which does whatever the OS # needs to do to provoke the "write" end of the trigger. def _physical_pull(self): raise NotImplementedError def handle_read(self): try: self.recv(8192) except socket.error: return while 1: with self.lock: if self.thunks: thunk = self.thunks.pop(0) else: return try: thunk[0](*thunk[1:]) except: nil, t, v, tbinfo = asyncore.compact_traceback() print ('exception in trigger thunk:' ' (%s:%s %s)' % (t, v, tbinfo)) def __repr__(self): return '' % (self.kind, positive_id(self)) if os.name == 'posix': class trigger(_triggerbase, asyncore.file_dispatcher): kind = "pipe" def __init__(self, map=None): _triggerbase.__init__(self) r, self.trigger = os.pipe() asyncore.file_dispatcher.__init__(self, r, map) if self.fd != r: # Starting in Python 2.6, the descriptor passed to # file_dispatcher gets duped and assigned to # self.fd. This breals the instantiation semantics and # is a bug imo. I dount it will get fixed, but maybe # it will. Who knows. For that reason, we test for the # fd changing rather than just checking the Python version. os.close(r) def _close(self): os.close(self.trigger) asyncore.file_dispatcher.close(self) def _physical_pull(self): os.write(self.trigger, 'x') else: # Windows version; uses just sockets, because a pipe isn't select'able # on Windows. class BindError(Exception): pass class trigger(_triggerbase, asyncore.dispatcher): kind = "loopback" def __init__(self, map=None): _triggerbase.__init__(self) # Get a pair of connected sockets. The trigger is the 'w' # end of the pair, which is connected to 'r'. 'r' is put # in the asyncore socket map. "pulling the trigger" then # means writing something on w, which will wake up r. w = socket.socket() # Disable buffering -- pulling the trigger sends 1 byte, # and we want that sent immediately, to wake up asyncore's # select() ASAP. w.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) count = 0 while 1: count += 1 # Bind to a local port; for efficiency, let the OS pick # a free port for us. # Unfortunately, stress tests showed that we may not # be able to connect to that port ("Address already in # use") despite that the OS picked it. This appears # to be a race bug in the Windows socket implementation. # So we loop until a connect() succeeds (almost always # on the first try). See the long thread at # http://mail.zope.org/pipermail/zope/2005-July/160433.html # for hideous details. a = socket.socket() a.bind(("127.0.0.1", 0)) connect_address = a.getsockname() # assigned (host, port) pair a.listen(1) try: w.connect(connect_address) break # success except socket.error, detail: if detail[0] != errno.WSAEADDRINUSE: # "Address already in use" is the only error # I've seen on two WinXP Pro SP2 boxes, under # Pythons 2.3.5 and 2.4.1. raise # (10048, 'Address already in use') # assert count <= 2 # never triggered in Tim's tests if count >= 10: # I've never seen it go above 2 a.close() w.close() raise BindError("Cannot bind trigger!") # Close `a` and try again. Note: I originally put a short # sleep() here, but it didn't appear to help or hurt. a.close() r, addr = a.accept() # r becomes asyncore's (self.)socket a.close() self.trigger = w asyncore.dispatcher.__init__(self, r, map) def _close(self): # self.socket is r, and self.trigger is w, from __init__ self.socket.close() self.trigger.close() def _physical_pull(self): self.trigger.send('x') zope2.13-2.13.21/source/ZODB3/src/ZEO/zrpc/__init__.py0000644000175000017500000000211612214017464020574 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## # zrpc is a package with the following modules # client -- manages connection creation to remote server # connection -- object dispatcher # log -- logging helper # error -- exceptions raised by zrpc # marshal -- internal, handles basic protocol issues # server -- manages incoming connections from remote clients # smac -- sized message async connections # trigger -- medusa's trigger # zrpc is not an advertised subpackage of ZEO; its interfaces are internal zope2.13-2.13.21/source/ZODB3/src/ZEO/zrpc/client.py0000644000175000017500000005571012214017464020323 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import asyncore import errno import logging import select import socket import sys import threading import time import types import ZEO.zrpc.trigger from ZEO.zrpc.connection import ManagedClientConnection from ZEO.zrpc.log import log from ZEO.zrpc.error import DisconnectedError from ZODB.POSException import ReadOnlyError from ZODB.loglevels import BLATHER def client_timeout(): return 30.0 def client_loop(map): read = asyncore.read write = asyncore.write _exception = asyncore._exception while map: try: # The next two lines intentionally don't use # iterators. Other threads can close dispatchers, causeing # the socket map to shrink. r = e = map.keys() w = [fd for (fd, obj) in map.items() if obj.writable()] try: r, w, e = select.select(r, w, e, client_timeout()) except select.error, err: if err[0] != errno.EINTR: if err[0] == errno.EBADF: # If a connection is closed while we are # calling select on it, we can get a bad # file-descriptor error. We'll check for this # case by looking for entries in r and w that # are not in the socket map. if [fd for fd in r if fd not in map]: continue if [fd for fd in w if fd not in map]: continue raise else: continue if not map: break if not (r or w or e): # The line intentionally doesn't use iterators. Other # threads can close dispatchers, causeing the socket # map to shrink. for obj in map.values(): if isinstance(obj, ManagedClientConnection): # Send a heartbeat message as a reply to a # non-existent message id. try: obj.send_reply(-1, None) except DisconnectedError: pass continue for fd in r: obj = map.get(fd) if obj is None: continue read(obj) for fd in w: obj = map.get(fd) if obj is None: continue write(obj) for fd in e: obj = map.get(fd) if obj is None: continue _exception(obj) except: if map: try: logging.getLogger(__name__+'.client_loop').critical( 'A ZEO client loop failed.', exc_info=sys.exc_info()) except: pass for fd, obj in map.items(): if not hasattr(obj, 'mgr'): continue try: obj.mgr.client.close() except: map.pop(fd, None) try: logging.getLogger(__name__+'.client_loop' ).critical( "Couldn't close a dispatcher.", exc_info=sys.exc_info()) except: pass class ConnectionManager(object): """Keeps a connection up over time""" sync_wait = 30 def __init__(self, addrs, client, tmin=1, tmax=180): self.client = client self._start_asyncore_loop() self.addrlist = self._parse_addrs(addrs) self.tmin = min(tmin, tmax) self.tmax = tmax self.cond = threading.Condition(threading.Lock()) self.connection = None # Protected by self.cond self.closed = 0 # If thread is not None, then there is a helper thread # attempting to connect. self.thread = None # Protected by self.cond def _start_asyncore_loop(self): self.map = {} self.trigger = ZEO.zrpc.trigger.trigger(self.map) self.loop_thread = threading.Thread( name="%s zeo client networking thread" % self.client.__name__, target=client_loop, args=(self.map,)) self.loop_thread.setDaemon(True) self.loop_thread.start() def __repr__(self): return "<%s for %s>" % (self.__class__.__name__, self.addrlist) def _parse_addrs(self, addrs): # Return a list of (addr_type, addr) pairs. # For backwards compatibility (and simplicity?) the # constructor accepts a single address in the addrs argument -- # a string for a Unix domain socket or a 2-tuple with a # hostname and port. It can also accept a list of such addresses. addr_type = self._guess_type(addrs) if addr_type is not None: return [(addr_type, addrs)] else: addrlist = [] for addr in addrs: addr_type = self._guess_type(addr) if addr_type is None: raise ValueError("unknown address in list: %s" % repr(addr)) addrlist.append((addr_type, addr)) return addrlist def _guess_type(self, addr): if isinstance(addr, types.StringType): return socket.AF_UNIX if (len(addr) == 2 and isinstance(addr[0], types.StringType) and isinstance(addr[1], types.IntType)): return socket.AF_INET # also denotes IPv6 # not anything I know about return None def close(self): """Prevent ConnectionManager from opening new connections""" self.closed = 1 self.cond.acquire() try: t = self.thread self.thread = None finally: self.cond.release() if t is not None: log("CM.close(): stopping and joining thread") t.stop() t.join(30) if t.isAlive(): log("CM.close(): self.thread.join() timed out", level=logging.WARNING) for fd, obj in self.map.items(): if obj is not self.trigger: try: obj.close() except: logging.getLogger(__name__+'.'+self.__class__.__name__ ).critical( "Couldn't close a dispatcher.", exc_info=sys.exc_info()) self.map.clear() self.trigger.pull_trigger() try: self.loop_thread.join(9) except RuntimeError: pass # we are the thread :) self.trigger.close() def attempt_connect(self): """Attempt a connection to the server without blocking too long. There isn't a crisp definition for too long. When a ClientStorage is created, it attempts to connect to the server. If the server isn't immediately available, it can operate from the cache. This method will start the background connection thread and wait a little while to see if it finishes quickly. """ # Will a single attempt take too long? # Answer: it depends -- normally, you'll connect or get a # connection refused error very quickly. Packet-eating # firewalls and other mishaps may cause the connect to take a # long time to time out though. It's also possible that you # connect quickly to a slow server, and the attempt includes # at least one roundtrip to the server (the register() call). # But that's as fast as you can expect it to be. self.connect() self.cond.acquire() try: t = self.thread conn = self.connection finally: self.cond.release() if t is not None and conn is None: event = t.one_attempt event.wait() self.cond.acquire() try: conn = self.connection finally: self.cond.release() return conn is not None def connect(self, sync=0): self.cond.acquire() try: if self.connection is not None: return t = self.thread if t is None: log("CM.connect(): starting ConnectThread") self.thread = t = ConnectThread(self, self.client, self.addrlist, self.tmin, self.tmax) t.setDaemon(1) t.start() if sync: while self.connection is None and t.isAlive(): self.cond.wait(self.sync_wait) if self.connection is None: log("CM.connect(sync=1): still waiting...") assert self.connection is not None finally: self.cond.release() def connect_done(self, conn, preferred): # Called by ConnectWrapper.notify_client() after notifying the client log("CM.connect_done(preferred=%s)" % preferred) self.cond.acquire() try: self.connection = conn if preferred: self.thread = None self.cond.notifyAll() # Wake up connect(sync=1) finally: self.cond.release() def close_conn(self, conn): # Called by the connection when it is closed self.cond.acquire() try: if conn is not self.connection: # Closing a non-current connection log("CM.close_conn() non-current", level=BLATHER) return log("CM.close_conn()") self.connection = None finally: self.cond.release() self.client.notifyDisconnected() if not self.closed: self.connect() def is_connected(self): self.cond.acquire() try: return self.connection is not None finally: self.cond.release() # When trying to do a connect on a non-blocking socket, some outcomes # are expected. Set _CONNECT_IN_PROGRESS to the errno value(s) expected # when an initial connect can't complete immediately. Set _CONNECT_OK # to the errno value(s) expected if the connect succeeds *or* if it's # already connected (our code can attempt redundant connects). if hasattr(errno, "WSAEWOULDBLOCK"): # Windows # Caution: The official Winsock docs claim that WSAEALREADY should be # treated as yet another "in progress" indicator, but we've never # seen this. _CONNECT_IN_PROGRESS = (errno.WSAEWOULDBLOCK,) # Win98: WSAEISCONN; Win2K: WSAEINVAL _CONNECT_OK = (0, errno.WSAEISCONN, errno.WSAEINVAL) else: # Unix _CONNECT_IN_PROGRESS = (errno.EINPROGRESS,) _CONNECT_OK = (0, errno.EISCONN) class ConnectThread(threading.Thread): """Thread that tries to connect to server given one or more addresses. The thread is passed a ConnectionManager and the manager's client as arguments. It calls testConnection() on the client when a socket connects; that should return 1 or 0 indicating whether this is a preferred or a fallback connection. It may also raise an exception, in which case the connection is abandoned. The thread will continue to run, attempting connections, until a preferred connection is seen and successfully handed over to the manager and client. As soon as testConnection() finds a preferred connection, or after all sockets have been tried and at least one fallback connection has been seen, notifyConnected(connection) is called on the client and connect_done() on the manager. If this was a preferred connection, the thread then exits; otherwise, it keeps trying until it gets a preferred connection, and then reconnects the client using that connection. """ __super_init = threading.Thread.__init__ # We don't expect clients to call any methods of this Thread other # than close() and those defined by the Thread API. def __init__(self, mgr, client, addrlist, tmin, tmax): self.__super_init(name="Connect(%s)" % addrlist) self.mgr = mgr self.client = client self.addrlist = addrlist self.tmin = tmin self.tmax = tmax self.stopped = 0 self.one_attempt = threading.Event() # A ConnectThread keeps track of whether it has finished a # call to try_connecting(). This allows the ConnectionManager # to make an attempt to connect right away, but not block for # too long if the server isn't immediately available. def stop(self): self.stopped = 1 def run(self): delay = self.tmin success = 0 # Don't wait too long the first time. # TODO: make timeout configurable? attempt_timeout = 5 while not self.stopped: success = self.try_connecting(attempt_timeout) if not self.one_attempt.isSet(): self.one_attempt.set() attempt_timeout = 75 if success > 0: break time.sleep(delay) if self.mgr.is_connected(): log("CT: still trying to replace fallback connection", level=logging.INFO) delay = min(delay*2, self.tmax) log("CT: exiting thread: %s" % self.getName()) def try_connecting(self, timeout): """Try connecting to all self.addrlist addresses. Return 1 if a preferred connection was found; 0 if no connection was found; and -1 if a fallback connection was found. If no connection is found within timeout seconds, return 0. """ log("CT: attempting to connect on %d sockets" % len(self.addrlist)) deadline = time.time() + timeout wrappers = self._create_wrappers() for wrap in wrappers.keys(): if wrap.state == "notified": return 1 try: if time.time() > deadline: return 0 r = self._connect_wrappers(wrappers, deadline) if r is not None: return r if time.time() > deadline: return 0 r = self._fallback_wrappers(wrappers, deadline) if r is not None: return r # Alas, no luck. assert not wrappers finally: for wrap in wrappers.keys(): wrap.close() del wrappers return 0 def _expand_addrlist(self): for domain, addr in self.addrlist: # AF_INET really means either IPv4 or IPv6, possibly # indirected by DNS. By design, DNS lookup is deferred # until connections get established, so that DNS # reconfiguration can affect failover if domain == socket.AF_INET: host, port = addr for (family, socktype, proto, cannoname, sockaddr ) in socket.getaddrinfo(host or 'localhost', port): # for IPv6, drop flowinfo, and restrict addresses # to [host]:port yield family, sockaddr[:2] else: yield domain, addr def _create_wrappers(self): # Create socket wrappers wrappers = {} # keys are active wrappers for domain, addr in self._expand_addrlist(): wrap = ConnectWrapper(domain, addr, self.mgr, self.client) wrap.connect_procedure() if wrap.state == "notified": for w in wrappers.keys(): w.close() return {wrap: wrap} if wrap.state != "closed": wrappers[wrap] = wrap return wrappers def _connect_wrappers(self, wrappers, deadline): # Next wait until they all actually connect (or fail) # The deadline is necessary, because we'd wait forever if a # sockets never connects or fails. while wrappers: if self.stopped: for wrap in wrappers.keys(): wrap.close() return 0 # Select connecting wrappers connecting = [wrap for wrap in wrappers.keys() if wrap.state == "connecting"] if not connecting: break if time.time() > deadline: break try: r, w, x = select.select([], connecting, connecting, 1.0) log("CT: select() %d, %d, %d" % tuple(map(len, (r,w,x)))) except select.error, msg: log("CT: select failed; msg=%s" % str(msg), level=logging.WARNING) continue # Exceptable wrappers are in trouble; close these suckers for wrap in x: log("CT: closing troubled socket %s" % str(wrap.addr)) del wrappers[wrap] wrap.close() # Writable sockets are connected for wrap in w: wrap.connect_procedure() if wrap.state == "notified": del wrappers[wrap] # Don't close this one for wrap in wrappers.keys(): wrap.close() return 1 if wrap.state == "closed": del wrappers[wrap] def _fallback_wrappers(self, wrappers, deadline): # If we've got wrappers left at this point, they're fallback # connections. Try notifying them until one succeeds. for wrap in wrappers.keys(): assert wrap.state == "tested" and wrap.preferred == 0 if self.mgr.is_connected(): wrap.close() else: wrap.notify_client() if wrap.state == "notified": del wrappers[wrap] # Don't close this one for wrap in wrappers.keys(): wrap.close() return -1 assert wrap.state == "closed" del wrappers[wrap] # TODO: should check deadline class ConnectWrapper: """An object that handles the connection procedure for one socket. This is a little state machine with states: closed opened connecting connected tested notified """ def __init__(self, domain, addr, mgr, client): """Store arguments and create non-blocking socket.""" self.domain = domain self.addr = addr self.mgr = mgr self.client = client # These attributes are part of the interface self.state = "closed" self.sock = None self.conn = None self.preferred = 0 log("CW: attempt to connect to %s" % repr(addr)) try: self.sock = socket.socket(domain, socket.SOCK_STREAM) except socket.error, err: log("CW: can't create socket, domain=%s: %s" % (domain, err), level=logging.ERROR) self.close() return self.sock.setblocking(0) self.state = "opened" def connect_procedure(self): """Call sock.connect_ex(addr) and interpret result.""" if self.state in ("opened", "connecting"): try: err = self.sock.connect_ex(self.addr) except socket.error, msg: log("CW: connect_ex(%r) failed: %s" % (self.addr, msg), level=logging.ERROR) self.close() return log("CW: connect_ex(%s) returned %s" % (self.addr, errno.errorcode.get(err) or str(err))) if err in _CONNECT_IN_PROGRESS: self.state = "connecting" return if err not in _CONNECT_OK: log("CW: error connecting to %s: %s" % (self.addr, errno.errorcode.get(err) or str(err)), level=logging.WARNING) self.close() return self.state = "connected" if self.state == "connected": self.test_connection() def test_connection(self): """Establish and test a connection at the zrpc level. Call the client's testConnection(), giving the client a chance to do app-level check of the connection. """ self.conn = ManagedClientConnection(self.sock, self.addr, self.mgr) self.sock = None # The socket is now owned by the connection try: self.preferred = self.client.testConnection(self.conn) self.state = "tested" except ReadOnlyError: log("CW: ReadOnlyError in testConnection (%s)" % repr(self.addr)) self.close() return except: log("CW: error in testConnection (%s)" % repr(self.addr), level=logging.ERROR, exc_info=True) self.close() return if self.preferred: self.notify_client() def notify_client(self): """Call the client's notifyConnected(). If this succeeds, call the manager's connect_done(). If the client is already connected, we assume it's a fallback connection, and the new connection must be a preferred connection. The client will close the old connection. """ try: self.client.notifyConnected(self.conn) except: log("CW: error in notifyConnected (%s)" % repr(self.addr), level=logging.ERROR, exc_info=True) self.close() return self.state = "notified" self.mgr.connect_done(self.conn, self.preferred) def close(self): """Close the socket and reset everything.""" self.state = "closed" self.mgr = self.client = None self.preferred = 0 if self.conn is not None: # Closing the ZRPC connection will eventually close the # socket, somewhere in asyncore. Guido asks: Why do we care? self.conn.close() self.conn = None if self.sock is not None: self.sock.close() self.sock = None def fileno(self): return self.sock.fileno() zope2.13-2.13.21/source/ZODB3/src/ZEO/zrpc/smac.py0000644000175000017500000003027312214017464017765 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Sized Message Async Connections. This class extends the basic asyncore layer with a record-marking layer. The message_output() method accepts an arbitrary sized string as its argument. It sends over the wire the length of the string encoded using struct.pack('>I') and the string itself. The receiver passes the original string to message_input(). This layer also supports an optional message authentication code (MAC). If a session key is present, it uses HMAC-SHA-1 to generate a 20-byte MAC. If a MAC is present, the high-order bit of the length is set to 1 and the MAC immediately follows the length. """ import asyncore import errno try: import hmac except ImportError: import _hmac as hmac import socket import struct import threading from types import StringType from ZEO.zrpc.log import log from ZEO.zrpc.error import DisconnectedError import ZEO.hash # Use the dictionary to make sure we get the minimum number of errno # entries. We expect that EWOULDBLOCK == EAGAIN on most systems -- # or that only one is actually used. tmp_dict = {errno.EWOULDBLOCK: 0, errno.EAGAIN: 0, errno.EINTR: 0, } expected_socket_read_errors = tuple(tmp_dict.keys()) tmp_dict = {errno.EAGAIN: 0, errno.EWOULDBLOCK: 0, errno.ENOBUFS: 0, errno.EINTR: 0, } expected_socket_write_errors = tuple(tmp_dict.keys()) del tmp_dict # We chose 60000 as the socket limit by looking at the largest strings # that we could pass to send() without blocking. SEND_SIZE = 60000 MAC_BIT = 0x80000000L _close_marker = object() class SizedMessageAsyncConnection(asyncore.dispatcher): __super_init = asyncore.dispatcher.__init__ __super_close = asyncore.dispatcher.close __closed = True # Marker indicating that we're closed socket = None # to outwit Sam's getattr def __init__(self, sock, addr, map=None): self.addr = addr # __input_lock protects __inp, __input_len, __state, __msg_size self.__input_lock = threading.Lock() self.__inp = None # None, a single String, or a list self.__input_len = 0 # Instance variables __state, __msg_size and __has_mac work together: # when __state == 0: # __msg_size == 4, and the next thing read is a message size; # __has_mac is set according to the MAC_BIT in the header # when __state == 1: # __msg_size is variable, and the next thing read is a message. # __has_mac indicates if we're in MAC mode or not (and # therefore, if we need to check the mac header) # The next thing read is always of length __msg_size. # The state alternates between 0 and 1. self.__state = 0 self.__has_mac = 0 self.__msg_size = 4 self.__output_messages = [] self.__output = [] self.__closed = False # Each side of the connection sends and receives messages. A # MAC is generated for each message and depends on each # previous MAC; the state of the MAC generator depends on the # history of operations it has performed. So the MACs must be # generated in the same order they are verified. # Each side is guaranteed to receive messages in the order # they are sent, but there is no ordering constraint between # message sends and receives. If the two sides are A and B # and message An indicates the nth message sent by A, then # A1 A2 B1 B2 and A1 B1 B2 A2 are both legitimate total # orderings of the messages. # As a result, there must be seperate MAC generators for each # side of the connection. If not, the generator state would # be different after A1 A2 B1 B2 than it would be after # A1 B1 B2 A2; if the generator state was different, the MAC # could not be verified. self.__hmac_send = None self.__hmac_recv = None self.__super_init(sock, map) # asyncore overwrites addr with the getpeername result # restore our value self.addr = addr def setSessionKey(self, sesskey): log("set session key %r" % sesskey) # Low-level construction is now delayed until data are sent. # This is to allow use of iterators that generate messages # only when we're ready to do I/O so that we can effeciently # transmit large files. Because we delay messages, we also # have to delay setting the session key to retain proper # ordering. # The low-level output queue supports strings, a special close # marker, and iterators. It doesn't support callbacks. We # can create a allback by providing an iterator that doesn't # yield anything. # The hack fucntion below is a callback in iterator's # clothing. :) It never yields anything, but is a generator # and thus iterator, because it contains a yield statement. def hack(): self.__hmac_send = hmac.HMAC(sesskey, digestmod=ZEO.hash) self.__hmac_recv = hmac.HMAC(sesskey, digestmod=ZEO.hash) if False: yield '' self.message_output(hack()) def get_addr(self): return self.addr # TODO: avoid expensive getattr calls? Can't remember exactly what # this comment was supposed to mean, but it has something to do # with the way asyncore uses getattr and uses if sock: def __nonzero__(self): return 1 def handle_read(self): self.__input_lock.acquire() try: # Use a single __inp buffer and integer indexes to make this fast. try: d = self.recv(8192) except socket.error, err: if err[0] in expected_socket_read_errors: return raise if not d: return input_len = self.__input_len + len(d) msg_size = self.__msg_size state = self.__state has_mac = self.__has_mac inp = self.__inp if msg_size > input_len: if inp is None: self.__inp = d elif type(self.__inp) is StringType: self.__inp = [self.__inp, d] else: self.__inp.append(d) self.__input_len = input_len return # keep waiting for more input # load all previous input and d into single string inp if isinstance(inp, StringType): inp = inp + d elif inp is None: inp = d else: inp.append(d) inp = "".join(inp) offset = 0 while (offset + msg_size) <= input_len: msg = inp[offset:offset + msg_size] offset = offset + msg_size if not state: msg_size = struct.unpack(">I", msg)[0] has_mac = msg_size & MAC_BIT if has_mac: msg_size ^= MAC_BIT msg_size += 20 elif self.__hmac_send: raise ValueError("Received message without MAC") state = 1 else: msg_size = 4 state = 0 # Obscure: We call message_input() with __input_lock # held!!! And message_input() may end up calling # message_output(), which has its own lock. But # message_output() cannot call message_input(), so # the locking order is always consistent, which # prevents deadlock. Also, message_input() may # take a long time, because it can cause an # incoming call to be handled. During all this # time, the __input_lock is held. That's a good # thing, because it serializes incoming calls. if has_mac: mac = msg[:20] msg = msg[20:] if self.__hmac_recv: self.__hmac_recv.update(msg) _mac = self.__hmac_recv.digest() if mac != _mac: raise ValueError("MAC failed: %r != %r" % (_mac, mac)) else: log("Received MAC but no session key set") elif self.__hmac_send: raise ValueError("Received message without MAC") self.message_input(msg) self.__state = state self.__has_mac = has_mac self.__msg_size = msg_size self.__inp = inp[offset:] self.__input_len = input_len - offset finally: self.__input_lock.release() def readable(self): return True def writable(self): return bool(self.__output_messages or self.__output) def should_close(self): self.__output_messages.append(_close_marker) def handle_write(self): output = self.__output messages = self.__output_messages while output or messages: # Process queued messages until we have enough output size = sum((len(s) for s in output)) while (size <= SEND_SIZE) and messages: message = messages[0] if message.__class__ is str: size += self.__message_output(messages.pop(0), output) elif message is _close_marker: del messages[:] del output[:] return self.close() else: try: message = message.next() except StopIteration: messages.pop(0) else: size += self.__message_output(message, output) v = "".join(output) del output[:] try: n = self.send(v) except socket.error, err: # Fix for https://bugs.launchpad.net/zodb/+bug/182833 # ensure the above mentioned "output" invariant output.insert(0, v) if err[0] in expected_socket_write_errors: break # we couldn't write anything raise if n < len(v): output.append(v[n:]) break # we can't write any more def handle_close(self): self.close() def message_output(self, message): if self.__closed: raise DisconnectedError( "This action is temporarily unavailable.

") self.__output_messages.append(message) def __message_output(self, message, output): # do two separate appends to avoid copying the message string size = 4 if self.__hmac_send: output.append(struct.pack(">I", len(message) | MAC_BIT)) self.__hmac_send.update(message) output.append(self.__hmac_send.digest()) size += 20 else: output.append(struct.pack(">I", len(message))) if len(message) <= SEND_SIZE: output.append(message) else: for i in range(0, len(message), SEND_SIZE): output.append(message[i:i+SEND_SIZE]) return size + len(message) def close(self): if not self.__closed: self.__closed = True self.__super_close() zope2.13-2.13.21/source/ZODB3/src/ZEO/zrpc/_hmac.py0000644000175000017500000000640412214017464020110 0ustar arnauarnau# This file is a slightly modified copy of Python 2.3's Lib/hmac.py. # This file is under the Python Software Foundation (PSF) license. """HMAC (Keyed-Hashing for Message Authentication) Python module. Implements the HMAC algorithm as described by RFC 2104. """ def _strxor(s1, s2): """Utility method. XOR the two strings s1 and s2 (must have same length). """ return "".join(map(lambda x, y: chr(ord(x) ^ ord(y)), s1, s2)) # The size of the digests returned by HMAC depends on the underlying # hashing module used. digest_size = None class HMAC: """RFC2104 HMAC class. This supports the API for Cryptographic Hash Functions (PEP 247). """ def __init__(self, key, msg = None, digestmod = None): """Create a new HMAC object. key: key for the keyed hash object. msg: Initial input for the hash, if provided. digestmod: A module supporting PEP 247. Defaults to the md5 module. """ if digestmod is None: import md5 digestmod = md5 self.digestmod = digestmod self.outer = digestmod.new() self.inner = digestmod.new() # Python 2.1 and 2.2 differ about the correct spelling try: self.digest_size = digestmod.digestsize except AttributeError: self.digest_size = digestmod.digest_size blocksize = 64 ipad = "\x36" * blocksize opad = "\x5C" * blocksize if len(key) > blocksize: key = digestmod.new(key).digest() key = key + chr(0) * (blocksize - len(key)) self.outer.update(_strxor(key, opad)) self.inner.update(_strxor(key, ipad)) if msg is not None: self.update(msg) ## def clear(self): ## raise NotImplementedError("clear() method not available in HMAC.") def update(self, msg): """Update this hashing object with the string msg. """ self.inner.update(msg) def copy(self): """Return a separate copy of this hashing object. An update to this copy won't affect the original object. """ other = HMAC("") other.digestmod = self.digestmod other.inner = self.inner.copy() other.outer = self.outer.copy() return other def digest(self): """Return the hash value of this hashing object. This returns a string containing 8-bit data. The object is not altered in any way by this function; you can continue updating the object after calling this function. """ h = self.outer.copy() h.update(self.inner.digest()) return h.digest() def hexdigest(self): """Like digest(), but returns a string of hexadecimal digits instead. """ return "".join([hex(ord(x))[2:].zfill(2) for x in tuple(self.digest())]) def new(key, msg = None, digestmod = None): """Create a new hashing object and return it. key: The starting key for the hash. msg: if available, will immediately be hashed into the object's starting state. You can now feed arbitrary strings into the object using its update() method, and can ask for the hash value at any time by calling its digest() method. """ return HMAC(key, msg, digestmod) zope2.13-2.13.21/source/ZODB3/src/ZEO/zrpc/server.py0000644000175000017500000000774012214017464020353 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import asyncore import socket import types # _has_dualstack: True if the dual-stack sockets are supported try: # Check whether IPv6 sockets can be created s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) except (socket.error, AttributeError): _has_dualstack = False else: # Check whether enabling dualstack (disabling v6only) works try: s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False) except (socket.error, AttributeError): _has_dualstack = False else: _has_dualstack = True s.close() del s from ZEO.zrpc.connection import Connection from ZEO.zrpc.log import log import ZEO.zrpc.log import logging # Export the main asyncore loop loop = asyncore.loop class Dispatcher(asyncore.dispatcher): """A server that accepts incoming RPC connections""" __super_init = asyncore.dispatcher.__init__ def __init__(self, addr, factory=Connection): self.__super_init() self.addr = addr self.factory = factory self._open_socket() def _open_socket(self): if type(self.addr) == types.TupleType: if self.addr[0] == '' and _has_dualstack: # Wildcard listen on all interfaces, both IPv4 and # IPv6 if possible self.create_socket(socket.AF_INET6, socket.SOCK_STREAM) self.socket.setsockopt( socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False) elif ':' in self.addr[0]: self.create_socket(socket.AF_INET6, socket.SOCK_STREAM) if _has_dualstack: # On Linux, IPV6_V6ONLY is off by default. # If the user explicitly asked for IPv6, don't bind to IPv4 self.socket.setsockopt( socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, True) else: self.create_socket(socket.AF_INET, socket.SOCK_STREAM) else: self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) self.set_reuse_addr() log("listening on %s" % str(self.addr), logging.INFO) self.bind(self.addr) self.listen(5) def writable(self): return 0 def readable(self): return 1 def handle_accept(self): try: sock, addr = self.accept() except socket.error, msg: log("accepted failed: %s" % msg) return # We could short-circuit the attempt below in some edge cases # and avoid a log message by checking for addr being None. # Unfortunately, our test for the code below, # quick_close_doesnt_kill_server, causes addr to be None and # we'd have to write a test for the non-None case, which is # *even* harder to provoke. :/ So we'll leave things as they # are for now. # It might be better to check whether the socket has been # closed, but I don't see a way to do that. :( # Drop flow-info from IPv6 addresses if addr: # Sometimes None on Mac. See above. addr = addr[:2] try: c = self.factory(sock, addr) except: if sock.fileno() in asyncore.socket_map: del asyncore.socket_map[sock.fileno()] ZEO.zrpc.log.logger.exception("Error in handle_accept") else: log("connect from %s: %s" % (repr(addr), c)) zope2.13-2.13.21/source/ZODB3/src/ZEO/zrpc/log.py0000644000175000017500000000467712214017464017634 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import os import threading import logging from ZODB.loglevels import BLATHER LOG_THREAD_ID = 0 # Set this to 1 during heavy debugging logger = logging.getLogger('ZEO.zrpc') _label = "%s" % os.getpid() def new_label(): global _label _label = str(os.getpid()) def log(message, level=BLATHER, label=None, exc_info=False): label = label or _label if LOG_THREAD_ID: label = label + ':' + threading.currentThread().getName() logger.log(level, '(%s) %s' % (label, message), exc_info=exc_info) REPR_LIMIT = 60 def short_repr(obj): "Return an object repr limited to REPR_LIMIT bytes." # Some of the objects being repr'd are large strings. A lot of memory # would be wasted to repr them and then truncate, so they are treated # specially in this function. # Also handle short repr of a tuple containing a long string. # This strategy works well for arguments to StorageServer methods. # The oid is usually first and will get included in its entirety. # The pickle is near the beginning, too, and you can often fit the # module name in the pickle. if isinstance(obj, str): if len(obj) > REPR_LIMIT: r = repr(obj[:REPR_LIMIT]) else: r = repr(obj) if len(r) > REPR_LIMIT: r = r[:REPR_LIMIT-4] + '...' + r[-1] return r elif isinstance(obj, (list, tuple)): elts = [] size = 0 for elt in obj: r = short_repr(elt) elts.append(r) size += len(r) if size > REPR_LIMIT: break if isinstance(obj, tuple): r = "(%s)" % (", ".join(elts)) else: r = "[%s]" % (", ".join(elts)) else: r = repr(obj) if len(r) > REPR_LIMIT: return r[:REPR_LIMIT] + '...' else: return r zope2.13-2.13.21/source/ZODB3/src/ZEO/zrpc/connection.py0000644000175000017500000007617612214017464021215 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import asyncore import sys import threading import logging import ZEO.zrpc.marshal import ZEO.zrpc.trigger from ZEO.zrpc import smac from ZEO.zrpc.error import ZRPCError, DisconnectedError from ZEO.zrpc.log import short_repr, log from ZODB.loglevels import BLATHER, TRACE import ZODB.POSException REPLY = ".reply" # message name used for replies exception_type_type = type(Exception) debug_zrpc = False class Delay: """Used to delay response to client for synchronous calls. When a synchronous call is made and the original handler returns without handling the call, it returns a Delay object that prevents the mainloop from sending a response. """ msgid = conn = sent = None def set_sender(self, msgid, conn): self.msgid = msgid self.conn = conn def reply(self, obj): self.sent = 'reply' self.conn.send_reply(self.msgid, obj) def error(self, exc_info): self.sent = 'error' log("Error raised in delayed method", logging.ERROR, exc_info=True) self.conn.return_error(self.msgid, *exc_info[:2]) def __repr__(self): return "%s[%s, %r, %r, %r]" % ( self.__class__.__name__, id(self), self.msgid, self.conn, self.sent) class Result(Delay): def __init__(self, *args): self.args = args def set_sender(self, msgid, conn): reply, callback = self.args conn.send_reply(msgid, reply, False) callback() class MTDelay(Delay): def __init__(self): self.ready = threading.Event() def set_sender(self, *args): Delay.set_sender(self, *args) self.ready.set() def reply(self, obj): self.ready.wait() self.conn.call_from_thread(self.conn.send_reply, self.msgid, obj) def error(self, exc_info): self.ready.wait() self.conn.call_from_thread(Delay.error, self, exc_info) # PROTOCOL NEGOTIATION # # The code implementing protocol version 2.0.0 (which is deployed # in the field and cannot be changed) *only* talks to peers that # send a handshake indicating protocol version 2.0.0. In that # version, both the client and the server immediately send out # their protocol handshake when a connection is established, # without waiting for their peer, and disconnect when a different # handshake is receive. # # The new protocol uses this to enable new clients to talk to # 2.0.0 servers. In the new protocol: # # The server sends its protocol handshake to the client at once. # # The client waits until it receives the server's protocol handshake # before sending its own handshake. The client sends the lower of its # own protocol version and the server protocol version, allowing it to # talk to servers using later protocol versions (2.0.2 and higher) as # well: the effective protocol used will be the lower of the client # and server protocol. However, this changed in ZODB 3.3.1 (and # should have changed in ZODB 3.3) because an older server doesn't # support MVCC methods required by 3.3 clients. # # [Ugly details: In order to treat the first received message (protocol # handshake) differently than all later messages, both client and server # start by patching their message_input() method to refer to their # recv_handshake() method instead. In addition, the client has to arrange # to queue (delay) outgoing messages until it receives the server's # handshake, so that the first message the client sends to the server is # the client's handshake. This multiply-special treatment of the first # message is delicate, and several asyncore and thread subtleties were # handled unsafely before ZODB 3.2.6. # ] # # The ZEO modules ClientStorage and ServerStub have backwards # compatibility code for dealing with the previous version of the # protocol. The client accepts the old version of some messages, # and will not send new messages when talking to an old server. # # As long as the client hasn't sent its handshake, it can't send # anything else; output messages are queued during this time. # (Output can happen because the connection testing machinery can # start sending requests before the handshake is received.) # # UPGRADING FROM ZEO 2.0.0 TO NEWER VERSIONS: # # Because a new client can talk to an old server, but not vice # versa, all clients should be upgraded before upgrading any # servers. Protocol upgrades beyond 2.0.1 will not have this # restriction, because clients using protocol 2.0.1 or later can # talk to both older and newer servers. # # No compatibility with protocol version 1 is provided. # Connection is abstract (it must be derived from). ManagedServerConnection # and ManagedClientConnection are the concrete subclasses. They need to # supply a handshake() method appropriate for their role in protocol # negotiation. class Connection(smac.SizedMessageAsyncConnection, object): """Dispatcher for RPC on object on both sides of socket. The connection supports synchronous calls, which expect a return, and asynchronous calls, which do not. It uses the Marshaller class to handle encoding and decoding of method calls and arguments. Marshaller uses pickle to encode arbitrary Python objects. The code here doesn't ever see the wire format. A Connection is designed for use in a multithreaded application, where a synchronous call must block until a response is ready. A socket connection between a client and a server allows either side to invoke methods on the other side. The processes on each end of the socket use a Connection object to manage communication. The Connection deals with decoded RPC messages. They are represented as four-tuples containing: msgid, flags, method name, and a tuple of method arguments. The msgid starts at zero and is incremented by one each time a method call message is sent. Each side of the connection has a separate msgid state. When one side of the connection (the client) calls a method, it sends a message with a new msgid. The other side (the server), replies with a message that has the same msgid, the string ".reply" (the global variable REPLY) as the method name, and the actual return value in the args position. Note that each side of the Connection can initiate a call, in which case it will be the client for that particular call. The protocol also supports asynchronous calls. The client does not wait for a return value for an asynchronous call. If a method call raises an Exception, the exception is propagated back to the client via the REPLY message. The client side will raise any exception it receives instead of returning the value to the caller. """ __super_init = smac.SizedMessageAsyncConnection.__init__ __super_close = smac.SizedMessageAsyncConnection.close __super_setSessionKey = smac.SizedMessageAsyncConnection.setSessionKey # Protocol history: # # Z200 -- Original ZEO 2.0 protocol # # Z201 -- Added invalidateTransaction() to client. # Renamed several client methods. # Added several sever methods: # lastTransaction() # getAuthProtocol() and scheme-specific authentication methods # getExtensionMethods(). # getInvalidations(). # # Z303 -- named after the ZODB release 3.3 # Added methods for MVCC: # loadBefore() # A Z303 client cannot talk to a Z201 server, because the latter # doesn't support MVCC. A Z201 client can talk to a Z303 server, # but because (at least) the type of the root object changed # from ZODB.PersistentMapping to persistent.mapping, the older # client can't actually make progress if a Z303 client created, # or ever modified, the root. # # Z308 -- named after the ZODB release 3.8 # Added blob-support server methods: # sendBlob # storeBlobStart # storeBlobChunk # storeBlobEnd # storeBlobShared # Added blob-support client methods: # receiveBlobStart # receiveBlobChunk # receiveBlobStop # # Z309 -- named after the ZODB release 3.9 # New server methods: # restorea, iterator_start, iterator_next, # iterator_record_start, iterator_record_next, # iterator_gc # # Z310 -- named after the ZODB release 3.10 # New server methods: # undoa # Doesn't support undo for older clients. # Undone oid info returned by vote. # # Z3101 -- checkCurrentSerialInTransaction # Protocol variables: # Our preferred protocol. current_protocol = "Z3101" # If we're a client, an exhaustive list of the server protocols we # can accept. servers_we_can_talk_to = ["Z308", "Z309", "Z310", current_protocol] # If we're a server, an exhaustive list of the client protocols we # can accept. clients_we_can_talk_to = [ "Z200", "Z201", "Z303", "Z308", "Z309", "Z310", current_protocol] # This is pretty excruciating. Details: # # 3.3 server 3.2 client # server sends Z303 to client # client computes min(Z303, Z201) == Z201 as the protocol to use # client sends Z201 to server # OK, because Z201 is in the server's clients_we_can_talk_to # # 3.2 server 3.3 client # server sends Z201 to client # client computes min(Z303, Z201) == Z201 as the protocol to use # Z201 isn't in the client's servers_we_can_talk_to, so client # raises exception # # 3.3 server 3.3 client # server sends Z303 to client # client computes min(Z303, Z303) == Z303 as the protocol to use # Z303 is in the client's servers_we_can_talk_to, so client # sends Z303 to server # OK, because Z303 is in the server's clients_we_can_talk_to # Exception types that should not be logged: unlogged_exception_types = () # Client constructor passes 'C' for tag, server constructor 'S'. This # is used in log messages, and to determine whether we can speak with # our peer. def __init__(self, sock, addr, obj, tag, map=None): self.obj = None self.decode = ZEO.zrpc.marshal.decode self.encode = ZEO.zrpc.marshal.encode self.fast_encode = ZEO.zrpc.marshal.fast_encode self.closed = False self.peer_protocol_version = None # set in recv_handshake() assert tag in "CS" self.tag = tag self.logger = logging.getLogger('ZEO.zrpc.Connection(%c)' % tag) if isinstance(addr, tuple): self.log_label = "(%s:%d) " % addr else: self.log_label = "(%s) " % addr # Supply our own socket map, so that we don't get registered with # the asyncore socket map just yet. The initial protocol messages # are treated very specially, and we dare not get invoked by asyncore # before that special-case setup is complete. Some of that setup # occurs near the end of this constructor, and the rest is done by # a concrete subclass's handshake() method. Unfortunately, because # we ultimately derive from asyncore.dispatcher, it's not possible # to invoke the superclass constructor without asyncore stuffing # us into _some_ socket map. ourmap = {} self.__super_init(sock, addr, map=ourmap) # The singleton dict is used in synchronous mode when a method # needs to call into asyncore to try to force some I/O to occur. # The singleton dict is a socket map containing only this object. self._singleton = {self._fileno: self} # waiting_for_reply is used internally to indicate whether # a call is in progress. setting a session key is deferred # until after the call returns. self.waiting_for_reply = False self.delay_sesskey = None self.register_object(obj) # The first message we see is a protocol handshake. message_input() # is temporarily replaced by recv_handshake() to treat that message # specially. revc_handshake() does "del self.message_input", which # uncovers the normal message_input() method thereafter. self.message_input = self.recv_handshake # Server and client need to do different things for protocol # negotiation, and handshake() is implemented differently in each. self.handshake() # Now it's safe to register with asyncore's socket map; it was not # safe before message_input was replaced, or before handshake() was # invoked. # Obscure: in Python 2.4, the base asyncore.dispatcher class grew # a ._map attribute, which is used instead of asyncore's global # socket map when ._map isn't None. Because we passed `ourmap` to # the base class constructor above, in 2.4 asyncore believes we want # to use `ourmap` instead of the global socket map -- but we don't. # So we have to replace our ._map with the global socket map, and # update the global socket map with `ourmap`. Replacing our ._map # isn't necessary before Python 2.4, but doesn't hurt then (it just # gives us an unused attribute in 2.3); updating the global socket # map is necessary regardless of Python version. if map is None: map = asyncore.socket_map self._map = map map.update(ourmap) def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.addr) __str__ = __repr__ # Defeat asyncore's dreaded __getattr__ def log(self, message, level=BLATHER, exc_info=False): self.logger.log(level, self.log_label + message, exc_info=exc_info) def close(self): self.mgr.close_conn(self) if self.closed: return self._singleton.clear() self.closed = True self.__super_close() self.trigger.pull_trigger() def register_object(self, obj): """Register obj as the true object to invoke methods on.""" self.obj = obj # Subclass must implement. handshake() is called by the constructor, # near its end, but before self is added to asyncore's socket map. # When a connection is created the first message sent is a 4-byte # protocol version. This allows the protocol to evolve over time, and # lets servers handle clients using multiple versions of the protocol. # In general, the server's handshake() just needs to send the server's # preferred protocol; the client's also needs to queue (delay) outgoing # messages until it sees the handshake from the server. def handshake(self): raise NotImplementedError # Replaces message_input() for the first message received. Records the # protocol sent by the peer in `peer_protocol_version`, restores the # normal message_input() method, and raises an exception if the peer's # protocol is unacceptable. That's all the server needs to do. The # client needs to do additional work in response to the server's # handshake, and extends this method. def recv_handshake(self, proto): # Extended by ManagedClientConnection. del self.message_input # uncover normal-case message_input() self.peer_protocol_version = proto if self.tag == 'C': good_protos = self.servers_we_can_talk_to else: assert self.tag == 'S' good_protos = self.clients_we_can_talk_to if proto in good_protos: self.log("received handshake %r" % proto, level=logging.INFO) else: self.log("bad handshake %s" % short_repr(proto), level=logging.ERROR) raise ZRPCError("bad handshake %r" % proto) def message_input(self, message): """Decode an incoming message and dispatch it""" # If something goes wrong during decoding, the marshaller # will raise an exception. The exception will ultimately # result in asycnore calling handle_error(), which will # close the connection. msgid, async, name, args = self.decode(message) if debug_zrpc: self.log("recv msg: %s, %s, %s, %s" % (msgid, async, name, short_repr(args)), level=TRACE) if name == 'loadEx': # Special case and inline the heck out of load case: try: ret = self.obj.loadEx(*args) except (SystemExit, KeyboardInterrupt): raise except Exception, msg: if not isinstance(msg, self.unlogged_exception_types): self.log("%s() raised exception: %s" % (name, msg), logging.ERROR, exc_info=True) self.return_error(msgid, *sys.exc_info()[:2]) else: try: self.message_output(self.fast_encode(msgid, 0, REPLY, ret)) self.poll() except: # Fall back to normal version for better error handling self.send_reply(msgid, ret) elif name == REPLY: assert not async self.handle_reply(msgid, args) else: self.handle_request(msgid, async, name, args) def handle_request(self, msgid, async, name, args): obj = self.obj if name.startswith('_') or not hasattr(obj, name): if obj is None: if debug_zrpc: self.log("no object calling %s%s" % (name, short_repr(args)), level=logging.DEBUG) return msg = "Invalid method name: %s on %s" % (name, repr(obj)) raise ZRPCError(msg) if debug_zrpc: self.log("calling %s%s" % (name, short_repr(args)), level=logging.DEBUG) meth = getattr(obj, name) try: self.waiting_for_reply = True try: ret = meth(*args) finally: self.waiting_for_reply = False except (SystemExit, KeyboardInterrupt): raise except Exception, msg: if not isinstance(msg, self.unlogged_exception_types): self.log("%s() raised exception: %s" % (name, msg), logging.ERROR, exc_info=True) error = sys.exc_info()[:2] if async: self.log("Asynchronous call raised exception: %s" % self, level=logging.ERROR, exc_info=True) else: self.return_error(msgid, *error) return if async: if ret is not None: raise ZRPCError("async method %s returned value %s" % (name, short_repr(ret))) else: if debug_zrpc: self.log("%s returns %s" % (name, short_repr(ret)), logging.DEBUG) if isinstance(ret, Delay): ret.set_sender(msgid, self) else: self.send_reply(msgid, ret, not self.delay_sesskey) if self.delay_sesskey: self.__super_setSessionKey(self.delay_sesskey) self.delay_sesskey = None def return_error(self, msgid, err_type, err_value): # Note that, ideally, this should be defined soley for # servers, but a test arranges to get it called on # a client. Too much trouble to fix it now. :/ if not isinstance(err_value, Exception): err_value = err_type, err_value # encode() can pass on a wide variety of exceptions from cPickle. # While a bare `except` is generally poor practice, in this case # it's acceptable -- we really do want to catch every exception # cPickle may raise. try: msg = self.encode(msgid, 0, REPLY, (err_type, err_value)) except: # see above try: r = short_repr(err_value) except: r = "" err = ZRPCError("Couldn't pickle error %.100s" % r) msg = self.encode(msgid, 0, REPLY, (ZRPCError, err)) self.message_output(msg) self.poll() def handle_error(self): if sys.exc_info()[0] == SystemExit: raise sys.exc_info() self.log("Error caught in asyncore", level=logging.ERROR, exc_info=True) self.close() def setSessionKey(self, key): if self.waiting_for_reply: self.delay_sesskey = key else: self.__super_setSessionKey(key) def send_call(self, method, args, async=False): # send a message and return its msgid if async: msgid = 0 else: msgid = self._new_msgid() if debug_zrpc: self.log("send msg: %d, %d, %s, ..." % (msgid, async, method), level=TRACE) buf = self.encode(msgid, async, method, args) self.message_output(buf) return msgid def callAsync(self, method, *args): if self.closed: raise DisconnectedError() self.send_call(method, args, 1) self.poll() def callAsyncNoPoll(self, method, *args): # Like CallAsync but doesn't poll. This exists so that we can # send invalidations atomically to all clients without # allowing any client to sneak in a load request. if self.closed: raise DisconnectedError() self.send_call(method, args, 1) def callAsyncNoSend(self, method, *args): # Like CallAsync but doesn't poll. This exists so that we can # send invalidations atomically to all clients without # allowing any client to sneak in a load request. if self.closed: raise DisconnectedError() self.send_call(method, args, 1) self.call_from_thread() def callAsyncIterator(self, iterator): """Queue a sequence of calls using an iterator The calls will not be interleaved with other calls from the same client. """ self.message_output(self.encode(0, 1, method, args) for method, args in iterator) def handle_reply(self, msgid, ret): assert msgid == -1 and ret is None def poll(self): """Invoke asyncore mainloop to get pending message out.""" if debug_zrpc: self.log("poll()", level=TRACE) self.trigger.pull_trigger() # import cProfile, time class ManagedServerConnection(Connection): """Server-side Connection subclass.""" # Exception types that should not be logged: unlogged_exception_types = (ZODB.POSException.POSKeyError, ) def __init__(self, sock, addr, obj, mgr): self.mgr = mgr map = {} Connection.__init__(self, sock, addr, obj, 'S', map=map) self.decode = ZEO.zrpc.marshal.server_decode self.trigger = ZEO.zrpc.trigger.trigger(map) self.call_from_thread = self.trigger.pull_trigger t = threading.Thread(target=server_loop, args=(map,)) t.setDaemon(True) t.start() # self.profile = cProfile.Profile() # def message_input(self, message): # self.profile.enable() # try: # Connection.message_input(self, message) # finally: # self.profile.disable() def handshake(self): # Send the server's preferred protocol to the client. self.message_output(self.current_protocol) def recv_handshake(self, proto): Connection.recv_handshake(self, proto) self.obj.notifyConnected(self) def close(self): self.obj.notifyDisconnected() Connection.close(self) # self.profile.dump_stats(str(time.time())+'.stats') def send_reply(self, msgid, ret, immediately=True): # encode() can pass on a wide variety of exceptions from cPickle. # While a bare `except` is generally poor practice, in this case # it's acceptable -- we really do want to catch every exception # cPickle may raise. try: msg = self.encode(msgid, 0, REPLY, ret) except: # see above try: r = short_repr(ret) except: r = "" err = ZRPCError("Couldn't pickle return %.100s" % r) msg = self.encode(msgid, 0, REPLY, (ZRPCError, err)) self.message_output(msg) if immediately: self.poll() poll = smac.SizedMessageAsyncConnection.handle_write def server_loop(map): while len(map) > 1: asyncore.poll(30.0, map) for o in map.values(): o.close() class ManagedClientConnection(Connection): """Client-side Connection subclass.""" __super_init = Connection.__init__ base_message_output = Connection.message_output def __init__(self, sock, addr, mgr): self.mgr = mgr # We can't use the base smac's message_output directly because the # client needs to queue outgoing messages until it's seen the # initial protocol handshake from the server. So we have our own # message_ouput() method, and support for initial queueing. This is # a delicate design, requiring an output mutex to be wholly # thread-safe. # Caution: we must set this up before calling the base class # constructor, because the latter registers us with asyncore; # we need to guarantee that we'll queue outgoing messages before # asyncore learns about us. self.output_lock = threading.Lock() self.queue_output = True self.queued_messages = [] # msgid_lock guards access to msgid self.msgid = 0 self.msgid_lock = threading.Lock() # replies_cond is used to block when a synchronous call is # waiting for a response self.replies_cond = threading.Condition() self.replies = {} self.__super_init(sock, addr, None, tag='C', map=mgr.map) self.trigger = mgr.trigger self.call_from_thread = self.trigger.pull_trigger self.call_from_thread() def close(self): Connection.close(self) self.replies_cond.acquire() self.replies_cond.notifyAll() self.replies_cond.release() # Our message_ouput() queues messages until recv_handshake() gets the # protocol handshake from the server. def message_output(self, message): self.output_lock.acquire() try: if self.queue_output: self.queued_messages.append(message) else: assert not self.queued_messages self.base_message_output(message) finally: self.output_lock.release() def handshake(self): # The client waits to see the server's handshake. Outgoing messages # are queued for the duration. The client will send its own # handshake after the server's handshake is seen, in recv_handshake() # below. It will then send any messages queued while waiting. assert self.queue_output # the constructor already set this def recv_handshake(self, proto): # The protocol to use is the older of our and the server's preferred # protocols. proto = min(proto, self.current_protocol) # Restore the normal message_input method, and raise an exception # if the protocol version is too old. Connection.recv_handshake(self, proto) # Tell the server the protocol in use, then send any messages that # were queued while waiting to hear the server's protocol, and stop # queueing messages. self.output_lock.acquire() try: self.base_message_output(proto) for message in self.queued_messages: self.base_message_output(message) self.queued_messages = [] self.queue_output = False finally: self.output_lock.release() def _new_msgid(self): self.msgid_lock.acquire() try: msgid = self.msgid self.msgid = self.msgid + 1 return msgid finally: self.msgid_lock.release() def call(self, method, *args): if self.closed: raise DisconnectedError() msgid = self.send_call(method, args) r_args = self.wait(msgid) if (isinstance(r_args, tuple) and len(r_args) > 1 and type(r_args[0]) == exception_type_type and issubclass(r_args[0], Exception)): inst = r_args[1] raise inst # error raised by server else: return r_args def wait(self, msgid): """Invoke asyncore mainloop and wait for reply.""" if debug_zrpc: self.log("wait(%d)" % msgid, level=TRACE) self.trigger.pull_trigger() self.replies_cond.acquire() try: while 1: if self.closed: raise DisconnectedError() reply = self.replies.get(msgid, self) if reply is not self: del self.replies[msgid] if debug_zrpc: self.log("wait(%d): reply=%s" % (msgid, short_repr(reply)), level=TRACE) return reply self.replies_cond.wait() finally: self.replies_cond.release() # For testing purposes, it is useful to begin a synchronous call # but not block waiting for its response. def _deferred_call(self, method, *args): if self.closed: raise DisconnectedError() msgid = self.send_call(method, args) self.trigger.pull_trigger() return msgid def _deferred_wait(self, msgid): r_args = self.wait(msgid) if (isinstance(r_args, tuple) and type(r_args[0]) == exception_type_type and issubclass(r_args[0], Exception)): inst = r_args[1] raise inst # error raised by server else: return r_args def handle_reply(self, msgid, args): if debug_zrpc: self.log("recv reply: %s, %s" % (msgid, short_repr(args)), level=TRACE) self.replies_cond.acquire() try: self.replies[msgid] = args self.replies_cond.notifyAll() finally: self.replies_cond.release() def send_reply(self, msgid, ret): # Whimper. Used to send heartbeat assert msgid == -1 and ret is None self.message_output('(J\xff\xff\xff\xffK\x00U\x06.replyNt.') zope2.13-2.13.21/source/ZODB3/src/ZEO/ServerStub.py0000644000175000017500000003214612214017464020171 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """RPC stubs for interface exported by StorageServer.""" import time from ZODB.utils import z64 ## # ZEO storage server. #

# Remote method calls can be synchronous or asynchronous. If the call # is synchronous, the client thread blocks until the call returns. A # single client can only have one synchronous request outstanding. If # several threads share a single client, threads other than the caller # will block only if the attempt to make another synchronous call. # An asynchronous call does not cause the client thread to block. An # exception raised by an asynchronous method is logged on the server, # but is not returned to the client. class StorageServer: """An RPC stub class for the interface exported by ClientStorage. This is the interface presented by the StorageServer to the ClientStorage; i.e. the ClientStorage calls these methods and they are executed in the StorageServer. See the StorageServer module for documentation on these methods, with the exception of _update(), which is documented here. """ def __init__(self, rpc): """Constructor. The argument is a connection: an instance of the zrpc.connection.Connection class. """ self.rpc = rpc def extensionMethod(self, name): return ExtensionMethodWrapper(self.rpc, name).call ## # Register current connection with a storage and a mode. # In effect, it is like an open call. # @param storage_name a string naming the storage. This argument # is primarily for backwards compatibility with servers # that supported multiple storages. # @param read_only boolean # @exception ValueError unknown storage_name or already registered # @exception ReadOnlyError storage is read-only and a read-write # connectio was requested def register(self, storage_name, read_only): self.rpc.call('register', storage_name, read_only) ## # Return dictionary of meta-data about the storage. # @defreturn dict def get_info(self): return self.rpc.call('get_info') ## # Check whether the server requires authentication. Returns # the name of the protocol. # @defreturn string def getAuthProtocol(self): return self.rpc.call('getAuthProtocol') ## # Return id of the last committed transaction # @defreturn string def lastTransaction(self): # Not in protocol version 2.0.0; see __init__() return self.rpc.call('lastTransaction') or z64 ## # Return invalidations for all transactions after tid. # @param tid transaction id # @defreturn 2-tuple, (tid, list) # @return tuple containing the last committed transaction # and a list of oids that were invalidated. Returns # None and an empty list if the server does not have # the list of oids available. def getInvalidations(self, tid): # Not in protocol version 2.0.0; see __init__() return self.rpc.call('getInvalidations', tid) ## # Check whether a serial number is current for oid. # If the serial number is not current, the # server will make an asynchronous invalidateVerify() call. # @param oid object id # @param s serial number # @defreturn async def zeoVerify(self, oid, s): self.rpc.callAsync('zeoVerify', oid, s) ## # Check whether current serial number is valid for oid. # If the serial number is not current, the server will make an # asynchronous invalidateVerify() call. # @param oid object id # @param serial client's current serial number # @defreturn async def verify(self, oid, serial): self.rpc.callAsync('verify', oid, serial) ## # Signal to the server that cache verification is done. # @defreturn async def endZeoVerify(self): self.rpc.callAsync('endZeoVerify') ## # Generate a new set of oids. # @param n number of new oids to return # @defreturn list # @return list of oids def new_oids(self, n=None): if n is None: return self.rpc.call('new_oids') else: return self.rpc.call('new_oids', n) ## # Pack the storage. # @param t pack time # @param wait optional, boolean. If true, the call will not # return until the pack is complete. def pack(self, t, wait=None): if wait is None: self.rpc.call('pack', t) else: self.rpc.call('pack', t, wait) ## # Return current data for oid. # @param oid object id # @defreturn 2-tuple # @return 2-tuple, current non-version data, serial number # @exception KeyError if oid is not found def zeoLoad(self, oid): return self.rpc.call('zeoLoad', oid)[:2] ## # Return current data for oid, and the tid of the # transaction that wrote the most recent revision. # @param oid object id # @defreturn 2-tuple # @return data, transaction id # @exception KeyError if oid is not found def loadEx(self, oid): return self.rpc.call("loadEx", oid) ## # Return non-current data along with transaction ids that identify # the lifetime of the specific revision. # @param oid object id # @param tid a transaction id that provides an upper bound on # the lifetime of the revision. That is, loadBefore # returns the revision that was current before tid committed. # @defreturn 4-tuple # @return data, serial numbr, start transaction id, end transaction id def loadBefore(self, oid, tid): return self.rpc.call("loadBefore", oid, tid) ## # Storage new revision of oid. # @param oid object id # @param serial serial number that this transaction read # @param data new data record for oid # @param id id of current transaction # @defreturn async def storea(self, oid, serial, data, id): self.rpc.callAsync('storea', oid, serial, data, id) def checkCurrentSerialInTransaction(self, oid, serial, id): self.rpc.callAsync('checkCurrentSerialInTransaction', oid, serial, id) def restorea(self, oid, serial, data, prev_txn, id): self.rpc.callAsync('restorea', oid, serial, data, prev_txn, id) def storeBlob(self, oid, serial, data, blobfilename, txn): # Store a blob to the server. We don't want to real all of # the data into memory, so we use a message iterator. This # allows us to read the blob data as needed. def store(): yield ('storeBlobStart', ()) f = open(blobfilename, 'rb') while 1: chunk = f.read(59000) if not chunk: break yield ('storeBlobChunk', (chunk, )) f.close() yield ('storeBlobEnd', (oid, serial, data, id(txn))) self.rpc.callAsyncIterator(store()) def storeBlobShared(self, oid, serial, data, filename, id): self.rpc.callAsync('storeBlobShared', oid, serial, data, filename, id) def deleteObject(self, oid, serial, id): self.rpc.callAsync('deleteObject', oid, serial, id) ## # Start two-phase commit for a transaction # @param id id used by client to identify current transaction. The # only purpose of this argument is to distinguish among multiple # threads using a single ClientStorage. # @param user name of user committing transaction (can be "") # @param description string containing transaction metadata (can be "") # @param ext dictionary of extended metadata (?) # @param tid optional explicit tid to pass to underlying storage # @param status optional status character, e.g "p" for pack # @defreturn async def tpc_begin(self, id, user, descr, ext, tid, status): self.rpc.callAsync('tpc_begin', id, user, descr, ext, tid, status) def vote(self, trans_id): return self.rpc.call('vote', trans_id) def tpc_finish(self, id): return self.rpc.call('tpc_finish', id) def tpc_abort(self, id): self.rpc.call('tpc_abort', id) def history(self, oid, length=None): if length is None: return self.rpc.call('history', oid) else: return self.rpc.call('history', oid, length) def record_iternext(self, next): return self.rpc.call('record_iternext', next) def sendBlob(self, oid, serial): return self.rpc.call('sendBlob', oid, serial) def getTid(self, oid): return self.rpc.call('getTid', oid) def loadSerial(self, oid, serial): return self.rpc.call('loadSerial', oid, serial) def new_oid(self): return self.rpc.call('new_oid') def undoa(self, trans_id, trans): self.rpc.callAsync('undoa', trans_id, trans) def undoLog(self, first, last): return self.rpc.call('undoLog', first, last) def undoInfo(self, first, last, spec): return self.rpc.call('undoInfo', first, last, spec) def iterator_start(self, start, stop): return self.rpc.call('iterator_start', start, stop) def iterator_next(self, iid): return self.rpc.call('iterator_next', iid) def iterator_record_start(self, txn_iid, tid): return self.rpc.call('iterator_record_start', txn_iid, tid) def iterator_record_next(self, iid): return self.rpc.call('iterator_record_next', iid) def iterator_gc(self, iids): return self.rpc.callAsync('iterator_gc', iids) def server_status(self): return self.rpc.call("server_status") def set_client_label(self, label): return self.rpc.callAsync('set_client_label', label) class StorageServer308(StorageServer): def __init__(self, rpc): if rpc.peer_protocol_version == 'Z200': self.lastTransaction = lambda: z64 self.getInvalidations = lambda tid: None self.getAuthProtocol = lambda: None StorageServer.__init__(self, rpc) def history(self, oid, length=None): if length is None: return self.rpc.call('history', oid, '') else: return self.rpc.call('history', oid, '', length) def getInvalidations(self, tid): # Not in protocol version 2.0.0; see __init__() result = self.rpc.call('getInvalidations', tid) if result is not None: result = result[0], [oid for (oid, version) in result[1]] return result def verify(self, oid, serial): self.rpc.callAsync('verify', oid, '', serial) def loadEx(self, oid): return self.rpc.call("loadEx", oid, '')[:2] def storea(self, oid, serial, data, id): self.rpc.callAsync('storea', oid, serial, data, '', id) def storeBlob(self, oid, serial, data, blobfilename, txn): # Store a blob to the server. We don't want to real all of # the data into memory, so we use a message iterator. This # allows us to read the blob data as needed. def store(): yield ('storeBlobStart', ()) f = open(blobfilename, 'rb') while 1: chunk = f.read(59000) if not chunk: break yield ('storeBlobChunk', (chunk, )) f.close() yield ('storeBlobEnd', (oid, serial, data, '', id(txn))) self.rpc.callAsyncIterator(store()) def storeBlobShared(self, oid, serial, data, filename, id): self.rpc.callAsync('storeBlobShared', oid, serial, data, filename, '', id) def zeoVerify(self, oid, s): self.rpc.callAsync('zeoVerify', oid, s, None) def iterator_start(self, start, stop): raise NotImplementedError def iterator_next(self, iid): raise NotImplementedError def iterator_record_start(self, txn_iid, tid): raise NotImplementedError def iterator_record_next(self, iid): raise NotImplementedError def iterator_gc(self, iids): raise NotImplementedError def stub(client, connection): start = time.time() # Wait until we know what version the other side is using. while connection.peer_protocol_version is None: if time.time()-start > 10: raise ValueError("Timeout waiting for protocol handshake") time.sleep(0.1) if connection.peer_protocol_version < 'Z309': return StorageServer308(connection) return StorageServer(connection) class ExtensionMethodWrapper: def __init__(self, rpc, name): self.rpc = rpc self.name = name def call(self, *a, **kwa): return self.rpc.call(self.name, *a, **kwa) zope2.13-2.13.21/source/ZODB3/src/ZEO/interfaces.py0000644000175000017500000000347312214017464020211 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import zope.interface class StaleCache(object): """A ZEO cache is stale and requires verification. """ def __init__(self, storage): self.storage = storage class IServeable(zope.interface.Interface): """Interface provided by storages that can be served by ZEO """ def getTid(oid): """The last transaction to change an object Return the transaction id of the last transaction that committed a change to an object with the given object id. """ def tpc_transaction(): """The current transaction being committed. If a storage is participating in a two-phase commit, then return the transaction (object) being committed. Otherwise return None. """ def lastInvalidations(size): """Get recent transaction invalidations This method is optional and is used to get invalidations performed by the most recent transactions. An iterable of up to size entries must be returned, where each entry is a transaction id and a sequence of object-id/empty-string pairs describing the objects written by the transaction, in chronological order. """ zope2.13-2.13.21/source/ZODB3/src/ZEO/zeopasswd.py0000644000175000017500000001030512214017464020075 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Update a user's authentication tokens for a ZEO server. usage: python zeopasswd.py [options] username [password] Specify either a configuration file: -C/--configuration -- ZConfig configuration file or the individual options: -f/--filename -- authentication database filename -p/--protocol -- authentication protocol name -r/--realm -- authentication database realm Additional options: -d/--delete -- delete user instead of updating password """ import getopt import getpass import sys import os import ZConfig import ZEO def usage(msg): print __doc__ print msg sys.exit(2) def options(args): """Password-specific options loaded from regular ZEO config file.""" try: opts, args = getopt.getopt(args, "dr:p:f:C:", ["configure=", "protocol=", "filename=", "realm"]) except getopt.error, msg: usage(msg) config = None delete = 0 auth_protocol = None auth_db = "" auth_realm = None for k, v in opts: if k == '-C' or k == '--configure': schemafile = os.path.join(os.path.dirname(ZEO.__file__), "schema.xml") schema = ZConfig.loadSchema(schemafile) config, nil = ZConfig.loadConfig(schema, v) if k == '-d' or k == '--delete': delete = 1 if k == '-p' or k == '--protocol': auth_protocol = v if k == '-f' or k == '--filename': auth_db = v if k == '-r' or k == '--realm': auth_realm = v if config is not None: if auth_protocol or auth_db: usage("Error: Conflicting options; use either -C *or* -p and -f") auth_protocol = config.zeo.authentication_protocol auth_db = config.zeo.authentication_database auth_realm = config.zeo.authentication_realm elif not (auth_protocol and auth_db): usage("Error: Must specifiy configuration file or protocol and database") password = None if delete: if not args: usage("Error: Must specify a username to delete") elif len(args) > 1: usage("Error: Too many arguments") username = args[0] else: if not args: usage("Error: Must specify a username") elif len(args) > 2: usage("Error: Too many arguments") elif len(args) == 1: username = args[0] else: username, password = args return auth_protocol, auth_db, auth_realm, delete, username, password def main(args=None, dbclass=None): if args is None: args = sys.argv[1:] p, auth_db, auth_realm, delete, username, password = options(args) if p is None: usage("Error: configuration does not specify auth protocol") if p == "digest": from ZEO.auth.auth_digest import DigestDatabase as Database elif p == "srp": from ZEO.auth.auth_srp import SRPDatabase as Database elif dbclass: # dbclass is used for testing tests.auth_plaintext, see testAuth.py Database = dbclass else: raise ValueError("Unknown database type %r" % p) if auth_db is None: usage("Error: configuration does not specify auth database") db = Database(auth_db, auth_realm) if delete: db.del_user(username) else: if password is None: password = getpass.getpass("Enter password: ") db.add_user(username, password) db.save() zope2.13-2.13.21/source/ZODB3/src/ZEO/__init__.py0000644000175000017500000000606712214017464017627 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ZEO -- Zope Enterprise Objects. See the file README.txt in this directory for an overview. ZEO is now part of ZODB; ZODB's home on the web is http://wiki.zope.org/ZODB """ def DB(*args, **kw): import ZEO.ClientStorage, ZODB return ZODB.DB(ZEO.ClientStorage.ClientStorage(*args, **kw)) def connection(*args, **kw): db = DB(*args, **kw) conn = db.open() conn.onCloseCallback(db.close) return conn def client(*args, **kw): import ZEO.ClientStorage return ZEO.ClientStorage.ClientStorage(*args, **kw) def server(path=None, blob_dir=None, storage_conf=None, zeo_conf=None, port=None): """Convenience function to start a server for interactive exploration This fuction starts a ZEO server, given a storage configuration or a file-storage path and blob directory. You can also supply a ZEO configuration string or a port. If neither a ZEO port or configuration is supplied, a port is chosen randomly. The server address and a stop function are returned. The address can be passed to ZEO.ClientStorage.ClientStorage or ZEO.DB to create a client to the server. The stop function can be called without arguments to stop the server. Arguments: path A file-storage path. This argument is ignored if a storage configuration is supplied. blob_dir A blob directory path. This argument is ignored if a storage configuration is supplied. storage_conf A storage configuration string. If none is supplied, then at least a file-storage path must be supplied and the storage configuration will be generated from the file-storage path and the blob directory. zeo_conf A ZEO server configuration string. port If no ZEO configuration is supplied, the one will be computed from the port. If no port is supplied, one will be chosedn randomly. """ import os, ZEO.tests.forker if storage_conf is None and path is None: storage_conf = '\n' if port is None and zeo_conf is None: port = ZEO.tests.forker.get_port() addr, admin, pid, config = ZEO.tests.forker.start_zeo_server( storage_conf, zeo_conf, port, keep=True, path=path, blob_dir=blob_dir, suicide=False) os.remove(config) def stop_server(): ZEO.tests.forker.shutdown_zeo_server(admin) os.waitpid(pid, 0) return addr, stop_server zope2.13-2.13.21/source/ZODB3/src/ZEO/schema.xml0000644000175000017500000000223112214017464017465 0ustar arnauarnau This schema describes the configuration of the ZEO storage server process.

One or more storages that are provided by the ZEO server. The section names are used as the storage names, and must be unique within each ZEO storage server. Traditionally, these names represent small integers starting at '1'.
zope2.13-2.13.21/source/ZODB3/src/ZEO/runzeo.py0000644000175000017500000003344212214017464017407 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Start the ZEO storage server. Usage: %s [-C URL] [-a ADDRESS] [-f FILENAME] [-h] Options: -C/--configuration URL -- configuration file or URL -a/--address ADDRESS -- server address of the form PORT, HOST:PORT, or PATH (a PATH must contain at least one "/") -f/--filename FILENAME -- filename for FileStorage -t/--timeout TIMEOUT -- transaction timeout in seconds (default no timeout) -h/--help -- print this usage message and exit -m/--monitor ADDRESS -- address of monitor server ([HOST:]PORT or PATH) --pid-file PATH -- relative path to output file containing this process's pid; default $(INSTANCE_HOME)/var/ZEO.pid but only if envar INSTANCE_HOME is defined Unless -C is specified, -a and -f are required. """ # The code here is designed to be reused by other, similar servers. # For the forseeable future, it must work under Python 2.1 as well as # 2.2 and above. import asyncore import os import sys import signal import socket import logging import ZConfig.datatypes import ZEO from zdaemon.zdoptions import ZDOptions logger = logging.getLogger('ZEO.runzeo') _pid = str(os.getpid()) def log(msg, level=logging.INFO, exc_info=False): """Internal: generic logging function.""" message = "(%s) %s" % (_pid, msg) logger.log(level, message, exc_info=exc_info) def parse_binding_address(arg): # Caution: Not part of the official ZConfig API. obj = ZConfig.datatypes.SocketBindingAddress(arg) return obj.family, obj.address def windows_shutdown_handler(): # Called by the signal mechanism on Windows to perform shutdown. import asyncore asyncore.close_all() class ZEOOptionsMixin: storages = None def handle_address(self, arg): self.family, self.address = parse_binding_address(arg) def handle_monitor_address(self, arg): self.monitor_family, self.monitor_address = parse_binding_address(arg) def handle_filename(self, arg): from ZODB.config import FileStorage # That's a FileStorage *opener*! class FSConfig: def __init__(self, name, path): self._name = name self.path = path self.stop = None def getSectionName(self): return self._name if not self.storages: self.storages = [] name = str(1 + len(self.storages)) conf = FileStorage(FSConfig(name, arg)) self.storages.append(conf) testing_exit_immediately = False def handle_test(self, *args): self.testing_exit_immediately = True def add_zeo_options(self): self.add(None, None, None, "test", self.handle_test) self.add(None, None, "a:", "address=", self.handle_address) self.add(None, None, "f:", "filename=", self.handle_filename) self.add("family", "zeo.address.family") self.add("address", "zeo.address.address", required="no server address specified; use -a or -C") self.add("read_only", "zeo.read_only", default=0) self.add("invalidation_queue_size", "zeo.invalidation_queue_size", default=100) self.add("invalidation_age", "zeo.invalidation_age") self.add("transaction_timeout", "zeo.transaction_timeout", "t:", "timeout=", float) self.add("monitor_address", "zeo.monitor_address.address", "m:", "monitor=", self.handle_monitor_address) self.add('auth_protocol', 'zeo.authentication_protocol', None, 'auth-protocol=', default=None) self.add('auth_database', 'zeo.authentication_database', None, 'auth-database=') self.add('auth_realm', 'zeo.authentication_realm', None, 'auth-realm=') self.add('pid_file', 'zeo.pid_filename', None, 'pid-file=') class ZEOOptions(ZDOptions, ZEOOptionsMixin): __doc__ = __doc__ logsectionname = "eventlog" schemadir = os.path.dirname(ZEO.__file__) def __init__(self): ZDOptions.__init__(self) self.add_zeo_options() self.add("storages", "storages", required="no storages specified; use -f or -C") def realize(self, *a, **k): ZDOptions.realize(self, *a, **k) nunnamed = [s for s in self.storages if s.name is None] if nunnamed: if len(nunnamed) > 1: return self.usage("No more than one storage may be unnamed.") if [s for s in self.storages if s.name == '1']: return self.usage( "Can't have an unnamed storage and a storage named 1.") for s in self.storages: if s.name is None: s.name = '1' break class ZEOServer: def __init__(self, options): self.options = options def main(self): self.setup_default_logging() self.check_socket() self.clear_socket() self.make_pidfile() try: self.open_storages() self.setup_signals() self.create_server() self.loop_forever() finally: self.close_storages() self.clear_socket() self.remove_pidfile() def setup_default_logging(self): if self.options.config_logger is not None: return # No log file is configured; default to stderr. root = logging.getLogger() root.setLevel(logging.INFO) fmt = logging.Formatter( "------\n%(asctime)s %(levelname)s %(name)s %(message)s", "%Y-%m-%dT%H:%M:%S") handler = logging.StreamHandler() handler.setFormatter(fmt) root.addHandler(handler) def check_socket(self): if self.can_connect(self.options.family, self.options.address): self.options.usage("address %s already in use" % repr(self.options.address)) def can_connect(self, family, address): s = socket.socket(family, socket.SOCK_STREAM) try: s.connect(address) except socket.error: return 0 else: s.close() return 1 def clear_socket(self): if isinstance(self.options.address, type("")): try: os.unlink(self.options.address) except os.error: pass def open_storages(self): self.storages = {} for opener in self.options.storages: log("opening storage %r using %s" % (opener.name, opener.__class__.__name__)) self.storages[opener.name] = opener.open() def setup_signals(self): """Set up signal handlers. The signal handler for SIGFOO is a method handle_sigfoo(). If no handler method is defined for a signal, the signal action is not changed from its initial value. The handler method is called without additional arguments. """ if os.name != "posix": if os.name == "nt": self.setup_win32_signals() return if hasattr(signal, 'SIGXFSZ'): signal.signal(signal.SIGXFSZ, signal.SIG_IGN) # Special case init_signames() for sig, name in signames.items(): method = getattr(self, "handle_" + name.lower(), None) if method is not None: def wrapper(sig_dummy, frame_dummy, method=method): method() signal.signal(sig, wrapper) def setup_win32_signals(self): # Borrow the Zope Signals package win32 support, if available. # Signals does a check/log for the availability of pywin32. try: import Signals.Signals except ImportError: logger.debug("Signals package not found. " "Windows-specific signal handler " "will *not* be installed.") return SignalHandler = Signals.Signals.SignalHandler if SignalHandler is not None: # may be None if no pywin32. SignalHandler.registerHandler(signal.SIGTERM, windows_shutdown_handler) SignalHandler.registerHandler(signal.SIGINT, windows_shutdown_handler) SIGUSR2 = 12 # not in signal module on Windows. SignalHandler.registerHandler(SIGUSR2, self.handle_sigusr2) def create_server(self): self.server = create_server(self.storages, self.options) def loop_forever(self): if self.options.testing_exit_immediately: print "testing exit immediately" else: asyncore.loop() def handle_sigterm(self): log("terminated by SIGTERM") sys.exit(0) def handle_sigint(self): log("terminated by SIGINT") sys.exit(0) def handle_sighup(self): log("restarted by SIGHUP") sys.exit(1) def handle_sigusr2(self): # log rotation signal - do the same as Zope 2.7/2.8... if self.options.config_logger is None or os.name not in ("posix", "nt"): log("received SIGUSR2, but it was not handled!", level=logging.WARNING) return loggers = [self.options.config_logger] if os.name == "posix": for l in loggers: l.reopen() log("Log files reopened successfully", level=logging.INFO) else: # nt - same rotation code as in Zope's Signals/Signals.py for l in loggers: for f in l.handler_factories: handler = f() if hasattr(handler, 'rotate') and callable(handler.rotate): handler.rotate() log("Log files rotation complete", level=logging.INFO) def close_storages(self): for name, storage in self.storages.items(): log("closing storage %r" % name) try: storage.close() except: # Keep going log("failed to close storage %r" % name, level=logging.ERROR, exc_info=True) def _get_pidfile(self): pidfile = self.options.pid_file # 'pidfile' is marked as not required. if not pidfile: # Try to find a reasonable location if the pidfile is not # set. If we are running in a Zope environment, we can # safely assume INSTANCE_HOME. instance_home = os.environ.get("INSTANCE_HOME") if not instance_home: # If all our attempts failed, just log a message and # proceed. logger.debug("'pidfile' option not set, and 'INSTANCE_HOME' " "environment variable could not be found. " "Cannot guess pidfile location.") return self.options.pid_file = os.path.join(instance_home, "var", "ZEO.pid") def make_pidfile(self): if not self.options.read_only: self._get_pidfile() pidfile = self.options.pid_file if pidfile is None: return pid = os.getpid() try: if os.path.exists(pidfile): os.unlink(pidfile) f = open(pidfile, 'w') print >> f, pid f.close() log("created PID file '%s'" % pidfile) except IOError: logger.error("PID file '%s' cannot be opened" % pidfile) def remove_pidfile(self): if not self.options.read_only: pidfile = self.options.pid_file if pidfile is None: return try: if os.path.exists(pidfile): os.unlink(pidfile) log("removed PID file '%s'" % pidfile) except IOError: logger.error("PID file '%s' could not be removed" % pidfile) def create_server(storages, options): from ZEO.StorageServer import StorageServer return StorageServer( options.address, storages, read_only = options.read_only, invalidation_queue_size = options.invalidation_queue_size, invalidation_age = options.invalidation_age, transaction_timeout = options.transaction_timeout, monitor_address = options.monitor_address, auth_protocol = options.auth_protocol, auth_database = options.auth_database, auth_realm = options.auth_realm, ) # Signal names signames = None def signame(sig): """Return a symbolic name for a signal. Return "signal NNN" if there is no corresponding SIG name in the signal module. """ if signames is None: init_signames() return signames.get(sig) or "signal %d" % sig def init_signames(): global signames signames = {} for name, sig in signal.__dict__.items(): k_startswith = getattr(name, "startswith", None) if k_startswith is None: continue if k_startswith("SIG") and not k_startswith("SIG_"): signames[sig] = name # Main program def main(args=None): options = ZEOOptions() options.realize(args) s = ZEOServer(options) s.main() if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZEO/zeoctl.py0000644000175000017500000000200212214017464017351 0ustar arnauarnau#!/usr/bin/env python2.3 ############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Wrapper script for zdctl.py that causes it to use the ZEO schema.""" import os import ZEO import zdaemon.zdctl # Main program def main(args=None): options = zdaemon.zdctl.ZDCtlOptions() options.schemadir = os.path.dirname(ZEO.__file__) options.schemafile = "zeoctl.xml" zdaemon.zdctl.main(args, options) if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZEO/zeoctl.xml0000644000175000017500000000150512214017464017530 0ustar arnauarnau This schema describes the configuration of the ZEO storage server controller. It differs from the schema for the storage server only in that the "runner" section is required.
zope2.13-2.13.21/source/ZODB3/src/ZEO/hash.py0000644000175000017500000000170412214017464017004 0ustar arnauarnau############################################################################## # # Copyright (c) 2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """In Python 2.6, the "sha" and "md5" modules have been deprecated in favor of using hashlib for both. This class allows for compatibility between versions.""" try: import hashlib sha1 = hashlib.sha1 new = sha1 except ImportError: import sha sha1 = sha.new new = sha1 digest_size = sha.digest_size zope2.13-2.13.21/source/ZODB3/src/ZEO/TransactionBuffer.py0000644000175000017500000001165512214017464021506 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """A TransactionBuffer store transaction updates until commit or abort. A transaction may generate enough data that it is not practical to always hold pending updates in memory. Instead, a TransactionBuffer is used to store the data until a commit or abort. """ # A faster implementation might store trans data in memory until it # reaches a certain size. from threading import Lock import os import cPickle import tempfile import ZODB.blob class TransactionBuffer: # Valid call sequences: # # ((store | invalidate)* begin_iterate next* clear)* close # # get_size can be called any time # The TransactionBuffer is used by client storage to hold update # data until the tpc_finish(). It is normally used by a single # thread, because only one thread can be in the two-phase commit # at one time. # It is possible, however, for one thread to close the storage # while another thread is in the two-phase commit. We must use # a lock to guard against this race, because unpredictable things # can happen in Python if one thread closes a file that another # thread is reading. In a debug build, an assert() can fail. # Caution: If an operation is performed on a closed TransactionBuffer, # it has no effect and does not raise an exception. The only time # this should occur is when a ClientStorage is closed in one # thread while another thread is in its tpc_finish(). It's not # clear what should happen in this case. If the tpc_finish() # completes without error, the Connection using it could have # inconsistent data. This should have minimal effect, though, # because the Connection is connected to a closed storage. def __init__(self): self.file = tempfile.TemporaryFile(suffix=".tbuf") self.lock = Lock() self.closed = 0 self.count = 0 self.size = 0 self.blobs = [] # It's safe to use a fast pickler because the only objects # stored are builtin types -- strings or None. self.pickler = cPickle.Pickler(self.file, 1) self.pickler.fast = 1 def close(self): self.clear() self.lock.acquire() try: self.closed = 1 try: self.file.close() except OSError: pass finally: self.lock.release() def store(self, oid, data): """Store oid, version, data for later retrieval""" self.lock.acquire() try: if self.closed: return self.pickler.dump((oid, data)) self.count += 1 # Estimate per-record cache size self.size = self.size + (data and len(data) or 0) + 31 finally: self.lock.release() def storeBlob(self, oid, blobfilename): self.blobs.append((oid, blobfilename)) def invalidate(self, oid): self.lock.acquire() try: if self.closed: return self.pickler.dump((oid, None)) self.count += 1 finally: self.lock.release() def clear(self): """Mark the buffer as empty""" self.lock.acquire() try: if self.closed: return self.file.seek(0) self.count = 0 self.size = 0 while self.blobs: oid, blobfilename = self.blobs.pop() if os.path.exists(blobfilename): ZODB.blob.remove_committed(blobfilename) finally: self.lock.release() def __iter__(self): self.lock.acquire() try: if self.closed: return self.file.flush() self.file.seek(0) return TBIterator(self.file, self.count) finally: self.lock.release() class TBIterator(object): def __init__(self, f, count): self.file = f self.count = count self.unpickler = cPickle.Unpickler(f) def __iter__(self): return self def next(self): """Return next tuple of data or None if EOF""" if self.count == 0: self.file.seek(0) self.size = 0 raise StopIteration oid_ver_data = self.unpickler.load() self.count -= 1 return oid_ver_data zope2.13-2.13.21/source/ZODB3/src/ZEO/Exceptions.py0000644000175000017500000000214112214017464020176 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Exceptions for ZEO.""" from ZODB.POSException import StorageError class ClientStorageError(StorageError): """An error occurred in the ZEO Client Storage.""" class UnrecognizedResult(ClientStorageError): """A server call returned an unrecognized result.""" class ClientDisconnected(ClientStorageError): """The database storage is disconnected from the storage.""" class AuthError(StorageError): """The client provided invalid authentication credentials.""" zope2.13-2.13.21/source/ZODB3/src/ZEO/util.py0000644000175000017500000000350612214017464017040 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Utilities for setting up the server environment.""" import os def parentdir(p, n=1): """Return the ancestor of p from n levels up.""" d = p while n: d = os.path.dirname(d) if not d or d == '.': d = os.getcwd() n -= 1 return d class Environment: """Determine location of the Data.fs & ZEO_SERVER.pid files. Pass the argv[0] used to start ZEO to the constructor. Use the zeo_pid and fs attributes to get the filenames. """ def __init__(self, argv0): v = os.environ.get("INSTANCE_HOME") if v is None: # looking for a Zope/var directory assuming that this code # is installed in Zope/lib/python/ZEO p = parentdir(argv0, 4) if os.path.isdir(os.path.join(p, "var")): v = p else: v = os.getcwd() self.home = v self.var = os.path.join(v, "var") if not os.path.isdir(self.var): self.var = self.home pid = os.environ.get("ZEO_SERVER_PID") if pid is None: pid = os.path.join(self.var, "ZEO_SERVER.pid") self.zeo_pid = pid self.fs = os.path.join(self.var, "Data.fs") zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/0000755000175000017500000000000012214017464016647 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZEO/tests/testConnection.py0000644000175000017500000002130412214017464022220 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Test setup for ZEO connection logic. The actual tests are in ConnectionTests.py; this file provides the platform-dependent scaffolding. """ from __future__ import with_statement from ZEO.tests import ConnectionTests, InvalidationTests from zope.testing import setupstack import os if os.environ.get('USE_ZOPE_TESTING_DOCTEST'): from zope.testing import doctest else: import doctest import unittest import ZEO.tests.forker import ZEO.tests.testMonitor import ZEO.zrpc.connection import ZODB.tests.util class FileStorageConfig: def getConfig(self, path, create, read_only): return """\ path %s create %s read-only %s """ % (path, create and 'yes' or 'no', read_only and 'yes' or 'no') class MappingStorageConfig: def getConfig(self, path, create, read_only): return """""" class FileStorageConnectionTests( FileStorageConfig, ConnectionTests.ConnectionTests, InvalidationTests.InvalidationTests ): """FileStorage-specific connection tests.""" class FileStorageReconnectionTests( FileStorageConfig, ConnectionTests.ReconnectionTests, ): """FileStorage-specific re-connection tests.""" # Run this at level 1 because MappingStorage can't do reconnection tests class FileStorageInvqTests( FileStorageConfig, ConnectionTests.InvqTests ): """FileStorage-specific invalidation queue tests.""" class FileStorageTimeoutTests( FileStorageConfig, ConnectionTests.TimeoutTests ): pass class MappingStorageConnectionTests( MappingStorageConfig, ConnectionTests.ConnectionTests ): """Mapping storage connection tests.""" # The ReconnectionTests can't work with MappingStorage because it's only an # in-memory storage and has no persistent state. class MappingStorageTimeoutTests( MappingStorageConfig, ConnectionTests.TimeoutTests ): pass class MonitorTests(ZEO.tests.testMonitor.MonitorTests): def check_connection_management(self): # Open and close a few connections, making sure that # the resulting number of clients is 0. s1 = self.openClientStorage() s2 = self.openClientStorage() s3 = self.openClientStorage() stats = self.parse(self.get_monitor_output())[1] self.assertEqual(stats.clients, 3) s1.close() s3.close() s2.close() ZEO.tests.forker.wait_until( "Number of clients shown in monitor drops to 0", lambda : self.parse(self.get_monitor_output())[1].clients == 0 ) def check_connection_management_with_old_client(self): # Check that connection management works even when using an # older protcool that requires a connection adapter. test_protocol = "Z303" current_protocol = ZEO.zrpc.connection.Connection.current_protocol ZEO.zrpc.connection.Connection.current_protocol = test_protocol ZEO.zrpc.connection.Connection.servers_we_can_talk_to.append( test_protocol) try: self.check_connection_management() finally: ZEO.zrpc.connection.Connection.current_protocol = current_protocol ZEO.zrpc.connection.Connection.servers_we_can_talk_to.pop() test_classes = [FileStorageConnectionTests, FileStorageReconnectionTests, FileStorageInvqTests, FileStorageTimeoutTests, MappingStorageConnectionTests, MappingStorageTimeoutTests, MonitorTests, ] def invalidations_while_connecting(): r""" As soon as a client registers with a server, it will recieve invalidations from the server. The client must be careful to queue these invalidations until it is ready to deal with them. At the time of the writing of this test, clients weren't careful enough about queing invalidations. This led to cache corruption in the form of both low-level file corruption as well as out-of-date records marked as current. This tests tries to provoke this bug by: - starting a server >>> addr, _ = start_server() - opening a client to the server that writes some objects, filling it's cache at the same time, >>> import ZODB.tests.MinPO, transaction >>> db = ZEO.DB(addr, client='x') >>> conn = db.open() >>> nobs = 1000 >>> for i in range(nobs): ... conn.root()[i] = ZODB.tests.MinPO.MinPO(0) >>> transaction.commit() >>> import zope.testing.loggingsupport, logging >>> handler = zope.testing.loggingsupport.InstalledHandler( ... 'ZEO', level=logging.INFO) # >>> logging.getLogger('ZEO').debug( # ... 'Initial tid %r' % conn.root()._p_serial) - disconnecting the first client (closing it with a persistent cache), >>> db.close() - starting a second client that writes objects more or less constantly, >>> import random, threading, time >>> stop = False >>> db2 = ZEO.DB(addr) >>> tm = transaction.TransactionManager() >>> conn2 = db2.open(transaction_manager=tm) >>> random = random.Random(0) >>> lock = threading.Lock() >>> def run(): ... while 1: ... i = random.randint(0, nobs-1) ... if stop: ... return ... with lock: ... conn2.root()[i].value += 1 ... tm.commit() ... #logging.getLogger('ZEO').debug( ... # 'COMMIT %s %s %r' % ( ... # i, conn2.root()[i].value, conn2.root()[i]._p_serial)) ... time.sleep(0) >>> thread = threading.Thread(target=run) >>> thread.setDaemon(True) >>> thread.start() - restarting the first client, and - testing for cache validity. >>> bad = False >>> try: ... for c in range(10): ... time.sleep(.1) ... db = ZODB.DB(ZEO.ClientStorage.ClientStorage(addr, client='x')) ... with lock: ... #logging.getLogger('ZEO').debug('Locked %s' % c) ... @wait_until("connected and we have caught up", timeout=199) ... def _(): ... if (db.storage.is_connected() ... and db.storage.lastTransaction() ... == db.storage._server.lastTransaction() ... ): ... #logging.getLogger('ZEO').debug( ... # 'Connected %r' % db.storage.lastTransaction()) ... return True ... ... conn = db.open() ... for i in range(1000): ... if conn.root()[i].value != conn2.root()[i].value: ... print 'bad', c, i, conn.root()[i].value, ... print conn2.root()[i].value ... bad = True ... print 'client debug log with lock held' ... while handler.records: ... record = handler.records.pop(0) ... print record.name, record.levelname, ... print handler.format(record) ... if bad: ... print open('server-%s.log' % addr[1]).read() ... #else: ... # logging.getLogger('ZEO').debug('GOOD %s' % c) ... db.close() ... finally: ... stop = True ... thread.join(10) >>> thread.isAlive() False >>> for record in handler.records: ... if record.levelno < logging.ERROR: ... continue ... print record.name, record.levelname ... print handler.format(record) >>> handler.uninstall() >>> db.close() >>> db2.close() """ def test_suite(): suite = unittest.TestSuite() for klass in test_classes: sub = unittest.makeSuite(klass, 'check') suite.addTest(sub) suite.addTest(doctest.DocTestSuite( setUp=ZEO.tests.forker.setUp, tearDown=setupstack.tearDown, )) suite.layer = ZODB.tests.util.MininalTestLayer('ZEO Connection Tests') return suite zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/zdoptions.test0000644000175000017500000000662112214017464021606 0ustar arnauarnauMinimal test of Server Options Handling ======================================= This is initially motivated by a desire to remove the requirement of specifying a storage name when there is only one storage. Storage Names ------------- It is an error not to specify any storages: >>> import StringIO, sys, ZEO.runzeo >>> stderr = sys.stderr >>> open('config', 'w').write(""" ... ... address 8100 ... ... """) >>> sys.stderr = StringIO.StringIO() >>> options = ZEO.runzeo.ZEOOptions() >>> options.realize('-C config'.split()) Traceback (most recent call last): ... SystemExit: 2 >>> print sys.stderr.getvalue() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE Error: not enough values for section type 'zodb.storage'; 0 found, 1 required ... But we can specify a storage without a name: >>> open('config', 'w').write(""" ... ... address 8100 ... ... ... ... """) >>> options = ZEO.runzeo.ZEOOptions() >>> options.realize('-C config'.split()) >>> [storage.name for storage in options.storages] ['1'] We can't have multiple unnamed storages: >>> sys.stderr = StringIO.StringIO() >>> open('config', 'w').write(""" ... ... address 8100 ... ... ... ... ... ... """) >>> options = ZEO.runzeo.ZEOOptions() >>> options.realize('-C config'.split()) Traceback (most recent call last): ... SystemExit: 2 >>> print sys.stderr.getvalue() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE Error: No more than one storage may be unnamed. ... Or an unnamed storage and one named '1': >>> sys.stderr = StringIO.StringIO() >>> open('config', 'w').write(""" ... ... address 8100 ... ... ... ... ... ... """) >>> options = ZEO.runzeo.ZEOOptions() >>> options.realize('-C config'.split()) Traceback (most recent call last): ... SystemExit: 2 >>> print sys.stderr.getvalue() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE Error: Can't have an unnamed storage and a storage named 1. ... But we can have multiple storages: >>> open('config', 'w').write(""" ... ... address 8100 ... ... ... ... ... ... """) >>> options = ZEO.runzeo.ZEOOptions() >>> options.realize('-C config'.split()) >>> [storage.name for storage in options.storages] ['x', 'y'] As long as the names are unique: >>> sys.stderr = StringIO.StringIO() >>> open('config', 'w').write(""" ... ... address 8100 ... ... ... ... ... ... """) >>> options = ZEO.runzeo.ZEOOptions() >>> options.realize('-C config'.split()) Traceback (most recent call last): ... SystemExit: 2 >>> print sys.stderr.getvalue() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE Error: section names must not be re-used within the same container:'1' ... .. Cleanup ===================================================== >>> sys.stderr = stderr zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/speed.py0000644000175000017500000001377012214017464020331 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## usage="""Test speed of a ZODB storage Options: -d file The data file to use as input. The default is this script. -n n The number of repititions -s module A module that defines a 'Storage' attribute, which is an open storage. If not specified, a FileStorage will ne used. -z Test compressing data -D Run in debug mode -L Test loads as well as stores by minimizing the cache after eachrun -M Output means only -C Run with a persistent client cache -U Run ZEO using a Unix domain socket -t n Number of concurrent threads to run. """ import asyncore import sys, os, getopt, time ##sys.path.insert(0, os.getcwd()) import persistent import transaction import ZODB from ZODB.POSException import ConflictError from ZEO.tests import forker class P(persistent.Persistent): pass fs_name = "zeo-speed.fs" class ZEOExit(asyncore.file_dispatcher): """Used to exit ZEO.StorageServer when run is done""" def writable(self): return 0 def readable(self): return 1 def handle_read(self): buf = self.recv(4) assert buf == "done" self.delete_fs() os._exit(0) def handle_close(self): print "Parent process exited unexpectedly" self.delete_fs() os._exit(0) def delete_fs(self): os.unlink(fs_name) os.unlink(fs_name + ".lock") os.unlink(fs_name + ".tmp") def work(db, results, nrep, compress, data, detailed, minimize, threadno=None): for j in range(nrep): for r in 1, 10, 100, 1000: t = time.time() conflicts = 0 jar = db.open() while 1: try: transaction.begin() rt = jar.root() key = 's%s' % r if rt.has_key(key): p = rt[key] else: rt[key] = p =P() for i in range(r): v = getattr(p, str(i), P()) if compress is not None: v.d = compress(data) else: v.d = data setattr(p, str(i), v) transaction.commit() except ConflictError: conflicts = conflicts + 1 else: break jar.close() t = time.time() - t if detailed: if threadno is None: print "%s\t%s\t%.4f\t%d" % (j, r, t, conflicts) else: print "%s\t%s\t%.4f\t%d\t%d" % (j, r, t, conflicts, threadno) results[r].append((t, conflicts)) rt=d=p=v=None # release all references if minimize: time.sleep(3) jar.cacheMinimize() def main(args): opts, args = getopt.getopt(args, 'zd:n:Ds:LMt:U') s = None compress = None data=sys.argv[0] nrep=5 minimize=0 detailed=1 cache = None domain = 'AF_INET' threads = 1 for o, v in opts: if o=='-n': nrep = int(v) elif o=='-d': data = v elif o=='-s': s = v elif o=='-z': import zlib compress = zlib.compress elif o=='-L': minimize=1 elif o=='-M': detailed=0 elif o=='-D': global debug os.environ['STUPID_LOG_FILE']='' os.environ['STUPID_LOG_SEVERITY']='-999' debug = 1 elif o == '-C': cache = 'speed' elif o == '-U': domain = 'AF_UNIX' elif o == '-t': threads = int(v) zeo_pipe = None if s: s = __import__(s, globals(), globals(), ('__doc__',)) s = s.Storage server = None else: s, server, pid = forker.start_zeo("FileStorage", (fs_name, 1), domain=domain) data=open(data).read() db=ZODB.DB(s, # disable cache deactivation cache_size=4000, cache_deactivate_after=6000,) print "Beginning work..." results={1:[], 10:[], 100:[], 1000:[]} if threads > 1: import threading l = [] for i in range(threads): t = threading.Thread(target=work, args=(db, results, nrep, compress, data, detailed, minimize, i)) l.append(t) for t in l: t.start() for t in l: t.join() else: work(db, results, nrep, compress, data, detailed, minimize) if server is not None: server.close() os.waitpid(pid, 0) if detailed: print '-'*24 print "num\tmean\tmin\tmax" for r in 1, 10, 100, 1000: times = [] for time, conf in results[r]: times.append(time) t = mean(times) print "%d\t%.4f\t%.4f\t%.4f" % (r, t, min(times), max(times)) def mean(l): tot = 0 for v in l: tot = tot + v return tot / len(l) ##def compress(s): ## c = zlib.compressobj() ## o = c.compress(s) ## return o + c.flush() if __name__=='__main__': main(sys.argv[1:]) zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/testZEO.py0000644000175000017500000015422412214017464020566 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Test suite for ZEO based on ZODB.tests.""" from ZEO.ClientStorage import ClientStorage from ZEO.tests.forker import get_port from ZEO.tests import forker, Cache, CommitLockTests, ThreadTests from ZEO.tests import IterationTests from ZEO.zrpc.error import DisconnectedError from ZODB.tests import StorageTestBase, BasicStorage, \ TransactionalUndoStorage, \ PackableStorage, Synchronization, ConflictResolution, RevisionStorage, \ MTStorage, ReadOnlyStorage, IteratorStorage, RecoveryStorage from ZODB.tests.MinPO import MinPO from ZODB.tests.StorageTestBase import zodb_unpickle from ZODB.utils import p64, u64 from zope.testing import renormalizing import doctest import logging import os import persistent import re import shutil import signal import stat import sys import tempfile import threading import time import transaction import unittest import ZEO.ServerStub import ZEO.StorageServer import ZEO.tests.ConnectionTests import ZEO.zrpc.connection import ZODB import ZODB.blob import ZODB.tests.hexstorage import ZODB.tests.testblob import ZODB.tests.util import ZODB.utils import zope.testing.setupstack logger = logging.getLogger('ZEO.tests.testZEO') class DummyDB: def invalidate(self, *args): pass def invalidateCache(*unused): pass transform_record_data = untransform_record_data = lambda self, v: v class CreativeGetState(persistent.Persistent): def __getstate__(self): self.name = 'me' return super(CreativeGetState, self).__getstate__() class MiscZEOTests: """ZEO tests that don't fit in elsewhere.""" def checkCreativeGetState(self): # This test covers persistent objects that provide their own # __getstate__ which modifies the state of the object. # For details see bug #98275 db = ZODB.DB(self._storage) cn = db.open() rt = cn.root() m = CreativeGetState() m.attr = 'hi' rt['a'] = m # This commit used to fail because of the `Mine` object being put back # into `changed` state although it was already stored causing the ZEO # cache to bail out. transaction.commit() cn.close() def checkLargeUpdate(self): obj = MinPO("X" * (10 * 128 * 1024)) self._dostore(data=obj) def checkZEOInvalidation(self): addr = self._storage._addr storage2 = self._wrap_client( ClientStorage(addr, wait=1, min_disconnect_poll=0.1)) try: oid = self._storage.new_oid() ob = MinPO('first') revid1 = self._dostore(oid, data=ob) data, serial = storage2.load(oid, '') self.assertEqual(zodb_unpickle(data), MinPO('first')) self.assertEqual(serial, revid1) revid2 = self._dostore(oid, data=MinPO('second'), revid=revid1) # Now, storage 2 should eventually get the new data. It # will take some time, although hopefully not much. # We'll poll till we get it and whine if we time out: for n in range(30): time.sleep(.1) data, serial = storage2.load(oid, '') if (serial == revid2 and zodb_unpickle(data) == MinPO('second') ): break else: raise AssertionError('Invalidation message was not sent!') finally: storage2.close() def checkVolatileCacheWithImmediateLastTransaction(self): # Earlier, a ClientStorage would not have the last transaction id # available right after successful connection, this is required now. addr = self._storage._addr storage2 = ClientStorage(addr) self.assert_(storage2.is_connected()) self.assertEquals(ZODB.utils.z64, storage2.lastTransaction()) storage2.close() self._dostore() storage3 = ClientStorage(addr) self.assert_(storage3.is_connected()) self.assertEquals(8, len(storage3.lastTransaction())) self.assertNotEquals(ZODB.utils.z64, storage3.lastTransaction()) storage3.close() class ConfigurationTests(unittest.TestCase): def checkDropCacheRatherVerifyConfiguration(self): from ZODB.config import storageFromString # the default is to do verification and not drop the cache cs = storageFromString(''' server localhost:9090 wait false ''') self.assertEqual(cs._drop_cache_rather_verify, False) cs.close() # now for dropping cs = storageFromString(''' server localhost:9090 wait false drop-cache-rather-verify true ''') self.assertEqual(cs._drop_cache_rather_verify, True) cs.close() class GenericTests( # Base class for all ZODB tests StorageTestBase.StorageTestBase, # ZODB test mixin classes (in the same order as imported) BasicStorage.BasicStorage, PackableStorage.PackableStorage, Synchronization.SynchronizedStorage, MTStorage.MTStorage, ReadOnlyStorage.ReadOnlyStorage, # ZEO test mixin classes (in the same order as imported) CommitLockTests.CommitLockVoteTests, ThreadTests.ThreadTests, # Locally defined (see above) MiscZEOTests, ): """Combine tests from various origins in one class.""" shared_blob_dir = False blob_cache_dir = None def setUp(self): StorageTestBase.StorageTestBase.setUp(self) logger.info("setUp() %s", self.id()) port = get_port(self) zconf = forker.ZEOConfig(('', port)) zport, adminaddr, pid, path = forker.start_zeo_server(self.getConfig(), zconf, port) self._pids = [pid] self._servers = [adminaddr] self._conf_path = path if not self.blob_cache_dir: # This is the blob cache for ClientStorage self.blob_cache_dir = tempfile.mkdtemp( 'blob_cache', dir=os.path.abspath(os.getcwd())) self._storage = self._wrap_client(ClientStorage( zport, '1', cache_size=20000000, min_disconnect_poll=0.5, wait=1, wait_timeout=60, blob_dir=self.blob_cache_dir, shared_blob_dir=self.shared_blob_dir)) self._storage.registerDB(DummyDB()) def _wrap_client(self, client): return client def tearDown(self): self._storage.close() for server in self._servers: forker.shutdown_zeo_server(server) if hasattr(os, 'waitpid'): # Not in Windows Python until 2.3 for pid in self._pids: os.waitpid(pid, 0) StorageTestBase.StorageTestBase.tearDown(self) def runTest(self): try: super(GenericTests, self).runTest() except: self._failed = True raise else: self._failed = False def open(self, read_only=0): # Needed to support ReadOnlyStorage tests. Ought to be a # cleaner way. addr = self._storage._addr self._storage.close() self._storage = ClientStorage(addr, read_only=read_only, wait=1) def checkWriteMethods(self): # ReadOnlyStorage defines checkWriteMethods. The decision # about where to raise the read-only error was changed after # Zope 2.5 was released. So this test needs to detect Zope # of the 2.5 vintage and skip the test. # The __version__ attribute was not present in Zope 2.5. if hasattr(ZODB, "__version__"): ReadOnlyStorage.ReadOnlyStorage.checkWriteMethods(self) def checkSortKey(self): key = '%s:%s' % (self._storage._storage, self._storage._server_addr) self.assertEqual(self._storage.sortKey(), key) def _do_store_in_separate_thread(self, oid, revid, voted): def do_store(): store = ZEO.ClientStorage.ClientStorage(self._storage._addr) try: t = transaction.get() store.tpc_begin(t) store.store(oid, revid, 'x', '', t) store.tpc_vote(t) store.tpc_finish(t) except Exception, v: import traceback print 'E'*70 print v traceback.print_exception(*sys.exc_info()) finally: store.close() thread = threading.Thread(name='T2', target=do_store) thread.setDaemon(True) thread.start() thread.join(voted and .1 or 9) return thread class FullGenericTests( GenericTests, Cache.TransUndoStorageWithCache, ConflictResolution.ConflictResolvingStorage, ConflictResolution.ConflictResolvingTransUndoStorage, PackableStorage.PackableUndoStorage, RevisionStorage.RevisionStorage, TransactionalUndoStorage.TransactionalUndoStorage, IteratorStorage.IteratorStorage, IterationTests.IterationTests, ): """Extend GenericTests with tests that MappingStorage can't pass.""" class FileStorageRecoveryTests(StorageTestBase.StorageTestBase, RecoveryStorage.RecoveryStorage): def getConfig(self): return """\ path %s """ % tempfile.mktemp(dir='.') def _new_storage(self): port = get_port(self) zconf = forker.ZEOConfig(('', port)) zport, adminaddr, pid, path = forker.start_zeo_server(self.getConfig(), zconf, port) self._pids.append(pid) self._servers.append(adminaddr) blob_cache_dir = tempfile.mkdtemp(dir='.') storage = ClientStorage( zport, '1', cache_size=20000000, min_disconnect_poll=0.5, wait=1, wait_timeout=60, blob_dir=blob_cache_dir) storage.registerDB(DummyDB()) return storage def setUp(self): StorageTestBase.StorageTestBase.setUp(self) self._pids = [] self._servers = [] self._storage = self._new_storage() self._dst = self._new_storage() def tearDown(self): self._storage.close() self._dst.close() for server in self._servers: forker.shutdown_zeo_server(server) if hasattr(os, 'waitpid'): # Not in Windows Python until 2.3 for pid in self._pids: os.waitpid(pid, 0) StorageTestBase.StorageTestBase.tearDown(self) def new_dest(self): return self._new_storage() class FileStorageTests(FullGenericTests): """Test ZEO backed by a FileStorage.""" def getConfig(self): return """\ path Data.fs """ _expected_interfaces = ( ('ZODB.interfaces', 'IStorageRestoreable'), ('ZODB.interfaces', 'IStorageIteration'), ('ZODB.interfaces', 'IStorageUndoable'), ('ZODB.interfaces', 'IStorageCurrentRecordIteration'), ('ZODB.interfaces', 'IExternalGC'), ('ZODB.interfaces', 'IStorage'), ('zope.interface', 'Interface'), ) def checkInterfaceFromRemoteStorage(self): # ClientStorage itself doesn't implement IStorageIteration, but the # FileStorage on the other end does, and thus the ClientStorage # instance that is connected to it reflects this. self.failIf(ZODB.interfaces.IStorageIteration.implementedBy( ZEO.ClientStorage.ClientStorage)) self.failUnless(ZODB.interfaces.IStorageIteration.providedBy( self._storage)) # This is communicated using ClientStorage's _info object: self.assertEquals(self._expected_interfaces, self._storage._info['interfaces'] ) class FileStorageHexTests(FileStorageTests): _expected_interfaces = ( ('ZODB.interfaces', 'IStorageRestoreable'), ('ZODB.interfaces', 'IStorageIteration'), ('ZODB.interfaces', 'IStorageUndoable'), ('ZODB.interfaces', 'IStorageCurrentRecordIteration'), ('ZODB.interfaces', 'IExternalGC'), ('ZODB.interfaces', 'IStorage'), ('ZODB.interfaces', 'IStorageWrapper'), ('zope.interface', 'Interface'), ) def getConfig(self): return """\ %import ZODB.tests path Data.fs """ class FileStorageClientHexTests(FileStorageHexTests): def getConfig(self): return """\ %import ZODB.tests path Data.fs """ def _wrap_client(self, client): return ZODB.tests.hexstorage.HexStorage(client) class MappingStorageTests(GenericTests): """ZEO backed by a Mapping storage.""" def getConfig(self): return """""" def checkSimpleIteration(self): # The test base class IteratorStorage assumes that we keep undo data # to construct our iterator, which we don't, so we disable this test. pass def checkUndoZombie(self): # The test base class IteratorStorage assumes that we keep undo data # to construct our iterator, which we don't, so we disable this test. pass class DemoStorageTests( GenericTests, ): def getConfig(self): return """ path Data.fs """ def checkUndoZombie(self): # The test base class IteratorStorage assumes that we keep undo data # to construct our iterator, which we don't, so we disable this test. pass def checkPackWithMultiDatabaseReferences(self): pass # DemoStorage pack doesn't do gc checkPackAllRevisions = checkPackWithMultiDatabaseReferences class HeartbeatTests(ZEO.tests.ConnectionTests.CommonSetupTearDown): """Make sure a heartbeat is being sent and that it does no harm This is really hard to test properly because we can't see the data flow between the client and server and we can't really tell what's going on in the server very well. :( """ def setUp(self): # Crank down the select frequency self.__old_client_timeout = ZEO.zrpc.client.client_timeout ZEO.zrpc.client.client_timeout = self.__client_timeout ZEO.tests.ConnectionTests.CommonSetupTearDown.setUp(self) __client_timeouts = 0 def __client_timeout(self): self.__client_timeouts += 1 return .1 def tearDown(self): ZEO.zrpc.client.client_timeout = self.__old_client_timeout ZEO.tests.ConnectionTests.CommonSetupTearDown.tearDown(self) def getConfig(self, path, create, read_only): return """""" def checkHeartbeatWithServerClose(self): # This is a minimal test that mainly tests that the heartbeat # function does no harm. self._storage = self.openClientStorage() client_timeouts = self.__client_timeouts forker.wait_until('got a timeout', lambda : self.__client_timeouts > client_timeouts ) self._dostore() if hasattr(os, 'kill') and hasattr(signal, 'SIGKILL'): # Kill server violently, in hopes of provoking problem os.kill(self._pids[0], signal.SIGKILL) self._servers[0] = None else: self.shutdownServer() forker.wait_until('disconnected', lambda : not self._storage.is_connected() ) self._storage.close() class ZRPCConnectionTests(ZEO.tests.ConnectionTests.CommonSetupTearDown): def getConfig(self, path, create, read_only): return """""" def checkCatastrophicClientLoopFailure(self): # Test what happens when the client loop falls over self._storage = self.openClientStorage() class Evil: def writable(self): raise SystemError("I'm evil") import zope.testing.loggingsupport handler = zope.testing.loggingsupport.InstalledHandler( 'ZEO.zrpc.client') self._storage._rpc_mgr.map[None] = Evil() try: self._storage._rpc_mgr.trigger.pull_trigger() except DisconnectedError: pass forker.wait_until( 'disconnected', lambda : not self._storage.is_connected() ) log = str(handler) handler.uninstall() self.assert_("ZEO client loop failed" in log) self.assert_("Couldn't close a dispatcher." in log) def checkExceptionLogsAtError(self): # Test the exceptions are logged at error self._storage = self.openClientStorage() conn = self._storage._connection # capture logging log = [] conn.logger.log = ( lambda l, m, *a, **kw: log.append((l,m % a, kw)) ) # This is a deliberately bogus call to get an exception # logged self._storage._connection.handle_request( 'foo', 0, 'history', (1, 2, 3, 4)) # test logging for level, message, kw in log: if message.endswith( ') history() raised exception: history() takes at' ' most 3 arguments (5 given)' ): self.assertEqual(level,logging.ERROR) self.assertEqual(kw,{'exc_info':True}) break else: self.fail("error not in log") # cleanup del conn.logger.log def checkConnectionInvalidationOnReconnect(self): storage = ClientStorage(self.addr, wait=1, min_disconnect_poll=0.1) self._storage = storage # and we'll wait for the storage to be reconnected: for i in range(100): if storage.is_connected(): break time.sleep(0.1) else: raise AssertionError("Couldn't connect to server") class DummyDB: _invalidatedCache = 0 def invalidateCache(self): self._invalidatedCache += 1 def invalidate(*a, **k): pass db = DummyDB() storage.registerDB(db) base = db._invalidatedCache # Now we'll force a disconnection and reconnection storage._connection.close() # and we'll wait for the storage to be reconnected: for i in range(100): if storage.is_connected(): break time.sleep(0.1) else: raise AssertionError("Couldn't connect to server") # Now, the root object in the connection should have been invalidated: self.assertEqual(db._invalidatedCache, base+1) class CommonBlobTests: def getConfig(self): return """ blob-dir blobs path Data.fs """ blobdir = 'blobs' blob_cache_dir = 'blob_cache' def checkStoreBlob(self): from ZODB.utils import oid_repr, tid_repr from ZODB.blob import Blob, BLOB_SUFFIX from ZODB.tests.StorageTestBase import zodb_pickle, ZERO, \ handle_serials import transaction somedata = 'a' * 10 blob = Blob() bd_fh = blob.open('w') bd_fh.write(somedata) bd_fh.close() tfname = bd_fh.name oid = self._storage.new_oid() data = zodb_pickle(blob) self.assert_(os.path.exists(tfname)) t = transaction.Transaction() try: self._storage.tpc_begin(t) r1 = self._storage.storeBlob(oid, ZERO, data, tfname, '', t) r2 = self._storage.tpc_vote(t) revid = handle_serials(oid, r1, r2) self._storage.tpc_finish(t) except: self._storage.tpc_abort(t) raise self.assert_(not os.path.exists(tfname)) filename = self._storage.fshelper.getBlobFilename(oid, revid) self.assert_(os.path.exists(filename)) self.assertEqual(somedata, open(filename).read()) def checkStoreBlob_wrong_partition(self): os_rename = os.rename try: def fail(*a): raise OSError os.rename = fail self.checkStoreBlob() finally: os.rename = os_rename def checkLoadBlob(self): from ZODB.blob import Blob from ZODB.tests.StorageTestBase import zodb_pickle, ZERO, \ handle_serials import transaction somedata = 'a' * 10 blob = Blob() bd_fh = blob.open('w') bd_fh.write(somedata) bd_fh.close() tfname = bd_fh.name oid = self._storage.new_oid() data = zodb_pickle(blob) t = transaction.Transaction() try: self._storage.tpc_begin(t) r1 = self._storage.storeBlob(oid, ZERO, data, tfname, '', t) r2 = self._storage.tpc_vote(t) serial = handle_serials(oid, r1, r2) self._storage.tpc_finish(t) except: self._storage.tpc_abort(t) raise filename = self._storage.loadBlob(oid, serial) self.assertEquals(somedata, open(filename, 'rb').read()) self.assert_(not(os.stat(filename).st_mode & stat.S_IWRITE)) self.assert_((os.stat(filename).st_mode & stat.S_IREAD)) def checkTemporaryDirectory(self): self.assertEquals(os.path.join(self.blob_cache_dir, 'tmp'), self._storage.temporaryDirectory()) def checkTransactionBufferCleanup(self): oid = self._storage.new_oid() open('blob_file', 'w').write('I am a happy blob.') t = transaction.Transaction() self._storage.tpc_begin(t) self._storage.storeBlob( oid, ZODB.utils.z64, 'foo', 'blob_file', '', t) self._storage.close() class BlobAdaptedFileStorageTests(FullGenericTests, CommonBlobTests): """ZEO backed by a BlobStorage-adapted FileStorage.""" def checkStoreAndLoadBlob(self): from ZODB.utils import oid_repr, tid_repr from ZODB.blob import Blob, BLOB_SUFFIX from ZODB.tests.StorageTestBase import zodb_pickle, ZERO, \ handle_serials import transaction somedata_path = os.path.join(self.blob_cache_dir, 'somedata') somedata = open(somedata_path, 'w+b') for i in range(1000000): somedata.write("%s\n" % i) somedata.seek(0) blob = Blob() bd_fh = blob.open('w') ZODB.utils.cp(somedata, bd_fh) bd_fh.close() tfname = bd_fh.name oid = self._storage.new_oid() data = zodb_pickle(blob) self.assert_(os.path.exists(tfname)) t = transaction.Transaction() try: self._storage.tpc_begin(t) r1 = self._storage.storeBlob(oid, ZERO, data, tfname, '', t) r2 = self._storage.tpc_vote(t) revid = handle_serials(oid, r1, r2) self._storage.tpc_finish(t) except: self._storage.tpc_abort(t) raise # The uncommitted data file should have been removed self.assert_(not os.path.exists(tfname)) def check_data(path): self.assert_(os.path.exists(path)) f = open(path, 'rb') somedata.seek(0) d1 = d2 = 1 while d1 or d2: d1 = f.read(8096) d2 = somedata.read(8096) self.assertEqual(d1, d2) # The file should be in the cache ... filename = self._storage.fshelper.getBlobFilename(oid, revid) check_data(filename) # ... and on the server server_filename = os.path.join( self.blobdir, ZODB.blob.BushyLayout().getBlobFilePath(oid, revid), ) self.assert_(server_filename.startswith(self.blobdir)) check_data(server_filename) # If we remove it from the cache and call loadBlob, it should # come back. We can do this in many threads. We'll instrument # the method that is used to request data from teh server to # verify that it is only called once. sendBlob_org = ZEO.ServerStub.StorageServer.sendBlob calls = [] def sendBlob(self, oid, serial): calls.append((oid, serial)) sendBlob_org(self, oid, serial) ZODB.blob.remove_committed(filename) returns = [] threads = [ threading.Thread( target=lambda : returns.append(self._storage.loadBlob(oid, revid)) ) for i in range(10) ] [thread.start() for thread in threads] [thread.join() for thread in threads] [self.assertEqual(r, filename) for r in returns] check_data(filename) class BlobWritableCacheTests(FullGenericTests, CommonBlobTests): blob_cache_dir = 'blobs' shared_blob_dir = True class FauxConn: addr = 'x' peer_protocol_version = ZEO.zrpc.connection.Connection.current_protocol class StorageServerClientWrapper: def __init__(self): self.serials = [] def serialnos(self, serials): self.serials.extend(serials) def info(self, info): pass class StorageServerWrapper: def __init__(self, server, storage_id): self.storage_id = storage_id self.server = ZEO.StorageServer.ZEOStorage(server, server.read_only) self.server.notifyConnected(FauxConn()) self.server.register(storage_id, False) self.server.client = StorageServerClientWrapper() def sortKey(self): return self.storage_id def __getattr__(self, name): return getattr(self.server, name) def registerDB(self, *args): pass def supportsUndo(self): return False def new_oid(self): return self.server.new_oids(1)[0] def tpc_begin(self, transaction): self.server.tpc_begin(id(transaction), '', '', {}, None, ' ') def tpc_vote(self, transaction): vote_result = self.server.vote(id(transaction)) assert vote_result is None result = self.server.client.serials[:] del self.server.client.serials[:] return result def store(self, oid, serial, data, version_ignored, transaction): self.server.storea(oid, serial, data, id(transaction)) def send_reply(self, *args): # Masquerade as conn pass def tpc_abort(self, transaction): self.server.tpc_abort(id(transaction)) def tpc_finish(self, transaction, func = lambda: None): self.server.tpc_finish(id(transaction)).set_sender(0, self) def multiple_storages_invalidation_queue_is_not_insane(): """ >>> from ZEO.StorageServer import StorageServer, ZEOStorage >>> from ZODB.FileStorage import FileStorage >>> from ZODB.DB import DB >>> from persistent.mapping import PersistentMapping >>> from transaction import commit >>> fs1 = FileStorage('t1.fs') >>> fs2 = FileStorage('t2.fs') >>> server = StorageServer(('', get_port()), dict(fs1=fs1, fs2=fs2)) >>> s1 = StorageServerWrapper(server, 'fs1') >>> s2 = StorageServerWrapper(server, 'fs2') >>> db1 = DB(s1); conn1 = db1.open() >>> db2 = DB(s2); conn2 = db2.open() >>> commit() >>> o1 = conn1.root() >>> for i in range(10): ... o1.x = PersistentMapping(); o1 = o1.x ... commit() >>> last = fs1.lastTransaction() >>> for i in range(5): ... o1.x = PersistentMapping(); o1 = o1.x ... commit() >>> o2 = conn2.root() >>> for i in range(20): ... o2.x = PersistentMapping(); o2 = o2.x ... commit() >>> trans, oids = s1.getInvalidations(last) >>> from ZODB.utils import u64 >>> sorted([int(u64(oid)) for oid in oids]) [10, 11, 12, 13, 14] >>> server.close_server() """ def getInvalidationsAfterServerRestart(): """ Clients were often forced to verify their caches after a server restart even if there weren't many transactions between the server restart and the client connect. Let's create a file storage and stuff some data into it: >>> from ZEO.StorageServer import StorageServer, ZEOStorage >>> from ZODB.FileStorage import FileStorage >>> from ZODB.DB import DB >>> from persistent.mapping import PersistentMapping >>> fs = FileStorage('t.fs') >>> db = DB(fs) >>> conn = db.open() >>> from transaction import commit >>> last = [] >>> for i in range(100): ... conn.root()[i] = PersistentMapping() ... commit() ... last.append(fs.lastTransaction()) >>> db.close() Now we'll open a storage server on the data, simulating a restart: >>> fs = FileStorage('t.fs') >>> sv = StorageServer(('', get_port()), dict(fs=fs)) >>> s = ZEOStorage(sv, sv.read_only) >>> s.notifyConnected(FauxConn()) >>> s.register('fs', False) If we ask for the last transaction, we should get the last transaction we saved: >>> s.lastTransaction() == last[-1] True If a storage implements the method lastInvalidations, as FileStorage does, then the stroage server will populate its invalidation data structure using lastTransactions. >>> tid, oids = s.getInvalidations(last[-10]) >>> tid == last[-1] True >>> from ZODB.utils import u64 >>> sorted([int(u64(oid)) for oid in oids]) [0, 92, 93, 94, 95, 96, 97, 98, 99, 100] (Note that the fact that we get oids for 92-100 is actually an artifact of the fact that the FileStorage lastInvalidations method returns all OIDs written by transactions, even if the OIDs were created and not modified. FileStorages don't record whether objects were created rather than modified. Objects that are just created don't need to be invalidated. This means we'll invalidate objects that dont' need to be invalidated, however, that's better than verifying caches.) >>> sv.close_server() >>> fs.close() If a storage doesn't implement lastInvalidations, a client can still avoid verifying its cache if it was up to date when the server restarted. To illustrate this, we'll create a subclass of FileStorage without this method: >>> class FS(FileStorage): ... lastInvalidations = property() >>> fs = FS('t.fs') >>> sv = StorageServer(('', get_port()), dict(fs=fs)) >>> st = StorageServerWrapper(sv, 'fs') >>> s = st.server Now, if we ask for the invalidations since the last committed transaction, we'll get a result: >>> tid, oids = s.getInvalidations(last[-1]) >>> tid == last[-1] True >>> oids [] >>> db = DB(st); conn = db.open() >>> ob = conn.root() >>> for i in range(5): ... ob.x = PersistentMapping(); ob = ob.x ... commit() ... last.append(fs.lastTransaction()) >>> ntid, oids = s.getInvalidations(tid) >>> ntid == last[-1] True >>> sorted([int(u64(oid)) for oid in oids]) [0, 101, 102, 103, 104] >>> fs.close() """ def tpc_finish_error(): r"""Server errors in tpc_finish weren't handled properly. >>> import ZEO.ClientStorage, ZEO.zrpc.connection >>> class Connection: ... peer_protocol_version = ( ... ZEO.zrpc.connection.Connection.current_protocol) ... def __init__(self, client): ... self.client = client ... def get_addr(self): ... return 'server' ... def is_async(self): ... return True ... def register_object(self, ob): ... pass ... def close(self): ... print 'connection closed' ... trigger = property(lambda self: self) ... pull_trigger = lambda self, func, *args: func(*args) >>> class ConnectionManager: ... def __init__(self, addr, client, tmin, tmax): ... self.client = client ... def connect(self, sync=1): ... self.client.notifyConnected(Connection(self.client)) ... def close(self): ... pass >>> class StorageServer: ... should_fail = True ... def __init__(self, conn): ... self.conn = conn ... self.t = None ... def get_info(self): ... return {} ... def endZeoVerify(self): ... self.conn.client.endVerify() ... def lastTransaction(self): ... return '\0'*8 ... def tpc_begin(self, t, *args): ... if self.t is not None: ... raise TypeError('already trans') ... self.t = t ... print 'begin', args ... def vote(self, t): ... if self.t != t: ... raise TypeError('bad trans') ... print 'vote' ... def tpc_finish(self, *args): ... if self.should_fail: ... raise TypeError() ... print 'finish' ... def tpc_abort(self, t): ... if self.t != t: ... raise TypeError('bad trans') ... self.t = None ... print 'abort' ... def iterator_gc(*args): ... pass >>> class ClientStorage(ZEO.ClientStorage.ClientStorage): ... ConnectionManagerClass = ConnectionManager ... StorageServerStubClass = StorageServer >>> class Transaction: ... user = 'test' ... description = '' ... _extension = {} >>> cs = ClientStorage(('', '')) >>> t1 = Transaction() >>> cs.tpc_begin(t1) begin ('test', '', {}, None, ' ') >>> cs.tpc_vote(t1) vote >>> cs.tpc_finish(t1) Traceback (most recent call last): ... TypeError >>> cs.tpc_abort(t1) abort >>> t2 = Transaction() >>> cs.tpc_begin(t2) begin ('test', '', {}, None, ' ') >>> cs.tpc_vote(t2) vote If client storage has an internal error after the storage finish succeeeds, it will close the connection, which will force a restart and reverification. >>> StorageServer.should_fail = False >>> cs._update_cache = lambda : None >>> try: cs.tpc_finish(t2) ... except: pass ... else: print "Should have failed" finish connection closed >>> cs.close() """ def client_has_newer_data_than_server(): """It is bad if a client has newer data than the server. >>> db = ZODB.DB('Data.fs') >>> db.close() >>> shutil.copyfile('Data.fs', 'Data.save') >>> addr, admin = start_server(keep=1) >>> db = ZEO.DB(addr, name='client', max_disconnect_poll=.01) >>> wait_connected(db.storage) >>> conn = db.open() >>> conn.root().x = 1 >>> transaction.commit() OK, we've added some data to the storage and the client cache has the new data. Now, we'll stop the server, put back the old data, and see what happens. :) >>> stop_server(admin) >>> shutil.copyfile('Data.save', 'Data.fs') >>> import zope.testing.loggingsupport >>> handler = zope.testing.loggingsupport.InstalledHandler( ... 'ZEO', level=logging.ERROR) >>> formatter = logging.Formatter('%(name)s %(levelname)s %(message)s') >>> _, admin = start_server(addr=addr) >>> for i in range(1000): ... while len(handler.records) < 5: ... time.sleep(.01) >>> db.close() >>> for record in handler.records[:5]: ... print formatter.format(record) ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE ZEO.ClientStorage CRITICAL client Client has seen newer transactions than server! ZEO.zrpc ERROR (...) CW: error in notifyConnected (('127.0.0.1', ...)) Traceback (most recent call last): ... ClientStorageError: client Client has seen newer transactions than server! ZEO.ClientStorage CRITICAL client Client has seen newer transactions than server! ZEO.zrpc ERROR (...) CW: error in notifyConnected (('127.0.0.1', ...)) Traceback (most recent call last): ... ClientStorageError: client Client has seen newer transactions than server! ... Note that the errors repeat because the client keeps on trying to connect. >>> handler.uninstall() >>> stop_server(admin) """ def history_over_zeo(): """ >>> addr, _ = start_server() >>> db = ZEO.DB(addr) >>> wait_connected(db.storage) >>> conn = db.open() >>> conn.root().x = 0 >>> transaction.commit() >>> len(db.history(conn.root()._p_oid, 99)) 2 >>> db.close() """ def dont_log_poskeyerrors_on_server(): """ >>> addr, admin = start_server() >>> cs = ClientStorage(addr) >>> cs.load(ZODB.utils.p64(1)) Traceback (most recent call last): ... POSKeyError: 0x01 >>> cs.close() >>> stop_server(admin) >>> 'POSKeyError' in open('server-%s.log' % addr[1]).read() False """ def open_convenience(): """Often, we just want to open a single connection. >>> addr, _ = start_server(path='data.fs') >>> conn = ZEO.connection(addr) >>> conn.root() {} >>> conn.root()['x'] = 1 >>> transaction.commit() >>> conn.close() Let's make sure the database was cloased when we closed the connection, and that the data is there. >>> db = ZEO.DB(addr) >>> conn = db.open() >>> conn.root() {'x': 1} >>> db.close() """ def client_asyncore_thread_has_name(): """ >>> addr, _ = start_server() >>> db = ZEO.DB(addr) >>> len([t for t in threading.enumerate() ... if ' zeo client networking thread' in t.getName()]) 1 >>> db.close() """ def runzeo_without_configfile(): """ >>> open('runzeo', 'w').write(''' ... import sys ... sys.path[:] = %r ... import ZEO.runzeo ... ZEO.runzeo.main(sys.argv[1:]) ... ''' % sys.path) >>> import subprocess, re >>> print re.sub('\d\d+|[:]', '', subprocess.Popen( ... [sys.executable, 'runzeo', '-a:%s' % get_port(), '-ft', '--test'], ... stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ... ).stdout.read()), # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE ------ --T INFO ZEO.runzeo () opening storage '1' using FileStorage ------ --T INFO ZEO.StorageServer StorageServer created RW with storages 1RWt ------ --T INFO ZEO.zrpc () listening on ... ------ --T INFO ZEO.runzeo () closing storage '1' testing exit immediately """ def close_client_storage_w_invalidations(): r""" Invalidations could cause errors when closing client storages, >>> addr, _ = start_server() >>> writing = threading.Event() >>> def mad_write_thread(): ... global writing ... conn = ZEO.connection(addr) ... writing.set() ... while writing.isSet(): ... conn.root.x = 1 ... transaction.commit() ... conn.close() >>> thread = threading.Thread(target=mad_write_thread) >>> thread.setDaemon(True) >>> thread.start() >>> _ = writing.wait() >>> time.sleep(.01) >>> for i in range(10): ... conn = ZEO.connection(addr) ... _ = conn._storage.load('\0'*8) ... conn.close() >>> writing.clear() >>> thread.join(1) """ def convenient_to_pass_port_to_client_and_ZEO_dot_client(): """Jim hates typing >>> addr, _ = start_server() >>> client = ZEO.client(addr[1]) >>> client.__name__ == "('127.0.0.1', %s)" % addr[1] True >>> client.close() """ def test_server_status(): """ You can get server status using the server_status method. >>> addr, _ = start_server(zeo_conf=dict(transaction_timeout=1)) >>> db = ZEO.DB(addr) >>> import pprint >>> pprint.pprint(db.storage.server_status(), width=1) {'aborts': 0, 'active_txns': 0, 'commits': 1, 'conflicts': 0, 'conflicts_resolved': 0, 'connections': 1, 'loads': 1, 'lock_time': None, 'start': 'Tue May 4 10:55:20 2010', 'stores': 1, 'timeout-thread-is-alive': True, 'verifying_clients': 0, 'waiting': 0} >>> db.close() """ def client_labels(): """ When looking at server logs, for servers with lots of clients coming from the same machine, it can be very difficult to correlate server log entries with actual clients. It's possible, sort of, but tedious. You can make this easier by passing a label to the ClientStorage constructor. >>> addr, _ = start_server() >>> db = ZEO.DB(addr, client_label='test-label-1') >>> db.close() >>> @wait_until ... def check_for_test_label_1(): ... for line in open('server-%s.log' % addr[1]): ... if 'test-label-1' in line: ... print line.split()[1:4] ... return True ['INFO', 'ZEO.StorageServer', '(test-label-1'] You can specify the client label via a configuration file as well: >>> import ZODB.config >>> db = ZODB.config.databaseFromString(''' ... ... ... server :%s ... client-label test-label-2 ... ... ... ''' % addr[1]) >>> db.close() >>> @wait_until ... def check_for_test_label_2(): ... for line in open('server-%s.log' % addr[1]): ... if 'test-label-2' in line: ... print line.split()[1:4] ... return True ['INFO', 'ZEO.StorageServer', '(test-label-2'] """ def invalidate_client_cache_entry_on_server_commit_error(): """ When the serials returned during commit includes an error, typically a conflict error, invalidate the cache entry. This is important when the cache is messed up. >>> addr, _ = start_server() >>> conn1 = ZEO.connection(addr) >>> conn1.root.x = conn1.root().__class__() >>> transaction.commit() >>> conn1.root.x {} >>> cs = ZEO.ClientStorage.ClientStorage(addr, client='cache') >>> conn2 = ZODB.connection(cs) >>> conn2.root.x {} >>> conn2.close() >>> cs.close() >>> conn1.root.x['x'] = 1 >>> transaction.commit() >>> conn1.root.x {'x': 1} Now, let's screw up the cache by making it have a last tid that is later than the root serial. >>> import ZEO.cache >>> cache = ZEO.cache.ClientCache('cache-1.zec') >>> cache.setLastTid(p64(u64(conn1.root.x._p_serial)+1)) >>> cache.close() We'll also update the server so that it's last tid is newer than the cache's: >>> conn1.root.y = 1 >>> transaction.commit() >>> conn1.root.y = 2 >>> transaction.commit() Now, if we reopen the client storage, we'll get the wrong root: >>> cs = ZEO.ClientStorage.ClientStorage(addr, client='cache') >>> conn2 = ZODB.connection(cs) >>> conn2.root.x {} And, we'll get a conflict error if we try to modify it: >>> conn2.root.x['y'] = 1 >>> transaction.commit() # doctest: +ELLIPSIS Traceback (most recent call last): ... ConflictError: ... But, if we abort, we'll get up to date data and we'll see the changes. >>> transaction.abort() >>> conn2.root.x {'x': 1} >>> conn2.root.x['y'] = 1 >>> transaction.commit() >>> sorted(conn2.root.x.items()) [('x', 1), ('y', 1)] >>> cs.close() >>> conn1.close() """ script_template = """ import sys sys.path[:] = %(path)r %(src)s """ def generate_script(name, src): open(name, 'w').write(script_template % dict( exe=sys.executable, path=sys.path, src=src, )) def runzeo_logrotate_on_sigusr2(): """ >>> port = get_port() >>> open('c', 'w').write(''' ... ... address %s ... ... ... ... ... ... path l ... ... ... ''' % port) >>> generate_script('s', ''' ... import ZEO.runzeo ... ZEO.runzeo.main() ... ''') >>> import subprocess, signal >>> p = subprocess.Popen([sys.executable, 's', '-Cc'], close_fds=True) >>> wait_until('started', ... lambda : os.path.exists('l') and ('listening on' in open('l').read()) ... ) >>> oldlog = open('l').read() >>> os.rename('l', 'o') >>> os.kill(p.pid, signal.SIGUSR2) >>> wait_until('new file', lambda : os.path.exists('l')) >>> s = ClientStorage(port) >>> s.close() >>> wait_until('See logging', lambda : ('Log files ' in open('l').read())) >>> open('o').read() == oldlog # No new data in old log True # Cleanup: >>> os.kill(p.pid, signal.SIGKILL) >>> _ = p.wait() """ def unix_domain_sockets(): """Make sure unix domain sockets work >>> addr, _ = start_server(port='./sock') >>> c = ZEO.connection(addr) >>> c.root.x = 1 >>> transaction.commit() >>> c.close() """ def gracefully_handle_abort_while_storing_many_blobs(): r""" >>> import logging, sys >>> old_level = logging.getLogger().getEffectiveLevel() >>> logging.getLogger().setLevel(logging.ERROR) >>> handler = logging.StreamHandler(sys.stdout) >>> logging.getLogger().addHandler(handler) >>> addr, _ = start_server(blob_dir='blobs') >>> c = ZEO.connection(addr, blob_dir='cblobs') >>> c.root.x = ZODB.blob.Blob('z'*(1<<20)) >>> c.root.y = ZODB.blob.Blob('z'*(1<<2)) >>> t = c.transaction_manager.get() >>> c.tpc_begin(t) >>> c.commit(t) We've called commit, but the blob sends are queued. We'll call abort right away, which will delete the temporary blob files. The queued iterators will try to open these files. >>> c.tpc_abort(t) Now we'll try to use the connection, mainly to wait for everything to get processed. Before we fixed this by making tpc_finish a synchronous call to the server. we'd get some sort of error here. >>> _ = c._storage._server.loadEx('\0'*8) >>> c.close() >>> logging.getLogger().removeHandler(handler) >>> logging.getLogger().setLevel(old_level) """ if sys.platform.startswith('win'): del runzeo_logrotate_on_sigusr2 del unix_domain_sockets if sys.version_info >= (2, 6): import multiprocessing def work_with_multiprocessing_process(name, addr, q): conn = ZEO.connection(addr) q.put((name, conn.root.x)) conn.close() class MultiprocessingTests(unittest.TestCase): layer = ZODB.tests.util.MininalTestLayer('work_with_multiprocessing') def test_work_with_multiprocessing(self): "Client storage should work with multi-processing." # Gaaa, zope.testing.runner.FakeInputContinueGenerator has no close if not hasattr(sys.stdin, 'close'): sys.stdin.close = lambda : None if not hasattr(sys.stdin, 'fileno'): sys.stdin.fileno = lambda : -1 self.globs = {} forker.setUp(self) addr, adminaddr = self.globs['start_server']() conn = ZEO.connection(addr) conn.root.x = 1 transaction.commit() q = multiprocessing.Queue() processes = [multiprocessing.Process( target=work_with_multiprocessing_process, args=(i, addr, q)) for i in range(3)] _ = [p.start() for p in processes] self.assertEqual(sorted(q.get(timeout=300) for p in processes), [(0, 1), (1, 1), (2, 1)]) _ = [p.join(30) for p in processes] conn.close() zope.testing.setupstack.tearDown(self) else: class MultiprocessingTests(unittest.TestCase): pass def quick_close_doesnt_kill_server(): r""" Start a server: >>> addr, _ = start_server() Now connect and immediately disconnect. This caused the server to die in the past: >>> import socket, struct >>> for i in range(5): ... s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ... s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, ... struct.pack('ii', 1, 0)) ... s.connect(addr) ... s.close() Now we should be able to connect as normal: >>> db = ZEO.DB(addr) >>> db.storage.is_connected() True >>> db.close() """ def sync_connect_doesnt_hang(): r""" >>> import threading >>> import ZEO.zrpc.client >>> ConnectThread = ZEO.zrpc.client.ConnectThread >>> ZEO.zrpc.client.ConnectThread = lambda *a, **kw: threading.Thread() >>> class CM(ZEO.zrpc.client.ConnectionManager): ... sync_wait = 1 ... _start_asyncore_loop = lambda self: None >>> cm = CM(('', 0), object()) Calling connect results in an exception being raised, instead of hanging indefinitely when the thread dies without setting up the connection. >>> cm.connect(sync=1) Traceback (most recent call last): ... AssertionError >>> cm.thread.isAlive() False >>> ZEO.zrpc.client.ConnectThread = ConnectThread """ def lp143344_extension_methods_not_lost_on_server_restart(): r""" Make sure we don't lose exension methods on server restart. >>> addr, adminaddr = start_server(keep=True) >>> conn = ZEO.connection(addr) >>> conn.root.x = 1 >>> transaction.commit() >>> conn.db().storage.answer_to_the_ultimate_question() 42 >>> stop_server(adminaddr) >>> wait_until('not connected', ... lambda : not conn.db().storage.is_connected()) >>> _ = start_server(addr=addr) >>> wait_until('connected', conn.db().storage.is_connected) >>> conn.root.x 1 >>> conn.db().storage.answer_to_the_ultimate_question() 42 >>> conn.close() """ def can_use_empty_string_for_local_host_on_client(): """We should be able to spell localhost with ''. >>> (_, port), _ = start_server() >>> conn = ZEO.connection(('', port)) >>> conn.root() {} >>> conn.root.x = 1 >>> transaction.commit() >>> conn.close() """ slow_test_classes = [ BlobAdaptedFileStorageTests, BlobWritableCacheTests, MappingStorageTests, DemoStorageTests, FileStorageTests, FileStorageHexTests, FileStorageClientHexTests, ] quick_test_classes = [ FileStorageRecoveryTests, ConfigurationTests, HeartbeatTests, ZRPCConnectionTests, ] class ServerManagingClientStorage(ClientStorage): class StorageServerStubClass(ZEO.ServerStub.StorageServer): # Wait for abort for the benefit of blob_transaction.txt def tpc_abort(self, id): self.rpc.call('tpc_abort', id) def __init__(self, name, blob_dir, shared=False, extrafsoptions=''): if shared: server_blob_dir = blob_dir else: server_blob_dir = 'server-'+blob_dir self.globs = {} port = forker.get_port2(self) addr, admin, pid, config = forker.start_zeo_server( """ blob-dir %s path %s %s """ % (server_blob_dir, name+'.fs', extrafsoptions), port=port, ) os.remove(config) zope.testing.setupstack.register(self, os.waitpid, pid, 0) zope.testing.setupstack.register( self, forker.shutdown_zeo_server, admin) if shared: ClientStorage.__init__(self, addr, blob_dir=blob_dir, shared_blob_dir=True) else: ClientStorage.__init__(self, addr, blob_dir=blob_dir) def close(self): ClientStorage.close(self) zope.testing.setupstack.tearDown(self) def create_storage_shared(name, blob_dir): return ServerManagingClientStorage(name, blob_dir, True) class ServerManagingClientStorageForIExternalGCTest( ServerManagingClientStorage): def pack(self, t=None, referencesf=None): ServerManagingClientStorage.pack(self, t, referencesf, wait=True) # Packing doesn't clear old versions out of zeo client caches, # so we'll clear the caches. self._cache.clear() ZEO.ClientStorage._check_blob_cache_size(self.blob_dir, 0) def test_suite(): suite = unittest.TestSuite() # Collect misc tests into their own layer to educe size of # unit test layer zeo = unittest.TestSuite() zeo.addTest(unittest.makeSuite(ZODB.tests.util.AAAA_Test_Runner_Hack)) zeo.addTest(doctest.DocTestSuite( setUp=forker.setUp, tearDown=zope.testing.setupstack.tearDown, checker=renormalizing.RENormalizing([ (re.compile(r"'start': '[^\n]+'"), 'start'), ]), )) zeo.addTest(doctest.DocTestSuite(ZEO.tests.IterationTests, setUp=forker.setUp, tearDown=zope.testing.setupstack.tearDown)) zeo.addTest(doctest.DocFileSuite('registerDB.test')) zeo.addTest( doctest.DocFileSuite( 'zeo-fan-out.test', 'zdoptions.test', 'drop_cache_rather_than_verify.txt', 'client-config.test', 'protocols.test', 'zeo_blob_cache.test', 'invalidation-age.txt', setUp=forker.setUp, tearDown=zope.testing.setupstack.tearDown, ), ) zeo.addTest(PackableStorage.IExternalGC_suite( lambda : ServerManagingClientStorageForIExternalGCTest( 'data.fs', 'blobs', extrafsoptions='pack-gc false') )) for klass in quick_test_classes: zeo.addTest(unittest.makeSuite(klass, "check")) zeo.layer = ZODB.tests.util.MininalTestLayer('testZeo-misc') suite.addTest(zeo) suite.addTest(unittest.makeSuite(MultiprocessingTests)) # Put the heavyweights in their own layers for klass in slow_test_classes: sub = unittest.makeSuite(klass, "check") sub.layer = ZODB.tests.util.MininalTestLayer(klass.__name__) suite.addTest(sub) suite.addTest(ZODB.tests.testblob.storage_reusable_suite( 'ClientStorageNonSharedBlobs', ServerManagingClientStorage)) suite.addTest(ZODB.tests.testblob.storage_reusable_suite( 'ClientStorageSharedBlobs', create_storage_shared)) return suite if __name__ == "__main__": unittest.main(defaultTest="test_suite") zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/ThreadTests.py0000644000175000017500000001202012214017464021446 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Compromising positions involving threads.""" import threading import transaction from ZODB.tests.StorageTestBase import zodb_pickle, MinPO import ZEO.ClientStorage ZERO = '\0'*8 class BasicThread(threading.Thread): def __init__(self, storage, doNextEvent, threadStartedEvent): self.storage = storage self.trans = transaction.Transaction() self.doNextEvent = doNextEvent self.threadStartedEvent = threadStartedEvent self.gotValueError = 0 self.gotDisconnected = 0 threading.Thread.__init__(self) self.setDaemon(1) def join(self): threading.Thread.join(self, 10) assert not self.isAlive() class GetsThroughVoteThread(BasicThread): # This thread gets partially through a transaction before it turns # execution over to another thread. We're trying to establish that a # tpc_finish() after a storage has been closed by another thread will get # a ClientStorageError error. # # This class gets does a tpc_begin(), store(), tpc_vote() and is waiting # to do the tpc_finish() when the other thread closes the storage. def run(self): self.storage.tpc_begin(self.trans) oid = self.storage.new_oid() self.storage.store(oid, ZERO, zodb_pickle(MinPO("c")), '', self.trans) self.storage.tpc_vote(self.trans) self.threadStartedEvent.set() self.doNextEvent.wait(10) try: self.storage.tpc_finish(self.trans) except ZEO.ClientStorage.ClientStorageError: self.gotValueError = 1 self.storage.tpc_abort(self.trans) class GetsThroughBeginThread(BasicThread): # This class is like the above except that it is intended to be run when # another thread is already in a tpc_begin(). Thus, this thread will # block in the tpc_begin until another thread closes the storage. When # that happens, this one will get disconnected too. def run(self): try: self.storage.tpc_begin(self.trans) except ZEO.ClientStorage.ClientStorageError: self.gotValueError = 1 class ThreadTests: # Thread 1 should start a transaction, but not get all the way through it. # Main thread should close the connection. Thread 1 should then get # disconnected. def checkDisconnectedOnThread2Close(self): doNextEvent = threading.Event() threadStartedEvent = threading.Event() thread1 = GetsThroughVoteThread(self._storage, doNextEvent, threadStartedEvent) thread1.start() threadStartedEvent.wait(10) self._storage.close() doNextEvent.set() thread1.join() self.assertEqual(thread1.gotValueError, 1) # Thread 1 should start a transaction, but not get all the way through # it. While thread 1 is in the middle of the transaction, a second thread # should start a transaction, and it will block in the tcp_begin() -- # because thread 1 has acquired the lock in its tpc_begin(). Now the main # thread closes the storage and both sub-threads should get disconnected. def checkSecondBeginFails(self): doNextEvent = threading.Event() threadStartedEvent = threading.Event() thread1 = GetsThroughVoteThread(self._storage, doNextEvent, threadStartedEvent) thread2 = GetsThroughBeginThread(self._storage, doNextEvent, threadStartedEvent) thread1.start() threadStartedEvent.wait(1) thread2.start() self._storage.close() doNextEvent.set() thread1.join() thread2.join() self.assertEqual(thread1.gotValueError, 1) self.assertEqual(thread2.gotValueError, 1) # Run a bunch of threads doing small and large stores in parallel def checkMTStores(self): threads = [] for i in range(5): t = threading.Thread(target=self.mtstorehelper) threads.append(t) t.start() for t in threads: t.join(30) for i in threads: self.failUnless(not t.isAlive()) # Helper for checkMTStores def mtstorehelper(self): name = threading.currentThread().getName() objs = [] for i in range(10): objs.append(MinPO("X" * 200000)) objs.append(MinPO("X")) for obj in objs: self._dostore(data=obj) zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/forker.py0000644000175000017500000002760312214017464020521 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Library for forking storage server and connecting client storage""" import os import random import sys import time import errno import socket import subprocess import logging import StringIO import tempfile import logging import ZODB.tests.util import zope.testing.setupstack logger = logging.getLogger('ZEO.tests.forker') class ZEOConfig: """Class to generate ZEO configuration file. """ def __init__(self, addr): if isinstance(addr, str): self.logpath = addr+'.log' else: self.logpath = 'server-%s.log' % addr[1] addr = '%s:%s' % addr self.address = addr self.read_only = None self.invalidation_queue_size = None self.invalidation_age = None self.monitor_address = None self.transaction_timeout = None self.authentication_protocol = None self.authentication_database = None self.authentication_realm = None self.loglevel = 'INFO' def dump(self, f): print >> f, "" print >> f, "address " + self.address if self.read_only is not None: print >> f, "read-only", self.read_only and "true" or "false" if self.invalidation_queue_size is not None: print >> f, "invalidation-queue-size", self.invalidation_queue_size if self.invalidation_age is not None: print >> f, "invalidation-age", self.invalidation_age if self.monitor_address is not None: print >> f, "monitor-address %s:%s" % self.monitor_address if self.transaction_timeout is not None: print >> f, "transaction-timeout", self.transaction_timeout if self.authentication_protocol is not None: print >> f, "authentication-protocol", self.authentication_protocol if self.authentication_database is not None: print >> f, "authentication-database", self.authentication_database if self.authentication_realm is not None: print >> f, "authentication-realm", self.authentication_realm print >> f, "" print >> f, """ level %s path %s """ % (self.loglevel, self.logpath) def __str__(self): f = StringIO.StringIO() self.dump(f) return f.getvalue() def encode_format(fmt): # The list of replacements mirrors # ZConfig.components.logger.handlers._control_char_rewrites for xform in (("\n", r"\n"), ("\t", r"\t"), ("\b", r"\b"), ("\f", r"\f"), ("\r", r"\r")): fmt = fmt.replace(*xform) return fmt def start_zeo_server(storage_conf=None, zeo_conf=None, port=None, keep=False, path='Data.fs', protocol=None, blob_dir=None, suicide=True, debug=False): """Start a ZEO server in a separate process. Takes two positional arguments a string containing the storage conf and a ZEOConfig object. Returns the ZEO address, the test server address, the pid, and the path to the config file. """ if not storage_conf: storage_conf = '\npath %s\n' % path if blob_dir: storage_conf = '\nblob-dir %s\n%s\n' % ( blob_dir, storage_conf) if port is None: raise AssertionError("The port wasn't specified") if isinstance(port, int): addr = 'localhost', port adminaddr = 'localhost', port+1 else: addr = port adminaddr = port+'-test' if zeo_conf is None or isinstance(zeo_conf, dict): z = ZEOConfig(addr) if zeo_conf: z.__dict__.update(zeo_conf) zeo_conf = z # Store the config info in a temp file. tmpfile = tempfile.mktemp(".conf", dir=os.getcwd()) fp = open(tmpfile, 'w') zeo_conf.dump(fp) fp.write(storage_conf) fp.close() # Find the zeoserver script import ZEO.tests.zeoserver script = ZEO.tests.zeoserver.__file__ if script.endswith('.pyc'): script = script[:-1] # Create a list of arguments, which we'll tuplify below qa = _quote_arg args = [qa(sys.executable), qa(script), '-C', qa(tmpfile)] if keep: args.append("-k") if debug: args.append("-d") if not suicide: args.append("-S") if protocol: args.extend(["-v", protocol]) d = os.environ.copy() d['PYTHONPATH'] = os.pathsep.join(sys.path) if sys.platform.startswith('win'): pid = os.spawnve(os.P_NOWAIT, sys.executable, tuple(args), d) else: pid = subprocess.Popen(args, env=d, close_fds=True).pid # We need to wait until the server starts, but not forever. # 30 seconds is a somewhat arbitrary upper bound. A BDBStorage # takes a long time to open -- more than 10 seconds on occasion. for i in range(300): time.sleep(0.1) try: if isinstance(adminaddr, str) and not os.path.exists(adminaddr): continue logger.debug('connect %s', i) if isinstance(adminaddr, str): s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) else: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(adminaddr) ack = s.recv(1024) s.close() logging.debug('acked: %s' % ack) break except socket.error, e: if e[0] not in (errno.ECONNREFUSED, errno.ECONNRESET): raise s.close() else: logging.debug('boo hoo') raise return addr, adminaddr, pid, tmpfile if sys.platform[:3].lower() == "win": def _quote_arg(s): return '"%s"' % s else: def _quote_arg(s): return s def shutdown_zeo_server(adminaddr): # Do this in a loop to guard against the possibility that the # client failed to connect to the adminaddr earlier. That really # only requires two iterations, but do a third for pure # superstition. for i in range(3): if isinstance(adminaddr, str): s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) else: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(.3) try: s.connect(adminaddr) except socket.timeout: # On FreeBSD 5.3 the connection just timed out if i > 0: break raise except socket.error, e: if (e[0] == errno.ECONNREFUSED or # MAC OS X uses EINVAL when connecting to a port # that isn't being listened on. (sys.platform == 'darwin' and e[0] == errno.EINVAL) ) and i > 0: break raise try: ack = s.recv(1024) except socket.error, e: ack = 'no ack received' logger.debug('shutdown_zeo_server(): acked: %s' % ack) s.close() def get_port(test=None): """Return a port that is not in use. Checks if a port is in use by trying to connect to it. Assumes it is not in use if connect raises an exception. We actually look for 2 consective free ports because most of the clients of this function will use the returned port and the next one. Raises RuntimeError after 10 tries. """ if test is not None: return get_port2(test) for i in range(10): port = random.randrange(20000, 30000) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: try: s.connect(('localhost', port)) except socket.error: pass # Perhaps we should check value of error too. else: continue try: s1.connect(('localhost', port+1)) except socket.error: pass # Perhaps we should check value of error too. else: continue return port finally: s.close() s1.close() raise RuntimeError("Can't find port") def get_port2(test): for i in range(10): while 1: port = random.randrange(20000, 30000) if port%3 == 0: break s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.bind(('localhost', port+2)) except socket.error, e: if e[0] != errno.EADDRINUSE: raise continue if not (can_connect(port) or can_connect(port+1)): zope.testing.setupstack.register(test, s.close) return port s.close() raise RuntimeError("Can't find port") def can_connect(port): c = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: c.connect(('localhost', port)) except socket.error: return False # Perhaps we should check value of error too. else: c.close() return True def setUp(test): ZODB.tests.util.setUp(test) servers = {} def start_server(storage_conf=None, zeo_conf=None, port=None, keep=False, addr=None, path='Data.fs', protocol=None, blob_dir=None, suicide=True, debug=False): """Start a ZEO server. Return the server and admin addresses. """ if port is None: if addr is None: port = get_port2(test) else: port = addr[1] elif addr is not None: raise TypeError("Can't specify port and addr") addr, adminaddr, pid, config_path = start_zeo_server( storage_conf, zeo_conf, port, keep, path, protocol, blob_dir, suicide, debug) os.remove(config_path) servers[adminaddr] = pid return addr, adminaddr test.globs['start_server'] = start_server def get_port(): return get_port2(test) test.globs['get_port'] = get_port def stop_server(adminaddr): pid = servers.pop(adminaddr) shutdown_zeo_server(adminaddr) os.waitpid(pid, 0) test.globs['stop_server'] = stop_server def cleanup_servers(): for adminaddr in list(servers): stop_server(adminaddr) zope.testing.setupstack.register(test, cleanup_servers) test.globs['wait_until'] = wait_until test.globs['wait_connected'] = wait_connected test.globs['wait_disconnected'] = wait_disconnected def wait_until(label=None, func=None, timeout=30, onfail=None): if label is None: if func is not None: label = func.__name__ elif not isinstance(label, basestring) and func is None: func = label label = func.__name__ if func is None: def wait_decorator(f): wait_until(label, f, timeout, onfail) return wait_decorator giveup = time.time() + timeout while not func(): if time.time() > giveup: if onfail is None: raise AssertionError("Timed out waiting for: ", label) else: return onfail() time.sleep(0.01) def wait_connected(storage): wait_until("storage is connected", storage.is_connected) def wait_disconnected(storage): wait_until("storage is disconnected", lambda : not storage.is_connected()) zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/test_cache.py0000644000175000017500000013545412214017464021337 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Basic unit tests for a client cache.""" from ZODB.utils import p64, repr_to_oid import doctest import os import re import string import struct import sys import tempfile import unittest import ZEO.cache import ZODB.tests.util import zope.testing.setupstack import zope.testing.renormalizing import ZEO.cache from ZODB.utils import p64, u64, z64 n1 = p64(1) n2 = p64(2) n3 = p64(3) n4 = p64(4) n5 = p64(5) def hexprint(file): file.seek(0) data = file.read() offset = 0 while data: line, data = data[:16], data[16:] printable = "" hex = "" for character in line: if (character in string.printable and not ord(character) in [12,13,9]): printable += character else: printable += '.' hex += character.encode('hex') + ' ' hex = hex[:24] + ' ' + hex[24:] hex = hex.ljust(49) printable = printable.ljust(16) print '%08x %s |%s|' % (offset, hex, printable) offset += 16 def oid(o): repr = '%016x' % o return repr_to_oid(repr) tid = oid class CacheTests(ZODB.tests.util.TestCase): def setUp(self): # The default cache size is much larger than we need here. Since # testSerialization reads the entire file into a string, it's not # good to leave it that big. ZODB.tests.util.TestCase.setUp(self) self.cache = ZEO.cache.ClientCache(size=1024**2) def tearDown(self): self.cache.close() if self.cache.path: os.remove(self.cache.path) ZODB.tests.util.TestCase.tearDown(self) def testLastTid(self): self.assertEqual(self.cache.getLastTid(), z64) self.cache.setLastTid(n2) self.assertEqual(self.cache.getLastTid(), n2) self.assertEqual(self.cache.getLastTid(), n2) self.cache.setLastTid(n3) self.assertEqual(self.cache.getLastTid(), n3) # Check that setting tids out of order gives an error: # the cache complains only when it's non-empty self.cache.store(n1, n3, None, 'x') self.assertRaises(ValueError, self.cache.setLastTid, n2) def testLoad(self): data1 = "data for n1" self.assertEqual(self.cache.load(n1), None) self.cache.store(n1, n3, None, data1) self.assertEqual(self.cache.load(n1), (data1, n3)) def testInvalidate(self): data1 = "data for n1" self.cache.store(n1, n3, None, data1) self.cache.invalidate(n2, n2) self.cache.invalidate(n1, n4) self.assertEqual(self.cache.load(n1), None) self.assertEqual(self.cache.loadBefore(n1, n4), (data1, n3, n4)) def testNonCurrent(self): data1 = "data for n1" data2 = "data for n2" self.cache.store(n1, n4, None, data1) self.cache.store(n1, n2, n3, data2) # can't say anything about state before n2 self.assertEqual(self.cache.loadBefore(n1, n2), None) # n3 is the upper bound of non-current record n2 self.assertEqual(self.cache.loadBefore(n1, n3), (data2, n2, n3)) # no data for between n2 and n3 self.assertEqual(self.cache.loadBefore(n1, n4), None) self.cache.invalidate(n1, n5) self.assertEqual(self.cache.loadBefore(n1, n5), (data1, n4, n5)) self.assertEqual(self.cache.loadBefore(n2, n4), None) def testException(self): self.cache.store(n1, n2, None, "data") self.cache.store(n1, n2, None, "data") self.assertRaises(ValueError, self.cache.store, n1, n3, None, "data") def testEviction(self): # Manually override the current maxsize cache = ZEO.cache.ClientCache(None, 3395) # Trivial test of eviction code. Doesn't test non-current # eviction. data = ["z" * i for i in range(100)] for i in range(50): n = p64(i) cache.store(n, n, None, data[i]) self.assertEquals(len(cache), i + 1) # The cache is now almost full. The next insert # should delete some objects. n = p64(50) cache.store(n, n, None, data[51]) self.assert_(len(cache) < 51) # TODO: Need to make sure eviction of non-current data # are handled correctly. def testSerialization(self): self.cache.store(n1, n2, None, "data for n1") self.cache.store(n3, n3, n4, "non-current data for n3") self.cache.store(n3, n4, n5, "more non-current data for n3") path = tempfile.mktemp() # Copy data from self.cache into path, reaching into the cache # guts to make the copy. dst = open(path, "wb+") src = self.cache.f src.seek(0) dst.write(src.read(self.cache.maxsize)) dst.close() copy = ZEO.cache.ClientCache(path) # Verify that internals of both objects are the same. # Could also test that external API produces the same results. eq = self.assertEqual eq(copy.getLastTid(), self.cache.getLastTid()) eq(len(copy), len(self.cache)) eq(dict(copy.current), dict(self.cache.current)) eq(dict([(k, dict(v)) for (k, v) in copy.noncurrent.items()]), dict([(k, dict(v)) for (k, v) in self.cache.noncurrent.items()]), ) def testCurrentObjectLargerThanCache(self): if self.cache.path: os.remove(self.cache.path) self.cache = ZEO.cache.ClientCache(size=50) # We store an object that is a bit larger than the cache can handle. self.cache.store(n1, n2, None, "x"*64) # We can see that it was not stored. self.assertEquals(None, self.cache.load(n1)) # If an object cannot be stored in the cache, it must not be # recorded as current. self.assert_(n1 not in self.cache.current) # Regression test: invalidation must still work. self.cache.invalidate(n1, n2) def testOldObjectLargerThanCache(self): if self.cache.path: os.remove(self.cache.path) cache = ZEO.cache.ClientCache(size=50) # We store an object that is a bit larger than the cache can handle. cache.store(n1, n2, n3, "x"*64) # We can see that it was not stored. self.assertEquals(None, cache.load(n1)) # If an object cannot be stored in the cache, it must not be # recorded as non-current. self.assert_(1 not in cache.noncurrent) def testVeryLargeCaches(self): cache = ZEO.cache.ClientCache('cache', size=(1<<32)+(1<<20)) cache.store(n1, n2, None, "x") cache.close() cache = ZEO.cache.ClientCache('cache', size=(1<<33)+(1<<20)) self.assertEquals(cache.load(n1), ('x', n2)) cache.close() def testConversionOfLargeFreeBlocks(self): f = open('cache', 'wb') f.write(ZEO.cache.magic+ '\0'*8 + 'f'+struct.pack(">I", (1<<32)-12) ) f.seek((1<<32)-1) f.write('x') f.close() cache = ZEO.cache.ClientCache('cache', size=1<<32) cache.close() cache = ZEO.cache.ClientCache('cache', size=1<<32) cache.close() f = open('cache', 'rb') f.seek(12) self.assertEquals(f.read(1), 'f') self.assertEquals(struct.unpack(">I", f.read(4))[0], ZEO.cache.max_block_size) f.close() if not sys.platform.startswith('linux'): # On platforms without sparse files, these tests are just way # too hard on the disk and take too long (especially in a windows # VM). del testVeryLargeCaches del testConversionOfLargeFreeBlocks def test_clear_zeo_cache(self): cache = self.cache for i in range(10): cache.store(p64(i), n2, None, str(i)) cache.store(p64(i), n1, n2, str(i)+'old') self.assertEqual(len(cache), 20) self.assertEqual(cache.load(n3), ('3', n2)) self.assertEqual(cache.loadBefore(n3, n2), ('3old', n1, n2)) cache.clear() self.assertEqual(len(cache), 0) self.assertEqual(cache.load(n3), None) self.assertEqual(cache.loadBefore(n3, n2), None) def testChangingCacheSize(self): # start with a small cache data = 'x' recsize = ZEO.cache.allocated_record_overhead+len(data) for extra in (2, recsize-2): cache = ZEO.cache.ClientCache( 'cache', size=ZEO.cache.ZEC_HEADER_SIZE+100*recsize+extra) for i in range(100): cache.store(p64(i), n1, None, data) self.assertEquals(len(cache), 100) self.assertEquals(os.path.getsize( 'cache'), ZEO.cache.ZEC_HEADER_SIZE+100*recsize+extra) # Now make it smaller cache.close() small = 50 cache = ZEO.cache.ClientCache( 'cache', size=ZEO.cache.ZEC_HEADER_SIZE+small*recsize+extra) self.assertEquals(len(cache), small) self.assertEquals(os.path.getsize( 'cache'), ZEO.cache.ZEC_HEADER_SIZE+small*recsize+extra) self.assertEquals(set(u64(oid) for (oid, tid) in cache.contents()), set(range(small))) for i in range(100, 110): cache.store(p64(i), n1, None, data) # We use small-1 below because an extra object gets # evicted because of the optimization to assure that we # always get a free block after a new allocated block. expected_len = small - 1 self.assertEquals(len(cache), expected_len) expected_oids = set(range(11, 50)+range(100, 110)) self.assertEquals( set(u64(oid) for (oid, tid) in cache.contents()), expected_oids) # Make sure we can reopen with same size cache.close() cache = ZEO.cache.ClientCache( 'cache', size=ZEO.cache.ZEC_HEADER_SIZE+small*recsize+extra) self.assertEquals(len(cache), expected_len) self.assertEquals(set(u64(oid) for (oid, tid) in cache.contents()), expected_oids) # Now make it bigger cache.close() large = 150 cache = ZEO.cache.ClientCache( 'cache', size=ZEO.cache.ZEC_HEADER_SIZE+large*recsize+extra) self.assertEquals(len(cache), expected_len) self.assertEquals(os.path.getsize( 'cache'), ZEO.cache.ZEC_HEADER_SIZE+large*recsize+extra) self.assertEquals(set(u64(oid) for (oid, tid) in cache.contents()), expected_oids) for i in range(200, 305): cache.store(p64(i), n1, None, data) # We use large-2 for the same reason we used small-1 above. expected_len = large-2 self.assertEquals(len(cache), expected_len) expected_oids = set(range(11, 50)+range(106, 110)+range(200, 305)) self.assertEquals(set(u64(oid) for (oid, tid) in cache.contents()), expected_oids) # Make sure we can reopen with same size cache.close() cache = ZEO.cache.ClientCache( 'cache', size=ZEO.cache.ZEC_HEADER_SIZE+large*recsize+extra) self.assertEquals(len(cache), expected_len) self.assertEquals(set(u64(oid) for (oid, tid) in cache.contents()), expected_oids) # Cleanup cache.close() os.remove('cache') def testSetAnyLastTidOnEmptyCache(self): self.cache.setLastTid(p64(5)) self.cache.setLastTid(p64(5)) self.cache.setLastTid(p64(3)) self.cache.setLastTid(p64(4)) def kill_does_not_cause_cache_corruption(): r""" If we kill a process while a cache is being written to, the cache isn't corrupted. To see this, we'll write a little script that writes records to a cache file repeatedly. >>> import os, random, sys, time >>> open('t', 'w').write(''' ... import os, random, sys, thread, time ... sys.path = %r ... ... def suicide(): ... time.sleep(random.random()/10) ... os._exit(0) ... ... import ZEO.cache ... from ZODB.utils import p64 ... cache = ZEO.cache.ClientCache('cache') ... oid = 0 ... t = 0 ... thread.start_new_thread(suicide, ()) ... while 1: ... oid += 1 ... t += 1 ... data = 'X' * random.randint(5000,25000) ... cache.store(p64(oid), p64(t), None, data) ... ... ''' % sys.path) >>> for i in range(10): ... _ = os.spawnl(os.P_WAIT, sys.executable, sys.executable, 't') ... if os.path.exists('cache'): ... cache = ZEO.cache.ClientCache('cache') ... cache.close() ... os.remove('cache') ... os.remove('cache.lock') """ def full_cache_is_valid(): r""" If we fill up the cache without any free space, the cache can still be used. >>> import ZEO.cache >>> cache = ZEO.cache.ClientCache('cache', 1000) >>> data = 'X' * (1000 - ZEO.cache.ZEC_HEADER_SIZE - 41) >>> cache.store(p64(1), p64(1), None, data) >>> cache.close() >>> cache = ZEO.cache.ClientCache('cache', 1000) >>> cache.store(p64(2), p64(2), None, 'XXX') >>> cache.close() """ def cannot_open_same_cache_file_twice(): r""" >>> import ZEO.cache >>> cache = ZEO.cache.ClientCache('cache', 1000) >>> cache2 = ZEO.cache.ClientCache('cache', 1000) Traceback (most recent call last): ... LockError: Couldn't lock 'cache.lock' >>> cache.close() """ def thread_safe(): r""" >>> import ZEO.cache, ZODB.utils >>> cache = ZEO.cache.ClientCache('cache', 1000000) >>> for i in range(100): ... cache.store(ZODB.utils.p64(i), ZODB.utils.p64(1), None, '0') >>> import random, sys, threading >>> random = random.Random(0) >>> stop = False >>> read_failure = None >>> def read_thread(): ... def pick_oid(): ... return ZODB.utils.p64(random.randint(0,99)) ... ... try: ... while not stop: ... cache.load(pick_oid()) ... cache.loadBefore(pick_oid(), ZODB.utils.p64(2)) ... except: ... global read_failure ... read_failure = sys.exc_info() >>> thread = threading.Thread(target=read_thread) >>> thread.start() >>> for tid in range(2,10): ... for oid in range(100): ... oid = ZODB.utils.p64(oid) ... cache.invalidate(oid, ZODB.utils.p64(tid)) ... cache.store(oid, ZODB.utils.p64(tid), None, str(tid)) >>> stop = True >>> thread.join() >>> if read_failure: ... print 'Read failure:' ... import traceback ... traceback.print_exception(*read_failure) >>> expected = '9', ZODB.utils.p64(9) >>> for oid in range(100): ... loaded = cache.load(ZODB.utils.p64(oid)) ... if loaded != expected: ... print oid, loaded >>> cache.close() """ def broken_non_current(): r""" In production, we saw a situation where an _del_noncurrent raused a key error when trying to free space, causing the cache to become unusable. I can't see why this would occur, but added a logging exception handler so, in the future, we'll still see cases in the log, but will ignore the error and keep going. >>> import ZEO.cache, ZODB.utils, logging, sys >>> logger = logging.getLogger('ZEO.cache') >>> logger.setLevel(logging.ERROR) >>> handler = logging.StreamHandler(sys.stdout) >>> logger.addHandler(handler) >>> cache = ZEO.cache.ClientCache('cache', 1000) >>> cache.store(ZODB.utils.p64(1), ZODB.utils.p64(1), None, '0') >>> cache.invalidate(ZODB.utils.p64(1), ZODB.utils.p64(2)) >>> cache._del_noncurrent(ZODB.utils.p64(1), ZODB.utils.p64(2)) ... # doctest: +NORMALIZE_WHITESPACE Couldn't find non-current ('\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02') >>> cache._del_noncurrent(ZODB.utils.p64(1), ZODB.utils.p64(1)) >>> cache._del_noncurrent(ZODB.utils.p64(1), ZODB.utils.p64(1)) # ... # doctest: +NORMALIZE_WHITESPACE Couldn't find non-current ('\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x01') >>> logger.setLevel(logging.NOTSET) >>> logger.removeHandler(handler) >>> cache.close() """ # def bad_magic_number(): See rename_bad_cache_file def cache_trace_analysis(): r""" Check to make sure the cache analysis scripts work. >>> import time >>> timetime = time.time >>> now = 1278864701.5 >>> time.time = lambda : now >>> os.environ["ZEO_CACHE_TRACE"] = 'yes' >>> import random >>> random = random.Random(42) >>> history = [] >>> serial = 1 >>> for i in range(1000): ... serial += 1 ... oid = random.randint(i+1000, i+6000) ... history.append(('s', p64(oid), p64(serial), ... 'x'*random.randint(200,2000))) ... for j in range(10): ... oid = random.randint(i+1000, i+6000) ... history.append(('l', p64(oid), p64(serial), ... 'x'*random.randint(200,2000))) >>> def cache_run(name, size): ... serial = 1 ... random.seed(42) ... global now ... now = 1278864701.5 ... cache = ZEO.cache.ClientCache(name, size*(1<<20)) ... for action, oid, serial, data in history: ... now += 1 ... if action == 's': ... cache.invalidate(oid, serial) ... cache.store(oid, serial, None, data) ... else: ... v = cache.load(oid) ... if v is None: ... cache.store(oid, serial, None, data) ... cache.close() >>> cache_run('cache', 2) >>> import ZEO.scripts.cache_stats, ZEO.scripts.cache_simul >>> def ctime(t): ... return time.asctime(time.gmtime(t-3600*4)) >>> ZEO.scripts.cache_stats.ctime = ctime >>> ZEO.scripts.cache_simul.ctime = ctime ############################################################ Stats >>> ZEO.scripts.cache_stats.main(['cache.trace']) loads hits inv(h) writes hitrate Jul 11 12:11-11 0 0 0 0 n/a Jul 11 12:11:41 ==================== Restart ==================== Jul 11 12:11-14 180 1 2 197 0.6% Jul 11 12:15-29 818 107 9 793 13.1% Jul 11 12:30-44 818 213 22 687 26.0% Jul 11 12:45-59 818 291 19 609 35.6% Jul 11 13:00-14 818 295 36 605 36.1% Jul 11 13:15-29 818 277 31 623 33.9% Jul 11 13:30-44 819 276 29 624 33.7% Jul 11 13:45-59 818 251 25 649 30.7% Jul 11 14:00-14 818 295 27 605 36.1% Jul 11 14:15-29 818 262 33 638 32.0% Jul 11 14:30-44 818 297 32 603 36.3% Jul 11 14:45-59 819 268 23 632 32.7% Jul 11 15:00-14 818 291 30 609 35.6% Jul 11 15:15-15 2 1 0 1 50.0% Read 18,876 trace records (641,776 bytes) in 0.0 seconds Versions: 0 records used a version First time: Sun Jul 11 12:11:41 2010 Last time: Sun Jul 11 15:15:01 2010 Duration: 11,000 seconds Data recs: 11,000 (58.3%), average size 1108 bytes Hit rate: 31.2% (load hits / loads) Count Code Function (action) 1 00 _setup_trace (initialization) 682 10 invalidate (miss) 318 1c invalidate (hit, saving non-current) 6,875 20 load (miss) 3,125 22 load (hit) 7,875 52 store (current, non-version) >>> ZEO.scripts.cache_stats.main('-q cache.trace'.split()) loads hits inv(h) writes hitrate Read 18,876 trace records (641,776 bytes) in 0.0 seconds Versions: 0 records used a version First time: Sun Jul 11 12:11:41 2010 Last time: Sun Jul 11 15:15:01 2010 Duration: 11,000 seconds Data recs: 11,000 (58.3%), average size 1108 bytes Hit rate: 31.2% (load hits / loads) Count Code Function (action) 1 00 _setup_trace (initialization) 682 10 invalidate (miss) 318 1c invalidate (hit, saving non-current) 6,875 20 load (miss) 3,125 22 load (hit) 7,875 52 store (current, non-version) >>> ZEO.scripts.cache_stats.main('-v cache.trace'.split()) ... # doctest: +ELLIPSIS loads hits inv(h) writes hitrate Jul 11 12:11:41 00 '' 0000000000000000 0000000000000000 - Jul 11 12:11-11 0 0 0 0 n/a Jul 11 12:11:41 ==================== Restart ==================== Jul 11 12:11:42 10 1065 0000000000000002 0000000000000000 - Jul 11 12:11:42 52 1065 0000000000000002 0000000000000000 - 245 Jul 11 12:11:43 20 947 0000000000000000 0000000000000000 - Jul 11 12:11:43 52 947 0000000000000002 0000000000000000 - 602 Jul 11 12:11:44 20 124b 0000000000000000 0000000000000000 - Jul 11 12:11:44 52 124b 0000000000000002 0000000000000000 - 1418 ... Jul 11 15:14:55 52 10cc 00000000000003e9 0000000000000000 - 1306 Jul 11 15:14:56 20 18a7 0000000000000000 0000000000000000 - Jul 11 15:14:56 52 18a7 00000000000003e9 0000000000000000 - 1610 Jul 11 15:14:57 22 18b5 000000000000031d 0000000000000000 - 1636 Jul 11 15:14:58 20 b8a 0000000000000000 0000000000000000 - Jul 11 15:14:58 52 b8a 00000000000003e9 0000000000000000 - 838 Jul 11 15:14:59 22 1085 0000000000000357 0000000000000000 - 217 Jul 11 15:00-14 818 291 30 609 35.6% Jul 11 15:15:00 22 1072 000000000000037e 0000000000000000 - 204 Jul 11 15:15:01 20 16c5 0000000000000000 0000000000000000 - Jul 11 15:15:01 52 16c5 00000000000003e9 0000000000000000 - 1712 Jul 11 15:15-15 2 1 0 1 50.0% Read 18,876 trace records (641,776 bytes) in 0.0 seconds Versions: 0 records used a version First time: Sun Jul 11 12:11:41 2010 Last time: Sun Jul 11 15:15:01 2010 Duration: 11,000 seconds Data recs: 11,000 (58.3%), average size 1108 bytes Hit rate: 31.2% (load hits / loads) Count Code Function (action) 1 00 _setup_trace (initialization) 682 10 invalidate (miss) 318 1c invalidate (hit, saving non-current) 6,875 20 load (miss) 3,125 22 load (hit) 7,875 52 store (current, non-version) >>> ZEO.scripts.cache_stats.main('-h cache.trace'.split()) loads hits inv(h) writes hitrate Jul 11 12:11-11 0 0 0 0 n/a Jul 11 12:11:41 ==================== Restart ==================== Jul 11 12:11-14 180 1 2 197 0.6% Jul 11 12:15-29 818 107 9 793 13.1% Jul 11 12:30-44 818 213 22 687 26.0% Jul 11 12:45-59 818 291 19 609 35.6% Jul 11 13:00-14 818 295 36 605 36.1% Jul 11 13:15-29 818 277 31 623 33.9% Jul 11 13:30-44 819 276 29 624 33.7% Jul 11 13:45-59 818 251 25 649 30.7% Jul 11 14:00-14 818 295 27 605 36.1% Jul 11 14:15-29 818 262 33 638 32.0% Jul 11 14:30-44 818 297 32 603 36.3% Jul 11 14:45-59 819 268 23 632 32.7% Jul 11 15:00-14 818 291 30 609 35.6% Jul 11 15:15-15 2 1 0 1 50.0% Read 18,876 trace records (641,776 bytes) in 0.0 seconds Versions: 0 records used a version First time: Sun Jul 11 12:11:41 2010 Last time: Sun Jul 11 15:15:01 2010 Duration: 11,000 seconds Data recs: 11,000 (58.3%), average size 1108 bytes Hit rate: 31.2% (load hits / loads) Count Code Function (action) 1 00 _setup_trace (initialization) 682 10 invalidate (miss) 318 1c invalidate (hit, saving non-current) 6,875 20 load (miss) 3,125 22 load (hit) 7,875 52 store (current, non-version) Histogram of object load frequency Unique oids: 4,585 Total loads: 10,000 loads objects %obj %load %cum 1 1,645 35.9% 16.4% 16.4% 2 1,465 32.0% 29.3% 45.8% 3 809 17.6% 24.3% 70.0% 4 430 9.4% 17.2% 87.2% 5 167 3.6% 8.3% 95.6% 6 49 1.1% 2.9% 98.5% 7 12 0.3% 0.8% 99.3% 8 7 0.2% 0.6% 99.9% 9 1 0.0% 0.1% 100.0% >>> ZEO.scripts.cache_stats.main('-s cache.trace'.split()) ... # doctest: +ELLIPSIS loads hits inv(h) writes hitrate Jul 11 12:11-11 0 0 0 0 n/a Jul 11 12:11:41 ==================== Restart ==================== Jul 11 12:11-14 180 1 2 197 0.6% Jul 11 12:15-29 818 107 9 793 13.1% Jul 11 12:30-44 818 213 22 687 26.0% Jul 11 12:45-59 818 291 19 609 35.6% Jul 11 13:00-14 818 295 36 605 36.1% Jul 11 13:15-29 818 277 31 623 33.9% Jul 11 13:30-44 819 276 29 624 33.7% Jul 11 13:45-59 818 251 25 649 30.7% Jul 11 14:00-14 818 295 27 605 36.1% Jul 11 14:15-29 818 262 33 638 32.0% Jul 11 14:30-44 818 297 32 603 36.3% Jul 11 14:45-59 819 268 23 632 32.7% Jul 11 15:00-14 818 291 30 609 35.6% Jul 11 15:15-15 2 1 0 1 50.0% Read 18,876 trace records (641,776 bytes) in 0.0 seconds Versions: 0 records used a version First time: Sun Jul 11 12:11:41 2010 Last time: Sun Jul 11 15:15:01 2010 Duration: 11,000 seconds Data recs: 11,000 (58.3%), average size 1108 bytes Hit rate: 31.2% (load hits / loads) Count Code Function (action) 1 00 _setup_trace (initialization) 682 10 invalidate (miss) 318 1c invalidate (hit, saving non-current) 6,875 20 load (miss) 3,125 22 load (hit) 7,875 52 store (current, non-version) Histograms of object sizes Unique sizes written: 1,782 size objs writes 200 5 5 201 4 4 202 4 4 203 1 1 204 1 1 205 6 6 206 8 8 ... 1,995 1 2 1,996 2 2 1,997 1 1 1,998 2 2 1,999 2 4 2,000 1 1 >>> ZEO.scripts.cache_stats.main('-S cache.trace'.split()) loads hits inv(h) writes hitrate Jul 11 12:11-11 0 0 0 0 n/a Jul 11 12:11:41 ==================== Restart ==================== Jul 11 12:11-14 180 1 2 197 0.6% Jul 11 12:15-29 818 107 9 793 13.1% Jul 11 12:30-44 818 213 22 687 26.0% Jul 11 12:45-59 818 291 19 609 35.6% Jul 11 13:00-14 818 295 36 605 36.1% Jul 11 13:15-29 818 277 31 623 33.9% Jul 11 13:30-44 819 276 29 624 33.7% Jul 11 13:45-59 818 251 25 649 30.7% Jul 11 14:00-14 818 295 27 605 36.1% Jul 11 14:15-29 818 262 33 638 32.0% Jul 11 14:30-44 818 297 32 603 36.3% Jul 11 14:45-59 819 268 23 632 32.7% Jul 11 15:00-14 818 291 30 609 35.6% Jul 11 15:15-15 2 1 0 1 50.0% >>> ZEO.scripts.cache_stats.main('-X cache.trace'.split()) loads hits inv(h) writes hitrate Jul 11 12:11-11 0 0 0 0 n/a Jul 11 12:11:41 ==================== Restart ==================== Jul 11 12:11-14 180 1 2 197 0.6% Jul 11 12:15-29 818 107 9 793 13.1% Jul 11 12:30-44 818 213 22 687 26.0% Jul 11 12:45-59 818 291 19 609 35.6% Jul 11 13:00-14 818 295 36 605 36.1% Jul 11 13:15-29 818 277 31 623 33.9% Jul 11 13:30-44 819 276 29 624 33.7% Jul 11 13:45-59 818 251 25 649 30.7% Jul 11 14:00-14 818 295 27 605 36.1% Jul 11 14:15-29 818 262 33 638 32.0% Jul 11 14:30-44 818 297 32 603 36.3% Jul 11 14:45-59 819 268 23 632 32.7% Jul 11 15:00-14 818 291 30 609 35.6% Jul 11 15:15-15 2 1 0 1 50.0% Read 18,876 trace records (641,776 bytes) in 0.0 seconds Versions: 0 records used a version First time: Sun Jul 11 12:11:41 2010 Last time: Sun Jul 11 15:15:01 2010 Duration: 11,000 seconds Data recs: 11,000 (58.3%), average size 1108 bytes Hit rate: 31.2% (load hits / loads) Count Code Function (action) 1 00 _setup_trace (initialization) 682 10 invalidate (miss) 318 1c invalidate (hit, saving non-current) 6,875 20 load (miss) 3,125 22 load (hit) 7,875 52 store (current, non-version) >>> ZEO.scripts.cache_stats.main('-i 5 cache.trace'.split()) loads hits inv(h) writes hitrate Jul 11 12:11-11 0 0 0 0 n/a Jul 11 12:11:41 ==================== Restart ==================== Jul 11 12:11-14 180 1 2 197 0.6% Jul 11 12:15-19 272 19 2 281 7.0% Jul 11 12:20-24 273 35 5 265 12.8% Jul 11 12:25-29 273 53 2 247 19.4% Jul 11 12:30-34 272 60 8 240 22.1% Jul 11 12:35-39 273 68 6 232 24.9% Jul 11 12:40-44 273 85 8 215 31.1% Jul 11 12:45-49 273 84 6 216 30.8% Jul 11 12:50-54 272 104 9 196 38.2% Jul 11 12:55-59 273 103 4 197 37.7% Jul 11 13:00-04 273 92 12 208 33.7% Jul 11 13:05-09 273 103 8 197 37.7% Jul 11 13:10-14 272 100 16 200 36.8% Jul 11 13:15-19 273 91 11 209 33.3% Jul 11 13:20-24 273 96 9 204 35.2% Jul 11 13:25-29 272 90 11 210 33.1% Jul 11 13:30-34 273 82 14 218 30.0% Jul 11 13:35-39 273 102 9 198 37.4% Jul 11 13:40-44 273 92 6 208 33.7% Jul 11 13:45-49 272 82 6 218 30.1% Jul 11 13:50-54 273 83 8 217 30.4% Jul 11 13:55-59 273 86 11 214 31.5% Jul 11 14:00-04 273 95 11 205 34.8% Jul 11 14:05-09 272 91 10 209 33.5% Jul 11 14:10-14 273 109 6 191 39.9% Jul 11 14:15-19 273 89 9 211 32.6% Jul 11 14:20-24 272 84 16 216 30.9% Jul 11 14:25-29 273 89 8 211 32.6% Jul 11 14:30-34 273 97 12 203 35.5% Jul 11 14:35-39 273 93 10 207 34.1% Jul 11 14:40-44 272 107 10 193 39.3% Jul 11 14:45-49 273 80 8 220 29.3% Jul 11 14:50-54 273 100 8 200 36.6% Jul 11 14:55-59 273 88 7 212 32.2% Jul 11 15:00-04 272 99 8 201 36.4% Jul 11 15:05-09 273 95 11 205 34.8% Jul 11 15:10-14 273 97 11 203 35.5% Jul 11 15:15-15 2 1 0 1 50.0% Read 18,876 trace records (641,776 bytes) in 0.0 seconds Versions: 0 records used a version First time: Sun Jul 11 12:11:41 2010 Last time: Sun Jul 11 15:15:01 2010 Duration: 11,000 seconds Data recs: 11,000 (58.3%), average size 1108 bytes Hit rate: 31.2% (load hits / loads) Count Code Function (action) 1 00 _setup_trace (initialization) 682 10 invalidate (miss) 318 1c invalidate (hit, saving non-current) 6,875 20 load (miss) 3,125 22 load (hit) 7,875 52 store (current, non-version) >>> ZEO.scripts.cache_simul.main('-s 2 -i 5 cache.trace'.split()) CircularCacheSimulation, cache size 2,097,152 bytes START TIME DUR. LOADS HITS INVALS WRITES HITRATE EVICTS INUSE Jul 11 12:11 3:17 180 1 2 197 0.6% 0 10.7 Jul 11 12:15 4:59 272 19 2 281 7.0% 0 26.4 Jul 11 12:20 4:59 273 35 5 265 12.8% 0 40.4 Jul 11 12:25 4:59 273 53 2 247 19.4% 0 54.8 Jul 11 12:30 4:59 272 60 8 240 22.1% 0 67.1 Jul 11 12:35 4:59 273 68 6 232 24.9% 0 79.8 Jul 11 12:40 4:59 273 85 8 215 31.1% 0 91.4 Jul 11 12:45 4:59 273 84 6 216 30.8% 77 99.1 Jul 11 12:50 4:59 272 104 9 196 38.2% 196 98.9 Jul 11 12:55 4:59 273 104 4 196 38.1% 188 99.1 Jul 11 13:00 4:59 273 92 12 208 33.7% 213 99.3 Jul 11 13:05 4:59 273 103 8 197 37.7% 190 99.0 Jul 11 13:10 4:59 272 100 16 200 36.8% 203 99.2 Jul 11 13:15 4:59 273 91 11 209 33.3% 222 98.7 Jul 11 13:20 4:59 273 96 9 204 35.2% 210 99.2 Jul 11 13:25 4:59 272 89 11 211 32.7% 212 99.1 Jul 11 13:30 4:59 273 82 14 218 30.0% 220 99.1 Jul 11 13:35 4:59 273 101 9 199 37.0% 191 99.5 Jul 11 13:40 4:59 273 92 6 208 33.7% 214 99.4 Jul 11 13:45 4:59 272 80 6 220 29.4% 217 99.3 Jul 11 13:50 4:59 273 81 8 219 29.7% 214 99.2 Jul 11 13:55 4:59 273 86 11 214 31.5% 208 98.8 Jul 11 14:00 4:59 273 95 11 205 34.8% 188 99.3 Jul 11 14:05 4:59 272 93 10 207 34.2% 207 99.3 Jul 11 14:10 4:59 273 110 6 190 40.3% 198 98.8 Jul 11 14:15 4:59 273 91 9 209 33.3% 209 99.1 Jul 11 14:20 4:59 272 85 16 215 31.2% 210 99.3 Jul 11 14:25 4:59 273 89 8 211 32.6% 226 99.3 Jul 11 14:30 4:59 273 96 12 204 35.2% 214 99.3 Jul 11 14:35 4:59 273 90 10 210 33.0% 213 99.3 Jul 11 14:40 4:59 272 106 10 194 39.0% 196 98.8 Jul 11 14:45 4:59 273 80 8 220 29.3% 230 99.0 Jul 11 14:50 4:59 273 99 8 201 36.3% 202 99.0 Jul 11 14:55 4:59 273 87 8 213 31.9% 205 99.4 Jul 11 15:00 4:59 272 98 8 202 36.0% 211 99.3 Jul 11 15:05 4:59 273 93 11 207 34.1% 198 99.2 Jul 11 15:10 4:59 273 96 11 204 35.2% 184 99.2 Jul 11 15:15 1 2 1 0 1 50.0% 1 99.2 -------------------------------------------------------------------------- Jul 11 12:45 2:30:01 8184 2794 286 6208 34.1% 6067 99.2 >>> cache_run('cache4', 4) >>> ZEO.scripts.cache_stats.main('cache4.trace'.split()) loads hits inv(h) writes hitrate Jul 11 12:11-11 0 0 0 0 n/a Jul 11 12:11:41 ==================== Restart ==================== Jul 11 12:11-14 180 1 2 197 0.6% Jul 11 12:15-29 818 107 9 793 13.1% Jul 11 12:30-44 818 213 22 687 26.0% Jul 11 12:45-59 818 322 23 578 39.4% Jul 11 13:00-14 818 381 43 519 46.6% Jul 11 13:15-29 818 450 44 450 55.0% Jul 11 13:30-44 819 503 47 397 61.4% Jul 11 13:45-59 818 496 49 404 60.6% Jul 11 14:00-14 818 516 48 384 63.1% Jul 11 14:15-29 818 532 59 368 65.0% Jul 11 14:30-44 818 516 51 384 63.1% Jul 11 14:45-59 819 529 53 371 64.6% Jul 11 15:00-14 818 515 49 385 63.0% Jul 11 15:15-15 2 2 0 0 100.0% Read 16,918 trace records (575,204 bytes) in 0.0 seconds Versions: 0 records used a version First time: Sun Jul 11 12:11:41 2010 Last time: Sun Jul 11 15:15:01 2010 Duration: 11,000 seconds Data recs: 11,000 (65.0%), average size 1104 bytes Hit rate: 50.8% (load hits / loads) Count Code Function (action) 1 00 _setup_trace (initialization) 501 10 invalidate (miss) 499 1c invalidate (hit, saving non-current) 4,917 20 load (miss) 5,083 22 load (hit) 5,917 52 store (current, non-version) >>> ZEO.scripts.cache_simul.main('-s 4 cache.trace'.split()) CircularCacheSimulation, cache size 4,194,304 bytes START TIME DUR. LOADS HITS INVALS WRITES HITRATE EVICTS INUSE Jul 11 12:11 3:17 180 1 2 197 0.6% 0 5.4 Jul 11 12:15 14:59 818 107 9 793 13.1% 0 27.4 Jul 11 12:30 14:59 818 213 22 687 26.0% 0 45.7 Jul 11 12:45 14:59 818 322 23 578 39.4% 0 61.4 Jul 11 13:00 14:59 818 381 43 519 46.6% 0 75.8 Jul 11 13:15 14:59 818 450 44 450 55.0% 0 88.2 Jul 11 13:30 14:59 819 503 47 397 61.4% 36 98.2 Jul 11 13:45 14:59 818 496 49 404 60.6% 388 98.5 Jul 11 14:00 14:59 818 515 48 385 63.0% 376 98.3 Jul 11 14:15 14:59 818 529 58 371 64.7% 391 98.1 Jul 11 14:30 14:59 818 511 51 389 62.5% 376 98.5 Jul 11 14:45 14:59 819 529 53 371 64.6% 410 97.9 Jul 11 15:00 14:59 818 512 49 388 62.6% 379 97.7 Jul 11 15:15 1 2 2 0 0 100.0% 0 97.7 -------------------------------------------------------------------------- Jul 11 13:30 1:45:01 5730 3597 355 2705 62.8% 2356 97.7 >>> cache_run('cache1', 1) >>> ZEO.scripts.cache_stats.main('cache1.trace'.split()) loads hits inv(h) writes hitrate Jul 11 12:11-11 0 0 0 0 n/a Jul 11 12:11:41 ==================== Restart ==================== Jul 11 12:11-14 180 1 2 197 0.6% Jul 11 12:15-29 818 107 9 793 13.1% Jul 11 12:30-44 818 160 16 740 19.6% Jul 11 12:45-59 818 158 8 742 19.3% Jul 11 13:00-14 818 141 21 759 17.2% Jul 11 13:15-29 818 128 17 772 15.6% Jul 11 13:30-44 819 151 13 749 18.4% Jul 11 13:45-59 818 120 17 780 14.7% Jul 11 14:00-14 818 159 17 741 19.4% Jul 11 14:15-29 818 141 13 759 17.2% Jul 11 14:30-44 818 157 16 743 19.2% Jul 11 14:45-59 819 133 13 767 16.2% Jul 11 15:00-14 818 158 10 742 19.3% Jul 11 15:15-15 2 1 0 1 50.0% Read 20,286 trace records (689,716 bytes) in 0.0 seconds Versions: 0 records used a version First time: Sun Jul 11 12:11:41 2010 Last time: Sun Jul 11 15:15:01 2010 Duration: 11,000 seconds Data recs: 11,000 (54.2%), average size 1105 bytes Hit rate: 17.1% (load hits / loads) Count Code Function (action) 1 00 _setup_trace (initialization) 828 10 invalidate (miss) 172 1c invalidate (hit, saving non-current) 8,285 20 load (miss) 1,715 22 load (hit) 9,285 52 store (current, non-version) >>> ZEO.scripts.cache_simul.main('-s 1 cache.trace'.split()) CircularCacheSimulation, cache size 1,048,576 bytes START TIME DUR. LOADS HITS INVALS WRITES HITRATE EVICTS INUSE Jul 11 12:11 3:17 180 1 2 197 0.6% 0 21.5 Jul 11 12:15 14:59 818 107 9 793 13.1% 96 99.6 Jul 11 12:30 14:59 818 160 16 740 19.6% 724 99.6 Jul 11 12:45 14:59 818 158 8 742 19.3% 741 99.2 Jul 11 13:00 14:59 818 140 21 760 17.1% 771 99.5 Jul 11 13:15 14:59 818 125 17 775 15.3% 781 99.6 Jul 11 13:30 14:59 819 147 13 753 17.9% 748 99.5 Jul 11 13:45 14:59 818 120 17 780 14.7% 763 99.5 Jul 11 14:00 14:59 818 159 17 741 19.4% 728 99.4 Jul 11 14:15 14:59 818 141 13 759 17.2% 787 99.6 Jul 11 14:30 14:59 818 150 15 750 18.3% 755 99.2 Jul 11 14:45 14:59 819 132 13 768 16.1% 771 99.5 Jul 11 15:00 14:59 818 154 10 746 18.8% 723 99.2 Jul 11 15:15 1 2 1 0 1 50.0% 0 99.3 -------------------------------------------------------------------------- Jul 11 12:15 3:00:01 9820 1694 169 9108 17.3% 8388 99.3 Cleanup: >>> del os.environ["ZEO_CACHE_TRACE"] >>> time.time = timetime >>> ZEO.scripts.cache_stats.ctime = time.ctime >>> ZEO.scripts.cache_simul.ctime = time.ctime """ def cache_simul_properly_handles_load_miss_after_eviction_and_inval(): r""" Set up evicted and then invalidated oid >>> os.environ["ZEO_CACHE_TRACE"] = 'yes' >>> cache = ZEO.cache.ClientCache('cache', 1<<21) >>> cache.store(p64(1), p64(1), None, 'x') >>> for i in range(10): ... cache.store(p64(2+i), p64(1), None, 'x'*(1<<19)) # Evict 1 >>> cache.store(p64(1), p64(1), None, 'x') >>> cache.invalidate(p64(1), p64(2)) >>> cache.load(p64(1)) >>> cache.close() Now try to do simulation: >>> import ZEO.scripts.cache_simul >>> ZEO.scripts.cache_simul.main('-s 1 cache.trace'.split()) ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE CircularCacheSimulation, cache size 1,048,576 bytes START TIME DUR. LOADS HITS INVALS WRITES HITRATE EVICTS INUSE ... 1 0 1 12 0.0% 10 50.0 -------------------------------------------------------------------------- ... 1 0 1 12 0.0% 10 50.0 >>> del os.environ["ZEO_CACHE_TRACE"] """ def invalidations_with_current_tid_dont_wreck_cache(): """ >>> cache = ZEO.cache.ClientCache('cache', 1000) >>> cache.store(p64(1), p64(1), None, 'data') >>> import logging, sys >>> handler = logging.StreamHandler(sys.stdout) >>> logging.getLogger().addHandler(handler) >>> old_level = logging.getLogger().getEffectiveLevel() >>> logging.getLogger().setLevel(logging.WARNING) >>> cache.invalidate(p64(1), p64(1)) Ignoring invalidation with same tid as current >>> cache.close() >>> cache = ZEO.cache.ClientCache('cache', 1000) >>> cache.close() >>> logging.getLogger().removeHandler(handler) >>> logging.getLogger().setLevel(old_level) """ def rename_bad_cache_file(): """ An attempt to open a bad cache file will cause it to be dropped and recreated. >>> open('cache', 'w').write('x'*100) >>> import logging, sys >>> handler = logging.StreamHandler(sys.stdout) >>> logging.getLogger().addHandler(handler) >>> old_level = logging.getLogger().getEffectiveLevel() >>> logging.getLogger().setLevel(logging.WARNING) >>> cache = ZEO.cache.ClientCache('cache', 1000) # doctest: +ELLIPSIS Moving bad cache file to 'cache.bad'. Traceback (most recent call last): ... ValueError: unexpected magic number: 'xxxx' >>> cache.store(p64(1), p64(1), None, 'data') >>> cache.close() >>> f = open('cache') >>> f.seek(0, 2) >>> print f.tell() 1000 >>> f.close() >>> open('cache', 'w').write('x'*200) >>> cache = ZEO.cache.ClientCache('cache', 1000) # doctest: +ELLIPSIS Removing bad cache file: 'cache' (prev bad exists). Traceback (most recent call last): ... ValueError: unexpected magic number: 'xxxx' >>> cache.store(p64(1), p64(1), None, 'data') >>> cache.close() >>> f = open('cache') >>> f.seek(0, 2) >>> print f.tell() 1000 >>> f.close() >>> f = open('cache.bad') >>> f.seek(0, 2) >>> print f.tell() 100 >>> f.close() >>> logging.getLogger().removeHandler(handler) >>> logging.getLogger().setLevel(old_level) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(CacheTests)) suite.addTest( doctest.DocTestSuite( setUp=zope.testing.setupstack.setUpDirectory, tearDown=zope.testing.setupstack.tearDown, checker=zope.testing.renormalizing.RENormalizing([ (re.compile(r'31\.3%'), '31.2%'), ]), ) ) return suite zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/zeo-fan-out.test0000644000175000017500000000736612214017464021730 0ustar arnauarnauZEO Fan Out =========== We should be able to set up ZEO servers with ZEO clients. Let's see if we can make it work. We'll use some helper functions. The first is a helper that starts ZEO servers for us and another one that picks ports. We'll start the first server: >>> (_, port0), adminaddr0 = start_server( ... '\npath fs\nblob-dir blobs\n', keep=1) Then we'll start 2 others that use this one: >>> addr1, _ = start_server( ... '\nserver %s\nblob-dir b1\n' % port0) >>> addr2, _ = start_server( ... '\nserver %s\nblob-dir b2\n' % port0) Now, let's create some client storages that connect to these: >>> import os, ZEO, ZODB.blob, ZODB.POSException, transaction >>> db0 = ZEO.DB(port0, blob_dir='cb0') >>> db1 = ZEO.DB(addr1, blob_dir='cb1') >>> tm1 = transaction.TransactionManager() >>> c1 = db1.open(transaction_manager=tm1) >>> r1 = c1.root() >>> r1 {} >>> db2 = ZEO.DB(addr2, blob_dir='cb2') >>> tm2 = transaction.TransactionManager() >>> c2 = db2.open(transaction_manager=tm2) >>> r2 = c2.root() >>> r2 {} If we update c1, we'll eventually see the change in c2: >>> import persistent.mapping >>> r1[1] = persistent.mapping.PersistentMapping() >>> r1[1].v = 1000 >>> r1[2] = persistent.mapping.PersistentMapping() >>> r1[2].v = -1000 >>> r1[3] = ZODB.blob.Blob('x'*4111222) >>> for i in range(1000, 2000): ... r1[i] = persistent.mapping.PersistentMapping() ... r1[i].v = 0 >>> tm1.commit() >>> blob_id = r1[3]._p_oid, r1[1]._p_serial >>> import time >>> for i in range(100): ... t = tm2.begin() ... if 1 in r2: ... break ... time.sleep(0.01) >>> tm2.abort() >>> r2[1].v 1000 >>> r2[2].v -1000 Now, let's see if we can break it. :) >>> def f(): ... c = db1.open(transaction.TransactionManager()) ... r = c.root() ... i = 0 ... while i < 100: ... r[1].v -= 1 ... r[2].v += 1 ... try: ... c.transaction_manager.commit() ... i += 1 ... except ZODB.POSException.ConflictError: ... c.transaction_manager.abort() ... c.close() >>> import threading >>> threadf = threading.Thread(target=f) >>> threadg = threading.Thread(target=f) >>> threadf.start() >>> threadg.start() >>> s2 = db2.storage >>> start_time = time.time() >>> while time.time() - start_time < 999: ... t = tm2.begin() ... if r2[1].v + r2[2].v: ... print 'oops', r2[1], r2[2] ... if r2[1].v == 800: ... break # we caught up ... path = s2.fshelper.getBlobFilename(*blob_id) ... if os.path.exists(path): ... ZODB.blob.remove_committed(path) ... s2._server.sendBlob(*blob_id) ... else: print 'Dang' >>> threadf.join() >>> threadg.join() If we shutdown and restart the source server, the variables will be invalidated: >>> stop_server(adminaddr0) >>> _ = start_server('\npath fs\n\n', ... port=port0) >>> for i in range(1000): ... c1.sync() ... c2.sync() ... if ( ... (r1[1]._p_changed is None) ... and ... (r1[2]._p_changed is None) ... and ... (r2[1]._p_changed is None) ... and ... (r2[2]._p_changed is None) ... ): ... print 'Cool' ... break ... time.sleep(0.01) ... else: ... print 'Dang' Cool Cleanup: >>> db0.close() >>> db1.close() >>> db2.close() zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/CommitLockTests.py0000644000175000017500000001323412214017464022310 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Tests of the distributed commit lock.""" import threading import time from persistent.TimeStamp import TimeStamp import transaction from ZODB.tests.StorageTestBase import zodb_pickle, MinPO import ZEO.ClientStorage from ZEO.Exceptions import ClientDisconnected from ZEO.tests.TestThread import TestThread ZERO = '\0'*8 class DummyDB: def invalidate(self, *args, **kwargs): pass class WorkerThread(TestThread): # run the entire test in a thread so that the blocking call for # tpc_vote() doesn't hang the test suite. def __init__(self, test, storage, trans): self.storage = storage self.trans = trans self.ready = threading.Event() TestThread.__init__(self, test) def testrun(self): try: self.storage.tpc_begin(self.trans) oid = self.storage.new_oid() p = zodb_pickle(MinPO("c")) self.storage.store(oid, ZERO, p, '', self.trans) oid = self.storage.new_oid() p = zodb_pickle(MinPO("c")) self.storage.store(oid, ZERO, p, '', self.trans) self.myvote() self.storage.tpc_finish(self.trans) except ClientDisconnected: pass def myvote(self): # The vote() call is synchronous, which makes it difficult to # coordinate the action of multiple threads that all call # vote(). This method sends the vote call, then sets the # event saying vote was called, then waits for the vote # response. It digs deep into the implementation of the client. # This method is a replacement for: # self.ready.set() # self.storage.tpc_vote(self.trans) rpc = self.storage._server.rpc msgid = rpc._deferred_call('vote', id(self.trans)) self.ready.set() rpc._deferred_wait(msgid) self.storage._check_serials() class CommitLockTests: NUM_CLIENTS = 5 # The commit lock tests verify that the storage successfully # blocks and restarts transactions when there is contention for a # single storage. There are a lot of cases to cover. # The general flow of these tests is to start a transaction by # getting far enough into 2PC to acquire the commit lock. Then # begin one or more other connections that also want to commit. # This causes the commit lock code to be exercised. Once the # other connections are started, the first transaction completes. def _cleanup(self): for store, trans in self._storages: store.tpc_abort(trans) store.close() self._storages = [] def _start_txn(self): txn = transaction.Transaction() self._storage.tpc_begin(txn) oid = self._storage.new_oid() self._storage.store(oid, ZERO, zodb_pickle(MinPO(1)), '', txn) return oid, txn def _begin_threads(self): # Start a second transaction on a different connection without # blocking the test thread. Returns only after each thread has # set it's ready event. self._storages = [] self._threads = [] for i in range(self.NUM_CLIENTS): storage = self._duplicate_client() txn = transaction.Transaction() tid = self._get_timestamp() t = WorkerThread(self, storage, txn) self._threads.append(t) t.start() t.ready.wait() # Close one of the connections abnormally to test server response if i == 0: storage.close() else: self._storages.append((storage, txn)) def _finish_threads(self): for t in self._threads: t.cleanup() def _duplicate_client(self): "Open another ClientStorage to the same server." # It's hard to find the actual address. # The rpc mgr addr attribute is a list. Each element in the # list is a socket domain (AF_INET, AF_UNIX, etc.) and an # address. addr = self._storage._addr new = ZEO.ClientStorage.ClientStorage(addr, wait=1) new.registerDB(DummyDB()) return new def _get_timestamp(self): t = time.time() t = TimeStamp(*time.gmtime(t)[:5]+(t%60,)) return `t` class CommitLockVoteTests(CommitLockTests): def checkCommitLockVoteFinish(self): oid, txn = self._start_txn() self._storage.tpc_vote(txn) self._begin_threads() self._storage.tpc_finish(txn) self._storage.load(oid, '') self._finish_threads() self._dostore() self._cleanup() def checkCommitLockVoteAbort(self): oid, txn = self._start_txn() self._storage.tpc_vote(txn) self._begin_threads() self._storage.tpc_abort(txn) self._finish_threads() self._dostore() self._cleanup() def checkCommitLockVoteClose(self): oid, txn = self._start_txn() self._storage.tpc_vote(txn) self._begin_threads() self._storage.close() self._finish_threads() self._cleanup() zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/zeoserver.py0000644000175000017500000001610212214017464021245 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Helper file used to launch a ZEO server cross platform""" import asyncore import errno import getopt import logging import os import signal import socket import sys import threading import time import ZEO.runzeo import ZEO.zrpc.connection def cleanup(storage): # FileStorage and the Berkeley storages have this method, which deletes # all files and directories used by the storage. This prevents @-files # from clogging up /tmp try: storage.cleanup() except AttributeError: pass logger = logging.getLogger('ZEO.tests.zeoserver') def log(label, msg, *args): message = "(%s) %s" % (label, msg) logger.debug(message, *args) class ZEOTestServer(asyncore.dispatcher): """A server for killing the whole process at the end of a test. The first time we connect to this server, we write an ack character down the socket. The other end should block on a recv() of the socket so it can guarantee the server has started up before continuing on. The second connect to the port immediately exits the process, via os._exit(), without writing data on the socket. It does close and clean up the storage first. The other end will get the empty string from its recv() which will be enough to tell it that the server has exited. I think this should prevent us from ever getting a legitimate addr-in-use error. """ __super_init = asyncore.dispatcher.__init__ def __init__(self, addr, server, keep): self.__super_init() self._server = server self._sockets = [self] self._keep = keep # Count down to zero, the number of connects self._count = 1 self._label ='%d @ %s' % (os.getpid(), addr) if isinstance(addr, str): self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) else: self.create_socket(socket.AF_INET, socket.SOCK_STREAM) # Some ZEO tests attempt a quick start of the server using the same # port so we have to set the reuse flag. self.set_reuse_addr() try: self.bind(addr) except: # We really want to see these exceptions import traceback traceback.print_exc() raise self.listen(5) self.log('bound and listening') def log(self, msg, *args): log(self._label, msg, *args) def handle_accept(self): sock, addr = self.accept() self.log('in handle_accept()') # When we're done with everything, close the storage. Do not write # the ack character until the storage is finished closing. if self._count <= 0: self.log('closing the storage') self._server.close_server() if not self._keep: for storage in self._server.storages.values(): cleanup(storage) self.log('exiting') # Close all the other sockets so that we don't have to wait # for os._exit() to get to it before starting the next # server process. for s in self._sockets: s.close() # Now explicitly close the socket returned from accept(), # since it didn't go through the wrapper. sock.close() os._exit(0) self.log('continuing') sock.send('X') self._count -= 1 def register_socket(self, sock): # Register a socket to be closed when server shutsdown. self._sockets.append(sock) class Suicide(threading.Thread): def __init__(self, addr): threading.Thread.__init__(self) self._adminaddr = addr def run(self): # If this process doesn't exit in 330 seconds, commit suicide. # The client threads in the ConcurrentUpdate tests will run for # as long as 300 seconds. Set this timeout to 330 to minimize # chance that the server gives up before the clients. time.sleep(999) log(str(os.getpid()), "suicide thread invoking shutdown") # If the server hasn't shut down yet, the client may not be # able to connect to it. If so, try to kill the process to # force it to shutdown. if hasattr(os, "kill"): os.kill(pid, signal.SIGTERM) time.sleep(5) os.kill(pid, signal.SIGKILL) else: from ZEO.tests.forker import shutdown_zeo_server # Nott: If the -k option was given to zeoserver, then the # process will go away but the temp files won't get # cleaned up. shutdown_zeo_server(self._adminaddr) def main(): global pid pid = os.getpid() label = str(pid) log(label, "starting") # We don't do much sanity checking of the arguments, since if we get it # wrong, it's a bug in the test suite. keep = 0 configfile = None suicide = True # Parse the arguments and let getopt.error percolate opts, args = getopt.getopt(sys.argv[1:], 'dkSC:v:') for opt, arg in opts: if opt == '-k': keep = 1 if opt == '-d': ZEO.zrpc.connection.debug_zrpc = True elif opt == '-C': configfile = arg elif opt == '-S': suicide = False elif opt == '-v': ZEO.zrpc.connection.Connection.current_protocol = arg zo = ZEO.runzeo.ZEOOptions() zo.realize(["-C", configfile]) addr = zo.address if zo.auth_protocol == "plaintext": __import__('ZEO.tests.auth_plaintext') if isinstance(addr, tuple): test_addr = addr[0], addr[1]+1 else: test_addr = addr + '-test' log(label, 'creating the storage server') storage = zo.storages[0].open() mon_addr = None if zo.monitor_address: mon_addr = zo.monitor_address server = ZEO.runzeo.create_server({"1": storage}, zo) try: log(label, 'creating the test server, keep: %s', keep) t = ZEOTestServer(test_addr, server, keep) except socket.error, e: if e[0] != errno.EADDRINUSE: raise log(label, 'addr in use, closing and exiting') storage.close() cleanup(storage) sys.exit(2) t.register_socket(server.dispatcher) if suicide: # Create daemon suicide thread d = Suicide(test_addr) d.setDaemon(1) d.start() # Loop for socket events log(label, 'entering asyncore loop') asyncore.loop() if __name__ == '__main__': import warnings warnings.simplefilter('ignore') main() zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/auth_plaintext.py0000644000175000017500000000404412214017464022254 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Implements plaintext password authentication. The password is stored in an SHA hash in the Database. The client sends over the plaintext password, and the SHA hashing is done on the server side. This mechanism offers *no network security at all*; the only security is provided by not storing plaintext passwords on disk. """ from ZEO.hash import sha1 from ZEO.StorageServer import ZEOStorage from ZEO.auth import register_module from ZEO.auth.base import Client, Database def session_key(username, realm, password): return sha1("%s:%s:%s" % (username, realm, password)).hexdigest() class StorageClass(ZEOStorage): def auth(self, username, password): try: dbpw = self.database.get_password(username) except LookupError: return 0 password_dig = sha1(password).hexdigest() if dbpw == password_dig: self.connection.setSessionKey(session_key(username, self.database.realm, password)) return self._finish_auth(dbpw == password_dig) class PlaintextClient(Client): extensions = ["auth"] def start(self, username, realm, password): if self.stub.auth(username, password): return session_key(username, realm, password) else: return None register_module("plaintext", StorageClass, PlaintextClient, Database) zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/testZEOOptions.py0000644000175000017500000000740412214017464022137 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Test suite for ZEO.runzeo.ZEOOptions.""" import os import tempfile import unittest import ZODB.config from ZEO.runzeo import ZEOOptions from zdaemon.tests.testzdoptions import TestZDOptions # When a hostname isn't specified in a socket binding address, ZConfig # supplies the empty string. DEFAULT_BINDING_HOST = "" class TestZEOOptions(TestZDOptions): OptionsClass = ZEOOptions input_args = ["-f", "Data.fs", "-a", "5555"] output_opts = [("-f", "Data.fs"), ("-a", "5555")] output_args = [] configdata = """ address 5555 path Data.fs """ def setUp(self): self.tempfilename = tempfile.mktemp() f = open(self.tempfilename, "w") f.write(self.configdata) f.close() def tearDown(self): try: os.remove(self.tempfilename) except os.error: pass def test_configure(self): # Hide the base class test_configure pass def test_default_help(self): pass # disable silly test w spurious failures def test_defaults_with_schema(self): options = self.OptionsClass() options.realize(["-C", self.tempfilename]) self.assertEqual(options.address, (DEFAULT_BINDING_HOST, 5555)) self.assertEqual(len(options.storages), 1) opener = options.storages[0] self.assertEqual(opener.name, "fs") self.assertEqual(opener.__class__, ZODB.config.FileStorage) self.assertEqual(options.read_only, 0) self.assertEqual(options.transaction_timeout, None) self.assertEqual(options.invalidation_queue_size, 100) def test_defaults_without_schema(self): options = self.OptionsClass() options.realize(["-a", "5555", "-f", "Data.fs"]) self.assertEqual(options.address, (DEFAULT_BINDING_HOST, 5555)) self.assertEqual(len(options.storages), 1) opener = options.storages[0] self.assertEqual(opener.name, "1") self.assertEqual(opener.__class__, ZODB.config.FileStorage) self.assertEqual(opener.config.path, "Data.fs") self.assertEqual(options.read_only, 0) self.assertEqual(options.transaction_timeout, None) self.assertEqual(options.invalidation_queue_size, 100) def test_commandline_overrides(self): options = self.OptionsClass() options.realize(["-C", self.tempfilename, "-a", "6666", "-f", "Wisdom.fs"]) self.assertEqual(options.address, (DEFAULT_BINDING_HOST, 6666)) self.assertEqual(len(options.storages), 1) opener = options.storages[0] self.assertEqual(opener.__class__, ZODB.config.FileStorage) self.assertEqual(opener.config.path, "Wisdom.fs") self.assertEqual(options.read_only, 0) self.assertEqual(options.transaction_timeout, None) self.assertEqual(options.invalidation_queue_size, 100) def test_suite(): suite = unittest.TestSuite() for cls in [TestZEOOptions]: suite.addTest(unittest.makeSuite(cls)) return suite if __name__ == "__main__": unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/client-config.test0000644000175000017500000000416412214017464022276 0ustar arnauarnauZEO Client Configuration ======================== Here we'll describe (and test) the various ZEO Client configuration options. To facilitate this, we'l start a server that our client can connect to: >>> addr, _ = start_server(blob_dir='server-blobs') The simplest client configuration specified a server address: >>> import ZODB.config >>> storage = ZODB.config.storageFromString(""" ... ... server %s:%s ... ... """ % addr) >>> storage.getName(), storage.__class__.__name__ ... # doctest: +ELLIPSIS ("[('localhost', ...)] (connected)", 'ClientStorage') >>> storage.blob_dir >>> storage._storage '1' >>> storage._cache.maxsize 20971520 >>> storage._cache.path >>> storage._rpc_mgr.tmin 5 >>> storage._rpc_mgr.tmax 300 >>> storage._is_read_only False >>> storage._read_only_fallback False >>> storage._drop_cache_rather_verify False >>> storage._blob_cache_size >>> storage.close() >>> storage = ZODB.config.storageFromString(""" ... ... server %s:%s ... blob-dir blobs ... storage 2 ... cache-size 100 ... name bob ... client cache ... min-disconnect-poll 1 ... max-disconnect-poll 5 ... read-only true ... drop-cache-rather-verify true ... blob-cache-size 1000MB ... blob-cache-size-check 10 ... wait false ... ... """ % addr) >>> storage.getName(), storage.__class__.__name__ ('bob (disconnected)', 'ClientStorage') >>> storage.blob_dir 'blobs' >>> storage._storage '2' >>> storage._cache.maxsize 100 >>> import os >>> storage._cache.path == os.path.abspath('cache-2.zec') True >>> storage._rpc_mgr.tmin 1 >>> storage._rpc_mgr.tmax 5 >>> storage._is_read_only True >>> storage._read_only_fallback False >>> storage._drop_cache_rather_verify True >>> storage._blob_cache_size 1048576000 >>> print storage._blob_cache_size_check 104857600 >>> storage.close() zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/testTransactionBuffer.py0000644000175000017500000000425712214017464023550 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import random import unittest from ZEO.TransactionBuffer import TransactionBuffer def random_string(size): """Return a random string of size size.""" l = [chr(random.randrange(256)) for i in range(size)] return "".join(l) def new_store_data(): """Return arbitrary data to use as argument to store() method.""" return random_string(8), random_string(random.randrange(1000)) def new_invalidate_data(): """Return arbitrary data to use as argument to invalidate() method.""" return random_string(8) class TransBufTests(unittest.TestCase): def checkTypicalUsage(self): tbuf = TransactionBuffer() tbuf.store(*new_store_data()) tbuf.invalidate(new_invalidate_data()) for o in tbuf: pass def doUpdates(self, tbuf): data = [] for i in range(10): d = new_store_data() tbuf.store(*d) data.append(d) d = new_invalidate_data() tbuf.invalidate(d) data.append(d) for i, x in enumerate(tbuf): if x[1] is None: # the tbuf add a dummy None to invalidates x = x[0] self.assertEqual(x, data[i]) def checkOrderPreserved(self): tbuf = TransactionBuffer() self.doUpdates(tbuf) def checkReusable(self): tbuf = TransactionBuffer() self.doUpdates(tbuf) tbuf.clear() self.doUpdates(tbuf) tbuf.clear() self.doUpdates(tbuf) def test_suite(): return unittest.makeSuite(TransBufTests, 'check') zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/testMonitor.py0000644000175000017500000000540412214017464021553 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Test that the monitor produce sensible results. $Id: testMonitor.py 113734 2010-06-21 15:33:46Z ctheune $ """ import socket import unittest from ZEO.tests.ConnectionTests import CommonSetupTearDown from ZEO.monitor import StorageStats class MonitorTests(CommonSetupTearDown): monitor = 1 def get_monitor_output(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('localhost', 42000)) L = [] while 1: buf = s.recv(8192) if buf: L.append(buf) else: break s.close() return "".join(L) def parse(self, s): # Return a list of StorageStats, one for each storage. lines = s.split("\n") self.assert_(lines[0].startswith("ZEO monitor server")) # lines[1] is a date # Break up rest of lines into sections starting with Storage: # and ending with a blank line. sections = [] cur = None for line in lines[2:]: if line.startswith("Storage:"): cur = [line] elif line: cur.append(line) else: if cur is not None: sections.append(cur) cur = None assert cur is None # bug in the test code if this fails d = {} for sect in sections: hdr = sect[0] key, value = hdr.split(":") storage = int(value) s = d[storage] = StorageStats() s.parse("\n".join(sect[1:])) return d def getConfig(self, path, create, read_only): return """""" def testMonitor(self): # Just open a client to know that the server is up and running # TODO: should put this in setUp. self.storage = self.openClientStorage() s = self.get_monitor_output() self.storage.close() self.assert_(s.find("monitor") != -1) d = self.parse(s) stats = d[1] self.assertEqual(stats.clients, 1) self.assertEqual(stats.commits, 0) def test_suite(): return unittest.makeSuite(MonitorTests) zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/testConversionSupport.py0000644000175000017500000001266412214017464023654 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import unittest class FakeStorageBase: def __getattr__(self, name): if name in ('getTid', 'history', 'load', 'loadSerial', 'lastTransaction', 'getSize', 'getName', 'supportsUndo', 'tpc_transaction'): return lambda *a, **k: None raise AttributeError(name) def isReadOnly(self): return False def __len__(self): return 4 class FakeStorage(FakeStorageBase): def record_iternext(self, next=None): if next == None: next = '0' next = str(int(next) + 1) oid = next if next == '4': next = None return oid, oid*8, 'data ' + oid, next class FakeServer: storages = { '1': FakeStorage(), '2': FakeStorageBase(), } def register_connection(*args): return None, None def test_server_record_iternext(): """ On the server, record_iternext calls are simply delegated to the underlying storage. >>> import ZEO.StorageServer >>> zeo = ZEO.StorageServer.ZEOStorage(FakeServer(), False) >>> zeo.register('1', False) >>> next = None >>> while 1: ... oid, serial, data, next = zeo.record_iternext(next) ... print oid ... if next is None: ... break 1 2 3 4 The storage info also reflects the fact that record_iternext is supported. >>> zeo.get_info()['supports_record_iternext'] True >>> zeo = ZEO.StorageServer.ZEOStorage(FakeServer(), False) >>> zeo.register('2', False) >>> zeo.get_info()['supports_record_iternext'] False """ def test_client_record_iternext(): """\ The client simply delegates record_iternext calls to it's server stub. There's really no decent way to test ZEO without running too much crazy stuff. I'd rather do a lame test than a really lame test, so here goes. First, fake out the connection manager so we can make a connection: >>> import ZEO.ClientStorage >>> from ZEO.ClientStorage import ClientStorage >>> oldConnectionManagerClass = ClientStorage.ConnectionManagerClass >>> class FauxConnectionManagerClass: ... def __init__(*a, **k): ... pass ... def attempt_connect(self): ... return True >>> ClientStorage.ConnectionManagerClass = FauxConnectionManagerClass >>> client = ClientStorage('', wait=False) >>> ClientStorage.ConnectionManagerClass = oldConnectionManagerClass Now we'll have our way with it's private _server attr: >>> client._server = FakeStorage() >>> next = None >>> while 1: ... oid, serial, data, next = client.record_iternext(next) ... print oid ... if next is None: ... break 1 2 3 4 """ def test_server_stub_record_iternext(): """\ The server stub simply delegates record_iternext calls to it's rpc. There's really no decent way to test ZEO without running to much crazy stuff. I'd rather do a lame test than a really lame test, so here goes. >>> class FauxRPC: ... storage = FakeStorage() ... def call(self, meth, *args): ... return getattr(self.storage, meth)(*args) ... peer_protocol_version = 1 >>> import ZEO.ServerStub >>> stub = ZEO.ServerStub.StorageServer(FauxRPC()) >>> next = None >>> while 1: ... oid, serial, data, next = stub.record_iternext(next) ... print oid ... if next is None: ... break 1 2 3 4 """ def history_to_version_compatible_storage(): """ Some storages work under ZODB <= 3.8 and ZODB >= 3.9. This means they have a history method that accepts a version parameter: >>> class VersionCompatibleStorage(FakeStorageBase): ... def history(self,oid,version='',size=1): ... return oid,version,size A ZEOStorage such as the following should support this type of storage: >>> class OurFakeServer(FakeServer): ... storages = {'1':VersionCompatibleStorage()} >>> import ZEO.StorageServer >>> zeo = ZEO.StorageServer.ZEOStorage(OurFakeServer(), False) >>> zeo.register('1', False) The ZEOStorage should sort out the following call such that the storage gets the correct parameters and so should return the parameters it was called with: >>> zeo.history('oid',99) ('oid', '', 99) The same problem occurs when a Z308 client connects to a Z309 server, but different code is executed: >>> from ZEO.StorageServer import ZEOStorage308Adapter >>> zeo = ZEOStorage308Adapter(VersionCompatibleStorage()) The history method should still return the parameters it was called with: >>> zeo.history('oid','',99) ('oid', '', 99) """ def test_suite(): return doctest.DocTestSuite() if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/__init__.py0000644000175000017500000000120112214017464020752 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/zeo_blob_cache.test0000644000175000017500000001157712214017464022501 0ustar arnauarnauZEO caching of blob data ======================== ZEO supports 2 modes for providing clients access to blob data: shared Blob data are shared via a network file system. The client shares a common blob directory with the server. non-shared Blob data are loaded from the storage server and cached locally. A maximum size for the blob data can be set and data are removed when the size is exceeded. In this test, we'll demonstrate that blobs data are removed from a ZEO cache when the amount of data stored exceeds a given limit. Let's start by setting up some data: >>> addr, _ = start_server(blob_dir='server-blobs') We'll also create a client. >>> import ZEO >>> db = ZEO.DB(addr, blob_dir='blobs', blob_cache_size=3000) Here, we passed a blob_cache_size parameter, which specifies a target blob cache size. This is not a hard limit, but rather a target. It defaults to a very large value. We also passed a blob_cache_size_check option. The blob_cache_size_check option specifies the number of bytes, as a percent of the target that can be written or downloaded from the server before the cache size is checked. The blob_cache_size_check option defaults to 100. We passed 10, to check after writing 10% of the target size. .. We're going to wait for any threads we started to finish, so... >>> import threading >>> old_threads = list(threading.enumerate()) We want to check for name collections in the blob cache dir. We'll try to provoke name collections by reducing the number of cache directory subdirectories. >>> import ZEO.ClientStorage >>> orig_blob_cache_layout_size = ZEO.ClientStorage.BlobCacheLayout.size >>> ZEO.ClientStorage.BlobCacheLayout.size = 11 Now, let's write some data: >>> import ZODB.blob, transaction, time >>> conn = db.open() >>> for i in range(1, 101): ... conn.root()[i] = ZODB.blob.Blob() ... conn.root()[i].open('w').write(chr(i)*100) >>> transaction.commit() We've committed 10000 bytes of data, but our target size is 3000. We expect to have not much more than the target size in the cache blob directory. >>> import os >>> def cache_size(d): ... size = 0 ... for base, dirs, files in os.walk(d): ... for f in files: ... if f.endswith('.blob'): ... try: ... size += os.stat(os.path.join(base, f)).st_size ... except OSError: ... if os.path.exists(os.path.join(base, f)): ... raise ... return size >>> def check(): ... return cache_size('blobs') < 5000 >>> def onfail(): ... return cache_size('blobs') >>> from ZEO.tests.forker import wait_until >>> wait_until("size is reduced", check, 99, onfail) If we read all of the blobs, data will be downloaded again, as necessary, but the cache size will remain not much bigger than the target: >>> for i in range(1, 101): ... data = conn.root()[i].open().read() ... if data != chr(i)*100: ... print 'bad data', `chr(i)`, `data` >>> wait_until("size is reduced", check, 99, onfail) >>> for i in range(1, 101): ... data = conn.root()[i].open().read() ... if data != chr(i)*100: ... print 'bad data', `chr(i)`, `data` >>> for i in range(1, 101): ... data = conn.root()[i].open('c').read() ... if data != chr(i)*100: ... print 'bad data', `chr(i)`, `data` >>> wait_until("size is reduced", check, 99, onfail) Now let see if we can stress things a bit. We'll create many clients and get them to pound on the blobs all at once to see if we can provoke problems: >>> import threading, random >>> def run(): ... db = ZEO.DB(addr, blob_dir='blobs', blob_cache_size=4000) ... conn = db.open() ... for i in range(300): ... time.sleep(0) ... i = random.randint(1, 100) ... data = conn.root()[i].open().read() ... if data != chr(i)*100: ... print 'bad data', `chr(i)`, `data` ... i = random.randint(1, 100) ... data = conn.root()[i].open('c').read() ... if data != chr(i)*100: ... print 'bad data', `chr(i)`, `data` ... db.close() >>> threads = [threading.Thread(target=run) for i in range(10)] >>> for thread in threads: ... thread.setDaemon(True) >>> for thread in threads: ... thread.start() >>> for thread in threads: ... thread.join(99) ... if thread.isAlive(): ... print "Can't join thread." >>> wait_until("size is reduced", check, 99, onfail) .. cleanup >>> for thread in threading.enumerate(): ... if thread not in old_threads: ... thread.join(33) >>> db.close() >>> ZEO.ClientStorage.BlobCacheLayout.size = orig_blob_cache_layout_size zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/testAuth.py0000644000175000017500000001152312214017464021024 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Test suite for AuthZEO.""" import os import tempfile import time import unittest from ZEO import zeopasswd from ZEO.Exceptions import ClientDisconnected from ZEO.tests.ConnectionTests import CommonSetupTearDown class AuthTest(CommonSetupTearDown): __super_getServerConfig = CommonSetupTearDown.getServerConfig __super_setUp = CommonSetupTearDown.setUp __super_tearDown = CommonSetupTearDown.tearDown realm = None def setUp(self): fd, self.pwfile = tempfile.mkstemp('pwfile') os.close(fd) if self.realm: self.pwdb = self.dbclass(self.pwfile, self.realm) else: self.pwdb = self.dbclass(self.pwfile) self.pwdb.add_user("foo", "bar") self.pwdb.save() self._checkZEOpasswd() self.__super_setUp() def _checkZEOpasswd(self): args = ["-f", self.pwfile, "-p", self.protocol] if self.protocol == "plaintext": from ZEO.auth.base import Database zeopasswd.main(args + ["-d", "foo"], Database) zeopasswd.main(args + ["foo", "bar"], Database) else: zeopasswd.main(args + ["-d", "foo"]) zeopasswd.main(args + ["foo", "bar"]) def tearDown(self): os.remove(self.pwfile) self.__super_tearDown() def getConfig(self, path, create, read_only): return "" def getServerConfig(self, addr, ro_svr): zconf = self.__super_getServerConfig(addr, ro_svr) zconf.authentication_protocol = self.protocol zconf.authentication_database = self.pwfile zconf.authentication_realm = self.realm return zconf def wait(self): for i in range(25): time.sleep(0.1) if self._storage.test_connection: return self.fail("Timed out waiting for client to authenticate") def testOK(self): # Sleep for 0.2 seconds to give the server some time to start up # seems to be needed before and after creating the storage self._storage = self.openClientStorage(wait=0, username="foo", password="bar", realm=self.realm) self.wait() self.assert_(self._storage._connection) self._storage._connection.poll() self.assert_(self._storage.is_connected()) # Make a call to make sure the mechanism is working self._storage.undoInfo() def testNOK(self): self._storage = self.openClientStorage(wait=0, username="foo", password="noogie", realm=self.realm) self.wait() # If the test established a connection, then it failed. self.failIf(self._storage._connection) def testUnauthenticatedMessage(self): # Test that an unauthenticated message is rejected by the server # if it was sent after the connection was authenticated. self._storage = self.openClientStorage(wait=0, username="foo", password="bar", realm=self.realm) # Sleep for 0.2 seconds to give the server some time to start up # seems to be needed before and after creating the storage self.wait() self._storage.undoInfo() # Manually clear the state of the hmac connection self._storage._connection._SizedMessageAsyncConnection__hmac_send = None # Once the client stops using the hmac, it should be disconnected. self.assertRaises(ClientDisconnected, self._storage.undoInfo) class PlainTextAuth(AuthTest): import ZEO.tests.auth_plaintext protocol = "plaintext" database = "authdb.sha" dbclass = ZEO.tests.auth_plaintext.Database realm = "Plaintext Realm" class DigestAuth(AuthTest): import ZEO.auth.auth_digest protocol = "digest" database = "authdb.digest" dbclass = ZEO.auth.auth_digest.DigestDatabase realm = "Digest Realm" test_classes = [PlainTextAuth, DigestAuth] def test_suite(): suite = unittest.TestSuite() for klass in test_classes: sub = unittest.makeSuite(klass) suite.addTest(sub) return suite if __name__ == "__main__": unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/TestThread.py0000644000175000017500000000427012214017464021273 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """A Thread base class for use with unittest.""" import threading import sys class TestThread(threading.Thread): """Base class for defining threads that run from unittest. The subclass should define a testrun() method instead of a run() method. Call cleanup() when the test is done with the thread, instead of join(). If the thread exits with an uncaught exception, it's captured and re-raised when cleanup() is called. cleanup() should be called by the main thread! Trying to tell unittest that a test failed from another thread creates a nightmare of timing-depending cascading failures and missed errors (tracebacks that show up on the screen, but don't cause unittest to believe the test failed). cleanup() also joins the thread. If the thread ended without raising an uncaught exception, and the join doesn't succeed in the timeout period, then the test is made to fail with a "Thread still alive" message. """ def __init__(self, testcase): threading.Thread.__init__(self) # In case this thread hangs, don't stop Python from exiting. self.setDaemon(1) self._exc_info = None self._testcase = testcase def run(self): try: self.testrun() except: self._exc_info = sys.exc_info() def cleanup(self, timeout=15): self.join(timeout) if self._exc_info: raise self._exc_info[0], self._exc_info[1], self._exc_info[2] if self.isAlive(): self._testcase.fail("Thread did not finish: %s" % self) zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/testZEO2.py0000644000175000017500000003726712214017464020657 0ustar arnauarnau############################################################################## # # Copyright Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.testing import setupstack, renormalizing import doctest import logging import pprint import re import sys import transaction import unittest import ZEO.StorageServer import ZEO.tests.servertesting import ZODB.blob import ZODB.FileStorage import ZODB.tests.util import ZODB.utils def proper_handling_of_blob_conflicts(): r""" Conflict errors weren't properly handled when storing blobs, the result being that the storage was left in a transaction. We originally saw this when restarting a block transaction, although it doesn't really matter. Set up the storage with some initial blob data. >>> fs = ZODB.FileStorage.FileStorage('t.fs', blob_dir='t.blobs') >>> db = ZODB.DB(fs) >>> conn = db.open() >>> conn.root.b = ZODB.blob.Blob('x') >>> transaction.commit() Get the iod and first serial. We'll use the serial later to provide out-of-date data. >>> oid = conn.root.b._p_oid >>> serial = conn.root.b._p_serial >>> conn.root.b.open('w').write('y') >>> transaction.commit() >>> data = fs.load(oid)[0] Create the server: >>> server = ZEO.tests.servertesting.StorageServer('x', {'1': fs}) And an initial client. >>> zs1 = ZEO.StorageServer.ZEOStorage(server) >>> conn1 = ZEO.tests.servertesting.Connection(1) >>> zs1.notifyConnected(conn1) >>> zs1.register('1', 0) >>> zs1.tpc_begin('0', '', '', {}) >>> zs1.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '0') >>> _ = zs1.vote('0') # doctest: +ELLIPSIS 1 callAsync serialnos ... In a second client, we'll try to commit using the old serial. This will conflict. It will be blocked at the vote call. >>> zs2 = ZEO.StorageServer.ZEOStorage(server) >>> conn2 = ZEO.tests.servertesting.Connection(2) >>> zs2.notifyConnected(conn2) >>> zs2.register('1', 0) >>> zs2.tpc_begin('1', '', '', {}) >>> zs2.storeBlobStart() >>> zs2.storeBlobChunk('z') >>> zs2.storeBlobEnd(oid, serial, data, '1') >>> delay = zs2.vote('1') >>> class Sender: ... def send_reply(self, id, reply): ... print 'reply', id, reply >>> delay.set_sender(1, Sender()) >>> logger = logging.getLogger('ZEO') >>> handler = logging.StreamHandler(sys.stdout) >>> logger.setLevel(logging.INFO) >>> logger.addHandler(handler) Now, when we abort the transaction for the first client. the second client will be restarted. It will get a conflict error, that is handled correctly: >>> zs1.tpc_abort('0') # doctest: +ELLIPSIS 2 callAsync serialnos ... reply 1 None >>> fs.tpc_transaction() is not None True >>> conn2.connected True >>> logger.setLevel(logging.NOTSET) >>> logger.removeHandler(handler) >>> zs2.tpc_abort('1') >>> fs.close() """ def proper_handling_of_errors_in_restart(): r""" It's critical that if there is an error in vote that the storage isn't left in tpc. >>> fs = ZODB.FileStorage.FileStorage('t.fs', blob_dir='t.blobs') >>> server = ZEO.tests.servertesting.StorageServer('x', {'1': fs}) And an initial client. >>> zs1 = ZEO.StorageServer.ZEOStorage(server) >>> conn1 = ZEO.tests.servertesting.Connection(1) >>> zs1.notifyConnected(conn1) >>> zs1.register('1', 0) >>> zs1.tpc_begin('0', '', '', {}) >>> zs1.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '0') Intentionally break zs1: >>> zs1._store = lambda : None >>> _ = zs1.vote('0') # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: () takes no arguments (3 given) We're not in a transaction: >>> fs.tpc_transaction() is None True We can start another client and get the storage lock. >>> zs1 = ZEO.StorageServer.ZEOStorage(server) >>> conn1 = ZEO.tests.servertesting.Connection(1) >>> zs1.notifyConnected(conn1) >>> zs1.register('1', 0) >>> zs1.tpc_begin('1', '', '', {}) >>> zs1.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '1') >>> _ = zs1.vote('1') # doctest: +ELLIPSIS 1 callAsync serialnos ... >>> zs1.tpc_finish('1').set_sender(0, conn1) >>> fs.close() """ def errors_in_vote_should_clear_lock(): """ So, we arrange to get an error in vote: >>> import ZODB.MappingStorage >>> vote_should_fail = True >>> class MappingStorage(ZODB.MappingStorage.MappingStorage): ... def tpc_vote(*args): ... if vote_should_fail: ... raise ValueError ... return ZODB.MappingStorage.MappingStorage.tpc_vote(*args) >>> server = ZEO.tests.servertesting.StorageServer( ... 'x', {'1': MappingStorage()}) >>> zs = ZEO.StorageServer.ZEOStorage(server) >>> conn = ZEO.tests.servertesting.Connection(1) >>> zs.notifyConnected(conn) >>> zs.register('1', 0) >>> zs.tpc_begin('0', '', '', {}) >>> zs.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '0') >>> zs.vote('0') Traceback (most recent call last): ... ValueError When we do, the storage server's transaction lock shouldn't be held: >>> '1' in server._commit_locks False Of course, if vote suceeds, the lock will be held: >>> vote_should_fail = False >>> zs.tpc_begin('1', '', '', {}) >>> zs.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '1') >>> _ = zs.vote('1') # doctest: +ELLIPSIS 1 callAsync serialnos ... >>> '1' in server._commit_locks True >>> zs.tpc_abort('1') """ def some_basic_locking_tests(): r""" >>> itid = 0 >>> def start_trans(zs): ... global itid ... itid += 1 ... tid = str(itid) ... zs.tpc_begin(tid, '', '', {}) ... zs.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', tid) ... return tid >>> server = ZEO.tests.servertesting.StorageServer() >>> handler = logging.StreamHandler(sys.stdout) >>> handler.setFormatter(logging.Formatter( ... '%(name)s %(levelname)s\n%(message)s')) >>> logging.getLogger('ZEO').addHandler(handler) >>> logging.getLogger('ZEO').setLevel(logging.DEBUG) We start a transaction and vote, this leads to getting the lock. >>> zs1 = ZEO.tests.servertesting.client(server, '1') >>> tid1 = start_trans(zs1) >>> zs1.vote(tid1) # doctest: +ELLIPSIS ZEO.StorageServer DEBUG (test-addr-1) ('1') lock: transactions waiting: 0 ZEO.StorageServer BLATHER (test-addr-1) Preparing to commit transaction: 1 objects, 36 bytes 1 callAsync serialnos ... If another client tried to vote, it's lock request will be queued and a delay will be returned: >>> zs2 = ZEO.tests.servertesting.client(server, '2') >>> tid2 = start_trans(zs2) >>> delay = zs2.vote(tid2) ZEO.StorageServer DEBUG (test-addr-2) ('1') queue lock: transactions waiting: 1 >>> delay.set_sender(0, zs2.connection) When we end the first transaction, the queued vote gets the lock. >>> zs1.tpc_abort(tid1) # doctest: +ELLIPSIS ZEO.StorageServer DEBUG (test-addr-1) ('1') unlock: transactions waiting: 1 ZEO.StorageServer DEBUG (test-addr-2) ('1') lock: transactions waiting: 0 ZEO.StorageServer BLATHER (test-addr-2) Preparing to commit transaction: 1 objects, 36 bytes 2 callAsync serialnos ... Let's try again with the first client. The vote will be queued: >>> tid1 = start_trans(zs1) >>> delay = zs1.vote(tid1) ZEO.StorageServer DEBUG (test-addr-1) ('1') queue lock: transactions waiting: 1 If the queued transaction is aborted, it will be dequeued: >>> zs1.tpc_abort(tid1) # doctest: +ELLIPSIS ZEO.StorageServer DEBUG (test-addr-1) ('1') dequeue lock: transactions waiting: 0 BTW, voting multiple times will error: >>> zs2.vote(tid2) Traceback (most recent call last): ... StorageTransactionError: Already voting (locked) >>> tid1 = start_trans(zs1) >>> delay = zs1.vote(tid1) ZEO.StorageServer DEBUG (test-addr-1) ('1') queue lock: transactions waiting: 1 >>> delay.set_sender(0, zs1.connection) >>> zs1.vote(tid1) Traceback (most recent call last): ... StorageTransactionError: Already voting (waiting) Note that the locking activity is logged at debug level to avoid cluttering log files, however, as the number of waiting votes increased, so does the logging level: >>> clients = [] >>> for i in range(9): ... client = ZEO.tests.servertesting.client(server, str(i+10)) ... tid = start_trans(client) ... delay = client.vote(tid) ... clients.append(client) ZEO.StorageServer DEBUG (test-addr-10) ('1') queue lock: transactions waiting: 2 ZEO.StorageServer DEBUG (test-addr-11) ('1') queue lock: transactions waiting: 3 ZEO.StorageServer WARNING (test-addr-12) ('1') queue lock: transactions waiting: 4 ZEO.StorageServer WARNING (test-addr-13) ('1') queue lock: transactions waiting: 5 ZEO.StorageServer WARNING (test-addr-14) ('1') queue lock: transactions waiting: 6 ZEO.StorageServer WARNING (test-addr-15) ('1') queue lock: transactions waiting: 7 ZEO.StorageServer WARNING (test-addr-16) ('1') queue lock: transactions waiting: 8 ZEO.StorageServer WARNING (test-addr-17) ('1') queue lock: transactions waiting: 9 ZEO.StorageServer CRITICAL (test-addr-18) ('1') queue lock: transactions waiting: 10 If a client with the transaction lock disconnects, it will abort and release the lock and one of the waiting clients will get the lock. >>> zs2.notifyDisconnected() # doctest: +ELLIPSIS ZEO.StorageServer INFO (test-addr-2) disconnected during locked transaction ZEO.StorageServer CRITICAL (test-addr-2) ('1') unlock: transactions waiting: 10 ZEO.StorageServer WARNING (test-addr-1) ('1') lock: transactions waiting: 9 ZEO.StorageServer BLATHER (test-addr-1) Preparing to commit transaction: 1 objects, 36 bytes 1 callAsync serialnos ... (In practice, waiting clients won't necessarily get the lock in order.) We can find out about the current lock state, and get other server statistics using the server_status method: >>> pprint.pprint(zs1.server_status(), width=1) {'aborts': 3, 'active_txns': 10, 'commits': 0, 'conflicts': 0, 'conflicts_resolved': 0, 'connections': 11, 'loads': 0, 'lock_time': 1272653598.693882, 'start': 'Fri Apr 30 14:53:18 2010', 'stores': 13, 'timeout-thread-is-alive': 'stub', 'verifying_clients': 0, 'waiting': 9} (Note that the connections count above is off by 1 due to the way the test infrastructure works.) If clients disconnect while waiting, they will be dequeued: >>> for client in clients: ... client.notifyDisconnected() ZEO.StorageServer INFO (test-addr-10) disconnected during unlocked transaction ZEO.StorageServer WARNING (test-addr-10) ('1') dequeue lock: transactions waiting: 8 ZEO.StorageServer INFO (test-addr-11) disconnected during unlocked transaction ZEO.StorageServer WARNING (test-addr-11) ('1') dequeue lock: transactions waiting: 7 ZEO.StorageServer INFO (test-addr-12) disconnected during unlocked transaction ZEO.StorageServer WARNING (test-addr-12) ('1') dequeue lock: transactions waiting: 6 ZEO.StorageServer INFO (test-addr-13) disconnected during unlocked transaction ZEO.StorageServer WARNING (test-addr-13) ('1') dequeue lock: transactions waiting: 5 ZEO.StorageServer INFO (test-addr-14) disconnected during unlocked transaction ZEO.StorageServer WARNING (test-addr-14) ('1') dequeue lock: transactions waiting: 4 ZEO.StorageServer INFO (test-addr-15) disconnected during unlocked transaction ZEO.StorageServer DEBUG (test-addr-15) ('1') dequeue lock: transactions waiting: 3 ZEO.StorageServer INFO (test-addr-16) disconnected during unlocked transaction ZEO.StorageServer DEBUG (test-addr-16) ('1') dequeue lock: transactions waiting: 2 ZEO.StorageServer INFO (test-addr-17) disconnected during unlocked transaction ZEO.StorageServer DEBUG (test-addr-17) ('1') dequeue lock: transactions waiting: 1 ZEO.StorageServer INFO (test-addr-18) disconnected during unlocked transaction ZEO.StorageServer DEBUG (test-addr-18) ('1') dequeue lock: transactions waiting: 0 >>> zs1.tpc_abort(tid1) >>> logging.getLogger('ZEO').setLevel(logging.NOTSET) >>> logging.getLogger('ZEO').removeHandler(handler) """ def lock_sanity_check(): r""" On one occasion with 3.10.0a1 in production, we had a case where a transaction lock wasn't released properly. One possibility, fron scant log information, is that the server and ZEOStorage had different ideas about whether the ZEOStorage was locked. The timeout thread properly closed the ZEOStorage's connection, but the ZEOStorage didn't release it's lock, presumably because it thought it wasn't locked. I'm not sure why this happened. I've refactored the logic quite a bit to try to deal with this, but the consequences of this failure are so severe, I'm adding some sanity checking when queueing lock requests. Helper to manage transactions: >>> itid = 0 >>> def start_trans(zs): ... global itid ... itid += 1 ... tid = str(itid) ... zs.tpc_begin(tid, '', '', {}) ... zs.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', tid) ... return tid Set up server and logging: >>> server = ZEO.tests.servertesting.StorageServer() >>> handler = logging.StreamHandler(sys.stdout) >>> handler.setFormatter(logging.Formatter( ... '%(name)s %(levelname)s\n%(message)s')) >>> logging.getLogger('ZEO').addHandler(handler) >>> logging.getLogger('ZEO').setLevel(logging.DEBUG) Now, we'll start a transaction, get the lock and then mark the ZEOStorage as closed and see if trying to get a lock cleans it up: >>> zs1 = ZEO.tests.servertesting.client(server, '1') >>> tid1 = start_trans(zs1) >>> zs1.vote(tid1) # doctest: +ELLIPSIS ZEO.StorageServer DEBUG (test-addr-1) ('1') lock: transactions waiting: 0 ZEO.StorageServer BLATHER (test-addr-1) Preparing to commit transaction: 1 objects, 36 bytes 1 callAsync serialnos ... >>> zs1.connection = None >>> zs2 = ZEO.tests.servertesting.client(server, '2') >>> tid2 = start_trans(zs2) >>> zs2.vote(tid2) # doctest: +ELLIPSIS ZEO.StorageServer CRITICAL (test-addr-1) Still locked after disconnected. Unlocking. ZEO.StorageServer DEBUG (test-addr-2) ('1') lock: transactions waiting: 0 ZEO.StorageServer BLATHER (test-addr-2) Preparing to commit transaction: 1 objects, 36 bytes 2 callAsync serialnos ... >>> zs1.txnlog.close() >>> zs2.tpc_abort(tid2) >>> logging.getLogger('ZEO').setLevel(logging.NOTSET) >>> logging.getLogger('ZEO').removeHandler(handler) """ def test_suite(): return unittest.TestSuite(( doctest.DocTestSuite( setUp=ZODB.tests.util.setUp, tearDown=setupstack.tearDown, checker=renormalizing.RENormalizing([ (re.compile('\d+/test-addr'), ''), (re.compile("'lock_time': \d+.\d+"), 'lock_time'), (re.compile(r"'start': '[^\n]+'"), 'start'), ]), ), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/stress.py0000644000175000017500000000730312214017464020547 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """A ZEO client-server stress test to look for leaks. The stress test should run in an infinite loop and should involve multiple connections. """ # TODO: This code is currently broken. import transaction import ZODB from ZODB.MappingStorage import MappingStorage from ZODB.tests import MinPO from ZEO.ClientStorage import ClientStorage from ZEO.tests import forker import os import random import types NUM_TRANSACTIONS_PER_CONN = 10 NUM_CONNECTIONS = 10 NUM_ROOTS = 20 MAX_DEPTH = 20 MIN_OBJSIZE = 128 MAX_OBJSIZE = 2048 def an_object(): """Return an object suitable for a PersistentMapping key""" size = random.randrange(MIN_OBJSIZE, MAX_OBJSIZE) if os.path.exists("/dev/urandom"): f = open("/dev/urandom") buf = f.read(size) f.close() return buf else: f = open(MinPO.__file__) l = list(f.read(size)) f.close() random.shuffle(l) return "".join(l) def setup(cn): """Initialize the database with some objects""" root = cn.root() for i in range(NUM_ROOTS): prev = an_object() for j in range(random.randrange(1, MAX_DEPTH)): o = MinPO.MinPO(prev) prev = o root[an_object()] = o transaction.commit() cn.close() def work(cn): """Do some work with a transaction""" cn.sync() root = cn.root() obj = random.choice(root.values()) # walk down to the bottom while not isinstance(obj.value, types.StringType): obj = obj.value obj.value = an_object() transaction.commit() def main(): # Yuck! Need to cleanup forker so that the API is consistent # across Unix and Windows, at least if that's possible. if os.name == "nt": zaddr, tport, pid = forker.start_zeo_server('MappingStorage', ()) def exitserver(): import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(tport) s.close() else: zaddr = '', random.randrange(20000, 30000) pid, exitobj = forker.start_zeo_server(MappingStorage(), zaddr) def exitserver(): exitobj.close() while 1: pid = start_child(zaddr) print "started", pid os.waitpid(pid, 0) exitserver() def start_child(zaddr): pid = os.fork() if pid != 0: return pid try: _start_child(zaddr) finally: os._exit(0) def _start_child(zaddr): storage = ClientStorage(zaddr, debug=1, min_disconnect_poll=0.5, wait=1) db = ZODB.DB(storage, pool_size=NUM_CONNECTIONS) setup(db.open()) conns = [] conn_count = 0 for i in range(NUM_CONNECTIONS): c = db.open() c.__count = 0 conns.append(c) conn_count += 1 while conn_count < 25: c = random.choice(conns) if c.__count > NUM_TRANSACTIONS_PER_CONN: conns.remove(c) c.close() conn_count += 1 c = db.open() c.__count = 0 conns.append(c) else: c.__count += 1 work(c) if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/invalidation-age.txt0000644000175000017500000001142412214017464022625 0ustar arnauarnauInvalidation age ================ When a ZEO client with a non-empty cache connects to the server, it needs to verify whether the data in its cache is current. It does this in one of 2 ways: quick verification It gets a list of invalidations from the server since the last transaction the client has seen and applies those to it's disk and in-memory caches. This is only possible if there haven't been too many transactions since the client was last connected. full verification If quick verification isn't possible, the client iterates through it's disk cache asking the server to verify whether each current entry is valid. Unfortunately, for large caches, full verification is soooooo not quick that it is impractical. Quick verificatioin is highly desireable. To support quick verification, the server keeps a list of recent invalidations. The size of this list is controlled by the invalidation_queue_size parameter. If there is a lot of database activity, the size might need to be quite large to support having clients be disconnected for more than a few minutes. A very large invalidation queue size can use a lot of memory. To suppliment the invalidation queue, you can also specify an invalidation_age parameter. When a client connects and presents the last transaction id it has seen, we first check to see if the invalidation queue has that transaction id. It it does, then we send all transactions since that id. Otherwise, we check to see if the difference between storage's last transaction id and the given id is less than or equal to the invalidation age. If it is, then we iterate over the storage, starting with the given id, to get the invalidations since the given id. NOTE: This assumes that iterating from a point near the "end" of a database is inexpensive. Don't use this option for a storage for which that is not the case. Here's an example. We set up a server, using an invalidation-queue-size of 5: >>> addr, admin = start_server(zeo_conf=dict(invalidation_queue_size=5), ... keep=True) Now, we'll open a client with a persistent cache, set up some data, and then close client: >>> import ZEO, transaction >>> db = ZEO.DB(addr, client='test') >>> conn = db.open() >>> for i in range(9): ... conn.root()[i] = conn.root().__class__() ... conn.root()[i].x = 0 >>> transaction.commit() >>> db.close() We'll open another client, and commit some transactions: >>> db = ZEO.DB(addr) >>> conn = db.open() >>> import transaction >>> for i in range(2): ... conn.root()[i].x = 1 ... transaction.commit() >>> db.close() If we reopen the first client, we'll do quick verification. We'll turn on logging so we can see this: >>> import logging, sys >>> old_logging_level = logging.getLogger().getEffectiveLevel() >>> logging.getLogger().setLevel(logging.INFO) >>> handler = logging.StreamHandler(sys.stdout) >>> logging.getLogger().addHandler(handler) >>> db = ZEO.DB(addr, client='test') # doctest: +ELLIPSIS ('localhost', ... ('localhost', ...) Recovering 2 invalidations >>> logging.getLogger().removeHandler(handler) >>> [v.x for v in db.open().root().values()] [1, 1, 0, 0, 0, 0, 0, 0, 0] Now, if we disconnect and commit more than 5 transactions, we'll see that verification is necessary: >>> db.close() >>> db = ZEO.DB(addr) >>> conn = db.open() >>> import transaction >>> for i in range(9): ... conn.root()[i].x = 2 ... transaction.commit() >>> db.close() >>> logging.getLogger().addHandler(handler) >>> db = ZEO.DB(addr, client='test') # doctest: +ELLIPSIS ('localhost', ... ('localhost', ...) Verifying cache ('localhost', ...) endVerify finishing ('localhost', ...) endVerify finished >>> logging.getLogger().removeHandler(handler) >>> [v.x for v in db.open().root().values()] [2, 2, 2, 2, 2, 2, 2, 2, 2] >>> db.close() But if we restart the server with invalidation-age set, we can do quick verification: >>> stop_server(admin) >>> addr, admin = start_server(zeo_conf=dict(invalidation_queue_size=5, ... invalidation_age=100)) >>> db = ZEO.DB(addr) >>> conn = db.open() >>> import transaction >>> for i in range(9): ... conn.root()[i].x = 3 ... transaction.commit() >>> db.close() >>> logging.getLogger().addHandler(handler) >>> db = ZEO.DB(addr, client='test') # doctest: +ELLIPSIS ('localhost', ... ('localhost', ...) Recovering 9 invalidations >>> logging.getLogger().removeHandler(handler) >>> [v.x for v in db.open().root().values()] [3, 3, 3, 3, 3, 3, 3, 3, 3] >>> db.close() >>> logging.getLogger().setLevel(old_logging_level) zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/protocols.test0000644000175000017500000001175312214017464021603 0ustar arnauarnauTest that multiple protocols are supported ========================================== A full test of all protocols isn't practical. But we'll do a limited test that at least the current and previous protocols are supported in both directions. Let's start a Z308 server >>> storage_conf = ''' ... ... blob-dir server-blobs ... ... path Data.fs ... ... ... ''' >>> addr, admin = start_server( ... storage_conf, dict(invalidation_queue_size=5), protocol='Z308') A current client should be able to connect to a old server: >>> import ZEO, ZODB.blob, transaction >>> db = ZEO.DB(addr, client='client', blob_dir='blobs') >>> wait_connected(db.storage) >>> db.storage._connection.peer_protocol_version 'Z308' >>> conn = db.open() >>> conn.root().x = 0 >>> transaction.commit() >>> len(db.history(conn.root()._p_oid, 99)) 2 >>> conn.root()['blob1'] = ZODB.blob.Blob() >>> conn.root()['blob1'].open('w').write('blob data 1') >>> transaction.commit() >>> db2 = ZEO.DB(addr, blob_dir='server-blobs', shared_blob_dir=True) >>> wait_connected(db2.storage) >>> conn2 = db2.open() >>> for i in range(5): ... conn2.root().x += 1 ... transaction.commit() >>> conn2.root()['blob2'] = ZODB.blob.Blob() >>> conn2.root()['blob2'].open('w').write('blob data 2') >>> transaction.commit() >>> @wait_until("Get the new data") ... def f(): ... conn.sync() ... return conn.root().x == 5 >>> db.close() >>> for i in range(2): ... conn2.root().x += 1 ... transaction.commit() >>> db = ZEO.DB(addr, client='client', blob_dir='blobs') >>> wait_connected(db.storage) >>> conn = db.open() >>> conn.root().x 7 >>> db.close() >>> for i in range(10): ... conn2.root().x += 1 ... transaction.commit() >>> db = ZEO.DB(addr, client='client', blob_dir='blobs') >>> wait_connected(db.storage) >>> conn = db.open() >>> conn.root().x 17 >>> conn.root()['blob1'].open().read() 'blob data 1' >>> conn.root()['blob2'].open().read() 'blob data 2' Note that when taking to a 3.8 server, iteration won't work: >>> db.storage.iterator() Traceback (most recent call last): ... NotImplementedError >>> db2.close() >>> db.close() >>> stop_server(admin) >>> import os, zope.testing.setupstack >>> os.remove('client-1.zec') >>> zope.testing.setupstack.rmtree('blobs') >>> zope.testing.setupstack.rmtree('server-blobs') And the other way around: >>> addr, _ = start_server(storage_conf, dict(invalidation_queue_size=5)) Note that we'll have to pull some hijinks: >>> import ZEO.zrpc.connection >>> old_current_protocol = ZEO.zrpc.connection.Connection.current_protocol >>> ZEO.zrpc.connection.Connection.current_protocol = 'Z308' >>> db = ZEO.DB(addr, client='client', blob_dir='blobs') >>> db.storage._connection.peer_protocol_version 'Z308' >>> wait_connected(db.storage) >>> conn = db.open() >>> conn.root().x = 0 >>> transaction.commit() >>> len(db.history(conn.root()._p_oid, 99)) 2 >>> conn.root()['blob1'] = ZODB.blob.Blob() >>> conn.root()['blob1'].open('w').write('blob data 1') >>> transaction.commit() >>> db2 = ZEO.DB(addr, blob_dir='server-blobs', shared_blob_dir=True) >>> wait_connected(db2.storage) >>> conn2 = db2.open() >>> for i in range(5): ... conn2.root().x += 1 ... transaction.commit() >>> conn2.root()['blob2'] = ZODB.blob.Blob() >>> conn2.root()['blob2'].open('w').write('blob data 2') >>> transaction.commit() >>> @wait_until() ... def x_to_be_5(): ... conn.sync() ... return conn.root().x == 5 >>> db.close() >>> for i in range(2): ... conn2.root().x += 1 ... transaction.commit() >>> db = ZEO.DB(addr, client='client', blob_dir='blobs') >>> wait_connected(db.storage) >>> conn = db.open() >>> conn.root().x 7 >>> db.close() >>> for i in range(10): ... conn2.root().x += 1 ... transaction.commit() >>> db = ZEO.DB(addr, client='client', blob_dir='blobs') >>> wait_connected(db.storage) >>> conn = db.open() >>> conn.root().x 17 >>> conn.root()['blob1'].open().read() 'blob data 1' >>> conn.root()['blob2'].open().read() 'blob data 2' Make some old protocol calls: >>> db.storage._server.rpc.call('getSerial', conn.root()._p_oid ... ) == conn.root()._p_serial True >>> p, s, v, x, y = db.storage._server.rpc.call('zeoLoad', ... conn.root()._p_oid) >>> (v, x, y) == ('', None, None) True >>> db.storage.load(conn.root()._p_oid) == (p, s) True >>> db2.close() >>> db.close() Undo the hijinks: >>> ZEO.zrpc.connection.Connection.current_protocol = old_current_protocol zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/Cache.py0000644000175000017500000000347612214017464020236 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Tests of the ZEO cache""" from ZODB.tests.MinPO import MinPO from ZODB.tests.StorageTestBase import zodb_unpickle from transaction import Transaction class TransUndoStorageWithCache: def checkUndoInvalidation(self): oid = self._storage.new_oid() revid = self._dostore(oid, data=MinPO(23)) revid = self._dostore(oid, revid=revid, data=MinPO(24)) revid = self._dostore(oid, revid=revid, data=MinPO(25)) info = self._storage.undoInfo() if not info: # Preserved this comment, but don't understand it: # "Perhaps we have an old storage implementation that # does do the negative nonsense." info = self._storage.undoInfo(0, 20) tid = info[0]['id'] # Now start an undo transaction t = Transaction() t.note('undo1') oids = self._begin_undos_vote(t, tid) # Make sure this doesn't load invalid data into the cache self._storage.load(oid, '') self._storage.tpc_finish(t) assert len(oids) == 1 assert oids[0] == oid data, revid = self._storage.load(oid, '') obj = zodb_unpickle(data) assert obj == MinPO(24) zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/IterationTests.py0000644000175000017500000001523312214017464022206 0ustar arnauarnau############################################################################## # # Copyright (c) 2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ZEO iterator protocol tests.""" import transaction class IterationTests: def checkIteratorGCProtocol(self): # Test garbage collection on protocol level. server = self._storage._server iid = server.iterator_start(None, None) # None signals the end of iteration. self.assertEquals(None, server.iterator_next(iid)) # The server has disposed the iterator already. self.assertRaises(KeyError, server.iterator_next, iid) iid = server.iterator_start(None, None) # This time, we tell the server to throw the iterator away. server.iterator_gc([iid]) self.assertRaises(KeyError, server.iterator_next, iid) def checkIteratorExhaustionStorage(self): # Test the storage's garbage collection mechanism. self._dostore() iterator = self._storage.iterator() # At this point, a wrapping iterator might not have called the CS # iterator yet. We'll consume one item to make sure this happens. iterator.next() self.assertEquals(1, len(self._storage._iterator_ids)) iid = list(self._storage._iterator_ids)[0] self.assertEquals([], list(iterator)) self.assertEquals(0, len(self._storage._iterator_ids)) # The iterator has run through, so the server has already disposed it. self.assertRaises(KeyError, self._storage._server.iterator_next, iid) def checkIteratorGCSpanTransactions(self): # Keep a hard reference to the iterator so it won't be automatically # garbage collected at the transaction boundary. self._dostore() iterator = self._storage.iterator() self._dostore() # As the iterator was not garbage collected, we can still use it. (We # don't see the transaction we just wrote being picked up, because # iterators only see the state from the point in time when they were # created.) self.assert_(list(iterator)) def checkIteratorGCStorageCommitting(self): # We want the iterator to be garbage-collected, so we don't keep any # hard references to it. The storage tracks its ID, though. # The odd little jig we do below arises from the fact that the # CS iterator may not be constructed right away if the CS is wrapped. # We need to actually do some iteration to get the iterator created. # We do a store to make sure the iterator isn't exhausted right away. self._dostore() self._storage.iterator().next() self.assertEquals(1, len(self._storage._iterator_ids)) iid = list(self._storage._iterator_ids)[0] # GC happens at the transaction boundary. After that, both the storage # and the server have forgotten the iterator. self._dostore() self.assertEquals(0, len(self._storage._iterator_ids)) self.assertRaises(KeyError, self._storage._server.iterator_next, iid) def checkIteratorGCStorageTPCAborting(self): # The odd little jig we do below arises from the fact that the # CS iterator may not be constructed right away if the CS is wrapped. # We need to actually do some iteration to get the iterator created. # We do a store to make sure the iterator isn't exhausted right away. self._dostore() self._storage.iterator().next() iid = list(self._storage._iterator_ids)[0] t = transaction.Transaction() self._storage.tpc_begin(t) self._storage.tpc_abort(t) self.assertEquals(0, len(self._storage._iterator_ids)) self.assertRaises(KeyError, self._storage._server.iterator_next, iid) def checkIteratorGCStorageDisconnect(self): # The odd little jig we do below arises from the fact that the # CS iterator may not be constructed right away if the CS is wrapped. # We need to actually do some iteration to get the iterator created. # We do a store to make sure the iterator isn't exhausted right away. self._dostore() self._storage.iterator().next() iid = list(self._storage._iterator_ids)[0] t = transaction.Transaction() self._storage.tpc_begin(t) # Show that after disconnecting, the client side GCs the iterators # as well. I'm calling this directly to avoid accidentally # calling tpc_abort implicitly. self._storage.notifyDisconnected() self.assertEquals(0, len(self._storage._iterator_ids)) def checkIteratorParallel(self): self._dostore() self._dostore() iter1 = self._storage.iterator() iter2 = self._storage.iterator() txn_info1 = iter1.next() txn_info2 = iter2.next() self.assertEquals(txn_info1.tid, txn_info2.tid) txn_info1 = iter1.next() txn_info2 = iter2.next() self.assertEquals(txn_info1.tid, txn_info2.tid) self.assertRaises(StopIteration, iter1.next) self.assertRaises(StopIteration, iter2.next) def iterator_sane_after_reconnect(): r"""Make sure that iterators are invalidated on disconnect. Start a server: >>> addr, adminaddr = start_server( ... '\npath fs\n', keep=1) Open a client storage to it and commit a some transactions: >>> import ZEO, transaction >>> db = ZEO.DB(addr) >>> conn = db.open() >>> for i in range(10): ... conn.root().i = i ... transaction.commit() Create an iterator: >>> it = conn._storage.iterator() >>> tid1 = it.next().tid Restart the storage: >>> stop_server(adminaddr) >>> wait_disconnected(conn._storage) >>> _ = start_server('\npath fs\n', addr=addr) >>> wait_connected(conn._storage) Now, we'll create a second iterator: >>> it2 = conn._storage.iterator() If we try to advance the first iterator, we should get an error: >>> it.next().tid > tid1 Traceback (most recent call last): ... ClientDisconnected: Disconnected iterator The second iterator should be peachy: >>> it2.next().tid == tid1 True Cleanup: >>> db.close() """ zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/drop_cache_rather_than_verify.txt0000644000175000017500000001333312214017464025445 0ustar arnauarnauAvoiding cache verifification ============================= For large databases it is common to also use very large ZEO cache files. If a client has beed disconnected for too long, cache verification might be necessary, but cache verification can be very hard on the storage server. When verification is needed, a ZEO.interfaces.StaleCache event is published. Applications may handle this event to perform actions such as exiting the process to avoid a cold restart. ClientStorage provides an option to drop it's cache rather than doing verification. When this option is used, and verification would be necessary, after publishing the event, ClientStorage: - Invalidates all object caches - Drops or clears it's client cache. (The end result is that the cache is working but empty.) - Logs a CRITICAL message. Here's an example that shows that this is actually what happens. Start a server, create a cient to it and commit some data >>> addr, admin = start_server(keep=1) >>> import ZEO, transaction >>> db = ZEO.DB(addr, drop_cache_rather_verify=True, client='cache', ... name='test') >>> wait_connected(db.storage) >>> conn = db.open() >>> conn.root()[1] = conn.root().__class__() >>> conn.root()[1].x = 1 >>> transaction.commit() >>> len(db.storage._cache) 3 Now, we'll stop the server and restart with a different address: >>> stop_server(admin) >>> addr2, admin = start_server(keep=1) And create another client and write some data to it: >>> db2 = ZEO.DB(addr2) >>> wait_connected(db2.storage) >>> conn2 = db2.open() >>> for i in range(5): ... conn2.root()[1].x += 1 ... transaction.commit() >>> db2.close() >>> stop_server(admin) Now, we'll restart the server. Before we do that, we'll capture logging and event data: >>> import logging, zope.testing.loggingsupport, zope.event >>> handler = zope.testing.loggingsupport.InstalledHandler( ... 'ZEO.ClientStorage', level=logging.ERROR) >>> events = [] >>> def event_handler(e): ... events.append(( ... len(e.storage._cache), str(handler), e.__class__.__name__)) >>> zope.event.subscribers.append(event_handler) Note that the event handler is saving away the length of the cache and the state of the log handler. We'll use this to show that the event is generated before the cache is dropped or the message is logged. Now, we'll restart the server on the original address: >>> _, admin = start_server(zeo_conf=dict(invalidation_queue_size=1), ... addr=addr, keep=1) >>> wait_connected(db.storage) Now, let's verify our assertions above: - Publishes a stale-cache event. >>> for e in events: ... print e (3, '', 'StaleCache') Note that the length of the cache when the event handler was called waa non-zero. This is because the cache wasn't cleared yet. Similarly, the dropping-cache message hasn't been logged yet. >>> del events[:] - Drops or clears it's client cache. (The end result is that the cache is working but empty.) >>> len(db.storage._cache) 0 - Invalidates all object caches >>> transaction.abort() >>> conn.root()._p_changed - Logs a CRITICAL message. >>> print handler ZEO.ClientStorage CRITICAL test dropping stale cache >>> handler.clear() If we access the root object, it'll be loaded from the server: >>> conn.root()[1].x 6 >>> len(db.storage._cache) 2 Similarly, if we simply disconnect the client, and write data from another client: >>> db.close() >>> db2 = ZEO.DB(addr) >>> wait_connected(db2.storage) >>> conn2 = db2.open() >>> for i in range(5): ... conn2.root()[1].x += 1 ... transaction.commit() >>> db2.close() >>> db = ZEO.DB(addr, drop_cache_rather_verify=True, client='cache', ... name='test') >>> wait_connected(db.storage) - Drops or clears it's client cache. (The end result is that the cache is working but empty.) >>> len(db.storage._cache) 1 (When a database is created, it checks to make sure the root object is in the database, which is why we get 1, rather than 0 objects in the cache.) - Publishes a stake-cache event. >>> for e in events: ... print e (2, '', 'StaleCache') >>> del events[:] - Logs a CRITICAL message. >>> print handler ZEO.ClientStorage CRITICAL test dropping stale cache >>> handler.clear() If we access the root object, it'll be loaded from the server: >>> conn = db.open() >>> conn.root()[1].x 11 Finally, let's look at what happens without the drop_cache_rather_verify option: >>> db.close() >>> db = ZEO.DB(addr, client='cache') >>> wait_connected(db.storage) >>> conn = db.open() >>> conn.root()[1].x 11 >>> conn.root()[2] = conn.root().__class__() >>> transaction.commit() >>> len(db.storage._cache) 4 >>> stop_server(admin) >>> addr2, admin = start_server(keep=1) >>> db2 = ZEO.DB(addr2) >>> wait_connected(db2.storage) >>> conn2 = db2.open() >>> for i in range(5): ... conn2.root()[1].x += 1 ... transaction.commit() >>> db2.close() >>> stop_server(admin) >>> _, admin = start_server(zeo_conf=dict(invalidation_queue_size=1), ... addr=addr) >>> wait_connected(db.storage) >>> for e in events: ... print e (4, '', 'StaleCache') >>> print handler >>> len(db.storage._cache) 3 Here we see the cache wasn't dropped, although one of the records was invalidated during verification. .. Cleanup >>> db.close() >>> handler.uninstall() >>> zope.event.subscribers.remove(event_handler) zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/InvalidationTests.py0000644000175000017500000004060012214017464022665 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import threading import time from random import Random import transaction from BTrees.check import check, display from BTrees.OOBTree import OOBTree from ZEO.tests.TestThread import TestThread from ZODB.DB import DB from ZODB.POSException import ReadConflictError, ConflictError # The tests here let several threads have a go at one or more database # instances simultaneously. Each thread appends a disjoint (from the # other threads) sequence of increasing integers to an OOBTree, one at # at time (per thread). This provokes lots of conflicts, and BTrees # work hard at conflict resolution too. An OOBTree is used because # that flavor has the smallest maximum bucket size, and so splits buckets # more often than other BTree flavors. # # When these tests were first written, they provoked an amazing number # of obscure timing-related bugs in cache consistency logic, revealed # by failure of the BTree to pass internal consistency checks at the end, # and/or by failure of the BTree to contain all the keys the threads # thought they added (i.e., the keys for which transaction.commit() # did not raise any exception). class FailableThread(TestThread): # mixin class # subclass must provide # - self.stop attribute (an event) # - self._testrun() method # TestThread.run() invokes testrun(). def testrun(self): try: self._testrun() except: # Report the failure here to all the other threads, so # that they stop quickly. self.stop.set() raise class StressTask: # Append integers startnum, startnum + step, startnum + 2*step, ... # to 'tree'. If sleep is given, sleep # that long after each append. At the end, instance var .added_keys # is a list of the ints the thread believes it added successfully. def __init__(self, db, threadnum, startnum, step=2, sleep=None): self.db = db self.threadnum = threadnum self.startnum = startnum self.step = step self.sleep = sleep self.added_keys = [] self.tm = transaction.TransactionManager() self.cn = self.db.open(transaction_manager=self.tm) self.cn.sync() def doStep(self): tree = self.cn.root()["tree"] key = self.startnum tree[key] = self.threadnum def commit(self): cn = self.cn key = self.startnum self.tm.get().note("add key %s" % key) try: self.tm.get().commit() except ConflictError, msg: self.tm.abort() else: if self.sleep: time.sleep(self.sleep) self.added_keys.append(key) self.startnum += self.step def cleanup(self): self.tm.get().abort() self.cn.close() def _runTasks(rounds, *tasks): '''run *task* interleaved for *rounds* rounds.''' def commit(run, actions): actions.append(':') for t in run: t.commit() del run[:] r = Random() r.seed(1064589285) # make it deterministic run = [] actions = [] try: for i in range(rounds): t = r.choice(tasks) if t in run: commit(run, actions) run.append(t) t.doStep() actions.append(`t.startnum`) commit(run,actions) # stderr.write(' '.join(actions)+'\n') finally: for t in tasks: t.cleanup() class StressThread(FailableThread): # Append integers startnum, startnum + step, startnum + 2*step, ... # to 'tree' until Event stop is set. If sleep is given, sleep # that long after each append. At the end, instance var .added_keys # is a list of the ints the thread believes it added successfully. def __init__(self, testcase, db, stop, threadnum, commitdict, startnum, step=2, sleep=None): TestThread.__init__(self, testcase) self.db = db self.stop = stop self.threadnum = threadnum self.startnum = startnum self.step = step self.sleep = sleep self.added_keys = [] self.commitdict = commitdict def _testrun(self): tm = transaction.TransactionManager() cn = self.db.open(transaction_manager=tm) while not self.stop.isSet(): try: tree = cn.root()["tree"] break except (ConflictError, KeyError): tm.abort() key = self.startnum while not self.stop.isSet(): try: tree[key] = self.threadnum tm.get().note("add key %s" % key) tm.commit() self.commitdict[self] = 1 if self.sleep: time.sleep(self.sleep) except (ReadConflictError, ConflictError), msg: tm.abort() else: self.added_keys.append(key) key += self.step cn.close() class LargeUpdatesThread(FailableThread): # A thread that performs a lot of updates. It attempts to modify # more than 25 objects so that it can test code that runs vote # in a separate thread when it modifies more than 25 objects. def __init__(self, test, db, stop, threadnum, commitdict, startnum, step=2, sleep=None): TestThread.__init__(self, test) self.db = db self.stop = stop self.threadnum = threadnum self.startnum = startnum self.step = step self.sleep = sleep self.added_keys = [] self.commitdict = commitdict def _testrun(self): cn = self.db.open() while not self.stop.isSet(): try: tree = cn.root()["tree"] break except (ConflictError, KeyError): # print "%d getting tree abort" % self.threadnum transaction.abort() keys_added = {} # set of keys we commit tkeys = [] while not self.stop.isSet(): # The test picks 50 keys spread across many buckets. # self.startnum and self.step ensure that all threads use # disjoint key sets, to minimize conflict errors. nkeys = len(tkeys) if nkeys < 50: tkeys = range(self.startnum, 3000, self.step) nkeys = len(tkeys) step = max(int(nkeys / 50), 1) keys = [tkeys[i] for i in range(0, nkeys, step)] for key in keys: try: tree[key] = self.threadnum except (ReadConflictError, ConflictError), msg: # print "%d setting key %s" % (self.threadnum, msg) transaction.abort() break else: # print "%d set #%d" % (self.threadnum, len(keys)) transaction.get().note("keys %s" % ", ".join(map(str, keys))) try: transaction.commit() self.commitdict[self] = 1 if self.sleep: time.sleep(self.sleep) except ConflictError, msg: # print "%d commit %s" % (self.threadnum, msg) transaction.abort() continue for k in keys: tkeys.remove(k) keys_added[k] = 1 self.added_keys = keys_added.keys() cn.close() class InvalidationTests: # Minimum # of seconds the main thread lets the workers run. The # test stops as soon as this much time has elapsed, and all threads # have managed to commit a change. MINTIME = 10 # Maximum # of seconds the main thread lets the workers run. We # stop after this long has elapsed regardless of whether all threads # have managed to commit a change. MAXTIME = 300 StressThread = StressThread def _check_tree(self, cn, tree): # Make sure the BTree is sane at the C level. retries = 3 while retries: retries -= 1 try: check(tree) tree._check() except ReadConflictError: if retries: transaction.abort() else: raise except: display(tree) raise def _check_threads(self, tree, *threads): # Make sure the thread's view of the world is consistent with # the actual database state. expected_keys = [] errormsgs = [] err = errormsgs.append for t in threads: if not t.added_keys: err("thread %d didn't add any keys" % t.threadnum) expected_keys.extend(t.added_keys) expected_keys.sort() for i in range(100): tree._p_jar.sync() actual_keys = list(tree.keys()) if expected_keys == actual_keys: break time.sleep(.1) else: err("expected keys != actual keys") for k in expected_keys: if k not in actual_keys: err("key %s expected but not in tree" % k) for k in actual_keys: if k not in expected_keys: err("key %s in tree but not expected" % k) self.fail('\n'.join(errormsgs)) def go(self, stop, commitdict, *threads): # Run the threads for t in threads: t.start() delay = self.MINTIME start = time.time() while time.time() - start <= self.MAXTIME: stop.wait(delay) if stop.isSet(): # Some thread failed. Stop right now. break delay = 2.0 if len(commitdict) >= len(threads): break # Some thread still hasn't managed to commit anything. stop.set() # Give all the threads some time to stop before trying to clean up. # cleanup() will cause the test to fail if some thread ended with # an uncaught exception, and unittest will call the base class # tearDown then immediately, but if other threads are still # running that can lead to a cascade of spurious exceptions. for t in threads: t.join(30) for t in threads: t.cleanup(10) def checkConcurrentUpdates2Storages_emulated(self): self._storage = storage1 = self.openClientStorage() storage2 = self.openClientStorage() db1 = DB(storage1) db2 = DB(storage2) cn = db1.open() tree = cn.root()["tree"] = OOBTree() transaction.commit() # DM: allow time for invalidations to come in and process them time.sleep(0.1) # Run two threads that update the BTree t1 = StressTask(db1, 1, 1,) t2 = StressTask(db2, 2, 2,) _runTasks(100, t1, t2) cn.sync() self._check_tree(cn, tree) self._check_threads(tree, t1, t2) cn.close() db1.close() db2.close() def checkConcurrentUpdates2Storages(self): self._storage = storage1 = self.openClientStorage() storage2 = self.openClientStorage() db1 = DB(storage1) db2 = DB(storage2) stop = threading.Event() cn = db1.open() tree = cn.root()["tree"] = OOBTree() transaction.commit() cn.close() # Run two threads that update the BTree cd = {} t1 = self.StressThread(self, db1, stop, 1, cd, 1) t2 = self.StressThread(self, db2, stop, 2, cd, 2) self.go(stop, cd, t1, t2) while db1.lastTransaction() != db2.lastTransaction(): db1._storage.sync() db2._storage.sync() cn = db1.open() tree = cn.root()["tree"] self._check_tree(cn, tree) self._check_threads(tree, t1, t2) cn.close() db1.close() db2.close() def checkConcurrentUpdates19Storages(self): n = 19 dbs = [DB(self.openClientStorage()) for i in range(n)] self._storage = dbs[0].storage stop = threading.Event() cn = dbs[0].open() tree = cn.root()["tree"] = OOBTree() transaction.commit() cn.close() # Run threads that update the BTree cd = {} threads = [self.StressThread(self, dbs[i], stop, i, cd, i, n) for i in range(n)] self.go(stop, cd, *threads) while len(set(db.lastTransaction() for db in dbs)) > 1: _ = [db._storage.sync() for db in dbs] cn = dbs[0].open() tree = cn.root()["tree"] self._check_tree(cn, tree) self._check_threads(tree, *threads) cn.close() _ = [db.close() for db in dbs] def checkConcurrentUpdates1Storage(self): self._storage = storage1 = self.openClientStorage() db1 = DB(storage1) stop = threading.Event() cn = db1.open() tree = cn.root()["tree"] = OOBTree() transaction.commit() cn.close() # Run two threads that update the BTree cd = {} t1 = self.StressThread(self, db1, stop, 1, cd, 1, sleep=0.01) t2 = self.StressThread(self, db1, stop, 2, cd, 2, sleep=0.01) self.go(stop, cd, t1, t2) cn = db1.open() tree = cn.root()["tree"] self._check_tree(cn, tree) self._check_threads(tree, t1, t2) cn.close() db1.close() def checkConcurrentUpdates2StoragesMT(self): self._storage = storage1 = self.openClientStorage() db1 = DB(storage1) db2 = DB(self.openClientStorage()) stop = threading.Event() cn = db1.open() tree = cn.root()["tree"] = OOBTree() transaction.commit() cn.close() # Run three threads that update the BTree. # Two of the threads share a single storage so that it # is possible for both threads to read the same object # at the same time. cd = {} t1 = self.StressThread(self, db1, stop, 1, cd, 1, 3) t2 = self.StressThread(self, db2, stop, 2, cd, 2, 3, 0.01) t3 = self.StressThread(self, db2, stop, 3, cd, 3, 3, 0.01) self.go(stop, cd, t1, t2, t3) while db1.lastTransaction() != db2.lastTransaction(): time.sleep(.1) time.sleep(.1) cn = db1.open() tree = cn.root()["tree"] self._check_tree(cn, tree) self._check_threads(tree, t1, t2, t3) cn.close() db1.close() db2.close() def checkConcurrentLargeUpdates(self): # Use 3 threads like the 2StorageMT test above. self._storage = storage1 = self.openClientStorage() db1 = DB(storage1) db2 = DB(self.openClientStorage()) stop = threading.Event() cn = db1.open() tree = cn.root()["tree"] = OOBTree() for i in range(0, 3000, 2): tree[i] = 0 transaction.commit() cn.close() # Run three threads that update the BTree. # Two of the threads share a single storage so that it # is possible for both threads to read the same object # at the same time. cd = {} t1 = LargeUpdatesThread(self, db1, stop, 1, cd, 1, 3, 0.02) t2 = LargeUpdatesThread(self, db2, stop, 2, cd, 2, 3, 0.01) t3 = LargeUpdatesThread(self, db2, stop, 3, cd, 3, 3, 0.01) self.go(stop, cd, t1, t2, t3) while db1.lastTransaction() != db2.lastTransaction(): db1._storage.sync() db2._storage.sync() cn = db1.open() tree = cn.root()["tree"] self._check_tree(cn, tree) # Purge the tree of the dummy entries mapping to 0. losers = [k for k, v in tree.items() if v == 0] for k in losers: del tree[k] transaction.commit() self._check_threads(tree, t1, t2, t3) cn.close() db1.close() db2.close() zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/ConnectionTests.py0000644000175000017500000013233712214017464022354 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import os import time import socket import asyncore import threading import logging import ZEO.ServerStub from ZEO.ClientStorage import ClientStorage from ZEO.Exceptions import ClientDisconnected from ZEO.zrpc.marshal import encode from ZEO.tests import forker from ZODB.DB import DB from ZODB.POSException import ReadOnlyError, ConflictError from ZODB.tests.StorageTestBase import StorageTestBase from ZODB.tests.MinPO import MinPO from ZODB.tests.StorageTestBase \ import zodb_pickle, zodb_unpickle, handle_all_serials, handle_serials import ZODB.tests.util import transaction from transaction import Transaction logger = logging.getLogger('ZEO.tests.ConnectionTests') ZERO = '\0'*8 class TestServerStub(ZEO.ServerStub.StorageServer): __super_getInvalidations = ZEO.ServerStub.StorageServer.getInvalidations def getInvalidations(self, tid): # squirrel the results away for inspection by test case self._last_invals = self.__super_getInvalidations(tid) return self._last_invals class TestClientStorage(ClientStorage): test_connection = False StorageServerStubClass = TestServerStub connection_count_for_tests = 0 def notifyConnected(self, conn): ClientStorage.notifyConnected(self, conn) self.connection_count_for_tests += 1 def verify_cache(self, stub): self.end_verify = threading.Event() self.verify_result = ClientStorage.verify_cache(self, stub) def endVerify(self): ClientStorage.endVerify(self) self.end_verify.set() def testConnection(self, conn): try: return ClientStorage.testConnection(self, conn) finally: self.test_connection = True class DummyDB: def invalidate(self, *args, **kwargs): pass def invalidateCache(self): pass class CommonSetupTearDown(StorageTestBase): """Common boilerplate""" __super_setUp = StorageTestBase.setUp __super_tearDown = StorageTestBase.tearDown keep = 0 invq = None timeout = None monitor = 0 db_class = DummyDB def setUp(self, before=None): """Test setup for connection tests. This starts only one server; a test may start more servers by calling self._newAddr() and then self.startServer(index=i) for i in 1, 2, ... """ self.__super_setUp() logging.info("setUp() %s", self.id()) self.file = 'storage_conf' self.addr = [] self._pids = [] self._servers = [] self.conf_paths = [] self.caches = [] self._newAddr() self.startServer() # self._old_log_level = logging.getLogger().getEffectiveLevel() # logging.getLogger().setLevel(logging.WARNING) # self._log_handler = logging.StreamHandler() # logging.getLogger().addHandler(self._log_handler) def tearDown(self): """Try to cause the tests to halt""" # logging.getLogger().setLevel(self._old_log_level) # logging.getLogger().removeHandler(self._log_handler) # logging.info("tearDown() %s" % self.id()) for p in self.conf_paths: os.remove(p) if getattr(self, '_storage', None) is not None: self._storage.close() if hasattr(self._storage, 'cleanup'): logging.debug("cleanup storage %s" % self._storage.__name__) self._storage.cleanup() for adminaddr in self._servers: if adminaddr is not None: forker.shutdown_zeo_server(adminaddr) for pid in self._pids: try: os.waitpid(pid, 0) except OSError: pass # The subprocess module may already have waited for c in self.caches: for i in 0, 1: for ext in "", ".trace", ".lock": path = "%s-%s.zec%s" % (c, "1", ext) # On Windows before 2.3, we don't have a way to wait for # the spawned server(s) to close, and they inherited # file descriptors for our open files. So long as those # processes are alive, we can't delete the files. Try # a few times then give up. need_to_delete = False if os.path.exists(path): need_to_delete = True for dummy in range(5): try: os.unlink(path) except: time.sleep(0.5) else: need_to_delete = False break if need_to_delete: os.unlink(path) # sometimes this is just gonna fail self.__super_tearDown() def _newAddr(self): self.addr.append(self._getAddr()) def _getAddr(self): return 'localhost', forker.get_port(self) def getConfig(self, path, create, read_only): raise NotImplementedError cache_id = 1 def openClientStorage(self, cache=None, cache_size=200000, wait=1, read_only=0, read_only_fallback=0, username=None, password=None, realm=None): if cache is None: cache = str(self.__class__.cache_id) self.__class__.cache_id += 1 self.caches.append(cache) storage = TestClientStorage(self.addr, client=cache, var='.', cache_size=cache_size, wait=wait, min_disconnect_poll=0.1, read_only=read_only, read_only_fallback=read_only_fallback, username=username, password=password, realm=realm) storage.registerDB(DummyDB()) return storage def getServerConfig(self, addr, ro_svr): zconf = forker.ZEOConfig(addr) if ro_svr: zconf.read_only = 1 if self.monitor: zconf.monitor_address = ("", 42000) if self.invq: zconf.invalidation_queue_size = self.invq if self.timeout: zconf.transaction_timeout = self.timeout return zconf def startServer(self, create=1, index=0, read_only=0, ro_svr=0, keep=None, path=None): addr = self.addr[index] logging.info("startServer(create=%d, index=%d, read_only=%d) @ %s" % (create, index, read_only, addr)) if path is None: path = "%s.%d" % (self.file, index) sconf = self.getConfig(path, create, read_only) zconf = self.getServerConfig(addr, ro_svr) if keep is None: keep = self.keep zeoport, adminaddr, pid, path = forker.start_zeo_server( sconf, zconf, addr[1], keep) self.conf_paths.append(path) self._pids.append(pid) self._servers.append(adminaddr) def shutdownServer(self, index=0): logging.info("shutdownServer(index=%d) @ %s" % (index, self._servers[index])) adminaddr = self._servers[index] if adminaddr is not None: forker.shutdown_zeo_server(adminaddr) self._servers[index] = None def pollUp(self, timeout=30.0, storage=None): if storage is None: storage = self._storage # Poll until we're connected. now = time.time() giveup = now + timeout while not storage.is_connected(): asyncore.poll(0.1) now = time.time() if now > giveup: self.fail("timed out waiting for storage to connect") # When the socket map is empty, poll() returns immediately, # and this is a pure busy-loop then. At least on some Linux # flavors, that can starve the thread trying to connect, # leading to grossly increased runtime (typical) or bogus # "timed out" failures. A little sleep here cures both. time.sleep(0.1) def pollDown(self, timeout=30.0): # Poll until we're disconnected. now = time.time() giveup = now + timeout while self._storage.is_connected(): asyncore.poll(0.1) now = time.time() if now > giveup: self.fail("timed out waiting for storage to disconnect") # See pollUp() for why we sleep a little here. time.sleep(0.1) class ConnectionTests(CommonSetupTearDown): """Tests that explicitly manage the server process. To test the cache or re-connection, these test cases explicit start and stop a ZEO storage server. """ def checkMultipleAddresses(self): for i in range(4): self._newAddr() self._storage = self.openClientStorage('test', 100000) oid = self._storage.new_oid() obj = MinPO(12) self._dostore(oid, data=obj) self._storage.close() def checkReadOnlyClient(self): # Open a read-only client to a read-write server; stores fail # Start a read-only client for a read-write server self._storage = self.openClientStorage(read_only=1) # Stores should fail here self.assertRaises(ReadOnlyError, self._dostore) self._storage.close() def checkReadOnlyServer(self): # Open a read-only client to a read-only *server*; stores fail # We don't want the read-write server created by setUp() self.shutdownServer() self._servers = [] # Start a read-only server self.startServer(create=0, index=0, ro_svr=1) # Start a read-only client self._storage = self.openClientStorage(read_only=1) # Stores should fail here self.assertRaises(ReadOnlyError, self._dostore) self._storage.close() # Get rid of the 'test left new threads behind' warning time.sleep(0.1) def checkReadOnlyFallbackWritable(self): # Open a fallback client to a read-write server; stores succeed # Start a read-only-fallback client for a read-write server self._storage = self.openClientStorage(read_only_fallback=1) # Stores should succeed here self._dostore() self._storage.close() def checkReadOnlyFallbackReadOnlyServer(self): # Open a fallback client to a read-only *server*; stores fail # We don't want the read-write server created by setUp() self.shutdownServer() self._servers = [] # Start a read-only server self.startServer(create=0, index=0, ro_svr=1) # Start a read-only-fallback client self._storage = self.openClientStorage(read_only_fallback=1) self.assert_(self._storage.isReadOnly()) # Stores should fail here self.assertRaises(ReadOnlyError, self._dostore) self._storage.close() def checkDisconnectionError(self): # Make sure we get a ClientDisconnected when we try to read an # object when we're not connected to a storage server and the # object is not in the cache. self.shutdownServer() self._storage = self.openClientStorage('test', 1000, wait=0) self.assertRaises(ClientDisconnected, self._storage.load, 'fredwash', '') self._storage.close() def checkBasicPersistence(self): # Verify cached data persists across client storage instances. # To verify that the cache is being used, the test closes the # server and then starts a new client with the server down. # When the server is down, a load() gets the data from its cache. self._storage = self.openClientStorage('test', 100000) oid = self._storage.new_oid() obj = MinPO(12) revid1 = self._dostore(oid, data=obj) self._storage.close() self.shutdownServer() self._storage = self.openClientStorage('test', 100000, wait=0) data, revid2 = self._storage.load(oid, '') self.assertEqual(zodb_unpickle(data), MinPO(12)) self.assertEqual(revid1, revid2) self._storage.close() def checkDisconnectedCacheWorks(self): # Check that the cache works when the client is disconnected. self._storage = self.openClientStorage('test') oid1 = self._storage.new_oid() obj1 = MinPO("1" * 500) self._dostore(oid1, data=obj1) oid2 = self._storage.new_oid() obj2 = MinPO("2" * 500) self._dostore(oid2, data=obj2) expected1 = self._storage.load(oid1, '') expected2 = self._storage.load(oid2, '') # Shut it all down, and try loading from the persistent cache file # without a server present. self._storage.close() self.shutdownServer() self._storage = self.openClientStorage('test', wait=False) self.assertEqual(expected1, self._storage.load(oid1, '')) self.assertEqual(expected2, self._storage.load(oid2, '')) self._storage.close() def checkDisconnectedCacheFails(self): # Like checkDisconnectedCacheWorks above, except the cache # file is so small that only one object can be remembered. self._storage = self.openClientStorage('test', cache_size=900) oid1 = self._storage.new_oid() obj1 = MinPO("1" * 500) self._dostore(oid1, data=obj1) oid2 = self._storage.new_oid() obj2 = MinPO("2" * 500) # The cache file is so small that adding oid2 will evict oid1. self._dostore(oid2, data=obj2) expected2 = self._storage.load(oid2, '') # Shut it all down, and try loading from the persistent cache file # without a server present. self._storage.close() self.shutdownServer() self._storage = self.openClientStorage('test', cache_size=900, wait=False) # oid2 should still be in cache. self.assertEqual(expected2, self._storage.load(oid2, '')) # But oid1 should have been purged, so that trying to load it will # try to fetch it from the (non-existent) ZEO server. self.assertRaises(ClientDisconnected, self._storage.load, oid1, '') self._storage.close() def checkVerificationInvalidationPersists(self): # This tests a subtle invalidation bug from ZODB 3.3: # invalidations processed as part of ZEO cache verification acted # kinda OK wrt the in-memory cache structures, but had no effect # on the cache file. So opening the file cache again could # incorrectly believe that a previously invalidated object was # still current. This takes some effort to set up. # First, using a persistent cache ('test'), create an object # MinPO(13). We used to see this again at the end of this test, # despite that we modify it, and despite that it gets invalidated # in 'test', before the end. self._storage = self.openClientStorage('test') oid = self._storage.new_oid() obj = MinPO(13) self._dostore(oid, data=obj) self._storage.close() # Now modify obj via a temp connection. `test` won't learn about # this until we open a connection using `test` again. self._storage = self.openClientStorage() pickle, rev = self._storage.load(oid, '') newobj = zodb_unpickle(pickle) self.assertEqual(newobj, obj) newobj.value = 42 # .value *should* be 42 forever after now, not 13 self._dostore(oid, data=newobj, revid=rev) self._storage.close() # Open 'test' again. `oid` in this cache should be (and is) # invalidated during cache verification. The bug was that it # got invalidated (kinda) in memory, but not in the cache file. self._storage = self.openClientStorage('test') # The invalidation happened already. Now create and store a new # object before closing this storage: this is so `test` believes # it's seen transactions beyond the one that invalidated `oid`, so # that the *next* time we open `test` it doesn't process another # invalidation for `oid`. It's also important that we not try to # load `oid` now: because it's been (kinda) invalidated in the # cache's memory structures, loading it now would fetch the # current revision from the server, thus hiding the bug. obj2 = MinPO(666) oid2 = self._storage.new_oid() self._dostore(oid2, data=obj2) self._storage.close() # Finally, open `test` again and load `oid`. `test` believes # it's beyond the transaction that modified `oid`, so its view # of whether it has an up-to-date `oid` comes solely from the disk # file, unaffected by cache verification. self._storage = self.openClientStorage('test') pickle, rev = self._storage.load(oid, '') newobj_copy = zodb_unpickle(pickle) # This used to fail, with # AssertionError: MinPO(13) != MinPO(42) # That is, `test` retained a stale revision of the object on disk. self.assertEqual(newobj_copy, newobj) self._storage.close() def checkBadMessage1(self): # not even close to a real message self._bad_message("salty") def checkBadMessage2(self): # just like a real message, but with an unpicklable argument global Hack class Hack: pass msg = encode(1, 0, "foo", (Hack(),)) self._bad_message(msg) del Hack def _bad_message(self, msg): # Establish a connection, then send the server an ill-formatted # request. Verify that the connection is closed and that it is # possible to establish a new connection. self._storage = self.openClientStorage() self._dostore() # break into the internals to send a bogus message zrpc_conn = self._storage._server.rpc zrpc_conn.message_output(msg) try: self._dostore() except ClientDisconnected: pass else: self._storage.close() self.fail("Server did not disconnect after bogus message") self._storage.close() self._storage = self.openClientStorage() self._dostore() self._storage.close() # Test case for multiple storages participating in a single # transaction. This is not really a connection test, but it needs # about the same infrastructure (several storage servers). # TODO: with the current ZEO code, this occasionally fails. # That's the point of this test. :-) def NOcheckMultiStorageTransaction(self): # Configuration parameters (larger values mean more likely deadlocks) N = 2 # These don't *have* to be all the same, but it's convenient this way self.nservers = N self.nthreads = N self.ntrans = N self.nobj = N # Start extra servers for i in range(1, self.nservers): self._newAddr() self.startServer(index=i) # Spawn threads that each do some transactions on all storages threads = [] try: for i in range(self.nthreads): t = MSTThread(self, "T%d" % i) threads.append(t) t.start() # Wait for all threads to finish for t in threads: t.join(60) self.failIf(t.isAlive(), "%s didn't die" % t.getName()) finally: for t in threads: t.closeclients() def checkCrossDBInvalidations(self): db1 = DB(self.openClientStorage()) c1 = db1.open() r1 = c1.root() r1["a"] = MinPO("a") transaction.commit() self.assertEqual(r1._p_state, 0) # up-to-date db2 = DB(self.openClientStorage()) r2 = db2.open().root() self.assertEqual(r2["a"].value, "a") r2["b"] = MinPO("b") transaction.commit() # Make sure the invalidation is received in the other client. # We've had problems with this timing out on "slow" and/or "very # busy" machines, so we increase the sleep time on each trip, and # are willing to wait quite a long time. for i in range(20): c1.sync() if r1._p_state == -1: break time.sleep(i / 10.0) self.assertEqual(r1._p_state, -1) # ghost r1.keys() # unghostify self.assertEqual(r1._p_serial, r2._p_serial) self.assertEqual(r1["b"].value, "b") db2.close() db1.close() def checkCheckForOutOfDateServer(self): # We don't want to connect a client to a server if the client # has seen newer transactions. self._storage = self.openClientStorage() self._dostore() self.shutdownServer() self.assertRaises(ClientDisconnected, self._storage.load, '\0'*8, '') self.startServer() # No matter how long we wait, the client won't reconnect: time.sleep(2) self.assertRaises(ClientDisconnected, self._storage.load, '\0'*8, '') class InvqTests(CommonSetupTearDown): invq = 3 def checkQuickVerificationWith2Clients(self): perstorage = self.openClientStorage(cache="test", cache_size=4000) self.assertEqual(perstorage.verify_result, "empty cache") self._storage = self.openClientStorage() oid = self._storage.new_oid() oid2 = self._storage.new_oid() # When we create a new storage, it should always do a full # verification self.assertEqual(self._storage.verify_result, "empty cache") # do two storages of the object to make sure an invalidation # message is generated revid = self._dostore(oid) revid = self._dostore(oid, revid) # Create a second object and revision to guarantee it doesn't # show up in the list of invalidations sent when perstore restarts. revid2 = self._dostore(oid2) revid2 = self._dostore(oid2, revid2) perstorage.load(oid, '') perstorage.close() forker.wait_until(lambda : os.path.exists('test-1.zec')) revid = self._dostore(oid, revid) perstorage = self.openClientStorage(cache="test") forker.wait_until( (lambda : perstorage.verify_result == "quick verification"), onfail=(lambda : None)) self.assertEqual(perstorage.verify_result, "quick verification") self.assertEqual(perstorage._server._last_invals, (revid, [oid])) self.assertEqual(perstorage.load(oid, ''), self._storage.load(oid, '')) perstorage.close() def checkVerificationWith2ClientsInvqOverflow(self): perstorage = self.openClientStorage(cache="test") self.assertEqual(perstorage.verify_result, "empty cache") self._storage = self.openClientStorage() oid = self._storage.new_oid() # When we create a new storage, it should always do a full # verification self.assertEqual(self._storage.verify_result, "empty cache") # do two storages of the object to make sure an invalidation # message is generated revid = self._dostore(oid) revid = self._dostore(oid, revid) forker.wait_until( "Client has seen all of the transactions from the server", lambda : perstorage.lastTransaction() == self._storage.lastTransaction() ) perstorage.load(oid, '') perstorage.close() # the test code sets invq bound to 2 for i in range(5): revid = self._dostore(oid, revid) perstorage = self.openClientStorage(cache="test") self.assertEqual(perstorage.verify_result, "full verification") t = time.time() + 30 while not perstorage.end_verify.isSet(): perstorage.sync() if time.time() > t: self.fail("timed out waiting for endVerify") self.assertEqual(self._storage.load(oid, '')[1], revid) self.assertEqual(perstorage.load(oid, ''), self._storage.load(oid, '')) perstorage.close() class ReconnectionTests(CommonSetupTearDown): # The setUp() starts a server automatically. In order for its # state to persist, we set the class variable keep to 1. In # order for its state to be cleaned up, the last startServer() # call in the test must pass keep=0. keep = 1 invq = 2 def checkReadOnlyStorage(self): # Open a read-only client to a read-only *storage*; stores fail # We don't want the read-write server created by setUp() self.shutdownServer() self._servers = [] # Start a read-only server self.startServer(create=0, index=0, read_only=1, keep=0) # Start a read-only client self._storage = self.openClientStorage(read_only=1) # Stores should fail here self.assertRaises(ReadOnlyError, self._dostore) def checkReadOnlyFallbackReadOnlyStorage(self): # Open a fallback client to a read-only *storage*; stores fail # We don't want the read-write server created by setUp() self.shutdownServer() self._servers = [] # Start a read-only server self.startServer(create=0, index=0, read_only=1, keep=0) # Start a read-only-fallback client self._storage = self.openClientStorage(read_only_fallback=1) # Stores should fail here self.assertRaises(ReadOnlyError, self._dostore) # TODO: Compare checkReconnectXXX() here to checkReconnection() # further down. Is the code here hopelessly naive, or is # checkReconnection() overwrought? def checkReconnectWritable(self): # A read-write client reconnects to a read-write server # Start a client self._storage = self.openClientStorage() # Stores should succeed here self._dostore() # Shut down the server self.shutdownServer() self._servers = [] # Poll until the client disconnects self.pollDown() # Stores should fail now self.assertRaises(ClientDisconnected, self._dostore) # Restart the server self.startServer(create=0) # Poll until the client connects self.pollUp() # Stores should succeed here self._dostore() self._storage.close() def checkReconnectReadOnly(self): # A read-only client reconnects from a read-write to a # read-only server # Start a client self._storage = self.openClientStorage(read_only=1) # Stores should fail here self.assertRaises(ReadOnlyError, self._dostore) # Shut down the server self.shutdownServer() self._servers = [] # Poll until the client disconnects self.pollDown() # Stores should still fail self.assertRaises(ReadOnlyError, self._dostore) # Restart the server self.startServer(create=0, read_only=1, keep=0) # Poll until the client connects self.pollUp() # Stores should still fail self.assertRaises(ReadOnlyError, self._dostore) def checkReconnectFallback(self): # A fallback client reconnects from a read-write to a # read-only server # Start a client in fallback mode self._storage = self.openClientStorage(read_only_fallback=1) # Stores should succeed here self._dostore() # Shut down the server self.shutdownServer() self._servers = [] # Poll until the client disconnects self.pollDown() # Stores should fail now self.assertRaises(ClientDisconnected, self._dostore) # Restart the server self.startServer(create=0, read_only=1, keep=0) # Poll until the client connects self.pollUp() # Stores should fail here self.assertRaises(ReadOnlyError, self._dostore) def checkReconnectUpgrade(self): # A fallback client reconnects from a read-only to a # read-write server # We don't want the read-write server created by setUp() self.shutdownServer() self._servers = [] # Start a read-only server self.startServer(create=0, read_only=1) # Start a client in fallback mode self._storage = self.openClientStorage(read_only_fallback=1) # Stores should fail here self.assertRaises(ReadOnlyError, self._dostore) # Shut down the server self.shutdownServer() self._servers = [] # Poll until the client disconnects self.pollDown() # Stores should fail now self.assertRaises(ClientDisconnected, self._dostore) # Restart the server, this time read-write self.startServer(create=0, keep=0) # Poll until the client sconnects self.pollUp() # Stores should now succeed self._dostore() def checkReconnectSwitch(self): # A fallback client initially connects to a read-only server, # then discovers a read-write server and switches to that # We don't want the read-write server created by setUp() self.shutdownServer() self._servers = [] # Allocate a second address (for the second server) self._newAddr() # Start a read-only server self.startServer(create=0, index=0, read_only=1, keep=0) # Start a client in fallback mode self._storage = self.openClientStorage(read_only_fallback=1) # Stores should fail here self.assertRaises(ReadOnlyError, self._dostore) # Start a read-write server self.startServer(index=1, read_only=0, keep=0) # After a while, stores should work for i in range(300): # Try for 30 seconds try: self._dostore() break except (ClientDisconnected, ReadOnlyError): # If the client isn't connected at all, sync() returns # quickly and the test fails because it doesn't wait # long enough for the client. time.sleep(0.1) else: self.fail("Couldn't store after starting a read-write server") def checkNoVerificationOnServerRestart(self): self._storage = self.openClientStorage() # When we create a new storage, it should always do a full # verification self.assertEqual(self._storage.verify_result, "empty cache") self._dostore() self.shutdownServer() self.pollDown() self._storage.verify_result = None self.startServer(create=0, keep=0) self.pollUp() # There were no transactions committed, so no verification # should be needed. self.assertEqual(self._storage.verify_result, "no verification") def checkNoVerificationOnServerRestartWith2Clients(self): perstorage = self.openClientStorage(cache="test") self.assertEqual(perstorage.verify_result, "empty cache") self._storage = self.openClientStorage() oid = self._storage.new_oid() # When we create a new storage, it should always do a full # verification self.assertEqual(self._storage.verify_result, "empty cache") # do two storages of the object to make sure an invalidation # message is generated revid = self._dostore(oid) revid = self._dostore(oid, revid) forker.wait_until( "Client has seen all of the transactions from the server", lambda : perstorage.lastTransaction() == self._storage.lastTransaction() ) perstorage.load(oid, '') self.shutdownServer() self.pollDown() self._storage.verify_result = None perstorage.verify_result = None logging.info('2ALLBEEF') self.startServer(create=0, keep=0) self.pollUp() self.pollUp(storage=perstorage) # There were no transactions committed, so no verification # should be needed. self.assertEqual(self._storage.verify_result, "no verification") self.assertEqual(perstorage.verify_result, "no verification") perstorage.close() self._storage.close() def checkDisconnectedAbort(self): self._storage = self.openClientStorage() self._dostore() oids = [self._storage.new_oid() for i in range(5)] txn = Transaction() self._storage.tpc_begin(txn) for oid in oids: data = zodb_pickle(MinPO(oid)) self._storage.store(oid, None, data, '', txn) self.shutdownServer() self.assertRaises(ClientDisconnected, self._storage.tpc_vote, txn) self._storage.tpc_abort(txn) self.startServer(create=0) self._storage._wait() self._dostore() # This test is supposed to cover the following error, although # I don't have much confidence that it does. The likely # explanation for the error is that the _tbuf contained # objects that weren't in the _seriald, because the client was # interrupted waiting for tpc_vote() to return. When the next # transaction committed, it tried to do something with the # bogus _tbuf entries. The explanation is wrong/incomplete, # because tpc_begin() should clear the _tbuf. # 2003-01-15T15:44:19 ERROR(200) ZODB A storage error occurred # in the last phase of a two-phase commit. This shouldn't happen. # Traceback (innermost last): # Module ZODB.Transaction, line 359, in _finish_one # Module ZODB.Connection, line 691, in tpc_finish # Module ZEO.ClientStorage, line 679, in tpc_finish # Module ZEO.ClientStorage, line 709, in _update_cache # KeyError: ... def checkReconnection(self): # Check that the client reconnects when a server restarts. self._storage = self.openClientStorage() oid = self._storage.new_oid() obj = MinPO(12) self._dostore(oid, data=obj) logging.info("checkReconnection(): About to shutdown server") self.shutdownServer() logging.info("checkReconnection(): About to restart server") self.startServer(create=0) forker.wait_until('reconnect', self._storage.is_connected) oid = self._storage.new_oid() obj = MinPO(12) while 1: try: self._dostore(oid, data=obj) break except ClientDisconnected: # Maybe the exception mess is better now logging.info("checkReconnection(): Error after" " server restart; retrying.", exc_info=True) transaction.abort() # Give the other thread a chance to run. time.sleep(0.1) logging.info("checkReconnection(): finished") self._storage.close() def checkMultipleServers(self): # Crude test-- just start two servers and do a commit at each one. self._newAddr() self._storage = self.openClientStorage('test', 100000) self._dostore() self.shutdownServer(index=0) # When we start the second server, we use file data file from # the original server so tha the new server is a replica of # the original. We need this becaise ClientStorage won't use # a server if the server's last transaction is earlier than # what the client has seen. self.startServer(index=1, path=self.file+'.0', create=False) # If we can still store after shutting down one of the # servers, we must be reconnecting to the other server. did_a_store = 0 for i in range(10): try: self._dostore() did_a_store = 1 break except ClientDisconnected: time.sleep(0.5) self.assert_(did_a_store) self._storage.close() class TimeoutTests(CommonSetupTearDown): timeout = 1 def checkTimeout(self): storage = self.openClientStorage() txn = Transaction() storage.tpc_begin(txn) storage.tpc_vote(txn) time.sleep(2) self.assertRaises(ClientDisconnected, storage.tpc_finish, txn) # Make sure it's logged as CRITICAL for line in open("server-%s.log" % self.addr[0][1]): if (('Transaction timeout after' in line) and ('CRITICAL ZEO.StorageServer' in line) ): break else: self.assert_(False, 'bad logging') storage.close() def checkTimeoutOnAbort(self): storage = self.openClientStorage() txn = Transaction() storage.tpc_begin(txn) storage.tpc_vote(txn) storage.tpc_abort(txn) storage.close() def checkTimeoutOnAbortNoLock(self): storage = self.openClientStorage() txn = Transaction() storage.tpc_begin(txn) storage.tpc_abort(txn) storage.close() def checkTimeoutAfterVote(self): self._storage = storage = self.openClientStorage() # Assert that the zeo cache is empty self.assert_(not list(storage._cache.contents())) # Create the object oid = storage.new_oid() obj = MinPO(7) # Now do a store, sleeping before the finish so as to cause a timeout t = Transaction() old_connection_count = storage.connection_count_for_tests storage.tpc_begin(t) revid1 = storage.store(oid, ZERO, zodb_pickle(obj), '', t) storage.tpc_vote(t) # Now sleep long enough for the storage to time out time.sleep(3) self.assert_( (not storage.is_connected()) or (storage.connection_count_for_tests > old_connection_count) ) storage._wait() self.assert_(storage.is_connected()) # We expect finish to fail self.assertRaises(ClientDisconnected, storage.tpc_finish, t) # The cache should still be empty self.assert_(not list(storage._cache.contents())) # Load should fail since the object should not be in either the cache # or the server. self.assertRaises(KeyError, storage.load, oid, '') def checkTimeoutProvokingConflicts(self): self._storage = storage = self.openClientStorage() # Assert that the zeo cache is empty. self.assert_(not list(storage._cache.contents())) # Create the object oid = storage.new_oid() obj = MinPO(7) # We need to successfully commit an object now so we have something to # conflict about. t = Transaction() storage.tpc_begin(t) revid1a = storage.store(oid, ZERO, zodb_pickle(obj), '', t) revid1b = storage.tpc_vote(t) revid1 = handle_serials(oid, revid1a, revid1b) storage.tpc_finish(t) # Now do a store, sleeping before the finish so as to cause a timeout. obj.value = 8 t = Transaction() old_connection_count = storage.connection_count_for_tests storage.tpc_begin(t) revid2a = storage.store(oid, revid1, zodb_pickle(obj), '', t) revid2b = storage.tpc_vote(t) revid2 = handle_serials(oid, revid2a, revid2b) # Now sleep long enough for the storage to time out. # This used to sleep for 3 seconds, and sometimes (but very rarely) # failed then. Now we try for a minute. It typically succeeds # on the second time thru the loop, and, since self.timeout is 1, # it's typically faster now (2/1.8 ~= 1.11 seconds sleeping instead # of 3). deadline = time.time() + 60 # wait up to a minute while time.time() < deadline: if (storage.is_connected() and (storage.connection_count_for_tests == old_connection_count) ): time.sleep(self.timeout / 1.8) else: break self.assert_( (not storage.is_connected()) or (storage.connection_count_for_tests > old_connection_count) ) storage._wait() self.assert_(storage.is_connected()) # We expect finish to fail. self.assertRaises(ClientDisconnected, storage.tpc_finish, t) storage.tpc_abort(t) # Now we think we've committed the second transaction, but we really # haven't. A third one should produce a POSKeyError on the server, # which manifests as a ConflictError on the client. obj.value = 9 t = Transaction() storage.tpc_begin(t) storage.store(oid, revid2, zodb_pickle(obj), '', t) self.assertRaises(ConflictError, storage.tpc_vote, t) # Even aborting won't help. storage.tpc_abort(t) self.assertRaises(ZODB.POSException.StorageTransactionError, storage.tpc_finish, t) # Try again. obj.value = 10 t = Transaction() storage.tpc_begin(t) storage.store(oid, revid2, zodb_pickle(obj), '', t) # Even aborting won't help. self.assertRaises(ConflictError, storage.tpc_vote, t) # Abort this one and try a transaction that should succeed. storage.tpc_abort(t) # Now do a store. obj.value = 11 t = Transaction() storage.tpc_begin(t) revid2a = storage.store(oid, revid1, zodb_pickle(obj), '', t) revid2b = storage.tpc_vote(t) revid2 = handle_serials(oid, revid2a, revid2b) storage.tpc_finish(t) # Now load the object and verify that it has a value of 11. data, revid = storage.load(oid, '') self.assertEqual(zodb_unpickle(data), MinPO(11)) self.assertEqual(revid, revid2) class MSTThread(threading.Thread): __super_init = threading.Thread.__init__ def __init__(self, testcase, name): self.__super_init(name=name) self.testcase = testcase self.clients = [] def run(self): tname = self.getName() testcase = self.testcase # Create client connections to each server clients = self.clients for i in range(len(testcase.addr)): c = testcase.openClientStorage(addr=testcase.addr[i]) c.__name = "C%d" % i clients.append(c) for i in range(testcase.ntrans): # Because we want a transaction spanning all storages, # we can't use _dostore(). This is several _dostore() calls # expanded in-line (mostly). # Create oid->serial mappings for c in clients: c.__oids = [] c.__serials = {} # Begin a transaction t = Transaction() for c in clients: #print "%s.%s.%s begin\n" % (tname, c.__name, i), c.tpc_begin(t) for j in range(testcase.nobj): for c in clients: # Create and store a new object on each server oid = c.new_oid() c.__oids.append(oid) data = MinPO("%s.%s.t%d.o%d" % (tname, c.__name, i, j)) #print data.value data = zodb_pickle(data) s = c.store(oid, ZERO, data, '', t) c.__serials.update(handle_all_serials(oid, s)) # Vote on all servers and handle serials for c in clients: #print "%s.%s.%s vote\n" % (tname, c.__name, i), s = c.tpc_vote(t) c.__serials.update(handle_all_serials(None, s)) # Finish on all servers for c in clients: #print "%s.%s.%s finish\n" % (tname, c.__name, i), c.tpc_finish(t) for c in clients: # Check that we got serials for all oids for oid in c.__oids: testcase.failUnless(c.__serials.has_key(oid)) # Check that we got serials for no other oids for oid in c.__serials.keys(): testcase.failUnless(oid in c.__oids) def closeclients(self): # Close clients opened by run() for c in self.clients: try: c.close() except: pass # Run IPv6 tests if V6 sockets are supported try: socket.socket(socket.AF_INET6, socket.SOCK_STREAM) except (socket.error, AttributeError): pass else: class V6Setup: def _getAddr(self): return '::1', forker.get_port(self) _g = globals() for name, value in _g.items(): if isinstance(value, type) and issubclass(value, CommonSetupTearDown): _g[name+"V6"] = type(name+"V6", (V6Setup, value), {}) zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/registerDB.test0000644000175000017500000000754012214017464021610 0ustar arnauarnauStorage Servers should call registerDB on storages to propigate invalidations ============================================================================= Storages servers propagate invalidations from their storages. Among other things, this allows client storages to be used in storage servers, allowing storage-server fan out, spreading read load over multiple storage servers. We'll create a Faux storage that has a registerDB method. >>> class FauxStorage: ... invalidations = [('trans0', ['ob0']), ... ('trans1', ['ob0', 'ob1']), ... ] ... def registerDB(self, db): ... self.db = db ... def isReadOnly(self): ... return False ... def getName(self): ... return 'faux' ... def lastTransaction(self): ... return self.invq[0][0] ... def lastInvalidations(self, size): ... return list(self.invalidations) We dont' want the storage server to try to bind to a socket. We'll subclass it and give it a do-nothing dispatcher "class": >>> import ZEO.StorageServer >>> class StorageServer(ZEO.StorageServer.StorageServer): ... DispatcherClass = lambda *a, **k: None We'll create a storage instance and a storage server using it: >>> storage = FauxStorage() >>> server = StorageServer('addr', dict(t=storage)) Our storage now has a db attribute that provides IStorageDB. It's references method is just the referencesf function from ZODB.Serialize >>> import ZODB.serialize >>> storage.db.references is ZODB.serialize.referencesf True To see the effects of the invalidation messages, we'll create a client stub that implements the client invalidation calls: >>> class Client: ... def __init__(self, name): ... self.name = name ... def invalidateTransaction(self, tid, invalidated): ... print 'invalidateTransaction', tid, self.name ... print invalidated >>> class Connection: ... def __init__(self, mgr, obj): ... self.mgr = mgr ... self.obj = obj ... def should_close(self): ... print 'closed', self.obj.name ... self.mgr.close_conn(self) ... def poll(self): ... pass ... ... @property ... def trigger(self): ... return self ... ... def pull_trigger(self): ... pass >>> class ZEOStorage: ... def __init__(self, server, name): ... self.name = name ... self.connection = Connection(server, self) ... self.client = Client(name) Now, we'll register the client with the storage server: >>> _ = server.register_connection('t', ZEOStorage(server, 1)) >>> _ = server.register_connection('t', ZEOStorage(server, 2)) Now, if we call invalidate, we'll see it propigate to the client: >>> storage.db.invalidate('trans2', ['ob1', 'ob2']) invalidateTransaction trans2 1 ['ob1', 'ob2'] invalidateTransaction trans2 2 ['ob1', 'ob2'] >>> storage.db.invalidate('trans3', ['ob1', 'ob2']) invalidateTransaction trans3 1 ['ob1', 'ob2'] invalidateTransaction trans3 2 ['ob1', 'ob2'] The storage servers queue will reflect the invalidations: >>> for tid, invalidated in server.invq['t']: ... print repr(tid), invalidated 'trans3' ['ob1', 'ob2'] 'trans2' ['ob1', 'ob2'] 'trans1' ['ob0', 'ob1'] 'trans0' ['ob0'] If we call invalidateCache, the storage server will close each of it's connections: >>> storage.db.invalidateCache() closed 1 closed 2 The connections will then reopen and revalidate their caches. The servers's invalidation queue will get reset >>> for tid, invalidated in server.invq['t']: ... print repr(tid), invalidated 'trans1' ['ob0', 'ob1'] 'trans0' ['ob0'] zope2.13-2.13.21/source/ZODB3/src/ZEO/tests/servertesting.py0000644000175000017500000000511312214017464022125 0ustar arnauarnau############################################################################## # # Copyright Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # Testing the current ZEO implementation is rather hard due to the # architecture, which mixes concerns, especially between application # and networking. Still, it's not as bad as it could be. # The 2 most important classes in the architecture are ZEOStorage and # StorageServer. A ZEOStorage is created for each client connection. # The StorageServer maintains data shared or needed for coordination # among clients. # The other important part of the architecture is connections. # Connections are used by ZEOStorages to send messages or return data # to clients. # Here, we'll try to provide some testing infrastructure to isolate # servers from the network. import ZEO.StorageServer import ZEO.zrpc.connection import ZEO.zrpc.error import ZODB.MappingStorage class StorageServer(ZEO.StorageServer.StorageServer): def __init__(self, addr='test_addr', storages=None, **kw): if storages is None: storages = {'1': ZODB.MappingStorage.MappingStorage()} ZEO.StorageServer.StorageServer.__init__(self, addr, storages, **kw) def DispatcherClass(*args, **kw): pass class Connection: peer_protocol_version = ZEO.zrpc.connection.Connection.current_protocol connected = True def __init__(self, name='connection', addr=''): name = str(name) self.name = name self.addr = addr or 'test-addr-'+name def close(self): print self.name, 'closed' self.connected = False def poll(self): if not self.connected: raise ZEO.zrpc.error.DisconnectedError() def callAsync(self, meth, *args): print self.name, 'callAsync', meth, repr(args) callAsyncNoPoll = callAsync def call_from_thread(self, *args): if args: args[0](*args[1:]) def send_reply(self, *args): pass def client(server, name='client', addr=''): zs = ZEO.StorageServer.ZEOStorage(server) zs.notifyConnected(Connection(name, addr)) zs.register('1', 0) return zs zope2.13-2.13.21/source/ZODB3/src/ZEO/version.txt0000644000175000017500000000001012214017464017722 0ustar arnauarnau3.7.0b3 zope2.13-2.13.21/source/ZODB3/src/ZEO/protocol.txt0000644000175000017500000000443312214017464020113 0ustar arnauarnauZEO Network Protocol (sans authentication) ========================================== This document describes the ZEO network protocol. It assumes that the optional authentication protocol isn't used. At the lowest level, the protocol consists of sized messages. All communication between the client and server consists of sized messages. A sized message consists of a 4-byte unsigned big-endian content length, followed by the content. There are two subprotocols, for protocol negotiation, and for normal operation. The normal operation protocol is a basic RPC protocol. In the protocol negotiation phase, the server sends a protocol identifier to the client. The client chooses a protocol to use to the server. The client or the server can fail if it doesn't like the protocol string sent by the other party. After sending their protocol strings, the client and server switch to RPC mode. The RPC protocol uses messages that are pickled tuples consisting of: message_id The message id is used to match replies with requests, allowing multiple outstanding synchronous requests. async_flag An integer 0 for a regular (2-way) request and 1 for a one-way request. Two-way requests have a reply. One way requests don't. ZRS tries to use as many one-way requests as possible to avoid network round trips. name The name of a method to call. If this is the special string ".reply", then the message is interpreted as a return from a synchronous call. args A tuple of positional arguments or returned values. After making a connection and negotiating the protocol, the following interactions occur: - The client requests the authentication protocol by calling getAuthProtocol. For this discussion, we'll assume the server returns None. Note that if the server doesn't require authentication, this step is optional. - The client calls register passing a storage identifier and a read-only flag. The server doesn't return a value, but it may raise an exception either if the storage doesn't exist, or if the storage is readonly and the read-only flag passed by the client is false. At this point, the client and server send each other messages as needed. The client may make regular or one-way calls to the server. The server sends replies and one-way calls to the client. zope2.13-2.13.21/source/ZODB3/src/ZEO/cache.py0000644000175000017500000007234612214017464017136 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Disk-based client cache for ZEO. ClientCache exposes an API used by the ZEO client storage. FileCache stores objects on disk using a 2-tuple of oid and tid as key. ClientCache's API is similar to a storage API, with methods like load(), store(), and invalidate(). It manages in-memory data structures that allow it to map this richer API onto the simple key-based API of the lower-level FileCache. """ from struct import pack, unpack import BTrees.LLBTree import BTrees.LOBTree import logging import os import tempfile import threading import time import ZODB.fsIndex import zc.lockfile from ZODB.utils import p64, u64, z64 logger = logging.getLogger("ZEO.cache") # A disk-based cache for ZEO clients. # # This class provides an interface to a persistent, disk-based cache # used by ZEO clients to store copies of database records from the # server. # # The details of the constructor as unspecified at this point. # # Each entry in the cache is valid for a particular range of transaction # ids. The lower bound is the transaction that wrote the data. The # upper bound is the next transaction that wrote a revision of the # object. If the data is current, the upper bound is stored as None; # the data is considered current until an invalidate() call is made. # # It is an error to call store() twice with the same object without an # intervening invalidate() to set the upper bound on the first cache # entry. Perhaps it will be necessary to have a call the removes # something from the cache outright, without keeping a non-current # entry. # Cache verification # # When the client is connected to the server, it receives # invalidations every time an object is modified. When the client is # disconnected then reconnects, it must perform cache verification to make # sure its cached data is synchronized with the storage's current state. # # quick verification # full verification # # FileCache stores a cache in a single on-disk file. # # On-disk cache structure. # # The file begins with a 12-byte header. The first four bytes are the # file's magic number - ZEC3 - indicating zeo cache version 4. The # next eight bytes are the last transaction id. magic = "ZEC3" ZEC_HEADER_SIZE = 12 # Maximum block size. Note that while we are doing a store, we may # need to write a free block that is almost twice as big. If we die # in the middle of a store, then we need to split the large free records # while opening. max_block_size = (1<<31) - 1 # After the header, the file contains a contiguous sequence of blocks. All # blocks begin with a one-byte status indicator: # # 'a' # Allocated. The block holds an object; the next 4 bytes are >I # format total block size. # # 'f' # Free. The block is free; the next 4 bytes are >I format total # block size. # # '1', '2', '3', '4' # The block is free, and consists of 1, 2, 3 or 4 bytes total. # # "Total" includes the status byte, and size bytes. There are no # empty (size 0) blocks. # Allocated blocks have more structure: # # 1 byte allocation status ('a'). # 4 bytes block size, >I format. # 8 byte oid # 8 byte start_tid # 8 byte end_tid # 2 byte version length must be 0 # 4 byte data size # data # 8 byte redundant oid for error detection. allocated_record_overhead = 43 # The cache's currentofs goes around the file, circularly, forever. # It's always the starting offset of some block. # # When a new object is added to the cache, it's stored beginning at # currentofs, and currentofs moves just beyond it. As many contiguous # blocks needed to make enough room for the new object are evicted, # starting at currentofs. Exception: if currentofs is close enough # to the end of the file that the new object can't fit in one # contiguous chunk, currentofs is reset to ZEC_HEADER_SIZE first. class locked(object): def __init__(self, func): self.func = func def __get__(self, inst, class_): if inst is None: return self def call(*args, **kw): inst._lock.acquire() try: return self.func(inst, *args, **kw) finally: inst._lock.release() return call class ClientCache(object): """A simple in-memory cache.""" # The default size of 200MB makes a lot more sense than the traditional # default of 20MB. The default here is misleading, though, since # ClientStorage is the only user of ClientCache, and it always passes an # explicit size of its own choosing. def __init__(self, path=None, size=200*1024**2, rearrange=.8): # - `path`: filepath for the cache file, or None (in which case # a temp file will be created) self.path = path # - `maxsize`: total size of the cache file # We set to the minimum size of less than the minimum. size = max(size, ZEC_HEADER_SIZE) self.maxsize = size # rearrange: if we read a current record and it's more than # rearrange*size from the end, then copy it forward to keep it # from being evicted. self.rearrange = rearrange * size # The number of records in the cache. self._len = 0 # {oid -> pos} self.current = ZODB.fsIndex.fsIndex() # {oid -> {tid->pos}} # Note that caches in the wild seem to have very little non-current # data, so this would seem to have little impact on memory consumption. # I wonder if we even need to store non-current data in the cache. self.noncurrent = BTrees.LOBTree.LOBTree() # tid for the most recent transaction we know about. This is also # stored near the start of the file. self.tid = z64 # Always the offset into the file of the start of a block. # New and relocated objects are always written starting at # currentofs. self.currentofs = ZEC_HEADER_SIZE # self.f is the open file object. # When we're not reusing an existing file, self.f is left None # here -- the scan() method must be called then to open the file # (and it sets self.f). fsize = ZEC_HEADER_SIZE if path: self._lock_file = zc.lockfile.LockFile(path + '.lock') if not os.path.exists(path): # Create a small empty file. We'll make it bigger in _initfile. self.f = open(path, 'wb+') self.f.write(magic+z64) logger.info("created persistent cache file %r", path) else: fsize = os.path.getsize(self.path) self.f = open(path, 'rb+') logger.info("reusing persistent cache file %r", path) else: # Create a small empty file. We'll make it bigger in _initfile. self.f = tempfile.TemporaryFile() self.f.write(magic+z64) logger.info("created temporary cache file %r", self.f.name) try: self._initfile(fsize) except: self.f.close() if not path: raise # unrecoverable temp file error :( badpath = path+'.bad' if os.path.exists(badpath): logger.critical( 'Removing bad cache file: %r (prev bad exists).', path, exc_info=1) os.remove(path) else: logger.critical('Moving bad cache file to %r.', badpath, exc_info=1) os.rename(path, badpath) self.f = open(path, 'wb+') self.f.write(magic+z64) self._initfile(ZEC_HEADER_SIZE) # Statistics: _n_adds, _n_added_bytes, # _n_evicts, _n_evicted_bytes, # _n_accesses self.clearStats() self._setup_trace(path) self._lock = threading.RLock() # Backward compatibility. Client code used to have to use the fc # attr to get to the file cache to get cache stats. @property def fc(self): return self def clear(self): self.f.seek(ZEC_HEADER_SIZE) self.f.truncate() self._initfile(ZEC_HEADER_SIZE) ## # Scan the current contents of the cache file, calling `install` # for each object found in the cache. This method should only # be called once to initialize the cache from disk. def _initfile(self, fsize): maxsize = self.maxsize f = self.f read = f.read seek = f.seek write = f.write seek(0) if read(4) != magic: seek(0) raise ValueError("unexpected magic number: %r" % read(4)) self.tid = read(8) if len(self.tid) != 8: raise ValueError("cache file too small -- no tid at start") # Populate .filemap and .key2entry to reflect what's currently in the # file, and tell our parent about it too (via the `install` callback). # Remember the location of the largest free block. That seems a # decent place to start currentofs. self.current = ZODB.fsIndex.fsIndex() self.noncurrent = BTrees.LOBTree.LOBTree() l = 0 last = ofs = ZEC_HEADER_SIZE first_free_offset = 0 current = self.current status = ' ' while ofs < fsize: seek(ofs) status = read(1) if status == 'a': size, oid, start_tid, end_tid, lver = unpack( ">I8s8s8sH", read(30)) if ofs+size <= maxsize: if end_tid == z64: assert oid not in current, (ofs, f.tell()) current[oid] = ofs else: assert start_tid < end_tid, (ofs, f.tell()) self._set_noncurrent(oid, start_tid, ofs) assert lver == 0, "Versions aren't supported" l += 1 else: # free block if first_free_offset == 0: first_free_offset = ofs if status == 'f': size, = unpack(">I", read(4)) if size > max_block_size: # Oops, we either have an old cache, or a we # crashed while storing. Split this block into two. assert size <= max_block_size*2 seek(ofs+max_block_size) write('f'+pack(">I", size-max_block_size)) seek(ofs) write('f'+pack(">I", max_block_size)) sync(f) elif status in '1234': size = int(status) else: raise ValueError("unknown status byte value %s in client " "cache file" % 0, hex(ord(status))) last = ofs ofs += size if ofs >= maxsize: # Oops, the file was bigger before. if ofs > maxsize: # The last record is too big. Replace it with a smaller # free record size = maxsize-last seek(last) if size > 4: write('f'+pack(">I", size)) else: write("012345"[size]) sync(f) ofs = maxsize break if fsize < maxsize: assert ofs==fsize # Make sure the OS really saves enough bytes for the file. seek(self.maxsize - 1) write('x') # add as many free blocks as are needed to fill the space seek(ofs) nfree = maxsize - ZEC_HEADER_SIZE for i in range(0, nfree, max_block_size): block_size = min(max_block_size, nfree-i) write('f' + pack(">I", block_size)) seek(block_size-5, 1) sync(self.f) # There is always data to read and assert last and status in ' f1234' first_free_offset = last else: assert ofs==maxsize if maxsize < fsize: seek(maxsize) f.truncate() # We use the first_free_offset because it is most likelyt the # place where we last wrote. self.currentofs = first_free_offset or ZEC_HEADER_SIZE self._len = l def _set_noncurrent(self, oid, tid, ofs): noncurrent_for_oid = self.noncurrent.get(u64(oid)) if noncurrent_for_oid is None: noncurrent_for_oid = BTrees.LLBTree.LLBucket() self.noncurrent[u64(oid)] = noncurrent_for_oid noncurrent_for_oid[u64(tid)] = ofs def _del_noncurrent(self, oid, tid): try: noncurrent_for_oid = self.noncurrent[u64(oid)] del noncurrent_for_oid[u64(tid)] if not noncurrent_for_oid: del self.noncurrent[u64(oid)] except KeyError: logger.error("Couldn't find non-current %r", (oid, tid)) def clearStats(self): self._n_adds = self._n_added_bytes = 0 self._n_evicts = self._n_evicted_bytes = 0 self._n_accesses = 0 def getStats(self): return (self._n_adds, self._n_added_bytes, self._n_evicts, self._n_evicted_bytes, self._n_accesses ) ## # The number of objects currently in the cache. def __len__(self): return self._len ## # Close the underlying file. No methods accessing the cache should be # used after this. def close(self): self._unsetup_trace() f = self.f self.f = None if f is not None: sync(f) f.close() if hasattr(self,'_lock_file'): self._lock_file.close() ## # Evict objects as necessary to free up at least nbytes bytes, # starting at currentofs. If currentofs is closer than nbytes to # the end of the file, currentofs is reset to ZEC_HEADER_SIZE first. # The number of bytes actually freed may be (and probably will be) # greater than nbytes, and is _makeroom's return value. The file is not # altered by _makeroom. filemap and key2entry are updated to reflect the # evictions, and it's the caller's responsibility both to fiddle # the file, and to update filemap, to account for all the space # freed (starting at currentofs when _makeroom returns, and # spanning the number of bytes retured by _makeroom). def _makeroom(self, nbytes): assert 0 < nbytes <= self.maxsize - ZEC_HEADER_SIZE, ( nbytes, self.maxsize) if self.currentofs + nbytes > self.maxsize: self.currentofs = ZEC_HEADER_SIZE ofs = self.currentofs seek = self.f.seek read = self.f.read current = self.current while nbytes > 0: seek(ofs) status = read(1) if status == 'a': size, oid, start_tid, end_tid = unpack(">I8s8s8s", read(28)) self._n_evicts += 1 self._n_evicted_bytes += size if end_tid == z64: del current[oid] else: self._del_noncurrent(oid, start_tid) self._len -= 1 else: if status == 'f': size = unpack(">I", read(4))[0] else: assert status in '1234' size = int(status) ofs += size nbytes -= size return ofs - self.currentofs ## # Update our idea of the most recent tid. This is stored in the # instance, and also written out near the start of the cache file. The # new tid must be strictly greater than our current idea of the most # recent tid. @locked def setLastTid(self, tid): if (not tid) or (tid == z64): return if (tid <= self.tid) and self._len: if tid == self.tid: return # Be a little forgiving raise ValueError("new last tid (%s) must be greater than " "previous one (%s)" % (u64(tid), u64(self.tid))) assert isinstance(tid, str) and len(tid) == 8, tid self.tid = tid self.f.seek(len(magic)) self.f.write(tid) self.f.flush() ## # Return the last transaction seen by the cache. # @return a transaction id # @defreturn string, or 8 nulls if no transaction is yet known def getLastTid(self): return self.tid ## # Return the current data record for oid. # @param oid object id # @return (data record, serial number, tid), or None if the object is not # in the cache # @defreturn 3-tuple: (string, string, string) @locked def load(self, oid): ofs = self.current.get(oid) if ofs is None: self._trace(0x20, oid) return None self.f.seek(ofs) read = self.f.read status = read(1) assert status == 'a', (ofs, self.f.tell(), oid) size, saved_oid, tid, end_tid, lver, ldata = unpack( ">I8s8s8sHI", read(34)) assert saved_oid == oid, (ofs, self.f.tell(), oid, saved_oid) assert end_tid == z64, (ofs, self.f.tell(), oid, tid, end_tid) assert lver == 0, "Versions aren't supported" data = read(ldata) assert len(data) == ldata, (ofs, self.f.tell(), oid, len(data), ldata) # WARNING: The following assert changes the file position. # We must not depend on this below or we'll fail in optimized mode. assert read(8) == oid, (ofs, self.f.tell(), oid) self._n_accesses += 1 self._trace(0x22, oid, tid, end_tid, ldata) ofsofs = self.currentofs - ofs if ofsofs < 0: ofsofs += self.maxsize if (ofsofs > self.rearrange and self.maxsize > 10*len(data) and size > 4): # The record is far back and might get evicted, but it's # valuable, so move it forward. # Remove fromn old loc: del self.current[oid] self.f.seek(ofs) self.f.write('f'+pack(">I", size)) # Write to new location: self._store(oid, tid, None, data, size) return data, tid ## # Return a non-current revision of oid that was current before tid. # @param oid object id # @param tid id of transaction that wrote next revision of oid # @return data record, serial number, start tid, and end tid # @defreturn 4-tuple: (string, string, string, string) @locked def loadBefore(self, oid, before_tid): noncurrent_for_oid = self.noncurrent.get(u64(oid)) if noncurrent_for_oid is None: self._trace(0x24, oid, "", before_tid) return None items = noncurrent_for_oid.items(None, u64(before_tid)-1) if not items: self._trace(0x24, oid, "", before_tid) return None tid, ofs = items[-1] self.f.seek(ofs) read = self.f.read status = read(1) assert status == 'a', (ofs, self.f.tell(), oid, before_tid) size, saved_oid, saved_tid, end_tid, lver, ldata = unpack( ">I8s8s8sHI", read(34)) assert saved_oid == oid, (ofs, self.f.tell(), oid, saved_oid) assert saved_tid == p64(tid), (ofs, self.f.tell(), oid, saved_tid, tid) assert end_tid != z64, (ofs, self.f.tell(), oid) assert lver == 0, "Versions aren't supported" data = read(ldata) assert len(data) == ldata, (ofs, self.f.tell()) # WARNING: The following assert changes the file position. # We must not depend on this below or we'll fail in optimized mode. assert read(8) == oid, (ofs, self.f.tell(), oid) if end_tid < before_tid: self._trace(0x24, oid, "", before_tid) return None self._n_accesses += 1 self._trace(0x26, oid, "", saved_tid) return data, saved_tid, end_tid ## # Store a new data record in the cache. # @param oid object id # @param start_tid the id of the transaction that wrote this revision # @param end_tid the id of the transaction that created the next # revision of oid. If end_tid is None, the data is # current. # @param data the actual data @locked def store(self, oid, start_tid, end_tid, data): seek = self.f.seek if end_tid is None: ofs = self.current.get(oid) if ofs: seek(ofs) read = self.f.read status = read(1) assert status == 'a', (ofs, self.f.tell(), oid) size, saved_oid, saved_tid, end_tid = unpack( ">I8s8s8s", read(28)) assert saved_oid == oid, (ofs, self.f.tell(), oid, saved_oid) assert end_tid == z64, (ofs, self.f.tell(), oid) if saved_tid == start_tid: return raise ValueError("already have current data for oid") else: noncurrent_for_oid = self.noncurrent.get(u64(oid)) if noncurrent_for_oid and (u64(start_tid) in noncurrent_for_oid): return size = allocated_record_overhead + len(data) # A number of cache simulation experiments all concluded that the # 2nd-level ZEO cache got a much higher hit rate if "very large" # objects simply weren't cached. For now, we ignore the request # only if the entire cache file is too small to hold the object. if size >= min(max_block_size, self.maxsize - ZEC_HEADER_SIZE): return self._n_adds += 1 self._n_added_bytes += size self._len += 1 self._store(oid, start_tid, end_tid, data, size) if end_tid: self._trace(0x54, oid, start_tid, end_tid, dlen=len(data)) else: self._trace(0x52, oid, start_tid, dlen=len(data)) def _store(self, oid, start_tid, end_tid, data, size): # Low-level store used by store and load # In the next line, we ask for an extra to make sure we always # have a free block after the new alocated block. This free # block acts as a ring pointer, so that on restart, we start # where we left off. nfreebytes = self._makeroom(size+1) assert size <= nfreebytes, (size, nfreebytes) excess = nfreebytes - size # If there's any excess (which is likely), we need to record a # free block following the end of the data record. That isn't # expensive -- it's all a contiguous write. if excess == 0: extra = '' elif excess < 5: extra = "01234"[excess] else: extra = 'f' + pack(">I", excess) ofs = self.currentofs seek = self.f.seek seek(ofs) write = self.f.write # Before writing data, we'll write a free block for the space freed. # We'll come back with a last atomic write to rewrite the start of the # allocated-block header. write('f'+pack(">I", nfreebytes)) # Now write the rest of the allocation block header and object data. write(pack(">8s8s8sHI", oid, start_tid, end_tid or z64, 0, len(data))) write(data) write(oid) write(extra) # Now, we'll go back and rewrite the beginning of the # allocated block header. seek(ofs) write('a'+pack(">I", size)) if end_tid: self._set_noncurrent(oid, start_tid, ofs) else: self.current[oid] = ofs self.currentofs += size ## # If `tid` is None, # forget all knowledge of `oid`. (`tid` can be None only for # invalidations generated by startup cache verification.) If `tid` # isn't None, and we had current # data for `oid`, stop believing we have current data, and mark the # data we had as being valid only up to `tid`. In all other cases, do # nothing. # # Paramters: # # - oid object id # - tid the id of the transaction that wrote a new revision of oid, # or None to forget all cached info about oid. @locked def invalidate(self, oid, tid): ofs = self.current.get(oid) if ofs is None: # 0x10 == invalidate (miss) self._trace(0x10, oid, tid) return self.f.seek(ofs) read = self.f.read status = read(1) assert status == 'a', (ofs, self.f.tell(), oid) size, saved_oid, saved_tid, end_tid = unpack(">I8s8s8s", read(28)) assert saved_oid == oid, (ofs, self.f.tell(), oid, saved_oid) assert end_tid == z64, (ofs, self.f.tell(), oid) del self.current[oid] if tid is None: self.f.seek(ofs) self.f.write('f'+pack(">I", size)) # 0x1E = invalidate (hit, discarding current or non-current) self._trace(0x1E, oid, tid) self._len -= 1 else: if tid == saved_tid: logger.warning("Ignoring invalidation with same tid as current") return self.f.seek(ofs+21) self.f.write(tid) self._set_noncurrent(oid, saved_tid, ofs) # 0x1C = invalidate (hit, saving non-current) self._trace(0x1C, oid, tid) ## # Generates (oid, serial) oairs for all objects in the # cache. This generator is used by cache verification. def contents(self): # May need to materialize list instead of iterating; # depends on whether the caller may change the cache. seek = self.f.seek read = self.f.read for oid, ofs in self.current.iteritems(): self._lock.acquire() try: seek(ofs) status = read(1) assert status == 'a', (ofs, self.f.tell(), oid) size, saved_oid, tid, end_tid = unpack(">I8s8s8s", read(28)) assert saved_oid == oid, (ofs, self.f.tell(), oid, saved_oid) assert end_tid == z64, (ofs, self.f.tell(), oid) result = oid, tid finally: self._lock.release() yield result def dump(self): from ZODB.utils import oid_repr print "cache size", len(self) L = list(self.contents()) L.sort() for oid, tid in L: print oid_repr(oid), oid_repr(tid) print "dll contents" L = list(self) L.sort(lambda x, y: cmp(x.key, y.key)) for x in L: end_tid = x.end_tid or z64 print oid_repr(x.key[0]), oid_repr(x.key[1]), oid_repr(end_tid) print # If `path` isn't None (== we're using a persistent cache file), and # envar ZEO_CACHE_TRACE is set to a non-empty value, try to open # path+'.trace' as a trace file, and store the file object in # self._tracefile. If not, or we can't write to the trace file, disable # tracing by setting self._trace to a dummy function, and set # self._tracefile to None. _tracefile = None def _trace(self, *a, **kw): pass def _setup_trace(self, path): _tracefile = None if path and os.environ.get("ZEO_CACHE_TRACE"): tfn = path + ".trace" try: _tracefile = open(tfn, "ab") except IOError, msg: logger.warning("cannot write tracefile %r (%s)", tfn, msg) else: logger.info("opened tracefile %r", tfn) if _tracefile is None: return now = time.time def _trace(code, oid="", tid=z64, end_tid=z64, dlen=0): # The code argument is two hex digits; bits 0 and 7 must be zero. # The first hex digit shows the operation, the second the outcome. # This method has been carefully tuned to be as fast as possible. # Note: when tracing is disabled, this method is hidden by a dummy. encoded = (dlen << 8) + code if tid is None: tid = z64 if end_tid is None: end_tid = z64 try: _tracefile.write( pack(">iiH8s8s", now(), encoded, len(oid), tid, end_tid) + oid, ) except: print `tid`, `end_tid` raise self._trace = _trace self._tracefile = _tracefile _trace(0x00) def _unsetup_trace(self): if self._tracefile is not None: del self._trace self._tracefile.close() del self._tracefile def sync(f): f.flush() if hasattr(os, 'fsync'): def sync(f): f.flush() os.fsync(f.fileno()) zope2.13-2.13.21/source/ZODB3/src/BTrees/0000755000175000017500000000000012214017464016234 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/BTrees/IFBTree.py0000644000175000017500000000150112214017464020023 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import zope.interface import BTrees.Interfaces # hack to overcome dynamic-linking headache. from _IFBTree import * zope.interface.moduleProvides(BTrees.Interfaces.IIntegerFloatBTreeModule) zope2.13-2.13.21/source/ZODB3/src/BTrees/BTreeModuleTemplate.c0000644000175000017500000004212212214017464022244 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #include "Python.h" /* include structmember.h for offsetof */ #include "structmember.h" #ifdef PERSISTENT #include "persistent/cPersistence.h" #else #define PER_USE_OR_RETURN(self, NULL) #define PER_ALLOW_DEACTIVATION(self) #define PER_PREVENT_DEACTIVATION(self) #define PER_DEL(self) #define PER_USE(O) 1 #define PER_ACCESSED(O) 1 #endif #include "py24compat.h" /* So sue me. This pair gets used all over the place, so much so that it * interferes with understanding non-persistence parts of algorithms. * PER_UNUSE can be used after a successul PER_USE or PER_USE_OR_RETURN. * It allows the object to become ghostified, and tells the persistence * machinery that the object's fields were used recently. */ #define PER_UNUSE(OBJ) do { \ PER_ALLOW_DEACTIVATION(OBJ); \ PER_ACCESSED(OBJ); \ } while (0) /* The tp_name slots of the various BTree types contain the fully * qualified names of the types, e.g. zodb.btrees.OOBTree.OOBTree. * The full name is usd to support pickling and because it is not * possible to modify the __module__ slot of a type dynamically. (This * may be a bug in Python 2.2). * * The MODULE_NAME here used to be "BTrees._". We actually want the module * name to point to the Python module rather than the C, so the underline * is now removed. */ #define MODULE_NAME "BTrees." MOD_NAME_PREFIX "BTree." static PyObject *sort_str, *reverse_str, *__setstate___str, *_bucket_type_str; static PyObject *ConflictError = NULL; static void PyVar_Assign(PyObject **v, PyObject *e) { Py_XDECREF(*v); *v=e;} #define ASSIGN(V,E) PyVar_Assign(&(V),(E)) #define UNLESS(E) if (!(E)) #define OBJECT(O) ((PyObject*)(O)) #define MIN_BUCKET_ALLOC 16 #define MAX_BTREE_SIZE(B) DEFAULT_MAX_BTREE_SIZE #define MAX_BUCKET_SIZE(B) DEFAULT_MAX_BUCKET_SIZE #define SameType_Check(O1, O2) ((O1)->ob_type==(O2)->ob_type) #define ASSERT(C, S, R) if (! (C)) { \ PyErr_SetString(PyExc_AssertionError, (S)); return (R); } #ifdef NEED_LONG_LONG_SUPPORT /* Helper code used to support long long instead of int. */ #ifndef PY_LONG_LONG #error "PY_LONG_LONG required but not defined" #endif static int longlong_check(PyObject *ob) { if (PyInt_Check(ob)) return 1; if (PyLong_Check(ob)) { /* check magnitude */ PY_LONG_LONG val = PyLong_AsLongLong(ob); if (val == -1 && PyErr_Occurred()) return 0; return 1; } return 0; } static PyObject * longlong_as_object(PY_LONG_LONG val) { static PY_LONG_LONG maxint = 0; if (maxint == 0) maxint = PyInt_GetMax(); if ((val > maxint) || (val < (-maxint-1))) return PyLong_FromLongLong(val); return PyInt_FromLong((long)val); } #endif /* Various kinds of BTree and Bucket structs are instances of * "sized containers", and have a common initial layout: * The stuff needed for all Python objects, or all Persistent objects. * int size: The maximum number of things that could be contained * without growing the container. * int len: The number of things currently contained. * * Invariant: 0 <= len <= size. * * A sized container typically goes on to declare one or more pointers * to contiguous arrays with 'size' elements each, the initial 'len' of * which are currently in use. */ #ifdef PERSISTENT #define sizedcontainer_HEAD \ cPersistent_HEAD \ int size; \ int len; #else #define sizedcontainer_HEAD \ PyObject_HEAD \ int size; \ int len; #endif /* Nothing is actually of type Sized, but (pointers to) BTree nodes and * Buckets can be cast to Sized* in contexts that only need to examine * the members common to all sized containers. */ typedef struct Sized_s { sizedcontainer_HEAD } Sized; #define SIZED(O) ((Sized*)(O)) /* A Bucket wraps contiguous vectors of keys and values. Keys are unique, * and stored in sorted order. The 'values' pointer may be NULL if the * Bucket is used to implement a set. Buckets serving as leafs of BTrees * are chained together via 'next', so that the entire BTree contents * can be traversed in sorted order quickly and easily. */ typedef struct Bucket_s { sizedcontainer_HEAD struct Bucket_s *next; /* the bucket with the next-larger keys */ KEY_TYPE *keys; /* 'len' keys, in increasing order */ VALUE_TYPE *values; /* 'len' corresponding values; NULL if a set */ } Bucket; #define BUCKET(O) ((Bucket*)(O)) /* A BTree is complicated. See Maintainer.txt. */ typedef struct BTreeItem_s { KEY_TYPE key; Sized *child; /* points to another BTree, or to a Bucket of some sort */ } BTreeItem; typedef struct BTree_s { sizedcontainer_HEAD /* firstbucket points to the bucket containing the smallest key in * the BTree. This is found by traversing leftmost child pointers * (data[0].child) until reaching a Bucket. */ Bucket *firstbucket; /* The BTree points to 'len' children, via the "child" fields of the data * array. There are len-1 keys in the 'key' fields, stored in increasing * order. data[0].key is unused. For i in 0 .. len-1, all keys reachable * from data[i].child are >= data[i].key and < data[i+1].key, at the * endpoints pretending that data[0].key is minus infinity and * data[len].key is positive infinity. */ BTreeItem *data; } BTree; static PyTypeObject BTreeType; static PyTypeObject BucketType; #define BTREE(O) ((BTree*)(O)) /* Use BTREE_SEARCH to find which child pointer to follow. * RESULT An int lvalue to hold the index i such that SELF->data[i].child * is the correct node to search next. * SELF A pointer to a BTree node. * KEY The key you're looking for, of type KEY_TYPE. * ONERROR What to do if key comparison raises an exception; for example, * perhaps 'return NULL'. * * See Maintainer.txt for discussion: this is optimized in subtle ways. * It's recommended that you call this at the start of a routine, waiting * to check for self->len == 0 after. */ #define BTREE_SEARCH(RESULT, SELF, KEY, ONERROR) { \ int _lo = 0; \ int _hi = (SELF)->len; \ int _i, _cmp; \ for (_i = _hi >> 1; _i > _lo; _i = (_lo + _hi) >> 1) { \ TEST_KEY_SET_OR(_cmp, (SELF)->data[_i].key, (KEY)) \ ONERROR; \ if (_cmp < 0) _lo = _i; \ else if (_cmp > 0) _hi = _i; \ else /* equal */ break; \ } \ (RESULT) = _i; \ } /* SetIteration structs are used in the internal set iteration protocol. * When you want to iterate over a set or bucket or BTree (even an * individual key!), * 1. Declare a new iterator: * SetIteration si = {0,0,0}; * Using "{0,0,0}" or "{0,0}" appear most common. Only one {0} is * necssary. At least one must be given so that finiSetIteration() works * correctly even if you don't get around to calling initSetIteration(). * 2. Initialize it via * initSetIteration(&si, PyObject *s, useValues) * It's an error if that returns an int < 0. In case of error on the * init call, calling finiSetIteration(&si) is optional. But if the * init call succeeds, you must eventually call finiSetIteration(), * and whether or not subsequent calls to si.next() fail. * 3. Get the first element: * if (si.next(&si) < 0) { there was an error } * If the set isn't empty, this sets si.position to an int >= 0, * si.key to the element's key (of type KEY_TYPE), and maybe si.value to * the element's value (of type VALUE_TYPE). si.value is defined * iff si.usesValue is true. * 4. Process all the elements: * while (si.position >= 0) { * do something with si.key and/or si.value; * if (si.next(&si) < 0) { there was an error; } * } * 5. Finalize the SetIterator: * finiSetIteration(&si); * This is mandatory! si may contain references to iterator objects, * keys and values, and they must be cleaned up else they'll leak. If * this were C++ we'd hide that in the destructor, but in C you have to * do it by hand. */ typedef struct SetIteration_s { PyObject *set; /* the set, bucket, BTree, ..., being iterated */ int position; /* initialized to 0; set to -1 by next() when done */ int usesValue; /* true iff 'set' has values & we iterate them */ KEY_TYPE key; /* next() sets to next key */ VALUE_TYPE value; /* next() may set to next value */ int (*next)(struct SetIteration_s*); /* function to get next key+value */ } SetIteration; /* Finish the set iteration protocol. This MUST be called by everyone * who starts a set iteration, unless the initial call to initSetIteration * failed; in that case, and only that case, calling finiSetIteration is * optional. */ static void finiSetIteration(SetIteration *i) { assert(i != NULL); if (i->set == NULL) return; Py_DECREF(i->set); i->set = NULL; /* so it doesn't hurt to call this again */ if (i->position > 0) { /* next() was called at least once, but didn't finish iterating * (else position would be negative). So the cached key and * value need to be cleaned up. */ DECREF_KEY(i->key); if (i->usesValue) { DECREF_VALUE(i->value); } } i->position = -1; /* stop any stray next calls from doing harm */ } static PyObject * IndexError(int i) { PyObject *v; v = PyInt_FromLong(i); if (!v) { v = Py_None; Py_INCREF(v); } PyErr_SetObject(PyExc_IndexError, v); Py_DECREF(v); return NULL; } /* Search for the bucket immediately preceding *current, in the bucket chain * starting at first. current, *current and first must not be NULL. * * Return: * 1 *current holds the correct bucket; this is a borrowed reference * 0 no such bucket exists; *current unaltered * -1 error; *current unaltered */ static int PreviousBucket(Bucket **current, Bucket *first) { Bucket *trailing = NULL; /* first travels; trailing follows it */ int result = 0; assert(current && *current && first); if (first == *current) return 0; do { trailing = first; PER_USE_OR_RETURN(first, -1); first = first->next; ((trailing)->state==cPersistent_STICKY_STATE && ((trailing)->state=cPersistent_UPTODATE_STATE)); PER_ACCESSED(trailing); if (first == *current) { *current = trailing; result = 1; break; } } while (first); return result; } static void * BTree_Malloc(size_t sz) { void *r; ASSERT(sz > 0, "non-positive size malloc", NULL); r = malloc(sz); if (r) return r; PyErr_NoMemory(); return NULL; } static void * BTree_Realloc(void *p, size_t sz) { void *r; ASSERT(sz > 0, "non-positive size realloc", NULL); if (p) r = realloc(p, sz); else r = malloc(sz); UNLESS (r) PyErr_NoMemory(); return r; } /* Shared keyword-argument list for BTree/Bucket * (iter)?(keys|values|items) */ static char *search_keywords[] = {"min", "max", "excludemin", "excludemax", 0}; #include "BTreeItemsTemplate.c" #include "BucketTemplate.c" #include "SetTemplate.c" #include "BTreeTemplate.c" #include "TreeSetTemplate.c" #include "SetOpTemplate.c" #include "MergeTemplate.c" static struct PyMethodDef module_methods[] = { {"difference", (PyCFunction) difference_m, METH_VARARGS, "difference(o1, o2) -- " "compute the difference between o1 and o2" }, {"union", (PyCFunction) union_m, METH_VARARGS, "union(o1, o2) -- compute the union of o1 and o2\n" }, {"intersection", (PyCFunction) intersection_m, METH_VARARGS, "intersection(o1, o2) -- " "compute the intersection of o1 and o2" }, #ifdef MERGE {"weightedUnion", (PyCFunction) wunion_m, METH_VARARGS, "weightedUnion(o1, o2 [, w1, w2]) -- compute the union of o1 and o2\n" "\nw1 and w2 are weights." }, {"weightedIntersection", (PyCFunction) wintersection_m, METH_VARARGS, "weightedIntersection(o1, o2 [, w1, w2]) -- " "compute the intersection of o1 and o2\n" "\nw1 and w2 are weights." }, #endif #ifdef MULTI_INT_UNION {"multiunion", (PyCFunction) multiunion_m, METH_VARARGS, "multiunion(seq) -- compute union of a sequence of integer sets.\n" "\n" "Each element of seq must be an integer set, or convertible to one\n" "via the set iteration protocol. The union returned is an IISet." }, #endif {NULL, NULL} /* sentinel */ }; static char BTree_module_documentation[] = "\n" MASTER_ID BTREEITEMSTEMPLATE_C "$Id: BTreeModuleTemplate.c 117967 2010-10-27 19:17:39Z jim $\n" BTREETEMPLATE_C BUCKETTEMPLATE_C KEYMACROS_H MERGETEMPLATE_C SETOPTEMPLATE_C SETTEMPLATE_C TREESETTEMPLATE_C VALUEMACROS_H BTREEITEMSTEMPLATE_C ; int init_persist_type(PyTypeObject *type) { type->ob_type = &PyType_Type; type->tp_base = cPersistenceCAPI->pertype; if (PyType_Ready(type) < 0) return 0; return 1; } void INITMODULE (void) { PyObject *m, *d, *c; sort_str = PyString_InternFromString("sort"); if (!sort_str) return; reverse_str = PyString_InternFromString("reverse"); if (!reverse_str) return; __setstate___str = PyString_InternFromString("__setstate__"); if (!__setstate___str) return; _bucket_type_str = PyString_InternFromString("_bucket_type"); if (!_bucket_type_str) return; /* Grab the ConflictError class */ m = PyImport_ImportModule("ZODB.POSException"); if (m != NULL) { c = PyObject_GetAttrString(m, "BTreesConflictError"); if (c != NULL) ConflictError = c; Py_DECREF(m); } if (ConflictError == NULL) { Py_INCREF(PyExc_ValueError); ConflictError=PyExc_ValueError; } /* Initialize the PyPersist_C_API and the type objects. */ cPersistenceCAPI = PyCObject_Import("persistent.cPersistence", "CAPI"); if (cPersistenceCAPI == NULL) return; BTreeItemsType.ob_type = &PyType_Type; BTreeIter_Type.ob_type = &PyType_Type; BTreeIter_Type.tp_getattro = PyObject_GenericGetAttr; BucketType.tp_new = PyType_GenericNew; SetType.tp_new = PyType_GenericNew; BTreeType.tp_new = PyType_GenericNew; TreeSetType.tp_new = PyType_GenericNew; if (!init_persist_type(&BucketType)) return; if (!init_persist_type(&BTreeType)) return; if (!init_persist_type(&SetType)) return; if (!init_persist_type(&TreeSetType)) return; if (PyDict_SetItem(BTreeType.tp_dict, _bucket_type_str, (PyObject *)&BucketType) < 0) { fprintf(stderr, "btree failed\n"); return; } if (PyDict_SetItem(TreeSetType.tp_dict, _bucket_type_str, (PyObject *)&SetType) < 0) { fprintf(stderr, "bucket failed\n"); return; } /* Create the module and add the functions */ m = Py_InitModule4("_" MOD_NAME_PREFIX "BTree", module_methods, BTree_module_documentation, (PyObject *)NULL, PYTHON_API_VERSION); /* Add some symbolic constants to the module */ d = PyModule_GetDict(m); if (PyDict_SetItemString(d, MOD_NAME_PREFIX "Bucket", (PyObject *)&BucketType) < 0) return; if (PyDict_SetItemString(d, MOD_NAME_PREFIX "BTree", (PyObject *)&BTreeType) < 0) return; if (PyDict_SetItemString(d, MOD_NAME_PREFIX "Set", (PyObject *)&SetType) < 0) return; if (PyDict_SetItemString(d, MOD_NAME_PREFIX "TreeSet", (PyObject *)&TreeSetType) < 0) return; if (PyDict_SetItemString(d, MOD_NAME_PREFIX "TreeIterator", (PyObject *)&BTreeIter_Type) < 0) return; /* We also want to be able to access these constants without the prefix * so that code can more easily exchange modules (particularly the integer * and long modules, but also others). The TreeIterator is only internal, * so we don't bother to expose that. */ if (PyDict_SetItemString(d, "Bucket", (PyObject *)&BucketType) < 0) return; if (PyDict_SetItemString(d, "BTree", (PyObject *)&BTreeType) < 0) return; if (PyDict_SetItemString(d, "Set", (PyObject *)&SetType) < 0) return; if (PyDict_SetItemString(d, "TreeSet", (PyObject *)&TreeSetType) < 0) return; #if defined(ZODB_64BIT_INTS) && defined(NEED_LONG_LONG_SUPPORT) if (PyDict_SetItemString(d, "using64bits", Py_True) < 0) return; #else if (PyDict_SetItemString(d, "using64bits", Py_False) < 0) return; #endif } zope2.13-2.13.21/source/ZODB3/src/BTrees/OLBTree.py0000644000175000017500000000150212214017464020040 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import zope.interface import BTrees.Interfaces # hack to overcome dynamic-linking headache. from _OLBTree import * zope.interface.moduleProvides(BTrees.Interfaces.IObjectIntegerBTreeModule) zope2.13-2.13.21/source/ZODB3/src/BTrees/floatvaluemacros.h0000644000175000017500000000167612214017464021766 0ustar arnauarnau #define VALUEMACROS_H "$Id: floatvaluemacros.h 28617 2004-12-09 23:43:03Z tim_one $\n" #define VALUE_TYPE float #undef VALUE_TYPE_IS_PYOBJECT #define TEST_VALUE(K, T) (((K) < (T)) ? -1 : (((K) > (T)) ? 1: 0)) #define VALUE_SAME(VALUE, TARGET) ( (VALUE) == (TARGET) ) #define DECLARE_VALUE(NAME) VALUE_TYPE NAME #define VALUE_PARSE "f" #define DECREF_VALUE(k) #define INCREF_VALUE(k) #define COPY_VALUE(V, E) (V=(E)) #define COPY_VALUE_TO_OBJECT(O, K) O=PyFloat_FromDouble(K) #define COPY_VALUE_FROM_ARG(TARGET, ARG, STATUS) \ if (PyFloat_Check(ARG)) TARGET = (float)PyFloat_AsDouble(ARG); \ else if (PyInt_Check(ARG)) TARGET = (float)PyInt_AsLong(ARG); \ else { \ PyErr_SetString(PyExc_TypeError, "expected float or int value"); \ (STATUS)=0; (TARGET)=0; } #define NORMALIZE_VALUE(V, MIN) ((MIN) > 0) ? ((V)/=(MIN)) : 0 #define MERGE_DEFAULT 1.0f #define MERGE(O1, w1, O2, w2) ((O1)*(w1)+(O2)*(w2)) #define MERGE_WEIGHT(O, w) ((O)*(w)) zope2.13-2.13.21/source/ZODB3/src/BTrees/_OOBTree.c0000644000175000017500000000213412214017464017776 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ #define MASTER_ID "$Id: _OOBTree.c 113734 2010-06-21 15:33:46Z ctheune $\n" /* OOBTree - object key, object value BTree Implements a collection using object type keys and object type values */ #define PERSISTENT #define MOD_NAME_PREFIX "OO" #define INITMODULE init_OOBTree #define DEFAULT_MAX_BUCKET_SIZE 30 #define DEFAULT_MAX_BTREE_SIZE 250 #include "objectkeymacros.h" #include "objectvaluemacros.h" #include "BTreeModuleTemplate.c" zope2.13-2.13.21/source/ZODB3/src/BTrees/SetTemplate.c0000644000175000017500000002100012214017464020620 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #define SETTEMPLATE_C "$Id: SetTemplate.c 113734 2010-06-21 15:33:46Z ctheune $\n" static PyObject * Set_insert(Bucket *self, PyObject *args) { PyObject *key; int i; UNLESS (PyArg_ParseTuple(args, "O", &key)) return NULL; if ( (i=_bucket_set(self, key, Py_None, 1, 1, 0)) < 0) return NULL; return PyInt_FromLong(i); } /* _Set_update and _TreeSet_update are identical except for the function they call to add the element to the set. */ static int _Set_update(Bucket *self, PyObject *seq) { int n=0, ind=0; PyObject *iter, *v; iter = PyObject_GetIter(seq); if (iter == NULL) return -1; while (1) { v = PyIter_Next(iter); if (v == NULL) { if (PyErr_Occurred()) goto err; else break; } ind = _bucket_set(self, v, Py_None, 1, 1, 0); Py_DECREF(v); if (ind < 0) goto err; else n += ind; } err: Py_DECREF(iter); if (ind < 0) return -1; return n; } static PyObject * Set_update(Bucket *self, PyObject *args) { PyObject *seq = NULL; int n = 0; if (!PyArg_ParseTuple(args, "|O:update", &seq)) return NULL; if (seq) { n = _Set_update(self, seq); if (n < 0) return NULL; } return PyInt_FromLong(n); } static PyObject * Set_remove(Bucket *self, PyObject *args) { PyObject *key; UNLESS (PyArg_ParseTuple(args, "O", &key)) return NULL; if (_bucket_set(self, key, NULL, 0, 1, 0) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static int _set_setstate(Bucket *self, PyObject *args) { PyObject *k, *items; Bucket *next=0; int i, l, copied=1; KEY_TYPE *keys; UNLESS (PyArg_ParseTuple(args, "O|O", &items, &next)) return -1; if (!PyTuple_Check(items)) { PyErr_SetString(PyExc_TypeError, "tuple required for first state element"); return -1; } if ((l=PyTuple_Size(items)) < 0) return -1; for (i=self->len; --i >= 0; ) { DECREF_KEY(self->keys[i]); } self->len=0; if (self->next) { Py_DECREF(self->next); self->next=0; } if (l > self->size) { UNLESS (keys=BTree_Realloc(self->keys, sizeof(KEY_TYPE)*l)) return -1; self->keys=keys; self->size=l; } for (i=0; ikeys[i], k, copied); UNLESS (copied) return -1; INCREF_KEY(self->keys[i]); } self->len=l; if (next) { self->next=next; Py_INCREF(next); } return 0; } static PyObject * set_setstate(Bucket *self, PyObject *args) { int r; UNLESS (PyArg_ParseTuple(args, "O", &args)) return NULL; PER_PREVENT_DEACTIVATION(self); r=_set_setstate(self, args); PER_UNUSE(self); if (r < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static struct PyMethodDef Set_methods[] = { {"__getstate__", (PyCFunction) bucket_getstate, METH_VARARGS, "__getstate__() -- Return the picklable state of the object"}, {"__setstate__", (PyCFunction) set_setstate, METH_VARARGS, "__setstate__() -- Set the state of the object"}, {"keys", (PyCFunction) bucket_keys, METH_KEYWORDS, "keys() -- Return the keys"}, {"has_key", (PyCFunction) bucket_has_key, METH_O, "has_key(key) -- Test whether the bucket contains the given key"}, {"clear", (PyCFunction) bucket_clear, METH_VARARGS, "clear() -- Remove all of the items from the bucket"}, {"maxKey", (PyCFunction) Bucket_maxKey, METH_VARARGS, "maxKey([key]) -- Find the maximum key\n\n" "If an argument is given, find the maximum <= the argument"}, {"minKey", (PyCFunction) Bucket_minKey, METH_VARARGS, "minKey([key]) -- Find the minimum key\n\n" "If an argument is given, find the minimum >= the argument"}, #ifdef PERSISTENT {"_p_resolveConflict", (PyCFunction) bucket__p_resolveConflict, METH_VARARGS, "_p_resolveConflict() -- Reinitialize from a newly created copy"}, {"_p_deactivate", (PyCFunction) bucket__p_deactivate, METH_KEYWORDS, "_p_deactivate() -- Reinitialize from a newly created copy"}, #endif {"add", (PyCFunction)Set_insert, METH_VARARGS, "add(id) -- Add a key to the set"}, {"insert", (PyCFunction)Set_insert, METH_VARARGS, "insert(id) -- Add a key to the set"}, {"update", (PyCFunction)Set_update, METH_VARARGS, "update(seq) -- Add the items from the given sequence to the set"}, {"remove", (PyCFunction)Set_remove, METH_VARARGS, "remove(id) -- Remove an id from the set"}, {NULL, NULL} /* sentinel */ }; static int Set_init(PyObject *self, PyObject *args, PyObject *kwds) { PyObject *v = NULL; if (!PyArg_ParseTuple(args, "|O:" MOD_NAME_PREFIX "Set", &v)) return -1; if (v) return _Set_update((Bucket *)self, v); else return 0; } static PyObject * set_repr(Bucket *self) { static PyObject *format; PyObject *r, *t; if (!format) format = PyString_FromString(MOD_NAME_PREFIX "Set(%s)"); UNLESS (t = PyTuple_New(1)) return NULL; UNLESS (r = bucket_keys(self, NULL, NULL)) goto err; PyTuple_SET_ITEM(t, 0, r); r = t; ASSIGN(r, PyString_Format(format, r)); return r; err: Py_DECREF(t); return NULL; } static Py_ssize_t set_length(Bucket *self) { int r; PER_USE_OR_RETURN(self, -1); r = self->len; PER_UNUSE(self); return r; } static PyObject * set_item(Bucket *self, Py_ssize_t index) { PyObject *r=0; PER_USE_OR_RETURN(self, NULL); if (index >= 0 && index < self->len) { COPY_KEY_TO_OBJECT(r, self->keys[index]); } else IndexError(index); PER_UNUSE(self); return r; } static PySequenceMethods set_as_sequence = { (lenfunc)set_length, /* sq_length */ (binaryfunc)0, /* sq_concat */ (ssizeargfunc)0, /* sq_repeat */ (ssizeargfunc)set_item, /* sq_item */ (ssizessizeargfunc)0, /* sq_slice */ (ssizeobjargproc)0, /* sq_ass_item */ (ssizessizeobjargproc)0, /* sq_ass_slice */ (objobjproc)bucket_contains, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }; static PyTypeObject SetType = { PyObject_HEAD_INIT(NULL) /* PyPersist_Type */ 0, /* ob_size */ MODULE_NAME MOD_NAME_PREFIX "Set", /* tp_name */ sizeof(Bucket), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)bucket_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)set_repr, /* tp_repr */ 0, /* tp_as_number */ &set_as_sequence, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ (traverseproc)bucket_traverse, /* tp_traverse */ (inquiry)bucket_tp_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)Bucket_getiter, /* tp_iter */ 0, /* tp_iternext */ Set_methods, /* tp_methods */ Bucket_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ Set_init, /* tp_init */ 0, /* tp_alloc */ 0, /*PyType_GenericNew,*/ /* tp_new */ }; static int nextSet(SetIteration *i) { if (i->position >= 0) { UNLESS(PER_USE(BUCKET(i->set))) return -1; if (i->position) { DECREF_KEY(i->key); } if (i->position < BUCKET(i->set)->len) { COPY_KEY(i->key, BUCKET(i->set)->keys[i->position]); INCREF_KEY(i->key); i->position ++; } else { i->position = -1; PER_ACCESSED(BUCKET(i->set)); } PER_ALLOW_DEACTIVATION(BUCKET(i->set)); } return 0; } zope2.13-2.13.21/source/ZODB3/src/BTrees/fsBTree.py0000644000175000017500000000150112214017464020135 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## # fsBTrees are data structures used for ZODB FileStorage. They are not # expected to be "public" excpect to FileStorage. # hack to overcome dynamic-linking headache. from _fsBTree import * zope2.13-2.13.21/source/ZODB3/src/BTrees/_OLBTree.c0000644000175000017500000000210712214017464017773 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ #define MASTER_ID "$Id: _OIBTree.c 25186 2004-06-02 15:07:33Z jim $\n" /* OIBTree - object key, int value BTree Implements a collection using object type keys and int type values */ #define PERSISTENT #define MOD_NAME_PREFIX "OL" #define INITMODULE init_OLBTree #define DEFAULT_MAX_BUCKET_SIZE 60 #define DEFAULT_MAX_BTREE_SIZE 250 #define ZODB_64BIT_INTS #include "objectkeymacros.h" #include "intvaluemacros.h" #include "BTreeModuleTemplate.c" zope2.13-2.13.21/source/ZODB3/src/BTrees/OOBTree.py0000644000175000017500000000150112214017464020042 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import zope.interface import BTrees.Interfaces # hack to overcome dynamic-linking headache. from _OOBTree import * zope.interface.moduleProvides(BTrees.Interfaces.IObjectObjectBTreeModule) zope2.13-2.13.21/source/ZODB3/src/BTrees/IIBTree.py0000644000175000017500000000150312214017464020030 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import zope.interface import BTrees.Interfaces # hack to overcome dynamic-linking headache. from _IIBTree import * zope.interface.moduleProvides(BTrees.Interfaces.IIntegerIntegerBTreeModule) zope2.13-2.13.21/source/ZODB3/src/BTrees/_IOBTree.c0000644000175000017500000000212312214017464017766 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ #define MASTER_ID "$Id: _IOBTree.c 113734 2010-06-21 15:33:46Z ctheune $\n" /* IOBTree - int key, object value BTree Implements a collection using int type keys and object type values */ #define PERSISTENT #define MOD_NAME_PREFIX "IO" #define DEFAULT_MAX_BUCKET_SIZE 60 #define DEFAULT_MAX_BTREE_SIZE 500 #define INITMODULE init_IOBTree #include "intkeymacros.h" #include "objectvaluemacros.h" #include "BTreeModuleTemplate.c" zope2.13-2.13.21/source/ZODB3/src/BTrees/objectvaluemacros.h0000644000175000017500000000101512214017464022112 0ustar arnauarnau #define VALUEMACROS_H "$Id: objectvaluemacros.h 24682 2004-05-14 19:32:43Z tim_one $\n" #define VALUE_TYPE PyObject * #define VALUE_TYPE_IS_PYOBJECT #define TEST_VALUE(VALUE, TARGET) PyObject_Compare((VALUE),(TARGET)) #define DECLARE_VALUE(NAME) VALUE_TYPE NAME #define INCREF_VALUE(k) Py_INCREF(k) #define DECREF_VALUE(k) Py_DECREF(k) #define COPY_VALUE(k,e) k=(e) #define COPY_VALUE_TO_OBJECT(O, K) O=(K); Py_INCREF(O) #define COPY_VALUE_FROM_ARG(TARGET, ARG, S) TARGET=(ARG) #define NORMALIZE_VALUE(V, MIN) Py_INCREF(V) zope2.13-2.13.21/source/ZODB3/src/BTrees/py24compat.h0000644000175000017500000000102712214017464020407 0ustar arnauarnau/* Backport type definitions from Python 2.5's object.h */ #ifndef BTREE_PY24COMPATH_H #define BTREE_PY24COMPAT_H #if PY_VERSION_HEX < 0x02050000 typedef Py_ssize_t (*lenfunc)(PyObject *); typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t); typedef PyObject *(*ssizessizeargfunc)(PyObject *, Py_ssize_t, Py_ssize_t); typedef int(*ssizeobjargproc)(PyObject *, Py_ssize_t, PyObject *); typedef int(*ssizessizeobjargproc)(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); #endif /* PY_VERSION_HEX */ #endif /* BTREE_PY24COMPAT_H */ zope2.13-2.13.21/source/ZODB3/src/BTrees/Interfaces.py0000644000175000017500000004233712214017464020702 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from zope.interface import Interface, Attribute class ICollection(Interface): def clear(): """Remove all of the items from the collection.""" def __nonzero__(): """Check if the collection is non-empty. Return a true value if the collection is non-empty and a false value otherwise. """ class IReadSequence(Interface): def __getitem__(index): """Return the value at the given index. An IndexError is raised if the index cannot be found. """ def __getslice__(index1, index2): """Return a subsequence from the original sequence. The subsequence includes the items from index1 up to, but not including, index2. """ class IKeyed(ICollection): def has_key(key): """Check whether the object has an item with the given key. Return a true value if the key is present, else a false value. """ def keys(min=None, max=None, excludemin=False, excludemax=False): """Return an IReadSequence containing the keys in the collection. The type of the IReadSequence is not specified. It could be a list or a tuple or some other type. All arguments are optional, and may be specified as keyword arguments, or by position. If a min is specified, then output is constrained to keys greater than or equal to the given min, and, if excludemin is specified and true, is further constrained to keys strictly greater than min. A min value of None is ignored. If min is None or not specified, and excludemin is true, the smallest key is excluded. If a max is specified, then output is constrained to keys less than or equal to the given max, and, if excludemax is specified and true, is further constrained to keys strictly less than max. A max value of None is ignored. If max is None or not specified, and excludemax is true, the largest key is excluded. """ def maxKey(key=None): """Return the maximum key. If a key argument if provided and not None, return the largest key that is less than or equal to the argument. Raise an exception if no such key exists. """ def minKey(key=None): """Return the minimum key. If a key argument if provided and not None, return the smallest key that is greater than or equal to the argument. Raise an exception if no such key exists. """ class ISetMutable(IKeyed): def insert(key): """Add the key (value) to the set. If the key was already in the set, return 0, otherwise return 1. """ def remove(key): """Remove the key from the set. Raises KeyError if key is not in the set. """ def update(seq): """Add the items from the given sequence to the set.""" class ISized(Interface): """An object that supports __len__.""" def __len__(): """Return the number of items in the container.""" class IKeySequence(IKeyed, ISized): def __getitem__(index): """Return the key in the given index position. This allows iteration with for loops and use in functions, like map and list, that read sequences. """ class ISet(IKeySequence, ISetMutable): pass class ITreeSet(IKeyed, ISetMutable): pass class IMinimalDictionary(ISized, IKeyed): def get(key, default): """Get the value associated with the given key. Return the default if has_key(key) is false. """ def __getitem__(key): """Get the value associated with the given key. Raise KeyError if has_key(key) is false. """ def __setitem__(key, value): """Set the value associated with the given key.""" def __delitem__(key): """Delete the value associated with the given key. Raise KeyError if has_key(key) is false. """ def values(min=None, max=None, excludemin=False, excludemax=False): """Return an IReadSequence containing the values in the collection. The type of the IReadSequence is not specified. It could be a list or a tuple or some other type. All arguments are optional, and may be specified as keyword arguments, or by position. If a min is specified, then output is constrained to values whose keys are greater than or equal to the given min, and, if excludemin is specified and true, is further constrained to values whose keys are strictly greater than min. A min value of None is ignored. If min is None or not specified, and excludemin is true, the value corresponding to the smallest key is excluded. If a max is specified, then output is constrained to values whose keys are less than or equal to the given max, and, if excludemax is specified and true, is further constrained to values whose keys are strictly less than max. A max value of None is ignored. If max is None or not specified, and excludemax is true, the value corresponding to the largest key is excluded. """ def items(min=None, max=None, excludemin=False, excludemax=False): """Return an IReadSequence containing the items in the collection. An item is a 2-tuple, a (key, value) pair. The type of the IReadSequence is not specified. It could be a list or a tuple or some other type. All arguments are optional, and may be specified as keyword arguments, or by position. If a min is specified, then output is constrained to items whose keys are greater than or equal to the given min, and, if excludemin is specified and true, is further constrained to items whose keys are strictly greater than min. A min value of None is ignored. If min is None or not specified, and excludemin is true, the item with the smallest key is excluded. If a max is specified, then output is constrained to items whose keys are less than or equal to the given max, and, if excludemax is specified and true, is further constrained to items whose keys are strictly less than max. A max value of None is ignored. If max is None or not specified, and excludemax is true, the item with the largest key is excluded. """ class IDictionaryIsh(IMinimalDictionary): def update(collection): """Add the items from the given collection object to the collection. The input collection must be a sequence of (key, value) 2-tuples, or an object with an 'items' method that returns a sequence of (key, value) pairs. """ def byValue(minValue): """Return a sequence of (value, key) pairs, sorted by value. Values < minValue are omitted and other values are "normalized" by the minimum value. This normalization may be a noop, but, for integer values, the normalization is division. """ def setdefault(key, d): """D.setdefault(k, d) -> D.get(k, d), also set D[k]=d if k not in D. Return the value like get() except that if key is missing, d is both returned and inserted into the dictionary as the value of k. Note that, unlike as for Python's dict.setdefault(), d is not optional. Python defaults d to None, but that doesn't make sense for mappings that can't have None as a value (for example, an IIBTree can have only integers as values). """ def pop(key, d): """D.pop(k[, d]) -> v, remove key and return the corresponding value. If key is not found, d is returned if given, otherwise KeyError is raised. """ class IBTree(IDictionaryIsh): def insert(key, value): """Insert a key and value into the collection. If the key was already in the collection, then there is no change and 0 is returned. If the key was not already in the collection, then the item is added and 1 is returned. This method is here to allow one to generate random keys and to insert and test whether the key was there in one operation. A standard idiom for generating new keys will be:: key = generate_key() while not t.insert(key, value): key=generate_key() """ class IMerge(Interface): """Object with methods for merging sets, buckets, and trees. These methods are supplied in modules that define collection classes with particular key and value types. The operations apply only to collections from the same module. For example, the IIBTree.union can only be used with IIBTree.IIBTree, IIBTree.IIBucket, IIBTree.IISet, and IIBTree.IITreeSet. The implementing module has a value type. The IOBTree and OOBTree modules have object value type. The IIBTree and OIBTree modules have integer value types. Other modules may be defined in the future that have other value types. The individual types are classified into set (Set and TreeSet) and mapping (Bucket and BTree) types. """ def difference(c1, c2): """Return the keys or items in c1 for which there is no key in c2. If c1 is None, then None is returned. If c2 is None, then c1 is returned. If neither c1 nor c2 is None, the output is a Set if c1 is a Set or TreeSet, and is a Bucket if c1 is a Bucket or BTree. """ def union(c1, c2): """Compute the Union of c1 and c2. If c1 is None, then c2 is returned, otherwise, if c2 is None, then c1 is returned. The output is a Set containing keys from the input collections. """ def intersection(c1, c2): """Compute the intersection of c1 and c2. If c1 is None, then c2 is returned, otherwise, if c2 is None, then c1 is returned. The output is a Set containing matching keys from the input collections. """ class IBTreeModule(Interface): """These are available in all modules (IOBTree, OIBTree, OOBTree, IIBTree, IFBTree, LFBTree, LOBTree, OLBTree, and LLBTree). """ BTree = Attribute( """The IBTree for this module. Also available as [prefix]BTree, as in IOBTree.""") Bucket = Attribute( """The leaf-node data buckets used by the BTree. (IBucket is not currently defined in this file, but is essentially IDictionaryIsh, with the exception of __nonzero__, as of this writing.) Also available as [prefix]Bucket, as in IOBucket.""") TreeSet = Attribute( """The ITreeSet for this module. Also available as [prefix]TreeSet, as in IOTreeSet.""") Set = Attribute( """The ISet for this module: the leaf-node data buckets used by the TreeSet. Also available as [prefix]BTree, as in IOSet.""") class IIMerge(IMerge): """Merge collections with integer value type. A primary intent is to support operations with no or integer values, which are used as "scores" to rate indiviual keys. That is, in this context, a BTree or Bucket is viewed as a set with scored keys, using integer scores. """ def weightedUnion(c1, c2, weight1=1, weight2=1): """Compute the weighted union of c1 and c2. If c1 and c2 are None, the output is (0, None). If c1 is None and c2 is not None, the output is (weight2, c2). If c1 is not None and c2 is None, the output is (weight1, c1). Else, and hereafter, c1 is not None and c2 is not None. If c1 and c2 are both sets, the output is 1 and the (unweighted) union of the sets. Else the output is 1 and a Bucket whose keys are the union of c1 and c2's keys, and whose values are:: v1*weight1 + v2*weight2 where: v1 is 0 if the key is not in c1 1 if the key is in c1 and c1 is a set c1[key] if the key is in c1 and c1 is a mapping v2 is 0 if the key is not in c2 1 if the key is in c2 and c2 is a set c2[key] if the key is in c2 and c2 is a mapping Note that c1 and c2 must be collections. """ def weightedIntersection(c1, c2, weight1=1, weight2=1): """Compute the weighted intersection of c1 and c2. If c1 and c2 are None, the output is (0, None). If c1 is None and c2 is not None, the output is (weight2, c2). If c1 is not None and c2 is None, the output is (weight1, c1). Else, and hereafter, c1 is not None and c2 is not None. If c1 and c2 are both sets, the output is the sum of the weights and the (unweighted) intersection of the sets. Else the output is 1 and a Bucket whose keys are the intersection of c1 and c2's keys, and whose values are:: v1*weight1 + v2*weight2 where: v1 is 1 if c1 is a set c1[key] if c1 is a mapping v2 is 1 if c2 is a set c2[key] if c2 is a mapping Note that c1 and c2 must be collections. """ class IMergeIntegerKey(IMerge): """IMerge-able objects with integer keys. Concretely, this means the types in IOBTree and IIBTree. """ def multiunion(seq): """Return union of (zero or more) integer sets, as an integer set. seq is a sequence of objects each convertible to an integer set. These objects are convertible to an integer set: + An integer, which is added to the union. + A Set or TreeSet from the same module (for example, an IIBTree.TreeSet for IIBTree.multiunion()). The elements of the set are added to the union. + A Bucket or BTree from the same module (for example, an IOBTree.IOBTree for IOBTree.multiunion()). The keys of the mapping are added to the union. The union is returned as a Set from the same module (for example, IIBTree.multiunion() returns an IIBTree.IISet). The point to this method is that it can run much faster than doing a sequence of two-input union() calls. Under the covers, all the integers in all the inputs are sorted via a single linear-time radix sort, then duplicates are removed in a second linear-time pass. """ class IBTreeFamily(Interface): """the 64-bit or 32-bit family""" IO = Attribute('The IIntegerObjectBTreeModule for this family') OI = Attribute('The IObjectIntegerBTreeModule for this family') II = Attribute('The IIntegerIntegerBTreeModule for this family') IF = Attribute('The IIntegerFloatBTreeModule for this family') OO = Attribute('The IObjectObjectBTreeModule for this family') maxint = Attribute('The maximum integer storable in this family') minint = Attribute('The minimum integer storable in this family') class IIntegerObjectBTreeModule(IBTreeModule, IMerge): """keys, or set values, are integers; values are objects. describes IOBTree and LOBTree""" family = Attribute('The IBTreeFamily of this module') class IObjectIntegerBTreeModule(IBTreeModule, IIMerge): """keys, or set values, are objects; values are integers. Object keys (and set values) must sort reliably (for instance, *not* on object id)! Homogenous key types recommended. describes OIBTree and LOBTree""" family = Attribute('The IBTreeFamily of this module') class IIntegerIntegerBTreeModule(IBTreeModule, IIMerge, IMergeIntegerKey): """keys, or set values, are integers; values are also integers. describes IIBTree and LLBTree""" family = Attribute('The IBTreeFamily of this module') class IObjectObjectBTreeModule(IBTreeModule, IMerge): """keys, or set values, are objects; values are also objects. Object keys (and set values) must sort reliably (for instance, *not* on object id)! Homogenous key types recommended. describes OOBTree""" # Note that there's no ``family`` attribute; all families include # the OO flavor of BTrees. class IIntegerFloatBTreeModule(IBTreeModule, IMerge): """keys, or set values, are integers; values are floats. describes IFBTree and LFBTree""" family = Attribute('The IBTreeFamily of this module') ############################################################### # IMPORTANT NOTE # # Getting the length of a BTree, TreeSet, or output of keys, # values, or items of same is expensive. If you need to get the # length, you need to maintain this separately. # # Eventually, I need to express this through the interfaces. # ################################################################ zope2.13-2.13.21/source/ZODB3/src/BTrees/Development.txt0000644000175000017500000004074312214017464021267 0ustar arnauarnau===================== Developer Information ===================== This document provides information for developers who maintain or extend `BTrees`. Macros ====== `BTrees` are defined using a "template", roughly akin to a C++ template. To create a new family of `BTrees`, create a source file that defines macros used to handle differences in key and value types: Configuration Macros -------------------- ``MASTER_ID`` A string to hold an RCS/CVS Id key to be included in compiled binaries. ``MOD_NAME_PREFIX`` A string (like "IO" or "OO") that provides the prefix used for the module. This gets used to generate type names and the internal module name string. ``DEFAULT_MAX_BUCKET_SIZE`` An int giving the maximum bucket size (number of key/value pairs). When a bucket gets larger than this due to an insertion *into a BTREE*, it splits. Inserting into a bucket directly doesn't split, and functions that produce a bucket output (e.g., ``union()``) also have no bound on how large a bucket may get. Someday this will be tunable on `BTree`. instances. ``DEFAULT_MAX_BTREE_SIZE`` An ``int`` giving the maximum size (number of children) of an internal btree node. Someday this will be tunable on ``BTree`` instances. Macros for Keys --------------- ``KEY_TYPE`` The C type declaration for keys (e.g., ``int`` or ``PyObject*``). ``KEY_TYPE_IS_PYOBJECT`` Define if ``KEY_TYPE`` is a ``PyObject*`, else ``undef``. ``KEY_CHECK(K)`` Tests whether the ``PyObject* K`` can be converted to the (``C``) key type (``KEY_TYPE``). The macro should return a boolean (zero for false, non-zero for true). When it returns false, its caller should probably set a ``TypeError`` exception. ``TEST_KEY_SET_OR(V, K, T)`` Like Python's ``cmp()``. Compares K(ey) to T(arget), where ``K`` and ``T`` are ``C`` values of type `KEY_TYPE`. ``V`` is assigned an `int` value depending on the outcome:: < 0 if K < T == 0 if K == T > 0 if K > T This macro acts like an ``if``, where the following statement is executed only if a Python exception has been raised because the values could not be compared. ``DECREF_KEY(K)`` ``K`` is a value of ``KEY_TYPE``. If ``KEY_TYPE`` is a flavor of ``PyObject*``, write this to do ``Py_DECREF(K)``. Else (e.g., ``KEY_TYPE`` is ``int``) make it a nop. ``INCREF_KEY(K)`` ``K`` is a value of `KEY_TYPE`. If `KEY_TYPE` is a flavor of ``PyObject*``, write this to do ``Py_INCREF(K)``. Else (e.g., `KEY_TYPE` is ``int``) make it a nop. ``COPY_KEY(K, E)`` Like ``K=E``. Copy a key from ``E`` to ``K``, both of ``KEY_TYPE``. Note that this doesn't ``decref K`` or ``incref E`` when ``KEY_TYPE`` is a ``PyObject*``; the caller is responsible for keeping refcounts straight. ``COPY_KEY_TO_OBJECT(O, K)`` Roughly like ``O=K``. ``O`` is a ``PyObject*``, and the macro must build a Python object form of ``K``, assign it to ``O``, and ensure that ``O`` owns the reference to its new value. It may do this by creating a new Python object based on ``K`` (e.g., ``PyInt_FromLong(K)`` when ``KEY_TYPE`` is ``int``), or simply by doing ``Py_INCREF(K)`` if ``KEY_TYPE`` is a ``PyObject*``. ``COPY_KEY_FROM_ARG(TARGET, ARG, STATUS)`` Copy an argument to the target without creating a new reference to ``ARG``. ``ARG`` is a ``PyObject*``, and ``TARGET`` is of type ``KEY_TYPE``. If this can't be done (for example, ``KEY_CHECK(ARG)`` returns false), set a Python error and set status to ``0``. If there is no error, leave status alone. Macros for Values ----------------- ``VALUE_TYPE`` The C type declaration for values (e.g., ``int`` or ``PyObject*``). ``VALUE_TYPE_IS_PYOBJECT`` Define if ``VALUE_TYPE`` is a ``PyObject*``, else ``undef``. ``TEST_VALUE(X, Y)`` Like Python's ``cmp()``. Compares ``X`` to ``Y``, where ``X`` & ``Y`` are ``C`` values of type ``VALUE_TYPE``. The macro returns an ``int``, with value:: < 0 if X < Y == 0 if X == Y > 0 if X > Y Bug: There is no provision for determining whether the comparison attempt failed (set a Python exception). ``DECREF_VALUE(K)`` Like ``DECREF_KEY``, except applied to values of ``VALUE_TYPE``. ``INCREF_VALUE(K)`` Like ``INCREF_KEY``, except applied to values of ``VALUE_TYPE``. ``COPY_VALUE(K, E)`` Like ``COPY_KEY``, except applied to values of ``VALUE_TYPE``. ``COPY_VALUE_TO_OBJECT(O, K)`` Like ``COPY_KEY_TO_OBJECT``, except applied to values of ``VALUE_TYPE``. ``COPY_VALUE_FROM_ARG(TARGET, ARG, STATUS)`` Like ``COPY_KEY_FROM_ARG``, except applied to values of ``VALUE_TYPE``. ``NORMALIZE_VALUE(V, MIN)`` Normalize the value, ``V``, using the parameter ``MIN``. This is almost certainly a YAGNI. It is a no-op for most types. For integers, ``V`` is replaced by ``V/MIN`` only if ``MIN > 0``. Macros for Set Operations ------------------------- ``MERGE_DEFAULT`` A value of ``VALUE_TYPE`` specifying the value to associate with set elements when sets are merged with mappings via weighed union or weighted intersection. ``MERGE(O1, w1, O2, w2)`` Performs a weighted merge of two values, ``O1`` and ``O2``, using weights ``w1`` and ``w2``. The result must be of ``VALUE_TYPE``. Note that weighted unions and weighted intersections are not enabled if this macro is left undefined. ``MERGE_WEIGHT(O, w)`` Computes a weighted value for ``O``. The result must be of ``VALUE_TYPE``. This is used for "filling out" weighted unions, i.e. to compute a weighted value for keys that appear in only one of the input mappings. If left undefined, ``MERGE_WEIGHT`` defaults to:: #define MERGE_WEIGHT(O, w) (O) ``MULTI_INT_UNION`` The value doesn't matter. If defined, `SetOpTemplate.c` compiles code for a ``multiunion()`` function (compute a union of many input sets at high speed). This currently makes sense only for structures with integer keys. BTree Clues =========== More or less random bits of helpful info. + In papers and textbooks, this flavor of BTree is usually called a B+-Tree, where "+" is a superscript. + All keys and all values live in the bucket leaf nodes. Keys in interior (BTree) nodes merely serve to guide a search efficiently toward the correct leaf. + When a key is deleted, it's physically removed from the bucket it's in, but this doesn't propagate back up the tree: since keys in interior nodes only serve to guide searches, it's OK-- and saves time --to leave "stale" keys in interior nodes. + No attempt is made to rebalance the tree after a deletion, unless a bucket thereby becomes entirely empty. "Classic BTrees" do rebalance, keeping all buckets at least half full (provided there are enough keys in the entire tree to fill half a bucket). The tradeoffs are murky. Pathological cases in the presence of deletion do exist. Pathologies include trees tending toward only one key per bucket, and buckets at differing depths (all buckets are at the same depth in a classic BTree). + ``DEFAULT_MAX_BUCKET_SIZE`` and ``DEFAULT_MAX_BTREE_SIZE`` are chosen mostly to "even out" pickle sizes in storage. That's why, e.g., an `IIBTree` has larger values than an `OOBTree`: pickles store ints more efficiently than they can store arbitrary Python objects. + In a non-empty BTree, every bucket node contains at least one key, and every BTree node contains at least one child and a non-NULL firstbucket pointer. However, a BTree node may not contain any keys. + An empty BTree consists solely of a BTree node with ``len==0`` and ``firstbucket==NULL``. + Although a BTree can become unbalanced under a mix of inserts and deletes (meaning both that there's nothing stronger that can be said about buckets than that they're not empty, and that buckets can appear at different depths), a BTree node always has children of the same kind: they're all buckets, or they're all BTree nodes. The ``BTREE_SEARCH`` Macro ========================== For notational ease, consider a fixed BTree node ``x``, and let :: K(i) mean x->data.key[i] C(i) mean all the keys reachable from x->data.child[i] For each ``i`` in ``0`` to ``x->len-1`` inclusive, :: K(i) <= C(i) < K(i+1) is a BTree node invariant, where we pretend that ``K(0)`` holds a key smaller than any possible key, and ``K(x->len)`` holds a key larger than any possible key. (Note that ``K(x->len)`` doesn't actually exist, and ``K(0)`` is never used although space for it exists in non-empty BTree nodes.) When searching for a key ``k``, then, the child pointer we want to follow is the one at index ``i`` such that ``K(i) <= k < K(i+1)``. There can be at most one such ``i``, since the ``K(i)`` are strictly increasing. And there is at least one such ``i`` provided the tree isn't empty (so that ``0 < len``). For the moment, assume the tree isn't empty (we'll get back to that later). The macro's chief loop invariant is :: K(lo) < k < K(hi) This holds trivially at the start, since ``lo`` is set to ``0``, and ``hi`` to ``x->len``, and we pretend ``K(0)`` is minus infinity and ``K(len)`` is plus infinity. Inside the loop, if ``K(i) < k`` we set ``lo`` to ``i``, and if ``K(i) > k`` we set ``hi`` to ``i``. These obviously preserve the invariant. If ``K(i) == k``, the loop breaks and sets the result to ``i``, and since ``K(i) == k`` in that case ``i`` is obviously the correct result. Other cases depend on how ``i = floor((lo + hi)/2)`` works, exactly. Suppose ``lo + d = hi`` for some ``d >= 0``. Then ``i = floor((lo + lo + d)/2) = floor(lo + d/2) = lo + floor(d/2)``. So: a. ``[d == 0] (lo == i == hi)`` if and only if ``(lo == hi)``. b. ``[d == 1] (lo == i < hi)`` if and only if ``(lo+1 == hi)``. c. ``[d > 1] (lo < i < hi)`` if and only if ``(lo+1 < hi)``. If the node is empty ``(x->len == 0)``, then ``lo==i==hi==0`` at the start, and the loop exits immediately (the first ``i > lo`` test fails), without entering the body. Else ``lo < hi`` at the start, and the invariant ``K(lo) < k < K(hi)`` holds. If ``lo+1 < hi``, we're in case (c): ``i`` is strictly between ``lo`` and ``hi``, so the loop body is entered, and regardless of whether the body sets the new ``lo`` or the new ``hi`` to ``i``, the new ``lo`` is strictly less than the new ``hi``, and the difference between the new ``lo`` and new ``hi`` is strictly less than the difference between the old ``lo`` and old ``hi``. So long as the new ``lo + 1`` remains < the new ``hi``, we stay in this case. We can't stay in this case forever, though: because ``hi-lo`` decreases on each trip but remains > ``0``, ``lo+1 == hi`` must eventually become true. (In fact, it becomes true quickly, in about ``log2(x->len)`` trips; the point is more that ``lo`` doesn't equal ``hi`` when the loop ends, it has to end with ``lo+1==hi`` and ``i==lo``). Then we're in case (b): ``i==lo==hi-1`` then, and the loop exits. The invariant still holds, with ``lo==i`` and ``hi==lo+1==i+1``:: K(i) < k < K(i+1) so ``i`` is again the correct answer. Optimization points: -------------------- + Division by 2 is done via shift rather via "/2". These are signed ints, and almost all C compilers treat signed int division as truncating, and shifting is not the same as truncation for signed int division. The compiler has no way to know these values aren't negative, so has to generate longer-winded code for "/2". But we know these values aren't negative, and exploit it. + The order of _cmp comparisons matters. We're in an interior BTree node, and are looking at only a tiny fraction of all the keys that exist. So finding the key exactly in this node is unlikely, and checking ``_cmp == 0`` is a waste of time to the same extent. It doesn't matter whether we check for ``_cmp < 0`` or ``_cmp > 0`` first, so long as we do both before worrying about equality. + At the start of a routine, it's better to run this macro even if ``x->len`` is ``0`` (check for that afterwards). We just called a function and so probably drained the pipeline. If the first thing we do then is read up ``self->len`` and check it against ``0``, we just sit there waiting for the data to get read up, and then another immediate test-and-branch, and for a very unlikely case (BTree nodes are rarely empty). It's better to get into the loop right away so the normal case makes progress ASAP. The ``BUCKET_SEARCH`` Macro =========================== This has a different job than ``BTREE_SEARCH``: the key ``0`` slot is legitimate in a bucket, and we want to find the index at which the key belongs. If the key is larger than the bucket's largest key, a new slot at index len is where it belongs, else it belongs at the smallest ``i`` with ``keys[i]`` >= the key we're looking for. We also need to know whether or not the key is present (``BTREE_SEARCH`` didn't care; it only wanted to find the next node to search). The mechanics of the search are quite similar, though. The primary loop invariant changes to (say we're searching for key ``k``):: K(lo-1) < k < K(hi) where ``K(i)`` means ``keys[i]``, and we pretend ``K(-1)`` is minus infinity and ``K(len)`` is plus infinity. If the bucket is empty, ``lo=hi=i=0`` at the start, the loop body is never entered, and the macro sets ``INDEX`` to 0 and ``ABSENT`` to true. That's why ``_cmp`` is initialized to 1 (``_cmp`` becomes ``ABSENT``). Else the bucket is not empty, lok``, ``hi`` is set to ``i``, preserving that ``K[hi] = K[i] > k``. If the loop exits after either of those, ``_cmp != 0``, so ``ABSENT`` becomes true. If ``K[i]=k``, the loop breaks, so that ``INDEX`` becomes ``i``, and ``ABSENT`` becomes false (``_cmp=0`` in this case). The same case analysis for ``BTREE_SEARCH`` on ``lo`` and ``hi`` holds here: a. ``(lo == i == hi)`` if and only if ``(lo == hi)``. b. ``(lo == i < hi)`` if and only if ``(lo+1 == hi)``. c. ``(lo < i < hi)`` if and only if ``(lo+1 < hi)``. So long as ``lo+1 < hi``, we're in case (c), and either break with equality (in which case the right results are obviously computed) or narrow the range. If equality doesn't obtain, the range eventually narrows to cases (a) or (b). To go from (c) to (a), we must have ``lo+2==hi`` at the start, and ``K[i]=K[lo+1] key``), because when it pays it narrows the range more (we get a little boost from setting ``lo=i+1`` in this case; the other case sets ``hi=i``, which isn't as much of a narrowing). zope2.13-2.13.21/source/ZODB3/src/BTrees/BucketTemplate.c0000644000175000017500000013703312214017464021320 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #define BUCKETTEMPLATE_C "$Id: BucketTemplate.c 113734 2010-06-21 15:33:46Z ctheune $\n" /* Use BUCKET_SEARCH to find the index at which a key belongs. * INDEX An int lvalue to hold the index i such that KEY belongs at * SELF->keys[i]. Note that this will equal SELF->len if KEY * is larger than the bucket's largest key. Else it's the * smallest i such that SELF->keys[i] >= KEY. * ABSENT An int lvalue to hold a Boolean result, true (!= 0) if the * key is absent, false (== 0) if the key is at INDEX. * SELF A pointer to a Bucket node. * KEY The key you're looking for, of type KEY_TYPE. * ONERROR What to do if key comparison raises an exception; for example, * perhaps 'return NULL'. * * See Maintainer.txt for discussion: this is optimized in subtle ways. * It's recommended that you call this at the start of a routine, waiting * to check for self->len == 0 after (if an empty bucket is special in * context; INDEX becomes 0 and ABSENT becomes true if this macro is run * with an empty SELF, and that may be all the invoker needs to know). */ #define BUCKET_SEARCH(INDEX, ABSENT, SELF, KEY, ONERROR) { \ int _lo = 0; \ int _hi = (SELF)->len; \ int _i; \ int _cmp = 1; \ for (_i = _hi >> 1; _lo < _hi; _i = (_lo + _hi) >> 1) { \ TEST_KEY_SET_OR(_cmp, (SELF)->keys[_i], (KEY)) \ ONERROR; \ if (_cmp < 0) _lo = _i + 1; \ else if (_cmp == 0) break; \ else _hi = _i; \ } \ (INDEX) = _i; \ (ABSENT) = _cmp; \ } /* ** _bucket_get ** ** Search a bucket for a given key. ** ** Arguments ** self The bucket ** keyarg The key to look for ** has_key Boolean; if true, return a true/false result; else return ** the value associated with the key. ** ** Return ** If has_key: ** Returns the Python int 0 if the key is absent, else returns ** has_key itself as a Python int. A BTree caller generally passes ** the depth of the bucket for has_key, so a true result returns ** the bucket depth then. ** Note that has_key should be true when searching set buckets. ** If not has_key: ** If the key is present, returns the associated value, and the ** caller owns the reference. Else returns NULL and sets KeyError. ** Whether or not has_key: ** If a comparison sets an exception, returns NULL. */ static PyObject * _bucket_get(Bucket *self, PyObject *keyarg, int has_key) { int i, cmp; KEY_TYPE key; PyObject *r = NULL; int copied = 1; COPY_KEY_FROM_ARG(key, keyarg, copied); UNLESS (copied) return NULL; UNLESS (PER_USE(self)) return NULL; BUCKET_SEARCH(i, cmp, self, key, goto Done); if (has_key) r = PyInt_FromLong(cmp ? 0 : has_key); else { if (cmp == 0) { COPY_VALUE_TO_OBJECT(r, self->values[i]); } else PyErr_SetObject(PyExc_KeyError, keyarg); } Done: PER_UNUSE(self); return r; } static PyObject * bucket_getitem(Bucket *self, PyObject *key) { return _bucket_get(self, key, 0); } /* ** Bucket_grow ** ** Resize a bucket. ** ** Arguments: self The bucket. ** newsize The new maximum capacity. If < 0, double the ** current size unless the bucket is currently empty, ** in which case use MIN_BUCKET_ALLOC. ** noval Boolean; if true, allocate only key space and not ** value space ** ** Returns: -1 on error, and MemoryError exception is set ** 0 on success */ static int Bucket_grow(Bucket *self, int newsize, int noval) { KEY_TYPE *keys; VALUE_TYPE *values; if (self->size) { if (newsize < 0) newsize = self->size * 2; if (newsize < 0) /* int overflow */ goto Overflow; UNLESS (keys = BTree_Realloc(self->keys, sizeof(KEY_TYPE) * newsize)) return -1; UNLESS (noval) { values = BTree_Realloc(self->values, sizeof(VALUE_TYPE) * newsize); if (values == NULL) { free(keys); return -1; } self->values = values; } self->keys = keys; } else { if (newsize < 0) newsize = MIN_BUCKET_ALLOC; UNLESS (self->keys = BTree_Malloc(sizeof(KEY_TYPE) * newsize)) return -1; UNLESS (noval) { self->values = BTree_Malloc(sizeof(VALUE_TYPE) * newsize); if (self->values == NULL) { free(self->keys); self->keys = NULL; return -1; } } } self->size = newsize; return 0; Overflow: PyErr_NoMemory(); return -1; } /* So far, bucket_append is called only by multiunion_m(), so is called * only when MULTI_INT_UNION is defined. Flavors of BTree/Bucket that * don't support MULTI_INT_UNION don't call bucket_append (yet), and * gcc complains if bucket_append is compiled in those cases. So only * compile bucket_append if it's going to be used. */ #ifdef MULTI_INT_UNION /* * Append a slice of the "from" bucket to self. * * self Append (at least keys) to this bucket. self must be activated * upon entry, and remains activated at exit. If copyValues * is true, self must be empty or already have a non-NULL values * pointer. self's access and modification times aren't updated. * from The bucket from which to take keys, and possibly values. from * must be activated upon entry, and remains activated at exit. * If copyValues is true, from must have a non-NULL values * pointer. self and from must not be the same. from's access * time isn't updated. * i, n The slice from[i : i+n] is appended to self. Must have * i >= 0, n > 0 and i+n <= from->len. * copyValues Boolean. If true, copy values from the slice as well as keys. * In this case, from must have a non-NULL values pointer, and * self must too (unless self is empty, in which case a values * vector will be allocated for it). * overallocate Boolean. If self doesn't have enough room upon entry to hold * all the appended stuff, then if overallocate is false exactly * enough room will be allocated to hold the new stuff, else if * overallocate is true an excess will be allocated. overallocate * may be a good idea if you expect to append more stuff to self * later; else overallocate should be false. * * CAUTION: If self is empty upon entry (self->size == 0), and copyValues is * false, then no space for values will get allocated. This can be a trap if * the caller intends to copy values itself. * * Return * -1 Error. * 0 OK. */ static int bucket_append(Bucket *self, Bucket *from, int i, int n, int copyValues, int overallocate) { int newlen; assert(self && from && self != from); assert(i >= 0); assert(n > 0); assert(i+n <= from->len); /* Make room. */ newlen = self->len + n; if (newlen > self->size) { int newsize = newlen; if (overallocate) /* boost by 25% -- pretty arbitrary */ newsize += newsize >> 2; if (Bucket_grow(self, newsize, ! copyValues) < 0) return -1; } assert(newlen <= self->size); /* Copy stuff. */ memcpy(self->keys + self->len, from->keys + i, n * sizeof(KEY_TYPE)); if (copyValues) { assert(self->values); assert(from->values); memcpy(self->values + self->len, from->values + i, n * sizeof(VALUE_TYPE)); } self->len = newlen; /* Bump refcounts. */ #ifdef KEY_TYPE_IS_PYOBJECT { int j; PyObject **p = from->keys + i; for (j = 0; j < n; ++j, ++p) { Py_INCREF(*p); } } #endif #ifdef VALUE_TYPE_IS_PYOBJECT if (copyValues) { int j; PyObject **p = from->values + i; for (j = 0; j < n; ++j, ++p) { Py_INCREF(*p); } } #endif return 0; } #endif /* MULTI_INT_UNION */ /* ** _bucket_set: Assign a value to a key in a bucket, delete a key+value ** pair, or just insert a key. ** ** Arguments ** self The bucket ** keyarg The key to look for ** v The value to associate with key; NULL means delete the key. ** If NULL, it's an error (KeyError) if the key isn't present. ** Note that if this is a set bucket, and you want to insert ** a new set element, v must be non-NULL although its exact ** value will be ignored. Passing Py_None is good for this. ** unique Boolean; when true, don't replace the value if the key is ** already present. ** noval Boolean; when true, operate on keys only (ignore values) ** changed ignored on input ** ** Return ** -1 on error ** 0 on success and the # of bucket entries didn't change ** 1 on success and the # of bucket entries did change ** *changed If non-NULL, set to 1 on any mutation of the bucket. */ static int _bucket_set(Bucket *self, PyObject *keyarg, PyObject *v, int unique, int noval, int *changed) { int i, cmp; KEY_TYPE key; /* Subtle: there may or may not be a value. If there is, we need to * check its type early, so that in case of error we can get out before * mutating the bucket. But because value isn't used on all paths, if * we don't initialize value then gcc gives a nuisance complaint that * value may be used initialized (it can't be, but gcc doesn't know * that). So we initialize it. However, VALUE_TYPE can be various types, * including int, PyObject*, and char[6], so it's a puzzle to spell * initialization. It so happens that {0} is a valid initializer for all * these types. */ VALUE_TYPE value = {0}; /* squash nuisance warning */ int result = -1; /* until proven innocent */ int copied = 1; COPY_KEY_FROM_ARG(key, keyarg, copied); UNLESS(copied) return -1; /* Copy the value early (if needed), so that in case of error a * pile of bucket mutations don't need to be undone. */ if (v && !noval) { COPY_VALUE_FROM_ARG(value, v, copied); UNLESS(copied) return -1; } UNLESS (PER_USE(self)) return -1; BUCKET_SEARCH(i, cmp, self, key, goto Done); if (cmp == 0) { /* The key exists, at index i. */ if (v) { /* The key exists at index i, and there's a new value. * If unique, we're not supposed to replace it. If noval, or this * is a set bucket (self->values is NULL), there's nothing to do. */ if (unique || noval || self->values == NULL) { result = 0; goto Done; } /* The key exists at index i, and we need to replace the value. */ #ifdef VALUE_SAME /* short-circuit if no change */ if (VALUE_SAME(self->values[i], value)) { result = 0; goto Done; } #endif if (changed) *changed = 1; DECREF_VALUE(self->values[i]); COPY_VALUE(self->values[i], value); INCREF_VALUE(self->values[i]); if (PER_CHANGED(self) >= 0) result = 0; goto Done; } /* The key exists at index i, and should be deleted. */ DECREF_KEY(self->keys[i]); self->len--; if (i < self->len) memmove(self->keys + i, self->keys + i+1, sizeof(KEY_TYPE)*(self->len - i)); if (self->values) { DECREF_VALUE(self->values[i]); if (i < self->len) memmove(self->values + i, self->values + i+1, sizeof(VALUE_TYPE)*(self->len - i)); } if (! self->len) { self->size = 0; free(self->keys); self->keys = NULL; if (self->values) { free(self->values); self->values = NULL; } } if (changed) *changed = 1; if (PER_CHANGED(self) >= 0) result = 1; goto Done; } /* The key doesn't exist, and belongs at index i. */ if (!v) { /* Can't delete a non-existent key. */ PyErr_SetObject(PyExc_KeyError, keyarg); goto Done; } /* The key doesn't exist and should be inserted at index i. */ if (self->len == self->size && Bucket_grow(self, -1, noval) < 0) goto Done; if (self->len > i) { memmove(self->keys + i + 1, self->keys + i, sizeof(KEY_TYPE) * (self->len - i)); if (self->values) { memmove(self->values + i + 1, self->values + i, sizeof(VALUE_TYPE) * (self->len - i)); } } COPY_KEY(self->keys[i], key); INCREF_KEY(self->keys[i]); if (! noval) { COPY_VALUE(self->values[i], value); INCREF_VALUE(self->values[i]); } self->len++; if (changed) *changed = 1; if (PER_CHANGED(self) >= 0) result = 1; Done: PER_UNUSE(self); return result; } /* ** bucket_setitem ** ** wrapper for _bucket_setitem (eliminates +1 return code) ** ** Arguments: self The bucket ** key The key to insert under ** v The value to insert ** ** Returns 0 on success ** -1 on failure */ static int bucket_setitem(Bucket *self, PyObject *key, PyObject *v) { if (_bucket_set(self, key, v, 0, 0, 0) < 0) return -1; return 0; } /** ** Accepts a sequence of 2-tuples, or any object with an items() ** method that returns an iterable object producing 2-tuples. */ static int update_from_seq(PyObject *map, PyObject *seq) { PyObject *iter, *o, *k, *v; int err = -1; /* One path creates a new seq object. The other path has an INCREF of the seq argument. So seq must always be DECREFed on the way out. */ /* Use items() if it's not a sequence. Alas, PySequence_Check() * returns true for a PeristentMapping or PersistentDict, and we * want to use items() in those cases too. */ if (!PySequence_Check(seq) || /* or it "looks like a dict" */ PyObject_HasAttrString(seq, "iteritems")) { PyObject *items; items = PyObject_GetAttrString(seq, "items"); if (items == NULL) return -1; seq = PyObject_CallObject(items, NULL); Py_DECREF(items); if (seq == NULL) return -1; } else Py_INCREF(seq); iter = PyObject_GetIter(seq); if (iter == NULL) goto err; while (1) { o = PyIter_Next(iter); if (o == NULL) { if (PyErr_Occurred()) goto err; else break; } if (!PyTuple_Check(o) || PyTuple_GET_SIZE(o) != 2) { Py_DECREF(o); PyErr_SetString(PyExc_TypeError, "Sequence must contain 2-item tuples"); goto err; } k = PyTuple_GET_ITEM(o, 0); v = PyTuple_GET_ITEM(o, 1); if (PyObject_SetItem(map, k, v) < 0) { Py_DECREF(o); goto err; } Py_DECREF(o); } err = 0; err: Py_DECREF(iter); Py_DECREF(seq); return err; } static PyObject * Mapping_update(PyObject *self, PyObject *seq) { if (update_from_seq(self, seq) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } /* ** bucket_split ** ** Splits one bucket into two ** ** Arguments: self The bucket ** index the index of the key to split at (O.O.B use midpoint) ** next the new bucket to split into ** ** Returns: 0 on success ** -1 on failure */ static int bucket_split(Bucket *self, int index, Bucket *next) { int next_size; ASSERT(self->len > 1, "split of empty bucket", -1); if (index < 0 || index >= self->len) index = self->len / 2; next_size = self->len - index; next->keys = BTree_Malloc(sizeof(KEY_TYPE) * next_size); if (!next->keys) return -1; memcpy(next->keys, self->keys + index, sizeof(KEY_TYPE) * next_size); if (self->values) { next->values = BTree_Malloc(sizeof(VALUE_TYPE) * next_size); if (!next->values) { free(next->keys); next->keys = NULL; return -1; } memcpy(next->values, self->values + index, sizeof(VALUE_TYPE) * next_size); } next->size = next_size; next->len = next_size; self->len = index; next->next = self->next; Py_INCREF(next); self->next = next; if (PER_CHANGED(self) < 0) return -1; return 0; } /* Set self->next to self->next->next, i.e. unlink self's successor from * the chain. * * Return: * -1 error * 0 OK */ static int Bucket_deleteNextBucket(Bucket *self) { int result = -1; /* until proven innocent */ Bucket *successor; PER_USE_OR_RETURN(self, -1); successor = self->next; if (successor) { Bucket *next; /* Before: self -> successor -> next * After: self --------------> next */ UNLESS (PER_USE(successor)) goto Done; next = successor->next; PER_UNUSE(successor); Py_XINCREF(next); /* it may be NULL, of course */ self->next = next; Py_DECREF(successor); if (PER_CHANGED(self) < 0) goto Done; } result = 0; Done: PER_UNUSE(self); return result; } /* Bucket_findRangeEnd -- Find the index of a range endpoint (possibly) contained in a bucket. Arguments: self The bucket keyarg The key to match against low Boolean; true for low end of range, false for high exclude_equal Boolean; if true, don't accept an exact match, and if there is one then move right if low and left if !low. offset The output offset If low true, *offset <- index of the smallest item >= key, if low false the index of the largest item <= key. In either case, if there is no such index, *offset is left alone and 0 is returned. Return: 0 No suitable index exists; *offset has not been changed 1 The correct index was stored into *offset -1 Error Example: Suppose the keys are [2, 4], and exclude_equal is false. Searching for 2 sets *offset to 0 and returns 1 regardless of low. Searching for 4 sets *offset to 1 and returns 1 regardless of low. Searching for 1: If low true, sets *offset to 0, returns 1. If low false, returns 0. Searching for 3: If low true, sets *offset to 1, returns 1. If low false, sets *offset to 0, returns 1. Searching for 5: If low true, returns 0. If low false, sets *offset to 1, returns 1. The 1, 3 and 5 examples are the same when exclude_equal is true. */ static int Bucket_findRangeEnd(Bucket *self, PyObject *keyarg, int low, int exclude_equal, int *offset) { int i, cmp; int result = -1; /* until proven innocent */ KEY_TYPE key; int copied = 1; COPY_KEY_FROM_ARG(key, keyarg, copied); UNLESS (copied) return -1; UNLESS (PER_USE(self)) return -1; BUCKET_SEARCH(i, cmp, self, key, goto Done); if (cmp == 0) { /* exact match at index i */ if (exclude_equal) { /* but we don't want an exact match */ if (low) ++i; else --i; } } /* Else keys[i-1] < key < keys[i], picturing infinities at OOB indices, * and i has the smallest item > key, which is correct for low. */ else if (! low) /* i-1 has the largest item < key (unless i-1 is 0OB) */ --i; result = 0 <= i && i < self->len; if (result) *offset = i; Done: PER_UNUSE(self); return result; } static PyObject * Bucket_maxminKey(Bucket *self, PyObject *args, int min) { PyObject *key=0; int rc, offset = 0; int empty_bucket = 1; if (args && ! PyArg_ParseTuple(args, "|O", &key)) return NULL; PER_USE_OR_RETURN(self, NULL); UNLESS (self->len) goto empty; /* Find the low range */ if (key) { if ((rc = Bucket_findRangeEnd(self, key, min, 0, &offset)) <= 0) { if (rc < 0) return NULL; empty_bucket = 0; goto empty; } } else if (min) offset = 0; else offset = self->len -1; COPY_KEY_TO_OBJECT(key, self->keys[offset]); PER_UNUSE(self); return key; empty: PyErr_SetString(PyExc_ValueError, empty_bucket ? "empty bucket" : "no key satisfies the conditions"); PER_UNUSE(self); return NULL; } static PyObject * Bucket_minKey(Bucket *self, PyObject *args) { return Bucket_maxminKey(self, args, 1); } static PyObject * Bucket_maxKey(Bucket *self, PyObject *args) { return Bucket_maxminKey(self, args, 0); } static int Bucket_rangeSearch(Bucket *self, PyObject *args, PyObject *kw, int *low, int *high) { PyObject *min = Py_None; PyObject *max = Py_None; int excludemin = 0; int excludemax = 0; int rc; if (args) { if (! PyArg_ParseTupleAndKeywords(args, kw, "|OOii", search_keywords, &min, &max, &excludemin, &excludemax)) return -1; } UNLESS (self->len) goto empty; /* Find the low range */ if (min != Py_None) { rc = Bucket_findRangeEnd(self, min, 1, excludemin, low); if (rc < 0) return -1; if (rc == 0) goto empty; } else { *low = 0; if (excludemin) { if (self->len < 2) goto empty; ++*low; } } /* Find the high range */ if (max != Py_None) { rc = Bucket_findRangeEnd(self, max, 0, excludemax, high); if (rc < 0) return -1; if (rc == 0) goto empty; } else { *high = self->len - 1; if (excludemax) { if (self->len < 2) goto empty; --*high; } } /* If min < max to begin with, it's quite possible that low > high now. */ if (*low <= *high) return 0; empty: *low = 0; *high = -1; return 0; } /* ** bucket_keys ** ** Generate a list of all keys in the bucket ** ** Arguments: self The Bucket ** args (unused) ** ** Returns: list of bucket keys */ static PyObject * bucket_keys(Bucket *self, PyObject *args, PyObject *kw) { PyObject *r = NULL, *key; int i, low, high; PER_USE_OR_RETURN(self, NULL); if (Bucket_rangeSearch(self, args, kw, &low, &high) < 0) goto err; r = PyList_New(high-low+1); if (r == NULL) goto err; for (i=low; i <= high; i++) { COPY_KEY_TO_OBJECT(key, self->keys[i]); if (PyList_SetItem(r, i-low , key) < 0) goto err; } PER_UNUSE(self); return r; err: PER_UNUSE(self); Py_XDECREF(r); return NULL; } /* ** bucket_values ** ** Generate a list of all values in the bucket ** ** Arguments: self The Bucket ** args (unused) ** ** Returns list of values */ static PyObject * bucket_values(Bucket *self, PyObject *args, PyObject *kw) { PyObject *r=0, *v; int i, low, high; PER_USE_OR_RETURN(self, NULL); if (Bucket_rangeSearch(self, args, kw, &low, &high) < 0) goto err; UNLESS (r=PyList_New(high-low+1)) goto err; for (i=low; i <= high; i++) { COPY_VALUE_TO_OBJECT(v, self->values[i]); UNLESS (v) goto err; if (PyList_SetItem(r, i-low, v) < 0) goto err; } PER_UNUSE(self); return r; err: PER_UNUSE(self); Py_XDECREF(r); return NULL; } /* ** bucket_items ** ** Returns a list of all items in a bucket ** ** Arguments: self The Bucket ** args (unused) ** ** Returns: list of all items in the bucket */ static PyObject * bucket_items(Bucket *self, PyObject *args, PyObject *kw) { PyObject *r=0, *o=0, *item=0; int i, low, high; PER_USE_OR_RETURN(self, NULL); if (Bucket_rangeSearch(self, args, kw, &low, &high) < 0) goto err; UNLESS (r=PyList_New(high-low+1)) goto err; for (i=low; i <= high; i++) { UNLESS (item = PyTuple_New(2)) goto err; COPY_KEY_TO_OBJECT(o, self->keys[i]); UNLESS (o) goto err; PyTuple_SET_ITEM(item, 0, o); COPY_VALUE_TO_OBJECT(o, self->values[i]); UNLESS (o) goto err; PyTuple_SET_ITEM(item, 1, o); if (PyList_SetItem(r, i-low, item) < 0) goto err; item = 0; } PER_UNUSE(self); return r; err: PER_UNUSE(self); Py_XDECREF(r); Py_XDECREF(item); return NULL; } static PyObject * bucket_byValue(Bucket *self, PyObject *omin) { PyObject *r=0, *o=0, *item=0; VALUE_TYPE min; VALUE_TYPE v; int i, l, copied=1; PER_USE_OR_RETURN(self, NULL); COPY_VALUE_FROM_ARG(min, omin, copied); UNLESS(copied) return NULL; for (i=0, l=0; i < self->len; i++) if (TEST_VALUE(self->values[i], min) >= 0) l++; UNLESS (r=PyList_New(l)) goto err; for (i=0, l=0; i < self->len; i++) { if (TEST_VALUE(self->values[i], min) < 0) continue; UNLESS (item = PyTuple_New(2)) goto err; COPY_KEY_TO_OBJECT(o, self->keys[i]); UNLESS (o) goto err; PyTuple_SET_ITEM(item, 1, o); COPY_VALUE(v, self->values[i]); NORMALIZE_VALUE(v, min); COPY_VALUE_TO_OBJECT(o, v); DECREF_VALUE(v); UNLESS (o) goto err; PyTuple_SET_ITEM(item, 0, o); if (PyList_SetItem(r, l, item) < 0) goto err; l++; item = 0; } item=PyObject_GetAttr(r,sort_str); UNLESS (item) goto err; ASSIGN(item, PyObject_CallObject(item, NULL)); UNLESS (item) goto err; ASSIGN(item, PyObject_GetAttr(r, reverse_str)); UNLESS (item) goto err; ASSIGN(item, PyObject_CallObject(item, NULL)); UNLESS (item) goto err; Py_DECREF(item); PER_UNUSE(self); return r; err: PER_UNUSE(self); Py_XDECREF(r); Py_XDECREF(item); return NULL; } static int _bucket_clear(Bucket *self) { const int len = self->len; /* Don't declare i at this level. If neither keys nor values are * PyObject*, i won't be referenced, and you'll get a nuisance compiler * wng for declaring it here. */ self->len = self->size = 0; if (self->next) { Py_DECREF(self->next); self->next = NULL; } /* Silence compiler warning about unused variable len for the case when neither key nor value is an object, i.e. II. */ (void)len; if (self->keys) { #ifdef KEY_TYPE_IS_PYOBJECT int i; for (i = 0; i < len; ++i) DECREF_KEY(self->keys[i]); #endif free(self->keys); self->keys = NULL; } if (self->values) { #ifdef VALUE_TYPE_IS_PYOBJECT int i; for (i = 0; i < len; ++i) DECREF_VALUE(self->values[i]); #endif free(self->values); self->values = NULL; } return 0; } #ifdef PERSISTENT static PyObject * bucket__p_deactivate(Bucket *self, PyObject *args, PyObject *keywords) { int ghostify = 1; PyObject *force = NULL; if (args && PyTuple_GET_SIZE(args) > 0) { PyErr_SetString(PyExc_TypeError, "_p_deactivate takes not positional arguments"); return NULL; } if (keywords) { int size = PyDict_Size(keywords); force = PyDict_GetItemString(keywords, "force"); if (force) size--; if (size) { PyErr_SetString(PyExc_TypeError, "_p_deactivate only accepts keyword arg force"); return NULL; } } if (self->jar && self->oid) { ghostify = self->state == cPersistent_UPTODATE_STATE; if (!ghostify && force) { if (PyObject_IsTrue(force)) ghostify = 1; if (PyErr_Occurred()) return NULL; } if (ghostify) { if (_bucket_clear(self) < 0) return NULL; PER_GHOSTIFY(self); } } Py_INCREF(Py_None); return Py_None; } #endif static PyObject * bucket_clear(Bucket *self, PyObject *args) { PER_USE_OR_RETURN(self, NULL); if (self->len) { if (_bucket_clear(self) < 0) return NULL; if (PER_CHANGED(self) < 0) goto err; } PER_UNUSE(self); Py_INCREF(Py_None); return Py_None; err: PER_UNUSE(self); return NULL; } /* * Return: * * For a set bucket (self->values is NULL), a one-tuple or two-tuple. The * first element is a tuple of keys, of length self->len. The second element * is the next bucket, present if and only if next is non-NULL: * * ( * (keys[0], keys[1], ..., keys[len-1]), * next iff non-NULL> * ) * * For a mapping bucket (self->values is not NULL), a one-tuple or two-tuple. * The first element is a tuple interleaving keys and values, of length * 2 * self->len. The second element is the next bucket, present iff next is * non-NULL: * * ( * (keys[0], values[0], keys[1], values[1], ..., * keys[len-1], values[len-1]), * next iff non-NULL> * ) */ static PyObject * bucket_getstate(Bucket *self) { PyObject *o = NULL, *items = NULL, *state; int i, len, l; PER_USE_OR_RETURN(self, NULL); len = self->len; if (self->values) { /* Bucket */ items = PyTuple_New(len * 2); if (items == NULL) goto err; for (i = 0, l = 0; i < len; i++) { COPY_KEY_TO_OBJECT(o, self->keys[i]); if (o == NULL) goto err; PyTuple_SET_ITEM(items, l, o); l++; COPY_VALUE_TO_OBJECT(o, self->values[i]); if (o == NULL) goto err; PyTuple_SET_ITEM(items, l, o); l++; } } else { /* Set */ items = PyTuple_New(len); if (items == NULL) goto err; for (i = 0; i < len; i++) { COPY_KEY_TO_OBJECT(o, self->keys[i]); if (o == NULL) goto err; PyTuple_SET_ITEM(items, i, o); } } if (self->next) state = Py_BuildValue("OO", items, self->next); else state = Py_BuildValue("(O)", items); Py_DECREF(items); PER_UNUSE(self); return state; err: PER_UNUSE(self); Py_XDECREF(items); return NULL; } static int _bucket_setstate(Bucket *self, PyObject *state) { PyObject *k, *v, *items; Bucket *next = NULL; int i, l, len, copied=1; KEY_TYPE *keys; VALUE_TYPE *values; if (!PyArg_ParseTuple(state, "O|O:__setstate__", &items, &next)) return -1; if (!PyTuple_Check(items)) { PyErr_SetString(PyExc_TypeError, "tuple required for first state element"); return -1; } len = PyTuple_Size(items); if (len < 0) return -1; len /= 2; for (i = self->len; --i >= 0; ) { DECREF_KEY(self->keys[i]); DECREF_VALUE(self->values[i]); } self->len = 0; if (self->next) { Py_DECREF(self->next); self->next = NULL; } if (len > self->size) { keys = BTree_Realloc(self->keys, sizeof(KEY_TYPE)*len); if (keys == NULL) return -1; values = BTree_Realloc(self->values, sizeof(VALUE_TYPE)*len); if (values == NULL) return -1; self->keys = keys; self->values = values; self->size = len; } for (i=0, l=0; i < len; i++) { k = PyTuple_GET_ITEM(items, l); l++; v = PyTuple_GET_ITEM(items, l); l++; COPY_KEY_FROM_ARG(self->keys[i], k, copied); if (!copied) return -1; COPY_VALUE_FROM_ARG(self->values[i], v, copied); if (!copied) return -1; INCREF_KEY(self->keys[i]); INCREF_VALUE(self->values[i]); } self->len = len; if (next) { self->next = next; Py_INCREF(next); } return 0; } static PyObject * bucket_setstate(Bucket *self, PyObject *state) { int r; PER_PREVENT_DEACTIVATION(self); r = _bucket_setstate(self, state); PER_UNUSE(self); if (r < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static PyObject * bucket_has_key(Bucket *self, PyObject *key) { return _bucket_get(self, key, 1); } static PyObject * bucket_setdefault(Bucket *self, PyObject *args) { PyObject *key; PyObject *failobj; /* default */ PyObject *value; /* return value */ int dummy_changed; /* in order to call _bucket_set */ if (! PyArg_UnpackTuple(args, "setdefault", 2, 2, &key, &failobj)) return NULL; value = _bucket_get(self, key, 0); if (value != NULL) return value; /* The key isn't in the bucket. If that's not due to a KeyError exception, * pass back the unexpected exception. */ if (! PyErr_ExceptionMatches(PyExc_KeyError)) return NULL; PyErr_Clear(); /* Associate `key` with `failobj` in the bucket, and return `failobj`. */ value = failobj; if (_bucket_set(self, key, failobj, 0, 0, &dummy_changed) < 0) value = NULL; Py_XINCREF(value); return value; } /* forward declaration */ static int Bucket_length(Bucket *self); static PyObject * bucket_pop(Bucket *self, PyObject *args) { PyObject *key; PyObject *failobj = NULL; /* default */ PyObject *value; /* return value */ int dummy_changed; /* in order to call _bucket_set */ if (! PyArg_UnpackTuple(args, "pop", 1, 2, &key, &failobj)) return NULL; value = _bucket_get(self, key, 0); if (value != NULL) { /* Delete key and associated value. */ if (_bucket_set(self, key, NULL, 0, 0, &dummy_changed) < 0) { Py_DECREF(value); return NULL; } return value; } /* The key isn't in the bucket. If that's not due to a KeyError exception, * pass back the unexpected exception. */ if (! PyErr_ExceptionMatches(PyExc_KeyError)) return NULL; if (failobj != NULL) { /* Clear the KeyError and return the explicit default. */ PyErr_Clear(); Py_INCREF(failobj); return failobj; } /* No default given. The only difference in this case is the error * message, which depends on whether the bucket is empty. */ if (Bucket_length(self) == 0) PyErr_SetString(PyExc_KeyError, "pop(): Bucket is empty"); return NULL; } /* Search bucket self for key. This is the sq_contains slot of the * PySequenceMethods. * * Return: * -1 error * 0 not found * 1 found */ static int bucket_contains(Bucket *self, PyObject *key) { PyObject *asobj = _bucket_get(self, key, 1); int result = -1; if (asobj != NULL) { result = PyInt_AsLong(asobj) ? 1 : 0; Py_DECREF(asobj); } return result; } /* ** bucket_getm ** */ static PyObject * bucket_getm(Bucket *self, PyObject *args) { PyObject *key, *d=Py_None, *r; if (!PyArg_ParseTuple(args, "O|O:get", &key, &d)) return NULL; r = _bucket_get(self, key, 0); if (r) return r; if (!PyErr_ExceptionMatches(PyExc_KeyError)) return NULL; PyErr_Clear(); Py_INCREF(d); return d; } /**************************************************************************/ /* Iterator support. */ /* A helper to build all the iterators for Buckets and Sets. * If args is NULL, the iterator spans the entire structure. Else it's an * argument tuple, with optional low and high arguments. * kind is 'k', 'v' or 'i'. * Returns a BTreeIter object, or NULL if error. */ static PyObject * buildBucketIter(Bucket *self, PyObject *args, PyObject *kw, char kind) { BTreeItems *items; int lowoffset, highoffset; BTreeIter *result = NULL; PER_USE_OR_RETURN(self, NULL); if (Bucket_rangeSearch(self, args, kw, &lowoffset, &highoffset) < 0) goto Done; items = (BTreeItems *)newBTreeItems(kind, self, lowoffset, self, highoffset); if (items == NULL) goto Done; result = BTreeIter_new(items); /* win or lose, we're done */ Py_DECREF(items); Done: PER_UNUSE(self); return (PyObject *)result; } /* The implementation of iter(Bucket_or_Set); the Bucket tp_iter slot. */ static PyObject * Bucket_getiter(Bucket *self) { return buildBucketIter(self, NULL, NULL, 'k'); } /* The implementation of Bucket.iterkeys(). */ static PyObject * Bucket_iterkeys(Bucket *self, PyObject *args, PyObject *kw) { return buildBucketIter(self, args, kw, 'k'); } /* The implementation of Bucket.itervalues(). */ static PyObject * Bucket_itervalues(Bucket *self, PyObject *args, PyObject *kw) { return buildBucketIter(self, args, kw, 'v'); } /* The implementation of Bucket.iteritems(). */ static PyObject * Bucket_iteritems(Bucket *self, PyObject *args, PyObject *kw) { return buildBucketIter(self, args, kw, 'i'); } /* End of iterator support. */ #ifdef PERSISTENT static PyObject *merge_error(int p1, int p2, int p3, int reason); static PyObject *bucket_merge(Bucket *s1, Bucket *s2, Bucket *s3); static PyObject * _bucket__p_resolveConflict(PyObject *ob_type, PyObject *s[3]) { PyObject *result = NULL; /* guilty until proved innocent */ Bucket *b[3] = {NULL, NULL, NULL}; PyObject *meth = NULL; PyObject *a = NULL; int i; for (i = 0; i < 3; i++) { PyObject *r; b[i] = (Bucket*)PyObject_CallObject((PyObject *)ob_type, NULL); if (b[i] == NULL) goto Done; if (s[i] == Py_None) /* None is equivalent to empty, for BTrees */ continue; meth = PyObject_GetAttr((PyObject *)b[i], __setstate___str); if (meth == NULL) goto Done; a = PyTuple_New(1); if (a == NULL) goto Done; PyTuple_SET_ITEM(a, 0, s[i]); Py_INCREF(s[i]); r = PyObject_CallObject(meth, a); /* b[i].__setstate__(s[i]) */ if (r == NULL) goto Done; Py_DECREF(r); Py_DECREF(a); Py_DECREF(meth); a = meth = NULL; } if (b[0]->next != b[1]->next || b[0]->next != b[2]->next) merge_error(-1, -1, -1, 0); else result = bucket_merge(b[0], b[1], b[2]); Done: Py_XDECREF(meth); Py_XDECREF(a); Py_XDECREF(b[0]); Py_XDECREF(b[1]); Py_XDECREF(b[2]); return result; } static PyObject * bucket__p_resolveConflict(Bucket *self, PyObject *args) { PyObject *s[3]; if (!PyArg_ParseTuple(args, "OOO", &s[0], &s[1], &s[2])) return NULL; return _bucket__p_resolveConflict((PyObject *)self->ob_type, s); } #endif /* Caution: Even though the _next attribute is read-only, a program could do arbitrary damage to the btree internals. For example, it could call clear() on a bucket inside a BTree. We need to decide if the convenience for inspecting BTrees is worth the risk. */ static struct PyMemberDef Bucket_members[] = { {"_next", T_OBJECT, offsetof(Bucket, next)}, {NULL} }; static struct PyMethodDef Bucket_methods[] = { {"__getstate__", (PyCFunction) bucket_getstate, METH_NOARGS, "__getstate__() -- Return the picklable state of the object"}, {"__setstate__", (PyCFunction) bucket_setstate, METH_O, "__setstate__() -- Set the state of the object"}, {"keys", (PyCFunction) bucket_keys, METH_KEYWORDS, "keys([min, max]) -- Return the keys"}, {"has_key", (PyCFunction) bucket_has_key, METH_O, "has_key(key) -- Test whether the bucket contains the given key"}, {"clear", (PyCFunction) bucket_clear, METH_VARARGS, "clear() -- Remove all of the items from the bucket"}, {"update", (PyCFunction) Mapping_update, METH_O, "update(collection) -- Add the items from the given collection"}, {"maxKey", (PyCFunction) Bucket_maxKey, METH_VARARGS, "maxKey([key]) -- Find the maximum key\n\n" "If an argument is given, find the maximum <= the argument"}, {"minKey", (PyCFunction) Bucket_minKey, METH_VARARGS, "minKey([key]) -- Find the minimum key\n\n" "If an argument is given, find the minimum >= the argument"}, {"values", (PyCFunction) bucket_values, METH_KEYWORDS, "values([min, max]) -- Return the values"}, {"items", (PyCFunction) bucket_items, METH_KEYWORDS, "items([min, max])) -- Return the items"}, {"byValue", (PyCFunction) bucket_byValue, METH_O, "byValue(min) -- " "Return value-keys with values >= min and reverse sorted by values"}, {"get", (PyCFunction) bucket_getm, METH_VARARGS, "get(key[,default]) -- Look up a value\n\n" "Return the default (or None) if the key is not found."}, {"setdefault", (PyCFunction) bucket_setdefault, METH_VARARGS, "D.setdefault(k, d) -> D.get(k, d), also set D[k]=d if k not in D.\n\n" "Return the value like get() except that if key is missing, d is both\n" "returned and inserted into the bucket as the value of k."}, {"pop", (PyCFunction) bucket_pop, METH_VARARGS, "D.pop(k[, d]) -> v, remove key and return the corresponding value.\n\n" "If key is not found, d is returned if given, otherwise KeyError\n" "is raised."}, {"iterkeys", (PyCFunction) Bucket_iterkeys, METH_KEYWORDS, "B.iterkeys([min[,max]]) -> an iterator over the keys of B"}, {"itervalues", (PyCFunction) Bucket_itervalues, METH_KEYWORDS, "B.itervalues([min[,max]]) -> an iterator over the values of B"}, {"iteritems", (PyCFunction) Bucket_iteritems, METH_KEYWORDS, "B.iteritems([min[,max]]) -> an iterator over the (key, value) items of B"}, #ifdef EXTRA_BUCKET_METHODS EXTRA_BUCKET_METHODS #endif #ifdef PERSISTENT {"_p_resolveConflict", (PyCFunction) bucket__p_resolveConflict, METH_VARARGS, "_p_resolveConflict() -- Reinitialize from a newly created copy"}, {"_p_deactivate", (PyCFunction) bucket__p_deactivate, METH_KEYWORDS, "_p_deactivate() -- Reinitialize from a newly created copy"}, #endif {NULL, NULL} }; static int Bucket_init(PyObject *self, PyObject *args, PyObject *kwds) { PyObject *v = NULL; if (!PyArg_ParseTuple(args, "|O:" MOD_NAME_PREFIX "Bucket", &v)) return -1; if (v) return update_from_seq(self, v); else return 0; } static void bucket_dealloc(Bucket *self) { if (self->state != cPersistent_GHOST_STATE) _bucket_clear(self); cPersistenceCAPI->pertype->tp_dealloc((PyObject *)self); } static int bucket_traverse(Bucket *self, visitproc visit, void *arg) { int err = 0; int i, len; #define VISIT(SLOT) \ if (SLOT) { \ err = visit((PyObject *)(SLOT), arg); \ if (err) \ goto Done; \ } /* Call our base type's traverse function. Because buckets are * subclasses of Peristent, there must be one. */ err = cPersistenceCAPI->pertype->tp_traverse((PyObject *)self, visit, arg); if (err) goto Done; /* If this is registered with the persistence system, cleaning up cycles * is the database's problem. It would be horrid to unghostify buckets * here just to chase pointers every time gc runs. */ if (self->state == cPersistent_GHOST_STATE) goto Done; len = self->len; (void)i; /* if neither keys nor values are PyObject*, "i" is otherwise unreferenced and we get a nuisance compiler wng */ #ifdef KEY_TYPE_IS_PYOBJECT /* Keys are Python objects so need to be traversed. */ for (i = 0; i < len; i++) VISIT(self->keys[i]); #endif #ifdef VALUE_TYPE_IS_PYOBJECT if (self->values != NULL) { /* self->values exists (this is a mapping bucket, not a set bucket), * and are Python objects, so need to be traversed. */ for (i = 0; i < len; i++) VISIT(self->values[i]); } #endif VISIT(self->next); Done: return err; #undef VISIT } static int bucket_tp_clear(Bucket *self) { if (self->state != cPersistent_GHOST_STATE) _bucket_clear(self); return 0; } /* Code to access Bucket objects as mappings */ static int Bucket_length( Bucket *self) { int r; UNLESS (PER_USE(self)) return -1; r = self->len; PER_UNUSE(self); return r; } static PyMappingMethods Bucket_as_mapping = { (lenfunc)Bucket_length, /*mp_length*/ (binaryfunc)bucket_getitem, /*mp_subscript*/ (objobjargproc)bucket_setitem, /*mp_ass_subscript*/ }; static PySequenceMethods Bucket_as_sequence = { (lenfunc)0, /* sq_length */ (binaryfunc)0, /* sq_concat */ (ssizeargfunc)0, /* sq_repeat */ (ssizeargfunc)0, /* sq_item */ (ssizessizeargfunc)0, /* sq_slice */ (ssizeobjargproc)0, /* sq_ass_item */ (ssizessizeobjargproc)0, /* sq_ass_slice */ (objobjproc)bucket_contains, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }; static PyObject * bucket_repr(Bucket *self) { PyObject *i, *r; char repr[10000]; int rv; i = bucket_items(self, NULL, NULL); if (!i) return NULL; r = PyObject_Repr(i); Py_DECREF(i); if (!r) { return NULL; } rv = PyOS_snprintf(repr, sizeof(repr), "%s(%s)", self->ob_type->tp_name, PyString_AS_STRING(r)); if (rv > 0 && rv < sizeof(repr)) { Py_DECREF(r); return PyString_FromStringAndSize(repr, strlen(repr)); } else { /* The static buffer wasn't big enough */ int size; PyObject *s; /* 3 for the parens and the null byte */ size = strlen(self->ob_type->tp_name) + PyString_GET_SIZE(r) + 3; s = PyString_FromStringAndSize(NULL, size); if (!s) { Py_DECREF(r); return r; } PyOS_snprintf(PyString_AS_STRING(s), size, "%s(%s)", self->ob_type->tp_name, PyString_AS_STRING(r)); Py_DECREF(r); return s; } } static PyTypeObject BucketType = { PyObject_HEAD_INIT(NULL) /* PyPersist_Type */ 0, /* ob_size */ MODULE_NAME MOD_NAME_PREFIX "Bucket",/* tp_name */ sizeof(Bucket), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)bucket_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)bucket_repr, /* tp_repr */ 0, /* tp_as_number */ &Bucket_as_sequence, /* tp_as_sequence */ &Bucket_as_mapping, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ (traverseproc)bucket_traverse, /* tp_traverse */ (inquiry)bucket_tp_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)Bucket_getiter, /* tp_iter */ 0, /* tp_iternext */ Bucket_methods, /* tp_methods */ Bucket_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ Bucket_init, /* tp_init */ 0, /* tp_alloc */ 0, /*PyType_GenericNew,*/ /* tp_new */ }; static int nextBucket(SetIteration *i) { if (i->position >= 0) { UNLESS(PER_USE(BUCKET(i->set))) return -1; if (i->position) { DECREF_KEY(i->key); DECREF_VALUE(i->value); } if (i->position < BUCKET(i->set)->len) { COPY_KEY(i->key, BUCKET(i->set)->keys[i->position]); INCREF_KEY(i->key); COPY_VALUE(i->value, BUCKET(i->set)->values[i->position]); INCREF_VALUE(i->value); i->position ++; } else { i->position = -1; PER_ACCESSED(BUCKET(i->set)); } PER_ALLOW_DEACTIVATION(BUCKET(i->set)); } return 0; } zope2.13-2.13.21/source/ZODB3/src/BTrees/_OIBTree.c0000644000175000017500000000212312214017464017766 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ #define MASTER_ID "$Id: _OIBTree.c 113734 2010-06-21 15:33:46Z ctheune $\n" /* OIBTree - object key, int value BTree Implements a collection using object type keys and int type values */ #define PERSISTENT #define MOD_NAME_PREFIX "OI" #define INITMODULE init_OIBTree #define DEFAULT_MAX_BUCKET_SIZE 60 #define DEFAULT_MAX_BTREE_SIZE 250 #include "objectkeymacros.h" #include "intvaluemacros.h" #include "BTreeModuleTemplate.c" zope2.13-2.13.21/source/ZODB3/src/BTrees/MergeTemplate.c0000644000175000017500000002755312214017464021147 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #define MERGETEMPLATE_C "$Id: MergeTemplate.c 113734 2010-06-21 15:33:46Z ctheune $\n" /**************************************************************************** Set operations ****************************************************************************/ static int merge_output(Bucket *r, SetIteration *i, int mapping) { if (r->len >= r->size && Bucket_grow(r, -1, !mapping) < 0) return -1; COPY_KEY(r->keys[r->len], i->key); INCREF_KEY(r->keys[r->len]); if (mapping) { COPY_VALUE(r->values[r->len], i->value); INCREF_VALUE(r->values[r->len]); } r->len++; return 0; } /* The "reason" argument is a little integer giving "a reason" for the * error. In the Zope3 codebase, these are mapped to explanatory strings * via zodb/btrees/interfaces.py. */ static PyObject * merge_error(int p1, int p2, int p3, int reason) { PyObject *r; UNLESS (r=Py_BuildValue("iiii", p1, p2, p3, reason)) r=Py_None; if (ConflictError == NULL) { ConflictError = PyExc_ValueError; Py_INCREF(ConflictError); } PyErr_SetObject(ConflictError, r); if (r != Py_None) { Py_DECREF(r); } return NULL; } /* It's hard to explain "the rules" for bucket_merge, in large part because * any automatic conflict-resolution scheme is going to be incorrect for * some endcases of *some* app. The scheme here is pretty conservative, * and should be OK for most apps. It's easier to explain what the code * allows than what it forbids: * * Leaving things alone: it's OK if both s2 and s3 leave a piece of s1 * alone (don't delete the key, and don't change the value). * * Key deletion: a transaction (s2 or s3) can delete a key (from s1), but * only if the other transaction (of s2 and s3) doesn't delete the same key. * However, it's not OK for s2 and s3 to, between them, end up deleting all * the keys. This is a higher-level constraint, due to that the caller of * bucket_merge() doesn't have enough info to unlink the resulting empty * bucket from its BTree correctly. It's also not OK if s2 or s3 are empty, * because the transaction that emptied the bucket unlinked the bucket from * the tree, and nothing we do here can get it linked back in again. * * Key insertion: s2 or s3 can add a new key, provided the other transaction * doesn't insert the same key. It's not OK even if they insert the same * pair. * * Mapping value modification: s2 or s3 can modify the value associated * with a key in s1, provided the other transaction doesn't make a * modification of the same key to a different value. It's OK if s2 and s3 * both give the same new value to the key while it's hard to be precise about * why, this doesn't seem consistent with that it's *not* OK for both to add * a new key mapping to the same value). */ static PyObject * bucket_merge(Bucket *s1, Bucket *s2, Bucket *s3) { Bucket *r=0; PyObject *s; SetIteration i1 = {0,0,0}, i2 = {0,0,0}, i3 = {0,0,0}; int cmp12, cmp13, cmp23, mapping, set; /* If either "after" bucket is empty, punt. */ if (s2->len == 0 || s3->len == 0) { merge_error(-1, -1, -1, 12); goto err; } if (initSetIteration(&i1, OBJECT(s1), 1) < 0) goto err; if (initSetIteration(&i2, OBJECT(s2), 1) < 0) goto err; if (initSetIteration(&i3, OBJECT(s3), 1) < 0) goto err; mapping = i1.usesValue | i2.usesValue | i3.usesValue; set = !mapping; if (mapping) r = (Bucket *)PyObject_CallObject((PyObject *)&BucketType, NULL); else r = (Bucket *)PyObject_CallObject((PyObject *)&SetType, NULL); if (r == NULL) goto err; if (i1.next(&i1) < 0) goto err; if (i2.next(&i2) < 0) goto err; if (i3.next(&i3) < 0) goto err; /* Consult zodb/btrees/interfaces.py for the meaning of the last * argument passed to merge_error(). */ /* TODO: This isn't passing on errors raised by value comparisons. */ while (i1.position >= 0 && i2.position >= 0 && i3.position >= 0) { TEST_KEY_SET_OR(cmp12, i1.key, i2.key) goto err; TEST_KEY_SET_OR(cmp13, i1.key, i3.key) goto err; if (cmp12==0) { if (cmp13==0) { if (set || (TEST_VALUE(i1.value, i2.value) == 0)) { /* change in i3 value or all same */ if (merge_output(r, &i3, mapping) < 0) goto err; } else if (set || (TEST_VALUE(i1.value, i3.value) == 0)) { /* change in i2 value */ if (merge_output(r, &i2, mapping) < 0) goto err; } else { /* conflicting value changes in i2 and i3 */ merge_error(i1.position, i2.position, i3.position, 1); goto err; } if (i1.next(&i1) < 0) goto err; if (i2.next(&i2) < 0) goto err; if (i3.next(&i3) < 0) goto err; } else if (cmp13 > 0) { /* insert i3 */ if (merge_output(r, &i3, mapping) < 0) goto err; if (i3.next(&i3) < 0) goto err; } else if (set || (TEST_VALUE(i1.value, i2.value) == 0)) { /* deleted in i3 */ if (i3.position == 1) { /* Deleted the first item. This will modify the parent node, so we don't know if merging will be safe */ merge_error(i1.position, i2.position, i3.position, 13); goto err; } if (i1.next(&i1) < 0) goto err; if (i2.next(&i2) < 0) goto err; } else { /* conflicting del in i3 and change in i2 */ merge_error(i1.position, i2.position, i3.position, 2); goto err; } } else if (cmp13 == 0) { if (cmp12 > 0) { /* insert i2 */ if (merge_output(r, &i2, mapping) < 0) goto err; if (i2.next(&i2) < 0) goto err; } else if (set || (TEST_VALUE(i1.value, i3.value) == 0)) { /* deleted in i2 */ if (i2.position == 1) { /* Deleted the first item. This will modify the parent node, so we don't know if merging will be safe */ merge_error(i1.position, i2.position, i3.position, 13); goto err; } if (i1.next(&i1) < 0) goto err; if (i3.next(&i3) < 0) goto err; } else { /* conflicting del in i2 and change in i3 */ merge_error(i1.position, i2.position, i3.position, 3); goto err; } } else { /* Both keys changed */ TEST_KEY_SET_OR(cmp23, i2.key, i3.key) goto err; if (cmp23==0) { /* dueling inserts or deletes */ merge_error(i1.position, i2.position, i3.position, 4); goto err; } if (cmp12 > 0) { /* insert i2 */ if (cmp23 > 0) { /* insert i3 first */ if (merge_output(r, &i3, mapping) < 0) goto err; if (i3.next(&i3) < 0) goto err; } else { /* insert i2 first */ if (merge_output(r, &i2, mapping) < 0) goto err; if (i2.next(&i2) < 0) goto err; } } else if (cmp13 > 0) { /* Insert i3 */ if (merge_output(r, &i3, mapping) < 0) goto err; if (i3.next(&i3) < 0) goto err; } else { /* 1<2 and 1<3: both deleted 1.key */ merge_error(i1.position, i2.position, i3.position, 5); goto err; } } } while (i2.position >= 0 && i3.position >= 0) { /* New inserts */ TEST_KEY_SET_OR(cmp23, i2.key, i3.key) goto err; if (cmp23==0) { /* dueling inserts */ merge_error(i1.position, i2.position, i3.position, 6); goto err; } if (cmp23 > 0) { /* insert i3 */ if (merge_output(r, &i3, mapping) < 0) goto err; if (i3.next(&i3) < 0) goto err; } else { /* insert i2 */ if (merge_output(r, &i2, mapping) < 0) goto err; if (i2.next(&i2) < 0) goto err; } } while (i1.position >= 0 && i2.position >= 0) { /* remainder of i1 deleted in i3 */ TEST_KEY_SET_OR(cmp12, i1.key, i2.key) goto err; if (cmp12 > 0) { /* insert i2 */ if (merge_output(r, &i2, mapping) < 0) goto err; if (i2.next(&i2) < 0) goto err; } else if (cmp12==0 && (set || (TEST_VALUE(i1.value, i2.value) == 0))) { /* delete i3 */ if (i1.next(&i1) < 0) goto err; if (i2.next(&i2) < 0) goto err; } else { /* Dueling deletes or delete and change */ merge_error(i1.position, i2.position, i3.position, 7); goto err; } } while (i1.position >= 0 && i3.position >= 0) { /* remainder of i1 deleted in i2 */ TEST_KEY_SET_OR(cmp13, i1.key, i3.key) goto err; if (cmp13 > 0) { /* insert i3 */ if (merge_output(r, &i3, mapping) < 0) goto err; if (i3.next(&i3) < 0) goto err; } else if (cmp13==0 && (set || (TEST_VALUE(i1.value, i3.value) == 0))) { /* delete i2 */ if (i1.next(&i1) < 0) goto err; if (i3.next(&i3) < 0) goto err; } else { /* Dueling deletes or delete and change */ merge_error(i1.position, i2.position, i3.position, 8); goto err; } } if (i1.position >= 0) { /* Dueling deletes */ merge_error(i1.position, i2.position, i3.position, 9); goto err; } while (i2.position >= 0) { /* Inserting i2 at end */ if (merge_output(r, &i2, mapping) < 0) goto err; if (i2.next(&i2) < 0) goto err; } while (i3.position >= 0) { /* Inserting i3 at end */ if (merge_output(r, &i3, mapping) < 0) goto err; if (i3.next(&i3) < 0) goto err; } /* If the output bucket is empty, conflict resolution doesn't have * enough info to unlink it from its containing BTree correctly. */ if (r->len == 0) { merge_error(-1, -1, -1, 10); goto err; } finiSetIteration(&i1); finiSetIteration(&i2); finiSetIteration(&i3); if (s1->next) { Py_INCREF(s1->next); r->next = s1->next; } s = bucket_getstate(r); Py_DECREF(r); return s; err: finiSetIteration(&i1); finiSetIteration(&i2); finiSetIteration(&i3); Py_XDECREF(r); return NULL; } zope2.13-2.13.21/source/ZODB3/src/BTrees/TreeSetTemplate.c0000644000175000017500000001535312214017464021456 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #define TREESETTEMPLATE_C "$Id: TreeSetTemplate.c 113734 2010-06-21 15:33:46Z ctheune $\n" static PyObject * TreeSet_insert(BTree *self, PyObject *args) { PyObject *key; int i; if (!PyArg_ParseTuple(args, "O:insert", &key)) return NULL; i = _BTree_set(self, key, Py_None, 1, 1); if (i < 0) return NULL; return PyInt_FromLong(i); } /* _Set_update and _TreeSet_update are identical except for the function they call to add the element to the set. */ static int _TreeSet_update(BTree *self, PyObject *seq) { int n=0, ind=0; PyObject *iter, *v; iter = PyObject_GetIter(seq); if (iter == NULL) return -1; while (1) { v = PyIter_Next(iter); if (v == NULL) { if (PyErr_Occurred()) goto err; else break; } ind = _BTree_set(self, v, Py_None, 1, 1); Py_DECREF(v); if (ind < 0) goto err; else n += ind; } err: Py_DECREF(iter); if (ind < 0) return -1; return n; } static PyObject * TreeSet_update(BTree *self, PyObject *args) { PyObject *seq = NULL; int n = 0; if (!PyArg_ParseTuple(args, "|O:update", &seq)) return NULL; if (seq) { n = _TreeSet_update(self, seq); if (n < 0) return NULL; } return PyInt_FromLong(n); } static PyObject * TreeSet_remove(BTree *self, PyObject *args) { PyObject *key; UNLESS (PyArg_ParseTuple(args, "O", &key)) return NULL; if (_BTree_set(self, key, NULL, 0, 1) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static PyObject * TreeSet_setstate(BTree *self, PyObject *args) { int r; if (!PyArg_ParseTuple(args,"O",&args)) return NULL; PER_PREVENT_DEACTIVATION(self); r=_BTree_setstate(self, args, 1); PER_UNUSE(self); if (r < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static struct PyMethodDef TreeSet_methods[] = { {"__getstate__", (PyCFunction) BTree_getstate, METH_NOARGS, "__getstate__() -> state\n\n" "Return the picklable state of the TreeSet."}, {"__setstate__", (PyCFunction) TreeSet_setstate, METH_VARARGS, "__setstate__(state)\n\n" "Set the state of the TreeSet."}, {"has_key", (PyCFunction) BTree_has_key, METH_O, "has_key(key)\n\n" "Return true if the TreeSet contains the given key."}, {"keys", (PyCFunction) BTree_keys, METH_KEYWORDS, "keys([min, max]) -> list of keys\n\n" "Returns the keys of the TreeSet. If min and max are supplied, only\n" "keys greater than min and less than max are returned."}, {"maxKey", (PyCFunction) BTree_maxKey, METH_VARARGS, "maxKey([max]) -> key\n\n" "Return the largest key in the BTree. If max is specified, return\n" "the largest key <= max."}, {"minKey", (PyCFunction) BTree_minKey, METH_VARARGS, "minKey([mi]) -> key\n\n" "Return the smallest key in the BTree. If min is specified, return\n" "the smallest key >= min."}, {"clear", (PyCFunction) BTree_clear, METH_NOARGS, "clear()\n\nRemove all of the items from the BTree."}, {"add", (PyCFunction)TreeSet_insert, METH_VARARGS, "add(id) -- Add an item to the set"}, {"insert", (PyCFunction)TreeSet_insert, METH_VARARGS, "insert(id) -- Add an item to the set"}, {"update", (PyCFunction)TreeSet_update, METH_VARARGS, "update(collection)\n\n Add the items from the given collection."}, {"remove", (PyCFunction)TreeSet_remove, METH_VARARGS, "remove(id) -- Remove a key from the set"}, {"_check", (PyCFunction) BTree_check, METH_NOARGS, "Perform sanity check on TreeSet, and raise exception if flawed."}, #ifdef PERSISTENT {"_p_resolveConflict", (PyCFunction) BTree__p_resolveConflict, METH_VARARGS, "_p_resolveConflict() -- Reinitialize from a newly created copy"}, {"_p_deactivate", (PyCFunction) BTree__p_deactivate, METH_KEYWORDS, "_p_deactivate()\n\nReinitialize from a newly created copy."}, #endif {NULL, NULL} /* sentinel */ }; static PyMappingMethods TreeSet_as_mapping = { (lenfunc)BTree_length, /*mp_length*/ }; static PySequenceMethods TreeSet_as_sequence = { (lenfunc)0, /* sq_length */ (binaryfunc)0, /* sq_concat */ (ssizeargfunc)0, /* sq_repeat */ (ssizeargfunc)0, /* sq_item */ (ssizessizeargfunc)0, /* sq_slice */ (ssizeobjargproc)0, /* sq_ass_item */ (ssizessizeobjargproc)0, /* sq_ass_slice */ (objobjproc)BTree_contains, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }; static int TreeSet_init(PyObject *self, PyObject *args, PyObject *kwds) { PyObject *v = NULL; if (!PyArg_ParseTuple(args, "|O:" MOD_NAME_PREFIX "TreeSet", &v)) return -1; if (v) return _TreeSet_update((BTree *)self, v); else return 0; } static PyTypeObject TreeSetType = { PyObject_HEAD_INIT(NULL) /* PyPersist_Type */ 0, /* ob_size */ MODULE_NAME MOD_NAME_PREFIX "TreeSet",/* tp_name */ sizeof(BTree), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)BTree_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ &BTree_as_number_for_nonzero, /* tp_as_number */ &TreeSet_as_sequence, /* tp_as_sequence */ &TreeSet_as_mapping, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ (traverseproc)BTree_traverse, /* tp_traverse */ (inquiry)BTree_tp_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)BTree_getiter, /* tp_iter */ 0, /* tp_iternext */ TreeSet_methods, /* tp_methods */ BTree_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ TreeSet_init, /* tp_init */ 0, /* tp_alloc */ 0, /*PyType_GenericNew,*/ /* tp_new */ }; zope2.13-2.13.21/source/ZODB3/src/BTrees/_IFBTree.c0000644000175000017500000000211612214017464017757 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ #define MASTER_ID "$Id: _IFBTree.c 113734 2010-06-21 15:33:46Z ctheune $\n" /* IFBTree - int key, float value BTree Implements a collection using int type keys and float type values */ /* Setup template macros */ #define PERSISTENT #define MOD_NAME_PREFIX "IF" #define INITMODULE init_IFBTree #define DEFAULT_MAX_BUCKET_SIZE 120 #define DEFAULT_MAX_BTREE_SIZE 500 #include "intkeymacros.h" #include "floatvaluemacros.h" #include "BTreeModuleTemplate.c" zope2.13-2.13.21/source/ZODB3/src/BTrees/sorters.c0000644000175000017500000003563712214017464020117 0ustar arnauarnau/***************************************************************************** Copyright (c) 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /* Revision information: $Id: sorters.c 113734 2010-06-21 15:33:46Z ctheune $ */ /* The only routine here intended to be used outside the file is size_t sort_int_nodups(int *p, size_t n) Sort the array of n ints pointed at by p, in place, and also remove duplicates. Return the number of unique elements remaining, which occupy a contiguous and monotonically increasing slice of the array starting at p. Example: If the input array is [3, 1, 2, 3, 1, 5, 2], sort_int_nodups returns 4, and the first 4 elements of the array are changed to [1, 2, 3, 5]. The content of the remaining array positions is not defined. Notes: + This is specific to n-byte signed ints, with endianness natural to the platform. `n` is determined based on ZODB_64BIT_INTS. + 4*n bytes of available heap memory are required for best speed (8*n when ZODB_64BIT_INTS is defined). */ #include #include #include #include #include /* The type of array elements to be sorted. Most of the routines don't care about the type, and will work fine for any scalar C type (provided they're recompiled with element_type appropriately redefined). However, the radix sort has to know everything about the type's internal representation. */ typedef KEY_TYPE element_type; /* The radixsort is faster than the quicksort for large arrays, but radixsort has high fixed overhead, making it a poor choice for small arrays. The crossover point isn't critical, and is sensitive to things like compiler and machine cache structure, so don't worry much about this. */ #define QUICKSORT_BEATS_RADIXSORT 800U /* In turn, the quicksort backs off to an insertion sort for very small slices. MAX_INSERTION is the largest slice quicksort leaves entirely to insertion. Because this version of quicksort uses a median-of-3 rule for selecting a pivot, MAX_INSERTION must be at least 2 (so that quicksort has at least 3 values to look at in a slice). Again, the exact value here isn't critical. */ #define MAX_INSERTION 25U #if MAX_INSERTION < 2U # error "MAX_INSERTION must be >= 2" #endif /* LSB-first radix sort of the n elements in 'in'. 'work' is work storage at least as large as 'in'. Depending on how many swaps are done internally, the final result may come back in 'in' or 'work'; and that pointer is returned. radixsort_int is specific to signed n-byte ints, with natural machine endianness. `n` is determined based on ZODB_64BIT_INTS. */ static element_type* radixsort_int(element_type *in, element_type *work, size_t n) { /* count[i][j] is the number of input elements that have byte value j in byte position i, where byte position 0 is the LSB. Note that holding i fixed, the sum of count[i][j] over all j in range(256) is n. */ #ifdef ZODB_64BIT_INTS size_t count[8][256]; #else size_t count[4][256]; #endif size_t i; int offset, offsetinc; /* Which byte position are we working on now? 0=LSB, 1, 2, ... */ int bytenum; #ifdef ZODB_64BIT_INTS assert(sizeof(element_type) == 8); #else assert(sizeof(element_type) == 4); #endif assert(in); assert(work); /* Compute all of count in one pass. */ memset(count, 0, sizeof(count)); for (i = 0; i < n; ++i) { element_type const x = in[i]; ++count[0][(x ) & 0xff]; ++count[1][(x >> 8) & 0xff]; ++count[2][(x >> 16) & 0xff]; ++count[3][(x >> 24) & 0xff]; #ifdef ZODB_64BIT_INTS ++count[4][(x >> 32) & 0xff]; ++count[5][(x >> 40) & 0xff]; ++count[6][(x >> 48) & 0xff]; ++count[7][(x >> 56) & 0xff]; #endif } /* For p an element_type* cast to char*, offset is how much farther we have to go to get to the LSB of the element; this is 0 for little- endian boxes and sizeof(element_type)-1 for big-endian. offsetinc is 1 or -1, respectively, telling us which direction to go from p+offset to get to the element's more-significant bytes. */ { element_type one = 1; if (*(char*)&one) { /* Little endian. */ offset = 0; offsetinc = 1; } else { /* Big endian. */ offset = sizeof(element_type) - 1; offsetinc = -1; } } /* The radix sort. */ for (bytenum = 0; bytenum < sizeof(element_type); ++bytenum, offset += offsetinc) { /* Do a stable distribution sort on byte position bytenum, from in to work. index[i] tells us the work index at which to store the next in element with byte value i. pinbyte points to the correct byte in the input array. */ size_t index[256]; unsigned char* pinbyte; size_t total = 0; size_t *pcount = count[bytenum]; /* Compute the correct output starting index for each possible byte value. */ if (bytenum < sizeof(element_type) - 1) { for (i = 0; i < 256; ++i) { const size_t icount = pcount[i]; index[i] = total; total += icount; if (icount == n) break; } if (i < 256) { /* All bytes in the current position have value i, so there's nothing to do on this pass. */ continue; } } else { /* The MSB of signed ints needs to be distributed differently than the other bytes, in order 0x80, 0x81, ... 0xff, 0x00, 0x01, ... 0x7f */ for (i = 128; i < 256; ++i) { const size_t icount = pcount[i]; index[i] = total; total += icount; if (icount == n) break; } if (i < 256) continue; for (i = 0; i < 128; ++i) { const size_t icount = pcount[i]; index[i] = total; total += icount; if (icount == n) break; } if (i < 128) continue; } assert(total == n); /* Distribute the elements according to byte value. Note that this is where most of the time is spent. Note: The loop is unrolled 4x by hand, for speed. This may be a pessimization someday, but was a significant win on my MSVC 6.0 timing tests. */ pinbyte = (unsigned char *)in + offset; i = 0; /* Reduce number of elements to copy to a multiple of 4. */ while ((n - i) & 0x3) { unsigned char byte = *pinbyte; work[index[byte]++] = in[i]; ++i; pinbyte += sizeof(element_type); } for (; i < n; i += 4, pinbyte += 4 * sizeof(element_type)) { unsigned char byte1 = *(pinbyte ); unsigned char byte2 = *(pinbyte + sizeof(element_type)); unsigned char byte3 = *(pinbyte + 2 * sizeof(element_type)); unsigned char byte4 = *(pinbyte + 3 * sizeof(element_type)); element_type in1 = in[i ]; element_type in2 = in[i+1]; element_type in3 = in[i+2]; element_type in4 = in[i+3]; work[index[byte1]++] = in1; work[index[byte2]++] = in2; work[index[byte3]++] = in3; work[index[byte4]++] = in4; } /* Swap in and work (just a pointer swap). */ { element_type *temp = in; in = work; work = temp; } } return in; } /* Remove duplicates from sorted array in, storing exactly one of each distinct element value into sorted array out. It's OK (and expected!) for in == out, but otherwise the n elements beginning at in must not overlap with the n beginning at out. Return the number of elements in out. */ static size_t uniq(element_type *out, element_type *in, size_t n) { size_t i; element_type lastelt; element_type *pout; assert(out); assert(in); if (n == 0) return 0; /* i <- first index in 'in' that contains a duplicate. in[0], in[1], ... in[i-1] are unique, but in[i-1] == in[i]. Set i to n if everything is unique. */ for (i = 1; i < n; ++i) { if (in[i-1] == in[i]) break; } /* in[:i] is unique; copy to out[:i] if needed. */ assert(i > 0); if (in != out) memcpy(out, in, i * sizeof(element_type)); pout = out + i; lastelt = in[i-1]; /* safe even when i == n */ for (++i; i < n; ++i) { element_type elt = in[i]; if (elt != lastelt) *pout++ = lastelt = elt; } return pout - out; } #if 0 /* insertionsort is no longer referenced directly, but I'd like to keep * the code here just in case. */ /* Straight insertion sort of the n elements starting at 'in'. */ static void insertionsort(element_type *in, size_t n) { element_type *p, *q; element_type minimum; /* smallest seen so far */ element_type *plimit = in + n; assert(in); if (n < 2) return; minimum = *in; for (p = in+1; p < plimit; ++p) { /* *in <= *(in+1) <= ... <= *(p-1). Slide *p into place. */ element_type thiselt = *p; if (thiselt < minimum) { /* This is a new minimum. This saves p-in compares when it happens, but should happen so rarely that it's not worth checking for its own sake: the point is that the far more popular 'else' branch can exploit that thiselt is *not* the smallest so far. */ memmove(in+1, in, (p - in) * sizeof(*in)); *in = minimum = thiselt; } else { /* thiselt >= minimum, so the loop will find a q with *q <= thiselt. This saves testing q >= in on each trip. It's such a simple loop that saving a per-trip test is a major speed win. */ for (q = p-1; *q > thiselt; --q) *(q+1) = *q; *(q+1) = thiselt; } } } #endif /* The maximum number of elements in the pending-work stack quicksort maintains. The maximum stack depth is approximately log2(n), so arrays of size up to approximately MAX_INSERTION * 2**STACKSIZE can be sorted. The memory burden for the stack is small, so better safe than sorry. */ #define STACKSIZE 60 /* A _stacknode remembers a contiguous slice of an array that needs to sorted. lo must be <= hi, and, unlike Python array slices, this includes both ends. */ struct _stacknode { element_type *lo; element_type *hi; }; static void quicksort(element_type *plo, size_t n) { element_type *phi; /* Swap two array elements. */ element_type _temp; #define SWAP(P, Q) (_temp = *(P), *(P) = *(Q), *(Q) = _temp) /* Stack of pending array slices to be sorted. */ struct _stacknode stack[STACKSIZE]; struct _stacknode *stackfree = stack; /* available stack slot */ /* Push an array slice on the pending-work stack. */ #define PUSH(PLO, PHI) \ do { \ assert(stackfree - stack < STACKSIZE); \ assert((PLO) <= (PHI)); \ stackfree->lo = (PLO); \ stackfree->hi = (PHI); \ ++stackfree; \ } while(0) assert(plo); phi = plo + n - 1; for (;;) { element_type pivot; element_type *pi, *pj; assert(plo <= phi); n = phi - plo + 1; if (n <= MAX_INSERTION) { /* Do a small insertion sort. Contra Knuth, we do this now instead of waiting until the end, because this little slice is likely still in cache now. */ element_type *p, *q; element_type minimum = *plo; for (p = plo+1; p <= phi; ++p) { /* *plo <= *(plo+1) <= ... <= *(p-1). Slide *p into place. */ element_type thiselt = *p; if (thiselt < minimum) { /* New minimum. */ memmove(plo+1, plo, (p - plo) * sizeof(*p)); *plo = minimum = thiselt; } else { /* thiselt >= minimum, so the loop will find a q with *q <= thiselt. */ for (q = p-1; *q > thiselt; --q) *(q+1) = *q; *(q+1) = thiselt; } } /* Pop another slice off the stack. */ if (stack == stackfree) break; /* no more slices -- we're done */ --stackfree; plo = stackfree->lo; phi = stackfree->hi; continue; } /* Parition the slice. For pivot, take the median of the leftmost, rightmost, and middle elements. First sort those three; then the median is the middle one. For technical reasons, the middle element is swapped to plo+1 first (see Knuth Vol 3 Ed 2 section 5.2.2 exercise 55 -- reverse-sorted arrays can take quadratic time otherwise!). */ { element_type *plop1 = plo + 1; element_type *pmid = plo + (n >> 1); assert(plo < pmid && pmid < phi); SWAP(plop1, pmid); /* Sort plo, plop1, phi. */ /* Smaller of rightmost two -> middle. */ if (*plop1 > *phi) SWAP(plop1, phi); /* Smallest of all -> left; if plo is already the smallest, the sort is complete. */ if (*plo > *plop1) { SWAP(plo, plop1); /* Largest of all -> right. */ if (*plop1 > *phi) SWAP(plop1, phi); } pivot = *plop1; pi = plop1; } assert(*plo <= pivot); assert(*pi == pivot); assert(*phi >= pivot); pj = phi; /* Partition wrt pivot. This is the time-critical part, and nearly every decision in the routine aims at making this loop as fast as possible -- even small points like arranging that all loop tests can be done correctly at the bottoms of loops instead of the tops, and that pointers can be derefenced directly as-is (without fiddly +1 or -1). The aim is to make the C here so simple that a compiler has a good shot at doing as well as hand-crafted assembler. */ for (;;) { /* Invariants: 1. pi < pj. 2. All elements at plo, plo+1 .. pi are <= pivot. 3. All elements at pj, pj+1 .. phi are >= pivot. 4. There is an element >= pivot to the right of pi. 5. There is an element <= pivot to the left of pj. Note that #4 and #5 save us from needing to check that the pointers stay in bounds. */ assert(pi < pj); do { ++pi; } while (*pi < pivot); assert(pi <= pj); do { --pj; } while (*pj > pivot); assert(pj >= pi - 1); if (pi < pj) SWAP(pi, pj); else break; } assert(plo+1 < pi && pi <= phi); assert(plo < pj && pj < phi); assert(*pi >= pivot); assert( (pi == pj && *pj == pivot) || (pj + 1 == pi && *pj <= pivot) ); /* Swap pivot into its final position, pj. */ assert(plo[1] == pivot); plo[1] = *pj; *pj = pivot; /* Subfiles are from plo to pj-1 inclusive, and pj+1 to phi inclusive. Push the larger one, and loop back to do the smaller one directly. */ if (pj - plo >= phi - pj) { PUSH(plo, pj-1); plo = pj+1; } else { PUSH(pj+1, phi); phi = pj-1; } } #undef PUSH #undef SWAP } /* Sort p and remove duplicates, as fast as we can. */ static size_t sort_int_nodups(KEY_TYPE *p, size_t n) { size_t nunique; element_type *work; assert(sizeof(KEY_TYPE) == sizeof(element_type)); assert(p); /* Use quicksort if the array is small, OR if malloc can't find enough temp memory for radixsort. */ work = NULL; if (n > QUICKSORT_BEATS_RADIXSORT) work = (element_type *)malloc(n * sizeof(element_type)); if (work) { element_type *out = radixsort_int(p, work, n); nunique = uniq(p, out, n); free(work); } else { quicksort(p, n); nunique = uniq(p, p, n); } return nunique; } zope2.13-2.13.21/source/ZODB3/src/BTrees/intvaluemacros.h0000644000175000017500000000375012214017464021446 0ustar arnauarnau #define VALUEMACROS_H "$Id: intvaluemacros.h 114787 2010-07-15 16:53:11Z jim $\n" #ifdef ZODB_64BIT_INTS #define NEED_LONG_LONG_SUPPORT #define VALUE_TYPE PY_LONG_LONG #define VALUE_PARSE "L" #define COPY_VALUE_TO_OBJECT(O, K) O=longlong_as_object(K) #define COPY_VALUE_FROM_ARG(TARGET, ARG, STATUS) \ if (PyInt_Check(ARG)) TARGET=PyInt_AS_LONG(ARG); else \ if (longlong_check(ARG)) TARGET=PyLong_AsLongLong(ARG); else \ if (PyLong_Check(ARG)) { \ PyErr_SetString(PyExc_ValueError, "long integer out of range"); \ (STATUS)=0; (TARGET)=0; } \ else { \ PyErr_SetString(PyExc_TypeError, "expected integer value"); \ (STATUS)=0; (TARGET)=0; } #else #define VALUE_TYPE int #define VALUE_PARSE "i" #define COPY_VALUE_TO_OBJECT(O, K) O=PyInt_FromLong(K) #define COPY_VALUE_FROM_ARG(TARGET, ARG, STATUS) \ if (PyInt_Check(ARG)) { \ long vcopy = PyInt_AS_LONG(ARG); \ if ((int)vcopy != vcopy) { \ PyErr_SetString(PyExc_TypeError, "integer out of range"); \ (STATUS)=0; (TARGET)=0; \ } \ else TARGET = vcopy; \ } else { \ PyErr_SetString(PyExc_TypeError, "expected integer key"); \ (STATUS)=0; (TARGET)=0; } #endif #undef VALUE_TYPE_IS_PYOBJECT #define TEST_VALUE(K, T) (((K) < (T)) ? -1 : (((K) > (T)) ? 1: 0)) #define VALUE_SAME(VALUE, TARGET) ( (VALUE) == (TARGET) ) #define DECLARE_VALUE(NAME) VALUE_TYPE NAME #define DECREF_VALUE(k) #define INCREF_VALUE(k) #define COPY_VALUE(V, E) (V=(E)) #define NORMALIZE_VALUE(V, MIN) ((MIN) > 0) ? ((V)/=(MIN)) : 0 #define MERGE_DEFAULT 1 #define MERGE(O1, w1, O2, w2) ((O1)*(w1)+(O2)*(w2)) #define MERGE_WEIGHT(O, w) ((O)*(w)) zope2.13-2.13.21/source/ZODB3/src/BTrees/Length.py0000644000175000017500000000324112214017464020027 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import persistent class Length(persistent.Persistent): """BTree lengths are often too expensive to compute. Objects that use BTrees need to keep track of lengths themselves. This class provides an object for doing this. As a bonus, the object support application-level conflict resolution. It is tempting to to assign length objects to __len__ attributes to provide instance-specific __len__ methods. However, this no longer works as expected, because new-style classes cache class-defined slot methods (like __len__) in C type slots. Thus, instance-defined slot fillers are ignored. """ value = 0 def __init__(self, v=0): self.value = v def __getstate__(self): return self.value def __setstate__(self, v): self.value = v def set(self, v): self.value = v def _p_resolveConflict(self, old, s1, s2): return s1 + s2 - old def change(self, delta): self.value += delta def __call__(self, *args): return self.value zope2.13-2.13.21/source/ZODB3/src/BTrees/_LOBTree.c0000644000175000017500000000210712214017464017773 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ #define MASTER_ID "$Id: _IOBTree.c 25186 2004-06-02 15:07:33Z jim $\n" /* IOBTree - int key, object value BTree Implements a collection using int type keys and object type values */ #define PERSISTENT #define MOD_NAME_PREFIX "LO" #define DEFAULT_MAX_BUCKET_SIZE 60 #define DEFAULT_MAX_BTREE_SIZE 500 #define INITMODULE init_LOBTree #define ZODB_64BIT_INTS #include "intkeymacros.h" #include "objectvaluemacros.h" #include "BTreeModuleTemplate.c" zope2.13-2.13.21/source/ZODB3/src/BTrees/__init__.py0000644000175000017500000000352112214017464020346 0ustar arnauarnau############################################################################# # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################# import zope.interface import BTrees.Interfaces class _Family(object): zope.interface.implements(BTrees.Interfaces.IBTreeFamily) from BTrees import OOBTree as OO class _Family32(_Family): from BTrees import OIBTree as OI from BTrees import IIBTree as II from BTrees import IOBTree as IO from BTrees import IFBTree as IF maxint = int(2**31-1) minint = -maxint - 1 def __reduce__(self): return _family32, () class _Family64(_Family): from BTrees import OLBTree as OI from BTrees import LLBTree as II from BTrees import LOBTree as IO from BTrees import LFBTree as IF maxint = 2**63-1 minint = -maxint - 1 def __reduce__(self): return _family64, () def _family32(): return family32 _family32.__safe_for_unpickling__ = True def _family64(): return family64 _family64.__safe_for_unpickling__ = True family32 = _Family32() family64 = _Family64() BTrees.family64.IO.family = family64 BTrees.family64.OI.family = family64 BTrees.family64.IF.family = family64 BTrees.family64.II.family = family64 BTrees.family32.IO.family = family32 BTrees.family32.OI.family = family32 BTrees.family32.IF.family = family32 BTrees.family32.II.family = family32 zope2.13-2.13.21/source/ZODB3/src/BTrees/BTreeTemplate.c0000644000175000017500000016775712214017464021124 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #define BTREETEMPLATE_C "$Id: BTreeTemplate.c 116139 2010-09-02 13:55:40Z jim $\n" /* Sanity-check a BTree. This is a private helper for BTree_check. Return: * -1 Error. If it's an internal inconsistency in the BTree, * AssertionError is set. * 0 No problem found. * * nextbucket is the bucket "one beyond the end" of the BTree; the last bucket * directly reachable from following right child pointers *should* be linked * to nextbucket (and this is checked). */ static int BTree_check_inner(BTree *self, Bucket *nextbucket) { int i; Bucket *bucketafter; Sized *child; char *errormsg = "internal error"; /* someone should have overriden */ Sized *activated_child = NULL; int result = -1; /* until proved innocent */ #define CHECK(CONDITION, ERRORMSG) \ if (!(CONDITION)) { \ errormsg = (ERRORMSG); \ goto Error; \ } PER_USE_OR_RETURN(self, -1); CHECK(self->len >= 0, "BTree len < 0"); CHECK(self->len <= self->size, "BTree len > size"); if (self->len == 0) { /* Empty BTree. */ CHECK(self->firstbucket == NULL, "Empty BTree has non-NULL firstbucket"); result = 0; goto Done; } /* Non-empty BTree. */ CHECK(self->firstbucket != NULL, "Non-empty BTree has NULL firstbucket"); /* Obscure: The first bucket is pointed to at least by self->firstbucket * and data[0].child of whichever BTree node it's a child of. However, * if persistence is enabled then the latter BTree node may be a ghost * at this point, and so its pointers "don't count": we can only rely * on self's pointers being intact. */ #ifdef PERSISTENT CHECK(self->firstbucket->ob_refcnt >= 1, "Non-empty BTree firstbucket has refcount < 1"); #else CHECK(self->firstbucket->ob_refcnt >= 2, "Non-empty BTree firstbucket has refcount < 2"); #endif for (i = 0; i < self->len; ++i) { CHECK(self->data[i].child != NULL, "BTree has NULL child"); } if (SameType_Check(self, self->data[0].child)) { /* Our children are also BTrees. */ child = self->data[0].child; UNLESS (PER_USE(child)) goto Done; activated_child = child; CHECK(self->firstbucket == BTREE(child)->firstbucket, "BTree has firstbucket different than " "its first child's firstbucket"); PER_ALLOW_DEACTIVATION(child); activated_child = NULL; for (i = 0; i < self->len; ++i) { child = self->data[i].child; CHECK(SameType_Check(self, child), "BTree children have different types"); if (i == self->len - 1) bucketafter = nextbucket; else { BTree *child2 = BTREE(self->data[i+1].child); UNLESS (PER_USE(child2)) goto Done; bucketafter = child2->firstbucket; PER_ALLOW_DEACTIVATION(child2); } if (BTree_check_inner(BTREE(child), bucketafter) < 0) goto Done; } } else { /* Our children are buckets. */ CHECK(self->firstbucket == BUCKET(self->data[0].child), "Bottom-level BTree node has inconsistent firstbucket belief"); for (i = 0; i < self->len; ++i) { child = self->data[i].child; UNLESS (PER_USE(child)) goto Done; activated_child = child; CHECK(!SameType_Check(self, child), "BTree children have different types"); CHECK(child->len >= 1, "Bucket length < 1"); /* no empty buckets! */ CHECK(child->len <= child->size, "Bucket len > size"); #ifdef PERSISTENT CHECK(child->ob_refcnt >= 1, "Bucket has refcount < 1"); #else CHECK(child->ob_refcnt >= 2, "Bucket has refcount < 2"); #endif if (i == self->len - 1) bucketafter = nextbucket; else bucketafter = BUCKET(self->data[i+1].child); CHECK(BUCKET(child)->next == bucketafter, "Bucket next pointer is damaged"); PER_ALLOW_DEACTIVATION(child); activated_child = NULL; } } result = 0; goto Done; Error: PyErr_SetString(PyExc_AssertionError, errormsg); result = -1; Done: /* No point updating access time -- this isn't a "real" use. */ PER_ALLOW_DEACTIVATION(self); if (activated_child) { PER_ALLOW_DEACTIVATION(activated_child); } return result; #undef CHECK } /* Sanity-check a BTree. This is the ._check() method. Return: * NULL Error. If it's an internal inconsistency in the BTree, * AssertionError is set. * Py_None No problem found. */ static PyObject* BTree_check(BTree *self) { PyObject *result = NULL; int i = BTree_check_inner(self, NULL); if (i >= 0) { result = Py_None; Py_INCREF(result); } return result; } /* ** _BTree_get ** ** Search a BTree. ** ** Arguments ** self a pointer to a BTree ** keyarg the key to search for, as a Python object ** has_key true/false; when false, try to return the associated ** value; when true, return a boolean ** Return ** When has_key false: ** If key exists, its associated value. ** If key doesn't exist, NULL and KeyError is set. ** When has_key true: ** A Python int is returned in any case. ** If key exists, the depth of the bucket in which it was found. ** If key doesn't exist, 0. */ static PyObject * _BTree_get(BTree *self, PyObject *keyarg, int has_key) { KEY_TYPE key; PyObject *result = NULL; /* guilty until proved innocent */ int copied = 1; COPY_KEY_FROM_ARG(key, keyarg, copied); UNLESS (copied) return NULL; PER_USE_OR_RETURN(self, NULL); if (self->len == 0) { /* empty BTree */ if (has_key) result = PyInt_FromLong(0); else PyErr_SetObject(PyExc_KeyError, keyarg); } else { for (;;) { int i; Sized *child; BTREE_SEARCH(i, self, key, goto Done); child = self->data[i].child; has_key += has_key != 0; /* bump depth counter, maybe */ if (SameType_Check(self, child)) { PER_UNUSE(self); self = BTREE(child); PER_USE_OR_RETURN(self, NULL); } else { result = _bucket_get(BUCKET(child), keyarg, has_key); break; } } } Done: PER_UNUSE(self); return result; } static PyObject * BTree_get(BTree *self, PyObject *key) { return _BTree_get(self, key, 0); } /* Create a new bucket for the BTree or TreeSet using the class attribute _bucket_type, which is normally initialized to BucketType or SetType as appropriate. */ static Sized * BTree_newBucket(BTree *self) { PyObject *factory; Sized *result; /* _bucket_type_str defined in BTreeModuleTemplate.c */ factory = PyObject_GetAttr((PyObject *)self->ob_type, _bucket_type_str); if (factory == NULL) return NULL; /* TODO: Should we check that the factory actually returns something of the appropriate type? How? The C code here is going to depend on any custom bucket type having the same layout at the C level. */ result = SIZED(PyObject_CallObject(factory, NULL)); Py_DECREF(factory); return result; } /* * Move data from the current BTree, from index onward, to the newly created * BTree 'next'. self and next must both be activated. If index is OOB (< 0 * or >= self->len), use self->len / 2 as the index (i.e., split at the * midpoint). self must have at least 2 children on entry, and index must * be such that self and next each have at least one child at exit. self's * accessed time is updated. * * Return: * -1 error * 0 OK */ static int BTree_split(BTree *self, int index, BTree *next) { int next_size; Sized *child; if (index < 0 || index >= self->len) index = self->len / 2; next_size = self->len - index; ASSERT(index > 0, "split creates empty tree", -1); ASSERT(next_size > 0, "split creates empty tree", -1); next->data = BTree_Malloc(sizeof(BTreeItem) * next_size); if (!next->data) return -1; memcpy(next->data, self->data + index, sizeof(BTreeItem) * next_size); next->size = next_size; /* but don't set len until we succeed */ /* Set next's firstbucket. self->firstbucket is still correct. */ child = next->data[0].child; if (SameType_Check(self, child)) { PER_USE_OR_RETURN(child, -1); next->firstbucket = BTREE(child)->firstbucket; PER_UNUSE(child); } else next->firstbucket = BUCKET(child); Py_INCREF(next->firstbucket); next->len = next_size; self->len = index; return PER_CHANGED(self) >= 0 ? 0 : -1; } /* Fwd decl -- BTree_grow and BTree_split_root reference each other. */ static int BTree_grow(BTree *self, int index, int noval); /* Split the root. This is a little special because the root isn't a child * of anything else, and the root needs to retain its object identity. So * this routine moves the root's data into a new child, and splits the * latter. This leaves the root with two children. * * Return: * 0 OK * -1 error * * CAUTION: The caller must call PER_CHANGED on self. */ static int BTree_split_root(BTree *self, int noval) { BTree *child; BTreeItem *d; /* Create a child BTree, and a new data vector for self. */ child = BTREE(PyObject_CallObject(OBJECT(self->ob_type), NULL)); if (!child) return -1; d = BTree_Malloc(sizeof(BTreeItem) * 2); if (!d) { Py_DECREF(child); return -1; } /* Move our data to new BTree. */ child->size = self->size; child->len = self->len; child->data = self->data; child->firstbucket = self->firstbucket; Py_INCREF(child->firstbucket); /* Point self to child and split the child. */ self->data = d; self->len = 1; self->size = 2; self->data[0].child = SIZED(child); /* transfers reference ownership */ return BTree_grow(self, 0, noval); } /* ** BTree_grow ** ** Grow a BTree ** ** Arguments: self The BTree ** index self->data[index].child needs to be split. index ** must be 0 if self is empty (len == 0), and a new ** empty bucket is created then. ** noval Boolean; is this a set (true) or mapping (false)? ** ** Returns: 0 on success ** -1 on failure ** ** CAUTION: If self is empty on entry, this routine adds an empty bucket. ** That isn't a legitimate BTree; if the caller doesn't put something in ** in the bucket (say, because of a later error), the BTree must be cleared ** to get rid of the empty bucket. */ static int BTree_grow(BTree *self, int index, int noval) { int i; Sized *v, *e = 0; BTreeItem *d; if (self->len == self->size) { if (self->size) { d = BTree_Realloc(self->data, sizeof(BTreeItem) * self->size * 2); if (d == NULL) return -1; self->data = d; self->size *= 2; } else { d = BTree_Malloc(sizeof(BTreeItem) * 2); if (d == NULL) return -1; self->data = d; self->size = 2; } } if (self->len) { d = self->data + index; v = d->child; /* Create a new object of the same type as the target value */ e = (Sized *)PyObject_CallObject((PyObject *)v->ob_type, NULL); if (e == NULL) return -1; UNLESS(PER_USE(v)) { Py_DECREF(e); return -1; } /* Now split between the original (v) and the new (e) at the midpoint*/ if (SameType_Check(self, v)) i = BTree_split((BTree *)v, -1, (BTree *)e); else i = bucket_split((Bucket *)v, -1, (Bucket *)e); PER_ALLOW_DEACTIVATION(v); if (i < 0) { Py_DECREF(e); assert(PyErr_Occurred()); return -1; } index++; d++; if (self->len > index) /* Shift up the old values one array slot */ memmove(d+1, d, sizeof(BTreeItem)*(self->len-index)); if (SameType_Check(self, v)) { COPY_KEY(d->key, BTREE(e)->data->key); /* We take the unused reference from e, so there's no reason to INCREF! */ /* INCREF_KEY(self->data[1].key); */ } else { COPY_KEY(d->key, BUCKET(e)->keys[0]); INCREF_KEY(d->key); } d->child = e; self->len++; if (self->len >= MAX_BTREE_SIZE(self) * 2) /* the root is huge */ return BTree_split_root(self, noval); } else { /* The BTree is empty. Create an empty bucket. See CAUTION in * the comments preceding. */ assert(index == 0); d = self->data; d->child = BTree_newBucket(self); if (d->child == NULL) return -1; self->len = 1; Py_INCREF(d->child); self->firstbucket = (Bucket *)d->child; } return 0; } /* Return the rightmost bucket reachable from following child pointers * from self. The caller gets a new reference to this bucket. Note that * bucket 'next' pointers are not followed: if self is an interior node * of a BTree, this returns the rightmost bucket in that node's subtree. * In case of error, returns NULL. * * self must not be a ghost; this isn't checked. The result may be a ghost. * * Pragmatics: Note that the rightmost bucket's last key is the largest * key in self's subtree. */ static Bucket * BTree_lastBucket(BTree *self) { Sized *pchild; Bucket *result; UNLESS (self->data && self->len) { IndexError(-1); /* is this the best action to take? */ return NULL; } pchild = self->data[self->len - 1].child; if (SameType_Check(self, pchild)) { self = BTREE(pchild); PER_USE_OR_RETURN(self, NULL); result = BTree_lastBucket(self); PER_UNUSE(self); } else { Py_INCREF(pchild); result = BUCKET(pchild); } return result; } static int BTree_deleteNextBucket(BTree *self) { Bucket *b; UNLESS (PER_USE(self)) return -1; b = BTree_lastBucket(self); if (b == NULL) goto err; if (Bucket_deleteNextBucket(b) < 0) goto err; Py_DECREF(b); PER_UNUSE(self); return 0; err: Py_XDECREF(b); PER_ALLOW_DEACTIVATION(self); return -1; } /* ** _BTree_clear ** ** Clears out all of the values in the BTree (firstbucket, keys, and children); ** leaving self an empty BTree. ** ** Arguments: self The BTree ** ** Returns: 0 on success ** -1 on failure ** ** Internal: Deallocation order is important. The danger is that a long ** list of buckets may get freed "at once" via decref'ing the first bucket, ** in which case a chain of consequenct Py_DECREF calls may blow the stack. ** Luckily, every bucket has a refcount of at least two, one due to being a ** BTree node's child, and another either because it's not the first bucket in ** the chain (so the preceding bucket points to it), or because firstbucket ** points to it. By clearing in the natural depth-first, left-to-right ** order, the BTree->bucket child pointers prevent Py_DECREF(bucket->next) ** calls from freeing bucket->next, and the maximum stack depth is equal ** to the height of the tree. **/ static int _BTree_clear(BTree *self) { const int len = self->len; if (self->firstbucket) { /* Obscure: The first bucket is pointed to at least by * self->firstbucket and data[0].child of whichever BTree node it's * a child of. However, if persistence is enabled then the latter * BTree node may be a ghost at this point, and so its pointers "don't * count": we can only rely on self's pointers being intact. */ #ifdef PERSISTENT ASSERT(self->firstbucket->ob_refcnt > 0, "Invalid firstbucket pointer", -1); #else ASSERT(self->firstbucket->ob_refcnt > 1, "Invalid firstbucket pointer", -1); #endif Py_DECREF(self->firstbucket); self->firstbucket = NULL; } if (self->data) { int i; if (len > 0) { /* 0 is special because key 0 is trash */ Py_DECREF(self->data[0].child); } for (i = 1; i < len; i++) { #ifdef KEY_TYPE_IS_PYOBJECT DECREF_KEY(self->data[i].key); #endif Py_DECREF(self->data[i].child); } free(self->data); self->data = NULL; } self->len = self->size = 0; return 0; } /* Set (value != 0) or delete (value=0) a tree item. If unique is non-zero, then only change if the key is new. If noval is non-zero, then don't set a value (the tree is a set). Return: -1 error 0 successful, and number of entries didn't change >0 successful, and number of entries did change Internal There are two distinct return values > 0: 1 Successful, number of entries changed, but firstbucket did not go away. 2 Successful, number of entries changed, firstbucket did go away. This can only happen on a delete (value == NULL). The caller may need to change its own firstbucket pointer, and in any case *someone* needs to adjust the 'next' pointer of the bucket immediately preceding the bucket that went away (it needs to point to the bucket immediately following the bucket that went away). */ static int _BTree_set(BTree *self, PyObject *keyarg, PyObject *value, int unique, int noval) { int changed = 0; /* did I mutate? */ int min; /* index of child I searched */ BTreeItem *d; /* self->data[min] */ int childlength; /* len(self->data[min].child) */ int status; /* our return value; and return value from callee */ int self_was_empty; /* was self empty at entry? */ KEY_TYPE key; int copied = 1; COPY_KEY_FROM_ARG(key, keyarg, copied); if (!copied) return -1; PER_USE_OR_RETURN(self, -1); self_was_empty = self->len == 0; if (self_was_empty) { /* We're empty. Make room. */ if (value) { if (BTree_grow(self, 0, noval) < 0) goto Error; } else { /* Can't delete a key from an empty BTree. */ PyErr_SetObject(PyExc_KeyError, keyarg); goto Error; } } /* Find the right child to search, and hand the work off to it. */ BTREE_SEARCH(min, self, key, goto Error); d = self->data + min; #ifdef PERSISTENT PER_READCURRENT(self, goto Error); #endif if (SameType_Check(self, d->child)) status = _BTree_set(BTREE(d->child), keyarg, value, unique, noval); else { int bucket_changed = 0; status = _bucket_set(BUCKET(d->child), keyarg, value, unique, noval, &bucket_changed); #ifdef PERSISTENT /* If a BTree contains only a single bucket, BTree.__getstate__() * includes the bucket's entire state, and the bucket doesn't get * an oid of its own. So if we have a single oid-less bucket that * changed, it's *our* oid that should be marked as changed -- the * bucket doesn't have one. */ if (bucket_changed && self->len == 1 && self->data[0].child->oid == NULL) { changed = 1; } #endif } if (status == 0) goto Done; if (status < 0) goto Error; assert(status == 1 || status == 2); /* The child changed size. Get its new size. Note that since the tree * rooted at the child changed size, so did the tree rooted at self: * our status must be >= 1 too. */ UNLESS(PER_USE(d->child)) goto Error; childlength = d->child->len; PER_UNUSE(d->child); if (value) { /* A bucket got bigger -- if it's "too big", split it. */ int toobig; assert(status == 1); /* can be 2 only on deletes */ if (SameType_Check(self, d->child)) toobig = childlength > MAX_BTREE_SIZE(d->child); else toobig = childlength > MAX_BUCKET_SIZE(d->child); if (toobig) { if (BTree_grow(self, min, noval) < 0) goto Error; changed = 1; /* BTree_grow mutated self */ } goto Done; /* and status still == 1 */ } /* A bucket got smaller. This is much harder, and despite that we * don't try to rebalance the tree. */ if (min && childlength) { /* We removed a key. but the node child is non-empty. If the deleted key is the node key, then update the node key using the smallest key of the node child. This doesn't apply to the 0th node, whos key is unused. */ int _cmp = 1; TEST_KEY_SET_OR(_cmp, key, d->key) goto Error; if (_cmp == 0) { /* Need to replace key with first key from child */ Bucket *bucket; if (SameType_Check(self, d->child)) { UNLESS(PER_USE(d->child)) goto Error; bucket = BTREE(d->child)->firstbucket; PER_UNUSE(d->child); } else bucket = BUCKET(d->child); UNLESS(PER_USE(bucket)) goto Error; DECREF_KEY(d->key); COPY_KEY(d->key, bucket->keys[0]); INCREF_KEY(d->key); PER_UNUSE(bucket); if (PER_CHANGED(self) < 0) goto Error; } } if (status == 2) { /* The child must be a BTree because bucket.set never returns 2 */ /* Two problems to solve: May have to adjust our own firstbucket, * and the bucket that went away needs to get unlinked. */ if (min) { /* This wasn't our firstbucket, so no need to adjust ours (note * that it can't be the firstbucket of any node above us either). * Tell "the tree to the left" to do the unlinking. */ if (BTree_deleteNextBucket(BTREE(d[-1].child)) < 0) goto Error; status = 1; /* we solved the child's firstbucket problem */ } else { /* This was our firstbucket. Update to new firstbucket value. */ Bucket *nextbucket; UNLESS(PER_USE(d->child)) goto Error; nextbucket = BTREE(d->child)->firstbucket; PER_UNUSE(d->child); Py_XINCREF(nextbucket); Py_DECREF(self->firstbucket); self->firstbucket = nextbucket; changed = 1; /* The caller has to do the unlinking -- we can't. Also, since * it was our firstbucket, it may also be theirs. */ assert(status == 2); } } /* If the child isn't empty, we're done! We did all that was possible for * us to do with the firstbucket problems the child gave us, and since the * child isn't empty don't create any new firstbucket problems of our own. */ if (childlength) goto Done; /* The child became empty: we need to remove it from self->data. * But first, if we're a bottom-level node, we've got more bucket-fiddling * to set up. */ if (! SameType_Check(self, d->child)) { /* We're about to delete a bucket, so need to adjust bucket pointers. */ if (min) { /* It's not our first bucket, so we can tell the previous * bucket to adjust its reference to it. It can't be anyone * else's first bucket either, so the caller needn't do anything. */ if (Bucket_deleteNextBucket(BUCKET(d[-1].child)) < 0) goto Error; /* status should be 1, and already is: if it were 2, the * block above would have set it to 1 in its min != 0 branch. */ assert(status == 1); } else { Bucket *nextbucket; /* It's our first bucket. We can't unlink it directly. */ /* 'changed' will be set true by the deletion code following. */ UNLESS(PER_USE(d->child)) goto Error; nextbucket = BUCKET(d->child)->next; PER_UNUSE(d->child); Py_XINCREF(nextbucket); Py_DECREF(self->firstbucket); self->firstbucket = nextbucket; status = 2; /* we're giving our caller a new firstbucket problem */ } } /* Remove the child from self->data. */ Py_DECREF(d->child); #ifdef KEY_TYPE_IS_PYOBJECT if (min) { DECREF_KEY(d->key); } else if (self->len > 1) { /* We're deleting the first child of a BTree with more than one * child. The key at d+1 is about to be shifted into slot 0, * and hence never to be referenced again (the key in slot 0 is * trash). */ DECREF_KEY((d+1)->key); } /* Else min==0 and len==1: we're emptying the BTree entirely, and * there is no key in need of decrefing. */ #endif --self->len; if (min < self->len) memmove(d, d+1, (self->len - min) * sizeof(BTreeItem)); changed = 1; Done: #ifdef PERSISTENT if (changed) { if (PER_CHANGED(self) < 0) goto Error; } #endif PER_UNUSE(self); return status; Error: assert(PyErr_Occurred()); if (self_was_empty) { /* BTree_grow may have left the BTree in an invalid state. Make * sure the tree is a legitimate empty tree. */ _BTree_clear(self); } PER_UNUSE(self); return -1; } /* ** BTree_setitem ** ** wrapper for _BTree_set ** ** Arguments: self The BTree ** key The key to insert ** v The value to insert ** ** Returns -1 on failure ** 0 on success */ static int BTree_setitem(BTree *self, PyObject *key, PyObject *v) { if (_BTree_set(self, key, v, 0, 0) < 0) return -1; return 0; } #ifdef PERSISTENT static PyObject * BTree__p_deactivate(BTree *self, PyObject *args, PyObject *keywords) { int ghostify = 1; PyObject *force = NULL; if (args && PyTuple_GET_SIZE(args) > 0) { PyErr_SetString(PyExc_TypeError, "_p_deactivate takes not positional arguments"); return NULL; } if (keywords) { int size = PyDict_Size(keywords); force = PyDict_GetItemString(keywords, "force"); if (force) size--; if (size) { PyErr_SetString(PyExc_TypeError, "_p_deactivate only accepts keyword arg force"); return NULL; } } if (self->jar && self->oid) { ghostify = self->state == cPersistent_UPTODATE_STATE; if (!ghostify && force) { if (PyObject_IsTrue(force)) ghostify = 1; if (PyErr_Occurred()) return NULL; } if (ghostify) { if (_BTree_clear(self) < 0) return NULL; PER_GHOSTIFY(self); } } Py_INCREF(Py_None); return Py_None; } #endif static PyObject * BTree_clear(BTree *self) { UNLESS (PER_USE(self)) return NULL; if (self->len) { if (_BTree_clear(self) < 0) goto err; if (PER_CHANGED(self) < 0) goto err; } PER_UNUSE(self); Py_INCREF(Py_None); return Py_None; err: PER_UNUSE(self); return NULL; } /* * Return: * * For an empty BTree (self->len == 0), None. * * For a BTree with one child (self->len == 1), and that child is a bucket, * and that bucket has a NULL oid, a one-tuple containing a one-tuple * containing the bucket's state: * * ( * ( * child[0].__getstate__(), * ), * ) * * Else a two-tuple. The first element is a tuple interleaving the BTree's * keys and direct children, of size 2*self->len - 1 (key[0] is unused and * is not saved). The second element is the firstbucket: * * ( * (child[0], key[1], child[1], key[2], child[2], ..., * key[len-1], child[len-1]), * self->firstbucket * ) * * In the above, key[i] means self->data[i].key, and similarly for child[i]. */ static PyObject * BTree_getstate(BTree *self) { PyObject *r = NULL; PyObject *o; int i, l; UNLESS (PER_USE(self)) return NULL; if (self->len) { r = PyTuple_New(self->len * 2 - 1); if (r == NULL) goto err; if (self->len == 1 && self->data->child->ob_type != self->ob_type #ifdef PERSISTENT && BUCKET(self->data->child)->oid == NULL #endif ) { /* We have just one bucket. Save its data directly. */ o = bucket_getstate((Bucket *)self->data->child); if (o == NULL) goto err; PyTuple_SET_ITEM(r, 0, o); ASSIGN(r, Py_BuildValue("(O)", r)); } else { for (i=0, l=0; i < self->len; i++) { if (i) { COPY_KEY_TO_OBJECT(o, self->data[i].key); PyTuple_SET_ITEM(r, l, o); l++; } o = (PyObject *)self->data[i].child; Py_INCREF(o); PyTuple_SET_ITEM(r,l,o); l++; } ASSIGN(r, Py_BuildValue("OO", r, self->firstbucket)); } } else { r = Py_None; Py_INCREF(r); } PER_UNUSE(self); return r; err: PER_UNUSE(self); Py_XDECREF(r); return NULL; } static int _BTree_setstate(BTree *self, PyObject *state, int noval) { PyObject *items, *firstbucket = NULL; BTreeItem *d; int len, l, i, copied=1; if (_BTree_clear(self) < 0) return -1; /* The state of a BTree can be one of the following: None -- an empty BTree A one-tuple -- a single bucket btree A two-tuple -- a BTree with more than one bucket See comments for BTree_getstate() for the details. */ if (state == Py_None) return 0; if (!PyArg_ParseTuple(state, "O|O:__setstate__", &items, &firstbucket)) return -1; if (!PyTuple_Check(items)) { PyErr_SetString(PyExc_TypeError, "tuple required for first state element"); return -1; } len = PyTuple_Size(items); if (len < 0) return -1; len = (len + 1) / 2; assert(len > 0); /* If the BTree is empty, it's state is None. */ assert(self->size == 0); /* We called _BTree_clear(). */ self->data = BTree_Malloc(sizeof(BTreeItem) * len); if (self->data == NULL) return -1; self->size = len; for (i = 0, d = self->data, l = 0; i < len; i++, d++) { PyObject *v; if (i) { /* skip the first key slot */ COPY_KEY_FROM_ARG(d->key, PyTuple_GET_ITEM(items, l), copied); l++; if (!copied) return -1; INCREF_KEY(d->key); } v = PyTuple_GET_ITEM(items, l); if (PyTuple_Check(v)) { /* Handle the special case in __getstate__() for a BTree with a single bucket. */ d->child = BTree_newBucket(self); if (!d->child) return -1; if (noval) { if (_set_setstate(BUCKET(d->child), v) < 0) return -1; } else { if (_bucket_setstate(BUCKET(d->child), v) < 0) return -1; } } else { d->child = (Sized *)v; Py_INCREF(v); } l++; } if (!firstbucket) firstbucket = (PyObject *)self->data->child; if (!PyObject_IsInstance(firstbucket, (PyObject *) (noval ? &SetType : &BucketType))) { PyErr_SetString(PyExc_TypeError, "No firstbucket in non-empty BTree"); return -1; } self->firstbucket = BUCKET(firstbucket); Py_INCREF(firstbucket); #ifndef PERSISTENT /* firstbucket is also the child of some BTree node, but that node may * be a ghost if persistence is enabled. */ assert(self->firstbucket->ob_refcnt > 1); #endif self->len = len; return 0; } static PyObject * BTree_setstate(BTree *self, PyObject *arg) { int r; PER_PREVENT_DEACTIVATION(self); r = _BTree_setstate(self, arg, 0); PER_UNUSE(self); if (r < 0) return NULL; Py_INCREF(Py_None); return Py_None; } #ifdef PERSISTENT /* Recognize the special cases of a BTree that's empty or contains a single * bucket. In the former case, return a borrowed reference to Py_None. * In this single-bucket case, the bucket state is embedded directly in the * BTree state, like so: * * ( * ( * thebucket.__getstate__(), * ), * ) * * When this obtains, return a borrowed reference to thebucket.__getstate__(). * Else return NULL with an exception set. The exception should always be * ConflictError then, but may be TypeError if the state makes no sense at all * for a BTree (corrupted or hostile state). */ PyObject * get_bucket_state(PyObject *t) { if (t == Py_None) return Py_None; /* an empty BTree */ if (! PyTuple_Check(t)) { PyErr_SetString(PyExc_TypeError, "_p_resolveConflict: expected tuple or None for state"); return NULL; } if (PyTuple_GET_SIZE(t) == 2) { /* A non-degenerate BTree. */ return merge_error(-1, -1, -1, 11); } /* We're in the one-bucket case. */ if (PyTuple_GET_SIZE(t) != 1) { PyErr_SetString(PyExc_TypeError, "_p_resolveConflict: expected 1- or 2-tuple for state"); return NULL; } t = PyTuple_GET_ITEM(t, 0); if (! PyTuple_Check(t) || PyTuple_GET_SIZE(t) != 1) { PyErr_SetString(PyExc_TypeError, "_p_resolveConflict: expected 1-tuple containing " "bucket state"); return NULL; } t = PyTuple_GET_ITEM(t, 0); if (! PyTuple_Check(t)) { PyErr_SetString(PyExc_TypeError, "_p_resolveConflict: expected tuple for bucket state"); return NULL; } return t; } /* Tricky. The only kind of BTree conflict we can actually potentially * resolve is the special case of a BTree containing a single bucket, * in which case this becomes a fancy way of calling the bucket conflict * resolution code. */ static PyObject * BTree__p_resolveConflict(BTree *self, PyObject *args) { PyObject *s[3]; PyObject *x, *y, *z; if (!PyArg_ParseTuple(args, "OOO", &x, &y, &z)) return NULL; s[0] = get_bucket_state(x); if (s[0] == NULL) return NULL; s[1] = get_bucket_state(y); if (s[1] == NULL) return NULL; s[2] = get_bucket_state(z); if (s[2] == NULL) return NULL; if (PyObject_IsInstance((PyObject *)self, (PyObject *)&BTreeType)) x = _bucket__p_resolveConflict(OBJECT(&BucketType), s); else x = _bucket__p_resolveConflict(OBJECT(&SetType), s); if (x == NULL) return NULL; return Py_BuildValue("((N))", x); } #endif /* BTree_findRangeEnd -- Find one end, expressed as a bucket and position, for a range search. If low, return bucket and index of the smallest item >= key, otherwise return bucket and index of the largest item <= key. If exclude_equal, exact matches aren't acceptable; if one is found, move right if low, or left if !low (this is for range searches exclusive of an endpoint). Return: -1 Error; offset and bucket unchanged 0 Not found; offset and bucket unchanged 1 Correct bucket and offset stored; the caller owns a new reference to the bucket. Internal: We do binary searches in BTree nodes downward, at each step following C(i) where K(i) <= key < K(i+1). As always, K(i) <= C(i) < K(i+1) too. (See Maintainer.txt for the meaning of that notation.) That eventually leads to a bucket where we do Bucket_findRangeEnd. That usually works, but there are two cases where it can fail to find the correct answer: 1. On a low search, we find a bucket with keys >= K(i), but that doesn't imply there are keys in the bucket >= key. For example, suppose a bucket has keys in 1..100, its successor's keys are in 200..300, and we're doing a low search on 150. We'll end up in the first bucket, but there are no keys >= 150 in it. K(i+1) > key, though, and all the keys in C(i+1) >= K(i+1) > key, so the first key in the next bucket (if any) is the correct result. This is easy to find by following the bucket 'next' pointer. 2. On a high search, again that the keys in the bucket are >= K(i) doesn't imply that any key in the bucket is <= key, but it's harder for this to fail (and an earlier version of this routine didn't catch it): if K(i) itself is in the bucket, it works (then K(i) <= key is *a* key in the bucket that's in the desired range). But when keys get deleted from buckets, they aren't also deleted from BTree nodes, so there's no guarantee that K(i) is in the bucket. For example, delete the smallest key S from some bucket, and S remains in the interior BTree nodes. Do a high search for S, and the BTree nodes direct the search to the bucket S used to be in, but all keys remaining in that bucket are > S. The largest key in the *preceding* bucket (if any) is < K(i), though, and K(i) <= key, so the largest key in the preceding bucket is < key and so is the proper result. This is harder to get at efficiently, as buckets are linked only in the increasing direction. While we're searching downward, deepest_smaller is set to the node deepest in the tree where we *could* have gone to the left of C(i). The rightmost bucket in deepest_smaller's subtree is the bucket preceding the bucket we find at first. This is clumsy to get at, but efficient. */ static int BTree_findRangeEnd(BTree *self, PyObject *keyarg, int low, int exclude_equal, Bucket **bucket, int *offset) { Sized *deepest_smaller = NULL; /* last possibility to move left */ int deepest_smaller_is_btree = 0; /* Boolean; if false, it's a bucket */ Bucket *pbucket; int self_got_rebound = 0; /* Boolean; when true, deactivate self */ int result = -1; /* Until proven innocent */ int i; KEY_TYPE key; int copied = 1; COPY_KEY_FROM_ARG(key, keyarg, copied); UNLESS (copied) return -1; /* We don't need to: PER_USE_OR_RETURN(self, -1); because the caller does. */ UNLESS (self->data && self->len) return 0; /* Search downward until hitting a bucket, stored in pbucket. */ for (;;) { Sized *pchild; int pchild_is_btree; BTREE_SEARCH(i, self, key, goto Done); pchild = self->data[i].child; pchild_is_btree = SameType_Check(self, pchild); if (i) { deepest_smaller = self->data[i-1].child; deepest_smaller_is_btree = pchild_is_btree; } if (pchild_is_btree) { if (self_got_rebound) { PER_UNUSE(self); } self = BTREE(pchild); self_got_rebound = 1; PER_USE_OR_RETURN(self, -1); } else { pbucket = BUCKET(pchild); break; } } /* Search the bucket for a suitable key. */ i = Bucket_findRangeEnd(pbucket, keyarg, low, exclude_equal, offset); if (i < 0) goto Done; if (i > 0) { Py_INCREF(pbucket); *bucket = pbucket; result = 1; goto Done; } /* This may be one of the two difficult cases detailed in the comments. */ if (low) { Bucket *next; UNLESS(PER_USE(pbucket)) goto Done; next = pbucket->next; if (next) { result = 1; Py_INCREF(next); *bucket = next; *offset = 0; } else result = 0; PER_UNUSE(pbucket); } /* High-end search: if it's possible to go left, do so. */ else if (deepest_smaller) { if (deepest_smaller_is_btree) { UNLESS(PER_USE(deepest_smaller)) goto Done; /* We own the reference this returns. */ pbucket = BTree_lastBucket(BTREE(deepest_smaller)); PER_UNUSE(deepest_smaller); if (pbucket == NULL) goto Done; /* error */ } else { pbucket = BUCKET(deepest_smaller); Py_INCREF(pbucket); } UNLESS(PER_USE(pbucket)) goto Done; result = 1; *bucket = pbucket; /* transfer ownership to caller */ *offset = pbucket->len - 1; PER_UNUSE(pbucket); } else result = 0; /* simply not found */ Done: if (self_got_rebound) { PER_UNUSE(self); } return result; } static PyObject * BTree_maxminKey(BTree *self, PyObject *args, int min) { PyObject *key=0; Bucket *bucket = NULL; int offset, rc; int empty_tree = 1; UNLESS (PyArg_ParseTuple(args, "|O", &key)) return NULL; UNLESS (PER_USE(self)) return NULL; UNLESS (self->data && self->len) goto empty; /* Find the range */ if (key) { if ((rc = BTree_findRangeEnd(self, key, min, 0, &bucket, &offset)) <= 0) { if (rc < 0) goto err; empty_tree = 0; goto empty; } PER_UNUSE(self); UNLESS (PER_USE(bucket)) { Py_DECREF(bucket); return NULL; } } else if (min) { bucket = self->firstbucket; PER_UNUSE(self); PER_USE_OR_RETURN(bucket, NULL); Py_INCREF(bucket); offset = 0; } else { bucket = BTree_lastBucket(self); PER_UNUSE(self); UNLESS (PER_USE(bucket)) { Py_DECREF(bucket); return NULL; } assert(bucket->len); offset = bucket->len - 1; } COPY_KEY_TO_OBJECT(key, bucket->keys[offset]); PER_UNUSE(bucket); Py_DECREF(bucket); return key; empty: PyErr_SetString(PyExc_ValueError, empty_tree ? "empty tree" : "no key satisfies the conditions"); err: PER_UNUSE(self); if (bucket) { PER_UNUSE(bucket); Py_DECREF(bucket); } return NULL; } static PyObject * BTree_minKey(BTree *self, PyObject *args) { return BTree_maxminKey(self, args, 1); } static PyObject * BTree_maxKey(BTree *self, PyObject *args) { return BTree_maxminKey(self, args, 0); } /* ** BTree_rangeSearch ** ** Generates a BTreeItems object based on the two indexes passed in, ** being the range between them. ** */ static PyObject * BTree_rangeSearch(BTree *self, PyObject *args, PyObject *kw, char type) { PyObject *min = Py_None; PyObject *max = Py_None; int excludemin = 0; int excludemax = 0; int rc; Bucket *lowbucket = NULL; Bucket *highbucket = NULL; int lowoffset; int highoffset; PyObject *result; if (args) { if (! PyArg_ParseTupleAndKeywords(args, kw, "|OOii", search_keywords, &min, &max, &excludemin, &excludemax)) return NULL; } UNLESS (PER_USE(self)) return NULL; UNLESS (self->data && self->len) goto empty; /* Find the low range */ if (min != Py_None) { if ((rc = BTree_findRangeEnd(self, min, 1, excludemin, &lowbucket, &lowoffset)) <= 0) { if (rc < 0) goto err; goto empty; } } else { lowbucket = self->firstbucket; lowoffset = 0; if (excludemin) { int bucketlen; UNLESS (PER_USE(lowbucket)) goto err; bucketlen = lowbucket->len; PER_UNUSE(lowbucket); if (bucketlen > 1) lowoffset = 1; else if (self->len < 2) goto empty; else { /* move to first item in next bucket */ Bucket *next; UNLESS (PER_USE(lowbucket)) goto err; next = lowbucket->next; PER_UNUSE(lowbucket); assert(next != NULL); lowbucket = next; /* and lowoffset is still 0 */ assert(lowoffset == 0); } } Py_INCREF(lowbucket); } /* Find the high range */ if (max != Py_None) { if ((rc = BTree_findRangeEnd(self, max, 0, excludemax, &highbucket, &highoffset)) <= 0) { Py_DECREF(lowbucket); if (rc < 0) goto err; goto empty; } } else { int bucketlen; highbucket = BTree_lastBucket(self); assert(highbucket != NULL); /* we know self isn't empty */ UNLESS (PER_USE(highbucket)) goto err_and_decref_buckets; bucketlen = highbucket->len; PER_UNUSE(highbucket); highoffset = bucketlen - 1; if (excludemax) { if (highoffset > 0) --highoffset; else if (self->len < 2) goto empty_and_decref_buckets; else { /* move to last item of preceding bucket */ int status; assert(highbucket != self->firstbucket); Py_DECREF(highbucket); status = PreviousBucket(&highbucket, self->firstbucket); if (status < 0) { Py_DECREF(lowbucket); goto err; } assert(status > 0); Py_INCREF(highbucket); UNLESS (PER_USE(highbucket)) goto err_and_decref_buckets; highoffset = highbucket->len - 1; PER_UNUSE(highbucket); } } assert(highoffset >= 0); } /* It's still possible that the range is empty, even if min < max. For * example, if min=3 and max=4, and 3 and 4 aren't in the BTree, but 2 and * 5 are, then the low position points to the 5 now and the high position * points to the 2 now. They're not necessarily even in the same bucket, * so there's no trick we can play with pointer compares to get out * cheap in general. */ if (lowbucket == highbucket && lowoffset > highoffset) goto empty_and_decref_buckets; /* definitely empty */ /* The buckets differ, or they're the same and the offsets show a non- * empty range. */ if (min != Py_None && max != Py_None && /* both args user-supplied */ lowbucket != highbucket) /* and different buckets */ { KEY_TYPE first; KEY_TYPE last; int cmp; /* Have to check the hard way: see how the endpoints compare. */ UNLESS (PER_USE(lowbucket)) goto err_and_decref_buckets; COPY_KEY(first, lowbucket->keys[lowoffset]); PER_UNUSE(lowbucket); UNLESS (PER_USE(highbucket)) goto err_and_decref_buckets; COPY_KEY(last, highbucket->keys[highoffset]); PER_UNUSE(highbucket); TEST_KEY_SET_OR(cmp, first, last) goto err_and_decref_buckets; if (cmp > 0) goto empty_and_decref_buckets; } PER_UNUSE(self); result = newBTreeItems(type, lowbucket, lowoffset, highbucket, highoffset); Py_DECREF(lowbucket); Py_DECREF(highbucket); return result; err_and_decref_buckets: Py_DECREF(lowbucket); Py_DECREF(highbucket); err: PER_UNUSE(self); return NULL; empty_and_decref_buckets: Py_DECREF(lowbucket); Py_DECREF(highbucket); empty: PER_UNUSE(self); return newBTreeItems(type, 0, 0, 0, 0); } /* ** BTree_keys */ static PyObject * BTree_keys(BTree *self, PyObject *args, PyObject *kw) { return BTree_rangeSearch(self, args, kw, 'k'); } /* ** BTree_values */ static PyObject * BTree_values(BTree *self, PyObject *args, PyObject *kw) { return BTree_rangeSearch(self, args, kw, 'v'); } /* ** BTree_items */ static PyObject * BTree_items(BTree *self, PyObject *args, PyObject *kw) { return BTree_rangeSearch(self, args, kw, 'i'); } static PyObject * BTree_byValue(BTree *self, PyObject *omin) { PyObject *r=0, *o=0, *item=0; VALUE_TYPE min; VALUE_TYPE v; int copied=1; SetIteration it = {0, 0, 1}; UNLESS (PER_USE(self)) return NULL; COPY_VALUE_FROM_ARG(min, omin, copied); UNLESS(copied) return NULL; UNLESS (r=PyList_New(0)) goto err; it.set=BTree_rangeSearch(self, NULL, NULL, 'i'); UNLESS(it.set) goto err; if (nextBTreeItems(&it) < 0) goto err; while (it.position >= 0) { if (TEST_VALUE(it.value, min) >= 0) { UNLESS (item = PyTuple_New(2)) goto err; COPY_KEY_TO_OBJECT(o, it.key); UNLESS (o) goto err; PyTuple_SET_ITEM(item, 1, o); COPY_VALUE(v, it.value); NORMALIZE_VALUE(v, min); COPY_VALUE_TO_OBJECT(o, v); DECREF_VALUE(v); UNLESS (o) goto err; PyTuple_SET_ITEM(item, 0, o); if (PyList_Append(r, item) < 0) goto err; Py_DECREF(item); item = 0; } if (nextBTreeItems(&it) < 0) goto err; } item=PyObject_GetAttr(r,sort_str); UNLESS (item) goto err; ASSIGN(item, PyObject_CallObject(item, NULL)); UNLESS (item) goto err; ASSIGN(item, PyObject_GetAttr(r, reverse_str)); UNLESS (item) goto err; ASSIGN(item, PyObject_CallObject(item, NULL)); UNLESS (item) goto err; Py_DECREF(item); finiSetIteration(&it); PER_UNUSE(self); return r; err: PER_UNUSE(self); Py_XDECREF(r); finiSetIteration(&it); Py_XDECREF(item); return NULL; } /* ** BTree_getm */ static PyObject * BTree_getm(BTree *self, PyObject *args) { PyObject *key, *d=Py_None, *r; UNLESS (PyArg_ParseTuple(args, "O|O", &key, &d)) return NULL; if ((r=_BTree_get(self, key, 0))) return r; UNLESS (PyErr_ExceptionMatches(PyExc_KeyError)) return NULL; PyErr_Clear(); Py_INCREF(d); return d; } static PyObject * BTree_has_key(BTree *self, PyObject *key) { return _BTree_get(self, key, 1); } static PyObject * BTree_setdefault(BTree *self, PyObject *args) { PyObject *key; PyObject *failobj; /* default */ PyObject *value; /* return value */ if (! PyArg_UnpackTuple(args, "setdefault", 2, 2, &key, &failobj)) return NULL; value = _BTree_get(self, key, 0); if (value != NULL) return value; /* The key isn't in the tree. If that's not due to a KeyError exception, * pass back the unexpected exception. */ if (! PyErr_ExceptionMatches(PyExc_KeyError)) return NULL; PyErr_Clear(); /* Associate `key` with `failobj` in the tree, and return `failobj`. */ value = failobj; if (_BTree_set(self, key, failobj, 0, 0) < 0) value = NULL; Py_XINCREF(value); return value; } /* forward declaration */ static Py_ssize_t BTree_length_or_nonzero(BTree *self, int nonzero); static PyObject * BTree_pop(BTree *self, PyObject *args) { PyObject *key; PyObject *failobj = NULL; /* default */ PyObject *value; /* return value */ if (! PyArg_UnpackTuple(args, "pop", 1, 2, &key, &failobj)) return NULL; value = _BTree_get(self, key, 0); if (value != NULL) { /* Delete key and associated value. */ if (_BTree_set(self, key, NULL, 0, 0) < 0) { Py_DECREF(value); return NULL;; } return value; } /* The key isn't in the tree. If that's not due to a KeyError exception, * pass back the unexpected exception. */ if (! PyErr_ExceptionMatches(PyExc_KeyError)) return NULL; if (failobj != NULL) { /* Clear the KeyError and return the explicit default. */ PyErr_Clear(); Py_INCREF(failobj); return failobj; } /* No default given. The only difference in this case is the error * message, which depends on whether the tree is empty. */ if (BTree_length_or_nonzero(self, 1) == 0) /* tree is empty */ PyErr_SetString(PyExc_KeyError, "pop(): BTree is empty"); return NULL; } /* Search BTree self for key. This is the sq_contains slot of the * PySequenceMethods. * * Return: * -1 error * 0 not found * 1 found */ static int BTree_contains(BTree *self, PyObject *key) { PyObject *asobj = _BTree_get(self, key, 1); int result = -1; if (asobj != NULL) { result = PyInt_AsLong(asobj) ? 1 : 0; Py_DECREF(asobj); } return result; } static PyObject * BTree_addUnique(BTree *self, PyObject *args) { int grew; PyObject *key, *v; UNLESS (PyArg_ParseTuple(args, "OO", &key, &v)) return NULL; if ((grew=_BTree_set(self, key, v, 1, 0)) < 0) return NULL; return PyInt_FromLong(grew); } /**************************************************************************/ /* Iterator support. */ /* A helper to build all the iterators for BTrees and TreeSets. * If args is NULL, the iterator spans the entire structure. Else it's an * argument tuple, with optional low and high arguments. * kind is 'k', 'v' or 'i'. * Returns a BTreeIter object, or NULL if error. */ static PyObject * buildBTreeIter(BTree *self, PyObject *args, PyObject *kw, char kind) { BTreeIter *result = NULL; BTreeItems *items = (BTreeItems *)BTree_rangeSearch(self, args, kw, kind); if (items) { result = BTreeIter_new(items); Py_DECREF(items); } return (PyObject *)result; } /* The implementation of iter(BTree_or_TreeSet); the BTree tp_iter slot. */ static PyObject * BTree_getiter(BTree *self) { return buildBTreeIter(self, NULL, NULL, 'k'); } /* The implementation of BTree.iterkeys(). */ static PyObject * BTree_iterkeys(BTree *self, PyObject *args, PyObject *kw) { return buildBTreeIter(self, args, kw, 'k'); } /* The implementation of BTree.itervalues(). */ static PyObject * BTree_itervalues(BTree *self, PyObject *args, PyObject *kw) { return buildBTreeIter(self, args, kw, 'v'); } /* The implementation of BTree.iteritems(). */ static PyObject * BTree_iteritems(BTree *self, PyObject *args, PyObject *kw) { return buildBTreeIter(self, args, kw, 'i'); } /* End of iterator support. */ /* Caution: Even though the _firstbucket attribute is read-only, a program could do arbitrary damage to the btree internals. For example, it could call clear() on a bucket inside a BTree. We need to decide if the convenience for inspecting BTrees is worth the risk. */ static struct PyMemberDef BTree_members[] = { {"_firstbucket", T_OBJECT, offsetof(BTree, firstbucket), RO}, {NULL} }; static struct PyMethodDef BTree_methods[] = { {"__getstate__", (PyCFunction) BTree_getstate, METH_NOARGS, "__getstate__() -> state\n\n" "Return the picklable state of the BTree."}, {"__setstate__", (PyCFunction) BTree_setstate, METH_O, "__setstate__(state)\n\n" "Set the state of the BTree."}, {"has_key", (PyCFunction) BTree_has_key, METH_O, "has_key(key)\n\n" "Return true if the BTree contains the given key."}, {"keys", (PyCFunction) BTree_keys, METH_KEYWORDS, "keys([min, max]) -> list of keys\n\n" "Returns the keys of the BTree. If min and max are supplied, only\n" "keys greater than min and less than max are returned."}, {"values", (PyCFunction) BTree_values, METH_KEYWORDS, "values([min, max]) -> list of values\n\n" "Returns the values of the BTree. If min and max are supplied, only\n" "values corresponding to keys greater than min and less than max are\n" "returned."}, {"items", (PyCFunction) BTree_items, METH_KEYWORDS, "items([min, max]) -> -- list of key, value pairs\n\n" "Returns the items of the BTree. If min and max are supplied, only\n" "items with keys greater than min and less than max are returned."}, {"byValue", (PyCFunction) BTree_byValue, METH_O, "byValue(min) -> list of value, key pairs\n\n" "Returns list of value, key pairs where the value is >= min. The\n" "list is sorted by value. Note that items() returns keys in the\n" "opposite order."}, {"get", (PyCFunction) BTree_getm, METH_VARARGS, "get(key[, default=None]) -> Value for key or default\n\n" "Return the value or the default if the key is not found."}, {"setdefault", (PyCFunction) BTree_setdefault, METH_VARARGS, "D.setdefault(k, d) -> D.get(k, d), also set D[k]=d if k not in D.\n\n" "Return the value like get() except that if key is missing, d is both\n" "returned and inserted into the BTree as the value of k."}, {"pop", (PyCFunction) BTree_pop, METH_VARARGS, "D.pop(k[, d]) -> v, remove key and return the corresponding value.\n\n" "If key is not found, d is returned if given, otherwise KeyError\n" "is raised."}, {"maxKey", (PyCFunction) BTree_maxKey, METH_VARARGS, "maxKey([max]) -> key\n\n" "Return the largest key in the BTree. If max is specified, return\n" "the largest key <= max."}, {"minKey", (PyCFunction) BTree_minKey, METH_VARARGS, "minKey([mi]) -> key\n\n" "Return the smallest key in the BTree. If min is specified, return\n" "the smallest key >= min."}, {"clear", (PyCFunction) BTree_clear, METH_NOARGS, "clear()\n\nRemove all of the items from the BTree."}, {"insert", (PyCFunction)BTree_addUnique, METH_VARARGS, "insert(key, value) -> 0 or 1\n\n" "Add an item if the key is not already used. Return 1 if the item was\n" "added, or 0 otherwise."}, {"update", (PyCFunction) Mapping_update, METH_O, "update(collection)\n\n Add the items from the given collection."}, {"iterkeys", (PyCFunction) BTree_iterkeys, METH_KEYWORDS, "B.iterkeys([min[,max]]) -> an iterator over the keys of B"}, {"itervalues", (PyCFunction) BTree_itervalues, METH_KEYWORDS, "B.itervalues([min[,max]]) -> an iterator over the values of B"}, {"iteritems", (PyCFunction) BTree_iteritems, METH_KEYWORDS, "B.iteritems([min[,max]]) -> an iterator over the (key, value) items of B"}, {"_check", (PyCFunction) BTree_check, METH_NOARGS, "Perform sanity check on BTree, and raise exception if flawed."}, #ifdef PERSISTENT {"_p_resolveConflict", (PyCFunction) BTree__p_resolveConflict, METH_VARARGS, "_p_resolveConflict() -- Reinitialize from a newly created copy"}, {"_p_deactivate", (PyCFunction) BTree__p_deactivate, METH_KEYWORDS, "_p_deactivate()\n\nReinitialize from a newly created copy."}, #endif {NULL, NULL} }; static int BTree_init(PyObject *self, PyObject *args, PyObject *kwds) { PyObject *v = NULL; if (!PyArg_ParseTuple(args, "|O:" MOD_NAME_PREFIX "BTree", &v)) return -1; if (v) return update_from_seq(self, v); else return 0; } static void BTree_dealloc(BTree *self) { if (self->state != cPersistent_GHOST_STATE) _BTree_clear(self); cPersistenceCAPI->pertype->tp_dealloc((PyObject *)self); } static int BTree_traverse(BTree *self, visitproc visit, void *arg) { int err = 0; int i, len; #define VISIT(SLOT) \ if (SLOT) { \ err = visit((PyObject *)(SLOT), arg); \ if (err) \ goto Done; \ } if (self->ob_type == &BTreeType) assert(self->ob_type->tp_dictoffset == 0); /* Call our base type's traverse function. Because BTrees are * subclasses of Peristent, there must be one. */ err = cPersistenceCAPI->pertype->tp_traverse((PyObject *)self, visit, arg); if (err) goto Done; /* If this is registered with the persistence system, cleaning up cycles * is the database's problem. It would be horrid to unghostify BTree * nodes here just to chase pointers every time gc runs. */ if (self->state == cPersistent_GHOST_STATE) goto Done; len = self->len; #ifdef KEY_TYPE_IS_PYOBJECT /* Keys are Python objects so need to be traversed. Note that the * key 0 slot is unused and should not be traversed. */ for (i = 1; i < len; i++) VISIT(self->data[i].key); #endif /* Children are always pointers, and child 0 is legit. */ for (i = 0; i < len; i++) VISIT(self->data[i].child); VISIT(self->firstbucket); Done: return err; #undef VISIT } static int BTree_tp_clear(BTree *self) { if (self->state != cPersistent_GHOST_STATE) _BTree_clear(self); return 0; } /* * Return the number of elements in a BTree. nonzero is a Boolean, and * when true requests just a non-empty/empty result. Testing for emptiness * is efficient (constant-time). Getting the true length takes time * proportional to the number of leaves (buckets). * * Return: * When nonzero true: * -1 error * 0 empty * 1 not empty * When nonzero false (possibly expensive!): * -1 error * >= 0 number of elements. */ static Py_ssize_t BTree_length_or_nonzero(BTree *self, int nonzero) { int result; Bucket *b; Bucket *next; PER_USE_OR_RETURN(self, -1); b = self->firstbucket; PER_UNUSE(self); if (nonzero) return b != NULL; result = 0; while (b) { PER_USE_OR_RETURN(b, -1); result += b->len; next = b->next; PER_UNUSE(b); b = next; } return result; } static Py_ssize_t BTree_length(BTree *self) { return BTree_length_or_nonzero(self, 0); } static PyMappingMethods BTree_as_mapping = { (lenfunc)BTree_length, /*mp_length*/ (binaryfunc)BTree_get, /*mp_subscript*/ (objobjargproc)BTree_setitem, /*mp_ass_subscript*/ }; static PySequenceMethods BTree_as_sequence = { (lenfunc)0, /* sq_length */ (binaryfunc)0, /* sq_concat */ (ssizeargfunc)0, /* sq_repeat */ (ssizeargfunc)0, /* sq_item */ (ssizessizeargfunc)0, /* sq_slice */ (ssizeobjargproc)0, /* sq_ass_item */ (ssizessizeobjargproc)0, /* sq_ass_slice */ (objobjproc)BTree_contains, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }; static Py_ssize_t BTree_nonzero(BTree *self) { return BTree_length_or_nonzero(self, 1); } static PyNumberMethods BTree_as_number_for_nonzero = { 0,0,0,0,0,0,0,0,0,0, (inquiry)BTree_nonzero}; static PyTypeObject BTreeType = { PyObject_HEAD_INIT(NULL) /* PyPersist_Type */ 0, /* ob_size */ MODULE_NAME MOD_NAME_PREFIX "BTree",/* tp_name */ sizeof(BTree), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)BTree_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ &BTree_as_number_for_nonzero, /* tp_as_number */ &BTree_as_sequence, /* tp_as_sequence */ &BTree_as_mapping, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ (traverseproc)BTree_traverse, /* tp_traverse */ (inquiry)BTree_tp_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)BTree_getiter, /* tp_iter */ 0, /* tp_iternext */ BTree_methods, /* tp_methods */ BTree_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ BTree_init, /* tp_init */ 0, /* tp_alloc */ 0, /*PyType_GenericNew,*/ /* tp_new */ }; zope2.13-2.13.21/source/ZODB3/src/BTrees/_LLBTree.c0000644000175000017500000000213412214017464017770 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ #define MASTER_ID "$Id: _IIBTree.c 25186 2004-06-02 15:07:33Z jim $\n" /* IIBTree - int key, int value BTree Implements a collection using int type keys and int type values */ /* Setup template macros */ #define PERSISTENT #define MOD_NAME_PREFIX "LL" #define INITMODULE init_LLBTree #define DEFAULT_MAX_BUCKET_SIZE 120 #define DEFAULT_MAX_BTREE_SIZE 500 #define ZODB_64BIT_INTS #include "intkeymacros.h" #include "intvaluemacros.h" #include "BTreeModuleTemplate.c" zope2.13-2.13.21/source/ZODB3/src/BTrees/BTreeItemsTemplate.c0000644000175000017500000005001112214017464022074 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #define BTREEITEMSTEMPLATE_C "$Id: BTreeItemsTemplate.c 113734 2010-06-21 15:33:46Z ctheune $\n" /* A BTreeItems struct is returned from calling .items(), .keys() or * .values() on a BTree-based data structure, and is also the result of * taking slices of those. It represents a contiguous slice of a BTree. * * The start of the slice is in firstbucket, at offset first. The end of * the slice is in lastbucket, at offset last. Both endpoints are inclusive. * It must possible to get from firstbucket to lastbucket via following * bucket 'next' pointers zero or more times. firstbucket, first, lastbucket, * and last are readonly after initialization. An empty slice is represented * by firstbucket == lastbucket == currentbucket == NULL. * * 'kind' determines whether this slice represents 'k'eys alone, 'v'alues * alone, or 'i'items (key+value pairs). 'kind' is also readonly after * initialization. * * The combination of currentbucket, currentoffset and pseudoindex acts as * a search finger. Offset currentoffset in bucket currentbucket is at index * pseudoindex, where pseudoindex==0 corresponds to offset first in bucket * firstbucket, and pseudoindex==-1 corresponds to offset last in bucket * lastbucket. The function BTreeItems_seek() can be used to set this combo * correctly for any in-bounds index, and uses this combo on input to avoid * needing to search from the start (or end) on each call. Calling * BTreeItems_seek() with consecutive larger positions is very efficent. * Calling it with consecutive smaller positions is more efficient than if * a search finger weren't being used at all, but is still quadratic time * in the number of buckets in the slice. */ typedef struct { PyObject_HEAD Bucket *firstbucket; /* First bucket */ Bucket *currentbucket; /* Current bucket (search finger) */ Bucket *lastbucket; /* Last bucket */ int currentoffset; /* Offset in currentbucket */ int pseudoindex; /* search finger index */ int first; /* Start offset in firstbucket */ int last; /* End offset in lastbucket */ char kind; /* 'k', 'v', 'i' */ } BTreeItems; #define ITEMS(O)((BTreeItems*)(O)) static PyObject * newBTreeItems(char kind, Bucket *lowbucket, int lowoffset, Bucket *highbucket, int highoffset); static void BTreeItems_dealloc(BTreeItems *self) { Py_XDECREF(self->firstbucket); Py_XDECREF(self->lastbucket); Py_XDECREF(self->currentbucket); PyObject_DEL(self); } static Py_ssize_t BTreeItems_length_or_nonzero(BTreeItems *self, int nonzero) { Py_ssize_t r; Bucket *b, *next; b = self->firstbucket; if (b == NULL) return 0; r = self->last + 1 - self->first; if (nonzero && r > 0) /* Short-circuit if all we care about is nonempty */ return 1; if (b == self->lastbucket) return r; Py_INCREF(b); PER_USE_OR_RETURN(b, -1); while ((next = b->next)) { r += b->len; if (nonzero && r > 0) /* Short-circuit if all we care about is nonempty */ break; if (next == self->lastbucket) break; /* we already counted the last bucket */ Py_INCREF(next); PER_UNUSE(b); Py_DECREF(b); b = next; PER_USE_OR_RETURN(b, -1); } PER_UNUSE(b); Py_DECREF(b); return r >= 0 ? r : 0; } static Py_ssize_t BTreeItems_length(BTreeItems *self) { return BTreeItems_length_or_nonzero(self, 0); } /* ** BTreeItems_seek ** ** Find the ith position in the BTreeItems. ** ** Arguments: self The BTree ** i the index to seek to, in 0 .. len(self)-1, or in ** -len(self) .. -1, as for indexing a Python sequence. ** ** ** Returns 0 if successful, -1 on failure to seek (like out-of-bounds). ** Upon successful return, index i is at offset self->currentoffset in bucket ** self->currentbucket. */ static int BTreeItems_seek(BTreeItems *self, Py_ssize_t i) { int delta, pseudoindex, currentoffset; Bucket *b, *currentbucket; int error; pseudoindex = self->pseudoindex; currentoffset = self->currentoffset; currentbucket = self->currentbucket; if (currentbucket == NULL) goto no_match; delta = i - pseudoindex; while (delta > 0) { /* move right */ int max; /* Want to move right delta positions; the most we can move right in * this bucket is currentbucket->len - currentoffset - 1 positions. */ PER_USE_OR_RETURN(currentbucket, -1); max = currentbucket->len - currentoffset - 1; b = currentbucket->next; PER_UNUSE(currentbucket); if (delta <= max) { currentoffset += delta; pseudoindex += delta; if (currentbucket == self->lastbucket && currentoffset > self->last) goto no_match; break; } /* Move to start of next bucket. */ if (currentbucket == self->lastbucket || b == NULL) goto no_match; currentbucket = b; pseudoindex += max + 1; delta -= max + 1; currentoffset = 0; } while (delta < 0) { /* move left */ int status; /* Want to move left -delta positions; the most we can move left in * this bucket is currentoffset positions. */ if ((-delta) <= currentoffset) { currentoffset += delta; pseudoindex += delta; if (currentbucket == self->firstbucket && currentoffset < self->first) goto no_match; break; } /* Move to end of previous bucket. */ if (currentbucket == self->firstbucket) goto no_match; status = PreviousBucket(¤tbucket, self->firstbucket); if (status == 0) goto no_match; else if (status < 0) return -1; pseudoindex -= currentoffset + 1; delta += currentoffset + 1; PER_USE_OR_RETURN(currentbucket, -1); currentoffset = currentbucket->len - 1; PER_UNUSE(currentbucket); } assert(pseudoindex == i); /* Alas, the user may have mutated the bucket since the last time we * were called, and if they deleted stuff, we may be pointing into * trash memory now. */ PER_USE_OR_RETURN(currentbucket, -1); error = currentoffset < 0 || currentoffset >= currentbucket->len; PER_UNUSE(currentbucket); if (error) { PyErr_SetString(PyExc_RuntimeError, "the bucket being iterated changed size"); return -1; } Py_INCREF(currentbucket); Py_DECREF(self->currentbucket); self->currentbucket = currentbucket; self->currentoffset = currentoffset; self->pseudoindex = pseudoindex; return 0; no_match: IndexError(i); return -1; } /* Return the right kind ('k','v','i') of entry from bucket b at offset i. * b must be activated. Returns NULL on error. */ static PyObject * getBucketEntry(Bucket *b, int i, char kind) { PyObject *result = NULL; assert(b); assert(0 <= i && i < b->len); switch (kind) { case 'k': COPY_KEY_TO_OBJECT(result, b->keys[i]); break; case 'v': COPY_VALUE_TO_OBJECT(result, b->values[i]); break; case 'i': { PyObject *key; PyObject *value;; COPY_KEY_TO_OBJECT(key, b->keys[i]); if (!key) break; COPY_VALUE_TO_OBJECT(value, b->values[i]); if (!value) { Py_DECREF(key); break; } result = PyTuple_New(2); if (result) { PyTuple_SET_ITEM(result, 0, key); PyTuple_SET_ITEM(result, 1, value); } else { Py_DECREF(key); Py_DECREF(value); } break; } default: PyErr_SetString(PyExc_AssertionError, "getBucketEntry: unknown kind"); break; } return result; } /* ** BTreeItems_item ** ** Arguments: self a BTreeItems structure ** i Which item to inspect ** ** Returns: the BTreeItems_item_BTree of self->kind, i ** (ie pulls the ith item out) */ static PyObject * BTreeItems_item(BTreeItems *self, Py_ssize_t i) { PyObject *result; if (BTreeItems_seek(self, i) < 0) return NULL; PER_USE_OR_RETURN(self->currentbucket, NULL); result = getBucketEntry(self->currentbucket, self->currentoffset, self->kind); PER_UNUSE(self->currentbucket); return result; } /* ** BTreeItems_slice ** ** Creates a new BTreeItems structure representing the slice ** between the low and high range ** ** Arguments: self The old BTreeItems structure ** ilow The start index ** ihigh The end index ** ** Returns: BTreeItems item */ static PyObject * BTreeItems_slice(BTreeItems *self, Py_ssize_t ilow, Py_ssize_t ihigh) { Bucket *lowbucket; Bucket *highbucket; int lowoffset; int highoffset; Py_ssize_t length = -1; /* len(self), but computed only if needed */ /* Complications: * A Python slice never raises IndexError, but BTreeItems_seek does. * Python did only part of index normalization before calling this: * ilow may be < 0 now, and ihigh may be arbitrarily large. It's * our responsibility to clip them. * A Python slice is exclusive of the high index, but a BTreeItems * struct is inclusive on both ends. */ /* First adjust ilow and ihigh to be legit endpoints in the Python * sense (ilow inclusive, ihigh exclusive). This block duplicates the * logic from Python's list_slice function (slicing for builtin lists). */ if (ilow < 0) ilow = 0; else { if (length < 0) length = BTreeItems_length(self); if (ilow > length) ilow = length; } if (ihigh < ilow) ihigh = ilow; else { if (length < 0) length = BTreeItems_length(self); if (ihigh > length) ihigh = length; } assert(0 <= ilow && ilow <= ihigh); assert(length < 0 || ihigh <= length); /* Now adjust for that our struct is inclusive on both ends. This is * easy *except* when the slice is empty: there's no good way to spell * that in an inclusive-on-both-ends scheme. For example, if the * slice is btree.items([:0]), ilow == ihigh == 0 at this point, and if * we were to subtract 1 from ihigh that would get interpreted by * BTreeItems_seek as meaning the *entire* set of items. Setting ilow==1 * and ihigh==0 doesn't work either, as BTreeItems_seek raises IndexError * if we attempt to seek to ilow==1 when the underlying sequence is empty. * It seems simplest to deal with empty slices as a special case here. */ if (ilow == ihigh) { /* empty slice */ lowbucket = highbucket = NULL; lowoffset = 1; highoffset = 0; } else { assert(ilow < ihigh); --ihigh; /* exclusive -> inclusive */ if (BTreeItems_seek(self, ilow) < 0) return NULL; lowbucket = self->currentbucket; lowoffset = self->currentoffset; if (BTreeItems_seek(self, ihigh) < 0) return NULL; highbucket = self->currentbucket; highoffset = self->currentoffset; } return newBTreeItems(self->kind, lowbucket, lowoffset, highbucket, highoffset); } static PySequenceMethods BTreeItems_as_sequence = { (lenfunc) BTreeItems_length, (binaryfunc)0, (ssizeargfunc)0, (ssizeargfunc) BTreeItems_item, (ssizessizeargfunc) BTreeItems_slice, }; /* Number Method items (just for nb_nonzero!) */ static int BTreeItems_nonzero(BTreeItems *self) { return BTreeItems_length_or_nonzero(self, 1); } static PyNumberMethods BTreeItems_as_number_for_nonzero = { 0,0,0,0,0,0,0,0,0,0, (inquiry)BTreeItems_nonzero}; static PyTypeObject BTreeItemsType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ MOD_NAME_PREFIX "BTreeItems", /*tp_name*/ sizeof(BTreeItems), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor) BTreeItems_dealloc, /*tp_dealloc*/ (printfunc)0, /*tp_print*/ (getattrfunc)0, /*obsolete tp_getattr*/ (setattrfunc)0, /*obsolete tp_setattr*/ (cmpfunc)0, /*tp_compare*/ (reprfunc)0, /*tp_repr*/ &BTreeItems_as_number_for_nonzero, /*tp_as_number*/ &BTreeItems_as_sequence, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ (hashfunc)0, /*tp_hash*/ (ternaryfunc)0, /*tp_call*/ (reprfunc)0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ /* Space for future expansion */ 0L,0L, "Sequence type used to iterate over BTree items." /* Documentation string */ }; /* Returns a new BTreeItems object representing the contiguous slice from * offset lowoffset in bucket lowbucket through offset highoffset in bucket * highbucket, inclusive. Pass lowbucket == NULL for an empty slice. * The currentbucket is set to lowbucket, currentoffset ot lowoffset, and * pseudoindex to 0. kind is 'k', 'v' or 'i' (see BTreeItems struct docs). */ static PyObject * newBTreeItems(char kind, Bucket *lowbucket, int lowoffset, Bucket *highbucket, int highoffset) { BTreeItems *self; UNLESS (self = PyObject_NEW(BTreeItems, &BTreeItemsType)) return NULL; self->kind=kind; self->first=lowoffset; self->last=highoffset; if (! lowbucket || ! highbucket || (lowbucket == highbucket && lowoffset > highoffset)) { self->firstbucket = 0; self->lastbucket = 0; self->currentbucket = 0; } else { Py_INCREF(lowbucket); self->firstbucket = lowbucket; Py_INCREF(highbucket); self->lastbucket = highbucket; Py_INCREF(lowbucket); self->currentbucket = lowbucket; } self->currentoffset = lowoffset; self->pseudoindex = 0; return OBJECT(self); } static int nextBTreeItems(SetIteration *i) { if (i->position >= 0) { if (i->position) { DECREF_KEY(i->key); DECREF_VALUE(i->value); } if (BTreeItems_seek(ITEMS(i->set), i->position) >= 0) { Bucket *currentbucket; currentbucket = BUCKET(ITEMS(i->set)->currentbucket); UNLESS(PER_USE(currentbucket)) { /* Mark iteration terminated, so that finiSetIteration doesn't * try to redundantly decref the key and value */ i->position = -1; return -1; } COPY_KEY(i->key, currentbucket->keys[ITEMS(i->set)->currentoffset]); INCREF_KEY(i->key); COPY_VALUE(i->value, currentbucket->values[ITEMS(i->set)->currentoffset]); INCREF_VALUE(i->value); i->position ++; PER_UNUSE(currentbucket); } else { i->position = -1; PyErr_Clear(); } } return 0; } static int nextTreeSetItems(SetIteration *i) { if (i->position >= 0) { if (i->position) { DECREF_KEY(i->key); } if (BTreeItems_seek(ITEMS(i->set), i->position) >= 0) { Bucket *currentbucket; currentbucket = BUCKET(ITEMS(i->set)->currentbucket); UNLESS(PER_USE(currentbucket)) { /* Mark iteration terminated, so that finiSetIteration doesn't * try to redundantly decref the key and value */ i->position = -1; return -1; } COPY_KEY(i->key, currentbucket->keys[ITEMS(i->set)->currentoffset]); INCREF_KEY(i->key); i->position ++; PER_UNUSE(currentbucket); } else { i->position = -1; PyErr_Clear(); } } return 0; } /* Support for the iteration protocol new in Python 2.2. */ static PyTypeObject BTreeIter_Type; /* The type of iterator objects, returned by e.g. iter(IIBTree()). */ typedef struct { PyObject_HEAD /* We use a BTreeItems object because it's convenient and flexible. * We abuse it two ways: * 1. We set currentbucket to NULL when the iteration is finished. * 2. We don't bother keeping pseudoindex in synch. */ BTreeItems *pitems; } BTreeIter; /* Return a new iterator object, to traverse the keys and/or values * represented by pitems. pitems must not be NULL. Returns NULL if error. */ static BTreeIter * BTreeIter_new(BTreeItems *pitems) { BTreeIter *result; assert(pitems != NULL); result = PyObject_New(BTreeIter, &BTreeIter_Type); if (result) { Py_INCREF(pitems); result->pitems = pitems; } return result; } /* The iterator's tp_dealloc slot. */ static void BTreeIter_dealloc(BTreeIter *bi) { Py_DECREF(bi->pitems); PyObject_Del(bi); } /* The implementation of the iterator's tp_iternext slot. Returns "the next" * item; returns NULL if error; returns NULL without setting an error if the * iteration is exhausted (that's the way to terminate the iteration protocol). */ static PyObject * BTreeIter_next(BTreeIter *bi, PyObject *args) { PyObject *result = NULL; /* until proven innocent */ BTreeItems *items = bi->pitems; int i = items->currentoffset; Bucket *bucket = items->currentbucket; if (bucket == NULL) /* iteration termination is sticky */ return NULL; PER_USE_OR_RETURN(bucket, NULL); if (i >= bucket->len) { /* We never leave this routine normally with i >= len: somebody * else mutated the current bucket. */ PyErr_SetString(PyExc_RuntimeError, "the bucket being iterated changed size"); /* Arrange for that this error is sticky too. */ items->currentoffset = INT_MAX; goto Done; } /* Build the result object, from bucket at offset i. */ result = getBucketEntry(bucket, i, items->kind); /* Advance position for next call. */ if (bucket == items->lastbucket && i >= items->last) { /* Next call should terminate the iteration. */ Py_DECREF(items->currentbucket); items->currentbucket = NULL; } else { ++i; if (i >= bucket->len) { Py_XINCREF(bucket->next); items->currentbucket = bucket->next; Py_DECREF(bucket); i = 0; } items->currentoffset = i; } Done: PER_UNUSE(bucket); return result; } static PyObject * BTreeIter_getiter(PyObject *it) { Py_INCREF(it); return it; } static PyTypeObject BTreeIter_Type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ MOD_NAME_PREFIX "-iterator", /* tp_name */ sizeof(BTreeIter), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ (destructor)BTreeIter_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /*PyObject_GenericGetAttr,*/ /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)BTreeIter_getiter, /* tp_iter */ (iternextfunc)BTreeIter_next, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ }; zope2.13-2.13.21/source/ZODB3/src/BTrees/LFBTree.py0000644000175000017500000000150112214017464020026 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import zope.interface import BTrees.Interfaces # hack to overcome dynamic-linking headache. from _LFBTree import * zope.interface.moduleProvides(BTrees.Interfaces.IIntegerFloatBTreeModule) zope2.13-2.13.21/source/ZODB3/src/BTrees/_fsBTree.c0000644000175000017500000001104312214017464020070 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ #define MASTER_ID "$Id: _fsBTree.c 113734 2010-06-21 15:33:46Z ctheune $\n" /* fsBTree - FileStorage index BTree This BTree implements a mapping from 2-character strings to six-character strings. This allows us to efficiently store a FileStorage index as a nested mapping of 6-character oid prefix to mapping of 2-character oid suffix to 6-character (byte) file positions. */ typedef unsigned char char2[2]; typedef unsigned char char6[6]; /* Setup template macros */ #define PERSISTENT #define MOD_NAME_PREFIX "fs" #define INITMODULE init_fsBTree #define DEFAULT_MAX_BUCKET_SIZE 500 #define DEFAULT_MAX_BTREE_SIZE 500 /*#include "intkeymacros.h"*/ #define KEYMACROS_H "$Id: _fsBTree.c 113734 2010-06-21 15:33:46Z ctheune $\n" #define KEY_TYPE char2 #undef KEY_TYPE_IS_PYOBJECT #define KEY_CHECK(K) (PyString_Check(K) && PyString_GET_SIZE(K)==2) #define TEST_KEY_SET_OR(V, K, T) if ( ( (V) = ((*(K) < *(T) || (*(K) == *(T) && (K)[1] < (T)[1])) ? -1 : ((*(K) == *(T) && (K)[1] == (T)[1]) ? 0 : 1)) ), 0 ) #define DECREF_KEY(KEY) #define INCREF_KEY(k) #define COPY_KEY(KEY, E) (*(KEY)=*(E), (KEY)[1]=(E)[1]) #define COPY_KEY_TO_OBJECT(O, K) O=PyString_FromStringAndSize((const char*)K,2) #define COPY_KEY_FROM_ARG(TARGET, ARG, STATUS) \ if (KEY_CHECK(ARG)) memcpy(TARGET, PyString_AS_STRING(ARG), 2); else { \ PyErr_SetString(PyExc_TypeError, "expected two-character string key"); \ (STATUS)=0; } /*#include "intvaluemacros.h"*/ #define VALUEMACROS_H "$Id: _fsBTree.c 113734 2010-06-21 15:33:46Z ctheune $\n" #define VALUE_TYPE char6 #undef VALUE_TYPE_IS_PYOBJECT #define TEST_VALUE(K, T) memcmp(K,T,6) #define DECREF_VALUE(k) #define INCREF_VALUE(k) #define COPY_VALUE(V, E) (memcpy(V, E, 6)) #define COPY_VALUE_TO_OBJECT(O, K) O=PyString_FromStringAndSize((const char*)K,6) #define COPY_VALUE_FROM_ARG(TARGET, ARG, STATUS) \ if ((PyString_Check(ARG) && PyString_GET_SIZE(ARG)==6)) \ memcpy(TARGET, PyString_AS_STRING(ARG), 6); else { \ PyErr_SetString(PyExc_TypeError, "expected six-character string key"); \ (STATUS)=0; } #define NORMALIZE_VALUE(V, MIN) #include "Python.h" static PyObject *bucket_toString(PyObject *self); static PyObject *bucket_fromString(PyObject *self, PyObject *state); #define EXTRA_BUCKET_METHODS \ {"toString", (PyCFunction) bucket_toString, METH_NOARGS, \ "toString() -- Return the state as a string"}, \ {"fromString", (PyCFunction) bucket_fromString, METH_O, \ "fromString(s) -- Set the state of the object from a string"}, \ #include "BTreeModuleTemplate.c" static PyObject * bucket_toString(PyObject *oself) { Bucket *self = (Bucket *)oself; PyObject *items = NULL; int len; PER_USE_OR_RETURN(self, NULL); len = self->len; items = PyString_FromStringAndSize(NULL, len*8); if (items == NULL) goto err; memcpy(PyString_AS_STRING(items), self->keys, len*2); memcpy(PyString_AS_STRING(items)+len*2, self->values, len*6); PER_UNUSE(self); return items; err: PER_UNUSE(self); Py_XDECREF(items); return NULL; } static PyObject * bucket_fromString(PyObject *oself, PyObject *state) { Bucket *self = (Bucket *)oself; int len; KEY_TYPE *keys; VALUE_TYPE *values; len = PyString_Size(state); if (len < 0) return NULL; if (len%8) { PyErr_SetString(PyExc_ValueError, "state string of wrong size"); return NULL; } len /= 8; if (self->next) { Py_DECREF(self->next); self->next = NULL; } if (len > self->size) { keys = BTree_Realloc(self->keys, sizeof(KEY_TYPE)*len); if (keys == NULL) return NULL; values = BTree_Realloc(self->values, sizeof(VALUE_TYPE)*len); if (values == NULL) return NULL; self->keys = keys; self->values = values; self->size = len; } memcpy(self->keys, PyString_AS_STRING(state), len*2); memcpy(self->values, PyString_AS_STRING(state)+len*2, len*6); self->len = len; Py_INCREF(self); return (PyObject *)self; } zope2.13-2.13.21/source/ZODB3/src/BTrees/OIBTree.py0000644000175000017500000000150212214017464020035 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import zope.interface import BTrees.Interfaces # hack to overcome dynamic-linking headache. from _OIBTree import * zope.interface.moduleProvides(BTrees.Interfaces.IObjectIntegerBTreeModule) zope2.13-2.13.21/source/ZODB3/src/BTrees/check.py0000644000175000017500000003407712214017464017676 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Utilities for working with BTrees (TreeSets, Buckets, and Sets) at a low level. The primary function is check(btree), which performs value-based consistency checks of a kind btree._check() does not perform. See the function docstring for details. display(btree) displays the internal structure of a BTree (TreeSet, etc) to stdout. CAUTION: When a BTree node has only a single bucket child, it can be impossible to get at the bucket from Python code (__getstate__() may squash the bucket object out of existence, as a pickling storage optimization). In such a case, the code here synthesizes a temporary bucket with the same keys (and values, if the bucket is of a mapping type). This has no first-order consequences, but can mislead if you pay close attention to reported object addresses and/or object identity (the synthesized bucket has an address that doesn't exist in the actual BTree). """ from types import TupleType from BTrees.OOBTree import OOBTree, OOBucket, OOSet, OOTreeSet from BTrees.OIBTree import OIBTree, OIBucket, OISet, OITreeSet from BTrees.IOBTree import IOBTree, IOBucket, IOSet, IOTreeSet from BTrees.IIBTree import IIBTree, IIBucket, IISet, IITreeSet from BTrees.IFBTree import IFBTree, IFBucket, IFSet, IFTreeSet from BTrees.OLBTree import OLBTree, OLBucket, OLSet, OLTreeSet from BTrees.LOBTree import LOBTree, LOBucket, LOSet, LOTreeSet from BTrees.LLBTree import LLBTree, LLBucket, LLSet, LLTreeSet from BTrees.LFBTree import LFBTree, LFBucket, LFSet, LFTreeSet from ZODB.utils import positive_id, oid_repr TYPE_UNKNOWN, TYPE_BTREE, TYPE_BUCKET = range(3) _type2kind = {} for kv in ('OO', 'II', 'IO', 'OI', 'IF', 'LL', 'LO', 'OL', 'LF', ): for name, kind in ( ('BTree', (TYPE_BTREE, True)), ('Bucket', (TYPE_BUCKET, True)), ('TreeSet', (TYPE_BTREE, False)), ('Set', (TYPE_BUCKET, False)), ): _type2kind[globals()[kv+name]] = kind # Return pair # # TYPE_BTREE or TYPE_BUCKET, is_mapping def classify(obj): return _type2kind[type(obj)] BTREE_EMPTY, BTREE_ONE, BTREE_NORMAL = range(3) # If the BTree is empty, returns # # BTREE_EMPTY, [], [] # # If the BTree has only one bucket, sometimes returns # # BTREE_ONE, bucket_state, None # # Else returns # # BTREE_NORMAL, list of keys, list of kids # # and the list of kids has one more entry than the list of keys. # # BTree.__getstate__() docs: # # For an empty BTree (self->len == 0), None. # # For a BTree with one child (self->len == 1), and that child is a bucket, # and that bucket has a NULL oid, a one-tuple containing a one-tuple # containing the bucket's state: # # ( # ( # child[0].__getstate__(), # ), # ) # # Else a two-tuple. The first element is a tuple interleaving the BTree's # keys and direct children, of size 2*self->len - 1 (key[0] is unused and # is not saved). The second element is the firstbucket: # # ( # (child[0], key[1], child[1], key[2], child[2], ..., # key[len-1], child[len-1]), # self->firstbucket # ) _btree2bucket = {} for kv in ('OO', 'II', 'IO', 'OI', 'IF', 'LL', 'LO', 'OL', 'LF', ): _btree2bucket[globals()[kv+'BTree']] = globals()[kv+'Bucket'] _btree2bucket[globals()[kv+'TreeSet']] = globals()[kv+'Set'] def crack_btree(t, is_mapping): state = t.__getstate__() if state is None: return BTREE_EMPTY, [], [] assert isinstance(state, TupleType) if len(state) == 1: state = state[0] assert isinstance(state, TupleType) and len(state) == 1 state = state[0] return BTREE_ONE, state, None assert len(state) == 2 data, firstbucket = state n = len(data) assert n & 1 kids = [] keys = [] i = 0 for x in data: if i & 1: keys.append(x) else: kids.append(x) i += 1 return BTREE_NORMAL, keys, kids # Returns # # keys, values # for a mapping; len(keys) == len(values) in this case # or # keys, [] # for a set # # bucket.__getstate__() docs: # # For a set bucket (self->values is NULL), a one-tuple or two-tuple. The # first element is a tuple of keys, of length self->len. The second element # is the next bucket, present if and only if next is non-NULL: # # ( # (keys[0], keys[1], ..., keys[len-1]), # next iff non-NULL> # ) # # For a mapping bucket (self->values is not NULL), a one-tuple or two-tuple. # The first element is a tuple interleaving keys and values, of length # 2 * self->len. The second element is the next bucket, present iff next is # non-NULL: # # ( # (keys[0], values[0], keys[1], values[1], ..., # keys[len-1], values[len-1]), # next iff non-NULL> # ) def crack_bucket(b, is_mapping): state = b.__getstate__() assert isinstance(state, TupleType) assert 1 <= len(state) <= 2 data = state[0] if not is_mapping: return data, [] keys = [] values = [] n = len(data) assert n & 1 == 0 i = 0 for x in data: if i & 1: values.append(x) else: keys.append(x) i += 1 return keys, values def type_and_adr(obj): if hasattr(obj, '_p_oid'): oid = oid_repr(obj._p_oid) else: oid = 'None' return "%s (0x%x oid=%s)" % (type(obj).__name__, positive_id(obj), oid) # Walker implements a depth-first search of a BTree (or TreeSet or Set or # Bucket). Subclasses must implement the visit_btree() and visit_bucket() # methods, and arrange to call the walk() method. walk() calls the # visit_XYZ() methods once for each node in the tree, in depth-first # left-to-right order. class Walker: def __init__(self, obj): self.obj = obj # obj is the BTree (BTree or TreeSet). # path is a list of indices, from the root. For example, if a BTree node # is child[5] of child[3] of the root BTree, [3, 5]. # parent is the parent BTree object, or None if this is the root BTree. # is_mapping is True for a BTree and False for a TreeSet. # keys is a list of the BTree's internal keys. # kids is a list of the BTree's children. # If the BTree is an empty root node, keys == kids == []. # Else len(kids) == len(keys) + 1. # lo and hi are slice bounds on the values the elements of keys *should* # lie in (lo inclusive, hi exclusive). lo is None if there is no lower # bound known, and hi is None if no upper bound is known. def visit_btree(self, obj, path, parent, is_mapping, keys, kids, lo, hi): raise NotImplementedError # obj is the bucket (Bucket or Set). # path is a list of indices, from the root. For example, if a bucket # node is child[5] of child[3] of the root BTree, [3, 5]. # parent is the parent BTree object. # is_mapping is True for a Bucket and False for a Set. # keys is a list of the bucket's keys. # values is a list of the bucket's values. # If is_mapping is false, values == []. Else len(keys) == len(values). # lo and hi are slice bounds on the values the elements of keys *should* # lie in (lo inclusive, hi exclusive). lo is None if there is no lower # bound known, and hi is None if no upper bound is known. def visit_bucket(self, obj, path, parent, is_mapping, keys, values, lo, hi): raise NotImplementedError def walk(self): obj = self.obj path = [] stack = [(obj, path, None, None, None)] while stack: obj, path, parent, lo, hi = stack.pop() kind, is_mapping = classify(obj) if kind is TYPE_BTREE: bkind, keys, kids = crack_btree(obj, is_mapping) if bkind is BTREE_NORMAL: # push the kids, in reverse order (so they're popped off # the stack in forward order) n = len(kids) for i in range(len(kids)-1, -1, -1): newlo, newhi = lo, hi if i < n-1: newhi = keys[i] if i > 0: newlo = keys[i-1] stack.append((kids[i], path + [i], obj, newlo, newhi)) elif bkind is BTREE_EMPTY: pass else: assert bkind is BTREE_ONE # Yuck. There isn't a bucket object to pass on, as # the bucket state is embedded directly in the BTree # state. Synthesize a bucket. assert kids is None # and "keys" is really the bucket # state bucket = _btree2bucket[type(obj)]() bucket.__setstate__(keys) stack.append((bucket, path + [0], obj, lo, hi)) keys = [] kids = [bucket] self.visit_btree(obj, path, parent, is_mapping, keys, kids, lo, hi) else: assert kind is TYPE_BUCKET keys, values = crack_bucket(obj, is_mapping) self.visit_bucket(obj, path, parent, is_mapping, keys, values, lo, hi) class Checker(Walker): def __init__(self, obj): Walker.__init__(self, obj) self.errors = [] def check(self): self.walk() if self.errors: s = "Errors found in %s:" % type_and_adr(self.obj) self.errors.insert(0, s) s = "\n".join(self.errors) raise AssertionError(s) def visit_btree(self, obj, path, parent, is_mapping, keys, kids, lo, hi): self.check_sorted(obj, path, keys, lo, hi) def visit_bucket(self, obj, path, parent, is_mapping, keys, values, lo, hi): self.check_sorted(obj, path, keys, lo, hi) def check_sorted(self, obj, path, keys, lo, hi): i, n = 0, len(keys) for x in keys: if lo is not None and not lo <= x: s = "key %r < lower bound %r at index %d" % (x, lo, i) self.complain(s, obj, path) if hi is not None and not x < hi: s = "key %r >= upper bound %r at index %d" % (x, hi, i) self.complain(s, obj, path) if i < n-1 and not x < keys[i+1]: s = "key %r at index %d >= key %r at index %d" % ( x, i, keys[i+1], i+1) self.complain(s, obj, path) i += 1 def complain(self, msg, obj, path): s = "%s, in %s, path from root %s" % ( msg, type_and_adr(obj), ".".join(map(str, path))) self.errors.append(s) class Printer(Walker): def __init__(self, obj): Walker.__init__(self, obj) def display(self): self.walk() def visit_btree(self, obj, path, parent, is_mapping, keys, kids, lo, hi): indent = " " * len(path) print "%s%s %s with %d children" % ( indent, ".".join(map(str, path)), type_and_adr(obj), len(kids)) indent += " " n = len(keys) for i in range(n): print "%skey %d: %r" % (indent, i, keys[i]) def visit_bucket(self, obj, path, parent, is_mapping, keys, values, lo, hi): indent = " " * len(path) print "%s%s %s with %d keys" % ( indent, ".".join(map(str, path)), type_and_adr(obj), len(keys)) indent += " " n = len(keys) for i in range(n): print "%skey %d: %r" % (indent, i, keys[i]), if is_mapping: print "value %r" % (values[i],) def check(btree): """Check internal value-based invariants in a BTree or TreeSet. The btree._check() method checks internal C-level pointer consistency. The check() function here checks value-based invariants: whether the keys in leaf bucket and internal nodes are in strictly increasing order, and whether they all lie in their expected range. The latter is a subtle invariant that can't be checked locally -- it requires propagating range info down from the root of the tree, and modifying it at each level for each child. Raises AssertionError if anything is wrong, with a string detail explaining the problems. The entire tree is checked before AssertionError is raised, and the string detail may be large (depending on how much went wrong). """ Checker(btree).check() def display(btree): "Display the internal structure of a BTree, Bucket, TreeSet or Set." Printer(btree).display() zope2.13-2.13.21/source/ZODB3/src/BTrees/_LFBTree.c0000644000175000017500000000214512214017464017764 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ #define MASTER_ID "$Id: _IFBTree.c 67074 2006-04-17 19:13:39Z fdrake $\n" /* IFBTree - int key, float value BTree Implements a collection using int type keys and float type values */ /* Setup template macros */ #define PERSISTENT #define MOD_NAME_PREFIX "LF" #define INITMODULE init_LFBTree #define DEFAULT_MAX_BUCKET_SIZE 120 #define DEFAULT_MAX_BTREE_SIZE 500 #define ZODB_64BIT_INTS #include "intkeymacros.h" #include "floatvaluemacros.h" #include "BTreeModuleTemplate.c" zope2.13-2.13.21/source/ZODB3/src/BTrees/intkeymacros.h0000644000175000017500000000345612214017464021125 0ustar arnauarnau #define KEYMACROS_H "$Id: intkeymacros.h 114787 2010-07-15 16:53:11Z jim $\n" #ifdef ZODB_64BIT_INTS /* PY_LONG_LONG as key */ #define NEED_LONG_LONG_SUPPORT #define KEY_TYPE PY_LONG_LONG #define KEY_CHECK longlong_check #define COPY_KEY_TO_OBJECT(O, K) O=longlong_as_object(K) #define COPY_KEY_FROM_ARG(TARGET, ARG, STATUS) \ if (PyInt_Check(ARG)) TARGET=PyInt_AS_LONG(ARG); else \ if (longlong_check(ARG)) TARGET=PyLong_AsLongLong(ARG); else \ if (PyLong_Check(ARG)) { \ PyErr_SetString(PyExc_ValueError, "long integer out of range"); \ (STATUS)=0; (TARGET)=0; } \ else { \ PyErr_SetString(PyExc_TypeError, "expected integer key"); \ (STATUS)=0; (TARGET)=0; } #else /* C int as key */ #define KEY_TYPE int #define KEY_CHECK PyInt_Check #define COPY_KEY_TO_OBJECT(O, K) O=PyInt_FromLong(K) #define COPY_KEY_FROM_ARG(TARGET, ARG, STATUS) \ if (PyInt_Check(ARG)) { \ long vcopy = PyInt_AS_LONG(ARG); \ if ((int)vcopy != vcopy) { \ PyErr_SetString(PyExc_TypeError, "integer out of range"); \ (STATUS)=0; (TARGET)=0; \ } \ else TARGET = vcopy; \ } else { \ PyErr_SetString(PyExc_TypeError, "expected integer key"); \ (STATUS)=0; (TARGET)=0; } #endif #undef KEY_TYPE_IS_PYOBJECT #define TEST_KEY_SET_OR(V, K, T) if ( ( (V) = (((K) < (T)) ? -1 : (((K) > (T)) ? 1: 0)) ) , 0 ) #define DECREF_KEY(KEY) #define INCREF_KEY(k) #define COPY_KEY(KEY, E) (KEY=(E)) #define MULTI_INT_UNION 1 zope2.13-2.13.21/source/ZODB3/src/BTrees/objectkeymacros.h0000644000175000017500000000071512214017464021574 0ustar arnauarnau#define KEYMACROS_H "$Id: objectkeymacros.h 117967 2010-10-27 19:17:39Z jim $\n" #define KEY_TYPE PyObject * #define KEY_TYPE_IS_PYOBJECT #define TEST_KEY_SET_OR(V, KEY, TARGET) if ( ( (V) = PyObject_Compare((KEY),(TARGET)) ), PyErr_Occurred() ) #define INCREF_KEY(k) Py_INCREF(k) #define DECREF_KEY(KEY) Py_DECREF(KEY) #define COPY_KEY(KEY, E) KEY=(E) #define COPY_KEY_TO_OBJECT(O, K) O=(K); Py_INCREF(O) #define COPY_KEY_FROM_ARG(TARGET, ARG, S) TARGET=(ARG) zope2.13-2.13.21/source/ZODB3/src/BTrees/IOBTree.py0000644000175000017500000000150212214017464020035 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import zope.interface import BTrees.Interfaces # hack to overcome dynamic-linking headache. from _IOBTree import * zope.interface.moduleProvides(BTrees.Interfaces.IIntegerObjectBTreeModule) zope2.13-2.13.21/source/ZODB3/src/BTrees/tests/0000755000175000017500000000000012214017464017376 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/BTrees/tests/test_compare.py0000644000175000017500000000534412214017464022443 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test errors during comparison of BTree keys.""" import unittest from BTrees.OOBTree import OOBucket as Bucket, OOSet as Set import transaction from ZODB.MappingStorage import MappingStorage from ZODB.DB import DB class CompareTest(unittest.TestCase): s = "A string with hi-bit-set characters: \700\701" u = u"A unicode string" def setUp(self): # These defaults only make sense if the default encoding # prevents s from being promoted to Unicode. self.assertRaises(UnicodeError, unicode, self.s) # An object needs to be added to the database to self.db = DB(MappingStorage()) root = self.db.open().root() self.bucket = root["bucket"] = Bucket() self.set = root["set"] = Set() transaction.commit() def tearDown(self): self.assert_(self.bucket._p_changed != 2) self.assert_(self.set._p_changed != 2) transaction.abort() def assertUE(self, callable, *args): self.assertRaises(UnicodeError, callable, *args) def testBucketGet(self): import sys import warnings _warnlog = [] def _showwarning(*args, **kw): _warnlog.append((args, kw)) warnings.showwarning, _before = _showwarning, warnings.showwarning try: self.bucket[self.s] = 1 self.assertUE(self.bucket.get, self.u) finally: warnings.showwarning = _before if sys.version_info >= (2, 6): self.assertEqual(len(_warnlog), 1) def testSetGet(self): self.set.insert(self.s) self.assertUE(self.set.remove, self.u) def testBucketSet(self): self.bucket[self.s] = 1 self.assertUE(self.bucket.__setitem__, self.u, 1) def testSetSet(self): self.set.insert(self.s) self.assertUE(self.set.insert, self.u) def testBucketMinKey(self): self.bucket[self.s] = 1 self.assertUE(self.bucket.minKey, self.u) def testSetMinKey(self): self.set.insert(self.s) self.assertUE(self.set.minKey, self.u) def test_suite(): return unittest.makeSuite(CompareTest) zope2.13-2.13.21/source/ZODB3/src/BTrees/tests/testLength.py0000644000175000017500000000327312214017464022076 0ustar arnauarnau############################################################################## # # Copyright (c) 2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """\ Test for BTrees.Length module. """ __docformat__ = "reStructuredText" import BTrees.Length import copy import sys import unittest class LengthTestCase(unittest.TestCase): def test_length_overflows_to_long(self): length = BTrees.Length.Length(sys.maxint) self.assertEqual(length(), sys.maxint) self.assert_(type(length()) is int) length.change(+1) self.assertEqual(length(), sys.maxint + 1) self.assert_(type(length()) is long) def test_length_underflows_to_long(self): minint = (-sys.maxint) - 1 length = BTrees.Length.Length(minint) self.assertEqual(length(), minint) self.assert_(type(length()) is int) length.change(-1) self.assertEqual(length(), minint - 1) self.assert_(type(length()) is long) def test_copy(self): # Test for https://bugs.launchpad.net/zodb/+bug/516653 length = BTrees.Length.Length() other = copy.copy(length) self.assertEqual(other(), 0) def test_suite(): return unittest.makeSuite(LengthTestCase) zope2.13-2.13.21/source/ZODB3/src/BTrees/tests/test_btreesubclass.py0000644000175000017500000000266012214017464023654 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from BTrees.OOBTree import OOBTree, OOBucket class B(OOBucket): pass class T(OOBTree): _bucket_type = B import unittest class SubclassTest(unittest.TestCase): def testSubclass(self): # test that a subclass that defines _bucket_type gets buckets # of that type t = T() # There's no good way to get a bucket at the moment. # __getstate__() is as good as it gets, but the default # getstate explicitly includes the pickle of the bucket # for small trees, so we have to be clever :-( # make sure there is more than one bucket in the tree for i in range(1000): t[i] = i state = t.__getstate__() self.assert_(state[0][0].__class__ is B) def test_suite(): return unittest.makeSuite(SubclassTest) zope2.13-2.13.21/source/ZODB3/src/BTrees/tests/testBTrees.py0000644000175000017500000021202112214017464022032 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import gc import pickle import random import StringIO from unittest import TestCase, TestSuite, TextTestRunner, makeSuite from types import ClassType import zope.interface.verify from BTrees.OOBTree import OOBTree, OOBucket, OOSet, OOTreeSet from BTrees.IOBTree import IOBTree, IOBucket, IOSet, IOTreeSet from BTrees.IIBTree import IIBTree, IIBucket, IISet, IITreeSet from BTrees.IFBTree import IFBTree, IFBucket, IFSet, IFTreeSet from BTrees.OIBTree import OIBTree, OIBucket, OISet, OITreeSet from BTrees.LOBTree import LOBTree, LOBucket, LOSet, LOTreeSet from BTrees.LLBTree import LLBTree, LLBucket, LLSet, LLTreeSet from BTrees.LFBTree import LFBTree, LFBucket, LFSet, LFTreeSet from BTrees.OLBTree import OLBTree, OLBucket, OLSet, OLTreeSet import BTrees from BTrees.IIBTree import using64bits from BTrees.check import check import transaction from ZODB import DB from ZODB.MappingStorage import MappingStorage class Base(TestCase): """ Tests common to all types: sets, buckets, and BTrees """ db = None def setUp(self): self.t = self.t_class() def tearDown(self): if self.db is not None: self.db.close() self.t = None del self.t def _getRoot(self): if self.db is None: # Unclear: On the next line, the ZODB4 flavor of this routine # [asses a cache_size argument: # self.db = DB(MappingStorage(), cache_size=1) # If that's done here, though, testLoadAndStore() and # testGhostUnghost() both nail the CPU and seemingly # never finish. self.db = DB(MappingStorage()) return self.db.open().root() def _closeRoot(self, root): root._p_jar.close() def testLoadAndStore(self): for i in 0, 10, 1000: t = self.t.__class__() self._populate(t, i) root = None root = self._getRoot() root[i] = t transaction.commit() root2 = self._getRoot() if hasattr(t, 'items'): self.assertEqual(list(root2[i].items()) , list(t.items())) else: self.assertEqual(list(root2[i].keys()) , list(t.keys())) self._closeRoot(root) self._closeRoot(root2) def testSetstateArgumentChecking(self): try: self.t.__class__().__setstate__(('',)) except TypeError, v: self.assertEqual(str(v), 'tuple required for first state element') else: raise AssertionError("Expected exception") def testGhostUnghost(self): for i in 0, 10, 1000: t = self.t.__class__() self._populate(t, i) root = self._getRoot() root[i] = t transaction.commit() root2 = self._getRoot() root2[i]._p_deactivate() transaction.commit() if hasattr(t, 'items'): self.assertEqual(list(root2[i].items()) , list(t.items())) else: self.assertEqual(list(root2[i].keys()) , list(t.keys())) self._closeRoot(root) self._closeRoot(root2) def testSimpleExclusiveKeyRange(self): t = self.t.__class__() self.assertEqual(list(t.keys()), []) self.assertEqual(list(t.keys(excludemin=True)), []) self.assertEqual(list(t.keys(excludemax=True)), []) self.assertEqual(list(t.keys(excludemin=True, excludemax=True)), []) self._populate(t, 1) self.assertEqual(list(t.keys()), [0]) self.assertEqual(list(t.keys(excludemin=True)), []) self.assertEqual(list(t.keys(excludemax=True)), []) self.assertEqual(list(t.keys(excludemin=True, excludemax=True)), []) t.clear() self._populate(t, 2) self.assertEqual(list(t.keys()), [0, 1]) self.assertEqual(list(t.keys(excludemin=True)), [1]) self.assertEqual(list(t.keys(excludemax=True)), [0]) self.assertEqual(list(t.keys(excludemin=True, excludemax=True)), []) t.clear() self._populate(t, 3) self.assertEqual(list(t.keys()), [0, 1, 2]) self.assertEqual(list(t.keys(excludemin=True)), [1, 2]) self.assertEqual(list(t.keys(excludemax=True)), [0, 1]) self.assertEqual(list(t.keys(excludemin=True, excludemax=True)), [1]) self.assertEqual(list(t.keys(-1, 3, excludemin=True, excludemax=True)), [0, 1, 2]) self.assertEqual(list(t.keys(0, 3, excludemin=True, excludemax=True)), [1, 2]) self.assertEqual(list(t.keys(-1, 2, excludemin=True, excludemax=True)), [0, 1]) self.assertEqual(list(t.keys(0, 2, excludemin=True, excludemax=True)), [1]) def testUpdatesDoReadChecksOnInternalNodes(self): t = self.t if not hasattr(t, '_firstbucket'): return self._populate(t, 1000) store = MappingStorage() db = DB(store) conn = db.open() conn.root.t = t transaction.commit() read = [] def readCurrent(ob): read.append(ob) conn.__class__.readCurrent(conn, ob) return 1 conn.readCurrent = readCurrent try: add = t.add remove = t.remove except AttributeError: def add(i): t[i] = i def remove(i): del t[i] # Modifying a thing remove(100) self.assert_(t in read) del read[:] add(100) self.assert_(t in read) del read[:] transaction.abort() conn.cacheMinimize() list(t) self.assert_(100 in t) self.assert_(not read) class MappingBase(Base): """ Tests common to mappings (buckets, btrees) """ def _populate(self, t, l): # Make some data for i in range(l): t[i]=i def testRepr(self): # test the repr because buckets have a complex repr implementation # internally the cutoff from a stack allocated buffer to a heap # allocated buffer is 10000. for i in range(1000): self.t[i] = i r = repr(self.t) # Make sure the repr is 10000 bytes long for a bucket. # But since the test is also run for btrees, skip the length # check if the repr starts with '<' if not r.startswith('<'): self.assert_(len(r) > 10000) def testGetItemFails(self): self.assertRaises(KeyError, self._getitemfail) def _getitemfail(self): return self.t[1] def testGetReturnsDefault(self): self.assertEqual(self.t.get(1) , None) self.assertEqual(self.t.get(1, 'foo') , 'foo') def testSetItemGetItemWorks(self): self.t[1] = 1 a = self.t[1] self.assertEqual(a , 1, `a`) def testReplaceWorks(self): self.t[1] = 1 self.assertEqual(self.t[1] , 1, self.t[1]) self.t[1] = 2 self.assertEqual(self.t[1] , 2, self.t[1]) def testLen(self): added = {} r = range(1000) for x in r: k = random.choice(r) self.t[k] = x added[k] = x addl = added.keys() self.assertEqual(len(self.t) , len(addl), len(self.t)) def testHasKeyWorks(self): self.t[1] = 1 self.assert_(self.t.has_key(1)) self.assert_(1 in self.t) self.assert_(0 not in self.t) self.assert_(2 not in self.t) def testValuesWorks(self): for x in range(100): self.t[x] = x*x v = self.t.values() for i in range(100): self.assertEqual(v[i], i*i) self.assertRaises(IndexError, lambda: v[i+1]) i = 0 for value in self.t.itervalues(): self.assertEqual(value, i*i) i += 1 def testValuesWorks1(self): for x in range(100): self.t[99-x] = x for x in range(40): lst = list(self.t.values(0+x,99-x)) lst.sort() self.assertEqual(lst,range(0+x,99-x+1)) lst = list(self.t.values(max=99-x, min=0+x)) lst.sort() self.assertEqual(lst,range(0+x,99-x+1)) def testValuesNegativeIndex(self): L = [-3, 6, -11, 4] for i in L: self.t[i] = i L.sort() vals = self.t.values() for i in range(-1, -5, -1): self.assertEqual(vals[i], L[i]) self.assertRaises(IndexError, lambda: vals[-5]) def testKeysWorks(self): for x in range(100): self.t[x] = x v = self.t.keys() i = 0 for x in v: self.assertEqual(x,i) i = i + 1 self.assertRaises(IndexError, lambda: v[i]) for x in range(40): lst = self.t.keys(0+x,99-x) self.assertEqual(list(lst), range(0+x, 99-x+1)) lst = self.t.keys(max=99-x, min=0+x) self.assertEqual(list(lst), range(0+x, 99-x+1)) self.assertEqual(len(v), 100) def testKeysNegativeIndex(self): L = [-3, 6, -11, 4] for i in L: self.t[i] = i L.sort() keys = self.t.keys() for i in range(-1, -5, -1): self.assertEqual(keys[i], L[i]) self.assertRaises(IndexError, lambda: keys[-5]) def testItemsWorks(self): for x in range(100): self.t[x] = 2*x v = self.t.items() i = 0 for x in v: self.assertEqual(x[0], i) self.assertEqual(x[1], 2*i) i += 1 self.assertRaises(IndexError, lambda: v[i+1]) i = 0 for x in self.t.iteritems(): self.assertEqual(x, (i, 2*i)) i += 1 items = list(self.t.items(min=12, max=20)) self.assertEqual(items, zip(range(12, 21), range(24, 43, 2))) items = list(self.t.iteritems(min=12, max=20)) self.assertEqual(items, zip(range(12, 21), range(24, 43, 2))) def testItemsNegativeIndex(self): L = [-3, 6, -11, 4] for i in L: self.t[i] = i L.sort() items = self.t.items() for i in range(-1, -5, -1): self.assertEqual(items[i], (L[i], L[i])) self.assertRaises(IndexError, lambda: items[-5]) def testDeleteInvalidKeyRaisesKeyError(self): self.assertRaises(KeyError, self._deletefail) def _deletefail(self): del self.t[1] def testMaxKeyMinKey(self): self.t[7] = 6 self.t[3] = 10 self.t[8] = 12 self.t[1] = 100 self.t[5] = 200 self.t[10] = 500 self.t[6] = 99 self.t[4] = 150 del self.t[7] t = self.t self.assertEqual(t.maxKey(), 10) self.assertEqual(t.maxKey(6), 6) self.assertEqual(t.maxKey(9), 8) self.assertEqual(t.minKey(), 1) self.assertEqual(t.minKey(3), 3) self.assertEqual(t.minKey(9), 10) try: t.maxKey(t.minKey() - 1) except ValueError, err: self.assertEqual(str(err), "no key satisfies the conditions") else: self.fail("expected ValueError") try: t.minKey(t.maxKey() + 1) except ValueError, err: self.assertEqual(str(err), "no key satisfies the conditions") else: self.fail("expected ValueError") def testClear(self): r = range(100) for x in r: rnd = random.choice(r) self.t[rnd] = 0 self.t.clear() diff = lsubtract(list(self.t.keys()), []) self.assertEqual(diff, []) def testUpdate(self): d={} l=[] for i in range(10000): k=random.randrange(-2000, 2001) d[k]=i l.append((k, i)) items=d.items() items.sort() self.t.update(d) self.assertEqual(list(self.t.items()), items) self.t.clear() self.assertEqual(list(self.t.items()), []) self.t.update(l) self.assertEqual(list(self.t.items()), items) # Before ZODB 3.4.2, update/construction from PersistentMapping failed. def testUpdateFromPersistentMapping(self): from persistent.mapping import PersistentMapping pm = PersistentMapping({1: 2}) self.t.update(pm) self.assertEqual(list(self.t.items()), [(1, 2)]) # Construction goes thru the same internals as .update(). t = self.t.__class__(pm) self.assertEqual(list(t.items()), [(1, 2)]) def testEmptyRangeSearches(self): t = self.t t.update([(1,1), (5,5), (9,9)]) self.assertEqual(list(t.keys(-6,-4)), [], list(t.keys(-6,-4))) self.assertEqual(list(t.keys(2,4)), [], list(t.keys(2,4))) self.assertEqual(list(t.keys(6,8)), [], list(t.keys(6,8))) self.assertEqual(list(t.keys(10,12)), [], list(t.keys(10,12))) self.assertEqual(list(t.keys(9, 1)), [], list(t.keys(9, 1))) # For IITreeSets, this one was returning 31 for len(keys), and # list(keys) produced a list with 100 elements. t.clear() t.update(zip(range(300), range(300))) keys = t.keys(200, 50) self.assertEqual(len(keys), 0) self.assertEqual(list(keys), []) self.assertEqual(list(t.iterkeys(200, 50)), []) keys = t.keys(max=50, min=200) self.assertEqual(len(keys), 0) self.assertEqual(list(keys), []) self.assertEqual(list(t.iterkeys(max=50, min=200)), []) def testSlicing(self): # Test that slicing of .keys()/.values()/.items() works exactly the # same way as slicing a Python list with the same contents. # This tests fixes to several bugs in this area, starting with # http://collector.zope.org/Zope/419, # "BTreeItems slice contains 1 too many elements". t = self.t for n in range(10): t.clear() self.assertEqual(len(t), 0) keys = [] values = [] items = [] for key in range(n): value = -2 * key t[key] = value keys.append(key) values.append(value) items.append((key, value)) self.assertEqual(len(t), n) kslice = t.keys() vslice = t.values() islice = t.items() self.assertEqual(len(kslice), n) self.assertEqual(len(vslice), n) self.assertEqual(len(islice), n) # Test whole-structure slices. x = kslice[:] self.assertEqual(list(x), keys[:]) x = vslice[:] self.assertEqual(list(x), values[:]) x = islice[:] self.assertEqual(list(x), items[:]) for lo in range(-2*n, 2*n+1): # Test one-sided slices. x = kslice[:lo] self.assertEqual(list(x), keys[:lo]) x = kslice[lo:] self.assertEqual(list(x), keys[lo:]) x = vslice[:lo] self.assertEqual(list(x), values[:lo]) x = vslice[lo:] self.assertEqual(list(x), values[lo:]) x = islice[:lo] self.assertEqual(list(x), items[:lo]) x = islice[lo:] self.assertEqual(list(x), items[lo:]) for hi in range(-2*n, 2*n+1): # Test two-sided slices. x = kslice[lo:hi] self.assertEqual(list(x), keys[lo:hi]) x = vslice[lo:hi] self.assertEqual(list(x), values[lo:hi]) x = islice[lo:hi] self.assertEqual(list(x), items[lo:hi]) # The specific test case from Zope collector 419. t.clear() for i in xrange(100): t[i] = 1 tslice = t.items()[20:80] self.assertEqual(len(tslice), 60) self.assertEqual(list(tslice), zip(range(20, 80), [1]*60)) def testIterators(self): t = self.t for keys in [], [-2], [1, 4], range(-170, 2000, 6): t.clear() for k in keys: t[k] = -3 * k self.assertEqual(list(t), keys) x = [] for k in t: x.append(k) self.assertEqual(x, keys) it = iter(t) self.assert_(it is iter(it)) x = [] try: while 1: x.append(it.next()) except StopIteration: pass self.assertEqual(x, keys) self.assertEqual(list(t.iterkeys()), keys) self.assertEqual(list(t.itervalues()), list(t.values())) self.assertEqual(list(t.iteritems()), list(t.items())) def testRangedIterators(self): t = self.t for keys in [], [-2], [1, 4], range(-170, 2000, 13): t.clear() values = [] for k in keys: value = -3 * k t[k] = value values.append(value) items = zip(keys, values) self.assertEqual(list(t.iterkeys()), keys) self.assertEqual(list(t.itervalues()), values) self.assertEqual(list(t.iteritems()), items) if not keys: continue min_mid_max = (keys[0], keys[len(keys) >> 1], keys[-1]) for key1 in min_mid_max: for lo in range(key1 - 1, key1 + 2): # Test one-sided range iterators. goodkeys = [k for k in keys if lo <= k] got = t.iterkeys(lo) self.assertEqual(goodkeys, list(got)) goodvalues = [t[k] for k in goodkeys] got = t.itervalues(lo) self.assertEqual(goodvalues, list(got)) gooditems = zip(goodkeys, goodvalues) got = t.iteritems(lo) self.assertEqual(gooditems, list(got)) for key2 in min_mid_max: for hi in range(key2 - 1, key2 + 2): goodkeys = [k for k in keys if lo <= k <= hi] got = t.iterkeys(min=lo, max=hi) self.assertEqual(goodkeys, list(got)) goodvalues = [t[k] for k in goodkeys] got = t.itervalues(lo, max=hi) self.assertEqual(goodvalues, list(got)) gooditems = zip(goodkeys, goodvalues) got = t.iteritems(max=hi, min=lo) self.assertEqual(gooditems, list(got)) def testBadUpdateTupleSize(self): # This one silently ignored the excess in Zope3. try: self.t.update([(1, 2, 3)]) except TypeError: pass else: self.fail("update() with 3-tuple didn't complain") # This one dumped core in Zope3. try: self.t.update([(1,)]) except TypeError: pass else: self.fail("update() with 1-tuple didn't complain") # This one should simply succeed. self.t.update([(1, 2)]) self.assertEqual(list(self.t.items()), [(1, 2)]) def testSimpleExclusivRanges(self): def identity(x): return x def dup(x): return [(y, y) for y in x] for methodname, f in (("keys", identity), ("values", identity), ("items", dup), ("iterkeys", identity), ("itervalues", identity), ("iteritems", dup)): t = self.t.__class__() meth = getattr(t, methodname, None) if meth is None: continue self.assertEqual(list(meth()), []) self.assertEqual(list(meth(excludemin=True)), []) self.assertEqual(list(meth(excludemax=True)), []) self.assertEqual(list(meth(excludemin=True, excludemax=True)), []) self._populate(t, 1) self.assertEqual(list(meth()), f([0])) self.assertEqual(list(meth(excludemin=True)), []) self.assertEqual(list(meth(excludemax=True)), []) self.assertEqual(list(meth(excludemin=True, excludemax=True)), []) t.clear() self._populate(t, 2) self.assertEqual(list(meth()), f([0, 1])) self.assertEqual(list(meth(excludemin=True)), f([1])) self.assertEqual(list(meth(excludemax=True)), f([0])) self.assertEqual(list(meth(excludemin=True, excludemax=True)), []) t.clear() self._populate(t, 3) self.assertEqual(list(meth()), f([0, 1, 2])) self.assertEqual(list(meth(excludemin=True)), f([1, 2])) self.assertEqual(list(meth(excludemax=True)), f([0, 1])) self.assertEqual(list(meth(excludemin=True, excludemax=True)), f([1])) self.assertEqual(list(meth(-1, 3, excludemin=True, excludemax=True)), f([0, 1, 2])) self.assertEqual(list(meth(0, 3, excludemin=True, excludemax=True)), f([1, 2])) self.assertEqual(list(meth(-1, 2, excludemin=True, excludemax=True)), f([0, 1])) self.assertEqual(list(meth(0, 2, excludemin=True, excludemax=True)), f([1])) def testSetdefault(self): t = self.t self.assertEqual(t.setdefault(1, 2), 2) # That should also have associated 1 with 2 in the tree. self.assert_(1 in t) self.assertEqual(t[1], 2) # And trying to change it again should have no effect. self.assertEqual(t.setdefault(1, 666), 2) self.assertEqual(t[1], 2) # Not enough arguments. self.assertRaises(TypeError, t.setdefault) self.assertRaises(TypeError, t.setdefault, 1) # Too many arguments. self.assertRaises(TypeError, t.setdefault, 1, 2, 3) def testPop(self): t = self.t # Empty container. # If no default given, raises KeyError. self.assertRaises(KeyError, t.pop, 1) # But if default given, returns that instead. self.assertEqual(t.pop(1, 42), 42) t[1] = 3 # KeyError when key is not in container and default is not passed. self.assertRaises(KeyError, t.pop, 5) self.assertEqual(list(t.items()), [(1, 3)]) # If key is in container, returns the value and deletes the key. self.assertEqual(t.pop(1), 3) self.assertEqual(len(t), 0) # If key is present, return value bypassing default. t[1] = 3 self.assertEqual(t.pop(1, 7), 3) self.assertEqual(len(t), 0) # Pop only one item. t[1] = 3 t[2] = 4 self.assertEqual(len(t), 2) self.assertEqual(t.pop(1), 3) self.assertEqual(len(t), 1) self.assertEqual(t[2], 4) self.assertEqual(t.pop(1, 3), 3) # Too few arguments. self.assertRaises(TypeError, t.pop) # Too many arguments. self.assertRaises(TypeError, t.pop, 1, 2, 3) class NormalSetTests(Base): """ Test common to all set types """ def _populate(self, t, l): # Make some data t.update(range(l)) def testInsertReturnsValue(self): t = self.t self.assertEqual(t.insert(5) , 1) self.assertEqual(t.add(4) , 1) def testDuplicateInsert(self): t = self.t t.insert(5) self.assertEqual(t.insert(5) , 0) self.assertEqual(t.add(5) , 0) def testInsert(self): t = self.t t.insert(1) self.assert_(t.has_key(1)) self.assert_(1 in t) self.assert_(2 not in t) def testBigInsert(self): t = self.t r = xrange(10000) for x in r: t.insert(x) for x in r: self.assert_(t.has_key(x)) self.assert_(x in t) def testRemoveSucceeds(self): t = self.t r = xrange(10000) for x in r: t.insert(x) for x in r: t.remove(x) def testRemoveFails(self): self.assertRaises(KeyError, self._removenonexistent) def _removenonexistent(self): self.t.remove(1) def testHasKeyFails(self): t = self.t self.assert_(not t.has_key(1)) self.assert_(1 not in t) def testKeys(self): t = self.t r = xrange(1000) for x in r: t.insert(x) diff = lsubtract(t.keys(), r) self.assertEqual(diff, []) def testClear(self): t = self.t r = xrange(1000) for x in r: t.insert(x) t.clear() diff = lsubtract(t.keys(), []) self.assertEqual(diff , [], diff) def testMaxKeyMinKey(self): t = self.t t.insert(1) t.insert(2) t.insert(3) t.insert(8) t.insert(5) t.insert(10) t.insert(6) t.insert(4) self.assertEqual(t.maxKey() , 10) self.assertEqual(t.maxKey(6) , 6) self.assertEqual(t.maxKey(9) , 8) self.assertEqual(t.minKey() , 1) self.assertEqual(t.minKey(3) , 3) self.assertEqual(t.minKey(9) , 10) self.assert_(t.minKey() in t) self.assert_(t.minKey()-1 not in t) self.assert_(t.maxKey() in t) self.assert_(t.maxKey()+1 not in t) try: t.maxKey(t.minKey() - 1) except ValueError, err: self.assertEqual(str(err), "no key satisfies the conditions") else: self.fail("expected ValueError") try: t.minKey(t.maxKey() + 1) except ValueError, err: self.assertEqual(str(err), "no key satisfies the conditions") else: self.fail("expected ValueError") def testUpdate(self): d={} l=[] for i in range(10000): k=random.randrange(-2000, 2001) d[k]=i l.append(k) items = d.keys() items.sort() self.t.update(l) self.assertEqual(list(self.t.keys()), items) def testEmptyRangeSearches(self): t = self.t t.update([1, 5, 9]) self.assertEqual(list(t.keys(-6,-4)), [], list(t.keys(-6,-4))) self.assertEqual(list(t.keys(2,4)), [], list(t.keys(2,4))) self.assertEqual(list(t.keys(6,8)), [], list(t.keys(6,8))) self.assertEqual(list(t.keys(10,12)), [], list(t.keys(10,12))) self.assertEqual(list(t.keys(9,1)), [], list(t.keys(9,1))) # For IITreeSets, this one was returning 31 for len(keys), and # list(keys) produced a list with 100 elements. t.clear() t.update(range(300)) keys = t.keys(200, 50) self.assertEqual(len(keys), 0) self.assertEqual(list(keys), []) keys = t.keys(max=50, min=200) self.assertEqual(len(keys), 0) self.assertEqual(list(keys), []) def testSlicing(self): # Test that slicing of .keys() works exactly the same way as slicing # a Python list with the same contents. t = self.t for n in range(10): t.clear() self.assertEqual(len(t), 0) keys = range(10*n, 11*n) t.update(keys) self.assertEqual(len(t), n) kslice = t.keys() self.assertEqual(len(kslice), n) # Test whole-structure slices. x = kslice[:] self.assertEqual(list(x), keys[:]) for lo in range(-2*n, 2*n+1): # Test one-sided slices. x = kslice[:lo] self.assertEqual(list(x), keys[:lo]) x = kslice[lo:] self.assertEqual(list(x), keys[lo:]) for hi in range(-2*n, 2*n+1): # Test two-sided slices. x = kslice[lo:hi] self.assertEqual(list(x), keys[lo:hi]) def testIterator(self): t = self.t for keys in [], [-2], [1, 4], range(-170, 2000, 6): t.clear() t.update(keys) self.assertEqual(list(t), keys) x = [] for k in t: x.append(k) self.assertEqual(x, keys) it = iter(t) self.assert_(it is iter(it)) x = [] try: while 1: x.append(it.next()) except StopIteration: pass self.assertEqual(x, keys) class ExtendedSetTests(NormalSetTests): def testLen(self): t = self.t r = xrange(10000) for x in r: t.insert(x) self.assertEqual(len(t) , 10000, len(t)) def testGetItem(self): t = self.t r = xrange(10000) for x in r: t.insert(x) for x in r: self.assertEqual(t[x] , x) class BTreeTests(MappingBase): """ Tests common to all BTrees """ def tearDown(self): self.t._check() check(self.t) MappingBase.tearDown(self) def testDeleteNoChildrenWorks(self): self.t[5] = 6 self.t[2] = 10 self.t[6] = 12 self.t[1] = 100 self.t[3] = 200 self.t[10] = 500 self.t[4] = 99 del self.t[4] diff = lsubtract(self.t.keys(), [1,2,3,5,6,10]) self.assertEqual(diff , [], diff) def testDeleteOneChildWorks(self): self.t[5] = 6 self.t[2] = 10 self.t[6] = 12 self.t[1] = 100 self.t[3] = 200 self.t[10] = 500 self.t[4] = 99 del self.t[3] diff = lsubtract(self.t.keys(), [1,2,4,5,6,10]) self.assertEqual(diff , [], diff) def testDeleteTwoChildrenNoInorderSuccessorWorks(self): self.t[5] = 6 self.t[2] = 10 self.t[6] = 12 self.t[1] = 100 self.t[3] = 200 self.t[10] = 500 self.t[4] = 99 del self.t[2] diff = lsubtract(self.t.keys(), [1,3,4,5,6,10]) self.assertEqual(diff , [], diff) def testDeleteTwoChildrenInorderSuccessorWorks(self): # 7, 3, 8, 1, 5, 10, 6, 4 -- del 3 self.t[7] = 6 self.t[3] = 10 self.t[8] = 12 self.t[1] = 100 self.t[5] = 200 self.t[10] = 500 self.t[6] = 99 self.t[4] = 150 del self.t[3] diff = lsubtract(self.t.keys(), [1,4,5,6,7,8,10]) self.assertEqual(diff , [], diff) def testDeleteRootWorks(self): # 7, 3, 8, 1, 5, 10, 6, 4 -- del 7 self.t[7] = 6 self.t[3] = 10 self.t[8] = 12 self.t[1] = 100 self.t[5] = 200 self.t[10] = 500 self.t[6] = 99 self.t[4] = 150 del self.t[7] diff = lsubtract(self.t.keys(), [1,3,4,5,6,8,10]) self.assertEqual(diff , [], diff) def testRandomNonOverlappingInserts(self): added = {} r = range(100) for x in r: k = random.choice(r) if not added.has_key(k): self.t[k] = x added[k] = 1 addl = added.keys() addl.sort() diff = lsubtract(list(self.t.keys()), addl) self.assertEqual(diff , [], (diff, addl, list(self.t.keys()))) def testRandomOverlappingInserts(self): added = {} r = range(100) for x in r: k = random.choice(r) self.t[k] = x added[k] = 1 addl = added.keys() addl.sort() diff = lsubtract(self.t.keys(), addl) self.assertEqual(diff , [], diff) def testRandomDeletes(self): r = range(1000) added = [] for x in r: k = random.choice(r) self.t[k] = x added.append(k) deleted = [] for x in r: k = random.choice(r) if self.t.has_key(k): self.assert_(k in self.t) del self.t[k] deleted.append(k) if self.t.has_key(k): self.fail( "had problems deleting %s" % k ) badones = [] for x in deleted: if self.t.has_key(x): badones.append(x) self.assertEqual(badones , [], (badones, added, deleted)) def testTargetedDeletes(self): r = range(1000) for x in r: k = random.choice(r) self.t[k] = x for x in r: try: del self.t[x] except KeyError: pass self.assertEqual(realseq(self.t.keys()) , [], realseq(self.t.keys())) def testPathologicalRightBranching(self): r = range(1000) for x in r: self.t[x] = 1 self.assertEqual(realseq(self.t.keys()) , r, realseq(self.t.keys())) for x in r: del self.t[x] self.assertEqual(realseq(self.t.keys()) , [], realseq(self.t.keys())) def testPathologicalLeftBranching(self): r = range(1000) revr = r[:] revr.reverse() for x in revr: self.t[x] = 1 self.assertEqual(realseq(self.t.keys()) , r, realseq(self.t.keys())) for x in revr: del self.t[x] self.assertEqual(realseq(self.t.keys()) , [], realseq(self.t.keys())) def testSuccessorChildParentRewriteExerciseCase(self): add_order = [ 85, 73, 165, 273, 215, 142, 233, 67, 86, 166, 235, 225, 255, 73, 175, 171, 285, 162, 108, 28, 283, 258, 232, 199, 260, 298, 275, 44, 261, 291, 4, 181, 285, 289, 216, 212, 129, 243, 97, 48, 48, 159, 22, 285, 92, 110, 27, 55, 202, 294, 113, 251, 193, 290, 55, 58, 239, 71, 4, 75, 129, 91, 111, 271, 101, 289, 194, 218, 77, 142, 94, 100, 115, 101, 226, 17, 94, 56, 18, 163, 93, 199, 286, 213, 126, 240, 245, 190, 195, 204, 100, 199, 161, 292, 202, 48, 165, 6, 173, 40, 218, 271, 228, 7, 166, 173, 138, 93, 22, 140, 41, 234, 17, 249, 215, 12, 292, 246, 272, 260, 140, 58, 2, 91, 246, 189, 116, 72, 259, 34, 120, 263, 168, 298, 118, 18, 28, 299, 192, 252, 112, 60, 277, 273, 286, 15, 263, 141, 241, 172, 255, 52, 89, 127, 119, 255, 184, 213, 44, 116, 231, 173, 298, 178, 196, 89, 184, 289, 98, 216, 115, 35, 132, 278, 238, 20, 241, 128, 179, 159, 107, 206, 194, 31, 260, 122, 56, 144, 118, 283, 183, 215, 214, 87, 33, 205, 183, 212, 221, 216, 296, 40, 108, 45, 188, 139, 38, 256, 276, 114, 270, 112, 214, 191, 147, 111, 299, 107, 101, 43, 84, 127, 67, 205, 251, 38, 91, 297, 26, 165, 187, 19, 6, 73, 4, 176, 195, 90, 71, 30, 82, 139, 210, 8, 41, 253, 127, 190, 102, 280, 26, 233, 32, 257, 194, 263, 203, 190, 111, 218, 199, 29, 81, 207, 18, 180, 157, 172, 192, 135, 163, 275, 74, 296, 298, 265, 105, 191, 282, 277, 83, 188, 144, 259, 6, 173, 81, 107, 292, 231, 129, 65, 161, 113, 103, 136, 255, 285, 289, 1 ] delete_order = [ 276, 273, 12, 275, 2, 286, 127, 83, 92, 33, 101, 195, 299, 191, 22, 232, 291, 226, 110, 94, 257, 233, 215, 184, 35, 178, 18, 74, 296, 210, 298, 81, 265, 175, 116, 261, 212, 277, 260, 234, 6, 129, 31, 4, 235, 249, 34, 289, 105, 259, 91, 93, 119, 7, 183, 240, 41, 253, 290, 136, 75, 292, 67, 112, 111, 256, 163, 38, 126, 139, 98, 56, 282, 60, 26, 55, 245, 225, 32, 52, 40, 271, 29, 252, 239, 89, 87, 205, 213, 180, 97, 108, 120, 218, 44, 187, 196, 251, 202, 203, 172, 28, 188, 77, 90, 199, 297, 282, 141, 100, 161, 216, 73, 19, 17, 189, 30, 258 ] for x in add_order: self.t[x] = 1 for x in delete_order: try: del self.t[x] except KeyError: if self.t.has_key(x): self.assertEqual(1,2,"failed to delete %s" % x) def testRangeSearchAfterSequentialInsert(self): r = range(100) for x in r: self.t[x] = 0 diff = lsubtract(list(self.t.keys(0, 100)), r) self.assertEqual(diff , [], diff) def testRangeSearchAfterRandomInsert(self): r = range(100) a = {} for x in r: rnd = random.choice(r) self.t[rnd] = 0 a[rnd] = 0 diff = lsubtract(list(self.t.keys(0, 100)), a.keys()) self.assertEqual(diff , [], diff) def testPathologicalRangeSearch(self): t = self.t # Build a 2-level tree with at least two buckets. for i in range(200): t[i] = i items, dummy = t.__getstate__() self.assert_(len(items) > 2) # at least two buckets and a key # All values in the first bucket are < firstkey. All in the # second bucket are >= firstkey, and firstkey is the first key in # the second bucket. firstkey = items[1] therange = t.keys(-1, firstkey) self.assertEqual(len(therange), firstkey + 1) self.assertEqual(list(therange), range(firstkey + 1)) # Now for the tricky part. If we delete firstkey, the second bucket # loses its smallest key, but firstkey remains in the BTree node. # If we then do a high-end range search on firstkey, the BTree node # directs us to look in the second bucket, but there's no longer any # key <= firstkey in that bucket. The correct answer points to the # end of the *first* bucket. The algorithm has to be smart enough # to "go backwards" in the BTree then; if it doesn't, it will # erroneously claim that the range is empty. del t[firstkey] therange = t.keys(min=-1, max=firstkey) self.assertEqual(len(therange), firstkey) self.assertEqual(list(therange), range(firstkey)) def testInsertMethod(self): t = self.t t[0] = 1 self.assertEqual(t.insert(0, 1) , 0) self.assertEqual(t.insert(1, 1) , 1) self.assertEqual(lsubtract(list(t.keys()), [0,1]) , []) def testDamagedIterator(self): # A cute one from Steve Alexander. This caused the BTreeItems # object to go insane, accessing memory beyond the allocated part # of the bucket. If it fails, the symptom is either a C-level # assertion error (if the BTree code was compiled without NDEBUG), # or most likely a segfault (if the BTree code was compiled with # NDEBUG). t = self.t.__class__() self._populate(t, 10) # In order for this to fail, it's important that k be a "lazy" # iterator, referring to the BTree by indirect position (index) # instead of a fully materialized list. Then the position can # end up pointing into trash memory, if the bucket pointed to # shrinks. k = t.keys() for dummy in range(20): try: del t[k[0]] except RuntimeError, detail: self.assertEqual(str(detail), "the bucket being iterated " "changed size") break LARGEST_32_BITS = 2147483647 SMALLEST_32_BITS = -LARGEST_32_BITS - 1 SMALLEST_POSITIVE_33_BITS = LARGEST_32_BITS + 1 LARGEST_NEGATIVE_33_BITS = SMALLEST_32_BITS - 1 LARGEST_64_BITS = 0x7fffffffffffffff SMALLEST_64_BITS = -LARGEST_64_BITS - 1 SMALLEST_POSITIVE_65_BITS = LARGEST_64_BITS + 1 LARGEST_NEGATIVE_65_BITS = SMALLEST_64_BITS - 1 class TestLongIntSupport: def getTwoValues(self): """Return two distinct values; these must compare as un-equal. These values must be usable as values. """ return object(), object() def getTwoKeys(self): """Return two distinct values, these must compare as un-equal. These values must be usable as keys. """ return 0, 1 def _set_value(self, key, value): self.t[key] = value class TestLongIntKeys(TestLongIntSupport): def testLongIntKeysWork(self): o1, o2 = self.getTwoValues() assert o1 != o2 # Test some small key values first: self.t[0L] = o1 self.assertEqual(self.t[0], o1) self.t[0] = o2 self.assertEqual(self.t[0L], o2) self.assertEqual(list(self.t.keys()), [0]) # Test some large key values too: k1 = SMALLEST_POSITIVE_33_BITS k2 = LARGEST_64_BITS k3 = SMALLEST_64_BITS self.t[k1] = o1 self.t[k2] = o2 self.t[k3] = o1 self.assertEqual(self.t[k1], o1) self.assertEqual(self.t[k2], o2) self.assertEqual(self.t[k3], o1) self.assertEqual(list(self.t.keys()), [k3, 0, k1, k2]) def testLongIntKeysOutOfRange(self): o1, o2 = self.getTwoValues() self.assertRaises( ValueError, self._set_value, SMALLEST_POSITIVE_65_BITS, o1) self.assertRaises( ValueError, self._set_value, LARGEST_NEGATIVE_65_BITS, o1) class TestLongIntValues(TestLongIntSupport): def testLongIntValuesWork(self): keys = list(self.getTwoKeys()) keys.sort() k1, k2 = keys assert k1 != k2 # This is the smallest positive integer that requires 33 bits: v1 = SMALLEST_POSITIVE_33_BITS v2 = v1 + 1 self.t[k1] = v1 self.t[k2] = v2 self.assertEqual(self.t[k1], v1) self.assertEqual(self.t[k2], v2) self.assertEqual(list(self.t.values()), [v1, v2]) def testLongIntValuesOutOfRange(self): k1, k2 = self.getTwoKeys() self.assertRaises( ValueError, self._set_value, k1, SMALLEST_POSITIVE_65_BITS) self.assertRaises( ValueError, self._set_value, k1, LARGEST_NEGATIVE_65_BITS) if not using64bits: # We're not using 64-bit ints in this build, so we don't expect # the long-integer tests to pass. class TestLongIntKeys: pass class TestLongIntValues: pass # tests of various type errors class TypeTest(TestCase): def testBadTypeRaises(self): self.assertRaises(TypeError, self._stringraises) self.assertRaises(TypeError, self._floatraises) self.assertRaises(TypeError, self._noneraises) class TestIOBTrees(TypeTest): def setUp(self): self.t = IOBTree() def _stringraises(self): self.t['c'] = 1 def _floatraises(self): self.t[2.5] = 1 def _noneraises(self): self.t[None] = 1 class TestOIBTrees(TypeTest): def setUp(self): self.t = OIBTree() def _stringraises(self): self.t[1] = 'c' def _floatraises(self): self.t[1] = 1.4 def _noneraises(self): self.t[1] = None def testEmptyFirstBucketReportedByGuido(self): b = self.t for i in xrange(29972): # reduce to 29971 and it works b[i] = i for i in xrange(30): # reduce to 29 and it works del b[i] b[i+40000] = i self.assertEqual(b.keys()[0], 30) class TestIIBTrees(TestCase): def setUp(self): self.t = IIBTree() def testNonIntegerKeyRaises(self): self.assertRaises(TypeError, self._stringraiseskey) self.assertRaises(TypeError, self._floatraiseskey) self.assertRaises(TypeError, self._noneraiseskey) def testNonIntegerValueRaises(self): self.assertRaises(TypeError, self._stringraisesvalue) self.assertRaises(TypeError, self._floatraisesvalue) self.assertRaises(TypeError, self._noneraisesvalue) def _stringraiseskey(self): self.t['c'] = 1 def _floatraiseskey(self): self.t[2.5] = 1 def _noneraiseskey(self): self.t[None] = 1 def _stringraisesvalue(self): self.t[1] = 'c' def _floatraisesvalue(self): self.t[1] = 1.4 def _noneraisesvalue(self): self.t[1] = None class TestIFBTrees(TestCase): def setUp(self): self.t = IFBTree() def testNonIntegerKeyRaises(self): self.assertRaises(TypeError, self._stringraiseskey) self.assertRaises(TypeError, self._floatraiseskey) self.assertRaises(TypeError, self._noneraiseskey) def testNonNumericValueRaises(self): self.assertRaises(TypeError, self._stringraisesvalue) self.assertRaises(TypeError, self._noneraisesvalue) self.t[1] = 1 self.t[1] = 1.0 def _stringraiseskey(self): self.t['c'] = 1 def _floatraiseskey(self): self.t[2.5] = 1 def _noneraiseskey(self): self.t[None] = 1 def _stringraisesvalue(self): self.t[1] = 'c' def _floatraisesvalue(self): self.t[1] = 1.4 def _noneraisesvalue(self): self.t[1] = None class TestI_Sets(TestCase): def testBadBadKeyAfterFirst(self): self.assertRaises(TypeError, self.t.__class__, [1, '']) self.assertRaises(TypeError, self.t.update, [1, '']) del self.t def testNonIntegerInsertRaises(self): self.assertRaises(TypeError,self._insertstringraises) self.assertRaises(TypeError,self._insertfloatraises) self.assertRaises(TypeError,self._insertnoneraises) def _insertstringraises(self): self.t.insert('a') def _insertfloatraises(self): self.t.insert(1.4) def _insertnoneraises(self): self.t.insert(None) class TestIOSets(TestI_Sets): def setUp(self): self.t = IOSet() class TestIOTreeSets(TestI_Sets): def setUp(self): self.t = IOTreeSet() class TestIISets(TestI_Sets): def setUp(self): self.t = IISet() class TestIITreeSets(TestI_Sets): def setUp(self): self.t = IITreeSet() class TestLOSets(TestI_Sets): def setUp(self): self.t = LOSet() class TestLOTreeSets(TestI_Sets): def setUp(self): self.t = LOTreeSet() class TestLLSets(TestI_Sets): def setUp(self): self.t = LLSet() class TestLLTreeSets(TestI_Sets): def setUp(self): self.t = LLTreeSet() class DegenerateBTree(TestCase): # Build a degenerate tree (set). Boxes are BTree nodes. There are # 5 leaf buckets, each containing a single int. Keys in the BTree # nodes don't appear in the buckets. Seven BTree nodes are purely # indirection nodes (no keys). Buckets aren't all at the same depth: # # +------------------------+ # | 4 | # +------------------------+ # | | # | v # | +-+ # | | | # | +-+ # | | # v v # +-------+ +-------------+ # | 2 | | 6 10 | # +-------+ +-------------+ # | | | | | # v v v v v # +-+ +-+ +-+ +-+ +-+ # | | | | | | | | | | # +-+ +-+ +-+ +-+ +-+ # | | | | | # v v v v v # 1 3 +-+ 7 11 # | | # +-+ # | # v # 5 # # This is nasty for many algorithms. Consider a high-end range search # for 4. The BTree nodes direct it to the 5 bucket, but the correct # answer is the 3 bucket, which requires going in a different direction # at the very top node already. Consider a low-end range search for # 9. The BTree nodes direct it to the 7 bucket, but the correct answer # is the 11 bucket. This is also a nasty-case tree for deletions. def _build_degenerate_tree(self): # Build the buckets and chain them together. bucket11 = IISet([11]) bucket7 = IISet() bucket7.__setstate__(((7,), bucket11)) bucket5 = IISet() bucket5.__setstate__(((5,), bucket7)) bucket3 = IISet() bucket3.__setstate__(((3,), bucket5)) bucket1 = IISet() bucket1.__setstate__(((1,), bucket3)) # Build the deepest layers of indirection nodes. ts = IITreeSet tree1 = ts() tree1.__setstate__(((bucket1,), bucket1)) tree3 = ts() tree3.__setstate__(((bucket3,), bucket3)) tree5lower = ts() tree5lower.__setstate__(((bucket5,), bucket5)) tree5 = ts() tree5.__setstate__(((tree5lower,), bucket5)) tree7 = ts() tree7.__setstate__(((bucket7,), bucket7)) tree11 = ts() tree11.__setstate__(((bucket11,), bucket11)) # Paste together the middle layers. tree13 = ts() tree13.__setstate__(((tree1, 2, tree3), bucket1)) tree5711lower = ts() tree5711lower.__setstate__(((tree5, 6, tree7, 10, tree11), bucket5)) tree5711 = ts() tree5711.__setstate__(((tree5711lower,), bucket5)) # One more. t = ts() t.__setstate__(((tree13, 4, tree5711), bucket1)) t._check() check(t) return t, [1, 3, 5, 7, 11] def testBasicOps(self): t, keys = self._build_degenerate_tree() self.assertEqual(len(t), len(keys)) self.assertEqual(list(t.keys()), keys) # has_key actually returns the depth of a bucket. self.assertEqual(t.has_key(1), 4) self.assertEqual(t.has_key(3), 4) self.assertEqual(t.has_key(5), 6) self.assertEqual(t.has_key(7), 5) self.assertEqual(t.has_key(11), 5) for i in 0, 2, 4, 6, 8, 9, 10, 12: self.assert_(i not in t) def _checkRanges(self, tree, keys): self.assertEqual(len(tree), len(keys)) sorted_keys = keys[:] sorted_keys.sort() self.assertEqual(list(tree.keys()), sorted_keys) for k in keys: self.assert_(k in tree) if keys: lokey = sorted_keys[0] hikey = sorted_keys[-1] self.assertEqual(lokey, tree.minKey()) self.assertEqual(hikey, tree.maxKey()) else: lokey = hikey = 42 # Try all range searches. for lo in range(lokey - 1, hikey + 2): for hi in range(lo - 1, hikey + 2): for skipmin in False, True: for skipmax in False, True: wantlo, wanthi = lo, hi if skipmin: wantlo += 1 if skipmax: wanthi -= 1 want = [k for k in keys if wantlo <= k <= wanthi] got = list(tree.keys(lo, hi, skipmin, skipmax)) self.assertEqual(want, got) def testRanges(self): t, keys = self._build_degenerate_tree() self._checkRanges(t, keys) def testDeletes(self): # Delete keys in all possible orders, checking each tree along # the way. # This is a tough test. Previous failure modes included: # 1. A variety of assertion failures in _checkRanges. # 2. Assorted "Invalid firstbucket pointer" failures at # seemingly random times, coming out of the BTree destructor. # 3. Under Python 2.3 CVS, some baffling # RuntimeWarning: tp_compare didn't return -1 or -2 for exception # warnings, possibly due to memory corruption after a BTree # goes insane. t, keys = self._build_degenerate_tree() for oneperm in permutations(keys): t, keys = self._build_degenerate_tree() for key in oneperm: t.remove(key) keys.remove(key) t._check() check(t) self._checkRanges(t, keys) # We removed all the keys, so the tree should be empty now. self.assertEqual(t.__getstate__(), None) # A damaged tree may trigger an "invalid firstbucket pointer" # failure at the time its destructor is invoked. Try to force # that to happen now, so it doesn't look like a baffling failure # at some unrelated line. del t # trigger destructor LP294788_ids = {} class ToBeDeleted(object): def __init__(self, id): assert type(id) is int #we don't want to store any object ref here self.id = id global LP294788_ids LP294788_ids[id] = 1 def __del__(self): global LP294788_ids LP294788_ids.pop(self.id, None) def __cmp__(self, other): return cmp(self.id, other.id) def __hash__(self): return hash(self.id) class BugFixes(TestCase): # Collector 1843. Error returns were effectively ignored in # Bucket_rangeSearch(), leading to "delayed" errors, or worse. def testFixed1843(self): t = IISet() t.insert(1) # This one used to fail to raise the TypeError when it occurred. self.assertRaises(TypeError, t.keys, "") # This one used to segfault. self.assertRaises(TypeError, t.keys, 0, "") def test_LP294788(self): # https://bugs.launchpad.net/bugs/294788 # BTree keeps some deleted objects referenced # The logic here together with the ToBeDeleted class is that # a separate reference dict is populated on object creation # and removed in __del__ # That means what's left in the reference dict is never GC'ed # therefore referenced somewhere # To simulate real life, some random data is used to exercise the tree t = OOBTree() trandom = random.Random('OOBTree') global LP294788_ids # /// BTree keys are integers, value is an object LP294788_ids = {} ids = {} for i in xrange(1024): if trandom.random() > 0.1: #add id = None while id is None or id in ids: id = trandom.randint(0,1000000) ids[id] = 1 t[id] = ToBeDeleted(id) else: #del id = trandom.choice(ids.keys()) del t[id] del ids[id] ids = ids.keys() trandom.shuffle(ids) for id in ids: del t[id] ids = None #to be on the safe side run a full GC gc.collect() #print LP294788_ids self.assertEqual(len(t), 0) self.assertEqual(len(LP294788_ids), 0) # \\\ # /// BTree keys are integers, value is a tuple having an object LP294788_ids = {} ids = {} for i in xrange(1024): if trandom.random() > 0.1: #add id = None while id is None or id in ids: id = trandom.randint(0,1000000) ids[id] = 1 t[id] = (id, ToBeDeleted(id), u'somename') else: #del id = trandom.choice(ids.keys()) del t[id] del ids[id] ids = ids.keys() trandom.shuffle(ids) for id in ids: del t[id] ids = None #to be on the safe side run a full GC gc.collect() #print LP294788_ids self.assertEqual(len(t), 0) self.assertEqual(len(LP294788_ids), 0) # \\\ # /// BTree keys are objects, value is an int t = OOBTree() LP294788_ids = {} ids = {} for i in xrange(1024): if trandom.random() > 0.1: #add id = None while id is None or id in ids: id = ToBeDeleted(trandom.randint(0,1000000)) ids[id] = 1 t[id] = 1 else: #del id = trandom.choice(ids.keys()) del ids[id] del t[id] ids = ids.keys() trandom.shuffle(ids) for id in ids: del t[id] #release all refs ids = obj = id = None #to be on the safe side run a full GC gc.collect() #print LP294788_ids self.assertEqual(len(t), 0) self.assertEqual(len(LP294788_ids), 0) # /// BTree keys are tuples having objects, value is an int t = OOBTree() LP294788_ids = {} ids = {} for i in xrange(1024): if trandom.random() > 0.1: #add id = None while id is None or id in ids: id = trandom.randint(0,1000000) id = (id, ToBeDeleted(id), u'somename') ids[id] = 1 t[id] = 1 else: #del id = trandom.choice(ids.keys()) del ids[id] del t[id] ids = ids.keys() trandom.shuffle(ids) for id in ids: del t[id] #release all refs ids = id = obj = key = None #to be on the safe side run a full GC gc.collect() #print LP294788_ids self.assertEqual(len(t), 0) self.assertEqual(len(LP294788_ids), 0) class IIBTreeTest(BTreeTests): def setUp(self): self.t = IIBTree() def testIIBTreeOverflow(self): good = set() b = self.t def trial(i): i = int(i) try: b[i] = 0 except TypeError: self.assertRaises(TypeError, b.__setitem__, 0, i) else: good.add(i) b[0] = i self.assertEqual(b[0], i) for i in range((1<<31) - 3, (1<<31) + 3): trial(i) trial(-i) del b[0] self.assertEqual(sorted(good), sorted(b)) class IFBTreeTest(BTreeTests): def setUp(self): self.t = IFBTree() class IOBTreeTest(BTreeTests): def setUp(self): self.t = IOBTree() class OIBTreeTest(BTreeTests): def setUp(self): self.t = OIBTree() class OOBTreeTest(BTreeTests): def setUp(self): self.t = OOBTree() if using64bits: class IIBTreeTest(BTreeTests, TestLongIntKeys, TestLongIntValues): def setUp(self): self.t = IIBTree() def getTwoValues(self): return 1, 2 class IFBTreeTest(BTreeTests, TestLongIntKeys): def setUp(self): self.t = IFBTree() def getTwoValues(self): return 0.5, 1.5 class IOBTreeTest(BTreeTests, TestLongIntKeys): def setUp(self): self.t = IOBTree() class OIBTreeTest(BTreeTests, TestLongIntValues): def setUp(self): self.t = OIBTree() def getTwoKeys(self): return object(), object() class LLBTreeTest(BTreeTests, TestLongIntKeys, TestLongIntValues): def setUp(self): self.t = LLBTree() def getTwoValues(self): return 1, 2 class LFBTreeTest(BTreeTests, TestLongIntKeys): def setUp(self): self.t = LFBTree() def getTwoValues(self): return 0.5, 1.5 class LOBTreeTest(BTreeTests, TestLongIntKeys): def setUp(self): self.t = LOBTree() class OLBTreeTest(BTreeTests, TestLongIntValues): def setUp(self): self.t = OLBTree() def getTwoKeys(self): return object(), object() class OOBTreeTest(BTreeTests): def setUp(self): self.t = OOBTree() # cmp error propagation tests class DoesntLikeBeingCompared: def __cmp__(self,other): raise ValueError('incomparable') class TestCmpError(TestCase): def testFoo(self): t = OOBTree() t['hello world'] = None try: t[DoesntLikeBeingCompared()] = None except ValueError,e: self.assertEqual(str(e), 'incomparable') else: self.fail('incomarable objects should not be allowed into ' 'the tree') # test for presence of generic names in module class ModuleTest(TestCase): module = None prefix = None iface = None def testNames(self): for name in ('Bucket', 'BTree', 'Set', 'TreeSet'): klass = getattr(self.module, name) self.assertEqual(klass.__module__, self.module.__name__) self.assert_(klass is getattr(self.module, self.prefix + name)) def testModuleProvides(self): self.assert_( zope.interface.verify.verifyObject(self.iface, self.module)) def testFamily(self): if self.prefix == 'OO': self.assert_( getattr(self.module, 'family', self) is self) elif 'L' in self.prefix: self.assert_(self.module.family is BTrees.family64) elif 'I' in self.prefix: self.assert_(self.module.family is BTrees.family32) class FamilyTest(TestCase): def test32(self): self.assert_( zope.interface.verify.verifyObject( BTrees.Interfaces.IBTreeFamily, BTrees.family32)) self.assertEquals( BTrees.family32.IO, BTrees.IOBTree) self.assertEquals( BTrees.family32.OI, BTrees.OIBTree) self.assertEquals( BTrees.family32.II, BTrees.IIBTree) self.assertEquals( BTrees.family32.IF, BTrees.IFBTree) self.assertEquals( BTrees.family32.OO, BTrees.OOBTree) s = IOTreeSet() s.insert(BTrees.family32.maxint) self.assert_(BTrees.family32.maxint in s) s = IOTreeSet() s.insert(BTrees.family32.minint) self.assert_(BTrees.family32.minint in s) s = IOTreeSet() # this next bit illustrates an, um, "interesting feature". If # the characteristics change to match the 64 bit version, please # feel free to change. big = BTrees.family32.maxint + 1 self.assertRaises(TypeError, s.insert, big) self.assertRaises(TypeError, s.insert, BTrees.family32.minint - 1) self.check_pickling(BTrees.family32) def test64(self): self.assert_( zope.interface.verify.verifyObject( BTrees.Interfaces.IBTreeFamily, BTrees.family64)) self.assertEquals( BTrees.family64.IO, BTrees.LOBTree) self.assertEquals( BTrees.family64.OI, BTrees.OLBTree) self.assertEquals( BTrees.family64.II, BTrees.LLBTree) self.assertEquals( BTrees.family64.IF, BTrees.LFBTree) self.assertEquals( BTrees.family64.OO, BTrees.OOBTree) s = LOTreeSet() s.insert(BTrees.family64.maxint) self.assert_(BTrees.family64.maxint in s) s = LOTreeSet() s.insert(BTrees.family64.minint) self.assert_(BTrees.family64.minint in s) s = LOTreeSet() self.assertRaises(ValueError, s.insert, BTrees.family64.maxint + 1) self.assertRaises(ValueError, s.insert, BTrees.family64.minint - 1) self.check_pickling(BTrees.family64) def check_pickling(self, family): # The "family" objects are singletons; they can be pickled and # unpickled, and the same instances will always be returned on # unpickling, whether from the same unpickler or different # unpicklers. s = pickle.dumps((family, family)) (f1, f2) = pickle.loads(s) self.failUnless(f1 is family) self.failUnless(f2 is family) # Using a single memo across multiple pickles: sio = StringIO.StringIO() p = pickle.Pickler(sio) p.dump(family) p.dump([family]) u = pickle.Unpickler(StringIO.StringIO(sio.getvalue())) f1 = u.load() f2, = u.load() self.failUnless(f1 is family) self.failUnless(f2 is family) # Using separate memos for each pickle: sio = StringIO.StringIO() p = pickle.Pickler(sio) p.dump(family) p.clear_memo() p.dump([family]) u = pickle.Unpickler(StringIO.StringIO(sio.getvalue())) f1 = u.load() f2, = u.load() self.failUnless(f1 is family) self.failUnless(f2 is family) class InternalKeysMappingTest(TestCase): """There must not be any internal keys not in the BTree """ def add_key(self, tree, key): tree[key] = key def test_internal_keys_after_deletion(self): """Make sure when a key's deleted, it's not an internal key We'll leverage __getstate__ to introspect the internal structures. We need to check BTrees with BTree children as well as BTrees with bucket children. """ from ZODB.MappingStorage import DB db = DB() conn = db.open() tree = conn.root.tree = self.t_class() i = 0 # Grow the btree until we have multiple buckets while 1: i += 1 self.add_key(tree, i) data = tree.__getstate__()[0] if len(data) >= 3: break transaction.commit() # Now, delete the internal key and make sure it's really gone key = data[1] del tree[key] data = tree.__getstate__()[0] self.assert_(data[1] != key) # The tree should have changed: self.assert_(tree._p_changed) # Grow the btree until we have multiple levels while 1: i += 1 self.add_key(tree, i) data = tree.__getstate__()[0] if data[0].__class__ == tree.__class__: assert len(data[2].__getstate__()[0]) >= 3 break # Now, delete the internal key and make sure it's really gone key = data[1] del tree[key] data = tree.__getstate__()[0] self.assert_(data[1] != key) transaction.abort() db.close() class InternalKeysSetTest: """There must not be any internal keys not in the TreeSet """ def add_key(self, tree, key): tree.add(key) def test_suite(): s = TestSuite() for kv in ('OO', 'II', 'IO', 'OI', 'IF', 'LL', 'LO', 'OL', 'LF', ): for name, bases in ( ('BTree', (InternalKeysMappingTest,)), ('TreeSet', (InternalKeysSetTest,)), ): klass = ClassType(kv + name + 'InternalKeyTest', bases, dict(t_class=globals()[kv+name])) s.addTest(makeSuite(klass)) for kv in ('OO', 'II', 'IO', 'OI', 'IF', 'LL', 'LO', 'OL', 'LF', ): for name, bases in ( ('Bucket', (MappingBase,)), ('TreeSet', (NormalSetTests,)), ('Set', (ExtendedSetTests,)), ): klass = ClassType(kv + name + 'Test', bases, dict(t_class=globals()[kv+name])) s.addTest(makeSuite(klass)) for kv, iface in ( ('OO', BTrees.Interfaces.IObjectObjectBTreeModule), ('IO', BTrees.Interfaces.IIntegerObjectBTreeModule), ('LO', BTrees.Interfaces.IIntegerObjectBTreeModule), ('OI', BTrees.Interfaces.IObjectIntegerBTreeModule), ('OL', BTrees.Interfaces.IObjectIntegerBTreeModule), ('II', BTrees.Interfaces.IIntegerIntegerBTreeModule), ('LL', BTrees.Interfaces.IIntegerIntegerBTreeModule), ('IF', BTrees.Interfaces.IIntegerFloatBTreeModule), ('LF', BTrees.Interfaces.IIntegerFloatBTreeModule)): s.addTest( makeSuite( ClassType( kv + 'ModuleTest', (ModuleTest,), dict( prefix=kv, module=getattr(BTrees, kv + 'BTree'), iface=iface)))) for klass in ( IIBTreeTest, IFBTreeTest, IOBTreeTest, OIBTreeTest, LLBTreeTest, LFBTreeTest, LOBTreeTest, OLBTreeTest, OOBTreeTest, # Note: there is no TestOOBTrees. The next three are # checking for assorted TypeErrors, and when both keys # and values are objects (OO), there's nothing to test. TestIIBTrees, TestIFBTrees, TestIOBTrees, TestOIBTrees, TestIOSets, TestIOTreeSets, TestIISets, TestIITreeSets, TestLOSets, TestLOTreeSets, TestLLSets, TestLLTreeSets, DegenerateBTree, TestCmpError, BugFixes, FamilyTest, ): s.addTest(makeSuite(klass)) return s ## utility functions def lsubtract(l1, l2): l1 = list(l1) l2 = list(l2) l = filter(lambda x, l1=l1: x not in l1, l2) l = l + filter(lambda x, l2=l2: x not in l2, l1) return l def realseq(itemsob): return [x for x in itemsob] def permutations(x): # Return a list of all permutations of list x. n = len(x) if n <= 1: return [x] result = [] x0 = x[0] for i in range(n): # Build the (n-1)! permutations with x[i] in the first position. xcopy = x[:] first, xcopy[i] = xcopy[i], x0 result.extend([[first] + p for p in permutations(xcopy[1:])]) return result def main(): TextTestRunner().run(test_suite()) if __name__ == '__main__': main() zope2.13-2.13.21/source/ZODB3/src/BTrees/tests/testSetOps.py0000644000175000017500000005152212214017464022072 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from unittest import TestCase, TestSuite, TextTestRunner, makeSuite from BTrees.OOBTree import OOBTree, OOBucket, OOSet, OOTreeSet from BTrees.IOBTree import IOBTree, IOBucket, IOSet, IOTreeSet from BTrees.IFBTree import IFBTree, IFBucket, IFSet, IFTreeSet from BTrees.IIBTree import IIBTree, IIBucket, IISet, IITreeSet from BTrees.OIBTree import OIBTree, OIBucket, OISet, OITreeSet from BTrees.LOBTree import LOBTree, LOBucket, LOSet, LOTreeSet from BTrees.LFBTree import LFBTree, LFBucket, LFSet, LFTreeSet from BTrees.LLBTree import LLBTree, LLBucket, LLSet, LLTreeSet from BTrees.OLBTree import OLBTree, OLBucket, OLSet, OLTreeSet # Subclasses have to set up: # builders - functions to build inputs, taking an optional keys arg # intersection, union, difference - set to the type-correct versions class SetResult(TestCase): def setUp(self): self.Akeys = [1, 3, 5, 6 ] self.Bkeys = [ 2, 3, 4, 6, 7] self.As = [makeset(self.Akeys) for makeset in self.builders] self.Bs = [makeset(self.Bkeys) for makeset in self.builders] self.emptys = [makeset() for makeset in self.builders] # Slow but obviously correct Python implementations of basic ops. def _union(self, x, y): result = list(x) for e in y: if e not in result: result.append(e) result.sort() return result def _intersection(self, x, y): result = [] for e in x: if e in y: result.append(e) return result def _difference(self, x, y): result = list(x) for e in y: if e in result: result.remove(e) # Difference preserves LHS values. if hasattr(x, "values"): result = [(k, x[k]) for k in result] return result def testNone(self): for op in self.union, self.intersection, self.difference: C = op(None, None) self.assert_(C is None) for op in self.union, self.intersection, self.difference: for A in self.As: C = op(A, None) self.assert_(C is A) C = op(None, A) if op is self.difference: self.assert_(C is None) else: self.assert_(C is A) def testEmptyUnion(self): for A in self.As: for E in self.emptys: C = self.union(A, E) self.assert_(not hasattr(C, "values")) self.assertEqual(list(C), self.Akeys) C = self.union(E, A) self.assert_(not hasattr(C, "values")) self.assertEqual(list(C), self.Akeys) def testEmptyIntersection(self): for A in self.As: for E in self.emptys: C = self.intersection(A, E) self.assert_(not hasattr(C, "values")) self.assertEqual(list(C), []) C = self.intersection(E, A) self.assert_(not hasattr(C, "values")) self.assertEqual(list(C), []) def testEmptyDifference(self): for A in self.As: for E in self.emptys: C = self.difference(A, E) # Difference preserves LHS values. self.assertEqual(hasattr(C, "values"), hasattr(A, "values")) if hasattr(A, "values"): self.assertEqual(list(C.items()), list(A.items())) else: self.assertEqual(list(C), self.Akeys) C = self.difference(E, A) self.assertEqual(hasattr(C, "values"), hasattr(E, "values")) self.assertEqual(list(C), []) def testUnion(self): inputs = self.As + self.Bs for A in inputs: for B in inputs: C = self.union(A, B) self.assert_(not hasattr(C, "values")) self.assertEqual(list(C), self._union(A, B)) def testIntersection(self): inputs = self.As + self.Bs for A in inputs: for B in inputs: C = self.intersection(A, B) self.assert_(not hasattr(C, "values")) self.assertEqual(list(C), self._intersection(A, B)) def testDifference(self): inputs = self.As + self.Bs for A in inputs: for B in inputs: C = self.difference(A, B) # Difference preserves LHS values. self.assertEqual(hasattr(C, "values"), hasattr(A, "values")) want = self._difference(A, B) if hasattr(A, "values"): self.assertEqual(list(C.items()), want) else: self.assertEqual(list(C), want) def testLargerInputs(self): from random import randint MAXSIZE = 200 MAXVAL = 400 for i in range(3): n = randint(0, MAXSIZE) Akeys = [randint(1, MAXVAL) for j in range(n)] As = [makeset(Akeys) for makeset in self.builders] Akeys = IISet(Akeys) n = randint(0, MAXSIZE) Bkeys = [randint(1, MAXVAL) for j in range(n)] Bs = [makeset(Bkeys) for makeset in self.builders] Bkeys = IISet(Bkeys) for op, simulator in ((self.union, self._union), (self.intersection, self._intersection), (self.difference, self._difference)): for A in As: for B in Bs: got = op(A, B) want = simulator(Akeys, Bkeys) self.assertEqual(list(got), want, (A, B, Akeys, Bkeys, list(got), want)) # Given a mapping builder (IIBTree, OOBucket, etc), return a function # that builds an object of that type given only a list of keys. def makeBuilder(mapbuilder): def result(keys=[], mapbuilder=mapbuilder): return mapbuilder(zip(keys, keys)) return result class PureOO(SetResult): from BTrees.OOBTree import union, intersection, difference builders = OOSet, OOTreeSet, makeBuilder(OOBTree), makeBuilder(OOBucket) class PureII(SetResult): from BTrees.IIBTree import union, intersection, difference builders = IISet, IITreeSet, makeBuilder(IIBTree), makeBuilder(IIBucket) class PureIO(SetResult): from BTrees.IOBTree import union, intersection, difference builders = IOSet, IOTreeSet, makeBuilder(IOBTree), makeBuilder(IOBucket) class PureIF(SetResult): from BTrees.IFBTree import union, intersection, difference builders = IFSet, IFTreeSet, makeBuilder(IFBTree), makeBuilder(IFBucket) class PureOI(SetResult): from BTrees.OIBTree import union, intersection, difference builders = OISet, OITreeSet, makeBuilder(OIBTree), makeBuilder(OIBucket) class PureLL(SetResult): from BTrees.LLBTree import union, intersection, difference builders = LLSet, LLTreeSet, makeBuilder(LLBTree), makeBuilder(LLBucket) class PureLO(SetResult): from BTrees.LOBTree import union, intersection, difference builders = LOSet, LOTreeSet, makeBuilder(LOBTree), makeBuilder(LOBucket) class PureLF(SetResult): from BTrees.LFBTree import union, intersection, difference builders = LFSet, LFTreeSet, makeBuilder(LFBTree), makeBuilder(LFBucket) class PureOL(SetResult): from BTrees.OLBTree import union, intersection, difference builders = OLSet, OLTreeSet, makeBuilder(OLBTree), makeBuilder(OLBucket) # Subclasses must set up (as class variables): # multiunion, union # mkset, mktreeset # mkbucket, mkbtree class MultiUnion(TestCase): def testEmpty(self): self.assertEqual(len(self.multiunion([])), 0) def testOne(self): for sequence in [3], range(20), range(-10, 0, 2) + range(1, 10, 2): seq1 = sequence[:] seq2 = sequence[:] seq2.reverse() seqsorted = sequence[:] seqsorted.sort() for seq in seq1, seq2, seqsorted: for builder in self.mkset, self.mktreeset: input = builder(seq) output = self.multiunion([input]) self.assertEqual(len(seq), len(output)) self.assertEqual(seqsorted, list(output)) def testValuesIgnored(self): for builder in self.mkbucket, self.mkbtree: input = builder([(1, 2), (3, 4), (5, 6)]) output = self.multiunion([input]) self.assertEqual([1, 3, 5], list(output)) def testBigInput(self): N = 100000 input = self.mkset(range(N)) output = self.multiunion([input] * 10) self.assertEqual(len(output), N) self.assertEqual(output.minKey(), 0) self.assertEqual(output.maxKey(), N-1) self.assertEqual(list(output), range(N)) def testLotsOfLittleOnes(self): from random import shuffle N = 5000 inputs = [] mkset, mktreeset = self.mkset, self.mktreeset for i in range(N): base = i * 4 - N inputs.append(mkset([base, base+1])) inputs.append(mktreeset([base+2, base+3])) shuffle(inputs) output = self.multiunion(inputs) self.assertEqual(len(output), N*4) self.assertEqual(list(output), range(-N, 3*N)) def testFunkyKeyIteration(self): # The internal set iteration protocol allows "iterating over" a # a single key as if it were a set. N = 100 union, mkset = self.union, self.mkset slow = mkset() for i in range(N): slow = union(slow, mkset([i])) fast = self.multiunion(range(N)) # acts like N distinct singleton sets self.assertEqual(len(slow), N) self.assertEqual(len(fast), N) self.assertEqual(list(slow), list(fast)) self.assertEqual(list(fast), range(N)) class TestIIMultiUnion(MultiUnion): from BTrees.IIBTree import multiunion, union from BTrees.IIBTree import IISet as mkset, IITreeSet as mktreeset from BTrees.IIBTree import IIBucket as mkbucket, IIBTree as mkbtree class TestIOMultiUnion(MultiUnion): from BTrees.IOBTree import multiunion, union from BTrees.IOBTree import IOSet as mkset, IOTreeSet as mktreeset from BTrees.IOBTree import IOBucket as mkbucket, IOBTree as mkbtree class TestIFMultiUnion(MultiUnion): from BTrees.IFBTree import multiunion, union from BTrees.IFBTree import IFSet as mkset, IFTreeSet as mktreeset from BTrees.IFBTree import IFBucket as mkbucket, IFBTree as mkbtree class TestLLMultiUnion(MultiUnion): from BTrees.LLBTree import multiunion, union from BTrees.LLBTree import LLSet as mkset, LLTreeSet as mktreeset from BTrees.LLBTree import LLBucket as mkbucket, LLBTree as mkbtree class TestLOMultiUnion(MultiUnion): from BTrees.LOBTree import multiunion, union from BTrees.LOBTree import LOSet as mkset, LOTreeSet as mktreeset from BTrees.LOBTree import LOBucket as mkbucket, LOBTree as mkbtree class TestLFMultiUnion(MultiUnion): from BTrees.LFBTree import multiunion, union from BTrees.LFBTree import LFSet as mkset, LFTreeSet as mktreeset from BTrees.LFBTree import LFBucket as mkbucket, LFBTree as mkbtree # Check that various special module functions are and aren't imported from # the expected BTree modules. class TestImports(TestCase): def testWeightedUnion(self): from BTrees.IIBTree import weightedUnion from BTrees.OIBTree import weightedUnion try: from BTrees.IOBTree import weightedUnion except ImportError: pass else: self.fail("IOBTree shouldn't have weightedUnion") from BTrees.LLBTree import weightedUnion from BTrees.OLBTree import weightedUnion try: from BTrees.LOBTree import weightedUnion except ImportError: pass else: self.fail("LOBTree shouldn't have weightedUnion") try: from BTrees.OOBTree import weightedUnion except ImportError: pass else: self.fail("OOBTree shouldn't have weightedUnion") def testWeightedIntersection(self): from BTrees.IIBTree import weightedIntersection from BTrees.OIBTree import weightedIntersection try: from BTrees.IOBTree import weightedIntersection except ImportError: pass else: self.fail("IOBTree shouldn't have weightedIntersection") from BTrees.LLBTree import weightedIntersection from BTrees.OLBTree import weightedIntersection try: from BTrees.LOBTree import weightedIntersection except ImportError: pass else: self.fail("LOBTree shouldn't have weightedIntersection") try: from BTrees.OOBTree import weightedIntersection except ImportError: pass else: self.fail("OOBTree shouldn't have weightedIntersection") def testMultiunion(self): from BTrees.IIBTree import multiunion from BTrees.IOBTree import multiunion try: from BTrees.OIBTree import multiunion except ImportError: pass else: self.fail("OIBTree shouldn't have multiunion") from BTrees.LLBTree import multiunion from BTrees.LOBTree import multiunion try: from BTrees.OLBTree import multiunion except ImportError: pass else: self.fail("OLBTree shouldn't have multiunion") try: from BTrees.OOBTree import multiunion except ImportError: pass else: self.fail("OOBTree shouldn't have multiunion") # Subclasses must set up (as class variables): # weightedUnion, weightedIntersection # builders -- sequence of constructors, taking items # union, intersection -- the module routines of those names # mkbucket -- the module bucket builder class Weighted(TestCase): def setUp(self): self.Aitems = [(1, 10), (3, 30), (5, 50), (6, 60)] self.Bitems = [(2, 21), (3, 31), (4, 41), (6, 61), (7, 71)] self.As = [make(self.Aitems) for make in self.builders] self.Bs = [make(self.Bitems) for make in self.builders] self.emptys = [make([]) for make in self.builders] weights = [] for w1 in -3, -1, 0, 1, 7: for w2 in -3, -1, 0, 1, 7: weights.append((w1, w2)) self.weights = weights def testBothNone(self): for op in self.weightedUnion, self.weightedIntersection: w, C = op(None, None) self.assert_(C is None) self.assertEqual(w, 0) w, C = op(None, None, 42, 666) self.assert_(C is None) self.assertEqual(w, 0) def testLeftNone(self): for op in self.weightedUnion, self.weightedIntersection: for A in self.As + self.emptys: w, C = op(None, A) self.assert_(C is A) self.assertEqual(w, 1) w, C = op(None, A, 42, 666) self.assert_(C is A) self.assertEqual(w, 666) def testRightNone(self): for op in self.weightedUnion, self.weightedIntersection: for A in self.As + self.emptys: w, C = op(A, None) self.assert_(C is A) self.assertEqual(w, 1) w, C = op(A, None, 42, 666) self.assert_(C is A) self.assertEqual(w, 42) # If obj is a set, return a bucket with values all 1; else return obj. def _normalize(self, obj): if isaset(obj): obj = self.mkbucket(zip(obj, [1] * len(obj))) return obj # Python simulation of weightedUnion. def _wunion(self, A, B, w1=1, w2=1): if isaset(A) and isaset(B): return 1, self.union(A, B).keys() A = self._normalize(A) B = self._normalize(B) result = [] for key in self.union(A, B): v1 = A.get(key, 0) v2 = B.get(key, 0) result.append((key, v1*w1 + v2*w2)) return 1, result def testUnion(self): inputs = self.As + self.Bs + self.emptys for A in inputs: for B in inputs: want_w, want_s = self._wunion(A, B) got_w, got_s = self.weightedUnion(A, B) self.assertEqual(got_w, want_w) if isaset(got_s): self.assertEqual(got_s.keys(), want_s) else: self.assertEqual(got_s.items(), want_s) for w1, w2 in self.weights: want_w, want_s = self._wunion(A, B, w1, w2) got_w, got_s = self.weightedUnion(A, B, w1, w2) self.assertEqual(got_w, want_w) if isaset(got_s): self.assertEqual(got_s.keys(), want_s) else: self.assertEqual(got_s.items(), want_s) # Python simulation weightedIntersection. def _wintersection(self, A, B, w1=1, w2=1): if isaset(A) and isaset(B): return w1 + w2, self.intersection(A, B).keys() A = self._normalize(A) B = self._normalize(B) result = [] for key in self.intersection(A, B): result.append((key, A[key]*w1 + B[key]*w2)) return 1, result def testIntersection(self): inputs = self.As + self.Bs + self.emptys for A in inputs: for B in inputs: want_w, want_s = self._wintersection(A, B) got_w, got_s = self.weightedIntersection(A, B) self.assertEqual(got_w, want_w) if isaset(got_s): self.assertEqual(got_s.keys(), want_s) else: self.assertEqual(got_s.items(), want_s) for w1, w2 in self.weights: want_w, want_s = self._wintersection(A, B, w1, w2) got_w, got_s = self.weightedIntersection(A, B, w1, w2) self.assertEqual(got_w, want_w) if isaset(got_s): self.assertEqual(got_s.keys(), want_s) else: self.assertEqual(got_s.items(), want_s) # Given a set builder (like OITreeSet or OISet), return a function that # takes a list of (key, value) pairs and builds a set out of the keys. def itemsToSet(setbuilder): def result(items, setbuilder=setbuilder): return setbuilder([key for key, value in items]) return result class TestWeightedII(Weighted): from BTrees.IIBTree import weightedUnion, weightedIntersection from BTrees.IIBTree import union, intersection from BTrees.IIBTree import IIBucket as mkbucket builders = IIBucket, IIBTree, itemsToSet(IISet), itemsToSet(IITreeSet) class TestWeightedOI(Weighted): from BTrees.OIBTree import weightedUnion, weightedIntersection from BTrees.OIBTree import union, intersection from BTrees.OIBTree import OIBucket as mkbucket builders = OIBucket, OIBTree, itemsToSet(OISet), itemsToSet(OITreeSet) class TestWeightedLL(Weighted): from BTrees.LLBTree import weightedUnion, weightedIntersection from BTrees.LLBTree import union, intersection from BTrees.LLBTree import LLBucket as mkbucket builders = LLBucket, LLBTree, itemsToSet(LLSet), itemsToSet(LLTreeSet) class TestWeightedOL(Weighted): from BTrees.OLBTree import weightedUnion, weightedIntersection from BTrees.OLBTree import union, intersection from BTrees.OLBTree import OLBucket as mkbucket builders = OLBucket, OLBTree, itemsToSet(OLSet), itemsToSet(OLTreeSet) # 'thing' is a bucket, btree, set or treeset. Return true iff it's one of the # latter two. def isaset(thing): return not hasattr(thing, 'values') def test_suite(): s = TestSuite() for klass in ( TestIIMultiUnion, TestIOMultiUnion, TestIFMultiUnion, TestLLMultiUnion, TestLOMultiUnion, TestLFMultiUnion, TestImports, PureOO, PureII, PureIO, PureIF, PureOI, PureLL, PureLO, PureLF, PureOL, TestWeightedII, TestWeightedOI, TestWeightedLL, TestWeightedOL, ): s.addTest(makeSuite(klass)) return s def main(): TextTestRunner().run(test_suite()) if __name__ == '__main__': main() zope2.13-2.13.21/source/ZODB3/src/BTrees/tests/__init__.py0000644000175000017500000000006412214017464021507 0ustar arnauarnau# If tests is a package, debugging is a bit easier. zope2.13-2.13.21/source/ZODB3/src/BTrees/tests/testBTreesUnicode.py0000644000175000017500000000465212214017464023352 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import unittest from BTrees.OOBTree import OOBTree # When an OOBtree contains unicode strings as keys, # it is neccessary accessing non-unicode strings are # either ascii strings or encoded as unicoded using the # corresponding encoding encoding = 'ISO-8859-1' class TestBTreesUnicode(unittest.TestCase): """ test unicode""" def setUp(self): """setup an OOBTree with some unicode strings""" self.s = unicode('dreit\xe4gigen', 'latin1') self.data = [('alien', 1), ('k\xf6nnten', 2), ('fox', 3), ('future', 4), ('quick', 5), ('zerst\xf6rt', 6), (unicode('dreit\xe4gigen','latin1'), 7), ] self.tree = OOBTree() for k, v in self.data: if isinstance(k, str): k = unicode(k, 'latin1') self.tree[k] = v def testAllKeys(self): # check every item of the tree for k, v in self.data: if isinstance(k, str): k = unicode(k, encoding) self.assert_(self.tree.has_key(k)) self.assertEqual(self.tree[k], v) def testUnicodeKeys(self): # try to access unicode keys in tree k, v = self.data[-1] self.assertEqual(k, self.s) self.assertEqual(self.tree[k], v) self.assertEqual(self.tree[self.s], v) def testAsciiKeys(self): # try to access some "plain ASCII" keys in the tree for k, v in self.data[0], self.data[2]: self.assert_(isinstance(k, str)) self.assertEqual(self.tree[k], v) def test_suite(): return unittest.makeSuite(TestBTreesUnicode) def main(): unittest.TextTestRunner().run(test_suite()) if __name__ == '__main__': main() zope2.13-2.13.21/source/ZODB3/src/BTrees/tests/test_fsBTree.py0000644000175000017500000000224412214017464022343 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import unittest def test_fsbucket_string_conversion(): """ fsBuckets have toString and fromString methods that can be used to get and set their state very efficiently: >>> from BTrees.fsBTree import fsBucket >>> b = fsBucket([(c*2, c*6) for c in 'abcdef']) >>> import pprint >>> b.toString() 'aabbccddeeffaaaaaabbbbbbccccccddddddeeeeeeffffff' >>> b2 = fsBucket().fromString(b.toString()) >>> b.__getstate__() == b2.__getstate__() True """ def test_suite(): return doctest.DocTestSuite() zope2.13-2.13.21/source/ZODB3/src/BTrees/tests/testConflict.py0000644000175000017500000006751112214017464022423 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import os from unittest import TestCase, TestSuite, makeSuite from types import ClassType from BTrees.OOBTree import OOBTree, OOBucket, OOSet, OOTreeSet from BTrees.IOBTree import IOBTree, IOBucket, IOSet, IOTreeSet from BTrees.IIBTree import IIBTree, IIBucket, IISet, IITreeSet from BTrees.IFBTree import IFBTree, IFBucket, IFSet, IFTreeSet from BTrees.OIBTree import OIBTree, OIBucket, OISet, OITreeSet from BTrees.LOBTree import LOBTree, LOBucket, LOSet, LOTreeSet from BTrees.LLBTree import LLBTree, LLBucket, LLSet, LLTreeSet from BTrees.LFBTree import LFBTree, LFBucket, LFSet, LFTreeSet from BTrees.OLBTree import OLBTree, OLBucket, OLSet, OLTreeSet import transaction from ZODB.POSException import ConflictError class Base: """ Tests common to all types: sets, buckets, and BTrees """ storage = None def setUp(self): self.t = self.t_type() def tearDown(self): transaction.abort() del self.t if self.storage is not None: self.storage.close() self.storage.cleanup() def openDB(self): from ZODB.FileStorage import FileStorage from ZODB.DB import DB n = 'fs_tmp__%s' % os.getpid() self.storage = FileStorage(n) self.db = DB(self.storage) return self.db class MappingBase(Base): """ Tests common to mappings (buckets, btrees) """ def _deletefail(self): del self.t[1] def _setupConflict(self): l=[ -5124, -7377, 2274, 8801, -9901, 7327, 1565, 17, -679, 3686, -3607, 14, 6419, -5637, 6040, -4556, -8622, 3847, 7191, -4067] e1=[(-1704, 0), (5420, 1), (-239, 2), (4024, 3), (-6984, 4)] e2=[(7745, 0), (4868, 1), (-2548, 2), (-2711, 3), (-3154, 4)] base=self.t base.update([(i, i*i) for i in l[:20]]) b1=base.__class__(base) b2=base.__class__(base) bm=base.__class__(base) items=base.items() return base, b1, b2, bm, e1, e2, items def testSimpleConflict(self): # Unlike all the other tests, invoke conflict resolution # by committing a transaction and catching a conflict # in the storage. self.openDB() r1 = self.db.open().root() r1["t"] = self.t transaction.commit() r2 = self.db.open().root() copy = r2["t"] list(copy) # unghostify self.assertEqual(self.t._p_serial, copy._p_serial) self.t.update({1:2, 2:3}) transaction.commit() copy.update({3:4}) transaction.commit() def testMergeDelete(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() del b1[items[1][0]] del b2[items[5][0]] del b1[items[-1][0]] del b2[items[-2][0]] del bm[items[1][0]] del bm[items[5][0]] del bm[items[-1][0]] del bm[items[-2][0]] test_merge(base, b1, b2, bm, 'merge delete') def testMergeDeleteAndUpdate(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() del b1[items[1][0]] b2[items[5][0]]=1 del b1[items[-1][0]] b2[items[-2][0]]=2 del bm[items[1][0]] bm[items[5][0]]=1 del bm[items[-1][0]] bm[items[-2][0]]=2 test_merge(base, b1, b2, bm, 'merge update and delete') def testMergeUpdate(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() b1[items[0][0]]=1 b2[items[5][0]]=2 b1[items[-1][0]]=3 b2[items[-2][0]]=4 bm[items[0][0]]=1 bm[items[5][0]]=2 bm[items[-1][0]]=3 bm[items[-2][0]]=4 test_merge(base, b1, b2, bm, 'merge update') def testFailMergeDelete(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() del b1[items[0][0]] del b2[items[0][0]] test_merge(base, b1, b2, bm, 'merge conflicting delete', should_fail=1) def testFailMergeUpdate(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() b1[items[0][0]]=1 b2[items[0][0]]=2 test_merge(base, b1, b2, bm, 'merge conflicting update', should_fail=1) def testFailMergeDeleteAndUpdate(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() del b1[items[0][0]] b2[items[0][0]]=-9 test_merge(base, b1, b2, bm, 'merge conflicting update and delete', should_fail=1) def testMergeInserts(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() b1[-99999]=-99999 b1[e1[0][0]]=e1[0][1] b2[99999]=99999 b2[e1[2][0]]=e1[2][1] bm[-99999]=-99999 bm[e1[0][0]]=e1[0][1] bm[99999]=99999 bm[e1[2][0]]=e1[2][1] test_merge(base, b1, b2, bm, 'merge insert') def testMergeInsertsFromEmpty(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() base.clear() b1.clear() b2.clear() bm.clear() b1.update(e1) bm.update(e1) b2.update(e2) bm.update(e2) test_merge(base, b1, b2, bm, 'merge insert from empty') def testFailMergeEmptyAndFill(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() b1.clear() bm.clear() b2.update(e2) bm.update(e2) test_merge(base, b1, b2, bm, 'merge insert from empty', should_fail=1) def testMergeEmpty(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() b1.clear() bm.clear() test_merge(base, b1, b2, bm, 'empty one and not other', should_fail=1) def testFailMergeInsert(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() b1[-99999]=-99999 b1[e1[0][0]]=e1[0][1] b2[99999]=99999 b2[e1[0][0]]=e1[0][1] test_merge(base, b1, b2, bm, 'merge conflicting inserts', should_fail=1) class SetTests(Base): "Set (as opposed to TreeSet) specific tests." def _setupConflict(self): l=[ -5124, -7377, 2274, 8801, -9901, 7327, 1565, 17, -679, 3686, -3607, 14, 6419, -5637, 6040, -4556, -8622, 3847, 7191, -4067] e1=[-1704, 5420, -239, 4024, -6984] e2=[7745, 4868, -2548, -2711, -3154] base=self.t base.update(l) b1=base.__class__(base) b2=base.__class__(base) bm=base.__class__(base) items=base.keys() return base, b1, b2, bm, e1, e2, items def testMergeDelete(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() b1.remove(items[1]) b2.remove(items[5]) b1.remove(items[-1]) b2.remove(items[-2]) bm.remove(items[1]) bm.remove(items[5]) bm.remove(items[-1]) bm.remove(items[-2]) test_merge(base, b1, b2, bm, 'merge delete') def testFailMergeDelete(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() b1.remove(items[0]) b2.remove(items[0]) test_merge(base, b1, b2, bm, 'merge conflicting delete', should_fail=1) def testMergeInserts(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() b1.insert(-99999) b1.insert(e1[0]) b2.insert(99999) b2.insert(e1[2]) bm.insert(-99999) bm.insert(e1[0]) bm.insert(99999) bm.insert(e1[2]) test_merge(base, b1, b2, bm, 'merge insert') def testMergeInsertsFromEmpty(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() base.clear() b1.clear() b2.clear() bm.clear() b1.update(e1) bm.update(e1) b2.update(e2) bm.update(e2) test_merge(base, b1, b2, bm, 'merge insert from empty') def testFailMergeEmptyAndFill(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() b1.clear() bm.clear() b2.update(e2) bm.update(e2) test_merge(base, b1, b2, bm, 'merge insert from empty', should_fail=1) def testMergeEmpty(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() b1.clear() bm.clear() test_merge(base, b1, b2, bm, 'empty one and not other', should_fail=1) def testFailMergeInsert(self): base, b1, b2, bm, e1, e2, items = self._setupConflict() b1.insert(-99999) b1.insert(e1[0]) b2.insert(99999) b2.insert(e1[0]) test_merge(base, b1, b2, bm, 'merge conflicting inserts', should_fail=1) def test_merge(o1, o2, o3, expect, message='failed to merge', should_fail=0): s1 = o1.__getstate__() s2 = o2.__getstate__() s3 = o3.__getstate__() expected = expect.__getstate__() if expected is None: expected = ((((),),),) if should_fail: try: merged = o1._p_resolveConflict(s1, s2, s3) except ConflictError, err: pass else: assert 0, message else: merged = o1._p_resolveConflict(s1, s2, s3) assert merged == expected, message class NastyConfict(Base, TestCase): t_type = OOBTree # This tests a problem that cropped up while trying to write # testBucketSplitConflict (below): conflict resolution wasn't # working at all in non-trivial cases. Symptoms varied from # strange complaints about pickling (despite that the test isn't # doing any *directly*), thru SystemErrors from Python and # AssertionErrors inside the BTree code. def testResolutionBlowsUp(self): b = self.t for i in range(0, 200, 4): b[i] = i # bucket 0 has 15 values: 0, 4 .. 56 # bucket 1 has 15 values: 60, 64 .. 116 # bucket 2 has 20 values: 120, 124 .. 196 state = b.__getstate__() # Looks like: ((bucket0, 60, bucket1, 120, bucket2), firstbucket) # If these fail, the *preconditions* for running the test aren't # satisfied -- the test itself hasn't been run yet. self.assertEqual(len(state), 2) self.assertEqual(len(state[0]), 5) self.assertEqual(state[0][1], 60) self.assertEqual(state[0][3], 120) # Invoke conflict resolution by committing a transaction. self.openDB() r1 = self.db.open().root() r1["t"] = self.t transaction.commit() r2 = self.db.open().root() copy = r2["t"] # Make sure all of copy is loaded. list(copy.values()) self.assertEqual(self.t._p_serial, copy._p_serial) self.t.update({1:2, 2:3}) transaction.commit() copy.update({3:4}) transaction.commit() # if this doesn't blow up list(copy.values()) # and this doesn't either, then fine def testBucketSplitConflict(self): # Tests that a bucket split is viewed as a conflict. # It's (almost necessarily) a white-box test, and sensitive to # implementation details. b = self.t for i in range(0, 200, 4): b[i] = i # bucket 0 has 15 values: 0, 4 .. 56 # bucket 1 has 15 values: 60, 64 .. 116 # bucket 2 has 20 values: 120, 124 .. 196 state = b.__getstate__() # Looks like: ((bucket0, 60, bucket1, 120, bucket2), firstbucket) # If these fail, the *preconditions* for running the test aren't # satisfied -- the test itself hasn't been run yet. self.assertEqual(len(state), 2) self.assertEqual(len(state[0]), 5) self.assertEqual(state[0][1], 60) self.assertEqual(state[0][3], 120) # Invoke conflict resolution by committing a transaction. self.openDB() tm1 = transaction.TransactionManager() r1 = self.db.open(transaction_manager=tm1).root() r1["t"] = self.t tm1.commit() tm2 = transaction.TransactionManager() r2 = self.db.open(transaction_manager=tm2).root() copy = r2["t"] # Make sure all of copy is loaded. list(copy.values()) self.assertEqual(self.t._p_serial, copy._p_serial) # In one transaction, add 16 new keys to bucket1, to force a bucket # split. b = self.t numtoadd = 16 candidate = 60 while numtoadd: if not b.has_key(candidate): b[candidate] = candidate numtoadd -= 1 candidate += 1 # bucket 0 has 15 values: 0, 4 .. 56 # bucket 1 has 15 values: 60, 61 .. 74 # bucket 2 has 16 values: [75, 76 .. 81] + [84, 88 ..116] # bucket 3 has 20 values: 120, 124 .. 196 state = b.__getstate__() # Looks like: ((b0, 60, b1, 75, b2, 120, b3), firstbucket) # The next block is still verifying preconditions. self.assertEqual(len(state) , 2) self.assertEqual(len(state[0]), 7) self.assertEqual(state[0][1], 60) self.assertEqual(state[0][3], 75) self.assertEqual(state[0][5], 120) tm1.commit() # In the other transaction, add 3 values near the tail end of bucket1. # This doesn't cause a split. b = copy for i in range(112, 116): b[i] = i # bucket 0 has 15 values: 0, 4 .. 56 # bucket 1 has 18 values: 60, 64 .. 112, 113, 114, 115, 116 # bucket 2 has 20 values: 120, 124 .. 196 state = b.__getstate__() # Looks like: ((bucket0, 60, bucket1, 120, bucket2), firstbucket) # The next block is still verifying preconditions. self.assertEqual(len(state), 2) self.assertEqual(len(state[0]), 5) self.assertEqual(state[0][1], 60) self.assertEqual(state[0][3], 120) self.assertRaises(ConflictError, tm2.commit) def testEmptyBucketConflict(self): # Tests that an emptied bucket *created by* conflict resolution is # viewed as a conflict: conflict resolution doesn't have enough # info to unlink the empty bucket from the BTree correctly. b = self.t for i in range(0, 200, 4): b[i] = i # bucket 0 has 15 values: 0, 4 .. 56 # bucket 1 has 15 values: 60, 64 .. 116 # bucket 2 has 20 values: 120, 124 .. 196 state = b.__getstate__() # Looks like: ((bucket0, 60, bucket1, 120, bucket2), firstbucket) # If these fail, the *preconditions* for running the test aren't # satisfied -- the test itself hasn't been run yet. self.assertEqual(len(state), 2) self.assertEqual(len(state[0]), 5) self.assertEqual(state[0][1], 60) self.assertEqual(state[0][3], 120) # Invoke conflict resolution by committing a transaction. self.openDB() tm1 = transaction.TransactionManager() r1 = self.db.open(transaction_manager=tm1).root() r1["t"] = self.t tm1.commit() tm2 = transaction.TransactionManager() r2 = self.db.open(transaction_manager=tm2).root() copy = r2["t"] # Make sure all of copy is loaded. list(copy.values()) self.assertEqual(self.t._p_serial, copy._p_serial) # In one transaction, delete half of bucket 1. b = self.t for k in 60, 64, 68, 72, 76, 80, 84, 88: del b[k] # bucket 0 has 15 values: 0, 4 .. 56 # bucket 1 has 7 values: 92, 96, 100, 104, 108, 112, 116 # bucket 2 has 20 values: 120, 124 .. 196 state = b.__getstate__() # Looks like: ((bucket0, 60, bucket1, 120, bucket2), firstbucket) # The next block is still verifying preconditions. self.assertEqual(len(state) , 2) self.assertEqual(len(state[0]), 5) self.assertEqual(state[0][1], 92) self.assertEqual(state[0][3], 120) tm1.commit() # In the other transaction, delete the other half of bucket 1. b = copy for k in 92, 96, 100, 104, 108, 112, 116: del b[k] # bucket 0 has 15 values: 0, 4 .. 56 # bucket 1 has 8 values: 60, 64, 68, 72, 76, 80, 84, 88 # bucket 2 has 20 values: 120, 124 .. 196 state = b.__getstate__() # Looks like: ((bucket0, 60, bucket1, 120, bucket2), firstbucket) # The next block is still verifying preconditions. self.assertEqual(len(state), 2) self.assertEqual(len(state[0]), 5) self.assertEqual(state[0][1], 60) self.assertEqual(state[0][3], 120) # Conflict resolution empties bucket1 entirely. This used to # create an "insane" BTree (a legit BTree cannot contain an empty # bucket -- it contains NULL pointers the BTree code doesn't # expect, and segfaults result). self.assertRaises(ConflictError, tm2.commit) def testEmptyBucketNoConflict(self): # Tests that a plain empty bucket (on input) is not viewed as a # conflict. b = self.t for i in range(0, 200, 4): b[i] = i # bucket 0 has 15 values: 0, 4 .. 56 # bucket 1 has 15 values: 60, 64 .. 116 # bucket 2 has 20 values: 120, 124 .. 196 state = b.__getstate__() # Looks like: ((bucket0, 60, bucket1, 120, bucket2), firstbucket) # If these fail, the *preconditions* for running the test aren't # satisfied -- the test itself hasn't been run yet. self.assertEqual(len(state), 2) self.assertEqual(len(state[0]), 5) self.assertEqual(state[0][1], 60) self.assertEqual(state[0][3], 120) # Invoke conflict resolution by committing a transaction. self.openDB() r1 = self.db.open().root() r1["t"] = self.t transaction.commit() r2 = self.db.open().root() copy = r2["t"] # Make sure all of copy is loaded. list(copy.values()) self.assertEqual(self.t._p_serial, copy._p_serial) # In one transaction, just add a key. b = self.t b[1] = 1 # bucket 0 has 16 values: [0, 1] + [4, 8 .. 56] # bucket 1 has 15 values: 60, 64 .. 116 # bucket 2 has 20 values: 120, 124 .. 196 state = b.__getstate__() # Looks like: ((bucket0, 60, bucket1, 120, bucket2), firstbucket) # The next block is still verifying preconditions. self.assertEqual(len(state), 2) self.assertEqual(len(state[0]), 5) self.assertEqual(state[0][1], 60) self.assertEqual(state[0][3], 120) transaction.commit() # In the other transaction, delete bucket 2. b = copy for k in range(120, 200, 4): del b[k] # bucket 0 has 15 values: 0, 4 .. 56 # bucket 1 has 15 values: 60, 64 .. 116 state = b.__getstate__() # Looks like: ((bucket0, 60, bucket1), firstbucket) # The next block is still verifying preconditions. self.assertEqual(len(state), 2) self.assertEqual(len(state[0]), 3) self.assertEqual(state[0][1], 60) # This shouldn't create a ConflictError. transaction.commit() # And the resulting BTree shouldn't have internal damage. b._check() # The snaky control flow in _bucket__p_resolveConflict ended up trying # to decref a NULL pointer if conflict resolution was fed 3 empty # buckets. http://collector.zope.org/Zope/553 def testThreeEmptyBucketsNoSegfault(self): self.t[1] = 1 bucket = self.t._firstbucket del self.t[1] state1 = bucket.__getstate__() state2 = bucket.__getstate__() state3 = bucket.__getstate__() self.assert_(state2 is not state1 and state2 is not state3 and state3 is not state1) self.assert_(state2 == state1 and state3 == state1) self.assertRaises(ConflictError, bucket._p_resolveConflict, state1, state2, state3) # When an empty BTree resolves conflicts, it computes the # bucket state as None, so... self.assertRaises(ConflictError, bucket._p_resolveConflict, None, None, None) def testCantResolveBTreeConflict(self): # Test that a conflict involving two different changes to # an internal BTree node is unresolvable. An internal node # only changes when there are enough additions or deletions # to a child bucket that the bucket is split or removed. # It's (almost necessarily) a white-box test, and sensitive to # implementation details. b = self.t for i in range(0, 200, 4): b[i] = i # bucket 0 has 15 values: 0, 4 .. 56 # bucket 1 has 15 values: 60, 64 .. 116 # bucket 2 has 20 values: 120, 124 .. 196 state = b.__getstate__() # Looks like: ((bucket0, 60, bucket1, 120, bucket2), firstbucket) # If these fail, the *preconditions* for running the test aren't # satisfied -- the test itself hasn't been run yet. self.assertEqual(len(state), 2) self.assertEqual(len(state[0]), 5) self.assertEqual(state[0][1], 60) self.assertEqual(state[0][3], 120) # Set up database connections to provoke conflict. self.openDB() tm1 = transaction.TransactionManager() r1 = self.db.open(transaction_manager=tm1).root() r1["t"] = self.t tm1.commit() tm2 = transaction.TransactionManager() r2 = self.db.open(transaction_manager=tm2).root() copy = r2["t"] # Make sure all of copy is loaded. list(copy.values()) self.assertEqual(self.t._p_serial, copy._p_serial) # Now one transaction should add enough keys to cause a split, # and another should remove all the keys in one bucket. for k in range(200, 300, 4): self.t[k] = k tm1.commit() for k in range(0, 60, 4): del copy[k] try: tm2.commit() except ConflictError, detail: self.assert_(str(detail).startswith('database conflict error')) else: self.fail("expected ConflictError") def testConflictWithOneEmptyBucket(self): # If one transaction empties a bucket, while another adds an item # to the bucket, all the changes "look resolvable": bucket conflict # resolution returns a bucket containing (only) the item added by # the latter transaction, but changes from the former transaction # removing the bucket are uncontested: the bucket is removed from # the BTree despite that resolution thinks it's non-empty! This # was first reported by Dieter Maurer, to zodb-dev on 22 Mar 2005. b = self.t for i in range(0, 200, 4): b[i] = i # bucket 0 has 15 values: 0, 4 .. 56 # bucket 1 has 15 values: 60, 64 .. 116 # bucket 2 has 20 values: 120, 124 .. 196 state = b.__getstate__() # Looks like: ((bucket0, 60, bucket1, 120, bucket2), firstbucket) # If these fail, the *preconditions* for running the test aren't # satisfied -- the test itself hasn't been run yet. self.assertEqual(len(state), 2) self.assertEqual(len(state[0]), 5) self.assertEqual(state[0][1], 60) self.assertEqual(state[0][3], 120) # Set up database connections to provoke conflict. self.openDB() tm1 = transaction.TransactionManager() r1 = self.db.open(transaction_manager=tm1).root() r1["t"] = self.t tm1.commit() tm2 = transaction.TransactionManager() r2 = self.db.open(transaction_manager=tm2).root() copy = r2["t"] # Make sure all of copy is loaded. list(copy.values()) self.assertEqual(self.t._p_serial, copy._p_serial) # Now one transaction empties the first bucket, and another adds a # key to the first bucket. for k in range(0, 60, 4): del self.t[k] tm1.commit() copy[1] = 1 try: tm2.commit() except ConflictError, detail: self.assert_(str(detail).startswith('database conflict error')) else: self.fail("expected ConflictError") # Same thing, except commit the transactions in the opposite order. b = OOBTree() for i in range(0, 200, 4): b[i] = i tm1 = transaction.TransactionManager() r1 = self.db.open(transaction_manager=tm1).root() r1["t"] = b tm1.commit() tm2 = transaction.TransactionManager() r2 = self.db.open(transaction_manager=tm2).root() copy = r2["t"] # Make sure all of copy is loaded. list(copy.values()) self.assertEqual(b._p_serial, copy._p_serial) # Now one transaction empties the first bucket, and another adds a # key to the first bucket. b[1] = 1 tm1.commit() for k in range(0, 60, 4): del copy[k] try: tm2.commit() except ConflictError, detail: self.assert_(str(detail).startswith('database conflict error')) else: self.fail("expected ConflictError") def testConflictOfInsertAndDeleteOfFirstBucketItem(self): """ Recently, BTrees became careful about removing internal keys (keys in internal aka BTree nodes) when they were deleted from buckets. This poses a problem for conflict resolution. We want to guard against a case in which the first key in a bucket is removed in one transaction while a key is added after that key but before the next key in another transaction with the result that the added key is unreachble original: Bucket(...), k1, Bucket((k1, v1), (k3, v3), ...) tran1 Bucket(...), k3, Bucket(k3, v3), ...) tran2 Bucket(...), k1, Bucket((k1, v1), (k2, v2), (k3, v3), ...) where k1 < k2 < k3 We don't want: Bucket(...), k3, Bucket((k2, v2), (k3, v3), ...) as k2 would be unfindable, so we want a conflict. """ mytype = self.t_type db = self.openDB() tm1 = transaction.TransactionManager() conn1 = db.open(tm1) conn1.root.t = t = mytype() for i in range(0, 200, 2): t[i] = i tm1.commit() k = t.__getstate__()[0][1] assert t.__getstate__()[0][2].keys()[0] == k tm2 = transaction.TransactionManager() conn2 = db.open(tm2) t[k+1] = k+1 del conn2.root.t[k] for i in range(200,300): conn2.root.t[i] = i tm1.commit() self.assertRaises(ConflictError, tm2.commit) tm2.abort() k = t.__getstate__()[0][1] t[k+1] = k+1 del conn2.root.t[k] tm2.commit() self.assertRaises(ConflictError, tm1.commit) tm1.abort() def test_suite(): suite = TestSuite() for kv in ('OO', 'II', 'IO', 'OI', 'IF', 'LL', 'LO', 'OL', 'LF', ): for name, bases in (('BTree', (MappingBase, TestCase)), ('Bucket', (MappingBase, TestCase)), ('TreeSet', (SetTests, TestCase)), ('Set', (SetTests, TestCase)), ): klass = ClassType(kv + name + 'Test', bases, dict(t_type=globals()[kv+name])) suite.addTest(makeSuite(klass)) suite.addTest(makeSuite(NastyConfict)) return suite zope2.13-2.13.21/source/ZODB3/src/BTrees/tests/test_check.py0000644000175000017500000000665012214017464022073 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test the BTree check.check() function.""" import unittest from BTrees.OOBTree import OOBTree from BTrees.check import check class CheckTest(unittest.TestCase): def setUp(self): self.t = t = OOBTree() for i in range(31): t[i] = 2*i self.state = t.__getstate__() def testNormal(self): s = self.state # Looks like (state, first_bucket) # where state looks like (bucket0, 15, bucket1). self.assertEqual(len(s), 2) self.assertEqual(len(s[0]), 3) self.assertEqual(s[0][1], 15) self.t._check() # shouldn't blow up check(self.t) # shouldn't blow up def testKeyTooLarge(self): # Damage an invariant by dropping the BTree key to 14. s = self.state news = (s[0][0], 14, s[0][2]), s[1] self.t.__setstate__(news) self.t._check() # not caught try: # Expecting "... key %r >= upper bound %r at index %d" check(self.t) except AssertionError, detail: self.failUnless(str(detail).find(">= upper bound") > 0) else: self.fail("expected self.t_check() to catch the problem") def testKeyTooSmall(self): # Damage an invariant by bumping the BTree key to 16. s = self.state news = (s[0][0], 16, s[0][2]), s[1] self.t.__setstate__(news) self.t._check() # not caught try: # Expecting "... key %r < lower bound %r at index %d" check(self.t) except AssertionError, detail: self.failUnless(str(detail).find("< lower bound") > 0) else: self.fail("expected self.t_check() to catch the problem") def testKeysSwapped(self): # Damage an invariant by swapping two key/value pairs. s = self.state # Looks like (state, first_bucket) # where state looks like (bucket0, 15, bucket1). (b0, num, b1), firstbucket = s self.assertEqual(b0[4], 8) self.assertEqual(b0[5], 10) b0state = b0.__getstate__() self.assertEqual(len(b0state), 2) # b0state looks like # ((k0, v0, k1, v1, ...), nextbucket) pairs, nextbucket = b0state self.assertEqual(pairs[8], 4) self.assertEqual(pairs[9], 8) self.assertEqual(pairs[10], 5) self.assertEqual(pairs[11], 10) newpairs = pairs[:8] + (5, 10, 4, 8) + pairs[12:] b0.__setstate__((newpairs, nextbucket)) self.t._check() # not caught try: check(self.t) except AssertionError, detail: self.failUnless(str(detail).find( "key 5 at index 4 >= key 4 at index 5") > 0) else: self.fail("expected self.t_check() to catch the problem") def test_suite(): return unittest.makeSuite(CheckTest) zope2.13-2.13.21/source/ZODB3/src/BTrees/LOBTree.py0000644000175000017500000000150212214017464020040 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import zope.interface import BTrees.Interfaces # hack to overcome dynamic-linking headache. from _LOBTree import * zope.interface.moduleProvides(BTrees.Interfaces.IIntegerObjectBTreeModule) zope2.13-2.13.21/source/ZODB3/src/BTrees/LLBTree.py0000644000175000017500000000150312214017464020036 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import zope.interface import BTrees.Interfaces # hack to overcome dynamic-linking headache. from _LLBTree import * zope.interface.moduleProvides(BTrees.Interfaces.IIntegerIntegerBTreeModule) zope2.13-2.13.21/source/ZODB3/src/BTrees/_IIBTree.c0000644000175000017500000000211012214017464017754 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ #define MASTER_ID "$Id: _IIBTree.c 113734 2010-06-21 15:33:46Z ctheune $\n" /* IIBTree - int key, int value BTree Implements a collection using int type keys and int type values */ /* Setup template macros */ #define PERSISTENT #define MOD_NAME_PREFIX "II" #define INITMODULE init_IIBTree #define DEFAULT_MAX_BUCKET_SIZE 120 #define DEFAULT_MAX_BTREE_SIZE 500 #include "intkeymacros.h" #include "intvaluemacros.h" #include "BTreeModuleTemplate.c" zope2.13-2.13.21/source/ZODB3/src/BTrees/SetOpTemplate.c0000644000175000017500000003635412214017464021141 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /**************************************************************************** Set operations ****************************************************************************/ #define SETOPTEMPLATE_C "$Id: SetOpTemplate.c 113734 2010-06-21 15:33:46Z ctheune $\n" #ifdef KEY_CHECK static int nextKeyAsSet(SetIteration *i) { if (i->position >= 0) { if (i->position) { DECREF_KEY(i->key); i->position = -1; } else i->position = 1; } return 0; } #endif /* initSetIteration * * Start the set iteration protocol. See the comments at struct SetIteration. * * Arguments * i The address of a SetIteration control struct. * s The address of the set, bucket, BTree, ..., to be iterated. * useValues Boolean; if true, and s has values (is a mapping), copy * them into i->value each time i->next() is called; else * ignore s's values even if s is a mapping. * * Return * 0 on success; -1 and an exception set if error. * i.usesValue is set to 1 (true) if s has values and useValues was * true; else usesValue is set to 0 (false). * i.set gets a new reference to s, or to some other object used to * iterate over s. * i.position is set to 0. * i.next is set to an appropriate iteration function. * i.key and i.value are left alone. * * Internal * i.position < 0 means iteration terminated. * i.position = 0 means iteration hasn't yet begun (next() hasn't * been called yet). * In all other cases, i.key, and possibly i.value, own references. * These must be cleaned up, either by next() routines, or by * finiSetIteration. * next() routines must ensure the above. They should return without * doing anything when i.position < 0. * It's the responsibility of {init, fini}setIteration to clean up * the reference in i.set, and to ensure that no stale references * live in i.key or i.value if iteration terminates abnormally. * A SetIteration struct has been cleaned up iff i.set is NULL. */ static int initSetIteration(SetIteration *i, PyObject *s, int useValues) { i->set = NULL; i->position = -1; /* set to 0 only on normal return */ i->usesValue = 0; /* assume it's a set or that values aren't iterated */ if (PyObject_IsInstance(s, (PyObject *)&BucketType)) { i->set = s; Py_INCREF(s); if (useValues) { i->usesValue = 1; i->next = nextBucket; } else i->next = nextSet; } else if (PyObject_IsInstance(s, (PyObject *)&SetType)) { i->set = s; Py_INCREF(s); i->next = nextSet; } else if (PyObject_IsInstance(s, (PyObject *)&BTreeType)) { i->set = BTree_rangeSearch(BTREE(s), NULL, NULL, 'i'); UNLESS(i->set) return -1; if (useValues) { i->usesValue = 1; i->next = nextBTreeItems; } else i->next = nextTreeSetItems; } else if (PyObject_IsInstance(s, (PyObject *)&TreeSetType)) { i->set = BTree_rangeSearch(BTREE(s), NULL, NULL, 'k'); UNLESS(i->set) return -1; i->next = nextTreeSetItems; } #ifdef KEY_CHECK else if (KEY_CHECK(s)) { int copied = 1; COPY_KEY_FROM_ARG(i->key, s, copied); UNLESS (copied) return -1; INCREF_KEY(i->key); i->set = s; Py_INCREF(s); i->next = nextKeyAsSet; } #endif else { PyErr_SetString(PyExc_TypeError, "invalid argument"); return -1; } i->position = 0; return 0; } #ifndef MERGE_WEIGHT #define MERGE_WEIGHT(O, w) (O) #endif static int copyRemaining(Bucket *r, SetIteration *i, int merge, /* See comment # 42 */ #ifdef MERGE VALUE_TYPE w) #else int w) #endif { while (i->position >= 0) { if(r->len >= r->size && Bucket_grow(r, -1, ! merge) < 0) return -1; COPY_KEY(r->keys[r->len], i->key); INCREF_KEY(r->keys[r->len]); if (merge) { COPY_VALUE(r->values[r->len], MERGE_WEIGHT(i->value, w)); INCREF_VALUE(r->values[r->len]); } r->len++; if (i->next(i) < 0) return -1; } return 0; } /* This is the workhorse for all set merge operations: the weighted and * unweighted flavors of union and intersection, and set difference. The * algorithm is conceptually simple but the code is complicated due to all * the options. * * s1, s2 * The input collections to be merged. * * usevalues1, usevalues2 * Booleans. In the output, should values from s1 (or s2) be used? This * only makes sense when an operation intends to support mapping outputs; * these should both be false for operations that want pure set outputs. * * w1, w2 * If usevalues1(2) are true, these are the weights to apply to the * input values. * * c1 * Boolean. Should keys that appear in c1 but not c2 appear in the output? * c12 * Boolean. Should keys that appear in both inputs appear in the output? * c2 * Boolean. Should keys that appear in c2 but not c1 appear in the output? * * Returns NULL if error, else a Set or Bucket, depending on whether a set or * mapping was requested. */ static PyObject * set_operation(PyObject *s1, PyObject *s2, int usevalues1, int usevalues2, /* Comment # 42 The following ifdef works around a template/type problem Weights are passed as integers. In particular, the weight passed by difference is one. This works fine in the int value and float value cases but makes no sense in the object value case. In the object value case, we don't do merging, so we don't use the weights, so it doesn't matter what they are. */ #ifdef MERGE VALUE_TYPE w1, VALUE_TYPE w2, #else int w1, int w2, #endif int c1, int c12, int c2) { Bucket *r=0; SetIteration i1 = {0,0,0}, i2 = {0,0,0}; int cmp, merge; if (initSetIteration(&i1, s1, usevalues1) < 0) goto err; if (initSetIteration(&i2, s2, usevalues2) < 0) goto err; merge = i1.usesValue | i2.usesValue; if (merge) { #ifndef MERGE if (c12 && i1.usesValue && i2.usesValue) goto invalid_set_operation; #endif if (! i1.usesValue&& i2.usesValue) { SetIteration t; int i; /* See comment # 42 above */ #ifdef MERGE VALUE_TYPE v; #else int v; #endif t=i1; i1=i2; i2=t; i=c1; c1=c2; c2=i; v=w1; w1=w2; w2=v; } #ifdef MERGE_DEFAULT i1.value=MERGE_DEFAULT; i2.value=MERGE_DEFAULT; #else if (i1.usesValue) { if (! i2.usesValue && c2) goto invalid_set_operation; } else { if (c1 || c12) goto invalid_set_operation; } #endif UNLESS(r=BUCKET(PyObject_CallObject(OBJECT(&BucketType), NULL))) goto err; } else { UNLESS(r=BUCKET(PyObject_CallObject(OBJECT(&SetType), NULL))) goto err; } if (i1.next(&i1) < 0) goto err; if (i2.next(&i2) < 0) goto err; while (i1.position >= 0 && i2.position >= 0) { TEST_KEY_SET_OR(cmp, i1.key, i2.key) goto err; if(cmp < 0) { if(c1) { if(r->len >= r->size && Bucket_grow(r, -1, ! merge) < 0) goto err; COPY_KEY(r->keys[r->len], i1.key); INCREF_KEY(r->keys[r->len]); if (merge) { COPY_VALUE(r->values[r->len], MERGE_WEIGHT(i1.value, w1)); INCREF_VALUE(r->values[r->len]); } r->len++; } if (i1.next(&i1) < 0) goto err; } else if(cmp==0) { if(c12) { if(r->len >= r->size && Bucket_grow(r, -1, ! merge) < 0) goto err; COPY_KEY(r->keys[r->len], i1.key); INCREF_KEY(r->keys[r->len]); if (merge) { #ifdef MERGE r->values[r->len] = MERGE(i1.value, w1, i2.value, w2); #else COPY_VALUE(r->values[r->len], i1.value); INCREF_VALUE(r->values[r->len]); #endif } r->len++; } if (i1.next(&i1) < 0) goto err; if (i2.next(&i2) < 0) goto err; } else { if(c2) { if(r->len >= r->size && Bucket_grow(r, -1, ! merge) < 0) goto err; COPY_KEY(r->keys[r->len], i2.key); INCREF_KEY(r->keys[r->len]); if (merge) { COPY_VALUE(r->values[r->len], MERGE_WEIGHT(i2.value, w2)); INCREF_VALUE(r->values[r->len]); } r->len++; } if (i2.next(&i2) < 0) goto err; } } if(c1 && copyRemaining(r, &i1, merge, w1) < 0) goto err; if(c2 && copyRemaining(r, &i2, merge, w2) < 0) goto err; finiSetIteration(&i1); finiSetIteration(&i2); return OBJECT(r); #ifndef MERGE_DEFAULT invalid_set_operation: PyErr_SetString(PyExc_TypeError, "invalid set operation"); #endif err: finiSetIteration(&i1); finiSetIteration(&i2); Py_XDECREF(r); return NULL; } static PyObject * difference_m(PyObject *ignored, PyObject *args) { PyObject *o1, *o2; UNLESS(PyArg_ParseTuple(args, "OO", &o1, &o2)) return NULL; if (o1 == Py_None || o2 == Py_None) { /* difference(None, X) -> None; difference(X, None) -> X */ Py_INCREF(o1); return o1; } return set_operation(o1, o2, 1, 0, /* preserve values from o1, ignore o2's */ 1, 0, /* o1's values multiplied by 1 */ 1, 0, 0); /* take only keys unique to o1 */ } static PyObject * union_m(PyObject *ignored, PyObject *args) { PyObject *o1, *o2; UNLESS(PyArg_ParseTuple(args, "OO", &o1, &o2)) return NULL; if (o1 == Py_None) { Py_INCREF(o2); return o2; } else if (o2 == Py_None) { Py_INCREF(o1); return o1; } return set_operation(o1, o2, 0, 0, /* ignore values in both */ 1, 1, /* the weights are irrelevant */ 1, 1, 1); /* take all keys */ } static PyObject * intersection_m(PyObject *ignored, PyObject *args) { PyObject *o1, *o2; UNLESS(PyArg_ParseTuple(args, "OO", &o1, &o2)) return NULL; if (o1 == Py_None) { Py_INCREF(o2); return o2; } else if (o2 == Py_None) { Py_INCREF(o1); return o1; } return set_operation(o1, o2, 0, 0, /* ignore values in both */ 1, 1, /* the weights are irrelevant */ 0, 1, 0); /* take only keys common to both */ } #ifdef MERGE static PyObject * wunion_m(PyObject *ignored, PyObject *args) { PyObject *o1, *o2; VALUE_TYPE w1 = 1, w2 = 1; UNLESS(PyArg_ParseTuple(args, "OO|" VALUE_PARSE VALUE_PARSE, &o1, &o2, &w1, &w2) ) return NULL; if (o1 == Py_None) return Py_BuildValue(VALUE_PARSE "O", (o2 == Py_None ? 0 : w2), o2); else if (o2 == Py_None) return Py_BuildValue(VALUE_PARSE "O", w1, o1); o1 = set_operation(o1, o2, 1, 1, w1, w2, 1, 1, 1); if (o1) ASSIGN(o1, Py_BuildValue(VALUE_PARSE "O", (VALUE_TYPE)1, o1)); return o1; } static PyObject * wintersection_m(PyObject *ignored, PyObject *args) { PyObject *o1, *o2; VALUE_TYPE w1 = 1, w2 = 1; UNLESS(PyArg_ParseTuple(args, "OO|" VALUE_PARSE VALUE_PARSE, &o1, &o2, &w1, &w2) ) return NULL; if (o1 == Py_None) return Py_BuildValue(VALUE_PARSE "O", (o2 == Py_None ? 0 : w2), o2); else if (o2 == Py_None) return Py_BuildValue(VALUE_PARSE "O", w1, o1); o1 = set_operation(o1, o2, 1, 1, w1, w2, 0, 1, 0); if (o1) ASSIGN(o1, Py_BuildValue(VALUE_PARSE "O", ((o1->ob_type == (PyTypeObject*)(&SetType)) ? w2+w1 : 1), o1)); return o1; } #endif #ifdef MULTI_INT_UNION #include "sorters.c" /* Input is a sequence of integer sets (or convertible to sets by the set iteration protocol). Output is the union of the sets. The point is to run much faster than doing pairs of unions. */ static PyObject * multiunion_m(PyObject *ignored, PyObject *args) { PyObject *seq; /* input sequence */ int n; /* length of input sequence */ PyObject *set = NULL; /* an element of the input sequence */ Bucket *result; /* result set */ SetIteration setiter = {0}; int i; UNLESS(PyArg_ParseTuple(args, "O", &seq)) return NULL; n = PyObject_Length(seq); if (n < 0) return NULL; /* Construct an empty result set. */ result = BUCKET(PyObject_CallObject(OBJECT(&SetType), NULL)); if (result == NULL) return NULL; /* For each set in the input sequence, append its elements to the result set. At this point, we ignore the possibility of duplicates. */ for (i = 0; i < n; ++i) { set = PySequence_GetItem(seq, i); if (set == NULL) goto Error; /* If set is a bucket, do a straight resize + memcpy. */ if (set->ob_type == (PyTypeObject*)&SetType || set->ob_type == (PyTypeObject*)&BucketType) { Bucket *b = BUCKET(set); int status = 0; UNLESS (PER_USE(b)) goto Error; if (b->len) status = bucket_append(result, b, 0, b->len, 0, i < n-1); PER_UNUSE(b); if (status < 0) goto Error; } else { /* No cheap way: iterate over set's elements one at a time. */ if (initSetIteration(&setiter, set, 0) < 0) goto Error; if (setiter.next(&setiter) < 0) goto Error; while (setiter.position >= 0) { if (result->len >= result->size && Bucket_grow(result, -1, 1) < 0) goto Error; COPY_KEY(result->keys[result->len], setiter.key); ++result->len; /* We know the key is an int, so no need to incref it. */ if (setiter.next(&setiter) < 0) goto Error; } finiSetIteration(&setiter); } Py_DECREF(set); set = NULL; } /* Combine, sort, remove duplicates, and reset the result's len. If the set shrinks (which happens if and only if there are duplicates), no point to realloc'ing the set smaller, as we expect the result set to be short-lived. */ if (result->len > 0) { size_t newlen; /* number of elements in final result set */ newlen = sort_int_nodups(result->keys, (size_t)result->len); result->len = (int)newlen; } return (PyObject *)result; Error: Py_DECREF(result); Py_XDECREF(set); finiSetIteration(&setiter); return NULL; } #endif zope2.13-2.13.21/source/ZODB3/src/ZODB/0000755000175000017500000000000012214017464015606 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZODB/ExportImport.py0000644000175000017500000001431612214017464020641 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Support for database export and import.""" import os from cStringIO import StringIO from cPickle import Pickler, Unpickler from tempfile import TemporaryFile import logging from ZODB.blob import Blob from ZODB.interfaces import IBlobStorage from ZODB.POSException import ExportError from ZODB.serialize import referencesf from ZODB.utils import p64, u64, cp, mktemp logger = logging.getLogger('ZODB.ExportImport') class ExportImport: def exportFile(self, oid, f=None): if f is None: f = TemporaryFile() elif isinstance(f, str): f = open(f,'w+b') f.write('ZEXP') oids = [oid] done_oids = {} done=done_oids.has_key load=self._storage.load supports_blobs = IBlobStorage.providedBy(self._storage) while oids: oid = oids.pop(0) if oid in done_oids: continue done_oids[oid] = True try: p, serial = load(oid, '') except: logger.debug("broken reference for oid %s", repr(oid), exc_info=True) else: referencesf(p, oids) f.writelines([oid, p64(len(p)), p]) if supports_blobs: if not isinstance(self._reader.getGhost(p), Blob): continue # not a blob blobfilename = self._storage.loadBlob(oid, serial) f.write(blob_begin_marker) f.write(p64(os.stat(blobfilename).st_size)) blobdata = open(blobfilename, "rb") cp(blobdata, f) blobdata.close() f.write(export_end_marker) return f def importFile(self, f, clue='', customImporters=None): # This is tricky, because we need to work in a transaction! if isinstance(f, str): f = open(f, 'rb') magic = f.read(4) if magic != 'ZEXP': if customImporters and customImporters.has_key(magic): f.seek(0) return customImporters[magic](self, f, clue) raise ExportError("Invalid export header") t = self.transaction_manager.get() if clue: t.note(clue) return_oid_list = [] self._import = f, return_oid_list self._register() t.savepoint(optimistic=True) # Return the root imported object. if return_oid_list: return self.get(return_oid_list[0]) else: return None def _importDuringCommit(self, transaction, f, return_oid_list): """Import data during two-phase commit. Invoked by the transaction manager mid commit. Appends one item, the OID of the first object created, to return_oid_list. """ oids = {} # IMPORTANT: This code should be consistent with the code in # serialize.py. It is currently out of date and doesn't handle # weak references. def persistent_load(ooid): """Remap a persistent id to a new ID and create a ghost for it.""" klass = None if isinstance(ooid, tuple): ooid, klass = ooid if ooid in oids: oid = oids[ooid] else: if klass is None: oid = self._storage.new_oid() else: oid = self._storage.new_oid(), klass oids[ooid] = oid return Ghost(oid) while 1: header = f.read(16) if header == export_end_marker: break if len(header) != 16: raise ExportError("Truncated export file") # Extract header information ooid = header[:8] length = u64(header[8:16]) data = f.read(length) if len(data) != length: raise ExportError("Truncated export file") if oids: oid = oids[ooid] if isinstance(oid, tuple): oid = oid[0] else: oids[ooid] = oid = self._storage.new_oid() return_oid_list.append(oid) # Blob support blob_begin = f.read(len(blob_begin_marker)) if blob_begin == blob_begin_marker: # Copy the blob data to a temporary file # and remember the name blob_len = u64(f.read(8)) blob_filename = mktemp() blob_file = open(blob_filename, "wb") cp(f, blob_file, blob_len) blob_file.close() else: f.seek(-len(blob_begin_marker),1) blob_filename = None pfile = StringIO(data) unpickler = Unpickler(pfile) unpickler.persistent_load = persistent_load newp = StringIO() pickler = Pickler(newp, 1) pickler.inst_persistent_id = persistent_id pickler.dump(unpickler.load()) pickler.dump(unpickler.load()) data = newp.getvalue() if blob_filename is not None: self._storage.storeBlob(oid, None, data, blob_filename, '', transaction) else: self._storage.store(oid, None, data, '', transaction) export_end_marker = '\377'*16 blob_begin_marker = '\000BLOBSTART' class Ghost(object): __slots__ = ("oid",) def __init__(self, oid): self.oid = oid def persistent_id(obj): if isinstance(obj, Ghost): return obj.oid zope2.13-2.13.21/source/ZODB3/src/ZODB/DemoStorage.py0000644000175000017500000003050512214017464020374 0ustar arnauarnau############################################################################## # # Copyright (c) Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Demo ZODB storage A demo storage supports demos by allowing a volatile changed database to be layered over a base database. The base storage must not change. """ import os import random import weakref import tempfile import threading import ZODB.BaseStorage import ZODB.blob import ZODB.interfaces import ZODB.MappingStorage import ZODB.POSException import ZODB.utils import zope.interface class DemoStorage(object): zope.interface.implements( ZODB.interfaces.IStorage, ZODB.interfaces.IStorageIteration, ) def __init__(self, name=None, base=None, changes=None, close_base_on_close=None, close_changes_on_close=None): if close_base_on_close is None: if base is None: base = ZODB.MappingStorage.MappingStorage() close_base_on_close = False else: close_base_on_close = True self.base = base self.close_base_on_close = close_base_on_close if changes is None: self._temporary_changes = True changes = ZODB.MappingStorage.MappingStorage() zope.interface.alsoProvides(self, ZODB.interfaces.IBlobStorage) if close_changes_on_close is None: close_changes_on_close = False else: if ZODB.interfaces.IBlobStorage.providedBy(changes): zope.interface.alsoProvides(self, ZODB.interfaces.IBlobStorage) if close_changes_on_close is None: close_changes_on_close = True self.changes = changes self.close_changes_on_close = close_changes_on_close self._issued_oids = set() self._stored_oids = set() self._commit_lock = threading.Lock() self._transaction = None if name is None: name = 'DemoStorage(%r, %r)' % (base.getName(), changes.getName()) self.__name__ = name self._copy_methods_from_changes(changes) self._next_oid = random.randint(1, 1<<62) def _blobify(self): if (self._temporary_changes and isinstance(self.changes, ZODB.MappingStorage.MappingStorage) ): blob_dir = tempfile.mkdtemp('.demoblobs') _temporary_blobdirs[ weakref.ref(self, cleanup_temporary_blobdir) ] = blob_dir self.changes = ZODB.blob.BlobStorage(blob_dir, self.changes) self._copy_methods_from_changes(self.changes) return True def cleanup(self): self.base.cleanup() self.changes.cleanup() __opened = True def opened(self): return self.__opened def close(self): self.__opened = False if self.close_base_on_close: self.base.close() if self.close_changes_on_close: self.changes.close() def _copy_methods_from_changes(self, changes): for meth in ( '_lock_acquire', '_lock_release', 'getSize', 'history', 'isReadOnly', 'registerDB', 'sortKey', 'tpc_transaction', 'tpc_vote', ): setattr(self, meth, getattr(changes, meth)) supportsUndo = getattr(changes, 'supportsUndo', None) if supportsUndo is not None and supportsUndo(): for meth in ('supportsUndo', 'undo', 'undoLog', 'undoInfo'): setattr(self, meth, getattr(changes, meth)) zope.interface.alsoProvides(self, ZODB.interfaces.IStorageUndoable) lastInvalidations = getattr(changes, 'lastInvalidations', None) if lastInvalidations is not None: self.lastInvalidations = lastInvalidations def getName(self): return self.__name__ __repr__ = getName def getTid(self, oid): try: return self.changes.getTid(oid) except ZODB.POSException.POSKeyError: return self.base.getTid(oid) def iterator(self, start=None, end=None): for t in self.base.iterator(start, end): yield t for t in self.changes.iterator(start, end): yield t def lastTransaction(self): t = self.changes.lastTransaction() if t == ZODB.utils.z64: t = self.base.lastTransaction() return t def __len__(self): return len(self.changes) def load(self, oid, version=''): try: return self.changes.load(oid, version) except ZODB.POSException.POSKeyError: return self.base.load(oid, version) def loadBefore(self, oid, tid): try: result = self.changes.loadBefore(oid, tid) except ZODB.POSException.POSKeyError: # The oid isn't in the changes, so defer to base return self.base.loadBefore(oid, tid) if result is None: # The oid *was* in the changes, but there aren't any # earlier records. Maybe there are in the base. try: result = self.base.loadBefore(oid, tid) except ZODB.POSException.POSKeyError: # The oid isn't in the base, so None will be the right result pass else: if result and not result[-1]: end_tid = None t = self.changes.load(oid) while t: end_tid = t[1] t = self.changes.loadBefore(oid, end_tid) result = result[:2] + (end_tid,) return result def loadBlob(self, oid, serial): try: return self.changes.loadBlob(oid, serial) except ZODB.POSException.POSKeyError: try: return self.base.loadBlob(oid, serial) except AttributeError: if not ZODB.interfaces.IBlobStorage.providedBy(self.base): raise ZODB.POSException.POSKeyError(oid, serial) raise except AttributeError: if self._blobify(): return self.loadBlob(oid, serial) raise def openCommittedBlobFile(self, oid, serial, blob=None): try: return self.changes.openCommittedBlobFile(oid, serial, blob) except ZODB.POSException.POSKeyError: try: return self.base.openCommittedBlobFile(oid, serial, blob) except AttributeError: if not ZODB.interfaces.IBlobStorage.providedBy(self.base): raise ZODB.POSException.POSKeyError(oid, serial) raise except AttributeError: if self._blobify(): return self.openCommittedBlobFile(oid, serial, blob) raise def loadSerial(self, oid, serial): try: return self.changes.loadSerial(oid, serial) except ZODB.POSException.POSKeyError: return self.base.loadSerial(oid, serial) @ZODB.utils.locked def new_oid(self): while 1: oid = ZODB.utils.p64(self._next_oid ) if oid not in self._issued_oids: try: self.changes.load(oid, '') except ZODB.POSException.POSKeyError: try: self.base.load(oid, '') except ZODB.POSException.POSKeyError: self._next_oid += 1 self._issued_oids.add(oid) return oid self._next_oid = random.randint(1, 1<<62) def pack(self, t, referencesf, gc=None): if gc is None: if self._temporary_changes: return self.changes.pack(t, referencesf) elif self._temporary_changes: return self.changes.pack(t, referencesf, gc=gc) elif gc: raise TypeError( "Garbage collection isn't supported" " when there is a base storage.") try: self.changes.pack(t, referencesf, gc=False) except TypeError, v: if 'gc' in str(v): pass # The gc arg isn't supported. Don't pack raise def pop(self): self.changes.close() return self.base def push(self, changes=None): return self.__class__(base=self, changes=changes, close_base_on_close=False) def store(self, oid, serial, data, version, transaction): assert version=='', "versions aren't supported" if transaction is not self._transaction: raise ZODB.POSException.StorageTransactionError(self, transaction) # Since the OID is being used, we don't have to keep up with it any # more. Save it now so we can forget it later. :) self._stored_oids.add(oid) # See if we already have changes for this oid try: old = self.changes.load(oid, '')[1] except ZODB.POSException.POSKeyError: try: old = self.base.load(oid, '')[1] except ZODB.POSException.POSKeyError: old = serial if old != serial: raise ZODB.POSException.ConflictError( oid=oid, serials=(old, serial)) # XXX untested branch return self.changes.store(oid, serial, data, '', transaction) def storeBlob(self, oid, oldserial, data, blobfilename, version, transaction): assert version=='', "versions aren't supported" if transaction is not self._transaction: raise ZODB.POSException.StorageTransactionError(self, transaction) # Since the OID is being used, we don't have to keep up with it any # more. Save it now so we can forget it later. :) self._stored_oids.add(oid) try: return self.changes.storeBlob( oid, oldserial, data, blobfilename, '', transaction) except AttributeError: if self._blobify(): return self.changes.storeBlob( oid, oldserial, data, blobfilename, '', transaction) raise checkCurrentSerialInTransaction = ( ZODB.BaseStorage.checkCurrentSerialInTransaction) def temporaryDirectory(self): try: return self.changes.temporaryDirectory() except AttributeError: if self._blobify(): return self.changes.temporaryDirectory() raise @ZODB.utils.locked def tpc_abort(self, transaction): if transaction is not self._transaction: return self._stored_oids = set() self._transaction = None self.changes.tpc_abort(transaction) self._commit_lock.release() @ZODB.utils.locked def tpc_begin(self, transaction, *a, **k): # The tid argument exists to support testing. if transaction is self._transaction: raise ZODB.POSException.StorageTransactionError( "Duplicate tpc_begin calls for same transaction") self._lock_release() self._commit_lock.acquire() self._lock_acquire() self.changes.tpc_begin(transaction, *a, **k) self._transaction = transaction self._stored_oids = set() @ZODB.utils.locked def tpc_finish(self, transaction, func = lambda tid: None): if (transaction is not self._transaction): raise ZODB.POSException.StorageTransactionError( "tpc_finish called with wrong transaction") self._issued_oids.difference_update(self._stored_oids) self._stored_oids = set() self._transaction = None self.changes.tpc_finish(transaction, func) self._commit_lock.release() _temporary_blobdirs = {} def cleanup_temporary_blobdir( ref, _temporary_blobdirs=_temporary_blobdirs, # Make sure it stays around ): blob_dir = _temporary_blobdirs.pop(ref, None) if blob_dir and os.path.exists(blob_dir): ZODB.blob.remove_committed_dir(blob_dir) zope2.13-2.13.21/source/ZODB3/src/ZODB/loglevels.py0000644000175000017500000000336012214017464020156 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Supplies custom logging levels BLATHER and TRACE. $Revision: 1.1 $ """ import logging __all__ = ["BLATHER", "TRACE"] # In the days of zLOG, there were 7 standard log levels, and ZODB/ZEO used # all of them. Here's how they map to the logging package's 5 standard # levels: # # zLOG logging # ------------- --------------- # PANIC (300) FATAL, CRITICAL (50) # ERROR (200) ERROR (40) # WARNING, PROBLEM (100) WARN (30) # INFO (0) INFO (20) # BLATHER (-100) none -- defined here as BLATHER (15) # DEBUG (-200) DEBUG (10) # TRACE (-300) none -- defined here as TRACE (5) # # TRACE is used by ZEO for extremely verbose trace output, enabled only # when chasing bottom-level communications bugs. It really should be at # a lower level than DEBUG. # # BLATHER is a harder call, and various instances could probably be folded # into INFO or DEBUG without real harm. BLATHER = 15 TRACE = 5 logging.addLevelName("BLATHER", BLATHER) logging.addLevelName("TRACE", TRACE) zope2.13-2.13.21/source/ZODB3/src/ZODB/config.xml0000644000175000017500000000025312214017464017575 0ustar arnauarnau zope2.13-2.13.21/source/ZODB3/src/ZODB/Connection.py0000644000175000017500000015016612214017464020270 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Database connection support $Id: Connection.py 121409 2011-04-12 13:50:07Z jim $""" import logging import sys import tempfile import threading import warnings import os import time from persistent import PickleCache # interfaces from persistent.interfaces import IPersistentDataManager from ZODB.interfaces import IConnection from ZODB.interfaces import IBlobStorage from ZODB.interfaces import IMVCCStorage from ZODB.blob import Blob, rename_or_copy_blob, remove_committed_dir from transaction.interfaces import ISavepointDataManager from transaction.interfaces import IDataManagerSavepoint from transaction.interfaces import ISynchronizer from zope.interface import implements import transaction import ZODB from ZODB.blob import SAVEPOINT_SUFFIX from ZODB.ConflictResolution import ResolvedSerial from ZODB.ExportImport import ExportImport from ZODB import POSException from ZODB.POSException import InvalidObjectReference, ConnectionStateError from ZODB.POSException import ConflictError, ReadConflictError from ZODB.POSException import Unsupported, ReadOnlyHistoryError from ZODB.POSException import POSKeyError from ZODB.serialize import ObjectWriter, ObjectReader from ZODB.utils import p64, u64, z64, oid_repr, positive_id from ZODB import utils global_reset_counter = 0 def resetCaches(): """Causes all connection caches to be reset as connections are reopened. Zope's refresh feature uses this. When you reload Python modules, instances of classes continue to use the old class definitions. To use the new code immediately, the refresh feature asks ZODB to clear caches by calling resetCaches(). When the instances are loaded by subsequent connections, they will use the new class definitions. """ global global_reset_counter global_reset_counter += 1 class Connection(ExportImport, object): """Connection to ZODB for loading and storing objects.""" implements(IConnection, ISavepointDataManager, IPersistentDataManager, ISynchronizer) _code_timestamp = 0 ########################################################################## # Connection methods, ZODB.IConnection def __init__(self, db, cache_size=400, before=None, cache_size_bytes=0): """Create a new Connection.""" self._log = logging.getLogger('ZODB.Connection') self._debug_info = () self._db = db self.large_record_size = db.large_record_size # historical connection self.before = before # Multi-database support self.connections = {self._db.database_name: self} storage = db.storage if IMVCCStorage.providedBy(storage): # Use a connection-specific storage instance. self._mvcc_storage = True storage = storage.new_instance() else: self._mvcc_storage = False self._normal_storage = self._storage = storage self.new_oid = db.new_oid self._savepoint_storage = None # Do we need to join a txn manager? self._needs_to_join = True self.transaction_manager = None self.opened = None # time.time() when DB.open() opened us self._reset_counter = global_reset_counter self._load_count = 0 # Number of objects unghosted self._store_count = 0 # Number of objects stored # Cache which can ghostify (forget the state of) objects not # recently used. Its API is roughly that of a dict, with # additional gc-related and invalidation-related methods. self._cache = PickleCache(self, cache_size, cache_size_bytes) # The pre-cache is used by get to avoid infinite loops when # objects immediately load their state whern they get their # persistent data set. self._pre_cache = {} # List of all objects (not oids) registered as modified by the # persistence machinery, or by add(), or whose access caused a # ReadConflictError (just to be able to clean them up from the # cache on abort with the other modified objects). All objects # of this list are either in _cache or in _added. self._registered_objects = [] # ids and serials of objects for which readCurrent was called # in a transaction. self._readCurrent = {} # Dict of oid->obj added explicitly through add(). Used as a # preliminary cache until commit time when objects are all moved # to the real _cache. The objects are moved to _creating at # commit time. self._added = {} # During commit this is turned into a list, which receives # objects added as a side-effect of storing a modified object. self._added_during_commit = None # During commit, all objects go to either _modified or _creating: # Dict of oid->flag of new objects (without serial), either # added by add() or implicitly added (discovered by the # serializer during commit). The flag is True for implicit # adding. Used during abort to remove created objects from the # _cache, and by persistent_id to check that a new object isn't # reachable from multiple databases. self._creating = {} # List of oids of modified objects, which have to be invalidated # in the cache on abort and in other connections on finish. self._modified = [] # _invalidated queues invalidate messages delivered from the DB # _inv_lock prevents one thread from modifying the set while # another is processing invalidations. All the invalidations # from a single transaction should be applied atomically, so # the lock must be held when reading _invalidated. # It sucks that we have to hold the lock to read _invalidated. # Normally, _invalidated is written by calling dict.update, which # will execute atomically by virtue of the GIL. But some storage # might generate oids where hash or compare invokes Python code. In # that case, the GIL can't save us. # Note: since that was written, it was officially declared that the # type of an oid is str. TODO: remove the related now-unnecessary # critical sections (if any -- this needs careful thought). self._inv_lock = threading.Lock() self._invalidated = set() # Flag indicating whether the cache has been invalidated: self._invalidatedCache = False # We intend to prevent committing a transaction in which # ReadConflictError occurs. _conflicts is the set of oids that # experienced ReadConflictError. Any time we raise ReadConflictError, # the oid should be added to this set, and we should be sure that the # object is registered. Because it's registered, Connection.commit() # will raise ReadConflictError again (because the oid is in # _conflicts). self._conflicts = {} # _txn_time stores the upper bound on transactions visible to # this connection. That is, all object revisions must be # written before _txn_time. If it is None, then the current # revisions are acceptable. self._txn_time = None # To support importFile(), implemented in the ExportImport base # class, we need to run _importDuringCommit() from our commit() # method. If _import is not None, it is a two-tuple of arguments # to pass to _importDuringCommit(). self._import = None self._reader = ObjectReader(self, self._cache, self._db.classFactory) def add(self, obj): """Add a new object 'obj' to the database and assign it an oid.""" if self.opened is None: raise ConnectionStateError("The database connection is closed") marker = object() oid = getattr(obj, "_p_oid", marker) if oid is marker: raise TypeError("Only first-class persistent objects may be" " added to a Connection.", obj) elif obj._p_jar is None: assert obj._p_oid is None oid = obj._p_oid = self.new_oid() obj._p_jar = self if self._added_during_commit is not None: self._added_during_commit.append(obj) self._register(obj) # Add to _added after calling register(), so that _added # can be used as a test for whether the object has been # registered with the transaction. self._added[oid] = obj elif obj._p_jar is not self: raise InvalidObjectReference(obj, obj._p_jar) def get(self, oid): """Return the persistent object with oid 'oid'.""" if self.opened is None: raise ConnectionStateError("The database connection is closed") obj = self._cache.get(oid, None) if obj is not None: return obj obj = self._added.get(oid, None) if obj is not None: return obj obj = self._pre_cache.get(oid, None) if obj is not None: return obj p, serial = self._storage.load(oid, '') obj = self._reader.getGhost(p) # Avoid infiniate loop if obj tries to load its state before # it is added to the cache and it's state refers to it. # (This will typically be the case for non-ghostifyable objects, # like persistent caches.) self._pre_cache[oid] = obj self._cache.new_ghost(oid, obj) self._pre_cache.pop(oid) return obj def cacheMinimize(self): """Deactivate all unmodified objects in the cache. """ for connection in self.connections.itervalues(): connection._cache.minimize() # TODO: we should test what happens when cacheGC is called mid-transaction. def cacheGC(self): """Reduce cache size to target size. """ for connection in self.connections.itervalues(): connection._cache.incrgc() __onCloseCallbacks = None def onCloseCallback(self, f): """Register a callable, f, to be called by close().""" if self.__onCloseCallbacks is None: self.__onCloseCallbacks = [] self.__onCloseCallbacks.append(f) def close(self, primary=True): """Close the Connection.""" if not self._needs_to_join: # We're currently joined to a transaction. raise ConnectionStateError("Cannot close a connection joined to " "a transaction") if self._cache is not None: self._cache.incrgc() # This is a good time to do some GC # Call the close callbacks. if self.__onCloseCallbacks is not None: for f in self.__onCloseCallbacks: try: f() except: # except what? f = getattr(f, 'im_self', f) self._log.error("Close callback failed for %s", f, exc_info=sys.exc_info()) self.__onCloseCallbacks = None self._debug_info = () if self.opened: self.transaction_manager.unregisterSynch(self) if self._mvcc_storage: self._storage.sync(force=False) if primary: for connection in self.connections.values(): if connection is not self: connection.close(False) # Return the connection to the pool. if self.opened is not None: self._db._returnToPool(self) # _returnToPool() set self.opened to None. # However, we can't assert that here, because self may # have been reused (by another thread) by the time we # get back here. else: self.opened = None am = self._db._activity_monitor if am is not None: am.closedConnection(self) def db(self): """Returns a handle to the database this connection belongs to.""" return self._db def isReadOnly(self): """Returns True if this connection is read only.""" if self.opened is None: raise ConnectionStateError("The database connection is closed") return self.before is not None or self._storage.isReadOnly() def invalidate(self, tid, oids): """Notify the Connection that transaction 'tid' invalidated oids.""" if self.before is not None: # This is a historical connection. Invalidations are irrelevant. return self._inv_lock.acquire() try: if self._txn_time is None: self._txn_time = tid elif (tid < self._txn_time) and (tid is not None): raise AssertionError("invalidations out of order, %r < %r" % (tid, self._txn_time)) self._invalidated.update(oids) finally: self._inv_lock.release() def invalidateCache(self): self._inv_lock.acquire() try: self._invalidatedCache = True finally: self._inv_lock.release() @property def root(self): """Return the database root object.""" return RootConvenience(self.get(z64)) def get_connection(self, database_name): """Return a Connection for the named database.""" connection = self.connections.get(database_name) if connection is None: new_con = self._db.databases[database_name].open( transaction_manager=self.transaction_manager, before=self.before, ) self.connections.update(new_con.connections) new_con.connections = self.connections connection = new_con return connection def _implicitlyAdding(self, oid): """Are we implicitly adding an object within the current transaction This is used in a check to avoid implicitly adding an object to a database in a multi-database situation. See serialize.ObjectWriter.persistent_id. """ return (self._creating.get(oid, 0) or ((self._savepoint_storage is not None) and self._savepoint_storage.creating.get(oid, 0) ) ) def sync(self): """Manually update the view on the database.""" self.transaction_manager.abort() self._storage_sync() def getDebugInfo(self): """Returns a tuple with different items for debugging the connection. """ return self._debug_info def setDebugInfo(self, *args): """Add the given items to the debug information of this connection.""" self._debug_info = self._debug_info + args def getTransferCounts(self, clear=False): """Returns the number of objects loaded and stored.""" res = self._load_count, self._store_count if clear: self._load_count = 0 self._store_count = 0 return res # Connection methods ########################################################################## ########################################################################## # Data manager (ISavepointDataManager) methods def abort(self, transaction): """Abort a transaction and forget all changes.""" # The order is important here. We want to abort registered # objects before we process the cache. Otherwise, we may un-add # objects added in savepoints. If they've been modified since # the savepoint, then they won't have _p_oid or _p_jar after # they've been unadded. This will make the code in _abort # confused. self._abort() if self._savepoint_storage is not None: self._abort_savepoint() self._invalidate_creating() self._tpc_cleanup() def _abort(self): """Abort a transaction and forget all changes.""" for obj in self._registered_objects: oid = obj._p_oid assert oid is not None if oid in self._added: del self._added[oid] if self._cache.get(oid) is not None: del self._cache[oid] del obj._p_jar del obj._p_oid if obj._p_changed: obj._p_changed = False else: # Note: If we invalidate a non-ghostifiable object # (i.e. a persistent class), the object will # immediately reread its state. That means that the # following call could result in a call to # self.setstate, which, of course, must succeed. # In general, it would be better if the read could be # delayed until the start of the next transaction. If # we read at the end of a transaction and if the # object was invalidated during this transaction, then # we'll read non-current data, which we'll discard # later in transaction finalization. Unfortnately, we # can only delay the read if this abort corresponds to # a top-level-transaction abort. We can't tell if # this is a top-level-transaction abort, so we have to # go ahead and invalidate now. Fortunately, it's # pretty unlikely that the object we are invalidating # was invalidated by another thread, so the risk of a # reread is pretty low. self._cache.invalidate(oid) def _tpc_cleanup(self): """Performs cleanup operations to support tpc_finish and tpc_abort.""" self._conflicts.clear() self._needs_to_join = True self._registered_objects = [] self._creating.clear() # Process pending invalidations. def _flush_invalidations(self): if self._mvcc_storage: # Poll the storage for invalidations. invalidated = self._storage.poll_invalidations() if invalidated is None: # special value: the transaction is so old that # we need to flush the whole cache. self._cache.invalidate(self._cache.cache_data.keys()) elif invalidated: self._cache.invalidate(invalidated) self._inv_lock.acquire() try: # Non-ghostifiable objects may need to read when they are # invalidated, so we'll quickly just replace the # invalidating dict with a new one. We'll then process # the invalidations after freeing the lock *and* after # resetting the time. This means that invalidations will # happen after the start of the transactions. They are # subject to conflict errors and to reading old data. # TODO: There is a potential problem lurking for persistent # classes. Suppose we have an invalidation of a persistent # class and of an instance. If the instance is # invalidated first and if the invalidation logic uses # data read from the class, then the invalidation could # be performed with stale data. Or, suppose that there # are instances of the class that are freed as a result of # invalidating some object. Perhaps code in their __del__ # uses class data. Really, the only way to properly fix # this is to, in fact, make classes ghostifiable. Then # we'd have to reimplement attribute lookup to check the # class state and, if necessary, activate the class. It's # much worse than that though, because we'd also need to # deal with slots. When a class is ghostified, we'd need # to replace all of the slot operations with versions that # reloaded the object when called. It's hard to say which # is better or worse. For now, it seems the risk of # using a class while objects are being invalidated seems # small enough to be acceptable. invalidated = dict.fromkeys(self._invalidated) self._invalidated = set() self._txn_time = None if self._invalidatedCache: self._invalidatedCache = False invalidated = self._cache.cache_data.copy() finally: self._inv_lock.release() self._cache.invalidate(invalidated) # Now is a good time to collect some garbage. self._cache.incrgc() def tpc_begin(self, transaction): """Begin commit of a transaction, starting the two-phase commit.""" self._modified = [] # _creating is a list of oids of new objects, which is used to # remove them from the cache if a transaction aborts. self._creating.clear() self._normal_storage.tpc_begin(transaction) def commit(self, transaction): """Commit changes to an object""" if self._savepoint_storage is not None: # We first checkpoint the current changes to the savepoint self.savepoint() # then commit all of the savepoint changes at once self._commit_savepoint(transaction) # No need to call _commit since savepoint did. else: self._commit(transaction) for oid, serial in self._readCurrent.iteritems(): try: self._storage.checkCurrentSerialInTransaction( oid, serial, transaction) except ConflictError: self._cache.invalidate(oid) raise def _commit(self, transaction): """Commit changes to an object""" if self.before is not None: raise ReadOnlyHistoryError() if self._import: # We are importing an export file. We alsways do this # while making a savepoint so we can copy export data # directly to our storage, typically a TmpStore. self._importDuringCommit(transaction, *self._import) self._import = None # Just in case an object is added as a side-effect of storing # a modified object. If, for example, a __getstate__() method # calls add(), the newly added objects will show up in # _added_during_commit. This sounds insane, but has actually # happened. self._added_during_commit = [] if self._invalidatedCache: raise ConflictError() for obj in self._registered_objects: oid = obj._p_oid assert oid if oid in self._conflicts: raise ReadConflictError(object=obj) if obj._p_jar is not self: raise InvalidObjectReference(obj, obj._p_jar) elif oid in self._added: assert obj._p_serial == z64 elif obj._p_changed: if oid in self._invalidated: resolve = getattr(obj, "_p_resolveConflict", None) if resolve is None: raise ConflictError(object=obj) self._modified.append(oid) else: # Nothing to do. It's been said that it's legal, e.g., for # an object to set _p_changed to false after it's been # changed and registered. continue self._store_objects(ObjectWriter(obj), transaction) for obj in self._added_during_commit: self._store_objects(ObjectWriter(obj), transaction) self._added_during_commit = None def _store_objects(self, writer, transaction): for obj in writer: oid = obj._p_oid serial = getattr(obj, "_p_serial", z64) if ((serial == z64) and ((self._savepoint_storage is None) or (oid not in self._savepoint_storage.creating) or self._savepoint_storage.creating[oid] ) ): # obj is a new object # Because obj was added, it is now in _creating, so it # can be removed from _added. If oid wasn't in # adding, then we are adding it implicitly. implicitly_adding = self._added.pop(oid, None) is None self._creating[oid] = implicitly_adding else: if (oid in self._invalidated and not hasattr(obj, '_p_resolveConflict')): raise ConflictError(object=obj) self._modified.append(oid) p = writer.serialize(obj) # This calls __getstate__ of obj if len(p) >= self.large_record_size: warnings.warn(large_object_message % (obj.__class__, len(p))) if isinstance(obj, Blob): if not IBlobStorage.providedBy(self._storage): raise Unsupported( "Storing Blobs in %s is not supported." % repr(self._storage)) if obj.opened(): raise ValueError("Can't commit with opened blobs.") blobfilename = obj._uncommitted() if blobfilename is None: assert serial is not None # See _uncommitted self._modified.pop() # not modified continue s = self._storage.storeBlob(oid, serial, p, blobfilename, '', transaction) # we invalidate the object here in order to ensure # that that the next attribute access of its name # unghostify it, which will cause its blob data # to be reattached "cleanly" obj._p_invalidate() else: s = self._storage.store(oid, serial, p, '', transaction) self._store_count += 1 # Put the object in the cache before handling the # response, just in case the response contains the # serial number for a newly created object try: self._cache[oid] = obj except: # Dang, I bet it's wrapped: # TODO: Deprecate, then remove, this. if hasattr(obj, 'aq_base'): self._cache[oid] = obj.aq_base else: raise self._cache.update_object_size_estimation(oid, len(p)) obj._p_estimated_size = len(p) self._handle_serial(oid, s) def _handle_serial(self, oid, serial, change=True): # if we write an object, we don't want to check if it was read # while current. This is a convenient choke point to do this. self._readCurrent.pop(oid, None) if not serial: return if not isinstance(serial, str): raise serial obj = self._cache.get(oid, None) if obj is None: return if serial == ResolvedSerial: del obj._p_changed # transition from changed to ghost else: if change: obj._p_changed = 0 # transition from changed to up-to-date obj._p_serial = serial def tpc_abort(self, transaction): if self._import: self._import = None if self._savepoint_storage is not None: self._abort_savepoint() self._storage.tpc_abort(transaction) # Note: If we invalidate a non-ghostifiable object (i.e. a # persistent class), the object will immediately reread its # state. That means that the following call could result in a # call to self.setstate, which, of course, must succeed. In # general, it would be better if the read could be delayed # until the start of the next transaction. If we read at the # end of a transaction and if the object was invalidated # during this transaction, then we'll read non-current data, # which we'll discard later in transaction finalization. We # could, theoretically queue this invalidation by calling # self.invalidate. Unfortunately, attempts to make that # change resulted in mysterious test failures. It's pretty # unlikely that the object we are invalidating was invalidated # by another thread, so the risk of a reread is pretty low. # It's really not worth the effort to pursue this. self._cache.invalidate(self._modified) self._invalidate_creating() while self._added: oid, obj = self._added.popitem() if obj._p_changed: obj._p_changed = False del obj._p_oid del obj._p_jar self._tpc_cleanup() def _invalidate_creating(self, creating=None): """Disown any objects newly saved in an uncommitted transaction.""" if creating is None: creating = self._creating self._creating = {} for oid in creating: o = self._cache.get(oid) if o is not None: del self._cache[oid] if o._p_changed: o._p_changed = False del o._p_jar del o._p_oid def tpc_vote(self, transaction): """Verify that a data manager can commit the transaction.""" try: vote = self._storage.tpc_vote except AttributeError: return try: s = vote(transaction) except ReadConflictError, v: if v.oid: self._cache.invalidate(v.oid) raise if s: for oid, serial in s: self._handle_serial(oid, serial) def tpc_finish(self, transaction): """Indicate confirmation that the transaction is done.""" def callback(tid): if self._mvcc_storage: # Inter-connection invalidation is not needed when the # storage provides MVCC. return d = dict.fromkeys(self._modified) self._db.invalidate(tid, d, self) # It's important that the storage calls the passed function # while it still has its lock. We don't want another thread # to be able to read any updated data until we've had a chance # to send an invalidation message to all of the other # connections! self._storage.tpc_finish(transaction, callback) self._tpc_cleanup() def sortKey(self): """Return a consistent sort key for this connection.""" return "%s:%s" % (self._storage.sortKey(), id(self)) # Data manager (ISavepointDataManager) methods ########################################################################## ########################################################################## # Transaction-manager synchronization -- ISynchronizer def beforeCompletion(self, txn): # We don't do anything before a commit starts. pass # Call the underlying storage's sync() method (if any), and process # pending invalidations regardless. Of course this should only be # called at transaction boundaries. def _storage_sync(self, *ignored): self._readCurrent.clear() sync = getattr(self._storage, 'sync', 0) if sync: sync() self._flush_invalidations() afterCompletion = _storage_sync newTransaction = _storage_sync # Transaction-manager synchronization -- ISynchronizer ########################################################################## ########################################################################## # persistent.interfaces.IPersistentDatamanager def oldstate(self, obj, tid): """Return copy of 'obj' that was written by transaction 'tid'.""" assert obj._p_jar is self p = self._storage.loadSerial(obj._p_oid, tid) return self._reader.getState(p) def setstate(self, obj): """Turns the ghost 'obj' into a real object by loading its state from the database.""" oid = obj._p_oid if self.opened is None: msg = ("Shouldn't load state for %s " "when the connection is closed" % oid_repr(oid)) self._log.error(msg) raise ConnectionStateError(msg) try: self._setstate(obj) except ConflictError: raise except: self._log.error("Couldn't load state for %s", oid_repr(oid), exc_info=sys.exc_info()) raise def _setstate(self, obj): # Helper for setstate(), which provides logging of failures. # The control flow is complicated here to avoid loading an # object revision that we are sure we aren't going to use. As # a result, invalidation tests occur before and after the # load. We can only be sure about invalidations after the # load. # If an object has been invalidated, among the cases to consider: # - Try MVCC # - Raise ConflictError. if self.before is not None: # Load data that was current before the time we have. before = self.before t = self._storage.loadBefore(obj._p_oid, before) if t is None: raise POSKeyError() # historical connection! p, serial, end = t else: # There is a harmless data race with self._invalidated. A # dict update could go on in another thread, but we don't care # because we have to check again after the load anyway. if self._invalidatedCache: raise ReadConflictError() if (obj._p_oid in self._invalidated): self._load_before_or_conflict(obj) return p, serial = self._storage.load(obj._p_oid, '') self._load_count += 1 self._inv_lock.acquire() try: invalid = obj._p_oid in self._invalidated finally: self._inv_lock.release() if invalid: self._load_before_or_conflict(obj) return self._reader.setGhostState(obj, p) obj._p_serial = serial self._cache.update_object_size_estimation(obj._p_oid, len(p)) obj._p_estimated_size = len(p) # Blob support if isinstance(obj, Blob): obj._p_blob_uncommitted = None obj._p_blob_committed = self._storage.loadBlob(obj._p_oid, serial) def _load_before_or_conflict(self, obj): """Load non-current state for obj or raise ReadConflictError.""" if not self._setstate_noncurrent(obj): self._register(obj) self._conflicts[obj._p_oid] = True raise ReadConflictError(object=obj) def _setstate_noncurrent(self, obj): """Set state using non-current data. Return True if state was available, False if not. """ try: # Load data that was current before the commit at txn_time. t = self._storage.loadBefore(obj._p_oid, self._txn_time) except KeyError: return False if t is None: return False data, start, end = t # The non-current transaction must have been written before # txn_time. It must be current at txn_time, but could have # been modified at txn_time. assert start < self._txn_time, (u64(start), u64(self._txn_time)) assert end is not None assert self._txn_time <= end, (u64(self._txn_time), u64(end)) self._reader.setGhostState(obj, data) obj._p_serial = start # MVCC Blob support if isinstance(obj, Blob): obj._p_blob_uncommitted = None obj._p_blob_committed = self._storage.loadBlob(obj._p_oid, start) return True def register(self, obj): """Register obj with the current transaction manager. A subclass could override this method to customize the default policy of one transaction manager for each thread. obj must be an object loaded from this Connection. """ assert obj._p_jar is self if obj._p_oid is None: # The actual complaint here is that an object without # an oid is being registered. I can't think of any way to # achieve that without assignment to _p_jar. If there is # a way, this will be a very confusing exception. raise ValueError("assigning to _p_jar is not supported") elif obj._p_oid in self._added: # It was registered before it was added to _added. return self._register(obj) def _register(self, obj=None): # The order here is important. We need to join before # registering the object, because joining may take a # savepoint, and the savepoint should not reflect the change # to the object. if self._needs_to_join: self.transaction_manager.get().join(self) self._needs_to_join = False if obj is not None: self._registered_objects.append(obj) def readCurrent(self, ob): assert ob._p_jar is self assert ob._p_oid is not None and ob._p_serial is not None self._readCurrent[ob._p_oid] = ob._p_serial # persistent.interfaces.IPersistentDatamanager ########################################################################## ########################################################################## # PROTECTED stuff (used by e.g. ZODB.DB.DB) def _cache_items(self): # find all items on the lru list items = self._cache.lru_items() # fine everything. some on the lru list, some not everything = self._cache.cache_data # remove those items that are on the lru list for k,v in items: del everything[k] # return a list of [ghosts....not recently used.....recently used] return everything.items() + items def open(self, transaction_manager=None, delegate=True): """Register odb, the DB that this Connection uses. This method is called by the DB every time a Connection is opened. Any invalidations received while the Connection was closed will be processed. If the global module function resetCaches() was called, the cache will be cleared. Parameters: odb: database that owns the Connection transaction_manager: transaction manager to use. None means use the default transaction manager. register for afterCompletion() calls. """ self.opened = time.time() if transaction_manager is None: transaction_manager = transaction.manager self.transaction_manager = transaction_manager if self._reset_counter != global_reset_counter: # New code is in place. Start a new cache. self._resetCache() else: self._flush_invalidations() transaction_manager.registerSynch(self) if self._cache is not None: self._cache.incrgc() # This is a good time to do some GC if delegate: # delegate open to secondary connections for connection in self.connections.values(): if connection is not self: connection.open(transaction_manager, False) def _resetCache(self): """Creates a new cache, discarding the old one. See the docstring for the resetCaches() function. """ self._reset_counter = global_reset_counter self._invalidated.clear() self._invalidatedCache = False cache_size = self._cache.cache_size cache_size_bytes = self._cache.cache_size_bytes self._cache = cache = PickleCache(self, cache_size, cache_size_bytes) if getattr(self, '_reader', None) is not None: self._reader._cache = cache def _release_resources(self): for c in self.connections.itervalues(): if c._mvcc_storage: c._storage.release() c._storage = c._normal_storage = None c._cache = PickleCache(self, 0, 0) ########################################################################## # Python protocol def __repr__(self): return '' % (positive_id(self),) # Python protocol ########################################################################## ########################################################################## # DEPRECATION candidates __getitem__ = get def exchange(self, old, new): # called by a ZClasses method that isn't executed by the test suite oid = old._p_oid new._p_oid = oid new._p_jar = self new._p_changed = 1 self._register(new) self._cache[oid] = new # DEPRECATION candidates ########################################################################## ########################################################################## # DEPRECATED methods # None at present. # DEPRECATED methods ########################################################################## ##################################################################### # Savepoint support def savepoint(self): if self._savepoint_storage is None: tmpstore = TmpStore(self._normal_storage) self._savepoint_storage = tmpstore self._storage = self._savepoint_storage self._creating.clear() self._commit(None) self._storage.creating.update(self._creating) self._creating.clear() self._registered_objects = [] state = (self._storage.position, self._storage.index.copy(), self._storage.creating.copy(), ) result = Savepoint(self, state) # While the interface doesn't guarantee this, savepoints are # sometimes used just to "break up" very long transactions, and as # a pragmatic matter this is a good time to reduce the cache # memory burden. self.cacheGC() return result def _rollback(self, state): self._abort() self._registered_objects = [] src = self._storage # Invalidate objects created *after* the savepoint. self._invalidate_creating((oid for oid in src.creating if oid not in state[2])) index = src.index src.reset(*state) self._cache.invalidate(index) def _commit_savepoint(self, transaction): """Commit all changes made in savepoints and begin 2-phase commit """ src = self._savepoint_storage self._storage = self._normal_storage self._savepoint_storage = None try: self._log.debug("Committing savepoints of size %s", src.getSize()) oids = src.index.keys() # Copy invalidating and creating info from temporary storage: self._modified.extend(oids) self._creating.update(src.creating) for oid in oids: data, serial = src.load(oid, src) obj = self._cache.get(oid, None) if obj is not None: self._cache.update_object_size_estimation( obj._p_oid, len(data)) obj._p_estimated_size = len(data) if isinstance(self._reader.getGhost(data), Blob): blobfilename = src.loadBlob(oid, serial) s = self._storage.storeBlob( oid, serial, data, blobfilename, '', transaction) # we invalidate the object here in order to ensure # that that the next attribute access of its name # unghostify it, which will cause its blob data # to be reattached "cleanly" self.invalidate(None, (oid, )) else: s = self._storage.store(oid, serial, data, '', transaction) self._handle_serial(oid, s, change=False) finally: src.close() def _abort_savepoint(self): """Discard all savepoint data.""" src = self._savepoint_storage self._invalidate_creating(src.creating) self._storage = self._normal_storage self._savepoint_storage = None # Note: If we invalidate a non-ghostifiable object (i.e. a # persistent class), the object will immediately reread it's # state. That means that the following call could result in a # call to self.setstate, which, of course, must succeed. In # general, it would be better if the read could be delayed # until the start of the next transaction. If we read at the # end of a transaction and if the object was invalidated # during this transaction, then we'll read non-current data, # which we'll discard later in transaction finalization. We # could, theoretically queue this invalidation by calling # self.invalidate. Unfortunately, attempts to make that # change resulted in mysterious test failures. It's pretty # unlikely that the object we are invalidating was invalidated # by another thread, so the risk of a reread is pretty low. # It's really not worth the effort to pursue this. # Note that we do this *after* reseting the storage so that, if # data are read, we read it from the reset storage! self._cache.invalidate(src.index) src.close() # Savepoint support ##################################################################### class Savepoint: implements(IDataManagerSavepoint) def __init__(self, datamanager, state): self.datamanager = datamanager self.state = state def rollback(self): self.datamanager._rollback(self.state) class TmpStore: """A storage-like thing to support savepoints.""" implements(IBlobStorage) def __init__(self, storage): self._storage = storage for method in ( 'getName', 'new_oid', 'getSize', 'sortKey', 'loadBefore', 'isReadOnly' ): setattr(self, method, getattr(storage, method)) self._file = tempfile.TemporaryFile() # position: current file position # _tpos: file position at last commit point self.position = 0L # index: map oid to pos of last committed version self.index = {} self.creating = {} self._blob_dir = None def __len__(self): return len(self.index) def close(self): self._file.close() if self._blob_dir is not None: remove_committed_dir(self._blob_dir) self._blob_dir = None def load(self, oid, version): pos = self.index.get(oid) if pos is None: return self._storage.load(oid, '') self._file.seek(pos) h = self._file.read(8) oidlen = u64(h) read_oid = self._file.read(oidlen) if read_oid != oid: raise POSException.StorageSystemError('Bad temporary storage') h = self._file.read(16) size = u64(h[8:]) serial = h[:8] return self._file.read(size), serial def store(self, oid, serial, data, version, transaction): # we have this funny signature so we can reuse the normal non-commit # commit logic assert version == '' self._file.seek(self.position) l = len(data) if serial is None: serial = z64 header = p64(len(oid)) + oid + serial + p64(l) self._file.write(header) self._file.write(data) self.index[oid] = self.position self.position += l + len(header) return serial def storeBlob(self, oid, serial, data, blobfilename, version, transaction): assert version == '' serial = self.store(oid, serial, data, '', transaction) targetpath = self._getBlobPath() if not os.path.exists(targetpath): os.makedirs(targetpath, 0700) targetname = self._getCleanFilename(oid, serial) rename_or_copy_blob(blobfilename, targetname, chmod=False) def loadBlob(self, oid, serial): """Return the filename where the blob file can be found. """ if not IBlobStorage.providedBy(self._storage): raise Unsupported( "Blobs are not supported by the underlying storage %r." % self._storage) filename = self._getCleanFilename(oid, serial) if not os.path.exists(filename): return self._storage.loadBlob(oid, serial) return filename def openCommittedBlobFile(self, oid, serial, blob=None): blob_filename = self.loadBlob(oid, serial) if blob is None: return open(blob_filename, 'rb') else: return ZODB.blob.BlobFile(blob_filename, 'r', blob) def _getBlobPath(self): blob_dir = self._blob_dir if blob_dir is None: blob_dir = tempfile.mkdtemp(dir=self.temporaryDirectory(), prefix='savepoints') self._blob_dir = blob_dir return blob_dir def _getCleanFilename(self, oid, tid): return os.path.join( self._getBlobPath(), "%s-%s%s" % (utils.oid_repr(oid), utils.tid_repr(tid), SAVEPOINT_SUFFIX,) ) def temporaryDirectory(self): return self._storage.temporaryDirectory() def reset(self, position, index, creating): self._file.truncate(position) self.position = position # Caution: We're typically called as part of a savepoint rollback. # Other machinery remembers the index to restore, and passes it to # us. If we simply bind self.index to `index`, then if the caller # didn't pass a copy of the index, the caller's index will mutate # when self.index mutates. This can be a disaster if the caller is a # savepoint to which the user rolls back again later (the savepoint # loses the original index it passed). Therefore, to be safe, we make # a copy of the index here. An alternative would be to ensure that # all callers pass copies. As is, our callers do not make copies. self.index = index.copy() self.creating = creating class RootConvenience(object): def __init__(self, root): self.__dict__['_root'] = root def __getattr__(self, name): try: return self._root[name] except KeyError: raise AttributeError(name) def __setattr__(self, name, v): self._root[name] = v def __delattr__(self, name): try: del self._root[name] except KeyError: raise AttributeError(name) def __call__(self): return self._root def __repr__(self): names = " ".join(sorted(self._root)) if len(names) > 60: names = names[:57].rsplit(' ', 1)[0] + ' ...' return "" % names large_object_message = """The %s object you're saving is large. (%s bytes.) Perhaps you're storing media which should be stored in blobs. Perhaps you're using a non-scalable data structure, such as a PersistentMapping or PersistentList. Perhaps you're storing data in objects that aren't persistent at all. In cases like that, the data is stored in the record of the containing persistent object. In any case, storing records this big is probably a bad idea. If you insist and want to get rid of this warning, use the large_record_size option of the ZODB.DB constructor (or the large-record-size option in a configuration file) to specify a larger size. """ zope2.13-2.13.21/source/ZODB3/src/ZODB/BaseStorage.py0000644000175000017500000003573112214017464020370 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Storage base class that is mostly a mistake The base class here is tightly coupled with its subclasses and its use is not recommended. It's still here for historical reasons. """ from __future__ import with_statement import cPickle import threading import time import logging from struct import pack as _structpack, unpack as _structunpack import zope.interface from persistent.TimeStamp import TimeStamp import ZODB.interfaces from ZODB import POSException from ZODB.utils import z64, oid_repr from ZODB.UndoLogCompatible import UndoLogCompatible log = logging.getLogger("ZODB.BaseStorage") import sys class BaseStorage(UndoLogCompatible): """Base class that supports storage implementations. XXX Base classes like this are an attractive nuisance. They often introduce more complexity than they save. While important logic is implemented here, we should consider exposing it as utility functions or as objects that can be used through composition. A subclass must define the following methods: load() store() close() cleanup() lastTransaction() It must override these hooks: _begin() _vote() _abort() _finish() _clear_temp() If it stores multiple revisions, it should implement loadSerial() loadBefore() Each storage will have two locks that are accessed via lock acquire and release methods bound to the instance. (Yuck.) _lock_acquire / _lock_release (reentrant) _commit_lock_acquire / _commit_lock_release The commit lock is acquired in tpc_begin() and released in tpc_abort() and tpc_finish(). It is never acquired with the other lock held. The other lock appears to protect _oid and _transaction and perhaps other things. It is always held when load() is called, so presumably the load() implementation should also acquire the lock. """ _transaction=None # Transaction that is being committed _tstatus=' ' # Transaction status, used for copying data _is_read_only = False def __init__(self, name, base=None): self.__name__= name log.debug("create storage %s", self.__name__) # Allocate locks: self._lock = threading.RLock() self.__commit_lock = threading.Lock() # Comment out the following 4 lines to debug locking: self._lock_acquire = self._lock.acquire self._lock_release = self._lock.release self._commit_lock_acquire = self.__commit_lock.acquire self._commit_lock_release = self.__commit_lock.release t = time.time() t = self._ts = TimeStamp(*(time.gmtime(t)[:5] + (t%60,))) self._tid = repr(t) # ._oid is the highest oid in use (0 is always in use -- it's # a reserved oid for the root object). Our new_oid() method # increments it by 1, and returns the result. It's really a # 64-bit integer stored as an 8-byte big-endian string. oid = getattr(base, '_oid', None) if oid is None: self._oid = z64 else: self._oid = oid ######################################################################## # The following methods are normally overridden on instances, # except when debugging: def _lock_acquire(self, *args): f = sys._getframe(1) sys.stdout.write("[la(%s:%s)\n" % (f.f_code.co_filename, f.f_lineno)) sys.stdout.flush() self._lock.acquire(*args) sys.stdout.write("la(%s:%s)]\n" % (f.f_code.co_filename, f.f_lineno)) sys.stdout.flush() def _lock_release(self, *args): f = sys._getframe(1) sys.stdout.write("[lr(%s:%s)\n" % (f.f_code.co_filename, f.f_lineno)) sys.stdout.flush() self._lock.release(*args) sys.stdout.write("lr(%s:%s)]\n" % (f.f_code.co_filename, f.f_lineno)) sys.stdout.flush() def _commit_lock_acquire(self, *args): f = sys._getframe(1) sys.stdout.write("[ca(%s:%s)\n" % (f.f_code.co_filename, f.f_lineno)) sys.stdout.flush() self.__commit_lock.acquire(*args) sys.stdout.write("ca(%s:%s)]\n" % (f.f_code.co_filename, f.f_lineno)) sys.stdout.flush() def _commit_lock_release(self, *args): f = sys._getframe(1) sys.stdout.write("[cr(%s:%s)\n" % (f.f_code.co_filename, f.f_lineno)) sys.stdout.flush() self.__commit_lock.release(*args) sys.stdout.write("cr(%s:%s)]\n" % (f.f_code.co_filename, f.f_lineno)) sys.stdout.flush() # ######################################################################## def sortKey(self): """Return a string that can be used to sort storage instances. The key must uniquely identify a storage and must be the same across multiple instantiations of the same storage. """ # name may not be sufficient, e.g. ZEO has a user-definable name. return self.__name__ def getName(self): return self.__name__ def getSize(self): return len(self)*300 # WAG! def history(self, oid, version, length=1, filter=None): return () def new_oid(self): if self._is_read_only: raise POSException.ReadOnlyError() self._lock_acquire() try: last = self._oid d = ord(last[-1]) if d < 255: # fast path for the usual case last = last[:-1] + chr(d+1) else: # there's a carry out of the last byte last_as_long, = _structunpack(">Q", last) last = _structpack(">Q", last_as_long + 1) self._oid = last return last finally: self._lock_release() # Update the maximum oid in use, under protection of a lock. The # maximum-in-use attribute is changed only if possible_new_max_oid is # larger than its current value. def set_max_oid(self, possible_new_max_oid): self._lock_acquire() try: if possible_new_max_oid > self._oid: self._oid = possible_new_max_oid finally: self._lock_release() def registerDB(self, db): pass # we don't care def isReadOnly(self): return self._is_read_only def tpc_abort(self, transaction): self._lock_acquire() try: if transaction is not self._transaction: return try: self._abort() self._clear_temp() self._transaction = None finally: self._commit_lock_release() finally: self._lock_release() def _abort(self): """Subclasses should redefine this to supply abort actions""" pass def tpc_begin(self, transaction, tid=None, status=' '): if self._is_read_only: raise POSException.ReadOnlyError() self._lock_acquire() try: if self._transaction is transaction: raise POSException.StorageTransactionError( "Duplicate tpc_begin calls for same transaction") self._lock_release() self._commit_lock_acquire() self._lock_acquire() self._transaction = transaction self._clear_temp() user = transaction.user desc = transaction.description ext = transaction._extension if ext: ext = cPickle.dumps(ext, 1) else: ext = "" self._ude = user, desc, ext if tid is None: now = time.time() t = TimeStamp(*(time.gmtime(now)[:5] + (now % 60,))) self._ts = t = t.laterThan(self._ts) self._tid = repr(t) else: self._ts = TimeStamp(tid) self._tid = tid self._tstatus = status self._begin(self._tid, user, desc, ext) finally: self._lock_release() def tpc_transaction(self): return self._transaction def _begin(self, tid, u, d, e): """Subclasses should redefine this to supply transaction start actions. """ pass def tpc_vote(self, transaction): self._lock_acquire() try: if transaction is not self._transaction: raise POSException.StorageTransactionError( "tpc_vote called with wrong transaction") self._vote() finally: self._lock_release() def _vote(self): """Subclasses should redefine this to supply transaction vote actions. """ pass def tpc_finish(self, transaction, f=None): # It's important that the storage calls the function we pass # while it still has its lock. We don't want another thread # to be able to read any updated data until we've had a chance # to send an invalidation message to all of the other # connections! self._lock_acquire() try: if transaction is not self._transaction: raise POSException.StorageTransactionError( "tpc_finish called with wrong transaction") try: if f is not None: f(self._tid) u, d, e = self._ude self._finish(self._tid, u, d, e) self._clear_temp() finally: self._ude = None self._transaction = None self._commit_lock_release() finally: self._lock_release() def _finish(self, tid, u, d, e): """Subclasses should redefine this to supply transaction finish actions """ pass def lastTransaction(self): with self._lock: return self._ltid def getTid(self, oid): self._lock_acquire() try: v = '' try: supportsVersions = self.supportsVersions except AttributeError: pass else: if supportsVersions(): v = self.modifiedInVersion(oid) pickledata, serial = self.load(oid, v) return serial finally: self._lock_release() def loadSerial(self, oid, serial): raise POSException.Unsupported( "Retrieval of historical revisions is not supported") def loadBefore(self, oid, tid): """Return most recent revision of oid before tid committed.""" return None def copyTransactionsFrom(self, other, verbose=0): """Copy transactions from another storage. This is typically used for converting data from one storage to another. `other` must have an .iterator() method. """ copy(other, self, verbose) def copy(source, dest, verbose=0): """Copy transactions from a source to a destination storage This is typically used for converting data from one storage to another. `source` must have an .iterator() method. """ _ts = None ok = 1 preindex = {}; preget = preindex.get # restore() is a new storage API method which has an identical # signature to store() except that it does not return anything. # Semantically, restore() is also identical to store() except that it # doesn't do the ConflictError or VersionLockError consistency # checks. The reason to use restore() over store() in this method is # that store() cannot be used to copy transactions spanning a version # commit or abort, or over transactional undos. # # We'll use restore() if it's available, otherwise we'll fall back to # using store(). However, if we use store, then # copyTransactionsFrom() may fail with VersionLockError or # ConflictError. restoring = hasattr(dest, 'restore') fiter = source.iterator() for transaction in fiter: tid = transaction.tid if _ts is None: _ts = TimeStamp(tid) else: t = TimeStamp(tid) if t <= _ts: if ok: print ('Time stamps out of order %s, %s' % (_ts, t)) ok = 0 _ts = t.laterThan(_ts) tid = `_ts` else: _ts = t if not ok: print ('Time stamps back in order %s' % (t)) ok = 1 if verbose: print _ts dest.tpc_begin(transaction, tid, transaction.status) for r in transaction: oid = r.oid if verbose: print oid_repr(oid), r.version, len(r.data) if restoring: dest.restore(oid, r.tid, r.data, r.version, r.data_txn, transaction) else: pre = preget(oid, None) s = dest.store(oid, pre, r.data, r.version, transaction) preindex[oid] = s dest.tpc_vote(transaction) dest.tpc_finish(transaction) # defined outside of BaseStorage to facilitate independent reuse. # just depends on _transaction attr and getTid method. def checkCurrentSerialInTransaction(self, oid, serial, transaction): if transaction is not self._transaction: raise POSException.StorageTransactionError(self, transaction) committed_tid = self.getTid(oid) if committed_tid != serial: raise POSException.ReadConflictError( oid=oid, serials=(committed_tid, serial)) BaseStorage.checkCurrentSerialInTransaction = checkCurrentSerialInTransaction class TransactionRecord(object): """Abstract base class for iterator protocol""" zope.interface.implements(ZODB.interfaces.IStorageTransactionInformation) def __init__(self, tid, status, user, description, extension): self.tid = tid self.status = status self.user = user self.description = description self.extension = extension # XXX This is a workaround to make the TransactionRecord compatible with a # transaction object because it is passed to tpc_begin(). def _ext_set(self, value): self.extension = value def _ext_get(self): return self.extension _extension = property(fset=_ext_set, fget=_ext_get) class DataRecord(object): """Abstract base class for iterator protocol""" zope.interface.implements(ZODB.interfaces.IStorageRecordInformation) version = '' def __init__(self, oid, tid, data, prev): self.oid = oid self.tid = tid self.data = data self.data_txn = prev zope2.13-2.13.21/source/ZODB3/src/ZODB/utils.txt0000644000175000017500000001301312214017464017505 0ustar arnauarnauZODB Utilits Module =================== The ZODB.utils module provides a number of helpful, somewhat random :), utility functions. >>> import ZODB.utils This document documents a few of them. Over time, it may document more. 64-bit integers and strings --------------------------------- ZODB uses 64-bit transaction ids that are typically represented as strings, but are sometimes manipulated as integers. Object ids are strings too and it is common to ise 64-bit strings that are just packed integers. Functions p64 and u64 pack and unpack integers as strings: >>> ZODB.utils.p64(250347764455111456) '\x03yi\xf7"\xa8\xfb ' >>> print ZODB.utils.u64('\x03yi\xf7"\xa8\xfb ') 250347764455111456 The contant z64 has zero packed as a 64-bit string: >>> ZODB.utils.z64 '\x00\x00\x00\x00\x00\x00\x00\x00' Transaction id generation ------------------------- Storages assign transaction ids as transactions are committed. These are based on UTC time, but must be strictly increasing. The newTid function akes this pretty easy. To see this work (in a predictable way), we'll first hack time.time: >>> import time >>> old_time = time.time >>> time.time = lambda : 1224825068.12 Now, if we ask for a new time stamp, we'll get one based on our faux time: >>> tid = ZODB.utils.newTid(None) >>> tid '\x03yi\xf7"\xa54\x88' newTid requires an old tid as an argument. The old tid may be None, if we don't have a previous transaction id. This time is based on the current time, which we can see by converting it to a time stamp. >>> import ZODB.TimeStamp >>> print ZODB.TimeStamp.TimeStamp(tid) 2008-10-24 05:11:08.120000 To assure that we get a new tid that is later than the old, we can pass an existing tid. Let's pass the tid we just got. >>> tid2 = ZODB.utils.newTid(tid) >>> long(ZODB.utils.u64(tid)), long(ZODB.utils.u64(tid2)) (250347764454864008L, 250347764454864009L) Here, since we called it at the same time, we got a time stamp that was only slightly larger than the previos one. Of course, at a later time, the time stamp we get will be based on the time: >>> time.time = lambda : 1224825069.12 >>> tid = ZODB.utils.newTid(tid2) >>> print ZODB.TimeStamp.TimeStamp(tid) 2008-10-24 05:11:09.120000 >>> time.time = old_time Locking support --------------- Storages are required to be thread safe. The locking descriptor helps automate that. It arranges for a lock to be acquired when a function is called and released when a function exits. To demonstrate this, we'll create a "lock" type that simply prints when it is called: >>> class Lock: ... def acquire(self): ... print 'acquire' ... def release(self): ... print 'release' Now we'll demonstrate the descriptor: >>> class C: ... _lock = Lock() ... _lock_acquire = _lock.acquire ... _lock_release = _lock.release ... ... @ZODB.utils.locked ... def meth(self, *args, **kw): ... print 'meth', args, kw The descriptor expects the instance it wraps to have a '_lock attribute. >>> C().meth(1, 2, a=3) acquire meth (1, 2) {'a': 3} release .. Edge cases We can get the method from the class: >>> C.meth # doctest: +ELLIPSIS >>> C.meth(C()) acquire meth () {} release >>> class C2: ... _lock = Lock() ... _lock_acquire = _lock.acquire ... _lock_release = _lock.release >>> C.meth(C2()) # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: unbound method meth() must be called with C instance as first argument (got C2 instance instead) Preconditions ------------- Often, we want to supply method preconditions. The locking descriptor supports optional method preconditions [1]_. >>> class C: ... def __init__(self): ... _lock = Lock() ... self._lock_acquire = _lock.acquire ... self._lock_release = _lock.release ... self._opened = True ... self._transaction = None ... ... def opened(self): ... """The object is open ... """ ... print 'checking if open' ... return self._opened ... ... def not_in_transaction(self): ... """The object is not in a transaction ... """ ... print 'checking if in a transaction' ... return self._transaction is None ... ... @ZODB.utils.locked(opened, not_in_transaction) ... def meth(self, *args, **kw): ... print 'meth', args, kw >>> c = C() >>> c.meth(1, 2, a=3) acquire checking if open checking if in a transaction meth (1, 2) {'a': 3} release >>> c._transaction = 1 >>> c.meth(1, 2, a=3) # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... AssertionError: ('Failed precondition: ', 'The object is not in a transaction') >>> c._opened = False >>> c.meth(1, 2, a=3) # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... AssertionError: ('Failed precondition: ', 'The object is open') .. [1] Arguably, preconditions should be handled via separate descriptors, but for ZODB storages, almost all methods need to be locked. Combining preconditions with locking provides both efficiency and concise expressions. A more general-purpose facility would almost certainly provide separate descriptors for preconditions. zope2.13-2.13.21/source/ZODB3/src/ZODB/POSException.py0000644000175000017500000002730012214017464020502 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ZODB-defined exceptions $Id: POSException.py 116131 2010-09-02 13:55:24Z jim $""" import sys from ZODB.utils import oid_repr, readable_tid_repr # BBB: We moved the two transactions to the transaction package from transaction.interfaces import TransactionError, TransactionFailedError import transaction.interfaces def _fmt_undo(oid, reason): s = reason and (": %s" % reason) or "" return "Undo error %s%s" % (oid_repr(oid), s) def _recon(class_, state): err = class_.__new__(class_) err.__setstate__(state) return err _recon.__no_side_effects__ = True class POSError(StandardError): """Persistent object system error.""" if sys.version_info[:2] == (2, 6): # The 'message' attribute was deprecated for BaseException with # Python 2.6; here we create descriptor properties to continue using it def __set_message(self, v): self.__dict__['message'] = v def __get_message(self): return self.__dict__['message'] def __del_message(self): del self.__dict__['message'] message = property(__get_message, __set_message, __del_message) if sys.version_info[:2] >= (2, 5): def __reduce__(self): # Copy extra data from internal structures state = self.__dict__.copy() if sys.version_info[:2] == (2, 5): state['message'] = self.message state['args'] = self.args return (_recon, (self.__class__, state)) class POSKeyError(POSError, KeyError): """Key not found in database.""" def __str__(self): return oid_repr(self.args[0]) class ConflictError(POSError, transaction.interfaces.TransientError): """Two transactions tried to modify the same object at once. This transaction should be resubmitted. Instance attributes: oid : string the OID (8-byte packed string) of the object in conflict class_name : string the fully-qualified name of that object's class message : string a human-readable explanation of the error serials : (string, string) a pair of 8-byte packed strings; these are the serial numbers related to conflict. The first is the revision of object that is in conflict, the currently committed serial. The second is the revision the current transaction read when it started. data : string The database record that failed to commit, used to put the class name in the error message. The caller should pass either object or oid as a keyword argument, but not both of them. If object is passed, it should be a persistent object with an _p_oid attribute. """ def __init__(self, message=None, object=None, oid=None, serials=None, data=None): if message is None: self.message = "database conflict error" else: self.message = message if object is None: self.oid = None self.class_name = None else: self.oid = object._p_oid klass = object.__class__ self.class_name = klass.__module__ + "." + klass.__name__ if oid is not None: assert self.oid is None self.oid = oid if data is not None: # avoid circular import chain from ZODB.utils import get_pickle_metadata self.class_name = "%s.%s" % get_pickle_metadata(data) ## else: ## if message != "data read conflict error": ## raise RuntimeError self.serials = serials def __str__(self): extras = [] if self.oid: extras.append("oid %s" % oid_repr(self.oid)) if self.class_name: extras.append("class %s" % self.class_name) if self.serials: current, old = self.serials extras.append("serial this txn started with %s" % readable_tid_repr(old)) extras.append("serial currently committed %s" % readable_tid_repr(current)) if extras: return "%s (%s)" % (self.message, ", ".join(extras)) else: return self.message def get_oid(self): return self.oid def get_class_name(self): return self.class_name def get_old_serial(self): return self.serials[1] def get_new_serial(self): return self.serials[0] def get_serials(self): return self.serials class ReadConflictError(ConflictError): """Conflict detected when object was loaded. An attempt was made to read an object that has changed in another transaction (eg. another thread or process). """ def __init__(self, message=None, object=None, serials=None, **kw): if message is None: message = "database read conflict error" ConflictError.__init__(self, message=message, object=object, serials=serials, **kw) class BTreesConflictError(ConflictError): """A special subclass for BTrees conflict errors.""" msgs = [# 0; i2 or i3 bucket split; positions are all -1 'Conflicting bucket split', # 1; keys the same, but i2 and i3 values differ, and both values # differ from i1's value 'Conflicting changes', # 2; i1's value changed in i2, but key+value deleted in i3 'Conflicting delete and change', # 3; i1's value changed in i3, but key+value deleted in i2 'Conflicting delete and change', # 4; i1 and i2 both added the same key, or both deleted the # same key 'Conflicting inserts or deletes', # 5; i2 and i3 both deleted the same key 'Conflicting deletes', # 6; i2 and i3 both added the same key 'Conflicting inserts', # 7; i2 and i3 both deleted the same key, or i2 changed the value # associated with a key and i3 deleted that key 'Conflicting deletes, or delete and change', # 8; i2 and i3 both deleted the same key, or i3 changed the value # associated with a key and i2 deleted that key 'Conflicting deletes, or delete and change', # 9; i2 and i3 both deleted the same key 'Conflicting deletes', # 10; i2 and i3 deleted all the keys, and didn't insert any, # leaving an empty bucket; conflict resolution doesn't have # enough info to unlink an empty bucket from its containing # BTree correctly 'Empty bucket from deleting all keys', # 11; conflicting changes in an internal BTree node 'Conflicting changes in an internal BTree node', # 12; i2 or i3 was empty 'Empty bucket in a transaction', # 13; delete of first key, which causes change to parent node 'Delete of first key', ] def __init__(self, p1, p2, p3, reason): self.p1 = p1 self.p2 = p2 self.p3 = p3 self.reason = reason def __repr__(self): return "BTreesConflictError(%d, %d, %d, %d)" % (self.p1, self.p2, self.p3, self.reason) def __str__(self): return "BTrees conflict error at %d/%d/%d: %s" % ( self.p1, self.p2, self.p3, self.msgs[self.reason]) class DanglingReferenceError(POSError, transaction.interfaces.TransactionError): """An object has a persistent reference to a missing object. If an object is stored and it has a reference to another object that does not exist (for example, it was deleted by pack), this exception may be raised. Whether a storage supports this feature, it a quality of implementation issue. Instance attributes: referer: oid of the object being written missing: referenced oid that does not have a corresponding object """ def __init__(self, Aoid, Boid): self.referer = Aoid self.missing = Boid def __str__(self): return "from %s to %s" % (oid_repr(self.referer), oid_repr(self.missing)) ############################################################################ # Only used in storages; versions are no longer supported. class VersionError(POSError): """An error in handling versions occurred.""" class VersionCommitError(VersionError): """An invalid combination of versions was used in a version commit.""" class VersionLockError(VersionError, transaction.interfaces.TransactionError): """Modification to an object modified in an unsaved version. An attempt was made to modify an object that has been modified in an unsaved version. """ ############################################################################ class UndoError(POSError): """An attempt was made to undo a non-undoable transaction.""" def __init__(self, reason, oid=None): self._reason = reason self._oid = oid def __str__(self): return _fmt_undo(self._oid, self._reason) class MultipleUndoErrors(UndoError): """Several undo errors occurred during a single transaction.""" def __init__(self, errs): # provide a reason and oid for clients that only look at that UndoError.__init__(self, *errs[0]) self._errs = errs def __str__(self): return "\n".join([_fmt_undo(*pair) for pair in self._errs]) class StorageError(POSError): """Base class for storage based exceptions.""" class StorageTransactionError(StorageError): """An operation was invoked for an invalid transaction or state.""" class StorageSystemError(StorageError): """Panic! Internal storage error!""" class MountedStorageError(StorageError): """Unable to access mounted storage.""" class ReadOnlyError(StorageError): """Unable to modify objects in a read-only storage.""" class TransactionTooLargeError(StorageTransactionError): """The transaction exhausted some finite storage resource.""" class ExportError(POSError): """An export file doesn't have the right format.""" class Unsupported(POSError): """A feature was used that is not supported by the storage.""" class ReadOnlyHistoryError(POSError): """Unable to add or modify objects in an historical connection.""" class InvalidObjectReference(POSError): """An object contains an invalid reference to another object. An invalid reference may be one of: o A reference to a wrapped persistent object. o A reference to an object in a different database connection. TODO: The exception ought to have a member that is the invalid object. """ class ConnectionStateError(POSError): """A Connection isn't in the required state for an operation. o An operation such as a load is attempted on a closed connection. o An attempt to close a connection is made while the connection is still joined to a transaction (for example, a transaction is in progress, with uncommitted modifications in the connection). """ zope2.13-2.13.21/source/ZODB3/src/ZODB/broken.py0000644000175000017500000002330212214017464017440 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Broken object support $Id: broken.py 113734 2010-06-21 15:33:46Z ctheune $ """ import sys import persistent import zope.interface import ZODB.interfaces broken_cache = {} class Broken(object): """Broken object base class Broken objects are placeholders for objects that can no longer be created because their class has gone away. Broken objects don't really do much of anything, except hold their state. The Broken class is used as a base class for creating classes in leu of missing classes:: >>> Atall = type('Atall', (Broken, ), {'__module__': 'not.there'}) The only thing the class can be used for is to create new objects:: >>> Atall() >>> Atall().__Broken_newargs__ () >>> Atall().__Broken_initargs__ () >>> Atall(1, 2).__Broken_newargs__ (1, 2) >>> Atall(1, 2).__Broken_initargs__ (1, 2) >>> a = Atall.__new__(Atall, 1, 2) >>> a >>> a.__Broken_newargs__ (1, 2) >>> a.__Broken_initargs__ You can't modify broken objects:: >>> a.x = 1 Traceback (most recent call last): ... BrokenModified: Can't change broken objects But you can set their state:: >>> a.__setstate__({'x': 1, }) You can pickle broken objects:: >>> r = a.__reduce__() >>> len(r) 3 >>> r[0] is rebuild True >>> r[1] ('not.there', 'Atall', 1, 2) >>> r[2] {'x': 1} >>> import cPickle >>> a2 = cPickle.loads(cPickle.dumps(a, 1)) >>> a2 >>> a2.__Broken_newargs__ (1, 2) >>> a2.__Broken_initargs__ >>> a2.__Broken_state__ {'x': 1} Cleanup:: >>> broken_cache.clear() """ zope.interface.implements(ZODB.interfaces.IBroken) __Broken_state__ = __Broken_initargs__ = None __name__ = 'broken object' def __new__(class_, *args): result = object.__new__(class_) result.__dict__['__Broken_newargs__'] = args return result def __init__(self, *args): self.__dict__['__Broken_initargs__'] = args def __reduce__(self): """We pickle broken objects in hope of being able to fix them later """ return (rebuild, ((self.__class__.__module__, self.__class__.__name__) + self.__Broken_newargs__), self.__Broken_state__, ) def __setstate__(self, state): self.__dict__['__Broken_state__'] = state def __repr__(self): return "" % ( self.__class__.__module__, self.__class__.__name__) def __setattr__(self, name, value): raise BrokenModified("Can't change broken objects") def find_global(modulename, globalname, # These are *not* optimizations. Callers can override these. Broken=Broken, type=type, ): """Find a global object, returning a broken class if it can't be found. This function looks up global variable in modules:: >>> import sys >>> find_global('sys', 'path') is sys.path True If an object can't be found, a broken class is returned:: >>> broken = find_global('ZODB.not.there', 'atall') >>> issubclass(broken, Broken) True >>> broken.__module__ 'ZODB.not.there' >>> broken.__name__ 'atall' Broken classes are cached:: >>> find_global('ZODB.not.there', 'atall') is broken True If we "repair" a missing global:: >>> class ZODBnotthere: ... atall = [] >>> sys.modules['ZODB.not'] = ZODBnotthere >>> sys.modules['ZODB.not.there'] = ZODBnotthere we can then get the repaired value:: >>> find_global('ZODB.not.there', 'atall') is ZODBnotthere.atall True Of course, if we beak it again:: >>> del sys.modules['ZODB.not'] >>> del sys.modules['ZODB.not.there'] we get the broken value:: >>> find_global('ZODB.not.there', 'atall') is broken True Cleanup:: >>> broken_cache.clear() """ # short circuit common case: try: return getattr(sys.modules[modulename], globalname) except (AttributeError, KeyError): pass try: __import__(modulename) except ImportError: pass else: module = sys.modules[modulename] try: return getattr(module, globalname) except AttributeError: pass try: return broken_cache[(modulename, globalname)] except KeyError: pass class_ = type(globalname, (Broken, ), {'__module__': modulename}) broken_cache[(modulename, globalname)] = class_ return class_ def rebuild(modulename, globalname, *args): """Recreate a broken object, possibly recreating the missing class This functions unpickles broken objects:: >>> broken = rebuild('ZODB.notthere', 'atall', 1, 2) >>> broken >>> broken.__Broken_newargs__ (1, 2) If we "repair" the brokenness:: >>> class notthere: # fake notthere module ... class atall(object): ... def __new__(self, *args): ... ob = object.__new__(self) ... ob.args = args ... return ob ... def __repr__(self): ... return 'atall %s %s' % self.args >>> sys.modules['ZODB.notthere'] = notthere >>> rebuild('ZODB.notthere', 'atall', 1, 2) atall 1 2 >>> del sys.modules['ZODB.notthere'] Cleanup:: >>> broken_cache.clear() """ class_ = find_global(modulename, globalname) return class_.__new__(class_, *args) class BrokenModified(TypeError): """Attempt to modify a broken object """ class PersistentBroken(Broken, persistent.Persistent): r"""Persistent broken objects Persistent broken objects are used for broken objects that are also persistent. In addition to having to track the original object data, they need to handle persistent meta data. Persistent broken classes are created from existing broken classes using the persistentBroken, function:: >>> Atall = type('Atall', (Broken, ), {'__module__': 'not.there'}) >>> PAtall = persistentBroken(Atall) (Note that we always get the *same* persistent broken class for a given broken class:: >>> persistentBroken(Atall) is PAtall True ) Persistent broken classes work a lot like broken classes:: >>> a = PAtall.__new__(PAtall, 1, 2) >>> a >>> a.__Broken_newargs__ (1, 2) >>> a.__Broken_initargs__ >>> a.x = 1 Traceback (most recent call last): ... BrokenModified: Can't change broken objects Unlike regular broken objects, persistent broken objects keep track of persistence meta data: >>> a._p_oid = '\0\0\0\0****' >>> a and persistent broken objects aren't directly picklable: >>> a.__reduce__() # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... BrokenModified: but you can get their state: >>> a.__setstate__({'y': 2}) >>> a.__getstate__() {'y': 2} Cleanup:: >>> broken_cache.clear() """ def __new__(class_, *args): result = persistent.Persistent.__new__(class_) result.__dict__['__Broken_newargs__'] = args return result def __reduce__(self, *args): raise BrokenModified(self) def __getstate__(self): return self.__Broken_state__ def __setattr__(self, name, value): if name.startswith('_p_'): persistent.Persistent.__setattr__(self, name, value) else: raise BrokenModified("Can't change broken objects") def __repr__(self): return "" % ( self.__class__.__module__, self.__class__.__name__, self._p_oid) def __getnewargs__(self): return self.__Broken_newargs__ def persistentBroken(class_): try: return class_.__dict__['__Broken_Persistent__'] except KeyError: class_.__Broken_Persistent__ = ( type(class_.__name__, (PersistentBroken, class_), {'__module__': class_.__module__}, ) ) return class_.__dict__['__Broken_Persistent__'] zope2.13-2.13.21/source/ZODB3/src/ZODB/subtransactions.txt0000644000175000017500000000271612214017464021577 0ustar arnauarnau========================= Subtransactions in ZODB 3 ========================= ZODB 3 provides limited support for subtransactions. Subtransactions are nested to *one* level. There are top-level transactions and subtransactions. When a transaction is committed, a flag is passed indicating whether it is a subtransaction or a top-level transaction. Consider the following exampler commit calls: - ``commit()`` A regular top-level transaction is committed. - ``commit(1)`` A subtransaction is committed. There is now one subtransaction of the current top-level transaction. - ``commit(1)`` A subtransaction is committed. There are now two subtransactions of the current top-level transaction. - ``abort(1)`` A subtransaction is aborted. There are still two subtransactions of the current top-level transaction; work done since the last ``commit(1)`` call is discarded. - ``commit()`` We now commit a top-level transaction. The work done in the previous two subtransactions *plus* work done since the last ``abort(1)`` call is saved. - ``commit(1)`` A subtransaction is committed. There is now one subtransaction of the current top-level transaction. - ``commit(1)`` A subtransaction is committed. There are now two subtransactions of the current top-level transaction. - ``abort()`` We now abort a top-level transaction. We discard the work done in the previous two subtransactions *plus* work done since the last ``commit(1)`` call. zope2.13-2.13.21/source/ZODB3/src/ZODB/DemoStorage.test0000644000175000017500000002544412214017464020731 0ustar arnauarnau========================== DemoStorage demo (doctest) ========================== DemoStorages provide a way to provide incremental updates to an existing, base, storage without updating the storage. .. We need to mess with time to prevent spurious test failures on windows >>> now = 1231019584.0 >>> def faux_time_time(): ... global now ... now += .1 ... return now >>> import time >>> real_time_time = time.time >>> time.time = faux_time_time To see how this works, we'll start by creating a base storage and puting an object (in addition to the root object) in it: >>> from ZODB.FileStorage import FileStorage >>> base = FileStorage('base.fs') >>> from ZODB.DB import DB >>> db = DB(base) >>> from persistent.mapping import PersistentMapping >>> conn = db.open() >>> conn.root()['1'] = PersistentMapping({'a': 1, 'b':2}) >>> import transaction >>> transaction.commit() >>> db.close() >>> import os >>> original_size = os.path.getsize('base.fs') Now, lets reopen the base storage in read-only mode: >>> base = FileStorage('base.fs', read_only=True) And open a new storage to store changes: >>> changes = FileStorage('changes.fs') and combine the 2 in a demofilestorage: >>> from ZODB.DemoStorage import DemoStorage >>> storage = DemoStorage(base=base, changes=changes) If there are no transactions, the storage reports the lastTransaction of the base database: >>> storage.lastTransaction() == base.lastTransaction() True Let's add some data: >>> db = DB(storage) >>> conn = db.open() >>> items = conn.root()['1'].items() >>> items.sort() >>> items [('a', 1), ('b', 2)] >>> conn.root()['2'] = PersistentMapping({'a': 3, 'b':4}) >>> transaction.commit() >>> conn.root()['2']['c'] = 5 >>> transaction.commit() Here we can see that we haven't modified the base storage: >>> original_size == os.path.getsize('base.fs') True But we have modified the changes database: >>> len(changes) 2 Our lastTransaction reflects the lastTransaction of the changes: >>> storage.lastTransaction() > base.lastTransaction() True >>> storage.lastTransaction() == changes.lastTransaction() True Let's walk over some of the methods so we can see how we delegate to the new underlying storages: >>> from ZODB.utils import p64, u64 >>> storage.load(p64(0), '') == changes.load(p64(0), '') True >>> storage.load(p64(0), '') == base.load(p64(0), '') False >>> storage.load(p64(1), '') == base.load(p64(1), '') True >>> serial = base.getTid(p64(0)) >>> storage.loadSerial(p64(0), serial) == base.loadSerial(p64(0), serial) True >>> serial = changes.getTid(p64(0)) >>> storage.loadSerial(p64(0), serial) == changes.loadSerial(p64(0), ... serial) True The object id of the new object is quite random, and typically large: >>> print u64(conn.root()['2']._p_oid) 3553260803050964942 Let's look at some other methods: >>> storage.getName() "DemoStorage('base.fs', 'changes.fs')" >>> storage.sortKey() == changes.sortKey() True >>> storage.getSize() == changes.getSize() True >>> len(storage) == len(changes) True Undo methods are simply copied from the changes storage: >>> [getattr(storage, name) == getattr(changes, name) ... for name in ('supportsUndo', 'undo', 'undoLog', 'undoInfo') ... ] [True, True, True, True] >>> db.close() Closing demo storages ===================== Normally, when a demo storage is closed, it's base and changes storage are closed: >>> from ZODB.MappingStorage import MappingStorage >>> demo = DemoStorage(base=MappingStorage(), changes=MappingStorage()) >>> demo.close() >>> demo.base.opened(), demo.changes.opened() (False, False) You can pass constructor arguments to control whether the base and changes storages should be closed when the demo storage is closed: >>> demo = DemoStorage( ... base=MappingStorage(), changes=MappingStorage(), ... close_base_on_close=False, close_changes_on_close=False, ... ) >>> demo.close() >>> demo.base.opened(), demo.changes.opened() (True, True) Storage Stacking ================ A common use case is to stack demo storages. DemoStorage provides some helper functions to help with this. The push method, just creates a new demo storage who's base is the original demo storage: >>> demo = DemoStorage() >>> demo2 = demo.push() >>> demo2.base is demo True We can also supply an explicit changes storage, if we wish: >>> changes = MappingStorage() >>> demo3 = demo2.push(changes) >>> demo3.changes is changes, demo3.base is demo2 (True, True) The pop method closes the changes storage and returns the base *without* closing it: >>> demo3.pop() is demo2 True >>> changes.opened() False If storage returned by push is closed, the original storage isn't: >>> demo3.push().close() >>> demo2.opened() True Blob Support ============ DemoStorage supports Blobs if the changes database supports blobs. >>> import ZODB.blob >>> base = ZODB.blob.BlobStorage('base', FileStorage('base.fs')) >>> db = DB(base) >>> conn = db.open() >>> conn.root()['blob'] = ZODB.blob.Blob() >>> conn.root()['blob'].open('w').write('state 1') >>> transaction.commit() >>> db.close() >>> base = ZODB.blob.BlobStorage('base', ... FileStorage('base.fs', read_only=True)) >>> changes = ZODB.blob.BlobStorage('changes', ... FileStorage('changes.fs', create=True)) >>> storage = DemoStorage(base=base, changes=changes) >>> db = DB(storage) >>> conn = db.open() >>> conn.root()['blob'].open().read() 'state 1' >>> _ = transaction.begin() >>> conn.root()['blob'].open('w').write('state 2') >>> transaction.commit() >>> conn.root()['blob'].open().read() 'state 2' >>> storage.temporaryDirectory() == changes.temporaryDirectory() True >>> db.close() It isn't necessary for the base database to support blobs. >>> base = FileStorage('base.fs', read_only=True) >>> changes = ZODB.blob.BlobStorage('changes', FileStorage('changes.fs')) >>> storage = DemoStorage(base=base, changes=changes) >>> db = DB(storage) >>> conn = db.open() >>> conn.root()['blob'].open().read() 'state 2' >>> _ = transaction.begin() >>> conn.root()['blob2'] = ZODB.blob.Blob() >>> conn.root()['blob2'].open('w').write('state 1') >>> conn.root()['blob2'].open().read() 'state 1' >>> db.close() If the changes database is created implicitly, it will get a blob storage wrapped around it when necessary: >>> base = ZODB.blob.BlobStorage('base', ... FileStorage('base.fs', read_only=True)) >>> storage = DemoStorage(base=base) >>> type(storage.changes).__name__ 'MappingStorage' >>> db = DB(storage) >>> conn = db.open() >>> conn.root()['blob'].open().read() 'state 1' >>> type(storage.changes).__name__ 'BlobStorage' >>> _ = transaction.begin() >>> conn.root()['blob'].open('w').write('state 2') >>> transaction.commit() >>> conn.root()['blob'].open().read() 'state 2' >>> storage.temporaryDirectory() == storage.changes.temporaryDirectory() True >>> db.close() .. Check that the temporary directory is gone For now, it won't go until the storage does. >>> transaction.abort() >>> blobdir = storage.temporaryDirectory() >>> del storage, _ >>> import gc >>> _ = gc.collect() >>> import os >>> os.path.exists(blobdir) False ZConfig support =============== You can configure demo storages using ZConfig, using name, changes, and base options: >>> import ZODB.config >>> storage = ZODB.config.storageFromString(""" ... ... ... """) >>> storage.getName() "DemoStorage('MappingStorage', 'MappingStorage')" >>> storage = ZODB.config.storageFromString(""" ... ... ... path base.fs ... ... ... ... path changes.fs ... ... ... """) >>> storage.getName() "DemoStorage('base.fs', 'changes.fs')" >>> storage.close() >>> storage = ZODB.config.storageFromString(""" ... ... name bob ... ... path base.fs ... ... ... ... path changes.fs ... ... ... """) >>> storage.getName() 'bob' >>> storage.base.getName() 'base.fs' >>> storage.close() Generating OIDs =============== When asked for a new OID DemoStorage chooses a value and then verifies that neither the base or changes storages already contain that OID. It chooses values sequentially from random starting points, picking new starting points whenever a chosen value us already in the changes or base. Under rare circumstances an OID can be chosen that has already been handed out, but which hasn't yet been comitted. Lets verify that if the same OID is chosen twice during a transaction that everything will still work. To test this, we need to hack random.randint a bit. >>> import random >>> randint = random.randint >>> rv = 42 >>> def faux_randint(min, max): ... print 'called randint' ... global rv ... rv += 1000 ... return rv >>> random.randint = faux_randint Now, we create a demostorage. >>> storage = DemoStorage() called randint If we ask for an oid, we'll get 1042. >>> u64(storage.new_oid()) 1042 oids are allocated seuentially: >>> u64(storage.new_oid()) 1043 Now, we'll save 1044 in changes so that it has to pick a new one randomly. >>> t = transaction.get() >>> ZODB.tests.util.store(storage.changes, 1044) >>> u64(storage.new_oid()) called randint 2042 Now, we hack rv to 1042 is given out again and we'll save 2043 in base to force another attempt: >>> rv -= 1000 >>> ZODB.tests.util.store(storage.changes, 2043) >>> oid = storage.new_oid() called randint called randint >>> u64(oid) 3042 DemoStorage keeps up with the issued OIDs to know when not to reissue them... >>> oid in storage._issued_oids True ...but once data is stored with a given OID... >>> ZODB.tests.util.store(storage, oid) ...there's no need to remember it any longer: >>> oid in storage._issued_oids False >>> storage.close() .. restore randint >>> random.randint = randint .. restore time >>> time.time = real_time_time zope2.13-2.13.21/source/ZODB3/src/ZODB/FileStorage/0000755000175000017500000000000012214017464020012 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZODB/FileStorage/FileStorage.py0000644000175000017500000021303712214017464022576 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Storage implementation using a log written to a single file. """ from __future__ import with_statement from cPickle import Pickler, loads from persistent.TimeStamp import TimeStamp from struct import pack, unpack from zc.lockfile import LockFile from ZODB.FileStorage.format import CorruptedError, CorruptedDataError from ZODB.FileStorage.format import FileStorageFormatter, DataHeader from ZODB.FileStorage.format import TRANS_HDR, TRANS_HDR_LEN from ZODB.FileStorage.format import TxnHeader, DATA_HDR, DATA_HDR_LEN from ZODB.FileStorage.fspack import FileStoragePacker from ZODB.fsIndex import fsIndex from ZODB import BaseStorage, ConflictResolution, POSException from ZODB.POSException import UndoError, POSKeyError, MultipleUndoErrors from ZODB.utils import p64, u64, z64 import base64 import contextlib import errno import logging import os import threading import time import ZODB.blob import ZODB.interfaces import zope.interface import ZODB.utils # Not all platforms have fsync fsync = getattr(os, "fsync", None) packed_version = "FS21" logger = logging.getLogger('ZODB.FileStorage') def panic(message, *data): logger.critical(message, *data) raise CorruptedTransactionError(message % data) class FileStorageError(POSException.StorageError): pass class PackError(FileStorageError): pass class FileStorageFormatError(FileStorageError): """Invalid file format The format of the given file is not valid. """ class CorruptedFileStorageError(FileStorageError, POSException.StorageSystemError): """Corrupted file storage.""" class CorruptedTransactionError(CorruptedFileStorageError): pass class FileStorageQuotaError(FileStorageError, POSException.StorageSystemError): """File storage quota exceeded.""" # Intended to be raised only in fspack.py, and ignored here. class RedundantPackWarning(FileStorageError): pass class TempFormatter(FileStorageFormatter): """Helper class used to read formatted FileStorage data.""" def __init__(self, afile): self._file = afile class FileStorage( FileStorageFormatter, ZODB.blob.BlobStorageMixin, ConflictResolution.ConflictResolvingStorage, BaseStorage.BaseStorage, ): zope.interface.implements( ZODB.interfaces.IStorage, ZODB.interfaces.IStorageRestoreable, ZODB.interfaces.IStorageIteration, ZODB.interfaces.IStorageUndoable, ZODB.interfaces.IStorageCurrentRecordIteration, ZODB.interfaces.IExternalGC, ) # Set True while a pack is in progress; undo is blocked for the duration. _pack_is_in_progress = False def __init__(self, file_name, create=False, read_only=False, stop=None, quota=None, pack_gc=True, pack_keep_old=True, packer=None, blob_dir=None): if read_only: self._is_read_only = True if create: raise ValueError("can't create a read-only file") elif stop is not None: raise ValueError("time-travel only supported in read-only mode") if stop is None: stop='\377'*8 # Lock the database and set up the temp file. if not read_only: # Create the lock file self._lock_file = LockFile(file_name + '.lock') self._tfile = open(file_name + '.tmp', 'w+b') self._tfmt = TempFormatter(self._tfile) else: self._tfile = None self._file_name = os.path.abspath(file_name) self._pack_gc = pack_gc self.pack_keep_old = pack_keep_old if packer is not None: self.packer = packer BaseStorage.BaseStorage.__init__(self, file_name) index, tindex = self._newIndexes() self._initIndex(index, tindex) # Now open the file self._file = None if not create: try: self._file = open(file_name, read_only and 'rb' or 'r+b') except IOError, exc: if exc.errno == errno.EFBIG: # The file is too big to open. Fail visibly. raise if exc.errno == errno.ENOENT: # The file doesn't exist. Create it. create = 1 # If something else went wrong, it's hard to guess # what the problem was. If the file does not exist, # create it. Otherwise, fail. if os.path.exists(file_name): raise else: create = 1 if self._file is None and create: if os.path.exists(file_name): os.remove(file_name) self._file = open(file_name, 'w+b') self._file.write(packed_version) self._files = FilePool(self._file_name) r = self._restore_index() if r is not None: self._used_index = 1 # Marker for testing index, start, ltid = r self._initIndex(index, tindex) self._pos, self._oid, tid = read_index( self._file, file_name, index, tindex, stop, ltid=ltid, start=start, read_only=read_only, ) else: self._used_index = 0 # Marker for testing self._pos, self._oid, tid = read_index( self._file, file_name, index, tindex, stop, read_only=read_only, ) self._save_index() self._ltid = tid # self._pos should always point just past the last # transaction. During 2PC, data is written after _pos. # invariant is restored at tpc_abort() or tpc_finish(). self._ts = tid = TimeStamp(tid) t = time.time() t = TimeStamp(*time.gmtime(t)[:5] + (t % 60,)) if tid > t: seconds = tid.timeTime() - t.timeTime() complainer = logger.warning if seconds > 30 * 60: # 30 minutes -- way screwed up complainer = logger.critical complainer("%s Database records %d seconds in the future", file_name, seconds) self._quota = quota if blob_dir: self.blob_dir = os.path.abspath(blob_dir) if create and os.path.exists(self.blob_dir): ZODB.blob.remove_committed_dir(self.blob_dir) self._blob_init(blob_dir) zope.interface.alsoProvides(self, ZODB.interfaces.IBlobStorageRestoreable) else: self.blob_dir = None self._blob_init_no_blobs() def copyTransactionsFrom(self, other): if self.blob_dir: return ZODB.blob.BlobStorageMixin.copyTransactionsFrom(self, other) else: return BaseStorage.BaseStorage.copyTransactionsFrom(self, other) def _initIndex(self, index, tindex): self._index=index self._tindex=tindex self._index_get=index.get def __len__(self): return len(self._index) def _newIndexes(self): # hook to use something other than builtin dict return fsIndex(), {} _saved = 0 def _save_index(self): """Write the database index to a file to support quick startup.""" if self._is_read_only: return index_name = self.__name__ + '.index' tmp_name = index_name + '.index_tmp' self._index.save(self._pos, tmp_name) try: try: os.remove(index_name) except OSError: pass os.rename(tmp_name, index_name) except: pass self._saved += 1 def _clear_index(self): index_name = self.__name__ + '.index' if os.path.exists(index_name): try: os.remove(index_name) except OSError: pass def _sane(self, index, pos): """Sanity check saved index data by reading the last undone trans Basically, we read the last not undone transaction and check to see that the included records are consistent with the index. Any invalid record records or inconsistent object positions cause zero to be returned. """ r = self._check_sanity(index, pos) if not r: logger.warning("Ignoring index for %s", self._file_name) return r def _check_sanity(self, index, pos): if pos < 100: return 0 # insane self._file.seek(0, 2) if self._file.tell() < pos: return 0 # insane ltid = None max_checked = 5 checked = 0 while checked < max_checked: self._file.seek(pos - 8) rstl = self._file.read(8) tl = u64(rstl) pos = pos - tl - 8 if pos < 4: return 0 # insane h = self._read_txn_header(pos) if not ltid: ltid = h.tid if h.tlen != tl: return 0 # inconsistent lengths if h.status == 'u': continue # undone trans, search back if h.status not in ' p': return 0 # insane if tl < h.headerlen(): return 0 # insane tend = pos + tl opos = pos + h.headerlen() if opos == tend: continue # empty trans while opos < tend and checked < max_checked: # Read the data records for this transaction h = self._read_data_header(opos) if opos + h.recordlen() > tend or h.tloc != pos: return 0 if index.get(h.oid, 0) != opos: return 0 # insane checked += 1 opos = opos + h.recordlen() return ltid def _restore_index(self): """Load database index to support quick startup.""" # Returns (index, pos, tid), or None in case of error. # The index returned is always an instance of fsIndex. If the # index cached in the file is a Python dict, it's converted to # fsIndex here, and, if we're not in read-only mode, the .index # file is rewritten with the converted fsIndex so we don't need to # convert it again the next time. file_name=self.__name__ index_name=file_name+'.index' if os.path.exists(index_name): try: info = fsIndex.load(index_name) except: logger.exception('loading index') return None else: return None index = info.get('index') pos = info.get('pos') if index is None or pos is None: return None pos = long(pos) if (isinstance(index, dict) or (isinstance(index, fsIndex) and isinstance(index._data, dict))): # Convert dictionary indexes to fsIndexes *or* convert fsIndexes # which have a dict `_data` attribute to a new fsIndex (newer # fsIndexes have an OOBTree as `_data`). newindex = fsIndex() newindex.update(index) index = newindex if not self._is_read_only: # Save the converted index. f = open(index_name, 'wb') p = Pickler(f, 1) info['index'] = index p.dump(info) f.close() # Now call this method again to get the new data. return self._restore_index() tid = self._sane(index, pos) if not tid: return None return index, pos, tid def close(self): self._file.close() self._files.close() if hasattr(self,'_lock_file'): self._lock_file.close() if self._tfile: self._tfile.close() try: self._save_index() except: # Log the error and continue logger.error("Error saving index on close()", exc_info=True) def getSize(self): return self._pos def _lookup_pos(self, oid): try: return self._index[oid] except KeyError: raise POSKeyError(oid) except TypeError: raise TypeError("invalid oid %r" % (oid,)) def load(self, oid, version=''): """Return pickle data and serial number.""" assert not version with self._files.get() as _file: pos = self._lookup_pos(oid) h = self._read_data_header(pos, oid, _file) if h.plen: data = _file.read(h.plen) return data, h.tid elif h.back: # Get the data from the backpointer, but tid from # current txn. data = self._loadBack_impl(oid, h.back, _file=_file)[0] return data, h.tid else: raise POSKeyError(oid) def loadSerial(self, oid, serial): with self._lock: pos = self._lookup_pos(oid) while 1: h = self._read_data_header(pos, oid) if h.tid == serial: break pos = h.prev if not pos: raise POSKeyError(oid) if h.plen: return self._file.read(h.plen) else: return self._loadBack_impl(oid, h.back)[0] def loadBefore(self, oid, tid): with self._files.get() as _file: pos = self._lookup_pos(oid) end_tid = None while True: h = self._read_data_header(pos, oid, _file) if h.tid < tid: break pos = h.prev end_tid = h.tid if not pos: return None if h.back: data, _, _, _ = self._loadBack_impl(oid, h.back, _file=_file) return data, h.tid, end_tid else: return _file.read(h.plen), h.tid, end_tid def store(self, oid, oldserial, data, version, transaction): if self._is_read_only: raise POSException.ReadOnlyError() if transaction is not self._transaction: raise POSException.StorageTransactionError(self, transaction) assert not version with self._lock: if oid > self._oid: self.set_max_oid(oid) old = self._index_get(oid, 0) committed_tid = None pnv = None if old: h = self._read_data_header(old, oid) committed_tid = h.tid if oldserial != committed_tid: data = self.tryToResolveConflict(oid, committed_tid, oldserial, data) pos = self._pos here = pos + self._tfile.tell() + self._thl self._tindex[oid] = here new = DataHeader(oid, self._tid, old, pos, 0, len(data)) self._tfile.write(new.asString()) self._tfile.write(data) # Check quota if self._quota is not None and here > self._quota: raise FileStorageQuotaError( "The storage quota has been exceeded.") if old and oldserial != committed_tid: return ConflictResolution.ResolvedSerial else: return self._tid def deleteObject(self, oid, oldserial, transaction): if self._is_read_only: raise POSException.ReadOnlyError() if transaction is not self._transaction: raise POSException.StorageTransactionError(self, transaction) with self._lock: old = self._index_get(oid, 0) if not old: raise POSException.POSKeyError(oid) h = self._read_data_header(old, oid) committed_tid = h.tid if oldserial != committed_tid: raise POSException.ConflictError( oid=oid, serials=(committed_tid, oldserial)) pos = self._pos here = pos + self._tfile.tell() + self._thl self._tindex[oid] = here new = DataHeader(oid, self._tid, old, pos, 0, 0) self._tfile.write(new.asString()) self._tfile.write(z64) # Check quota if self._quota is not None and here > self._quota: raise FileStorageQuotaError( "The storage quota has been exceeded.") def _data_find(self, tpos, oid, data): # Return backpointer for oid. Must call with the lock held. # This is a file offset to oid's data record if found, else 0. # The data records in the transaction at tpos are searched for oid. # If a data record for oid isn't found, returns 0. # Else if oid's data record contains a backpointer, that # backpointer is returned. # Else oid's data record contains the data, and the file offset of # oid's data record is returned. This data record should contain # a pickle identical to the 'data' argument. # Unclear: If the length of the stored data doesn't match len(data), # an exception is raised. If the lengths match but the data isn't # the same, 0 is returned. Why the discrepancy? self._file.seek(tpos) h = self._file.read(TRANS_HDR_LEN) tid, tl, status, ul, dl, el = unpack(TRANS_HDR, h) self._file.read(ul + dl + el) tend = tpos + tl + 8 pos = self._file.tell() while pos < tend: h = self._read_data_header(pos) if h.oid == oid: # Make sure this looks like the right data record if h.plen == 0: # This is also a backpointer. Gotta trust it. return pos if h.plen != len(data): # The expected data doesn't match what's in the # backpointer. Something is wrong. logger.error("Mismatch between data and" " backpointer at %d", pos) return 0 _data = self._file.read(h.plen) if data != _data: return 0 return pos pos += h.recordlen() self._file.seek(pos) return 0 def restore(self, oid, serial, data, version, prev_txn, transaction): # A lot like store() but without all the consistency checks. This # should only be used when we /know/ the data is good, hence the # method name. While the signature looks like store() there are some # differences: # # - serial is the serial number of /this/ revision, not of the # previous revision. It is used instead of self._tid, which is # ignored. # # - Nothing is returned # # - data can be None, which indicates a George Bailey object # (i.e. one who's creation has been transactionally undone). # # prev_txn is a backpointer. In the original database, it's possible # that the data was actually living in a previous transaction. This # can happen for transactional undo and other operations, and is used # as a space saving optimization. Under some circumstances the # prev_txn may not actually exist in the target database (i.e. self) # for example, if it's been packed away. In that case, the prev_txn # should be considered just a hint, and is ignored if the transaction # doesn't exist. if self._is_read_only: raise POSException.ReadOnlyError() if transaction is not self._transaction: raise POSException.StorageTransactionError(self, transaction) if version: raise TypeError("Versions are no-longer supported") with self._lock: if oid > self._oid: self.set_max_oid(oid) prev_pos = 0 if prev_txn is not None: prev_txn_pos = self._txn_find(prev_txn, 0) if prev_txn_pos: prev_pos = self._data_find(prev_txn_pos, oid, data) old = self._index_get(oid, 0) # Calculate the file position in the temporary file here = self._pos + self._tfile.tell() + self._thl # And update the temp file index self._tindex[oid] = here if prev_pos: # If there is a valid prev_pos, don't write data. data = None if data is None: dlen = 0 else: dlen = len(data) # Write the recovery data record new = DataHeader(oid, serial, old, self._pos, 0, dlen) self._tfile.write(new.asString()) # Finally, write the data or a backpointer. if data is None: if prev_pos: self._tfile.write(p64(prev_pos)) else: # Write a zero backpointer, which indicates an # un-creation transaction. self._tfile.write(z64) else: self._tfile.write(data) def supportsUndo(self): return 1 def _clear_temp(self): self._tindex.clear() if self._tfile is not None: self._tfile.seek(0) def _begin(self, tid, u, d, e): self._nextpos = 0 self._thl = TRANS_HDR_LEN + len(u) + len(d) + len(e) if self._thl > 65535: # one of u, d, or e may be > 65535 # We have to check lengths here because struct.pack # doesn't raise an exception on overflow! if len(u) > 65535: raise FileStorageError('user name too long') if len(d) > 65535: raise FileStorageError('description too long') if len(e) > 65535: raise FileStorageError('too much extension data') def tpc_vote(self, transaction): with self._lock: if transaction is not self._transaction: raise POSException.StorageTransactionError( "tpc_vote called with wrong transaction") dlen = self._tfile.tell() if not dlen: return # No data in this trans self._tfile.seek(0) user, descr, ext = self._ude self._file.seek(self._pos) tl = self._thl + dlen try: h = TxnHeader(self._tid, tl, "c", len(user), len(descr), len(ext)) h.user = user h.descr = descr h.ext = ext self._file.write(h.asString()) ZODB.utils.cp(self._tfile, self._file, dlen) self._file.write(p64(tl)) self._file.flush() except: # Hm, an error occurred writing out the data. Maybe the # disk is full. We don't want any turd at the end. self._file.truncate(self._pos) raise self._nextpos = self._pos + (tl + 8) def tpc_finish(self, transaction, f=None): with self._files.write_lock(): with self._lock: if transaction is not self._transaction: raise POSException.StorageTransactionError( "tpc_finish called with wrong transaction") try: if f is not None: f(self._tid) u, d, e = self._ude self._finish(self._tid, u, d, e) self._clear_temp() finally: self._ude = None self._transaction = None self._commit_lock_release() def _finish(self, tid, u, d, e): # If self._nextpos is 0, then the transaction didn't write any # data, so we don't bother writing anything to the file. if self._nextpos: # Clear the checkpoint flag self._file.seek(self._pos+16) self._file.write(self._tstatus) try: # At this point, we may have committed the data to disk. # If we fail from here, we're in bad shape. self._finish_finish(tid) except: # Ouch. This is bad. Let's try to get back to where we were # and then roll over and die logger.critical("Failure in _finish. Closing.", exc_info=True) self.close() raise def _finish_finish(self, tid): # This is a separate method to allow tests to replace it with # something broken. :) self._file.flush() if fsync is not None: fsync(self._file.fileno()) self._pos = self._nextpos self._index.update(self._tindex) self._ltid = tid self._blob_tpc_finish() def _abort(self): if self._nextpos: self._file.truncate(self._pos) self._nextpos=0 self._blob_tpc_abort() def _undoDataInfo(self, oid, pos, tpos): """Return the tid, data pointer, and data for the oid record at pos """ if tpos: pos = tpos - self._pos - self._thl tpos = self._tfile.tell() h = self._tfmt._read_data_header(pos, oid) afile = self._tfile else: h = self._read_data_header(pos, oid) afile = self._file if h.oid != oid: raise UndoError("Invalid undo transaction id", oid) if h.plen: data = afile.read(h.plen) else: data = '' pos = h.back if tpos: self._tfile.seek(tpos) # Restore temp file to end return h.tid, pos, data def getTid(self, oid): with self._lock: pos = self._lookup_pos(oid) h = self._read_data_header(pos, oid) if h.plen == 0 and h.back == 0: # Undone creation raise POSKeyError(oid) return h.tid def _transactionalUndoRecord(self, oid, pos, tid, pre): """Get the undo information for a data record 'pos' points to the data header for 'oid' in the transaction being undone. 'tid' refers to the transaction being undone. 'pre' is the 'prev' field of the same data header. Return a 3-tuple consisting of a pickle, data pointer, and current position. If the pickle is true, then the data pointer must be 0, but the pickle can be empty *and* the pointer 0. """ copy = 1 # Can we just copy a data pointer # First check if it is possible to undo this record. tpos = self._tindex.get(oid, 0) ipos = self._index.get(oid, 0) tipos = tpos or ipos if tipos != pos: # Eek, a later transaction modified the data, but, # maybe it is pointing at the same data we are. ctid, cdataptr, cdata = self._undoDataInfo(oid, ipos, tpos) if cdataptr != pos: # We aren't sure if we are talking about the same data try: if ( # The current record wrote a new pickle cdataptr == tipos or # Backpointers are different self._loadBackPOS(oid, pos) != self._loadBackPOS(oid, cdataptr) ): if pre and not tpos: copy = 0 # we'll try to do conflict resolution else: # We bail if: # - We don't have a previous record, which should # be impossible. raise UndoError("no previous record", oid) except KeyError: # LoadBack gave us a key error. Bail. raise UndoError("_loadBack() failed", oid) # Return the data that should be written in the undo record. if not pre: # There is no previous revision, because the object creation # is being undone. return "", 0, ipos if copy: # we can just copy our previous-record pointer forward return "", pre, ipos try: bdata = self._loadBack_impl(oid, pre)[0] except KeyError: # couldn't find oid; what's the real explanation for this? raise UndoError("_loadBack() failed for %s", oid) try: data = self.tryToResolveConflict(oid, ctid, tid, bdata, cdata) return data, 0, ipos except POSException.ConflictError: pass raise UndoError("Some data were modified by a later transaction", oid) # undoLog() returns a description dict that includes an id entry. # The id is opaque to the client, but contains the transaction id. # The transactionalUndo() implementation does a simple linear # search through the file (from the end) to find the transaction. def undoLog(self, first=0, last=-20, filter=None): if last < 0: # -last is supposed to be the max # of transactions. Convert to # a positive index. Should have x - first = -last, which # means x = first - last. This is spelled out here because # the normalization code was incorrect for years (used +1 # instead -- off by 1), until ZODB 3.4. last = first - last with self._lock: if self._pack_is_in_progress: raise UndoError( 'Undo is currently disabled for database maintenance.

') us = UndoSearch(self._file, self._pos, first, last, filter) while not us.finished(): # Hold lock for batches of 20 searches, so default search # parameters will finish without letting another thread run. for i in range(20): if us.finished(): break us.search() # Give another thread a chance, so that a long undoLog() # operation doesn't block all other activity. self._lock_release() self._lock_acquire() return us.results def undo(self, transaction_id, transaction): """Undo a transaction, given by transaction_id. Do so by writing new data that reverses the action taken by the transaction. Usually, we can get by with just copying a data pointer, by writing a file position rather than a pickle. Sometimes, we may do conflict resolution, in which case we actually copy new data that results from resolution. """ if self._is_read_only: raise POSException.ReadOnlyError() if transaction is not self._transaction: raise POSException.StorageTransactionError(self, transaction) with self._lock: # Find the right transaction to undo and call _txn_undo_write(). tid = base64.decodestring(transaction_id + '\n') assert len(tid) == 8 tpos = self._txn_find(tid, 1) tindex = self._txn_undo_write(tpos) self._tindex.update(tindex) return self._tid, tindex.keys() def _txn_find(self, tid, stop_at_pack): pos = self._pos while pos > 39: self._file.seek(pos - 8) pos = pos - u64(self._file.read(8)) - 8 self._file.seek(pos) h = self._file.read(TRANS_HDR_LEN) _tid = h[:8] if _tid == tid: return pos if stop_at_pack: # check the status field of the transaction header if h[16] == 'p': break raise UndoError("Invalid transaction id") def _txn_undo_write(self, tpos): # a helper function to write the data records for transactional undo otloc = self._pos here = self._pos + self._tfile.tell() + self._thl base = here - self._tfile.tell() # Let's move the file pointer back to the start of the txn record. th = self._read_txn_header(tpos) if th.status != " ": raise UndoError('non-undoable transaction') tend = tpos + th.tlen pos = tpos + th.headerlen() tindex = {} # keep track of failures, cause we may succeed later failures = {} # Read the data records for this transaction while pos < tend: h = self._read_data_header(pos) if h.oid in failures: del failures[h.oid] # second chance! assert base + self._tfile.tell() == here, (here, base, self._tfile.tell()) try: p, prev, ipos = self._transactionalUndoRecord( h.oid, pos, h.tid, h.prev) except UndoError, v: # Don't fail right away. We may be redeemed later! failures[h.oid] = v else: if self.blob_dir and not p and prev: try: up, userial = self._loadBackTxn(h.oid, prev) except ZODB.POSException.POSKeyError: pass # It was removed, so no need to copy data else: if self.is_blob_record(up): # We're undoing a blob modification operation. # We have to copy the blob data tmp = ZODB.utils.mktemp(dir=self.fshelper.temp_dir) ZODB.utils.cp( self.openCommittedBlobFile(h.oid, userial), open(tmp, 'wb')) self._blob_storeblob(h.oid, self._tid, tmp) new = DataHeader(h.oid, self._tid, ipos, otloc, 0, len(p)) # TODO: This seek shouldn't be necessary, but some other # bit of code is messing with the file pointer. assert self._tfile.tell() == here - base, (here, base, self._tfile.tell()) self._tfile.write(new.asString()) if p: self._tfile.write(p) else: self._tfile.write(p64(prev)) tindex[h.oid] = here here += new.recordlen() pos += h.recordlen() if pos > tend: raise UndoError("non-undoable transaction") if failures: raise MultipleUndoErrors(failures.items()) return tindex def history(self, oid, size=1, filter=None): with self._lock: r = [] pos = self._lookup_pos(oid) while 1: if len(r) >= size: return r h = self._read_data_header(pos) th = self._read_txn_header(h.tloc) if th.ext: d = loads(th.ext) else: d = {} d.update({"time": TimeStamp(h.tid).timeTime(), "user_name": th.user, "description": th.descr, "tid": h.tid, "size": h.plen, }) if filter is None or filter(d): r.append(d) if h.prev: pos = h.prev else: return r def _redundant_pack(self, file, pos): assert pos > 8, pos file.seek(pos - 8) p = u64(file.read(8)) file.seek(pos - p + 8) return file.read(1) not in ' u' @staticmethod def packer(storage, referencesf, stop, gc): # Our default packer is built around the original packer. We # simply adapt the old interface to the new. We don't really # want to invest much in the old packer, at least for now. assert referencesf is not None p = FileStoragePacker(storage, referencesf, stop, gc) opos = p.pack() if opos is None: return None return opos, p.index def pack(self, t, referencesf, gc=None): """Copy data from the current database file to a packed file Non-current records from transactions with time-stamp strings less than packtss are ommitted. As are all undone records. Also, data back pointers that point before packtss are resolved and the associated data are copied, since the old records are not copied. """ if self._is_read_only: raise POSException.ReadOnlyError() stop=`TimeStamp(*time.gmtime(t)[:5]+(t%60,))` if stop==z64: raise FileStorageError('Invalid pack time') # If the storage is empty, there's nothing to do. if not self._index: return with self._lock: if self._pack_is_in_progress: raise FileStorageError('Already packing') self._pack_is_in_progress = True if gc is None: gc = self._pack_gc oldpath = self._file_name + ".old" if os.path.exists(oldpath): os.remove(oldpath) if self.blob_dir and os.path.exists(self.blob_dir + ".old"): ZODB.blob.remove_committed_dir(self.blob_dir + ".old") cleanup = [] have_commit_lock = False try: pack_result = None try: pack_result = self.packer(self, referencesf, stop, gc) except RedundantPackWarning, detail: logger.info(str(detail)) if pack_result is None: return have_commit_lock = True opos, index = pack_result with self._files.write_lock(): with self._lock: self._files.empty() self._file.close() try: os.rename(self._file_name, oldpath) except Exception: self._file = open(self._file_name, 'r+b') raise # OK, we're beyond the point of no return os.rename(self._file_name + '.pack', self._file_name) self._file = open(self._file_name, 'r+b') self._initIndex(index, self._tindex) self._pos = opos # We're basically done. Now we need to deal with removed # blobs and removing the .old file (see further down). if self.blob_dir: self._commit_lock_release() have_commit_lock = False self._remove_blob_files_tagged_for_removal_during_pack() finally: if have_commit_lock: self._commit_lock_release() with self._lock: self._pack_is_in_progress = False if not self.pack_keep_old: os.remove(oldpath) with self._lock: self._save_index() def _remove_blob_files_tagged_for_removal_during_pack(self): lblob_dir = len(self.blob_dir) fshelper = self.fshelper old = self.blob_dir+'.old' link_or_copy = ZODB.blob.link_or_copy # Helper to clean up dirs left empty after moving things to old def maybe_remove_empty_dir_containing(path, level=0): path = os.path.dirname(path) if len(path) <= lblob_dir or os.listdir(path): return # Path points to an empty dir. There may be a race. We # might have just removed the dir for an oid (or a parent # dir) and while we're cleaning up it's parent, another # thread is adding a new entry to it. # We don't have to worry about level 0, as this is just a # directory containing an object's revisions. If it is # enmpty, the object must have been garbage. # If the level is 1 or higher, we need to be more # careful. We'll get the storage lock and double check # that the dir is still empty before removing it. removed = False if level: self._lock_acquire() try: if not os.listdir(path): os.rmdir(path) removed = True finally: if level: self._lock_release() if removed: maybe_remove_empty_dir_containing(path, level+1) if self.pack_keep_old: # Helpers that move oid dir or revision file to the old dir. os.mkdir(old, 0777) link_or_copy(os.path.join(self.blob_dir, '.layout'), os.path.join(old, '.layout')) def handle_file(path): newpath = old+path[lblob_dir:] dest = os.path.dirname(newpath) if not os.path.exists(dest): os.makedirs(dest, 0700) os.rename(path, newpath) handle_dir = handle_file else: # Helpers that remove an oid dir or revision file. handle_file = ZODB.blob.remove_committed handle_dir = ZODB.blob.remove_committed_dir # Fist step: move or remove oids or revisions for line in open(os.path.join(self.blob_dir, '.removed')): line = line.strip().decode('hex') if len(line) == 8: # oid is garbage, re/move dir path = fshelper.getPathForOID(line) if not os.path.exists(path): # Hm, already gone. Odd. continue handle_dir(path) maybe_remove_empty_dir_containing(path, 1) continue if len(line) != 16: raise ValueError("Bad record in ", self.blob_dir, '.removed') oid, tid = line[:8], line[8:] path = fshelper.getBlobFilename(oid, tid) if not os.path.exists(path): # Hm, already gone. Odd. continue handle_file(path) assert not os.path.exists(path) maybe_remove_empty_dir_containing(path) os.remove(os.path.join(self.blob_dir, '.removed')) if not self.pack_keep_old: return # Second step, copy remaining files. for path, dir_names, file_names in os.walk(self.blob_dir): for file_name in file_names: if not file_name.endswith('.blob'): continue file_path = os.path.join(path, file_name) dest = os.path.dirname(old+file_path[lblob_dir:]) if not os.path.exists(dest): os.makedirs(dest, 0700) link_or_copy(file_path, old+file_path[lblob_dir:]) def iterator(self, start=None, stop=None): return FileIterator(self._file_name, start, stop) def lastInvalidations(self, count): file = self._file seek = file.seek read = file.read with self._lock: pos = self._pos while count > 0 and pos > 4: count -= 1 seek(pos-8) pos = pos - 8 - u64(read(8)) seek(0) return [(trans.tid, [r.oid for r in trans]) for trans in FileIterator(self._file_name, pos=pos)] def lastTid(self, oid): """Return last serialno committed for object oid. If there is no serialno for this oid -- which can only occur if it is a new object -- return None. """ try: return self.getTid(oid) except KeyError: return None def cleanup(self): """Remove all files created by this storage.""" for ext in '', '.old', '.tmp', '.lock', '.index', '.pack': try: os.remove(self._file_name + ext) except OSError, e: if e.errno != errno.ENOENT: raise def record_iternext(self, next=None): index = self._index oid = index.minKey(next) oid_as_long, = unpack(">Q", oid) next_oid = pack(">Q", oid_as_long + 1) try: next_oid = index.minKey(next_oid) except ValueError: # "empty tree" error next_oid = None data, tid = self.load(oid, "") return oid, tid, data, next_oid ###################################################################### # The following 2 methods are for testing a ZEO extension mechanism def getExtensionMethods(self): return dict(answer_to_the_ultimate_question=None) def answer_to_the_ultimate_question(self): return 42 # ###################################################################### def shift_transactions_forward(index, tindex, file, pos, opos): """Copy transactions forward in the data file This might be done as part of a recovery effort """ # Cache a bunch of methods seek=file.seek read=file.read write=file.write index_get=index.get # Initialize, pv=z64 p1=opos p2=pos offset=p2-p1 # Copy the data in two stages. In the packing stage, # we skip records that are non-current or that are for # unreferenced objects. We also skip undone transactions. # # After the packing stage, we copy everything but undone # transactions, however, we have to update various back pointers. # We have to have the storage lock in the second phase to keep # data from being changed while we're copying. pnv=None while 1: # Read the transaction record seek(pos) h=read(TRANS_HDR_LEN) if len(h) < TRANS_HDR_LEN: break tid, stl, status, ul, dl, el = unpack(TRANS_HDR,h) if status=='c': break # Oops. we found a checkpoint flag. tl=u64(stl) tpos=pos tend=tpos+tl otpos=opos # start pos of output trans thl=ul+dl+el h2=read(thl) if len(h2) != thl: raise PackError(opos) # write out the transaction record seek(opos) write(h) write(h2) thl=TRANS_HDR_LEN+thl pos=tpos+thl opos=otpos+thl while pos < tend: # Read the data records for this transaction seek(pos) h=read(DATA_HDR_LEN) oid,serial,sprev,stloc,vlen,splen = unpack(DATA_HDR, h) assert not vlen plen=u64(splen) dlen=DATA_HDR_LEN+(plen or 8) tindex[oid]=opos if plen: p=read(plen) else: p=read(8) p=u64(p) if p >= p2: p=p-offset elif p >= p1: # Ick, we're in trouble. Let's bail # to the index and hope for the best p=index_get(oid, 0) p=p64(p) # WRITE seek(opos) sprev=p64(index_get(oid, 0)) write(pack(DATA_HDR, oid, serial, sprev, p64(otpos), 0, splen)) write(p) opos=opos+dlen pos=pos+dlen # skip the (intentionally redundant) transaction length pos=pos+8 if status != 'u': index.update(tindex) # Record the position tindex.clear() write(stl) opos=opos+8 return opos def search_back(file, pos): seek=file.seek read=file.read seek(0,2) s=p=file.tell() while p > pos: seek(p-8) l=u64(read(8)) if l <= 0: break p=p-l-8 return p, s def recover(file_name): file=open(file_name, 'r+b') index={} tindex={} pos, oid, tid = read_index(file, file_name, index, tindex, recover=1) if oid is not None: print "Nothing to recover" return opos=pos pos, sz = search_back(file, pos) if pos < sz: npos = shift_transactions_forward(index, tindex, file, pos, opos) file.truncate(npos) print "Recovered file, lost %s, ended up with %s bytes" % ( pos-opos, npos) def read_index(file, name, index, tindex, stop='\377'*8, ltid=z64, start=4L, maxoid=z64, recover=0, read_only=0): """Scan the file storage and update the index. Returns file position, max oid, and last transaction id. It also stores index information in the three dictionary arguments. Arguments: file -- a file object (the Data.fs) name -- the name of the file (presumably file.name) index -- fsIndex, oid -> data record file offset tindex -- dictionary, oid -> data record offset tindex is cleared before return There are several default arguments that affect the scan or the return values. TODO: document them. start -- the file position at which to start scanning for oids added beyond the ones the passed-in indices know about. The .index file caches the highest ._pos FileStorage knew about when the the .index file was last saved, and that's the intended value to pass in for start; accept the default (and pass empty indices) to recreate the index from scratch maxoid -- ignored (it meant something prior to ZODB 3.2.6; the argument still exists just so the signature of read_index() stayed the same) The file position returned is the position just after the last valid transaction record. The oid returned is the maximum object id in `index`, or z64 if the index is empty. The transaction id is the tid of the last transaction, or ltid if the index is empty. """ read = file.read seek = file.seek seek(0, 2) file_size = file.tell() fmt = TempFormatter(file) if file_size: if file_size < start: raise FileStorageFormatError(file.name) seek(0) if read(4) != packed_version: raise FileStorageFormatError(name) else: if not read_only: file.write(packed_version) return 4L, z64, ltid index_get = index.get pos = start seek(start) tid = '\0' * 7 + '\1' while 1: # Read the transaction record h = read(TRANS_HDR_LEN) if not h: break if len(h) != TRANS_HDR_LEN: if not read_only: logger.warning('%s truncated at %s', name, pos) seek(pos) file.truncate() break tid, tl, status, ul, dl, el = unpack(TRANS_HDR, h) if tid <= ltid: logger.warning("%s time-stamp reduction at %s", name, pos) ltid = tid if pos+(tl+8) > file_size or status=='c': # Hm, the data were truncated or the checkpoint flag wasn't # cleared. They may also be corrupted, # in which case, we don't want to totally lose the data. if not read_only: logger.warning("%s truncated, possibly due to damaged" " records at %s", name, pos) _truncate(file, name, pos) break if status not in ' up': logger.warning('%s has invalid status, %s, at %s', name, status, pos) if tl < TRANS_HDR_LEN + ul + dl + el: # We're in trouble. Find out if this is bad data in the # middle of the file, or just a turd that Win 9x dropped # at the end when the system crashed. # Skip to the end and read what should be the transaction length # of the last transaction. seek(-8, 2) rtl = u64(read(8)) # Now check to see if the redundant transaction length is # reasonable: if file_size - rtl < pos or rtl < TRANS_HDR_LEN: logger.critical('%s has invalid transaction header at %s', name, pos) if not read_only: logger.warning( "It appears that there is invalid data at the end of " "the file, possibly due to a system crash. %s " "truncated to recover from bad data at end." % name) _truncate(file, name, pos) break else: if recover: return pos, None, None panic('%s has invalid transaction header at %s', name, pos) if tid >= stop: break tpos = pos tend = tpos + tl if status == 'u': # Undone transaction, skip it seek(tend) h = u64(read(8)) if h != tl: if recover: return tpos, None, None panic('%s has inconsistent transaction length at %s', name, pos) pos = tend + 8 continue pos = tpos + TRANS_HDR_LEN + ul + dl + el while pos < tend: # Read the data records for this transaction h = fmt._read_data_header(pos) dlen = h.recordlen() tindex[h.oid] = pos if pos + dlen > tend or h.tloc != tpos: if recover: return tpos, None, None panic("%s data record exceeds transaction record at %s", name, pos) if index_get(h.oid, 0) != h.prev: if h.prev: if recover: return tpos, None, None logger.error("%s incorrect previous pointer at %s", name, pos) else: logger.warning("%s incorrect previous pointer at %s", name, pos) pos += dlen if pos != tend: if recover: return tpos, None, None panic("%s data records don't add up at %s",name,tpos) # Read the (intentionally redundant) transaction length seek(pos) h = u64(read(8)) if h != tl: if recover: return tpos, None, None panic("%s redundant transaction length check failed at %s", name, pos) pos += 8 index.update(tindex) tindex.clear() # Caution: fsIndex doesn't have an efficient __nonzero__ or __len__. # That's why we do try/except instead. fsIndex.maxKey() is fast. try: maxoid = index.maxKey() except ValueError: # The index is empty. maxoid == z64 return pos, maxoid, ltid def _truncate(file, name, pos): file.seek(0, 2) file_size = file.tell() try: i = 0 while 1: oname='%s.tr%s' % (name, i) if os.path.exists(oname): i += 1 else: logger.warning("Writing truncated data from %s to %s", name, oname) o = open(oname,'wb') file.seek(pos) ZODB.utils.cp(file, o, file_size-pos) o.close() break except: logger.error("couldn\'t write truncated data for %s", name, exc_info=True) raise POSException.StorageSystemError("Couldn't save truncated data") file.seek(pos) file.truncate() class FileIterator(FileStorageFormatter): """Iterate over the transactions in a FileStorage file. """ _ltid = z64 _file = None def __init__(self, filename, start=None, stop=None, pos=4L): assert isinstance(filename, str) file = open(filename, 'rb') self._file = file self._file_name = filename if file.read(4) != packed_version: raise FileStorageFormatError(file.name) file.seek(0,2) self._file_size = file.tell() if (pos < 4) or pos > self._file_size: raise ValueError("Given position is greater than the file size", pos, self._file_size) self._pos = pos assert start is None or isinstance(start, str) assert stop is None or isinstance(stop, str) self._start = start self._stop = stop if start: if self._file_size <= 4: return self._skip_to_start(start) def __len__(self): # Define a bogus __len__() to make the iterator work # with code like builtin list() and tuple() in Python 2.1. # There's a lot of C code that expects a sequence to have # an __len__() but can cope with any sort of mistake in its # implementation. So just return 0. return 0 # This allows us to pass an iterator as the `other' argument to # copyTransactionsFrom() in BaseStorage. The advantage here is that we # can create the iterator manually, e.g. setting start and stop, and then # just let copyTransactionsFrom() do its thing. def iterator(self): return self def close(self): file = self._file if file is not None: self._file = None file.close() def _skip_to_start(self, start): file = self._file pos1 = self._pos file.seek(pos1) tid1 = file.read(8) if len(tid1) < 8: raise CorruptedError("Couldn't read tid.") if start < tid1: pos2 = pos1 tid2 = tid1 file.seek(4) tid1 = file.read(8) if start <= tid1: self._pos = 4 return pos1 = 4 else: if start == tid1: return # Try to read the last transaction. We could be unlucky and # opened the file while committing a transaction. In that # case, we'll just scan from the beginning if the file is # small enough, otherwise we'll fail. file.seek(self._file_size-8) l = u64(file.read(8)) if not (l + 12 <= self._file_size and self._read_num(self._file_size-l) == l): if self._file_size < (1<<20): return self._scan_foreward(start) raise ValueError("Can't find last transaction in large file") pos2 = self._file_size-l-8 file.seek(pos2) tid2 = file.read(8) if tid2 < tid1: raise CorruptedError("Tids out of order.") if tid2 <= start: if tid2 == start: self._pos = pos2 else: self._pos = self._file_size return t1 = ZODB.TimeStamp.TimeStamp(tid1).timeTime() t2 = ZODB.TimeStamp.TimeStamp(tid2).timeTime() ts = ZODB.TimeStamp.TimeStamp(start).timeTime() if (ts - t1) < (t2 - ts): return self._scan_forward(pos1, start) else: return self._scan_backward(pos2, start) def _scan_forward(self, pos, start): logger.debug("Scan forward %s:%s looking for %r", self._file_name, pos, start) file = self._file while 1: # Read the transaction record h = self._read_txn_header(pos) if h.tid >= start: self._pos = pos return pos += h.tlen + 8 def _scan_backward(self, pos, start): logger.debug("Scan backward %s:%s looking for %r", self._file_name, pos, start) file = self._file seek = file.seek read = file.read while 1: pos -= 8 seek(pos) tlen = ZODB.utils.u64(read(8)) pos -= tlen h = self._read_txn_header(pos) if h.tid <= start: if h.tid == start: self._pos = pos else: self._pos = pos + tlen + 8 return # Iterator protocol def __iter__(self): return self def next(self): if self._file is None: raise StopIteration() pos = self._pos while True: # Read the transaction record try: h = self._read_txn_header(pos) except CorruptedDataError, err: # If buf is empty, we've reached EOF. if not err.buf: break raise if h.tid <= self._ltid: logger.warning("%s time-stamp reduction at %s", self._file.name, pos) self._ltid = h.tid if self._stop is not None and h.tid > self._stop: break if h.status == "c": # Assume we've hit the last, in-progress transaction break if pos + h.tlen + 8 > self._file_size: # Hm, the data were truncated or the checkpoint flag wasn't # cleared. They may also be corrupted, # in which case, we don't want to totally lose the data. logger.warning("%s truncated, possibly due to" " damaged records at %s", self._file.name, pos) break if h.status not in " up": logger.warning('%s has invalid status,' ' %s, at %s', self._file.name, h.status, pos) if h.tlen < h.headerlen(): # We're in trouble. Find out if this is bad data in # the middle of the file, or just a turd that Win 9x # dropped at the end when the system crashed. Skip to # the end and read what should be the transaction # length of the last transaction. self._file.seek(-8, 2) rtl = u64(self._file.read(8)) # Now check to see if the redundant transaction length is # reasonable: if self._file_size - rtl < pos or rtl < TRANS_HDR_LEN: logger.critical("%s has invalid transaction header at %s", self._file.name, pos) logger.warning( "It appears that there is invalid data at the end of " "the file, possibly due to a system crash. %s " "truncated to recover from bad data at end." % self._file.name) break else: logger.warning("%s has invalid transaction header at %s", self._file.name, pos) break tpos = pos tend = tpos + h.tlen if h.status != "u": pos = tpos + h.headerlen() e = {} if h.elen: try: e = loads(h.ext) except: pass result = TransactionRecord(h.tid, h.status, h.user, h.descr, e, pos, tend, self._file, tpos) # Read the (intentionally redundant) transaction length self._file.seek(tend) rtl = u64(self._file.read(8)) if rtl != h.tlen: logger.warning("%s redundant transaction length check" " failed at %s", self._file.name, tend) break self._pos = tend + 8 return result self.close() raise StopIteration() class TransactionRecord(BaseStorage.TransactionRecord): def __init__(self, tid, status, user, desc, ext, pos, tend, file, tpos): BaseStorage.TransactionRecord.__init__( self, tid, status, user, desc, ext) self._pos = pos self._tend = tend self._file = file self._tpos = tpos def __iter__(self): return TransactionRecordIterator(self) class TransactionRecordIterator(FileStorageFormatter): """Iterate over the transactions in a FileStorage file.""" def __init__(self, record): self._file = record._file self._pos = record._pos self._tpos = record._tpos self._tend = record._tend def __iter__(self): return self def next(self): pos = self._pos while pos < self._tend: # Read the data records for this transaction h = self._read_data_header(pos) dlen = h.recordlen() if pos + dlen > self._tend or h.tloc != self._tpos: logger.warning("%s data record exceeds transaction" " record at %s", file.name, pos) break self._pos = pos + dlen prev_txn = None if h.plen: data = self._file.read(h.plen) else: if h.back == 0: # If the backpointer is 0, then this transaction # undoes the object creation. It undid the # transaction that created it. Return None # instead of a pickle to indicate this. data = None else: data, tid = self._loadBackTxn(h.oid, h.back, False) # Caution: :ooks like this only goes one link back. # Should it go to the original data like BDBFullStorage? prev_txn = self.getTxnFromData(h.oid, h.back) return Record(h.oid, h.tid, data, prev_txn, pos) raise StopIteration() class Record(BaseStorage.DataRecord): def __init__(self, oid, tid, data, prev, pos): super(Record, self).__init__(oid, tid, data, prev) self.pos = pos class UndoSearch: def __init__(self, file, pos, first, last, filter=None): self.file = file self.pos = pos self.first = first self.last = last self.filter = filter # self.i is the index of the transaction we're _going_ to find # next. When it reaches self.first, we should start appending # to self.results. When it reaches self.last, we're done # (although we may finish earlier). self.i = 0 self.results = [] self.stop = False def finished(self): """Return True if UndoSearch has found enough records.""" # BAW: Why 39 please? This makes no sense (see also below). return self.i >= self.last or self.pos < 39 or self.stop def search(self): """Search for another record.""" dict = self._readnext() if dict is not None and (self.filter is None or self.filter(dict)): if self.i >= self.first: self.results.append(dict) self.i += 1 def _readnext(self): """Read the next record from the storage.""" self.file.seek(self.pos - 8) self.pos -= u64(self.file.read(8)) + 8 self.file.seek(self.pos) h = self.file.read(TRANS_HDR_LEN) tid, tl, status, ul, dl, el = unpack(TRANS_HDR, h) if status == 'p': self.stop = 1 return None if status != ' ': return None d = u = '' if ul: u = self.file.read(ul) if dl: d = self.file.read(dl) e = {} if el: try: e = loads(self.file.read(el)) except: pass d = {'id': base64.encodestring(tid).rstrip(), 'time': TimeStamp(tid).timeTime(), 'user_name': u, 'size': tl, 'description': d} d.update(e) return d class FilePool: closed = False writing = False writers = 0 def __init__(self, file_name): self.name = file_name self._files = [] self._out = [] self._cond = threading.Condition() @contextlib.contextmanager def write_lock(self): with self._cond: self.writers += 1 while self.writing or self._out: self._cond.wait() if self.closed: raise ValueError('closed') self.writing = True try: yield None finally: with self._cond: self.writing = False if self.writers > 0: self.writers -= 1 self._cond.notifyAll() @contextlib.contextmanager def get(self): with self._cond: while self.writers: self._cond.wait() assert not self.writing if self.closed: raise ValueError('closed') try: f = self._files.pop() except IndexError: f = open(self.name, 'rb') self._out.append(f) try: yield f finally: self._out.remove(f) self._files.append(f) if not self._out: with self._cond: if self.writers and not self._out: self._cond.notifyAll() def empty(self): while self._files: self._files.pop().close() def close(self): with self._cond: self.closed = True while self._out: self._out.pop().close() self.empty() self.writing = self.writers = 0 zope2.13-2.13.21/source/ZODB3/src/ZODB/FileStorage/fspack.py0000644000175000017500000005574112214017464021647 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """FileStorage helper to perform pack. A storage contains an ordered set of object revisions. When a storage is packed, object revisions that are not reachable as of the pack time are deleted. The notion of reachability is complicated by backpointers -- object revisions that point to earlier revisions of the same object. An object revisions is reachable at a certain time if it is reachable from the revision of the root at that time or if it is reachable from a backpointer after that time. """ from ZODB.FileStorage.format import DataHeader, TRANS_HDR_LEN from ZODB.FileStorage.format import FileStorageFormatter, CorruptedDataError from ZODB.utils import p64, u64, z64 import logging import os import ZODB.fsIndex import ZODB.POSException logger = logging.getLogger(__name__) class PackError(ZODB.POSException.POSError): pass class PackCopier(FileStorageFormatter): def __init__(self, f, index, tindex): self._file = f self._index = index self._tindex = tindex self._pos = None def _txn_find(self, tid, stop_at_pack): # _pos always points just past the last transaction pos = self._pos while pos > 4: self._file.seek(pos - 8) pos = pos - u64(self._file.read(8)) - 8 self._file.seek(pos) h = self._file.read(TRANS_HDR_LEN) _tid = h[:8] if _tid == tid: return pos if stop_at_pack: if h[16] == 'p': break raise PackError("Invalid backpointer transaction id") def _data_find(self, tpos, oid, data): # Return backpointer for oid. Must call with the lock held. # This is a file offset to oid's data record if found, else 0. # The data records in the transaction at tpos are searched for oid. # If a data record for oid isn't found, returns 0. # Else if oid's data record contains a backpointer, that # backpointer is returned. # Else oid's data record contains the data, and the file offset of # oid's data record is returned. This data record should contain # a pickle identical to the 'data' argument. # Unclear: If the length of the stored data doesn't match len(data), # an exception is raised. If the lengths match but the data isn't # the same, 0 is returned. Why the discrepancy? h = self._read_txn_header(tpos) tend = tpos + h.tlen pos = self._file.tell() while pos < tend: h = self._read_data_header(pos) if h.oid == oid: # Make sure this looks like the right data record if h.plen == 0: # This is also a backpointer. Gotta trust it. return pos if h.plen != len(data): # The expected data doesn't match what's in the # backpointer. Something is wrong. logger.error("Mismatch between data and backpointer at %d", pos) return 0 _data = self._file.read(h.plen) if data != _data: return 0 return pos pos += h.recordlen() return 0 def copy(self, oid, serial, data, prev_txn, txnpos, datapos): prev_pos = self._resolve_backpointer(prev_txn, oid, data) old = self._index.get(oid, 0) # Calculate the pos the record will have in the storage. here = datapos # And update the temp file index self._tindex[oid] = here if prev_pos: # If there is a valid prev_pos, don't write data. data = None if data is None: dlen = 0 else: dlen = len(data) # Write the recovery data record h = DataHeader(oid, serial, old, txnpos, 0, dlen) self._file.write(h.asString()) # Write the data or a backpointer if data is None: if prev_pos: self._file.write(p64(prev_pos)) else: # Write a zero backpointer, which indicates an # un-creation transaction. self._file.write(z64) else: self._file.write(data) def setTxnPos(self, pos): self._pos = pos def _resolve_backpointer(self, prev_txn, oid, data): pos = self._file.tell() try: prev_pos = 0 if prev_txn is not None: prev_txn_pos = self._txn_find(prev_txn, 0) if prev_txn_pos: prev_pos = self._data_find(prev_txn_pos, oid, data) return prev_pos finally: self._file.seek(pos) class GC(FileStorageFormatter): def __init__(self, file, eof, packtime, gc, referencesf): self._file = file self._name = file.name self.eof = eof self.packtime = packtime self.gc = gc # packpos: position of first txn header after pack time self.packpos = None # {oid -> current data record position}: self.oid2curpos = ZODB.fsIndex.fsIndex() # The set of reachable revisions of each object. # # This set as managed using two data structures. The first is # an fsIndex mapping oids to one data record pos. Since only # a few objects will have more than one revision, we use this # efficient data structure to handle the common case. The # second is a dictionary mapping objects to lists of # positions; it is used to handle the same number of objects # for which we must keep multiple revisions. self.reachable = ZODB.fsIndex.fsIndex() self.reach_ex = {} # keep ltid for consistency checks during initial scan self.ltid = z64 self.referencesf = referencesf def isReachable(self, oid, pos): """Return 1 if revision of `oid` at `pos` is reachable.""" rpos = self.reachable.get(oid) if rpos is None: return 0 if rpos == pos: return 1 return pos in self.reach_ex.get(oid, []) def findReachable(self): self.buildPackIndex() if self.gc: self.findReachableAtPacktime([z64]) self.findReachableFromFuture() # These mappings are no longer needed and may consume a lot of # space. del self.oid2curpos else: self.reachable = self.oid2curpos def buildPackIndex(self): pos = 4L # We make the initial assumption that the database has been # packed before and set unpacked to True only after seeing the # first record with a status == " ". If we get to the packtime # and unpacked is still False, we need to watch for a redundant # pack. unpacked = False while pos < self.eof: th = self._read_txn_header(pos) if th.tid > self.packtime: break self.checkTxn(th, pos) if th.status != "p": unpacked = True tpos = pos end = pos + th.tlen pos += th.headerlen() while pos < end: dh = self._read_data_header(pos) self.checkData(th, tpos, dh, pos) if dh.plen or dh.back: self.oid2curpos[dh.oid] = pos else: if dh.oid in self.oid2curpos: del self.oid2curpos[dh.oid] pos += dh.recordlen() tlen = self._read_num(pos) if tlen != th.tlen: self.fail(pos, "redundant transaction length does not " "match initial transaction length: %d != %d", tlen, th.tlen) pos += 8 self.packpos = pos if unpacked: return # check for a redundant pack. If the first record following # the newly computed packpos has status 'p', then it was # packed earlier and the current pack is redudant. try: th = self._read_txn_header(pos) except CorruptedDataError, err: if err.buf != "": raise if th.status == 'p': # Delayed import to cope with circular imports. # TODO: put exceptions in a separate module. from ZODB.FileStorage.FileStorage import RedundantPackWarning raise RedundantPackWarning( "The database has already been packed to a later time" " or no changes have been made since the last pack") def findReachableAtPacktime(self, roots): """Mark all objects reachable from the oids in roots as reachable.""" reachable = self.reachable oid2curpos = self.oid2curpos todo = list(roots) while todo: oid = todo.pop() if oid in reachable: continue try: pos = oid2curpos[oid] except KeyError: if oid == z64 and len(oid2curpos) == 0: # special case, pack to before creation time continue raise reachable[oid] = pos for oid in self.findrefs(pos): if oid not in reachable: todo.append(oid) def findReachableFromFuture(self): # In this pass, the roots are positions of object revisions. # We add a pos to extra_roots when there is a backpointer to a # revision that was not current at the packtime. The # non-current revision could refer to objects that were # otherwise unreachable at the packtime. extra_roots = [] pos = self.packpos while pos < self.eof: th = self._read_txn_header(pos) self.checkTxn(th, pos) tpos = pos end = pos + th.tlen pos += th.headerlen() while pos < end: dh = self._read_data_header(pos) self.checkData(th, tpos, dh, pos) if dh.back and dh.back < self.packpos: if self.reachable.has_key(dh.oid): L = self.reach_ex.setdefault(dh.oid, []) if dh.back not in L: L.append(dh.back) extra_roots.append(dh.back) else: self.reachable[dh.oid] = dh.back pos += dh.recordlen() tlen = self._read_num(pos) if tlen != th.tlen: self.fail(pos, "redundant transaction length does not " "match initial transaction length: %d != %d", tlen, th.tlen) pos += 8 for pos in extra_roots: refs = self.findrefs(pos) self.findReachableAtPacktime(refs) def findrefs(self, pos): """Return a list of oids referenced as of packtime.""" dh = self._read_data_header(pos) # Chase backpointers until we get to the record with the refs while dh.back: dh = self._read_data_header(dh.back) if dh.plen: return self.referencesf(self._file.read(dh.plen)) else: return [] class FileStoragePacker(FileStorageFormatter): # path is the storage file path. # stop is the pack time, as a TimeStamp. # la and lr are the acquire() and release() methods of the storage's lock. # cla and clr similarly, for the storage's commit lock. # current_size is the storage's _pos. All valid data at the start # lives before that offset (there may be a checkpoint transaction in # progress after it). def __init__(self, storage, referencesf, stop, gc=True): self._storage = storage if storage.blob_dir: self.pack_blobs = True self.blob_removed = open( os.path.join(storage.blob_dir, '.removed'), 'w') else: self.pack_blobs = False path = storage._file.name self._name = path # We open our own handle on the storage so that much of pack can # proceed in parallel. It's important to close this file at every # return point, else on Windows the caller won't be able to rename # or remove the storage file. self._file = open(path, "rb") self._path = path self._stop = stop self.locked = False self.file_end = storage.getSize() self.gc = GC(self._file, self.file_end, self._stop, gc, referencesf) # The packer needs to acquire the parent's commit lock # during the copying stage, so the two sets of lock acquire # and release methods are passed to the constructor. self._lock_acquire = storage._lock_acquire self._lock_release = storage._lock_release self._commit_lock_acquire = storage._commit_lock_acquire self._commit_lock_release = storage._commit_lock_release # The packer will use several indexes. # index: oid -> pos # tindex: oid -> pos, for current txn # oid2tid: not used by the packer self.index = ZODB.fsIndex.fsIndex() self.tindex = {} self.oid2tid = {} self.toid2tid = {} self.toid2tid_delete = {} def pack(self): # Pack copies all data reachable at the pack time or later. # # Copying occurs in two phases. In the first phase, txns # before the pack time are copied if the contain any reachable # data. In the second phase, all txns after the pack time # are copied. # # Txn and data records contain pointers to previous records. # Because these pointers are stored as file offsets, they # must be updated when we copy data. # TODO: Should add sanity checking to pack. self.gc.findReachable() # Setup the destination file and copy the metadata. # TODO: rename from _tfile to something clearer. self._tfile = open(self._name + ".pack", "w+b") self._file.seek(0) self._tfile.write(self._file.read(self._metadata_size)) self._copier = PackCopier(self._tfile, self.index, self.tindex) ipos, opos = self.copyToPacktime() assert ipos == self.gc.packpos if ipos == opos: # pack didn't free any data. there's no point in continuing. self._tfile.close() self._file.close() os.remove(self._name + ".pack") return None self._commit_lock_acquire() self.locked = True try: self._lock_acquire() try: # Re-open the file in unbuffered mode. # The main thread may write new transactions to the # file, which creates the possibility that we will # read a status 'c' transaction into the pack thread's # stdio buffer even though we're acquiring the commit # lock. Transactions can still be in progress # throughout much of packing, and are written to the # same physical file but via a distinct Python file # object. The code used to leave off the trailing 0 # argument, and then on every platform except native # Windows it was observed that we could read stale # data from the tail end of the file. self._file.close() # else self.gc keeps the original # alive & open self._file = open(self._path, "rb", 0) self._file.seek(0, 2) self.file_end = self._file.tell() finally: self._lock_release() if ipos < self.file_end: self.copyRest(ipos) # OK, we've copied everything. Now we need to wrap things up. pos = self._tfile.tell() self._tfile.flush() self._tfile.close() self._file.close() return pos except: if self.locked: self._commit_lock_release() raise def copyToPacktime(self): offset = 0L # the amount of space freed by packing pos = self._metadata_size new_pos = pos while pos < self.gc.packpos: th = self._read_txn_header(pos) new_tpos, pos = self.copyDataRecords(pos, th) if new_tpos: new_pos = self._tfile.tell() + 8 tlen = new_pos - new_tpos - 8 # Update the transaction length self._tfile.seek(new_tpos + 8) self._tfile.write(p64(tlen)) self._tfile.seek(new_pos - 8) self._tfile.write(p64(tlen)) tlen = self._read_num(pos) if tlen != th.tlen: self.fail(pos, "redundant transaction length does not " "match initial transaction length: %d != %d", tlen, th.tlen) pos += 8 return pos, new_pos def copyDataRecords(self, pos, th): """Copy any current data records between pos and tend. Returns position of txn header in output file and position of next record in the input file. If any data records are copied, also write txn header (th). """ copy = 0 new_tpos = 0L tend = pos + th.tlen pos += th.headerlen() while pos < tend: h = self._read_data_header(pos) if not self.gc.isReachable(h.oid, pos): if self.pack_blobs: # We need to find out if this is a blob, so get the data: if h.plen: data = self._file.read(h.plen) else: data = self.fetchDataViaBackpointer(h.oid, h.back) if data and self._storage.is_blob_record(data): # We need to remove the blob record. Maybe we # need to remove oid: # But first, we need to make sure the record # we're looking at isn't a dup of the current # record. There's a bug in ZEO blob support that causes # duplicate data records. rpos = self.gc.reachable.get(h.oid) is_dup = (rpos and self._read_data_header(rpos).tid == h.tid) if not is_dup: if h.oid not in self.gc.reachable: self.blob_removed.write( h.oid.encode('hex')+'\n') else: self.blob_removed.write( (h.oid+h.tid).encode('hex')+'\n') pos += h.recordlen() continue pos += h.recordlen() # If we are going to copy any data, we need to copy # the transaction header. Note that we will need to # patch up the transaction length when we are done. if not copy: th.status = "p" s = th.asString() new_tpos = self._tfile.tell() self._tfile.write(s) new_pos = new_tpos + len(s) copy = 1 if h.plen: data = self._file.read(h.plen) else: data = self.fetchDataViaBackpointer(h.oid, h.back) self.writePackedDataRecord(h, data, new_tpos) new_pos = self._tfile.tell() return new_tpos, pos def fetchDataViaBackpointer(self, oid, back): """Return the data for oid via backpointer back If `back` is 0 or ultimately resolves to 0, return None. In this case, the transaction undoes the object creation. """ if back == 0: return None data, tid = self._loadBackTxn(oid, back, 0) return data def writePackedDataRecord(self, h, data, new_tpos): # Update the header to reflect current information, then write # it to the output file. if data is None: data = "" h.prev = 0 h.back = 0 h.plen = len(data) h.tloc = new_tpos pos = self._tfile.tell() self.index[h.oid] = pos self._tfile.write(h.asString()) self._tfile.write(data) if not data: # Packed records never have backpointers (?). # If there is no data, write a z64 backpointer. # This is a George Bailey event. self._tfile.write(z64) def copyRest(self, ipos): # After the pack time, all data records are copied. # Copy one txn at a time, using copy() for data. try: while 1: ipos = self.copyOne(ipos) except CorruptedDataError, err: # The last call to copyOne() will raise # CorruptedDataError, because it will attempt to read past # the end of the file. Double-check that the exception # occurred for this reason. self._file.seek(0, 2) endpos = self._file.tell() if endpos != err.pos: raise def copyOne(self, ipos): # The call below will raise CorruptedDataError at EOF. th = self._read_txn_header(ipos) # Release commit lock while writing to pack file self._commit_lock_release() self.locked = False pos = self._tfile.tell() self._copier.setTxnPos(pos) self._tfile.write(th.asString()) tend = ipos + th.tlen ipos += th.headerlen() while ipos < tend: h = self._read_data_header(ipos) ipos += h.recordlen() prev_txn = None if h.plen: data = self._file.read(h.plen) else: data = self.fetchDataViaBackpointer(h.oid, h.back) if h.back: prev_txn = self.getTxnFromData(h.oid, h.back) self._copier.copy(h.oid, h.tid, data, prev_txn, pos, self._tfile.tell()) tlen = self._tfile.tell() - pos assert tlen == th.tlen self._tfile.write(p64(tlen)) ipos += 8 self.index.update(self.tindex) self.tindex.clear() self._commit_lock_acquire() self.locked = True return ipos zope2.13-2.13.21/source/ZODB3/src/ZODB/FileStorage/iterator.test0000644000175000017500000001046512214017464022552 0ustar arnauarnauFileStorage-specific iterator tests =================================== The FileStorage iterator has some special features that deserve some special tests. We'll make some assertions about time, so we'll take it over: >>> now = 1229959248 >>> def faux_time(): ... global now ... now += 0.1 ... return now >>> import time >>> time_time = time.time >>> time.time = faux_time Commit a bunch of transactions: >>> import ZODB.FileStorage, transaction >>> db = ZODB.DB('data.fs') >>> tids = [db.storage.lastTransaction()] >>> poss = [db.storage._pos] >>> conn = db.open() >>> for i in range(100): ... conn.root()[i] = conn.root().__class__() ... transaction.commit() ... tids.append(db.storage.lastTransaction()) ... poss.append(db.storage._pos) Deciding where to start ----------------------- By default, we start at the beginning: >>> it = ZODB.FileStorage.FileIterator('data.fs') >>> it.next().tid == tids[0] True The file iterator has an optimization to deal with large files. It can serarch from either the front or the back of the file, depending on the starting transaction given. To see this, we'll turn on debug logging: >>> import logging, sys >>> old_log_level = logging.getLogger().getEffectiveLevel() >>> logging.getLogger().setLevel(logging.DEBUG) >>> handler = logging.StreamHandler(sys.stdout) >>> logging.getLogger().addHandler(handler) If we specify a start transaction, we'll scan forward or backward, as seems best and set the next record to that: >>> it = ZODB.FileStorage.FileIterator('data.fs', tids[0]) >>> it.next().tid == tids[0] True >>> it = ZODB.FileStorage.FileIterator('data.fs', tids[1]) Scan forward data.fs:4 looking for '\x03z\xbd\xd8\xd06\x9c\xcc' >>> it.next().tid == tids[1] True >>> it = ZODB.FileStorage.FileIterator('data.fs', tids[30]) Scan forward data.fs:4 looking for '\x03z\xbd\xd8\xdc\x96.\xcc' >>> it.next().tid == tids[30] True >>> it = ZODB.FileStorage.FileIterator('data.fs', tids[70]) Scan backward data.fs:117080 looking for '\x03z\xbd\xd8\xed\xa7>\xcc' >>> it.next().tid == tids[70] True >>> it = ZODB.FileStorage.FileIterator('data.fs', tids[-2]) Scan backward data.fs:117080 looking for '\x03z\xbd\xd8\xfa\x06\xd0\xcc' >>> it.next().tid == tids[-2] True >>> it = ZODB.FileStorage.FileIterator('data.fs', tids[-1]) >>> it.next().tid == tids[-1] True We can also supply a file position. This can speed up finding the starting point, or just pick up where another iterator left off: >>> it = ZODB.FileStorage.FileIterator('data.fs', pos=poss[50]) >>> it.next().tid == tids[51] True >>> it = ZODB.FileStorage.FileIterator('data.fs', tids[0], pos=4) >>> it.next().tid == tids[0] True >>> it = ZODB.FileStorage.FileIterator('data.fs', tids[-1], pos=poss[-2]) >>> it.next().tid == tids[-1] True >>> it = ZODB.FileStorage.FileIterator('data.fs', tids[50], pos=poss[50]) Scan backward data.fs:35936 looking for '\x03z\xbd\xd8\xe5\x1e\xb6\xcc' >>> it.next().tid == tids[50] True >>> it = ZODB.FileStorage.FileIterator('data.fs', tids[49], pos=poss[50]) Scan backward data.fs:35936 looking for '\x03z\xbd\xd8\xe4\xb1|\xcc' >>> it.next().tid == tids[49] True >>> it = ZODB.FileStorage.FileIterator('data.fs', tids[51], pos=poss[50]) >>> it.next().tid == tids[51] True >>> logging.getLogger().setLevel(old_log_level) >>> logging.getLogger().removeHandler(handler) If a starting transaction is before the first transaction in the file, then the first transaction is returned. >>> from ZODB.utils import p64, u64 >>> it = ZODB.FileStorage.FileIterator('data.fs', p64(u64(tids[0])-1)) >>> it.next().tid == tids[0] True If it is after the last transaction, then iteration be empty: >>> it = ZODB.FileStorage.FileIterator('data.fs', p64(u64(tids[-1])+1)) >>> list(it) [] Even if we write more transactions: >>> it = ZODB.FileStorage.FileIterator('data.fs', p64(u64(tids[-1])+1)) >>> for i in range(10): ... conn.root()[i] = conn.root().__class__() ... transaction.commit() >>> list(it) [] .. Cleanup >>> time.time = time_time >>> it.close() >>> db.close() zope2.13-2.13.21/source/ZODB3/src/ZODB/FileStorage/fsoids.py0000644000175000017500000001746312214017464021666 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import ZODB.FileStorage from ZODB.utils import get_pickle_metadata, p64, oid_repr, tid_repr from ZODB.serialize import get_refs from ZODB.TimeStamp import TimeStamp # Extract module.class string from pickle. def get_class(pickle): return "%s.%s" % get_pickle_metadata(pickle) # Shorten a string for display. def shorten(s, size=50): if len(s) <= size: return s # Stick ... in the middle. navail = size - 5 nleading = navail // 2 ntrailing = size - nleading return s[:nleading] + " ... " + s[-ntrailing:] class Tracer(object): """Trace all occurrences of a set of oids in a FileStorage. Create passing a path to an existing FileStorage. Call register_oids(oid, ...) one or more times to specify which oids to investigate. Call run() to do the analysis. This isn't swift -- it has to read every byte in the database, in order to find all references. Call report() to display the results. """ def __init__(self, path): import os if not os.path.isfile(path): raise ValueError("must specify an existing FileStorage") self.path = path # Map an interesting tid to (status, user, description, pos). self.tid2info = {} # List of messages. Each is a tuple of the form # (oid, tid, string) # The order in the tuple is important, because it defines the # sort order for grouping. self.msgs = [] # The set of interesting oids, specified by register_oid() calls. # Maps oid to # of revisions. self.oids = {} # Maps interesting oid to its module.class name. If a creation # record for an interesting oid is never seen, it won't appear # in this mapping. self.oid2name = {} def register_oids(self, *oids): """ Declare that oids (0 or more) are "interesting". An oid can be given as a native 8-byte string, or as an integer. Info will be gathered about all appearances of this oid in the entire database, including references. """ for oid in oids: if isinstance(oid, str): assert len(oid) == 8 else: oid = p64(oid) self.oids[oid] = 0 # 0 revisions seen so far def _msg(self, oid, tid, *args): args = map(str, args) self.msgs.append( (oid, tid, ' '.join(args)) ) self._produced_msg = True def report(self): """Show all msgs, grouped by oid and sub-grouped by tid.""" msgs = self.msgs oids = self.oids oid2name = self.oid2name # First determine which oids weren't seen at all, and synthesize msgs # for them. NOT_SEEN = "this oid was not defined (no data record for it found)" for oid in oids: if oid not in oid2name: msgs.append( (oid, None, NOT_SEEN) ) msgs.sort() # oids are primary key, tids secondary current_oid = current_tid = None for oid, tid, msg in msgs: if oid != current_oid: nrev = oids[oid] revision = "revision" + (nrev != 1 and 's' or '') name = oid2name.get(oid, "") print "oid", oid_repr(oid), name, nrev, revision current_oid = oid current_tid = None if msg is NOT_SEEN: assert tid is None print " ", msg continue if tid != current_tid: current_tid = tid status, user, description, pos = self.tid2info[tid] print " tid %s offset=%d %s" % (tid_repr(tid), pos, TimeStamp(tid)) print " tid user=%r" % shorten(user) print " tid description=%r" % shorten(description) print " ", msg # Do the analysis. def run(self): """Find all occurrences of the registered oids in the database.""" # Maps oid of a reference to its module.class name. self._ref2name = {} for txn in ZODB.FileStorage.FileIterator(self.path): self._check_trec(txn) # Process next transaction record. def _check_trec(self, txn): # txn has members tid, status, user, description, # _extension, _pos, _tend, _file, _tpos self._produced_msg = False # Map and list for save data records for current transaction. self._records_map = {} self._records = [] for drec in txn: self._save_references(drec) for drec in self._records: self._check_drec(drec) if self._produced_msg: # Copy txn info for later output. self.tid2info[txn.tid] = (txn.status, txn.user, txn.description, txn._tpos) def _save_references(self, drec): # drec has members oid, tid, data, data_txn tid, oid, pick, pos = drec.tid, drec.oid, drec.data, drec.pos if pick: if oid in self.oids: klass = get_class(pick) self._msg(oid, tid, "new revision", klass, "at", pos) self.oids[oid] += 1 self.oid2name[oid] = self._ref2name[oid] = klass self._records_map[oid] = drec self._records.append(drec) elif oid in self.oids: self._msg(oid, tid, "creation undo at", pos) # Process next data record. If a message is produced, self._produced_msg # will be set True. def _check_drec(self, drec): # drec has members oid, tid, data, data_txn tid, oid, pick, pos = drec.tid, drec.oid, drec.data, drec.pos ref2name = self._ref2name ref2name_get = ref2name.get records_map_get = self._records_map.get if pick: oid_in_oids = oid in self.oids for ref, klass in get_refs(pick): if ref in self.oids: oidclass = ref2name_get(oid, None) if oidclass is None: ref2name[oid] = oidclass = get_class(pick) self._msg(ref, tid, "referenced by", oid_repr(oid), oidclass, "at", pos) if oid_in_oids: if klass is None: klass = ref2name_get(ref, None) if klass is None: r = records_map_get(ref, None) # For save memory we only save references # seen in one transaction with interesting # objects changes. So in some circumstances # we may still got "" class name. if r is None: klass = "" else: ref2name[ref] = klass = get_class(r.data) elif isinstance(klass, tuple): ref2name[ref] = klass = "%s.%s" % klass self._msg(oid, tid, "references", oid_repr(ref), klass, "at", pos) zope2.13-2.13.21/source/ZODB3/src/ZODB/FileStorage/interfaces.py0000644000175000017500000000456212214017464022516 0ustar arnauarnau############################################################################## # # Copyright (c) Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import zope.interface class IFileStoragePacker(zope.interface.Interface): def __call__(storage, referencesf, stop, gc): """Pack the file storage into a new file The new file will have the same name as the old file with '.pack' appended. (The packer can get the old file name via storage._file.name.) If blobs are supported, if the storages blob_dir attribute is not None or empty, then a .removed file most be created in the blob directory. This file contains of the form: (oid+serial).encode('hex')+'\n' or, of the form: oid.encode('hex')+'\n' If packing is unnecessary, or would not change the file, then no pack or removed files are created None is returned, otherwise a tuple is returned with: - the size of the packed file, and - the packed index If and only if packing was necessary (non-None) and there was no error, then the commit lock must be acquired. In addition, it is up to FileStorage to: - Rename the .pack file, and - process the blob_dir/.removed file by removing the blobs corresponding to the file records. """ class IFileStorage(zope.interface.Interface): packer = zope.interface.Attribute( "The IFileStoragePacker to be used for packing." ) _file = zope.interface.Attribute( "The file object used to access the underlying data." ) def _lock_acquire(): "Acquire the storage lock" def _lock_release(): "Release the storage lock" def _commit_lock_acquire(): "Acquire the storage commit lock" def _commit_lock_release(): "Release the storage commit lock" zope2.13-2.13.21/source/ZODB3/src/ZODB/FileStorage/__init__.py0000644000175000017500000000035612214017464022127 0ustar arnauarnau# this is a package from ZODB.FileStorage.FileStorage import FileStorage, TransactionRecord from ZODB.FileStorage.FileStorage import FileIterator, Record, packed_version # BBB Alias for compatibility RecordIterator = TransactionRecord zope2.13-2.13.21/source/ZODB3/src/ZODB/FileStorage/tests.py0000644000175000017500000001246412214017464021535 0ustar arnauarnau############################################################################## # # Copyright (c) Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import os import time import transaction import unittest import ZODB.blob import ZODB.FileStorage import ZODB.tests.util def pack_keep_old(): """Should a copy of the database be kept? The pack_keep_old constructor argument controls whether a .old file (and .old directory for blobs is kept.) >>> fs = ZODB.FileStorage.FileStorage('data.fs', blob_dir='blobs') >>> db = ZODB.DB(fs) >>> conn = db.open() >>> import ZODB.blob >>> conn.root()[1] = ZODB.blob.Blob() >>> conn.root()[1].open('w').write('some data') >>> conn.root()[2] = ZODB.blob.Blob() >>> conn.root()[2].open('w').write('some data') >>> transaction.commit() >>> conn.root()[1].open('w').write('some other data') >>> del conn.root()[2] >>> transaction.commit() >>> old_size = os.stat('data.fs').st_size >>> def get_blob_size(d): ... result = 0 ... for path, dirs, file_names in os.walk(d): ... for file_name in file_names: ... result += os.stat(os.path.join(path, file_name)).st_size ... return result >>> blob_size = get_blob_size('blobs') >>> db.pack(time.time()+1) >>> packed_size = os.stat('data.fs').st_size >>> packed_size < old_size True >>> os.stat('data.fs.old').st_size == old_size True >>> packed_blob_size = get_blob_size('blobs') >>> packed_blob_size < blob_size True >>> get_blob_size('blobs.old') == blob_size True >>> db.close() >>> fs = ZODB.FileStorage.FileStorage('data.fs', blob_dir='blobs', ... create=True, pack_keep_old=False) >>> db = ZODB.DB(fs) >>> conn = db.open() >>> conn.root()[1] = ZODB.blob.Blob() >>> conn.root()[1].open('w').write('some data') >>> conn.root()[2] = ZODB.blob.Blob() >>> conn.root()[2].open('w').write('some data') >>> transaction.commit() >>> conn.root()[1].open('w').write('some other data') >>> del conn.root()[2] >>> transaction.commit() >>> db.pack(time.time()+1) >>> os.stat('data.fs').st_size == packed_size True >>> os.path.exists('data.fs.old') False >>> get_blob_size('blobs') == packed_blob_size True >>> os.path.exists('blobs.old') False >>> db.close() """ def pack_with_repeated_blob_records(): """ There is a bug in ZEO that causes duplicate bloc database records to be written in a blob store operation. (Maybe this has been fixed by the time you read this, but there might still be transactions in the wild that have duplicate records. >>> fs = ZODB.FileStorage.FileStorage('t', blob_dir='bobs') >>> db = ZODB.DB(fs) >>> conn = db.open() >>> conn.root()[1] = ZODB.blob.Blob() >>> transaction.commit() >>> tm = transaction.TransactionManager() >>> oid = conn.root()[1]._p_oid >>> blob_record, oldserial = fs.load(oid) Now, create a transaction with multiple saves: >>> trans = tm.begin() >>> fs.tpc_begin(trans) >>> open('ablob', 'w').write('some data') >>> _ = fs.store(oid, oldserial, blob_record, '', trans) >>> _ = fs.storeBlob(oid, oldserial, blob_record, 'ablob', '', trans) >>> fs.tpc_vote(trans) >>> fs.tpc_finish(trans) >>> time.sleep(.01) >>> db.pack() >>> conn.sync() >>> conn.root()[1].open().read() 'some data' >>> db.close() """ def _save_index(): """ _save_index can fail for large indexes. >>> import ZODB.utils >>> fs = ZODB.FileStorage.FileStorage('data.fs') >>> t = transaction.begin() >>> fs.tpc_begin(t) >>> oid = 0 >>> for i in range(5000): ... oid += (1<<16) ... _ = fs.store(ZODB.utils.p64(oid), ZODB.utils.z64, 'x', '', t) >>> fs.tpc_vote(t) >>> fs.tpc_finish(t) >>> import sys >>> old_limit = sys.getrecursionlimit() >>> sys.setrecursionlimit(50) >>> fs._save_index() Make sure we can restore: >>> import logging >>> handler = logging.StreamHandler(sys.stdout) >>> logger = logging.getLogger('ZODB.FileStorage') >>> logger.setLevel(logging.DEBUG) >>> logger.addHandler(handler) >>> index, pos, tid = fs._restore_index() >>> index.items() == fs._index.items() True >>> pos, tid = fs._pos, fs._tid cleanup >>> fs.close() >>> logger.setLevel(logging.NOTSET) >>> logger.removeHandler(handler) >>> sys.setrecursionlimit(old_limit) """ def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite( 'zconfig.txt', 'iterator.test', setUp=ZODB.tests.util.setUp, tearDown=ZODB.tests.util.tearDown, ), doctest.DocTestSuite( setUp=ZODB.tests.util.setUp, tearDown=ZODB.tests.util.tearDown, ), )) zope2.13-2.13.21/source/ZODB3/src/ZODB/FileStorage/format.py0000644000175000017500000002216212214017464021657 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## # # File-based ZODB storage # # Files are arranged as follows. # # - The first 4 bytes are a file identifier. # # - The rest of the file consists of a sequence of transaction # "records". # # A transaction record consists of: # # - 8-byte transaction id, which is also a time stamp. # # - 8-byte transaction record length - 8. # # - 1-byte status code # ' ' (a blank) completed transaction that hasn't been packed # 'p' completed transaction that has been packed # 'c' checkpoint -- a transaction in progress, at the end of the file; # it's been thru vote() but not finish(); if finish() completes # normally, it will be overwritten with a blank; if finish() dies # (e.g., out of disk space), cleanup code will try to truncate # the file to chop off this incomplete transaction # 'u' uncertain; no longer used; was previously used to record something # about non-transactional undo # # - 2-byte length of user name # # - 2-byte length of description # # - 2-byte length of extension attributes # # - user name # # - description # # - extension attributes # # * A sequence of data records # # - 8-byte redundant transaction length -8 # # A data record consists of # # - 8-byte oid. # # - 8-byte tid, which matches the transaction id in the transaction record. # # - 8-byte previous-record file-position. # # - 8-byte beginning of transaction record file position. # # - 2-bytes with zero values. (Was version length.) # # - 8-byte data length # # ? data # (data length > 0) # # ? 8-byte position of data record containing data # (data length == 0) # # Note that the lengths and positions are all big-endian. # Also, the object ids time stamps are big-endian, so comparisons # are meaningful. # # Backpointers # # When we undo a record, we don't copy (or delete) # data. Instead, we write records with back pointers. import struct import logging from ZODB.POSException import POSKeyError from ZODB.utils import u64, oid_repr class CorruptedError(Exception): pass class CorruptedDataError(CorruptedError): def __init__(self, oid=None, buf=None, pos=None): self.oid = oid self.buf = buf self.pos = pos def __str__(self): if self.oid: msg = "Error reading oid %s. Found %r" % (oid_repr(self.oid), self.buf) else: msg = "Error reading unknown oid. Found %r" % self.buf if self.pos: msg += " at %d" % self.pos return msg # the struct formats for the headers TRANS_HDR = ">8sQcHHH" DATA_HDR = ">8s8sQQHQ" # constants to support various header sizes TRANS_HDR_LEN = 23 DATA_HDR_LEN = 42 assert struct.calcsize(TRANS_HDR) == TRANS_HDR_LEN assert struct.calcsize(DATA_HDR) == DATA_HDR_LEN logger = logging.getLogger('ZODB.FileStorage.format') class FileStorageFormatter(object): """Mixin class that can read and write the low-level format.""" # subclasses must provide _file _metadata_size = 4L _format_version = "21" def _read_num(self, pos): """Read an 8-byte number.""" self._file.seek(pos) return u64(self._file.read(8)) def _read_data_header(self, pos, oid=None, _file=None): """Return a DataHeader object for data record at pos. If ois is not None, raise CorruptedDataError if oid passed does not match oid in file. """ if _file is None: _file = self._file _file.seek(pos) s = _file.read(DATA_HDR_LEN) if len(s) != DATA_HDR_LEN: raise CorruptedDataError(oid, s, pos) h = DataHeaderFromString(s) if oid is not None and oid != h.oid: raise CorruptedDataError(oid, s, pos) if not h.plen: h.back = u64(_file.read(8)) return h def _read_txn_header(self, pos, tid=None): self._file.seek(pos) s = self._file.read(TRANS_HDR_LEN) if len(s) != TRANS_HDR_LEN: raise CorruptedDataError(tid, s, pos) h = TxnHeaderFromString(s) if tid is not None and tid != h.tid: raise CorruptedDataError(tid, s, pos) h.user = self._file.read(h.ulen) h.descr = self._file.read(h.dlen) h.ext = self._file.read(h.elen) return h def _loadBack_impl(self, oid, back, fail=True, _file=None): # shared implementation used by various _loadBack methods # # If the backpointer ultimately resolves to 0: # If fail is True, raise KeyError for zero backpointer. # If fail is False, return the empty data from the record # with no backpointer. if _file is None: _file = self._file while 1: if not back: # If backpointer is 0, object does not currently exist. raise POSKeyError(oid) h = self._read_data_header(back, _file=_file) if h.plen: return _file.read(h.plen), h.tid, back, h.tloc if h.back == 0 and not fail: return None, h.tid, back, h.tloc back = h.back def _loadBackTxn(self, oid, back, fail=True): """Return data and txn id for backpointer.""" return self._loadBack_impl(oid, back, fail)[:2] def _loadBackPOS(self, oid, back): return self._loadBack_impl(oid, back)[2] def getTxnFromData(self, oid, back): """Return transaction id for data at back.""" h = self._read_data_header(back, oid) return h.tid def fail(self, pos, msg, *args): s = ("%s:%s:" + msg) % ((self._name, pos) + args) logger.error(s) raise CorruptedError(s) def checkTxn(self, th, pos): if th.tid <= self.ltid: self.fail(pos, "time-stamp reduction: %s <= %s", oid_repr(th.tid), oid_repr(self.ltid)) self.ltid = th.tid if th.status == "c": self.fail(pos, "transaction with checkpoint flag set") if not th.status in " pu": # recognize " ", "p", and "u" as valid self.fail(pos, "invalid transaction status: %r", th.status) if th.tlen < th.headerlen(): self.fail(pos, "invalid transaction header: " "txnlen (%d) < headerlen(%d)", th.tlen, th.headerlen()) def checkData(self, th, tpos, dh, pos): if dh.tloc != tpos: self.fail(pos, "data record does not point to transaction header" ": %d != %d", dh.tloc, tpos) if pos + dh.recordlen() > tpos + th.tlen: self.fail(pos, "data record size exceeds transaction size: " "%d > %d", pos + dh.recordlen(), tpos + th.tlen) if dh.prev >= pos: self.fail(pos, "invalid previous pointer: %d", dh.prev) if dh.back: if dh.back >= pos: self.fail(pos, "invalid back pointer: %d", dh.prev) if dh.plen: self.fail(pos, "data record has back pointer and data") def DataHeaderFromString(s): return DataHeader(*struct.unpack(DATA_HDR, s)) class DataHeader(object): """Header for a data record.""" __slots__ = ("oid", "tid", "prev", "tloc", "plen", "back") def __init__(self, oid, tid, prev, tloc, vlen, plen): if vlen: raise ValueError( "Non-zero version length. Versions aren't supported.") self.oid = oid self.tid = tid self.prev = prev self.tloc = tloc self.plen = plen self.back = 0 # default def asString(self): return struct.pack(DATA_HDR, self.oid, self.tid, self.prev, self.tloc, 0, self.plen) def recordlen(self): return DATA_HDR_LEN + (self.plen or 8) def TxnHeaderFromString(s): return TxnHeader(*struct.unpack(TRANS_HDR, s)) class TxnHeader(object): """Header for a transaction record.""" __slots__ = ("tid", "tlen", "status", "user", "descr", "ext", "ulen", "dlen", "elen") def __init__(self, tid, tlen, status, ulen, dlen, elen): self.tid = tid self.tlen = tlen self.status = status self.ulen = ulen self.dlen = dlen self.elen = elen assert elen >= 0 def asString(self): s = struct.pack(TRANS_HDR, self.tid, self.tlen, self.status, self.ulen, self.dlen, self.elen) return "".join(map(str, [s, self.user, self.descr, self.ext])) def headerlen(self): return TRANS_HDR_LEN + self.ulen + self.dlen + self.elen zope2.13-2.13.21/source/ZODB3/src/ZODB/FileStorage/zconfig.txt0000644000175000017500000001262412214017464022217 0ustar arnauarnauDefining FileStorages using ZConfig =================================== ZODB provides support for defining many storages, including FileStorages, using ZConfig. To define a FileStorage, you use a filestorage section, and define a path: >>> import ZODB.config >>> fs = ZODB.config.storageFromString(""" ... ... path my.fs ... ... """) >>> fs._file.name 'my.fs' >>> fs.close() There are a number of options we can provide: blob-dir If supplied, the file storage will provide blob support and this is the name of a directory to hold blob data. The directory will be created if it doeesn't exist. If no value (or an empty value) is provided, then no blob support will be provided. (You can still use a BlobStorage to provide blob support.) >>> fs = ZODB.config.storageFromString(""" ... ... path my.fs ... blob-dir blobs ... ... """) >>> fs._file.name 'my.fs' >>> import os >>> os.path.basename(fs.blob_dir) 'blobs' create Flag that indicates whether the storage should be truncated if it already exists. To demonstrate this, we'll first write some data: >>> db = ZODB.DB(fs) >>> conn = db.open() >>> import ZODB.blob, transaction >>> conn.root()[1] = ZODB.blob.Blob() >>> transaction.commit() >>> db.close() Then reopen with the create option: >>> fs = ZODB.config.storageFromString(""" ... ... path my.fs ... blob-dir blobs ... create true ... ... """) Because the file was truncated, we no-longer have object 0: >>> fs.load('\0'*8) Traceback (most recent call last): ... POSKeyError: 0x00 >>> sorted(os.listdir('blobs')) ['.layout', 'tmp'] >>> fs.close() read-only If true, only reads may be executed against the storage. Note that the "pack" operation is not considered a write operation and is still allowed on a read-only filestorage. >>> fs = ZODB.config.storageFromString(""" ... ... path my.fs ... read-only true ... ... """) >>> fs.isReadOnly() True >>> fs.close() quota Maximum allowed size of the storage file. Operations which would cause the size of the storage to exceed the quota will result in a ZODB.FileStorage.FileStorageQuotaError being raised. >>> fs = ZODB.config.storageFromString(""" ... ... path my.fs ... quota 10 ... ... """) >>> db = ZODB.DB(fs) # writes object 0 Traceback (most recent call last): ... FileStorageQuotaError: The storage quota has been exceeded. >>> fs.close() packer The dotten name (dotten module name and object name) of a packer object. This is used to provide an alternative pack implementation. To demonstrate this, we'll create a null packer that just prints some information about it's arguments: >>> def packer(storage, referencesf, stop, gc): ... print referencesf, storage is fs, gc, storage.pack_keep_old >>> ZODB.FileStorage.config_demo_printing_packer = packer >>> fs = ZODB.config.storageFromString(""" ... ... path my.fs ... packer ZODB.FileStorage.config_demo_printing_packer ... ... """) >>> import time >>> db = ZODB.DB(fs) # writes object 0 >>> fs.pack(time.time(), 42) 42 True True True >>> fs.close() If the packer contains a ':', then the text after the first ':' is interpreted as an expression. This is handy to pass limited configuration information to the packer: >>> def packer_factory(name): ... def packer(storage, referencesf, stop, gc): ... print repr(name), referencesf, storage is fs, gc, ... print storage.pack_keep_old ... return packer >>> ZODB.FileStorage.config_demo_printing_packer_factory = packer_factory >>> fs = ZODB.config.storageFromString(""" ... ... path my.fs ... packer ZODB.FileStorage:config_demo_printing_packer_factory('bob ') ... ... """) >>> import time >>> db = ZODB.DB(fs) # writes object 0 >>> fs.pack(time.time(), 42) 'bob ' 42 True True True >>> fs.close() pack-gc If false, then no garbage collection will be performed when packing. This can make packing go much faster and can avoid problems when objects are referenced only from other databases. >>> fs = ZODB.config.storageFromString(""" ... ... path my.fs ... packer ZODB.FileStorage.config_demo_printing_packer ... pack-gc false ... ... """) >>> fs.pack(time.time(), 42) 42 True False True Note that if we pass the gc option to pack, then this will override the value set in the configuration: >>> fs.pack(time.time(), 42, gc=True) 42 True True True >>> fs.close() pack-keep-old If false, then old files aren't kept when packing >>> fs = ZODB.config.storageFromString(""" ... ... path my.fs ... packer ZODB.FileStorage.config_demo_printing_packer ... pack-keep-old false ... ... """) >>> fs.pack(time.time(), 42) 42 True True False >>> fs.close() zope2.13-2.13.21/source/ZODB3/src/ZODB/FileStorage/fsdump.py0000644000175000017500000001101112214017464021654 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import struct from ZODB.FileStorage import FileIterator from ZODB.FileStorage.format import TRANS_HDR, TRANS_HDR_LEN from ZODB.FileStorage.format import DATA_HDR, DATA_HDR_LEN from ZODB.TimeStamp import TimeStamp from ZODB.utils import u64, get_pickle_metadata def fsdump(path, file=None, with_offset=1): iter = FileIterator(path) for i, trans in enumerate(iter): if with_offset: print >> file, ("Trans #%05d tid=%016x time=%s offset=%d" % (i, u64(trans.tid), TimeStamp(trans.tid), trans._pos)) else: print >> file, ("Trans #%05d tid=%016x time=%s" % (i, u64(trans.tid), TimeStamp(trans.tid))) print >> file, (" status=%r user=%r description=%r" % (trans.status, trans.user, trans.description)) for j, rec in enumerate(trans): if rec.data is None: fullclass = "undo or abort of object creation" size = "" else: modname, classname = get_pickle_metadata(rec.data) size = " size=%d" % len(rec.data) fullclass = "%s.%s" % (modname, classname) if rec.data_txn: # It would be nice to print the transaction number # (i) but it would be expensive to keep track of. bp = " bp=%016x" % u64(rec.data_txn) else: bp = "" print >> file, (" data #%05d oid=%016x%s class=%s%s" % (j, u64(rec.oid), size, fullclass, bp)) iter.close() def fmt(p64): # Return a nicely formatted string for a packaged 64-bit value return "%016x" % u64(p64) class Dumper: """A very verbose dumper for debuggin FileStorage problems.""" # TODO: Should revise this class to use FileStorageFormatter. def __init__(self, path, dest=None): self.file = open(path, "rb") self.dest = dest def dump(self): fid = self.file.read(4) print >> self.dest, "*" * 60 print >> self.dest, "file identifier: %r" % fid while self.dump_txn(): pass def dump_txn(self): pos = self.file.tell() h = self.file.read(TRANS_HDR_LEN) if not h: return False tid, tlen, status, ul, dl, el = struct.unpack(TRANS_HDR, h) end = pos + tlen print >> self.dest, "=" * 60 print >> self.dest, "offset: %d" % pos print >> self.dest, "end pos: %d" % end print >> self.dest, "transaction id: %s" % fmt(tid) print >> self.dest, "trec len: %d" % tlen print >> self.dest, "status: %r" % status user = descr = extra = "" if ul: user = self.file.read(ul) if dl: descr = self.file.read(dl) if el: extra = self.file.read(el) print >> self.dest, "user: %r" % user print >> self.dest, "description: %r" % descr print >> self.dest, "len(extra): %d" % el while self.file.tell() < end: self.dump_data(pos) stlen = self.file.read(8) print >> self.dest, "redundant trec len: %d" % u64(stlen) return 1 def dump_data(self, tloc): pos = self.file.tell() h = self.file.read(DATA_HDR_LEN) assert len(h) == DATA_HDR_LEN oid, revid, prev, tloc, vlen, dlen = struct.unpack(DATA_HDR, h) print >> self.dest, "-" * 60 print >> self.dest, "offset: %d" % pos print >> self.dest, "oid: %s" % fmt(oid) print >> self.dest, "revid: %s" % fmt(revid) print >> self.dest, "previous record offset: %d" % prev print >> self.dest, "transaction offset: %d" % tloc assert not vlen print >> self.dest, "len(data): %d" % dlen self.file.read(dlen) if not dlen: sbp = self.file.read(8) print >> self.dest, "backpointer: %d" % u64(sbp) def main(): import sys fsdump(sys.argv[1]) zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/0000755000175000017500000000000012214017464017275 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/fsstats.py0000644000175000017500000001313112214017464021335 0ustar arnauarnau#!/usr/bin/env python2.3 """Print details statistics from fsdump output.""" import re import sys rx_txn = re.compile("tid=([0-9a-f]+).*size=(\d+)") rx_data = re.compile("oid=([0-9a-f]+) class=(\S+) size=(\d+)") def sort_byhsize(seq, reverse=False): L = [(v.size(), k, v) for k, v in seq] L.sort() if reverse: L.reverse() return [(k, v) for n, k, v in L] class Histogram(dict): def add(self, size): self[size] = self.get(size, 0) + 1 def size(self): return sum(self.itervalues()) def mean(self): product = sum([k * v for k, v in self.iteritems()]) return product / self.size() def median(self): # close enough? n = self.size() / 2 L = self.keys() L.sort() L.reverse() while 1: k = L.pop() if self[k] > n: return k n -= self[k] def mode(self): mode = 0 value = 0 for k, v in self.iteritems(): if v > value: value = v mode = k return mode def make_bins(self, binsize): maxkey = max(self.iterkeys()) self.binsize = binsize self.bins = [0] * (1 + maxkey / binsize) for k, v in self.iteritems(): b = k / binsize self.bins[b] += v def report(self, name, binsize=50, usebins=False, gaps=True, skip=True): if usebins: # Use existing bins with whatever size they have binsize = self.binsize else: # Make new bins self.make_bins(binsize) maxval = max(self.bins) # Print up to 40 dots for a value dot = max(maxval / 40, 1) tot = sum(self.bins) print name print "Total", tot, print "Median", self.median(), print "Mean", self.mean(), print "Mode", self.mode(), print "Max", max(self) print "One * represents", dot gap = False cum = 0 for i, n in enumerate(self.bins): if gaps and (not n or (skip and not n / dot)): if not gap: print " ..." gap = True continue gap = False p = 100 * n / tot cum += n pc = 100 * cum / tot print "%6d %6d %3d%% %3d%% %s" % ( i * binsize, n, p, pc, "*" * (n / dot)) print def class_detail(class_size): # summary of classes fmt = "%5s %6s %6s %6s %-50.50s" labels = ["num", "median", "mean", "mode", "class"] print fmt % tuple(labels) print fmt % tuple(["-" * len(s) for s in labels]) for klass, h in sort_byhsize(class_size.iteritems()): print fmt % (h.size(), h.median(), h.mean(), h.mode(), klass) print # per class details for klass, h in sort_byhsize(class_size.iteritems(), reverse=True): h.make_bins(50) if len(filter(None, h.bins)) == 1: continue h.report("Object size for %s" % klass, usebins=True) def revision_detail(lifetimes, classes): # Report per-class details for any object modified more than once for name, oids in classes.iteritems(): h = Histogram() keep = False for oid in dict.fromkeys(oids, 1): L = lifetimes.get(oid) n = len(L) h.add(n) if n > 1: keep = True if keep: h.report("Number of revisions for %s" % name, binsize=10) def main(path=None): if path is None: path = sys.argv[1] txn_objects = Histogram() # histogram of txn size in objects txn_bytes = Histogram() # histogram of txn size in bytes obj_size = Histogram() # histogram of object size n_updates = Histogram() # oid -> num updates n_classes = Histogram() # class -> num objects lifetimes = {} # oid -> list of tids class_size = {} # class -> histogram of object size classes = {} # class -> list of oids MAX = 0 objects = 0 tid = None f = open(path, "rb") for i, line in enumerate(f): if MAX and i > MAX: break if line.startswith(" data"): m = rx_data.search(line) if not m: continue oid, klass, size = m.groups() size = int(size) obj_size.add(size) n_updates.add(oid) n_classes.add(klass) h = class_size.get(klass) if h is None: h = class_size[klass] = Histogram() h.add(size) L = lifetimes.setdefault(oid, []) L.append(tid) L = classes.setdefault(klass, []) L.append(oid) objects += 1 elif line.startswith("Trans"): if tid is not None: txn_objects.add(objects) m = rx_txn.search(line) if not m: continue tid, size = m.groups() size = int(size) objects = 0 txn_bytes.add(size) f.close() print "Summary: %d txns, %d objects, %d revisions" % ( txn_objects.size(), len(n_updates), n_updates.size()) print txn_bytes.report("Transaction size (bytes)", binsize=1024) txn_objects.report("Transaction size (objects)", binsize=10) obj_size.report("Object size", binsize=128) # object lifetime info h = Histogram() for k, v in lifetimes.items(): h.add(len(v)) h.report("Number of revisions", binsize=10, skip=False) # details about revisions revision_detail(lifetimes, classes) class_detail(class_size) if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/space.py0000644000175000017500000000303712214017464020745 0ustar arnauarnau#!/usr/bin/env python2.3 """Report on the space used by objects in a storage. usage: space.py data.fs The current implementation only supports FileStorage. Current limitations / simplifications: Ignores revisions and versions. """ from ZODB.FileStorage import FileStorage from ZODB.utils import U64, get_pickle_metadata def run(path, v=0): fs = FileStorage(path, read_only=1) # break into the file implementation if hasattr(fs._index, 'iterkeys'): iter = fs._index.iterkeys() else: iter = fs._index.keys() totals = {} for oid in iter: data, serialno = fs.load(oid, '') mod, klass = get_pickle_metadata(data) key = "%s.%s" % (mod, klass) bytes, count = totals.get(key, (0, 0)) bytes += len(data) count += 1 totals[key] = bytes, count if v: print "%8s %5d %s" % (U64(oid), len(data), key) L = totals.items() L.sort(lambda a, b: cmp(a[1], b[1])) L.reverse() print "Totals per object class:" for key, (bytes, count) in L: print "%8d %8d %s" % (count, bytes, key) def main(): import sys import getopt try: opts, args = getopt.getopt(sys.argv[1:], "v") except getopt.error, msg: print msg print "usage: space.py [-v] Data.fs" sys.exit(2) if len(args) != 1: print "usage: space.py [-v] Data.fs" sys.exit(2) v = 0 for o, a in opts: if o == "-v": v += 1 path = args[0] run(path, v) if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/fstest.py0000644000175000017500000001516212214017464021164 0ustar arnauarnau#!/usr/bin/env python2.3 ############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Simple consistency checker for FileStorage. usage: fstest.py [-v] data.fs The fstest tool will scan all the data in a FileStorage and report an error if it finds any corrupt transaction data. The tool will print a message when the first error is detected, then exit. The tool accepts one or more -v arguments. If a single -v is used, it will print a line of text for each transaction record it encounters. If two -v arguments are used, it will also print a line of text for each object. The objects for a transaction will be printed before the transaction itself. Note: It does not check the consistency of the object pickles. It is possible for the damage to occur only in the part of the file that stores object pickles. Those errors will go undetected. """ # The implementation is based closely on the read_index() function in # ZODB.FileStorage. If anything about the FileStorage layout changes, # this file will need to be udpated. import string import struct import sys class FormatError(ValueError): """There is a problem with the format of the FileStorage.""" class Status: checkpoint = 'c' undone = 'u' packed_version = 'FS21' TREC_HDR_LEN = 23 DREC_HDR_LEN = 42 VERBOSE = 0 def hexify(s): """Format an 8-bite string as hex""" l = [] for c in s: h = hex(ord(c)) if h[:2] == '0x': h = h[2:] if len(h) == 1: l.append("0") l.append(h) return "0x" + string.join(l, '') def chatter(msg, level=1): if VERBOSE >= level: sys.stdout.write(msg) def U64(v): """Unpack an 8-byte string as a 64-bit long""" h, l = struct.unpack(">II", v) if h: return (h << 32) + l else: return l def check(path): file = open(path, 'rb') file.seek(0, 2) file_size = file.tell() if file_size == 0: raise FormatError("empty file") file.seek(0) if file.read(4) != packed_version: raise FormatError("invalid file header") pos = 4L tid = '\000' * 8 # lowest possible tid to start i = 0 while pos: _pos = pos pos, tid = check_trec(path, file, pos, tid, file_size) if tid is not None: chatter("%10d: transaction tid %s #%d \n" % (_pos, hexify(tid), i)) i = i + 1 def check_trec(path, file, pos, ltid, file_size): """Read an individual transaction record from file. Returns the pos of the next transaction and the transaction id. It also leaves the file pointer set to pos. The path argument is used for generating error messages. """ h = file.read(TREC_HDR_LEN) if not h: return None, None if len(h) != TREC_HDR_LEN: raise FormatError("%s truncated at %s" % (path, pos)) tid, stl, status, ul, dl, el = struct.unpack(">8s8scHHH", h) tmeta_len = TREC_HDR_LEN + ul + dl + el if tid <= ltid: raise FormatError("%s time-stamp reduction at %s: %s <= %s" % (path, pos, hexify(tid), hexify(ltid))) ltid = tid tl = U64(stl) # transaction record length - 8 if pos + tl + 8 > file_size: raise FormatError("%s truncated possibly because of" " damaged records at %s" % (path, pos)) if status == Status.checkpoint: raise FormatError("%s checkpoint flag was not cleared at %s" % (path, pos)) if status not in ' up': raise FormatError("%s has invalid status '%s' at %s" % (path, status, pos)) if tmeta_len > tl: raise FormatError("%s has an invalid transaction header" " at %s" % (path, pos)) tpos = pos tend = tpos + tl if status != Status.undone: pos = tpos + tmeta_len file.read(ul + dl + el) # skip transaction metadata i = 0 while pos < tend: _pos = pos pos, oid = check_drec(path, file, pos, tpos, tid) if pos > tend: raise FormatError("%s has data records that extend beyond" " the transaction record; end at %s" % (path, pos)) chatter("%10d: object oid %s #%d\n" % (_pos, hexify(oid), i), level=2) i = i + 1 file.seek(tend) rtl = file.read(8) if rtl != stl: raise FormatError("%s has inconsistent transaction length" " for undone transaction at %s" % (path, pos)) pos = tend + 8 return pos, tid def check_drec(path, file, pos, tpos, tid): """Check a data record for the current transaction record""" h = file.read(DREC_HDR_LEN) if len(h) != DREC_HDR_LEN: raise FormatError("%s truncated at %s" % (path, pos)) oid, serial, _prev, _tloc, vlen, _plen = ( struct.unpack(">8s8s8s8sH8s", h)) prev = U64(_prev) tloc = U64(_tloc) plen = U64(_plen) dlen = DREC_HDR_LEN + (plen or 8) if vlen: dlen = dlen + 16 + vlen file.seek(8, 1) pv = U64(file.read(8)) file.seek(vlen, 1) # skip the version data if tloc != tpos: raise FormatError("%s data record exceeds transaction record " "at %s: tloc %d != tpos %d" % (path, pos, tloc, tpos)) pos = pos + dlen if plen: file.seek(plen, 1) else: file.seek(8, 1) # _loadBack() ? return pos, oid def usage(): print __doc__ sys.exit(-1) def main(args=None): if args is None: args = sys.argv[1:] import getopt global VERBOSE try: opts, args = getopt.getopt(args, 'v') if len(args) != 1: raise ValueError("expected one argument") for k, v in opts: if k == '-v': VERBOSE = VERBOSE + 1 except (getopt.error, ValueError): usage() try: check(args[0]) except FormatError, msg: print msg sys.exit(-1) chatter("no errors detected") if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/zodbload.py0000644000175000017500000007423012214017464021453 0ustar arnauarnau#!/usr/bin/env python2.3 ############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test script for testing ZODB under a heavy zope-like load. Note that, to be as realistic as possible with ZEO, you should run this script multiple times, to simulate multiple clients. Here's how this works. The script starts some number of threads. Each thread, sequentially executes jobs. There is a job producer that produces jobs. Input data are provided by a mail producer that hands out message from a mailbox. Execution continues until there is an error, which will normally occur when the mailbox is exhausted. Command-line options are used to provide job definitions. Job definitions have perameters of the form name=value. Jobs have 2 standard parameters: frequency=integer The frequency of the job. The default is 1. sleep=float The number os seconds to sleep before performing the job. The default is 0. Usage: loadmail2 [options] Options: -edit [frequency=integer] [sleep=float] Define an edit job. An edit job edits a random already-saved email message, deleting and inserting a random number of words. After editing the message, the message is (re)cataloged. -insert [number=int] [frequency=integer] [sleep=float] Insert some number of email messages. -index [number=int] [frequency=integer] [sleep=float] Insert and index (catalog) some number of email messages. -search [terms='word1 word2 ...'] [frequency=integer] [sleep=float] Search the catalog. A query is givem with one or more terms as would be entered into a typical seach box. If no query is given, then queries will be randomly selected based on a set of built-in word list. -setup Set up the database. This will delete any existing Data.fs file. (Of course, this may have no effect, if there is a custom_zodb that defined a different storage.) It also adds a mail folder and a catalog. -options file Read options from the given file. Th efile should be a python source file that defines a sequence of options named 'options'. -threads n Specify the number of threads to execute. If not specified (< 2), then jobs are run in a single (main) thread. -mbox filename Specify the mailbox for getting input data. There is a (lame) syntax for providing options within the filename. The filename may be followed by up to 3 integers, min, max, and start: -mbox 'foo.mbox 0 100 10000' The messages from min to max will be read from the mailbox. They will be assigned message numbers starting with start. So, in the example above, we read the first hundred messages and assign thgem message numbers starting with 10001. The maxmum can be given as a negative number, in which case, it specifies the number of messages to read. The start defaults to the minimum. The following two options: -mbox 'foo.mbox 300 400 300' and -mbox 'foo.mbox 300 -100' are equivalent $Id: zodbload.py 113734 2010-06-21 15:33:46Z ctheune $ """ import mailbox import math import os import random import re import sys import threading import time import transaction class JobProducer: def __init__(self): self.jobs = [] def add(self, callable, frequency, sleep, repeatp=0): self.jobs.extend([(callable, sleep, repeatp)] * int(frequency)) random.shuffle(self.jobs) def next(self): factory, sleep, repeatp = random.choice(self.jobs) time.sleep(sleep) callable, args = factory.create() return factory, callable, args, repeatp def __nonzero__(self): return not not self.jobs class MBox: def __init__(self, filename): if ' ' in filename: filename = filename.split() if len(filename) < 4: filename += [0, 0, -1][-(4-len(filename)):] filename, min, max, start = filename min = int(min) max = int(max) start = int(start) if start < 0: start = min if max < 0: # negative max is treated as a count self._max = start - max elif max > 0: self._max = start + max - min else: self._max = 0 else: self._max = 0 min = start = 0 if filename.endswith('.bz2'): f = os.popen("bunzip2 <"+filename, 'r') filename = filename[-4:] else: f = open(filename) self._mbox = mb = mailbox.UnixMailbox(f) self.number = start while min: mb.next() min -= 1 self._lock = threading.Lock() self.__name__ = os.path.splitext(os.path.split(filename)[1])[0] self._max = max def next(self): self._lock.acquire() try: if self._max > 0 and self.number >= self._max: raise IndexError(self.number + 1) message = self._mbox.next() message.body = message.fp.read() message.headers = list(message.headers) self.number += 1 message.number = self.number message.mbox = self.__name__ return message finally: self._lock.release() bins = 9973 #bins = 11 def mailfolder(app, mboxname, number): mail = getattr(app, mboxname, None) if mail is None: app.manage_addFolder(mboxname) mail = getattr(app, mboxname) from BTrees.Length import Length mail.length = Length() for i in range(bins): mail.manage_addFolder('b'+str(i)) bin = hash(str(number))%bins return getattr(mail, 'b'+str(bin)) def VmSize(): try: f = open('/proc/%s/status' % os.getpid()) except: return 0 else: l = filter(lambda l: l[:7] == 'VmSize:', f.readlines()) if l: l = l[0][7:].strip().split()[0] return int(l) return 0 def setup(lib_python): try: os.remove(os.path.join(lib_python, '..', '..', 'var', 'Data.fs')) except: pass import Zope2 import Products import AccessControl.SecurityManagement app=Zope2.app() Products.ZCatalog.ZCatalog.manage_addZCatalog(app, 'cat', '') from Products.ZCTextIndex.ZCTextIndex import PLexicon from Products.ZCTextIndex.Lexicon import Splitter, CaseNormalizer app.cat._setObject('lex', PLexicon('lex', '', Splitter(), CaseNormalizer()) ) class extra: doc_attr = 'PrincipiaSearchSource' lexicon_id = 'lex' index_type = 'Okapi BM25 Rank' app.cat.addIndex('PrincipiaSearchSource', 'ZCTextIndex', extra) transaction.commit() system = AccessControl.SpecialUsers.system AccessControl.SecurityManagement.newSecurityManager(None, system) app._p_jar.close() def do(db, f, args): """Do something in a transaction, retrying of necessary Measure the speed of both the compurartion and the commit """ from ZODB.POSException import ConflictError wcomp = ccomp = wcommit = ccommit = 0.0 rconflicts = wconflicts = 0 start = time.time() while 1: connection = db.open() try: transaction.begin() t=time.time() c=time.clock() try: try: r = f(connection, *args) except ConflictError: rconflicts += 1 transaction.abort() continue finally: wcomp += time.time() - t ccomp += time.clock() - c t=time.time() c=time.clock() try: try: transaction.commit() break except ConflictError: wconflicts += 1 transaction.abort() continue finally: wcommit += time.time() - t ccommit += time.clock() - c finally: connection.close() return start, wcomp, ccomp, rconflicts, wconflicts, wcommit, ccommit, r def run1(tid, db, factory, job, args): (start, wcomp, ccomp, rconflicts, wconflicts, wcommit, ccommit, r ) = do(db, job, args) start = "%.4d-%.2d-%.2d %.2d:%.2d:%.2d" % time.localtime(start)[:6] print "%s %s %8.3g %8.3g %s %s\t%8.3g %8.3g %s %r" % ( start, tid, wcomp, ccomp, rconflicts, wconflicts, wcommit, ccommit, factory.__name__, r) def run(jobs, tid=''): import Zope2 while 1: factory, job, args, repeatp = jobs.next() run1(tid, Zope2.DB, factory, job, args) if repeatp: while 1: i = random.randint(0,100) if i > repeatp: break run1(tid, Zope2.DB, factory, job, args) def index(connection, messages, catalog, max): app = connection.root()['Application'] for message in messages: mail = mailfolder(app, message.mbox, message.number) if max: # Cheat and use folder implementation secrets # to avoid having to read the old data _objects = mail._objects if len(_objects) >= max: for d in _objects[:len(_objects)-max+1]: del mail.__dict__[d['id']] mail._objects = _objects[len(_objects)-max+1:] docid = 'm'+str(message.number) mail.manage_addDTMLDocument(docid, file=message.body) # increment counted getattr(app, message.mbox).length.change(1) doc = mail[docid] for h in message.headers: h = h.strip() l = h.find(':') if l <= 0: continue name = h[:l].lower() if name=='subject': name='title' v = h[l+1:].strip() type='string' if name=='title': doc.manage_changeProperties(title=h) else: try: doc.manage_addProperty(name, v, type) except: pass if catalog: app.cat.catalog_object(doc) return message.number class IndexJob: needs_mbox = 1 catalog = 1 prefix = 'index' def __init__(self, mbox, number=1, max=0): self.__name__ = "%s%s_%s" % (self.prefix, number, mbox.__name__) self.mbox, self.number, self.max = mbox, int(number), int(max) def create(self): messages = [self.mbox.next() for i in range(self.number)] return index, (messages, self.catalog, self.max) class InsertJob(IndexJob): catalog = 0 prefix = 'insert' wordre = re.compile(r'(\w{3,20})') stop = 'and', 'not' def edit(connection, mbox, catalog=1): app = connection.root()['Application'] mail = getattr(app, mbox.__name__, None) if mail is None: time.sleep(1) return "No mailbox %s" % mbox.__name__ nmessages = mail.length() if nmessages < 2: time.sleep(1) return "No messages to edit in %s" % mbox.__name__ # find a message to edit: while 1: number = random.randint(1, nmessages-1) did = 'm' + str(number) mail = mailfolder(app, mbox.__name__, number) doc = getattr(mail, did, None) if doc is not None: break text = doc.raw.split() norig = len(text) if norig > 10: ndel = int(math.exp(random.randint(0, int(math.log(norig))))) nins = int(math.exp(random.randint(0, int(math.log(norig))))) else: ndel = 0 nins = 10 for j in range(ndel): j = random.randint(0,len(text)-1) word = text[j] m = wordre.search(word) if m: word = m.group(1).lower() if (not wordsd.has_key(word)) and word not in stop: words.append(word) wordsd[word] = 1 del text[j] for j in range(nins): word = random.choice(words) text.append(word) doc.raw = ' '.join(text) if catalog: app.cat.catalog_object(doc) return norig, ndel, nins class EditJob: needs_mbox = 1 prefix = 'edit' catalog = 1 def __init__(self, mbox): self.__name__ = "%s_%s" % (self.prefix, mbox.__name__) self.mbox = mbox def create(self): return edit, (self.mbox, self.catalog) class ModifyJob(EditJob): prefix = 'modify' catalog = 0 def search(connection, terms, number): app = connection.root()['Application'] cat = app.cat n = 0 for i in number: term = random.choice(terms) results = cat(PrincipiaSearchSource=term) n += len(results) for result in results: obj = result.getObject() # Apparently, there is a bug in Zope that leads obj to be None # on occasion. if obj is not None: obj.getId() return n class SearchJob: def __init__(self, terms='', number=10): if terms: terms = terms.split() self.__name__ = "search_" + '_'.join(terms) self.terms = terms else: self.__name__ = 'search' self.terms = words number = min(int(number), len(self.terms)) self.number = range(number) def create(self): return search, (self.terms, self.number) words=['banishment', 'indirectly', 'imprecise', 'peeks', 'opportunely', 'bribe', 'sufficiently', 'Occidentalized', 'elapsing', 'fermenting', 'listen', 'orphanage', 'younger', 'draperies', 'Ida', 'cuttlefish', 'mastermind', 'Michaels', 'populations', 'lent', 'cater', 'attentional', 'hastiness', 'dragnet', 'mangling', 'scabbards', 'princely', 'star', 'repeat', 'deviation', 'agers', 'fix', 'digital', 'ambitious', 'transit', 'jeeps', 'lighted', 'Prussianizations', 'Kickapoo', 'virtual', 'Andrew', 'generally', 'boatsman', 'amounts', 'promulgation', 'Malay', 'savaging', 'courtesan', 'nursed', 'hungered', 'shiningly', 'ship', 'presides', 'Parke', 'moderns', 'Jonas', 'unenlightening', 'dearth', 'deer', 'domesticates', 'recognize', 'gong', 'penetrating', 'dependents', 'unusually', 'complications', 'Dennis', 'imbalances', 'nightgown', 'attached', 'testaments', 'congresswoman', 'circuits', 'bumpers', 'braver', 'Boreas', 'hauled', 'Howe', 'seethed', 'cult', 'numismatic', 'vitality', 'differences', 'collapsed', 'Sandburg', 'inches', 'head', 'rhythmic', 'opponent', 'blanketer', 'attorneys', 'hen', 'spies', 'indispensably', 'clinical', 'redirection', 'submit', 'catalysts', 'councilwoman', 'kills', 'topologies', 'noxious', 'exactions', 'dashers', 'balanced', 'slider', 'cancerous', 'bathtubs', 'legged', 'respectably', 'crochets', 'absenteeism', 'arcsine', 'facility', 'cleaners', 'bobwhite', 'Hawkins', 'stockade', 'provisional', 'tenants', 'forearms', 'Knowlton', 'commit', 'scornful', 'pediatrician', 'greets', 'clenches', 'trowels', 'accepts', 'Carboloy', 'Glenn', 'Leigh', 'enroll', 'Madison', 'Macon', 'oiling', 'entertainingly', 'super', 'propositional', 'pliers', 'beneficiary', 'hospitable', 'emigration', 'sift', 'sensor', 'reserved', 'colonization', 'shrilled', 'momentously', 'stevedore', 'Shanghaiing', 'schoolmasters', 'shaken', 'biology', 'inclination', 'immoderate', 'stem', 'allegory', 'economical', 'daytime', 'Newell', 'Moscow', 'archeology', 'ported', 'scandals', 'Blackfoot', 'leery', 'kilobit', 'empire', 'obliviousness', 'productions', 'sacrificed', 'ideals', 'enrolling', 'certainties', 'Capsicum', 'Brookdale', 'Markism', 'unkind', 'dyers', 'legislates', 'grotesquely', 'megawords', 'arbitrary', 'laughing', 'wildcats', 'thrower', 'sex', 'devils', 'Wehr', 'ablates', 'consume', 'gossips', 'doorways', 'Shari', 'advanced', 'enumerable', 'existentially', 'stunt', 'auctioneers', 'scheduler', 'blanching', 'petulance', 'perceptibly', 'vapors', 'progressed', 'rains', 'intercom', 'emergency', 'increased', 'fluctuating', 'Krishna', 'silken', 'reformed', 'transformation', 'easter', 'fares', 'comprehensible', 'trespasses', 'hallmark', 'tormenter', 'breastworks', 'brassiere', 'bladders', 'civet', 'death', 'transformer', 'tolerably', 'bugle', 'clergy', 'mantels', 'satin', 'Boswellizes', 'Bloomington', 'notifier', 'Filippo', 'circling', 'unassigned', 'dumbness', 'sentries', 'representativeness', 'souped', 'Klux', 'Kingstown', 'gerund', 'Russell', 'splices', 'bellow', 'bandies', 'beefers', 'cameramen', 'appalled', 'Ionian', 'butterball', 'Portland', 'pleaded', 'admiringly', 'pricks', 'hearty', 'corer', 'deliverable', 'accountably', 'mentors', 'accorded', 'acknowledgement', 'Lawrenceville', 'morphology', 'eucalyptus', 'Rena', 'enchanting', 'tighter', 'scholars', 'graduations', 'edges', 'Latinization', 'proficiency', 'monolithic', 'parenthesizing', 'defy', 'shames', 'enjoyment', 'Purdue', 'disagrees', 'barefoot', 'maims', 'flabbergast', 'dishonorable', 'interpolation', 'fanatics', 'dickens', 'abysses', 'adverse', 'components', 'bowl', 'belong', 'Pipestone', 'trainees', 'paw', 'pigtail', 'feed', 'whore', 'conditioner', 'Volstead', 'voices', 'strain', 'inhabits', 'Edwin', 'discourses', 'deigns', 'cruiser', 'biconvex', 'biking', 'depreciation', 'Harrison', 'Persian', 'stunning', 'agar', 'rope', 'wagoner', 'elections', 'reticulately', 'Cruz', 'pulpits', 'wilt', 'peels', 'plants', 'administerings', 'deepen', 'rubs', 'hence', 'dissension', 'implored', 'bereavement', 'abyss', 'Pennsylvania', 'benevolent', 'corresponding', 'Poseidon', 'inactive', 'butchers', 'Mach', 'woke', 'loading', 'utilizing', 'Hoosier', 'undo', 'Semitization', 'trigger', 'Mouthe', 'mark', 'disgracefully', 'copier', 'futility', 'gondola', 'algebraic', 'lecturers', 'sponged', 'instigators', 'looted', 'ether', 'trust', 'feeblest', 'sequencer', 'disjointness', 'congresses', 'Vicksburg', 'incompatibilities', 'commend', 'Luxembourg', 'reticulation', 'instructively', 'reconstructs', 'bricks', 'attache', 'Englishman', 'provocation', 'roughen', 'cynic', 'plugged', 'scrawls', 'antipode', 'injected', 'Daedalus', 'Burnsides', 'asker', 'confronter', 'merriment', 'disdain', 'thicket', 'stinker', 'great', 'tiers', 'oust', 'antipodes', 'Macintosh', 'tented', 'packages', 'Mediterraneanize', 'hurts', 'orthodontist', 'seeder', 'readying', 'babying', 'Florida', 'Sri', 'buckets', 'complementary', 'cartographer', 'chateaus', 'shaves', 'thinkable', 'Tehran', 'Gordian', 'Angles', 'arguable', 'bureau', 'smallest', 'fans', 'navigated', 'dipole', 'bootleg', 'distinctive', 'minimization', 'absorbed', 'surmised', 'Malawi', 'absorbent', 'close', 'conciseness', 'hopefully', 'declares', 'descent', 'trick', 'portend', 'unable', 'mildly', 'Morse', 'reference', 'scours', 'Caribbean', 'battlers', 'astringency', 'likelier', 'Byronizes', 'econometric', 'grad', 'steak', 'Austrian', 'ban', 'voting', 'Darlington', 'bison', 'Cetus', 'proclaim', 'Gilbertson', 'evictions', 'submittal', 'bearings', 'Gothicizer', 'settings', 'McMahon', 'densities', 'determinants', 'period', 'DeKastere', 'swindle', 'promptness', 'enablers', 'wordy', 'during', 'tables', 'responder', 'baffle', 'phosgene', 'muttering', 'limiters', 'custodian', 'prevented', 'Stouffer', 'waltz', 'Videotex', 'brainstorms', 'alcoholism', 'jab', 'shouldering', 'screening', 'explicitly', 'earner', 'commandment', 'French', 'scrutinizing', 'Gemma', 'capacitive', 'sheriff', 'herbivore', 'Betsey', 'Formosa', 'scorcher', 'font', 'damming', 'soldiers', 'flack', 'Marks', 'unlinking', 'serenely', 'rotating', 'converge', 'celebrities', 'unassailable', 'bawling', 'wording', 'silencing', 'scotch', 'coincided', 'masochists', 'graphs', 'pernicious', 'disease', 'depreciates', 'later', 'torus', 'interject', 'mutated', 'causer', 'messy', 'Bechtel', 'redundantly', 'profoundest', 'autopsy', 'philosophic', 'iterate', 'Poisson', 'horridly', 'silversmith', 'millennium', 'plunder', 'salmon', 'missioner', 'advances', 'provers', 'earthliness', 'manor', 'resurrectors', 'Dahl', 'canto', 'gangrene', 'gabler', 'ashore', 'frictionless', 'expansionism', 'emphasis', 'preservations', 'Duane', 'descend', 'isolated', 'firmware', 'dynamites', 'scrawled', 'cavemen', 'ponder', 'prosperity', 'squaw', 'vulnerable', 'opthalmic', 'Simms', 'unite', 'totallers', 'Waring', 'enforced', 'bridge', 'collecting', 'sublime', 'Moore', 'gobble', 'criticizes', 'daydreams', 'sedate', 'apples', 'Concordia', 'subsequence', 'distill', 'Allan', 'seizure', 'Isadore', 'Lancashire', 'spacings', 'corresponded', 'hobble', 'Boonton', 'genuineness', 'artifact', 'gratuities', 'interviewee', 'Vladimir', 'mailable', 'Bini', 'Kowalewski', 'interprets', 'bereave', 'evacuated', 'friend', 'tourists', 'crunched', 'soothsayer', 'fleetly', 'Romanizations', 'Medicaid', 'persevering', 'flimsy', 'doomsday', 'trillion', 'carcasses', 'guess', 'seersucker', 'ripping', 'affliction', 'wildest', 'spokes', 'sheaths', 'procreate', 'rusticates', 'Schapiro', 'thereafter', 'mistakenly', 'shelf', 'ruination', 'bushel', 'assuredly', 'corrupting', 'federation', 'portmanteau', 'wading', 'incendiary', 'thing', 'wanderers', 'messages', 'Paso', 'reexamined', 'freeings', 'denture', 'potting', 'disturber', 'laborer', 'comrade', 'intercommunicating', 'Pelham', 'reproach', 'Fenton', 'Alva', 'oasis', 'attending', 'cockpit', 'scout', 'Jude', 'gagging', 'jailed', 'crustaceans', 'dirt', 'exquisitely', 'Internet', 'blocker', 'smock', 'Troutman', 'neighboring', 'surprise', 'midscale', 'impart', 'badgering', 'fountain', 'Essen', 'societies', 'redresses', 'afterwards', 'puckering', 'silks', 'Blakey', 'sequel', 'greet', 'basements', 'Aubrey', 'helmsman', 'album', 'wheelers', 'easternmost', 'flock', 'ambassadors', 'astatine', 'supplant', 'gird', 'clockwork', 'foxes', 'rerouting', 'divisional', 'bends', 'spacer', 'physiologically', 'exquisite', 'concerts', 'unbridled', 'crossing', 'rock', 'leatherneck', 'Fortescue', 'reloading', 'Laramie', 'Tim', 'forlorn', 'revert', 'scarcer', 'spigot', 'equality', 'paranormal', 'aggrieves', 'pegs', 'committeewomen', 'documented', 'interrupt', 'emerald', 'Battelle', 'reconverted', 'anticipated', 'prejudices', 'drowsiness', 'trivialities', 'food', 'blackberries', 'Cyclades', 'tourist', 'branching', 'nugget', 'Asilomar', 'repairmen', 'Cowan', 'receptacles', 'nobler', 'Nebraskan', 'territorial', 'chickadee', 'bedbug', 'darted', 'vigilance', 'Octavia', 'summands', 'policemen', 'twirls', 'style', 'outlawing', 'specifiable', 'pang', 'Orpheus', 'epigram', 'Babel', 'butyrate', 'wishing', 'fiendish', 'accentuate', 'much', 'pulsed', 'adorned', 'arbiters', 'counted', 'Afrikaner', 'parameterizes', 'agenda', 'Americanism', 'referenda', 'derived', 'liquidity', 'trembling', 'lordly', 'Agway', 'Dillon', 'propellers', 'statement', 'stickiest', 'thankfully', 'autograph', 'parallel', 'impulse', 'Hamey', 'stylistic', 'disproved', 'inquirer', 'hoisting', 'residues', 'variant', 'colonials', 'dequeued', 'especial', 'Samoa', 'Polaris', 'dismisses', 'surpasses', 'prognosis', 'urinates', 'leaguers', 'ostriches', 'calculative', 'digested', 'divided', 'reconfigurer', 'Lakewood', 'illegalities', 'redundancy', 'approachability', 'masterly', 'cookery', 'crystallized', 'Dunham', 'exclaims', 'mainline', 'Australianizes', 'nationhood', 'pusher', 'ushers', 'paranoia', 'workstations', 'radiance', 'impedes', 'Minotaur', 'cataloging', 'bites', 'fashioning', 'Alsop', 'servants', 'Onondaga', 'paragraph', 'leadings', 'clients', 'Latrobe', 'Cornwallis', 'excitingly', 'calorimetric', 'savior', 'tandem', 'antibiotics', 'excuse', 'brushy', 'selfish', 'naive', 'becomes', 'towers', 'popularizes', 'engender', 'introducing', 'possession', 'slaughtered', 'marginally', 'Packards', 'parabola', 'utopia', 'automata', 'deterrent', 'chocolates', 'objectives', 'clannish', 'aspirin', 'ferociousness', 'primarily', 'armpit', 'handfuls', 'dangle', 'Manila', 'enlivened', 'decrease', 'phylum', 'hardy', 'objectively', 'baskets', 'chaired', 'Sepoy', 'deputy', 'blizzard', 'shootings', 'breathtaking', 'sticking', 'initials', 'epitomized', 'Forrest', 'cellular', 'amatory', 'radioed', 'horrified', 'Neva', 'simultaneous', 'delimiter', 'expulsion', 'Himmler', 'contradiction', 'Remus', 'Franklinizations', 'luggage', 'moisture', 'Jews', 'comptroller', 'brevity', 'contradictions', 'Ohio', 'active', 'babysit', 'China', 'youngest', 'superstition', 'clawing', 'raccoons', 'chose', 'shoreline', 'helmets', 'Jeffersonian', 'papered', 'kindergarten', 'reply', 'succinct', 'split', 'wriggle', 'suitcases', 'nonce', 'grinders', 'anthem', 'showcase', 'maimed', 'blue', 'obeys', 'unreported', 'perusing', 'recalculate', 'rancher', 'demonic', 'Lilliputianize', 'approximation', 'repents', 'yellowness', 'irritates', 'Ferber', 'flashlights', 'booty', 'Neanderthal', 'someday', 'foregoes', 'lingering', 'cloudiness', 'guy', 'consumer', 'Berkowitz', 'relics', 'interpolating', 'reappearing', 'advisements', 'Nolan', 'turrets', 'skeletal', 'skills', 'mammas', 'Winsett', 'wheelings', 'stiffen', 'monkeys', 'plainness', 'braziers', 'Leary', 'advisee', 'jack', 'verb', 'reinterpret', 'geometrical', 'trolleys', 'arboreal', 'overpowered', 'Cuzco', 'poetical', 'admirations', 'Hobbes', 'phonemes', 'Newsweek', 'agitator', 'finally', 'prophets', 'environment', 'easterners', 'precomputed', 'faults', 'rankly', 'swallowing', 'crawl', 'trolley', 'spreading', 'resourceful', 'go', 'demandingly', 'broader', 'spiders', 'Marsha', 'debris', 'operates', 'Dundee', 'alleles', 'crunchier', 'quizzical', 'hanging', 'Fisk'] wordsd = {} for word in words: wordsd[word] = 1 def collect_options(args, jobs, options): while args: arg = args.pop(0) if arg.startswith('-'): name = arg[1:] if name == 'options': fname = args.pop(0) d = {} execfile(fname, d) collect_options(list(d['options']), jobs, options) elif options.has_key(name): v = args.pop(0) if options[name] != None: raise ValueError( "Duplicate values for %s, %s and %s" % (name, v, options[name]) ) options[name] = v elif name == 'setup': options['setup'] = 1 elif globals().has_key(name.capitalize()+'Job'): job = name kw = {} while args and args[0].find("=") > 0: arg = args.pop(0).split('=') name, v = arg[0], '='.join(arg[1:]) if kw.has_key(name): raise ValueError( "Duplicate parameter %s for job %s" % (name, job) ) kw[name]=v if kw.has_key('frequency'): frequency = kw['frequency'] del kw['frequency'] else: frequency = 1 if kw.has_key('sleep'): sleep = float(kw['sleep']) del kw['sleep'] else: sleep = 0.0001 if kw.has_key('repeat'): repeatp = float(kw['repeat']) del kw['repeat'] else: repeatp = 0 jobs.append((job, kw, frequency, sleep, repeatp)) else: raise ValueError("not an option or job", name) else: raise ValueError("Expected an option", arg) def find_lib_python(): for b in os.getcwd(), os.path.split(sys.argv[0])[0]: for i in range(6): d = ['..']*i + ['lib', 'python'] p = os.path.join(b, *d) if os.path.isdir(p): return p raise ValueError("Couldn't find lib/python") def main(args=None): lib_python = find_lib_python() sys.path.insert(0, lib_python) if args is None: args = sys.argv[1:] if not args: print __doc__ sys.exit(0) print args random.seed(hash(tuple(args))) # always use the same for the given args options = {"mbox": None, "threads": None} jobdefs = [] collect_options(args, jobdefs, options) mboxes = {} if options["mbox"]: mboxes[options["mbox"]] = MBox(options["mbox"]) # Perform a ZConfig-based Zope initialization: zetup(os.path.join(lib_python, '..', '..', 'etc', 'zope.conf')) if options.has_key('setup'): setup(lib_python) else: import Zope2 Zope2.startup() jobs = JobProducer() for job, kw, frequency, sleep, repeatp in jobdefs: Job = globals()[job.capitalize()+'Job'] if getattr(Job, 'needs_mbox', 0): if not kw.has_key("mbox"): if not options["mbox"]: raise ValueError( "no mailbox (mbox option) file specified") kw['mbox'] = mboxes[options["mbox"]] else: if not mboxes.has_key[kw["mbox"]]: mboxes[kw['mbox']] = MBox[kw['mbox']] kw["mbox"] = mboxes[kw['mbox']] jobs.add(Job(**kw), frequency, sleep, repeatp) if not jobs: print "No jobs to execute" return threads = int(options['threads'] or '0') if threads > 1: threads = [threading.Thread(target=run, args=(jobs, i), name=str(i)) for i in range(threads)] for thread in threads: thread.start() for thread in threads: thread.join() else: run(jobs) def zetup(configfile_name): from Zope.Startup.options import ZopeOptions from Zope.Startup import handlers as h from App import config opts = ZopeOptions() opts.configfile = configfile_name opts.realize(args=[]) h.handleConfig(opts.configroot, opts.confighandlers) config.setConfiguration(opts.configroot) from Zope.Startup import dropPrivileges dropPrivileges(opts.configroot) if __name__ == '__main__': main() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/netspace.py0000644000175000017500000000642012214017464021453 0ustar arnauarnau#!/usr/bin/env python2.3 """Report on the net size of objects counting subobjects. usage: netspace.py [-P | -v] data.fs -P: do a pack first -v: print info for all objects, even if a traversal path isn't found """ import ZODB from ZODB.FileStorage import FileStorage from ZODB.utils import U64, get_pickle_metadata from ZODB.referencesf import referencesf def find_paths(root, maxdist): """Find Python attribute traversal paths for objects to maxdist distance. Starting at a root object, traverse attributes up to distance levels from the root, looking for persistent objects. Return a dict mapping oids to traversal paths. TODO: Assumes that the keys of the root are not themselves persistent objects. TODO: Doesn't traverse containers. """ paths = {} # Handle the root as a special case because it's a dict objs = [] for k, v in root.items(): oid = getattr(v, '_p_oid', None) objs.append((k, v, oid, 0)) for path, obj, oid, dist in objs: if oid is not None: paths[oid] = path if dist < maxdist: getattr(obj, 'foo', None) # unghostify try: items = obj.__dict__.items() except AttributeError: continue for k, v in items: oid = getattr(v, '_p_oid', None) objs.append(("%s.%s" % (path, k), v, oid, dist + 1)) return paths def main(path): fs = FileStorage(path, read_only=1) if PACK: fs.pack() db = ZODB.DB(fs) rt = db.open().root() paths = find_paths(rt, 3) def total_size(oid): cache = {} cache_size = 1000 def _total_size(oid, seen): v = cache.get(oid) if v is not None: return v data, serialno = fs.load(oid, '') size = len(data) for suboid in referencesf(data): if seen.has_key(suboid): continue seen[suboid] = 1 size += _total_size(suboid, seen) cache[oid] = size if len(cache) == cache_size: cache.popitem() return size return _total_size(oid, {}) keys = fs._index.keys() keys.sort() keys.reverse() if not VERBOSE: # If not running verbosely, don't print an entry for an object # unless it has an entry in paths. keys = filter(paths.has_key, keys) fmt = "%8s %5d %8d %s %s.%s" for oid in keys: data, serialno = fs.load(oid, '') mod, klass = get_pickle_metadata(data) refs = referencesf(data) path = paths.get(oid, '-') print fmt % (U64(oid), len(data), total_size(oid), path, mod, klass) def Main(): import sys import getopt global PACK global VERBOSE PACK = 0 VERBOSE = 0 try: opts, args = getopt.getopt(sys.argv[1:], 'Pv') path, = args except getopt.error, err: print err print __doc__ sys.exit(2) except ValueError: print "expected one argument, got", len(args) print __doc__ sys.exit(2) for o, v in opts: if o == '-P': PACK = 1 if o == '-v': VERBOSE += 1 main(path) if __name__ == "__main__": Main() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/manual_tests/0000755000175000017500000000000012214017464021774 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/manual_tests/testfstest.py0000644000175000017500000001255712214017464024570 0ustar arnauarnau"""Verify that fstest.py can find errors. Note: To run this test script fstest.py must be on your PYTHONPATH. """ from cStringIO import StringIO import re import struct import unittest import ZODB.tests.util import fstest from fstest import FormatError, U64 class TestCorruptedFS(ZODB.tests.util.TestCase): f = open('test-checker.fs', 'rb') datafs = f.read() f.close() del f def setUp(self): ZODB.tests.util.TestCase.setUp(self) self._temp = 'Data.fs' self._file = open(self._temp, 'wb') def tearDown(self): if not self._file.closed: self._file.close() ZODB.tests.util.TestCase.tearDown(self) def noError(self): if not self._file.closed: self._file.close() fstest.check(self._temp) def detectsError(self, rx): if not self._file.closed: self._file.close() try: fstest.check(self._temp) except FormatError, msg: mo = re.search(rx, str(msg)) self.failIf(mo is None, "unexpected error: %s" % msg) else: self.fail("fstest did not detect corruption") def getHeader(self): buf = self._datafs.read(16) if not buf: return 0, '' tl = U64(buf[8:]) return tl, buf def copyTransactions(self, n): """Copy at most n transactions from the good data""" f = self._datafs = StringIO(self.datafs) self._file.write(f.read(4)) for i in range(n): tl, data = self.getHeader() if not tl: return self._file.write(data) rec = f.read(tl - 8) self._file.write(rec) def testGood(self): self._file.write(self.datafs) self.noError() def testTwoTransactions(self): self.copyTransactions(2) self.noError() def testEmptyFile(self): self.detectsError("empty file") def testInvalidHeader(self): self._file.write('SF12') self.detectsError("invalid file header") def testTruncatedTransaction(self): self._file.write(self.datafs[:4+22]) self.detectsError("truncated") def testCheckpointFlag(self): self.copyTransactions(2) tl, data = self.getHeader() assert tl > 0, "ran out of good transaction data" self._file.write(data) self._file.write('c') self._file.write(self._datafs.read(tl - 9)) self.detectsError("checkpoint flag") def testInvalidStatus(self): self.copyTransactions(2) tl, data = self.getHeader() assert tl > 0, "ran out of good transaction data" self._file.write(data) self._file.write('Z') self._file.write(self._datafs.read(tl - 9)) self.detectsError("invalid status") def testTruncatedRecord(self): self.copyTransactions(3) tl, data = self.getHeader() assert tl > 0, "ran out of good transaction data" self._file.write(data) buf = self._datafs.read(tl / 2) self._file.write(buf) self.detectsError("truncated possibly") def testBadLength(self): self.copyTransactions(2) tl, data = self.getHeader() assert tl > 0, "ran out of good transaction data" self._file.write(data) buf = self._datafs.read(tl - 8) self._file.write(buf[0]) assert tl <= 1<<16, "can't use this transaction for this test" self._file.write("\777\777") self._file.write(buf[3:]) self.detectsError("invalid transaction header") def testDecreasingTimestamps(self): self.copyTransactions(0) tl, data = self.getHeader() buf = self._datafs.read(tl - 8) t1 = data + buf tl, data = self.getHeader() buf = self._datafs.read(tl - 8) t2 = data + buf self._file.write(t2[:8] + t1[8:]) self._file.write(t1[:8] + t2[8:]) self.detectsError("time-stamp") def testTruncatedData(self): # This test must re-write the transaction header length in # order to trigger the error in check_drec(). If it doesn't, # the truncated data record would also caught a truncated # transaction record. self.copyTransactions(1) tl, data = self.getHeader() pos = self._file.tell() self._file.write(data) buf = self._datafs.read(tl - 8) hdr = buf[:15] ul, dl, el = struct.unpack(">HHH", hdr[-6:]) self._file.write(buf[:15 + ul + dl + el]) data = buf[15 + ul + dl + el:] self._file.write(data[:24]) self._file.seek(pos + 8, 0) newlen = struct.pack(">II", 0, tl - (len(data) - 24)) self._file.write(newlen) self.detectsError("truncated at") def testBadDataLength(self): self.copyTransactions(1) tl, data = self.getHeader() self._file.write(data) buf = self._datafs.read(tl - 8) hdr = buf[:7] # write the transaction meta data ul, dl, el = struct.unpack(">HHH", hdr[-6:]) self._file.write(buf[:7 + ul + dl + el]) # write the first part of the data header data = buf[7 + ul + dl + el:] self._file.write(data[:24]) self._file.write("\000" * 4 + "\077" + "\000" * 3) self._file.write(data[32:]) self.detectsError("record exceeds transaction") if __name__ == "__main__": unittest.main() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/manual_tests/test-checker.fs0000644000175000017500000000144212214017464024710 0ustar arnauarnauFS21?ž4\B3“ initial database creation?ž4\B39(cPersistence PersistentMapping qNt.}qU _containerq}s.“?ž4™"£™š ?ž4™"£™4ŸY((U PersistenceqUPersistentMappingqtqNt.}qU _containerq}qUaUThis is a test.qss.š?ž5±\w1 ?ž5±\w¶A„((U PersistenceqUPersistentMappingqtqNt.}qU _containerq}q(UaUThis is a test.qK(Uq(hUPersistentMappingq ttQus.?ž5±\wAB((U PersistenceqUPersistentMappingqtqNt.}qU _containerq}qs.1?ž5.5°w  ?ž5.5°wz_((U PersistenceqUPersistentMappingqtqNt.}qU _containerq}qUaUThis is another test.qss. zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/fsoids.py0000644000175000017500000000446212214017464021144 0ustar arnauarnau#!/usr/bin/env python2.3 ############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """FileStorage oid-tracer. usage: fsoids.py [-f oid_file] Data.fs [oid]... Display information about all occurrences of specified oids in a FileStorage. This is meant for heavy debugging. This includes all revisions of the oids, all objects referenced by the oids, and all revisions of all objects referring to the oids. If specified, oid_file is an input text file, containing one oid per line. oids are specified as integers, in any of Python's integer notations (typically like 0x341a). One or more oids can also be specified on the command line. The output is grouped by oid, from smallest to largest, and sub-grouped by transaction, from oldest to newest. This will not alter the FileStorage, but running against a live FileStorage is not recommended (spurious error messages may result). See testfsoids.py for a tutorial doctest. """ import sys from ZODB.FileStorage.fsoids import Tracer def usage(): print __doc__ def main(): import getopt try: opts, args = getopt.getopt(sys.argv[1:], 'f:') if not args: usage() raise ValueError("Must specify a FileStorage") path = None for k, v in opts: if k == '-f': path = v except (getopt.error, ValueError): usage() raise c = Tracer(args[0]) for oid in args[1:]: as_int = int(oid, 0) # 0 == auto-detect base c.register_oids(as_int) if path is not None: for line in open(path): as_int = int(line, 0) c.register_oids(as_int) if not c.oids: raise ValueError("no oids specified") c.run() c.report() if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/__init__.py0000644000175000017500000000000212214017464021376 0ustar arnauarnau# zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/repozo.py0000644000175000017500000005040112214017464021165 0ustar arnauarnau#!/usr/bin/env python2.3 # repozo.py -- incremental and full backups of a Data.fs file. # # Originally written by Anthony Baxter # Significantly modified by Barry Warsaw """repozo.py -- incremental and full backups of a Data.fs file and index. Usage: %(program)s [options] Where: Exactly one of -B or -R must be specified: -B / --backup Backup current ZODB file. -R / --recover Restore a ZODB file from a backup. -v / --verbose Verbose mode. -h / --help Print this text and exit. -r dir --repository=dir Repository directory containing the backup files. This argument is required. The directory must already exist. You should not edit the files in this directory, or add your own files to it. Options for -B/--backup: -f file --file=file Source Data.fs file. This argument is required. -F / --full Force a full backup. By default, an incremental backup is made if possible (e.g., if a pack has occurred since the last incremental backup, a full backup is necessary). -Q / --quick Verify via md5 checksum only the last incremental written. This significantly reduces the disk i/o at the (theoretical) cost of inconsistency. This is a probabilistic way of determining whether a full backup is necessary. -z / --gzip Compress with gzip the backup files. Uses the default zlib compression level. By default, gzip compression is not used. -k / --kill-old-on-full If a full backup is created, remove any prior full or incremental backup files (and associated metadata files) from the repository directory. Options for -R/--recover: -D str --date=str Recover state as of this date. Specify UTC (not local) time. yyyy-mm-dd[-hh[-mm[-ss]]] By default, current time is used. -o filename --output=filename Write recovered ZODB to given file. By default, the file is written to stdout. Note: for the stdout case, the index file will **not** be restored automatically. """ import os import shutil import sys try: # the hashlib package is available from Python 2.5 from hashlib import md5 except ImportError: # the md5 package is deprecated in Python 2.6 from md5 import new as md5 import gzip import time import errno import getopt from ZODB.FileStorage import FileStorage program = sys.argv[0] BACKUP = 1 RECOVER = 2 COMMASPACE = ', ' READCHUNK = 16 * 1024 VERBOSE = False class WouldOverwriteFiles(Exception): pass class NoFiles(Exception): pass def usage(code, msg=''): outfp = sys.stderr if code == 0: outfp = sys.stdout print >> outfp, __doc__ % globals() if msg: print >> outfp, msg sys.exit(code) def log(msg, *args): if VERBOSE: # Use stderr here so that -v flag works with -R and no -o print >> sys.stderr, msg % args def parseargs(argv): global VERBOSE try: opts, args = getopt.getopt(argv, 'BRvhr:f:FQzkD:o:', ['backup', 'recover', 'verbose', 'help', 'repository=', 'file=', 'full', 'quick', 'gzip', 'kill-old-on-full', 'date=', 'output=', ]) except getopt.error, msg: usage(1, msg) class Options: mode = None # BACKUP or RECOVER file = None # name of input Data.fs file repository = None # name of directory holding backups full = False # True forces full backup date = None # -D argument, if any output = None # where to write recovered data; None = stdout quick = False # -Q flag state gzip = False # -z flag state killold = False # -k flag state options = Options() for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt in ('-v', '--verbose'): VERBOSE = True elif opt in ('-R', '--recover'): if options.mode is not None: usage(1, '-B and -R are mutually exclusive') options.mode = RECOVER elif opt in ('-B', '--backup'): if options.mode is not None: usage(1, '-B and -R are mutually exclusive') options.mode = BACKUP elif opt in ('-Q', '--quick'): options.quick = True elif opt in ('-f', '--file'): options.file = arg elif opt in ('-r', '--repository'): options.repository = arg elif opt in ('-F', '--full'): options.full = True elif opt in ('-D', '--date'): options.date = arg elif opt in ('-o', '--output'): options.output = arg elif opt in ('-z', '--gzip'): options.gzip = True elif opt in ('-k', '--kill-old-on-full'): options.killold = True else: assert False, (opt, arg) # Any other arguments are invalid if args: usage(1, 'Invalid arguments: ' + COMMASPACE.join(args)) # Sanity checks if options.mode is None: usage(1, 'Either --backup or --recover is required') if options.repository is None: usage(1, '--repository is required') if options.mode == BACKUP: if options.date is not None: log('--date option is ignored in backup mode') options.date = None if options.output is not None: log('--output option is ignored in backup mode') options.output = None else: assert options.mode == RECOVER if options.file is not None: log('--file option is ignored in recover mode') options.file = None if options.killold is not None: log('--kill-old-on-full option is ignored in recover mode') options.killold = None return options # afile is a Python file object, or created by gzip.open(). The latter # doesn't have a fileno() method, so to fsync it we need to reach into # its underlying file object. def fsync(afile): afile.flush() fileobject = getattr(afile, 'fileobj', afile) os.fsync(fileobject.fileno()) # Read bytes (no more than n, or to EOF if n is None) in chunks from the # current position in file fp. Pass each chunk as an argument to func(). # Return the total number of bytes read == the total number of bytes # passed in all to func(). Leaves the file position just after the # last byte read. def dofile(func, fp, n=None): bytesread = 0L while n is None or n > 0: if n is None: todo = READCHUNK else: todo = min(READCHUNK, n) data = fp.read(todo) if not data: break func(data) nread = len(data) bytesread += nread if n is not None: n -= nread return bytesread def checksum(fp, n): # Checksum the first n bytes of the specified file sum = md5() def func(data): sum.update(data) dofile(func, fp, n) return sum.hexdigest() def copyfile(options, dst, start, n): # Copy bytes from file src, to file dst, starting at offset start, for n # length of bytes. For robustness, we first write, flush and fsync # to a temp file, then rename the temp file at the end. sum = md5() ifp = open(options.file, 'rb') ifp.seek(start) tempname = os.path.join(os.path.dirname(dst), 'tmp.tmp') if options.gzip: ofp = gzip.open(tempname, 'wb') else: ofp = open(tempname, 'wb') def func(data): sum.update(data) ofp.write(data) ndone = dofile(func, ifp, n) assert ndone == n ifp.close() fsync(ofp) ofp.close() os.rename(tempname, dst) return sum.hexdigest() def concat(files, ofp=None): # Concatenate a bunch of files from the repository, output to `outfile' if # given. Return the number of bytes written and the md5 checksum of the # bytes. sum = md5() def func(data): sum.update(data) if ofp: ofp.write(data) bytesread = 0 for f in files: # Auto uncompress if f.endswith('fsz'): ifp = gzip.open(f, 'rb') else: ifp = open(f, 'rb') bytesread += dofile(func, ifp) ifp.close() if ofp: ofp.close() return bytesread, sum.hexdigest() def gen_filename(options, ext=None): if ext is None: if options.full: ext = '.fs' else: ext = '.deltafs' if options.gzip: ext += 'z' # Hook for testing now = getattr(options, 'test_now', time.gmtime()[:6]) t = now + (ext,) return '%04d-%02d-%02d-%02d-%02d-%02d%s' % t # Return a list of files needed to reproduce state at time options.date. # This is a list, in chronological order, of the .fs[z] and .deltafs[z] # files, from the time of the most recent full backup preceding # options.date, up to options.date. import re is_data_file = re.compile(r'\d{4}(?:-\d\d){5}\.(?:delta)?fsz?$').match del re def find_files(options): when = options.date if not when: when = gen_filename(options, '') log('looking for files between last full backup and %s...', when) all = filter(is_data_file, os.listdir(options.repository)) all.sort() all.reverse() # newest file first # Find the last full backup before date, then include all the # incrementals between that full backup and "when". needed = [] for fname in all: root, ext = os.path.splitext(fname) if root <= when: needed.append(fname) if ext in ('.fs', '.fsz'): break # Make the file names relative to the repository directory needed = [os.path.join(options.repository, f) for f in needed] # Restore back to chronological order needed.reverse() if needed: log('files needed to recover state as of %s:', when) for f in needed: log('\t%s', f) else: log('no files found') return needed # Scan the .dat file corresponding to the last full backup performed. # Return # # filename, startpos, endpos, checksum # # of the last incremental. If there is no .dat file, or the .dat file # is empty, return # # None, None, None, None def scandat(repofiles): fullfile = repofiles[0] datfile = os.path.splitext(fullfile)[0] + '.dat' fn = startpos = endpos = sum = None # assume .dat file missing or empty try: fp = open(datfile) except IOError, e: if e.errno <> errno.ENOENT: raise else: # We only care about the last one. lines = fp.readlines() fp.close() if lines: fn, startpos, endpos, sum = lines[-1].split() startpos = long(startpos) endpos = long(endpos) return fn, startpos, endpos, sum def delete_old_backups(options): # Delete all full backup files except for the most recent full backup file all = filter(is_data_file, os.listdir(options.repository)) all.sort() deletable = [] full = [] for fname in all: root, ext = os.path.splitext(fname) if ext in ('.fs', '.fsz'): full.append(fname) if ext in ('.fs', '.fsz', '.deltafs', '.deltafsz'): deletable.append(fname) # keep most recent full if not full: return recentfull = full.pop(-1) deletable.remove(recentfull) root, ext = os.path.splitext(recentfull) dat = root + '.dat' if dat in deletable: deletable.remove(dat) index = root + '.index' if index in deletable: deletable.remove(index) for fname in deletable: log('removing old backup file %s (and .dat / .index)', fname) root, ext = os.path.splitext(fname) try: os.unlink(os.path.join(options.repository, root + '.dat')) except OSError: pass try: os.unlink(os.path.join(options.repository, root + '.index')) except OSError: pass os.unlink(os.path.join(options.repository, fname)) def do_full_backup(options): options.full = True dest = os.path.join(options.repository, gen_filename(options)) if os.path.exists(dest): raise WouldOverwriteFiles('Cannot overwrite existing file: %s' % dest) # Find the file position of the last completed transaction. fs = FileStorage(options.file, read_only=True) # Note that the FileStorage ctor calls read_index() which scans the file # and returns "the position just after the last valid transaction record". # getSize() then returns this position, which is exactly what we want, # because we only want to copy stuff from the beginning of the file to the # last valid transaction record. pos = fs.getSize() # Save the storage index into the repository index_file = os.path.join(options.repository, gen_filename(options, '.index')) log('writing index') fs._index.save(pos, index_file) fs.close() log('writing full backup: %s bytes to %s', pos, dest) sum = copyfile(options, dest, 0, pos) # Write the data file for this full backup datfile = os.path.splitext(dest)[0] + '.dat' fp = open(datfile, 'w') print >> fp, dest, 0, pos, sum fp.flush() os.fsync(fp.fileno()) fp.close() if options.killold: delete_old_backups(options) def do_incremental_backup(options, reposz, repofiles): options.full = False dest = os.path.join(options.repository, gen_filename(options)) if os.path.exists(dest): raise WouldOverwriteFiles('Cannot overwrite existing file: %s' % dest) # Find the file position of the last completed transaction. fs = FileStorage(options.file, read_only=True) # Note that the FileStorage ctor calls read_index() which scans the file # and returns "the position just after the last valid transaction record". # getSize() then returns this position, which is exactly what we want, # because we only want to copy stuff from the beginning of the file to the # last valid transaction record. pos = fs.getSize() log('writing index') index_file = os.path.join(options.repository, gen_filename(options, '.index')) fs._index.save(pos, index_file) fs.close() log('writing incremental: %s bytes to %s', pos-reposz, dest) sum = copyfile(options, dest, reposz, pos - reposz) # The first file in repofiles points to the last full backup. Use this to # get the .dat file and append the information for this incrementatl to # that file. fullfile = repofiles[0] datfile = os.path.splitext(fullfile)[0] + '.dat' # This .dat file better exist. Let the exception percolate if not. fp = open(datfile, 'a') print >> fp, dest, reposz, pos, sum fp.flush() os.fsync(fp.fileno()) fp.close() def do_backup(options): repofiles = find_files(options) # See if we need to do a full backup if options.full or not repofiles: log('doing a full backup') do_full_backup(options) return srcsz = os.path.getsize(options.file) if options.quick: fn, startpos, endpos, sum = scandat(repofiles) # If the .dat file was missing, or was empty, do a full backup if (fn, startpos, endpos, sum) == (None, None, None, None): log('missing or empty .dat file (full backup)') do_full_backup(options) return # Has the file shrunk, possibly because of a pack? if srcsz < endpos: log('file shrunk, possibly because of a pack (full backup)') do_full_backup(options) return # Now check the md5 sum of the source file, from the last # incremental's start and stop positions. srcfp = open(options.file, 'rb') srcfp.seek(startpos) srcsum = checksum(srcfp, endpos-startpos) srcfp.close() log('last incremental file: %s', fn) log('last incremental checksum: %s', sum) log('source checksum range: [%s..%s], sum: %s', startpos, endpos, srcsum) if sum == srcsum: if srcsz == endpos: log('No changes, nothing to do') return log('doing incremental, starting at: %s', endpos) do_incremental_backup(options, endpos, repofiles) return else: # This was is much slower, and more disk i/o intensive, but it's also # more accurate since it checks the actual existing files instead of # the information in the .dat file. # # See if we can do an incremental, based on the files that already # exist. This call of concat() will not write an output file. reposz, reposum = concat(repofiles) log('repository state: %s bytes, md5: %s', reposz, reposum) # Get the md5 checksum of the source file, up to two file positions: # the entire size of the file, and up to the file position of the last # incremental backup. srcfp = open(options.file, 'rb') srcsum = checksum(srcfp, srcsz) srcfp.seek(0) srcsum_backedup = checksum(srcfp, reposz) srcfp.close() log('current state : %s bytes, md5: %s', srcsz, srcsum) log('backed up state : %s bytes, md5: %s', reposz, srcsum_backedup) # Has nothing changed? if srcsz == reposz and srcsum == reposum: log('No changes, nothing to do') return # Has the file shrunk, probably because of a pack? if srcsz < reposz: log('file shrunk, possibly because of a pack (full backup)') do_full_backup(options) return # The source file is larger than the repository. If the md5 checksums # match, then we know we can do an incremental backup. If they don't, # then perhaps the file was packed at some point (or a # non-transactional undo was performed, but this is deprecated). Only # do a full backup if forced to. if reposum == srcsum_backedup: log('doing incremental, starting at: %s', reposz) do_incremental_backup(options, reposz, repofiles) return # The checksums don't match, meaning the front of the source file has # changed. We'll need to do a full backup in that case. log('file changed, possibly because of a pack (full backup)') do_full_backup(options) def do_recover(options): # Find the first full backup at or before the specified date repofiles = find_files(options) if not repofiles: if options.date: raise NoFiles('No files in repository before %s', options.date) else: raise NoFiles('No files in repository') if options.output is None: log('Recovering file to stdout') outfp = sys.stdout else: log('Recovering file to %s', options.output) outfp = open(options.output, 'wb') reposz, reposum = concat(repofiles, outfp) if outfp <> sys.stdout: outfp.close() log('Recovered %s bytes, md5: %s', reposz, reposum) if options.output is not None: last_base = os.path.splitext(repofiles[-1])[0] source_index = '%s.index' % last_base target_index = '%s.index' % options.output if os.path.exists(source_index): log('Restoring index file %s to %s', source_index, target_index) shutil.copyfile(source_index, target_index) else: log('No index file to restore: %s', source_index) def main(argv=None): if argv is None: argv = sys.argv[1:] options = parseargs(argv) if options.mode == BACKUP: try: do_backup(options) except WouldOverwriteFiles, e: print >> sys.stderr, str(e) sys.exit(1) else: assert options.mode == RECOVER try: do_recover(options) except NoFiles, e: print >> sys.stderr, str(e) sys.exit(1) if __name__ == '__main__': main() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/README.txt0000644000175000017500000000735112214017464021001 0ustar arnauarnauThis directory contains a collection of utilities for managing ZODB databases. Some are more useful than others. If you install ZODB using distutils ("python setup.py install"), a few of these will be installed. Unless otherwise noted, these scripts are invoked with the name of the Data.fs file as their only argument. Example: checkbtrees.py data.fs. analyze.py -- a transaction analyzer for FileStorage Reports on the data in a FileStorage. The report is organized by class. It shows total data, as well as separate reports for current and historical revisions of objects. checkbtrees.py -- checks BTrees in a FileStorage for corruption Attempts to find all the BTrees contained in a Data.fs, calls their _check() methods, and runs them through BTrees.check.check(). fsdump.py -- summarize FileStorage contents, one line per revision Prints a report of FileStorage contents, with one line for each transaction and one line for each data record in that transaction. Includes time stamps, file positions, and class names. fsoids.py -- trace all uses of specified oids in a FileStorage For heavy debugging. A set of oids is specified by text file listing and/or command line. A report is generated showing all uses of these oids in the database: all new-revision creation/modifications, all references from all revisions of other objects, and all creation undos. fstest.py -- simple consistency checker for FileStorage usage: fstest.py [-v] data.fs The fstest tool will scan all the data in a FileStorage and report an error if it finds any corrupt transaction data. The tool will print a message when the first error is detected an exit. The tool accepts one or more -v arguments. If a single -v is used, it will print a line of text for each transaction record it encounters. If two -v arguments are used, it will also print a line of text for each object. The objects for a transaction will be printed before the transaction itself. Note: It does not check the consistency of the object pickles. It is possible for the damage to occur only in the part of the file that stores object pickles. Those errors will go undetected. space.py -- report space used by objects in a FileStorage usage: space.py [-v] data.fs This ignores revisions and versions. netspace.py -- hackish attempt to report on size of objects usage: netspace.py [-P | -v] data.fs -P: do a pack first -v: print info for all objects, even if a traversal path isn't found Traverses objects from the database root and attempts to calculate size of object, including all reachable subobjects. parsezeolog.py -- parse BLATHER logs from ZEO server This script may be obsolete. It has not been tested against the current log output of the ZEO server. Reports on the time and size of transactions committed by a ZEO server, by inspecting log messages at BLATHER level. repozo.py -- incremental backup utility for FileStorage Run the script with the -h option to see usage details. timeout.py -- script to test transaction timeout usage: timeout.py address delay [storage-name] This script connects to a storage, begins a transaction, calls store() and tpc_vote(), and then sleeps forever. This should trigger the transaction timeout feature of the server. zodbload.py -- exercise ZODB under a heavy synthesized Zope-like load See the module docstring for details. Note that this script requires Zope. New in ZODB3 3.1.4. fsrefs.py -- check FileStorage for dangling references fstail.py -- display the most recent transactions in a FileStorage usage: fstail.py [-n nxtn] data.fs The most recent ntxn transactions are displayed, to stdout. Optional argument -n specifies ntxn, and defaults to 10. migrate.py -- do a storage migration and gather statistics See the module docstring for details. zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/referrers.py0000644000175000017500000000201512214017464021644 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Compute a table of object id referrers $Id: referrers.py 120291 2011-02-11 23:50:41Z jim $ """ from ZODB.serialize import referencesf def referrers(storage): result = {} for transaction in storage.iterator(): for record in transaction: for oid in referencesf(record.data): result.setdefault(oid, []).append((record.oid, record.tid)) return result zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/migrateblobs.py0000644000175000017500000000523712214017464022330 0ustar arnauarnau############################################################################## # # Copyright (c) 2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """A script to migrate a blob directory into a different layout. """ import logging import optparse import os import shutil from ZODB.blob import FilesystemHelper from ZODB.utils import oid_repr def link_or_copy(f1, f2): try: os.link(f1, f2) except OSError: shutil.copy(f1, f2) # Check if we actually have link try: os.link except AttributeError: link_or_copy = shutil.copy def migrate(source, dest, layout): source_fsh = FilesystemHelper(source) source_fsh.create() dest_fsh = FilesystemHelper(dest, layout) dest_fsh.create() print "Migrating blob data from `%s` (%s) to `%s` (%s)" % ( source, source_fsh.layout_name, dest, dest_fsh.layout_name) for oid, path in source_fsh.listOIDs(): dest_path = dest_fsh.getPathForOID(oid, create=True) files = os.listdir(path) for file in files: source_file = os.path.join(path, file) dest_file = os.path.join(dest_path, file) link_or_copy(source_file, dest_file) print "\tOID: %s - %s files " % (oid_repr(oid), len(files)) def main(source=None, dest=None, layout="bushy"): usage = "usage: %prog [options] " description = ("Create the new directory and migrate all blob " "data to while using the new for " "") parser = optparse.OptionParser(usage=usage, description=description) parser.add_option("-l", "--layout", default=layout, type='choice', choices=['bushy', 'lawn'], help="Define the layout to use for the new directory " "(bushy or lawn). Default: %default") options, args = parser.parse_args() if not len(args) == 2: parser.error("source and destination must be given") logging.getLogger().addHandler(logging.StreamHandler()) logging.getLogger().setLevel(0) source, dest = args migrate(source, dest, options.layout) if __name__ == '__main__': main() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/checkbtrees.py0000644000175000017500000000610112214017464022127 0ustar arnauarnau#!/usr/bin/env python2.3 """Check the consistency of BTrees in a Data.fs usage: checkbtrees.py data.fs Try to find all the BTrees in a Data.fs, call their _check() methods, and run them through BTrees.check.check(). """ from types import IntType import ZODB from ZODB.FileStorage import FileStorage from BTrees.check import check # Set of oids we've already visited. Since the object structure is # a general graph, this is needed to prevent unbounded paths in the # presence of cycles. It's also helpful in eliminating redundant # checking when a BTree is pointed to by many objects. oids_seen = {} # Append (obj, path) to L if and only if obj is a persistent object # and we haven't seen it before. def add_if_new_persistent(L, obj, path): global oids_seen getattr(obj, '_', None) # unghostify if hasattr(obj, '_p_oid'): oid = obj._p_oid if not oids_seen.has_key(oid): L.append((obj, path)) oids_seen[oid] = 1 def get_subobjects(obj): getattr(obj, '_', None) # unghostify sub = [] try: attrs = obj.__dict__.items() except AttributeError: attrs = () for pair in attrs: sub.append(pair) # what if it is a mapping? try: items = obj.items() except AttributeError: items = () for k, v in items: if not isinstance(k, IntType): sub.append(("", k)) if not isinstance(v, IntType): sub.append(("[%s]" % repr(k), v)) # what if it is a sequence? i = 0 while 1: try: elt = obj[i] except: break sub.append(("[%d]" % i, elt)) i += 1 return sub def main(fname=None): if fname is None: import sys try: fname, = sys.argv[1:] except: print __doc__ sys.exit(2) fs = FileStorage(fname, read_only=1) cn = ZODB.DB(fs).open() rt = cn.root() todo = [] add_if_new_persistent(todo, rt, '') found = 0 while todo: obj, path = todo.pop(0) found += 1 if not path: print "", repr(obj) else: print path, repr(obj) mod = str(obj.__class__.__module__) if mod.startswith("BTrees"): if hasattr(obj, "_check"): try: obj._check() except AssertionError, msg: print "*" * 60 print msg print "*" * 60 try: check(obj) except AssertionError, msg: print "*" * 60 print msg print "*" * 60 if found % 100 == 0: cn.cacheMinimize() for k, v in get_subobjects(obj): if k.startswith('['): # getitem newpath = "%s%s" % (path, k) else: newpath = "%s.%s" % (path, k) add_if_new_persistent(todo, v, newpath) print "total", len(fs._index), "found", found if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/fsrefs.py0000644000175000017500000001346412214017464021147 0ustar arnauarnau#!/usr/bin/env python2.3 ############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Check FileStorage for dangling references. usage: fsrefs.py [-v] data.fs fsrefs.py checks object sanity by trying to load the current revision of every object O in the database, and also verifies that every object directly reachable from each such O exists in the database. It's hard to explain exactly what it does because it relies on undocumented features in Python's cPickle module: many of the crucial steps of loading an object are taken, but application objects aren't actually created. This saves a lot of time, and allows fsrefs to be run even if the code implementing the object classes isn't available. A read-only connection to the specified FileStorage is made, but it is not recommended to run fsrefs against a live FileStorage. Because a live FileStorage is mutating while fsrefs runs, it's not possible for fsrefs to get a wholly consistent view of the database across the entire time fsrefs is running; spurious error messages may result. fsrefs doesn't normally produce any output. If an object fails to load, the oid of the object is given in a message saying so, and if -v was specified then the traceback corresponding to the load failure is also displayed (this is the only effect of the -v flag). Three other kinds of errors are also detected, when an object O loads OK, and directly refers to a persistent object P but there's a problem with P: - If P doesn't exist in the database, a message saying so is displayed. The unsatisifiable reference to P is often called a "dangling reference"; P is called "missing" in the error output. - If the current state of the database is such that P's creation has been undone, then P can't be loaded either. This is also a kind of dangling reference, but is identified as "object creation was undone". - If P can't be loaded (but does exist in the database), a message saying that O refers to an object that can't be loaded is displayed. fsrefs also (indirectly) checks that the .index file is sane, because fsrefs uses the index to get its idea of what constitutes "all the objects in the database". Note these limitations: because fsrefs only looks at the current revision of objects, it does not attempt to load objects in versions, or non-current revisions of objects; therefore fsrefs cannot find problems in versions or in non-current revisions. """ import traceback import types from ZODB.FileStorage import FileStorage from ZODB.TimeStamp import TimeStamp from ZODB.utils import u64, oid_repr, get_pickle_metadata from ZODB.serialize import get_refs from ZODB.POSException import POSKeyError VERBOSE = 0 # There's a problem with oid. 'data' is its pickle, and 'serial' its # serial number. 'missing' is a list of (oid, class, reason) triples, # explaining what the problem(s) is(are). def report(oid, data, serial, missing): from_mod, from_class = get_pickle_metadata(data) if len(missing) > 1: plural = "s" else: plural = "" ts = TimeStamp(serial) print "oid %s %s.%s" % (hex(u64(oid)), from_mod, from_class) print "last updated: %s, tid=%s" % (ts, hex(u64(serial))) print "refers to invalid object%s:" % plural for oid, info, reason in missing: if isinstance(info, types.TupleType): description = "%s.%s" % info else: description = str(info) print "\toid %s %s: %r" % (oid_repr(oid), reason, description) print def main(path=None): if path is None: import sys import getopt opts, args = getopt.getopt(sys.argv[1:], "v") for k, v in opts: if k == "-v": VERBOSE += 1 path, = args fs = FileStorage(path, read_only=1) # Set of oids in the index that failed to load due to POSKeyError. # This is what happens if undo is applied to the transaction creating # the object (the oid is still in the index, but its current data # record has a backpointer of 0, and POSKeyError is raised then # because of that backpointer). undone = {} # Set of oids that were present in the index but failed to load. # This does not include oids in undone. noload = {} for oid in fs._index.keys(): try: data, serial = fs.load(oid, "") except (KeyboardInterrupt, SystemExit): raise except POSKeyError: undone[oid] = 1 except: if VERBOSE: traceback.print_exc() noload[oid] = 1 inactive = noload.copy() inactive.update(undone) for oid in fs._index.keys(): if oid in inactive: continue data, serial = fs.load(oid, "") refs = get_refs(data) missing = [] # contains 3-tuples of oid, klass-metadata, reason for ref, klass in refs: if klass is None: klass = '' if ref not in fs._index: missing.append((ref, klass, "missing")) if ref in noload: missing.append((ref, klass, "failed to load")) if ref in undone: missing.append((ref, klass, "object creation was undone")) if missing: report(oid, data, serial, missing) if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/analyze.py0000644000175000017500000001043612214017464021316 0ustar arnauarnau#!/usr/bin/env python2.4 # Based on a transaction analyzer by Matt Kromer. import pickle import sys import types from ZODB.FileStorage import FileStorage from cStringIO import StringIO class FakeError(Exception): def __init__(self, module, name): Exception.__init__(self) self.module = module self.name = name class FakeUnpickler(pickle.Unpickler): def find_class(self, module, name): raise FakeError(module, name) class Report: def __init__(self): self.OIDMAP = {} self.TYPEMAP = {} self.TYPESIZE = {} self.FREEMAP = {} self.USEDMAP = {} self.TIDS = 0 self.OIDS = 0 self.DBYTES = 0 self.COIDS = 0 self.CBYTES = 0 self.FOIDS = 0 self.FBYTES = 0 def shorten(s, n): l = len(s) if l <= n: return s while len(s) + 3 > n: # account for ... i = s.find(".") if i == -1: # In the worst case, just return the rightmost n bytes return s[-n:] else: s = s[i + 1:] l = len(s) return "..." + s def report(rep): print "Processed %d records in %d transactions" % (rep.OIDS, rep.TIDS) print "Average record size is %7.2f bytes" % (rep.DBYTES * 1.0 / rep.OIDS) print ("Average transaction size is %7.2f bytes" % (rep.DBYTES * 1.0 / rep.TIDS)) print "Types used:" fmt = "%-46s %7s %9s %6s %7s" fmtp = "%-46s %7d %9d %5.1f%% %7.2f" # per-class format fmts = "%46s %7d %8dk %5.1f%% %7.2f" # summary format print fmt % ("Class Name", "Count", "TBytes", "Pct", "AvgSize") print fmt % ('-'*46, '-'*7, '-'*9, '-'*5, '-'*7) typemap = rep.TYPEMAP.keys() typemap.sort() cumpct = 0.0 for t in typemap: pct = rep.TYPESIZE[t] * 100.0 / rep.DBYTES cumpct += pct print fmtp % (shorten(t, 46), rep.TYPEMAP[t], rep.TYPESIZE[t], pct, rep.TYPESIZE[t] * 1.0 / rep.TYPEMAP[t]) print fmt % ('='*46, '='*7, '='*9, '='*5, '='*7) print "%46s %7d %9s %6s %6.2fk" % ('Total Transactions', rep.TIDS, ' ', ' ', rep.DBYTES * 1.0 / rep.TIDS / 1024.0) print fmts % ('Total Records', rep.OIDS, rep.DBYTES / 1024.0, cumpct, rep.DBYTES * 1.0 / rep.OIDS) print fmts % ('Current Objects', rep.COIDS, rep.CBYTES / 1024.0, rep.CBYTES * 100.0 / rep.DBYTES, rep.CBYTES * 1.0 / rep.COIDS) if rep.FOIDS: print fmts % ('Old Objects', rep.FOIDS, rep.FBYTES / 1024.0, rep.FBYTES * 100.0 / rep.DBYTES, rep.FBYTES * 1.0 / rep.FOIDS) def analyze(path): fs = FileStorage(path, read_only=1) fsi = fs.iterator() report = Report() for txn in fsi: analyze_trans(report, txn) return report def analyze_trans(report, txn): report.TIDS += 1 for rec in txn: analyze_rec(report, rec) def get_type(record): try: unpickled = FakeUnpickler(StringIO(record.data)).load() except FakeError, err: return "%s.%s" % (err.module, err.name) except: raise classinfo = unpickled[0] if isinstance(classinfo, types.TupleType): mod, klass = classinfo return "%s.%s" % (mod, klass) else: return str(classinfo) def analyze_rec(report, record): oid = record.oid report.OIDS += 1 if record.data is None: # No pickle -- aborted version or undo of object creation. return try: size = len(record.data) # Ignores various overhead report.DBYTES += size if oid not in report.OIDMAP: type = get_type(record) report.OIDMAP[oid] = type report.USEDMAP[oid] = size report.COIDS += 1 report.CBYTES += size else: type = report.OIDMAP[oid] fsize = report.USEDMAP[oid] report.FREEMAP[oid] = report.FREEMAP.get(oid, 0) + fsize report.USEDMAP[oid] = size report.FOIDS += 1 report.FBYTES += fsize report.CBYTES += size - fsize report.TYPEMAP[type] = report.TYPEMAP.get(type, 0) + 1 report.TYPESIZE[type] = report.TYPESIZE.get(type, 0) + size except Exception, err: print err if __name__ == "__main__": path = sys.argv[1] report(analyze(path)) zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/fstail.py0000644000175000017500000000316512214017464021136 0ustar arnauarnau#!/usr/bin/env python2.3 ############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Tool to dump the last few transactions from a FileStorage.""" from ZODB.fstools import prev_txn import binascii import getopt import sys try: from hashlib import sha1 except ImportError: from sha import sha as sha1 def main(path, ntxn): f = open(path, "rb") f.seek(0, 2) th = prev_txn(f) i = ntxn while th and i > 0: hash = sha1(th.get_raw_data()).digest() l = len(str(th.get_timestamp())) + 1 th.read_meta() print "%s: hash=%s" % (th.get_timestamp(), binascii.hexlify(hash)) print ("user=%r description=%r length=%d offset=%d" % (th.user, th.descr, th.length, th.get_data_offset())) print th = th.prev_txn() i -= 1 def Main(): ntxn = 10 opts, args = getopt.getopt(sys.argv[1:], "n:") path, = args for k, v in opts: if k == '-n': ntxn = int(v) main(path, ntxn) if __name__ == "__main__": Main() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/tests/0000755000175000017500000000000012214017464020437 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/tests/test_doc.py0000644000175000017500000000234212214017464022616 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import re import unittest import ZODB.tests.util import zope.testing.renormalizing checker = zope.testing.renormalizing.RENormalizing([ (re.compile( '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+'), '2007-11-10 15:18:48.543001'), (re.compile('hash=[0-9a-f]{40}'), 'hash=b16422d09fabdb45d4e4325e4b42d7d6f021d3c3')]) def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite( 'referrers.txt', 'fstail.txt', setUp=ZODB.tests.util.setUp, tearDown=ZODB.tests.util.tearDown, checker=checker), )) zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/tests/referrers.txt0000644000175000017500000000250412214017464023200 0ustar arnauarnauGetting Object Referrers ======================== The referrers module provides a way to get object referrers. It provides a referrers method that takes an iterable storage object. It returns a dictionary mapping object ids to lists of referrer object versions, which each version is a tuple an object id nd serial nummber. To see how this works, we'll create a small database: >>> import transaction >>> from persistent.mapping import PersistentMapping >>> from ZODB.FileStorage import FileStorage >>> from ZODB.DB import DB >>> import os, tempfile >>> dest = tempfile.mkdtemp() >>> fs = FileStorage(os.path.join(dest, 'Data.fs')) >>> db = DB(fs) >>> conn = db.open() >>> conn.root()['a'] = PersistentMapping() >>> conn.root()['b'] = PersistentMapping() >>> transaction.commit() >>> roid = conn.root()._p_oid >>> aoid = conn.root()['a']._p_oid >>> boid = conn.root()['b']._p_oid >>> s1 = conn.root()['b']._p_serial >>> conn.root()['a']['b'] = conn.root()['b'] >>> transaction.commit() >>> s2 = conn.root()['a']._p_serial Now we'll get the storage and compute the referrers: >>> import ZODB.scripts.referrers >>> referrers = ZODB.scripts.referrers.referrers(fs) >>> referrers[boid] == [(roid, s1), (aoid, s2)] True .. Cleanup >>> db.close() zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/tests/test_repozo.py0000644000175000017500000010332312214017464023370 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest import os try: # the hashlib package is available from Python 2.5 from hashlib import md5 except ImportError: # the md5 package is deprecated in Python 2.6 from md5 import new as md5 import ZODB.tests.util # layer used at class scope _NOISY = os.environ.get('NOISY_REPOZO_TEST_OUTPUT') class OurDB: _file_name = None def __init__(self, dir): from BTrees.OOBTree import OOBTree import transaction self.dir = dir self.getdb() conn = self.db.open() conn.root()['tree'] = OOBTree() transaction.commit() self.pos = self.db.storage._pos self.close() def getdb(self): from ZODB import DB from ZODB.FileStorage import FileStorage self._file_name = storage_filename = os.path.join(self.dir, 'Data.fs') storage = FileStorage(storage_filename) self.db = DB(storage) def gettree(self): self.getdb() conn = self.db.open() return conn.root()['tree'] def pack(self): self.getdb() self.db.pack() def close(self): if self.db is not None: self.db.close() self.db = None def mutate(self): # Make random mutations to the btree in the database. import random import transaction tree = self.gettree() for dummy in range(100): if random.random() < 0.6: tree[random.randrange(100000)] = random.randrange(100000) else: keys = tree.keys() if keys: del tree[keys[0]] transaction.commit() self.pos = self.db.storage._pos self.maxkey = self.db.storage._oid self.close() class FileopsBase: def _makeChunks(self): from ZODB.scripts.repozo import READCHUNK return ['x' * READCHUNK, 'y' * READCHUNK, 'z'] def _makeFile(self, text=None): from StringIO import StringIO if text is None: text = ''.join(self._makeChunks()) return StringIO(text) class Test_dofile(unittest.TestCase, FileopsBase): def _callFUT(self, func, fp, n): from ZODB.scripts.repozo import dofile return dofile(func, fp, n) def test_empty_read_all(self): chunks = [] file = self._makeFile('') bytes = self._callFUT(chunks.append, file, None) self.assertEqual(bytes, 0) self.assertEqual(chunks, []) def test_empty_read_count(self): chunks = [] file = self._makeFile('') bytes = self._callFUT(chunks.append, file, 42) self.assertEqual(bytes, 0) self.assertEqual(chunks, []) def test_nonempty_read_all(self): chunks = [] file = self._makeFile() bytes = self._callFUT(chunks.append, file, None) self.assertEqual(bytes, file.tell()) self.assertEqual(chunks, self._makeChunks()) def test_nonempty_read_count(self): chunks = [] file = self._makeFile() bytes = self._callFUT(chunks.append, file, 42) self.assertEqual(bytes, 42) self.assertEqual(chunks, ['x' * 42]) class Test_checksum(unittest.TestCase, FileopsBase): def _callFUT(self, fp, n): from ZODB.scripts.repozo import checksum return checksum(fp, n) def test_empty_read_all(self): file = self._makeFile('') sum = self._callFUT(file, None) self.assertEqual(sum, md5('').hexdigest()) def test_empty_read_count(self): file = self._makeFile('') sum = self._callFUT(file, 42) self.assertEqual(sum, md5('').hexdigest()) def test_nonempty_read_all(self): file = self._makeFile() sum = self._callFUT(file, None) self.assertEqual(sum, md5(''.join(self._makeChunks())).hexdigest()) def test_nonempty_read_count(self): chunks = [] file = self._makeFile() sum = self._callFUT(file, 42) self.assertEqual(sum, md5('x' * 42).hexdigest()) class OptionsTestBase: _repository_directory = None _data_directory = None def tearDown(self): if self._repository_directory is not None: from shutil import rmtree rmtree(self._repository_directory) if self._data_directory is not None: from shutil import rmtree rmtree(self._data_directory) def _makeOptions(self, **kw): import tempfile self._repository_directory = tempfile.mkdtemp() class Options(object): repository = self._repository_directory def __init__(self, **kw): self.__dict__.update(kw) return Options(**kw) class Test_copyfile(OptionsTestBase, unittest.TestCase): def _callFUT(self, options, dest, start, n): from ZODB.scripts.repozo import copyfile return copyfile(options, dest, start, n) def test_no_gzip(self): options = self._makeOptions(gzip=False) source = options.file = os.path.join(self._repository_directory, 'source.txt') f = open(source, 'wb') f.write('x' * 1000) f.close() target = os.path.join(self._repository_directory, 'target.txt') sum = self._callFUT(options, target, 0, 100) self.assertEqual(sum, md5('x' * 100).hexdigest()) self.assertEqual(open(target, 'rb').read(), 'x' * 100) def test_w_gzip(self): import gzip options = self._makeOptions(gzip=True) source = options.file = os.path.join(self._repository_directory, 'source.txt') f = open(source, 'wb') f.write('x' * 1000) f.close() target = os.path.join(self._repository_directory, 'target.txt') sum = self._callFUT(options, target, 0, 100) self.assertEqual(sum, md5('x' * 100).hexdigest()) self.assertEqual(gzip.open(target, 'rb').read(), 'x' * 100) class Test_concat(OptionsTestBase, unittest.TestCase): def _callFUT(self, files, ofp): from ZODB.scripts.repozo import concat return concat(files, ofp) def _makeFile(self, name, text, gzip_file=False): import gzip import tempfile if self._repository_directory is None: self._repository_directory = tempfile.mkdtemp() fqn = os.path.join(self._repository_directory, name) if gzip_file: f = gzip.open(fqn, 'wb') else: f = open(fqn, 'wb') f.write(text) f.flush() f.close() return fqn def test_empty_list_no_ofp(self): bytes, sum = self._callFUT([], None) self.assertEqual(bytes, 0) self.assertEqual(sum, md5('').hexdigest()) def test_w_plain_files_no_ofp(self): files = [self._makeFile(x, x, False) for x in 'ABC'] bytes, sum = self._callFUT(files, None) self.assertEqual(bytes, 3) self.assertEqual(sum, md5('ABC').hexdigest()) def test_w_gzipped_files_no_ofp(self): files = [self._makeFile('%s.fsz' % x, x, True) for x in 'ABC'] bytes, sum = self._callFUT(files, None) self.assertEqual(bytes, 3) self.assertEqual(sum, md5('ABC').hexdigest()) def test_w_ofp(self): class Faux: _closed = False def __init__(self): self._written = [] def write(self, data): self._written.append(data) def close(self): self._closed = True files = [self._makeFile(x, x, False) for x in 'ABC'] ofp = Faux() bytes, sum = self._callFUT(files, ofp) self.assertEqual(ofp._written, [x for x in 'ABC']) self.failUnless(ofp._closed) _marker = object() class Test_gen_filename(OptionsTestBase, unittest.TestCase): def _callFUT(self, options, ext=_marker): from ZODB.scripts.repozo import gen_filename if ext is _marker: return gen_filename(options) return gen_filename(options, ext) def test_explicit_ext(self): options = self._makeOptions(test_now = (2010, 5, 14, 12, 52, 31)) fn = self._callFUT(options, '.txt') self.assertEqual(fn, '2010-05-14-12-52-31.txt') def test_full_no_gzip(self): options = self._makeOptions(test_now = (2010, 5, 14, 12, 52, 31), full = True, gzip = False, ) fn = self._callFUT(options) self.assertEqual(fn, '2010-05-14-12-52-31.fs') def test_full_w_gzip(self): options = self._makeOptions(test_now = (2010, 5, 14, 12, 52, 31), full = True, gzip = True, ) fn = self._callFUT(options) self.assertEqual(fn, '2010-05-14-12-52-31.fsz') def test_incr_no_gzip(self): options = self._makeOptions(test_now = (2010, 5, 14, 12, 52, 31), full = False, gzip = False, ) fn = self._callFUT(options) self.assertEqual(fn, '2010-05-14-12-52-31.deltafs') def test_incr_w_gzip(self): options = self._makeOptions(test_now = (2010, 5, 14, 12, 52, 31), full = False, gzip = True, ) fn = self._callFUT(options) self.assertEqual(fn, '2010-05-14-12-52-31.deltafsz') class Test_find_files(OptionsTestBase, unittest.TestCase): def _callFUT(self, options): from ZODB.scripts.repozo import find_files return find_files(options) def _makeFile(self, hour, min, sec, ext): # call _makeOptions first! name = '2010-05-14-%02d-%02d-%02d%s' % (hour, min, sec, ext) fqn = os.path.join(self._repository_directory, name) f = open(fqn, 'wb') f.write(name) f.flush() f.close() return fqn def test_no_files(self): options = self._makeOptions(date='2010-05-14-13-30-57') found = self._callFUT(options) self.assertEqual(found, []) def test_explicit_date(self): options = self._makeOptions(date='2010-05-14-13-30-57') files = [] for h, m, s, e in [(2, 13, 14, '.fs'), (2, 13, 14, '.dat'), (3, 14, 15, '.deltafs'), (4, 14, 15, '.deltafs'), (5, 14, 15, '.deltafs'), (12, 13, 14, '.fs'), (12, 13, 14, '.dat'), (13, 14, 15, '.deltafs'), (14, 15, 16, '.deltafs'), ]: files.append(self._makeFile(h, m, s, e)) found = self._callFUT(options) # Older files, .dat file not included self.assertEqual(found, [files[5], files[7]]) def test_using_gen_filename(self): options = self._makeOptions(date=None, test_now=(2010, 5, 14, 13, 30, 57)) files = [] for h, m, s, e in [(2, 13, 14, '.fs'), (2, 13, 14, '.dat'), (3, 14, 15, '.deltafs'), (4, 14, 15, '.deltafs'), (5, 14, 15, '.deltafs'), (12, 13, 14, '.fs'), (12, 13, 14, '.dat'), (13, 14, 15, '.deltafs'), (14, 15, 16, '.deltafs'), ]: files.append(self._makeFile(h, m, s, e)) found = self._callFUT(options) # Older files, .dat file not included self.assertEqual(found, [files[5], files[7]]) class Test_scandat(OptionsTestBase, unittest.TestCase): def _callFUT(self, repofiles): from ZODB.scripts.repozo import scandat return scandat(repofiles) def test_no_dat_file(self): options = self._makeOptions() fsfile = os.path.join(self._repository_directory, 'foo.fs') fn, startpos, endpos, sum = self._callFUT([fsfile]) self.assertEqual(fn, None) self.assertEqual(startpos, None) self.assertEqual(endpos, None) self.assertEqual(sum, None) def test_empty_dat_file(self): options = self._makeOptions() fsfile = os.path.join(self._repository_directory, 'foo.fs') datfile = os.path.join(self._repository_directory, 'foo.dat') open(datfile, 'wb').close() fn, startpos, endpos, sum = self._callFUT([fsfile]) self.assertEqual(fn, None) self.assertEqual(startpos, None) self.assertEqual(endpos, None) self.assertEqual(sum, None) def test_single_line(self): options = self._makeOptions() fsfile = os.path.join(self._repository_directory, 'foo.fs') datfile = os.path.join(self._repository_directory, 'foo.dat') f = open(datfile, 'wb') f.write('foo.fs 0 123 ABC\n') f.flush() f.close() fn, startpos, endpos, sum = self._callFUT([fsfile]) self.assertEqual(fn, 'foo.fs') self.assertEqual(startpos, 0) self.assertEqual(endpos, 123) self.assertEqual(sum, 'ABC') def test_multiple_lines(self): options = self._makeOptions() fsfile = os.path.join(self._repository_directory, 'foo.fs') datfile = os.path.join(self._repository_directory, 'foo.dat') f = open(datfile, 'wb') f.write('foo.fs 0 123 ABC\n') f.write('bar.deltafs 123 456 DEF\n') f.flush() f.close() fn, startpos, endpos, sum = self._callFUT([fsfile]) self.assertEqual(fn, 'bar.deltafs') self.assertEqual(startpos, 123) self.assertEqual(endpos, 456) self.assertEqual(sum, 'DEF') class Test_delete_old_backups(OptionsTestBase, unittest.TestCase): def _makeOptions(self, filenames=()): options = super(Test_delete_old_backups, self)._makeOptions() for filename in filenames: fqn = os.path.join(options.repository, filename) f = open(fqn, 'wb') f.write('testing delete_old_backups') f.close() return options def _callFUT(self, options=None, filenames=()): from ZODB.scripts.repozo import delete_old_backups if options is None: options = self._makeOptions(filenames) return delete_old_backups(options) def test_empty_dir_doesnt_raise(self): self._callFUT() self.assertEqual(len(os.listdir(self._repository_directory)), 0) def test_no_repozo_files_doesnt_raise(self): FILENAMES = ['bogus.txt', 'not_a_repozo_file'] self._callFUT(filenames=FILENAMES) remaining = os.listdir(self._repository_directory) self.assertEqual(len(remaining), len(FILENAMES)) for name in FILENAMES: fqn = os.path.join(self._repository_directory, name) self.failUnless(os.path.isfile(fqn)) def test_doesnt_remove_current_repozo_files(self): FILENAMES = ['2009-12-20-10-08-03.fs', '2009-12-20-10-08-03.dat', '2009-12-20-10-08-03.index', ] self._callFUT(filenames=FILENAMES) remaining = os.listdir(self._repository_directory) self.assertEqual(len(remaining), len(FILENAMES)) for name in FILENAMES: fqn = os.path.join(self._repository_directory, name) self.failUnless(os.path.isfile(fqn)) def test_removes_older_repozo_files(self): OLDER_FULL = ['2009-12-20-00-01-03.fs', '2009-12-20-00-01-03.dat', '2009-12-20-00-01-03.index', ] DELTAS = ['2009-12-21-00-00-01.deltafs', '2009-12-21-00-00-01.index', '2009-12-22-00-00-01.deltafs', '2009-12-22-00-00-01.index', ] CURRENT_FULL = ['2009-12-23-00-00-01.fs', '2009-12-23-00-00-01.dat', '2009-12-23-00-00-01.index', ] FILENAMES = OLDER_FULL + DELTAS + CURRENT_FULL self._callFUT(filenames=FILENAMES) remaining = os.listdir(self._repository_directory) self.assertEqual(len(remaining), len(CURRENT_FULL)) for name in OLDER_FULL: fqn = os.path.join(self._repository_directory, name) self.failIf(os.path.isfile(fqn)) for name in DELTAS: fqn = os.path.join(self._repository_directory, name) self.failIf(os.path.isfile(fqn)) for name in CURRENT_FULL: fqn = os.path.join(self._repository_directory, name) self.failUnless(os.path.isfile(fqn)) def test_removes_older_repozo_files_zipped(self): OLDER_FULL = ['2009-12-20-00-01-03.fsz', '2009-12-20-00-01-03.dat', '2009-12-20-00-01-03.index', ] DELTAS = ['2009-12-21-00-00-01.deltafsz', '2009-12-21-00-00-01.index', '2009-12-22-00-00-01.deltafsz', '2009-12-22-00-00-01.index', ] CURRENT_FULL = ['2009-12-23-00-00-01.fsz', '2009-12-23-00-00-01.dat', '2009-12-23-00-00-01.index', ] FILENAMES = OLDER_FULL + DELTAS + CURRENT_FULL self._callFUT(filenames=FILENAMES) remaining = os.listdir(self._repository_directory) self.assertEqual(len(remaining), len(CURRENT_FULL)) for name in OLDER_FULL: fqn = os.path.join(self._repository_directory, name) self.failIf(os.path.isfile(fqn)) for name in DELTAS: fqn = os.path.join(self._repository_directory, name) self.failIf(os.path.isfile(fqn)) for name in CURRENT_FULL: fqn = os.path.join(self._repository_directory, name) self.failUnless(os.path.isfile(fqn)) class Test_do_full_backup(OptionsTestBase, unittest.TestCase): def _callFUT(self, options): from ZODB.scripts.repozo import do_full_backup return do_full_backup(options) def _makeDB(self): import tempfile datadir = self._data_directory = tempfile.mkdtemp() return OurDB(self._data_directory) def test_dont_overwrite_existing_file(self): from ZODB.scripts.repozo import WouldOverwriteFiles from ZODB.scripts.repozo import gen_filename db = self._makeDB() options = self._makeOptions(full=True, file=db._file_name, gzip=False, test_now = (2010, 5, 14, 10, 51, 22), ) f = open(os.path.join(self._repository_directory, gen_filename(options)), 'w') f.write('TESTING') f.flush() f.close() self.assertRaises(WouldOverwriteFiles, self._callFUT, options) def test_empty(self): import struct from ZODB.scripts.repozo import gen_filename from ZODB.fsIndex import fsIndex db = self._makeDB() options = self._makeOptions(file=db._file_name, gzip=False, killold=False, test_now = (2010, 5, 14, 10, 51, 22), ) self._callFUT(options) target = os.path.join(self._repository_directory, gen_filename(options)) original = open(db._file_name, 'rb').read() self.assertEqual(open(target, 'rb').read(), original) datfile = os.path.join(self._repository_directory, gen_filename(options, '.dat')) self.assertEqual(open(datfile).read(), '%s 0 %d %s\n' % (target, len(original), md5(original).hexdigest())) ndxfile = os.path.join(self._repository_directory, gen_filename(options, '.index')) ndx_info = fsIndex.load(ndxfile) self.assertEqual(ndx_info['pos'], len(original)) index = ndx_info['index'] pZero = struct.pack(">Q", 0) pOne = struct.pack(">Q", 1) self.assertEqual(index.minKey(), pZero) self.assertEqual(index.maxKey(), pOne) class Test_do_incremental_backup(OptionsTestBase, unittest.TestCase): def _callFUT(self, options, reposz, repofiles): from ZODB.scripts.repozo import do_incremental_backup return do_incremental_backup(options, reposz, repofiles) def _makeDB(self): import tempfile datadir = self._data_directory = tempfile.mkdtemp() return OurDB(self._data_directory) def test_dont_overwrite_existing_file(self): from ZODB.scripts.repozo import WouldOverwriteFiles from ZODB.scripts.repozo import gen_filename from ZODB.scripts.repozo import find_files db = self._makeDB() options = self._makeOptions(full=False, file=db._file_name, gzip=False, test_now = (2010, 5, 14, 10, 51, 22), date = None, ) f = open(os.path.join(self._repository_directory, gen_filename(options)), 'w') f.write('TESTING') f.flush() f.close() repofiles = find_files(options) self.assertRaises(WouldOverwriteFiles, self._callFUT, options, 0, repofiles) def test_no_changes(self): import struct from ZODB.scripts.repozo import gen_filename from ZODB.fsIndex import fsIndex db = self._makeDB() oldpos = db.pos options = self._makeOptions(file=db._file_name, gzip=False, killold=False, test_now = (2010, 5, 14, 10, 51, 22), date = None, ) fullfile = os.path.join(self._repository_directory, '2010-05-14-00-00-00.fs') original = open(db._file_name, 'rb').read() last = len(original) f = open(fullfile, 'wb') f.write(original) f.flush() f.close() datfile = os.path.join(self._repository_directory, '2010-05-14-00-00-00.dat') repofiles = [fullfile, datfile] self._callFUT(options, oldpos, repofiles) target = os.path.join(self._repository_directory, gen_filename(options)) self.assertEqual(open(target, 'rb').read(), '') self.assertEqual(open(datfile).read(), '%s %d %d %s\n' % (target, oldpos, oldpos, md5('').hexdigest())) ndxfile = os.path.join(self._repository_directory, gen_filename(options, '.index')) ndx_info = fsIndex.load(ndxfile) self.assertEqual(ndx_info['pos'], oldpos) index = ndx_info['index'] pZero = struct.pack(">Q", 0) pOne = struct.pack(">Q", 1) self.assertEqual(index.minKey(), pZero) self.assertEqual(index.maxKey(), pOne) def test_w_changes(self): import struct from ZODB.scripts.repozo import gen_filename from ZODB.fsIndex import fsIndex db = self._makeDB() oldpos = db.pos options = self._makeOptions(file=db._file_name, gzip=False, killold=False, test_now = (2010, 5, 14, 10, 51, 22), date = None, ) fullfile = os.path.join(self._repository_directory, '2010-05-14-00-00-00.fs') original = open(db._file_name, 'rb').read() f = open(fullfile, 'wb') f.write(original) f.flush() f.close() datfile = os.path.join(self._repository_directory, '2010-05-14-00-00-00.dat') repofiles = [fullfile, datfile] db.mutate() newpos = db.pos self._callFUT(options, oldpos, repofiles) target = os.path.join(self._repository_directory, gen_filename(options)) f = open(db._file_name, 'rb') f.seek(oldpos) increment = f.read() self.assertEqual(open(target, 'rb').read(), increment) self.assertEqual(open(datfile).read(), '%s %d %d %s\n' % (target, oldpos, newpos, md5(increment).hexdigest())) ndxfile = os.path.join(self._repository_directory, gen_filename(options, '.index')) ndx_info = fsIndex.load(ndxfile) self.assertEqual(ndx_info['pos'], newpos) index = ndx_info['index'] pZero = struct.pack(">Q", 0) self.assertEqual(index.minKey(), pZero) self.assertEqual(index.maxKey(), db.maxkey) class Test_do_recover(OptionsTestBase, unittest.TestCase): def _callFUT(self, options): from ZODB.scripts.repozo import do_recover return do_recover(options) def _makeFile(self, hour, min, sec, ext, text=None): # call _makeOptions first! name = '2010-05-14-%02d-%02d-%02d%s' % (hour, min, sec, ext) if text is None: text = name fqn = os.path.join(self._repository_directory, name) f = open(fqn, 'wb') f.write(text) f.flush() f.close() return fqn def test_no_files(self): from ZODB.scripts.repozo import NoFiles options = self._makeOptions(date=None, test_now=(2010, 5, 15, 13, 30, 57)) self.assertRaises(NoFiles, self._callFUT, options) def test_no_files_before_explicit_date(self): from ZODB.scripts.repozo import NoFiles options = self._makeOptions(date='2010-05-13-13-30-57') files = [] for h, m, s, e in [(2, 13, 14, '.fs'), (2, 13, 14, '.dat'), (3, 14, 15, '.deltafs'), (4, 14, 15, '.deltafs'), (5, 14, 15, '.deltafs'), (12, 13, 14, '.fs'), (12, 13, 14, '.dat'), (13, 14, 15, '.deltafs'), (14, 15, 16, '.deltafs'), ]: files.append(self._makeFile(h, m, s, e)) self.assertRaises(NoFiles, self._callFUT, options) def test_w_full_backup_latest_no_index(self): import tempfile dd = self._data_directory = tempfile.mkdtemp() output = os.path.join(dd, 'Data.fs') index = os.path.join(dd, 'Data.fs.index') options = self._makeOptions(date='2010-05-15-13-30-57', output=output) self._makeFile(2, 3, 4, '.fs', 'AAA') self._makeFile(4, 5, 6, '.fs', 'BBB') self._callFUT(options) self.assertEqual(open(output, 'rb').read(), 'BBB') def test_w_full_backup_latest_index(self): import tempfile dd = self._data_directory = tempfile.mkdtemp() output = os.path.join(dd, 'Data.fs') index = os.path.join(dd, 'Data.fs.index') options = self._makeOptions(date='2010-05-15-13-30-57', output=output) self._makeFile(2, 3, 4, '.fs', 'AAA') self._makeFile(4, 5, 6, '.fs', 'BBB') self._makeFile(4, 5, 6, '.index', 'CCC') self._callFUT(options) self.assertEqual(open(output, 'rb').read(), 'BBB') self.assertEqual(open(index, 'rb').read(), 'CCC') def test_w_incr_backup_latest_no_index(self): import tempfile dd = self._data_directory = tempfile.mkdtemp() output = os.path.join(dd, 'Data.fs') index = os.path.join(dd, 'Data.fs.index') options = self._makeOptions(date='2010-05-15-13-30-57', output=output) self._makeFile(2, 3, 4, '.fs', 'AAA') self._makeFile(4, 5, 6, '.deltafs', 'BBB') self._callFUT(options) self.assertEqual(open(output, 'rb').read(), 'AAABBB') def test_w_incr_backup_latest_index(self): import tempfile dd = self._data_directory = tempfile.mkdtemp() output = os.path.join(dd, 'Data.fs') index = os.path.join(dd, 'Data.fs.index') options = self._makeOptions(date='2010-05-15-13-30-57', output=output) self._makeFile(2, 3, 4, '.fs', 'AAA') self._makeFile(4, 5, 6, '.deltafs', 'BBB') self._makeFile(4, 5, 6, '.index', 'CCC') self._callFUT(options) self.assertEqual(open(output, 'rb').read(), 'AAABBB') self.assertEqual(open(index, 'rb').read(), 'CCC') class MonteCarloTests(unittest.TestCase): layer = ZODB.tests.util.MininalTestLayer('repozo') def setUp(self): # compute directory names import tempfile self.basedir = tempfile.mkdtemp() self.backupdir = os.path.join(self.basedir, 'backup') self.datadir = os.path.join(self.basedir, 'data') self.restoredir = os.path.join(self.basedir, 'restore') self.copydir = os.path.join(self.basedir, 'copy') self.currdir = os.getcwd() # create empty directories os.mkdir(self.backupdir) os.mkdir(self.datadir) os.mkdir(self.restoredir) os.mkdir(self.copydir) os.chdir(self.datadir) self.db = OurDB(self.datadir) def tearDown(self): os.chdir(self.currdir) import shutil shutil.rmtree(self.basedir) def _callRepozoMain(self, argv): from ZODB.scripts.repozo import main main(argv) def test_via_monte_carlo(self): self.saved_snapshots = [] # list of (name, time) pairs for copies. for i in range(100): self.mutate_pack_backup(i) # Verify snapshots can be reproduced exactly. for copyname, copytime in self.saved_snapshots: if _NOISY: print "Checking that", copyname, print "at", copytime, "is reproducible." self.assertRestored(copyname, copytime) def mutate_pack_backup(self, i): import random from shutil import copyfile from time import gmtime from time import sleep self.db.mutate() # Pack about each tenth time. if random.random() < 0.1: if _NOISY: print "packing" self.db.pack() self.db.close() # Make an incremental backup, half the time with gzip (-z). argv = ['-BQr', self.backupdir, '-f', 'Data.fs'] if _NOISY: argv.insert(0, '-v') if random.random() < 0.5: argv.insert(0, '-z') self._callRepozoMain(argv) # Save snapshots to assert that dated restores are possible if i % 9 == 0: srcname = os.path.join(self.datadir, 'Data.fs') copytime = '%04d-%02d-%02d-%02d-%02d-%02d' % (gmtime()[:6]) copyname = os.path.join(self.copydir, "Data%d.fs" % i) copyfile(srcname, copyname) self.saved_snapshots.append((copyname, copytime)) # Make sure the clock moves at least a second. sleep(1.01) # Verify current Data.fs can be reproduced exactly. self.assertRestored() def assertRestored(self, correctpath='Data.fs', when=None): # Do recovery to time 'when', and check that it's identical to correctpath. # restore to Restored.fs restoredfile = os.path.join(self.restoredir, 'Restored.fs') argv = ['-Rr', self.backupdir, '-o', restoredfile] if _NOISY: argv.insert(0, '-v') if when is not None: argv.append('-D') argv.append(when) self._callRepozoMain(argv) # check restored file content is equal to file that was backed up f = file(correctpath, 'rb') g = file(restoredfile, 'rb') fguts = f.read() gguts = g.read() f.close() g.close() msg = ("guts don't match\ncorrectpath=%r when=%r\n cmd=%r" % (correctpath, when, ' '.join(argv))) self.assertEquals(fguts, gguts, msg) def test_suite(): return unittest.TestSuite([ unittest.makeSuite(Test_dofile), unittest.makeSuite(Test_checksum), unittest.makeSuite(Test_copyfile), unittest.makeSuite(Test_concat), unittest.makeSuite(Test_gen_filename), unittest.makeSuite(Test_find_files), unittest.makeSuite(Test_scandat), unittest.makeSuite(Test_delete_old_backups), unittest.makeSuite(Test_do_full_backup), unittest.makeSuite(Test_do_incremental_backup), #unittest.makeSuite(Test_do_backup), #TODO unittest.makeSuite(Test_do_recover), # N.B.: this test take forever to run (~40sec on a fast laptop), # *and* it is non-deterministic. unittest.makeSuite(MonteCarloTests), ]) zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/tests/__init__.py0000644000175000017500000000000012214017464022536 0ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/tests/test_fstest.py0000644000175000017500000000263012214017464023361 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.testing import setupstack import doctest import ZODB def test_fstest_verbose(): r""" >>> db = ZODB.DB('data.fs') >>> db.close() >>> import ZODB.scripts.fstest >>> ZODB.scripts.fstest.main(['data.fs']) >>> ZODB.scripts.fstest.main(['data.fs']) >>> ZODB.scripts.fstest.main(['-v', 'data.fs']) ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE 4: transaction tid ... #0 no errors detected >>> ZODB.scripts.fstest.main(['-vvv', 'data.fs']) ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE 52: object oid 0x0000000000000000 #0 4: transaction tid ... #0 no errors detected """ def test_suite(): return doctest.DocTestSuite( setUp=setupstack.setUpDirectory, tearDown=setupstack.tearDown) zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/tests/fstail.txt0000644000175000017500000000227312214017464022466 0ustar arnauarnau==================== The `fstail` utility ==================== The `fstail` utility shows information for a FileStorage about the last `n` transactions: We have to prepare a FileStorage first: >>> from ZODB.FileStorage import FileStorage >>> from ZODB.DB import DB >>> import transaction >>> from tempfile import mktemp >>> storagefile = mktemp() >>> base_storage = FileStorage(storagefile) >>> database = DB(base_storage) >>> connection1 = database.open() >>> root = connection1.root() >>> root['foo'] = 1 >>> transaction.commit() Now lets have a look at the last transactions of this FileStorage: >>> from ZODB.scripts.fstail import main >>> main(storagefile, 5) 2007-11-10 15:18:48.543001: hash=b16422d09fabdb45d4e4325e4b42d7d6f021d3c3 user='' description='' length=132 offset=185 2007-11-10 15:18:48.543001: hash=b16422d09fabdb45d4e4325e4b42d7d6f021d3c3 user='' description='initial database creation' length=150 offset=52 Now clean up the storage again: >>> import os >>> base_storage.close() >>> os.unlink(storagefile) >>> os.unlink(storagefile+'.index') >>> os.unlink(storagefile+'.lock') >>> os.unlink(storagefile+'.tmp') zope2.13-2.13.21/source/ZODB3/src/ZODB/scripts/migrate.py0000644000175000017500000002575612214017464021316 0ustar arnauarnau#!/usr/bin/env python2.3 ############################################################################## # # Copyright (c) 2001, 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """A script to gather statistics while doing a storage migration. This is very similar to a standard storage's copyTransactionsFrom() method, except that it's geared to run as a script, and it collects useful pieces of information as it's working. This script can be used to stress test a storage since it blasts transactions at it as fast as possible. You can get a good sense of the performance of a storage by running this script. Actually it just counts the size of pickles in the transaction via the iterator protocol, so storage overheads aren't counted. Usage: %(PROGRAM)s [options] [source-storage-args] [destination-storage-args] Options: -S sourcetype --stype=sourcetype This is the name of a recognized type for the source database. Use -T to print out the known types. Defaults to "file". -D desttype --dtype=desttype This is the name of the recognized type for the destination database. Use -T to print out the known types. Defaults to "file". -o filename --output=filename Print results in filename, otherwise stdout. -m txncount --max=txncount Stop after committing txncount transactions. -k txncount --skip=txncount Skip the first txncount transactions. -p/--profile Turn on specialized profiling. -t/--timestamps Print tids as timestamps. -T/--storage_types Print all the recognized storage types and exit. -v/--verbose Turns on verbose output. Multiple -v options increase the verbosity. -h/--help Print this message and exit. Positional arguments: source-storage-args: Semicolon separated list of arguments for the source storage, as key=val pairs. E.g. "file_name=Data.fs;read_only=1" destination-storage-args: Comma separated list of arguments for the source storage, as key=val pairs. E.g. "name=full;frequency=3600" """ import re import sys import time import getopt import marshal import profile from ZODB import utils from ZODB import StorageTypes from ZODB.TimeStamp import TimeStamp PROGRAM = sys.argv[0] ZERO = '\0'*8 try: True, False except NameError: True = 1 False = 0 def usage(code, msg=''): print >> sys.stderr, __doc__ % globals() if msg: print >> sys.stderr, msg sys.exit(code) def error(code, msg): print >> sys.stderr, msg print "use --help for usage message" sys.exit(code) def main(): try: opts, args = getopt.getopt( sys.argv[1:], 'hvo:pm:k:D:S:Tt', ['help', 'verbose', 'output=', 'profile', 'storage_types', 'max=', 'skip=', 'dtype=', 'stype=', 'timestamps']) except getopt.error, msg: error(2, msg) class Options: stype = 'FileStorage' dtype = 'FileStorage' verbose = 0 outfile = None profilep = False maxtxn = -1 skiptxn = -1 timestamps = False options = Options() for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt in ('-v', '--verbose'): options.verbose += 1 elif opt in ('-T', '--storage_types'): print_types() sys.exit(0) elif opt in ('-S', '--stype'): options.stype = arg elif opt in ('-D', '--dtype'): options.dtype = arg elif opt in ('-o', '--output'): options.outfile = arg elif opt in ('-p', '--profile'): options.profilep = True elif opt in ('-m', '--max'): options.maxtxn = int(arg) elif opt in ('-k', '--skip'): options.skiptxn = int(arg) elif opt in ('-t', '--timestamps'): options.timestamps = True if len(args) > 2: error(2, "too many arguments") srckws = {} if len(args) > 0: srcargs = args[0] for kv in re.split(r';\s*', srcargs): key, val = kv.split('=') srckws[key] = val destkws = {} if len(args) > 1: destargs = args[1] for kv in re.split(r';\s*', destargs): key, val = kv.split('=') destkws[key] = val if options.stype not in StorageTypes.storage_types.keys(): usage(2, 'Source database type must be provided') if options.dtype not in StorageTypes.storage_types.keys(): usage(2, 'Destination database type must be provided') # Open the output file if options.outfile is None: options.outfp = sys.stdout options.outclosep = False else: options.outfp = open(options.outfile, 'w') options.outclosep = True if options.verbose > 0: print 'Opening source database...' modname, sconv = StorageTypes.storage_types[options.stype] kw = sconv(**srckws) __import__(modname) sclass = getattr(sys.modules[modname], options.stype) srcdb = sclass(**kw) if options.verbose > 0: print 'Opening destination database...' modname, dconv = StorageTypes.storage_types[options.dtype] kw = dconv(**destkws) __import__(modname) dclass = getattr(sys.modules[modname], options.dtype) dstdb = dclass(**kw) try: t0 = time.time() doit(srcdb, dstdb, options) t1 = time.time() if options.verbose > 0: print 'Migration time: %8.3f' % (t1-t0) finally: # Done srcdb.close() dstdb.close() if options.outclosep: options.outfp.close() def doit(srcdb, dstdb, options): outfp = options.outfp profilep = options.profilep verbose = options.verbose # some global information largest_pickle = 0 largest_txn_in_size = 0 largest_txn_in_objects = 0 total_pickle_size = 0L total_object_count = 0 # Ripped from BaseStorage.copyTransactionsFrom() ts = None ok = True prevrevids = {} counter = 0 skipper = 0 if options.timestamps: print "%4s. %26s %6s %8s %5s %5s %5s %5s %5s" % ( "NUM", "TID AS TIMESTAMP", "OBJS", "BYTES", # Does anybody know what these times mean? "t4-t0", "t1-t0", "t2-t1", "t3-t2", "t4-t3") else: print "%4s. %20s %6s %8s %6s %6s %6s %6s %6s" % ( "NUM", "TRANSACTION ID", "OBJS", "BYTES", # Does anybody know what these times mean? "t4-t0", "t1-t0", "t2-t1", "t3-t2", "t4-t3") for txn in srcdb.iterator(): skipper += 1 if skipper <= options.skiptxn: continue counter += 1 if counter > options.maxtxn >= 0: break tid = txn.tid if ts is None: ts = TimeStamp(tid) else: t = TimeStamp(tid) if t <= ts: if ok: print >> sys.stderr, ( 'Time stamps are out of order %s, %s' % (ts, t)) ok = False ts = t.laterThan(ts) tid = `ts` else: ts = t if not ok: print >> sys.stderr, ( 'Time stamps are back in order %s' % t) ok = True if verbose > 1: print ts prof = None if profilep and (counter % 100) == 0: prof = profile.Profile() objects = 0 size = 0 newrevids = RevidAccumulator() t0 = time.time() dstdb.tpc_begin(txn, tid, txn.status) t1 = time.time() for r in txn: oid = r.oid objects += 1 thissize = len(r.data) size += thissize if thissize > largest_pickle: largest_pickle = thissize if verbose > 1: if not r.version: vstr = 'norev' else: vstr = r.version print utils.U64(oid), vstr, len(r.data) oldrevid = prevrevids.get(oid, ZERO) result = dstdb.store(oid, oldrevid, r.data, r.version, txn) newrevids.store(oid, result) t2 = time.time() result = dstdb.tpc_vote(txn) t3 = time.time() newrevids.tpc_vote(result) prevrevids.update(newrevids.get_dict()) # Profile every 100 transactions if prof: prof.runcall(dstdb.tpc_finish, txn) else: dstdb.tpc_finish(txn) t4 = time.time() # record the results if objects > largest_txn_in_objects: largest_txn_in_objects = objects if size > largest_txn_in_size: largest_txn_in_size = size if options.timestamps: tidstr = str(TimeStamp(tid)) format = "%4d. %26s %6d %8d %5.3f %5.3f %5.3f %5.3f %5.3f" else: tidstr = utils.U64(tid) format = "%4d. %20s %6d %8d %6.4f %6.4f %6.4f %6.4f %6.4f" print >> outfp, format % (skipper, tidstr, objects, size, t4-t0, t1-t0, t2-t1, t3-t2, t4-t3) total_pickle_size += size total_object_count += objects if prof: prof.create_stats() fp = open('profile-%02d.txt' % (counter / 100), 'wb') marshal.dump(prof.stats, fp) fp.close() print >> outfp, "Largest pickle: %8d" % largest_pickle print >> outfp, "Largest transaction: %8d" % largest_txn_in_size print >> outfp, "Largest object count: %8d" % largest_txn_in_objects print >> outfp, "Total pickle size: %14d" % total_pickle_size print >> outfp, "Total object count: %8d" % total_object_count # helper to deal with differences between old-style store() return and # new-style store() return that supports ZEO import types class RevidAccumulator: def __init__(self): self.data = {} def _update_from_list(self, list): for oid, serial in list: if not isinstance(serial, types.StringType): raise serial self.data[oid] = serial def store(self, oid, result): if isinstance(result, types.StringType): self.data[oid] = result elif result is not None: self._update_from_list(result) def tpc_vote(self, result): if result is not None: self._update_from_list(result) def get_dict(self): return self.data if __name__ == '__main__': main() zope2.13-2.13.21/source/ZODB3/src/ZODB/persistentclass.py0000644000175000017500000001472212214017464021414 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Persistent Class Support $Id: persistentclass.py 113734 2010-06-21 15:33:46Z ctheune $ """ # Notes: # # Persistent classes are non-ghostable. This has some interesting # ramifications: # # - When an object is invalidated, it must reload it's state # # - When an object is loaded from the database, it's state must be # loaded. Unfortunately, there isn't a clear signal when an object is # loaded from the database. This should probably be fixed. # # In the mean time, we need to infer. This should be viewed as a # short term hack. # # Here's the strategy we'll use: # # - We'll have a need to be loaded flag that we'll set in # __new__, through an extra argument. # # - When setting _p_oid and _p_jar, if both are set and we need to be # loaded, then we'll load out state. # # - We'll use _p_changed is None to indicate that we're in this state. # class _p_DataDescr(object): # Descr used as base for _p_ data. Data are stored in # _p_class_dict. def __init__(self, name): self.__name__ = name def __get__(self, inst, cls): if inst is None: return self if '__global_persistent_class_not_stored_in_DB__' in inst.__dict__: raise AttributeError(self.__name__) return inst._p_class_dict.get(self.__name__) def __set__(self, inst, v): inst._p_class_dict[self.__name__] = v def __delete__(self, inst): raise AttributeError(self.__name__) class _p_oid_or_jar_Descr(_p_DataDescr): # Special descr for _p_oid and _p_jar that loads # state when set if both are set and and _p_changed is None # # See notes above def __set__(self, inst, v): get = inst._p_class_dict.get if v == get(self.__name__): return inst._p_class_dict[self.__name__] = v jar = get('_p_jar') if (jar is not None and get('_p_oid') is not None and get('_p_changed') is None ): jar.setstate(inst) class _p_ChangedDescr(object): # descriptor to handle special weird emantics of _p_changed def __get__(self, inst, cls): if inst is None: return self return inst._p_class_dict['_p_changed'] def __set__(self, inst, v): if v is None: return inst._p_class_dict['_p_changed'] = bool(v) def __delete__(self, inst): inst._p_invalidate() class _p_MethodDescr(object): """Provide unassignable class attributes """ def __init__(self, func): self.func = func def __get__(self, inst, cls): if inst is None: return cls return self.func.__get__(inst, cls) def __set__(self, inst, v): raise AttributeError(self.__name__) def __delete__(self, inst): raise AttributeError(self.__name__) special_class_descrs = '__dict__', '__weakref__' class PersistentMetaClass(type): _p_jar = _p_oid_or_jar_Descr('_p_jar') _p_oid = _p_oid_or_jar_Descr('_p_oid') _p_changed = _p_ChangedDescr() _p_serial = _p_DataDescr('_p_serial') def __new__(self, name, bases, cdict, _p_changed=False): cdict = dict([(k, v) for (k, v) in cdict.items() if not k.startswith('_p_')]) cdict['_p_class_dict'] = {'_p_changed': _p_changed} return super(PersistentMetaClass, self).__new__( self, name, bases, cdict) def __getnewargs__(self): return self.__name__, self.__bases__, {}, None __getnewargs__ = _p_MethodDescr(__getnewargs__) def _p_maybeupdate(self, name): get = self._p_class_dict.get data_manager = get('_p_jar') if ( (data_manager is not None) and (get('_p_oid') is not None) and (get('_p_changed') == False) ): self._p_changed = True data_manager.register(self) def __setattr__(self, name, v): if not ((name.startswith('_p_') or name.startswith('_v'))): self._p_maybeupdate(name) super(PersistentMetaClass, self).__setattr__(name, v) def __delattr__(self, name): if not ((name.startswith('_p_') or name.startswith('_v'))): self._p_maybeupdate(name) super(PersistentMetaClass, self).__delattr__(name) def _p_deactivate(self): # persistent classes can't be ghosts pass _p_deactivate = _p_MethodDescr(_p_deactivate) def _p_invalidate(self): # reset state self._p_class_dict['_p_changed'] = None self._p_jar.setstate(self) _p_invalidate = _p_MethodDescr(_p_invalidate) def __getstate__(self): return (self.__bases__, dict([(k, v) for (k, v) in self.__dict__.items() if not (k.startswith('_p_') or k.startswith('_v_') or k in special_class_descrs ) ]), ) __getstate__ = _p_MethodDescr(__getstate__) def __setstate__(self, state): self.__bases__, cdict = state cdict = dict([(k, v) for (k, v) in cdict.items() if not k.startswith('_p_')]) _p_class_dict = self._p_class_dict self._p_class_dict = {} to_remove = [k for k in self.__dict__ if ((k not in cdict) and (k not in special_class_descrs) and (k != '_p_class_dict') )] for k in to_remove: delattr(self, k) for k, v in cdict.items(): setattr(self, k, v) self._p_class_dict = _p_class_dict self._p_changed = False __setstate__ = _p_MethodDescr(__setstate__) def _p_activate(self): self._p_jar.setstate(self) _p_activate = _p_MethodDescr(_p_activate) zope2.13-2.13.21/source/ZODB3/src/ZODB/fsIndex.py0000644000175000017500000001732212214017464017565 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Implement an OID to File-position (long integer) mapping.""" # To save space, we do two things: # # 1. We split the keys (OIDS) into 6-byte prefixes and 2-byte suffixes. # We use the prefixes as keys in a mapping from prefix to mappings # of suffix to data: # # data is {prefix -> {suffix -> data}} # # 2. We limit the data size to 48 bits. This should allow databases # as large as 256 terabytes. # # Most of the space is consumed by items in the mappings from 2-byte # suffix to 6-byte data. This should reduce the overall memory usage to # 8-16 bytes per OID. # # Because # - the mapping from suffix to data contains at most 65535 entries, # - this is an in-memory data structure # - new keys are inserted sequentially, # we use a BTree bucket instead of a full BTree to store the results. # # We use p64 to convert integers to 8-byte strings and lop off the two # high-order bytes when saving. On loading data, we add the leading # bytes back before using u64 to convert the data back to (long) # integers. from __future__ import with_statement import cPickle import struct from BTrees._fsBTree import fsBucket from BTrees.OOBTree import OOBTree # convert between numbers and six-byte strings def num2str(n): return struct.pack(">Q", n)[2:] def str2num(s): return struct.unpack(">Q", "\000\000" + s)[0] def prefix_plus_one(s): num = str2num(s) return num2str(num + 1) def prefix_minus_one(s): num = str2num(s) return num2str(num - 1) class fsIndex(object): def __init__(self, data=None): self._data = OOBTree() if data: self.update(data) def __getstate__(self): return dict( state_version = 1, _data = [(k, v.toString()) for (k, v) in self._data.iteritems() ] ) def __setstate__(self, state): version = state.pop('state_version', 0) getattr(self, '_setstate_%s' % version)(state) def _setstate_0(self, state): self.__dict__.clear() self.__dict__.update(state) def _setstate_1(self, state): self._data = OOBTree([ (k, fsBucket().fromString(v)) for (k, v) in state['_data'] ]) def __getitem__(self, key): return str2num(self._data[key[:6]][key[6:]]) def save(self, pos, fname): with open(fname, 'wb') as f: pickler = cPickle.Pickler(f, 1) pickler.fast = True pickler.dump(pos) for k, v in self._data.iteritems(): pickler.dump((k, v.toString())) pickler.dump(None) @classmethod def load(class_, fname): with open(fname, 'rb') as f: unpickler = cPickle.Unpickler(f) pos = unpickler.load() if not isinstance(pos, (int, long)): return pos # Old format index = class_() data = index._data while 1: v = unpickler.load() if not v: break k, v = v data[k] = fsBucket().fromString(v) return dict(pos=pos, index=index) def get(self, key, default=None): tree = self._data.get(key[:6], default) if tree is default: return default v = tree.get(key[6:], default) if v is default: return default return str2num(v) def __setitem__(self, key, value): value = num2str(value) treekey = key[:6] tree = self._data.get(treekey) if tree is None: tree = fsBucket() self._data[treekey] = tree tree[key[6:]] = value def __delitem__(self, key): treekey = key[:6] tree = self._data.get(treekey) if tree is None: raise KeyError, key del tree[key[6:]] if not tree: del self._data[treekey] def __len__(self): r = 0 for tree in self._data.itervalues(): r += len(tree) return r def update(self, mapping): for k, v in mapping.items(): self[k] = v def has_key(self, key): v = self.get(key, self) return v is not self def __contains__(self, key): tree = self._data.get(key[:6]) if tree is None: return False v = tree.get(key[6:], None) if v is None: return False return True def clear(self): self._data.clear() def __iter__(self): for prefix, tree in self._data.iteritems(): for suffix in tree: yield prefix + suffix iterkeys = __iter__ def keys(self): return list(self.iterkeys()) def iteritems(self): for prefix, tree in self._data.iteritems(): for suffix, value in tree.iteritems(): yield (prefix + suffix, str2num(value)) def items(self): return list(self.iteritems()) def itervalues(self): for tree in self._data.itervalues(): for value in tree.itervalues(): yield str2num(value) def values(self): return list(self.itervalues()) # Comment below applies for the following minKey and maxKey methods # # Obscure: what if `tree` is actually empty? We're relying here on # that this class doesn't implement __delitem__: once a key gets # into an fsIndex, the only way it can go away is by invoking # clear(). Therefore nothing in _data.values() is ever empty. # # Note that because `tree` is an fsBTree, its minKey()/maxKey() methods are # very efficient. def minKey(self, key=None): if key is None: smallest_prefix = self._data.minKey() else: smallest_prefix = self._data.minKey(key[:6]) tree = self._data[smallest_prefix] assert tree if key is None: smallest_suffix = tree.minKey() else: try: smallest_suffix = tree.minKey(key[6:]) except ValueError: # 'empty tree' (no suffix >= arg) next_prefix = prefix_plus_one(smallest_prefix) smallest_prefix = self._data.minKey(next_prefix) tree = self._data[smallest_prefix] assert tree smallest_suffix = tree.minKey() return smallest_prefix + smallest_suffix def maxKey(self, key=None): if key is None: biggest_prefix = self._data.maxKey() else: biggest_prefix = self._data.maxKey(key[:6]) tree = self._data[biggest_prefix] assert tree if key is None: biggest_suffix = tree.maxKey() else: try: biggest_suffix = tree.maxKey(key[6:]) except ValueError: # 'empty tree' (no suffix <= arg) next_prefix = prefix_minus_one(biggest_prefix) biggest_prefix = self._data.maxKey(next_prefix) tree = self._data[biggest_prefix] assert tree biggest_suffix = tree.maxKey() return biggest_prefix + biggest_suffix zope2.13-2.13.21/source/ZODB3/src/ZODB/blob.py0000644000175000017500000010236512214017464017105 0ustar arnauarnau############################################################################## # # Copyright (c) 2005-2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Blobs """ import cPickle import cStringIO import base64 import binascii import logging import os import re import shutil import stat import sys import tempfile import weakref import zope.interface import ZODB.interfaces from ZODB.interfaces import BlobError from ZODB import utils from ZODB.POSException import POSKeyError import persistent logger = logging.getLogger('ZODB.blob') BLOB_SUFFIX = ".blob" SAVEPOINT_SUFFIX = ".spb" LAYOUT_MARKER = '.layout' LAYOUTS = {} valid_modes = 'r', 'w', 'r+', 'a', 'c' # Threading issues: # We want to support closing blob files when they are destroyed. # This introduces a threading issue, since a blob file may be destroyed # via GC in any thread. class Blob(persistent.Persistent): """A BLOB supports efficient handling of large data within ZODB.""" zope.interface.implements(ZODB.interfaces.IBlob) _p_blob_uncommitted = None # Filename of the uncommitted (dirty) data _p_blob_committed = None # Filename of the committed data readers = writers = None def __init__(self, data=None): # Raise exception if Blobs are getting subclassed # refer to ZODB-Bug No.127182 by Jim Fulton on 2007-07-20 if (self.__class__ is not Blob): raise TypeError('Blobs do not support subclassing.') self.__setstate__() if data is not None: self.open('w').write(data) def __setstate__(self, state=None): # we use lists here because it will allow us to add and remove # atomically self.readers = [] self.writers = [] def __getstate__(self): return None def _p_deactivate(self): # Only ghostify if we are unopened. if self.readers or self.writers: return super(Blob, self)._p_deactivate() def _p_invalidate(self): # Force-close any open readers or writers, # XXX should we warn of this? Maybe? if self._p_changed is None: return for ref in (self.readers or [])+(self.writers or []): f = ref() if f is not None: f.close() if (self._p_blob_uncommitted): os.remove(self._p_blob_uncommitted) super(Blob, self)._p_invalidate() def opened(self): return bool(self.readers or self.writers) def closed(self, f): # We use try/except below because another thread might remove # the ref after we check it if the file is GCed. for file_refs in (self.readers, self.writers): for ref in file_refs: if ref() is f: try: file_refs.remove(ref) except ValueError: pass return def open(self, mode="r"): if mode not in valid_modes: raise ValueError("invalid mode", mode) if mode == 'c': if (self._p_blob_uncommitted or not self._p_blob_committed or self._p_blob_committed.endswith(SAVEPOINT_SUFFIX) ): raise BlobError('Uncommitted changes') return self._p_jar._storage.openCommittedBlobFile( self._p_oid, self._p_serial) if self.writers: raise BlobError("Already opened for writing.") if self.readers is None: self.readers = [] if mode == 'r': result = None to_open = self._p_blob_uncommitted if not to_open: to_open = self._p_blob_committed if to_open: result = self._p_jar._storage.openCommittedBlobFile( self._p_oid, self._p_serial, self) else: self._create_uncommitted_file() to_open = self._p_blob_uncommitted assert to_open if result is None: result = BlobFile(to_open, mode, self) def destroyed(ref, readers=self.readers): try: readers.remove(ref) except ValueError: pass self.readers.append(weakref.ref(result, destroyed)) else: if self.readers: raise BlobError("Already opened for reading.") if mode == 'w': if self._p_blob_uncommitted is None: self._create_uncommitted_file() result = BlobFile(self._p_blob_uncommitted, mode, self) else: # 'r+' and 'a' if self._p_blob_uncommitted is None: # Create a new working copy self._create_uncommitted_file() result = BlobFile(self._p_blob_uncommitted, mode, self) if self._p_blob_committed: utils.cp(open(self._p_blob_committed), result) if mode == 'r+': result.seek(0) else: # Re-use existing working copy result = BlobFile(self._p_blob_uncommitted, mode, self) def destroyed(ref, writers=self.writers): try: writers.remove(ref) except ValueError: pass self.writers.append(weakref.ref(result, destroyed)) self._p_changed = True return result def committed(self): if (self._p_blob_uncommitted or not self._p_blob_committed or self._p_blob_committed.endswith(SAVEPOINT_SUFFIX) ): raise BlobError('Uncommitted changes') result = self._p_blob_committed # We do this to make sure we have the file and to let the # storage know we're accessing the file. n = self._p_jar._storage.loadBlob(self._p_oid, self._p_serial) assert result == n, (result, n) return result def consumeFile(self, filename): """Will replace the current data of the blob with the file given under filename. """ if self.writers: raise BlobError("Already opened for writing.") if self.readers: raise BlobError("Already opened for reading.") previous_uncommitted = bool(self._p_blob_uncommitted) if previous_uncommitted: # If we have uncommitted data, we move it aside for now # in case the consumption doesn't work. target = self._p_blob_uncommitted target_aside = target+".aside" os.rename(target, target_aside) else: target = self._create_uncommitted_file() # We need to unlink the freshly created target again # to allow link() to do its job os.remove(target) try: rename_or_copy_blob(filename, target, chmod=False) except: # Recover from the failed consumption: First remove the file, it # might exist and mark the pointer to the uncommitted file. self._p_blob_uncommitted = None if os.path.exists(target): os.remove(target) # If there was a file moved aside, bring it back including the # pointer to the uncommitted file. if previous_uncommitted: os.rename(target_aside, target) self._p_blob_uncommitted = target # Re-raise the exception to make the application aware of it. raise else: if previous_uncommitted: # The relinking worked so we can remove the data that we had # set aside. os.remove(target_aside) # We changed the blob state and have to make sure we join the # transaction. self._p_changed = True # utility methods def _create_uncommitted_file(self): assert self._p_blob_uncommitted is None, ( "Uncommitted file already exists.") if self._p_jar: tempdir = self._p_jar.db()._storage.temporaryDirectory() else: tempdir = tempfile.gettempdir() filename = utils.mktemp(dir=tempdir) self._p_blob_uncommitted = filename def cleanup(ref): if os.path.exists(filename): os.remove(filename) self._p_blob_ref = weakref.ref(self, cleanup) return filename def _uncommitted(self): # hand uncommitted data to connection, relinquishing responsibility # for it. filename = self._p_blob_uncommitted if filename is None and self._p_blob_committed is None: filename = self._create_uncommitted_file() self._p_blob_uncommitted = self._p_blob_ref = None return filename class BlobFile(file): """A BlobFile that holds a file handle to actual blob data. It is a file that can be used within a transaction boundary; a BlobFile is just a Python file object, we only override methods which cause a change to blob data in order to call methods on our 'parent' persistent blob object signifying that the change happened. """ # XXX these files should be created in the same partition as # the storage later puts them to avoid copying them ... def __init__(self, name, mode, blob): super(BlobFile, self).__init__(name, mode+'b') self.blob = blob def close(self): self.blob.closed(self) file.close(self) _pid = str(os.getpid()) def log(msg, level=logging.INFO, subsys=_pid, exc_info=False): message = "(%s) %s" % (subsys, msg) logger.log(level, message, exc_info=exc_info) class FilesystemHelper: # Storages that implement IBlobStorage can choose to use this # helper class to generate and parse blob filenames. This is not # a set-in-stone interface for all filesystem operations dealing # with blobs and storages needn't indirect through this if they # want to perform blob storage differently. def __init__(self, base_dir, layout_name='automatic'): self.base_dir = os.path.abspath(base_dir) + os.path.sep self.temp_dir = os.path.join(base_dir, 'tmp') if layout_name == 'automatic': layout_name = auto_layout_select(base_dir) if layout_name == 'lawn': log('The `lawn` blob directory layout is deprecated due to ' 'scalability issues on some file systems, please consider ' 'migrating to the `bushy` layout.', level=logging.WARN) self.layout_name = layout_name self.layout = LAYOUTS[layout_name] def create(self): if not os.path.exists(self.base_dir): os.makedirs(self.base_dir, 0700) log("Blob directory '%s' does not exist. " "Created new directory." % self.base_dir) if not os.path.exists(self.temp_dir): os.makedirs(self.temp_dir, 0700) log("Blob temporary directory '%s' does not exist. " "Created new directory." % self.temp_dir) if not os.path.exists(os.path.join(self.base_dir, LAYOUT_MARKER)): layout_marker = open( os.path.join(self.base_dir, LAYOUT_MARKER), 'wb') layout_marker.write(self.layout_name) else: layout = open(os.path.join(self.base_dir, LAYOUT_MARKER), 'rb' ).read().strip() if layout != self.layout_name: raise ValueError( "Directory layout `%s` selected for blob directory %s, but " "marker found for layout `%s`" % (self.layout_name, self.base_dir, layout)) def isSecure(self, path): """Ensure that (POSIX) path mode bits are 0700.""" return (os.stat(path).st_mode & 077) == 0 def checkSecure(self): if not self.isSecure(self.base_dir): log('Blob dir %s has insecure mode setting' % self.base_dir, level=logging.WARNING) def getPathForOID(self, oid, create=False): """Given an OID, return the path on the filesystem where the blob data relating to that OID is stored. If the create flag is given, the path is also created if it didn't exist already. """ # OIDs are numbers and sometimes passed around as integers. For our # computations we rely on the 64-bit packed string representation. if isinstance(oid, int): oid = utils.p64(oid) path = self.layout.oid_to_path(oid) path = os.path.join(self.base_dir, path) if create and not os.path.exists(path): try: os.makedirs(path, 0700) except OSError: # We might have lost a race. If so, the directory # must exist now assert os.path.exists(path) return path def getOIDForPath(self, path): """Given a path, return an OID, if the path is a valid path for an OID. The inverse function to `getPathForOID`. Raises ValueError if the path is not valid for an OID. """ path = path[len(self.base_dir):] return self.layout.path_to_oid(path) def createPathForOID(self, oid): """Given an OID, creates a directory on the filesystem where the blob data relating to that OID is stored, if it doesn't exist. """ return self.getPathForOID(oid, create=True) def getBlobFilename(self, oid, tid): """Given an oid and a tid, return the full filename of the 'committed' blob file related to that oid and tid. """ # TIDs are numbers and sometimes passed around as integers. For our # computations we rely on the 64-bit packed string representation if isinstance(oid, int): oid = utils.p64(oid) if isinstance(tid, int): tid = utils.p64(tid) return os.path.join(self.base_dir, self.layout.getBlobFilePath(oid, tid), ) def blob_mkstemp(self, oid, tid): """Given an oid and a tid, return a temporary file descriptor and a related filename. The file is guaranteed to exist on the same partition as committed data, which is important for being able to rename the file without a copy operation. The directory in which the file will be placed, which is the return value of self.getPathForOID(oid), must exist before this method may be called successfully. """ oidpath = self.getPathForOID(oid) fd, name = tempfile.mkstemp(suffix='.tmp', prefix=utils.tid_repr(tid), dir=oidpath) return fd, name def splitBlobFilename(self, filename): """Returns the oid and tid for a given blob filename. If the filename cannot be recognized as a blob filename, (None, None) is returned. """ if not filename.endswith(BLOB_SUFFIX): return None, None path, filename = os.path.split(filename) oid = self.getOIDForPath(path) serial = filename[:-len(BLOB_SUFFIX)] serial = utils.repr_to_oid(serial) return oid, serial def getOIDsForSerial(self, search_serial): """Return all oids related to a particular tid that exist in blob data. """ oids = [] for oid, oidpath in self.listOIDs(): for filename in os.listdir(oidpath): blob_path = os.path.join(oidpath, filename) oid, serial = self.splitBlobFilename(blob_path) if search_serial == serial: oids.append(oid) return oids def listOIDs(self): """Iterates over all paths under the base directory that contain blob files. """ for path, dirs, files in os.walk(self.base_dir): # Make sure we traverse in a stable order. This is mainly to make # testing predictable. dirs.sort() files.sort() try: oid = self.getOIDForPath(path) except ValueError: continue yield oid, path class NoBlobsFileSystemHelper: @property def temp_dir(self): raise TypeError("Blobs are not supported") getPathForOID = getBlobFilename = temp_dir class BlobStorageError(Exception): """The blob storage encountered an invalid state.""" def auto_layout_select(path): # A heuristic to look at a path and determine which directory layout to # use. layout_marker = os.path.join(path, LAYOUT_MARKER) if os.path.exists(layout_marker): layout = open(layout_marker, 'rb').read() layout = layout.strip() log('Blob directory `%s` has layout marker set. ' 'Selected `%s` layout. ' % (path, layout), level=logging.DEBUG) elif not os.path.exists(path): log('Blob directory %s does not exist. ' 'Selected `bushy` layout. ' % path) layout = 'bushy' else: # look for a non-hidden file in the directory has_files = False for name in os.listdir(path): if not name.startswith('.'): has_files = True break if not has_files: log('Blob directory `%s` is unused and has no layout marker set. ' 'Selected `bushy` layout. ' % path) layout = 'bushy' else: log('Blob directory `%s` is used but has no layout marker set. ' 'Selected `lawn` layout. ' % path) layout = 'lawn' return layout class BushyLayout(object): """A bushy directory layout for blob directories. Creates an 8-level directory structure (one level per byte) in big-endian order from the OID of an object. """ blob_path_pattern = re.compile( r'(0x[0-9a-f]{1,2}\%s){7,7}0x[0-9a-f]{1,2}$' % os.path.sep) def oid_to_path(self, oid): directories = [] # Create the bushy directory structure with the least significant byte # first for byte in str(oid): directories.append('0x%s' % binascii.hexlify(byte)) return os.path.sep.join(directories) def path_to_oid(self, path): if self.blob_path_pattern.match(path) is None: raise ValueError("Not a valid OID path: `%s`" % path) path = path.split(os.path.sep) # Each path segment stores a byte in hex representation. Turn it into # an int and then get the character for our byte string. oid = ''.join(binascii.unhexlify(byte[2:]) for byte in path) return oid def getBlobFilePath(self, oid, tid): """Given an oid and a tid, return the full filename of the 'committed' blob file related to that oid and tid. """ oid_path = self.oid_to_path(oid) filename = "%s%s" % (utils.tid_repr(tid), BLOB_SUFFIX) return os.path.join(oid_path, filename) LAYOUTS['bushy'] = BushyLayout() class LawnLayout(BushyLayout): """A shallow directory layout for blob directories. Creates a single level of directories (one for each oid). """ def oid_to_path(self, oid): return utils.oid_repr(oid) def path_to_oid(self, path): try: if path == '': # This is a special case where repr_to_oid converts '' to the # OID z64. raise TypeError() return utils.repr_to_oid(path) except TypeError: raise ValueError('Not a valid OID path: `%s`' % path) LAYOUTS['lawn'] = LawnLayout() class BlobStorageMixin(object): """A mix-in to help storages support blobs.""" def _blob_init(self, blob_dir, layout='automatic'): # XXX Log warning if storage is ClientStorage self.fshelper = FilesystemHelper(blob_dir, layout) self.fshelper.create() self.fshelper.checkSecure() self.dirty_oids = [] def _blob_init_no_blobs(self): self.fshelper = NoBlobsFileSystemHelper() self.dirty_oids = [] def _blob_tpc_abort(self): """Blob cleanup to be called from subclass tpc_abort """ while self.dirty_oids: oid, serial = self.dirty_oids.pop() clean = self.fshelper.getBlobFilename(oid, serial) if os.path.exists(clean): remove_committed(clean) def _blob_tpc_finish(self): """Blob cleanup to be called from subclass tpc_finish """ self.dirty_oids = [] def registerDB(self, db): self.__untransform_record_data = db.untransform_record_data try: m = super(BlobStorageMixin, self).registerDB except AttributeError: pass else: m(db) def __untransform_record_data(self, record): return record def is_blob_record(self, record): if record: return is_blob_record(self.__untransform_record_data(record)) def copyTransactionsFrom(self, other): copyTransactionsFromTo(other, self) def loadBlob(self, oid, serial): """Return the filename where the blob file can be found. """ filename = self.fshelper.getBlobFilename(oid, serial) if not os.path.exists(filename): raise POSKeyError("No blob file", oid, serial) return filename def openCommittedBlobFile(self, oid, serial, blob=None): blob_filename = self.loadBlob(oid, serial) if blob is None: return open(blob_filename, 'rb') else: return BlobFile(blob_filename, 'r', blob) def restoreBlob(self, oid, serial, data, blobfilename, prev_txn, transaction): """Write blob data already committed in a separate database """ self.restore(oid, serial, data, '', prev_txn, transaction) self._blob_storeblob(oid, serial, blobfilename) return self._tid def _blob_storeblob(self, oid, serial, blobfilename): self._lock_acquire() try: self.fshelper.getPathForOID(oid, create=True) targetname = self.fshelper.getBlobFilename(oid, serial) rename_or_copy_blob(blobfilename, targetname) # if oid already in there, something is really hosed. # The underlying storage should have complained anyway self.dirty_oids.append((oid, serial)) finally: self._lock_release() def storeBlob(self, oid, oldserial, data, blobfilename, version, transaction): """Stores data that has a BLOB attached.""" assert not version, "Versions aren't supported." serial = self.store(oid, oldserial, data, '', transaction) self._blob_storeblob(oid, serial, blobfilename) return self._tid def temporaryDirectory(self): return self.fshelper.temp_dir class BlobStorage(BlobStorageMixin): """A wrapper/proxy storage to support blobs. """ zope.interface.implements(ZODB.interfaces.IBlobStorage) def __init__(self, base_directory, storage, layout='automatic'): assert not ZODB.interfaces.IBlobStorage.providedBy(storage) self.__storage = storage self._blob_init(base_directory, layout) try: supportsUndo = storage.supportsUndo except AttributeError: supportsUndo = False else: supportsUndo = supportsUndo() self.__supportsUndo = supportsUndo self._blobs_pack_is_in_progress = False if ZODB.interfaces.IStorageRestoreable.providedBy(storage): iblob = ZODB.interfaces.IBlobStorageRestoreable else: iblob = ZODB.interfaces.IBlobStorage zope.interface.directlyProvides( self, iblob, zope.interface.providedBy(storage)) def __getattr__(self, name): return getattr(self.__storage, name) def __len__(self): return len(self.__storage) def __repr__(self): normal_storage = self.__storage return '' % (normal_storage, hex(id(self))) def tpc_finish(self, *arg, **kw): # We need to override the base storage's tpc_finish instead of # providing a _finish method because methods found on the proxied # object aren't rebound to the proxy self.__storage.tpc_finish(*arg, **kw) self._blob_tpc_finish() def tpc_abort(self, *arg, **kw): # We need to override the base storage's abort instead of # providing an _abort method because methods found on the proxied object # aren't rebound to the proxy self.__storage.tpc_abort(*arg, **kw) self._blob_tpc_abort() def _packUndoing(self, packtime, referencesf): # Walk over all existing revisions of all blob files and check # if they are still needed by attempting to load the revision # of that object from the database. This is maybe the slowest # possible way to do this, but it's safe. for oid, oid_path in self.fshelper.listOIDs(): files = os.listdir(oid_path) for filename in files: filepath = os.path.join(oid_path, filename) whatever, serial = self.fshelper.splitBlobFilename(filepath) try: self.loadSerial(oid, serial) except POSKeyError: remove_committed(filepath) if not os.listdir(oid_path): shutil.rmtree(oid_path) def _packNonUndoing(self, packtime, referencesf): for oid, oid_path in self.fshelper.listOIDs(): exists = True try: self.load(oid, None) # no version support except (POSKeyError, KeyError): exists = False if exists: files = os.listdir(oid_path) files.sort() latest = files[-1] # depends on ever-increasing tids files.remove(latest) for file in files: remove_committed(os.path.join(oid_path, file)) else: remove_committed_dir(oid_path) continue if not os.listdir(oid_path): shutil.rmtree(oid_path) def pack(self, packtime, referencesf): """Remove all unused OID/TID combinations.""" self._lock_acquire() try: if self._blobs_pack_is_in_progress: raise BlobStorageError('Already packing') self._blobs_pack_is_in_progress = True finally: self._lock_release() try: # Pack the underlying storage, which will allow us to determine # which serials are current. unproxied = self.__storage result = unproxied.pack(packtime, referencesf) # Perform a pack on the blob data. if self.__supportsUndo: self._packUndoing(packtime, referencesf) else: self._packNonUndoing(packtime, referencesf) finally: self._lock_acquire() self._blobs_pack_is_in_progress = False self._lock_release() return result def undo(self, serial_id, transaction): undo_serial, keys = self.__storage.undo(serial_id, transaction) # serial_id is the transaction id of the txn that we wish to undo. # "undo_serial" is the transaction id of txn in which the undo is # performed. "keys" is the list of oids that are involved in the # undo transaction. # The serial_id is assumed to be given to us base-64 encoded # (belying the web UI legacy of the ZODB code :-() serial_id = base64.decodestring(serial_id+'\n') self._lock_acquire() try: # we get all the blob oids on the filesystem related to the # transaction we want to undo. for oid in self.fshelper.getOIDsForSerial(serial_id): # we want to find the serial id of the previous revision # of this blob object. load_result = self.loadBefore(oid, serial_id) if load_result is None: # There was no previous revision of this blob # object. The blob was created in the transaction # represented by serial_id. We copy the blob data # to a new file that references the undo # transaction in case a user wishes to undo this # undo. It would be nice if we had some way to # link to old blobs. orig_fn = self.fshelper.getBlobFilename(oid, serial_id) new_fn = self.fshelper.getBlobFilename(oid, undo_serial) else: # A previous revision of this blob existed before the # transaction implied by "serial_id". We copy the blob # data to a new file that references the undo transaction # in case a user wishes to undo this undo. data, serial_before, serial_after = load_result orig_fn = self.fshelper.getBlobFilename(oid, serial_before) new_fn = self.fshelper.getBlobFilename(oid, undo_serial) orig = open(orig_fn, "r") new = open(new_fn, "wb") utils.cp(orig, new) orig.close() new.close() self.dirty_oids.append((oid, undo_serial)) finally: self._lock_release() return undo_serial, keys def new_instance(self): """Implementation of IMVCCStorage.new_instance. This method causes all storage instances to be wrapped with a blob storage wrapper. """ base_dir = self.fshelper.base_dir s = self.__storage.new_instance() res = BlobStorage(base_dir, s) return res copied = logging.getLogger('ZODB.blob.copied').debug def rename_or_copy_blob(f1, f2, chmod=True): """Try to rename f1 to f2, fallback to copy. Under certain conditions a rename might not work, e.g. because the target directory is on a different partition. In this case we try to copy the data and remove the old file afterwards. """ try: os.rename(f1, f2) except OSError: copied("Copied blob file %r to %r.", f1, f2) file1 = open(f1, 'rb') file2 = open(f2, 'wb') try: utils.cp(file1, file2) finally: file1.close() file2.close() remove_committed(f1) if chmod: os.chmod(f2, stat.S_IREAD) if sys.platform == 'win32': # On Windows, you can't remove read-only files, so make the # file writable first. def remove_committed(filename): os.chmod(filename, stat.S_IWRITE) os.remove(filename) def remove_committed_dir(path): for (dirpath, dirnames, filenames) in os.walk(path): for filename in filenames: filename = os.path.join(dirpath, filename) remove_committed(filename) shutil.rmtree(path) link_or_copy = shutil.copy else: remove_committed = os.remove remove_committed_dir = shutil.rmtree link_or_copy = os.link def find_global_Blob(module, class_): if module == 'ZODB.blob' and class_ == 'Blob': return Blob def is_blob_record(record): """Check whether a database record is a blob record. This is primarily intended to be used when copying data from one storage to another. """ if record and ('ZODB.blob' in record): unpickler = cPickle.Unpickler(cStringIO.StringIO(record)) unpickler.find_global = find_global_Blob try: return unpickler.load() is Blob except (MemoryError, KeyboardInterrupt, SystemExit): raise except Exception: pass return False def copyTransactionsFromTo(source, destination): for trans in source.iterator(): destination.tpc_begin(trans, trans.tid, trans.status) for record in trans: blobfilename = None if is_blob_record(record.data): try: blobfilename = source.loadBlob(record.oid, record.tid) except POSKeyError: pass if blobfilename is not None: fd, name = tempfile.mkstemp( suffix='.tmp', dir=destination.fshelper.temp_dir) os.close(fd) utils.cp(open(blobfilename, 'rb'), open(name, 'wb')) destination.restoreBlob(record.oid, record.tid, record.data, name, record.data_txn, trans) else: destination.restore(record.oid, record.tid, record.data, '', record.data_txn, trans) destination.tpc_vote(trans) destination.tpc_finish(trans) zope2.13-2.13.21/source/ZODB3/src/ZODB/component.xml0000644000175000017500000003016412214017464020336 0ustar arnauarnau Path name to the main storage file. The names for supplemental files, including index and lock files, will be computed from this. If supplied, the file storage will provide blob support and this is the name of a directory to hold blob data. The directory will be created if it doeesn't exist. If no value (or an empty value) is provided, then no blob support will be provided. (You can still use a BlobStorage to provide blob support.) Flag that indicates whether the storage should be truncated if it already exists. If true, only reads may be executed against the storage. Note that the "pack" operation is not considered a write operation and is still allowed on a read-only filestorage. Maximum allowed size of the storage file. Operations which would cause the size of the storage to exceed the quota will result in a ZODB.FileStorage.FileStorageQuotaError being raised. The dotted name (dotted module name and object name) of a packer object. This is used to provide an alternative pack implementation. If false, then no garbage collection will be performed when packing. This can make packing go much faster and can avoid problems when objects are referenced only from other databases. If true, a copy of the database before packing is kept in a ".old" file. Path name to the blob cache directory. Tells whether the cache is a shared writable directory and that the ZEO protocol should not transfer the file but only the filename when committing. Maximum size of the ZEO blob cache, in bytes. If not set, then the cache size isn't checked and the blob directory will grow without bound. This option is ignored if shared_blob_dir is true. ZEO check size as percent of blob_cache_size. The ZEO cache size will be checked when this many bytes have been loaded into the cache. Defaults to 10% of the blob cache size. This option is ignored if shared_blob_dir is true. The name of the storage that the client wants to use. If the ZEO server serves more than one storage, the client selects the storage it wants to use by name. The default name is '1', which is also the default name for the ZEO server. The maximum size of the client cache, in bytes, KB or MB. The storage name. If unspecified, the address of the server will be used as the name. Enables persistent cache files. The string passed here is used to construct the cache filenames. If it is not specified, the client creates a temporary cache that will only be used by the current object. The directory where persistent cache files are stored. By default cache files, if they are persistent, are stored in the current directory. The minimum delay in seconds between attempts to connect to the server, in seconds. Defaults to 5 seconds. The maximum delay in seconds between attempts to connect to the server, in seconds. Defaults to 300 seconds. A boolean indicating whether the constructor should wait for the client to connect to the server and verify the cache before returning. The default is true. A flag indicating whether this should be a read-only storage, defaulting to false (i.e. writing is allowed by default). A flag indicating whether a read-only remote storage should be acceptable as a fallback when no writable storages are available. Defaults to false. At most one of read_only and read_only_fallback should be true. The authentication username of the server. The authentication password of the server. The authentication realm of the server. Some authentication schemes use a realm to identify the logic set of usernames that are accepted by this server. A flag indicating whether the client cache should be dropped instead of an expensive verification. A label for the client in server logs

Target size, in number of objects, of each connection's object cache. Target size, in total estimated size for objects, of each connection's object cache. "0" means no limit. The expected maximum number of simultaneously open connections. There is no hard limit (as many connections as are requested will be opened, until system resources are exhausted). Exceeding pool-size connections causes a warning message to be logged, and exceeding twice pool-size connections causes a critical message to be logged. The minimum interval that an unused (non-historical) connection should be kept. The expected maximum total number of historical connections simultaneously open. Target size, in number of objects, of each historical connection's object cache. Target size, in total estimated size of objects, of each historical connection's object cache. The minimum interval that an unused historical connection should be kept. When multidatabases are in use, this is the name given to this database in the collection. The name must be unique across all databases in the collection. The collection must also be given a mapping from its databases' names to their databases, but that cannot be specified in a ZODB config file. Applications using multidatabases typical supply a way to configure the mapping in their own config files, using the "databases" parameter of a DB constructor. If set to false, implicit cross references (the only kind currently possible) are disallowed. Path name to the blob storage directory.
zope2.13-2.13.21/source/ZODB3/src/ZODB/conversionhack.py0000644000175000017500000000201312214017464021170 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import persistent.mapping class fixer: def __of__(self, parent): def __setstate__(state, self=parent): self._container=state del self.__setstate__ return __setstate__ fixer=fixer() class hack: pass hack=hack() def __basicnew__(): r=persistent.mapping.PersistentMapping() r.__setstate__=fixer return r hack.__basicnew__=__basicnew__ zope2.13-2.13.21/source/ZODB3/src/ZODB/historical_connections.txt0000644000175000017500000002266212214017464023122 0ustar arnauarnau====================== Historical Connections ====================== Usage ===== A database can be opened with a read-only, historical connection when given a specific transaction or datetime. This can enable full-context application level conflict resolution, historical exploration and preparation for reverts, or even the use of a historical database revision as "production" while development continues on a "development" head. A database can be opened historically ``at`` or ``before`` a given transaction serial or datetime. Here's a simple example. It should work with any storage that supports ``loadBefore``. We'll begin our example with a fairly standard set up. We - make a storage and a database; - open a normal connection; - modify the database through the connection; - commit a transaction, remembering the time in UTC; - modify the database again; and - commit a transaction. >>> import ZODB.MappingStorage >>> db = ZODB.MappingStorage.DB() >>> conn = db.open() >>> import persistent.mapping >>> conn.root()['first'] = persistent.mapping.PersistentMapping(count=0) >>> import transaction >>> transaction.commit() We wait for some time to pass, record he time, and then make some other changes. >>> import time >>> time.sleep(.01) >>> import datetime >>> now = datetime.datetime.utcnow() >>> time.sleep(.01) >>> root = conn.root() >>> root['second'] = persistent.mapping.PersistentMapping() >>> root['first']['count'] += 1 >>> transaction.commit() Now we will show a historical connection. We'll open one using the ``now`` value we generated above, and then demonstrate that the state of the original connection, at the mutable head of the database, is different than the historical state. >>> transaction1 = transaction.TransactionManager() >>> historical_conn = db.open(transaction_manager=transaction1, at=now) >>> sorted(conn.root().keys()) ['first', 'second'] >>> conn.root()['first']['count'] 1 >>> historical_conn.root().keys() ['first'] >>> historical_conn.root()['first']['count'] 0 Moreover, the historical connection cannot commit changes. >>> historical_conn.root()['first']['count'] += 1 >>> historical_conn.root()['first']['count'] 1 >>> transaction1.commit() Traceback (most recent call last): ... ReadOnlyHistoryError >>> transaction1.abort() >>> historical_conn.root()['first']['count'] 0 (It is because of the mutable behavior outside of transactional semantics that we must have a separate connection, and associated object cache, per thread, even though the semantics should be readonly.) As demonstrated, a timezone-naive datetime will be interpreted as UTC. You can also pass a timezone-aware datetime or a serial (transaction id). Here's opening with a serial--the serial of the root at the time of the first commit. >>> historical_serial = historical_conn.root()._p_serial >>> historical_conn.close() >>> historical_conn = db.open(transaction_manager=transaction1, ... at=historical_serial) >>> historical_conn.root().keys() ['first'] >>> historical_conn.root()['first']['count'] 0 >>> historical_conn.close() We've shown the ``at`` argument. You can also ask to look ``before`` a datetime or serial. (It's an error to pass both [#not_both]_) In this example, we're looking at the database immediately prior to the most recent change to the root. >>> serial = conn.root()._p_serial >>> historical_conn = db.open( ... transaction_manager=transaction1, before=serial) >>> historical_conn.root().keys() ['first'] >>> historical_conn.root()['first']['count'] 0 In fact, ``at`` arguments are translated into ``before`` values because the underlying mechanism is a storage's loadBefore method. When you look at a connection's ``before`` attribute, it is normalized into a ``before`` serial, no matter what you pass into ``db.open``. >>> print conn.before None >>> historical_conn.before == serial True >>> conn.close() Configuration ============= Like normal connections, the database lets you set how many total historical connections can be active without generating a warning, and how many objects should be kept in each historical connection's object cache. >>> db.getHistoricalPoolSize() 3 >>> db.setHistoricalPoolSize(4) >>> db.getHistoricalPoolSize() 4 >>> db.getHistoricalCacheSize() 1000 >>> db.setHistoricalCacheSize(2000) >>> db.getHistoricalCacheSize() 2000 In addition, you can specify the minimum number of seconds that an unused historical connection should be kept. >>> db.getHistoricalTimeout() 300 >>> db.setHistoricalTimeout(400) >>> db.getHistoricalTimeout() 400 All three of these values can be specified in a ZConfig file. >>> import ZODB.config >>> db2 = ZODB.config.databaseFromString(''' ... ... ... historical-pool-size 3 ... historical-cache-size 1500 ... historical-timeout 6m ... ... ''') >>> db2.getHistoricalPoolSize() 3 >>> db2.getHistoricalCacheSize() 1500 >>> db2.getHistoricalTimeout() 360 The pool lets us reuse connections. To see this, we'll open some connections, close them, and then open them again: >>> conns1 = [db2.open(before=serial) for i in range(4)] >>> _ = [c.close() for c in conns1] >>> conns2 = [db2.open(before=serial) for i in range(4)] Now let's look at what we got. The first connection in conns 2 is the last connection in conns1, because it was the last connection closed. >>> conns2[0] is conns1[-1] True Also for the next two: >>> (conns2[1] is conns1[-2]), (conns2[2] is conns1[-3]) (True, True) But not for the last: >>> conns2[3] is conns1[-4] False Because the pool size was set to 3. Connections are also discarded if they haven't been used in a while. To see this, let's close two of the connections: >>> conns2[0].close(); conns2[1].close() We'l also set the historical timeout to be very low: >>> db2.setHistoricalTimeout(.01) >>> time.sleep(.1) >>> conns2[2].close(); conns2[3].close() Now, when we open 4 connections: >>> conns1 = [db2.open(before=serial) for i in range(4)] We'll see that only the last 2 connections from conn2 are in the result: >>> [c in conns1 for c in conns2] [False, False, True, True] If you change the historical cache size, that changes the size of the persistent cache on our connection. >>> historical_conn._cache.cache_size 2000 >>> db.setHistoricalCacheSize(1500) >>> historical_conn._cache.cache_size 1500 Invalidations ============= Invalidations are ignored for historical connections. This is another white box test. >>> historical_conn = db.open( ... transaction_manager=transaction1, at=serial) >>> conn = db.open() >>> sorted(conn.root().keys()) ['first', 'second'] >>> conn.root()['first']['count'] 1 >>> sorted(historical_conn.root().keys()) ['first', 'second'] >>> historical_conn.root()['first']['count'] 1 >>> conn.root()['first']['count'] += 1 >>> conn.root()['third'] = persistent.mapping.PersistentMapping() >>> transaction.commit() >>> len(historical_conn._invalidated) 0 >>> historical_conn.close() Note that if you try to open an historical connection to a time in the future, you will get an error. >>> historical_conn = db.open( ... at=datetime.datetime.utcnow()+datetime.timedelta(1)) Traceback (most recent call last): ... ValueError: cannot open an historical connection in the future. Warnings ======== First, if you use datetimes to get a historical connection, be aware that the conversion from datetime to transaction id has some pitfalls. Generally, the transaction ids in the database are only as time-accurate as the system clock was when the transaction id was created. Moreover, leap seconds are handled somewhat naively in the ZODB (largely because they are handled naively in Unix/ POSIX time) so any minute that contains a leap second may contain serials that are a bit off. This is not generally a problem for the ZODB, because serials are guaranteed to increase, but it does highlight the fact that serials are not guaranteed to be accurately connected to time. Generally, they are about as reliable as time.time. Second, historical connections currently introduce potentially wide variance in memory requirements for the applications. Since you can open up many connections to different serials, and each gets their own pool, you may collect quite a few connections. For now, at least, if you use this feature you need to be particularly careful of your memory usage. Get rid of pools when you know you can, and reuse the exact same values for ``at`` or ``before`` when possible. If historical connections are used for conflict resolution, these connections will probably be temporary--not saved in a pool--so that the extra memory usage would also be brief and unlikely to overlap. .. cleanup >>> db.close() >>> db2.close() .. ......... .. .. Footnotes .. .. ......... .. .. [#not_both] It is an error to try and pass both `at` and `before`. >>> historical_conn = db.open( ... transaction_manager=transaction1, at=now, before=historical_serial) Traceback (most recent call last): ... ValueError: can only pass zero or one of `at` and `before` zope2.13-2.13.21/source/ZODB3/src/ZODB/config.py0000644000175000017500000001727412214017464017440 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Open database and storage from a configuration.""" import os from cStringIO import StringIO import ZConfig import ZODB db_schema_path = os.path.join(ZODB.__path__[0], "config.xml") _db_schema = None s_schema_path = os.path.join(ZODB.__path__[0], "storage.xml") _s_schema = None def getDbSchema(): global _db_schema if _db_schema is None: _db_schema = ZConfig.loadSchema(db_schema_path) return _db_schema def getStorageSchema(): global _s_schema if _s_schema is None: _s_schema = ZConfig.loadSchema(s_schema_path) return _s_schema def databaseFromString(s): return databaseFromFile(StringIO(s)) def databaseFromFile(f): config, handle = ZConfig.loadConfigFile(getDbSchema(), f) return databaseFromConfig(config.database) def databaseFromURL(url): config, handler = ZConfig.loadConfig(getDbSchema(), url) return databaseFromConfig(config.database) def databaseFromConfig(database_factories): databases = {} first = None for factory in database_factories: db = factory.open(databases) if first is None: first = db return first def storageFromString(s): return storageFromFile(StringIO(s)) def storageFromFile(f): config, handle = ZConfig.loadConfigFile(getStorageSchema(), f) return storageFromConfig(config.storage) def storageFromURL(url): config, handler = ZConfig.loadConfig(getStorageSchema(), url) return storageFromConfig(config.storage) def storageFromConfig(section): return section.open() class BaseConfig: """Object representing a configured storage or database. Methods: open() -- open and return the configured object Attributes: name -- name of the storage """ def __init__(self, config): self.config = config self.name = config.getSectionName() def open(self, database_name='unnamed', databases=None): """Open and return the storage object.""" raise NotImplementedError class ZODBDatabase(BaseConfig): def open(self, databases=None): section = self.config storage = section.storage.open() options = {} def _option(name, oname=None): v = getattr(section, name) if v is not None: if oname is None: oname = name options[oname] = v _option('pool_timeout') _option('allow_implicit_cross_references', 'xrefs') _option('large_record_size') try: return ZODB.DB( storage, pool_size=section.pool_size, cache_size=section.cache_size, cache_size_bytes=section.cache_size_bytes, historical_pool_size=section.historical_pool_size, historical_cache_size=section.historical_cache_size, historical_cache_size_bytes=section.historical_cache_size_bytes, historical_timeout=section.historical_timeout, database_name=section.database_name or self.name or '', databases=databases, **options) except: storage.close() raise class MappingStorage(BaseConfig): def open(self): from ZODB.MappingStorage import MappingStorage return MappingStorage(self.config.name) class DemoStorage(BaseConfig): def open(self): base = changes = None for factory in self.config.factories: if factory.name == 'changes': changes = factory.open() else: if base is None: base = factory.open() else: raise ValueError("Too many base storages defined!") from ZODB.DemoStorage import DemoStorage return DemoStorage(self.config.name, base=base, changes=changes) class FileStorage(BaseConfig): def open(self): from ZODB.FileStorage import FileStorage config = self.config options = {} if getattr(config, 'packer', None): packer = config.packer if ':' in packer: m, expr = packer.split(':', 1) m = __import__(m, {}, {}, ['*']) options['packer'] = eval(expr, m.__dict__) else: m, name = config.packer.rsplit('.', 1) m = __import__(m, {}, {}, ['*']) options['packer'] = getattr(m, name) for name in ('blob_dir', 'create', 'read_only', 'quota', 'pack_gc', 'pack_keep_old'): v = getattr(config, name, self) if v is not self: options[name] = v return FileStorage(config.path, **options) class BlobStorage(BaseConfig): def open(self): from ZODB.blob import BlobStorage base = self.config.base.open() return BlobStorage(self.config.blob_dir, base) class ZEOClient(BaseConfig): def open(self): from ZEO.ClientStorage import ClientStorage # config.server is a multikey of socket-connection-address values # where the value is a socket family, address tuple. L = [server.address for server in self.config.server] options = {} if self.config.blob_cache_size is not None: options['blob_cache_size'] = self.config.blob_cache_size if self.config.blob_cache_size_check is not None: options['blob_cache_size_check'] = self.config.blob_cache_size_check if self.config.client_label is not None: options['client_label'] = self.config.client_label return ClientStorage( L, blob_dir=self.config.blob_dir, shared_blob_dir=self.config.shared_blob_dir, storage=self.config.storage, cache_size=self.config.cache_size, name=self.config.name, client=self.config.client, var=self.config.var, min_disconnect_poll=self.config.min_disconnect_poll, max_disconnect_poll=self.config.max_disconnect_poll, wait=self.config.wait, read_only=self.config.read_only, read_only_fallback=self.config.read_only_fallback, drop_cache_rather_verify=self.config.drop_cache_rather_verify, username=self.config.username, password=self.config.password, realm=self.config.realm, **options) class BDBStorage(BaseConfig): def open(self): from BDBStorage.BerkeleyBase import BerkeleyConfig storageclass = self.get_storageclass() bconf = BerkeleyConfig() for name in dir(BerkeleyConfig): if name.startswith('_'): continue setattr(bconf, name, getattr(self.config, name)) return storageclass(self.config.envdir, config=bconf) class BDBMinimalStorage(BDBStorage): def get_storageclass(self): import BDBStorage.BDBMinimalStorage return BDBStorage.BDBMinimalStorage.BDBMinimalStorage class BDBFullStorage(BDBStorage): def get_storageclass(self): import BDBStorage.BDBFullStorage return BDBStorage.BDBFullStorage.BDBFullStorage zope2.13-2.13.21/source/ZODB3/src/ZODB/ConflictResolution.py0000644000175000017500000002357012214017464022014 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import logging from cStringIO import StringIO from cPickle import Unpickler, Pickler from pickle import PicklingError import zope.interface from ZODB.POSException import ConflictError from ZODB.loglevels import BLATHER logger = logging.getLogger('ZODB.ConflictResolution') ResolvedSerial = 'rs' class BadClassName(Exception): pass class BadClass(object): def __init__(self, *args): self.args = args def __reduce__(self): raise BadClassName(*self.args) _class_cache = {} _class_cache_get = _class_cache.get def find_global(*args): cls = _class_cache_get(args, 0) if cls == 0: # Not cached. Try to import try: module = __import__(args[0], {}, {}, ['cluck']) except ImportError: cls = 1 else: cls = getattr(module, args[1], 1) _class_cache[args] = cls if cls == 1: logger.log(BLATHER, "Unable to load class", exc_info=True) if cls == 1: # Not importable if (isinstance(args, tuple) and len(args) == 2 and isinstance(args[0], basestring) and isinstance(args[1], basestring) ): return BadClass(*args) else: raise BadClassName(*args) return cls def state(self, oid, serial, prfactory, p=''): p = p or self.loadSerial(oid, serial) p = self._crs_untransform_record_data(p) file = StringIO(p) unpickler = Unpickler(file) unpickler.find_global = find_global unpickler.persistent_load = prfactory.persistent_load unpickler.load() # skip the class tuple return unpickler.load() class IPersistentReference(zope.interface.Interface): '''public contract for references to persistent objects from an object with conflicts.''' oid = zope.interface.Attribute( 'The oid of the persistent object that this reference represents') database_name = zope.interface.Attribute( '''The name of the database of the reference, *if* different. If not different, None.''') klass = zope.interface.Attribute( '''class meta data. Presence is not reliable.''') weak = zope.interface.Attribute( '''bool: whether this reference is weak''') def __cmp__(other): '''if other is equivalent reference, return 0; else raise ValueError. Equivalent in this case means that oid and database_name are the same. If either is a weak reference, we only support `is` equivalence, and otherwise raise a ValueError even if the datbase_names and oids are the same, rather than guess at the correct semantics. It is impossible to sort reliably, since the actual persistent class may have its own comparison, and we have no idea what it is. We assert that it is reasonably safe to assume that an object is equivalent to itself, but that's as much as we can say. We don't compare on 'is other', despite the PersistentReferenceFactory.data cache, because it is possible to have two references to the same object that are spelled with different data (for instance, one with a class and one without).''' class PersistentReference(object): zope.interface.implements(IPersistentReference) weak = False oid = database_name = klass = None def __init__(self, data): self.data = data # see serialize.py, ObjectReader._persistent_load if isinstance(data, tuple): self.oid, klass = data if isinstance(klass, BadClass): # We can't use the BadClass directly because, if # resolution succeeds, there's no good way to pickle # it. Fortunately, a class reference in a persistent # reference is allowed to be a module+name tuple. self.data = self.oid, klass.args elif isinstance(data, str): self.oid = data else: # a list reference_type = data[0] # 'm' = multi_persistent: (database_name, oid, klass) # 'n' = multi_oid: (database_name, oid) # 'w' = persistent weakref: (oid) # or persistent weakref: (oid, database_name) # else it is a weakref: reference_type if reference_type == 'm': self.database_name, self.oid, klass = data[1] if isinstance(klass, BadClass): # see above wrt BadClass data[1] = self.database_name, self.oid, klass.args elif reference_type == 'n': self.database_name, self.oid = data[1] elif reference_type == 'w': try: self.oid, = data[1] except ValueError: self.oid, self.database_name = data[1] self.weak = True else: assert len(data) == 1, 'unknown reference format' self.oid = data[0] self.weak = True def __cmp__(self, other): if self is other or ( isinstance(other, PersistentReference) and self.oid == other.oid and self.database_name == other.database_name and not self.weak and not other.weak): return 0 else: raise ValueError( "can't reliably compare against different " "PersistentReferences") def __repr__(self): return "PR(%s %s)" % (id(self), self.data) def __getstate__(self): raise PicklingError("Can't pickle PersistentReference") @property def klass(self): # for tests data = self.data if isinstance(data, tuple): return data[1] elif isinstance(data, list) and data[0] == 'm': return data[1][2] class PersistentReferenceFactory: data = None def persistent_load(self, ref): if self.data is None: self.data = {} key = tuple(ref) # lists are not hashable; formats are different enough # even after eliminating list/tuple distinction r = self.data.get(key, None) if r is None: r = PersistentReference(ref) self.data[key] = r return r def persistent_id(object): if getattr(object, '__class__', 0) is not PersistentReference: return None return object.data _unresolvable = {} def tryToResolveConflict(self, oid, committedSerial, oldSerial, newpickle, committedData=''): # class_tuple, old, committed, newstate = ('',''), 0, 0, 0 try: prfactory = PersistentReferenceFactory() newpickle = self._crs_untransform_record_data(newpickle) file = StringIO(newpickle) unpickler = Unpickler(file) unpickler.find_global = find_global unpickler.persistent_load = prfactory.persistent_load meta = unpickler.load() if isinstance(meta, tuple): klass = meta[0] newargs = meta[1] or () if isinstance(klass, tuple): klass = find_global(*klass) else: klass = meta newargs = () if klass in _unresolvable: raise ConflictError inst = klass.__new__(klass, *newargs) try: resolve = inst._p_resolveConflict except AttributeError: _unresolvable[klass] = 1 raise ConflictError oldData = self.loadSerial(oid, oldSerial) if not committedData: committedData = self.loadSerial(oid, committedSerial) if newpickle == oldData: # old -> new diff is empty, so merge is trivial return committedData if committedData == oldData: # old -> committed diff is empty, so merge is trivial return newpickle newstate = unpickler.load() old = state(self, oid, oldSerial, prfactory, oldData) committed = state(self, oid, committedSerial, prfactory, committedData) resolved = resolve(old, committed, newstate) file = StringIO() pickler = Pickler(file,1) pickler.inst_persistent_id = persistent_id pickler.dump(meta) pickler.dump(resolved) return self._crs_transform_record_data(file.getvalue(1)) except (ConflictError, BadClassName): pass except: # If anything else went wrong, catch it here and avoid passing an # arbitrary exception back to the client. The error here will mask # the original ConflictError. A client can recover from a # ConflictError, but not necessarily from other errors. But log # the error so that any problems can be fixed. logger.error("Unexpected error", exc_info=True) raise ConflictError(oid=oid, serials=(committedSerial, oldSerial), data=newpickle) class ConflictResolvingStorage(object): "Mix-in class that provides conflict resolution handling for storages" tryToResolveConflict = tryToResolveConflict _crs_transform_record_data = _crs_untransform_record_data = ( lambda self, o: o) def registerDB(self, wrapper): self._crs_untransform_record_data = wrapper.untransform_record_data self._crs_transform_record_data = wrapper.transform_record_data super(ConflictResolvingStorage, self).registerDB(wrapper) zope2.13-2.13.21/source/ZODB3/src/ZODB/interfaces.py0000644000175000017500000013751112214017464020313 0ustar arnauarnau############################################################################## # # Copyright (c) Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.interface import Interface, Attribute class IConnection(Interface): """Connection to ZODB for loading and storing objects. The Connection object serves as a data manager. The root() method on a Connection returns the root object for the database. This object and all objects reachable from it are associated with the Connection that loaded them. When a transaction commits, it uses the Connection to store modified objects. Typical use of ZODB is for each thread to have its own Connection and that no thread should have more than one Connection to the same database. A thread is associated with a Connection by loading objects from that Connection. Objects loaded by one thread should not be used by another thread. A Connection can be frozen to a serial--a transaction id, a single point in history-- when it is created. By default, a Connection is not associated with a serial; it uses current data. A Connection frozen to a serial is read-only. Each Connection provides an isolated, consistent view of the database, by managing independent copies of objects in the database. At transaction boundaries, these copies are updated to reflect the current state of the database. You should not instantiate this class directly; instead call the open() method of a DB instance. In many applications, root() is the only method of the Connection that you will need to use. Synchronization --------------- A Connection instance is not thread-safe. It is designed to support a thread model where each thread has its own transaction. If an application has more than one thread that uses the connection or the transaction the connection is registered with, the application should provide locking. The Connection manages movement of objects in and out of object storage. TODO: We should document an intended API for using a Connection via multiple threads. TODO: We should explain that the Connection has a cache and that multiple calls to get() will return a reference to the same object, provided that one of the earlier objects is still referenced. Object identity is preserved within a connection, but not across connections. TODO: Mention the database pool. A database connection always presents a consistent view of the objects in the database, although it may not always present the most current revision of any particular object. Modifications made by concurrent transactions are not visible until the next transaction boundary (abort or commit). Two options affect consistency. By default, the mvcc and synch options are enabled by default. If you pass mvcc=False to db.open(), the Connection will never read non-current revisions of an object. Instead it will raise a ReadConflictError to indicate that the current revision is unavailable because it was written after the current transaction began. The logic for handling modifications assumes that the thread that opened a Connection (called db.open()) is the thread that will use the Connection. If this is not true, you should pass synch=False to db.open(). When the synch option is disabled, some transaction boundaries will be missed by the Connection; in particular, if a transaction does not involve any modifications to objects loaded from the Connection and synch is disabled, the Connection will miss the transaction boundary. Two examples of this behavior are db.undo() and read-only transactions. Groups of methods: User Methods: root, get, add, close, db, sync, isReadOnly, cacheGC, cacheFullSweep, cacheMinimize Experimental Methods: onCloseCallbacks Database Invalidation Methods: invalidate Other Methods: exchange, getDebugInfo, setDebugInfo, getTransferCounts """ def add(ob): """Add a new object 'obj' to the database and assign it an oid. A persistent object is normally added to the database and assigned an oid when it becomes reachable to an object already in the database. In some cases, it is useful to create a new object and use its oid (_p_oid) in a single transaction. This method assigns a new oid regardless of whether the object is reachable. The object is added when the transaction commits. The object must implement the IPersistent interface and must not already be associated with a Connection. Parameters: obj: a Persistent object Raises TypeError if obj is not a persistent object. Raises InvalidObjectReference if obj is already associated with another connection. Raises ConnectionStateError if the connection is closed. """ def get(oid): """Return the persistent object with oid 'oid'. If the object was not in the cache and the object's class is ghostable, then a ghost will be returned. If the object is already in the cache, a reference to the cached object will be returned. Applications seldom need to call this method, because objects are loaded transparently during attribute lookup. Parameters: oid: an object id Raises KeyError if oid does not exist. It is possible that an object does not exist as of the current transaction, but existed in the past. It may even exist again in the future, if the transaction that removed it is undone. Raises ConnectionStateError if the connection is closed. """ def cacheMinimize(): """Deactivate all unmodified objects in the cache. Call _p_deactivate() on each cached object, attempting to turn it into a ghost. It is possible for individual objects to remain active. """ def cacheGC(): """Reduce cache size to target size. Call _p_deactivate() on cached objects until the cache size falls under the target size. """ def onCloseCallback(f): """Register a callable, f, to be called by close(). f will be called with no arguments before the Connection is closed. Parameters: f: method that will be called on `close` """ def close(): """Close the Connection. When the Connection is closed, all callbacks registered by onCloseCallback() are invoked and the cache is garbage collected. A closed Connection should not be used by client code. It can't load or store objects. Objects in the cache are not freed, because Connections are re-used and the cache is expected to be useful to the next client. """ def db(): """Returns a handle to the database this connection belongs to.""" def isReadOnly(): """Returns True if the storage for this connection is read only.""" def invalidate(tid, oids): """Notify the Connection that transaction 'tid' invalidated oids. When the next transaction boundary is reached, objects will be invalidated. If any of the invalidated objects are accessed by the current transaction, the revision written before Connection.tid will be used. The DB calls this method, even when the Connection is closed. Parameters: tid: the storage-level id of the transaction that committed oids: oids is an iterable of oids. """ def root(): """Return the database root object. The root is a persistent.mapping.PersistentMapping. """ # Multi-database support. connections = Attribute( """A mapping from database name to a Connection to that database. In multi-database use, the Connections of all members of a database collection share the same .connections object. In single-database use, of course this mapping contains a single entry. """) # TODO: should this accept all the arguments one may pass to DB.open()? def get_connection(database_name): """Return a Connection for the named database. This is intended to be called from an open Connection associated with a multi-database. In that case, database_name must be the name of a database within the database collection (probably the name of a different database than is associated with the calling Connection instance, but it's fine to use the name of the calling Connection object's database). A Connection for the named database is returned. If no connection to that database is already open, a new Connection is opened. So long as the multi-database remains open, passing the same name to get_connection() multiple times returns the same Connection object each time. """ def sync(): """Manually update the view on the database. This includes aborting the current transaction, getting a fresh and consistent view of the data (synchronizing with the storage if possible) and calling cacheGC() for this connection. This method was especially useful in ZODB 3.2 to better support read-only connections that were affected by a couple of problems. """ # Debug information def getDebugInfo(): """Returns a tuple with different items for debugging the connection. Debug information can be added to a connection by using setDebugInfo. """ def setDebugInfo(*items): """Add the given items to the debug information of this connection.""" def getTransferCounts(clear=False): """Returns the number of objects loaded and stored. If clear is True, reset the counters. """ def invalidateCache(): """Invalidate the connection cache This invalidates *all* objects in the cache. If the connection is open, subsequent reads will fail until a new transaction begins or until the connection os reopned. """ def readCurrent(obj): """Make sure an object being read is current This is used when applications want to ensure a higher level of consistency for some operations. This should be called when an object is read and the information read is used to write a separate object. """ class IStorageWrapper(Interface): """Storage wrapper interface This interface provides 3 facilities: - Out-of-band invalidation support A storage can notify it's wrapper of object invalidations that don't occur due to direct operations on the storage. Currently this is only used by ZEO client storages to pass invalidation messages sent from a server. - Record-reference extraction The references method can be used to extract referenced object IDs from a database record. This can be used by storages to provide more advanced garbage collection. A wrapper storage that transforms data will provide a references method that untransforms data passed to it and then pass the data to the layer above it. - Record transformation A storage wrapper may transform data, for example for compression or encryption. Methods are provided to transform or untransform data. This interface may be implemented by storage adapters or other intermediaries. For example, a storage adapter that provides encryption and/or compresssion will apply record transformations in it's references method. """ def invalidateCache(): """Discard all cached data This can be necessary if there have been major changes to stored data and it is either impractical to enumerate them or there would be so many that it would be inefficient to do so. """ def invalidate(transaction_id, oids, version=''): """Invalidate object ids committed by the given transaction The oids argument is an iterable of object identifiers. The version argument is provided for backward compatibility. If passed, it must be an empty string. """ def references(record, oids=None): """Scan the given record for object ids A list of object ids is returned. If a list is passed in, then it will be used and augmented. Otherwise, a new list will be created and returned. """ def transform_record_data(data): """Return transformed data """ def untransform_record_data(data): """Return untransformed data """ IStorageDB = IStorageWrapper # for backward compatibility class IDatabase(IStorageDB): """ZODB DB. """ # TODO: This interface is incomplete. # XXX how is it incomplete? databases = Attribute( """A mapping from database name to DB (database) object. In multi-database use, all DB members of a database collection share the same .databases object. In single-database use, of course this mapping contains a single entry. """) storage = Attribute( """The object that provides storage for the database This attribute is useful primarily for tests. Normal application code should rarely, if ever, have a need to use this attribute. """) def open(transaction_manager=None, serial=''): """Return an IConnection object for use by application code. transaction_manager: transaction manager to use. None means use the default transaction manager. serial: the serial (transaction id) of the database to open. An empty string (the default) means to open it to the newest serial. Specifying a serial results in a read-only historical connection. Note that the connection pool is managed as a stack, to increase the likelihood that the connection's stack will include useful objects. """ # TODO: Should this method be moved into some subinterface? def pack(t=None, days=0): """Pack the storage, deleting unused object revisions. A pack is always performed relative to a particular time, by default the current time. All object revisions that are not reachable as of the pack time are deleted from the storage. The cost of this operation varies by storage, but it is usually an expensive operation. There are two optional arguments that can be used to set the pack time: t, pack time in seconds since the epcoh, and days, the number of days to subtract from t or from the current time if t is not specified. """ # TODO: Should this method be moved into some subinterface? def undo(id, txn=None): """Undo a transaction identified by id. A transaction can be undone if all of the objects involved in the transaction were not modified subsequently, if any modifications can be resolved by conflict resolution, or if subsequent changes resulted in the same object state. The value of id should be generated by calling undoLog() or undoInfo(). The value of id is not the same as a transaction id used by other methods; it is unique to undo(). id: a storage-specific transaction identifier txn: transaction context to use for undo(). By default, uses the current transaction. """ def close(): """Close the database and its underlying storage. It is important to close the database, because the storage may flush in-memory data structures to disk when it is closed. Leaving the storage open with the process exits can cause the next open to be slow. What effect does closing the database have on existing connections? Technically, they remain open, but their storage is closed, so they stop behaving usefully. Perhaps close() should also close all the Connections. """ class IStorage(Interface): """A storage is responsible for storing and retrieving data of objects. Consistency and locking ----------------------- When transactions are committed, a storage assigns monotonically increasing transaction identifiers (tids) to the transactions and to the object versions written by the transactions. ZODB relies on this to decide if data in object caches are up to date and to implement multi-version concurrency control. There are methods in IStorage and in derived interfaces that provide information about the current revisions (tids) for objects or for the database as a whole. It is critical for the proper working of ZODB that the resulting tids are increasing with respect to the object identifier given or to the databases. That is, if there are 2 results for an object or for the database, R1 and R2, such that R1 is returned before R2, then the tid returned by R2 must be greater than or equal to the tid returned by R1. (When thinking about results for the database, think of these as results for all objects in the database.) This implies some sort of locking strategy. The key method is tcp_finish, which causes new tids to be generated and also, through the callback passed to it, returns new current tids for the objects stored in a transaction and for the database as a whole. The IStorage methods affected are lastTransaction, load, store, and tpc_finish. Derived interfaces may introduce additional methods. """ def close(): """Close the storage. Finalize the storage, releasing any external resources. The storage should not be used after this method is called. """ def getName(): """The name of the storage The format and interpretation of this name is storage dependent. It could be a file name, a database name, etc.. This is used soley for informational purposes. """ def getSize(): """An approximate size of the database, in bytes. This is used soley for informational purposes. """ def history(oid, size=1): """Return a sequence of history information dictionaries. Up to size objects (including no objects) may be returned. The information provides a log of the changes made to the object. Data are reported in reverse chronological order. Each dictionary has the following keys: time UTC seconds since the epoch (as in time.time) that the object revision was committed. tid The transaction identifier of the transaction that committed the version. serial An alias for tid, which expected by older clients. user_name The user identifier, if any (or an empty string) of the user on whos behalf the revision was committed. description The transaction description for the transaction that committed the revision. size The size of the revision data record. If the transaction had extension items, then these items are also included if they don't conflict with the keys above. """ def isReadOnly(): """Test whether a storage allows committing new transactions For a given storage instance, this method always returns the same value. Read-only-ness is a static property of a storage. """ # XXX Note that this method doesn't really buy us much, # especially since we have to account for the fact that a # ostensibly non-read-only storage may be read-only # transiently. It would be better to just have read-only errors. def lastTransaction(): """Return the id of the last committed transaction. If no transactions have been committed, return a string of 8 null (0) characters. """ def __len__(): """The approximate number of objects in the storage This is used soley for informational purposes. """ def load(oid, version): """Load data for an object id The version argumement should always be an empty string. It exists soley for backward compatibility with older storage implementations. A data record and serial are returned. The serial is a transaction identifier of the transaction that wrote the data record. A POSKeyError is raised if there is no record for the object id. """ def loadBefore(oid, tid): """Load the object data written before a transaction id If there isn't data before the object before the given transaction, then None is returned, otherwise three values are returned: - The data record - The transaction id of the data record - The transaction id of the following revision, if any, or None. If the object id isn't in the storage, then POSKeyError is raised. """ def loadSerial(oid, serial): """Load the object record for the give transaction id If a matching data record can be found, it is returned, otherwise, POSKeyError is raised. """ # The following two methods are effectively part of the interface, # as they are generally needed when one storage wraps # another. This deserves some thought, at probably debate, before # adding them. # # def _lock_acquire(): # """Acquire the storage lock # """ # def _lock_release(): # """Release the storage lock # """ def new_oid(): """Allocate a new object id. The object id returned is reserved at least as long as the storage is opened. The return value is a string. """ def pack(pack_time, referencesf): """Pack the storage It is up to the storage to interpret this call, however, the general idea is that the storage free space by: - discarding object revisions that were old and not current as of the given pack time. - garbage collecting objects that aren't reachable from the root object via revisions remaining after discarding revisions that were not current as of the pack time. The pack time is given as a UTC time in seconds since the epoch. The second argument is a function that should be used to extract object references from database records. This is needed to determine which objects are referenced from object revisions. """ def registerDB(wrapper): """Register a storage wrapper IStorageWrapper. The passed object is a wrapper object that provides an upcall interface to support composition. Note that, for historical reasons, an implementation may require a second argument, however, if required, the None will be passed as the second argument. Also, for historical reasons, this is called registerDB rather than register_wrapper. """ def sortKey(): """Sort key used to order distributed transactions When a transaction involved multiple storages, 2-phase commit operations are applied in sort-key order. This must be unique among storages used in a transaction. Obviously, the storage can't assure this, but it should construct the sort key so it has a reasonable chance of being unique. The result must be a string. """ def store(oid, serial, data, version, transaction): """Store data for the object id, oid. Arguments: oid The object identifier. This is either a string consisting of 8 nulls or a string previously returned by new_oid. serial The serial of the data that was read when the object was loaded from the database. If the object was created in the current transaction this will be a string consisting of 8 nulls. data The data record. This is opaque to the storage. version This must be an empty string. It exists for backward compatibility. transaction A transaction object. This should match the current transaction for the storage, set by tpc_begin. The new serial for the object is returned, but not necessarily immediately. It may be returned directly, or on a subsequent store or tpc_vote call. The return value may be: - None, or - A new serial (string) for the object If None is returned, then a new serial (or other special values) must ve returned in tpc_vote results. A serial, returned as a string, may be the special value ZODB.ConflictResolution.ResolvedSerial to indicate that a conflict occured and that the object should be invalidated. Several different exceptions may be raised when an error occurs. ConflictError is raised when serial does not match the most recent serial number for object oid and the conflict was not resolved by the storage. StorageTransactionError is raised when transaction does not match the current transaction. StorageError or, more often, a subclass of it is raised when an internal error occurs while the storage is handling the store() call. """ def tpc_abort(transaction): """Abort the transaction. Any changes made by the transaction are discarded. This call is ignored is the storage is not participating in two-phase commit or if the given transaction is not the same as the transaction the storage is commiting. """ def tpc_begin(transaction): """Begin the two-phase commit process. If storage is already participating in a two-phase commit using the same transaction, a StorageTransactionError is raised. If the storage is already participating in a two-phase commit using a different transaction, the call blocks until the current transaction ends (commits or aborts). """ def tpc_finish(transaction, func = lambda tid: None): """Finish the transaction, making any transaction changes permanent. Changes must be made permanent at this point. This call raises a StorageTransactionError if the storage isn't participating in two-phase commit or if it is committing a different transaction. Failure of this method is extremely serious. The second argument is a call-back function that must be called while the storage transaction lock is held. It takes the new transaction id generated by the transaction. """ def tpc_vote(transaction): """Provide a storage with an opportunity to veto a transaction This call raises a StorageTransactionError if the storage isn't participating in two-phase commit or if it is commiting a different transaction. If a transaction can be committed by a storage, then the method should return. If a transaction cannot be committed, then an exception should be raised. If this method returns without an error, then there must not be an error if tpc_finish or tpc_abort is called subsequently. The return value can be either None or a sequence of object-id and serial pairs giving new serials for objects who's ids were passed to previous store calls in the same transaction. After the tpc_vote call, new serials must have been returned, either from tpc_vote or store for objects passed to store. A serial returned in a sequence of oid/serial pairs, may be the special value ZODB.ConflictResolution.ResolvedSerial to indicate that a conflict occured and that the object should be invalidated. """ class IStorageRestoreable(IStorage): """Copying Transactions The IStorageRestoreable interface supports copying already-committed transactions from one storage to another. This is typically done for replication or for moving data from one storage implementation to another. """ def tpc_begin(transaction, tid=None): """Begin the two-phase commit process. If storage is already participating in a two-phase commit using the same transaction, the call is ignored. If the storage is already participating in a two-phase commit using a different transaction, the call blocks until the current transaction ends (commits or aborts). If a transaction id is given, then the transaction will use the given id rather than generating a new id. This is used when copying already committed transactions from another storage. """ # Note that the current implementation also accepts a status. # This is an artifact of: # - Earlier use of an undo status to undo revisions in place, # and, # - Incorrect pack garbage-collection algorithms (possibly # including the existing FileStorage implementation), that # failed to take into account records after the pack time. def restore(oid, serial, data, version, prev_txn, transaction): """Write data already committed in a separate database The restore method is used when copying data from one database to a replica of the database. It differs from store in that the data have already been committed, so there is no check for conflicts and no new transaction is is used for the data. Arguments: oid The object id for the record serial The transaction identifier that originally committed this object. data The record data. This will be None if the transaction undid the creation of the object. prev_txn The identifier of a previous transaction that held the object data. The target storage can sometimes use this as a hint to save space. transaction The current transaction. Nothing is returned. """ class IStorageRecordInformation(Interface): """Provide information about a single storage record """ oid = Attribute("The object id") tid = Attribute("The transaction id") data = Attribute("The data record") version = Attribute("The version id") data_txn = Attribute("The previous transaction id") class IStorageTransactionInformation(Interface): """Provide information about a storage transaction. Can be iterated over to retrieve the records modified in the transaction. """ tid = Attribute("Transaction id") status = Attribute("Transaction Status") # XXX what are valid values? user = Attribute("Transaction user") description = Attribute("Transaction Description") extension = Attribute( "A dictionary carrying the transaction's extension data") def __iter__(): """Iterate over the transaction's records given as IStorageRecordInformation objects. """ class IStorageIteration(Interface): """API for iterating over the contents of a storage.""" def iterator(start=None, stop=None): """Return an IStorageTransactionInformation iterator. If the start argument is not None, then iteration will start with the first transaction whose identifier is greater than or equal to start. If the stop argument is not None, then iteration will end with the last transaction whose identifier is less than or equal to stop. The iterator provides access to the data as available at the time when the iterator was retrieved. """ class IStorageUndoable(IStorage): """A storage supporting transactional undo. """ def supportsUndo(): """Return True, indicating that the storage supports undo. """ def undo(transaction_id, transaction): """Undo the transaction corresponding to the given transaction id. The transaction id is a value returned from undoInfo or undoLog, which may not be a stored transaction identifier as used elsewhere in the storage APIs. This method must only be called in the first phase of two-phase commit (after tpc_begin but before tpc_vote). It returns a serial (transaction id) and a sequence of object ids for objects affected by the transaction. """ # Used by DB (Actually, by TransactionalUndo) def undoLog(first, last, filter=None): """Return a sequence of descriptions for undoable transactions. Application code should call undoLog() on a DB instance instead of on the storage directly. A transaction description is a mapping with at least these keys: "time": The time, as float seconds since the epoch, when the transaction committed. "user_name": The value of the `.user` attribute on that transaction. "description": The value of the `.description` attribute on that transaction. "id`" A string uniquely identifying the transaction to the storage. If it's desired to undo this transaction, this is the `transaction_id` to pass to `undo()`. In addition, if any name+value pairs were added to the transaction by `setExtendedInfo()`, those may be added to the transaction description mapping too (for example, FileStorage's `undoLog()` does this). `filter` is a callable, taking one argument. A transaction description mapping is passed to `filter` for each potentially undoable transaction. The sequence returned by `undoLog()` excludes descriptions for which `filter` returns a false value. By default, `filter` always returns a true value. ZEO note: Arbitrary callables cannot be passed from a ZEO client to a ZEO server, and a ZEO client's implementation of `undoLog()` ignores any `filter` argument that may be passed. ZEO clients should use the related `undoInfo()` method instead (if they want to do filtering). Now picture a list containing descriptions of all undoable transactions that pass the filter, most recent transaction first (at index 0). The `first` and `last` arguments specify the slice of this (conceptual) list to be returned: `first`: This is the index of the first transaction description in the slice. It must be >= 0. `last`: If >= 0, first:last acts like a Python slice, selecting the descriptions at indices `first`, first+1, ..., up to but not including index `last`. At most last-first descriptions are in the slice, and `last` should be at least as large as `first` in this case. If `last` is less than 0, then abs(last) is taken to be the maximum number of descriptions in the slice (which still begins at index `first`). When `last` < 0, the same effect could be gotten by passing the positive first-last for `last` instead. """ # DB pass through def undoInfo(first=0, last=-20, specification=None): """Return a sequence of descriptions for undoable transactions. This is like `undoLog()`, except for the `specification` argument. If given, `specification` is a dictionary, and `undoInfo()` synthesizes a `filter` function `f` for `undoLog()` such that `f(desc)` returns true for a transaction description mapping `desc` if and only if `desc` maps each key in `specification` to the same value `specification` maps that key to. In other words, only extensions (or supersets) of `specification` match. ZEO note: `undoInfo()` passes the `specification` argument from a ZEO client to its ZEO server (while a ZEO client ignores any `filter` argument passed to `undoLog()`). """ # DB pass-through class IMVCCStorage(IStorage): """A storage that provides MVCC semantics internally. MVCC (multi-version concurrency control) means each user of a database has a snapshot view of the database. The snapshot view does not change, even if concurrent connections commit transactions, until a transaction boundary. Relational databases that support serializable transaction isolation provide MVCC. Storages that implement IMVCCStorage, such as RelStorage, provide MVCC semantics at the ZODB storage layer. When ZODB.Connection uses a storage that implements IMVCCStorage, each connection uses a connection-specific storage instance, and that storage instance provides a snapshot of the database. By contrast, storages that do not implement IMVCCStorage, such as FileStorage, rely on ZODB.Connection to provide MVCC semantics, so in that case, one storage instance is shared by many ZODB.Connections. Applications that use ZODB.Connection always have a snapshot view of the database; IMVCCStorage only modifies which layer of ZODB provides MVCC. Furthermore, IMVCCStorage changes the way object invalidation works. An essential feature of ZODB is the propagation of object invalidation messages to keep in-memory caches up to date. Storages like FileStorage and ZEO.ClientStorage send invalidation messages to all other Connection instances at transaction commit time. Storages that implement IMVCCStorage, on the other hand, expect the ZODB.Connection to poll for a list of invalidated objects. Certain methods of IMVCCStorage implementations open persistent back end database sessions and retain the sessions even after the method call finishes:: load loadEx loadSerial loadBefore store restore new_oid history tpc_begin tpc_vote tpc_abort tpc_finish If you know that the storage instance will no longer be used after calling any of these methods, you should call the release method to release the persistent sessions. The persistent sessions will be reopened as necessary if you call one of those methods again. Other storage methods open short lived back end sessions and close the back end sessions before returning. These include:: __len__ getSize undoLog undo pack iterator These methods do not provide MVCC semantics, so these methods operate on the most current view of the database, rather than the snapshot view that the other methods use. """ def new_instance(): """Creates and returns another storage instance. The returned instance provides IMVCCStorage and connects to the same back-end database. The database state visible by the instance will be a snapshot that varies independently of other storage instances. """ def release(): """Release all persistent sessions used by this storage instance. After this call, the storage instance can still be used; calling methods that use persistent sessions will cause the persistent sessions to be reopened. """ def poll_invalidations(): """Poll the storage for external changes. Returns either a sequence of OIDs that have changed, or None. When a sequence is returned, the corresponding objects should be removed from the ZODB in-memory cache. When None is returned, the storage is indicating that so much time has elapsed since the last poll that it is no longer possible to enumerate all of the changed OIDs, since the previous transaction seen by the connection has already been packed. In that case, the ZODB in-memory cache should be cleared. """ def sync(force=True): """Updates the internal snapshot to the current state of the database. If the force parameter is False, the storage may choose to ignore this call. By ignoring this call, a storage can reduce the frequency of database polls, thus reducing database load. """ class IStorageCurrentRecordIteration(IStorage): def record_iternext(next=None): """Iterate over the records in a storage Use like this: >>> next = None >>> while 1: ... oid, tid, data, next = storage.record_iternext(next) ... # do things with oid, tid, and data ... if next is None: ... break """ class IExternalGC(IStorage): def deleteObject(oid, serial, transaction): """Mark an object as deleted This method marks an object as deleted via a new object revision. Subsequent attempts to load current data for the object will fail with a POSKeyError, but loads for non-current data will suceed if there are previous non-delete records. The object will be removed from the storage when all not-delete records are removed. The serial argument must match the most recently committed serial for the object. This is a seat belt. This method can only be called in the first phase of 2-phase commit. """ class ReadVerifyingStorage(IStorage): def checkCurrentSerialInTransaction(oid, serial, transaction): """Check whether the given serial number is current. The method is called during the first phase of 2-phase commit to verify that data read in a transaction is current. The storage should raise a ReadConflictError if the serial is not current, although it may raise the exception later, in a call to store or in a call to tpc_vote. If no exception is raised, then the serial must remain current through the end of the transaction. """ class IBlob(Interface): """A BLOB supports efficient handling of large data within ZODB.""" def open(mode): """Open a blob Returns a file(-like) object for handling the blob data. mode: Mode to open the file with. Possible values: r,w,r+,a,c The mode 'c' is similar to 'r', except that an orinary file object is returned and may be used in a separate transaction and after the blob's database connection has been closed. """ def committed(): """Return a file name for committed data. The returned file name may be opened for reading or handed to other processes for reading. The file name isn't guarenteed to be valid indefinately. The file may be removed in the future as a result of garbage collection depending on system configuration. A BlobError will be raised if the blob has any uncommitted data. """ def consumeFile(filename): """Consume a file. Replace the current data of the blob with the file given under filename. The blob must not be opened for reading or writing when consuming a file. The blob will take over ownership of the file and will either rename or copy and remove it. The file must not be open. """ class IBlobStorage(Interface): """A storage supporting BLOBs.""" def storeBlob(oid, oldserial, data, blobfilename, version, transaction): """Stores data that has a BLOB attached. The blobfilename argument names a file containing blob data. The storage will take ownership of the file and will rename it (or copy and remove it) immediately, or at transaction-commit time. The file must not be open. The new serial for the object is returned, but not necessarily immediately. It may be returned directly, or on a subsequent store or tpc_vote call. The return value may be: - None - A new serial (string) for the object, or - An iterable of object-id and serial pairs giving new serials for objects. A serial, returned as a string or in a sequence of oid/serial pairs, may be the special value ZODB.ConflictResolution.ResolvedSerial to indicate that a conflict occured and that the object should be invalidated. Several different exceptions may be raised when an error occurs. ConflictError is raised when serial does not match the most recent serial number for object oid and the conflict was not resolved by the storage. StorageTransactionError is raised when transaction does not match the current transaction. StorageError or, more often, a subclass of it is raised when an internal error occurs while the storage is handling the store() call. """ def loadBlob(oid, serial): """Return the filename of the Blob data for this OID and serial. Returns a filename. Raises POSKeyError if the blobfile cannot be found. """ def openCommittedBlobFile(oid, serial, blob=None): """Return a file for committed data for the given object id and serial If a blob is provided, then a BlobFile object is returned, otherwise, an ordinary file is returned. In either case, the file is opened for binary reading. This method is used to allow storages that cache blob data to make sure that data are available at least long enough for the file to be opened. """ def temporaryDirectory(): """Return a directory that should be used for uncommitted blob data. If Blobs use this, then commits can be performed with a simple rename. """ class IBlobStorageRestoreable(IBlobStorage, IStorageRestoreable): def restoreBlob(oid, serial, data, blobfilename, prev_txn, transaction): """Write blob data already committed in a separate database See the restore and storeBlob methods. """ class IBroken(Interface): """Broken objects are placeholders for objects that can no longer be created because their class has gone away. They cannot be modified, but they retain their state. This allows them to be rebuild should the missing class be found again. A broken object's __class__ can be used to determine the original class' name (__name__) and module (__module__). The original object's state and initialization arguments are available in broken object attributes to aid analysis and reconstruction. """ def __setattr__(name, value): """You cannot modify broken objects. This will raise a ZODB.broken.BrokenModified exception. """ __Broken_newargs__ = Attribute("Arguments passed to __new__.") __Broken_initargs__ = Attribute("Arguments passed to __init__.") __Broken_state__ = Attribute("Value passed to __setstate__.") class BlobError(Exception): pass class StorageStopIteration(IndexError, StopIteration): """A combination of StopIteration and IndexError to provide a backwards-compatible exception. """ zope2.13-2.13.21/source/ZODB3/src/ZODB/transact.py0000644000175000017500000000370712214017464020006 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Tools to simplify transactions within applications.""" from ZODB.POSException import ReadConflictError, ConflictError import transaction def _commit(note): t = transaction.get() if note: t.note(note) t.commit() def transact(f, note=None, retries=5): """Returns transactional version of function argument f. Higher-order function that converts a regular function into a transactional function. The transactional function will retry up to retries time before giving up. If note, it will be added to the transaction metadata when it commits. The retries occur on ConflictErrors. If some other TransactionError occurs, the transaction will not be retried. """ # TODO: deal with ZEO disconnected errors? def g(*args, **kwargs): n = retries while n: n -= 1 try: r = f(*args, **kwargs) except ReadConflictError, msg: transaction.abort() if not n: raise continue try: _commit(note) except ConflictError, msg: transaction.abort() if not n: raise continue return r raise RuntimeError("couldn't commit transaction") return g zope2.13-2.13.21/source/ZODB3/src/ZODB/utils.py0000644000175000017500000002044212214017464017322 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import sys import time import struct from struct import pack, unpack from binascii import hexlify, unhexlify import cPickle as pickle from cStringIO import StringIO import warnings from tempfile import mkstemp import os from persistent.TimeStamp import TimeStamp __all__ = ['z64', 'p64', 'u64', 'U64', 'cp', 'newTid', 'oid_repr', 'serial_repr', 'tid_repr', 'positive_id', 'readable_tid_repr', 'DEPRECATED_ARGUMENT', 'deprecated37', 'deprecated38', 'get_pickle_metadata', 'locked', ] # A unique marker to give as the default value for a deprecated argument. # The method should then do a # # if that_arg is not DEPRECATED_ARGUMENT: # complain # # dance. DEPRECATED_ARGUMENT = object() # Raise DeprecationWarning, noting that the deprecated thing will go # away in ZODB 3.7. Point to the caller of our caller (i.e., at the # code using the deprecated thing). def deprecated37(msg): warnings.warn("This will be removed in ZODB 3.7:\n%s" % msg, DeprecationWarning, stacklevel=3) # Raise DeprecationWarning, noting that the deprecated thing will go # away in ZODB 3.8. Point to the caller of our caller (i.e., at the # code using the deprecated thing). def deprecated38(msg): warnings.warn("This will be removed in ZODB 3.8:\n%s" % msg, DeprecationWarning, stacklevel=3) z64 = '\0'*8 assert sys.hexversion >= 0x02030000 # The distinction between ints and longs is blurred in Python 2.2, # so u64() are U64() really the same. def p64(v): """Pack an integer or long into a 8-byte string""" return pack(">Q", v) def u64(v): """Unpack an 8-byte string into a 64-bit long integer.""" return unpack(">Q", v)[0] U64 = u64 def cp(f1, f2, length=None): """Copy all data from one file to another. It copies the data from the current position of the input file (f1) appending it to the current position of the output file (f2). It copies at most 'length' bytes. If 'length' isn't given, it copies until the end of the input file. """ read = f1.read write = f2.write n = 8192 if length is None: old_pos = f1.tell() f1.seek(0,2) length = f1.tell() f1.seek(old_pos) while length > 0: if n > length: n = length data = read(n) if not data: break write(data) length -= len(data) def newTid(old): t = time.time() ts = TimeStamp(*time.gmtime(t)[:5]+(t%60,)) if old is not None: ts = ts.laterThan(TimeStamp(old)) return `ts` def oid_repr(oid): if isinstance(oid, str) and len(oid) == 8: # Convert to hex and strip leading zeroes. as_hex = hexlify(oid).lstrip('0') # Ensure two characters per input byte. if len(as_hex) & 1: as_hex = '0' + as_hex elif as_hex == '': as_hex = '00' return '0x' + as_hex else: return repr(oid) def repr_to_oid(repr): if repr.startswith("0x"): repr = repr[2:] as_bin = unhexlify(repr) as_bin = "\x00"*(8-len(as_bin)) + as_bin return as_bin serial_repr = oid_repr tid_repr = serial_repr # For example, produce # '0x03441422948b4399 2002-04-14 20:50:34.815000' # for 8-byte string tid '\x03D\x14"\x94\x8bC\x99'. def readable_tid_repr(tid): result = tid_repr(tid) if isinstance(tid, str) and len(tid) == 8: result = "%s %s" % (result, TimeStamp(tid)) return result # Addresses can "look negative" on some boxes, some of the time. If you # feed a "negative address" to an %x format, Python 2.3 displays it as # unsigned, but produces a FutureWarning, because Python 2.4 will display # it as signed. So when you want to prodce an address, use positive_id() to # obtain it. # _ADDRESS_MASK is 2**(number_of_bits_in_a_native_pointer). Adding this to # a negative address gives a positive int with the same hex representation as # the significant bits in the original. _ADDRESS_MASK = 256 ** struct.calcsize('P') def positive_id(obj): """Return id(obj) as a non-negative integer.""" result = id(obj) if result < 0: result += _ADDRESS_MASK assert result > 0 return result # Given a ZODB pickle, return pair of strings (module_name, class_name). # Do this without importing the module or class object. # See ZODB/serialize.py's module docstring for the only docs that exist about # ZODB pickle format. If the code here gets smarter, please update those # docs to be at least as smart. The code here doesn't appear to make sense # for what serialize.py calls formats 5 and 6. def get_pickle_metadata(data): # ZODB's data records contain two pickles. The first is the class # of the object, the second is the object. We're only trying to # pick apart the first here, to extract the module and class names. if data.startswith('(c'): # pickle MARK GLOBAL opcode sequence global_prefix = 2 elif data.startswith('c'): # pickle GLOBAL opcode global_prefix = 1 else: global_prefix = 0 if global_prefix: # Formats 1 and 2. # Don't actually unpickle a class, because it will attempt to # load the class. Just break open the pickle and get the # module and class from it. The module and class names are given by # newline-terminated strings following the GLOBAL opcode. modname, classname, rest = data.split('\n', 2) modname = modname[global_prefix:] # strip GLOBAL opcode return modname, classname # Else there are a bunch of other possible formats. f = StringIO(data) u = pickle.Unpickler(f) try: class_info = u.load() except Exception, err: return '', '' if isinstance(class_info, tuple): if isinstance(class_info[0], tuple): # Formats 3 and 4. modname, classname = class_info[0] else: # Formats 5 and 6 (probably) end up here. modname, classname = class_info else: # This isn't a known format. modname = repr(class_info) classname = '' return modname, classname def mktemp(dir=None): """Create a temp file, known by name, in a semi-secure manner.""" handle, filename = mkstemp(dir=dir) os.close(handle) return filename class Locked(object): def __init__(self, func, inst=None, class_=None, preconditions=()): self.im_func = func self.im_self = inst self.im_class = class_ self.preconditions = preconditions def __get__(self, inst, class_): return self.__class__(self.im_func, inst, class_, self.preconditions) def __call__(self, *args, **kw): inst = self.im_self if inst is None: inst = args[0] func = self.im_func.__get__(self.im_self, self.im_class) inst._lock_acquire() try: for precondition in self.preconditions: if not precondition(inst): raise AssertionError( "Failed precondition: ", precondition.__doc__.strip()) return func(*args, **kw) finally: inst._lock_release() class locked(object): def __init__(self, *preconditions): self.preconditions = preconditions def __get__(self, inst, class_): # We didn't get any preconditions, so we have a single "precondition", # which is actually the function to call. func, = self.preconditions return Locked(func, inst, class_) def __call__(self, func): return Locked(func, preconditions=self.preconditions) zope2.13-2.13.21/source/ZODB3/src/ZODB/MappingStorage.py0000644000175000017500000002635512214017464021113 0ustar arnauarnau############################################################################## # # Copyright (c) Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """A simple in-memory mapping-based ZODB storage This storage provides an example implementation of a fairly full storage without distracting storage details. """ import BTrees import time import threading import ZODB.BaseStorage import ZODB.interfaces import ZODB.POSException import ZODB.TimeStamp import ZODB.utils import zope.interface class MappingStorage(object): zope.interface.implements( ZODB.interfaces.IStorage, ZODB.interfaces.IStorageIteration, ) def __init__(self, name='MappingStorage'): self.__name__ = name self._data = {} # {oid->{tid->pickle}} self._transactions = BTrees.OOBTree.OOBTree() # {tid->TransactionRecord} self._ltid = ZODB.utils.z64 self._last_pack = None _lock = threading.RLock() self._lock_acquire = _lock.acquire self._lock_release = _lock.release self._commit_lock = threading.Lock() self._opened = True self._transaction = None self._oid = 0 ###################################################################### # Preconditions: def opened(self): """The storage is open """ return self._opened def not_in_transaction(self): """The storage is not committing a transaction """ return self._transaction is None # ###################################################################### # testing framework (lame) def cleanup(self): pass # ZODB.interfaces.IStorage @ZODB.utils.locked def close(self): self._opened = False # ZODB.interfaces.IStorage def getName(self): return self.__name__ # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def getSize(self): size = 0 for oid, tid_data in self._data.items(): size += 50 for tid, pickle in tid_data.items(): size += 100+len(pickle) return size # ZEO.interfaces.IServeable @ZODB.utils.locked(opened) def getTid(self, oid): tid_data = self._data.get(oid) if tid_data: return tid_data.maxKey() raise ZODB.POSException.POSKeyError(oid) # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def history(self, oid, size=1): tid_data = self._data.get(oid) if not tid_data: raise ZODB.POSException.POSKeyError(oid) tids = tid_data.keys()[-size:] tids.reverse() return [ dict( time = ZODB.TimeStamp.TimeStamp(tid), tid = tid, serial = tid, user_name = self._transactions[tid].user, description = self._transactions[tid].description, extension = self._transactions[tid].extension, size = len(tid_data[tid]) ) for tid in tids] # ZODB.interfaces.IStorage def isReadOnly(self): return False # ZODB.interfaces.IStorageIteration def iterator(self, start=None, end=None): for transaction_record in self._transactions.values(start, end): yield transaction_record # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def lastTransaction(self): return self._ltid # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def __len__(self): return len(self._data) # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def load(self, oid, version=''): assert not version, "Versions are not supported" tid_data = self._data.get(oid) if tid_data: tid = tid_data.maxKey() return tid_data[tid], tid raise ZODB.POSException.POSKeyError(oid) # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def loadBefore(self, oid, tid): tid_data = self._data.get(oid) if tid_data: before = ZODB.utils.u64(tid) if not before: return None before = ZODB.utils.p64(before-1) tids_before = tid_data.keys(None, before) if tids_before: tids_after = tid_data.keys(tid, None) tid = tids_before[-1] return (tid_data[tid], tid, (tids_after and tids_after[0] or None) ) else: raise ZODB.POSException.POSKeyError(oid) # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def loadSerial(self, oid, serial): tid_data = self._data.get(oid) if tid_data: try: return tid_data[serial] except KeyError: pass raise ZODB.POSException.POSKeyError(oid, serial) # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def new_oid(self): self._oid += 1 return ZODB.utils.p64(self._oid) # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def pack(self, t, referencesf, gc=True): if not self._data: return stop = `ZODB.TimeStamp.TimeStamp(*time.gmtime(t)[:5]+(t%60,))` if self._last_pack is not None and self._last_pack >= stop: if self._last_pack == stop: return raise ValueError("Already packed to a later time") self._last_pack = stop transactions = self._transactions # Step 1, remove old non-current records for oid, tid_data in self._data.items(): tids_to_remove = tid_data.keys(None, stop) if tids_to_remove: tids_to_remove.pop() # Keep the last, if any if tids_to_remove: for tid in tids_to_remove: del tid_data[tid] if transactions[tid].pack(oid): del transactions[tid] if gc: # Step 2, GC. A simple sweep+copy new_data = BTrees.OOBTree.OOBTree() to_copy = set([ZODB.utils.z64]) while to_copy: oid = to_copy.pop() tid_data = self._data.pop(oid) new_data[oid] = tid_data for pickle in tid_data.values(): for oid in referencesf(pickle): if oid in new_data: continue to_copy.add(oid) # Remove left over data from transactions for oid, tid_data in self._data.items(): for tid in tid_data: if transactions[tid].pack(oid): del transactions[tid] self._data.clear() self._data.update(new_data) # ZODB.interfaces.IStorage def registerDB(self, db): pass # ZODB.interfaces.IStorage def sortKey(self): return self.__name__ # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def store(self, oid, serial, data, version, transaction): assert not version, "Versions are not supported" if transaction is not self._transaction: raise ZODB.POSException.StorageTransactionError(self, transaction) old_tid = None tid_data = self._data.get(oid) if tid_data: old_tid = tid_data.maxKey() if serial != old_tid: raise ZODB.POSException.ConflictError( oid=oid, serials=(old_tid, serial), data=data) self._tdata[oid] = data return self._tid checkCurrentSerialInTransaction = ( ZODB.BaseStorage.checkCurrentSerialInTransaction) # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def tpc_abort(self, transaction): if transaction is not self._transaction: return self._transaction = None self._commit_lock.release() # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def tpc_begin(self, transaction, tid=None): # The tid argument exists to support testing. if transaction is self._transaction: raise ZODB.POSException.StorageTransactionError( "Duplicate tpc_begin calls for same transaction") self._lock_release() self._commit_lock.acquire() self._lock_acquire() self._transaction = transaction self._tdata = {} if tid is None: if self._transactions: old_tid = self._transactions.maxKey() else: old_tid = None tid = ZODB.utils.newTid(old_tid) self._tid = tid # ZODB.interfaces.IStorage @ZODB.utils.locked(opened) def tpc_finish(self, transaction, func = lambda tid: None): if (transaction is not self._transaction): raise ZODB.POSException.StorageTransactionError( "tpc_finish called with wrong transaction") tid = self._tid func(tid) tdata = self._tdata for oid in tdata: tid_data = self._data.get(oid) if tid_data is None: tid_data = BTrees.OOBTree.OOBucket() self._data[oid] = tid_data tid_data[tid] = tdata[oid] self._ltid = tid self._transactions[tid] = TransactionRecord(tid, transaction, tdata) self._transaction = None del self._tdata self._commit_lock.release() # ZEO.interfaces.IServeable @ZODB.utils.locked(opened) def tpc_transaction(self): return self._transaction # ZODB.interfaces.IStorage def tpc_vote(self, transaction): if transaction is not self._transaction: raise ZODB.POSException.StorageTransactionError( "tpc_vote called with wrong transaction") class TransactionRecord: status = ' ' def __init__(self, tid, transaction, data): self.tid = tid self.user = transaction.user self.description = transaction.description extension = transaction._extension self.extension = extension self.data = data _extension = property(lambda self: self._extension, lambda self, v: setattr(self, '_extension', v), ) def __iter__(self): for oid, data in self.data.items(): yield DataRecord(oid, self.tid, data) def pack(self, oid): self.status = 'p' del self.data[oid] return not self.data class DataRecord(object): """Abstract base class for iterator protocol""" zope.interface.implements(ZODB.interfaces.IStorageRecordInformation) version = '' data_txn = None def __init__(self, oid, tid, data): self.oid = oid self.tid = tid self.data = data def DB(*args, **kw): return ZODB.DB(MappingStorage(), *args, **kw) zope2.13-2.13.21/source/ZODB3/src/ZODB/__init__.py0000644000175000017500000000202712214017464017720 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import sys from persistent import TimeStamp from persistent import list from persistent import mapping # Backward compat for old imports. sys.modules['ZODB.TimeStamp'] = sys.modules['persistent.TimeStamp'] sys.modules['ZODB.PersistentMapping'] = sys.modules['persistent.mapping'] sys.modules['ZODB.PersistentList'] = sys.modules['persistent.list'] del mapping, list, sys from DB import DB, connection zope2.13-2.13.21/source/ZODB3/src/ZODB/persistentclass.txt0000644000175000017500000001663312214017464021606 0ustar arnauarnau================== Persistent Classes ================== NOTE: persistent classes are EXPERIMENTAL and, in some sense, incomplete. This module exists largely to test changes made to support Zope 2 ZClasses, with their historical flaws. The persistentclass module provides a meta class that can be used to implement persistent classes. Persistent classes have the following properties: - They cannot be turned into ghosts - They can only contain picklable subobjects - They don't live in regular file-system modules Let's look at an example: >>> def __init__(self, name): ... self.name = name >>> def foo(self): ... return self.name, self.kind >>> import ZODB.persistentclass >>> class C: ... __metaclass__ = ZODB.persistentclass.PersistentMetaClass ... __init__ = __init__ ... __module__ = '__zodb__' ... foo = foo ... kind = 'sample' This example is obviously a bit contrived. In particular, we defined the methods outside of the class. Why? Because all of the items in a persistent class must be picklable. We defined the methods as global functions to make them picklable. Also note that we explictly set the module. Persistent classes don't live in normal Python modules. Rather, they live in the database. We use information in ``__module__`` to record where in the database. When we want to use a database, we will need to supply a custom class factory to load instances of the class. The class we created works a lot like other persistent objects. It has standard standard persistent attributes: >>> C._p_oid >>> C._p_jar >>> C._p_serial >>> C._p_changed False Because we haven't saved the object, the jar, oid, and serial are all None and it's not changed. We can create and use instances of the class: >>> c = C('first') >>> c.foo() ('first', 'sample') We can modify the class and none of the persistent attributes will change because the object hasn't been saved. >>> def bar(self): ... print 'bar', self.name >>> C.bar = bar >>> c.bar() bar first >>> C._p_oid >>> C._p_jar >>> C._p_serial >>> C._p_changed False Now, we can store the class in a database. We're going to use an explicit transaction manager so that we can show parallel transactions without having to use threads. >>> import transaction >>> tm = transaction.TransactionManager() >>> connection = some_database.open(transaction_manager=tm) >>> connection.root()['C'] = C >>> tm.commit() Now, if we look at the persistence variables, we'll see that they have values: >>> C._p_oid '\x00\x00\x00\x00\x00\x00\x00\x01' >>> C._p_jar is not None True >>> C._p_serial is not None True >>> C._p_changed False Now, if we modify the class: >>> def baz(self): ... print 'baz', self.name >>> C.baz = baz >>> c.baz() baz first We'll see that the class has changed: >>> C._p_changed True If we abort the transaction: >>> tm.abort() Then the class will return to it's prior state: >>> c.baz() Traceback (most recent call last): ... AttributeError: 'C' object has no attribute 'baz' >>> c.bar() bar first We can open another connection and access the class there. >>> tm2 = transaction.TransactionManager() >>> connection2 = some_database.open(transaction_manager=tm2) >>> C2 = connection2.root()['C'] >>> c2 = C2('other') >>> c2.bar() bar other If we make changes without commiting them: >>> C.bar = baz >>> c.bar() baz first >>> C is C2 False Other connections are unaffected: >>> connection2.sync() >>> c2.bar() bar other Until we commit: >>> tm.commit() >>> connection2.sync() >>> c2.bar() baz other Similarly, we don't see changes made in other connections: >>> C2.color = 'red' >>> tm2.commit() >>> c.color Traceback (most recent call last): ... AttributeError: 'C' object has no attribute 'color' until we sync: >>> connection.sync() >>> c.color 'red' Instances of Persistent Classes ------------------------------- We can, of course, store instances of persistent classes in the database: >>> c.color = 'blue' >>> connection.root()['c'] = c >>> tm.commit() >>> connection2.sync() >>> connection2.root()['c'].color 'blue' NOTE: If a non-persistent instance of a persistent class is copied, the class may be copied as well. This is usually not the desired result. Persistent instances of persistent classes ------------------------------------------ Persistent instances of persistent classes are handled differently than normal instances. When we copy a persistent instances of a persistent class, we want to avoid copying the class. Lets create a persistent class that subclasses Persistent: >>> import persistent >>> class P(persistent.Persistent, C): ... __module__ = '__zodb__' ... color = 'green' >>> connection.root()['P'] = P >>> import persistent.mapping >>> connection.root()['obs'] = persistent.mapping.PersistentMapping() >>> p = P('p') >>> connection.root()['obs']['p'] = p >>> tm.commit() You might be wondering why we didn't just stick 'p' into the root object. We created an intermediate persistent object instead. We are storing persistent classes in the root object. To create a ghost for a persistent instance of a persistent class, we need to be able to be able to access the root object and it must be loaded first. If the instance was in the root object, we'd be unable to create it while loading the root object. Now, if we try to load it, we get a broken oject: >>> connection2.sync() >>> connection2.root()['obs']['p'] because the module, `__zodb__` can't be loaded. We need to provide a class factory that knows about this special module. Here we'll supply a sample class factory that looks up a class name in the database root if the module is `__zodb__`. It falls back to the normal class lookup for other modules: >>> from ZODB.broken import find_global >>> def classFactory(connection, modulename, globalname): ... if modulename == '__zodb__': ... return connection.root()[globalname] ... return find_global(modulename, globalname) >>> some_database.classFactory = classFactory Normally, the classFactory should be set before a database is opened. We'll reopen the connections we're using. We'll assign the old connections to a variable first to prevent getting them from the connection pool: >>> old = connection, connection2 >>> connection = some_database.open(transaction_manager=tm) >>> connection2 = some_database.open(transaction_manager=tm2) Now, we can read the object: >>> connection2.root()['obs']['p'].color 'green' >>> connection2.root()['obs']['p'].color = 'blue' >>> tm2.commit() >>> connection.sync() >>> p = connection.root()['obs']['p'] >>> p.color 'blue' Copying ------- If we copy an instance via export/import, the copy and the original share the same class: >>> file = connection.exportFile(p._p_oid) >>> file.seek(0) >>> cp = connection.importFile(file) >>> file.close() >>> cp.color 'blue' >>> cp is not p True >>> cp.__class__ is p.__class__ True >>> tm.abort() XXX test abort of import zope2.13-2.13.21/source/ZODB3/src/ZODB/ConflictResolution.txt0000644000175000017500000004700612214017464022203 0ustar arnauarnau=================== Conflict Resolution =================== Overview ======== Conflict resolution is a way to resolve transaction conflicts that would otherwise abort a transaction. As such, it risks data integrity in order to try to avoid throwing away potentially computationally expensive transactions. The risk of harming data integrity should not be underestimated. Writing conflict resolution code takes some responsibility for transactional integrity away from the ZODB, and puts it in the hands of the developer writing the conflict resolution code. The current conflict resolution code is implemented with a storage mix-in found in ZODB/ConflictResolution.py. The idea's proposal, and an explanation of the interface, can be found here: http://www.zope.org/Members/jim/ZODB/ApplicationLevelConflictResolution Here is the most pertinent section, somewhat modified for this document's use: A new interface is proposed to allow object authors to provide a method for resolving conflicts. When a conflict is detected, then the database checks to see if the class of the object being saved defines the method, _p_resolveConflict. If the method is defined, then the method is called on the object. If the method succeeds, then the object change can be committed, otherwise a ConflictError is raised as usual. def _p_resolveConflict(oldState, savedState, newState): Return the state of the object after resolving different changes. Arguments: oldState The state of the object that the changes made by the current transaction were based on. The method is permitted to modify this value. savedState The state of the object that is currently stored in the database. This state was written after oldState and reflects changes made by a transaction that committed before the current transaction. The method is permitted to modify this value. newState The state after changes made by the current transaction. The method is not permitted to modify this value. This method should compute a new state by merging changes reflected in savedState and newState, relative to oldState. If the method cannot resolve the changes, then it should raise ZODB.POSException.ConflictError. Consider an extremely simple example, a counter:: from persistent import Persistent class PCounter(Persistent): '`value` is readonly; increment it with `inc`.' _val = 0 def inc(self): self._val += 1 @property def value(self): return self._val def _p_resolveConflict(self, oldState, savedState, newState): oldState['_val'] = ( savedState.get('_val', 0) + newState.get('_val', 0) - oldState.get('_val', 0)) return oldState .. -> src >>> import ConflictResolution_txt >>> exec src in ConflictResolution_txt.__dict__ >>> PCounter = ConflictResolution_txt.PCounter >>> PCounter.__module__ = 'ConflictResolution_txt' By "state", the excerpt above means the value used by __getstate__ and __setstate__: a dictionary, in most cases. We'll look at more details below, but let's continue the example above with a simple successful resolution story. First we create a storage and a database, and put a PCounter in the database. >>> import ZODB >>> db = ZODB.DB('Data.fs') >>> import transaction >>> tm_A = transaction.TransactionManager() >>> conn_A = db.open(transaction_manager=tm_A) >>> p_A = conn_A.root()['p'] = PCounter() >>> p_A.value 0 >>> tm_A.commit() Now get another copy of 'p' so we can make a conflict. Think of `conn_A` (connection A) as one thread, and `conn_B` (connection B) as a concurrent thread. `p_A` is a view on the object in the first connection, and `p_B` is a view on *the same persistent object* in the second connection. >>> tm_B = transaction.TransactionManager() >>> conn_B = db.open(transaction_manager=tm_B) >>> p_B = conn_B.root()['p'] >>> p_B.value 0 >>> p_A._p_oid == p_B._p_oid True Now we can make a conflict, and see it resolved. >>> p_A.inc() >>> p_A.value 1 >>> p_B.inc() >>> p_B.value 1 >>> tm_B.commit() >>> p_B.value 1 >>> tm_A.commit() >>> p_A.value 2 We need to synchronize connection B, in any of a variety of ways, to see the change from connection A. >>> p_B.value 1 >>> trans = tm_B.begin() >>> p_B.value 2 A very similar class found in real world use is BTrees.Length.Length. This conflict resolution approach is simple, yet powerful. However, it has a few caveats and rough edges in practice. The simplicity, then, is a bit of a disguise. Again, be warned, writing conflict resolution code means that you claim significant responsibilty for your data integrity. Because of the rough edges, the current conflict resolution approach is slated for change (as of this writing, according to Jim Fulton, the ZODB primary author and maintainer). Others have talked about different approaches as well (see, for instance, http://www.python.org/~jeremy/weblog/031031c.html). But for now, the _p_resolveConflict method is what we have. Caveats and Dangers =================== Here are caveats for working with this conflict resolution approach. Each sub-section has a "DANGERS" section that outlines what might happen if you ignore the warning. We work from the least danger to the most. Conflict Resolution Is on the Server ------------------------------------ If you are using ZEO or ZRS, be aware that the classes for which you have conflict resolution code *and* the classes of the non-persistent objects they reference must be available to import by the *server* (or ZRS primary). DANGERS: You think you are going to get conflict resolution, but you won't. Ignore `self` ------------- Even though the _p_resolveConflict method has a "self", ignore it. Don't change it. You make changes by returning the state. This is effectively a class method. DANGERS: The changes you make to the instance will be discarded. The instance is not initialized, so other methods that depend on instance attributes will not work. Here's an example of a broken _p_resolveConflict method:: class PCounter2(PCounter): def __init__(self): self.data = [] def _p_resolveConflict(self, oldState, savedState, newState): self.data.append('bad idea') return super(PCounter2, self)._p_resolveConflict( oldState, savedState, newState) .. -> src >>> exec src in ConflictResolution_txt.__dict__ >>> PCounter2 = ConflictResolution_txt.PCounter2 >>> PCounter2.__module__ = 'ConflictResolution_txt' Now we'll prepare for the conflict again. >>> p2_A = conn_A.root()['p2'] = PCounter2() >>> p2_A.value 0 >>> tm_A.commit() >>> trans = tm_B.begin() # sync >>> p2_B = conn_B.root()['p2'] >>> p2_B.value 0 >>> p2_A._p_oid == p2_B._p_oid True And now we will make a conflict. >>> p2_A.inc() >>> p2_A.value 1 >>> p2_B.inc() >>> p2_B.value 1 >>> tm_B.commit() >>> p2_B.value 1 >>> tm_A.commit() # doctest: +ELLIPSIS Traceback (most recent call last): ... ConflictError: database conflict error... oops! >>> tm_A.abort() >>> p2_A.value 1 >>> trans = tm_B.begin() >>> p2_B.value 1 Watch Out for Persistent Objects in the State --------------------------------------------- If the object state has a reference to Persistent objects (instances of classes that inherit from persistent.Persistent) then these references *will not be loaded and are inaccessible*. Instead, persistent objects in the state dictionary are ZODB.ConflictResolution.PersistentReference instances. These objects have the following interface:: class IPersistentReference(zope.interface.Interface): '''public contract for references to persistent objects from an object with conflicts.''' oid = zope.interface.Attribute( 'The oid of the persistent object that this reference represents') database_name = zope.interface.Attribute( '''The name of the database of the reference, *if* different. If not different, None.''') klass = zope.interface.Attribute( '''class meta data. Presence is not reliable.''') weak = zope.interface.Attribute( '''bool: whether this reference is weak''') def __cmp__(other): '''if other is equivalent reference, return 0; else raise ValueError. Equivalent in this case means that oid and database_name are the same. If either is a weak reference, we only support `is` equivalence, and otherwise raise a ValueError even if the datbase_names and oids are the same, rather than guess at the correct semantics. It is impossible to sort reliably, since the actual persistent class may have its own comparison, and we have no idea what it is. We assert that it is reasonably safe to assume that an object is equivalent to itself, but that's as much as we can say. We don't compare on 'is other', despite the PersistentReferenceFactory.data cache, because it is possible to have two references to the same object that are spelled with different data (for instance, one with a class and one without).''' So let's look at one of these. Let's assume we have three, `old`, `saved`, and `new`, each representing a persistent reference to the same object within a _p_resolveConflict call from the oldState, savedState, and newState [#get_persistent_reference]_. They have an oid, `weak` is False, and `database_name` is None. `klass` happens to be set but this is not always the case. >>> isinstance(new.oid, str) True >>> new.weak False >>> print new.database_name None >>> new.klass is PCounter True There are a few subtleties to highlight here. First, notice that the database_name is only present if this is a cross-database reference (see cross-database-references.txt in this directory, and examples below). The database name and oid is sometimes a reasonable way to reliably sort Persistent objects (see zope.app.keyreference, for instance) but if your code compares one PersistentReference with a database_name and another without, you need to refuse to give an answer and raise an exception, because you can't know how the unknown database_name sorts. We already saw a persistent reference with a database_name of None. Now let's suppose `new` is an example of a cross-database reference from a database named '2' [#cross-database]_. >>> new.database_name '2' As seen, the database_name is available for this cross-database reference, and not for others. References to persistent objects, as defined in seialize.py, have other variations, such as weak references, which are handled but not discussed here [#instantiation_test]_ Second, notice the __cmp__ behavior [#cmp_test]_. This is new behavior after ZODB 3.8 and addresses a serious problem for when persistent objects are compared in an _p_resolveConflict, such as that in the ZODB BTrees code. Prior to this change, it was not safe to use Persistent objects as keys in a BTree. You needed to define a __cmp__ for them to be sorted reliably out of the context of conflict resolution, but then during conflict resolution the sorting would be arbitrary, on the basis of the persistent reference's memory location. This could have lead to inconsistent state for BTrees (or BTree module buckets or tree sets or sets). Here's an example of how the new behavior stops potentially incorrect resolution. >>> import BTrees >>> treeset_A = conn_A.root()['treeset'] = BTrees.family32.OI.TreeSet() >>> tm_A.commit() >>> trans = tm_B.begin() # sync >>> treeset_B = conn_B.root()['treeset'] >>> treeset_A.insert(PCounter()) 1 >>> treeset_B.insert(PCounter()) 1 >>> tm_B.commit() >>> tm_A.commit() # doctest: +ELLIPSIS Traceback (most recent call last): ... ConflictError: database conflict error... >>> tm_A.abort() Third, note that, even if the persistent object to which the reference refers changes in the same transaction, the reference is still the same. DANGERS: subtle and potentially serious. Beyond the two subtleties above, which should now be addressed, there is a general problem for objects that are composites of smaller persistent objects--for instance, a BTree, in which the BTree and each bucket is a persistent object; or a zc.queue.CompositePersistentQueue, which is a persistent queue of persistent queues. Consider the following situation. It is actually solved, but it is a concrete example of what might go wrong. A BTree (persistent object) has a two buckets (persistent objects). The second bucket has one persistent object in it. Concurrently, one thread deletes the one object in the second bucket, which causes the BTree to dump the bucket; and another thread puts an object in the second bucket. What happens during conflict resolution? Remember, each persistent object cannot see the other. From the perspective of the BTree object, it has no conflicts: one transaction modified it, causing it to lose a bucket; and the other transaction did not change it. From the perspective of the bucket, one transaction deleted an object and the other added it: it will resolve conflicts and say that the bucket has the new object and not the old one. However, it will be garbage collected, and effectively the addition of the new object will be lost. As mentioned, this story is actually solved for BTrees. As BTrees/MergeTemplate.c explains, whenever savedState or newState for a bucket shows an empty bucket, the code refuses to resolve the conflict: this avoids the situation above. >>> bucket_A = conn_A.root()['bucket'] = BTrees.family32.II.Bucket() >>> bucket_A[0] = 255 >>> tm_A.commit() >>> trans = tm_B.begin() # sync >>> bucket_B = conn_B.root()['bucket'] >>> bucket_B[1] = 254 >>> del bucket_A[0] >>> tm_B.commit() >>> tm_A.commit() # doctest: +ELLIPSIS Traceback (most recent call last): ... ConflictError: database conflict error... >>> tm_A.abort() However, the story highlights the kinds of subtle problems that units made up of multiple composite Persistent objects need to contemplate. Any structure made up of objects that contain persistent objects with conflict resolution code, as a catalog index is made up of multiple BTree Buckets and Sets, each with conflict resolution, needs to think through these kinds of problems or be faced with potential data integrity issues. .. cleanup >>> db.close() >>> db1.close() >>> db2.close() .. ......... .. .. FOOTNOTES .. .. ......... .. .. [#get_persistent_reference] We'll catch persistent references with a class mutable. :: class PCounter3(PCounter): data = [] def _p_resolveConflict(self, oldState, savedState, newState): PCounter3.data.append( (oldState.get('other'), savedState.get('other'), newState.get('other'))) return super(PCounter3, self)._p_resolveConflict( oldState, savedState, newState) .. -> src >>> exec src in ConflictResolution_txt.__dict__ >>> PCounter3 = ConflictResolution_txt.PCounter3 >>> PCounter3.__module__ = 'ConflictResolution_txt' >>> p3_A = conn_A.root()['p3'] = PCounter3() >>> p3_A.other = conn_A.root()['p'] >>> tm_A.commit() >>> trans = tm_B.begin() # sync >>> p3_B = conn_B.root()['p3'] >>> p3_A.inc() >>> p3_B.inc() >>> tm_B.commit() >>> tm_A.commit() >>> old, saved, new = PCounter3.data[-1] .. [#cross-database] We need a whole different set of databases for this. See cross-database-references.txt in this directory for a discussion of what is going on here. >>> databases = {} >>> db1 = ZODB.DB('1', databases=databases, database_name='1') >>> db2 = ZODB.DB('2', databases=databases, database_name='2') >>> tm_multi_A = transaction.TransactionManager() >>> conn_1A = db1.open(transaction_manager=tm_multi_A) >>> conn_2A = conn_1A.get_connection('2') >>> p4_1A = conn_1A.root()['p4'] = PCounter3() >>> p5_2A = conn_2A.root()['p5'] = PCounter3() >>> conn_2A.add(p5_2A) >>> p4_1A.other = p5_2A >>> tm_multi_A.commit() >>> tm_multi_B = transaction.TransactionManager() >>> conn_1B = db1.open(transaction_manager=tm_multi_B) >>> p4_1B = conn_1B.root()['p4'] >>> p4_1A.inc() >>> p4_1B.inc() >>> tm_multi_B.commit() >>> tm_multi_A.commit() >>> old, saved, new = PCounter3.data[-1] .. [#instantiation_test] We'll simply instantiate PersistentReferences with examples of types described in ZODB/serialize.py. >>> from ZODB.ConflictResolution import PersistentReference >>> ref1 = PersistentReference('my_oid') >>> ref1.oid 'my_oid' >>> print ref1.klass None >>> print ref1.database_name None >>> ref1.weak False >>> ref2 = PersistentReference(('my_oid', 'my_class')) >>> ref2.oid 'my_oid' >>> ref2.klass 'my_class' >>> print ref2.database_name None >>> ref2.weak False >>> ref3 = PersistentReference(['w', ('my_oid',)]) >>> ref3.oid 'my_oid' >>> print ref3.klass None >>> print ref3.database_name None >>> ref3.weak True >>> ref3a = PersistentReference(['w', ('my_oid', 'other_db')]) >>> ref3a.oid 'my_oid' >>> print ref3a.klass None >>> ref3a.database_name 'other_db' >>> ref3a.weak True >>> ref4 = PersistentReference(['m', ('other_db', 'my_oid', 'my_class')]) >>> ref4.oid 'my_oid' >>> ref4.klass 'my_class' >>> ref4.database_name 'other_db' >>> ref4.weak False >>> ref5 = PersistentReference(['n', ('other_db', 'my_oid')]) >>> ref5.oid 'my_oid' >>> print ref5.klass None >>> ref5.database_name 'other_db' >>> ref5.weak False >>> ref6 = PersistentReference(['my_oid']) # legacy >>> ref6.oid 'my_oid' >>> print ref6.klass None >>> print ref6.database_name None >>> ref6.weak True .. [#cmp_test] All references are equal to themselves. >>> ref1 == ref1 and ref2 == ref2 and ref4 == ref4 and ref5 == ref5 True >>> ref3 == ref3 and ref3a == ref3a and ref6 == ref6 # weak references True Non-weak references with the same oid and database_name are equal. >>> ref1 == ref2 and ref4 == ref5 True Everything else raises a ValueError: weak references with the same oid and database, and references with a different database_name or oid. >>> ref3 == ref6 Traceback (most recent call last): ... ValueError: can't reliably compare against different PersistentReferences >>> ref1 == PersistentReference(('another_oid', 'my_class')) Traceback (most recent call last): ... ValueError: can't reliably compare against different PersistentReferences >>> ref4 == PersistentReference( ... ['m', ('another_db', 'my_oid', 'my_class')]) Traceback (most recent call last): ... ValueError: can't reliably compare against different PersistentReferences zope2.13-2.13.21/source/ZODB3/src/ZODB/collaborations.txt0000644000175000017500000001350012214017464021361 0ustar arnauarnau======================= Collabortation Diagrams ======================= This file contains several collaboration diagrams for the ZODB. Simple fetch, modify, commit ============================ Participants ------------ - ``DB``: ``ZODB.DB.DB`` - ``C``: ``ZODB.Connection.Connection`` - ``S``: ``ZODB.FileStorage.FileStorage`` - ``T``: ``transaction.interfaces.ITransaction`` - ``TM``: ``transaction.interfaces.ITransactionManager`` - ``o1``, ``o2``, ...: pre-existing persistent objects Scenario -------- :: DB.open() create C TM.registerSynch(C) TM.begin() create T C.get(1) # fetches o1 C.get(2) # fetches o2 C.get(3) # fetches o3 o1.modify() # anything that modifies o1 C.register(o1) T.join(C) o2.modify() C.register(o2) # T.join(C) does not happen again o1.modify() # C.register(o1) doesn't happen again, because o1 was already # in the changed state. T.commit() C.beforeCompletion(T) C.tpc_begin(T) S.tpc_begin(T) C.commit(T) S.store(1, ..., T) S.store(2, ..., T) # o3 is not stored, because it wasn't modified C.tpc_vote(T) S.tpc_vote(T) C.tpc_finish(T) S.tpc_finish(T, f) # f is a callback function, which arranges # to call DB.invalidate (next) DB.invalidate(tid, {1: 1, 2: 1}, C) C2.invalidate(tid, {1: 1, 2: 1}) # for all connections # C2 to DB, where C2 # is not C TM.free(T) C.afterCompletion(T) C._flush_invalidations() # Processes invalidations that may have come in from other # transactions. Simple fetch, modify, abort =========================== Participants ------------ - ``DB``: ``ZODB.DB.DB`` - ``C``: ``ZODB.Connection.Connection`` - ``S``: ``ZODB.FileStorage.FileStorage`` - ``T``: ``transaction.interfaces.ITransaction`` - ``TM``: ``transaction.interfaces.ITransactionManager`` - ``o1``, ``o2``, ...: pre-existing persistent objects Scenario -------- :: DB.open() create C TM.registerSynch(C) TM.begin() create T C.get(1) # fetches o1 C.get(2) # fetches o2 C.get(3) # fetches o3 o1.modify() # anything that modifies o1 C.register(o1) T.join(C) o2.modify() C.register(o2) # T.join(C) does not happen again o1.modify() # C.register(o1) doesn't happen again, because o1 was already # in the changed state. T.abort() C.beforeCompletion(T) C.abort(T) C._cache.invalidate(1) # toss changes to o1 C._cache.invalidate(2) # toss changes to o2 # o3 wasn't modified, and its cache entry isn't invalidated. TM.free(T) C.afterCompletion(T) C._flush_invalidations() # Processes invalidations that may have come in from other # transactions. Rollback of a savepoint ======================= Participants ------------ - ``T``: ``transaction.interfaces.ITransaction`` - ``o1``, ``o2``, ``o3``: some persistent objects - ``C1``, ``C2``, ``C3``: resource managers - ``S1``, ``S2``: Transaction savepoint objects - ``s11``, ``s21``, ``s22``: resource-manager savepoints Scenario -------- :: create T o1.modify() C1.regisiter(o1) T.join(C1) T.savepoint() C1.savepoint() return s11 return S1 = Savepoint(T, [r11]) o1.modify() C1.regisiter(o1) o2.modify() C2.regisiter(o2) T.join(C2) T.savepoint() C1.savepoint() return s21 C2.savepoint() return s22 return S2 = Savepoint(T, [r21, r22]) o3.modify() C3.regisiter(o3) T.join(C3) S1.rollback() S2.rollback() T.discard() C1.discard() C2.discard() C3.discard() o3.invalidate() S2.discard() s21.discard() # roll back changes since previous, which is r11 C1.discard(s21) o1.invalidate() # truncates temporary storage to s21's position s22.discard() # roll back changes since previous, which is r11 C1.discard(s22) o2.invalidate() # truncates temporary storage to beginning, because # s22 was the first savepoint. (Perhaps conection # savepoints record the log position before the # data were written, which is 0 in this case. T.commit() C1.beforeCompletion(T) C2.beforeCompletion(T) C3.beforeCompletion(T) C1.tpc_begin(T) S1.tpc_begin(T) C2.tpc_begin(T) C3.tpc_begin(T) C1.commit(T) S1.store(1, ..., T) C2.commit(T) C3.commit(T) C1.tpc_vote(T) S1.tpc_vote(T) C2.tpc_vote(T) C3.tpc_vote(T) C1.tpc_finish(T) S1.tpc_finish(T, f) # f is a callback function, which arranges c# to call DB.invalidate (next) DB.invalidate(tid, {1: 1}, C) TM.free(T) C1.afterCompletion(T) C1._flush_invalidations() C2.afterCompletion(T) C2._flush_invalidations() C3.afterCompletion(T) C3._flush_invalidations() zope2.13-2.13.21/source/ZODB3/src/ZODB/DB.py0000644000175000017500000010343412214017464016452 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Database objects """ import cPickle import cStringIO import sys import threading import logging import datetime import time import warnings from ZODB.broken import find_global from ZODB.utils import z64 from ZODB.Connection import Connection import ZODB.serialize import transaction.weakset from zope.interface import implements from ZODB.interfaces import IDatabase from ZODB.interfaces import IMVCCStorage import transaction from persistent.TimeStamp import TimeStamp logger = logging.getLogger('ZODB.DB') class AbstractConnectionPool(object): """Manage a pool of connections. CAUTION: Methods should be called under the protection of a lock. This class does no locking of its own. There's no limit on the number of connections this can keep track of, but a warning is logged if there are more than pool_size active connections, and a critical problem if more than twice pool_size. New connections are registered via push(). This will log a message if "too many" connections are active. When a connection is explicitly closed, tell the pool via repush(). That adds the connection to a stack of connections available for reuse, and throws away the oldest stack entries if the stack is too large. pop() pops this stack. When a connection is obtained via pop(), the pool holds only a weak reference to it thereafter. It's not necessary to inform the pool if the connection goes away. A connection handed out by pop() counts against pool_size only so long as it exists, and provided it isn't repush()'ed. A weak reference is retained so that DB methods like connectionDebugInfo() can still gather statistics. """ def __init__(self, size, timeout): # The largest # of connections we expect to see alive simultaneously. self._size = size # The minimum number of seconds that an available connection should # be kept, or None. self._timeout = timeout # A weak set of all connections we've seen. A connection vanishes # from this set if pop() hands it out, it's not reregistered via # repush(), and it becomes unreachable. self.all = transaction.weakset.WeakSet() def setSize(self, size): """Change our belief about the expected maximum # of live connections. If the pool_size is smaller than the current value, this may discard the oldest available connections. """ self._size = size self._reduce_size() def setTimeout(self, timeout): old = self._timeout self._timeout = timeout if timeout < old: self._reduce_size() def getSize(self): return self._size def getTimeout(self): return self._timeout timeout = property(getTimeout, lambda self, v: self.setTimeout(v)) size = property(getSize, lambda self, v: self.setSize(v)) class ConnectionPool(AbstractConnectionPool): def __init__(self, size, timeout=1<<31): super(ConnectionPool, self).__init__(size, timeout) # A stack of connections available to hand out. This is a subset # of self.all. push() and repush() add to this, and may remove # the oldest available connections if the pool is too large. # pop() pops this stack. There are never more than size entries # in this stack. self.available = [] def _append(self, c): available = self.available cactive = c._cache.cache_non_ghost_count if (available and (available[-1][1]._cache.cache_non_ghost_count > cactive) ): i = len(available) - 1 while (i and (available[i-1][1]._cache.cache_non_ghost_count > cactive) ): i -= 1 available.insert(i, (time.time(), c)) else: available.append((time.time(), c)) def push(self, c): """Register a new available connection. We must not know about c already. c will be pushed onto the available stack even if we're over the pool size limit. """ assert c not in self.all assert c not in self.available self._reduce_size(strictly_less=True) self.all.add(c) self._append(c) n = len(self.all) limit = self.size if n > limit: reporter = logger.warn if n > 2 * limit: reporter = logger.critical reporter("DB.open() has %s open connections with a pool_size " "of %s", n, limit) def repush(self, c): """Reregister an available connection formerly obtained via pop(). This pushes it on the stack of available connections, and may discard older available connections. """ assert c in self.all assert c not in self.available self._reduce_size(strictly_less=True) self._append(c) def _reduce_size(self, strictly_less=False): """Throw away the oldest available connections until we're under our target size (strictly_less=False, the default) or no more than that (strictly_less=True). """ threshhold = time.time() - self.timeout target = self.size if strictly_less: target -= 1 available = self.available while ( (len(available) > target) or (available and available[0][0] < threshhold) ): t, c = available.pop(0) self.all.remove(c) c._release_resources() def reduce_size(self): self._reduce_size() def pop(self): """Pop an available connection and return it. Return None if none are available - in this case, the caller should create a new connection, register it via push(), and call pop() again. The caller is responsible for serializing this sequence. """ result = None if self.available: _, result = self.available.pop() # Leave it in self.all, so we can still get at it for statistics # while it's alive. assert result in self.all return result def map(self, f): """For every live connection c, invoke f(c).""" self.all.map(f) def availableGC(self): """Perform garbage collection on available connections. If a connection is no longer viable because it has timed out, it is garbage collected.""" threshhold = time.time() - self.timeout to_remove = () for (t, c) in self.available: if t < threshhold: to_remove += (c,) self.all.remove(c) c._release_resources() else: c.cacheGC() if to_remove: self.available[:] = [i for i in self.available if i[1] not in to_remove] class KeyedConnectionPool(AbstractConnectionPool): # this pool keeps track of keyed connections all together. It makes # it possible to make assertions about total numbers of keyed connections. # The keys in this case are "before" TIDs, but this is used by other # packages as well. # see the comments in ConnectionPool for method descriptions. def __init__(self, size, timeout=1<<31): super(KeyedConnectionPool, self).__init__(size, timeout) self.pools = {} def setSize(self, v): self._size = v for pool in self.pools.values(): pool.setSize(v) def setTimeout(self, v): self._timeout = v for pool in self.pools.values(): pool.setTimeout(v) def push(self, c, key): pool = self.pools.get(key) if pool is None: pool = self.pools[key] = ConnectionPool(self.size, self.timeout) pool.push(c) def repush(self, c, key): self.pools[key].repush(c) def _reduce_size(self, strictly_less=False): for key, pool in list(self.pools.items()): pool._reduce_size(strictly_less) if not pool.all: del self.pools[key] def reduce_size(self): self._reduce_size() def pop(self, key): pool = self.pools.get(key) if pool is not None: return pool.pop() def map(self, f): for pool in self.pools.itervalues(): pool.map(f) def availableGC(self): for key, pool in self.pools.items(): pool.availableGC() if not pool.all: del self.pools[key] @property def test_all(self): result = set() for pool in self.pools.itervalues(): result.update(pool.all) return frozenset(result) @property def test_available(self): result = [] for pool in self.pools.itervalues(): result.extend(pool.available) return tuple(result) def toTimeStamp(dt): utc_struct = dt.utctimetuple() # if this is a leapsecond, this will probably fail. That may be a good # thing: leapseconds are not really accounted for with serials. args = utc_struct[:5]+(utc_struct[5] + dt.microsecond/1000000.0,) return TimeStamp(*args) def getTID(at, before): if at is not None: if before is not None: raise ValueError('can only pass zero or one of `at` and `before`') if isinstance(at, datetime.datetime): at = toTimeStamp(at) else: at = TimeStamp(at) before = repr(at.laterThan(at)) elif before is not None: if isinstance(before, datetime.datetime): before = repr(toTimeStamp(before)) else: before = repr(TimeStamp(before)) return before class DB(object): """The Object Database ------------------- The DB class coordinates the activities of multiple database Connection instances. Most of the work is done by the Connections created via the open method. The DB instance manages a pool of connections. If a connection is closed, it is returned to the pool and its object cache is preserved. A subsequent call to open() will reuse the connection. There is no hard limit on the pool size. If more than `pool_size` connections are opened, a warning is logged, and if more than twice that many, a critical problem is logged. The class variable 'klass' is used by open() to create database connections. It is set to Connection, but a subclass could override it to provide a different connection implementation. The database provides a few methods intended for application code -- open, close, undo, and pack -- and a large collection of methods for inspecting the database and its connections' caches. :Cvariables: - `klass`: Class used by L{open} to create database connections :Groups: - `User Methods`: __init__, open, close, undo, pack, classFactory - `Inspection Methods`: getName, getSize, objectCount, getActivityMonitor, setActivityMonitor - `Connection Pool Methods`: getPoolSize, getHistoricalPoolSize, setPoolSize, setHistoricalPoolSize, getHistoricalTimeout, setHistoricalTimeout - `Transaction Methods`: invalidate - `Other Methods`: lastTransaction, connectionDebugInfo - `Cache Inspection Methods`: cacheDetail, cacheExtremeDetail, cacheFullSweep, cacheLastGCTime, cacheMinimize, cacheSize, cacheDetailSize, getCacheSize, getHistoricalCacheSize, setCacheSize, setHistoricalCacheSize """ implements(IDatabase) klass = Connection # Class to use for connections _activity_monitor = next = previous = None def __init__(self, storage, pool_size=7, pool_timeout=1<<31, cache_size=400, cache_size_bytes=0, historical_pool_size=3, historical_cache_size=1000, historical_cache_size_bytes=0, historical_timeout=300, database_name='unnamed', databases=None, xrefs=True, large_record_size=1<<24, **storage_args): """Create an object database. :Parameters: - `storage`: the storage used by the database, e.g. FileStorage - `pool_size`: expected maximum number of open connections - `cache_size`: target size of Connection object cache - `cache_size_bytes`: target size measured in total estimated size of objects in the Connection object cache. "0" means unlimited. - `historical_pool_size`: expected maximum number of total historical connections - `historical_cache_size`: target size of Connection object cache for historical (`at` or `before`) connections - `historical_cache_size_bytes` -- similar to `cache_size_bytes` for the historical connection. - `historical_timeout`: minimum number of seconds that an unused historical connection will be kept, or None. - `xrefs` - Boolian flag indicating whether implicit cross-database references are allowed """ if isinstance(storage, basestring): from ZODB import FileStorage storage = ZODB.FileStorage.FileStorage(storage, **storage_args) elif storage is None: from ZODB import MappingStorage storage = ZODB.MappingStorage.MappingStorage(**storage_args) # Allocate lock. x = threading.RLock() self._a = x.acquire self._r = x.release # pools and cache sizes self.pool = ConnectionPool(pool_size, pool_timeout) self.historical_pool = KeyedConnectionPool(historical_pool_size, historical_timeout) self._cache_size = cache_size self._cache_size_bytes = cache_size_bytes self._historical_cache_size = historical_cache_size self._historical_cache_size_bytes = historical_cache_size_bytes # Setup storage self.storage = storage self.references = ZODB.serialize.referencesf try: storage.registerDB(self) except TypeError: storage.registerDB(self, None) # Backward compat if (not hasattr(storage, 'tpc_vote')) and not storage.isReadOnly(): warnings.warn( "Storage doesn't have a tpc_vote and this violates " "the storage API. Violently monkeypatching in a do-nothing " "tpc_vote.", DeprecationWarning, 2) storage.tpc_vote = lambda *args: None if IMVCCStorage.providedBy(storage): temp_storage = storage.new_instance() else: temp_storage = storage try: try: temp_storage.load(z64, '') except KeyError: # Create the database's root in the storage if it doesn't exist from persistent.mapping import PersistentMapping root = PersistentMapping() # Manually create a pickle for the root to put in the storage. # The pickle must be in the special ZODB format. file = cStringIO.StringIO() p = cPickle.Pickler(file, 1) p.dump((root.__class__, None)) p.dump(root.__getstate__()) t = transaction.Transaction() t.description = 'initial database creation' temp_storage.tpc_begin(t) temp_storage.store(z64, None, file.getvalue(), '', t) temp_storage.tpc_vote(t) temp_storage.tpc_finish(t) finally: if IMVCCStorage.providedBy(temp_storage): temp_storage.release() # Multi-database setup. if databases is None: databases = {} self.databases = databases self.database_name = database_name if database_name in databases: raise ValueError("database_name %r already in databases" % database_name) databases[database_name] = self self.xrefs = xrefs self.large_record_size = large_record_size @property def _storage(self): # Backward compatibility return self.storage # This is called by Connection.close(). def _returnToPool(self, connection): """Return a connection to the pool. connection._db must be self on entry. """ self._a() try: assert connection._db is self connection.opened = None if connection.before: self.historical_pool.repush(connection, connection.before) else: self.pool.repush(connection) finally: self._r() def _connectionMap(self, f): """Call f(c) for all connections c in all pools, live and historical. """ self._a() try: self.pool.map(f) self.historical_pool.map(f) finally: self._r() def cacheDetail(self): """Return information on objects in the various caches Organized by class. """ detail = {} def f(con, detail=detail): for oid, ob in con._cache.items(): module = getattr(ob.__class__, '__module__', '') module = module and '%s.' % module or '' c = "%s%s" % (module, ob.__class__.__name__) if c in detail: detail[c] += 1 else: detail[c] = 1 self._connectionMap(f) detail = detail.items() detail.sort() return detail def cacheExtremeDetail(self): detail = [] conn_no = [0] # A mutable reference to a counter def f(con, detail=detail, rc=sys.getrefcount, conn_no=conn_no): conn_no[0] += 1 cn = conn_no[0] for oid, ob in con._cache_items(): id = '' if hasattr(ob, '__dict__'): d = ob.__dict__ if d.has_key('id'): id = d['id'] elif d.has_key('__name__'): id = d['__name__'] module = getattr(ob.__class__, '__module__', '') module = module and ('%s.' % module) or '' # What refcount ('rc') should we return? The intent is # that we return the true Python refcount, but as if the # cache didn't exist. This routine adds 3 to the true # refcount: 1 for binding to name 'ob', another because # ob lives in the con._cache_items() list we're iterating # over, and calling sys.getrefcount(ob) boosts ob's # count by 1 too. So the true refcount is 3 less than # sys.getrefcount(ob) returns. But, in addition to that, # the cache holds an extra reference on non-ghost objects, # and we also want to pretend that doesn't exist. detail.append({ 'conn_no': cn, 'oid': oid, 'id': id, 'klass': "%s%s" % (module, ob.__class__.__name__), 'rc': rc(ob) - 3 - (ob._p_changed is not None), 'state': ob._p_changed, #'references': con.references(oid), }) self._connectionMap(f) return detail def cacheFullSweep(self): self._connectionMap(lambda c: c._cache.full_sweep()) def cacheLastGCTime(self): m = [0] def f(con, m=m): t = con._cache.cache_last_gc_time if t > m[0]: m[0] = t self._connectionMap(f) return m[0] def cacheMinimize(self): self._connectionMap(lambda c: c._cache.minimize()) def cacheSize(self): m = [0] def f(con, m=m): m[0] += con._cache.cache_non_ghost_count self._connectionMap(f) return m[0] def cacheDetailSize(self): m = [] def f(con, m=m): m.append({'connection': repr(con), 'ngsize': con._cache.cache_non_ghost_count, 'size': len(con._cache)}) self._connectionMap(f) m.sort() return m def close(self): """Close the database and its underlying storage. It is important to close the database, because the storage may flush in-memory data structures to disk when it is closed. Leaving the storage open with the process exits can cause the next open to be slow. What effect does closing the database have on existing connections? Technically, they remain open, but their storage is closed, so they stop behaving usefully. Perhaps close() should also close all the Connections. """ noop = lambda *a: None self.close = noop @self._connectionMap def _(c): c.transaction_manager.abort() c.afterCompletion = c.newTransaction = c.close = noop c._release_resources() self.storage.close() del self.storage def getCacheSize(self): return self._cache_size def getCacheSizeBytes(self): return self._cache_size_bytes def lastTransaction(self): return self.storage.lastTransaction() def getName(self): return self.storage.getName() def getPoolSize(self): return self.pool.size def getSize(self): return self.storage.getSize() def getHistoricalCacheSize(self): return self._historical_cache_size def getHistoricalCacheSizeBytes(self): return self._historical_cache_size_bytes def getHistoricalPoolSize(self): return self.historical_pool.size def getHistoricalTimeout(self): return self.historical_pool.timeout def invalidate(self, tid, oids, connection=None, version=''): """Invalidate references to a given oid. This is used to indicate that one of the connections has committed a change to the object. The connection commiting the change should be passed in to prevent useless (but harmless) messages to the connection. """ # Storages, esp. ZEO tests, need the version argument still. :-/ assert version=='' # Notify connections. def inval(c): if c is not connection: c.invalidate(tid, oids) self._connectionMap(inval) def invalidateCache(self): """Invalidate each of the connection caches """ self._connectionMap(lambda c: c.invalidateCache()) transform_record_data = untransform_record_data = lambda self, data: data def objectCount(self): return len(self.storage) def open(self, transaction_manager=None, at=None, before=None): """Return a database Connection for use by application code. Note that the connection pool is managed as a stack, to increase the likelihood that the connection's stack will include useful objects. :Parameters: - `transaction_manager`: transaction manager to use. None means use the default transaction manager. - `at`: a datetime.datetime or 8 character transaction id of the time to open the database with a read-only connection. Passing both `at` and `before` raises a ValueError, and passing neither opens a standard writable transaction of the newest state. A timezone-naive datetime.datetime is treated as a UTC value. - `before`: like `at`, but opens the readonly state before the tid or datetime. """ # `at` is normalized to `before`, since we use storage.loadBefore # as the underlying implementation of both. before = getTID(at, before) if (before is not None and before > self.lastTransaction() and before > getTID(self.lastTransaction(), None)): raise ValueError( 'cannot open an historical connection in the future.') if isinstance(transaction_manager, basestring): if transaction_manager: raise TypeError("Versions aren't supported.") warnings.warn( "A version string was passed to open.\n" "The first argument is a transaction manager.", DeprecationWarning, 2) transaction_manager = None self._a() try: # result <- a connection if before is not None: result = self.historical_pool.pop(before) if result is None: c = self.klass(self, self._historical_cache_size, before, self._historical_cache_size_bytes, ) self.historical_pool.push(c, before) result = self.historical_pool.pop(before) else: result = self.pool.pop() if result is None: c = self.klass(self, self._cache_size, None, self._cache_size_bytes, ) self.pool.push(c) result = self.pool.pop() assert result is not None # open the connection. result.open(transaction_manager) # A good time to do some cache cleanup. # (note we already have the lock) self.pool.availableGC() self.historical_pool.availableGC() return result finally: self._r() def connectionDebugInfo(self): result = [] t = time.time() def get_info(c): # `result`, `time` and `before` are lexically inherited. o = c.opened d = c.getDebugInfo() if d: if len(d) == 1: d = d[0] else: d = '' d = "%s (%s)" % (d, len(c._cache)) # output UTC time with the standard Z time zone indicator result.append({ 'opened': o and ("%s (%.2fs)" % ( time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(o)), t-o)), 'info': d, 'before': c.before, }) self._connectionMap(get_info) return result def getActivityMonitor(self): return self._activity_monitor def pack(self, t=None, days=0): """Pack the storage, deleting unused object revisions. A pack is always performed relative to a particular time, by default the current time. All object revisions that are not reachable as of the pack time are deleted from the storage. The cost of this operation varies by storage, but it is usually an expensive operation. There are two optional arguments that can be used to set the pack time: t, pack time in seconds since the epcoh, and days, the number of days to subtract from t or from the current time if t is not specified. """ if t is None: t = time.time() t -= days * 86400 try: self.storage.pack(t, self.references) except: logger.error("packing", exc_info=True) raise def setActivityMonitor(self, am): self._activity_monitor = am def classFactory(self, connection, modulename, globalname): # Zope will rebind this method to arbitrary user code at runtime. return find_global(modulename, globalname) def setCacheSize(self, size): self._a() try: self._cache_size = size def setsize(c): c._cache.cache_size = size self.pool.map(setsize) finally: self._r() def setCacheSizeBytes(self, size): self._a() try: self._cache_size_bytes = size def setsize(c): c._cache.cache_size_bytes = size self.pool.map(setsize) finally: self._r() def setHistoricalCacheSize(self, size): self._a() try: self._historical_cache_size = size def setsize(c): c._cache.cache_size = size self.historical_pool.map(setsize) finally: self._r() def setHistoricalCacheSizeBytes(self, size): self._a() try: self._historical_cache_size_bytes = size def setsize(c): c._cache.cache_size_bytes = size self.historical_pool.map(setsize) finally: self._r() def setPoolSize(self, size): self._a() try: self.pool.size = size finally: self._r() def setHistoricalPoolSize(self, size): self._a() try: self.historical_pool.size = size finally: self._r() def setHistoricalTimeout(self, timeout): self._a() try: self.historical_pool.timeout = timeout finally: self._r() def history(self, *args, **kw): return self.storage.history(*args, **kw) def supportsUndo(self): try: f = self.storage.supportsUndo except AttributeError: return False return f() def undoLog(self, *args, **kw): if not self.supportsUndo(): return () return self.storage.undoLog(*args, **kw) def undoInfo(self, *args, **kw): if not self.supportsUndo(): return () return self.storage.undoInfo(*args, **kw) def undoMultiple(self, ids, txn=None): """Undo multiple transactions identified by ids. A transaction can be undone if all of the objects involved in the transaction were not modified subsequently, if any modifications can be resolved by conflict resolution, or if subsequent changes resulted in the same object state. The values in ids should be generated by calling undoLog() or undoInfo(). The value of ids are not the same as a transaction ids used by other methods; they are unique to undo(). :Parameters: - `ids`: a sequence of storage-specific transaction identifiers - `txn`: transaction context to use for undo(). By default, uses the current transaction. """ if not self.supportsUndo(): raise NotImplementedError if txn is None: txn = transaction.get() if isinstance(ids, basestring): ids = [ids] txn.join(TransactionalUndo(self, ids)) def undo(self, id, txn=None): """Undo a transaction identified by id. A transaction can be undone if all of the objects involved in the transaction were not modified subsequently, if any modifications can be resolved by conflict resolution, or if subsequent changes resulted in the same object state. The value of id should be generated by calling undoLog() or undoInfo(). The value of id is not the same as a transaction id used by other methods; it is unique to undo(). :Parameters: - `id`: a transaction identifier - `txn`: transaction context to use for undo(). By default, uses the current transaction. """ self.undoMultiple([id], txn) def transaction(self): return ContextManager(self) def new_oid(self): return self.storage.new_oid() class ContextManager: """PEP 343 context manager """ def __init__(self, db): self.db = db def __enter__(self): self.tm = transaction.TransactionManager() self.conn = self.db.open(self.tm) return self.conn def __exit__(self, t, v, tb): if t is None: self.tm.commit() else: self.tm.abort() self.conn.close() resource_counter_lock = threading.Lock() resource_counter = 0 class TransactionalUndo(object): def __init__(self, db, tids): self._db = db self._storage = db.storage self._tids = tids self._oids = set() def abort(self, transaction): pass def tpc_begin(self, transaction): self._storage.tpc_begin(transaction) def commit(self, transaction): for tid in self._tids: result = self._storage.undo(tid, transaction) if result: self._oids.update(result[1]) def tpc_vote(self, transaction): for oid, _ in self._storage.tpc_vote(transaction) or (): self._oids.add(oid) def tpc_finish(self, transaction): self._storage.tpc_finish( transaction, lambda tid: self._db.invalidate(tid, self._oids) ) def tpc_abort(self, transaction): self._storage.tpc_abort(transaction) def sortKey(self): return "%s:%s" % (self._storage.sortKey(), id(self)) def connection(*args, **kw): db = DB(*args, **kw) conn = db.open() conn.onCloseCallback(db.close) return conn zope2.13-2.13.21/source/ZODB3/src/ZODB/fstools.py0000644000175000017500000001121512214017464017651 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Tools for using FileStorage data files. TODO: This module needs tests. Caution: This file needs to be kept in sync with FileStorage.py. """ import cPickle import struct from ZODB.FileStorage.format import TRANS_HDR, DATA_HDR, TRANS_HDR_LEN from ZODB.FileStorage.format import DATA_HDR_LEN from ZODB.utils import u64 from persistent.TimeStamp import TimeStamp class TxnHeader: """Object representing a transaction record header. Attribute Position Value --------- -------- ----- tid 0- 8 transaction id length 8-16 length of entire transaction record - 8 status 16-17 status of transaction (' ', 'u', 'p'?) user_len 17-19 length of user field (pack code H) descr_len 19-21 length of description field (pack code H) ext_len 21-23 length of extensions (pack code H) """ def __init__(self, file, pos): self._file = file self._pos = pos self._read_header() def _read_header(self): self._file.seek(self._pos) self._hdr = self._file.read(TRANS_HDR_LEN) (self.tid, self.length, self.status, self.user_len, self.descr_len, self.ext_len) = struct.unpack(TRANS_HDR, self._hdr) def read_meta(self): """Load user, descr, and ext attributes.""" self.user = "" self.descr = "" self.ext = {} if not (self.user_len or self.descr_len or self.ext_len): return self._file.seek(self._pos + TRANS_HDR_LEN) if self.user_len: self.user = self._file.read(self.user_len) if self.descr_len: self.descr = self._file.read(self.descr_len) if self.ext_len: self._ext = self._file.read(self.ext_len) self.ext = cPickle.loads(self._ext) def get_data_offset(self): return (self._pos + TRANS_HDR_LEN + self.user_len + self.descr_len + self.ext_len) def get_timestamp(self): return TimeStamp(self.tid) def get_raw_data(self): data_off = self.get_data_offset() data_len = self.length - (data_off - self._pos) self._file.seek(data_off) return self._file.read(data_len) def next_txn(self): off = self._pos + self.length + 8 self._file.seek(off) s = self._file.read(8) if not s: return None return TxnHeader(self._file, off) def prev_txn(self): if self._pos == 4: return None self._file.seek(self._pos - 8) tlen = u64(self._file.read(8)) return TxnHeader(self._file, self._pos - (tlen + 8)) class DataHeader: """Object representing a data record header. Attribute Position Value --------- -------- ----- oid 0- 8 object id serial 8-16 object serial numver prev_rec_pos 16-24 position of previous data record for object txn_pos 24-32 position of txn header version_len 32-34 length of version (always 0) data_len 34-42 length of data """ def __init__(self, file, pos): self._file = file self._pos = pos self._read_header() def _read_header(self): self._file.seek(self._pos) self._hdr = self._file.read(DATA_HDR_LEN) # always read the longer header, just in case (self.oid, self.serial, prev_rec_pos, txn_pos, vlen, data_len ) = struct.unpack(DATA_HDR, self._hdr[:DATA_HDR_LEN]) assert not vlen self.prev_rec_pos = u64(prev_rec_pos) self.txn_pos = u64(txn_pos) self.data_len = u64(data_len) def next_offset(self): """Return offset of next record.""" off = self._pos + self.data_len off += DATA_HDR_LEN if self.data_len == 0: off += 8 # backpointer return off def prev_txn(f): """Return transaction located before current file position.""" f.seek(-8, 1) tlen = u64(f.read(8)) + 8 return TxnHeader(f, f.tell() - tlen) zope2.13-2.13.21/source/ZODB3/src/ZODB/cross-database-references.txt0000644000175000017500000001420512214017464023363 0ustar arnauarnau========================= Cross-Database References ========================= Persistent references to objects in different databases within a multi-database are allowed. Lets set up a multi-database with 2 databases: >>> import ZODB.tests.util, transaction, persistent >>> databases = {} >>> db1 = ZODB.tests.util.DB(databases=databases, database_name='1') >>> db2 = ZODB.tests.util.DB(databases=databases, database_name='2') And create a persistent object in the first database: >>> tm = transaction.TransactionManager() >>> conn1 = db1.open(transaction_manager=tm) >>> p1 = MyClass() >>> conn1.root()['p'] = p1 >>> tm.commit() First, we get a connection to the second database. We get the second connection using the first connection's `get_connection` method. This is important. When using multiple databases, we need to make sure we use a consistent set of connections so that the objects in the connection caches are connected in a consistent manner. >>> conn2 = conn1.get_connection('2') Now, we'll create a second persistent object in the second database. We'll have a reference to the first object: >>> p2 = MyClass() >>> conn2.root()['p'] = p2 >>> p2.p1 = p1 >>> tm.commit() Now, let's open a separate connection to database 2. We use it to read `p2`, use `p2` to get to `p1`, and verify that it is in database 1: >>> conn = db2.open() >>> p2x = conn.root()['p'] >>> p1x = p2x.p1 >>> p2x is p2, p2x._p_oid == p2._p_oid, p2x._p_jar.db() is db2 (False, True, True) >>> p1x is p1, p1x._p_oid == p1._p_oid, p1x._p_jar.db() is db1 (False, True, True) It isn't valid to create references outside a multi database: >>> db3 = ZODB.tests.util.DB() >>> conn3 = db3.open(transaction_manager=tm) >>> p3 = MyClass() >>> conn3.root()['p'] = p3 >>> tm.commit() >>> p2.p3 = p3 >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Traceback (most recent call last): ... InvalidObjectReference: ('Attempt to store an object from a foreign database connection', , ) >>> tm.abort() Databases for new objects ------------------------- Objects are normally added to a database by making them reachable from an object already in the database. This is unambiguous when there is only one database. With multiple databases, it is not so clear what happens. Consider: >>> p4 = MyClass() >>> p1.p4 = p4 >>> p2.p4 = p4 In this example, the new object is reachable from both `p1` in database 1 and `p2` in database 2. If we commit, which database should `p4` end up in? This sort of ambiguity could lead to subtle bugs. For that reason, an error is generated if we commit changes when new objects are reachable from multiple databases: >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Traceback (most recent call last): ... InvalidObjectReference: ("A new object is reachable from multiple databases. Won't try to guess which one was correct!", , ) >>> tm.abort() To resolve this ambiguity, we can commit before an object becomes reachable from multiple databases. >>> p4 = MyClass() >>> p1.p4 = p4 >>> tm.commit() >>> p2.p4 = p4 >>> tm.commit() >>> p4._p_jar.db().database_name '1' This doesn't work with a savepoint: >>> p5 = MyClass() >>> p1.p5 = p5 >>> s = tm.savepoint() >>> p2.p5 = p5 >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Traceback (most recent call last): ... InvalidObjectReference: ("A new object is reachable from multiple databases. Won't try to guess which one was correct!", , ) >>> tm.abort() (Maybe it should.) We can disambiguate this situation by using the connection add method to explicitly say what database an object belongs to: >>> p5 = MyClass() >>> p1.p5 = p5 >>> p2.p5 = p5 >>> conn1.add(p5) >>> tm.commit() >>> p5._p_jar.db().database_name '1' This the most explicit and thus the best way, when practical, to avoid the ambiguity. Dissallowing implicit cross-database references ----------------------------------------------- The database contructor accepts a xrefs keyword argument that defaults to True. If False is passed, the implicit cross database references are disallowed. (Note that currently, implicit cross references are the only kind of cross references allowed.) >>> databases = {} >>> db1 = ZODB.tests.util.DB(databases=databases, database_name='1') >>> db2 = ZODB.tests.util.DB(databases=databases, database_name='2', ... xrefs=False) In this example, we allow cross-references from db1 to db2, but not the other way around. >>> c1 = db1.open() >>> c2 = c1.get_connection('2') >>> c1.root.x = c2.root() >>> transaction.commit() >>> c2.root.x = c1.root() >>> transaction.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Traceback (most recent call last): ... InvalidObjectReference: ("Database '2' doesn't allow implicit cross-database references", , {'x': {}}) >>> transaction.abort() NOTE ---- This implementation is incomplete. It allows creating and using cross-database references, however, there are a number of facilities missing: cross-database garbage collection Garbage collection is done on a database by database basis. If an object on a database only has references to it from other databases, then the object will be garbage collected when its database is packed. The cross-database references to it will be broken. cross-database undo Undo is only applied to a single database. Fixing this for multiple databases is going to be extremely difficult. Undo currently poses consistency problems, so it is not (or should not be) widely used. Cross-database aware (tolerant) export/import The export/import facility needs to be aware, at least, of cross-database references. zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/0000755000175000017500000000000012214017464016750 5ustar arnauarnauzope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testConnectionSavepoint.txt0000644000175000017500000001171612214017464024407 0ustar arnauarnau========== Savepoints ========== Savepoints provide a way to save to disk intermediate work done during a transaction allowing: - partial transaction (subtransaction) rollback (abort) - state of saved objects to be freed, freeing on-line memory for other uses Savepoints make it possible to write atomic subroutines that don't make top-level transaction commitments. Applications ------------ To demonstrate how savepoints work with transactions, we'll show an example. >>> import ZODB.tests.util >>> db = ZODB.tests.util.DB() >>> connection = db.open() >>> root = connection.root() >>> root['name'] = 'bob' As with other data managers, we can commit changes: >>> import transaction >>> transaction.commit() >>> root['name'] 'bob' and abort changes: >>> root['name'] = 'sally' >>> root['name'] 'sally' >>> transaction.abort() >>> root['name'] 'bob' Now, let's look at an application that manages funds for people. It allows deposits and debits to be entered for multiple people. It accepts a sequence of entries and generates a sequence of status messages. For each entry, it applies the change and then validates the user's account. If the user's account is invalid, we roll back the change for that entry. The success or failure of an entry is indicated in the output status. First we'll initialize some accounts: >>> root['bob-balance'] = 0.0 >>> root['bob-credit'] = 0.0 >>> root['sally-balance'] = 0.0 >>> root['sally-credit'] = 100.0 >>> transaction.commit() Now, we'll define a validation function to validate an account: >>> def validate_account(name): ... if root[name+'-balance'] + root[name+'-credit'] < 0: ... raise ValueError('Overdrawn', name) And a function to apply entries. If the function fails in some unexpected way, it rolls back all of its changes and prints the error: >>> def apply_entries(entries): ... savepoint = transaction.savepoint() ... try: ... for name, amount in entries: ... entry_savepoint = transaction.savepoint() ... try: ... root[name+'-balance'] += amount ... validate_account(name) ... except ValueError, error: ... entry_savepoint.rollback() ... print 'Error', str(error) ... else: ... print 'Updated', name ... except Exception, error: ... savepoint.rollback() ... print 'Unexpected exception', error Now let's try applying some entries: >>> apply_entries([ ... ('bob', 10.0), ... ('sally', 10.0), ... ('bob', 20.0), ... ('sally', 10.0), ... ('bob', -100.0), ... ('sally', -100.0), ... ]) Updated bob Updated sally Updated bob Updated sally Error ('Overdrawn', 'bob') Updated sally >>> root['bob-balance'] 30.0 >>> root['sally-balance'] -80.0 If we provide entries that cause an unexpected error: >>> apply_entries([ ... ('bob', 10.0), ... ('sally', 10.0), ... ('bob', '20.0'), ... ('sally', 10.0), ... ]) Updated bob Updated sally Unexpected exception unsupported operand type(s) for +=: 'float' and 'str' Because the apply_entries used a savepoint for the entire function, it was able to rollback the partial changes without rolling back changes made in the previous call to ``apply_entries``: >>> root['bob-balance'] 30.0 >>> root['sally-balance'] -80.0 If we now abort the outer transactions, the earlier changes will go away: >>> transaction.abort() >>> root['bob-balance'] 0.0 >>> root['sally-balance'] 0.0 Savepoint invalidation ---------------------- A savepoint can be used any number of times: >>> root['bob-balance'] = 100.0 >>> root['bob-balance'] 100.0 >>> savepoint = transaction.savepoint() >>> root['bob-balance'] = 200.0 >>> root['bob-balance'] 200.0 >>> savepoint.rollback() >>> root['bob-balance'] 100.0 >>> savepoint.rollback() # redundant, but should be harmless >>> root['bob-balance'] 100.0 >>> root['bob-balance'] = 300.0 >>> root['bob-balance'] 300.0 >>> savepoint.rollback() >>> root['bob-balance'] 100.0 However, using a savepoint invalidates any savepoints that come after it: >>> root['bob-balance'] = 200.0 >>> root['bob-balance'] 200.0 >>> savepoint1 = transaction.savepoint() >>> root['bob-balance'] = 300.0 >>> root['bob-balance'] 300.0 >>> savepoint2 = transaction.savepoint() >>> savepoint.rollback() >>> root['bob-balance'] 100.0 >>> savepoint2.rollback() Traceback (most recent call last): ... InvalidSavepointRollbackError >>> savepoint1.rollback() Traceback (most recent call last): ... InvalidSavepointRollbackError >>> transaction.abort() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testTimeStamp.py0000644000175000017500000001235412214017464022132 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test the TimeStamp utility type""" import time import unittest from persistent.TimeStamp import TimeStamp EPSILON = 0.000001 class TimeStampTests(unittest.TestCase): def checkYMDTimeStamp(self): self._check_ymd(2001, 6, 3) def _check_ymd(self, yr, mo, dy): ts = TimeStamp(yr, mo, dy) self.assertEqual(ts.year(), yr) self.assertEqual(ts.month(), mo) self.assertEqual(ts.day(), dy) self.assertEquals(ts.hour(), 0) self.assertEquals(ts.minute(), 0) self.assertEquals(ts.second(), 0) t = time.gmtime(ts.timeTime()) self.assertEquals(yr, t[0]) self.assertEquals(mo, t[1]) self.assertEquals(dy, t[2]) def checkFullTimeStamp(self): native_ts = int(time.time()) # fractional seconds get in the way t = time.gmtime(native_ts) # the corresponding GMT struct tm ts = TimeStamp(*t[:6]) # Seconds are stored internally via (conceptually) multiplying by # 2**32 then dividing by 60, ending up with a 32-bit integer. # While this gives a lot of room for cramming many distinct # TimeStamps into a second, it's not good at roundtrip accuracy. # For example, 1 second is stored as int(2**32/60) == 71582788. # Converting back gives 71582788*60.0/2**32 == 0.9999999962747097. # In general, we can lose up to 0.999... to truncation during # storing, creating an absolute error up to about 1*60.0/2**32 == # 0.000000014 on the seconds value we get back. This is so even # when we have an exact integral second value going in (as we # do in this test), so we can't expect equality in any comparison # involving seconds. Minutes (etc) are stored exactly, so we # can expect equality for those. self.assert_(abs(ts.timeTime() - native_ts) < EPSILON) self.assertEqual(ts.year(), t[0]) self.assertEqual(ts.month(), t[1]) self.assertEqual(ts.day(), t[2]) self.assertEquals(ts.hour(), t[3]) self.assertEquals(ts.minute(), t[4]) self.assert_(abs(ts.second() - t[5]) < EPSILON) def checkRawTimestamp(self): t = time.gmtime() ts1 = TimeStamp(*t[:6]) ts2 = TimeStamp(`ts1`) self.assertEquals(ts1, ts2) self.assertEquals(ts1.timeTime(), ts2.timeTime()) self.assertEqual(ts1.year(), ts2.year()) self.assertEqual(ts1.month(), ts2.month()) self.assertEqual(ts1.day(), ts2.day()) self.assertEquals(ts1.hour(), ts2.hour()) self.assertEquals(ts1.minute(), ts2.minute()) self.assert_(abs(ts1.second() - ts2.second()) < EPSILON) def checkDictKey(self): t = time.gmtime() ts1 = TimeStamp(*t[:6]) ts2 = TimeStamp(2000, *t[1:6]) d = {} d[ts1] = 1 d[ts2] = 2 self.assertEquals(len(d), 2) def checkCompare(self): ts1 = TimeStamp(1972, 6, 27) ts2 = TimeStamp(1971, 12, 12) self.assert_(ts1 > ts2) self.assert_(ts2 <= ts1) def checkLaterThan(self): t = time.gmtime() ts = TimeStamp(*t[:6]) ts2 = ts.laterThan(ts) self.assert_(ts2 > ts) # TODO: should test for bogus inputs to TimeStamp constructor def checkTimeStamp(self): # Alternate test suite t = TimeStamp(2002, 1, 23, 10, 48, 5) # GMT self.assertEquals(str(t), '2002-01-23 10:48:05.000000') self.assertEquals(repr(t), '\x03B9H\x15UUU') self.assertEquals(TimeStamp('\x03B9H\x15UUU'), t) self.assertEquals(t.year(), 2002) self.assertEquals(t.month(), 1) self.assertEquals(t.day(), 23) self.assertEquals(t.hour(), 10) self.assertEquals(t.minute(), 48) self.assertEquals(round(t.second()), 5) self.assertEquals(t.timeTime(), 1011782885) t1 = TimeStamp(2002, 1, 23, 10, 48, 10) self.assertEquals(str(t1), '2002-01-23 10:48:10.000000') self.assert_(t == t) self.assert_(t != t1) self.assert_(t < t1) self.assert_(t <= t1) self.assert_(t1 >= t) self.assert_(t1 > t) self.failIf(t == t1) self.failIf(t != t) self.failIf(t > t1) self.failIf(t >= t1) self.failIf(t1 < t) self.failIf(t1 <= t) self.assertEquals(cmp(t, t), 0) self.assertEquals(cmp(t, t1), -1) self.assertEquals(cmp(t1, t), 1) self.assertEquals(t1.laterThan(t), t1) self.assert_(t.laterThan(t1) > t1) self.assertEquals(TimeStamp(2002,1,23), TimeStamp(2002,1,23,0,0,0)) def test_suite(): return unittest.makeSuite(TimeStampTests, 'check') zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testConnection.py0000644000175000017500000011412212214017464022322 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unit tests for the Connection class.""" from __future__ import with_statement import doctest import unittest from persistent import Persistent import transaction from ZODB.config import databaseFromString from ZODB.utils import p64 from zope.interface.verify import verifyObject import ZODB.tests.util class ConnectionDotAdd(ZODB.tests.util.TestCase): def setUp(self): ZODB.tests.util.TestCase.setUp(self) from ZODB.Connection import Connection self.db = StubDatabase() self.datamgr = Connection(self.db) self.datamgr.open() self.transaction = StubTransaction() def check_add(self): from ZODB.POSException import InvalidObjectReference obj = StubObject() self.assert_(obj._p_oid is None) self.assert_(obj._p_jar is None) self.datamgr.add(obj) self.assert_(obj._p_oid is not None) self.assert_(obj._p_jar is self.datamgr) self.assert_(self.datamgr.get(obj._p_oid) is obj) # Only first-class persistent objects may be added. self.assertRaises(TypeError, self.datamgr.add, object()) # Adding to the same connection does not fail. Object keeps the # same oid. oid = obj._p_oid self.datamgr.add(obj) self.assertEqual(obj._p_oid, oid) # Cannot add an object from a different connection. obj2 = StubObject() obj2._p_jar = object() self.assertRaises(InvalidObjectReference, self.datamgr.add, obj2) def checkResetOnAbort(self): # Check that _p_oid and _p_jar are reset when a transaction is # aborted. obj = StubObject() self.datamgr.add(obj) oid = obj._p_oid self.datamgr.abort(self.transaction) self.assert_(obj._p_oid is None) self.assert_(obj._p_jar is None) self.assertRaises(KeyError, self.datamgr.get, oid) def checkResetOnTpcAbort(self): obj = StubObject() self.datamgr.add(obj) oid = obj._p_oid # Simulate an error while committing some other object. self.datamgr.tpc_begin(self.transaction) # Let's pretend something bad happens here. # Call tpc_abort, clearing everything. self.datamgr.tpc_abort(self.transaction) self.assert_(obj._p_oid is None) self.assert_(obj._p_jar is None) self.assertRaises(KeyError, self.datamgr.get, oid) def checkTpcAbortAfterCommit(self): obj = StubObject() self.datamgr.add(obj) oid = obj._p_oid self.datamgr.tpc_begin(self.transaction) self.datamgr.commit(self.transaction) # Let's pretend something bad happened here. self.datamgr.tpc_abort(self.transaction) self.assert_(obj._p_oid is None) self.assert_(obj._p_jar is None) self.assertRaises(KeyError, self.datamgr.get, oid) self.assertEquals(self.db.storage._stored, [oid]) def checkCommit(self): obj = StubObject() self.datamgr.add(obj) oid = obj._p_oid self.datamgr.tpc_begin(self.transaction) self.datamgr.commit(self.transaction) self.datamgr.tpc_finish(self.transaction) self.assert_(obj._p_oid is oid) self.assert_(obj._p_jar is self.datamgr) # This next assert_ is covered by an assert in tpc_finish. ##self.assert_(not self.datamgr._added) self.assertEquals(self.db.storage._stored, [oid]) self.assertEquals(self.db.storage._finished, [oid]) def checkModifyOnGetstate(self): member = StubObject() subobj = StubObject() subobj.member = member obj = ModifyOnGetStateObject(subobj) self.datamgr.add(obj) self.datamgr.tpc_begin(self.transaction) self.datamgr.commit(self.transaction) self.datamgr.tpc_finish(self.transaction) storage = self.db.storage self.assert_(obj._p_oid in storage._stored, "object was not stored") self.assert_(subobj._p_oid in storage._stored, "subobject was not stored") self.assert_(member._p_oid in storage._stored, "member was not stored") self.assert_(self.datamgr._added_during_commit is None) def checkUnusedAddWorks(self): # When an object is added, but not committed, it shouldn't be stored, # but also it should be an error. obj = StubObject() self.datamgr.add(obj) self.datamgr.tpc_begin(self.transaction) self.datamgr.tpc_finish(self.transaction) self.assert_(obj._p_oid not in self.datamgr._storage._stored) def check__resetCacheResetsReader(self): # https://bugs.launchpad.net/zodb/+bug/142667 old_cache = self.datamgr._cache self.datamgr._resetCache() new_cache = self.datamgr._cache self.failIf(new_cache is old_cache) self.failUnless(self.datamgr._reader._cache is new_cache) class UserMethodTests(unittest.TestCase): # add isn't tested here, because there are a bunch of traditional # unit tests for it. def test_root(self): r"""doctest of root() method The root() method is simple, and the tests are pretty minimal. Ensure that a new database has a root and that it is a PersistentMapping. >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> root = cn.root() >>> type(root).__name__ 'PersistentMapping' >>> root._p_oid '\x00\x00\x00\x00\x00\x00\x00\x00' >>> root._p_jar is cn True >>> db.close() """ def test_get(self): r"""doctest of get() method The get() method return the persistent object corresponding to an oid. >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> obj = cn.get(p64(0)) >>> obj._p_oid '\x00\x00\x00\x00\x00\x00\x00\x00' The object is a ghost. >>> obj._p_state -1 And multiple calls with the same oid, return the same object. >>> obj2 = cn.get(p64(0)) >>> obj is obj2 True If all references to the object are released, then a new object will be returned. The cache doesn't keep unreferenced ghosts alive. (The next object returned my still have the same id, because Python may re-use the same memory.) >>> del obj, obj2 >>> cn._cache.get(p64(0), None) If the object is unghosted, then it will stay in the cache after the last reference is released. (This is true only if there is room in the cache and the object is recently used.) >>> obj = cn.get(p64(0)) >>> obj._p_activate() >>> y = id(obj) >>> del obj >>> obj = cn.get(p64(0)) >>> id(obj) == y True >>> obj._p_state 0 A request for an object that doesn't exist will raise a POSKeyError. >>> cn.get(p64(1)) Traceback (most recent call last): ... POSKeyError: 0x01 """ def test_close(self): r"""doctest of close() method This is a minimal test, because most of the interesting effects on closing a connection involve its interaction with the database and the transaction. >>> db = databaseFromString("\n\n") >>> cn = db.open() It's safe to close a connection multiple times. >>> cn.close() >>> cn.close() >>> cn.close() It's not possible to load or store objects once the storage is closed. >>> cn.get(p64(0)) Traceback (most recent call last): ... ConnectionStateError: The database connection is closed >>> p = Persistent() >>> cn.add(p) Traceback (most recent call last): ... ConnectionStateError: The database connection is closed """ def test_close_with_pending_changes(self): r"""doctest to ensure close() w/ pending changes complains >>> import transaction Just opening and closing is fine. >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> cn.close() Opening, making a change, committing, and closing is fine. >>> cn = db.open() >>> cn.root()['a'] = 1 >>> transaction.commit() >>> cn.close() Opening, making a change, and aborting is fine. >>> cn = db.open() >>> cn.root()['a'] = 1 >>> transaction.abort() >>> cn.close() But trying to close with a change pending complains. >>> cn = db.open() >>> cn.root()['a'] = 10 >>> cn.close() Traceback (most recent call last): ... ConnectionStateError: Cannot close a connection joined to a transaction This leaves the connection as it was, so we can still commit the change. >>> transaction.commit() >>> cn2 = db.open() >>> cn2.root()['a'] 10 >>> cn.close(); cn2.close() >>> db.close() """ def test_onCloseCallbacks(self): r"""doctest of onCloseCallback() method >>> db = databaseFromString("\n\n") >>> cn = db.open() Every function registered is called, even if it raises an exception. They are only called once. >>> L = [] >>> def f(): ... L.append("f") >>> def g(): ... L.append("g") ... return 1 / 0 >>> cn.onCloseCallback(g) >>> cn.onCloseCallback(f) >>> cn.close() >>> L ['g', 'f'] >>> del L[:] >>> cn.close() >>> L [] The implementation keeps a list of callbacks that is reset to a class variable (which is bound to None) after the connection is closed. >>> cn._Connection__onCloseCallbacks """ def test_close_dispatches_to_activity_monitors(self): r"""doctest that connection close updates activity monitors Set up a multi-database: >>> db1 = ZODB.DB(None) >>> db2 = ZODB.DB(None, databases=db1.databases, database_name='2', ... cache_size=10) >>> conn1 = db1.open() >>> conn2 = conn1.get_connection('2') Add activity monitors to both dbs: >>> from ZODB.ActivityMonitor import ActivityMonitor >>> db1.setActivityMonitor(ActivityMonitor()) >>> db2.setActivityMonitor(ActivityMonitor()) Commit a transaction that affects both connections: >>> conn1.root()[0] = conn1.root().__class__() >>> conn2.root()[0] = conn2.root().__class__() >>> transaction.commit() After closing the primary connection, both monitors should be up to date: >>> conn1.close() >>> len(db1.getActivityMonitor().log) 1 >>> len(db2.getActivityMonitor().log) 1 """ def test_db(self): r"""doctest of db() method >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> cn.db() is db True >>> cn.close() >>> cn.db() is db True """ def test_isReadOnly(self): r"""doctest of isReadOnly() method >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> cn.isReadOnly() False >>> cn.close() >>> cn.isReadOnly() Traceback (most recent call last): ... ConnectionStateError: The database connection is closed An expedient way to create a read-only storage: >>> db.storage.isReadOnly = lambda: True >>> cn = db.open() >>> cn.isReadOnly() True """ def test_cache(self): r"""doctest of cacheMinimize(). Thus test us minimal, just verifying that the method can be called and has some effect. We need other tests that verify the cache works as intended. >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> r = cn.root() >>> cn.cacheMinimize() >>> r._p_state -1 >>> r._p_activate() >>> r._p_state # up to date 0 >>> cn.cacheMinimize() >>> r._p_state # ghost again -1 """ def test_transaction_retry_convenience(): """ Simple test to verify integration with the transaction retry helper my verifying that we can raise ConflictError and have it handled properly. This is an adaptation of the convenience tests in transaction. >>> db = ZODB.tests.util.DB() >>> conn = db.open() >>> dm = conn.root() >>> ntry = 0 >>> with transaction.manager: ... dm['ntry'] = 0 >>> import ZODB.POSException >>> for attempt in transaction.manager.attempts(): ... with attempt as t: ... t.note('test') ... print dm['ntry'], ntry ... ntry += 1 ... dm['ntry'] = ntry ... if ntry % 3: ... raise ZODB.POSException.ConflictError() 0 0 0 1 0 2 """ class InvalidationTests(unittest.TestCase): # It's harder to write serious tests, because some of the critical # correctness issues relate to concurrency. We'll have to depend # on the various concurrent updates and NZODBThreads tests to # handle these. def test_invalidate(self): r""" This test initializes the database with several persistent objects, then manually delivers invalidations and verifies that they have the expected effect. >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> p1 = Persistent() >>> p2 = Persistent() >>> p3 = Persistent() >>> r = cn.root() >>> r.update(dict(p1=p1, p2=p2, p3=p3)) >>> transaction.commit() Transaction ids are 8-byte strings, just like oids; p64() will create one from an int. >>> cn.invalidate(p64(1), {p1._p_oid: 1}) >>> cn._txn_time '\x00\x00\x00\x00\x00\x00\x00\x01' >>> p1._p_oid in cn._invalidated True >>> p2._p_oid in cn._invalidated False >>> cn.invalidate(p64(10), {p2._p_oid: 1, p64(76): 1}) >>> cn._txn_time '\x00\x00\x00\x00\x00\x00\x00\x01' >>> p1._p_oid in cn._invalidated True >>> p2._p_oid in cn._invalidated True Calling invalidate() doesn't affect the object state until a transaction boundary. >>> p1._p_state 0 >>> p2._p_state 0 >>> p3._p_state 0 The sync() method will abort the current transaction and process any pending invalidations. >>> cn.sync() >>> p1._p_state -1 >>> p2._p_state -1 >>> p3._p_state 0 >>> cn._invalidated set([]) """ def test_invalidateCache(): """The invalidateCache method invalidates a connection's cache. It also prevents reads until the end of a transaction:: >>> from ZODB.tests.util import DB >>> import transaction >>> db = DB() >>> tm = transaction.TransactionManager() >>> connection = db.open(transaction_manager=tm) >>> connection.root()['a'] = StubObject() >>> connection.root()['a'].x = 1 >>> connection.root()['b'] = StubObject() >>> connection.root()['b'].x = 1 >>> connection.root()['c'] = StubObject() >>> connection.root()['c'].x = 1 >>> tm.commit() >>> connection.root()['b']._p_deactivate() >>> connection.root()['c'].x = 2 So we have a connection and an active transaction with some modifications. Lets call invalidateCache: >>> connection.invalidateCache() Now, if we try to load an object, we'll get a read conflict: >>> connection.root()['b'].x Traceback (most recent call last): ... ReadConflictError: database read conflict error If we try to commit the transaction, we'll get a conflict error: >>> tm.commit() Traceback (most recent call last): ... ConflictError: database conflict error and the cache will have been cleared: >>> print connection.root()['a']._p_changed None >>> print connection.root()['b']._p_changed None >>> print connection.root()['c']._p_changed None But we'll be able to access data again: >>> connection.root()['b'].x 1 Aborting a transaction after a read conflict also lets us read data and go on about our business: >>> connection.invalidateCache() >>> connection.root()['c'].x Traceback (most recent call last): ... ReadConflictError: database read conflict error >>> tm.abort() >>> connection.root()['c'].x 1 >>> connection.root()['c'].x = 2 >>> tm.commit() >>> db.close() """ def connection_root_convenience(): """Connection root attributes can now be used as objects with attributes >>> db = ZODB.tests.util.DB() >>> conn = db.open() >>> conn.root.x Traceback (most recent call last): ... AttributeError: x >>> del conn.root.x Traceback (most recent call last): ... AttributeError: x >>> conn.root()['x'] = 1 >>> conn.root.x 1 >>> conn.root.y = 2 >>> sorted(conn.root().items()) [('x', 1), ('y', 2)] >>> conn.root >>> del conn.root.x >>> sorted(conn.root().items()) [('y', 2)] >>> conn.root.rather_long_name = 1 >>> conn.root.rather_long_name2 = 1 >>> conn.root.rather_long_name4 = 1 >>> conn.root.rather_long_name5 = 1 >>> conn.root """ class proper_ghost_initialization_with_empty__p_deactivate_class(Persistent): def _p_deactivate(self): pass def proper_ghost_initialization_with_empty__p_deactivate(): """ See https://bugs.launchpad.net/zodb/+bug/185066 >>> db = ZODB.tests.util.DB() >>> conn = db.open() >>> C = proper_ghost_initialization_with_empty__p_deactivate_class >>> conn.root.x = x = C() >>> conn.root.x.y = 1 >>> transaction.commit() >>> conn2 = db.open() >>> conn2.root.x._p_changed >>> conn2.root.x.y 1 """ def readCurrent(): r""" The connection's readCurrent method is called to provide a higher level of consistency in cases where an object if read to compute an update to a separate object. When this is used, the checkCurrentSerialInTransaction method on the storage is called in 2-phase commit. To demonstrate this, we'll create a storage and give it a test implementation of checkCurrentSerialInTransaction. >>> import ZODB.MappingStorage >>> store = ZODB.MappingStorage.MappingStorage() >>> from ZODB.POSException import ReadConflictError >>> bad = set() >>> def checkCurrentSerialInTransaction(oid, serial, trans): ... print 'checkCurrentSerialInTransaction', `oid` ... if not trans == transaction.get(): print 'oops' ... if oid in bad: ... raise ReadConflictError(oid=oid) >>> store.checkCurrentSerialInTransaction = checkCurrentSerialInTransaction Now, we'll use the storage as usual. checkCurrentSerialInTransaction won't normally be called: >>> db = ZODB.DB(store) >>> conn = db.open() >>> conn.root.a = ZODB.tests.util.P('a') >>> conn.root.b = ZODB.tests.util.P('b') >>> transaction.commit() If we call readCurrent for an object and we modify another object, then checkCurrentSerialInTransaction will be called for the object readCurrent was called on. >>> conn.readCurrent(conn.root.a) >>> conn.root.b.x = 0 >>> transaction.commit() checkCurrentSerialInTransaction '\x00\x00\x00\x00\x00\x00\x00\x01' It doesn't matter how often we call readCurrent, checkCurrentSerialInTransaction will be called only once: >>> conn.readCurrent(conn.root.a) >>> conn.readCurrent(conn.root.a) >>> conn.readCurrent(conn.root.a) >>> conn.readCurrent(conn.root.a) >>> conn.root.b.x += 1 >>> transaction.commit() checkCurrentSerialInTransaction '\x00\x00\x00\x00\x00\x00\x00\x01' checkCurrentSerialInTransaction won't be called if another object isn't modified: >>> conn.readCurrent(conn.root.a) >>> transaction.commit() Or if the object it was called on is modified: >>> conn.readCurrent(conn.root.a) >>> conn.root.a.x = 0 >>> conn.root.b.x += 1 >>> transaction.commit() If the storage raises a conflict error, it'll be propigated: >>> _ = str(conn.root.a) # do read >>> bad.add(conn.root.a._p_oid) >>> conn.readCurrent(conn.root.a) >>> conn.root.b.x += 1 >>> transaction.commit() Traceback (most recent call last): ... ReadConflictError: database read conflict error (oid 0x01) >>> transaction.abort() The conflict error will cause the affected object to be invalidated: >>> conn.root.a._p_changed The storage may raise it later: >>> def checkCurrentSerialInTransaction(oid, serial, trans): ... if not trans == transaction.get(): print 'oops' ... print 'checkCurrentSerialInTransaction', `oid` ... store.badness = ReadConflictError(oid=oid) >>> def tpc_vote(t): ... if store.badness: ... badness = store.badness ... store.badness = None ... raise badness >>> store.checkCurrentSerialInTransaction = checkCurrentSerialInTransaction >>> store.badness = None >>> store.tpc_vote = tpc_vote It will still be propigated: >>> _ = str(conn.root.a) # do read >>> conn.readCurrent(conn.root.a) >>> conn.root.b.x = +1 >>> transaction.commit() Traceback (most recent call last): ... ReadConflictError: database read conflict error (oid 0x01) >>> transaction.abort() The conflict error will cause the affected object to be invalidated: >>> conn.root.a._p_changed Read checks don't leak accross transactions: >>> conn.readCurrent(conn.root.a) >>> transaction.commit() >>> conn.root.b.x = +1 >>> transaction.commit() Read checks to work accross savepoints. >>> conn.readCurrent(conn.root.a) >>> conn.root.b.x = +1 >>> _ = transaction.savepoint() >>> transaction.commit() Traceback (most recent call last): ... ReadConflictError: database read conflict error (oid 0x01) >>> transaction.abort() >>> conn.readCurrent(conn.root.a) >>> _ = transaction.savepoint() >>> conn.root.b.x = +1 >>> transaction.commit() Traceback (most recent call last): ... ReadConflictError: database read conflict error (oid 0x01) >>> transaction.abort() """ def cache_management_of_subconnections(): """Make that cache management works for subconnections. When we use multi-databases, we open a connection in one database and access connections to other databases through it. This test verifies thatcache management is applied to all of the connections. Set up a multi-database: >>> db1 = ZODB.DB(None) >>> db2 = ZODB.DB(None, databases=db1.databases, database_name='2', ... cache_size=10) >>> conn1 = db1.open() >>> conn2 = conn1.get_connection('2') Populate it with some data, more than will fit in the cache: >>> for i in range(100): ... conn2.root()[i] = conn2.root().__class__() Upon commit, the cache is reduced to the cache size: >>> transaction.commit() >>> conn2._cache.cache_non_ghost_count 10 Fill it back up: >>> for i in range(100): ... _ = str(conn2.root()[i]) >>> conn2._cache.cache_non_ghost_count 101 Doing cache GC on the primary also does it on the secondary: >>> conn1.cacheGC() >>> conn2._cache.cache_non_ghost_count 10 Ditto for cache minimize: >>> conn1.cacheMinimize() >>> conn2._cache.cache_non_ghost_count 0 Fill it back up: >>> for i in range(100): ... _ = str(conn2.root()[i]) >>> conn2._cache.cache_non_ghost_count 101 GC is done on reopen: >>> conn1.close() >>> db1.open() is conn1 True >>> conn2 is conn1.get_connection('2') True >>> conn2._cache.cache_non_ghost_count 10 """ class C_invalidations_of_new_objects_work_after_savepoint(Persistent): def __init__(self): self.settings = 1 def _p_invalidate(self): print 'INVALIDATE', self.settings Persistent._p_invalidate(self) print self.settings # POSKeyError here def abort_of_savepoint_creating_new_objects_w_exotic_invalidate_doesnt_break(): r""" Before, the following would fail with a POSKeyError, which was somewhat surprizing, in a very edgy sort of way. :) Really, when an object add is aborted, the object should be "removed" from the db and its invalidatuon method shouldm't even be called: >>> conn = ZODB.connection(None) >>> conn.root.x = x = C_invalidations_of_new_objects_work_after_savepoint() >>> _ = transaction.savepoint() >>> x._p_oid '\x00\x00\x00\x00\x00\x00\x00\x01' >>> x._p_jar is conn True >>> transaction.abort() After the abort, the oid and jar are None: >>> x._p_oid >>> x._p_jar """ class Clp9460655(Persistent): def __init__(self, word, id): super(Clp9460655, self).__init__() self.id = id self._word = word def lp9460655(): r""" >>> conn = ZODB.connection(None) >>> root = conn.root() >>> Word = Clp9460655 >>> from BTrees.OOBTree import OOBTree >>> data = root['data'] = OOBTree() >>> commonWords = [] >>> count = "0" >>> for x in ('hello', 'world', 'how', 'are', 'you'): ... commonWords.append(Word(x, count)) ... count = str(int(count) + 1) >>> sv = transaction.savepoint() >>> for word in commonWords: ... sv2 = transaction.savepoint() ... data[word.id] = word >>> sv.rollback() >>> print commonWords[1].id # raises POSKeyError 1 """ def lp615758_transaction_abort_Incomplete_cleanup_for_new_objects(): r""" As the following"DocTest" demonstrates, "abort" forgets to reset "_p_changed" for new (i.e. "added") objects. >>> class P(Persistent): pass ... >>> c = ZODB.connection(None) >>> obj = P() >>> c.add(obj) >>> obj.x = 1 >>> obj._p_changed True >>> transaction.abort() >>> obj._p_changed False >>> c.close() """ class Clp485456_setattr_in_getstate_doesnt_cause_multiple_stores(Persistent): def __getstate__(self): self.got = 1 return self.__dict__.copy() def lp485456_setattr_in_setstate_doesnt_cause_multiple_stores(): r""" >>> C = Clp485456_setattr_in_getstate_doesnt_cause_multiple_stores >>> conn = ZODB.connection(None) >>> oldstore = conn._storage.store >>> def store(oid, *args): ... print 'storing', repr(oid) ... return oldstore(oid, *args) >>> conn._storage.store = store When we commit a change, we only get a single store call >>> conn.root.x = C() >>> transaction.commit() storing '\x00\x00\x00\x00\x00\x00\x00\x00' storing '\x00\x00\x00\x00\x00\x00\x00\x01' >>> conn.add(C()) >>> transaction.commit() storing '\x00\x00\x00\x00\x00\x00\x00\x02' We still see updates: >>> conn.root.x.y = 1 >>> transaction.commit() storing '\x00\x00\x00\x00\x00\x00\x00\x01' Not not non-updates: >>> transaction.commit() Let's try some combinations with savepoints: >>> conn.root.n = 0 >>> _ = transaction.savepoint() >>> oldspstore = conn._storage.store >>> def store(oid, *args): ... print 'savepoint storing', repr(oid) ... return oldspstore(oid, *args) >>> conn._storage.store = store >>> conn.root.y = C() >>> _ = transaction.savepoint() savepoint storing '\x00\x00\x00\x00\x00\x00\x00\x00' savepoint storing '\x00\x00\x00\x00\x00\x00\x00\x03' >>> conn.root.y.x = 1 >>> _ = transaction.savepoint() savepoint storing '\x00\x00\x00\x00\x00\x00\x00\x03' >>> transaction.commit() storing '\x00\x00\x00\x00\x00\x00\x00\x00' storing '\x00\x00\x00\x00\x00\x00\x00\x03' >>> conn.close() """ class _PlayPersistent(Persistent): def setValueWithSize(self, size=0): self.value = size*' ' __init__ = setValueWithSize class EstimatedSizeTests(ZODB.tests.util.TestCase): """check that size estimations are handled correctly.""" def setUp(self): ZODB.tests.util.TestCase.setUp(self) self.db = db = databaseFromString("\n\n") self.conn = c = db.open() self.obj = obj = _PlayPersistent() c.root()['obj'] = obj transaction.commit() def test_size_set_on_write_commit(self): obj, cache = self.obj, self.conn._cache # we have just written "obj". Its size should not be zero size, cache_size = obj._p_estimated_size, cache.total_estimated_size self.assert_(size > 0) self.assert_(cache_size > size) # increase the size, write again and check that the size changed obj.setValueWithSize(1000) transaction.commit() new_size = obj._p_estimated_size self.assert_(new_size > size) self.assertEqual(cache.total_estimated_size, cache_size + new_size - size) def test_size_set_on_write_savepoint(self): obj, cache = self.obj, self.conn._cache # we have just written "obj". Its size should not be zero size, cache_size = obj._p_estimated_size, cache.total_estimated_size # increase the size, write again and check that the size changed obj.setValueWithSize(1000) transaction.savepoint() new_size = obj._p_estimated_size self.assert_(new_size > size) self.assertEqual(cache.total_estimated_size, cache_size + new_size - size) def test_size_set_on_load(self): c = self.db.open() # new connection obj = c.root()['obj'] # the object is still a ghost and '_p_estimated_size' not yet set # access to unghost cache = c._cache cache_size = cache.total_estimated_size obj.value size = obj._p_estimated_size self.assert_(size > 0) self.assertEqual(cache.total_estimated_size, cache_size + size) # we test here as well that the deactivation works reduced the cache # size obj._p_deactivate() self.assertEqual(cache.total_estimated_size, cache_size) def test_configuration(self): # verify defaults .... expected = 0 # ... on db db = self.db self.assertEqual(db.getCacheSizeBytes(), expected) self.assertEqual(db.getHistoricalCacheSizeBytes(), expected) # ... on connection conn = self.conn self.assertEqual(conn._cache.cache_size_bytes, expected) # verify explicit setting ... expected = 10000 # ... on db db = databaseFromString("\n" " cache-size-bytes %d\n" " historical-cache-size-bytes %d\n" " \n" "" % (expected, expected+1) ) self.assertEqual(db.getCacheSizeBytes(), expected) self.assertEqual(db.getHistoricalCacheSizeBytes(), expected+1) # ... on connectionB conn = db.open() self.assertEqual(conn._cache.cache_size_bytes, expected) # test huge (larger than 4 byte) size limit db = databaseFromString("\n" " cache-size-bytes 8GB\n" " \n" "" ) self.assertEqual(db.getCacheSizeBytes(), 0x1L << 33) def test_cache_garbage_collection(self): db = self.db # activate size based cache garbage collection db.setCacheSizeBytes(1) conn = self.conn cache = conn._cache # verify the change worked as expected self.assertEqual(cache.cache_size_bytes, 1) # verify our entrance assumption is fullfilled self.assert_(cache.total_estimated_size > 1) conn.cacheGC() self.assert_(cache.total_estimated_size <= 1) # sanity check self.assert_(cache.total_estimated_size >= 0) def test_cache_garbage_collection_shrinking_object(self): db = self.db # activate size based cache garbage collection db.setCacheSizeBytes(1000) obj, conn, cache = self.obj, self.conn, self.conn._cache # verify the change worked as expected self.assertEqual(cache.cache_size_bytes, 1000) # verify our entrance assumption is fullfilled self.assert_(cache.total_estimated_size > 1) # give the objects some size obj.setValueWithSize(500) transaction.savepoint() self.assert_(cache.total_estimated_size > 500) # make the object smaller obj.setValueWithSize(100) transaction.savepoint() # make sure there was no overflow self.assert_(cache.total_estimated_size != 0) # the size is not larger than the allowed maximum self.assert_(cache.total_estimated_size <= 1000) # ---- stubs class StubObject(Persistent): pass class StubTransaction: pass class ErrorOnGetstateException(Exception): pass class ErrorOnGetstateObject(Persistent): def __getstate__(self): raise ErrorOnGetstateException class ModifyOnGetStateObject(Persistent): def __init__(self, p): self._v_p = p def __getstate__(self): self._p_jar.add(self._v_p) self.p = self._v_p return Persistent.__getstate__(self) class StubStorage: """Very simple in-memory storage that does *just* enough to support tests. Only one concurrent transaction is supported. Voting is not supported. Inspect self._stored and self._finished to see how the storage has been used during a unit test. Whenever an object is stored in the store() method, its oid is appended to self._stored. When a transaction is finished, the oids that have been stored during the transaction are appended to self._finished. """ # internal _oid = 1 _transaction = None def __init__(self): # internal self._stored = [] self._finished = [] self._data = {} self._transdata = {} self._transstored = [] def new_oid(self): oid = str(self._oid) self._oid += 1 return oid def sortKey(self): return 'StubStorage sortKey' def tpc_begin(self, transaction): if transaction is None: raise TypeError('transaction may not be None') elif self._transaction is None: self._transaction = transaction elif self._transaction != transaction: raise RuntimeError( 'StubStorage uses only one transaction at a time') def tpc_abort(self, transaction): if transaction is None: raise TypeError('transaction may not be None') elif self._transaction != transaction: raise RuntimeError( 'StubStorage uses only one transaction at a time') del self._transaction self._transdata.clear() def tpc_finish(self, transaction, callback): if transaction is None: raise TypeError('transaction may not be None') elif self._transaction != transaction: raise RuntimeError( 'StubStorage uses only one transaction at a time') self._finished.extend(self._transstored) self._data.update(self._transdata) callback(transaction) del self._transaction self._transdata.clear() self._transstored = [] def load(self, oid, version=''): if version != '': raise TypeError('StubStorage does not support versions.') return self._data[oid] def store(self, oid, serial, p, version, transaction): if version != '': raise TypeError('StubStorage does not support versions.') if transaction is None: raise TypeError('transaction may not be None') elif self._transaction != transaction: raise RuntimeError( 'StubStorage uses only one transaction at a time') self._stored.append(oid) self._transstored.append(oid) self._transdata[oid] = (p, serial) # Explicitly returing None, as we're not pretending to be a ZEO # storage return None class TestConnectionInterface(unittest.TestCase): def test_connection_interface(self): from ZODB.interfaces import IConnection db = databaseFromString("\n\n") cn = db.open() verifyObject(IConnection, cn) class StubDatabase: def __init__(self): self.storage = StubStorage() self.new_oid = self.storage.new_oid classFactory = None database_name = 'stubdatabase' databases = {'stubdatabase': database_name} def invalidate(self, transaction, dict_with_oid_keys, connection): pass large_record_size = 1<<30 def test_suite(): s = unittest.makeSuite(ConnectionDotAdd, 'check') s.addTest(doctest.DocTestSuite()) s.addTest(unittest.makeSuite(TestConnectionInterface)) s.addTest(unittest.makeSuite(EstimatedSizeTests)) return s zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testDemoStorage.py0000644000175000017500000001764412214017464022447 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from ZODB.DB import DB from ZODB.tests import ( BasicStorage, HistoryStorage, IteratorStorage, MTStorage, PackableStorage, RevisionStorage, StorageTestBase, Synchronization, ) import os if os.environ.get('USE_ZOPE_TESTING_DOCTEST'): from zope.testing import doctest else: import doctest import random import transaction import unittest import ZODB.DemoStorage import ZODB.tests.hexstorage import ZODB.tests.util import ZODB.utils class DemoStorageTests( StorageTestBase.StorageTestBase, BasicStorage.BasicStorage, HistoryStorage.HistoryStorage, IteratorStorage.ExtendedIteratorStorage, IteratorStorage.IteratorStorage, MTStorage.MTStorage, PackableStorage.PackableStorage, RevisionStorage.RevisionStorage, Synchronization.SynchronizedStorage, ): def setUp(self): StorageTestBase.StorageTestBase.setUp(self) self._storage = ZODB.DemoStorage.DemoStorage() def checkOversizeNote(self): # This base class test checks for the common case where a storage # doesnt support huge transaction metadata. This storage doesnt # have this limit, so we inhibit this test here. pass def checkLoadDelegation(self): # Minimal test of loadEX w/o version -- ironically db = DB(self._storage) # creates object 0. :) s2 = ZODB.DemoStorage.DemoStorage(base=self._storage) self.assertEqual(s2.load(ZODB.utils.z64, ''), self._storage.load(ZODB.utils.z64, '')) def checkLengthAndBool(self): self.assertEqual(len(self._storage), 0) self.assert_(not self._storage) db = DB(self._storage) # creates object 0. :) self.assertEqual(len(self._storage), 1) self.assert_(self._storage) conn = db.open() for i in range(10): conn.root()[i] = conn.root().__class__() transaction.commit() self.assertEqual(len(self._storage), 11) self.assert_(self._storage) def checkLoadBeforeUndo(self): pass # we don't support undo yet checkUndoZombie = checkLoadBeforeUndo class DemoStorageHexTests(DemoStorageTests): def setUp(self): StorageTestBase.StorageTestBase.setUp(self) self._storage = ZODB.tests.hexstorage.HexStorage( ZODB.DemoStorage.DemoStorage()) class DemoStorageWrappedBase(DemoStorageTests): def setUp(self): StorageTestBase.StorageTestBase.setUp(self) self._base = self._makeBaseStorage() self._storage = ZODB.DemoStorage.DemoStorage(base=self._base) def tearDown(self): self._base.close() StorageTestBase.StorageTestBase.tearDown(self) def _makeBaseStorage(self): raise NotImplementedError def checkPackOnlyOneObject(self): pass # Wrapping demo storages don't do gc def checkPackWithMultiDatabaseReferences(self): pass # we never do gc checkPackAllRevisions = checkPackWithMultiDatabaseReferences class DemoStorageWrappedAroundMappingStorage(DemoStorageWrappedBase): def _makeBaseStorage(self): from ZODB.MappingStorage import MappingStorage return MappingStorage() class DemoStorageWrappedAroundFileStorage(DemoStorageWrappedBase): def _makeBaseStorage(self): from ZODB.FileStorage import FileStorage return FileStorage('FileStorageTests.fs') class DemoStorageWrappedAroundHexMappingStorage(DemoStorageWrappedBase): def _makeBaseStorage(self): from ZODB.MappingStorage import MappingStorage return ZODB.tests.hexstorage.HexStorage(MappingStorage()) def setUp(test): random.seed(0) ZODB.tests.util.setUp(test) def testSomeDelegation(): r""" >>> class S: ... def __init__(self, name): ... self.name = name ... def registerDB(self, db): ... print self.name, db ... def close(self): ... print self.name, 'closed' ... sortKey = getSize = __len__ = history = getTid = None ... tpc_finish = tpc_vote = tpc_transaction = None ... _lock_acquire = _lock_release = lambda self: None ... getName = lambda self: 'S' ... isReadOnly = tpc_transaction = None ... supportsUndo = undo = undoLog = undoInfo = None ... supportsTransactionalUndo = None ... def new_oid(self): ... return '\0' * 8 ... def tpc_begin(self, t, tid, status): ... print 'begin', tid, status ... def tpc_abort(self, t): ... pass >>> from ZODB.DemoStorage import DemoStorage >>> storage = DemoStorage(base=S(1), changes=S(2)) >>> storage.registerDB(1) 2 1 >>> storage.close() 1 closed 2 closed >>> storage.tpc_begin(1, 2, 3) begin 2 3 >>> storage.tpc_abort(1) """ def blob_pos_key_error_with_non_blob_base(): """ >>> storage = ZODB.DemoStorage.DemoStorage() >>> storage.loadBlob(ZODB.utils.p64(1), ZODB.utils.p64(1)) Traceback (most recent call last): ... POSKeyError: 0x01 >>> storage.openCommittedBlobFile(ZODB.utils.p64(1), ZODB.utils.p64(1)) Traceback (most recent call last): ... POSKeyError: 0x01 """ def load_before_base_storage_current(): """ Here we'll exercise that DemoStorage's loadBefore method works properly when deferring to a record that is current in the base storage. >>> import time >>> import transaction >>> import ZODB.DB >>> import ZODB.DemoStorage >>> import ZODB.MappingStorage >>> import ZODB.utils >>> base = ZODB.MappingStorage.MappingStorage() >>> basedb = ZODB.DB(base) >>> conn = basedb.open() >>> conn.root()['foo'] = 'bar' >>> transaction.commit() >>> conn.close() >>> storage = ZODB.DemoStorage.DemoStorage(base=base) >>> db = ZODB.DB(storage) >>> conn = db.open() >>> conn.root()['foo'] = 'baz' >>> time.sleep(.1) # Windows has a low-resolution clock >>> transaction.commit() >>> oid = ZODB.utils.z64 >>> base_current = storage.base.load(oid) >>> tid = ZODB.utils.p64(ZODB.utils.u64(base_current[1]) + 1) >>> base_record = storage.base.loadBefore(oid, tid) >>> base_record[-1] is None True >>> base_current == base_record[:2] True >>> t = storage.loadBefore(oid, tid) The data and tid are the values from the base storage, but the next tid is from changes. >>> t[:2] == base_record[:2] True >>> t[-1] == storage.changes.load(oid)[1] True >>> conn.close() >>> db.close() >>> base.close() """ def test_suite(): suite = unittest.TestSuite(( doctest.DocTestSuite( setUp=setUp, tearDown=ZODB.tests.util.tearDown, ), doctest.DocFileSuite( '../DemoStorage.test', setUp=setUp, tearDown=ZODB.tests.util.tearDown, ), )) suite.addTest(unittest.makeSuite(DemoStorageTests, 'check')) suite.addTest(unittest.makeSuite(DemoStorageHexTests, 'check')) suite.addTest(unittest.makeSuite(DemoStorageWrappedAroundFileStorage, 'check')) suite.addTest(unittest.makeSuite(DemoStorageWrappedAroundMappingStorage, 'check')) suite.addTest(unittest.makeSuite(DemoStorageWrappedAroundHexMappingStorage, 'check')) return suite zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/speed.py0000644000175000017500000000667312214017464020436 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## usage="""Test speed of a ZODB storage Options: -d file The data file to use as input. The default is this script. -n n The number of repititions -s module A module that defines a 'Storage' attribute, which is an open storage. If not specified, a FileStorage will ne used. -z Test compressing data -D Run in debug mode -L Test loads as well as stores by minimizing the cache after eachrun -M Output means only """ import sys, os, getopt, string, time sys.path.insert(0, os.getcwd()) import ZODB, ZODB.FileStorage import persistent import transaction class P(persistent.Persistent): pass def main(args): opts, args = getopt.getopt(args, 'zd:n:Ds:LM') z=s=None data=sys.argv[0] nrep=5 minimize=0 detailed=1 for o, v in opts: if o=='-n': nrep=string.atoi(v) elif o=='-d': data=v elif o=='-s': s=v elif o=='-z': global zlib import zlib z=compress elif o=='-L': minimize=1 elif o=='-M': detailed=0 elif o=='-D': global debug os.environ['STUPID_LOG_FILE']='' os.environ['STUPID_LOG_SEVERITY']='-999' if s: s=__import__(s, globals(), globals(), ('__doc__',)) s=s.Storage else: s=ZODB.FileStorage.FileStorage('zeo_speed.fs', create=1) data=open(data).read() db=ZODB.DB(s, # disable cache deactivation cache_size=4000, cache_deactivate_after=6000,) results={1:0, 10:0, 100:0, 1000:0} for j in range(nrep): for r in 1, 10, 100, 1000: t=time.time() jar=db.open() transaction.begin() rt=jar.root() key='s%s' % r if rt.has_key(key): p=rt[key] else: rt[key]=p=P() for i in range(r): if z is not None: d=z(data) else: d=data v=getattr(p, str(i), P()) v.d=d setattr(p,str(i),v) transaction.commit() jar.close() t=time.time()-t if detailed: sys.stderr.write("%s\t%s\t%.4f\n" % (j, r, t)) sys.stdout.flush() results[r]=results[r]+t rt=d=p=v=None # release all references if minimize: time.sleep(3) jar.cacheMinimize(3) if detailed: print '-'*24 for r in 1, 10, 100, 1000: t=results[r]/nrep sys.stderr.write("mean:\t%s\t%.4f\t%.4f (s/o)\n" % (r, t, t/r)) db.close() def compress(s): c=zlib.compressobj() o=c.compress(s) return o+c.flush() if __name__=='__main__': main(sys.argv[1:]) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/StorageTestBase.py0000644000175000017500000001612512214017464022366 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Provide a mixin base class for storage tests. The StorageTestBase class provides basic setUp() and tearDown() semantics (which you can override), and it also provides a helper method _dostore() which performs a complete store transaction for a single object revision. """ import sys import time from cPickle import Pickler, Unpickler from cStringIO import StringIO import transaction from ZODB.utils import u64 from ZODB.tests.MinPO import MinPO import ZODB.tests.util ZERO = '\0'*8 def snooze(): # In Windows, it's possible that two successive time.time() calls return # the same value. Tim guarantees that time never runs backwards. You # usually want to call this before you pack a storage, or must make other # guarantees about increasing timestamps. now = time.time() while now == time.time(): time.sleep(0.1) def _persistent_id(obj): oid = getattr(obj, "_p_oid", None) if getattr(oid, "__get__", None) is not None: return None else: return oid def zodb_pickle(obj): """Create a pickle in the format expected by ZODB.""" f = StringIO() p = Pickler(f, 1) p.inst_persistent_id = _persistent_id klass = obj.__class__ assert not hasattr(obj, '__getinitargs__'), "not ready for constructors" args = None mod = getattr(klass, '__module__', None) if mod is not None: klass = mod, klass.__name__ state = obj.__getstate__() p.dump((klass, args)) p.dump(state) return f.getvalue(1) def persistent_load(pid): # helper for zodb_unpickle return "ref to %s.%s oid=%s" % (pid[1][0], pid[1][1], u64(pid[0])) def zodb_unpickle(data): """Unpickle an object stored using the format expected by ZODB.""" f = StringIO(data) u = Unpickler(f) u.persistent_load = persistent_load klass_info = u.load() if isinstance(klass_info, tuple): if isinstance(klass_info[0], type): # Unclear: what is the second part of klass_info? klass, xxx = klass_info assert not xxx else: if isinstance(klass_info[0], tuple): modname, klassname = klass_info[0] else: modname, klassname = klass_info if modname == "__main__": ns = globals() else: mod = import_helper(modname) ns = mod.__dict__ try: klass = ns[klassname] except KeyError: print >> sys.stderr, "can't find %s in %r" % (klassname, ns) inst = klass() else: raise ValueError("expected class info: %s" % repr(klass_info)) state = u.load() inst.__setstate__(state) return inst def handle_all_serials(oid, *args): """Return dict of oid to serialno from store() and tpc_vote(). Raises an exception if one of the calls raised an exception. The storage interface got complicated when ZEO was introduced. Any individual store() call can return None or a sequence of 2-tuples where the 2-tuple is either oid, serialno or an exception to be raised by the client. The original interface just returned the serialno for the object. """ d = {} for arg in args: if isinstance(arg, str): d[oid] = arg elif arg is None: pass else: for oid, serial in arg: if not isinstance(serial, str): raise serial # error from ZEO server d[oid] = serial return d def handle_serials(oid, *args): """Return the serialno for oid based on multiple return values. A helper for function _handle_all_serials(). """ return handle_all_serials(oid, *args)[oid] def import_helper(name): __import__(name) return sys.modules[name] class StorageTestBase(ZODB.tests.util.TestCase): # It would be simpler if concrete tests didn't need to extend # setUp() and tearDown(). _storage = None def _close(self): # You should override this if closing your storage requires additional # shutdown operations. if self._storage is not None: self._storage.close() def tearDown(self): self._close() ZODB.tests.util.TestCase.tearDown(self) def _dostore(self, oid=None, revid=None, data=None, already_pickled=0, user=None, description=None): """Do a complete storage transaction. The defaults are: - oid=None, ask the storage for a new oid - revid=None, use a revid of ZERO - data=None, pickle up some arbitrary data (the integer 7) Returns the object's new revision id. """ if oid is None: oid = self._storage.new_oid() if revid is None: revid = ZERO if data is None: data = MinPO(7) if type(data) == int: data = MinPO(data) if not already_pickled: data = zodb_pickle(data) # Begin the transaction t = transaction.Transaction() if user is not None: t.user = user if description is not None: t.description = description try: self._storage.tpc_begin(t) # Store an object r1 = self._storage.store(oid, revid, data, '', t) # Finish the transaction r2 = self._storage.tpc_vote(t) revid = handle_serials(oid, r1, r2) self._storage.tpc_finish(t) except: self._storage.tpc_abort(t) raise return revid def _dostoreNP(self, oid=None, revid=None, data=None, user=None, description=None): return self._dostore(oid, revid, data, 1, user, description) # The following methods depend on optional storage features. def _undo(self, tid, expected_oids=None, note=None): # Undo a tid that affects a single object (oid). # This is very specialized. t = transaction.Transaction() t.note(note or "undo") self._storage.tpc_begin(t) undo_result = self._storage.undo(tid, t) vote_result = self._storage.tpc_vote(t) self._storage.tpc_finish(t) if expected_oids is not None: oids = undo_result and undo_result[1] or [] oids.extend(oid for (oid, _) in vote_result or ()) self.assertEqual(len(oids), len(expected_oids), repr(oids)) for oid in expected_oids: self.assert_(oid in oids) return self._storage.lastTransaction() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testDB.py0000644000175000017500000002234612214017464020516 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from ZODB.tests.MinPO import MinPO import doctest import os import sys import time import transaction import unittest import ZODB import ZODB.tests.util # Return total number of connections across all pools in a db._pools. def nconn(pools): return sum([len(pool.all) for pool in pools.values()]) class DBTests(ZODB.tests.util.TestCase): def setUp(self): ZODB.tests.util.TestCase.setUp(self) self.db = ZODB.DB('test.fs') def tearDown(self): self.db.close() ZODB.tests.util.TestCase.tearDown(self) def dowork(self): c = self.db.open() r = c.root() o = r[time.time()] = MinPO(0) transaction.commit() for i in range(25): o.value = MinPO(i) transaction.commit() o = o.value serial = o._p_serial root_serial = r._p_serial c.close() return serial, root_serial # make sure the basic methods are callable def testSets(self): self.db.setCacheSize(15) self.db.setHistoricalCacheSize(15) def test_references(self): # TODO: For now test that we're using referencesf. We really should # have tests of referencesf. import ZODB.serialize self.assert_(self.db.references is ZODB.serialize.referencesf) def test_invalidateCache(): """The invalidateCache method invalidates a connection caches for all of the connections attached to a database:: >>> from ZODB.tests.util import DB >>> import transaction >>> db = DB() >>> tm1 = transaction.TransactionManager() >>> c1 = db.open(transaction_manager=tm1) >>> c1.root()['a'] = MinPO(1) >>> tm1.commit() >>> tm2 = transaction.TransactionManager() >>> c2 = db.open(transaction_manager=tm2) >>> c1.root()['a']._p_deactivate() >>> tm3 = transaction.TransactionManager() >>> c3 = db.open(transaction_manager=tm3) >>> c3.root()['a'].value 1 >>> c3.close() >>> db.invalidateCache() >>> c1.root()['a'].value Traceback (most recent call last): ... ReadConflictError: database read conflict error >>> c2.root()['a'].value Traceback (most recent call last): ... ReadConflictError: database read conflict error >>> c3 is db.open(transaction_manager=tm3) True >>> print c3.root()['a']._p_changed None >>> db.close() """ def connectionDebugInfo(): r"""DB.connectionDebugInfo provides information about connections. >>> import time >>> now = 1228423244.5 >>> def faux_time(): ... global now ... now += .1 ... return now >>> real_time = time.time >>> time.time = faux_time >>> from ZODB.tests.util import DB >>> import transaction >>> db = DB() >>> c1 = db.open() >>> c1.setDebugInfo('test info') >>> c1.root()['a'] = MinPO(1) >>> transaction.commit() >>> c2 = db.open() >>> _ = c1.root()['a'] >>> c2.close() >>> c3 = db.open(before=c1.root()._p_serial) >>> info = db.connectionDebugInfo() >>> import pprint >>> pprint.pprint(sorted(info, key=lambda i: str(i['opened'])), width=1) [{'before': None, 'info': 'test info (2)', 'opened': '2008-12-04T20:40:44Z (1.40s)'}, {'before': '\x03zY\xd8\xc0m9\xdd', 'info': ' (0)', 'opened': '2008-12-04T20:40:45Z (0.30s)'}, {'before': None, 'info': ' (0)', 'opened': None}] >>> time.time = real_time """ def passing_a_file_name_to_DB(): """You can pass a file-storage file name to DB. (Also note that we can access DB in ZODB.) >>> db = ZODB.DB('data.fs') >>> db.storage # doctest: +ELLIPSIS >> os.path.exists('data.fs') True >>> db.close() """ def passing_None_to_DB(): """You can pass None DB to get a MappingStorage. (Also note that we can access DB in ZODB.) >>> db = ZODB.DB(None) >>> db.storage # doctest: +ELLIPSIS >> db.close() """ def open_convenience(): """Often, we just want to open a single connection. >>> conn = ZODB.connection('data.fs') >>> conn.root() {} >>> conn.root()['x'] = 1 >>> transaction.commit() >>> conn.close() Let's make sure the database was cloased when we closed the connection, and that the data is there. >>> db = ZODB.DB('data.fs') >>> conn = db.open() >>> conn.root() {'x': 1} >>> db.close() We can pass storage-specific arguments if they don't conflict with DB arguments. >>> conn = ZODB.connection('data.fs', blob_dir='blobs') >>> conn.root()['b'] = ZODB.blob.Blob('test') >>> transaction.commit() >>> conn.close() >>> db = ZODB.DB('data.fs', blob_dir='blobs') >>> conn = db.open() >>> conn.root()['b'].open().read() 'test' >>> db.close() """ if sys.version_info >= (2, 6): def db_with_transaction(): """Using databases with with The transaction method returns a context manager that when entered starts a transaction with a private transaction manager. To illustrate this, we start a trasnaction using a regular connection and see that it isn't automatically committed or aborted as we use the transaction context manager. >>> db = ZODB.tests.util.DB() >>> conn = db.open() >>> conn.root()['x'] = conn.root().__class__() >>> transaction.commit() >>> conn.root()['x']['x'] = 1 >>> with db.transaction() as conn2: ... conn2.root()['y'] = 1 >>> conn2.opened Now, we'll open a 3rd connection a verify that >>> conn3 = db.open() >>> conn3.root()['x'] {} >>> conn3.root()['y'] 1 >>> conn3.close() Let's try again, but this time, we'll have an exception: >>> with db.transaction() as conn2: ... conn2.root()['y'] = 2 ... XXX Traceback (most recent call last): ... NameError: name 'XXX' is not defined >>> conn2.opened >>> conn3 = db.open() >>> conn3.root()['x'] {} >>> conn3.root()['y'] 1 >>> conn3.close() >>> transaction.commit() >>> conn3 = db.open() >>> conn3.root()['x'] {'x': 1} >>> db.close() """ def connection_allows_empty_version_for_idiots(): r""" >>> db = ZODB.DB('t.fs') >>> c = ZODB.tests.util.assert_deprecated( ... (lambda : db.open('')), ... 'A version string was passed to open') >>> c.root() {} >>> db.close() """ def warn_when_data_records_are_big(): """ When data records are large, a warning is issued to try to prevent new users from shooting themselves in the foot. >>> db = ZODB.DB('t.fs', create=True) >>> conn = db.open() >>> conn.root.x = 'x'*(1<<24) >>> ZODB.tests.util.assert_warning(UserWarning, transaction.commit, ... "object you're saving is large.") >>> db.close() The large_record_size option can be used to control the record size: >>> db = ZODB.DB('t.fs', create=True, large_record_size=999) >>> conn = db.open() >>> conn.root.x = 'x' >>> transaction.commit() >>> conn.root.x = 'x'*999 >>> ZODB.tests.util.assert_warning(UserWarning, transaction.commit, ... "object you're saving is large.") >>> db.close() We can also specify it using a configuration option: >>> import ZODB.config >>> db = ZODB.config.databaseFromString(''' ... ... large-record-size 1MB ... ... path t.fs ... create true ... ... ... ''') >>> conn = db.open() >>> conn.root.x = 'x' >>> transaction.commit() >>> conn.root.x = 'x'*(1<<20) >>> ZODB.tests.util.assert_warning(UserWarning, transaction.commit, ... "object you're saving is large.") >>> db.close() """ # ' def minimally_test_connection_timeout(): """There's a mechanism to discard old connections. Make sure it doesn't error. :) >>> db = ZODB.DB(None, pool_timeout=.01) >>> c1 = db.open() >>> c2 = db.open() >>> c1.close() >>> c2.close() >>> time.sleep(.02) >>> db.open() is c2 True >>> db.pool.available [] """ def test_suite(): s = unittest.makeSuite(DBTests) s.addTest(doctest.DocTestSuite( setUp=ZODB.tests.util.setUp, tearDown=ZODB.tests.util.tearDown, )) return s zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testUtils.py0000644000175000017500000000735412214017464021333 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test the routines to convert between long and 64-bit strings""" from persistent import Persistent import doctest import random import unittest NUM = 100 from ZODB.utils import U64, p64, u64 class TestUtils(unittest.TestCase): small = [random.randrange(1, 1L<<32, int=long) for i in range(NUM)] large = [random.randrange(1L<<32, 1L<<64, int=long) for i in range(NUM)] all = small + large def checkLongToStringToLong(self): for num in self.all: s = p64(num) n = U64(s) self.assertEquals(num, n, "U64() failed") n2 = u64(s) self.assertEquals(num, n2, "u64() failed") def checkKnownConstants(self): self.assertEquals("\000\000\000\000\000\000\000\001", p64(1)) self.assertEquals("\000\000\000\001\000\000\000\000", p64(1L<<32)) self.assertEquals(u64("\000\000\000\000\000\000\000\001"), 1) self.assertEquals(U64("\000\000\000\000\000\000\000\001"), 1) self.assertEquals(u64("\000\000\000\001\000\000\000\000"), 1L<<32) self.assertEquals(U64("\000\000\000\001\000\000\000\000"), 1L<<32) def checkPersistentIdHandlesDescriptor(self): from ZODB.serialize import ObjectWriter class P(Persistent): pass writer = ObjectWriter(None) self.assertEqual(writer.persistent_id(P), None) # It's hard to know where to put this test. We're checking that the # ConflictError constructor uses utils.py's get_pickle_metadata() to # deduce the class path from a pickle, instead of actually loading # the pickle (and so also trying to import application module and # class objects, which isn't a good idea on a ZEO server when avoidable). def checkConflictErrorDoesntImport(self): from ZODB.serialize import ObjectWriter from ZODB.POSException import ConflictError from ZODB.tests.MinPO import MinPO import cPickle as pickle obj = MinPO() data = ObjectWriter().serialize(obj) # The pickle contains a GLOBAL ('c') opcode resolving to MinPO's # module and class. self.assert_('cZODB.tests.MinPO\nMinPO\n' in data) # Fiddle the pickle so it points to something "impossible" instead. data = data.replace('cZODB.tests.MinPO\nMinPO\n', 'cpath.that.does.not.exist\nlikewise.the.class\n') # Pickle can't resolve that GLOBAL opcode -- gets ImportError. self.assertRaises(ImportError, pickle.loads, data) # Verify that building ConflictError doesn't get ImportError. try: raise ConflictError(object=obj, data=data) except ConflictError, detail: # And verify that the msg names the impossible path. self.assert_('path.that.does.not.exist.likewise.the.class' in str(detail)) else: self.fail("expected ConflictError, but no exception raised") def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestUtils, 'check')) suite.addTest(doctest.DocFileSuite('../utils.txt')) return suite zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/PackableStorage.py0000644000175000017500000006752012214017464022363 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Run some tests relevant for storages that support pack().""" from cStringIO import StringIO from persistent import Persistent from persistent.mapping import PersistentMapping from ZODB import DB from ZODB.POSException import ConflictError, StorageError from ZODB.serialize import referencesf from ZODB.tests.MinPO import MinPO from ZODB.tests.MTStorage import TestThread from ZODB.tests.StorageTestBase import snooze import cPickle import doctest import time import transaction import ZODB.interfaces import ZODB.tests.util import zope.testing.setupstack ZERO = '\0'*8 # This class is for the root object. It must not contain a getoid() method # (really, attribute). The persistent pickling machinery -- in the dumps() # function below -- will pickle Root objects as normal, but any attributes # which reference persistent Object instances will get pickled as persistent # ids, not as the object's state. This makes the referencesf stuff work, # because it pickle sniffs for persistent ids (so we have to get those # persistent ids into the root object's pickle). class Root: pass # This is the persistent Object class. Because it has a getoid() method, the # persistent pickling machinery -- in the dumps() function below -- will # pickle the oid string instead of the object's actual state. Yee haw, this # stuff is deep. ;) class Object(object): def __init__(self, oid): self._oid = oid def getoid(self): return self._oid class C(Persistent): pass # Here's where all the magic occurs. Sadly, the pickle module is a bit # underdocumented, but here's what happens: by setting the persistent_id # attribute to getpersid() on the pickler, that function gets called for every # object being pickled. By returning None when the object has no getoid # attribute, it signals pickle to serialize the object as normal. That's how # the Root instance gets pickled correctly. But, if the object has a getoid # attribute, then by returning that method's value, we tell pickle to # serialize the persistent id of the object instead of the object's state. # That sets the pickle up for proper sniffing by the referencesf machinery. # Fun, huh? def dumps(obj): def getpersid(obj): if hasattr(obj, 'getoid'): return obj.getoid() return None s = StringIO() p = cPickle.Pickler(s, 1) p.inst_persistent_id = getpersid p.dump(obj) p.dump(None) return s.getvalue() def pdumps(obj): s = StringIO() p = cPickle.Pickler(s) p.dump(obj) p.dump(None) return s.getvalue() class PackableStorageBase: # We keep a cache of object ids to instances so that the unpickler can # easily return any persistent object. @property def _cache(self): try: return self.__cache except AttributeError: self.__cache = {} return self.__cache def _newobj(self): # This is a convenience method to create a new persistent Object # instance. It asks the storage for a new object id, creates the # instance with the given oid, populates the cache and returns the # object. oid = self._storage.new_oid() obj = Object(oid) self._cache[obj.getoid()] = obj return obj def _makeloader(self): # This is the other side of the persistent pickling magic. We need a # custom unpickler to mirror our custom pickler above. By setting the # persistent_load function of the unpickler to self._cache.get(), # whenever a persistent id is unpickled, it will actually return the # Object instance out of the cache. As far as returning a function # with an argument bound to an instance attribute method, we do it # this way because it makes the code in the tests more succinct. # # BUT! Be careful in your use of loads() vs. cPickle.loads(). loads() # should only be used on the Root object's pickle since it's the only # special one. All the Object instances should use cPickle.loads(). def loads(str, persfunc=self._cache.get): fp = StringIO(str) u = cPickle.Unpickler(fp) u.persistent_load = persfunc return u.load() return loads def _initroot(self): try: self._storage.load(ZERO, '') except KeyError: from transaction import Transaction file = StringIO() p = cPickle.Pickler(file, 1) p.dump((PersistentMapping, None)) p.dump({'_container': {}}) t=Transaction() t.description='initial database creation' self._storage.tpc_begin(t) self._storage.store(ZERO, None, file.getvalue(), '', t) self._storage.tpc_vote(t) self._storage.tpc_finish(t) def _sanity_check(self): # Iterate over the storage to make sure it's sane. if not ZODB.interfaces.IStorageIteration.providedBy(self._storage): return it = self._storage.iterator() for txn in it: for data in txn: pass class PackableStorage(PackableStorageBase): def checkPackEmptyStorage(self): self._storage.pack(time.time(), referencesf) def checkPackTomorrow(self): self._initroot() self._storage.pack(time.time() + 10000, referencesf) def checkPackYesterday(self): self._initroot() self._storage.pack(time.time() - 10000, referencesf) def _PackWhileWriting(self, pack_now): # A storage should allow some reading and writing during # a pack. This test attempts to exercise locking code # in the storage to test that it is safe. It generates # a lot of revisions, so that pack takes a long time. db = DB(self._storage) conn = db.open() root = conn.root() for i in range(10): root[i] = MinPO(i) transaction.commit() snooze() packt = time.time() choices = range(10) for dummy in choices: for i in choices: root[i].value = MinPO(i) transaction.commit() # How many client threads should we run, and how long should we # wait for them to finish? Hard to say. Running 4 threads and # waiting 30 seconds too often left a thread still alive on Tim's # Win98SE box, during ZEO flavors of this test. Those tend to # run one thread at a time to completion, and take about 10 seconds # per thread. There doesn't appear to be a compelling reason to # run that many threads. Running 3 threads and waiting up to a # minute seems to work well in practice. The ZEO tests normally # finish faster than that, and the non-ZEO tests very much faster # than that. NUM_LOOP_TRIP = 50 timer = ElapsedTimer(time.time()) threads = [ClientThread(db, choices, NUM_LOOP_TRIP, timer, i) for i in range(3)] for t in threads: t.start() if pack_now: db.pack(time.time()) else: db.pack(packt) for t in threads: t.join(60) liveness = [t.isAlive() for t in threads] if True in liveness: # They should have finished by now. print 'Liveness:', liveness # Combine the outcomes, and sort by start time. outcomes = [] for t in threads: outcomes.extend(t.outcomes) # each outcome list has as many of these as a loop trip got thru: # thread_id # elapsed millis at loop top # elapsed millis at attempt to assign to self.root[index] # index into self.root getting replaced # elapsed millis when outcome known # 'OK' or 'Conflict' # True if we got beyond this line, False if it raised an # exception (one possible Conflict cause): # self.root[index].value = MinPO(j) def cmp_by_time(a, b): return cmp((a[1], a[0]), (b[1], b[0])) outcomes.sort(cmp_by_time) counts = [0] * 4 for outcome in outcomes: n = len(outcome) assert n >= 2 tid = outcome[0] print 'tid:%d top:%5d' % (tid, outcome[1]), if n > 2: print 'commit:%5d' % outcome[2], if n > 3: print 'index:%2d' % outcome[3], if n > 4: print 'known:%5d' % outcome[4], if n > 5: print '%8s' % outcome[5], if n > 6: print 'assigned:%5s' % outcome[6], counts[tid] += 1 if counts[tid] == NUM_LOOP_TRIP: print 'thread %d done' % tid, print self.fail('a thread is still alive') self._sanity_check() def checkPackWhileWriting(self): self._PackWhileWriting(pack_now=False) def checkPackNowWhileWriting(self): self._PackWhileWriting(pack_now=True) def checkPackLotsWhileWriting(self): # This is like the other pack-while-writing tests, except it packs # repeatedly until the client thread is done. At the time it was # introduced, it reliably provoked # CorruptedError: ... transaction with checkpoint flag set # in the ZEO flavor of the FileStorage tests. db = DB(self._storage) conn = db.open() root = conn.root() choices = range(10) for i in choices: root[i] = MinPO(i) transaction.commit() snooze() packt = time.time() for dummy in choices: for i in choices: root[i].value = MinPO(i) transaction.commit() NUM_LOOP_TRIP = 100 timer = ElapsedTimer(time.time()) thread = ClientThread(db, choices, NUM_LOOP_TRIP, timer, 0) thread.start() while thread.isAlive(): db.pack(packt) snooze() packt = time.time() thread.join() self._sanity_check() def checkPackWithMultiDatabaseReferences(self): databases = {} db = DB(self._storage, databases=databases, database_name='') otherdb = ZODB.tests.util.DB(databases=databases, database_name='o') conn = db.open() root = conn.root() root[1] = C() transaction.commit() del root[1] transaction.commit() root[2] = conn.get_connection('o').root() transaction.commit() db.pack(time.time()+1) # some valid storages always return 0 for len() self.assertTrue(len(self._storage) in (0, 1)) def checkPackAllRevisions(self): self._initroot() eq = self.assertEqual raises = self.assertRaises # Create a `persistent' object obj = self._newobj() oid = obj.getoid() obj.value = 1 # Commit three different revisions revid1 = self._dostoreNP(oid, data=pdumps(obj)) obj.value = 2 revid2 = self._dostoreNP(oid, revid=revid1, data=pdumps(obj)) obj.value = 3 revid3 = self._dostoreNP(oid, revid=revid2, data=pdumps(obj)) # Now make sure all three revisions can be extracted data = self._storage.loadSerial(oid, revid1) pobj = cPickle.loads(data) eq(pobj.getoid(), oid) eq(pobj.value, 1) data = self._storage.loadSerial(oid, revid2) pobj = cPickle.loads(data) eq(pobj.getoid(), oid) eq(pobj.value, 2) data = self._storage.loadSerial(oid, revid3) pobj = cPickle.loads(data) eq(pobj.getoid(), oid) eq(pobj.value, 3) # Now pack all transactions; need to sleep a second to make # sure that the pack time is greater than the last commit time. now = packtime = time.time() while packtime <= now: packtime = time.time() self._storage.pack(packtime, referencesf) # All revisions of the object should be gone, since there is no # reference from the root object to this object. raises(KeyError, self._storage.loadSerial, oid, revid1) raises(KeyError, self._storage.loadSerial, oid, revid2) raises(KeyError, self._storage.loadSerial, oid, revid3) def checkPackJustOldRevisions(self): eq = self.assertEqual raises = self.assertRaises loads = self._makeloader() # Create a root object. This can't be an instance of Object, # otherwise the pickling machinery will serialize it as a persistent # id and not as an object that contains references (persistent ids) to # other objects. root = Root() # Create a persistent object, with some initial state obj = self._newobj() oid = obj.getoid() # Link the root object to the persistent object, in order to keep the # persistent object alive. Store the root object. root.obj = obj root.value = 0 revid0 = self._dostoreNP(ZERO, data=dumps(root)) # Make sure the root can be retrieved data, revid = self._storage.load(ZERO, '') eq(revid, revid0) eq(loads(data).value, 0) # Commit three different revisions of the other object obj.value = 1 revid1 = self._dostoreNP(oid, data=pdumps(obj)) obj.value = 2 revid2 = self._dostoreNP(oid, revid=revid1, data=pdumps(obj)) obj.value = 3 revid3 = self._dostoreNP(oid, revid=revid2, data=pdumps(obj)) # Now make sure all three revisions can be extracted data = self._storage.loadSerial(oid, revid1) pobj = cPickle.loads(data) eq(pobj.getoid(), oid) eq(pobj.value, 1) data = self._storage.loadSerial(oid, revid2) pobj = cPickle.loads(data) eq(pobj.getoid(), oid) eq(pobj.value, 2) data = self._storage.loadSerial(oid, revid3) pobj = cPickle.loads(data) eq(pobj.getoid(), oid) eq(pobj.value, 3) # Now pack just revisions 1 and 2. The object's current revision # should stay alive because it's pointed to by the root. now = packtime = time.time() while packtime <= now: packtime = time.time() self._storage.pack(packtime, referencesf) # Make sure the revisions are gone, but that object zero and revision # 3 are still there and correct data, revid = self._storage.load(ZERO, '') eq(revid, revid0) eq(loads(data).value, 0) raises(KeyError, self._storage.loadSerial, oid, revid1) raises(KeyError, self._storage.loadSerial, oid, revid2) data = self._storage.loadSerial(oid, revid3) pobj = cPickle.loads(data) eq(pobj.getoid(), oid) eq(pobj.value, 3) data, revid = self._storage.load(oid, '') eq(revid, revid3) pobj = cPickle.loads(data) eq(pobj.getoid(), oid) eq(pobj.value, 3) def checkPackOnlyOneObject(self): eq = self.assertEqual raises = self.assertRaises loads = self._makeloader() # Create a root object. This can't be an instance of Object, # otherwise the pickling machinery will serialize it as a persistent # id and not as an object that contains references (persistent ids) to # other objects. root = Root() # Create a persistent object, with some initial state obj1 = self._newobj() oid1 = obj1.getoid() # Create another persistent object, with some initial state. obj2 = self._newobj() oid2 = obj2.getoid() # Link the root object to the persistent objects, in order to keep # them alive. Store the root object. root.obj1 = obj1 root.obj2 = obj2 root.value = 0 revid0 = self._dostoreNP(ZERO, data=dumps(root)) # Make sure the root can be retrieved data, revid = self._storage.load(ZERO, '') eq(revid, revid0) eq(loads(data).value, 0) # Commit three different revisions of the first object obj1.value = 1 revid1 = self._dostoreNP(oid1, data=pdumps(obj1)) obj1.value = 2 revid2 = self._dostoreNP(oid1, revid=revid1, data=pdumps(obj1)) obj1.value = 3 revid3 = self._dostoreNP(oid1, revid=revid2, data=pdumps(obj1)) # Now make sure all three revisions can be extracted data = self._storage.loadSerial(oid1, revid1) pobj = cPickle.loads(data) eq(pobj.getoid(), oid1) eq(pobj.value, 1) data = self._storage.loadSerial(oid1, revid2) pobj = cPickle.loads(data) eq(pobj.getoid(), oid1) eq(pobj.value, 2) data = self._storage.loadSerial(oid1, revid3) pobj = cPickle.loads(data) eq(pobj.getoid(), oid1) eq(pobj.value, 3) # Now commit a revision of the second object obj2.value = 11 revid4 = self._dostoreNP(oid2, data=pdumps(obj2)) # And make sure the revision can be extracted data = self._storage.loadSerial(oid2, revid4) pobj = cPickle.loads(data) eq(pobj.getoid(), oid2) eq(pobj.value, 11) # Now pack just revisions 1 and 2 of object1. Object1's current # revision should stay alive because it's pointed to by the root, as # should Object2's current revision. now = packtime = time.time() while packtime <= now: packtime = time.time() self._storage.pack(packtime, referencesf) # Make sure the revisions are gone, but that object zero, object2, and # revision 3 of object1 are still there and correct. data, revid = self._storage.load(ZERO, '') eq(revid, revid0) eq(loads(data).value, 0) raises(KeyError, self._storage.loadSerial, oid1, revid1) raises(KeyError, self._storage.loadSerial, oid1, revid2) data = self._storage.loadSerial(oid1, revid3) pobj = cPickle.loads(data) eq(pobj.getoid(), oid1) eq(pobj.value, 3) data, revid = self._storage.load(oid1, '') eq(revid, revid3) pobj = cPickle.loads(data) eq(pobj.getoid(), oid1) eq(pobj.value, 3) data, revid = self._storage.load(oid2, '') eq(revid, revid4) eq(loads(data).value, 11) data = self._storage.loadSerial(oid2, revid4) pobj = cPickle.loads(data) eq(pobj.getoid(), oid2) eq(pobj.value, 11) class PackableStorageWithOptionalGC(PackableStorage): def checkPackAllRevisionsNoGC(self): self._initroot() eq = self.assertEqual raises = self.assertRaises # Create a `persistent' object obj = self._newobj() oid = obj.getoid() obj.value = 1 # Commit three different revisions revid1 = self._dostoreNP(oid, data=pdumps(obj)) obj.value = 2 revid2 = self._dostoreNP(oid, revid=revid1, data=pdumps(obj)) obj.value = 3 revid3 = self._dostoreNP(oid, revid=revid2, data=pdumps(obj)) # Now make sure all three revisions can be extracted data = self._storage.loadSerial(oid, revid1) pobj = cPickle.loads(data) eq(pobj.getoid(), oid) eq(pobj.value, 1) data = self._storage.loadSerial(oid, revid2) pobj = cPickle.loads(data) eq(pobj.getoid(), oid) eq(pobj.value, 2) data = self._storage.loadSerial(oid, revid3) pobj = cPickle.loads(data) eq(pobj.getoid(), oid) eq(pobj.value, 3) # Now pack all transactions; need to sleep a second to make # sure that the pack time is greater than the last commit time. now = packtime = time.time() while packtime <= now: packtime = time.time() self._storage.pack(packtime, referencesf, gc=False) # Only old revisions of the object should be gone. We don't gc raises(KeyError, self._storage.loadSerial, oid, revid1) raises(KeyError, self._storage.loadSerial, oid, revid2) self._storage.loadSerial(oid, revid3) class PackableUndoStorage(PackableStorageBase): def checkPackUnlinkedFromRoot(self): eq = self.assertEqual db = DB(self._storage) conn = db.open() root = conn.root() txn = transaction.get() txn.note('root') txn.commit() now = packtime = time.time() while packtime <= now: packtime = time.time() obj = C() obj.value = 7 root['obj'] = obj txn = transaction.get() txn.note('root -> o1') txn.commit() del root['obj'] txn = transaction.get() txn.note('root -x-> o1') txn.commit() self._storage.pack(packtime, referencesf) log = self._storage.undoLog() tid = log[0]['id'] db.undo(tid) txn = transaction.get() txn.note('undo root -x-> o1') txn.commit() conn.sync() eq(root['obj'].value, 7) def checkRedundantPack(self): # It is an error to perform a pack with a packtime earlier # than a previous packtime. The storage can't do a full # traversal as of the packtime, because the previous pack may # have removed revisions necessary for a full traversal. # It should be simple to test that a storage error is raised, # but this test case goes to the trouble of constructing a # scenario that would lose data if the earlier packtime was # honored. self._initroot() db = DB(self._storage) conn = db.open() root = conn.root() root["d"] = d = PersistentMapping() transaction.commit() snooze() obj = d["obj"] = C() obj.value = 1 transaction.commit() snooze() packt1 = time.time() lost_oid = obj._p_oid obj = d["anotherobj"] = C() obj.value = 2 transaction.commit() snooze() packt2 = time.time() db.pack(packt2) # BDBStorage allows the second pack, but doesn't lose data. try: db.pack(packt1) except StorageError: pass # This object would be removed by the second pack, even though # it is reachable. self._storage.load(lost_oid, "") def checkPackUndoLog(self): self._initroot() # Create a `persistent' object obj = self._newobj() oid = obj.getoid() obj.value = 1 # Commit two different revisions revid1 = self._dostoreNP(oid, data=pdumps(obj)) obj.value = 2 snooze() packtime = time.time() snooze() self._dostoreNP(oid, revid=revid1, data=pdumps(obj)) # Now pack the first transaction self.assertEqual(3, len(self._storage.undoLog())) self._storage.pack(packtime, referencesf) # The undo log contains only the most resent transaction self.assertEqual(1,len(self._storage.undoLog())) def dont_checkPackUndoLogUndoable(self): # A disabled test. I wanted to test that the content of the # undo log was consistent, but every storage appears to # include something slightly different. If the result of this # method is only used to fill a GUI then this difference # doesnt matter. Perhaps re-enable this test once we agree # what should be asserted. self._initroot() # Create two `persistent' object obj1 = self._newobj() oid1 = obj1.getoid() obj1.value = 1 obj2 = self._newobj() oid2 = obj2.getoid() obj2.value = 2 # Commit the first revision of each of them revid11 = self._dostoreNP(oid1, data=pdumps(obj1), description="1-1") revid22 = self._dostoreNP(oid2, data=pdumps(obj2), description="2-2") # remember the time. everything above here will be packed away snooze() packtime = time.time() snooze() # Commit two revisions of the first object obj1.value = 3 revid13 = self._dostoreNP(oid1, revid=revid11, data=pdumps(obj1), description="1-3") obj1.value = 4 self._dostoreNP(oid1, revid=revid13, data=pdumps(obj1), description="1-4") # Commit one revision of the second object obj2.value = 5 self._dostoreNP(oid2, revid=revid22, data=pdumps(obj2), description="2-5") # Now pack self.assertEqual(6,len(self._storage.undoLog())) print '\ninitial undoLog was' for r in self._storage.undoLog(): print r self._storage.pack(packtime, referencesf) # The undo log contains only two undoable transaction. print '\nafter packing undoLog was' for r in self._storage.undoLog(): print r # what can we assert about that? # A number of these threads are kicked off by _PackWhileWriting(). Their # purpose is to abuse the database passed to the constructor with lots of # random write activity while the main thread is packing it. class ClientThread(TestThread): def __init__(self, db, choices, loop_trip, timer, thread_id): TestThread.__init__(self) self.root = db.open().root() self.choices = choices self.loop_trip = loop_trip self.millis = timer.elapsed_millis self.thread_id = thread_id # list of lists; each list has as many of these as a loop trip # got thru: # thread_id # elapsed millis at loop top # elapsed millis at attempt # index into self.root getting replaced # elapsed millis when outcome known # 'OK' or 'Conflict' # True if we got beyond this line, False if it raised an exception: # self.root[index].value = MinPO(j) self.outcomes = [] def runtest(self): from random import choice for j in range(self.loop_trip): assign_worked = False alist = [self.thread_id, self.millis()] self.outcomes.append(alist) try: index = choice(self.choices) alist.extend([self.millis(), index]) self.root[index].value = MinPO(j) assign_worked = True transaction.commit() alist.append(self.millis()) alist.append('OK') except ConflictError: alist.append(self.millis()) alist.append('Conflict') transaction.abort() alist.append(assign_worked) class ElapsedTimer: def __init__(self, start_time): self.start_time = start_time def elapsed_millis(self): return int((time.time() - self.start_time) * 1000) def IExternalGC_suite(factory): """Return a test suite for a generic . Pass a factory taking a name and a blob directory name. """ def setup(test): ZODB.tests.util.setUp(test) test.globs['create_storage'] = factory return doctest.DocFileSuite( 'IExternalGC.test', setUp=setup, tearDown=zope.testing.setupstack.tearDown) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/HistoryStorage.py0000644000175000017500000000413112214017464022307 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Run the history() related tests for a storage. Any storage that supports the history() method should be able to pass all these tests. """ from ZODB.tests.MinPO import MinPO class HistoryStorage: def checkSimpleHistory(self): eq = self.assertEqual # Store a couple of revisions of the object oid = self._storage.new_oid() self.assertRaises(KeyError,self._storage.history,oid) revid1 = self._dostore(oid, data=MinPO(11)) revid2 = self._dostore(oid, revid=revid1, data=MinPO(12)) revid3 = self._dostore(oid, revid=revid2, data=MinPO(13)) # Now get various snapshots of the object's history h = self._storage.history(oid, size=1) eq(len(h), 1) d = h[0] eq(d['tid'], revid3) # Try to get 2 historical revisions h = self._storage.history(oid, size=2) eq(len(h), 2) d = h[0] eq(d['tid'], revid3) d = h[1] eq(d['tid'], revid2) # Try to get all 3 historical revisions h = self._storage.history(oid, size=3) eq(len(h), 3) d = h[0] eq(d['tid'], revid3) d = h[1] eq(d['tid'], revid2) d = h[2] eq(d['tid'], revid1) # There should be no more than 3 revisions h = self._storage.history(oid, size=4) eq(len(h), 3) d = h[0] eq(d['tid'], revid3) d = h[1] eq(d['tid'], revid2) d = h[2] eq(d['tid'], revid1) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/multidb.txt0000644000175000017500000001327212214017464021156 0ustar arnauarnau================== Multiple Databases ================== Multi-database support adds the ability to tie multiple databases into a collection. The original proposal is in the fishbowl: http://www.zope.org/Wikis/ZODB/MultiDatabases/ It was implemented during the PyCon 2005 sprints, but in a simpler form, by Jim Fulton, Christian Theune, and Tim Peters. Overview: No private attributes were added, and one new method was introduced. ``DB``: - a new ``.database_name`` attribute holds the name of this database. - a new ``.databases`` attribute maps from database name to ``DB`` object; all databases in a multi-database collection share the same ``.databases`` object - the ``DB`` constructor has new optional arguments with the same names (``database_name=`` and ``databases=``). ``Connection``: - a new ``.connections`` attribute maps from database name to a ``Connection`` for the database with that name; the ``.connections`` mapping object is also shared among databases in a collection. - a new ``.get_connection(database_name)`` method returns a ``Connection`` for a database in the collection; if a connection is already open, it's returned (this is the value ``.connections[database_name]``), else a new connection is opened (and stored as ``.connections[database_name]``) Creating a multi-database starts with creating a named ``DB``: >>> from ZODB.tests.test_storage import MinimalMemoryStorage >>> from ZODB import DB >>> dbmap = {} >>> db = DB(MinimalMemoryStorage(), database_name='root', databases=dbmap) The database name is accessible afterwards and in a newly created collection: >>> db.database_name 'root' >>> db.databases # doctest: +ELLIPSIS {'root': } >>> db.databases is dbmap True Adding another database to the collection works like this: >>> db2 = DB(MinimalMemoryStorage(), ... database_name='notroot', ... databases=dbmap) The new ``db2`` now shares the ``databases`` dictionary with db and has two entries: >>> db2.databases is db.databases is dbmap True >>> len(db2.databases) 2 >>> names = dbmap.keys(); names.sort(); print names ['notroot', 'root'] It's an error to try to insert a database with a name already in use: >>> db3 = DB(MinimalMemoryStorage(), ... database_name='root', ... databases=dbmap) Traceback (most recent call last): ... ValueError: database_name 'root' already in databases Because that failed, ``db.databases`` wasn't changed: >>> len(db.databases) # still 2 2 You can (still) get a connection to a database this way: >>> import transaction >>> tm = transaction.TransactionManager() >>> cn = db.open(transaction_manager=tm) >>> cn # doctest: +ELLIPSIS This is the only connection in this collection right now: >>> cn.connections # doctest: +ELLIPSIS {'root': } Getting a connection to a different database from an existing connection in the same database collection (this enables 'connection binding' within a given thread/transaction/context ...): >>> cn2 = cn.get_connection('notroot') >>> cn2 # doctest: +ELLIPSIS The second connection gets the same transaction manager as the first: >>> cn2.transaction_manager is tm True Now there are two connections in that collection: >>> cn2.connections is cn.connections True >>> len(cn2.connections) 2 >>> names = cn.connections.keys(); names.sort(); print names ['notroot', 'root'] So long as this database group remains open, the same ``Connection`` objects are returned: >>> cn.get_connection('root') is cn True >>> cn.get_connection('notroot') is cn2 True >>> cn2.get_connection('root') is cn True >>> cn2.get_connection('notroot') is cn2 True Of course trying to get a connection for a database not in the group raises an exception: >>> cn.get_connection('no way') Traceback (most recent call last): ... KeyError: 'no way' Clean up: >>> for a_db in dbmap.values(): ... a_db.close() Configuration from File ----------------------- The database name can also be specified in a config file, starting in ZODB 3.6: >>> from ZODB.config import databaseFromString >>> config = """ ... ... ... database-name this_is_the_name ... ... """ >>> db = databaseFromString(config) >>> print db.database_name this_is_the_name >>> db.databases.keys() ['this_is_the_name'] However, the ``.databases`` attribute cannot be configured from file. It can be passed to the `ZConfig` factory. I'm not sure of the clearest way to test that here; this is ugly: >>> from ZODB.config import getDbSchema >>> import ZConfig >>> from cStringIO import StringIO Derive a new `config2` string from the `config` string, specifying a different database_name: >>> config2 = config.replace("this_is_the_name", "another_name") Now get a `ZConfig` factory from `config2`: >>> f = StringIO(config2) >>> zconfig, handle = ZConfig.loadConfigFile(getDbSchema(), f) >>> factory = zconfig.database The desired ``databases`` mapping can be passed to this factory: >>> db2 = factory[0].open(databases=db.databases) >>> print db2.database_name # has the right name another_name >>> db.databases is db2.databases # shares .databases with `db` True >>> all = db2.databases.keys() >>> all.sort() >>> all # and db.database_name & db2.database_name are the keys ['another_name', 'this_is_the_name'] Cleanup. >>> db.close() >>> db2.close() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/test_doctest_files.py0000644000175000017500000000335612214017464023217 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import unittest __test__ = dict( cross_db_refs_to_blank_db_name = """ There was a bug that caused bad refs to be generated is a database name was blank. >>> import ZODB.tests.util, persistent.mapping, transaction >>> dbs = {} >>> db1 = ZODB.tests.util.DB(database_name='', databases=dbs) >>> db2 = ZODB.tests.util.DB(database_name='2', databases=dbs) >>> conn1 = db1.open() >>> conn2 = conn1.get_connection('2') >>> for i in range(10): ... conn1.root()[i] = persistent.mapping.PersistentMapping() ... transaction.commit() >>> conn2.root()[0] = conn1.root()[9] >>> transaction.commit() >>> conn2.root()._p_deactivate() >>> conn2.root()[0] is conn1.root()[9] True >>> list(conn2.root()[0].keys()) [] """, ) def test_suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocFileSuite("dbopen.txt", "multidb.txt", "synchronizers.txt", )) suite.addTest(doctest.DocTestSuite()) return suite zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testMappingStorage.py0000644000175000017500000000442112214017464023143 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import ZODB.MappingStorage import unittest import ZODB.tests.hexstorage from ZODB.tests import ( BasicStorage, HistoryStorage, IteratorStorage, MTStorage, PackableStorage, RevisionStorage, StorageTestBase, Synchronization, ) class MappingStorageTests( StorageTestBase.StorageTestBase, BasicStorage.BasicStorage, HistoryStorage.HistoryStorage, IteratorStorage.ExtendedIteratorStorage, IteratorStorage.IteratorStorage, MTStorage.MTStorage, PackableStorage.PackableStorageWithOptionalGC, RevisionStorage.RevisionStorage, Synchronization.SynchronizedStorage, ): def setUp(self): StorageTestBase.StorageTestBase.setUp(self, ) self._storage = ZODB.MappingStorage.MappingStorage() def checkOversizeNote(self): # This base class test checks for the common case where a storage # doesnt support huge transaction metadata. This storage doesnt # have this limit, so we inhibit this test here. pass def checkLoadBeforeUndo(self): pass # we don't support undo yet checkUndoZombie = checkLoadBeforeUndo class MappingStorageHexTests(MappingStorageTests): def setUp(self): StorageTestBase.StorageTestBase.setUp(self, ) self._storage = ZODB.tests.hexstorage.HexStorage( ZODB.MappingStorage.MappingStorage()) def test_suite(): suite = unittest.makeSuite(MappingStorageTests, 'check') suite = unittest.makeSuite(MappingStorageHexTests, 'check') return suite if __name__ == "__main__": loader = unittest.TestLoader() loader.testMethodPrefix = "check" unittest.main(testLoader=loader) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testConnectionSavepoint.py0000644000175000017500000001351212214017464024214 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import persistent.mapping import transaction import unittest import ZODB.tests.util def testAddingThenModifyThenAbort(): """\ We ran into a problem in which abort failed after adding an object in a savepoint and then modifying the object. The problem was that, on commit, the savepoint was aborted before the modifications were aborted. Because the object was added in the savepoint, its _p_oid and _p_jar were cleared when the savepoint was aborted. The object was in the registered-object list. There's an invariant for this list that states that all objects in the list should have an oid and (correct) jar. The fix was to abort work done after the savepoint before aborting the savepoint. >>> import ZODB.tests.util >>> db = ZODB.tests.util.DB() >>> connection = db.open() >>> root = connection.root() >>> ob = persistent.mapping.PersistentMapping() >>> root['ob'] = ob >>> sp = transaction.savepoint() >>> ob.x = 1 >>> transaction.abort() """ def testModifyThenSavePointThenModifySomeMoreThenCommit(): """\ We got conflict errors when we committed after we modified an object in a savepoint, and then modified it some more after the last savepoint. The problem was that we were effectively commiting the object twice -- when commiting the current data and when committing the savepoint. The fix was to first make a new savepoint to move new changes to the savepoint storage and *then* to commit the savepoint storage. >>> import ZODB.tests.util >>> db = ZODB.tests.util.DB() >>> connection = db.open() >>> root = connection.root() >>> sp = transaction.savepoint() >>> root['a'] = 1 >>> sp = transaction.savepoint() >>> root['a'] = 2 >>> transaction.commit() """ def testCantCloseConnectionWithActiveSavepoint(): """ >>> import ZODB.tests.util >>> db = ZODB.tests.util.DB() >>> connection = db.open() >>> root = connection.root() >>> root['a'] = 1 >>> sp = transaction.savepoint() >>> connection.close() Traceback (most recent call last): ... ConnectionStateError: Cannot close a connection joined to a transaction >>> db.close() """ def testSavepointDoesCacheGC(): """\ Although the interface doesn't guarantee this internal detail, making a savepoint should do incremental gc on connection memory caches. Indeed, one traditional use for savepoints is simply to free memory space midstream during a long transaction. Before ZODB 3.4.2, making a savepoint failed to trigger cache gc, and this test verifies that it now does. >>> import ZODB >>> from ZODB.tests.MinPO import MinPO >>> from ZODB.MappingStorage import MappingStorage >>> import transaction >>> CACHESIZE = 5 # something tiny >>> LOOPCOUNT = CACHESIZE * 10 >>> st = MappingStorage("Test") >>> db = ZODB.DB(st, cache_size=CACHESIZE) >>> cn = db.open() >>> rt = cn.root() Now attach substantially more than CACHESIZE persistent objects to the root: >>> for i in range(LOOPCOUNT): ... rt[i] = MinPO(i) >>> transaction.commit() Now modify all of them; the cache should contain LOOPCOUNT MinPO objects then, + 1 for the root object: >>> for i in range(LOOPCOUNT): ... obj = rt[i] ... obj.value = -i >>> len(cn._cache) == LOOPCOUNT + 1 True Making a savepoint at this time used to leave the cache holding the same number of objects. Make sure the cache shrinks now instead. >>> dummy = transaction.savepoint() >>> len(cn._cache) <= CACHESIZE + 1 True Verify all the values are as expected: >>> failures = [] >>> for i in range(LOOPCOUNT): ... obj = rt[i] ... if obj.value != -i: ... failures.append(obj) >>> failures [] >>> transaction.abort() >>> db.close() """ def testIsReadonly(): """\ The connection isReadonly method relies on the _storage to have an isReadOnly. We simply rely on the underlying storage method. >>> import ZODB.tests.util >>> db = ZODB.tests.util.DB() >>> connection = db.open() >>> root = connection.root() >>> root['a'] = 1 >>> sp = transaction.savepoint() >>> connection.isReadOnly() False """ class SelfActivatingObject(persistent.Persistent): def _p_invalidate(self): super(SelfActivatingObject, self)._p_invalidate() self._p_activate() def testInvalidateAfterRollback(): """\ The rollback used to invalidate objects before resetting the TmpStore. This caused problems for custom _p_invalidate methods that would load the wrong state. >>> import ZODB.tests.util >>> db = ZODB.tests.util.DB() >>> connection = db.open() >>> root = connection.root() >>> root['p'] = p = SelfActivatingObject() >>> transaction.commit() >>> p.foo = 1 >>> sp = transaction.savepoint() >>> p.foo = 2 >>> sp2 = transaction.savepoint() >>> sp.rollback() >>> p.foo # This used to wrongly return 2 1 """ def tearDown(test): transaction.abort() def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('testConnectionSavepoint.txt', tearDown=tearDown), doctest.DocTestSuite(tearDown=tearDown), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testBroken.py0000644000175000017500000000510312214017464021441 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test broken-object suppport """ import sys import unittest import persistent import transaction import os if os.environ.get('USE_ZOPE_TESTING_DOCTEST'): from zope.testing.doctest import DocTestSuite else: from doctest import DocTestSuite from ZODB.tests.util import DB def test_integration(): r"""Test the integration of broken object support with the databse: >>> db = DB() We'll create a fake module with a class: >>> class NotThere: ... Atall = type('Atall', (persistent.Persistent, ), ... {'__module__': 'ZODB.not.there'}) And stuff this into sys.modules to simulate a regular module: >>> sys.modules['ZODB.not.there'] = NotThere >>> sys.modules['ZODB.not'] = NotThere Now, we'll create and save an instance, and make sure we can load it in another connection: >>> a = NotThere.Atall() >>> a.x = 1 >>> conn1 = db.open() >>> conn1.root()['a'] = a >>> transaction.commit() >>> conn2 = db.open() >>> a2 = conn2.root()['a'] >>> a2.__class__ is a.__class__ True >>> a2.x 1 Now, we'll uninstall the module, simulating having the module go away: >>> del sys.modules['ZODB.not.there'] and we'll try to load the object in another connection: >>> conn3 = db.open() >>> a3 = conn3.root()['a'] >>> a3 # doctest: +NORMALIZE_WHITESPACE >>> a3.__Broken_state__ {'x': 1} Broken objects provide an interface: >>> from ZODB.interfaces import IBroken >>> IBroken.providedBy(a3) True Let's clean up: >>> db.close() >>> del sys.modules['ZODB.not'] Cleanup: >>> import ZODB.broken >>> ZODB.broken.broken_cache.clear() """ def test_suite(): return unittest.TestSuite(( DocTestSuite('ZODB.broken'), DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/blob_packing.txt0000644000175000017500000000727212214017464022133 0ustar arnauarnauPacking support for blob data ============================= Set up: >>> from ZODB.serialize import referencesf >>> from ZODB.blob import Blob >>> from ZODB import utils >>> from ZODB.DB import DB >>> import transaction A helper method to assure a unique timestamp across multiple platforms: >>> from ZODB.tests.testblob import new_time UNDOING ======= We need a database with an undoing blob supporting storage: >>> blob_storage = create_storage() >>> database = DB(blob_storage) Create our root object: >>> connection1 = database.open() >>> root = connection1.root() Put some revisions of a blob object in our database and on the filesystem: >>> import os >>> tids = [] >>> times = [] >>> nothing = transaction.begin() >>> times.append(new_time()) >>> blob = Blob() >>> blob.open('w').write('this is blob data 0') >>> root['blob'] = blob >>> transaction.commit() >>> tids.append(blob._p_serial) >>> nothing = transaction.begin() >>> times.append(new_time()) >>> root['blob'].open('w').write('this is blob data 1') >>> transaction.commit() >>> tids.append(blob._p_serial) >>> nothing = transaction.begin() >>> times.append(new_time()) >>> root['blob'].open('w').write('this is blob data 2') >>> transaction.commit() >>> tids.append(blob._p_serial) >>> nothing = transaction.begin() >>> times.append(new_time()) >>> root['blob'].open('w').write('this is blob data 3') >>> transaction.commit() >>> tids.append(blob._p_serial) >>> nothing = transaction.begin() >>> times.append(new_time()) >>> root['blob'].open('w').write('this is blob data 4') >>> transaction.commit() >>> tids.append(blob._p_serial) >>> oid = root['blob']._p_oid >>> fns = [ blob_storage.fshelper.getBlobFilename(oid, x) for x in tids ] >>> [ os.path.exists(x) for x in fns ] [True, True, True, True, True] Do a pack to the slightly before the first revision was written: >>> packtime = times[0] >>> blob_storage.pack(packtime, referencesf) >>> [ os.path.exists(x) for x in fns ] [True, True, True, True, True] Do a pack to the slightly before the second revision was written: >>> packtime = times[1] >>> blob_storage.pack(packtime, referencesf) >>> [ os.path.exists(x) for x in fns ] [True, True, True, True, True] Do a pack to the slightly before the third revision was written: >>> packtime = times[2] >>> blob_storage.pack(packtime, referencesf) >>> [ os.path.exists(x) for x in fns ] [False, True, True, True, True] Do a pack to the slightly before the fourth revision was written: >>> packtime = times[3] >>> blob_storage.pack(packtime, referencesf) >>> [ os.path.exists(x) for x in fns ] [False, False, True, True, True] Do a pack to the slightly before the fifth revision was written: >>> packtime = times[4] >>> blob_storage.pack(packtime, referencesf) >>> [ os.path.exists(x) for x in fns ] [False, False, False, True, True] Do a pack to now: >>> packtime = new_time() >>> blob_storage.pack(packtime, referencesf) >>> [ os.path.exists(x) for x in fns ] [False, False, False, False, True] Delete the object and do a pack, it should get rid of the most current revision as well as the entire directory: >>> nothing = transaction.begin() >>> del root['blob'] >>> transaction.commit() >>> packtime = new_time() >>> blob_storage.pack(packtime, referencesf) >>> [ os.path.exists(x) for x in fns ] [False, False, False, False, False] >>> os.path.exists(os.path.split(fns[0])[0]) False Clean up our blob directory and database: >>> blob_storage.close() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testblob.py0000644000175000017500000005164412214017464021152 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from pickle import Pickler from pickle import Unpickler from StringIO import StringIO from ZODB.blob import Blob from ZODB.DB import DB from ZODB.FileStorage import FileStorage from ZODB.tests.testConfig import ConfigTestBase import os if os.environ.get('USE_ZOPE_TESTING_DOCTEST'): from zope.testing import doctest else: import doctest import os import random import re import struct import sys import time import transaction import unittest import ZConfig import ZODB.blob import ZODB.interfaces import ZODB.tests.IteratorStorage import ZODB.tests.StorageTestBase import ZODB.tests.util import zope.testing.renormalizing def new_time(): """Create a _new_ time stamp. This method also makes sure that after retrieving a timestamp that was *before* a transaction was committed, that at least one second passes so the packing time actually is before the commit time. """ now = new_time = time.time() while new_time <= now: new_time = time.time() time.sleep(1) return new_time class ZODBBlobConfigTest(ConfigTestBase): def test_map_config1(self): self._test( """ blob-dir blobs """) def test_file_config1(self): self._test( """ blob-dir blobs path Data.fs """) def test_blob_dir_needed(self): self.assertRaises(ZConfig.ConfigurationSyntaxError, self._test, """ """) class BlobCloneTests(ZODB.tests.util.TestCase): def testDeepCopyCanInvalidate(self): """ Tests regression for invalidation problems related to missing readers and writers values in cloned objects (see http://mail.zope.org/pipermail/zodb-dev/2008-August/012054.html) """ import ZODB.MappingStorage database = DB(ZODB.blob.BlobStorage( 'blobs', ZODB.MappingStorage.MappingStorage())) connection = database.open() root = connection.root() transaction.begin() root['blob'] = Blob() transaction.commit() stream = StringIO() p = Pickler(stream, 1) p.dump(root['blob']) u = Unpickler(stream) stream.seek(0) clone = u.load() clone._p_invalidate() # it should also be possible to open the cloned blob # (even though it won't contain the original data) clone.open() # tearDown database.close() class BlobTestBase(ZODB.tests.StorageTestBase.StorageTestBase): def setUp(self): ZODB.tests.StorageTestBase.StorageTestBase.setUp(self) self._storage = self.create_storage() class BlobUndoTests(BlobTestBase): def testUndoWithoutPreviousVersion(self): database = DB(self._storage) connection = database.open() root = connection.root() transaction.begin() root['blob'] = Blob() transaction.commit() database.undo(database.undoLog(0, 1)[0]['id']) transaction.commit() # the blob footprint object should exist no longer self.assertRaises(KeyError, root.__getitem__, 'blob') database.close() def testUndo(self): database = DB(self._storage) connection = database.open() root = connection.root() transaction.begin() blob = Blob() blob.open('w').write('this is state 1') root['blob'] = blob transaction.commit() transaction.begin() blob = root['blob'] blob.open('w').write('this is state 2') transaction.commit() database.undo(database.undoLog(0, 1)[0]['id']) transaction.commit() self.assertEqual(blob.open('r').read(), 'this is state 1') database.close() def testUndoAfterConsumption(self): database = DB(self._storage) connection = database.open() root = connection.root() transaction.begin() open('consume1', 'w').write('this is state 1') blob = Blob() blob.consumeFile('consume1') root['blob'] = blob transaction.commit() transaction.begin() blob = root['blob'] open('consume2', 'w').write('this is state 2') blob.consumeFile('consume2') transaction.commit() database.undo(database.undoLog(0, 1)[0]['id']) transaction.commit() self.assertEqual(blob.open('r').read(), 'this is state 1') database.close() def testRedo(self): database = DB(self._storage) connection = database.open() root = connection.root() blob = Blob() transaction.begin() blob.open('w').write('this is state 1') root['blob'] = blob transaction.commit() transaction.begin() blob = root['blob'] blob.open('w').write('this is state 2') transaction.commit() database.undo(database.undoLog(0, 1)[0]['id']) transaction.commit() self.assertEqual(blob.open('r').read(), 'this is state 1') database.undo(database.undoLog(0, 1)[0]['id']) transaction.commit() self.assertEqual(blob.open('r').read(), 'this is state 2') database.close() def testRedoOfCreation(self): database = DB(self._storage) connection = database.open() root = connection.root() blob = Blob() transaction.begin() blob.open('w').write('this is state 1') root['blob'] = blob transaction.commit() database.undo(database.undoLog(0, 1)[0]['id']) transaction.commit() self.assertRaises(KeyError, root.__getitem__, 'blob') database.undo(database.undoLog(0, 1)[0]['id']) transaction.commit() self.assertEqual(blob.open('r').read(), 'this is state 1') database.close() class RecoveryBlobStorage(BlobTestBase, ZODB.tests.IteratorStorage.IteratorDeepCompare): def setUp(self): BlobTestBase.setUp(self) self._dst = self.create_storage('dest') def tearDown(self): self._dst.close() BlobTestBase.tearDown(self) # Requires a setUp() that creates a self._dst destination storage def testSimpleBlobRecovery(self): self.assert_( ZODB.interfaces.IBlobStorageRestoreable.providedBy(self._storage) ) db = DB(self._storage) conn = db.open() conn.root()[1] = ZODB.blob.Blob() transaction.commit() conn.root()[2] = ZODB.blob.Blob() conn.root()[2].open('w').write('some data') transaction.commit() conn.root()[3] = ZODB.blob.Blob() conn.root()[3].open('w').write( (''.join(struct.pack(">I", random.randint(0, (1<<32)-1)) for i in range(random.randint(10000,20000))) )[:-random.randint(1,4)] ) transaction.commit() conn.root()[2] = ZODB.blob.Blob() conn.root()[2].open('w').write('some other data') transaction.commit() self._dst.copyTransactionsFrom(self._storage) self.compare(self._storage, self._dst) def gc_blob_removes_uncommitted_data(): """ >>> blob = Blob() >>> blob.open('w').write('x') >>> fname = blob._p_blob_uncommitted >>> os.path.exists(fname) True >>> blob = None >>> os.path.exists(fname) False """ def commit_from_wrong_partition(): """ It should be possible to commit changes even when a blob is on a different partition. We can simulare this by temporarily breaking os.rename. :) >>> def fail(*args): ... raise OSError >>> os_rename = os.rename >>> os.rename = fail >>> import logging >>> logger = logging.getLogger('ZODB.blob.copied') >>> handler = logging.StreamHandler(sys.stdout) >>> logger.propagate = False >>> logger.setLevel(logging.DEBUG) >>> logger.addHandler(handler) >>> blob_storage = create_storage() >>> database = DB(blob_storage) >>> connection = database.open() >>> root = connection.root() >>> from ZODB.blob import Blob >>> root['blob'] = Blob() >>> root['blob'].open('w').write('test') >>> transaction.commit() # doctest: +ELLIPSIS Copied blob file ... >>> root['blob'].open().read() 'test' Works with savepoints too: >>> root['blob2'] = Blob() >>> root['blob2'].open('w').write('test2') >>> _ = transaction.savepoint() # doctest: +ELLIPSIS Copied blob file ... >>> transaction.commit() # doctest: +ELLIPSIS Copied blob file ... >>> root['blob2'].open().read() 'test2' >>> os.rename = os_rename >>> logger.propagate = True >>> logger.setLevel(0) >>> logger.removeHandler(handler) >>> handler.close() >>> database.close() """ def packing_with_uncommitted_data_non_undoing(): """ This covers regression for bug #130459. When uncommitted data exists it formerly was written to the root of the blob_directory and confused our packing strategy. We now use a separate temporary directory that is ignored while packing. >>> import transaction >>> from ZODB.DB import DB >>> from ZODB.serialize import referencesf >>> blob_storage = create_storage() >>> database = DB(blob_storage) >>> connection = database.open() >>> root = connection.root() >>> from ZODB.blob import Blob >>> root['blob'] = Blob() >>> connection.add(root['blob']) >>> root['blob'].open('w').write('test') >>> blob_storage.pack(new_time(), referencesf) Clean up: >>> database.close() """ def packing_with_uncommitted_data_undoing(): """ This covers regression for bug #130459. When uncommitted data exists it formerly was written to the root of the blob_directory and confused our packing strategy. We now use a separate temporary directory that is ignored while packing. >>> from ZODB.serialize import referencesf >>> blob_storage = create_storage() >>> database = DB(blob_storage) >>> connection = database.open() >>> root = connection.root() >>> from ZODB.blob import Blob >>> root['blob'] = Blob() >>> connection.add(root['blob']) >>> root['blob'].open('w').write('test') >>> blob_storage.pack(new_time(), referencesf) Clean up: >>> database.close() """ def secure_blob_directory(): """ This is a test for secure creation and verification of secure settings of blob directories. >>> blob_storage = create_storage(blob_dir='blobs') Two directories are created: >>> os.path.isdir('blobs') True >>> tmp_dir = os.path.join('blobs', 'tmp') >>> os.path.isdir(tmp_dir) True They are only accessible by the owner: >>> oct(os.stat('blobs').st_mode) '040700' >>> oct(os.stat(tmp_dir).st_mode) '040700' These settings are recognized as secure: >>> blob_storage.fshelper.isSecure('blobs') True >>> blob_storage.fshelper.isSecure(tmp_dir) True After making the permissions of tmp_dir more liberal, the directory is recognized as insecure: >>> os.chmod(tmp_dir, 040711) >>> blob_storage.fshelper.isSecure(tmp_dir) False Clean up: >>> blob_storage.close() """ # On windows, we can't create secure blob directories, at least not # with APIs in the standard library, so there's no point in testing # this. if sys.platform == 'win32': del secure_blob_directory def loadblob_tmpstore(): """ This is a test for assuring that the TmpStore's loadBlob implementation falls back correctly to loadBlob on the backend. First, let's setup a regular database and store a blob: >>> blob_storage = create_storage() >>> database = DB(blob_storage) >>> connection = database.open() >>> root = connection.root() >>> from ZODB.blob import Blob >>> root['blob'] = Blob() >>> connection.add(root['blob']) >>> root['blob'].open('w').write('test') >>> import transaction >>> transaction.commit() >>> blob_oid = root['blob']._p_oid >>> tid = connection._storage.lastTransaction() Now we open a database with a TmpStore in front: >>> database.close() >>> from ZODB.Connection import TmpStore >>> tmpstore = TmpStore(blob_storage) We can access the blob correctly: >>> tmpstore.loadBlob(blob_oid, tid) == blob_storage.loadBlob(blob_oid, tid) True Clean up: >>> tmpstore.close() >>> database.close() """ def is_blob_record(): r""" >>> bs = create_storage() >>> db = DB(bs) >>> conn = db.open() >>> conn.root()['blob'] = ZODB.blob.Blob() >>> transaction.commit() >>> ZODB.blob.is_blob_record(bs.load(ZODB.utils.p64(0), '')[0]) False >>> ZODB.blob.is_blob_record(bs.load(ZODB.utils.p64(1), '')[0]) True An invalid pickle yields a false value: >>> ZODB.blob.is_blob_record("Hello world!") False >>> ZODB.blob.is_blob_record('c__main__\nC\nq\x01.') False >>> ZODB.blob.is_blob_record('cWaaaa\nC\nq\x01.') False As does None, which may occur in delete records: >>> ZODB.blob.is_blob_record(None) False >>> db.close() """ def do_not_depend_on_cwd(): """ >>> bs = create_storage() >>> here = os.getcwd() >>> os.mkdir('evil') >>> os.chdir('evil') >>> db = DB(bs) >>> conn = db.open() >>> conn.root()['blob'] = ZODB.blob.Blob() >>> conn.root()['blob'].open('w').write('data') >>> transaction.commit() >>> os.chdir(here) >>> conn.root()['blob'].open().read() 'data' >>> bs.close() """ def savepoint_isolation(): """Make sure savepoint data is distinct accross transactions >>> bs = create_storage() >>> db = DB(bs) >>> conn = db.open() >>> conn.root.b = ZODB.blob.Blob('initial') >>> transaction.commit() >>> conn.root.b.open('w').write('1') >>> _ = transaction.savepoint() >>> tm = transaction.TransactionManager() >>> conn2 = db.open(transaction_manager=tm) >>> conn2.root.b.open('w').write('2') >>> _ = tm.savepoint() >>> conn.root.b.open().read() '1' >>> conn2.root.b.open().read() '2' >>> transaction.abort() >>> tm.commit() >>> conn.sync() >>> conn.root.b.open().read() '2' >>> db.close() """ def savepoint_commits_without_invalidations_out_of_order(): """Make sure transactions with blobs can be commited without the invalidations out of order error (LP #509801) >>> bs = create_storage() >>> db = DB(bs) >>> tm1 = transaction.TransactionManager() >>> conn1 = db.open(transaction_manager=tm1) >>> conn1.root.b = ZODB.blob.Blob('initial') >>> tm1.commit() >>> conn1.root.b.open('w').write('1') >>> _ = tm1.savepoint() >>> tm2 = transaction.TransactionManager() >>> conn2 = db.open(transaction_manager=tm2) >>> conn2.root.b.open('w').write('2') >>> _ = tm1.savepoint() >>> conn1.root.b.open().read() '1' >>> conn2.root.b.open().read() '2' >>> tm2.commit() >>> tm1.commit() # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ConflictError: database conflict error... >>> tm1.abort() >>> db.close() """ def savepoint_cleanup(): """Make sure savepoint data gets cleaned up. >>> bs = create_storage() >>> tdir = bs.temporaryDirectory() >>> os.listdir(tdir) [] >>> db = DB(bs) >>> conn = db.open() >>> conn.root.b = ZODB.blob.Blob('initial') >>> _ = transaction.savepoint() >>> len(os.listdir(tdir)) 1 >>> transaction.abort() >>> os.listdir(tdir) [] >>> conn.root.b = ZODB.blob.Blob('initial') >>> transaction.commit() >>> conn.root.b.open('w').write('1') >>> _ = transaction.savepoint() >>> transaction.abort() >>> os.listdir(tdir) [] >>> db.close() """ def lp440234_Setting__p_changed_of_a_Blob_w_no_uncomitted_changes_is_noop(): r""" >>> conn = ZODB.connection('data.fs', blob_dir='blobs') >>> blob = ZODB.blob.Blob('blah') >>> conn.add(blob) >>> transaction.commit() >>> old_serial = blob._p_serial >>> blob._p_changed = True >>> transaction.commit() >>> blob.open().read() 'blah' >>> old_serial == blob._p_serial True >>> conn.close() """ def setUp(test): ZODB.tests.util.setUp(test) test.globs['rmtree'] = zope.testing.setupstack.rmtree def setUpBlobAdaptedFileStorage(test): setUp(test) def create_storage(name='data', blob_dir=None): if blob_dir is None: blob_dir = '%s.bobs' % name return ZODB.blob.BlobStorage(blob_dir, FileStorage('%s.fs' % name)) test.globs['create_storage'] = create_storage def storage_reusable_suite(prefix, factory, test_blob_storage_recovery=False, test_packing=False, test_undo=True, ): """Return a test suite for a generic IBlobStorage. Pass a factory taking a name and a blob directory name. """ def setup(test): setUp(test) def create_storage(name='data', blob_dir=None): if blob_dir is None: blob_dir = '%s.bobs' % name return factory(name, blob_dir) test.globs['create_storage'] = create_storage suite = unittest.TestSuite() suite.addTest(doctest.DocFileSuite( "blob_connection.txt", "blob_importexport.txt", "blob_transaction.txt", setUp=setup, tearDown=zope.testing.setupstack.tearDown, optionflags=doctest.ELLIPSIS, )) if test_packing: suite.addTest(doctest.DocFileSuite( "blob_packing.txt", setUp=setup, tearDown=zope.testing.setupstack.tearDown, )) suite.addTest(doctest.DocTestSuite( setUp=setup, tearDown=zope.testing.setupstack.tearDown, checker = zope.testing.renormalizing.RENormalizing([ (re.compile(r'\%(sep)s\%(sep)s' % dict(sep=os.path.sep)), '/'), (re.compile(r'\%(sep)s' % dict(sep=os.path.sep)), '/'), ]), )) def create_storage(self, name='data', blob_dir=None): if blob_dir is None: blob_dir = '%s.bobs' % name return factory(name, blob_dir) def add_test_based_on_test_class(class_): new_class = class_.__class__( prefix+class_.__name__, (class_, ), dict(create_storage=create_storage), ) suite.addTest(unittest.makeSuite(new_class)) if test_blob_storage_recovery: add_test_based_on_test_class(RecoveryBlobStorage) if test_undo: add_test_based_on_test_class(BlobUndoTests) suite.layer = ZODB.tests.util.MininalTestLayer(prefix+'BlobTests') return suite def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(ZODBBlobConfigTest)) suite.addTest(unittest.makeSuite(BlobCloneTests)) suite.addTest(doctest.DocFileSuite( "blob_basic.txt", "blob_consume.txt", "blob_tempdir.txt", "blobstorage_packing.txt", setUp=setUp, tearDown=zope.testing.setupstack.tearDown, optionflags=doctest.ELLIPSIS, )) suite.addTest(doctest.DocFileSuite( "blob_layout.txt", optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE, setUp=setUp, tearDown=zope.testing.setupstack.tearDown, checker = zope.testing.renormalizing.RENormalizing([ (re.compile(r'\%(sep)s\%(sep)s' % dict(sep=os.path.sep)), '/'), (re.compile(r'\%(sep)s' % dict(sep=os.path.sep)), '/'), (re.compile(r'\S+/((old|bushy|lawn)/\S+/foo[23456]?)'), r'\1'), ]), )) suite.addTest(storage_reusable_suite( 'BlobAdaptedFileStorage', lambda name, blob_dir: ZODB.blob.BlobStorage(blob_dir, FileStorage('%s.fs' % name)), test_blob_storage_recovery=True, test_packing=True, )) return suite if __name__ == '__main__': unittest.main(defaultTest = 'test_suite') zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/blob_basic.txt0000644000175000017500000001115312214017464021571 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## ZODB Blob support ================= You create a blob like this:: >>> from ZODB.blob import Blob >>> myblob = Blob() A blob implements the IBlob interface:: >>> from ZODB.interfaces import IBlob >>> IBlob.providedBy(myblob) True We can open a new blob file for reading, but it won't have any data:: >>> myblob.open("r").read() '' But we can write data to a new Blob by opening it for writing:: >>> f = myblob.open("w") >>> f.write("Hi, Blob!") If we try to open a Blob again while it is open for writing, we get an error:: >>> myblob.open("r") Traceback (most recent call last): ... BlobError: Already opened for writing. We can close the file:: >>> f.close() Now we can open it for reading:: >>> f2 = myblob.open("r") And we get the data back:: >>> f2.read() 'Hi, Blob!' If we want to, we can open it again:: >>> f3 = myblob.open("r") >>> f3.read() 'Hi, Blob!' But we can't open it for writing, while it is opened for reading:: >>> myblob.open("a") Traceback (most recent call last): ... BlobError: Already opened for reading. Before we can write, we have to close the readers:: >>> f2.close() >>> f3.close() Now we can open it for writing again and e.g. append data:: >>> f4 = myblob.open("a") >>> f4.write("\nBlob is fine.") We can't open a blob while it is open for writing: >>> myblob.open("w") Traceback (most recent call last): ... BlobError: Already opened for writing. >>> myblob.open("r") Traceback (most recent call last): ... BlobError: Already opened for writing. >>> f4.close() Now we can read it:: >>> f4a = myblob.open("r") >>> f4a.read() 'Hi, Blob!\nBlob is fine.' >>> f4a.close() You shouldn't need to explicitly close a blob unless you hold a reference to it via a name. If the first line in the following test kept a reference around via a name, the second call to open it in a writable mode would fail with a BlobError, but it doesn't:: >>> myblob.open("r+").read() 'Hi, Blob!\nBlob is fine.' >>> f4b = myblob.open("a") >>> f4b.close() We can read lines out of the blob too:: >>> f5 = myblob.open("r") >>> f5.readline() 'Hi, Blob!\n' >>> f5.readline() 'Blob is fine.' >>> f5.close() We can seek to certain positions in a blob and read portions of it:: >>> f6 = myblob.open('r') >>> f6.seek(4) >>> int(f6.tell()) 4 >>> f6.read(5) 'Blob!' >>> f6.close() We can use the object returned by a blob open call as an iterable:: >>> f7 = myblob.open('r') >>> for line in f7: ... print line Hi, Blob! Blob is fine. >>> f7.close() We can truncate a blob:: >>> f8 = myblob.open('a') >>> f8.truncate(0) >>> f8.close() >>> f8 = myblob.open('r') >>> f8.read() '' >>> f8.close() Blobs are always opened in binary mode:: >>> f9 = myblob.open("r") >>> f9.mode 'rb' >>> f9.close() Blobs that have not been committed can be opened using any mode, except for "c":: >>> from ZODB.blob import BlobError, valid_modes >>> for mode in valid_modes: ... try: ... f10 = Blob().open(mode) ... except BlobError: ... print 'open failed with mode "%s"' % mode ... else: ... f10.close() open failed with mode "c" Some cleanup in this test is needed:: >>> import transaction >>> transaction.get().abort() Subclassing Blobs ----------------- Blobs are not subclassable:: >>> class SubBlob(Blob): ... pass >>> my_sub_blob = SubBlob() Traceback (most recent call last): ... TypeError: Blobs do not support subclassing. Passing data to the blob constructor ------------------------------------ If you have a small amount of data, you can pass it to the blob constructor. (This is a convenience, mostly for writing tests.) >>> myblob = Blob('some data') >>> myblob.open().read() 'some data' zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/blob_layout.txt0000644000175000017500000002365512214017464022037 0ustar arnauarnau====================== Blob directory layouts ====================== The internal structure of the blob directories is governed by so called `layouts`. The current default layout is called `bushy`. The original blob implementation used a layout that we now call `lawn` and which is still available for backwards compatibility. Layouts implement two methods: one for computing a relative path for an OID and one for turning a relative path back into an OID. Our terminology is roughly the same as used in `DirectoryStorage`. The `bushy` layout ================== The bushy layout splits the OID into the 8 byte parts, reverses them and creates one directory level for each part, named by the hexlified representation of the byte value. This results in 8 levels of directories, the leaf directories being used for the revisions of the blobs and at most 256 entries per directory level: >>> from ZODB.blob import BushyLayout >>> bushy = BushyLayout() >>> bushy.oid_to_path('\x00\x00\x00\x00\x00\x00\x00\x00') '0x00/0x00/0x00/0x00/0x00/0x00/0x00/0x00' >>> bushy.oid_to_path('\x00\x00\x00\x00\x00\x00\x00\x01') '0x00/0x00/0x00/0x00/0x00/0x00/0x00/0x01' >>> import os >>> bushy.path_to_oid(os.path.join( ... '0x01', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00')) '\x01\x00\x00\x00\x00\x00\x00\x00' >>> bushy.path_to_oid(os.path.join( ... '0xff', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00')) '\xff\x00\x00\x00\x00\x00\x00\x00' Paths that do not represent an OID will cause a ValueError: >>> bushy.path_to_oid('tmp') Traceback (most recent call last): ValueError: Not a valid OID path: `tmp` The `lawn` layout ================= The lawn layout creates on directory for each blob named by the blob's hex representation of its OID. This has some limitations on various file systems like performance penalties or the inability to store more than a given number of blobs at the same time (e.g. 32k on ext3). >>> from ZODB.blob import LawnLayout >>> lawn = LawnLayout() >>> lawn.oid_to_path('\x00\x00\x00\x00\x00\x00\x00\x00') '0x00' >>> lawn.oid_to_path('\x00\x00\x00\x00\x00\x00\x00\x01') '0x01' >>> lawn.path_to_oid('0x01') '\x00\x00\x00\x00\x00\x00\x00\x01' Paths that do not represent an OID will cause a ValueError: >>> lawn.path_to_oid('tmp') Traceback (most recent call last): ValueError: Not a valid OID path: `tmp` >>> lawn.path_to_oid('') Traceback (most recent call last): ValueError: Not a valid OID path: `` Auto-detecting the layout of a directory ======================================== To allow easier migration, we provide an auto-detection feature that analyses a blob directory and decides for a strategy to use. In general it prefers to choose the `bushy` layout, except if it determines that the directory has already been used to create a lawn structure. >>> from ZODB.blob import auto_layout_select 1. Non-existing directories will trigger a bushy layout: >>> import os, shutil >>> auto_layout_select('blobs') 'bushy' 2. Empty directories will trigger a bushy layout too: >>> os.mkdir('blobs') >>> auto_layout_select('blobs') 'bushy' 3. If the directory contains a marker for the strategy it will be used: >>> from ZODB.blob import LAYOUT_MARKER >>> import os.path >>> open(os.path.join('blobs', LAYOUT_MARKER), 'wb').write('bushy') >>> auto_layout_select('blobs') 'bushy' >>> open(os.path.join('blobs', LAYOUT_MARKER), 'wb').write('lawn') >>> auto_layout_select('blobs') 'lawn' >>> shutil.rmtree('blobs') 4. If the directory does not contain a marker but other files that are not hidden, we assume that it was created with an earlier version of the blob implementation and uses our `lawn` layout: >>> os.mkdir('blobs') >>> open(os.path.join('blobs', '0x0101'), 'wb').write('foo') >>> auto_layout_select('blobs') 'lawn' >>> shutil.rmtree('blobs') 5. If the directory contains only hidden files, use the bushy layout: >>> os.mkdir('blobs') >>> open(os.path.join('blobs', '.svn'), 'wb').write('blah') >>> auto_layout_select('blobs') 'bushy' >>> shutil.rmtree('blobs') Directory layout markers ======================== When the file system helper (FSH) is asked to create the directory structure, it will leave a marker with the choosen layout if no marker exists yet: >>> from ZODB.blob import FilesystemHelper >>> blobs = 'blobs' >>> fsh = FilesystemHelper(blobs) >>> fsh.layout_name 'bushy' >>> fsh.create() >>> open(os.path.join(blobs, LAYOUT_MARKER), 'rb').read() 'bushy' If the FSH finds a marker, then it verifies whether its content matches the strategy that was chosen. It will raise an exception if we try to work with a directory that has a different marker than the chosen strategy: >>> fsh = FilesystemHelper(blobs, 'lawn') >>> fsh.layout_name 'lawn' >>> fsh.create() # doctest: +ELLIPSIS Traceback (most recent call last): ValueError: Directory layout `lawn` selected for blob directory .../blobs/, but marker found for layout `bushy` >>> rmtree(blobs) This function interacts with the automatic detection in the way, that an unmarked directory will be marked the first time when it is auto-guessed and the marker will be used in the future: >>> import ZODB.FileStorage >>> from ZODB.blob import BlobStorage >>> datafs = 'data.fs' >>> base_storage = ZODB.FileStorage.FileStorage(datafs) >>> os.mkdir(blobs) >>> open(os.path.join(blobs, 'foo'), 'wb').write('foo') >>> blob_storage = BlobStorage(blobs, base_storage) >>> blob_storage.fshelper.layout_name 'lawn' >>> open(os.path.join(blobs, LAYOUT_MARKER), 'rb').read() 'lawn' >>> blob_storage = BlobStorage('blobs', base_storage, layout='bushy') ... # doctest: +ELLIPSIS Traceback (most recent call last): ValueError: Directory layout `bushy` selected for blob directory .../blobs/, but marker found for layout `lawn` >>> base_storage.close() >>> rmtree('blobs') Migrating between directory layouts =================================== A script called `migrateblobs.py` is distributed with the ZODB for offline migration capabilities between different directory layouts. It can migrate any blob directory layout to any other layout. It leaves the original blob directory untouched (except from eventually creating a temporary directory and the storage layout marker). The migration is accessible as a library function: >>> from ZODB.scripts.migrateblobs import migrate Create a `lawn` directory structure and migrate it to the new `bushy` one: >>> from ZODB.blob import FilesystemHelper >>> d = 'd' >>> os.mkdir(d) >>> old = os.path.join(d, 'old') >>> old_fsh = FilesystemHelper(old, 'lawn') >>> old_fsh.create() >>> blob1 = old_fsh.getPathForOID(7039, create=True) >>> blob2 = old_fsh.getPathForOID(10, create=True) >>> blob3 = old_fsh.getPathForOID(7034, create=True) >>> open(os.path.join(blob1, 'foo'), 'wb').write('foo') >>> open(os.path.join(blob1, 'foo2'), 'wb').write('bar') >>> open(os.path.join(blob2, 'foo3'), 'wb').write('baz') >>> open(os.path.join(blob2, 'foo4'), 'wb').write('qux') >>> open(os.path.join(blob3, 'foo5'), 'wb').write('quux') >>> open(os.path.join(blob3, 'foo6'), 'wb').write('corge') Committed blobs have their permissions set to 000 The migration function is called with the old and the new path and the layout that shall be used for the new directory: >>> bushy = os.path.join(d, 'bushy') >>> migrate(old, bushy, 'bushy') # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE Migrating blob data from `.../old` (lawn) to `.../bushy` (bushy) OID: 0x0a - 2 files OID: 0x1b7a - 2 files OID: 0x1b7f - 2 files The new directory now contains the same files in different directories, but with the same sizes and permissions: >>> lawn_files = {} >>> for base, dirs, files in os.walk(old): ... for file_name in files: ... lawn_files[file_name] = os.path.join(base, file_name) >>> bushy_files = {} >>> for base, dirs, files in os.walk(bushy): ... for file_name in files: ... bushy_files[file_name] = os.path.join(base, file_name) >>> len(lawn_files) == len(bushy_files) True >>> for file_name, lawn_path in sorted(lawn_files.items()): ... if file_name == '.layout': ... continue ... lawn_stat = os.stat(lawn_path) ... bushy_path = bushy_files[file_name] ... bushy_stat = os.stat(bushy_path) ... print lawn_path, '-->', bushy_path ... if ((lawn_stat.st_mode, lawn_stat.st_size) != ... (bushy_stat.st_mode, bushy_stat.st_size)): ... print 'oops' old/0x1b7f/foo --> bushy/0x00/0x00/0x00/0x00/0x00/0x00/0x1b/0x7f/foo old/0x1b7f/foo2 --> bushy/0x00/0x00/0x00/0x00/0x00/0x00/0x1b/0x7f/foo2 old/0x0a/foo3 --> bushy/0x00/0x00/0x00/0x00/0x00/0x00/0x00/0x0a/foo3 old/0x0a/foo4 --> bushy/0x00/0x00/0x00/0x00/0x00/0x00/0x00/0x0a/foo4 old/0x1b7a/foo5 --> bushy/0x00/0x00/0x00/0x00/0x00/0x00/0x1b/0x7a/foo5 old/0x1b7a/foo6 --> bushy/0x00/0x00/0x00/0x00/0x00/0x00/0x1b/0x7a/foo6 We can also migrate the bushy layout back to the lawn layout: >>> lawn = os.path.join(d, 'lawn') >>> migrate(bushy, lawn, 'lawn') Migrating blob data from `.../bushy` (bushy) to `.../lawn` (lawn) OID: 0x0a - 2 files OID: 0x1b7a - 2 files OID: 0x1b7f - 2 files >>> lawn_files = {} >>> for base, dirs, files in os.walk(lawn): ... for file_name in files: ... lawn_files[file_name] = os.path.join(base, file_name) >>> len(lawn_files) == len(bushy_files) True >>> for file_name, lawn_path in sorted(lawn_files.items()): ... if file_name == '.layout': ... continue ... lawn_stat = os.stat(lawn_path) ... bushy_path = bushy_files[file_name] ... bushy_stat = os.stat(bushy_path) ... print bushy_path, '-->', lawn_path ... if ((lawn_stat.st_mode, lawn_stat.st_size) != ... (bushy_stat.st_mode, bushy_stat.st_size)): ... print 'oops' bushy/0x00/0x00/0x00/0x00/0x00/0x00/0x1b/0x7f/foo --> lawn/0x1b7f/foo bushy/0x00/0x00/0x00/0x00/0x00/0x00/0x1b/0x7f/foo2 --> lawn/0x1b7f/foo2 bushy/0x00/0x00/0x00/0x00/0x00/0x00/0x00/0x0a/foo3 --> lawn/0x0a/foo3 bushy/0x00/0x00/0x00/0x00/0x00/0x00/0x00/0x0a/foo4 --> lawn/0x0a/foo4 bushy/0x00/0x00/0x00/0x00/0x00/0x00/0x1b/0x7a/foo5 --> lawn/0x1b7a/foo5 bushy/0x00/0x00/0x00/0x00/0x00/0x00/0x1b/0x7a/foo6 --> lawn/0x1b7a/foo6 >>> rmtree(d) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/MTStorage.py0000644000175000017500000001621112214017464021170 0ustar arnauarnauimport random import sys import threading import time from persistent.mapping import PersistentMapping import transaction import ZODB from ZODB.tests.StorageTestBase import zodb_pickle, zodb_unpickle from ZODB.tests.StorageTestBase import handle_serials from ZODB.tests.MinPO import MinPO from ZODB.POSException import ConflictError SHORT_DELAY = 0.01 def sort(l): "Sort a list in place and return it." l.sort() return l class TestThread(threading.Thread): """Base class for defining threads that run from unittest. If the thread exits with an uncaught exception, catch it and re-raise it when the thread is joined. The re-raise will cause the test to fail. The subclass should define a runtest() method instead of a run() method. """ def __init__(self): threading.Thread.__init__(self) self._exc_info = None def run(self): try: self.runtest() except: self._exc_info = sys.exc_info() def join(self, timeout=None): threading.Thread.join(self, timeout) if self._exc_info: raise self._exc_info[0], self._exc_info[1], self._exc_info[2] class ZODBClientThread(TestThread): __super_init = TestThread.__init__ def __init__(self, db, test, commits=10, delay=SHORT_DELAY): self.__super_init() self.setDaemon(1) self.db = db self.test = test self.commits = commits self.delay = delay def runtest(self): conn = self.db.open() conn.sync() root = conn.root() d = self.get_thread_dict(root) if d is None: self.test.fail() else: for i in range(self.commits): self.commit(d, i) self.test.assertEqual(sort(d.keys()), range(self.commits)) def commit(self, d, num): d[num] = time.time() time.sleep(self.delay) transaction.commit() time.sleep(self.delay) # Return a new PersistentMapping, and store it on the root object under # the name (.getName()) of the current thread. def get_thread_dict(self, root): # This is vicious: multiple threads are slamming changes into the # root object, then trying to read the root object, simultaneously # and without any coordination. Conflict errors are rampant. It # used to go around at most 10 times, but that fairly often failed # to make progress in the 7-thread tests on some test boxes. Going # around (at most) 1000 times was enough so that a 100-thread test # reliably passed on Tim's hyperthreaded WinXP box (but at the # original 10 retries, the same test reliably failed with 15 threads). name = self.getName() MAXRETRIES = 1000 for i in range(MAXRETRIES): try: root[name] = PersistentMapping() transaction.commit() break except ConflictError: root._p_jar.sync() else: raise ConflictError("Exceeded %d attempts to store" % MAXRETRIES) for j in range(MAXRETRIES): try: return root.get(name) except ConflictError: root._p_jar.sync() raise ConflictError("Exceeded %d attempts to read" % MAXRETRIES) class StorageClientThread(TestThread): __super_init = TestThread.__init__ def __init__(self, storage, test, commits=10, delay=SHORT_DELAY): self.__super_init() self.storage = storage self.test = test self.commits = commits self.delay = delay self.oids = {} def runtest(self): for i in range(self.commits): self.dostore(i) self.check() def check(self): for oid, revid in self.oids.items(): data, serial = self.storage.load(oid, '') self.test.assertEqual(serial, revid) obj = zodb_unpickle(data) self.test.assertEqual(obj.value[0], self.getName()) def pause(self): time.sleep(self.delay) def oid(self): oid = self.storage.new_oid() self.oids[oid] = None return oid def dostore(self, i): data = zodb_pickle(MinPO((self.getName(), i))) t = transaction.Transaction() oid = self.oid() self.pause() self.storage.tpc_begin(t) self.pause() # Always create a new object, signified by None for revid r1 = self.storage.store(oid, None, data, '', t) self.pause() r2 = self.storage.tpc_vote(t) self.pause() self.storage.tpc_finish(t) self.pause() revid = handle_serials(oid, r1, r2) self.oids[oid] = revid class ExtStorageClientThread(StorageClientThread): def runtest(self): # pick some other storage ops to execute, depending in part # on the features provided by the storage. names = ["do_load"] storage = self.storage try: supportsUndo = storage.supportsUndo except AttributeError: pass else: if supportsUndo(): names += ["do_loadSerial", "do_undoLog", "do_iterator"] ops = [getattr(self, meth) for meth in names] assert ops, "Didn't find an storage ops in %s" % self.storage # do a store to guarantee there's at least one oid in self.oids self.dostore(0) for i in range(self.commits - 1): meth = random.choice(ops) meth() self.dostore(i) self.check() def pick_oid(self): return random.choice(self.oids.keys()) def do_load(self): oid = self.pick_oid() self.storage.load(oid, '') def do_loadSerial(self): oid = self.pick_oid() self.storage.loadSerial(oid, self.oids[oid]) def do_undoLog(self): self.storage.undoLog(0, -20) def do_iterator(self): try: iter = self.storage.iterator() except AttributeError: # It's hard to detect that a ZEO ClientStorage # doesn't have this method, but does have all the others. return for obj in iter: pass class MTStorage: "Test a storage with multiple client threads executing concurrently." def _checkNThreads(self, n, constructor, *args): threads = [constructor(*args) for i in range(n)] for t in threads: t.start() for t in threads: t.join(60) for t in threads: self.failIf(t.isAlive(), "thread failed to finish in 60 seconds") def check2ZODBThreads(self): db = ZODB.DB(self._storage) self._checkNThreads(2, ZODBClientThread, db, self) db.close() def check7ZODBThreads(self): db = ZODB.DB(self._storage) self._checkNThreads(7, ZODBClientThread, db, self) db.close() def check2StorageThreads(self): self._checkNThreads(2, StorageClientThread, self._storage, self) def check7StorageThreads(self): self._checkNThreads(7, StorageClientThread, self._storage, self) def check4ExtStorageThread(self): self._checkNThreads(4, ExtStorageClientThread, self._storage, self) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/IExternalGC.test0000644000175000017500000000701412214017464021760 0ustar arnauarnauStorage Support for external GC =============================== A storage that provides IExternalGC supports external garbage collectors by providing a deleteObject method that transactionally deletes an object. A create_storage function is provided that creates a storage. >>> storage = create_storage() >>> import ZODB.blob, transaction >>> db = ZODB.DB(storage) >>> conn = db.open() >>> conn.root()[0] = conn.root().__class__() >>> conn.root()[1] = ZODB.blob.Blob('some data') >>> transaction.commit() >>> oid0 = conn.root()[0]._p_oid >>> oid1 = conn.root()[1]._p_oid >>> del conn.root()[0] >>> del conn.root()[1] >>> transaction.commit() At this point, object 0 and 1 is garbage, but it's still in the storage: >>> p0, s0 = storage.load(oid0, '') >>> p1, s1 = storage.load(oid1, '') The storage is configured not to gc on pack, so even if we pack, these objects won't go away: >>> len(storage) 3 >>> import time >>> db.pack(time.time()+1) >>> len(storage) 3 >>> p0, s0 = storage.load(oid0, '') >>> p1, s1 = storage.load(oid1, '') Now we'll use the new deleteObject API to delete the objects. We can't go through the database to do this, so we'll have to manage the transaction ourselves. >>> txn = transaction.begin() >>> storage.tpc_begin(txn) >>> storage.deleteObject(oid0, s0, txn) >>> storage.deleteObject(oid1, s1, txn) >>> storage.tpc_vote(txn) >>> storage.tpc_finish(txn) >>> tid = storage.lastTransaction() Now if we try to load data for the objects, we get a POSKeyError: >>> storage.load(oid0, '') # doctest: +ELLIPSIS Traceback (most recent call last): ... POSKeyError: ... >>> storage.load(oid1, '') # doctest: +ELLIPSIS Traceback (most recent call last): ... POSKeyError: ... We can still get the data if we load before the time we deleted. >>> storage.loadBefore(oid0, conn.root()._p_serial) == (p0, s0, tid) True >>> storage.loadBefore(oid1, conn.root()._p_serial) == (p1, s1, tid) True >>> open(storage.loadBlob(oid1, s1)).read() 'some data' If we pack, however, the old data will be removed and the data will be gone: >>> db.pack(time.time()+1) >>> len(db.storage) 1 >>> time.sleep(.1) >>> storage.load(oid0, '') # doctest: +ELLIPSIS Traceback (most recent call last): ... POSKeyError: ... >>> storage.load(oid1, '') # doctest: +ELLIPSIS Traceback (most recent call last): ... POSKeyError: ... >>> storage.loadBefore(oid0, conn.root()._p_serial) # doctest: +ELLIPSIS Traceback (most recent call last): ... POSKeyError: ... >>> storage.loadBefore(oid1, conn.root()._p_serial) # doctest: +ELLIPSIS Traceback (most recent call last): ... POSKeyError: ... >>> storage.loadBlob(oid1, s1) # doctest: +ELLIPSIS Traceback (most recent call last): ... POSKeyError: ... A conflict error is raised if the serial we provide to deleteObject isn't current: >>> conn.root()[0] = conn.root().__class__() >>> transaction.commit() >>> oid = conn.root()[0]._p_oid >>> bad_serial = conn.root()[0]._p_serial >>> conn.root()[0].x = 1 >>> transaction.commit() >>> txn = transaction.begin() >>> storage.tpc_begin(txn) >>> storage.deleteObject(oid, bad_serial, txn); storage.tpc_vote(txn) ... # doctest: +ELLIPSIS Traceback (most recent call last): ... ConflictError: database conflict error ... >>> storage.tpc_abort(txn) >>> storage.close() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testmvcc.py0000644000175000017500000002716512214017464021165 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## r""" Multi-version concurrency control tests ======================================= Multi-version concurrency control (MVCC) exploits storages that store multiple revisions of an object to avoid read conflicts. Normally when an object is read from the storage, its most recent revision is read. Under MVCC, an older revision may be read so that the transaction sees a consistent view of the database. ZODB guarantees execution-time consistency: A single transaction will always see a consistent view of the database while it is executing. If transaction A is running, has already read an object O1, and a different transaction B modifies object O2, then transaction A can no longer read the current revision of O2. It must either read the version of O2 that is consistent with O1 or raise a ReadConflictError. When MVCC is in use, A will do the former. This note includes doctests that explain how MVCC is implemented (and test that the implementation is correct). The tests use a MinimalMemoryStorage that implements MVCC support, but not much else. >>> from ZODB.tests.test_storage import MinimalMemoryStorage >>> from ZODB import DB >>> db = DB(MinimalMemoryStorage()) We will use two different connections with different transaction managers to make sure that the connections act independently, even though they'll be run from a single thread. >>> import transaction >>> tm1 = transaction.TransactionManager() >>> cn1 = db.open(transaction_manager=tm1) The test will just use some MinPO objects. The next few lines just setup an initial database state. >>> from ZODB.tests.MinPO import MinPO >>> r = cn1.root() >>> r["a"] = MinPO(1) >>> r["b"] = MinPO(1) >>> tm1.get().commit() Now open a second connection. >>> tm2 = transaction.TransactionManager() >>> cn2 = db.open(transaction_manager=tm2) Connection high-water mark -------------------------- The ZODB Connection tracks a transaction high-water mark, which bounds the latest transaction id that can be read by the current transaction and still present a consistent view of the database. Transactions with ids up to but not including the high-water mark are OK to read. When a transaction commits, the database sends invalidations to all the other connections; the invalidation contains the transaction id and the oids of modified objects. The Connection stores the high-water mark in _txn_time, which is set to None until an invalidation arrives. >>> cn = db.open() >>> print cn._txn_time None >>> cn.invalidate(100, dict.fromkeys([1, 2])) >>> cn._txn_time 100 >>> cn.invalidate(200, dict.fromkeys([1, 2])) >>> cn._txn_time 100 A connection's high-water mark is set to the transaction id taken from the first invalidation processed by the connection. Transaction ids are monotonically increasing, so the first one seen during the current transaction remains the high-water mark for the duration of the transaction. We'd like simple abort and commit calls to make txn boundaries, but that doesn't work unless an object is modified. sync() will abort a transaction and process invalidations. >>> cn.sync() >>> print cn._txn_time # the high-water mark got reset to None None Basic functionality ------------------- The next bit of code includes a simple MVCC test. One transaction will modify "a." The other transaction will then modify "b" and commit. >>> r1 = cn1.root() >>> r1["a"].value = 2 >>> tm1.get().commit() >>> txn = db.lastTransaction() The second connection has its high-water mark set now. >>> cn2._txn_time == txn True It is safe to read "b," because it was not modified by the concurrent transaction. >>> r2 = cn2.root() >>> r2["b"]._p_serial < cn2._txn_time True >>> r2["b"].value 1 >>> r2["b"].value = 2 It is not safe, however, to read the current revision of "a" because it was modified at the high-water mark. If we read it, we'll get a non-current version. >>> r2["a"].value 1 >>> r2["a"]._p_serial < cn2._txn_time True We can confirm that we have a non-current revision by asking the storage. >>> db.storage.isCurrent(r2["a"]._p_oid, r2["a"]._p_serial) False It's possible to modify "a", but we get a conflict error when we commit the transaction. >>> r2["a"].value = 3 >>> tm2.get().commit() Traceback (most recent call last): ... ConflictError: database conflict error (oid 0x01, class ZODB.tests.MinPO.MinPO) >>> tm2.get().abort() This example will demonstrate that we can commit a transaction if we only modify current revisions. >>> print cn2._txn_time None >>> r1 = cn1.root() >>> r1["a"].value = 3 >>> tm1.get().commit() >>> txn = db.lastTransaction() >>> cn2._txn_time == txn True >>> r2["b"].value = r2["a"].value + 1 >>> r2["b"].value 3 >>> tm2.get().commit() >>> print cn2._txn_time None Object cache ------------ A Connection keeps objects in its cache so that multiple database references will always point to the same Python object. At transaction boundaries, objects modified by other transactions are ghostified so that the next transaction doesn't see stale state. We need to be sure the non-current objects loaded by MVCC are always ghosted. It should be trivial, because MVCC is only used when an invalidation has been received for an object. First get the database back in an initial state. >>> cn1.sync() >>> r1["a"].value = 0 >>> r1["b"].value = 0 >>> tm1.get().commit() >>> cn2.sync() >>> r2["a"].value 0 >>> r2["b"].value = 1 >>> tm2.get().commit() >>> r1["b"].value 0 >>> cn1.sync() # cn2 modified 'b', so cn1 should get a ghost for b >>> r1["b"]._p_state # -1 means GHOST -1 Closing the connection, committing a transaction, and aborting a transaction, should all have the same effect on non-current objects in cache. >>> def testit(): ... cn1.sync() ... r1["a"].value = 0 ... r1["b"].value = 0 ... tm1.get().commit() ... cn2.sync() ... r2["b"].value = 1 ... tm2.get().commit() >>> testit() >>> r1["b"]._p_state # 0 means UPTODATE, although note it's an older revision 0 >>> r1["b"].value 0 >>> r1["a"].value = 1 >>> tm1.get().commit() >>> r1["b"]._p_state -1 When a connection is closed, it is saved by the database. It will be reused by the next open() call (along with its object cache). >>> testit() >>> r1["a"].value = 1 >>> tm1.get().abort() >>> cn1.close() >>> cn3 = db.open() >>> cn1 is cn3 True >>> r1 = cn1.root() Although "b" is a ghost in cn1 at this point (because closing a connection has the same effect on non-current objects in the connection's cache as committing a transaction), not every object is a ghost. The root was in the cache and was current, so our first reference to it doesn't return a ghost. >>> r1._p_state # UPTODATE 0 >>> r1["b"]._p_state # GHOST -1 Interaction with Savepoints --------------------------- Basically, making a savepoint shouldn't have any effect on what a thread sees. Before ZODB 3.4.1, the internal TmpStore used when savepoints are pending didn't delegate all the methods necessary to make this work, so we'll do a quick test of that here. First get a clean slate: >>> cn1.close(); cn2.close() >>> cn1 = db.open(transaction_manager=tm1) >>> r1 = cn1.root() >>> r1["a"].value = 0 >>> r1["b"].value = 1 >>> tm1.commit() Now modify "a", but not "b", and make a savepoint. >>> r1["a"].value = 42 >>> sp = cn1.savepoint() Over in the other connection, modify "b" and commit it. This makes the first connection's state for b "old". >>> cn2 = db.open(transaction_manager=tm2) >>> r2 = cn2.root() >>> r2["a"].value, r2["b"].value # shouldn't see the change to "a" (0, 1) >>> r2["b"].value = 43 >>> tm2.commit() >>> r2["a"].value, r2["b"].value (0, 43) Now deactivate "b" in the first connection, and (re)fetch it. The first connection should still see 1, due to MVCC, but to get this old state TmpStore needs to handle the loadBefore() method. >>> r1["b"]._p_deactivate() Before 3.4.1, the next line died with AttributeError: TmpStore instance has no attribute 'loadBefore' >>> r1["b"]._p_state # ghost -1 >>> r1["b"].value 1 Just for fun, finish the commit and make sure both connections see the same things now. >>> tm1.commit() >>> cn1.sync(); cn2.sync() >>> r1["a"].value, r1["b"].value (42, 43) >>> r2["a"].value, r2["b"].value (42, 43) Late invalidation ----------------- The combination of ZEO and MVCC adds more complexity. Since invalidations are delivered asynchronously by ZEO, it is possible for an invalidation to arrive just after a request to load the invalidated object is sent. The connection can't use the just-loaded data, because the invalidation arrived first. The complexity for MVCC is that it must check for invalidated objects after it has loaded them, just in case. Rather than add all the complexity of ZEO to these tests, the MinimalMemoryStorage has a hook. We'll write a subclass that will deliver an invalidation when it loads an object. The hook allows us to test the Connection code. >>> class TestStorage(MinimalMemoryStorage): ... def __init__(self): ... self.hooked = {} ... self.count = 0 ... super(TestStorage, self).__init__() ... def registerDB(self, db): ... self.db = db ... def hook(self, oid, tid, version): ... if oid in self.hooked: ... self.db.invalidate(tid, {oid:1}) ... self.count += 1 We can execute this test with a single connection, because we're synthesizing the invalidation that is normally generated by the second connection. We need to create two revisions so that there is a non-current revision to load. >>> ts = TestStorage() >>> db = DB(ts) >>> cn1 = db.open(transaction_manager=tm1) >>> r1 = cn1.root() >>> r1["a"] = MinPO(0) >>> r1["b"] = MinPO(0) >>> tm1.get().commit() >>> r1["b"].value = 1 >>> tm1.get().commit() >>> cn1.cacheMinimize() # makes everything in cache a ghost >>> oid = r1["b"]._p_oid >>> ts.hooked[oid] = 1 Once the oid is hooked, an invalidation will be delivered the next time it is activated. The code below activates the object, then confirms that the hook worked and that the old state was retrieved. >>> oid in cn1._invalidated False >>> r1["b"]._p_state -1 >>> r1["b"]._p_activate() >>> oid in cn1._invalidated True >>> ts.count 1 >>> r1["b"].value 0 No earlier revision available ----------------------------- We'll reuse the code from the example above, except that there will only be a single revision of "b." As a result, the attempt to activate "b" will result in a ReadConflictError. >>> ts = TestStorage() >>> db = DB(ts) >>> cn1 = db.open(transaction_manager=tm1) >>> r1 = cn1.root() >>> r1["a"] = MinPO(0) >>> r1["b"] = MinPO(0) >>> tm1.get().commit() >>> cn1.cacheMinimize() # makes everything in cache a ghost >>> oid = r1["b"]._p_oid >>> ts.hooked[oid] = 1 Again, once the oid is hooked, an invalidation will be delivered the next time it is activated. The code below activates the object, but unlike the section above, this is no older state to retrieve. >>> oid in cn1._invalidated False >>> r1["b"]._p_state -1 >>> r1["b"]._p_activate() Traceback (most recent call last): ... ReadConflictError: database read conflict error (oid 0x02, class ZODB.tests.MinPO.MinPO) >>> oid in cn1._invalidated True >>> ts.count 1 """ import doctest def test_suite(): return doctest.DocTestSuite() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/test_storage.py0000644000175000017500000001141512214017464022027 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """A storage used for unittests. The primary purpose of this module is to have a minimal multi-version storage to use for unit tests. MappingStorage isn't sufficient. Since even a minimal storage has some complexity, we run standard storage tests against the test storage. """ from __future__ import with_statement import bisect import unittest from ZODB.BaseStorage import BaseStorage from ZODB import POSException from ZODB.utils import z64 from ZODB.tests import StorageTestBase from ZODB.tests import BasicStorage, MTStorage, Synchronization from ZODB.tests import RevisionStorage class Transaction(object): """Hold data for current transaction for MinimalMemoryStorage.""" def __init__(self, tid): self.index = {} self.tid = tid def store(self, oid, data): self.index[(oid, self.tid)] = data def cur(self): return dict.fromkeys([oid for oid, tid in self.index.keys()], self.tid) class MinimalMemoryStorage(BaseStorage, object): """Simple in-memory storage that supports revisions. This storage is needed to test multi-version concurrency control. It is similar to MappingStorage, but keeps multiple revisions. It does not support versions. It doesn't implement operations like pack(), because they aren't necessary for testing. """ def __init__(self): super(MinimalMemoryStorage, self).__init__("name") # _index maps oid, tid pairs to data records self._index = {} # _cur maps oid to current tid self._cur = {} self._ltid = z64 def isCurrent(self, oid, serial): return serial == self._cur[oid] def hook(self, oid, tid, version): # A hook for testing pass def __len__(self): return len(self._index) def _clear_temp(self): pass def load(self, oid, version=''): assert version == '' with self._lock: assert not version tid = self._cur[oid] self.hook(oid, tid, '') return self._index[(oid, tid)], tid def _begin(self, tid, u, d, e): self._txn = Transaction(tid) def store(self, oid, serial, data, v, txn): if txn is not self._transaction: raise POSException.StorageTransactionError(self, txn) assert not v if self._cur.get(oid) != serial: if not (serial is None or self._cur.get(oid) in [None, z64]): raise POSException.ConflictError( oid=oid, serials=(self._cur.get(oid), serial), data=data) self._txn.store(oid, data) return self._tid def _abort(self): del self._txn def _finish(self, tid, u, d, e): with self._lock: self._index.update(self._txn.index) self._cur.update(self._txn.cur()) self._ltid = self._tid def loadBefore(self, the_oid, the_tid): # It's okay if loadBefore() is really expensive, because this # storage is just used for testing. with self._lock: tids = [tid for oid, tid in self._index if oid == the_oid] if not tids: raise KeyError(the_oid) tids.sort() i = bisect.bisect_left(tids, the_tid) - 1 if i == -1: return None tid = tids[i] j = i + 1 if j == len(tids): end_tid = None else: end_tid = tids[j] return self._index[(the_oid, tid)], tid, end_tid def loadSerial(self, oid, serial): return self._index[(oid, serial)] def close(self): pass cleanup = close class MinimalTestSuite(StorageTestBase.StorageTestBase, BasicStorage.BasicStorage, MTStorage.MTStorage, Synchronization.SynchronizedStorage, RevisionStorage.RevisionStorage, ): def setUp(self): StorageTestBase.StorageTestBase.setUp(self) self._storage = MinimalMemoryStorage() # we don't implement undo def checkLoadBeforeUndo(self): pass def test_suite(): return unittest.makeSuite(MinimalTestSuite, "check") zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/test_cache.py0000644000175000017500000001556612214017464021441 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test behavior of Connection plus cPickleCache.""" from persistent import Persistent from ZODB.config import databaseFromString import doctest import transaction class RecalcitrantObject(Persistent): """A Persistent object that will not become a ghost.""" deactivations = 0 def _p_deactivate(self): self.__class__.deactivations += 1 def init(cls): cls.deactivations = 0 init = classmethod(init) class RegularObject(Persistent): deactivations = 0 invalidations = 0 def _p_deactivate(self): self.__class__.deactivations += 1 super(RegularObject, self)._p_deactivate() def _p_invalidate(self): self.__class__.invalidations += 1 super(RegularObject, self)._p_invalidate() def init(cls): cls.deactivations = 0 cls.invalidations = 0 init = classmethod(init) class PersistentObject(Persistent): pass class CacheTests: def test_cache(self): r"""Test basic cache methods. Let's start with a clean transaction >>> transaction.abort() >>> RegularObject.init() >>> db = databaseFromString("\n" ... "cache-size 4\n" ... "\n" ... "") >>> cn = db.open() >>> r = cn.root() >>> L = [] >>> for i in range(5): ... o = RegularObject() ... L.append(o) ... r[i] = o >>> transaction.commit() After committing a transaction and calling cacheGC(), there should be cache-size (4) objects in the cache. One of the RegularObjects was deactivated. >>> cn._cache.ringlen() 4 >>> RegularObject.deactivations 1 If we explicitly activate the objects again, the ringlen should go back up to 5. >>> for o in L: ... o._p_activate() >>> cn._cache.ringlen() 5 >>> cn.cacheGC() >>> cn._cache.ringlen() 4 >>> RegularObject.deactivations 2 >>> cn.cacheMinimize() >>> cn._cache.ringlen() 0 >>> RegularObject.deactivations 6 If we activate all the objects again and mark one as modified, then the one object should not be deactivated even by a minimize. >>> for o in L: ... o._p_activate() >>> o.attr = 1 >>> cn._cache.ringlen() 5 >>> cn.cacheMinimize() >>> cn._cache.ringlen() 1 >>> RegularObject.deactivations 10 Clean up >>> transaction.abort() """ def test_cache_gc_recalcitrant(self): r"""Test that a cacheGC() call will return. It's possible for a particular object to ignore the _p_deactivate() call. We want to check several things in this case. The cache should called the real _p_deactivate() method not the one provided by Persistent. The cacheGC() call should also return when it's looked at each item, regardless of whether it became a ghost. >>> RecalcitrantObject.init() >>> db = databaseFromString("\n" ... "cache-size 4\n" ... "\n" ... "") >>> cn = db.open() >>> r = cn.root() >>> L = [] >>> for i in range(5): ... o = RecalcitrantObject() ... L.append(o) ... r[i] = o >>> transaction.commit() >>> [o._p_state for o in L] [0, 0, 0, 0, 0] The Connection calls cacheGC() after it commits a transaction. Since the cache will now have more objects that it's target size, it will call _p_deactivate() on each RecalcitrantObject. >>> RecalcitrantObject.deactivations 5 >>> [o._p_state for o in L] [0, 0, 0, 0, 0] An explicit call to cacheGC() has the same effect. >>> cn.cacheGC() >>> RecalcitrantObject.deactivations 10 >>> [o._p_state for o in L] [0, 0, 0, 0, 0] """ def test_cache_on_abort(self): r"""Test that the cache handles transaction abort correctly. >>> RegularObject.init() >>> db = databaseFromString("\n" ... "cache-size 4\n" ... "\n" ... "") >>> cn = db.open() >>> r = cn.root() >>> L = [] >>> for i in range(5): ... o = RegularObject() ... L.append(o) ... r[i] = o >>> transaction.commit() >>> RegularObject.deactivations 1 Modify three of the objects and verify that they are deactivated when the transaction aborts. >>> for i in range(0, 5, 2): ... L[i].attr = i >>> [L[i]._p_state for i in range(0, 5, 2)] [1, 1, 1] >>> cn._cache.ringlen() 5 >>> transaction.abort() >>> cn._cache.ringlen() 2 >>> RegularObject.deactivations 4 """ def test_gc_on_open_connections(self): r"""Test that automatic GC is not applied to open connections. This test (and the corresponding fix) was introduced because of bug report 113923. We start with a persistent object and add a list attribute:: >>> db = databaseFromString("\n" ... "cache-size 0\n" ... "\n" ... "") >>> cn1 = db.open() >>> r = cn1.root() >>> r['ob'] = PersistentObject() >>> r['ob'].l = [] >>> transaction.commit() Now, let's modify the object in a way that doesn't get noticed. Then, we open another connection which triggers automatic garbage connection. After that, the object should not have been ghostified:: >>> r['ob'].l.append(1) >>> cn2 = db.open() >>> r['ob'].l [1] """ def test_suite(): return doctest.DocTestSuite() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/IteratorStorage.py0000644000175000017500000002271312214017464022445 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Run tests against the iterator() interface for storages. Any storage that supports the iterator() method should be able to pass all these tests. """ from ZODB.tests.MinPO import MinPO from ZODB.tests.StorageTestBase import zodb_pickle, zodb_unpickle from ZODB.utils import U64, p64 from transaction import Transaction import itertools import ZODB.blob class IteratorCompare: def iter_verify(self, txniter, revids, val0): eq = self.assertEqual oid = self._oid val = val0 for reciter, revid in itertools.izip(txniter, revids + [None]): eq(reciter.tid, revid) for rec in reciter: eq(rec.oid, oid) eq(rec.tid, revid) eq(zodb_unpickle(rec.data), MinPO(val)) val = val + 1 eq(val, val0 + len(revids)) class IteratorStorage(IteratorCompare): def checkSimpleIteration(self): # Store a bunch of revisions of a single object self._oid = oid = self._storage.new_oid() revid1 = self._dostore(oid, data=MinPO(11)) revid2 = self._dostore(oid, revid=revid1, data=MinPO(12)) revid3 = self._dostore(oid, revid=revid2, data=MinPO(13)) # Now iterate over all the transactions and compare carefully txniter = self._storage.iterator() self.iter_verify(txniter, [revid1, revid2, revid3], 11) def checkUndoZombie(self): oid = self._storage.new_oid() revid = self._dostore(oid, data=MinPO(94)) # Get the undo information info = self._storage.undoInfo() tid = info[0]['id'] # Undo the creation of the object, rendering it a zombie t = Transaction() self._storage.tpc_begin(t) oids = self._storage.undo(tid, t) self._storage.tpc_vote(t) self._storage.tpc_finish(t) # Now attempt to iterator over the storage iter = self._storage.iterator() for txn in iter: for rec in txn: pass # The last transaction performed an undo of the transaction that # created object oid. (As Barry points out, the object is now in the # George Bailey state.) Assert that the final data record contains # None in the data attribute. self.assertEqual(rec.oid, oid) self.assertEqual(rec.data, None) def checkTransactionExtensionFromIterator(self): oid = self._storage.new_oid() revid = self._dostore(oid, data=MinPO(1)) iter = self._storage.iterator() count = 0 for txn in iter: self.assertEqual(txn.extension, {}) count +=1 self.assertEqual(count, 1) def checkIterationIntraTransaction(self): # TODO: Try this test with logging enabled. If you see something # like # # ZODB FS FS21 warn: FileStorageTests.fs truncated, possibly due to # damaged records at 4 # # Then the code in FileIterator.next() hasn't yet been fixed. # Should automate that check. oid = self._storage.new_oid() t = Transaction() data = zodb_pickle(MinPO(0)) try: self._storage.tpc_begin(t) self._storage.store(oid, '\0'*8, data, '', t) self._storage.tpc_vote(t) # Don't do tpc_finish yet it = self._storage.iterator() for x in it: pass finally: self._storage.tpc_finish(t) def checkLoad_was_checkLoadEx(self): oid = self._storage.new_oid() self._dostore(oid, data=42) data, tid = self._storage.load(oid, "") self.assertEqual(zodb_unpickle(data), MinPO(42)) match = False for txn in self._storage.iterator(): for rec in txn: if rec.oid == oid and rec.tid == tid: self.assertEqual(txn.tid, tid) match = True if not match: self.fail("Could not find transaction with matching id") def checkIterateRepeatedly(self): self._dostore() transactions = self._storage.iterator() self.assertEquals(1, len(list(transactions))) # The iterator can only be consumed once: self.assertEquals(0, len(list(transactions))) def checkIterateRecordsRepeatedly(self): self._dostore() tinfo = self._storage.iterator().next() self.assertEquals(1, len(list(tinfo))) self.assertEquals(1, len(list(tinfo))) def checkIterateWhileWriting(self): self._dostore() iterator = self._storage.iterator() # We have one transaction with 1 modified object. txn_1 = iterator.next() self.assertEquals(1, len(list(txn_1))) # We store another transaction with 1 object, the already running # iterator does not pick this up. self._dostore() self.assertRaises(StopIteration, iterator.next) class ExtendedIteratorStorage(IteratorCompare): def checkExtendedIteration(self): # Store a bunch of revisions of a single object self._oid = oid = self._storage.new_oid() revid1 = self._dostore(oid, data=MinPO(11)) revid2 = self._dostore(oid, revid=revid1, data=MinPO(12)) revid3 = self._dostore(oid, revid=revid2, data=MinPO(13)) revid4 = self._dostore(oid, revid=revid3, data=MinPO(14)) # Note that the end points are included # Iterate over all of the transactions with explicit start/stop txniter = self._storage.iterator(revid1, revid4) self.iter_verify(txniter, [revid1, revid2, revid3, revid4], 11) # Iterate over some of the transactions with explicit start txniter = self._storage.iterator(revid3) self.iter_verify(txniter, [revid3, revid4], 13) # Iterate over some of the transactions with explicit stop txniter = self._storage.iterator(None, revid2) self.iter_verify(txniter, [revid1, revid2], 11) # Iterate over some of the transactions with explicit start+stop txniter = self._storage.iterator(revid2, revid3) self.iter_verify(txniter, [revid2, revid3], 12) # Specify an upper bound somewhere in between values revid3a = p64((U64(revid3) + U64(revid4)) / 2) txniter = self._storage.iterator(revid2, revid3a) self.iter_verify(txniter, [revid2, revid3], 12) # Specify a lower bound somewhere in between values. # revid2 == revid1+1 is very likely on Windows. Adding 1 before # dividing ensures that "the midpoint" we compute is strictly larger # than revid1. revid1a = p64((U64(revid1) + 1 + U64(revid2)) / 2) assert revid1 < revid1a txniter = self._storage.iterator(revid1a, revid3a) self.iter_verify(txniter, [revid2, revid3], 12) # Specify an empty range txniter = self._storage.iterator(revid3, revid2) self.iter_verify(txniter, [], 13) # Specify a singleton range txniter = self._storage.iterator(revid3, revid3) self.iter_verify(txniter, [revid3], 13) class IteratorDeepCompare: def compare(self, storage1, storage2): eq = self.assertEqual iter1 = storage1.iterator() iter2 = storage2.iterator() for txn1, txn2 in itertools.izip(iter1, iter2): eq(txn1.tid, txn2.tid) eq(txn1.status, txn2.status) eq(txn1.user, txn2.user) eq(txn1.description, txn2.description) eq(txn1.extension, txn2.extension) itxn1 = iter(txn1) itxn2 = iter(txn2) for rec1, rec2 in itertools.izip(itxn1, itxn2): eq(rec1.oid, rec2.oid) eq(rec1.tid, rec2.tid) eq(rec1.data, rec2.data) if ZODB.blob.is_blob_record(rec1.data): try: fn1 = storage1.loadBlob(rec1.oid, rec1.tid) except ZODB.POSException.POSKeyError: self.assertRaises( ZODB.POSException.POSKeyError, storage2.loadBlob, rec1.oid, rec1.tid) else: fn2 = storage2.loadBlob(rec1.oid, rec1.tid) self.assert_(fn1 != fn2) eq(open(fn1, 'rb').read(), open(fn2, 'rb').read()) # Make sure there are no more records left in rec1 and rec2, # meaning they were the same length. # Additionally, check that we're backwards compatible to the # IndexError we used to raise before. self.assertRaises(StopIteration, itxn1.next) self.assertRaises(StopIteration, itxn2.next) # Make sure ther are no more records left in txn1 and txn2, meaning # they were the same length self.assertRaises(StopIteration, iter1.next) self.assertRaises(StopIteration, iter2.next) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/hexstorage.py0000644000175000017500000001243112214017464021474 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import ZODB.blob import ZODB.interfaces import zope.interface class HexStorage(object): zope.interface.implements(ZODB.interfaces.IStorageWrapper) copied_methods = ( 'close', 'getName', 'getSize', 'history', 'isReadOnly', 'lastTransaction', 'new_oid', 'sortKey', 'tpc_abort', 'tpc_begin', 'tpc_finish', 'tpc_vote', 'loadBlob', 'openCommittedBlobFile', 'temporaryDirectory', 'supportsUndo', 'undo', 'undoLog', 'undoInfo', ) def __init__(self, base): self.base = base base.registerDB(self) for name in self.copied_methods: v = getattr(base, name, None) if v is not None: setattr(self, name, v) zope.interface.directlyProvides(self, zope.interface.providedBy(base)) def __getattr__(self, name): return getattr(self.base, name) def __len__(self): return len(self.base) def load(self, oid, version=''): data, serial = self.base.load(oid, version) return data[2:].decode('hex'), serial def loadBefore(self, oid, tid): r = self.base.loadBefore(oid, tid) if r is not None: data, serial, after = r return data[2:].decode('hex'), serial, after else: return r def loadSerial(self, oid, serial): return self.base.loadSerial(oid, serial)[2:].decode('hex') def pack(self, pack_time, referencesf, gc=True): def refs(p, oids=None): return referencesf(p[2:].decode('hex'), oids) return self.base.pack(pack_time, refs, gc) def registerDB(self, db): self.db = db self._db_transform = db.transform_record_data self._db_untransform = db.untransform_record_data _db_transform = _db_untransform = lambda self, data: data def store(self, oid, serial, data, version, transaction): return self.base.store( oid, serial, '.h'+data.encode('hex'), version, transaction) def restore(self, oid, serial, data, version, prev_txn, transaction): return self.base.restore( oid, serial, data and ('.h'+data.encode('hex')), version, prev_txn, transaction) def iterator(self, start=None, stop=None): for t in self.base.iterator(start, stop): yield Transaction(self, t) def storeBlob(self, oid, oldserial, data, blobfilename, version, transaction): return self.base.storeBlob(oid, oldserial, '.h'+data.encode('hex'), blobfilename, version, transaction) def restoreBlob(self, oid, serial, data, blobfilename, prev_txn, transaction): return self.base.restoreBlob(oid, serial, data and ('.h'+data.encode('hex')), blobfilename, prev_txn, transaction) def invalidateCache(self): return self.db.invalidateCache() def invalidate(self, transaction_id, oids, version=''): return self.db.invalidate(transaction_id, oids, version) def references(self, record, oids=None): return self.db.references(record[2:].decode('hex'), oids) def transform_record_data(self, data): return '.h'+self._db_transform(data).encode('hex') def untransform_record_data(self, data): return self._db_untransform(data[2:].decode('hex')) def record_iternext(self, next=None): oid, tid, data, next = self.base.record_iternext(next) return oid, tid, data[2:].decode('hex'), next def copyTransactionsFrom(self, other): ZODB.blob.copyTransactionsFromTo(other, self) class ServerHexStorage(HexStorage): """Use on ZEO storage server when Hex is used on client Don't do conversion as part of load/store, but provide pickle decoding. """ copied_methods = HexStorage.copied_methods + ( 'load', 'loadBefore', 'loadSerial', 'store', 'restore', 'iterator', 'storeBlob', 'restoreBlob', 'record_iternext', ) class Transaction(object): def __init__(self, store, trans): self.__store = store self.__trans = trans def __iter__(self): for r in self.__trans: if r.data: r.data = self.__store.untransform_record_data(r.data) yield r def __getattr__(self, name): return getattr(self.__trans, name) class ZConfigHex: _factory = HexStorage def __init__(self, config): self.config = config self.name = config.getSectionName() def open(self): base = self.config.base.open() return self._factory(base) class ZConfigServerHex(ZConfigHex): _factory = ServerHexStorage zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testActivityMonitor.py0000644000175000017500000000631412214017464023372 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the default activity monitor. See ZODB/ActivityMonitor.py $Id: testActivityMonitor.py 113734 2010-06-21 15:33:46Z ctheune $ """ import unittest import time from ZODB.ActivityMonitor import ActivityMonitor class FakeConnection: loads = 0 stores = 0 def _transferred(self, loads, stores): self.loads = self.loads + loads self.stores = self.stores + stores def getTransferCounts(self, clear=0): res = self.loads, self.stores if clear: self.loads = self.stores = 0 return res class Tests(unittest.TestCase): def testAddLogEntries(self): am = ActivityMonitor(history_length=3600) self.assertEqual(len(am.log), 0) c = FakeConnection() c._transferred(1, 2) am.closedConnection(c) c._transferred(3, 7) am.closedConnection(c) self.assertEqual(len(am.log), 2) def testTrim(self): am = ActivityMonitor(history_length=0.1) c = FakeConnection() c._transferred(1, 2) am.closedConnection(c) time.sleep(0.2) c._transferred(3, 7) am.closedConnection(c) self.assert_(len(am.log) <= 1) def testSetHistoryLength(self): am = ActivityMonitor(history_length=3600) c = FakeConnection() c._transferred(1, 2) am.closedConnection(c) time.sleep(0.2) c._transferred(3, 7) am.closedConnection(c) self.assertEqual(len(am.log), 2) am.setHistoryLength(0.1) self.assertEqual(am.getHistoryLength(), 0.1) self.assert_(len(am.log) <= 1) def testActivityAnalysis(self): am = ActivityMonitor(history_length=3600) c = FakeConnection() c._transferred(1, 2) am.closedConnection(c) c._transferred(3, 7) am.closedConnection(c) res = am.getActivityAnalysis(start=0, end=0, divisions=10) lastend = 0 for n in range(9): div = res[n] self.assertEqual(div['stores'], 0) self.assertEqual(div['loads'], 0) self.assert_(div['start'] > 0) self.assert_(div['start'] >= lastend) self.assert_(div['start'] < div['end']) lastend = div['end'] div = res[9] self.assertEqual(div['stores'], 9) self.assertEqual(div['loads'], 4) self.assert_(div['start'] > 0) self.assert_(div['start'] >= lastend) self.assert_(div['start'] < div['end']) def test_suite(): return unittest.makeSuite(Tests) if __name__=='__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/test_fsdump.py0000644000175000017500000000444512214017464021666 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## r""" fsdump test =========== Let's get a path to work with first. >>> path = 'Data.fs' More imports. >>> import ZODB >>> from ZODB.FileStorage import FileStorage >>> import transaction as txn >>> from BTrees.OOBTree import OOBTree >>> from ZODB.FileStorage.fsdump import fsdump # we're testing this Create an empty FileStorage. >>> st = FileStorage(path) For empty DB fsdump() output definitely empty: >>> fsdump(path) Create a root object and try again: >>> db = ZODB.DB(st) # yes, that creates a root object! >>> fsdump(path) #doctest: +ELLIPSIS Trans #00000 tid=... time=... offset=52 status=' ' user='' description='initial database creation' data #00000 oid=0000000000000000 size=60 class=persistent.mapping.PersistentMapping Now we see first transaction with root object. Let's add a BTree: >>> root = db.open().root() >>> root['tree'] = OOBTree() >>> txn.get().note('added an OOBTree') >>> txn.get().commit() >>> fsdump(path) #doctest: +ELLIPSIS Trans #00000 tid=... time=... offset=52 status=' ' user='' description='initial database creation' data #00000 oid=0000000000000000 size=60 class=persistent.mapping.PersistentMapping Trans #00001 tid=... time=... offset=201 status=' ' user='' description='added an OOBTree' data #00000 oid=0000000000000000 size=107 class=persistent.mapping.PersistentMapping data #00001 oid=0000000000000001 size=29 class=BTrees.OOBTree.OOBTree Now we see two transactions and two changed objects. Clean up. >>> st.close() """ import doctest import zope.testing.setupstack def test_suite(): return doctest.DocTestSuite( setUp=zope.testing.setupstack.setUpDirectory, tearDown=zope.testing.setupstack.tearDown) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/blob_transaction.txt0000644000175000017500000002740612214017464023045 0ustar arnauarnau############################################################################## # # Copyright (c) 2005-2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## Transaction support for Blobs ============================= We need a database with a blob supporting storage:: >>> import ZODB.blob, transaction >>> blob_dir = 'blobs' >>> blob_storage = create_storage(blob_dir=blob_dir) >>> database = ZODB.DB(blob_storage) >>> connection1 = database.open() >>> root1 = connection1.root() Putting a Blob into a Connection works like any other Persistent object:: >>> blob1 = ZODB.blob.Blob() >>> blob1.open('w').write('this is blob 1') >>> root1['blob1'] = blob1 >>> 'blob1' in root1 True Aborting a blob add leaves the blob unchanged: >>> transaction.abort() >>> 'blob1' in root1 False >>> blob1._p_oid >>> blob1._p_jar >>> blob1.open().read() 'this is blob 1' It doesn't clear the file because there is no previously committed version: >>> fname = blob1._p_blob_uncommitted >>> import os >>> os.path.exists(fname) True Let's put the blob back into the root and commit the change: >>> root1['blob1'] = blob1 >>> transaction.commit() Now, if we make a change and abort it, we'll return to the committed state: >>> os.path.exists(fname) False >>> blob1._p_blob_uncommitted >>> blob1.open('w').write('this is new blob 1') >>> blob1.open().read() 'this is new blob 1' >>> fname = blob1._p_blob_uncommitted >>> os.path.exists(fname) True >>> transaction.abort() >>> os.path.exists(fname) False >>> blob1._p_blob_uncommitted >>> blob1.open().read() 'this is blob 1' Opening a blob gives us a filehandle. Getting data out of the resulting filehandle is accomplished via the filehandle's read method:: >>> connection2 = database.open() >>> root2 = connection2.root() >>> blob1a = root2['blob1'] >>> blob1afh1 = blob1a.open("r") >>> blob1afh1.read() 'this is blob 1' Let's make another filehandle for read only to blob1a. Aach file handle has a reference to the (same) underlying blob:: >>> blob1afh2 = blob1a.open("r") >>> blob1afh2.blob is blob1afh1.blob True Let's close the first filehandle we got from the blob:: >>> blob1afh1.close() Let's abort this transaction, and ensure that the filehandles that we opened are still open:: >>> transaction.abort() >>> blob1afh2.read() 'this is blob 1' >>> blob1afh2.close() If we open a blob for append, writing any number of bytes to the blobfile should result in the blob being marked "dirty" in the connection (we just aborted above, so the object should be "clean" when we start):: >>> bool(blob1a._p_changed) False >>> blob1a.open('r').read() 'this is blob 1' >>> blob1afh3 = blob1a.open('a') >>> bool(blob1a._p_changed) True >>> blob1afh3.write('woot!') >>> blob1afh3.close() We can open more than one blob object during the course of a single transaction:: >>> blob2 = ZODB.blob.Blob() >>> blob2.open('w').write('this is blob 3') >>> root2['blob2'] = blob2 >>> transaction.commit() Since we committed the current transaction above, the aggregate changes we've made to blob, blob1a (these refer to the same object) and blob2 (a different object) should be evident:: >>> blob1.open('r').read() 'this is blob 1woot!' >>> blob1a.open('r').read() 'this is blob 1woot!' >>> blob2.open('r').read() 'this is blob 3' We shouldn't be able to persist a blob filehandle at commit time (although the exception which is raised when an object cannot be pickled appears to be particulary unhelpful for casual users at the moment):: >>> root1['wontwork'] = blob1.open('r') >>> transaction.commit() Traceback (most recent call last): ... TypeError: coercing to Unicode: need string or buffer, BlobFile found Abort for good measure:: >>> transaction.abort() Attempting to change a blob simultaneously from two different connections should result in a write conflict error:: >>> tm1 = transaction.TransactionManager() >>> tm2 = transaction.TransactionManager() >>> root3 = database.open(transaction_manager=tm1).root() >>> root4 = database.open(transaction_manager=tm2).root() >>> blob1c3 = root3['blob1'] >>> blob1c4 = root4['blob1'] >>> blob1c3fh1 = blob1c3.open('a').write('this is from connection 3') >>> blob1c4fh1 = blob1c4.open('a').write('this is from connection 4') >>> tm1.commit() >>> root3['blob1'].open('r').read() 'this is blob 1woot!this is from connection 3' >>> tm2.commit() Traceback (most recent call last): ... ConflictError: database conflict error (oid 0x01, class ZODB.blob.Blob...) After the conflict, the winning transaction's result is visible on both connections:: >>> root3['blob1'].open('r').read() 'this is blob 1woot!this is from connection 3' >>> tm2.abort() >>> root4['blob1'].open('r').read() 'this is blob 1woot!this is from connection 3' You can't commit a transaction while blob files are open: >>> f = root3['blob1'].open('w') >>> tm1.commit() Traceback (most recent call last): ... ValueError: Can't commit with opened blobs. >>> f.close() >>> tm1.abort() >>> f = root3['blob1'].open('w') >>> f.close() >>> f = root3['blob1'].open('r') >>> tm1.commit() Traceback (most recent call last): ... ValueError: Can't commit with opened blobs. >>> f.close() >>> tm1.abort() Savepoints and Blobs -------------------- We do support optimistic savepoints: >>> connection5 = database.open() >>> root5 = connection5.root() >>> blob = ZODB.blob.Blob() >>> blob_fh = blob.open("w") >>> blob_fh.write("I'm a happy blob.") >>> blob_fh.close() >>> root5['blob'] = blob >>> transaction.commit() >>> root5['blob'].open("r").read() "I'm a happy blob." >>> blob_fh = root5['blob'].open("a") >>> blob_fh.write(" And I'm singing.") >>> blob_fh.close() >>> root5['blob'].open("r").read() "I'm a happy blob. And I'm singing." >>> savepoint = transaction.savepoint(optimistic=True) >>> root5['blob'].open("r").read() "I'm a happy blob. And I'm singing." Savepoints store the blobs in temporary directories in the temporary directory of the blob storage: >>> len([name for name in os.listdir(os.path.join(blob_dir, 'tmp')) ... if name.startswith('savepoint')]) 1 After committing the transaction, the temporary savepoint files are moved to the committed location again: >>> transaction.commit() >>> len([name for name in os.listdir(os.path.join(blob_dir, 'tmp')) ... if name.startswith('savepoint')]) 0 We support non-optimistic savepoints too: >>> root5['blob'].open("a").write(" And I'm dancing.") >>> root5['blob'].open("r").read() "I'm a happy blob. And I'm singing. And I'm dancing." >>> savepoint = transaction.savepoint() Again, the savepoint creates a new savepoints directory: >>> len([name for name in os.listdir(os.path.join(blob_dir, 'tmp')) ... if name.startswith('savepoint')]) 1 >>> root5['blob'].open("w").write(" And the weather is beautiful.") >>> savepoint.rollback() >>> root5['blob'].open("r").read() "I'm a happy blob. And I'm singing. And I'm dancing." >>> transaction.abort() The savepoint blob directory gets cleaned up on an abort: >>> len([name for name in os.listdir(os.path.join(blob_dir, 'tmp')) ... if name.startswith('savepoint')]) 0 Reading Blobs outside of a transaction -------------------------------------- If you want to read from a Blob outside of transaction boundaries (e.g. to stream a file to the browser), committed method to get the name of a file that can be opened. >>> connection6 = database.open() >>> root6 = connection6.root() >>> blob = ZODB.blob.Blob() >>> blob_fh = blob.open("w") >>> blob_fh.write("I'm a happy blob.") >>> blob_fh.close() >>> root6['blob'] = blob >>> transaction.commit() >>> open(blob.committed()).read() "I'm a happy blob." We can also read committed data by calling open with a 'c' flag: >>> f = blob.open('c') This just returns a regular file object: >>> type(f) and doesn't prevent us from opening the blob for writing: >>> blob.open('w').write('x') >>> blob.open().read() 'x' >>> f.read() "I'm a happy blob." >>> f.close() >>> transaction.abort() An exception is raised if we call committed on a blob that has uncommitted changes: >>> blob = ZODB.blob.Blob() >>> blob.committed() Traceback (most recent call last): ... BlobError: Uncommitted changes >>> blob.open('c') Traceback (most recent call last): ... BlobError: Uncommitted changes >>> blob.open('w').write("I'm a happy blob.") >>> root6['blob6'] = blob >>> blob.committed() Traceback (most recent call last): ... BlobError: Uncommitted changes >>> blob.open('c') Traceback (most recent call last): ... BlobError: Uncommitted changes >>> s = transaction.savepoint() >>> blob.committed() Traceback (most recent call last): ... BlobError: Uncommitted changes >>> blob.open('c') Traceback (most recent call last): ... BlobError: Uncommitted changes >>> transaction.commit() >>> open(blob.committed()).read() "I'm a happy blob." You can't open a committed blob file for writing: >>> open(blob.committed(), 'w') # doctest: +ELLIPSIS Traceback (most recent call last): ... IOError: ... tpc_abort --------- If a transaction is aborted in the middle of 2-phase commit, any data stored are discarded. >>> olddata, oldserial = blob_storage.load(blob._p_oid, '') >>> t = transaction.get() >>> blob_storage.tpc_begin(t) >>> open('blobfile', 'w').write('This data should go away') >>> s1 = blob_storage.storeBlob(blob._p_oid, oldserial, olddata, 'blobfile', ... '', t) >>> new_oid = blob_storage.new_oid() >>> open('blobfile2', 'w').write('This data should go away too') >>> s2 = blob_storage.storeBlob(new_oid, '\0'*8, olddata, 'blobfile2', ... '', t) >>> serials = blob_storage.tpc_vote(t) >>> if s1 is None: ... s1 = [s for (oid, s) in serials if oid == blob._p_oid][0] >>> if s2 is None: ... s2 = [s for (oid, s) in serials if oid == new_oid][0] >>> blob_storage.tpc_abort(t) Now, the serial for the existing blob should be the same: >>> blob_storage.load(blob._p_oid, '') == (olddata, oldserial) True And we shouldn't be able to read the data that we saved: >>> blob_storage.loadBlob(blob._p_oid, s1) Traceback (most recent call last): ... POSKeyError: 'No blob file' Of course the old data should be unaffected: >>> open(blob_storage.loadBlob(blob._p_oid, oldserial)).read() "I'm a happy blob." Similarly, the new object wasn't added to the storage: >>> blob_storage.load(new_oid, '') Traceback (most recent call last): ... POSKeyError: 0x... >>> blob_storage.loadBlob(blob._p_oid, s2) Traceback (most recent call last): ... POSKeyError: 'No blob file' .. clean up >>> tm1.abort() >>> tm2.abort() >>> database.close() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/component.xml0000644000175000017500000000073312214017464021477 0ustar arnauarnau
zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/test_datamanageradapter.py0000644000175000017500000001217512214017464024174 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from doctest import DocTestSuite from transaction._transaction import DataManagerAdapter from ZODB.tests.sampledm import DataManager def test_normal_commit(): """ So, we have a data manager: >>> dm = DataManager() and we do some work that modifies uncommited state: >>> dm.inc() >>> dm.state, dm.delta (0, 1) Now we'll commit the changes. When the data manager joins a transaction, the transaction will create an adapter. >>> dma = DataManagerAdapter(dm) and register it as a modified object. At commit time, the transaction will get the "jar" like this: >>> jar = getattr(dma, '_p_jar', dma) and, of course, the jar and the adapter will be the same: >>> jar is dma True The transaction will call tpc_begin: >>> t1 = '1' >>> jar.tpc_begin(t1) Then the transaction will call commit on the jar: >>> jar.commit(t1) This doesn't actually do anything. :) >>> dm.state, dm.delta (0, 1) The transaction will then call tpc_vote: >>> jar.tpc_vote(t1) This prepares the data manager: >>> dm.state, dm.delta (1, 1) >>> dm.prepared True Finally, tpc_finish is called: >>> jar.tpc_finish(t1) and the data manager finishes the two-phase commit: >>> dm.state, dm.delta (1, 0) >>> dm.prepared False """ def test_abort(): """ So, we have a data manager: >>> dm = DataManager() and we do some work that modifies uncommited state: >>> dm.inc() >>> dm.state, dm.delta (0, 1) When the data manager joins a transaction, the transaction will create an adapter. >>> dma = DataManagerAdapter(dm) and register it as a modified object. Now we'll abort the transaction. The transaction will get the "jar" like this: >>> jar = getattr(dma, '_p_jar', dma) and, of course, the jar and the adapter will be the same: >>> jar is dma True Then the transaction will call abort on the jar: >>> t1 = '1' >>> jar.abort(t1) Which aborts the changes in the data manager: >>> dm.state, dm.delta (0, 0) """ def test_tpc_abort_phase1(): """ So, we have a data manager: >>> dm = DataManager() and we do some work that modifies uncommited state: >>> dm.inc() >>> dm.state, dm.delta (0, 1) Now we'll commit the changes. When the data manager joins a transaction, the transaction will create an adapter. >>> dma = DataManagerAdapter(dm) and register it as a modified object. At commit time, the transaction will get the "jar" like this: >>> jar = getattr(dma, '_p_jar', dma) and, of course, the jar and the adapter will be the same: >>> jar is dma True The transaction will call tpc_begin: >>> t1 = '1' >>> jar.tpc_begin(t1) Then the transaction will call commit on the jar: >>> jar.commit(t1) This doesn't actually do anything. :) >>> dm.state, dm.delta (0, 1) At this point, the transaction decides to abort. It calls tpc_abort: >>> jar.tpc_abort(t1) Which causes the state of the data manager to be restored: >>> dm.state, dm.delta (0, 0) """ def test_tpc_abort_phase2(): """ So, we have a data manager: >>> dm = DataManager() and we do some work that modifies uncommited state: >>> dm.inc() >>> dm.state, dm.delta (0, 1) Now we'll commit the changes. When the data manager joins a transaction, the transaction will create an adapter. >>> dma = DataManagerAdapter(dm) and register it as a modified object. At commit time, the transaction will get the "jar" like this: >>> jar = getattr(dma, '_p_jar', dma) and, of course, the jar and the adapter will be the same: >>> jar is dma True The transaction will call tpc_begin: >>> t1 = '1' >>> jar.tpc_begin(t1) Then the transaction will call commit on the jar: >>> jar.commit(t1) This doesn't actually do anything. :) >>> dm.state, dm.delta (0, 1) The transaction calls vote: >>> jar.tpc_vote(t1) This prepares the data manager: >>> dm.state, dm.delta (1, 1) >>> dm.prepared True At this point, the transaction decides to abort. It calls tpc_abort: >>> jar.tpc_abort(t1) Which causes the state of the data manager to be restored: >>> dm.state, dm.delta (0, 0) >>> dm.prepared False """ def test_suite(): return DocTestSuite() if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testhistoricalconnections.py0000644000175000017500000000170512214017464024631 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import manuel.doctest import manuel.footnote import manuel.testing import ZODB.tests.util def test_suite(): return manuel.testing.TestSuite( manuel.doctest.Manuel() + manuel.footnote.Manuel(), '../historical_connections.txt', setUp=ZODB.tests.util.setUp, tearDown=ZODB.tests.util.tearDown, ) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/sampledm.py0000644000175000017500000002470412214017464021133 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample objects for use in tests """ class DataManager(object): """Sample data manager This class provides a trivial data-manager implementation and doc strings to illustrate the the protocol and to provide a tool for writing tests. Our sample data manager has state that is updated through an inc method and through transaction operations. When we create a sample data manager: >>> dm = DataManager() It has two bits of state, state: >>> dm.state 0 and delta: >>> dm.delta 0 Both of which are initialized to 0. state is meant to model committed state, while delta represents tentative changes within a transaction. We change the state by calling inc: >>> dm.inc() which updates delta: >>> dm.delta 1 but state isn't changed until we commit the transaction: >>> dm.state 0 To commit the changes, we use 2-phase commit. We execute the first stage by calling prepare. We need to pass a transation. Our sample data managers don't really use the transactions for much, so we'll be lazy and use strings for transactions: >>> t1 = '1' >>> dm.prepare(t1) The sample data manager updates the state when we call prepare: >>> dm.state 1 >>> dm.delta 1 This is mainly so we can detect some affect of calling the methods. Now if we call commit: >>> dm.commit(t1) Our changes are"permanent". The state reflects the changes and the delta has been reset to 0. >>> dm.state 1 >>> dm.delta 0 """ def __init__(self): self.state = 0 self.sp = 0 self.transaction = None self.delta = 0 self.prepared = False def inc(self, n=1): self.delta += n def prepare(self, transaction): """Prepare to commit data >>> dm = DataManager() >>> dm.inc() >>> t1 = '1' >>> dm.prepare(t1) >>> dm.commit(t1) >>> dm.state 1 >>> dm.inc() >>> t2 = '2' >>> dm.prepare(t2) >>> dm.abort(t2) >>> dm.state 1 It is en error to call prepare more than once without an intervening commit or abort: >>> dm.prepare(t1) >>> dm.prepare(t1) Traceback (most recent call last): ... TypeError: Already prepared >>> dm.prepare(t2) Traceback (most recent call last): ... TypeError: Already prepared >>> dm.abort(t1) If there was a preceeding savepoint, the transaction must match: >>> rollback = dm.savepoint(t1) >>> dm.prepare(t2) Traceback (most recent call last): ,,, TypeError: ('Transaction missmatch', '2', '1') >>> dm.prepare(t1) """ if self.prepared: raise TypeError('Already prepared') self._checkTransaction(transaction) self.prepared = True self.transaction = transaction self.state += self.delta def _checkTransaction(self, transaction): if (transaction is not self.transaction and self.transaction is not None): raise TypeError("Transaction missmatch", transaction, self.transaction) def abort(self, transaction): """Abort a transaction The abort method can be called before two-phase commit to throw away work done in the transaction: >>> dm = DataManager() >>> dm.inc() >>> dm.state, dm.delta (0, 1) >>> t1 = '1' >>> dm.abort(t1) >>> dm.state, dm.delta (0, 0) The abort method also throws away work done in savepoints: >>> dm.inc() >>> r = dm.savepoint(t1) >>> dm.inc() >>> r = dm.savepoint(t1) >>> dm.state, dm.delta (0, 2) >>> dm.abort(t1) >>> dm.state, dm.delta (0, 0) If savepoints are used, abort must be passed the same transaction: >>> dm.inc() >>> r = dm.savepoint(t1) >>> t2 = '2' >>> dm.abort(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '1') >>> dm.abort(t1) The abort method is also used to abort a two-phase commit: >>> dm.inc() >>> dm.state, dm.delta (0, 1) >>> dm.prepare(t1) >>> dm.state, dm.delta (1, 1) >>> dm.abort(t1) >>> dm.state, dm.delta (0, 0) Of course, the transactions passed to prepare and abort must match: >>> dm.prepare(t1) >>> dm.abort(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '1') >>> dm.abort(t1) """ self._checkTransaction(transaction) if self.transaction is not None: self.transaction = None if self.prepared: self.state -= self.delta self.prepared = False self.delta = 0 def commit(self, transaction): """Complete two-phase commit >>> dm = DataManager() >>> dm.state 0 >>> dm.inc() We start two-phase commit by calling prepare: >>> t1 = '1' >>> dm.prepare(t1) We complete it by calling commit: >>> dm.commit(t1) >>> dm.state 1 It is an error ro call commit without calling prepare first: >>> dm.inc() >>> t2 = '2' >>> dm.commit(t2) Traceback (most recent call last): ... TypeError: Not prepared to commit >>> dm.prepare(t2) >>> dm.commit(t2) If course, the transactions given to prepare and commit must be the same: >>> dm.inc() >>> t3 = '3' >>> dm.prepare(t3) >>> dm.commit(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '3') """ if not self.prepared: raise TypeError('Not prepared to commit') self._checkTransaction(transaction) self.delta = 0 self.transaction = None self.prepared = False def savepoint(self, transaction): """Provide the ability to rollback transaction state Savepoints provide a way to: - Save partial transaction work. For some data managers, this could allow resources to be used more efficiently. - Provide the ability to revert state to a point in a transaction without aborting the entire transaction. In other words, savepoints support partial aborts. Savepoints don't use two-phase commit. If there are errors in setting or rolling back to savepoints, the application should abort the containing transaction. This is *not* the responsibility of the data manager. Savepoints are always associated with a transaction. Any work done in a savepoint's transaction is tentative until the transaction is committed using two-phase commit. >>> dm = DataManager() >>> dm.inc() >>> t1 = '1' >>> r = dm.savepoint(t1) >>> dm.state, dm.delta (0, 1) >>> dm.inc() >>> dm.state, dm.delta (0, 2) >>> r.rollback() >>> dm.state, dm.delta (0, 1) >>> dm.prepare(t1) >>> dm.commit(t1) >>> dm.state, dm.delta (1, 0) Savepoints must have the same transaction: >>> r1 = dm.savepoint(t1) >>> dm.state, dm.delta (1, 0) >>> dm.inc() >>> dm.state, dm.delta (1, 1) >>> t2 = '2' >>> r2 = dm.savepoint(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '1') >>> r2 = dm.savepoint(t1) >>> dm.inc() >>> dm.state, dm.delta (1, 2) If we rollback to an earlier savepoint, we discard all work done later: >>> r1.rollback() >>> dm.state, dm.delta (1, 0) and we can no longer rollback to the later savepoint: >>> r2.rollback() Traceback (most recent call last): ... TypeError: ('Attempt to roll back to invalid save point', 3, 2) We can roll back to a savepoint as often as we like: >>> r1.rollback() >>> r1.rollback() >>> r1.rollback() >>> dm.state, dm.delta (1, 0) >>> dm.inc() >>> dm.inc() >>> dm.inc() >>> dm.state, dm.delta (1, 3) >>> r1.rollback() >>> dm.state, dm.delta (1, 0) But we can't rollback to a savepoint after it has been committed: >>> dm.prepare(t1) >>> dm.commit(t1) >>> r1.rollback() Traceback (most recent call last): ... TypeError: Attempt to rollback stale rollback """ if self.prepared: raise TypeError("Can't get savepoint during two-phase commit") self._checkTransaction(transaction) self.transaction = transaction self.sp += 1 return Rollback(self) class Rollback(object): def __init__(self, dm): self.dm = dm self.sp = dm.sp self.delta = dm.delta self.transaction = dm.transaction def rollback(self): if self.transaction is not self.dm.transaction: raise TypeError("Attempt to rollback stale rollback") if self.dm.sp < self.sp: raise TypeError("Attempt to roll back to invalid save point", self.sp, self.dm.sp) self.dm.sp = self.sp self.dm.delta = self.delta def test_suite(): from doctest import DocTestSuite return DocTestSuite() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testconflictresolution.py0000644000175000017500000002074012214017464024152 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import manuel.doctest import manuel.footnote import doctest import manuel.capture import manuel.testing import persistent import transaction import unittest import ZODB.ConflictResolution import ZODB.tests.util import ZODB.POSException import zope.testing.module def setUp(test): ZODB.tests.util.setUp(test) zope.testing.module.setUp(test, 'ConflictResolution_txt') ZODB.ConflictResolution._class_cache.clear() ZODB.ConflictResolution._unresolvable.clear() def tearDown(test): zope.testing.module.tearDown(test) ZODB.tests.util.tearDown(test) ZODB.ConflictResolution._class_cache.clear() ZODB.ConflictResolution._unresolvable.clear() class ResolveableWhenStateDoesNotChange(persistent.Persistent): def _p_resolveConflict(old, committed, new): raise ZODB.POSException.ConflictError class Unresolvable(persistent.Persistent): pass def succeed_with_resolution_when_state_is_unchanged(): """ If a conflicting change doesn't change the state, then don't even bother calling _p_resolveConflict >>> db = ZODB.DB('t.fs') # FileStorage! >>> storage = db.storage >>> conn = db.open() >>> conn.root.x = ResolveableWhenStateDoesNotChange() >>> conn.root.x.v = 1 >>> transaction.commit() >>> serial1 = conn.root.x._p_serial >>> conn.root.x.v = 2 >>> transaction.commit() >>> serial2 = conn.root.x._p_serial >>> oid = conn.root.x._p_oid So, let's try resolving when the old and committed states are the same bit the new state (pickle) is different: >>> p = storage.tryToResolveConflict( ... oid, serial1, serial1, storage.loadSerial(oid, serial2)) >>> p == storage.loadSerial(oid, serial2) True And when the old and new states are the same bit the committed state is different: >>> p = storage.tryToResolveConflict( ... oid, serial2, serial1, storage.loadSerial(oid, serial1)) >>> p == storage.loadSerial(oid, serial2) True But we still conflict if both the committed and new are different than the original: >>> p = storage.tryToResolveConflict( ... oid, serial2, serial1, storage.loadSerial(oid, serial2)) ... # doctest: +ELLIPSIS Traceback (most recent call last): ... ConflictError: database conflict error (oid 0x01, ... Of course, none of this applies if content doesn't support conflict resolution. >>> conn.root.y = Unresolvable() >>> conn.root.y.v = 1 >>> transaction.commit() >>> oid = conn.root.y._p_oid >>> serial = conn.root.y._p_serial >>> p = storage.tryToResolveConflict( ... oid, serial, serial, storage.loadSerial(oid, serial)) ... # doctest: +ELLIPSIS Traceback (most recent call last): ... ConflictError: database conflict error (oid 0x02, ... >>> db.close() """ class Resolveable(persistent.Persistent): def _p_resolveConflict(self, old, committed, new): resolved = {} for k in old: if k not in committed: if k in new and new[k] == old[k]: continue raise ZODB.POSException.ConflictError if k not in new: if k in committed and committed[k] == old[k]: continue raise ZODB.POSException.ConflictError if committed[k] != old[k]: if new[k] == old[k]: resolved[k] = committed[k] continue raise ZODB.POSException.ConflictError if new[k] != old[k]: if committed[k] == old[k]: resolved[k] = new[k] continue raise ZODB.POSException.ConflictError resolved[k] = old[k] for k in new: if k in old: continue if k in committed: raise ZODB.POSException.ConflictError resolved[k] = new[k] for k in committed: if k in old: continue if k in new: raise ZODB.POSException.ConflictError resolved[k] = committed[k] return resolved def resolve_even_when_referenced_classes_are_absent(): """ We often want to be able to resolve even when there are pesistent references to classes that can't be imported. >>> class P(persistent.Persistent): ... pass >>> db = ZODB.DB('t.fs') # FileStorage! >>> storage = db.storage >>> conn = db.open() >>> conn.root.x = Resolveable() >>> transaction.commit() >>> oid = conn.root.x._p_oid >>> serial = conn.root.x._p_serial >>> conn.root.x.a = P() >>> transaction.commit() >>> aid = conn.root.x.a._p_oid >>> serial1 = conn.root.x._p_serial >>> del conn.root.x.a >>> conn.root.x.b = P() >>> transaction.commit() >>> serial2 = conn.root.x._p_serial Bwahaha: >>> P_aside = P >>> del P Now, even though we can't import P, we can still resolve the conflict: >>> p = storage.tryToResolveConflict( ... oid, serial1, serial, storage.loadSerial(oid, serial2)) And load the pickle: >>> conn2 = db.open() >>> P = P_aside >>> p = conn2._reader.getState(p) >>> sorted(p), p['a'] is conn2.get(aid), p['b'] is conn2.root.x.b (['a', 'b'], True, True) >>> isinstance(p['a'], P) and isinstance(p['b'], P) True Oooooof course, this won't work if the subobjects aren't persistent: >>> class NP: ... pass >>> conn.root.x = Resolveable() >>> transaction.commit() >>> oid = conn.root.x._p_oid >>> serial = conn.root.x._p_serial >>> conn.root.x.a = a = NP() >>> transaction.commit() >>> serial1 = conn.root.x._p_serial >>> del conn.root.x.a >>> conn.root.x.b = b = NP() >>> transaction.commit() >>> serial2 = conn.root.x._p_serial Bwahaha: >>> del NP >>> storage.tryToResolveConflict( ... oid, serial1, serial, storage.loadSerial(oid, serial2)) ... # doctest: +ELLIPSIS Traceback (most recent call last): ... ConflictError: database conflict error (oid ... >>> db.close() """ def resolve_even_when_xdb_referenced_classes_are_absent(): """Cross-database persistent refs! >>> class P(persistent.Persistent): ... pass >>> databases = {} >>> db = ZODB.DB('t.fs', databases=databases, database_name='') >>> db2 = ZODB.DB('o.fs', databases=databases, database_name='o') >>> storage = db.storage >>> conn = db.open() >>> conn.root.x = Resolveable() >>> transaction.commit() >>> oid = conn.root.x._p_oid >>> serial = conn.root.x._p_serial >>> p = P(); conn.get_connection('o').add(p) >>> conn.root.x.a = p >>> transaction.commit() >>> aid = conn.root.x.a._p_oid >>> serial1 = conn.root.x._p_serial >>> del conn.root.x.a >>> p = P(); conn.get_connection('o').add(p) >>> conn.root.x.b = p >>> transaction.commit() >>> serial2 = conn.root.x._p_serial >>> del p Bwahaha: >>> P_aside = P >>> del P Now, even though we can't import P, we can still resolve the conflict: >>> p = storage.tryToResolveConflict( ... oid, serial1, serial, storage.loadSerial(oid, serial2)) And load the pickle: >>> conn2 = db.open() >>> conn2o = conn2.get_connection('o') >>> P = P_aside >>> p = conn2._reader.getState(p) >>> sorted(p), p['a'] is conn2o.get(aid), p['b'] is conn2.root.x.b (['a', 'b'], True, True) >>> isinstance(p['a'], P) and isinstance(p['b'], P) True >>> db.close() >>> db2.close() """ def test_suite(): return unittest.TestSuite([ manuel.testing.TestSuite( manuel.doctest.Manuel() + manuel.footnote.Manuel() + manuel.capture.Manuel(), '../ConflictResolution.txt', setUp=setUp, tearDown=tearDown, ), doctest.DocTestSuite( setUp=setUp, tearDown=tearDown), ]) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/MinPO.py0000644000175000017500000000167712214017464020317 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """A minimal persistent object to use for tests""" from persistent import Persistent class MinPO(Persistent): def __init__(self, value=None): self.value = value def __cmp__(self, aMinPO): return cmp(self.value, aMinPO.value) def __repr__(self): return "MinPO(%s)" % self.value zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/ConflictResolution.py0000644000175000017500000001427512214017464023160 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for application-level conflict resolution.""" from ZODB.POSException import ConflictError, UndoError from persistent import Persistent from transaction import Transaction from ZODB.tests.StorageTestBase import zodb_unpickle, zodb_pickle class PCounter(Persistent): _value = 0 def __repr__(self): return "" % self._value def inc(self): self._value = self._value + 1 def _p_resolveConflict(self, oldState, savedState, newState): savedDiff = savedState['_value'] - oldState['_value'] newDiff = newState['_value'] - oldState['_value'] oldState['_value'] = oldState['_value'] + savedDiff + newDiff return oldState # Insecurity: What if _p_resolveConflict _thinks_ it resolved the # conflict, but did something wrong? class PCounter2(PCounter): def _p_resolveConflict(self, oldState, savedState, newState): raise ConflictError class PCounter3(PCounter): def _p_resolveConflict(self, oldState, savedState, newState): raise AttributeError("no attribute (testing conflict resolution)") class PCounter4(PCounter): def _p_resolveConflict(self, oldState, savedState): raise RuntimeError("Can't get here; not enough args") class ConflictResolvingStorage: def checkResolve(self): obj = PCounter() obj.inc() oid = self._storage.new_oid() revid1 = self._dostoreNP(oid, data=zodb_pickle(obj)) obj.inc() obj.inc() # The effect of committing two transactions with the same # pickle is to commit two different transactions relative to # revid1 that add two to _value. revid2 = self._dostoreNP(oid, revid=revid1, data=zodb_pickle(obj)) revid3 = self._dostoreNP(oid, revid=revid1, data=zodb_pickle(obj)) data, serialno = self._storage.load(oid, '') inst = zodb_unpickle(data) self.assertEqual(inst._value, 5) def checkUnresolvable(self): obj = PCounter2() obj.inc() oid = self._storage.new_oid() revid1 = self._dostoreNP(oid, data=zodb_pickle(obj)) obj.inc() obj.inc() # The effect of committing two transactions with the same # pickle is to commit two different transactions relative to # revid1 that add two to _value. revid2 = self._dostoreNP(oid, revid=revid1, data=zodb_pickle(obj)) try: self._dostoreNP(oid, revid=revid1, data=zodb_pickle(obj)) except ConflictError, err: self.assert_("PCounter2" in str(err)) else: self.fail("Expected ConflictError") def checkZClassesArentResolved(self): from ZODB.ConflictResolution import find_global, BadClassName dummy_class_tuple = ('*foobar', ()) self.assertRaises(BadClassName, find_global, '*foobar', ()) def checkBuggyResolve1(self): obj = PCounter3() obj.inc() oid = self._storage.new_oid() revid1 = self._dostoreNP(oid, data=zodb_pickle(obj)) obj.inc() obj.inc() # The effect of committing two transactions with the same # pickle is to commit two different transactions relative to # revid1 that add two to _value. revid2 = self._dostoreNP(oid, revid=revid1, data=zodb_pickle(obj)) self.assertRaises(ConflictError, self._dostoreNP, oid, revid=revid1, data=zodb_pickle(obj)) def checkBuggyResolve2(self): obj = PCounter4() obj.inc() oid = self._storage.new_oid() revid1 = self._dostoreNP(oid, data=zodb_pickle(obj)) obj.inc() obj.inc() # The effect of committing two transactions with the same # pickle is to commit two different transactions relative to # revid1 that add two to _value. revid2 = self._dostoreNP(oid, revid=revid1, data=zodb_pickle(obj)) self.assertRaises(ConflictError, self._dostoreNP, oid, revid=revid1, data=zodb_pickle(obj)) class ConflictResolvingTransUndoStorage: def checkUndoConflictResolution(self): # This test is based on checkNotUndoable in the # TransactionalUndoStorage test suite. Except here, conflict # resolution should allow us to undo the transaction anyway. obj = PCounter() obj.inc() oid = self._storage.new_oid() revid_a = self._dostore(oid, data=obj) obj.inc() revid_b = self._dostore(oid, revid=revid_a, data=obj) obj.inc() revid_c = self._dostore(oid, revid=revid_b, data=obj) # Start the undo info = self._storage.undoInfo() tid = info[1]['id'] t = Transaction() self._storage.tpc_begin(t) self._storage.undo(tid, t) self._storage.tpc_vote(t) self._storage.tpc_finish(t) def checkUndoUnresolvable(self): # This test is based on checkNotUndoable in the # TransactionalUndoStorage test suite. Except here, conflict # resolution should allow us to undo the transaction anyway. obj = PCounter2() obj.inc() oid = self._storage.new_oid() revid_a = self._dostore(oid, data=obj) obj.inc() revid_b = self._dostore(oid, revid=revid_a, data=obj) obj.inc() revid_c = self._dostore(oid, revid=revid_b, data=obj) # Start the undo info = self._storage.undoInfo() tid = info[1]['id'] t = Transaction() self.assertRaises(UndoError, self._begin_undos_vote, t, tid) self._storage.tpc_abort(t) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/blob_tempdir.txt0000644000175000017500000000300612214017464022152 0ustar arnauarnau======================================= Temporary directory handling with blobs ======================================= When creating uncommitted data files for a blob (e.g. by calling `blob.open('w')`) we need to decide where to create them. The decision depends on whether the blob is already stored in a database or not. Case 1: Blobs that are not in a database yet ============================================ Let's create a new blob and open it for writing:: >>> from ZODB.blob import Blob >>> b = Blob() >>> w = b.open('w') The created file is in the default temporary directory:: >>> import tempfile >>> w.name.startswith(tempfile.gettempdir()) True >>> w.close() Case 2: Blobs that are in a database ==================================== For this case we instanciate a blob and add it to a database immediately. First, we need a datatabase with blob support:: >>> from ZODB.MappingStorage import MappingStorage >>> from ZODB.blob import BlobStorage >>> from ZODB.DB import DB >>> import os.path >>> base_storage = MappingStorage('test') >>> blob_dir = os.path.abspath('blobs') >>> blob_storage = BlobStorage(blob_dir, base_storage) >>> database = DB(blob_storage) Now we create a blob and put it in the database. After that we open it for writing and expect the file to be in the blob temporary directory:: >>> blob = Blob() >>> connection = database.open() >>> connection.add(blob) >>> w = blob.open('w') >>> w.name.startswith(os.path.join(blob_dir, 'tmp')) True >>> w.close() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/Synchronization.py0000644000175000017500000001026312214017464022525 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test the storage's implemenetation of the storage synchronization spec. The Synchronization spec http://www.zope.org/Documentation/Developer/Models/ZODB/ ZODB_Architecture_Storage_Interface_State_Synchronization_Diag.html It specifies two states committing and non-committing. A storage starts in the non-committing state. tpc_begin() transfers to the committting state; tpc_abort() and tpc_finish() transfer back to non-committing. Several other methods are only allowed in one state or another. Many methods allowed only in the committing state require that they apply to the currently committing transaction. The spec is silent on a variety of methods that don't appear to modify the state, e.g. load(), undoLog(), pack(). It's unclear whether there is a separate set of synchronization rules that apply to these methods or if the synchronization is implementation dependent, i.e. only what is need to guarantee a corrected implementation. The synchronization spec is also silent on whether there is any contract implied with the caller. If the storage can assume that a single client is single-threaded and that it will not call, e.g., store() until after it calls tpc_begin(), the implementation can be substantially simplified. New and/or unspecified methods: tpc_vote(): handled like tpc_abort undo(): how's that handled? Methods that have nothing to do with committing/non-committing: load(), loadSerial(), getName(), getSize(), __len__(), history(), undoLog(), pack(). Specific questions: The spec & docs say that undo() takes three arguments, the second being a transaction. If the specified arg isn't the current transaction, the undo() should raise StorageTransactionError. This isn't implemented anywhere. It looks like undo can be called at anytime. FileStorage does not allow undo() during a pack. How should this be tested? Is it a general restriction? """ from transaction import Transaction from ZODB.POSException import StorageTransactionError OID = "\000" * 8 SERIALNO = "\000" * 8 TID = "\000" * 8 class SynchronizedStorage: def verifyNotCommitting(self, callable, *args): self.assertRaises(StorageTransactionError, callable, *args) def verifyWrongTrans(self, callable, *args): t = Transaction() self._storage.tpc_begin(t) self.assertRaises(StorageTransactionError, callable, *args) self._storage.tpc_abort(t) def checkStoreNotCommitting(self): self.verifyNotCommitting(self._storage.store, OID, SERIALNO, "", "", Transaction()) def checkStoreWrongTrans(self): self.verifyWrongTrans(self._storage.store, OID, SERIALNO, "", "", Transaction()) def checkAbortNotCommitting(self): self._storage.tpc_abort(Transaction()) def checkAbortWrongTrans(self): t = Transaction() self._storage.tpc_begin(t) self._storage.tpc_abort(Transaction()) self._storage.tpc_abort(t) def checkFinishNotCommitting(self): t = Transaction() self.assertRaises(StorageTransactionError, self._storage.tpc_finish, t) self._storage.tpc_abort(t) def checkFinishWrongTrans(self): t = Transaction() self._storage.tpc_begin(t) self.assertRaises(StorageTransactionError, self._storage.tpc_finish, Transaction()) self._storage.tpc_abort(t) def checkBeginCommitting(self): t = Transaction() self._storage.tpc_begin(t) self._storage.tpc_abort(t) # TODO: how to check undo? zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/dbopen.txt0000644000175000017500000002406312214017464020765 0ustar arnauarnau===================== Connection Management ===================== Here we exercise the connection management done by the DB class. >>> from ZODB import DB >>> from ZODB.MappingStorage import MappingStorage as Storage Capturing log messages from DB is important for some of the examples: >>> from zope.testing.loggingsupport import InstalledHandler >>> handler = InstalledHandler('ZODB.DB') Create a storage, and wrap it in a DB wrapper: >>> st = Storage() >>> db = DB(st) By default, we can open 7 connections without any log messages: >>> conns = [db.open() for dummy in range(7)] >>> handler.records [] Open one more, and we get a warning: >>> conns.append(db.open()) >>> len(handler.records) 1 >>> msg = handler.records[0] >>> print msg.name, msg.levelname, msg.getMessage() ZODB.DB WARNING DB.open() has 8 open connections with a pool_size of 7 Open 6 more, and we get 6 more warnings: >>> conns.extend([db.open() for dummy in range(6)]) >>> len(conns) 14 >>> len(handler.records) 7 >>> msg = handler.records[-1] >>> print msg.name, msg.levelname, msg.getMessage() ZODB.DB WARNING DB.open() has 14 open connections with a pool_size of 7 Add another, so that it's more than twice the default, and the level rises to critical: >>> conns.append(db.open()) >>> len(conns) 15 >>> len(handler.records) 8 >>> msg = handler.records[-1] >>> print msg.name, msg.levelname, msg.getMessage() ZODB.DB CRITICAL DB.open() has 15 open connections with a pool_size of 7 While it's boring, it's important to verify that the same relationships hold if the default pool size is overridden. >>> handler.clear() >>> st.close() >>> st = Storage() >>> PS = 2 # smaller pool size >>> db = DB(st, pool_size=PS) >>> conns = [db.open() for dummy in range(PS)] >>> handler.records [] A warning for opening one more: >>> conns.append(db.open()) >>> len(handler.records) 1 >>> msg = handler.records[0] >>> print msg.name, msg.levelname, msg.getMessage() ZODB.DB WARNING DB.open() has 3 open connections with a pool_size of 2 More warnings through 4 connections: >>> conns.extend([db.open() for dummy in range(PS-1)]) >>> len(conns) 4 >>> len(handler.records) 2 >>> msg = handler.records[-1] >>> print msg.name, msg.levelname, msg.getMessage() ZODB.DB WARNING DB.open() has 4 open connections with a pool_size of 2 And critical for going beyond that: >>> conns.append(db.open()) >>> len(conns) 5 >>> len(handler.records) 3 >>> msg = handler.records[-1] >>> print msg.name, msg.levelname, msg.getMessage() ZODB.DB CRITICAL DB.open() has 5 open connections with a pool_size of 2 We can change the pool size on the fly: >>> handler.clear() >>> db.setPoolSize(6) >>> conns.append(db.open()) >>> handler.records # no log msg -- the pool is bigger now [] >>> conns.append(db.open()) # but one more and there's a warning again >>> len(handler.records) 1 >>> msg = handler.records[0] >>> print msg.name, msg.levelname, msg.getMessage() ZODB.DB WARNING DB.open() has 7 open connections with a pool_size of 6 Enough of that. >>> handler.clear() >>> st.close() More interesting is the stack-like nature of connection reuse. So long as we keep opening new connections, and keep them alive, all connections returned are distinct: >>> st = Storage() >>> db = DB(st) >>> c1 = db.open() >>> c2 = db.open() >>> c3 = db.open() >>> c1 is c2 or c1 is c3 or c2 is c3 False Let's put some markers on the connections, so we can identify these specific objects later: >>> c1.MARKER = 'c1' >>> c2.MARKER = 'c2' >>> c3.MARKER = 'c3' Now explicitly close c1 and c2: >>> c1.close() >>> c2.close() Reaching into the internals, we can see that db's connection pool now has two connections available for reuse, and knows about three connections in all: >>> pool = db.pool >>> len(pool.available) 2 >>> len(pool.all) 3 Since we closed c2 last, it's at the top of the available stack, so will be reused by the next open(): >>> c1 = db.open() >>> c1.MARKER 'c2' >>> len(pool.available), len(pool.all) (1, 3) >>> c3.close() # now the stack has c3 on top, then c1 >>> c2 = db.open() >>> c2.MARKER 'c3' >>> len(pool.available), len(pool.all) (1, 3) >>> c3 = db.open() >>> c3.MARKER 'c1' >>> len(pool.available), len(pool.all) (0, 3) It's a bit more complicated though. The connection pool tries to keep connections with larger caches at the top of the stack. It does this by having connections with smaller caches "sink" below connections with larger caches when they are closed. To see this, we'll add some objects to the caches: >>> for i in range(10): ... c1.root()[i] = c1.root().__class__() >>> import transaction >>> transaction.commit() >>> c1._cache.cache_non_ghost_count 11 >>> for i in range(5): ... _ = len(c2.root()[i]) >>> c2._cache.cache_non_ghost_count 6 Now, we'll close the connections and get them back: >>> c1.close() >>> c2.close() >>> c3.close() We closed c3 last, but c1 is the biggest, so we get c1 on the next open: >>> db.open() is c1 True Similarly, c2 is the next buggest, so we get that next: >>> db.open() is c2 True and finally c3: >>> db.open() is c3 True What about the 3 in pool.all? We've seen that closing connections doesn't reduce pool.all, and it would be bad if DB kept connections alive forever. In fact pool.all is a "weak set" of connections -- it holds weak references to connections. That alone doesn't keep connection objects alive. The weak set allows DB's statistics methods to return info about connections that are still alive. >>> len(db.cacheDetailSize()) # one result for each connection's cache 3 If a connection object is abandoned (it becomes unreachable), then it will vanish from pool.all automatically. However, connections are involved in cycles, so exactly when a connection vanishes from pool.all isn't predictable. It can be forced by running gc.collect(): >>> import gc >>> dummy = gc.collect() >>> len(pool.all) 3 >>> c3 = None >>> dummy = gc.collect() # removes c3 from pool.all >>> len(pool.all) 2 Note that c3 is really gone; in particular it didn't get added back to the stack of available connections by magic: >>> len(pool.available) 0 Nothing in that last block should have logged any msgs: >>> handler.records [] If "too many" connections are open, then closing one may kick an older closed one out of the available connection stack. >>> st.close() >>> st = Storage() >>> db = DB(st, pool_size=3) >>> conns = [db.open() for dummy in range(6)] >>> len(handler.records) # 3 warnings for the "excess" connections 3 >>> pool = db.pool >>> len(pool.available), len(pool.all) (0, 6) Let's mark them: >>> for i, c in enumerate(conns): ... c.MARKER = i Closing connections adds them to the stack: >>> for i in range(3): ... conns[i].close() >>> len(pool.available), len(pool.all) (3, 6) >>> del conns[:3] # leave the ones with MARKERs 3, 4 and 5 Closing another one will purge the one with MARKER 0 from the stack (since it was the first added to the stack): >>> [c.MARKER for (t, c) in pool.available] [0, 1, 2] >>> conns[0].close() # MARKER 3 >>> len(pool.available), len(pool.all) (3, 5) >>> [c.MARKER for (t, c) in pool.available] [1, 2, 3] Similarly for the other two: >>> conns[1].close(); conns[2].close() >>> len(pool.available), len(pool.all) (3, 3) >>> [c.MARKER for (t, c) in pool.available] [3, 4, 5] Reducing the pool size may also purge the oldest closed connections: >>> db.setPoolSize(2) # gets rid of MARKER 3 >>> len(pool.available), len(pool.all) (2, 2) >>> [c.MARKER for (t, c) in pool.available] [4, 5] Since MARKER 5 is still the last one added to the stack, it will be the first popped: >>> c1 = db.open(); c2 = db.open() >>> c1.MARKER, c2.MARKER (5, 4) >>> len(pool.available), len(pool.all) (0, 2) Next: when a closed Connection is removed from .available due to exceeding pool_size, that Connection's cache is cleared (this behavior was new in ZODB 3.6b6). While user code may still hold a reference to that Connection, once it vanishes from .available it's really not usable for anything sensible (it can never be in the open state again). Waiting for gc to reclaim the Connection and its cache eventually works, but that can take "a long time" and caches can hold on to many objects, and limited resources (like RDB connections), for the duration. >>> st.close() >>> st = Storage() >>> db = DB(st, pool_size=2) >>> conn0 = db.open() >>> len(conn0._cache) # empty now 0 >>> import transaction >>> conn0.root()['a'] = 1 >>> transaction.commit() >>> len(conn0._cache) # but now the cache holds the root object 1 Now open more connections so that the total exceeds pool_size (2): >>> conn1 = db.open(); _ = conn1.root()['a'] >>> conn2 = db.open(); _ = conn2.root()['a'] Note that we accessed the objects in the new connections so they would be of the same size, so that when they get closed, they don't sink below conn0. >>> pool = db.pool >>> len(pool.all), len(pool.available) # all Connections are in use (3, 0) Return pool_size (2) Connections to the pool: >>> conn0.close() >>> conn1.close() >>> len(pool.all), len(pool.available) (3, 2) >>> len(conn0._cache) # nothing relevant has changed yet 1 When we close the third connection, conn0 will be booted from .all, and we expect its cache to be cleared then: >>> conn2.close() >>> len(pool.all), len(pool.available) (2, 2) >>> len(conn0._cache) # conn0's cache is empty again 0 >>> del conn0, conn1, conn2 Clean up. >>> st.close() >>> handler.uninstall() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/Corruption.py0000644000175000017500000000437012214017464021472 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Do some minimal tests of data corruption""" import os import random import stat import ZODB.FileStorage from StorageTestBase import StorageTestBase class FileStorageCorruptTests(StorageTestBase): def setUp(self): StorageTestBase.setUp(self) self._storage = ZODB.FileStorage.FileStorage('Data.fs', create=1) def _do_stores(self): oids = [] for i in range(5): oid = self._storage.new_oid() revid = self._dostore(oid) oids.append((oid, revid)) return oids def _check_stores(self, oids): for oid, revid in oids: data, s_revid = self._storage.load(oid, '') self.assertEqual(s_revid, revid) def checkTruncatedIndex(self): oids = self._do_stores() self._close() # truncation the index file self.failUnless(os.path.exists('Data.fs.index')) f = open('Data.fs.index', 'r+') f.seek(0, 2) size = f.tell() f.seek(size / 2) f.truncate() f.close() self._storage = ZODB.FileStorage.FileStorage('Data.fs') self._check_stores(oids) def checkCorruptedIndex(self): oids = self._do_stores() self._close() # truncation the index file self.failUnless(os.path.exists('Data.fs.index')) size = os.stat('Data.fs.index')[stat.ST_SIZE] f = open('Data.fs.index', 'r+') while f.tell() < size: f.seek(random.randrange(1, size / 10), 1) f.write('\000') f.close() self._storage = ZODB.FileStorage.FileStorage('Data.fs') self._check_stores(oids) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/__init__.py0000644000175000017500000000004612214017464021061 0ustar arnauarnau# Having this makes debugging better. zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/PersistentStorage.py0000644000175000017500000000323112214017464023006 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test that a storage's values persist across open and close.""" class PersistentStorage: def checkUpdatesPersist(self): oids = [] def new_oid_wrapper(l=oids, new_oid=self._storage.new_oid): oid = new_oid() l.append(oid) return oid self._storage.new_oid = new_oid_wrapper self._dostore() oid = self._storage.new_oid() revid = self._dostore(oid) oid = self._storage.new_oid() revid = self._dostore(oid, data=1) revid = self._dostore(oid, revid, data=2) self._dostore(oid, revid, data=3) # keep copies of all the objects objects = [] for oid in oids: p, s = self._storage.load(oid, '') objects.append((oid, '', p, s)) self._storage.close() self.open() # keep copies of all the objects for oid, ver, p, s in objects: _p, _s = self._storage.load(oid, ver) self.assertEquals(p, _p) self.assertEquals(s, _s) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/synchronizers.txt0000644000175000017500000000614612214017464022440 0ustar arnauarnau============= Synchronizers ============= Here are some tests that storage ``sync()`` methods get called at appropriate times in the life of a transaction. The tested behavior is new in ZODB 3.4. First define a lightweight storage with a ``sync()`` method: >>> import ZODB >>> from ZODB.MappingStorage import MappingStorage >>> import transaction >>> class SimpleStorage(MappingStorage): ... sync_called = False ... ... def sync(self, *args): ... self.sync_called = True Make a change locally: >>> st = SimpleStorage() >>> db = ZODB.DB(st) >>> cn = db.open() >>> rt = cn.root() >>> rt['a'] = 1 Sync should not have been called yet. >>> st.sync_called # False before 3.4 False ``sync()`` is called by the Connection's ``afterCompletion()`` hook after the commit completes. >>> transaction.commit() >>> st.sync_called # False before 3.4 True ``sync()`` is also called by the ``afterCompletion()`` hook after an abort. >>> st.sync_called = False >>> rt['b'] = 2 >>> transaction.abort() >>> st.sync_called # False before 3.4 True And ``sync()`` is called whenever we explicitly start a new transaction, via the ``newTransaction()`` hook. >>> st.sync_called = False >>> dummy = transaction.begin() >>> st.sync_called # False before 3.4 True Clean up. Closing db isn't enough -- closing a DB doesn't close its `Connections`. Leaving our `Connection` open here can cause the ``SimpleStorage.sync()`` method to get called later, during another test, and our doctest-synthesized module globals no longer exist then. You get a weird traceback then ;-) >>> cn.close() One more, very obscure. It was the case that if the first action a new threaded transaction manager saw was a ``begin()`` call, then synchronizers registered after that in the same transaction weren't communicated to the `Transaction` object, and so the synchronizers' ``afterCompletion()`` hooks weren't called when the transaction commited. None of the test suites (ZODB's, Zope 2.8's, or Zope3's) caught that, but apparently Zope 3 takes this path at some point when serving pages. >>> tm = transaction.ThreadTransactionManager() >>> st.sync_called = False >>> dummy = tm.begin() # we're doing this _before_ opening a connection >>> cn = db.open(transaction_manager=tm) >>> rt = cn.root() # make a change >>> rt['c'] = 3 >>> st.sync_called False Now ensure that ``cn.afterCompletion() -> st.sync()`` gets called by commit despite that the `Connection` registered after the transaction began: >>> tm.commit() >>> st.sync_called True And try the same thing with a non-threaded transaction manager: >>> cn.close() >>> tm = transaction.TransactionManager() >>> st.sync_called = False >>> dummy = tm.begin() # we're doing this _before_ opening a connection >>> cn = db.open(transaction_manager=tm) >>> rt = cn.root() # make a change >>> rt['d'] = 4 >>> st.sync_called False >>> tm.commit() >>> st.sync_called True >>> cn.close() >>> db.close() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/warnhook.py0000644000175000017500000000410212214017464021147 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import warnings class WarningsHook: """Hook to capture warnings generated by Python. The function warnings.showwarning() is designed to be hooked by application code, allowing the application to customize the way it handles warnings. This hook captures the unformatted warning information and stores it in a list. A test can inspect this list after the test is over. Issues: The warnings module has lots of delicate internal state. If a warning has been reported once, it won't be reported again. It may be necessary to extend this class with a mechanism for modifying the internal state so that we can be guaranteed a warning will be reported. If Python is run with a warnings filter, e.g. python -Werror, then a test that is trying to inspect a particular warning will fail. Perhaps this class can be extended to install more-specific filters the test to work anyway. """ def __init__(self): self.original = None self.warnings = [] def install(self): self.original = warnings.showwarning warnings.showwarning = self.showwarning def uninstall(self): assert self.original is not None warnings.showwarning = self.original self.original = None def showwarning(self, message, category, filename, lineno): self.warnings.append((str(message), category, filename, lineno)) def clear(self): self.warnings = [] zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testFileStorage.py0000644000175000017500000005541712214017464022442 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import cPickle import doctest import os if os.environ.get('USE_ZOPE_TESTING_DOCTEST'): from zope.testing import doctest import unittest import transaction import ZODB.FileStorage import ZODB.tests.hexstorage import ZODB.tests.testblob import ZODB.tests.util import zope.testing.setupstack from ZODB import POSException from ZODB import DB from ZODB.fsIndex import fsIndex from ZODB.tests import StorageTestBase, BasicStorage, TransactionalUndoStorage from ZODB.tests import PackableStorage, Synchronization, ConflictResolution from ZODB.tests import HistoryStorage, IteratorStorage, Corruption from ZODB.tests import RevisionStorage, PersistentStorage, MTStorage from ZODB.tests import ReadOnlyStorage, RecoveryStorage from ZODB.tests.StorageTestBase import MinPO, zodb_pickle class FileStorageTests( StorageTestBase.StorageTestBase, BasicStorage.BasicStorage, TransactionalUndoStorage.TransactionalUndoStorage, RevisionStorage.RevisionStorage, PackableStorage.PackableStorageWithOptionalGC, PackableStorage.PackableUndoStorage, Synchronization.SynchronizedStorage, ConflictResolution.ConflictResolvingStorage, ConflictResolution.ConflictResolvingTransUndoStorage, HistoryStorage.HistoryStorage, IteratorStorage.IteratorStorage, IteratorStorage.ExtendedIteratorStorage, PersistentStorage.PersistentStorage, MTStorage.MTStorage, ReadOnlyStorage.ReadOnlyStorage ): def open(self, **kwargs): self._storage = ZODB.FileStorage.FileStorage('FileStorageTests.fs', **kwargs) def setUp(self): StorageTestBase.StorageTestBase.setUp(self) self.open(create=1) def checkLongMetadata(self): s = "X" * 75000 try: self._dostore(user=s) except POSException.StorageError: pass else: self.fail("expect long user field to raise error") try: self._dostore(description=s) except POSException.StorageError: pass else: self.fail("expect long user field to raise error") def check_use_fsIndex(self): self.assertEqual(self._storage._index.__class__, fsIndex) # A helper for checking that when an .index contains a dict for the # index, it's converted to an fsIndex when the file is opened. def convert_index_to_dict(self): # Convert the index in the current .index file to a Python dict. # Return the index originally found. data = fsIndex.load('FileStorageTests.fs.index') index = data['index'] newindex = dict(index) data['index'] = newindex cPickle.dump(data, open('FileStorageTests.fs.index', 'wb'), 1) return index def check_conversion_to_fsIndex(self, read_only=False): from ZODB.fsIndex import fsIndex # Create some data, and remember the index. for i in range(10): self._dostore() oldindex_as_dict = dict(self._storage._index) # Save the index. self._storage.close() # Convert it to a dict. old_index = self.convert_index_to_dict() self.assert_(isinstance(old_index, fsIndex)) new_index = self.convert_index_to_dict() self.assert_(isinstance(new_index, dict)) # Verify it's converted to fsIndex in memory upon open. self.open(read_only=read_only) self.assert_(isinstance(self._storage._index, fsIndex)) # Verify it has the right content. newindex_as_dict = dict(self._storage._index) self.assertEqual(oldindex_as_dict, newindex_as_dict) # Check that the type on disk has changed iff read_only is False. self._storage.close() current_index = self.convert_index_to_dict() if read_only: self.assert_(isinstance(current_index, dict)) else: self.assert_(isinstance(current_index, fsIndex)) def check_conversion_to_fsIndex_readonly(self): # Same thing, but the disk .index should continue to hold a # Python dict. self.check_conversion_to_fsIndex(read_only=True) def check_conversion_from_dict_to_btree_data_in_fsIndex(self): # To support efficient range searches on its keys as part of # implementing a record iteration protocol in FileStorage, we # converted the fsIndex class from using a dictionary as its # self._data attribute to using an OOBTree in its stead. from ZODB.fsIndex import fsIndex from BTrees.OOBTree import OOBTree # Create some data, and remember the index. for i in range(10): self._dostore() data_dict = dict(self._storage._index._data) # Replace the OOBTree with a dictionary and commit it. self._storage._index._data = data_dict transaction.commit() # Save the index. self._storage.close() # Verify it's converted to fsIndex in memory upon open. self.open() self.assert_(isinstance(self._storage._index, fsIndex)) self.assert_(isinstance(self._storage._index._data, OOBTree)) # Verify it has the right content. new_data_dict = dict(self._storage._index._data) self.assertEqual(len(data_dict), len(new_data_dict)) for k in data_dict: old_tree = data_dict[k] new_tree = new_data_dict[k] self.assertEqual(list(old_tree.items()), list(new_tree.items())) def check_save_after_load_with_no_index(self): for i in range(10): self._dostore() self._storage.close() os.remove('FileStorageTests.fs.index') self.open() self.assertEqual(self._storage._saved, 1) def checkStoreBumpsOid(self): # If .store() is handed an oid bigger than the storage knows # about already, it's crucial that the storage bump its notion # of the largest oid in use. t = transaction.Transaction() self._storage.tpc_begin(t) giant_oid = '\xee' * 8 # Store an object. # oid, serial, data, version, transaction r1 = self._storage.store(giant_oid, '\0'*8, 'data', '', t) # Finish the transaction. r2 = self._storage.tpc_vote(t) self._storage.tpc_finish(t) # Before ZODB 3.2.6, this failed, with ._oid == z64. self.assertEqual(self._storage._oid, giant_oid) def checkRestoreBumpsOid(self): # As above, if .restore() is handed an oid bigger than the storage # knows about already, it's crucial that the storage bump its notion # of the largest oid in use. Because copyTransactionsFrom(), and # ZRS recovery, use the .restore() method, this is plain critical. t = transaction.Transaction() self._storage.tpc_begin(t) giant_oid = '\xee' * 8 # Store an object. # oid, serial, data, version, prev_txn, transaction r1 = self._storage.restore(giant_oid, '\0'*8, 'data', '', None, t) # Finish the transaction. r2 = self._storage.tpc_vote(t) self._storage.tpc_finish(t) # Before ZODB 3.2.6, this failed, with ._oid == z64. self.assertEqual(self._storage._oid, giant_oid) def checkCorruptionInPack(self): # This sets up a corrupt .fs file, with a redundant transaction # length mismatch. The implementation of pack in many releases of # ZODB blew up if the .fs file had such damage: it detected the # damage, but the code to raise CorruptedError referenced an undefined # global. import time from ZODB.utils import U64, p64 from ZODB.FileStorage.format import CorruptedError from ZODB.serialize import referencesf db = DB(self._storage) conn = db.open() conn.root()['xyz'] = 1 transaction.commit() # Ensure it's all on disk. db.close() self._storage.close() # Reopen before damaging. self.open() # Open .fs directly, and damage content. f = open('FileStorageTests.fs', 'r+b') f.seek(0, 2) pos2 = f.tell() - 8 f.seek(pos2) tlen2 = U64(f.read(8)) # length-8 of the last transaction pos1 = pos2 - tlen2 + 8 # skip over the tid at the start f.seek(pos1) tlen1 = U64(f.read(8)) # should be redundant length-8 self.assertEqual(tlen1, tlen2) # verify that it is redundant # Now damage the second copy. f.seek(pos2) f.write(p64(tlen2 - 1)) f.close() # Try to pack. This used to yield # NameError: global name 's' is not defined try: self._storage.pack(time.time(), referencesf) except CorruptedError, detail: self.assert_("redundant transaction length does not match " "initial transaction length" in str(detail)) else: self.fail("expected CorruptedError") def check_record_iternext(self): db = DB(self._storage) conn = db.open() conn.root()['abc'] = MinPO('abc') conn.root()['xyz'] = MinPO('xyz') transaction.commit() # Ensure it's all on disk. db.close() self._storage.close() self.open() key = None for x in ('\000', '\001', '\002'): oid, tid, data, next_oid = self._storage.record_iternext(key) self.assertEqual(oid, ('\000' * 7) + x) key = next_oid expected_data, expected_tid = self._storage.load(oid, '') self.assertEqual(expected_data, data) self.assertEqual(expected_tid, tid) if x == '\002': self.assertEqual(next_oid, None) else: self.assertNotEqual(next_oid, None) class FileStorageHexTests(FileStorageTests): def open(self, **kwargs): self._storage = ZODB.tests.hexstorage.HexStorage( ZODB.FileStorage.FileStorage('FileStorageTests.fs',**kwargs)) class FileStorageTestsWithBlobsEnabled(FileStorageTests): def open(self, **kwargs): if 'blob_dir' not in kwargs: kwargs = kwargs.copy() kwargs['blob_dir'] = 'blobs' FileStorageTests.open(self, **kwargs) class FileStorageHexTestsWithBlobsEnabled(FileStorageTests): def open(self, **kwargs): if 'blob_dir' not in kwargs: kwargs = kwargs.copy() kwargs['blob_dir'] = 'blobs' FileStorageTests.open(self, **kwargs) self._storage = ZODB.tests.hexstorage.HexStorage(self._storage) class FileStorageRecoveryTest( StorageTestBase.StorageTestBase, RecoveryStorage.RecoveryStorage, ): def setUp(self): StorageTestBase.StorageTestBase.setUp(self) self._storage = ZODB.FileStorage.FileStorage("Source.fs", create=True) self._dst = ZODB.FileStorage.FileStorage("Dest.fs", create=True) def tearDown(self): self._dst.close() StorageTestBase.StorageTestBase.tearDown(self) def new_dest(self): return ZODB.FileStorage.FileStorage('Dest.fs') class FileStorageHexRecoveryTest(FileStorageRecoveryTest): def setUp(self): StorageTestBase.StorageTestBase.setUp(self) self._storage = ZODB.tests.hexstorage.HexStorage( ZODB.FileStorage.FileStorage("Source.fs", create=True)) self._dst = ZODB.tests.hexstorage.HexStorage( ZODB.FileStorage.FileStorage("Dest.fs", create=True)) class FileStorageNoRestore(ZODB.FileStorage.FileStorage): @property def restore(self): raise Exception class FileStorageNoRestoreRecoveryTest(FileStorageRecoveryTest): # This test actually verifies a code path of # BaseStorage.copyTransactionsFrom. For simplicity of implementation, we # use a FileStorage deprived of its restore method. def setUp(self): StorageTestBase.StorageTestBase.setUp(self) self._storage = FileStorageNoRestore("Source.fs", create=True) self._dst = FileStorageNoRestore("Dest.fs", create=True) def new_dest(self): return FileStorageNoRestore('Dest.fs') def checkRestoreAcrossPack(self): # Skip this check as it calls restore directly. pass class AnalyzeDotPyTest(StorageTestBase.StorageTestBase): def setUp(self): StorageTestBase.StorageTestBase.setUp(self) self._storage = ZODB.FileStorage.FileStorage("Source.fs", create=True) def checkanalyze(self): import new, sys, pickle from BTrees.OOBTree import OOBTree from ZODB.scripts import analyze # Set up a module to act as a broken import module_name = 'brokenmodule' module = new.module(module_name) sys.modules[module_name] = module class Broken(MinPO): __module__ = module_name module.Broken = Broken oids = [[self._storage.new_oid(), None] for i in range(3)] for i in range(2): t = transaction.Transaction() self._storage.tpc_begin(t) # sometimes data is in this format j = 0 oid, revid = oids[j] serial = self._storage.store( oid, revid, pickle.dumps(OOBTree, 1), "", t) oids[j][1] = serial # and it could be from a broken module j = 1 oid, revid = oids[j] serial = self._storage.store( oid, revid, pickle.dumps(Broken, 1), "", t) oids[j][1] = serial # but mostly it looks like this j = 2 o = MinPO(j) oid, revid = oids[j] serial = self._storage.store(oid, revid, zodb_pickle(o), "", t) oids[j][1] = serial self._storage.tpc_vote(t) self._storage.tpc_finish(t) # now break the import of the Broken class del sys.modules[module_name] # from ZODB.scripts.analyze.analyze fsi = self._storage.iterator() rep = analyze.Report() for txn in fsi: analyze.analyze_trans(rep, txn) # from ZODB.scripts.analyze.report typemap = rep.TYPEMAP.keys() typemap.sort() cumpct = 0.0 for t in typemap: pct = rep.TYPESIZE[t] * 100.0 / rep.DBYTES cumpct += pct self.assertAlmostEqual(cumpct, 100.0, 0, "Failed to analyze some records") # Raise an exception if the tids in FileStorage fs aren't # strictly increasing. def checkIncreasingTids(fs): lasttid = '\0' * 8 for txn in fs.iterator(): if lasttid >= txn.tid: raise ValueError("tids out of order %r >= %r" % (lasttid, txn.tid)) lasttid = txn.tid # Return a TimeStamp object 'minutes' minutes in the future. def timestamp(minutes): import time from persistent.TimeStamp import TimeStamp t = time.time() + 60 * minutes return TimeStamp(*time.gmtime(t)[:5] + (t % 60,)) def testTimeTravelOnOpen(): """ >>> from ZODB.FileStorage import FileStorage >>> from zope.testing.loggingsupport import InstalledHandler Arrange to capture log messages -- they're an important part of this test! >>> handler = InstalledHandler('ZODB.FileStorage') Create a new file storage. >>> st = FileStorage('temp.fs', create=True) >>> db = DB(st) >>> db.close() First check the normal case: transactions are recorded with increasing tids, and time doesn't run backwards. >>> st = FileStorage('temp.fs') >>> db = DB(st) >>> conn = db.open() >>> conn.root()['xyz'] = 1 >>> transaction.get().commit() >>> checkIncreasingTids(st) >>> db.close() >>> st.cleanup() # remove .fs, .index, etc files >>> handler.records # i.e., no log messages [] Now force the database to have transaction records with tids from the future. >>> st = FileStorage('temp.fs', create=True) >>> st._ts = timestamp(15) # 15 minutes in the future >>> db = DB(st) >>> db.close() >>> st = FileStorage('temp.fs') # this should log a warning >>> db = DB(st) >>> conn = db.open() >>> conn.root()['xyz'] = 1 >>> transaction.get().commit() >>> checkIncreasingTids(st) >>> db.close() >>> st.cleanup() >>> [record.levelname for record in handler.records] ['WARNING'] >>> handler.clear() And one more time, with transaction records far in the future. We expect to log a critical error then, as a time so far in the future probably indicates a real problem with the system. Shorter spans may be due to clock drift. >>> st = FileStorage('temp.fs', create=True) >>> st._ts = timestamp(60) # an hour in the future >>> db = DB(st) >>> db.close() >>> st = FileStorage('temp.fs') # this should log a critical error >>> db = DB(st) >>> conn = db.open() >>> conn.root()['xyz'] = 1 >>> transaction.get().commit() >>> checkIncreasingTids(st) >>> db.close() >>> st.cleanup() >>> [record.levelname for record in handler.records] ['CRITICAL'] >>> handler.clear() >>> handler.uninstall() """ def lastInvalidations(): """ The last invalidations method is used by a storage server to populate it's data structure of recent invalidations. The lastInvalidations method is passed a count and must return up to count number of the most recent transactions. We'll create a FileStorage and populate it with some data, keeping track of the transactions along the way: >>> fs = ZODB.FileStorage.FileStorage('t.fs', create=True) >>> db = DB(fs) >>> conn = db.open() >>> from persistent.mapping import PersistentMapping >>> last = [] >>> for i in range(100): ... conn.root()[i] = PersistentMapping() ... transaction.commit() ... last.append(fs.lastTransaction()) Now, we can call lastInvalidations on it: >>> invalidations = fs.lastInvalidations(10) >>> [t for (t, oids) in invalidations] == last[-10:] True >>> from ZODB.utils import u64 >>> [[int(u64(oid)) for oid in oids] ... for (i, oids) in invalidations] ... # doctest: +NORMALIZE_WHITESPACE [[0, 91], [0, 92], [0, 93], [0, 94], [0, 95], [0, 96], [0, 97], [0, 98], [0, 99], [0, 100]] If we ask for more transactions than there are, we'll get as many as there are: >>> len(fs.lastInvalidations(1000)) 101 Of course, calling lastInvalidations on an empty storage refturns no data: >>> db.close() >>> fs = ZODB.FileStorage.FileStorage('t.fs', create=True) >>> list(fs.lastInvalidations(10)) [] >>> fs.close() """ def deal_with_finish_failures(): r""" It's really bad to get errors in FileStorage's _finish method, as that can cause the file storage to be in an inconsistent state. The data file will be fine, but the internal data structures might be hosed. For this reason, FileStorage will close if there is an error after it has finished writing transaction data. It bothers to do very little after writing this data, so this should rarely, if ever, happen. >>> fs = ZODB.FileStorage.FileStorage('data.fs') >>> db = DB(fs) >>> conn = db.open() >>> conn.root()[1] = 1 >>> transaction.commit() Now, we'll indentially break the file storage. It provides a hook for this purpose. :) >>> fs._finish_finish = lambda : None >>> conn.root()[1] = 1 >>> import zope.testing.loggingsupport >>> handler = zope.testing.loggingsupport.InstalledHandler( ... 'ZODB.FileStorage') >>> transaction.commit() Traceback (most recent call last): ... TypeError: () takes no arguments (1 given) >>> print handler ZODB.FileStorage CRITICAL Failure in _finish. Closing. >>> handler.uninstall() >>> fs.load('\0'*8, '') # doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: ... >>> db.close() >>> fs = ZODB.FileStorage.FileStorage('data.fs') >>> db = DB(fs) >>> conn = db.open() >>> conn.root() {1: 1} >>> transaction.abort() >>> db.close() """ def pack_with_open_blob_files(): """ Make sure packing works while there are open blob files. >>> fs = ZODB.FileStorage.FileStorage('data.fs', blob_dir='blobs') >>> db = ZODB.DB(fs) >>> tm1 = transaction.TransactionManager() >>> conn1 = db.open(tm1) >>> import ZODB.blob >>> conn1.root()[1] = ZODB.blob.Blob() >>> conn1.add(conn1.root()[1]) >>> conn1.root()[1].open('w').write('some data') >>> tm1.commit() >>> tm2 = transaction.TransactionManager() >>> conn2 = db.open(tm2) >>> f = conn1.root()[1].open() >>> conn1.root()[2] = ZODB.blob.Blob() >>> conn1.add(conn1.root()[2]) >>> conn1.root()[2].open('w').write('some more data') >>> db.pack() >>> f.read() 'some data' >>> f.close() >>> tm1.commit() >>> conn2.sync() >>> conn2.root()[2].open().read() 'some more data' >>> db.close() """ def test_suite(): suite = unittest.TestSuite() for klass in [ FileStorageTests, FileStorageHexTests, Corruption.FileStorageCorruptTests, FileStorageRecoveryTest, FileStorageHexRecoveryTest, FileStorageNoRestoreRecoveryTest, FileStorageTestsWithBlobsEnabled, FileStorageHexTestsWithBlobsEnabled, AnalyzeDotPyTest, ]: suite.addTest(unittest.makeSuite(klass, "check")) suite.addTest(doctest.DocTestSuite( setUp=zope.testing.setupstack.setUpDirectory, tearDown=zope.testing.setupstack.tearDown)) suite.addTest(ZODB.tests.testblob.storage_reusable_suite( 'BlobFileStorage', lambda name, blob_dir: ZODB.FileStorage.FileStorage('%s.fs' % name, blob_dir=blob_dir), test_blob_storage_recovery=True, test_packing=True, )) suite.addTest(ZODB.tests.testblob.storage_reusable_suite( 'BlobFileHexStorage', lambda name, blob_dir: ZODB.tests.hexstorage.HexStorage( ZODB.FileStorage.FileStorage('%s.fs' % name, blob_dir=blob_dir)), test_blob_storage_recovery=True, test_packing=True, )) suite.addTest(PackableStorage.IExternalGC_suite( lambda : ZODB.FileStorage.FileStorage( 'data.fs', blob_dir='blobs', pack_gc=False))) suite.layer = ZODB.tests.util.MininalTestLayer('testFileStorage') return suite if __name__=='__main__': unittest.main() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testfsoids.py0000644000175000017500000001375712214017464021526 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## r""" fsoids test, of the workhorse fsoids.Trace class ================================================ Let's get a path to work with first. >>> path = 'Data.fs' More imports. >>> import ZODB >>> from ZODB.FileStorage import FileStorage >>> import transaction as txn >>> from BTrees.OOBTree import OOBTree >>> from ZODB.FileStorage.fsoids import Tracer # we're testing this Create an empty FileStorage. >>> st = FileStorage(path) There's not a lot interesting in an empty DB! >>> t = Tracer(path) >>> t.register_oids(0x123456) >>> t.register_oids(1) >>> t.register_oids(0) >>> t.run() >>> t.report() oid 0x00 0 revisions this oid was not defined (no data record for it found) oid 0x01 0 revisions this oid was not defined (no data record for it found) oid 0x123456 0 revisions this oid was not defined (no data record for it found) That didn't tell us much, but does show that the specified oids are sorted into increasing order. Create a root object and try again: >>> db = ZODB.DB(st) # yes, that creates a root object! >>> t = Tracer(path) >>> t.register_oids(0, 1) >>> t.run(); t.report() #doctest: +ELLIPSIS oid 0x00 persistent.mapping.PersistentMapping 1 revision tid 0x... offset=4 ... tid user='' tid description='initial database creation' new revision persistent.mapping.PersistentMapping at 52 oid 0x01 0 revisions this oid was not defined (no data record for it found) So we see oid 0 has been used in our one transaction, and that it was created there, and is a PersistentMapping. 4 is the file offset to the start of the transaction record, and 52 is the file offset to the start of the data record for oid 0 within this transaction. Because tids are timestamps too, the "..." parts vary across runs. The initial line for a tid actually looks like this: tid 0x035748597843b877 offset=4 2004-08-20 20:41:28.187000 Let's add a BTree and try again: >>> root = db.open().root() >>> root['tree'] = OOBTree() >>> txn.get().note('added an OOBTree') >>> txn.get().commit() >>> t = Tracer(path) >>> t.register_oids(0, 1) >>> t.run(); t.report() #doctest: +ELLIPSIS oid 0x00 persistent.mapping.PersistentMapping 2 revisions tid 0x... offset=4 ... tid user='' tid description='initial database creation' new revision persistent.mapping.PersistentMapping at 52 tid 0x... offset=162 ... tid user='' tid description='added an OOBTree' new revision persistent.mapping.PersistentMapping at 201 references 0x01 BTrees.OOBTree.OOBTree at 201 oid 0x01 BTrees.OOBTree.OOBTree 1 revision tid 0x... offset=162 ... tid user='' tid description='added an OOBTree' new revision BTrees.OOBTree.OOBTree at 350 referenced by 0x00 persistent.mapping.PersistentMapping at 201 So there are two revisions of oid 0 now, and the second references oid 1. One more, storing a reference in the BTree back to the root object: >>> tree = root['tree'] >>> tree['root'] = root >>> txn.get().note('circling back to the root') >>> txn.get().commit() >>> t = Tracer(path) >>> t.register_oids(0, 1, 2) >>> t.run(); t.report() #doctest: +ELLIPSIS oid 0x00 persistent.mapping.PersistentMapping 2 revisions tid 0x... offset=4 ... tid user='' tid description='initial database creation' new revision persistent.mapping.PersistentMapping at 52 tid 0x... offset=162 ... tid user='' tid description='added an OOBTree' new revision persistent.mapping.PersistentMapping at 201 references 0x01 BTrees.OOBTree.OOBTree at 201 tid 0x... offset=429 ... tid user='' tid description='circling back to the root' referenced by 0x01 BTrees.OOBTree.OOBTree at 477 oid 0x01 BTrees.OOBTree.OOBTree 2 revisions tid 0x... offset=162 ... tid user='' tid description='added an OOBTree' new revision BTrees.OOBTree.OOBTree at 350 referenced by 0x00 persistent.mapping.PersistentMapping at 201 tid 0x... offset=429 ... tid user='' tid description='circling back to the root' new revision BTrees.OOBTree.OOBTree at 477 references 0x00 persistent.mapping.PersistentMapping at 477 oid 0x02 0 revisions this oid was not defined (no data record for it found) Note that we didn't create any new object there (oid 2 is still unused), we just made oid 1 refer to oid 0. Therefore there's a new "new revision" line in the output for oid 1. Note that there's also new output for oid 0, even though the root object didn't change: we got new output for oid 0 because it's a traced oid and the new transaction made a new reference *to* it. Since the Trace constructor takes only one argument, the only sane thing you can do to make it fail is to give it a path to a file that doesn't exist: >>> Tracer('/eiruowieuu/lsijflfjlsijflsdf/eurowiurowioeuri/908479287.fs') Traceback (most recent call last): ... ValueError: must specify an existing FileStorage You get the same kind of exception if you pass it a path to an existing directory (the path must be to a file, not a directory): >>> import os >>> Tracer(os.path.dirname(__file__)) Traceback (most recent call last): ... ValueError: must specify an existing FileStorage Clean up. >>> st.close() >>> st.cleanup() # remove .fs, .index, etc """ import doctest def test_suite(): return doctest.DocTestSuite() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/RecoveryStorage.py0000644000175000017500000001704512214017464022454 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """More recovery and iterator tests.""" import transaction from transaction import Transaction from ZODB.tests.IteratorStorage import IteratorDeepCompare from ZODB.tests.StorageTestBase import MinPO, snooze from ZODB import DB from ZODB.serialize import referencesf import time class RecoveryStorage(IteratorDeepCompare): # Requires a setUp() that creates a self._dst destination storage def checkSimpleRecovery(self): oid = self._storage.new_oid() revid = self._dostore(oid, data=11) revid = self._dostore(oid, revid=revid, data=12) revid = self._dostore(oid, revid=revid, data=13) self._dst.copyTransactionsFrom(self._storage) self.compare(self._storage, self._dst) def checkRestoreAcrossPack(self): db = DB(self._storage) c = db.open() r = c.root() obj = r["obj1"] = MinPO(1) transaction.commit() obj = r["obj2"] = MinPO(1) transaction.commit() self._dst.copyTransactionsFrom(self._storage) self._dst.pack(time.time(), referencesf) self._undo(self._storage.undoInfo()[0]['id']) # copy the final transaction manually. even though there # was a pack, the restore() ought to succeed. it = self._storage.iterator() # Get the last transaction and its record iterator. Record iterators # can't be accessed out-of-order, so we need to do this in a bit # complicated way: for final in it: records = list(final) self._dst.tpc_begin(final, final.tid, final.status) for r in records: self._dst.restore(r.oid, r.tid, r.data, '', r.data_txn, final) self._dst.tpc_vote(final) self._dst.tpc_finish(final) def checkPackWithGCOnDestinationAfterRestore(self): raises = self.assertRaises db = DB(self._storage) conn = db.open() root = conn.root() root.obj = obj1 = MinPO(1) txn = transaction.get() txn.note('root -> obj') txn.commit() root.obj.obj = obj2 = MinPO(2) txn = transaction.get() txn.note('root -> obj -> obj') txn.commit() del root.obj txn = transaction.get() txn.note('root -X->') txn.commit() # Now copy the transactions to the destination self._dst.copyTransactionsFrom(self._storage) # Now pack the destination. snooze() self._dst.pack(time.time(), referencesf) # And check to see that the root object exists, but not the other # objects. data, serial = self._dst.load(root._p_oid, '') raises(KeyError, self._dst.load, obj1._p_oid, '') raises(KeyError, self._dst.load, obj2._p_oid, '') def checkRestoreWithMultipleObjectsInUndoRedo(self): from ZODB.FileStorage import FileStorage # Undo creates backpointers in (at least) FileStorage. ZODB 3.2.1 # FileStorage._data_find() had an off-by-8 error, neglecting to # account for the size of the backpointer when searching a # transaction with multiple data records. The results were # unpredictable. For example, it could raise a Python exception # due to passing a negative offset to file.seek(), or could # claim that a transaction didn't have data for an oid despite # that it actually did. # # The former failure mode was seen in real life, in a ZRS secondary # doing recovery. On my box today, the second failure mode is # what happens in this test (with an unpatched _data_find, of # course). Note that the error can only "bite" if more than one # data record is in a transaction, and the oid we're looking for # follows at least one data record with a backpointer. # # Unfortunately, _data_find() is a low-level implementation detail, # and this test does some horrid white-box abuse to test it. is_filestorage = isinstance(self._storage, FileStorage) db = DB(self._storage) c = db.open() r = c.root() # Create some objects. r["obj1"] = MinPO(1) r["obj2"] = MinPO(1) transaction.commit() # Add x attributes to them. r["obj1"].x = 'x1' r["obj2"].x = 'x2' transaction.commit() r = db.open().root() self.assertEquals(r["obj1"].x, 'x1') self.assertEquals(r["obj2"].x, 'x2') # Dirty tricks. if is_filestorage: obj1_oid = r["obj1"]._p_oid obj2_oid = r["obj2"]._p_oid # This will be the offset of the next transaction, which # will contain two backpointers. pos = self._storage.getSize() # Undo the attribute creation. info = self._storage.undoInfo() tid = info[0]['id'] t = Transaction() self._storage.tpc_begin(t) oids = self._storage.undo(tid, t) self._storage.tpc_vote(t) self._storage.tpc_finish(t) r = db.open().root() self.assertRaises(AttributeError, getattr, r["obj1"], 'x') self.assertRaises(AttributeError, getattr, r["obj2"], 'x') if is_filestorage: # _data_find should find data records for both objects in that # transaction. Without the patch, the second assert failed # (it claimed it couldn't find a data record for obj2) on my # box, but other failure modes were possible. self.assert_(self._storage._data_find(pos, obj1_oid, '') > 0) self.assert_(self._storage._data_find(pos, obj2_oid, '') > 0) # The offset of the next ("redo") transaction. pos = self._storage.getSize() # Undo the undo (restore the attributes). info = self._storage.undoInfo() tid = info[0]['id'] t = Transaction() self._storage.tpc_begin(t) oids = self._storage.undo(tid, t) self._storage.tpc_vote(t) self._storage.tpc_finish(t) r = db.open().root() self.assertEquals(r["obj1"].x, 'x1') self.assertEquals(r["obj2"].x, 'x2') if is_filestorage: # Again _data_find should find both objects in this txn, and # again the second assert failed on my box. self.assert_(self._storage._data_find(pos, obj1_oid, '') > 0) self.assert_(self._storage._data_find(pos, obj2_oid, '') > 0) # Indirectly provoke .restore(). .restore in turn indirectly # provokes _data_find too, but not usefully for the purposes of # the specific bug this test aims at: copyTransactionsFrom() uses # storage iterators that chase backpointers themselves, and # return the data they point at instead. The result is that # _data_find didn't actually see anything dangerous in this # part of the test. self._dst.copyTransactionsFrom(self._storage) self.compare(self._storage, self._dst) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/blob_importexport.txt0000644000175000017500000000457512214017464023276 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## Import/export support for blob data =================================== Set up: >>> import ZODB.blob, transaction >>> from persistent.mapping import PersistentMapping We need an database with an undoing blob supporting storage: >>> database1 = ZODB.DB(create_storage('1')) >>> database2 = ZODB.DB(create_storage('2')) Create our root object for database1: >>> connection1 = database1.open() >>> root1 = connection1.root() Put a couple blob objects in our database1 and on the filesystem: >>> import time, os >>> nothing = transaction.begin() >>> data1 = 'x'*100000 >>> blob1 = ZODB.blob.Blob() >>> blob1.open('w').write(data1) >>> data2 = 'y'*100000 >>> blob2 = ZODB.blob.Blob() >>> blob2.open('w').write(data2) >>> d = PersistentMapping({'blob1':blob1, 'blob2':blob2}) >>> root1['blobdata'] = d >>> transaction.commit() Export our blobs from a database1 connection: >>> conn = root1['blobdata']._p_jar >>> oid = root1['blobdata']._p_oid >>> exportfile = 'export' >>> nothing = connection1.exportFile(oid, exportfile) Import our exported data into database2: >>> connection2 = database2.open() >>> root2 = connection2.root() >>> nothing = transaction.begin() >>> data = root2._p_jar.importFile(exportfile) >>> root2['blobdata'] = data >>> transaction.commit() Make sure our data exists: >>> items1 = root1['blobdata'] >>> items2 = root2['blobdata'] >>> bool(items1.keys() == items2.keys()) True >>> items1['blob1'].open().read() == items2['blob1'].open().read() True >>> items1['blob2'].open().read() == items2['blob2'].open().read() True >>> transaction.get().abort() .. cleanup >>> database1.close() >>> database2.close() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testpersistentclass.py0000644000175000017500000000375412214017464023461 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import sys import transaction import unittest import ZODB.persistentclass import ZODB.tests.util def class_with_circular_ref_to_self(): """ It should be possible for a class to reger to itself. >>> class C: ... __metaclass__ = ZODB.persistentclass.PersistentMetaClass >>> C.me = C >>> db = ZODB.tests.util.DB() >>> conn = db.open() >>> conn.root()['C'] = C >>> transaction.commit() >>> conn2 = db.open() >>> C2 = conn2.root()['C'] >>> c = C2() >>> c.__class__.__name__ 'C' """ # XXX need to update files to get newer testing package class FakeModule: def __init__(self, name, dict): self.__dict__ = dict self.__name__ = name def setUp(test): ZODB.tests.util.setUp(test) test.globs['some_database'] = ZODB.tests.util.DB() module = FakeModule('ZODB.persistentclass_txt', test.globs) sys.modules[module.__name__] = module def tearDown(test): test.globs['some_database'].close() del sys.modules['ZODB.persistentclass_txt'] ZODB.tests.util.tearDown(test) def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite("../persistentclass.txt", setUp=setUp, tearDown=tearDown), doctest.DocTestSuite(setUp=setUp, tearDown=tearDown), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/util.py0000644000175000017500000001035212214017464020300 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Conventience function for creating test databases """ from __future__ import with_statement from ZODB.MappingStorage import DB import atexit import os import persistent import sys import tempfile import time import transaction import unittest import warnings import ZODB.utils import zope.testing.setupstack def setUp(test, name='test'): transaction.abort() d = tempfile.mkdtemp(prefix=name) zope.testing.setupstack.register(test, zope.testing.setupstack.rmtree, d) zope.testing.setupstack.register( test, setattr, tempfile, 'tempdir', tempfile.tempdir) tempfile.tempdir = d zope.testing.setupstack.register(test, os.chdir, os.getcwd()) os.chdir(d) zope.testing.setupstack.register(test, transaction.abort) tearDown = zope.testing.setupstack.tearDown class TestCase(unittest.TestCase): def setUp(self): self.globs = {} name = self.__class__.__name__ mname = getattr(self, '_TestCase__testMethodName', '') if mname: name += '-' + mname setUp(self, name) tearDown = tearDown def pack(db): db.pack(time.time()+1) class P(persistent.Persistent): def __init__(self, name=None): self.name = name def __repr__(self): return 'P(%s)' % self.name class MininalTestLayer: __bases__ = () __module__ = '' def __init__(self, name): self.__name__ = name def setUp(self): self.here = os.getcwd() self.tmp = tempfile.mkdtemp(self.__name__, dir=os.getcwd()) os.chdir(self.tmp) # sigh. tearDown isn't called when a layer is run in a sub-process. atexit.register(clean, self.tmp) def tearDown(self): os.chdir(self.here) zope.testing.setupstack.rmtree(self.tmp) testSetUp = testTearDown = lambda self: None def clean(tmp): if os.path.isdir(tmp): zope.testing.setupstack.rmtree(tmp) class AAAA_Test_Runner_Hack(unittest.TestCase): """Hack to work around a bug in the test runner. The first later (lex sorted) is run first in the foreground """ layer = MininalTestLayer('!no tests here!') def testNothing(self): pass def assert_warning(category, func, warning_text=''): if sys.version_info < (2, 6): return func() # Can't use catch_warnings :( with warnings.catch_warnings(record=True) as w: warnings.simplefilter('default') result = func() for warning in w: if ((warning.category is category) and (warning_text in str(warning.message))): return result raise AssertionError(w) def assert_deprecated(func, warning_text=''): return assert_warning(DeprecationWarning, func, warning_text) def wait(func=None, timeout=30): if func is None: return lambda f: wait(f, timeout) for i in xrange(int(timeout*100)): if func(): return time.sleep(.01) raise AssertionError def store(storage, oid, value='x', serial=ZODB.utils.z64): if not isinstance(oid, str): oid = ZODB.utils.p64(oid) if not isinstance(serial, str): serial = ZODB.utils.p64(serial) t = transaction.get() storage.tpc_begin(t) storage.store(oid, serial, value, '', t) storage.tpc_vote(t) storage.tpc_finish(t) def mess_with_time(test=None, globs=None, now=1278864701.5): now = [now] def faux_time(): now[0] += 1 return now[0] if test is None and globs is not None: # sigh faux_time.globs = globs test = faux_time import time zope.testing.setupstack.register(test, setattr, time, 'time', time.time) time.time = faux_time zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/MVCCMappingStorage.py0000644000175000017500000001137412214017464022721 0ustar arnauarnau############################################################################## # # Copyright (c) Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """An extension of MappingStorage that depends on polling. Each Connection has its own view of the database. Polling updates each connection's view. """ import ZODB.utils import ZODB.POSException from ZODB.interfaces import IMVCCStorage from ZODB.MappingStorage import MappingStorage from zope.interface import implements class MVCCMappingStorage(MappingStorage): implements(IMVCCStorage) def __init__(self, name="MVCC Mapping Storage"): MappingStorage.__init__(self, name=name) # _polled_tid contains the transaction ID at the last poll. self._polled_tid = '' self._data_snapshot = None # {oid->(state, tid)} self._main_lock_acquire = self._lock_acquire self._main_lock_release = self._lock_release def new_instance(self): """Returns a storage instance that is a view of the same data. """ inst = MVCCMappingStorage(name=self.__name__) # All instances share the same OID data, transaction log, commit lock, # and OID sequence. inst._data = self._data inst._transactions = self._transactions inst._commit_lock = self._commit_lock inst.new_oid = self.new_oid inst.pack = self.pack inst._main_lock_acquire = self._lock_acquire inst._main_lock_release = self._lock_release return inst @ZODB.utils.locked(MappingStorage.opened) def sync(self, force=False): self._data_snapshot = None def release(self): pass @ZODB.utils.locked(MappingStorage.opened) def load(self, oid, version=''): assert not version, "Versions are not supported" if self._data_snapshot is None: self.poll_invalidations() info = self._data_snapshot.get(oid) if info: return info raise ZODB.POSException.POSKeyError(oid) def poll_invalidations(self): """Poll the storage for changes by other connections. """ # prevent changes to _transactions and _data during analysis self._main_lock_acquire() try: if self._transactions: new_tid = self._transactions.maxKey() else: new_tid = '' # Copy the current data into a snapshot. This is obviously # very inefficient for large storages, but it's good for # tests. self._data_snapshot = {} for oid, tid_data in self._data.items(): if tid_data: tid = tid_data.maxKey() self._data_snapshot[oid] = tid_data[tid], tid if self._polled_tid: if not self._transactions.has_key(self._polled_tid): # This connection is so old that we can no longer enumerate # all the changes. self._polled_tid = new_tid return None changed_oids = set() for tid, txn in self._transactions.items( self._polled_tid, new_tid, excludemin=True, excludemax=False): if txn.status == 'p': # This transaction has been packed, so it is no longer # possible to enumerate all changed oids. self._polled_tid = new_tid return None if tid == self._ltid: # ignore the transaction committed by this connection continue changed_oids.update(txn.data.keys()) finally: self._main_lock_release() self._polled_tid = new_tid return list(changed_oids) def tpc_finish(self, transaction, func = lambda tid: None): self._data_snapshot = None MappingStorage.tpc_finish(self, transaction, func) def tpc_abort(self, transaction): self._data_snapshot = None MappingStorage.tpc_abort(self, transaction) def pack(self, t, referencesf, gc=True): # prevent all concurrent commits during packing self._commit_lock.acquire() try: MappingStorage.pack(self, t, referencesf, gc) finally: self._commit_lock.release() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testMVCCMappingStorage.py0000644000175000017500000001437612214017464023626 0ustar arnauarnau############################################################################## # # Copyright (c) Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from persistent.mapping import PersistentMapping import transaction from ZODB.DB import DB from ZODB.tests.MVCCMappingStorage import MVCCMappingStorage import ZODB.blob import ZODB.tests.testblob from ZODB.tests import ( BasicStorage, HistoryStorage, IteratorStorage, MTStorage, PackableStorage, RevisionStorage, StorageTestBase, Synchronization, ) class MVCCTests: def checkCrossConnectionInvalidation(self): # Verify connections see updated state at txn boundaries. # This will fail if the Connection doesn't poll for changes. db = DB(self._storage) try: c1 = db.open(transaction.TransactionManager()) r1 = c1.root() r1['myobj'] = 'yes' c2 = db.open(transaction.TransactionManager()) r2 = c2.root() self.assert_('myobj' not in r2) c1.transaction_manager.commit() self.assert_('myobj' not in r2) c2.sync() self.assert_('myobj' in r2) self.assert_(r2['myobj'] == 'yes') finally: db.close() def checkCrossConnectionIsolation(self): # Verify MVCC isolates connections. # This will fail if Connection doesn't poll for changes. db = DB(self._storage) try: c1 = db.open() r1 = c1.root() r1['alpha'] = PersistentMapping() r1['gamma'] = PersistentMapping() transaction.commit() # Open a second connection but don't load root['alpha'] yet c2 = db.open() r2 = c2.root() r1['alpha']['beta'] = 'yes' storage = c1._storage t = transaction.Transaction() t.description = 'isolation test 1' storage.tpc_begin(t) c1.commit(t) storage.tpc_vote(t) storage.tpc_finish(t) # The second connection will now load root['alpha'], but due to # MVCC, it should continue to see the old state. self.assert_(r2['alpha']._p_changed is None) # A ghost self.assert_(not r2['alpha']) self.assert_(r2['alpha']._p_changed == 0) # make root['alpha'] visible to the second connection c2.sync() # Now it should be in sync self.assert_(r2['alpha']._p_changed is None) # A ghost self.assert_(r2['alpha']) self.assert_(r2['alpha']._p_changed == 0) self.assert_(r2['alpha']['beta'] == 'yes') # Repeat the test with root['gamma'] r1['gamma']['delta'] = 'yes' storage = c1._storage t = transaction.Transaction() t.description = 'isolation test 2' storage.tpc_begin(t) c1.commit(t) storage.tpc_vote(t) storage.tpc_finish(t) # The second connection will now load root[3], but due to MVCC, # it should continue to see the old state. self.assert_(r2['gamma']._p_changed is None) # A ghost self.assert_(not r2['gamma']) self.assert_(r2['gamma']._p_changed == 0) # make root[3] visible to the second connection c2.sync() # Now it should be in sync self.assert_(r2['gamma']._p_changed is None) # A ghost self.assert_(r2['gamma']) self.assert_(r2['gamma']._p_changed == 0) self.assert_(r2['gamma']['delta'] == 'yes') finally: db.close() class MVCCMappingStorageTests( StorageTestBase.StorageTestBase, BasicStorage.BasicStorage, HistoryStorage.HistoryStorage, IteratorStorage.ExtendedIteratorStorage, IteratorStorage.IteratorStorage, MTStorage.MTStorage, PackableStorage.PackableStorageWithOptionalGC, RevisionStorage.RevisionStorage, Synchronization.SynchronizedStorage, MVCCTests ): def setUp(self): self._storage = MVCCMappingStorage() def tearDown(self): self._storage.close() def checkLoadBeforeUndo(self): pass # we don't support undo yet checkUndoZombie = checkLoadBeforeUndo def checkTransactionIdIncreases(self): import time from ZODB.utils import newTid from ZODB.TimeStamp import TimeStamp t = transaction.Transaction() self._storage.tpc_begin(t) self._storage.tpc_vote(t) self._storage.tpc_finish(t) # Add a fake transaction transactions = self._storage._transactions self.assertEqual(1, len(transactions)) fake_timestamp = 'zzzzzzzy' # the year 5735 ;-) transactions[fake_timestamp] = transactions.values()[0] # Verify the next transaction comes after the fake transaction t = transaction.Transaction() self._storage.tpc_begin(t) self.assertEqual(self._storage._tid, 'zzzzzzzz') def create_blob_storage(name, blob_dir): s = MVCCMappingStorage(name) return ZODB.blob.BlobStorage(blob_dir, s) def test_suite(): suite = unittest.makeSuite(MVCCMappingStorageTests, 'check') # Note: test_packing doesn't work because even though MVCCMappingStorage # retains history, it does not provide undo methods, so the # BlobStorage wrapper calls _packNonUndoing instead of _packUndoing, # causing blobs to get deleted even though object states are retained. suite.addTest(ZODB.tests.testblob.storage_reusable_suite( 'MVCCMapping', create_blob_storage, test_undo=False, )) return suite if __name__ == "__main__": loader = unittest.TestLoader() loader.testMethodPrefix = "check" unittest.main(testLoader=loader) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testSerialize.py0000644000175000017500000001030312214017464022146 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import cPickle import cStringIO as StringIO import sys import unittest from ZODB import serialize class ClassWithNewargs(int): def __new__(cls, value): return int.__new__(cls, value) def __getnewargs__(self): return int(self), class ClassWithoutNewargs(object): def __init__(self, value): self.value = value def make_pickle(ob): sio = StringIO.StringIO() p = cPickle.Pickler(sio, 1) p.dump(ob) return sio.getvalue() def test_factory(conn, module_name, name): return globals()[name] class SerializerTestCase(unittest.TestCase): # old format: (module, name), None old_style_without_newargs = make_pickle( ((__name__, "ClassWithoutNewargs"), None)) # old format: (module, name), argtuple old_style_with_newargs = make_pickle( ((__name__, "ClassWithNewargs"), (1,))) # new format: klass new_style_without_newargs = make_pickle( ClassWithoutNewargs) # new format: klass, argtuple new_style_with_newargs = make_pickle( (ClassWithNewargs, (1,))) def test_getClassName(self): r = serialize.ObjectReader(factory=test_factory) eq = self.assertEqual eq(r.getClassName(self.old_style_with_newargs), __name__ + ".ClassWithNewargs") eq(r.getClassName(self.new_style_with_newargs), __name__ + ".ClassWithNewargs") eq(r.getClassName(self.old_style_without_newargs), __name__ + ".ClassWithoutNewargs") eq(r.getClassName(self.new_style_without_newargs), __name__ + ".ClassWithoutNewargs") def test_getGhost(self): # Use a TestObjectReader since we need _get_class() to be # implemented; otherwise this is just a BaseObjectReader. class TestObjectReader(serialize.ObjectReader): # A production object reader would optimize this, but we # don't need to in a test def _get_class(self, module, name): __import__(module) return getattr(sys.modules[module], name) r = TestObjectReader(factory=test_factory) g = r.getGhost(self.old_style_with_newargs) self.assert_(isinstance(g, ClassWithNewargs)) self.assertEqual(g, 1) g = r.getGhost(self.old_style_without_newargs) self.assert_(isinstance(g, ClassWithoutNewargs)) g = r.getGhost(self.new_style_with_newargs) self.assert_(isinstance(g, ClassWithNewargs)) g = r.getGhost(self.new_style_without_newargs) self.assert_(isinstance(g, ClassWithoutNewargs)) def test_myhasattr(self): class OldStyle: bar = "bar" def __getattr__(self, name): if name == "error": raise ValueError("whee!") else: raise AttributeError(name) class NewStyle(object): bar = "bar" def _raise(self): raise ValueError("whee!") error = property(_raise) self.assertRaises(ValueError, serialize.myhasattr, OldStyle(), "error") self.assertRaises(ValueError, serialize.myhasattr, NewStyle(), "error") self.assert_(serialize.myhasattr(OldStyle(), "bar")) self.assert_(serialize.myhasattr(NewStyle(), "bar")) self.assert_(not serialize.myhasattr(OldStyle(), "rat")) self.assert_(not serialize.myhasattr(NewStyle(), "rat")) def test_suite(): suite = unittest.makeSuite(SerializerTestCase) suite.addTest(doctest.DocTestSuite("ZODB.serialize")) return suite zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/ReadOnlyStorage.py0000644000175000017500000000413212214017464022364 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from ZODB.POSException import ReadOnlyError, Unsupported import transaction class ReadOnlyStorage: def _create_data(self): # test a read-only storage that already has some data self.oids = {} for i in range(10): oid = self._storage.new_oid() revid = self._dostore(oid) self.oids[oid] = revid def _make_readonly(self): self._storage.close() self.open(read_only=True) self.assert_(self._storage.isReadOnly()) def checkReadMethods(self): self._create_data() self._make_readonly() # Note that this doesn't check _all_ read methods. for oid in self.oids.keys(): data, revid = self._storage.load(oid, '') self.assertEqual(revid, self.oids[oid]) # Storages without revisions may not have loadSerial(). try: _data = self._storage.loadSerial(oid, revid) self.assertEqual(data, _data) except Unsupported: pass def checkWriteMethods(self): self._make_readonly() self.assertRaises(ReadOnlyError, self._storage.new_oid) t = transaction.Transaction() self.assertRaises(ReadOnlyError, self._storage.tpc_begin, t) self.assertRaises(ReadOnlyError, self._storage.store, '\000' * 8, None, '', '', t) self.assertRaises(ReadOnlyError, self._storage.undo, '\000' * 8, t) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testCache.py0000644000175000017500000003673712214017464021245 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """A few simple tests of the public cache API. Each DB Connection has a separate PickleCache. The Cache serves two purposes. It acts like a memo for unpickling. It also keeps recent objects in memory under the assumption that they may be used again. """ from persistent.cPickleCache import PickleCache from persistent import Persistent from persistent.mapping import PersistentMapping from ZODB.tests.MinPO import MinPO from ZODB.utils import p64 import doctest import gc import sys import threading import transaction import unittest import ZODB import ZODB.MappingStorage import ZODB.tests.util class CacheTestBase(ZODB.tests.util.TestCase): def setUp(self): ZODB.tests.util.TestCase.setUp(self) store = ZODB.MappingStorage.MappingStorage() self.db = ZODB.DB(store, cache_size = self.CACHE_SIZE) self.conns = [] def tearDown(self): self.db.close() ZODB.tests.util.TestCase.tearDown(self) CACHE_SIZE = 20 def noodle_new_connection(self): """Do some reads and writes on a new connection.""" c = self.db.open() self.conns.append(c) self.noodle_connection(c) def noodle_connection(self, c): r = c.root() i = len(self.conns) d = r.get(i) if d is None: d = r[i] = PersistentMapping() transaction.commit() for i in range(15): o = d.get(i) if o is None: o = d[i] = MinPO(i) o.value += 1 transaction.commit() # CantGetRidOfMe is used by checkMinimizeTerminates. make_trouble = True class CantGetRidOfMe(MinPO): def __init__(self, value): MinPO.__init__(self, value) self.an_attribute = 42 def __del__(self): # Referencing an attribute of self causes self to be # loaded into the cache again, which also resurrects # self. if make_trouble: self.an_attribute class DBMethods(CacheTestBase): def setUp(self): CacheTestBase.setUp(self) for i in range(4): self.noodle_new_connection() def checkCacheDetail(self): for name, count in self.db.cacheDetail(): self.assert_(isinstance(name, str)) self.assert_(isinstance(count, int)) def checkCacheExtremeDetail(self): expected = ['conn_no', 'id', 'oid', 'rc', 'klass', 'state'] for dict in self.db.cacheExtremeDetail(): for k, v in dict.items(): self.assert_(k in expected) # TODO: not really sure how to do a black box test of the cache. # Should the full sweep and minimize calls always remove things? def checkFullSweep(self): old_size = self.db.cacheSize() self.db.cacheFullSweep() new_size = self.db.cacheSize() self.assert_(new_size < old_size, "%s < %s" % (old_size, new_size)) def checkMinimize(self): old_size = self.db.cacheSize() self.db.cacheMinimize() new_size = self.db.cacheSize() self.assert_(new_size < old_size, "%s < %s" % (old_size, new_size)) def checkMinimizeTerminates(self): # This is tricky. cPickleCache had a case where it could get into # an infinite loop, but we don't want the test suite to hang # if this bug reappears. So this test spawns a thread to run the # dangerous operation, and the main thread complains if the worker # thread hasn't finished in 30 seconds (arbitrary, but way more # than enough). In that case, the worker thread will continue # running forever (until killed externally), but at least the # test suite will move on. # # The bug was triggered by having a persistent object whose __del__ # method references an attribute of the object. An attempt to # ghostify such an object will clear the attribute, and if the # cache also releases the last Python reference to the object then # (due to ghostifying it), the __del__ method gets invoked. # Referencing the attribute loads the object again, and also # puts it back into the cPickleCache. If the cache implementation # isn't looking out for this, it can get into an infinite loop # then, endlessly trying to ghostify an object that in turn keeps # unghostifying itself again. class Worker(threading.Thread): def __init__(self, testcase): threading.Thread.__init__(self) self.testcase = testcase def run(self): global make_trouble # Make CantGetRidOfMe.__del__ dangerous. make_trouble = True conn = self.testcase.conns[0] r = conn.root() d = r[1] for i in range(len(d)): d[i] = CantGetRidOfMe(i) transaction.commit() self.testcase.db.cacheMinimize() # Defang the nasty objects. Else, because they're # immortal now, they hang around and create trouble # for subsequent tests. make_trouble = False self.testcase.db.cacheMinimize() w = Worker(self) w.start() w.join(30) if w.isAlive(): self.fail("cacheMinimize still running after 30 seconds -- " "almost certainly in an infinite loop") # TODO: don't have an explicit test for incrgc, because the # connection and database call it internally. # Same for the get and invalidate methods. def checkLRUitems(self): # get a cache c = self.conns[0]._cache c.lru_items() def checkClassItems(self): c = self.conns[0]._cache c.klass_items() class LRUCacheTests(CacheTestBase): def checkLRU(self): # verify the LRU behavior of the cache dataset_size = 5 CACHE_SIZE = dataset_size*2+1 # a cache big enough to hold the objects added in two # transactions, plus the root object self.db.setCacheSize(CACHE_SIZE) c = self.db.open() r = c.root() l = {} # the root is the only thing in the cache, because all the # other objects are new self.assertEqual(len(c._cache), 1) # run several transactions for t in range(5): for i in range(dataset_size): l[(t,i)] = r[i] = MinPO(i) transaction.commit() # commit() will register the objects, placing them in the # cache. at the end of commit, the cache will be reduced # down to CACHE_SIZE items if len(l)>CACHE_SIZE: self.assertEqual(c._cache.ringlen(), CACHE_SIZE) for i in range(dataset_size): # Check objects added in the first two transactions. # They must all be ghostified. self.assertEqual(l[(0,i)]._p_changed, None) self.assertEqual(l[(1,i)]._p_changed, None) # Check objects added in the last two transactions. # They must all still exist in memory, but have # had their changes flushed self.assertEqual(l[(3,i)]._p_changed, 0) self.assertEqual(l[(4,i)]._p_changed, 0) # Of the objects added in the middle transaction, most # will have been ghostified. There is one cache slot # that may be occupied by either one of those objects or # the root, depending on precise order of access. We do # not bother to check this def checkSize(self): self.assertEqual(self.db.cacheSize(), 0) self.assertEqual(self.db.cacheDetailSize(), []) CACHE_SIZE = 10 self.db.setCacheSize(CACHE_SIZE) CONNS = 3 for i in range(CONNS): self.noodle_new_connection() self.assertEquals(self.db.cacheSize(), CACHE_SIZE * CONNS) details = self.db.cacheDetailSize() self.assertEquals(len(details), CONNS) for d in details: self.assertEquals(d['ngsize'], CACHE_SIZE) # The assertion below is non-sensical # The (poorly named) cache size is a target for non-ghosts. # The cache *usually* contains non-ghosts, so that the # size normally exceeds the target size. #self.assertEquals(d['size'], CACHE_SIZE) def checkDetail(self): CACHE_SIZE = 10 self.db.setCacheSize(CACHE_SIZE) CONNS = 3 for i in range(CONNS): self.noodle_new_connection() gc.collect() # Obscure: The above gc.collect call is necessary to make this test # pass. # # This test then only works because the order of computations # and object accesses in the "noodle" calls is such that the # persistent mapping containing the MinPO objects is # deactivated before the MinPO objects. # # - Without the gc call, the cache will contain ghost MinPOs # and the check of the MinPO count below will fail. That's # because the counts returned by cacheDetail include ghosts. # # - If the mapping object containing the MinPOs isn't # deactivated, there will be one fewer non-ghost MinPO and # the test will fail anyway. # # This test really needs to be thought through and documented # better. for klass, count in self.db.cacheDetail(): if klass.endswith('MinPO'): self.assertEqual(count, CONNS * CACHE_SIZE) if klass.endswith('PersistentMapping'): # one root per connection self.assertEqual(count, CONNS) for details in self.db.cacheExtremeDetail(): # one 'details' dict per object if details['klass'].endswith('PersistentMapping'): self.assertEqual(details['state'], None) else: self.assert_(details['klass'].endswith('MinPO')) self.assertEqual(details['state'], 0) # The cache should never hold an unreferenced ghost. if details['state'] is None: # i.e., it's a ghost self.assert_(details['rc'] > 0) class StubDataManager: def setklassstate(self, object): pass class StubObject(Persistent): pass class CacheErrors(unittest.TestCase): def setUp(self): self.jar = StubDataManager() self.cache = PickleCache(self.jar) def checkGetBogusKey(self): self.assertEqual(self.cache.get(p64(0)), None) try: self.cache[12] except KeyError: pass else: self.fail("expected KeyError") try: self.cache[12] = 12 except TypeError: pass else: self.fail("expected TyepError") try: del self.cache[12] except TypeError: pass else: self.fail("expected TypeError") def checkBogusObject(self): def add(key, obj): self.cache[key] = obj nones = sys.getrefcount(None) key = p64(2) # value isn't persistent self.assertRaises(TypeError, add, key, 12) o = StubObject() # o._p_oid == None self.assertRaises(TypeError, add, key, o) o._p_oid = p64(3) self.assertRaises(ValueError, add, key, o) o._p_oid = key # o._p_jar == None self.assertRaises(Exception, add, key, o) o._p_jar = self.jar self.cache[key] = o # make sure it can be added multiple times self.cache[key] = o # same object, different keys self.assertRaises(ValueError, add, p64(0), o) self.assertEqual(sys.getrefcount(None), nones) def checkTwoCaches(self): jar2 = StubDataManager() cache2 = PickleCache(jar2) o = StubObject() key = o._p_oid = p64(1) o._p_jar = jar2 cache2[key] = o try: self.cache[key] = o except ValueError: pass else: self.fail("expected ValueError because object already in cache") def checkReadOnlyAttrsWhenCached(self): o = StubObject() key = o._p_oid = p64(1) o._p_jar = self.jar self.cache[key] = o try: o._p_oid = p64(2) except ValueError: pass else: self.fail("expect that you can't change oid of cached object") try: del o._p_jar except ValueError: pass else: self.fail("expect that you can't delete jar of cached object") def checkTwoObjsSameOid(self): # Try to add two distinct objects with the same oid to the cache. # This has always been an error, but the error message prior to # ZODB 3.2.6 didn't make sense. This test verifies that (a) an # exception is raised; and, (b) the error message is the intended # one. obj1 = StubObject() key = obj1._p_oid = p64(1) obj1._p_jar = self.jar self.cache[key] = obj1 obj2 = StubObject() obj2._p_oid = key obj2._p_jar = self.jar try: self.cache[key] = obj2 except ValueError, detail: self.assertEqual(str(detail), "A different object already has the same oid") else: self.fail("two objects with the same oid should have failed") def check_basic_cache_size_estimation(): """Make sure the basic accounting is correct: >>> import ZODB.MappingStorage >>> db = ZODB.MappingStorage.DB() >>> conn = db.open() The cache is empty initially: >>> conn._cache.total_estimated_size 0 We force the root to be loaded and the cache grows: >>> getattr(conn.root, 'z', None) >>> conn._cache.total_estimated_size 64 We add some data and the cache grows: >>> conn.root.z = ZODB.tests.util.P('x'*100) >>> import transaction >>> transaction.commit() >>> conn._cache.total_estimated_size 320 Loading the objects in another connection gets the same sizes: >>> conn2 = db.open() >>> conn2._cache.total_estimated_size 0 >>> getattr(conn2.root, 'x', None) >>> conn2._cache.total_estimated_size 128 >>> _ = conn2.root.z.name >>> conn2._cache.total_estimated_size 320 If we deactivate, the size goes down: >>> conn2.root.z._p_deactivate() >>> conn2._cache.total_estimated_size 128 Loading data directly, rather than through traversal updates the cache size correctly: >>> conn3 = db.open() >>> _ = conn3.get(conn2.root.z._p_oid).name >>> conn3._cache.total_estimated_size 192 """ def test_suite(): s = unittest.makeSuite(DBMethods, 'check') s.addTest(unittest.makeSuite(LRUCacheTests, 'check')) s.addTest(unittest.makeSuite(CacheErrors, 'check')) s.addTest(doctest.DocTestSuite()) return s zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testConfig.py0000644000175000017500000001461612214017464021437 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import tempfile import unittest import transaction import ZODB.config import ZODB.tests.util from ZODB.POSException import ReadOnlyError class ConfigTestBase(ZODB.tests.util.TestCase): def _opendb(self, s): return ZODB.config.databaseFromString(s) def tearDown(self): ZODB.tests.util.TestCase.tearDown(self) if getattr(self, "storage", None) is not None: self.storage.cleanup() def _test(self, s): db = self._opendb(s) self.storage = db._storage # Do something with the database to make sure it works cn = db.open() rt = cn.root() rt["test"] = 1 transaction.commit() db.close() class ZODBConfigTest(ConfigTestBase): def test_map_config1(self): self._test( """ """) def test_map_config2(self): self._test( """ cache-size 1000 """) def test_file_config1(self): path = tempfile.mktemp() self._test( """ path %s """ % path) def test_file_config2(self): path = tempfile.mktemp() cfg = """ path %s create false read-only true """ % path self.assertRaises(ReadOnlyError, self._test, cfg) def test_demo_config(self): cfg = """ name foo """ self._test(cfg) class ZEOConfigTest(ConfigTestBase): def test_zeo_config(self): # We're looking for a port that doesn't exist so a # connection attempt will fail. Instead of elaborate # logic to loop over a port calculation, we'll just pick a # simple "random", likely to not-exist port number and add # an elaborate comment explaining this instead. Go ahead, # grep for 9. from ZEO.ClientStorage import ClientDisconnected import ZConfig from ZODB.config import getDbSchema from StringIO import StringIO cfg = """ server localhost:56897 wait false """ config, handle = ZConfig.loadConfigFile(getDbSchema(), StringIO(cfg)) self.assertEqual(config.database[0].config.storage.config.blob_dir, None) self.assertRaises(ClientDisconnected, self._test, cfg) cfg = """ blob-dir blobs server localhost:56897 wait false """ config, handle = ZConfig.loadConfigFile(getDbSchema(), StringIO(cfg)) self.assertEqual(config.database[0].config.storage.config.blob_dir, 'blobs') self.assertRaises(ClientDisconnected, self._test, cfg) def database_xrefs_config(): r""" >>> db = ZODB.config.databaseFromString( ... "\n\n\n\n") >>> db.xrefs True >>> db = ZODB.config.databaseFromString( ... "\nallow-implicit-cross-references true\n" ... "\n\n\n") >>> db.xrefs True >>> db = ZODB.config.databaseFromString( ... "\nallow-implicit-cross-references false\n" ... "\n\n\n") >>> db.xrefs False """ def multi_atabases(): r"""If there are multiple codb sections -> multidatabase >>> db = ZODB.config.databaseFromString(''' ... ... ... ... ... ... ... ... ... ... database-name Bar ... ... ... ... ''') >>> sorted(db.databases) ['', 'Bar', 'foo'] >>> db.database_name '' >>> db.databases[db.database_name] is db True >>> db.databases['foo'] is not db True >>> db.databases['Bar'] is not db True >>> db.databases['Bar'] is not db.databases['foo'] True Can't have repeats: >>> ZODB.config.databaseFromString(''' ... ... ... ... ... ... ... ... ... ... ... ... ... ''') # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ConfigurationSyntaxError: section names must not be re-used within the same container:'1' (line 9) >>> ZODB.config.databaseFromString(''' ... ... ... ... ... ... ... ... ... ''') # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ValueError: database_name '' already in databases """ def test_suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite( setUp=ZODB.tests.util.setUp, tearDown=ZODB.tests.util.tearDown)) suite.addTest(unittest.makeSuite(ZODBConfigTest)) suite.addTest(unittest.makeSuite(ZEOConfigTest)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/blob_connection.txt0000644000175000017500000000536212214017464022654 0ustar arnauarnauConnection support for Blobs tests ================================== Connections handle Blobs specially. To demonstrate that, we first need a Blob with some data: >>> from ZODB.interfaces import IBlob >>> from ZODB.blob import Blob >>> import transaction >>> blob = Blob() >>> data = blob.open("w") >>> data.write("I'm a happy Blob.") >>> data.close() We also need a database with a blob supporting storage. (We're going to use FileStorage rather than MappingStorage here because we will want ``loadBefore`` for one of our examples.) >>> blob_storage = create_storage() >>> from ZODB.DB import DB >>> database = DB(blob_storage) Putting a Blob into a Connection works like every other object: >>> connection = database.open() >>> root = connection.root() >>> root['myblob'] = blob >>> transaction.commit() We can also commit a transaction that seats a blob into place without calling the blob's open method: >>> nothing = transaction.begin() >>> anotherblob = Blob() >>> root['anotherblob'] = anotherblob >>> nothing = transaction.commit() Getting stuff out of there works similarly: >>> transaction2 = transaction.TransactionManager() >>> connection2 = database.open(transaction_manager=transaction2) >>> root = connection2.root() >>> blob2 = root['myblob'] >>> IBlob.providedBy(blob2) True >>> blob2.open("r").read() "I'm a happy Blob." >>> transaction2.abort() MVCC also works. >>> transaction3 = transaction.TransactionManager() >>> connection3 = database.open(transaction_manager=transaction3) >>> f = connection.root()['myblob'].open('w') >>> f.write('I am an ecstatic Blob.') >>> f.close() >>> transaction.commit() >>> connection3.root()['myblob'].open('r').read() "I'm a happy Blob." >>> transaction2.abort() >>> transaction3.abort() >>> connection2.close() >>> connection3.close() You can't put blobs into a database that has uses a Non-Blob-Storage, though: >>> from ZODB.MappingStorage import MappingStorage >>> no_blob_storage = MappingStorage() >>> database2 = DB(no_blob_storage) >>> connection2 = database2.open(transaction_manager=transaction2) >>> root = connection2.root() >>> root['myblob'] = Blob() >>> transaction2.commit() # doctest: +ELLIPSIS Traceback (most recent call last): ... Unsupported: Storing Blobs in is not supported. >>> transaction2.abort() >>> connection2.close() After testing this, we don't need the storage directory and databases anymore: >>> transaction.abort() >>> connection.close() >>> database.close() >>> database2.close() >>> blob_storage.close() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/TransactionalUndoStorage.py0000644000175000017500000006677112214017464024320 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Check undo(). Any storage that supports undo() must pass these tests. """ import time import types from persistent import Persistent import transaction from transaction import Transaction from ZODB import POSException from ZODB.serialize import referencesf from ZODB.utils import p64 from ZODB import DB from ZODB.tests.MinPO import MinPO from ZODB.tests.StorageTestBase import zodb_pickle, zodb_unpickle ZERO = '\0'*8 class C(Persistent): pass def snooze(): # In Windows, it's possible that two successive time.time() calls return # the same value. Tim guarantees that time never runs backwards. You # usually want to call this before you pack a storage, or must make other # guarantees about increasing timestamps. now = time.time() while now == time.time(): time.sleep(0.1) def listeq(L1, L2): """Return True if L1.sort() == L2.sort()""" c1 = L1[:] c2 = L2[:] c1.sort() c2.sort() return c1 == c2 class TransactionalUndoStorage: def _transaction_begin(self): self.__serials = {} def _transaction_store(self, oid, rev, data, vers, trans): r = self._storage.store(oid, rev, data, vers, trans) if r: if type(r) == types.StringType: self.__serials[oid] = r else: for oid, serial in r: self.__serials[oid] = serial def _transaction_vote(self, trans): r = self._storage.tpc_vote(trans) if r: for oid, serial in r: self.__serials[oid] = serial def _transaction_newserial(self, oid): return self.__serials[oid] def _multi_obj_transaction(self, objs): newrevs = {} t = Transaction() self._storage.tpc_begin(t) self._transaction_begin() for oid, rev, data in objs: self._transaction_store(oid, rev, data, '', t) newrevs[oid] = None self._transaction_vote(t) self._storage.tpc_finish(t) for oid in newrevs.keys(): newrevs[oid] = self._transaction_newserial(oid) return newrevs def _iterate(self): """Iterate over the storage in its final state.""" # This is testing that the iterator() code works correctly. # The hasattr() guards against ZEO, which doesn't support iterator. if not hasattr(self._storage, "iterator"): return iter = self._storage.iterator() for txn in iter: for rec in txn: pass def _begin_undos_vote(self, t, *tids): self._storage.tpc_begin(t) oids = [] for tid in tids: undo_result = self._storage.undo(tid, t) if undo_result: oids.extend(undo_result[1]) oids.extend(oid for (oid, _) in self._storage.tpc_vote(t) or ()) return oids def undo(self, tid, note): t = Transaction() t.note(note) oids = self._begin_undos_vote(t, tid) self._storage.tpc_finish(t) return oids def checkSimpleTransactionalUndo(self): eq = self.assertEqual oid = self._storage.new_oid() revid = self._dostore(oid, data=MinPO(23)) revid = self._dostore(oid, revid=revid, data=MinPO(24)) revid = self._dostore(oid, revid=revid, data=MinPO(25)) info = self._storage.undoInfo() # Now start an undo transaction self._undo(info[0]["id"], [oid], note="undo1") data, revid = self._storage.load(oid, '') eq(zodb_unpickle(data), MinPO(24)) # Do another one info = self._storage.undoInfo() self._undo(info[2]["id"], [oid], note="undo2") data, revid = self._storage.load(oid, '') eq(zodb_unpickle(data), MinPO(23)) # Try to undo the first record info = self._storage.undoInfo() self._undo(info[4]["id"], [oid], note="undo3") # This should fail since we've undone the object's creation self.assertRaises(KeyError, self._storage.load, oid, '') # And now let's try to redo the object's creation info = self._storage.undoInfo() self._undo(info[0]["id"], [oid]) data, revid = self._storage.load(oid, '') eq(zodb_unpickle(data), MinPO(23)) self._iterate() def checkCreationUndoneGetTid(self): # create an object oid = self._storage.new_oid() self._dostore(oid, data=MinPO(23)) # undo its creation info = self._storage.undoInfo() tid = info[0]['id'] t = Transaction() t.note('undo1') self._begin_undos_vote(t, tid) self._storage.tpc_finish(t) # Check that calling getTid on an uncreated object raises a KeyError # The current version of FileStorage fails this test self.assertRaises(KeyError, self._storage.getTid, oid) def checkUndoCreationBranch1(self): eq = self.assertEqual oid = self._storage.new_oid() revid = self._dostore(oid, data=MinPO(11)) revid = self._dostore(oid, revid=revid, data=MinPO(12)) # Undo the last transaction info = self._storage.undoInfo() self._undo(info[0]['id'], [oid]) data, revid = self._storage.load(oid, '') eq(zodb_unpickle(data), MinPO(11)) # Now from here, we can either redo the last undo, or undo the object # creation. Let's undo the object creation. info = self._storage.undoInfo() self._undo(info[2]['id'], [oid]) self.assertRaises(KeyError, self._storage.load, oid, '') self._iterate() def checkUndoCreationBranch2(self): eq = self.assertEqual oid = self._storage.new_oid() revid = self._dostore(oid, data=MinPO(11)) revid = self._dostore(oid, revid=revid, data=MinPO(12)) # Undo the last transaction info = self._storage.undoInfo() self._undo(info[0]['id'], [oid]) data, revid = self._storage.load(oid, '') eq(zodb_unpickle(data), MinPO(11)) # Now from here, we can either redo the last undo, or undo the object # creation. Let's redo the last undo info = self._storage.undoInfo() self._undo(info[0]['id'], [oid]) data, revid = self._storage.load(oid, '') eq(zodb_unpickle(data), MinPO(12)) self._iterate() def checkTwoObjectUndo(self): eq = self.assertEqual # Convenience p31, p32, p51, p52 = map(zodb_pickle, map(MinPO, (31, 32, 51, 52))) oid1 = self._storage.new_oid() oid2 = self._storage.new_oid() revid1 = revid2 = ZERO # Store two objects in the same transaction t = Transaction() self._storage.tpc_begin(t) self._transaction_begin() self._transaction_store(oid1, revid1, p31, '', t) self._transaction_store(oid2, revid2, p51, '', t) # Finish the transaction self._transaction_vote(t) revid1 = self._transaction_newserial(oid1) revid2 = self._transaction_newserial(oid2) self._storage.tpc_finish(t) eq(revid1, revid2) # Update those same two objects t = Transaction() self._storage.tpc_begin(t) self._transaction_begin() self._transaction_store(oid1, revid1, p32, '', t) self._transaction_store(oid2, revid2, p52, '', t) # Finish the transaction self._transaction_vote(t) revid1 = self._transaction_newserial(oid1) revid2 = self._transaction_newserial(oid2) self._storage.tpc_finish(t) eq(revid1, revid2) # Make sure the objects have the current value data, revid1 = self._storage.load(oid1, '') eq(zodb_unpickle(data), MinPO(32)) data, revid2 = self._storage.load(oid2, '') eq(zodb_unpickle(data), MinPO(52)) # Now attempt to undo the transaction containing two objects info = self._storage.undoInfo() self._undo(info[0]['id'], [oid1, oid2]) data, revid1 = self._storage.load(oid1, '') eq(zodb_unpickle(data), MinPO(31)) data, revid2 = self._storage.load(oid2, '') eq(zodb_unpickle(data), MinPO(51)) self._iterate() def checkTwoObjectUndoAtOnce(self): # Convenience eq = self.assertEqual unless = self.failUnless p30, p31, p32, p50, p51, p52 = map(zodb_pickle, map(MinPO, (30, 31, 32, 50, 51, 52))) oid1 = self._storage.new_oid() oid2 = self._storage.new_oid() revid1 = revid2 = ZERO # Store two objects in the same transaction d = self._multi_obj_transaction([(oid1, revid1, p30), (oid2, revid2, p50), ]) eq(d[oid1], d[oid2]) # Update those same two objects d = self._multi_obj_transaction([(oid1, d[oid1], p31), (oid2, d[oid2], p51), ]) eq(d[oid1], d[oid2]) # Update those same two objects d = self._multi_obj_transaction([(oid1, d[oid1], p32), (oid2, d[oid2], p52), ]) eq(d[oid1], d[oid2]) revid1 = self._transaction_newserial(oid1) revid2 = self._transaction_newserial(oid2) eq(revid1, revid2) # Make sure the objects have the current value data, revid1 = self._storage.load(oid1, '') eq(zodb_unpickle(data), MinPO(32)) data, revid2 = self._storage.load(oid2, '') eq(zodb_unpickle(data), MinPO(52)) # Now attempt to undo the transaction containing two objects info = self._storage.undoInfo() tid = info[0]['id'] tid1 = info[1]['id'] t = Transaction() oids = self._begin_undos_vote(t, tid, tid1) self._storage.tpc_finish(t) # We get the finalization stuff called an extra time: eq(len(oids), 4) unless(oid1 in oids) unless(oid2 in oids) data, revid1 = self._storage.load(oid1, '') eq(zodb_unpickle(data), MinPO(30)) data, revid2 = self._storage.load(oid2, '') eq(zodb_unpickle(data), MinPO(50)) # Now try to undo the one we just did to undo, whew info = self._storage.undoInfo() self._undo(info[0]['id'], [oid1, oid2]) data, revid1 = self._storage.load(oid1, '') eq(zodb_unpickle(data), MinPO(32)) data, revid2 = self._storage.load(oid2, '') eq(zodb_unpickle(data), MinPO(52)) self._iterate() def checkTwoObjectUndoAgain(self): eq = self.assertEqual p31, p32, p33, p51, p52, p53 = map( zodb_pickle, map(MinPO, (31, 32, 33, 51, 52, 53))) # Like the above, but the first revision of the objects are stored in # different transactions. oid1 = self._storage.new_oid() oid2 = self._storage.new_oid() revid1 = self._dostore(oid1, data=p31, already_pickled=1) revid2 = self._dostore(oid2, data=p51, already_pickled=1) # Update those same two objects t = Transaction() self._storage.tpc_begin(t) self._transaction_begin() self._transaction_store(oid1, revid1, p32, '', t) self._transaction_store(oid2, revid2, p52, '', t) # Finish the transaction self._transaction_vote(t) self._storage.tpc_finish(t) revid1 = self._transaction_newserial(oid1) revid2 = self._transaction_newserial(oid2) eq(revid1, revid2) # Now attempt to undo the transaction containing two objects info = self._storage.undoInfo() self._undo(info[0]["id"], [oid1, oid2]) data, revid1 = self._storage.load(oid1, '') eq(zodb_unpickle(data), MinPO(31)) data, revid2 = self._storage.load(oid2, '') eq(zodb_unpickle(data), MinPO(51)) # Like the above, but this time, the second transaction contains only # one object. t = Transaction() self._storage.tpc_begin(t) self._transaction_begin() self._transaction_store(oid1, revid1, p33, '', t) self._transaction_store(oid2, revid2, p53, '', t) # Finish the transaction self._transaction_vote(t) self._storage.tpc_finish(t) revid1 = self._transaction_newserial(oid1) revid2 = self._transaction_newserial(oid2) eq(revid1, revid2) # Update in different transactions revid1 = self._dostore(oid1, revid=revid1, data=MinPO(34)) revid2 = self._dostore(oid2, revid=revid2, data=MinPO(54)) # Now attempt to undo the transaction containing two objects info = self._storage.undoInfo() tid = info[1]['id'] t = Transaction() oids = self._begin_undos_vote(t, tid) self._storage.tpc_finish(t) eq(len(oids), 1) self.failUnless(oid1 in oids) self.failUnless(not oid2 in oids) data, revid1 = self._storage.load(oid1, '') eq(zodb_unpickle(data), MinPO(33)) data, revid2 = self._storage.load(oid2, '') eq(zodb_unpickle(data), MinPO(54)) self._iterate() def checkNotUndoable(self): eq = self.assertEqual # Set things up so we've got a transaction that can't be undone oid = self._storage.new_oid() revid_a = self._dostore(oid, data=MinPO(51)) revid_b = self._dostore(oid, revid=revid_a, data=MinPO(52)) revid_c = self._dostore(oid, revid=revid_b, data=MinPO(53)) # Start the undo info = self._storage.undoInfo() tid = info[1]['id'] t = Transaction() self.assertRaises(POSException.UndoError, self._begin_undos_vote, t, tid) self._storage.tpc_abort(t) # Now have more fun: object1 and object2 are in the same transaction, # which we'll try to undo to, but one of them has since modified in # different transaction, so the undo should fail. oid1 = oid revid1 = revid_c oid2 = self._storage.new_oid() revid2 = ZERO p81, p82, p91, p92 = map(zodb_pickle, map(MinPO, (81, 82, 91, 92))) t = Transaction() self._storage.tpc_begin(t) self._transaction_begin() self._transaction_store(oid1, revid1, p81, '', t) self._transaction_store(oid2, revid2, p91, '', t) self._transaction_vote(t) self._storage.tpc_finish(t) revid1 = self._transaction_newserial(oid1) revid2 = self._transaction_newserial(oid2) eq(revid1, revid2) # Make sure the objects have the expected values data, revid_11 = self._storage.load(oid1, '') eq(zodb_unpickle(data), MinPO(81)) data, revid_22 = self._storage.load(oid2, '') eq(zodb_unpickle(data), MinPO(91)) eq(revid_11, revid1) eq(revid_22, revid2) # Now modify oid2 revid2 = self._dostore(oid2, revid=revid2, data=MinPO(92)) self.assertNotEqual(revid1, revid2) self.assertNotEqual(revid2, revid_22) info = self._storage.undoInfo() tid = info[1]['id'] t = Transaction() self.assertRaises(POSException.UndoError, self._begin_undos_vote, t, tid) self._storage.tpc_abort(t) self._iterate() def checkTransactionalUndoAfterPack(self): # bwarsaw Date: Thu Mar 28 21:04:43 2002 UTC # This is a test which should provoke the underlying bug in # transactionalUndo() on a standby storage. If our hypothesis # is correct, the bug is in FileStorage, and is caused by # encoding the file position in the `id' field of the undoLog # information. Note that Full just encodes the tid, but this # is a problem for FileStorage (we have a strategy for fixing # this). # So, basically, this makes sure that undo info doesn't depend # on file positions. We change the file positions in an undo # record by packing. # Add a few object revisions oid = '\0'*8 revid0 = self._dostore(oid, data=MinPO(50)) revid1 = self._dostore(oid, revid=revid0, data=MinPO(51)) snooze() packtime = time.time() snooze() # time.time() now distinct from packtime revid2 = self._dostore(oid, revid=revid1, data=MinPO(52)) self._dostore(oid, revid=revid2, data=MinPO(53)) # Now get the undo log info = self._storage.undoInfo() self.assertEqual(len(info), 4) tid = info[0]['id'] # Now pack just the initial revision of the object. We need the # second revision otherwise we won't be able to undo the third # revision! self._storage.pack(packtime, referencesf) # Make some basic assertions about the undo information now info2 = self._storage.undoInfo() self.assertEqual(len(info2), 2) # And now attempt to undo the last transaction t = Transaction() oids = self._begin_undos_vote(t, tid) self._storage.tpc_finish(t) self.assertEqual(len(oids), 1) self.assertEqual(oids[0], oid) data, revid = self._storage.load(oid, '') # The object must now be at the second state self.assertEqual(zodb_unpickle(data), MinPO(52)) self._iterate() def checkTransactionalUndoAfterPackWithObjectUnlinkFromRoot(self): eq = self.assertEqual db = DB(self._storage) conn = db.open() root = conn.root() o1 = C() o2 = C() root['obj'] = o1 o1.obj = o2 txn = transaction.get() txn.note('o1 -> o2') txn.commit() now = packtime = time.time() while packtime <= now: packtime = time.time() o3 = C() o2.obj = o3 txn = transaction.get() txn.note('o1 -> o2 -> o3') txn.commit() o1.obj = o3 txn = transaction.get() txn.note('o1 -> o3') txn.commit() log = self._storage.undoLog() eq(len(log), 4) for entry in zip(log, ('o1 -> o3', 'o1 -> o2 -> o3', 'o1 -> o2', 'initial database creation')): eq(entry[0]['description'], entry[1]) self._storage.pack(packtime, referencesf) log = self._storage.undoLog() for entry in zip(log, ('o1 -> o3', 'o1 -> o2 -> o3')): eq(entry[0]['description'], entry[1]) tid = log[0]['id'] db.undo(tid) txn = transaction.get() txn.note('undo') txn.commit() # undo does a txn-undo, but doesn't invalidate conn.sync() log = self._storage.undoLog() for entry in zip(log, ('undo', 'o1 -> o3', 'o1 -> o2 -> o3')): eq(entry[0]['description'], entry[1]) eq(o1.obj, o2) eq(o1.obj.obj, o3) self._iterate() def checkPackAfterUndoDeletion(self): db = DB(self._storage) cn = db.open() root = cn.root() pack_times = [] def set_pack_time(): pack_times.append(time.time()) snooze() root["key0"] = MinPO(0) root["key1"] = MinPO(1) root["key2"] = MinPO(2) txn = transaction.get() txn.note("create 3 keys") txn.commit() set_pack_time() del root["key1"] txn = transaction.get() txn.note("delete 1 key") txn.commit() set_pack_time() root._p_deactivate() cn.sync() self.assert_(listeq(root.keys(), ["key0", "key2"])) L = db.undoInfo() db.undo(L[0]["id"]) txn = transaction.get() txn.note("undo deletion") txn.commit() set_pack_time() root._p_deactivate() cn.sync() self.assert_(listeq(root.keys(), ["key0", "key1", "key2"])) for t in pack_times: self._storage.pack(t, referencesf) root._p_deactivate() cn.sync() self.assert_(listeq(root.keys(), ["key0", "key1", "key2"])) for i in range(3): obj = root["key%d" % i] self.assertEqual(obj.value, i) root.items() self._inter_pack_pause() def checkPackAfterUndoManyTimes(self): db = DB(self._storage) cn = db.open() rt = cn.root() rt["test"] = MinPO(1) transaction.commit() rt["test2"] = MinPO(2) transaction.commit() rt["test"] = MinPO(3) txn = transaction.get() txn.note("root of undo") txn.commit() packtimes = [] for i in range(10): L = db.undoInfo() db.undo(L[0]["id"]) txn = transaction.get() txn.note("undo %d" % i) txn.commit() rt._p_deactivate() cn.sync() self.assertEqual(rt["test"].value, i % 2 and 3 or 1) self.assertEqual(rt["test2"].value, 2) packtimes.append(time.time()) snooze() for t in packtimes: self._storage.pack(t, referencesf) cn.sync() # TODO: Is _cache supposed to have a clear() method, or not? # cn._cache.clear() # The last undo set the value to 3 and pack should # never change that. self.assertEqual(rt["test"].value, 3) self.assertEqual(rt["test2"].value, 2) self._inter_pack_pause() def _inter_pack_pause(self): # DirectoryStorage needs a pause between packs, # most other storages dont. pass def checkTransactionalUndoIterator(self): # check that data_txn set in iterator makes sense if not hasattr(self._storage, "iterator"): return s = self._storage BATCHES = 4 OBJECTS = 4 orig = [] for i in range(BATCHES): t = Transaction() tid = p64(i + 1) s.tpc_begin(t, tid) for j in range(OBJECTS): oid = s.new_oid() obj = MinPO(i * OBJECTS + j) s.store(oid, None, zodb_pickle(obj), '', t) orig.append((tid, oid)) s.tpc_vote(t) s.tpc_finish(t) orig = [(tid, oid, s.getTid(oid)) for tid, oid in orig] i = 0 for tid, oid, revid in orig: self._dostore(oid, revid=revid, data=MinPO(revid), description="update %s" % i) # Undo the OBJECTS transactions that modified objects created # in the ith original transaction. def undo(i): info = s.undoInfo() t = Transaction() s.tpc_begin(t) base = i * OBJECTS + i for j in range(OBJECTS): tid = info[base + j]['id'] s.undo(tid, t) s.tpc_vote(t) s.tpc_finish(t) for i in range(BATCHES): undo(i) # There are now (2 + OBJECTS) * BATCHES transactions: # BATCHES original transactions, followed by # OBJECTS * BATCHES modifications, followed by # BATCHES undos transactions = s.iterator() eq = self.assertEqual for i in range(BATCHES): txn = transactions.next() tid = p64(i + 1) eq(txn.tid, tid) L1 = [(rec.oid, rec.tid, rec.data_txn) for rec in txn] L2 = [(oid, revid, None) for _tid, oid, revid in orig if _tid == tid] eq(L1, L2) for i in range(BATCHES * OBJECTS): txn = transactions.next() eq(len([rec for rec in txn if rec.data_txn is None]), 1) for i in range(BATCHES): txn = transactions.next() # The undos are performed in reverse order. otid = p64(BATCHES - i) L1 = [(rec.oid, rec.data_txn) for rec in txn] L2 = [(oid, otid) for _tid, oid, revid in orig if _tid == otid] L1.sort() L2.sort() eq(L1, L2) self.assertRaises(StopIteration, transactions.next) def checkUndoLogMetadata(self): # test that the metadata is correct in the undo log t = transaction.get() t.note('t1') t.setExtendedInfo('k2','this is transaction metadata') t.setUser('u3',path='p3') db = DB(self._storage) conn = db.open() root = conn.root() o1 = C() root['obj'] = o1 txn = transaction.get() txn.commit() l = self._storage.undoLog() self.assertEqual(len(l),2) d = l[0] self.assertEqual(d['description'],'t1') self.assertEqual(d['k2'],'this is transaction metadata') self.assertEqual(d['user_name'],'p3 u3') # A common test body for index tests on undoInfo and undoLog. Before # ZODB 3.4, they always returned a wrong number of results (one too # few _or_ too many, depending on how they were called). def _exercise_info_indices(self, method_name): db = DB(self._storage) info_func = getattr(db, method_name) cn = db.open() rt = cn.root() # Do some transactions. for key in "abcdefghijklmnopqrstuvwxyz": rt[key] = ord(key) transaction.commit() # 26 letters = 26 transactions, + the hidden transaction to make # the root object, == 27 expected. allofem = info_func(0, 100000) self.assertEqual(len(allofem), 27) # Asking for no more than 100000 should do the same. redundant = info_func(last=-1000000) self.assertEqual(allofem, redundant) # By default, we should get only 20 back. default = info_func() self.assertEqual(len(default), 20) # And they should be the most recent 20. self.assertEqual(default, allofem[:20]) # If we ask for only one, we should get only the most recent. fresh = info_func(last=1) self.assertEqual(len(fresh), 1) self.assertEqual(fresh[0], allofem[0]) # Another way of asking for only the most recent. redundant = info_func(last=-1) self.assertEqual(fresh, redundant) # Try a slice that doesn't start at 0. oddball = info_func(first=11, last=17) self.assertEqual(len(oddball), 17-11) self.assertEqual(oddball, allofem[11 : 11+len(oddball)]) # And another way to spell the same thing. redundant = info_func(first=11, last=-6) self.assertEqual(oddball, redundant) cn.close() # Caution: don't close db; the framework does that. If you close # it here, the ZODB tests still work, but the ZRS RecoveryStorageTests # fail (closing the DB here in those tests closes the ZRS primary # before a ZRS secondary even starts, and then the latter can't # find a server to recover from). def checkIndicesInUndoInfo(self): self._exercise_info_indices("undoInfo") def checkIndicesInUndoLog(self): self._exercise_info_indices("undoLog") zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/blobstorage_packing.txt0000644000175000017500000001166612214017464023522 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## Packing support for blob data ============================= Set up: >>> from ZODB.MappingStorage import MappingStorage >>> from ZODB.serialize import referencesf >>> from ZODB.blob import Blob, BlobStorage >>> from ZODB import utils >>> from ZODB.DB import DB >>> import transaction >>> storagefile = 'Data.fs' >>> blob_dir = 'blobs' A helper method to assure a unique timestamp across multiple platforms: >>> from ZODB.tests.testblob import new_time UNDOING ======= See blob_packing.txt. NON-UNDOING =========== We need an database with a NON-undoing blob supporting storage: >>> base_storage = MappingStorage('storage') >>> blob_storage = BlobStorage(blob_dir, base_storage) >>> database = DB(blob_storage) Create our root object: >>> connection1 = database.open() >>> root = connection1.root() Put some revisions of a blob object in our database and on the filesystem: >>> import time, os >>> tids = [] >>> times = [] >>> nothing = transaction.begin() >>> times.append(new_time()) >>> blob = Blob() >>> blob.open('w').write('this is blob data 0') >>> root['blob'] = blob >>> transaction.commit() >>> tids.append(blob_storage._tid) >>> nothing = transaction.begin() >>> times.append(new_time()) >>> root['blob'].open('w').write('this is blob data 1') >>> transaction.commit() >>> tids.append(blob_storage._tid) >>> nothing = transaction.begin() >>> times.append(new_time()) >>> root['blob'].open('w').write('this is blob data 2') >>> transaction.commit() >>> tids.append(blob_storage._tid) >>> nothing = transaction.begin() >>> times.append(new_time()) >>> root['blob'].open('w').write('this is blob data 3') >>> transaction.commit() >>> tids.append(blob_storage._tid) >>> nothing = transaction.begin() >>> times.append(new_time()) >>> root['blob'].open('w').write('this is blob data 4') >>> transaction.commit() >>> tids.append(blob_storage._tid) >>> oid = root['blob']._p_oid >>> fns = [ blob_storage.fshelper.getBlobFilename(oid, x) for x in tids ] >>> [ os.path.exists(x) for x in fns ] [True, True, True, True, True] Get our blob filenames for this oid. >>> fns = [ blob_storage.fshelper.getBlobFilename(oid, x) for x in tids ] Do a pack to the slightly before the first revision was written: >>> packtime = times[0] >>> blob_storage.pack(packtime, referencesf) >>> [ os.path.exists(x) for x in fns ] [False, False, False, False, True] Do a pack to now: >>> packtime = new_time() >>> blob_storage.pack(packtime, referencesf) >>> [ os.path.exists(x) for x in fns ] [False, False, False, False, True] Delete the object and do a pack, it should get rid of the most current revision as well as the entire directory: >>> nothing = transaction.begin() >>> del root['blob'] >>> transaction.commit() >>> packtime = new_time() >>> blob_storage.pack(packtime, referencesf) >>> [ os.path.exists(x) for x in fns ] [False, False, False, False, False] >>> os.path.exists(os.path.split(fns[0])[0]) False Avoiding parallel packs ======================= Blob packing (similar to FileStorage) can only be run once at a time. For this, a flag (_blobs_pack_is_in_progress) is set. If the pack method is called while this flag is set, it will refuse to perform another pack, until the flag is reset: >>> blob_storage._blobs_pack_is_in_progress False >>> blob_storage._blobs_pack_is_in_progress = True >>> blob_storage.pack(packtime, referencesf) Traceback (most recent call last): BlobStorageError: Already packing >>> blob_storage._blobs_pack_is_in_progress = False >>> blob_storage.pack(packtime, referencesf) We can also see, that the flag is set during the pack, by leveraging the knowledge that the underlying storage's pack method is also called: >>> def dummy_pack(time, ref): ... print "_blobs_pack_is_in_progress =", ... print blob_storage._blobs_pack_is_in_progress ... return base_pack(time, ref) >>> base_pack = base_storage.pack >>> base_storage.pack = dummy_pack >>> blob_storage.pack(packtime, referencesf) _blobs_pack_is_in_progress = True >>> blob_storage._blobs_pack_is_in_progress False >>> base_storage.pack = base_pack zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/BasicStorage.py0000644000175000017500000003453012214017464021675 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Run the basic tests for a storage as described in the official storage API The most complete and most out-of-date description of the interface is: http://www.zope.org/Documentation/Developer/Models/ZODB/ZODB_Architecture_Storage_Interface_Info.html All storages should be able to pass these tests. """ from __future__ import with_statement from ZODB import POSException from ZODB.tests.MinPO import MinPO from ZODB.tests.StorageTestBase import zodb_unpickle, zodb_pickle from ZODB.tests.StorageTestBase import handle_serials import threading import time import transaction import zope.interface import zope.interface.verify ZERO = '\0'*8 class BasicStorage: def checkBasics(self): self.assertEqual(self._storage.lastTransaction(), '\0'*8) t = transaction.Transaction() self._storage.tpc_begin(t) self.assertRaises(POSException.StorageTransactionError, self._storage.tpc_begin, t) # Aborting is easy self._storage.tpc_abort(t) # Test a few expected exceptions when we're doing operations giving a # different Transaction object than the one we've begun on. self._storage.tpc_begin(t) self.assertRaises( POSException.StorageTransactionError, self._storage.store, ZERO, ZERO, '', '', transaction.Transaction()) self.assertRaises( POSException.StorageTransactionError, self._storage.store, ZERO, 1, '2', '', transaction.Transaction()) self.assertRaises( POSException.StorageTransactionError, self._storage.tpc_vote, transaction.Transaction()) self._storage.tpc_abort(t) def checkSerialIsNoneForInitialRevision(self): eq = self.assertEqual oid = self._storage.new_oid() txn = transaction.Transaction() self._storage.tpc_begin(txn) # Use None for serial. Don't use _dostore() here because that coerces # serial=None to serial=ZERO. r1 = self._storage.store(oid, None, zodb_pickle(MinPO(11)), '', txn) r2 = self._storage.tpc_vote(txn) self._storage.tpc_finish(txn) newrevid = handle_serials(oid, r1, r2) data, revid = self._storage.load(oid, '') value = zodb_unpickle(data) eq(value, MinPO(11)) eq(revid, newrevid) def checkStore(self): revid = ZERO newrevid = self._dostore(revid=None) # Finish the transaction. self.assertNotEqual(newrevid, revid) def checkStoreAndLoad(self): eq = self.assertEqual oid = self._storage.new_oid() self._dostore(oid=oid, data=MinPO(7)) data, revid = self._storage.load(oid, '') value = zodb_unpickle(data) eq(value, MinPO(7)) # Now do a bunch of updates to an object for i in range(13, 22): revid = self._dostore(oid, revid=revid, data=MinPO(i)) # Now get the latest revision of the object data, revid = self._storage.load(oid, '') eq(zodb_unpickle(data), MinPO(21)) def checkConflicts(self): oid = self._storage.new_oid() revid1 = self._dostore(oid, data=MinPO(11)) self._dostore(oid, revid=revid1, data=MinPO(12)) self.assertRaises(POSException.ConflictError, self._dostore, oid, revid=revid1, data=MinPO(13)) def checkWriteAfterAbort(self): oid = self._storage.new_oid() t = transaction.Transaction() self._storage.tpc_begin(t) self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t) # Now abort this transaction self._storage.tpc_abort(t) # Now start all over again oid = self._storage.new_oid() self._dostore(oid=oid, data=MinPO(6)) def checkAbortAfterVote(self): oid1 = self._storage.new_oid() revid1 = self._dostore(oid=oid1, data=MinPO(-2)) oid = self._storage.new_oid() t = transaction.Transaction() self._storage.tpc_begin(t) self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t) # Now abort this transaction self._storage.tpc_vote(t) self._storage.tpc_abort(t) # Now start all over again oid = self._storage.new_oid() revid = self._dostore(oid=oid, data=MinPO(6)) for oid, revid in [(oid1, revid1), (oid, revid)]: data, _revid = self._storage.load(oid, '') self.assertEqual(revid, _revid) def checkStoreTwoObjects(self): noteq = self.assertNotEqual p31, p32, p51, p52 = map(MinPO, (31, 32, 51, 52)) oid1 = self._storage.new_oid() oid2 = self._storage.new_oid() noteq(oid1, oid2) revid1 = self._dostore(oid1, data=p31) revid2 = self._dostore(oid2, data=p51) noteq(revid1, revid2) revid3 = self._dostore(oid1, revid=revid1, data=p32) revid4 = self._dostore(oid2, revid=revid2, data=p52) noteq(revid3, revid4) def checkGetTid(self): if not hasattr(self._storage, 'getTid'): return eq = self.assertEqual p41, p42 = map(MinPO, (41, 42)) oid = self._storage.new_oid() self.assertRaises(KeyError, self._storage.getTid, oid) # Now store a revision revid1 = self._dostore(oid, data=p41) eq(revid1, self._storage.getTid(oid)) # And another one revid2 = self._dostore(oid, revid=revid1, data=p42) eq(revid2, self._storage.getTid(oid)) def checkLen(self): # len(storage) reports the number of objects. # check it is zero when empty self.assertEqual(len(self._storage),0) # check it is correct when the storage contains two object. # len may also be zero, for storages that do not keep track # of this number self._dostore(data=MinPO(22)) self._dostore(data=MinPO(23)) self.assert_(len(self._storage) in [0,2]) def checkGetSize(self): self._dostore(data=MinPO(25)) size = self._storage.getSize() # The storage API doesn't make any claims about what size # means except that it ought to be printable. str(size) def checkNote(self): oid = self._storage.new_oid() t = transaction.Transaction() self._storage.tpc_begin(t) t.note('this is a test') self._storage.store(oid, ZERO, zodb_pickle(MinPO(5)), '', t) self._storage.tpc_vote(t) self._storage.tpc_finish(t) def checkInterfaces(self): for iface in zope.interface.providedBy(self._storage): zope.interface.verify.verifyObject(iface, self._storage) def checkMultipleEmptyTransactions(self): # There was a bug in handling empty transactions in mapping # storage that caused the commit lock not to be released. :( transaction.begin() t = transaction.get() self._storage.tpc_begin(t) self._storage.tpc_vote(t) self._storage.tpc_finish(t) t.commit() transaction.begin() t = transaction.get() self._storage.tpc_begin(t) # Hung here before self._storage.tpc_vote(t) self._storage.tpc_finish(t) t.commit() def _do_store_in_separate_thread(self, oid, revid, voted): # We'll run the competing trans in a separate thread: thread = threading.Thread(name='T2', target=self._dostore, args=(oid,), kwargs=dict(revid=revid)) thread.setDaemon(True) thread.start() thread.join(.1) return thread def check_checkCurrentSerialInTransaction(self): oid = '\0\0\0\0\0\0\0\xf0' tid = self._dostore(oid) tid2 = self._dostore(oid, revid=tid) data = 'cpersistent\nPersistent\nq\x01.N.' # a simple persistent obj #---------------------------------------------------------------------- # stale read transaction.begin() t = transaction.get() self._storage.tpc_begin(t) try: self._storage.store('\0\0\0\0\0\0\0\xf1', '\0\0\0\0\0\0\0\0', data, '', t) self._storage.checkCurrentSerialInTransaction(oid, tid, t) self._storage.tpc_vote(t) except POSException.ReadConflictError, v: self.assert_(v.oid) == oid self.assert_(v.serials == (tid2, tid)) else: if 0: self.assert_(False, "No conflict error") self._storage.tpc_abort(t) #---------------------------------------------------------------------- # non-stale read, no stress. :) transaction.begin() t = transaction.get() self._storage.tpc_begin(t) self._storage.store('\0\0\0\0\0\0\0\xf2', '\0\0\0\0\0\0\0\0', data, '', t) self._storage.checkCurrentSerialInTransaction(oid, tid2, t) self._storage.tpc_vote(t) self._storage.tpc_finish(t) #---------------------------------------------------------------------- # non-stale read, competition after vote. The competing # transaction must produce a tid > this transaction's tid transaction.begin() t = transaction.get() self._storage.tpc_begin(t) self._storage.store('\0\0\0\0\0\0\0\xf3', '\0\0\0\0\0\0\0\0', data, '', t) self._storage.checkCurrentSerialInTransaction(oid, tid2, t) self._storage.tpc_vote(t) # We'll run the competing trans in a separate thread: thread = self._do_store_in_separate_thread(oid, tid2, True) self._storage.tpc_finish(t) thread.join(33) tid3 = self._storage.load(oid)[1] self.assert_(tid3 > self._storage.load('\0\0\0\0\0\0\0\xf3')[1]) #---------------------------------------------------------------------- # non-stale competing trans after checkCurrentSerialInTransaction transaction.begin() t = transaction.get() self._storage.tpc_begin(t) self._storage.store('\0\0\0\0\0\0\0\xf4', '\0\0\0\0\0\0\0\0', data, '', t) self._storage.checkCurrentSerialInTransaction(oid, tid3, t) thread = self._do_store_in_separate_thread(oid, tid3, False) # There are 2 possibilities: # 1. The store happens before this transaction completes, # in which case, the vote below fails. # 2. The store happens after this trans, in which case, the # tid of the object is greater than this transaction's tid. try: self._storage.tpc_vote(t) except POSException.ReadConflictError: thread.join() # OK :) else: self._storage.tpc_finish(t) thread.join() tid4 = self._storage.load(oid)[1] self.assert_(tid4 > self._storage.load('\0\0\0\0\0\0\0\xf4')[1]) def check_tid_ordering_w_commit(self): # It's important that storages always give a consistent # ordering for revisions, tids. This is most likely to fail # around commit. Here we'll do some basic tests to check this. # We'll use threads to arrange for ordering to go wrong and # verify that a storage gets it right. # First, some initial data. t = transaction.get() self._storage.tpc_begin(t) self._storage.store(ZERO, ZERO, 'x', '', t) self._storage.tpc_vote(t) tids = [] self._storage.tpc_finish(t, lambda tid: tids.append(tid)) # OK, now we'll start a new transaction, take it to finish, # and then block finish while we do some other operations. t = transaction.get() self._storage.tpc_begin(t) self._storage.store(ZERO, tids[0], 'y', '', t) self._storage.tpc_vote(t) to_join = [] def run_in_thread(func): t = threading.Thread(target=func) t.setDaemon(True) t.start() to_join.append(t) started = threading.Event() finish = threading.Event() @run_in_thread def commit(): def callback(tid): started.set() tids.append(tid) finish.wait() self._storage.tpc_finish(t, callback) results = {} started.wait() attempts = [] attempts_cond = threading.Condition() def update_attempts(): with attempts_cond: attempts.append(1) attempts_cond.notifyAll() @run_in_thread def lastTransaction(): update_attempts() results['lastTransaction'] = self._storage.lastTransaction() @run_in_thread def load(): update_attempts() results['load'] = self._storage.load(ZERO, '')[1] expected_attempts = 2 if hasattr(self._storage, 'getTid'): expected_attempts += 1 @run_in_thread def getTid(): update_attempts() results['getTid'] = self._storage.getTid(ZERO) if hasattr(self._storage, 'lastInvalidations'): expected_attempts += 1 @run_in_thread def lastInvalidations(): update_attempts() invals = self._storage.lastInvalidations(1) if invals: results['lastInvalidations'] = invals[0][0] with attempts_cond: while len(attempts) < expected_attempts: attempts_cond.wait() time.sleep(.01) # for good measure :) finish.set() for t in to_join: t.join(1) self.assertEqual(results.pop('load'), tids[1]) self.assertEqual(results.pop('lastTransaction'), tids[1]) for m, tid in results.items(): self.assertEqual(tid, tids[1]) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/RevisionStorage.py0000644000175000017500000001477712214017464022465 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Check loadSerial() on storages that support historical revisions.""" from ZODB.tests.MinPO import MinPO from ZODB.tests.StorageTestBase import zodb_unpickle, zodb_pickle, snooze from ZODB.tests.StorageTestBase import handle_serials from ZODB.utils import p64, u64 import transaction ZERO = '\0'*8 class RevisionStorage: def checkLoadSerial(self): oid = self._storage.new_oid() revid = ZERO revisions = {} for i in range(31, 38): revid = self._dostore(oid, revid=revid, data=MinPO(i)) revisions[revid] = MinPO(i) # Now make sure all the revisions have the correct value for revid, value in revisions.items(): data = self._storage.loadSerial(oid, revid) self.assertEqual(zodb_unpickle(data), value) def checkLoadBefore(self): # Store 10 revisions of one object and then make sure that we # can get all the non-current revisions back. oid = self._storage.new_oid() revs = [] revid = None for i in range(10): # We need to ensure that successive timestamps are at least # two apart, so that a timestamp exists that's unambiguously # between successive timestamps. Each call to snooze() # guarantees that the next timestamp will be at least one # larger (and probably much more than that) than the previous # one. snooze() snooze() revid = self._dostore(oid, revid, data=MinPO(i)) revs.append(self._storage.load(oid, "")) prev = u64(revs[0][1]) for i in range(1, 10): tid = revs[i][1] cur = u64(tid) middle = prev + (cur - prev) // 2 assert prev < middle < cur # else the snooze() trick failed prev = cur t = self._storage.loadBefore(oid, p64(middle)) self.assert_(t is not None) data, start, end = t self.assertEqual(revs[i-1][0], data) self.assertEqual(tid, end) def checkLoadBeforeEdges(self): # Check the edges cases for a non-current load. oid = self._storage.new_oid() self.assertRaises(KeyError, self._storage.loadBefore, oid, p64(0)) revid1 = self._dostore(oid, data=MinPO(1)) self.assertEqual(self._storage.loadBefore(oid, p64(0)), None) self.assertEqual(self._storage.loadBefore(oid, revid1), None) cur = p64(u64(revid1) + 1) data, start, end = self._storage.loadBefore(oid, cur) self.assertEqual(zodb_unpickle(data), MinPO(1)) self.assertEqual(start, revid1) self.assertEqual(end, None) revid2 = self._dostore(oid, revid=revid1, data=MinPO(2)) data, start, end = self._storage.loadBefore(oid, cur) self.assertEqual(zodb_unpickle(data), MinPO(1)) self.assertEqual(start, revid1) self.assertEqual(end, revid2) def checkLoadBeforeOld(self): # Look for a very old revision. With the BaseStorage implementation # this should require multple history() calls. oid = self._storage.new_oid() revs = [] revid = None for i in range(50): revid = self._dostore(oid, revid, data=MinPO(i)) revs.append(revid) data, start, end = self._storage.loadBefore(oid, revs[12]) self.assertEqual(zodb_unpickle(data), MinPO(11)) self.assertEqual(start, revs[11]) self.assertEqual(end, revs[12]) # Unsure: Is it okay to assume everyone testing against RevisionStorage # implements undo? def checkLoadBeforeUndo(self): # Do several transactions then undo them. oid = self._storage.new_oid() revid = None for i in range(5): revid = self._dostore(oid, revid, data=MinPO(i)) revs = [] for i in range(4): info = self._storage.undoInfo() tid = info[0]["id"] # Always undo the most recent txn, so the value will # alternate between 3 and 4. self._undo(tid, note="undo %d" % i) revs.append(self._storage.load(oid, "")) prev_tid = None for i, (data, tid) in enumerate(revs): t = self._storage.loadBefore(oid, p64(u64(tid) + 1)) self.assertEqual(data, t[0]) self.assertEqual(tid, t[1]) if prev_tid: self.assert_(prev_tid < t[1]) prev_tid = t[1] if i < 3: self.assertEqual(revs[i+1][1], t[2]) else: self.assertEqual(None, t[2]) def checkLoadBeforeConsecutiveTids(self): eq = self.assertEqual oid = self._storage.new_oid() def helper(tid, revid, x): data = zodb_pickle(MinPO(x)) t = transaction.Transaction() try: self._storage.tpc_begin(t, p64(tid)) r1 = self._storage.store(oid, revid, data, '', t) # Finish the transaction r2 = self._storage.tpc_vote(t) newrevid = handle_serials(oid, r1, r2) self._storage.tpc_finish(t) except: self._storage.tpc_abort(t) raise return newrevid revid1 = helper(1, None, 1) revid2 = helper(2, revid1, 2) revid3 = helper(3, revid2, 3) data, start_tid, end_tid = self._storage.loadBefore(oid, p64(2)) eq(zodb_unpickle(data), MinPO(1)) eq(u64(start_tid), 1) eq(u64(end_tid), 2) def checkLoadBeforeCreation(self): eq = self.assertEqual oid1 = self._storage.new_oid() oid2 = self._storage.new_oid() revid1 = self._dostore(oid1) revid2 = self._dostore(oid2) results = self._storage.loadBefore(oid2, revid2) eq(results, None) # TODO: There are other edge cases to handle, including pack. zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testPersistentMapping.py0000644000175000017500000001410512214017464023677 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Verify that PersistentMapping works with old versions of Zope. The comments in PersistentMapping.py address the issue in some detail. The pickled form of a PersistentMapping must use _container to store the actual mapping, because old versions of Zope used this attribute. If the new code doesn't generate pickles that are consistent with the old code, developers will have a hard time testing the new code. """ import unittest import transaction from transaction import Transaction import ZODB from ZODB.MappingStorage import MappingStorage import cPickle import cStringIO import sys # This pickle contains a persistent mapping pickle created from the # old code. pickle = ('((U\x0bPersistenceq\x01U\x11PersistentMappingtq\x02Nt.}q\x03U\n' '_containerq\x04}q\x05U\x07versionq\x06U\x03oldq\x07ss.\n') class PMTests(unittest.TestCase): def checkOldStyleRoot(self): # The Persistence module doesn't exist in Zope3's idea of what ZODB # is, but the global `pickle` references it explicitly. So just # bail if Persistence isn't available. try: import Persistence except ImportError: return # insert the pickle in place of the root s = MappingStorage() t = Transaction() s.tpc_begin(t) s.store('\000' * 8, None, pickle, '', t) s.tpc_vote(t) s.tpc_finish(t) db = ZODB.DB(s) # If the root can be loaded successfully, we should be okay. r = db.open().root() # But make sure it looks like a new mapping self.assert_(hasattr(r, 'data')) self.assert_(not hasattr(r, '_container')) # TODO: This test fails in ZODB 3.3a1. It's making some assumption(s) # about pickles that aren't true. Hard to say when it stopped working, # because this entire test suite hasn't been run for a long time, due to # a mysterious "return None" at the start of the test_suite() function # below. I noticed that when the new checkBackwardCompat() test wasn't # getting run. def TODO_checkNewPicklesAreSafe(self): s = MappingStorage() db = ZODB.DB(s) r = db.open().root() r[1] = 1 r[2] = 2 r[3] = r transaction.commit() # MappingStorage stores serialno + pickle in its _index. root_pickle = s._index['\000' * 8][8:] f = cStringIO.StringIO(root_pickle) u = cPickle.Unpickler(f) klass_info = u.load() klass = find_global(*klass_info[0]) inst = klass.__new__(klass) state = u.load() inst.__setstate__(state) self.assert_(hasattr(inst, '_container')) self.assert_(not hasattr(inst, 'data')) def checkBackwardCompat(self): # Verify that the sanest of the ZODB 3.2 dotted paths still works. from persistent.mapping import PersistentMapping as newPath from ZODB.PersistentMapping import PersistentMapping as oldPath self.assert_(oldPath is newPath) def checkBasicOps(self): from persistent.mapping import PersistentMapping m = PersistentMapping({'x': 1}, a=2, b=3) m['name'] = 'bob' self.assertEqual(m['name'], "bob") self.assertEqual(m.get('name', 42), "bob") self.assert_('name' in m) try: m['fred'] except KeyError: pass else: self.fail("expected KeyError") self.assert_('fred' not in m) self.assertEqual(m.get('fred'), None) self.assertEqual(m.get('fred', 42), 42) keys = m.keys() keys.sort() self.assertEqual(keys, ['a', 'b', 'name', 'x']) values = m.values() values.sort() self.assertEqual(values, [1, 2, 3, 'bob']) items = m.items() items.sort() self.assertEqual(items, [('a', 2), ('b', 3), ('name', 'bob'), ('x', 1)]) keys = list(m.iterkeys()) keys.sort() self.assertEqual(keys, ['a', 'b', 'name', 'x']) values = list(m.itervalues()) values.sort() self.assertEqual(values, [1, 2, 3, 'bob']) items = list(m.iteritems()) items.sort() self.assertEqual(items, [('a', 2), ('b', 3), ('name', 'bob'), ('x', 1)]) # PersistentMapping didn't have an __iter__ method before ZODB 3.4.2. # Check that it plays well now with the Python iteration protocol. def checkIteration(self): from persistent.mapping import PersistentMapping m = PersistentMapping({'x': 1}, a=2, b=3) m['name'] = 'bob' def check(keylist): keylist.sort() self.assertEqual(keylist, ['a', 'b', 'name', 'x']) check(list(m)) check([key for key in m]) i = iter(m) keylist = [] while 1: try: key = i.next() except StopIteration: break keylist.append(key) check(keylist) def find_global(modulename, classname): """Helper for this test suite to get special PersistentMapping""" if classname == "PersistentMapping": class PersistentMapping(object): def __setstate__(self, state): self.__dict__.update(state) return PersistentMapping else: __import__(modulename) mod = sys.modules[modulename] return getattr(mod, classname) def test_suite(): return unittest.makeSuite(PMTests, 'check') if __name__ == "__main__": unittest.main() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testfsIndex.py0000644000175000017500000001507012214017464021625 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import random import unittest from ZODB.fsIndex import fsIndex from ZODB.utils import p64, z64 from ZODB.tests.util import setUp, tearDown class Test(unittest.TestCase): def setUp(self): self.index = fsIndex() for i in range(200): self.index[p64(i * 1000)] = (i * 1000L + 1) def test__del__(self): index = self.index self.assert_(p64(1000) in index) self.assert_(p64(100*1000) in index) del self.index[p64(1000)] del self.index[p64(100*1000)] self.assert_(p64(1000) not in index) self.assert_(p64(100*1000) not in index) for key in list(self.index): del index[key] self.assert_(not index) # Whitebox. Make sure empty buckets are removed self.assert_(not index._data) def testInserts(self): index = self.index for i in range(0,200): self.assertEqual((i,index[p64(i*1000)]), (i,(i*1000L+1))) self.assertEqual(len(index), 200) key=p64(2000) self.assertEqual(index.get(key), 2001) key=p64(2001) self.assertEqual(index.get(key), None) self.assertEqual(index.get(key, ''), '') # self.failUnless(len(index._data) > 1) def testUpdate(self): index = self.index d={} for i in range(200): d[p64(i*1000)]=(i*1000L+1) index.update(d) for i in range(400,600): d[p64(i*1000)]=(i*1000L+1) index.update(d) for i in range(100, 500): d[p64(i*1000)]=(i*1000L+2) index.update(d) self.assertEqual(index.get(p64(2000)), 2001) self.assertEqual(index.get(p64(599000)), 599001) self.assertEqual(index.get(p64(399000)), 399002) self.assertEqual(len(index), 600) def testKeys(self): keys = list(iter(self.index)) keys.sort() for i, k in enumerate(keys): self.assertEqual(k, p64(i * 1000)) keys = list(self.index.iterkeys()) keys.sort() for i, k in enumerate(keys): self.assertEqual(k, p64(i * 1000)) keys = self.index.keys() keys.sort() for i, k in enumerate(keys): self.assertEqual(k, p64(i * 1000)) def testValues(self): values = list(self.index.itervalues()) values.sort() for i, v in enumerate(values): self.assertEqual(v, (i * 1000L + 1)) values = self.index.values() values.sort() for i, v in enumerate(values): self.assertEqual(v, (i * 1000L + 1)) def testItems(self): items = list(self.index.iteritems()) items.sort() for i, item in enumerate(items): self.assertEqual(item, (p64(i * 1000), (i * 1000L + 1))) items = self.index.items() items.sort() for i, item in enumerate(items): self.assertEqual(item, (p64(i * 1000), (i * 1000L + 1))) def testMaxKey(self): index = self.index index.clear() # An empty index should complain. self.assertRaises(ValueError, index.maxKey) # Now build up a tree with random values, and check maxKey at each # step. correct_max = "" # smaller than anything we'll add for i in range(1000): key = p64(random.randrange(100000000)) index[key] = i correct_max = max(correct_max, key) index_max = index.maxKey() self.assertEqual(index_max, correct_max) index.clear() a = '\000\000\000\000\000\001\000\000' b = '\000\000\000\000\000\002\000\000' c = '\000\000\000\000\000\003\000\000' d = '\000\000\000\000\000\004\000\000' index[a] = 1 index[c] = 2 self.assertEqual(index.maxKey(b), a) self.assertEqual(index.maxKey(d), c) self.assertRaises(ValueError, index.maxKey, z64) def testMinKey(self): index = self.index index.clear() # An empty index should complain. self.assertRaises(ValueError, index.minKey) # Now build up a tree with random values, and check minKey at each # step. correct_min = "\xff" * 8 # bigger than anything we'll add for i in range(1000): key = p64(random.randrange(100000000)) index[key] = i correct_min = min(correct_min, key) index_min = index.minKey() self.assertEqual(index_min, correct_min) index.clear() a = '\000\000\000\000\000\001\000\000' b = '\000\000\000\000\000\002\000\000' c = '\000\000\000\000\000\003\000\000' d = '\000\000\000\000\000\004\000\000' index[a] = 1 index[c] = 2 self.assertEqual(index.minKey(b), c) self.assertRaises(ValueError, index.minKey, d) def fsIndex_save_and_load(): """ fsIndex objects now have save methods for saving them to disk in a new format. The fsIndex class has a load class method that can load data. Let's start by creating an fsIndex. We'll bother to allocate the object ids to get multiple buckets: >>> index = fsIndex(dict((p64(i), i) for i in xrange(0, 1<<28, 1<<15))) >>> len(index._data) 4096 Now, we'll save the data to disk and then load it: >>> index.save(42, 'index') Note that we pass a file position, which gets saved with the index data. >>> info = fsIndex.load('index') >>> info['pos'] 42 >>> info['index'].__getstate__() == index.__getstate__() True If we save the data in the old format, we can still read it: >>> import cPickle >>> cPickle.dump(dict(pos=42, index=index), open('old', 'wb'), 1) >>> info = fsIndex.load('old') >>> info['pos'] 42 >>> info['index'].__getstate__() == index.__getstate__() True """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(Test)) suite.addTest(doctest.DocTestSuite(setUp=setUp, tearDown=tearDown)) return suite zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/loggingsupport.py0000644000175000017500000000643712214017464022417 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Support for testing logging code If you want to test that your code generates proper log output, you can create and install a handler that collects output: >>> handler = InstalledHandler('foo.bar') The handler is installed into loggers for all of the names passed. In addition, the logger level is set to 1, which means, log everything. If you want to log less than everything, you can provide a level keyword argument. The level setting effects only the named loggers. Then, any log output is collected in the handler: >>> logging.getLogger('foo.bar').exception('eek') >>> logging.getLogger('foo.bar').info('blah blah') >>> for record in handler.records: ... print record.name, record.levelname ... print ' ', record.getMessage() foo.bar ERROR eek foo.bar INFO blah blah A similar effect can be gotten by just printing the handler: >>> print handler foo.bar ERROR eek foo.bar INFO blah blah After checking the log output, you need to uninstall the handler: >>> handler.uninstall() At which point, the handler won't get any more log output. Let's clear the handler: >>> handler.clear() >>> handler.records [] And then log something: >>> logging.getLogger('foo.bar').info('blah') and, sure enough, we still have no output: >>> handler.records [] $Id: loggingsupport.py 28349 2004-11-06 00:10:32Z tim_one $ """ import logging class Handler(logging.Handler): def __init__(self, *names, **kw): logging.Handler.__init__(self) self.names = names self.records = [] self.setLoggerLevel(**kw) def setLoggerLevel(self, level=1): self.level = level self.oldlevels = {} def emit(self, record): self.records.append(record) def clear(self): del self.records[:] def install(self): for name in self.names: logger = logging.getLogger(name) self.oldlevels[name] = logger.level logger.setLevel(self.level) logger.addHandler(self) def uninstall(self): for name in self.names: logger = logging.getLogger(name) logger.setLevel(self.oldlevels[name]) logger.removeHandler(self) def __str__(self): return '\n'.join( [("%s %s\n %s" % (record.name, record.levelname, '\n'.join([line for line in record.getMessage().split('\n') if line.strip()]) ) ) for record in self.records] ) class InstalledHandler(Handler): def __init__(self, *names): Handler.__init__(self, *names) self.install() zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/blob_consume.txt0000644000175000017500000000721712214017464022167 0ustar arnauarnauConsuming existing files ======================== The ZODB Blob implementation allows to import existing files as Blobs within an O(1) operation we call `consume`:: Let's create a file:: >>> to_import = open('to_import', 'wb') >>> to_import.write("I'm a Blob and I feel fine.") The file *must* be closed before giving it to consumeFile: >>> to_import.close() Now, let's consume this file in a blob by specifying it's name:: >>> from ZODB.blob import Blob >>> blob = Blob() >>> blob.consumeFile('to_import') After the consumeFile operation, the original file has been removed: >>> import os >>> os.path.exists('to_import') False We now can call open on the blob and read and write the data:: >>> blob_read = blob.open('r') >>> blob_read.read() "I'm a Blob and I feel fine." >>> blob_read.close() >>> blob_write = blob.open('w') >>> blob_write.write('I was changed.') >>> blob_write.close() We can not consume a file when there is a reader or writer around for a blob already:: >>> open('to_import', 'wb').write('I am another blob.') >>> blob_read = blob.open('r') >>> blob.consumeFile('to_import') Traceback (most recent call last): BlobError: Already opened for reading. >>> blob_read.close() >>> blob_write = blob.open('w') >>> blob.consumeFile('to_import') Traceback (most recent call last): BlobError: Already opened for writing. >>> blob_write.close() Now, after closing all readers and writers we can consume files again:: >>> blob.consumeFile('to_import') >>> blob_read = blob.open('r') >>> blob_read.read() 'I am another blob.' >>> blob_read.close() Edge cases ========== There are some edge cases what happens when the link() operation fails. We simulate this in different states: Case 1: We don't have uncommitted data, but the link operation fails. We fall back to try a copy/remove operation that is successfull:: >>> open('to_import', 'wb').write('Some data.') >>> def failing_rename(f1, f2): ... import exceptions ... if f1 == 'to_import': ... raise exceptions.OSError("I can't link.") ... os_rename(f1, f2) >>> blob = Blob() >>> os_rename = os.rename >>> os.rename = failing_rename >>> blob.consumeFile('to_import') The blob did not have data before, so it shouldn't have data now:: >>> blob.open('r').read() 'Some data.' Case 2: We don't have uncommitted data and both the link operation and the copy fail. The exception will be re-raised and the target file will not exist:: >>> blob = Blob() >>> import ZODB.utils >>> utils_cp = ZODB.utils.cp >>> def failing_copy(f1, f2): ... import exceptions ... raise exceptions.OSError("I can't copy.") >>> ZODB.utils.cp = failing_copy >>> open('to_import', 'wb').write('Some data.') >>> blob.consumeFile('to_import') Traceback (most recent call last): OSError: I can't copy. The blob did not have data before, so it shouldn't have data now:: >>> blob.open('r').read() '' Case 3: We have uncommitted data, but the link and the copy operations fail. The exception will be re-raised and the target file will exist with the previous uncomitted data:: >>> blob = Blob() >>> blob_writing = blob.open('w') >>> blob_writing.write('Uncommitted data') >>> blob_writing.close() >>> blob.consumeFile('to_import') Traceback (most recent call last): OSError: I can't copy. The blob did existed before and had uncommitted data, this shouldn't have changed:: >>> blob.open('r').read() 'Uncommitted data' >>> os.rename = os_rename >>> ZODB.utils.cp = utils_cp zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testRecover.py0000644000175000017500000001557012214017464021637 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the file storage recovery script.""" import base64 import os import random import sys import unittest import StringIO import ZODB import ZODB.tests.util from ZODB.FileStorage import FileStorage import ZODB.fsrecover from persistent.mapping import PersistentMapping import transaction class RecoverTest(ZODB.tests.util.TestCase): path = None def setUp(self): ZODB.tests.util.TestCase.setUp(self) self.path = 'source.fs' self.storage = FileStorage(self.path) self.populate() self.dest = 'dest.fs' self.recovered = None def tearDown(self): self.storage.close() if self.recovered is not None: self.recovered.close() temp = FileStorage(self.dest) temp.close() ZODB.tests.util.TestCase.tearDown(self) def populate(self): db = ZODB.DB(self.storage) cn = db.open() rt = cn.root() # Create a bunch of objects; the Data.fs is about 100KB. for i in range(50): d = rt[i] = PersistentMapping() transaction.commit() for j in range(50): d[j] = "a" * j transaction.commit() def damage(self, num, size): self.storage.close() # Drop size null bytes into num random spots. for i in range(num): offset = random.randint(0, self.storage._pos - size) f = open(self.path, "a+b") f.seek(offset) f.write("\0" * size) f.close() ITERATIONS = 5 # Run recovery, from self.path to self.dest. Return whatever # recovery printed to stdout, as a string. def recover(self): orig_stdout = sys.stdout faux_stdout = StringIO.StringIO() try: sys.stdout = faux_stdout try: ZODB.fsrecover.recover(self.path, self.dest, verbose=0, partial=True, force=False, pack=1) except SystemExit: raise RuntimeError("recover tried to exit") finally: sys.stdout = orig_stdout return faux_stdout.getvalue() # Caution: because recovery is robust against many kinds of damage, # it's almost impossible for a call to self.recover() to raise an # exception. As a result, these tests may pass even if fsrecover.py # is broken badly. testNoDamage() tries to ensure that at least # recovery doesn't produce any error msgs if the input .fs is in # fact not damaged. def testNoDamage(self): output = self.recover() self.assert_('error' not in output, output) self.assert_('\n0 bytes removed during recovery' in output, output) # Verify that the recovered database is identical to the original. before = file(self.path, 'rb') before_guts = before.read() before.close() after = file(self.dest, 'rb') after_guts = after.read() after.close() self.assertEqual(before_guts, after_guts, "recovery changed a non-damaged .fs file") def testOneBlock(self): for i in range(self.ITERATIONS): self.damage(1, 1024) output = self.recover() self.assert_('error' in output, output) self.recovered = FileStorage(self.dest) self.recovered.close() os.remove(self.path) os.rename(self.dest, self.path) def testFourBlocks(self): for i in range(self.ITERATIONS): self.damage(4, 512) output = self.recover() self.assert_('error' in output, output) self.recovered = FileStorage(self.dest) self.recovered.close() os.remove(self.path) os.rename(self.dest, self.path) def testBigBlock(self): for i in range(self.ITERATIONS): self.damage(1, 32 * 1024) output = self.recover() self.assert_('error' in output, output) self.recovered = FileStorage(self.dest) self.recovered.close() os.remove(self.path) os.rename(self.dest, self.path) def testBadTransaction(self): # Find transaction headers and blast them. L = self.storage.undoLog() r = L[3] tid = base64.decodestring(r["id"] + "\n") pos1 = self.storage._txn_find(tid, 0) r = L[8] tid = base64.decodestring(r["id"] + "\n") pos2 = self.storage._txn_find(tid, 0) self.storage.close() # Overwrite the entire header. f = open(self.path, "a+b") f.seek(pos1 - 50) f.write("\0" * 100) f.close() output = self.recover() self.assert_('error' in output, output) self.recovered = FileStorage(self.dest) self.recovered.close() os.remove(self.path) os.rename(self.dest, self.path) # Overwrite part of the header. f = open(self.path, "a+b") f.seek(pos2 + 10) f.write("\0" * 100) f.close() output = self.recover() self.assert_('error' in output, output) self.recovered = FileStorage(self.dest) self.recovered.close() # Issue 1846: When a transaction had 'c' status (not yet committed), # the attempt to open a temp file to write the trailing bytes fell # into an infinite loop. def testUncommittedAtEnd(self): # Find a transaction near the end. L = self.storage.undoLog() r = L[1] tid = base64.decodestring(r["id"] + "\n") pos = self.storage._txn_find(tid, 0) # Overwrite its status with 'c'. f = open(self.path, "r+b") f.seek(pos + 16) current_status = f.read(1) self.assertEqual(current_status, ' ') f.seek(pos + 16) f.write('c') f.close() # Try to recover. The original bug was that this never completed -- # infinite loop in fsrecover.py. Also, in the ZODB 3.2 line, # reference to an undefined global masked the infinite loop. self.recover() # Verify the destination got truncated. self.assertEqual(os.path.getsize(self.dest), pos) # Get rid of the temp file holding the truncated bytes. os.remove(ZODB.fsrecover._trname) def test_suite(): return unittest.makeSuite(RecoverTest) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testPersistentList.py0000644000175000017500000001350712214017464023224 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test the list interface to PersistentList """ import unittest from persistent.list import PersistentList l0 = [] l1 = [0] l2 = [0, 1] class TestPList(unittest.TestCase): def checkTheWorld(self): # Test constructors u = PersistentList() u0 = PersistentList(l0) u1 = PersistentList(l1) u2 = PersistentList(l2) uu = PersistentList(u) uu0 = PersistentList(u0) uu1 = PersistentList(u1) uu2 = PersistentList(u2) v = PersistentList(tuple(u)) class OtherList: def __init__(self, initlist): self.__data = initlist def __len__(self): return len(self.__data) def __getitem__(self, i): return self.__data[i] v0 = PersistentList(OtherList(u0)) vv = PersistentList("this is also a sequence") # Test __repr__ eq = self.assertEqual eq(str(u0), str(l0), "str(u0) == str(l0)") eq(repr(u1), repr(l1), "repr(u1) == repr(l1)") eq(`u2`, `l2`, "`u2` == `l2`") # Test __cmp__ and __len__ def mycmp(a, b): r = cmp(a, b) if r < 0: return -1 if r > 0: return 1 return r all = [l0, l1, l2, u, u0, u1, u2, uu, uu0, uu1, uu2] for a in all: for b in all: eq(mycmp(a, b), mycmp(len(a), len(b)), "mycmp(a, b) == mycmp(len(a), len(b))") # Test __getitem__ for i in range(len(u2)): eq(u2[i], i, "u2[i] == i") # Test __setitem__ uu2[0] = 0 uu2[1] = 100 try: uu2[2] = 200 except IndexError: pass else: raise TestFailed("uu2[2] shouldn't be assignable") # Test __delitem__ del uu2[1] del uu2[0] try: del uu2[0] except IndexError: pass else: raise TestFailed("uu2[0] shouldn't be deletable") # Test __getslice__ for i in range(-3, 4): eq(u2[:i], l2[:i], "u2[:i] == l2[:i]") eq(u2[i:], l2[i:], "u2[i:] == l2[i:]") for j in range(-3, 4): eq(u2[i:j], l2[i:j], "u2[i:j] == l2[i:j]") # Test __setslice__ for i in range(-3, 4): u2[:i] = l2[:i] eq(u2, l2, "u2 == l2") u2[i:] = l2[i:] eq(u2, l2, "u2 == l2") for j in range(-3, 4): u2[i:j] = l2[i:j] eq(u2, l2, "u2 == l2") uu2 = u2[:] uu2[:0] = [-2, -1] eq(uu2, [-2, -1, 0, 1], "uu2 == [-2, -1, 0, 1]") uu2[0:] = [] eq(uu2, [], "uu2 == []") # Test __contains__ for i in u2: self.failUnless(i in u2, "i in u2") for i in min(u2)-1, max(u2)+1: self.failUnless(i not in u2, "i not in u2") # Test __delslice__ uu2 = u2[:] del uu2[1:2] del uu2[0:1] eq(uu2, [], "uu2 == []") uu2 = u2[:] del uu2[1:] del uu2[:1] eq(uu2, [], "uu2 == []") # Test __add__, __radd__, __mul__ and __rmul__ #self.failUnless(u1 + [] == [] + u1 == u1, "u1 + [] == [] + u1 == u1") self.failUnless(u1 + [1] == u2, "u1 + [1] == u2") #self.failUnless([-1] + u1 == [-1, 0], "[-1] + u1 == [-1, 0]") self.failUnless(u2 == u2*1 == 1*u2, "u2 == u2*1 == 1*u2") self.failUnless(u2+u2 == u2*2 == 2*u2, "u2+u2 == u2*2 == 2*u2") self.failUnless(u2+u2+u2 == u2*3 == 3*u2, "u2+u2+u2 == u2*3 == 3*u2") # Test append u = u1[:] u.append(1) eq(u, u2, "u == u2") # Test insert u = u2[:] u.insert(0, -1) eq(u, [-1, 0, 1], "u == [-1, 0, 1]") # Test pop u = PersistentList([0, -1, 1]) u.pop() eq(u, [0, -1], "u == [0, -1]") u.pop(0) eq(u, [-1], "u == [-1]") # Test remove u = u2[:] u.remove(1) eq(u, u1, "u == u1") # Test count u = u2*3 eq(u.count(0), 3, "u.count(0) == 3") eq(u.count(1), 3, "u.count(1) == 3") eq(u.count(2), 0, "u.count(2) == 0") # Test index eq(u2.index(0), 0, "u2.index(0) == 0") eq(u2.index(1), 1, "u2.index(1) == 1") try: u2.index(2) except ValueError: pass else: raise TestFailed("expected ValueError") # Test reverse u = u2[:] u.reverse() eq(u, [1, 0], "u == [1, 0]") u.reverse() eq(u, u2, "u == u2") # Test sort u = PersistentList([1, 0]) u.sort() eq(u, u2, "u == u2") # Test extend u = u1[:] u.extend(u2) eq(u, u1 + u2, "u == u1 + u2") def checkBackwardCompat(self): # Verify that the sanest of the ZODB 3.2 dotted paths still works. from ZODB.PersistentList import PersistentList as oldPath self.assert_(oldPath is PersistentList) def test_suite(): return unittest.makeSuite(TestPList, 'check') if __name__ == "__main__": loader = unittest.TestLoader() loader.testMethodPrefix = "check" unittest.main(testLoader=loader) zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testcrossdatabasereferences.py0000644000175000017500000001340012214017464025100 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import persistent import unittest class MyClass(persistent.Persistent): pass class MyClass_w_getnewargs(persistent.Persistent): def __getnewargs__(self): return () def test_must_use_consistent_connections(): """ It's important to use consistent connections. References to separate connections to the same database or multi-database won't work. For example, it's tempting to open a second database using the database open function, but this doesn't work: >>> import ZODB.tests.util, transaction, persistent >>> databases = {} >>> db1 = ZODB.tests.util.DB(databases=databases, database_name='1') >>> db2 = ZODB.tests.util.DB(databases=databases, database_name='2') >>> tm = transaction.TransactionManager() >>> conn1 = db1.open(transaction_manager=tm) >>> p1 = MyClass() >>> conn1.root()['p'] = p1 >>> tm.commit() >>> conn2 = db2.open(transaction_manager=tm) >>> p2 = MyClass() >>> conn2.root()['p'] = p2 >>> p2.p1 = p1 >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Traceback (most recent call last): ... InvalidObjectReference: ('Attempt to store a reference to an object from a separate connection to the same database or multidatabase', , ) >>> tm.abort() Even without multi-databases, a common mistake is to mix objects in different connections to the same database. >>> conn2 = db1.open(transaction_manager=tm) >>> p2 = MyClass() >>> conn2.root()['p'] = p2 >>> p2.p1 = p1 >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Traceback (most recent call last): ... InvalidObjectReference: ('Attempt to store a reference to an object from a separate connection to the same database or multidatabase', , ) >>> tm.abort() """ def test_connection_management_doesnt_get_caching_wrong(): """ If a connection participates in a multidatabase, then it's connections must remain so that references between it's cached objects remain sane. >>> import ZODB.tests.util, transaction, persistent >>> databases = {} >>> db1 = ZODB.tests.util.DB(databases=databases, database_name='1') >>> db2 = ZODB.tests.util.DB(databases=databases, database_name='2') >>> tm = transaction.TransactionManager() >>> conn1 = db1.open(transaction_manager=tm) >>> conn2 = conn1.get_connection('2') >>> z = MyClass() >>> conn2.root()['z'] = z >>> tm.commit() >>> x = MyClass() >>> x.z = z >>> conn1.root()['x'] = x >>> y = MyClass() >>> y.z = z >>> conn1.root()['y'] = y >>> tm.commit() >>> conn1.root()['x'].z is conn1.root()['y'].z True So, we have 2 objects in conn1 that point to the same object in conn2. Now, we'll deactivate one, close and repopen the connection, and see if we get the same objects: >>> x._p_deactivate() >>> conn1.close() >>> conn1 = db1.open(transaction_manager=tm) >>> conn1.root()['x'].z is conn1.root()['y'].z True >>> db1.close() >>> db2.close() """ def test_explicit_adding_with_savepoint(): """ >>> import ZODB.tests.util, transaction, persistent >>> databases = {} >>> db1 = ZODB.tests.util.DB(databases=databases, database_name='1') >>> db2 = ZODB.tests.util.DB(databases=databases, database_name='2') >>> tm = transaction.TransactionManager() >>> conn1 = db1.open(transaction_manager=tm) >>> conn2 = conn1.get_connection('2') >>> z = MyClass() >>> conn1.root()['z'] = z >>> conn1.add(z) >>> s = tm.savepoint() >>> conn2.root()['z'] = z >>> tm.commit() >>> z._p_jar.db().database_name '1' >>> db1.close() >>> db2.close() """ def test_explicit_adding_with_savepoint2(): """ >>> import ZODB.tests.util, transaction, persistent >>> databases = {} >>> db1 = ZODB.tests.util.DB(databases=databases, database_name='1') >>> db2 = ZODB.tests.util.DB(databases=databases, database_name='2') >>> tm = transaction.TransactionManager() >>> conn1 = db1.open(transaction_manager=tm) >>> conn2 = conn1.get_connection('2') >>> z = MyClass() >>> conn1.root()['z'] = z >>> conn1.add(z) >>> s = tm.savepoint() >>> conn2.root()['z'] = z >>> z.x = 1 >>> tm.commit() >>> z._p_jar.db().database_name '1' >>> db1.close() >>> db2.close() """ def tearDownDbs(test): test.globs['db1'].close() test.globs['db2'].close() def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('../cross-database-references.txt', globs=dict(MyClass=MyClass), tearDown=tearDownDbs, ), doctest.DocFileSuite('../cross-database-references.txt', globs=dict(MyClass=MyClass_w_getnewargs), tearDown=tearDownDbs, ), doctest.DocTestSuite(), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/testZODB.py0000644000175000017500000005151012214017464020762 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from persistent import Persistent from persistent.mapping import PersistentMapping from ZODB.POSException import ReadConflictError from ZODB.POSException import TransactionFailedError import transaction import unittest import ZODB import ZODB.FileStorage import ZODB.MappingStorage import ZODB.tests.util class P(Persistent): pass class ZODBTests(ZODB.tests.util.TestCase): def setUp(self): ZODB.tests.util.TestCase.setUp(self) self._storage = ZODB.FileStorage.FileStorage( 'ZODBTests.fs', create=1) self._db = ZODB.DB(self._storage) def tearDown(self): self._db.close() ZODB.tests.util.TestCase.tearDown(self) def populate(self): transaction.begin() conn = self._db.open() root = conn.root() root['test'] = pm = PersistentMapping() for n in range(100): pm[n] = PersistentMapping({0: 100 - n}) transaction.get().note('created test data') transaction.commit() conn.close() def checkExportImport(self, abort_it=False): self.populate() conn = self._db.open() try: self.duplicate(conn, abort_it) finally: conn.close() conn = self._db.open() try: self.verify(conn, abort_it) finally: conn.close() def duplicate(self, conn, abort_it): transaction.begin() transaction.get().note('duplication') root = conn.root() ob = root['test'] assert len(ob) > 10, 'Insufficient test data' try: import tempfile f = tempfile.TemporaryFile() ob._p_jar.exportFile(ob._p_oid, f) assert f.tell() > 0, 'Did not export correctly' f.seek(0) new_ob = ob._p_jar.importFile(f) self.assertEqual(new_ob, ob) root['dup'] = new_ob f.close() if abort_it: transaction.abort() else: transaction.commit() except: transaction.abort() raise def verify(self, conn, abort_it): transaction.begin() root = conn.root() ob = root['test'] try: ob2 = root['dup'] except KeyError: if abort_it: # Passed the test. return else: raise else: self.failUnless(not abort_it, 'Did not abort duplication') l1 = list(ob.items()) l1.sort() l2 = list(ob2.items()) l2.sort() l1 = map(lambda (k, v): (k, v[0]), l1) l2 = map(lambda (k, v): (k, v[0]), l2) self.assertEqual(l1, l2) self.assert_(ob._p_oid != ob2._p_oid) self.assertEqual(ob._p_jar, ob2._p_jar) oids = {} for v in ob.values(): oids[v._p_oid] = 1 for v in ob2.values(): assert not oids.has_key(v._p_oid), ( 'Did not fully separate duplicate from original') transaction.commit() def checkExportImportAborted(self): self.checkExportImport(abort_it=True) def checkResetCache(self): # The cache size after a reset should be 0. Note that # _resetCache is not a public API, but the resetCaches() # function is, and resetCaches() causes _resetCache() to be # called. self.populate() conn = self._db.open() conn.root() self.assert_(len(conn._cache) > 0) # Precondition conn._resetCache() self.assertEqual(len(conn._cache), 0) def checkResetCachesAPI(self): # Checks the resetCaches() API. # (resetCaches used to be called updateCodeTimestamp.) self.populate() conn = self._db.open() conn.root() self.assert_(len(conn._cache) > 0) # Precondition ZODB.Connection.resetCaches() conn.close() self.assert_(len(conn._cache) > 0) # Still not flushed conn.open() # simulate the connection being reopened self.assertEqual(len(conn._cache), 0) def checkExplicitTransactionManager(self): # Test of transactions that apply to only the connection, # not the thread. tm1 = transaction.TransactionManager() conn1 = self._db.open(transaction_manager=tm1) tm2 = transaction.TransactionManager() conn2 = self._db.open(transaction_manager=tm2) try: r1 = conn1.root() r2 = conn2.root() if r1.has_key('item'): del r1['item'] tm1.get().commit() r1.get('item') r2.get('item') r1['item'] = 1 tm1.get().commit() self.assertEqual(r1['item'], 1) # r2 has not seen a transaction boundary, # so it should be unchanged. self.assertEqual(r2.get('item'), None) conn2.sync() # Now r2 is updated. self.assertEqual(r2['item'], 1) # Now, for good measure, send an update in the other direction. r2['item'] = 2 tm2.get().commit() self.assertEqual(r1['item'], 1) self.assertEqual(r2['item'], 2) conn1.sync() conn2.sync() self.assertEqual(r1['item'], 2) self.assertEqual(r2['item'], 2) finally: conn1.close() conn2.close() def checkSavepointDoesntGetInvalidations(self): # Prior to ZODB 3.2.9 and 3.4, Connection.tpc_finish() processed # invalidations even for a subtxn commit. This could make # inconsistent state visible after a subtxn commit. There was a # suspicion that POSKeyError was possible as a result, but I wasn't # able to construct a case where that happened. # Subtxns are deprecated now, but it's good to check that the # same kind of thing doesn't happen when making savepoints either. # Set up the database, to hold # root --> "p" -> value = 1 # --> "q" -> value = 2 tm1 = transaction.TransactionManager() conn = self._db.open(transaction_manager=tm1) r1 = conn.root() p = P() p.value = 1 r1["p"] = p q = P() q.value = 2 r1["q"] = q tm1.commit() # Now txn T1 changes p.value to 3 locally (subtxn commit). p.value = 3 tm1.savepoint() # Start new txn T2 with a new connection. tm2 = transaction.TransactionManager() cn2 = self._db.open(transaction_manager=tm2) r2 = cn2.root() p2 = r2["p"] self.assertEqual(p._p_oid, p2._p_oid) # T2 shouldn't see T1's change of p.value to 3, because T1 didn't # commit yet. self.assertEqual(p2.value, 1) # Change p.value to 4, and q.value to 5. Neither should be visible # to T1, because T1 is still in progress. p2.value = 4 q2 = r2["q"] self.assertEqual(q._p_oid, q2._p_oid) self.assertEqual(q2.value, 2) q2.value = 5 tm2.commit() # Back to T1. p and q still have the expected values. rt = conn.root() self.assertEqual(rt["p"].value, 3) self.assertEqual(rt["q"].value, 2) # Now make another savepoint in T1. This shouldn't change what # T1 sees for p and q. rt["r"] = P() tm1.savepoint() # Making that savepoint in T1 should not process invalidations # from T2's commit. p.value should still be 3 here (because that's # what T1 savepointed earlier), and q.value should still be 2. # Prior to ZODB 3.2.9 and 3.4, q.value was 5 here. rt = conn.root() try: self.assertEqual(rt["p"].value, 3) self.assertEqual(rt["q"].value, 2) finally: tm1.abort() def checkTxnBeginImpliesAbort(self): # begin() should do an abort() first, if needed. cn = self._db.open() rt = cn.root() rt['a'] = 1 transaction.begin() # should abort adding 'a' to the root rt = cn.root() self.assertRaises(KeyError, rt.__getitem__, 'a') transaction.begin() rt = cn.root() self.assertRaises(KeyError, rt.__getitem__, 'a') # One more time. transaction.begin() rt = cn.root() rt['a'] = 3 transaction.begin() rt = cn.root() self.assertRaises(KeyError, rt.__getitem__, 'a') self.assertRaises(KeyError, rt.__getitem__, 'b') # That used methods of the default transaction *manager*. Alas, # that's not necessarily the same as using methods of the current # transaction, and, in fact, when this test was written, # Transaction.begin() didn't do anything (everything from here # down failed). # Later (ZODB 3.6): Transaction.begin() no longer exists, so the # rest of this test was tossed. def checkFailingCommitSticks(self): # See also checkFailingSavepointSticks. cn = self._db.open() rt = cn.root() rt['a'] = 1 # Arrange for commit to fail during tpc_vote. poisoned = PoisonedObject(PoisonedJar(break_tpc_vote=True)) transaction.get().register(poisoned) self.assertRaises(PoisonedError, transaction.get().commit) # Trying to commit again fails too. self.assertRaises(TransactionFailedError, transaction.commit) self.assertRaises(TransactionFailedError, transaction.commit) self.assertRaises(TransactionFailedError, transaction.commit) # The change to rt['a'] is lost. self.assertRaises(KeyError, rt.__getitem__, 'a') # Trying to modify an object also fails, because Transaction.join() # also raises TransactionFailedError. self.assertRaises(TransactionFailedError, rt.__setitem__, 'b', 2) # Clean up via abort(), and try again. transaction.abort() rt['a'] = 1 transaction.commit() self.assertEqual(rt['a'], 1) # Cleaning up via begin() should also work. rt['a'] = 2 transaction.get().register(poisoned) self.assertRaises(PoisonedError, transaction.commit) self.assertRaises(TransactionFailedError, transaction.commit) # The change to rt['a'] is lost. self.assertEqual(rt['a'], 1) # Trying to modify an object also fails. self.assertRaises(TransactionFailedError, rt.__setitem__, 'b', 2) # Clean up via begin(), and try again. transaction.begin() rt['a'] = 2 transaction.commit() self.assertEqual(rt['a'], 2) cn.close() def checkFailingSavepointSticks(self): cn = self._db.open() rt = cn.root() rt['a'] = 1 transaction.savepoint() self.assertEqual(rt['a'], 1) rt['b'] = 2 # Make a jar that raises PoisonedError when making a savepoint. poisoned = PoisonedJar(break_savepoint=True) transaction.get().join(poisoned) self.assertRaises(PoisonedError, transaction.savepoint) # Trying to make a savepoint again fails too. self.assertRaises(TransactionFailedError, transaction.savepoint) self.assertRaises(TransactionFailedError, transaction.savepoint) # Top-level commit also fails. self.assertRaises(TransactionFailedError, transaction.commit) # The changes to rt['a'] and rt['b'] are lost. self.assertRaises(KeyError, rt.__getitem__, 'a') self.assertRaises(KeyError, rt.__getitem__, 'b') # Trying to modify an object also fails, because Transaction.join() # also raises TransactionFailedError. self.assertRaises(TransactionFailedError, rt.__setitem__, 'b', 2) # Clean up via abort(), and try again. transaction.abort() rt['a'] = 1 transaction.commit() self.assertEqual(rt['a'], 1) # Cleaning up via begin() should also work. rt['a'] = 2 transaction.get().join(poisoned) self.assertRaises(PoisonedError, transaction.savepoint) # Trying to make a savepoint again fails too. self.assertRaises(TransactionFailedError, transaction.savepoint) # The change to rt['a'] is lost. self.assertEqual(rt['a'], 1) # Trying to modify an object also fails. self.assertRaises(TransactionFailedError, rt.__setitem__, 'b', 2) # Clean up via begin(), and try again. transaction.begin() rt['a'] = 2 transaction.savepoint() self.assertEqual(rt['a'], 2) transaction.commit() cn2 = self._db.open() rt = cn.root() self.assertEqual(rt['a'], 2) cn.close() cn2.close() def checkMultipleUndoInOneTransaction(self): # Verify that it's possible to perform multiple undo # operations within a transaction. If ZODB performs the undo # operations in a nondeterministic order, this test will often # fail. conn = self._db.open() try: root = conn.root() # Add transactions that set root["state"] to (0..5) for state_num in range(6): transaction.begin() root['state'] = state_num transaction.get().note('root["state"] = %d' % state_num) transaction.commit() # Undo all but the first. Note that no work is actually # performed yet. transaction.begin() log = self._db.undoLog() self._db.undoMultiple([log[i]['id'] for i in range(5)]) transaction.get().note('undo states 1 through 5') # Now attempt all those undo operations. transaction.commit() # Sanity check: we should be back to the first state. self.assertEqual(root['state'], 0) finally: transaction.abort() conn.close() class ReadConflictTests(ZODB.tests.util.TestCase): def setUp(self): ZODB.tests.utils.TestCase.setUp(self) self._storage = ZODB.MappingStorage.MappingStorage() def readConflict(self, shouldFail=True): # Two transactions run concurrently. Each reads some object, # then one commits and the other tries to read an object # modified by the first. This read should fail with a conflict # error because the object state read is not necessarily # consistent with the objects read earlier in the transaction. tm1 = transaction.TransactionManager() conn = self._db.open(transaction_manager=tm1) r1 = conn.root() r1["p"] = self.obj self.obj.child1 = P() tm1.get().commit() # start a new transaction with a new connection tm2 = transaction.TransactionManager() cn2 = self._db.open(transaction_manager=tm2) # start a new transaction with the other connection r2 = cn2.root() self.assertEqual(r1._p_serial, r2._p_serial) self.obj.child2 = P() tm1.get().commit() # resume the transaction using cn2 obj = r2["p"] # An attempt to access obj should fail, because r2 was read # earlier in the transaction and obj was modified by the othe # transaction. if shouldFail: self.assertRaises(ReadConflictError, lambda: obj.child1) # And since ReadConflictError was raised, attempting to commit # the transaction should re-raise it. checkNotIndependent() # failed this part of the test for a long time. self.assertRaises(ReadConflictError, tm2.get().commit) # And since that commit failed, trying to commit again should # fail again. self.assertRaises(TransactionFailedError, tm2.get().commit) # And again. self.assertRaises(TransactionFailedError, tm2.get().commit) # Etc. self.assertRaises(TransactionFailedError, tm2.get().commit) else: # make sure that accessing the object succeeds obj.child1 tm2.get().abort() def checkReadConflict(self): self.obj = P() self.readConflict() def checkReadConflictIgnored(self): # Test that an application that catches a read conflict and # continues can not commit the transaction later. root = self._db.open().root() root["real_data"] = real_data = PersistentMapping() root["index"] = index = PersistentMapping() real_data["a"] = PersistentMapping({"indexed_value": 0}) real_data["b"] = PersistentMapping({"indexed_value": 1}) index[1] = PersistentMapping({"b": 1}) index[0] = PersistentMapping({"a": 1}) transaction.commit() # load some objects from one connection tm = transaction.TransactionManager() cn2 = self._db.open(transaction_manager=tm) r2 = cn2.root() real_data2 = r2["real_data"] index2 = r2["index"] real_data["b"]["indexed_value"] = 0 del index[1]["b"] index[0]["b"] = 1 transaction.commit() del real_data2["a"] try: del index2[0]["a"] except ReadConflictError: # This is the crux of the text. Ignore the error. pass else: self.fail("No conflict occurred") # real_data2 still ready to commit self.assert_(real_data2._p_changed) # index2 values not ready to commit self.assert_(not index2._p_changed) self.assert_(not index2[0]._p_changed) self.assert_(not index2[1]._p_changed) self.assertRaises(ReadConflictError, tm.get().commit) self.assertRaises(TransactionFailedError, tm.get().commit) tm.get().abort() def checkReadConflictErrorClearedDuringAbort(self): # When a transaction is aborted, the "memory" of which # objects were the cause of a ReadConflictError during # that transaction should be cleared. root = self._db.open().root() data = PersistentMapping({'d': 1}) root["data"] = data transaction.commit() # Provoke a ReadConflictError. tm2 = transaction.TransactionManager() cn2 = self._db.open(transaction_manager=tm2) r2 = cn2.root() data2 = r2["data"] data['d'] = 2 transaction.commit() try: data2['d'] = 3 except ReadConflictError: pass else: self.fail("No conflict occurred") # Explicitly abort cn2's transaction. tm2.get().abort() # cn2 should retain no memory of the read conflict after an abort(), # but 3.2.3 had a bug wherein it did. data_conflicts = data._p_jar._conflicts data2_conflicts = data2._p_jar._conflicts self.failIf(data_conflicts) self.failIf(data2_conflicts) # this used to fail # And because of that, we still couldn't commit a change to data2['d'] # in the new transaction. cn2.sync() # process the invalidation for data2['d'] data2['d'] = 3 tm2.get().commit() # 3.2.3 used to raise ReadConflictError cn2.close() class PoisonedError(Exception): pass # PoisonedJar arranges to raise PoisonedError from interesting places. class PoisonedJar: def __init__(self, break_tpc_begin=False, break_tpc_vote=False, break_savepoint=False): self.break_tpc_begin = break_tpc_begin self.break_tpc_vote = break_tpc_vote self.break_savepoint = break_savepoint def sortKey(self): return str(id(self)) def tpc_begin(self, *args): if self.break_tpc_begin: raise PoisonedError("tpc_begin fails") # A way to poison a top-level commit. def tpc_vote(self, *args): if self.break_tpc_vote: raise PoisonedError("tpc_vote fails") # A way to poison a savepoint -- also a way to poison a subtxn commit. def savepoint(self): if self.break_savepoint: raise PoisonedError("savepoint fails") def commit(*args): pass def abort(*self): pass class PoisonedObject: def __init__(self, poisonedjar): self._p_jar = poisonedjar def test_suite(): suite = unittest.makeSuite(ZODBTests, 'check') return suite if __name__ == "__main__": unittest.main(defaultTest="test_suite") zope2.13-2.13.21/source/ZODB3/src/ZODB/tests/dangle.py0000644000175000017500000000334212214017464020556 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Functional test to produce a dangling reference.""" import time import transaction from ZODB.FileStorage import FileStorage from ZODB import DB from persistent import Persistent class P(Persistent): pass def create_dangling_ref(db): rt = db.open().root() rt[1] = o1 = P() transaction.get().note("create o1") transaction.commit() rt[2] = o2 = P() transaction.get().note("create o2") transaction.commit() c = o1.child = P() transaction.get().note("set child on o1") transaction.commit() o1.child = P() transaction.get().note("replace child on o1") transaction.commit() time.sleep(2) # The pack should remove the reference to c, because it is no # longer referenced from o1. But the object still exists and has # an oid, so a new commit of it won't create a new object. db.pack() print repr(c._p_oid) o2.child = c transaction.get().note("set child on o2") transaction.commit() def main(): fs = FileStorage("dangle.fs") db = DB(fs) create_dangling_ref(db) db.close() if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/ZODB/ActivityMonitor.py0000644000175000017500000000714712214017464021335 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ZODB transfer activity monitoring $Id: ActivityMonitor.py 121409 2011-04-12 13:50:07Z jim $""" import threading import time class ActivityMonitor: """ZODB load/store activity monitor This simple implementation just keeps a small log in memory and iterates over the log when getActivityAnalysis() is called. It assumes that log entries are added in chronological sequence. """ def __init__(self, history_length=3600): self.history_length = history_length # Number of seconds self.log = [] # [(time, loads, stores)] self.trim_lock = threading.Lock() def closedConnection(self, conn): log = self.log now = time.time() loads, stores = conn.getTransferCounts(1) log.append((now, loads, stores)) self.trim(now) def trim(self, now): self.trim_lock.acquire() log = self.log cutoff = now - self.history_length n = 0 loglen = len(log) while n < loglen and log[n][0] < cutoff: n = n + 1 if n: del log[:n] self.trim_lock.release() def setHistoryLength(self, history_length): self.history_length = history_length self.trim(time.time()) def getHistoryLength(self): return self.history_length def getActivityAnalysis(self, start=0, end=0, divisions=10): res = [] now = time.time() if start == 0: start = now - self.history_length if end == 0: end = now for n in range(divisions): res.append({ 'start': start + (end - start) * n / divisions, 'end': start + (end - start) * (n + 1) / divisions, 'loads': 0, 'stores': 0, 'connections': 0, }) div = res[0] div_end = div['end'] div_index = 0 connections = 0 total_loads = 0 total_stores = 0 for t, loads, stores in self.log: if t < start: # We could use a binary search to find the start. continue elif t > end: # We could use a binary search to find the end also. break while t > div_end: div['loads'] = total_loads div['stores'] = total_stores div['connections'] = connections total_loads = 0 total_stores = 0 connections = 0 div_index = div_index + 1 if div_index < divisions: div = res[div_index] div_end = div['end'] connections = connections + 1 total_loads = total_loads + loads total_stores = total_stores + stores div['stores'] = div['stores'] + total_stores div['loads'] = div['loads'] + total_loads div['connections'] = div['connections'] + connections return res zope2.13-2.13.21/source/ZODB3/src/ZODB/storage.xml0000644000175000017500000000015012214017464017770 0ustar arnauarnau
zope2.13-2.13.21/source/ZODB3/src/ZODB/UndoLogCompatible.py0000644000175000017500000000300412214017464021524 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Provide backward compatibility with storages that only have undoLog().""" class UndoLogCompatible: def undoInfo(self, first=0, last=-20, specification=None): if specification: # filter(desc) returns true iff `desc` is a "superdict" # of `specification`, meaning that `desc` contains the same # (key, value) pairs as `specification`, and possibly additional # (key, value) pairs. Another way to do this might be # d = desc.copy() # d.update(specification) # return d == desc def filter(desc, spec=specification.items()): get = desc.get for k, v in spec: if get(k, None) != v: return 0 return 1 else: filter = None return self.undoLog(first, last, filter) zope2.13-2.13.21/source/ZODB3/src/ZODB/serialize.py0000644000175000017500000005473212214017464020162 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Support for ZODB object serialization. ZODB serializes objects using a custom format based on Python pickles. When an object is unserialized, it can be loaded as either a ghost or a real object. A ghost is a persistent object of the appropriate type but without any state. The first time a ghost is accessed, the persistence machinery traps access and loads the actual state. A ghost allows many persistent objects to be loaded while minimizing the memory consumption of referenced but otherwise unused objects. Pickle format ------------- ZODB stores serialized objects using a custom format based on pickle. Each serialized object has two parts: the class description and the object state. The class description must provide enough information to call the class's ``__new__`` and create an empty object. Once the object exists as a ghost, its state is passed to ``__setstate__``. The class description can be in a variety of formats, in part to provide backwards compatibility with earlier versions of Zope. The four current formats for class description are: 1. type(obj) 2. type(obj), obj.__getnewargs__() 3. (module name, class name), None 7. (module name, class name), obj.__getnewargs__() The second of these options is used if the object has a __getnewargs__() method. It is intended to support objects like persistent classes that have custom C layouts that are determined by arguments to __new__(). The third and fourth (#3 & #7) apply to instances of a persistent class (which means the class itself is persistent, not that it's a subclass of Persistent). The type object is usually stored using the standard pickle mechanism, which involves the pickle GLOBAL opcode (giving the type's module and name as strings). The type may itself be a persistent object, in which case a persistent reference (see below) is used. It's unclear what "usually" means in the last paragraph. There are two useful places to concentrate confusion about exactly which formats exist: - ObjectReader.getClassName() below returns a dotted "module.class" string, via actually loading a pickle. This requires that the implementation of application objects be available. - ZODB/utils.py's get_pickle_metadata() tries to return the module and class names (as strings) without importing any application modules or classes, via analyzing the pickle. Earlier versions of Zope supported several other kinds of class descriptions. The current serialization code reads these descriptions, but does not write them. The three earlier formats are: 4. (module name, class name), __getinitargs__() 5. class, None 6. class, __getinitargs__() Formats 4 and 6 are used only if the class defines a __getinitargs__() method, but we really can't tell them apart from formats 7 and 2 (respectively). Formats 5 and 6 are used if the class does not have a __module__ attribute (I'm not sure when this applies, but I think it occurs for some but not all ZClasses). Persistent references --------------------- When one persistent object pickle refers to another persistent object, the database uses a persistent reference. ZODB persistent references are of the form:: oid A simple object reference. (oid, class meta data) A persistent object reference [reference_type, args] An extended reference Extension references come in a number of subforms, based on the reference types. The following reference types are defined: 'w' Persistent weak reference. The arguments consist of an oid and optionally a database name. The following are planned for the future: 'n' Multi-database simple object reference. The arguments consist of a database name, and an object id. 'm' Multi-database persistent object reference. The arguments consist of a database name, an object id, and class meta data. The following legacy format is also supported. [oid] A persistent weak reference Because the persistent object reference forms include class information, it is not possible to change the class of a persistent object for which this form is used. If a transaction changed the class of an object, a new record with new class metadata would be written but all the old references would still use the old class. (It is possible that we could deal with this limitation in the future.) An object id is used alone when a class requires arguments to it's __new__ method, which is signalled by the class having a __getnewargs__ attribute. A number of legacyforms are defined: """ import cPickle import cStringIO import logging from persistent import Persistent from persistent.wref import WeakRefMarker, WeakRef from ZODB import broken from ZODB.broken import Broken from ZODB.POSException import InvalidObjectReference _oidtypes = str, type(None) # Might to update or redo coptimizations to reflect weakrefs: # from ZODB.coptimizations import new_persistent_id def myhasattr(obj, name, _marker=object()): """Make sure we don't mask exceptions like hasattr(). We don't want exceptions other than AttributeError to be masked, since that too often masks other programming errors. Three-argument getattr() doesn't mask those, so we use that to implement our own hasattr() replacement. """ return getattr(obj, name, _marker) is not _marker class ObjectWriter: """Serializes objects for storage in the database. The ObjectWriter creates object pickles in the ZODB format. It also detects new persistent objects reachable from the current object. """ _jar = None def __init__(self, obj=None): self._file = cStringIO.StringIO() self._p = cPickle.Pickler(self._file, 1) self._p.inst_persistent_id = self.persistent_id self._stack = [] if obj is not None: self._stack.append(obj) jar = obj._p_jar assert myhasattr(jar, "new_oid") self._jar = jar def persistent_id(self, obj): """Return the persistent id for obj. >>> from ZODB.tests.util import P >>> class DummyJar: ... xrefs = True ... def new_oid(self): ... return 42 ... def db(self): ... return self ... databases = {} >>> jar = DummyJar() >>> class O: ... _p_jar = jar >>> writer = ObjectWriter(O) Normally, object references include the oid and a cached named reference to the class. Having the class information available allows fast creation of the ghost, avoiding requiring an additional database lookup. >>> bob = P('bob') >>> oid, cls = writer.persistent_id(bob) >>> oid 42 >>> cls is P True If a persistent object does not already have an oid and jar, these will be assigned by persistent_id(): >>> bob._p_oid 42 >>> bob._p_jar is jar True If the object already has a persistent id, the id is not changed: >>> bob._p_oid = 24 >>> oid, cls = writer.persistent_id(bob) >>> oid 24 >>> cls is P True If the jar doesn't match that of the writer, an error is raised: >>> bob._p_jar = DummyJar() >>> writer.persistent_id(bob) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Traceback (most recent call last): ... InvalidObjectReference: ('Attempt to store an object from a foreign database connection', , P(bob)) Constructor arguments used by __new__(), as returned by __getnewargs__(), can affect memory allocation, but may also change over the life of the object. This makes it useless to cache even the object's class. >>> class PNewArgs(P): ... def __getnewargs__(self): ... return () >>> sam = PNewArgs('sam') >>> writer.persistent_id(sam) 42 >>> sam._p_oid 42 >>> sam._p_jar is jar True Check that simple objects don't get accused of persistence: >>> writer.persistent_id(42) >>> writer.persistent_id(object()) Check that a classic class doesn't get identified improperly: >>> class ClassicClara: ... pass >>> clara = ClassicClara() >>> writer.persistent_id(clara) """ # Most objects are not persistent. The following cheap test # identifies most of them. For these, we return None, # signalling that the object should be pickled normally. if not isinstance(obj, (Persistent, type, WeakRef)): # Not persistent, pickle normally return None # Any persistent object must have an oid: try: oid = obj._p_oid except AttributeError: # Not persistent, pickle normally return None if not (oid is None or isinstance(oid, str)): # Deserves a closer look: # Make sure it's not a descriptor if hasattr(oid, '__get__'): # The oid is a descriptor. That means obj is a non-persistent # class whose instances are persistent, so ... # Not persistent, pickle normally return None if oid is WeakRefMarker: # we have a weakref, see weakref.py oid = obj.oid if oid is None: target = obj() # get the referenced object oid = target._p_oid if oid is None: # Here we are causing the object to be saved in # the database. One could argue that we shouldn't # do this, because a weakref should not cause an object # to be added. We'll be optimistic, though, and # assume that the object will be added eventually. oid = self._jar.new_oid() target._p_jar = self._jar target._p_oid = oid self._stack.append(target) obj.oid = oid obj.dm = target._p_jar obj.database_name = obj.dm.db().database_name if obj.dm is self._jar: return ['w', (oid, )] else: return ['w', (oid, obj.database_name)] # Since we have an oid, we have either a persistent instance # (an instance of Persistent), or a persistent class. # NOTE! Persistent classes don't (and can't) subclass persistent. database_name = None if oid is None: oid = obj._p_oid = self._jar.new_oid() obj._p_jar = self._jar self._stack.append(obj) elif obj._p_jar is not self._jar: if not self._jar.db().xrefs: raise InvalidObjectReference( "Database %r doesn't allow implicit cross-database " "references" % self._jar.db().database_name, self._jar, obj) try: otherdb = obj._p_jar.db() database_name = otherdb.database_name except AttributeError: otherdb = self if self._jar.db().databases.get(database_name) is not otherdb: raise InvalidObjectReference( "Attempt to store an object from a foreign " "database connection", self._jar, obj, ) if self._jar.get_connection(database_name) is not obj._p_jar: raise InvalidObjectReference( "Attempt to store a reference to an object from " "a separate connection to the same database or " "multidatabase", self._jar, obj, ) # OK, we have an object from another database. # Lets make sure the object ws not *just* loaded. if obj._p_jar._implicitlyAdding(oid): raise InvalidObjectReference( "A new object is reachable from multiple databases. " "Won't try to guess which one was correct!", self._jar, obj, ) klass = type(obj) if hasattr(klass, '__getnewargs__'): # We don't want to save newargs in object refs. # It's possible that __getnewargs__ is degenerate and # returns (), but we don't want to have to deghostify # the object to find out. # Note that this has the odd effect that, if the class has # __getnewargs__ of its own, we'll lose the optimization # of caching the class info. if database_name is not None: return ['n', (database_name, oid)] return oid # Note that we never get here for persistent classes. # We'll use direct refs for normal classes. if database_name is not None: return ['m', (database_name, oid, klass)] return oid, klass def serialize(self, obj): # We don't use __class__ here, because obj could be a persistent proxy. # We don't want to be fooled by proxies. klass = type(obj) # We want to serialize persistent classes by name if they have # a non-None non-empty module so as not to have a direct # ref. This is important when copying. We probably want to # revisit this in the future. newargs = getattr(obj, "__getnewargs__", None) if (isinstance(getattr(klass, '_p_oid', 0), _oidtypes) and klass.__module__): # This is a persistent class with a non-empty module. This # uses pickle format #3 or #7. klass = klass.__module__, klass.__name__ if newargs is None: meta = klass, None else: meta = klass, newargs() elif newargs is None: # Pickle format #1. meta = klass else: # Pickle format #2. meta = klass, newargs() return self._dump(meta, obj.__getstate__()) def _dump(self, classmeta, state): # To reuse the existing cStringIO object, we must reset # the file position to 0 and truncate the file after the # new pickle is written. self._file.seek(0) self._p.clear_memo() self._p.dump(classmeta) self._p.dump(state) self._file.truncate() return self._file.getvalue() def __iter__(self): return NewObjectIterator(self._stack) class NewObjectIterator: # The pickler is used as a forward iterator when the connection # is looking for new objects to pickle. def __init__(self, stack): self._stack = stack def __iter__(self): return self def next(self): if self._stack: elt = self._stack.pop() return elt else: raise StopIteration class ObjectReader: def __init__(self, conn=None, cache=None, factory=None): self._conn = conn self._cache = cache self._factory = factory def _get_class(self, module, name): return self._factory(self._conn, module, name) def _get_unpickler(self, pickle): file = cStringIO.StringIO(pickle) unpickler = cPickle.Unpickler(file) unpickler.persistent_load = self._persistent_load factory = self._factory conn = self._conn def find_global(modulename, name): return factory(conn, modulename, name) unpickler.find_global = find_global return unpickler loaders = {} def _persistent_load(self, reference): if isinstance(reference, tuple): return self.load_persistent(*reference) elif isinstance(reference, str): return self.load_oid(reference) else: try: reference_type, args = reference except ValueError: # weakref return self.loaders['w'](self, *reference) else: return self.loaders[reference_type](self, *args) def load_persistent(self, oid, klass): # Quick instance reference. We know all we need to know # to create the instance w/o hitting the db, so go for it! obj = self._cache.get(oid, None) if obj is not None: return obj if isinstance(klass, tuple): klass = self._get_class(*klass) if issubclass(klass, Broken): # We got a broken class. We might need to make it # PersistentBroken if not issubclass(klass, broken.PersistentBroken): klass = broken.persistentBroken(klass) try: obj = klass.__new__(klass) except TypeError: # Couldn't create the instance. Maybe there's more # current data in the object's actual record! return self._conn.get(oid) # TODO: should be done by connection self._cache.new_ghost(oid, obj) return obj def load_multi_persistent(self, database_name, oid, klass): conn = self._conn.get_connection(database_name) # TODO, make connection _cache attr public reader = ObjectReader(conn, conn._cache, self._factory) return reader.load_persistent(oid, klass) loaders['m'] = load_multi_persistent def load_persistent_weakref(self, oid, database_name=None): obj = WeakRef.__new__(WeakRef) obj.oid = oid if database_name is None: obj.dm = self._conn else: obj.database_name = database_name try: obj.dm = self._conn.get_connection(database_name) except KeyError: # XXX Not sure what to do here. It seems wrong to # fail since this is a weak reference. For now we'll # just pretend that the target object has gone. pass return obj loaders['w'] = load_persistent_weakref def load_oid(self, oid): obj = self._cache.get(oid, None) if obj is not None: return obj return self._conn.get(oid) def load_multi_oid(self, database_name, oid): conn = self._conn.get_connection(database_name) # TODO, make connection _cache attr public reader = ObjectReader(conn, conn._cache, self._factory) return reader.load_oid(oid) loaders['n'] = load_multi_oid def getClassName(self, pickle): unpickler = self._get_unpickler(pickle) klass = unpickler.load() if isinstance(klass, tuple): klass, args = klass if isinstance(klass, tuple): # old style reference return "%s.%s" % klass return "%s.%s" % (klass.__module__, klass.__name__) def getGhost(self, pickle): unpickler = self._get_unpickler(pickle) klass = unpickler.load() if isinstance(klass, tuple): # Here we have a separate class and args. # This could be an old record, so the class module ne a named # refernce klass, args = klass if isinstance(klass, tuple): # Old module_name, class_name tuple klass = self._get_class(*klass) if args is None: args = () else: # Definitely new style direct class reference args = () if issubclass(klass, Broken): # We got a broken class. We might need to make it # PersistentBroken if not issubclass(klass, broken.PersistentBroken): klass = broken.persistentBroken(klass) return klass.__new__(klass, *args) def getState(self, pickle): unpickler = self._get_unpickler(pickle) try: unpickler.load() # skip the class metadata return unpickler.load() except EOFError, msg: log = logging.getLogger("ZODB.serialize") log.exception("Unpickling error: %r", pickle) raise def setGhostState(self, obj, pickle): state = self.getState(pickle) obj.__setstate__(state) def referencesf(p, oids=None): """Return a list of object ids found in a pickle A list may be passed in, in which case, information is appended to it. Only ordinary internal references are included. Weak and multi-database references are not included. """ refs = [] u = cPickle.Unpickler(cStringIO.StringIO(p)) u.persistent_load = refs u.noload() u.noload() # Now we have a list of referencs. Need to convert to list of # oids: if oids is None: oids = [] for reference in refs: if isinstance(reference, tuple): oid = reference[0] elif isinstance(reference, str): oid = reference else: assert isinstance(reference, list) continue oids.append(oid) return oids oid_klass_loaders = { 'w': lambda oid, database_name=None: None, } def get_refs(a_pickle): """Return oid and class information for references in a pickle The result of a list of oid and class information tuples. If the reference doesn't contain class information, then the klass information is None. """ refs = [] u = cPickle.Unpickler(cStringIO.StringIO(a_pickle)) u.persistent_load = refs u.noload() u.noload() # Now we have a list of referencs. Need to convert to list of # oids and class info: result = [] for reference in refs: if isinstance(reference, tuple): data = reference elif isinstance(reference, str): data = reference, None else: assert isinstance(reference, list) continue result.append(data) return result zope2.13-2.13.21/source/ZODB3/src/ZODB/fsrecover.py0000644000175000017500000002413312214017464020161 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Simple script for repairing damaged FileStorage files. Usage: %s [-f] [-v level] [-p] [-P seconds] input output Recover data from a FileStorage data file, skipping over damaged data. Any damaged data will be lost. This could lead to useless output if critical data is lost. Options: -f Overwrite output file even if it exists. -v level Set the verbosity level: 0 -- show progress indicator (default) 1 -- show transaction times and sizes 2 -- show transaction times and sizes, and show object (record) ids, versions, and sizes -p Copy partial transactions. If a data record in the middle of a transaction is bad, the data up to the bad data are packed. The output record is marked as packed. If this option is not used, transactions with any bad data are skipped. -P t Pack data to t seconds in the past. Note that if the "-p" option is used, then t should be 0. Important: The ZODB package must be importable. You may need to adjust PYTHONPATH accordingly. """ # Algorithm: # # position to start of input # while 1: # if end of file: # break # try: # copy_transaction # except: # scan for transaction # continue import sys import os import getopt import time from struct import unpack from cPickle import loads try: import ZODB except ImportError: if os.path.exists('ZODB'): sys.path.append('.') elif os.path.exists('FileStorage.py'): sys.path.append('..') import ZODB import ZODB.FileStorage from ZODB.utils import u64 from ZODB.FileStorage import TransactionRecord from persistent.TimeStamp import TimeStamp def die(mess='', show_docstring=False): if mess: print >> sys.stderr, mess + '\n' if show_docstring: print >> sys.stderr, __doc__ % sys.argv[0] sys.exit(1) class ErrorFound(Exception): pass def error(mess, *args): raise ErrorFound(mess % args) def read_txn_header(f, pos, file_size, outp, ltid): # Read the transaction record f.seek(pos) h = f.read(23) if len(h) < 23: raise EOFError tid, stl, status, ul, dl, el = unpack(">8s8scHHH",h) tl = u64(stl) if pos + (tl + 8) > file_size: error("bad transaction length at %s", pos) if tl < (23 + ul + dl + el): error("invalid transaction length, %s, at %s", tl, pos) if ltid and tid < ltid: error("time-stamp reducation %s < %s, at %s", u64(tid), u64(ltid), pos) if status == "c": truncate(f, pos, file_size, outp) raise EOFError if status not in " up": error("invalid status, %r, at %s", status, pos) tpos = pos tend = tpos + tl if status == "u": # Undone transaction, skip it f.seek(tend) h = f.read(8) if h != stl: error("inconsistent transaction length at %s", pos) pos = tend + 8 return pos, None, tid pos = tpos+(23+ul+dl+el) user = f.read(ul) description = f.read(dl) if el: try: e=loads(f.read(el)) except: e={} else: e={} result = TransactionRecord(tid, status, user, description, e, pos, tend, f, tpos) pos = tend # Read the (intentionally redundant) transaction length f.seek(pos) h = f.read(8) if h != stl: error("redundant transaction length check failed at %s", pos) pos += 8 return pos, result, tid def truncate(f, pos, file_size, outp): """Copy data from pos to end of f to a .trNNN file.""" # _trname is global so that the test suite can know the path too (in # order to delete the file when the test ends). global _trname i = 0 while 1: _trname = outp + ".tr%d" % i if os.path.exists(_trname): i += 1 else: break tr = open(_trname, "wb") copy(f, tr, file_size - pos) f.seek(pos) tr.close() def copy(src, dst, n): while n: buf = src.read(8096) if not buf: break if len(buf) > n: buf = buf[:n] dst.write(buf) n -= len(buf) def scan(f, pos): """Return a potential transaction location following pos in f. This routine scans forward from pos looking for the last data record in a transaction. A period '.' always occurs at the end of a pickle, and an 8-byte transaction length follows the last pickle. If a period is followed by a plausible 8-byte transaction length, assume that we have found the end of a transaction. The caller should try to verify that the returned location is actually a transaction header. """ while 1: f.seek(pos) data = f.read(8096) if not data: return 0 s = 0 while 1: l = data.find(".", s) if l < 0: pos += len(data) break # If we are less than 8 bytes from the end of the # string, we need to read more data. s = l + 1 if s > len(data) - 8: pos += l break tl = u64(data[s:s+8]) if tl < pos: return pos + s + 8 def iprogress(i): if i % 2: print ".", else: print (i/2) % 10, sys.stdout.flush() def progress(p): for i in range(p): iprogress(i) def main(): try: opts, args = getopt.getopt(sys.argv[1:], "fv:pP:") except getopt.error, msg: die(str(msg), show_docstring=True) if len(args) != 2: die("two positional arguments required", show_docstring=True) inp, outp = args force = partial = False verbose = 0 pack = None for opt, v in opts: if opt == "-v": verbose = int(v) elif opt == "-p": partial = True elif opt == "-f": force = True elif opt == "-P": pack = time.time() - float(v) recover(inp, outp, verbose, partial, force, pack) def recover(inp, outp, verbose=0, partial=False, force=False, pack=None): print "Recovering", inp, "into", outp if os.path.exists(outp) and not force: die("%s exists" % outp) f = open(inp, "rb") if f.read(4) != ZODB.FileStorage.packed_version: die("input is not a file storage") f.seek(0,2) file_size = f.tell() ofs = ZODB.FileStorage.FileStorage(outp, create=1) _ts = None ok = 1 prog1 = 0 undone = 0 pos = 4L ltid = None while pos: try: npos, txn, tid = read_txn_header(f, pos, file_size, outp, ltid) except EOFError: break except (KeyboardInterrupt, SystemExit): raise except Exception, err: print "error reading txn header:", err if not verbose: progress(prog1) pos = scan(f, pos) if verbose > 1: print "looking for valid txn header at", pos continue ltid = tid if txn is None: undone = undone + npos - pos pos = npos continue else: pos = npos tid = txn.tid if _ts is None: _ts = TimeStamp(tid) else: t = TimeStamp(tid) if t <= _ts: if ok: print ("Time stamps out of order %s, %s" % (_ts, t)) ok = 0 _ts = t.laterThan(_ts) tid = `_ts` else: _ts = t if not ok: print ("Time stamps back in order %s" % (t)) ok = 1 ofs.tpc_begin(txn, tid, txn.status) if verbose: print "begin", pos, _ts, if verbose > 1: print sys.stdout.flush() nrec = 0 try: for r in txn: if verbose > 1: if r.data is None: l = "bp" else: l = len(r.data) print "%7d %s %s" % (u64(r.oid), l) ofs.restore(r.oid, r.tid, r.data, '', r.data_txn, txn) nrec += 1 except (KeyboardInterrupt, SystemExit): raise except Exception, err: if partial and nrec: ofs._status = "p" ofs.tpc_vote(txn) ofs.tpc_finish(txn) if verbose: print "partial" else: ofs.tpc_abort(txn) print "error copying transaction:", err if not verbose: progress(prog1) pos = scan(f, pos) if verbose > 1: print "looking for valid txn header at", pos else: ofs.tpc_vote(txn) ofs.tpc_finish(txn) if verbose: print "finish" sys.stdout.flush() if not verbose: prog = pos * 20l / file_size while prog > prog1: prog1 = prog1 + 1 iprogress(prog1) bad = file_size - undone - ofs._pos print "\n%s bytes removed during recovery" % bad if undone: print "%s bytes of undone transaction data were skipped" % undone if pack is not None: print "Packing ..." from ZODB.serialize import referencesf ofs.pack(pack, referencesf) ofs.close() if __name__ == "__main__": main() zope2.13-2.13.21/source/ZODB3/src/CHANGES.txt0000644000175000017500000003022112214017464016657 0ustar arnauarnau================ Change History ================ 3.10.5 (2011-11-19) =================== Bugs Fixed ---------- - Conflict resolution failed when state included cross-database persistent references with classes that couldn't be imported. 3.10.4 (2011-11-17) =================== Bugs Fixed ---------- - Conflict resolution failed when state included persistent references with classes that couldn't be imported. 3.10.3 (2011-04-12) =================== Bugs Fixed ---------- - "activity monitor not updated for subconnections when connection returned to pool" https://bugs.launchpad.net/zodb/+bug/737198 - "Blob temp file get's removed before it should", https://bugs.launchpad.net/zodb/+bug/595378 A way this to happen is that a transaction is aborted after the commit process has started. I don't know how this would happen in the wild. In 3.10.3, the ZEO tpc_abort call to the server is changed to be synchronous, which should address this case. Maybe there's another case. Performance enhancements ------------------------ - Improved ZEO client cache implementation to make it less likely to evict objects that are being used. - Small (possibly negligable) reduction in CPU in ZEO storage servers to service object loads and in networking code. 3.10.2 (2011-02-12) =================== Bugs Fixed ---------- - 3.10 introduced an optimization to try to address BTree conflict errors arrising for basing BTree keys on object ids. The optimization caused object ids allocated in aborted transactions to be reused. Unfortunately, this optimzation led to some rather severe failures in some applications. The symptom is a conflict error in which one of the serials mentioned is zero. This optimization has been removed. See (for example): https://bugs.launchpad.net/zodb/+bug/665452 - ZEO server transaction timeouts weren't logged as critical. https://bugs.launchpad.net/zodb/+bug/670986 3.10.1 (2010-10-27) =================== Bugs Fixed ---------- - When a transaction rolled back a savepoint after adding objects and subsequently added more objects and committed, an error could be raised "ValueError: A different object already has the same oid" causing the transaction to fail. Worse, this could leave a database in a state where subsequent transactions in the same process would fail. https://bugs.launchpad.net/zodb/+bug/665452 - Unix domain sockets didn't work for ZEO (since the addition of IPv6 support). https://bugs.launchpad.net/zodb/+bug/663259 - Removed a missfeature that can cause performance problems when using an external garbage collector with ZEO. When objects were deleted from a storage, invalidations were sent to clients. This makes no sense. It's wildly unlikely that the other connections/clients have copies of the garbage. In normal storage garbage collection, we don't send invalidations. There's no reason to send them when an external garbage collector is used. - ZEO client cache simulation misshandled invalidations causing incorrect statistics and errors. 3.10.0 (2010-10-08) =================== New Features ------------ - There are a number of performance enhancements for ZEO storage servers. - FileStorage indexes use a new format. They are saved and loaded much faster and take less space. Old indexes can still be read, but new indexes won't be readable by older versions of ZODB. - The API for undoing multiple transactions has changed. To undo multiple transactions in a single transaction, pass a list of transaction identifiers to a database's undoMultiple method. Calling a database's undo method multiple times in the same transaction now raises an exception. - The ZEO protocol for undo has changed. The only user-visible consequence of this is that when ZODB 3.10 ZEO servers won't support undo for older clients. - The storage API (IStorage) has been tightened. Now, storages should raise a StorageTransactionError when invalid transactions are passed to tpc_begin, tpc_vote, or tpc_finish. - ZEO clients (``ClientStorage`` instances) now work in forked processes, including those created via ``multiprocessing.Process`` instances. - Broken objects now provide the IBroken interface. - As a convenience, you can now pass an integer port as an address to the ZEO ClientStorage constructor. - As a convenience, there's a new ``client`` function in the ZEO package for constructing a ClientStorage instance. It takes the same arguments as the ClientStorage constructor. - DemoStorages now accept constructor athuments, close_base_on_close and close_changes_on_close, to control whether underlying storages are closed when the DemoStorage is closed. https://bugs.launchpad.net/zodb/+bug/118512 - Removed the dependency on zope.proxy. - Removed support for the _p_independent mini framework, which was made moot by the introduction of multi-version concurrency control several years ago. - Added support for the transaction retry convenience (transaction-manager attempts method) introduced in the ``transaction`` 1.1.0 release. - Enhanced the database opening conveniences: - You can now pass storage keyword arguments to ZODB.DB and ZODB.connection. - You can now pass None (rather than a storage or file name) to get a database with a mapping storage. - Databases now warn when committing very large records (> 16MB). This is to try to warn people of likely design mistakes. There is a new option (large_record_size/large-record-size) to control the record size at which the warning is issued. - Added support for wrapper storages that transform pickle data. Applications for this include compression and encryption. An example wrapper storage implementation, ZODB.tests.hexstorage, was included for testing. It is important that storage implementations not assume that storages contain pickles. Renamed IStorageDB to IStorageWrapper and expanded it to provide methods for transforming and untransforming data records. Storages implementations should use these methods to get pickle data from stored records. - Deprecated ZODB.interfaces.StorageStopIteration. Storage iterator implementations should just raise StopIteration, which means they can now be implemented as generators. - The filestorage packer configuration option noe accepts values of the form ``modname:expression``, allowing the use of packer factories with options. - Added a new API that allows applications to make sure that current data are read. For example, with:: self._p_jar.readCurrent(ob) A conflict error will be raised if the version of ob read by the transaction isn't current when the transaction is committed. Normally, ZODB only assures that objects read are consistent, but not necessarily up to date. Checking whether an object is up to date is important when information read from one object is used to update another. BTrees are an important case of reading one object to update another. Internal nodes are read to decide which leave notes are updated when a BTree is updated. BTrees now use this new API to make sure that internal nodes are up to date on updates. - When transactions are aborted, new object ids allocated during the transaction are saved and used in subsequent transactions. This can help in situations where object ids are used as BTree keys and the sequential allocation of object ids leads to conflict errors. - ZEO servers now support a server_status method for for getting information on the number of clients, lock requests and general statistics. - ZEO clients now support a client_label constructor argument and client-label configuration-file option to specify a label for a client in server logs. This makes it easier to identify specific clients corresponding to server log entries, especially when there are multiple clients originating from the same machine. - Improved ZEO server commit lock logging. Now, locking activity is logged at the debug level until the number of waiting lock requests gets above 3. Log at the critical level when the number of waiting lock requests gets above 9. - The file-storage backup script, repozo, will now create a backup index file if an output file name is given via the --output/-o option. - Added a '--kill-old-on-full' argument to the repozo backup options: if passed, remove any older full or incremental backup files from the repository after doing a full backup. (https://bugs.launchpad.net/zope2/+bug/143158) - The mkzeoinst script has been moved to a separate project: http://pypi.python.org/pypi/zope.mkzeoinstance and is no-longer included with ZODB. - Removed untested unsupported dbmstorage fossile. - ZEO servers no longer log their pids in every log message. It's just not interesting. :) Bugs fixed ---------- - When a pool timeout was specified for a database and old connections were removed due to timing out, an error occured due to a bug in the connection cleanup logic. - When multi-database connections were no longer used and cleaned up, their subconnections weren't cleaned up properly. - ZEO didn't work with IPv6 addrsses. Added IPv6 support contributed by Martin v. Löwis. - A file storage bug could cause ZEO clients to have incorrect information about current object revisions after reconnecting to a database server. - Updated the 'repozo --kill-old-on-full' option to remove any '.index' files corresponding to backups being removed. - ZEO extension methods failed when a client reconnected to a storage. (https://bugs.launchpad.net/zodb/+bug/143344) - Clarified the return Value for lastTransaction in the case when there aren't any transactions. Now a string of 8 nulls (aka "z64") is specified. - Setting _p_changed on a blob wo actually writing anything caused an error. (https://bugs.launchpad.net/zodb/+bug/440234) - The verbose mode of the fstest was broken. (https://bugs.launchpad.net/zodb/+bug/475996) - Object ids created in a savepoint that is rolled back wren't being reused. (https://bugs.launchpad.net/zodb/+bug/588389) - Database connections didn't invalidate cache entries when conflict errors were raised in response to checkCurrentSerialInTransaction errors. Normally, this shouldn't be a problem, since there should be pending invalidations for these oids which will cause the object to be invalidated. There have been issues with ZEO persistent cache management that have caused out of date data to remain in the cache. (It's possible that the last of these were addressed in the 3.10.0b5.) Invalidating read data when there is a conflict error provides some extra insurance. - The interface, ZODB.interfaces.IStorage was incorrect. The store method should never return a sequence of oid and serial pairs. - When a demo storage push method was used to create a new demo storage and the new storage was closed, the original was (incorrectly) closed. - There were numerous bugs in the ZEO cache tracing and analysis code. Cache simulation, while not perfect, seems to be much more accurate now than it was before. The ZEO cache trace statistics and simulation scripts have been given more descriptive names and moved to the ZEO scripts package. - BTree sets and tree sets didn't correctly check values passed to update or to constructors, causing Python to exit under certain circumstances. - Fixed bug in copying a BTrees.Length instance. (https://bugs.launchpad.net/zodb/+bug/516653) - Fixed a serious bug that caused cache failures when run with Python optimization turned on. https://bugs.launchpad.net/zodb/+bug/544305 - When using using a ClientStorage in a Storage server, there was a threading bug that caused clients to get disconnected. - On Mac OS X, clients that connected and disconnected quickly could cause a ZEO server to stop accepting connections, due to a failure to catch errors in the initial part of the connection process. The failure to properly handle exceptions while accepting connections is potentially problematic on other platforms. Fixes: https://bugs.launchpad.net/zodb/+bug/135108 - Object state management wasn't done correctly when classes implemented custom _p_deavtivate methods. (https://bugs.launchpad.net/zodb/+bug/185066) zope2.13-2.13.21/source/ZODB3/ez_setup.pyc0000644000175000017500000002316612214017465016645 0ustar arnauarnauó 40Rc@sdZddlZdZdejd Zi"dd6dd 6d d 6d d 6dd6dd6dd6dd6dd6dd6dd6dd6dd6d d!6d"d#6d$d%6d&d'6d(d)6d*d+6d,d-6d.d/6d0d16d2d36d4d56d6d76d8d96d:d;6d<d=6d>d?6d@dA6dBdC6dDdE6dFdG6dHdI6ZddlZddlZyddJlmZWn!e k rlddJlmZnXdK„Z eeej dLdM„Z eeej dLdN„Z edO„ZdP„ZedQkreejƒdRkrejdSdTkreejdRƒqeejdSƒndS(Us Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. iÿÿÿÿNs0.6c9s0http://pypi.python.org/packages/%s/s/setuptools/it 8822caf901250d848b996b7f25c6e6cassetuptools-0.6b1-py2.3.eggt b79a8a403e4502fbb85ee3f1941735cbssetuptools-0.6b1-py2.4.eggt 5657759d8a6d8fc44070a9d07272d99bssetuptools-0.6b2-py2.3.eggt 4996a8d169d2be661fa32a6e52e4f82assetuptools-0.6b2-py2.4.eggt bb31c0fc7399a63579975cad9f5a0618ssetuptools-0.6b3-py2.3.eggt 38a8c6b3d6ecd22247f179f7da669facssetuptools-0.6b3-py2.4.eggt 62045a24ed4e1ebc77fe039aa4e6f7e5ssetuptools-0.6b4-py2.3.eggt 4cb2a185d228dacffb2d17f103b3b1c4ssetuptools-0.6b4-py2.4.eggt b3f2b5539d65cb7f74ad79127f1a908cssetuptools-0.6c1-py2.3.eggt b45adeda0667d2d2ffe14009364f2a4bssetuptools-0.6c1-py2.4.eggt f0064bf6aa2b7d0f3ba0b43f20817c27ssetuptools-0.6c2-py2.3.eggt 616192eec35f47e8ea16cd6a122b7277ssetuptools-0.6c2-py2.4.eggt f181fa125dfe85a259c9cd6f1d7b78fassetuptools-0.6c3-py2.3.eggt e0ed74682c998bfb73bf803a50e7b71essetuptools-0.6c3-py2.4.eggt abef16fdd61955514841c7c6bd98965essetuptools-0.6c3-py2.5.eggt b0b9131acab32022bfac7f44c5d7971fssetuptools-0.6c4-py2.3.eggt 2a1f9656d4fbf3c97bf946c0a124e6e2ssetuptools-0.6c4-py2.4.eggt 8f5a052e32cdb9c72bcf4b5526f28afcssetuptools-0.6c4-py2.5.eggt ee9fd80965da04f2f3e6b3576e9d8167ssetuptools-0.6c5-py2.3.eggt afe2adf1c01701ee841761f5bcd8aa64ssetuptools-0.6c5-py2.4.eggt a8d3f61494ccaa8714dfed37bccd3d5dssetuptools-0.6c5-py2.5.eggt 35686b78116a668847237b69d549ec20ssetuptools-0.6c6-py2.3.eggt 3c56af57be3225019260a644430065abssetuptools-0.6c6-py2.4.eggt b2f8a7520709a5b34f80946de5f02f53ssetuptools-0.6c6-py2.5.eggt 209fdf9adc3a615e5115b725658e13e2ssetuptools-0.6c7-py2.3.eggt 5a8f954807d46a0fb67cf1f26c55a82essetuptools-0.6c7-py2.4.eggt 45d2ad28f9750e7434111fde831e8372ssetuptools-0.6c7-py2.5.eggt 50759d29b349db8cfd807ba8303f1902ssetuptools-0.6c8-py2.3.eggt cba38d74f7d483c06e9daa6070cce6dessetuptools-0.6c8-py2.4.eggt 1721747ee329dc150590a58b3e1ac95bssetuptools-0.6c8-py2.5.eggt a83c4020414807b496e4cfbe08507c03ssetuptools-0.6c9-py2.3.eggt 260a2be2e5388d66bdaee06abec6342assetuptools-0.6c9-py2.4.eggt fe67c3e5a17b12c0e7c541b7ea43a8e6ssetuptools-0.6c9-py2.5.eggt ca37b1ff16fa2ede6e19383e7b59245assetuptools-0.6c9-py2.6.egg(tmd5cCsV|tkrRt|ƒjƒ}|t|krRtjd|IJtjdƒqRn|S(Ns:md5 validation of %s failed! (Possible download problem?)i(tmd5_dataR"t hexdigesttsyststderrtexit(tegg_nametdatatdigest((s ez_setup.pyt _validate_md5=s icsódtjkpdtjk}‡‡‡‡fd†}yddl}Wntk r]|ƒSXy|jdˆƒdSWnu|jk r×}|rÀtjdˆ|jdfIJtjd ƒqï~tjd=|ƒSn|j k rî|ƒSXdS( sŸAutomatically find/download setuptools and make it available on sys.path `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where setuptools will be downloaded, if it is not already available. If `download_delay` is specified, it should be the number of seconds that will be paused before initiating a download, should one be required. If an older version of setuptools is installed, this routine will print a message to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling script. t pkg_resourcest setuptoolscsAtˆˆˆˆƒ}tjjd|ƒddl}||_dS(Niiÿÿÿÿ(tdownload_setuptoolsR%tpathtinsertR-tbootstrap_install_from(teggR-(t download_basetdownload_delaytto_dirtversion(s ez_setup.pyt do_downloadXs iÿÿÿÿNs setuptools>=s×The required version of setuptools (>=%s) is not available, and can't be installed while this script is running. Please install a more recent version first, using 'easy_install -U setuptools'. (Currently using %r)ii( R%tmodulesR,t ImportErrortrequiretVersionConflictR&targsR'tDistributionNotFound(R6R3R5R4t was_importedR7R,te((R3R4R5R6s ez_setup.pytuse_setuptoolsHs&   cCsGddl}ddl}d|tjd f}||}tjj||ƒ}d} } tjj|ƒs7z ddl m } |r¹| j d||||ƒddl m } | |ƒn| j d|ƒ|j|ƒ} t|| jƒƒ} t|d ƒ} | j| ƒWd| r | jƒn| r3| jƒnXntjj|ƒS( s€Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. iÿÿÿÿNssetuptools-%s-py%s.eggi(tlogs --------------------------------------------------------------------------- This script requires setuptools version %s to run (even to display help). I will attempt to download it for you (from %s), but you may need to enable firewall access for this script first. I will start the download in %d seconds. (Note: if this machine does not have network access, please obtain the file %s and place it in this directory before rerunning this script.) ---------------------------------------------------------------------------(tsleepsDownloading %stwb(turllib2tshutilR%R6tosR/tjointNonetexistst distutilsRAtwarnttimeRBturlopenR+treadtopentwritetclosetrealpath(R6R3R5tdelayRDRER(turltsavetotsrctdstRARBR)((s ez_setup.pyR.qs0      cCs®yddl}Wn“tk r¥d}zPt|ddƒ}tjjd|ƒddlm}|t |ƒ|gƒSWd|r¡t jj |ƒr¡t j |ƒnXn-X|j dkrÒtjdIJtjdƒnd |}ddl}y|j|ƒWny|jk rtyddlm}Wn!tk rCddlm}nX|t |ƒtddƒgƒtjdƒn6X|r˜ddlm}||ƒnd G|Gd GHd GHdS( s-Install or upgrade setuptools and EasyInstalliÿÿÿÿNRSi(tmains0.0.1sYou have an obsolete version of setuptools installed. Please remove it from your system entirely before rerunning this script.is setuptools>=sSetuptools versionsor greater has been installed.s:(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)(R-R9RHR.R%R/R0tsetuptools.command.easy_installRXtlistRFRItunlinkt __version__R&R'R,R:R;t easy_install(targvR6R-R2RXtreqR,((s ez_setup.pyRXÃs>       c Csnddl}xU|D]M}tjj|ƒ}t|dƒ}t|jƒƒjƒt|<|j ƒqWgtj ƒD]}d|^qq}|j ƒdj |ƒ}ddl }|jtjtƒ} t| dƒ}|jƒ} |j ƒ|jd| ƒ} | stjdIJtjdƒn| | jd ƒ || | jd ƒ} t| d ƒ}|j| ƒ|j ƒdS( s Update our built-in md5 registryiÿÿÿÿNtrbs %r: %r, ts md5_data = { ([^}]+)}sInternal error!iitw(treRFR/tbasenameROR"RNR$R#RQtitemstsortRGtinspectt getsourcefileR%R8t__name__tsearchR&R'tstarttendRP( t filenamesRctnametbasetftitR)treplRgtsrcfileRVtmatch((s ez_setup.pyt update_md5ìs,  #    ( t__main__iis --md5update(t__doc__R%tDEFAULT_VERSIONR6t DEFAULT_URLR#RFthashlibR"R9R+tcurdirR@R.RXRuRitlenR^(((s ez_setup.pytsl     (  P )  (zope2.13-2.13.21/source/zdaemon/0000755000175000017500000000000012214017516015073 5ustar arnauarnauzope2.13-2.13.21/source/zdaemon/setup.py0000644000175000017500000000405512214017516016611 0ustar arnauarnau############################################################################## # # Copyright (c) 2006-2009 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## version = '2.0.7' import os entry_points = """ [console_scripts] zdaemon = zdaemon.zdctl:main """ def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() try: from setuptools import setup setuptools_options = dict( zip_safe=False, entry_points=entry_points, include_package_data = True, install_requires=["ZConfig"], extras_require=dict(test=['zope.testing', 'mock']), ) except ImportError: from distutils.core import setup setuptools_options = {} name = "zdaemon" setup( name=name, version = version, url="http://www.python.org/pypi/zdaemon", license="ZPL 2.1", description= "Daemon process control library and tools for Unix-based systems", author="Zope Corporation and Contributors", author_email="zope-dev@zope.org", long_description=( read('README.txt') + '\n' + read('src/zdaemon/README.txt') + '\n' + read('CHANGES.txt') + '\n' + '========\n' + 'Download\n' + '========\n' ), packages=["zdaemon", "zdaemon.tests"], package_dir={"": "src"}, classifiers = [ 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Zope Public License', 'Topic :: Utilities', 'Operating System :: POSIX', ], **setuptools_options) zope2.13-2.13.21/source/zdaemon/PKG-INFO0000644000175000017500000006010012214017516016165 0ustar arnauarnauMetadata-Version: 1.0 Name: zdaemon Version: 2.0.7 Summary: Daemon process control library and tools for Unix-based systems Home-page: http://www.python.org/pypi/zdaemon Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ***************************************************** ``zdaemon`` process controller for Unix-based systems ***************************************************** ``zdaemon`` is a Unix (Unix, Linux, Mac OS X) Python program that wraps commands to make them behave as proper daemons. .. contents:: =============== Using zdaemon =============== zdaemon provides a script, zdaemon, that can be used to running other programs as POSIX (Unix) daemons. (Of course, it is only usable on POSIX-complient systems. Using zdaemon requires specifying a number of options, which can be given in a configuration file, or as command-line options. It also accepts commands teling it what do do. The commands are: start Start a process as a daemon stop Stop a running daemon process restart Stop and then restart a program status Find out if the program is running foreground or fg Run a program kill signal Send a signal to the daemon process reopen_transcript Reopen the transcript log. See the discussion of the transcript log below. help command Get help on a command Commands can be given on a command line, or can be given using an interactive interpreter. Let's start with a simple example. We'll use command-line options to run the echo command: >>> system("./zdaemon -p 'echo hello world' fg") echo hello world hello world Here we used the -p option to specify a program to run. We can specify a program name and command-line options in the program command. Note, however, that the command-line parsing is pretty primitive. Quotes and spaces aren't handled correctly. Let's look at a slightly more complex example. We'll run the sleep command as a daemon :) >>> system("./zdaemon -p 'sleep 100' start") . . daemon process started, pid=819 This ran the sleep deamon. We can check whether it ran with the status command: >>> system("./zdaemon -p 'sleep 100' status") program running; pid=819 We can stop it with the stop command: >>> system("./zdaemon -p 'sleep 100' stop") . . daemon process stopped >>> system("./zdaemon -p 'sleep 100' status") daemon manager not running Normally, we control zdaemon using a configuration file. Let's create a typical configuration file: >>> open('conf', 'w').write( ... ''' ... ... program sleep 100 ... ... ''') Now, we can run with the -C option to read the configuration file: >>> system("./zdaemon -Cconf start") . . daemon process started, pid=1136 If we list the directory: >>> system("ls") conf zdaemon zdsock We'll see that a file, zdsock, was created. This is a unix-domain socket used internally by ZDaemon. We'll normally want to control where this goes. >>> system("./zdaemon -Cconf stop") . . daemon process stopped >>> open('conf', 'w').write( ... ''' ... ... program sleep 100 ... socket-name /tmp/demo.zdsock ... ... '''.replace('/tmp', tmpdir)) >>> system("./zdaemon -Cconf start") . . daemon process started, pid=1139 >>> system("ls") conf zdaemon >>> import os >>> os.path.exists("/tmp/demo.zdsock".replace('/tmp', tmpdir)) True >>> system("./zdaemon -Cconf stop") . . daemon process stopped In the example, we included a command-line argument in the program option. We can also provide options on the command line: >>> open('conf', 'w').write( ... ''' ... ... program sleep ... socket-name /tmp/demo.zdsock ... ... '''.replace('/tmp', tmpdir)) >>> system("./zdaemon -Cconf start 100") . . daemon process started, pid=1149 >>> system("./zdaemon -Cconf status") program running; pid=1149 >>> system("./zdaemon -Cconf stop") . . daemon process stopped Environment Variables ===================== Sometimes, it is necessary to set environment variables before running a program. Perhaps the most common case for this is setting LD_LIBRARY_PATH so that dynamically loaded libraries can be found. >>> open('conf', 'w').write( ... ''' ... ... program env ... socket-name /tmp/demo.zdsock ... ... ... LD_LIBRARY_PATH /home/foo/lib ... HOME /home/foo ... ... '''.replace('/tmp', tmpdir)) >>> system("./zdaemon -Cconf fg") env USER=jim HOME=/home/foo LOGNAME=jim USERNAME=jim TERM=dumb PATH=/home/jim/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin EMACS=t LANG=en_US.UTF-8 SHELL=/bin/bash EDITOR=emacs LD_LIBRARY_PATH=/home/foo/lib Transcript log ============== When zdaemon run a program in daemon mode, it disconnects the program's standard input, standard output, and standard error from the controlling terminal. It can optionally redirect the output to standard error and standard output to a file. This is done with the transcript option. This is, of course, useful for logging output from long-running applications. Let's look at an example. We'll have a long-running process that simple tails a data file: >>> f = open('data', 'w', 0) >>> import os >>> f.write('rec 1\n'); os.fsync(f.fileno()) >>> open('conf', 'w').write( ... ''' ... ... program tail -f data ... transcript log ... ... ''') >>> system("./zdaemon -Cconf start") . . daemon process started, pid=7963 .. Wait a little bit to make sure tail has a chance to work >>> import time >>> time.sleep(0.1) Now, if we look at the log file, it contains the tail output: >>> open('log').read() 'rec 1\n' We can rotate the transcript log by renaming it and telling zdaemon to reopen it: >>> import os >>> os.rename('log', 'log.1') If we generate more output: >>> f.write('rec 2\n'); os.fsync(f.fileno()) .. Wait a little bit to make sure tail has a chance to work >>> time.sleep(1) The output will appear in the old file, because zdaemon still has it open: >>> open('log.1').read() 'rec 1\nrec 2\n' Now, if we tell zdaemon to reopen the file: >>> system("./zdaemon -Cconf reopen_transcript") and generate some output: >>> f.write('rec 3\n'); os.fsync(f.fileno()) .. Wait a little bit to make sure tail has a chance to work >>> time.sleep(1) the output will show up in the new file, not the old: >>> open('log').read() 'rec 3\n' >>> open('log.1').read() 'rec 1\nrec 2\n' Reference Documentation ======================= The following options are available for use in the runner section of configuration files and as command-line options. program Command-line option: -p or --program This option gives the command used to start the subprocess managed by zdaemon. This is currently a simple list of whitespace-delimited words. The first word is the program file, subsequent words are its command line arguments. If the program file contains no slashes, it is searched using $PATH. (Note that there is no way to to include whitespace in the program file or an argument, and under certain circumstances other shell metacharacters are also a problem.) socket-name Command-line option: -s or --socket-name. The pathname of the Unix domain socket used for communication between the zdaemon command-line tool and a deamon-management process. The default is relative to the current directory in which zdaemon is started. You want to specify an absolute pathname here. This defaults to "zdsock", which is created in the directory in which zdrun is started. daemon Command-line option: -d or --daemon. If this option is true, zdaemon runs in the background as a true daemon. It forks a child process which becomes the subprocess manager, while the parent exits (making the shell that started it believe it is done). The child process also does the following: - if the directory option is set, change into that directory - redirect stdin, stdout and stderr to /dev/null - call setsid() so it becomes a session leader - call umask() with specified value The default for this option is on by default. The command-line option therefore has no effect. To disable daemon mode, you must use a configuration file:: program sleep 1 daemon off directory Command-line option: -z or --directory. If the daemon option is true (default), this option can specify a directory into which zdrun.py changes as part of the "daemonizing". If the daemon option is false, this option is ignored. backoff-limit Command-line option: -b or --backoff-limit. When the subprocess crashes, zdaemon inserts a one-second delay before it restarts it. When the subprocess crashes again right away, the delay is incremented by one second, and so on. What happens when the delay has reached the value of backoff-limit (in seconds), depends on the value of the forever option. If forever is false, zdaemon gives up at this point, and exits. An always-crashing subprocess will have been restarted exactly backoff-limit times in this case. If forever is true, zdaemon continues to attempt to restart the process, keeping the delay at backoff-limit seconds. If the subprocess stays up for more than backoff-limit seconds, the delay is reset to 1 second. This defaults to 10. forever Command-line option: -f or --forever. If this option is true, zdaemon will keep restarting a crashing subprocess forever. If it is false, it will give up after backoff-limit crashes in a row. See the description of backoff-limit for details. This is disabled by default. exit-codes Command-line option: -x or --exit-codes. This defaults to 0,2. If the subprocess exits with an exit status that is equal to one of the integers in this list, zdaemon will not restart it. The default list requires some explanation. Exit status 0 is considered a willful successful exit; the ZEO and Zope server processes use this exit status when they want to stop without being restarted. (Including in response to a SIGTERM.) Exit status 2 is typically issued for command line syntax errors; in this case, restarting the program will not help! NOTE: this mechanism overrides the backoff-limit and forever options; i.e. even if forever is true, a subprocess exit status code in this list makes zdaemon give up. To disable this, change the value to an empty list. user Command-line option: -u or --user. When zdaemon is started by root, this option specifies the user as who the the zdaemon process (and hence the daemon subprocess) will run. This can be a user name or a numeric user id. Both the user and the group are set from the corresponding password entry, using setuid() and setgid(). This is done before zdaemon does anything else besides parsing its command line arguments. NOTE: when zdaemon is not started by root, specifying this option is an error. (XXX This may be a mistake.) XXX The zdaemon event log file may be opened *before* setuid() is called. Is this good or bad? umask Command-line option: -m or --umask. When daemon mode is used, this option specifies the octal umask of the subprocess. default-to-interactive If this option is true, zdaemon enters interactive mode when it is invoked without a positional command argument. If it is false, you must use the -i or --interactive command line option to zdaemon to enter interactive mode. This is enabled by default. logfile This option specifies a log file that is the default target of the "logtail" zdaemon command. NOTE: This is NOT the log file to which zdaemon writes its logging messages! That log file is specified by the section described below. transcript The name of a file in which a transcript of all output from the command being run will be written to when daemonized. If not specified, output from the command will be discarded. This only takes effect when the "daemon" option is enabled. prompt The prompt shown by the controller program. The default must be provided by the application. (Note that a few other options are available to support old configuration files, but aren't needed any more and can generally be ignored.) In addition to the runner section, you can use an eventlog section that specified one or more logfile subsections:: path /var/log/foo/foo.log path STDOUT In this example, log output is sent to a file and to standard out. Log output from zdaemon usually isn't very interesting but can be handy for debugging. =========== Changelog =========== 2.0.7 (2012-06-08) ================== - Fixed: The change in 2.0.6 to set a user's supplemental groups broke common configurations in which the effective user was set via ``su`` or ``sudo -u`` prior to invoking zdaemon. Now, zdaemon doesn't set groups or the effective user if the effective user is already set to the configured user. 2.0.6 (2012-06-07) ================== - Fixed: When the ``user`` option was used to run as a particular user, supplemental groups weren't set to the user's supplemental groups. 2.0.5 (2012-06-07) ================== (Accidental release. Please ignore.) 2.0.4 (2009-04-20) ================== - Version 2.0.3 broke support for relative paths to the socket (``-s`` option and ``socket-name`` parameter), now relative paths work again as in version 2.0.2. - Fixed change log format, made table of contents nicer. - Fixed author's email address. - Removed zpkg stuff. 2.0.3 (2009-04-11) ================== - Added support to bootstrap on Jython. - If the run directory does not exist it will be created. This allow to use `/var/run/mydaemon` as run directory when /var/run is a tmpfs (LP #318118). Bugs Fixed ---------- - No longer uses a hardcoded filename (/tmp/demo.zdsock) in unit tests. This lets you run the tests on Python 2.4 and 2.5 simultaneously without spurious errors. - make -h work again for both runner and control scripts. Help is now taken from the __doc__ of the options class users by the zdaemon script being run. 2.0.2 (2008-04-05) ================== Bugs Fixed ---------- - Fixed backwards incompatible change in handling of environment option. 2.0.1 (2007-10-31) ================== Bugs Fixed ---------- - Fixed test renormalizer that did not work in certain cases where the environment was complex. 2.0.0 (2007-07-19) ================== - Final release for 2.0.0. 2.0a6 (2007-01-11) ================== Bugs Fixed ---------- - When the user option was used, it only affected running the daemon. 2.0a3, 2.0a4, 2.0a5 (2007-01-10) ================================ Bugs Fixed ---------- - The new (2.0) mechanism used by zdaemon to start the daemon manager broke some applications that extended zdaemon. - Added extra checks to deal with programs that extend zdaemon and copy the schema and thus don't see updates to the ZConfig schema. 2.0a2 (2007-01-10) ================== New Features ------------ - Added support for setting environment variables in the configuration file. This is useful when zdaemon is used to run programs that need environment variables set (e.g. LD_LIBRARY_PATH). - Added a command to rotate the transcript log. 2.0a1 (2006-12-21) ================== Bugs Fixed ---------- - In non-daemon mode, start hung, producing annoying dots when the program exited. - The start command hung producing annoying dots if the deamon failed to start. - foreground and start had different semantics because one used os.system and another used os.spawn New Features ------------ - Documentation - Command-line arguments can now be supplied to the start and foreground (fg) commands - zdctl now invokes itself to run zdrun. This means that it's no-longer necessary to generate a separate zdrun script. This especially when the magic techniques to find and run zdrun using directory sniffing fail to set the path corrrectly. - The daemon mode is now enabled by default. To get non-deamon mode, you have to use a configuration file and set deamon to off there. The old -d option is kept for backward compatibility, but is a no-op. 1.4a1 (2005-11-21) ================== - Fixed a bug in the distribution setup file. 1.4a1 (2005-11-05) ================== - First semi-formal release. After some unknown release(???) =============================== - Made 'zdaemon.zdoptions' not fail for --help when __main__.__doc__ is None. After 1.1 ========= - Updated test 'testRunIgnoresParentSignals': o Use 'mkdtemp' to create a temporary directory to hold the test socket rather than creating the test socket in the test directory. Hopefully this will be more robust. Sometimes the test directory has a path so long that the test socket can't be created. o Changed management of 'donothing.sh'. This script is now created by the test in the temporarary directory with the necessary permissions. This is to avoids possible mangling of permissions leading to spurious test failures. It also avoids management of a file in the source tree, which is a bonus. - Rearranged source tree to conform to more usual zpkg-based layout: o Python package lives under 'src'. o Dependencies added to 'src' as 'svn:externals'. o Unit tests can now be run from a checkout. - Made umask-based test failures due to running as root emit a more forceful warning. 1.1 (2005-06-09) ================ - SVN tag: svn://svn.zope.org/repos/main/zdaemon/tags/zdaemon-1.1 - Tagged to make better 'svn:externals' linkage possible. To-Dos ====== More docs: - Document/demonstrate some important features, such as: - working directory Bugs: - help command ======== Download ======== Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Zope Public License Classifier: Topic :: Utilities Classifier: Operating System :: POSIX zope2.13-2.13.21/source/zdaemon/pip-egg-info/0000755000175000017500000000000012214017516017354 5ustar arnauarnauzope2.13-2.13.21/source/zdaemon/pip-egg-info/zdaemon.egg-info/0000755000175000017500000000000012214017516022503 5ustar arnauarnauzope2.13-2.13.21/source/zdaemon/pip-egg-info/zdaemon.egg-info/PKG-INFO0000644000175000017500000006010012214017516023575 0ustar arnauarnauMetadata-Version: 1.1 Name: zdaemon Version: 2.0.7 Summary: Daemon process control library and tools for Unix-based systems Home-page: http://www.python.org/pypi/zdaemon Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ***************************************************** ``zdaemon`` process controller for Unix-based systems ***************************************************** ``zdaemon`` is a Unix (Unix, Linux, Mac OS X) Python program that wraps commands to make them behave as proper daemons. .. contents:: =============== Using zdaemon =============== zdaemon provides a script, zdaemon, that can be used to running other programs as POSIX (Unix) daemons. (Of course, it is only usable on POSIX-complient systems. Using zdaemon requires specifying a number of options, which can be given in a configuration file, or as command-line options. It also accepts commands teling it what do do. The commands are: start Start a process as a daemon stop Stop a running daemon process restart Stop and then restart a program status Find out if the program is running foreground or fg Run a program kill signal Send a signal to the daemon process reopen_transcript Reopen the transcript log. See the discussion of the transcript log below. help command Get help on a command Commands can be given on a command line, or can be given using an interactive interpreter. Let's start with a simple example. We'll use command-line options to run the echo command: >>> system("./zdaemon -p 'echo hello world' fg") echo hello world hello world Here we used the -p option to specify a program to run. We can specify a program name and command-line options in the program command. Note, however, that the command-line parsing is pretty primitive. Quotes and spaces aren't handled correctly. Let's look at a slightly more complex example. We'll run the sleep command as a daemon :) >>> system("./zdaemon -p 'sleep 100' start") . . daemon process started, pid=819 This ran the sleep deamon. We can check whether it ran with the status command: >>> system("./zdaemon -p 'sleep 100' status") program running; pid=819 We can stop it with the stop command: >>> system("./zdaemon -p 'sleep 100' stop") . . daemon process stopped >>> system("./zdaemon -p 'sleep 100' status") daemon manager not running Normally, we control zdaemon using a configuration file. Let's create a typical configuration file: >>> open('conf', 'w').write( ... ''' ... ... program sleep 100 ... ... ''') Now, we can run with the -C option to read the configuration file: >>> system("./zdaemon -Cconf start") . . daemon process started, pid=1136 If we list the directory: >>> system("ls") conf zdaemon zdsock We'll see that a file, zdsock, was created. This is a unix-domain socket used internally by ZDaemon. We'll normally want to control where this goes. >>> system("./zdaemon -Cconf stop") . . daemon process stopped >>> open('conf', 'w').write( ... ''' ... ... program sleep 100 ... socket-name /tmp/demo.zdsock ... ... '''.replace('/tmp', tmpdir)) >>> system("./zdaemon -Cconf start") . . daemon process started, pid=1139 >>> system("ls") conf zdaemon >>> import os >>> os.path.exists("/tmp/demo.zdsock".replace('/tmp', tmpdir)) True >>> system("./zdaemon -Cconf stop") . . daemon process stopped In the example, we included a command-line argument in the program option. We can also provide options on the command line: >>> open('conf', 'w').write( ... ''' ... ... program sleep ... socket-name /tmp/demo.zdsock ... ... '''.replace('/tmp', tmpdir)) >>> system("./zdaemon -Cconf start 100") . . daemon process started, pid=1149 >>> system("./zdaemon -Cconf status") program running; pid=1149 >>> system("./zdaemon -Cconf stop") . . daemon process stopped Environment Variables ===================== Sometimes, it is necessary to set environment variables before running a program. Perhaps the most common case for this is setting LD_LIBRARY_PATH so that dynamically loaded libraries can be found. >>> open('conf', 'w').write( ... ''' ... ... program env ... socket-name /tmp/demo.zdsock ... ... ... LD_LIBRARY_PATH /home/foo/lib ... HOME /home/foo ... ... '''.replace('/tmp', tmpdir)) >>> system("./zdaemon -Cconf fg") env USER=jim HOME=/home/foo LOGNAME=jim USERNAME=jim TERM=dumb PATH=/home/jim/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin EMACS=t LANG=en_US.UTF-8 SHELL=/bin/bash EDITOR=emacs LD_LIBRARY_PATH=/home/foo/lib Transcript log ============== When zdaemon run a program in daemon mode, it disconnects the program's standard input, standard output, and standard error from the controlling terminal. It can optionally redirect the output to standard error and standard output to a file. This is done with the transcript option. This is, of course, useful for logging output from long-running applications. Let's look at an example. We'll have a long-running process that simple tails a data file: >>> f = open('data', 'w', 0) >>> import os >>> f.write('rec 1\n'); os.fsync(f.fileno()) >>> open('conf', 'w').write( ... ''' ... ... program tail -f data ... transcript log ... ... ''') >>> system("./zdaemon -Cconf start") . . daemon process started, pid=7963 .. Wait a little bit to make sure tail has a chance to work >>> import time >>> time.sleep(0.1) Now, if we look at the log file, it contains the tail output: >>> open('log').read() 'rec 1\n' We can rotate the transcript log by renaming it and telling zdaemon to reopen it: >>> import os >>> os.rename('log', 'log.1') If we generate more output: >>> f.write('rec 2\n'); os.fsync(f.fileno()) .. Wait a little bit to make sure tail has a chance to work >>> time.sleep(1) The output will appear in the old file, because zdaemon still has it open: >>> open('log.1').read() 'rec 1\nrec 2\n' Now, if we tell zdaemon to reopen the file: >>> system("./zdaemon -Cconf reopen_transcript") and generate some output: >>> f.write('rec 3\n'); os.fsync(f.fileno()) .. Wait a little bit to make sure tail has a chance to work >>> time.sleep(1) the output will show up in the new file, not the old: >>> open('log').read() 'rec 3\n' >>> open('log.1').read() 'rec 1\nrec 2\n' Reference Documentation ======================= The following options are available for use in the runner section of configuration files and as command-line options. program Command-line option: -p or --program This option gives the command used to start the subprocess managed by zdaemon. This is currently a simple list of whitespace-delimited words. The first word is the program file, subsequent words are its command line arguments. If the program file contains no slashes, it is searched using $PATH. (Note that there is no way to to include whitespace in the program file or an argument, and under certain circumstances other shell metacharacters are also a problem.) socket-name Command-line option: -s or --socket-name. The pathname of the Unix domain socket used for communication between the zdaemon command-line tool and a deamon-management process. The default is relative to the current directory in which zdaemon is started. You want to specify an absolute pathname here. This defaults to "zdsock", which is created in the directory in which zdrun is started. daemon Command-line option: -d or --daemon. If this option is true, zdaemon runs in the background as a true daemon. It forks a child process which becomes the subprocess manager, while the parent exits (making the shell that started it believe it is done). The child process also does the following: - if the directory option is set, change into that directory - redirect stdin, stdout and stderr to /dev/null - call setsid() so it becomes a session leader - call umask() with specified value The default for this option is on by default. The command-line option therefore has no effect. To disable daemon mode, you must use a configuration file:: program sleep 1 daemon off directory Command-line option: -z or --directory. If the daemon option is true (default), this option can specify a directory into which zdrun.py changes as part of the "daemonizing". If the daemon option is false, this option is ignored. backoff-limit Command-line option: -b or --backoff-limit. When the subprocess crashes, zdaemon inserts a one-second delay before it restarts it. When the subprocess crashes again right away, the delay is incremented by one second, and so on. What happens when the delay has reached the value of backoff-limit (in seconds), depends on the value of the forever option. If forever is false, zdaemon gives up at this point, and exits. An always-crashing subprocess will have been restarted exactly backoff-limit times in this case. If forever is true, zdaemon continues to attempt to restart the process, keeping the delay at backoff-limit seconds. If the subprocess stays up for more than backoff-limit seconds, the delay is reset to 1 second. This defaults to 10. forever Command-line option: -f or --forever. If this option is true, zdaemon will keep restarting a crashing subprocess forever. If it is false, it will give up after backoff-limit crashes in a row. See the description of backoff-limit for details. This is disabled by default. exit-codes Command-line option: -x or --exit-codes. This defaults to 0,2. If the subprocess exits with an exit status that is equal to one of the integers in this list, zdaemon will not restart it. The default list requires some explanation. Exit status 0 is considered a willful successful exit; the ZEO and Zope server processes use this exit status when they want to stop without being restarted. (Including in response to a SIGTERM.) Exit status 2 is typically issued for command line syntax errors; in this case, restarting the program will not help! NOTE: this mechanism overrides the backoff-limit and forever options; i.e. even if forever is true, a subprocess exit status code in this list makes zdaemon give up. To disable this, change the value to an empty list. user Command-line option: -u or --user. When zdaemon is started by root, this option specifies the user as who the the zdaemon process (and hence the daemon subprocess) will run. This can be a user name or a numeric user id. Both the user and the group are set from the corresponding password entry, using setuid() and setgid(). This is done before zdaemon does anything else besides parsing its command line arguments. NOTE: when zdaemon is not started by root, specifying this option is an error. (XXX This may be a mistake.) XXX The zdaemon event log file may be opened *before* setuid() is called. Is this good or bad? umask Command-line option: -m or --umask. When daemon mode is used, this option specifies the octal umask of the subprocess. default-to-interactive If this option is true, zdaemon enters interactive mode when it is invoked without a positional command argument. If it is false, you must use the -i or --interactive command line option to zdaemon to enter interactive mode. This is enabled by default. logfile This option specifies a log file that is the default target of the "logtail" zdaemon command. NOTE: This is NOT the log file to which zdaemon writes its logging messages! That log file is specified by the section described below. transcript The name of a file in which a transcript of all output from the command being run will be written to when daemonized. If not specified, output from the command will be discarded. This only takes effect when the "daemon" option is enabled. prompt The prompt shown by the controller program. The default must be provided by the application. (Note that a few other options are available to support old configuration files, but aren't needed any more and can generally be ignored.) In addition to the runner section, you can use an eventlog section that specified one or more logfile subsections:: path /var/log/foo/foo.log path STDOUT In this example, log output is sent to a file and to standard out. Log output from zdaemon usually isn't very interesting but can be handy for debugging. =========== Changelog =========== 2.0.7 (2012-06-08) ================== - Fixed: The change in 2.0.6 to set a user's supplemental groups broke common configurations in which the effective user was set via ``su`` or ``sudo -u`` prior to invoking zdaemon. Now, zdaemon doesn't set groups or the effective user if the effective user is already set to the configured user. 2.0.6 (2012-06-07) ================== - Fixed: When the ``user`` option was used to run as a particular user, supplemental groups weren't set to the user's supplemental groups. 2.0.5 (2012-06-07) ================== (Accidental release. Please ignore.) 2.0.4 (2009-04-20) ================== - Version 2.0.3 broke support for relative paths to the socket (``-s`` option and ``socket-name`` parameter), now relative paths work again as in version 2.0.2. - Fixed change log format, made table of contents nicer. - Fixed author's email address. - Removed zpkg stuff. 2.0.3 (2009-04-11) ================== - Added support to bootstrap on Jython. - If the run directory does not exist it will be created. This allow to use `/var/run/mydaemon` as run directory when /var/run is a tmpfs (LP #318118). Bugs Fixed ---------- - No longer uses a hardcoded filename (/tmp/demo.zdsock) in unit tests. This lets you run the tests on Python 2.4 and 2.5 simultaneously without spurious errors. - make -h work again for both runner and control scripts. Help is now taken from the __doc__ of the options class users by the zdaemon script being run. 2.0.2 (2008-04-05) ================== Bugs Fixed ---------- - Fixed backwards incompatible change in handling of environment option. 2.0.1 (2007-10-31) ================== Bugs Fixed ---------- - Fixed test renormalizer that did not work in certain cases where the environment was complex. 2.0.0 (2007-07-19) ================== - Final release for 2.0.0. 2.0a6 (2007-01-11) ================== Bugs Fixed ---------- - When the user option was used, it only affected running the daemon. 2.0a3, 2.0a4, 2.0a5 (2007-01-10) ================================ Bugs Fixed ---------- - The new (2.0) mechanism used by zdaemon to start the daemon manager broke some applications that extended zdaemon. - Added extra checks to deal with programs that extend zdaemon and copy the schema and thus don't see updates to the ZConfig schema. 2.0a2 (2007-01-10) ================== New Features ------------ - Added support for setting environment variables in the configuration file. This is useful when zdaemon is used to run programs that need environment variables set (e.g. LD_LIBRARY_PATH). - Added a command to rotate the transcript log. 2.0a1 (2006-12-21) ================== Bugs Fixed ---------- - In non-daemon mode, start hung, producing annoying dots when the program exited. - The start command hung producing annoying dots if the deamon failed to start. - foreground and start had different semantics because one used os.system and another used os.spawn New Features ------------ - Documentation - Command-line arguments can now be supplied to the start and foreground (fg) commands - zdctl now invokes itself to run zdrun. This means that it's no-longer necessary to generate a separate zdrun script. This especially when the magic techniques to find and run zdrun using directory sniffing fail to set the path corrrectly. - The daemon mode is now enabled by default. To get non-deamon mode, you have to use a configuration file and set deamon to off there. The old -d option is kept for backward compatibility, but is a no-op. 1.4a1 (2005-11-21) ================== - Fixed a bug in the distribution setup file. 1.4a1 (2005-11-05) ================== - First semi-formal release. After some unknown release(???) =============================== - Made 'zdaemon.zdoptions' not fail for --help when __main__.__doc__ is None. After 1.1 ========= - Updated test 'testRunIgnoresParentSignals': o Use 'mkdtemp' to create a temporary directory to hold the test socket rather than creating the test socket in the test directory. Hopefully this will be more robust. Sometimes the test directory has a path so long that the test socket can't be created. o Changed management of 'donothing.sh'. This script is now created by the test in the temporarary directory with the necessary permissions. This is to avoids possible mangling of permissions leading to spurious test failures. It also avoids management of a file in the source tree, which is a bonus. - Rearranged source tree to conform to more usual zpkg-based layout: o Python package lives under 'src'. o Dependencies added to 'src' as 'svn:externals'. o Unit tests can now be run from a checkout. - Made umask-based test failures due to running as root emit a more forceful warning. 1.1 (2005-06-09) ================ - SVN tag: svn://svn.zope.org/repos/main/zdaemon/tags/zdaemon-1.1 - Tagged to make better 'svn:externals' linkage possible. To-Dos ====== More docs: - Document/demonstrate some important features, such as: - working directory Bugs: - help command ======== Download ======== Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Zope Public License Classifier: Topic :: Utilities Classifier: Operating System :: POSIX zope2.13-2.13.21/source/zdaemon/pip-egg-info/zdaemon.egg-info/dependency_links.txt0000644000175000017500000000000112214017516026551 0ustar arnauarnau zope2.13-2.13.21/source/zdaemon/pip-egg-info/zdaemon.egg-info/requires.txt0000644000175000017500000000004112214017516025076 0ustar arnauarnauZConfig [test] zope.testing mockzope2.13-2.13.21/source/zdaemon/pip-egg-info/zdaemon.egg-info/entry_points.txt0000644000175000017500000000006012214017516025775 0ustar arnauarnau [console_scripts] zdaemon = zdaemon.zdctl:main zope2.13-2.13.21/source/zdaemon/pip-egg-info/zdaemon.egg-info/top_level.txt0000644000175000017500000000001012214017516025224 0ustar arnauarnauzdaemon zope2.13-2.13.21/source/zdaemon/pip-egg-info/zdaemon.egg-info/SOURCES.txt0000644000175000017500000000116512214017516024372 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zdaemon.egg-info/PKG-INFO pip-egg-info/zdaemon.egg-info/SOURCES.txt pip-egg-info/zdaemon.egg-info/dependency_links.txt pip-egg-info/zdaemon.egg-info/entry_points.txt pip-egg-info/zdaemon.egg-info/not-zip-safe pip-egg-info/zdaemon.egg-info/requires.txt pip-egg-info/zdaemon.egg-info/top_level.txt src/zdaemon/__init__.py src/zdaemon/zdctl.py src/zdaemon/zdoptions.py src/zdaemon/zdrun.py src/zdaemon/tests/__init__.py src/zdaemon/tests/nokill.py src/zdaemon/tests/parent.py src/zdaemon/tests/tests.py src/zdaemon/tests/testuser.py src/zdaemon/tests/testzdoptions.py src/zdaemon/tests/testzdrun.pyzope2.13-2.13.21/source/zdaemon/pip-egg-info/zdaemon.egg-info/not-zip-safe0000644000175000017500000000000112214017516024731 0ustar arnauarnau zope2.13-2.13.21/source/zdaemon/README.txt0000644000175000017500000000045212214017516016572 0ustar arnauarnau***************************************************** ``zdaemon`` process controller for Unix-based systems ***************************************************** ``zdaemon`` is a Unix (Unix, Linux, Mac OS X) Python program that wraps commands to make them behave as proper daemons. .. contents:: zope2.13-2.13.21/source/zdaemon/setup.cfg0000644000175000017500000000007312214017516016714 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zdaemon/buildout.cfg0000644000175000017500000000074712214017516017413 0ustar arnauarnau[buildout] develop = . parts = test scripts coverage-test coverage-report find-links = http://download.zope.org/distribution/ [test] recipe = zc.recipe.testrunner eggs = zdaemon [test] [coverage-test] recipe = zc.recipe.testrunner eggs = ${test:eggs} defaults = ['--coverage', '../../coverage'] [coverage-report] recipe = zc.recipe.egg eggs = z3c.coverage scripts = coverage=coverage-report arguments = ('coverage', 'coverage/report') [scripts] recipe = zc.recipe.egg eggs = zdaemon zope2.13-2.13.21/source/zdaemon/bootstrap.py0000644000175000017500000000413112214017516017461 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 68905 2006-06-29 10:46:56Z jim $ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources is_jython = sys.platform.startswith('java') if is_jython: import subprocess ws = pkg_resources.working_set if is_jython: assert subprocess.Popen( [sys.executable] + ['-c', 'from setuptools.command.easy_install import main; main()', '-mqNxd', tmpeggs, 'zc.buildout'], env = dict( PYTHONPATH = ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', 'from setuptools.command.easy_install import main; main()', '-mqNxd', tmpeggs, 'zc.buildout', {'PYTHONPATH': ws.find(pkg_resources.Requirement.parse('setuptools')).location }, ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zdaemon/CHANGES.txt0000644000175000017500000001206712214017516016712 0ustar arnauarnau=========== Changelog =========== 2.0.7 (2012-06-08) ================== - Fixed: The change in 2.0.6 to set a user's supplemental groups broke common configurations in which the effective user was set via ``su`` or ``sudo -u`` prior to invoking zdaemon. Now, zdaemon doesn't set groups or the effective user if the effective user is already set to the configured user. 2.0.6 (2012-06-07) ================== - Fixed: When the ``user`` option was used to run as a particular user, supplemental groups weren't set to the user's supplemental groups. 2.0.5 (2012-06-07) ================== (Accidental release. Please ignore.) 2.0.4 (2009-04-20) ================== - Version 2.0.3 broke support for relative paths to the socket (``-s`` option and ``socket-name`` parameter), now relative paths work again as in version 2.0.2. - Fixed change log format, made table of contents nicer. - Fixed author's email address. - Removed zpkg stuff. 2.0.3 (2009-04-11) ================== - Added support to bootstrap on Jython. - If the run directory does not exist it will be created. This allow to use `/var/run/mydaemon` as run directory when /var/run is a tmpfs (LP #318118). Bugs Fixed ---------- - No longer uses a hardcoded filename (/tmp/demo.zdsock) in unit tests. This lets you run the tests on Python 2.4 and 2.5 simultaneously without spurious errors. - make -h work again for both runner and control scripts. Help is now taken from the __doc__ of the options class users by the zdaemon script being run. 2.0.2 (2008-04-05) ================== Bugs Fixed ---------- - Fixed backwards incompatible change in handling of environment option. 2.0.1 (2007-10-31) ================== Bugs Fixed ---------- - Fixed test renormalizer that did not work in certain cases where the environment was complex. 2.0.0 (2007-07-19) ================== - Final release for 2.0.0. 2.0a6 (2007-01-11) ================== Bugs Fixed ---------- - When the user option was used, it only affected running the daemon. 2.0a3, 2.0a4, 2.0a5 (2007-01-10) ================================ Bugs Fixed ---------- - The new (2.0) mechanism used by zdaemon to start the daemon manager broke some applications that extended zdaemon. - Added extra checks to deal with programs that extend zdaemon and copy the schema and thus don't see updates to the ZConfig schema. 2.0a2 (2007-01-10) ================== New Features ------------ - Added support for setting environment variables in the configuration file. This is useful when zdaemon is used to run programs that need environment variables set (e.g. LD_LIBRARY_PATH). - Added a command to rotate the transcript log. 2.0a1 (2006-12-21) ================== Bugs Fixed ---------- - In non-daemon mode, start hung, producing annoying dots when the program exited. - The start command hung producing annoying dots if the deamon failed to start. - foreground and start had different semantics because one used os.system and another used os.spawn New Features ------------ - Documentation - Command-line arguments can now be supplied to the start and foreground (fg) commands - zdctl now invokes itself to run zdrun. This means that it's no-longer necessary to generate a separate zdrun script. This especially when the magic techniques to find and run zdrun using directory sniffing fail to set the path corrrectly. - The daemon mode is now enabled by default. To get non-deamon mode, you have to use a configuration file and set deamon to off there. The old -d option is kept for backward compatibility, but is a no-op. 1.4a1 (2005-11-21) ================== - Fixed a bug in the distribution setup file. 1.4a1 (2005-11-05) ================== - First semi-formal release. After some unknown release(???) =============================== - Made 'zdaemon.zdoptions' not fail for --help when __main__.__doc__ is None. After 1.1 ========= - Updated test 'testRunIgnoresParentSignals': o Use 'mkdtemp' to create a temporary directory to hold the test socket rather than creating the test socket in the test directory. Hopefully this will be more robust. Sometimes the test directory has a path so long that the test socket can't be created. o Changed management of 'donothing.sh'. This script is now created by the test in the temporarary directory with the necessary permissions. This is to avoids possible mangling of permissions leading to spurious test failures. It also avoids management of a file in the source tree, which is a bonus. - Rearranged source tree to conform to more usual zpkg-based layout: o Python package lives under 'src'. o Dependencies added to 'src' as 'svn:externals'. o Unit tests can now be run from a checkout. - Made umask-based test failures due to running as root emit a more forceful warning. 1.1 (2005-06-09) ================ - SVN tag: svn://svn.zope.org/repos/main/zdaemon/tags/zdaemon-1.1 - Tagged to make better 'svn:externals' linkage possible. To-Dos ====== More docs: - Document/demonstrate some important features, such as: - working directory Bugs: - help command zope2.13-2.13.21/source/zdaemon/src/0000755000175000017500000000000012214017516015662 5ustar arnauarnauzope2.13-2.13.21/source/zdaemon/src/zdaemon/0000755000175000017500000000000012214017516017317 5ustar arnauarnauzope2.13-2.13.21/source/zdaemon/src/zdaemon/component.xml0000644000175000017500000002427012214017516022050 0ustar arnauarnau This section describes the options for zdctl.py and zdrun.py. The only required option is "program". Many other options have no default value specified in the schema; in some cases, the program calculates a dynamic default, in others, the feature associated with the option is disabled. For those options that also have corresponding command-line options, the command line option (short and long form) are given here too.
Log configuration for zdctl.py and zdrun.py. These applications will normally use the eventlog section at the top level of the configuration, but will use this eventlog section if it exists. (This is done so that the combined schema for the runner and the controlled application will write to the same logs by default, but a separation of logs can be achieved if desired.)
Command-line option: -p or --program (zdctl.py only). This option gives the command used to start the subprocess managed by zdrun.py. This is currently a simple list of whitespace-delimited words. The first word is the program file, subsequent words are its command line arguments. If the program file contains no slashes, it is searched using $PATH. (XXX There is no way to to include whitespace in the program file or an argument, and under certain circumstances other shell metacharacters are also a problem, e.g. the "foreground" command of zdctl.py.) NOTE: zdrun.py doesn't use this option; it uses its positional arguments. Rather, zdctl.py uses this option to determine the positional argument with which to invoke zdrun.py. (XXX This could be better.) Path to the Python interpreter. Used by zdctl.py to start the zdrun.py process. Defaults to sys.executable. Path to the zdrun.py script. Used by zdctl.py to start the zdrun.py process. Defaults to a file named "zdrun.py" in the same directory as zdctl.py. Command-line option: -s or --socket-name. The pathname of the Unix domain socket used for communication between zdctl.py and zdrun.py. The default is relative to the current directory in which zdctl.py and zdrun.py are started. You want to specify an absolute pathname here. Command-line option: -d or --daemon. If this option is true, zdrun.py runs in the background as a true daemon. It forks a child process which becomes the subprocess manager, while the parent exits (making the shell that started it believe it is done). The child process also does the following: - if the directory option is set, change into that directory - redirect stdin, stdout and stderr to /dev/null - call setsid() so it becomes a session leader - call umask() with specified value Command-line option: -z or --directory. If the daemon option is true, this option can specify a directory into which zdrun.py changes as part of the "daemonizing". If the daemon option is false, this option is ignored. Command-line option: -b or --backoff-limit. When the subprocess crashes, zdrun.py inserts a one-second delay before it restarts it. When the subprocess crashes again right away, the delay is incremented by one second, and so on. What happens when the delay has reached the value of backoff-limit (in seconds), depends on the value of the forever option. If forever is false, zdrun.py gives up at this point, and exits. An always-crashing subprocess will have been restarted exactly backoff-limit times in this case. If forever is true, zdrun.py continues to attempt to restart the process, keeping the delay at backoff-limit seconds. If the subprocess stays up for more than backoff-limit seconds, the delay is reset to 1 second. Command-line option: -f or --forever. If this option is true, zdrun.py will keep restarting a crashing subprocess forever. If it is false, it will give up after backoff-limit crashes in a row. See the description of backoff-limit for details. Command-line option: -x or --exit-codes. If the subprocess exits with an exit status that is equal to one of the integers in this list, zdrun.py will not restart it. The default list requires some explanation. Exit status 0 is considered a willful successful exit; the ZEO and Zope server processes use this exit status when they want to stop without being restarted. (Including in response to a SIGTERM.) Exit status 2 is typically issued for command line syntax errors; in this case, restarting the program will not help! NOTE: this mechanism overrides the backoff-limit and forever options; i.e. even if forever is true, a subprocess exit status code in this list makes zdrun.py give up. To disable this, change the value to an empty list. Command-line option: -u or --user. When zdrun.py is started by root, this option specifies the user as who the the zdrun.py process (and hence the daemon subprocess) will run. This can be a user name or a numeric user id. Both the user and the group are set from the corresponding password entry, using setuid() and setgid(). This is done before zdrun.py does anything else besides parsing its command line arguments. NOTE: when zdrun.py is not started by root, specifying this option is an error. (XXX This may be a mistake.) XXX The zdrun.py event log file may be opened *before* setuid() is called. Is this good or bad? Command-line option: -m or --umask. When daemon mode is used, this option specifies the octal umask of the subprocess. If this option is true, the zdrun.py process will remain even when the daemon subprocess is stopped. In this case, zdctl.py will restart zdrun.py as necessary. If this option is false, zdrun.py will exit when the daemon subprocess is stopped (unless zdrun.py intends to restart it). If this option is true, zdctl.py enters interactive mode when it is invoked without a positional command argument. If it is false, you must use the -i or --interactive command line option to zdctl.py to enter interactive mode. This option specifies a log file that is the default target of the "logtail" zdctl.py command. NOTE: This is NOT the log file to which zdrun.py writes its logging messages! That log file is specified by the <eventlog> section. The name of a file in which a transcript of all output from the command being run will be written to when daemonized. If not specified, output from the command will be discarded. This only takes effect when the "daemon" option is enabled. The prompt shown by the controller program. The default must be provided by the application.
zope2.13-2.13.21/source/zdaemon/src/zdaemon/sample.conf0000644000175000017500000000066512214017516021456 0ustar arnauarnau# Sample config file for zdctl.py and zdrun.py (which share a schema). # Harmless example program sleep 100 # Repeat the defaults backoff-limit 10 daemon True forever True socket-name zdsock exit-codes 0,2 # user has no default umask 022 directory . default-to-interactive True hang-around False level info path /tmp/zdrun.log zope2.13-2.13.21/source/zdaemon/src/zdaemon/__init__.py0000644000175000017500000000127612214017516021436 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """zdaemon -- a package to manage a daemon application.""" zope2.13-2.13.21/source/zdaemon/src/zdaemon/README.txt0000644000175000017500000003167112214017516021025 0ustar arnauarnau=============== Using zdaemon =============== zdaemon provides a script, zdaemon, that can be used to running other programs as POSIX (Unix) daemons. (Of course, it is only usable on POSIX-complient systems. Using zdaemon requires specifying a number of options, which can be given in a configuration file, or as command-line options. It also accepts commands teling it what do do. The commands are: start Start a process as a daemon stop Stop a running daemon process restart Stop and then restart a program status Find out if the program is running foreground or fg Run a program kill signal Send a signal to the daemon process reopen_transcript Reopen the transcript log. See the discussion of the transcript log below. help command Get help on a command Commands can be given on a command line, or can be given using an interactive interpreter. Let's start with a simple example. We'll use command-line options to run the echo command: >>> system("./zdaemon -p 'echo hello world' fg") echo hello world hello world Here we used the -p option to specify a program to run. We can specify a program name and command-line options in the program command. Note, however, that the command-line parsing is pretty primitive. Quotes and spaces aren't handled correctly. Let's look at a slightly more complex example. We'll run the sleep command as a daemon :) >>> system("./zdaemon -p 'sleep 100' start") . . daemon process started, pid=819 This ran the sleep deamon. We can check whether it ran with the status command: >>> system("./zdaemon -p 'sleep 100' status") program running; pid=819 We can stop it with the stop command: >>> system("./zdaemon -p 'sleep 100' stop") . . daemon process stopped >>> system("./zdaemon -p 'sleep 100' status") daemon manager not running Normally, we control zdaemon using a configuration file. Let's create a typical configuration file: >>> open('conf', 'w').write( ... ''' ... ... program sleep 100 ... ... ''') Now, we can run with the -C option to read the configuration file: >>> system("./zdaemon -Cconf start") . . daemon process started, pid=1136 If we list the directory: >>> system("ls") conf zdaemon zdsock We'll see that a file, zdsock, was created. This is a unix-domain socket used internally by ZDaemon. We'll normally want to control where this goes. >>> system("./zdaemon -Cconf stop") . . daemon process stopped >>> open('conf', 'w').write( ... ''' ... ... program sleep 100 ... socket-name /tmp/demo.zdsock ... ... '''.replace('/tmp', tmpdir)) >>> system("./zdaemon -Cconf start") . . daemon process started, pid=1139 >>> system("ls") conf zdaemon >>> import os >>> os.path.exists("/tmp/demo.zdsock".replace('/tmp', tmpdir)) True >>> system("./zdaemon -Cconf stop") . . daemon process stopped In the example, we included a command-line argument in the program option. We can also provide options on the command line: >>> open('conf', 'w').write( ... ''' ... ... program sleep ... socket-name /tmp/demo.zdsock ... ... '''.replace('/tmp', tmpdir)) >>> system("./zdaemon -Cconf start 100") . . daemon process started, pid=1149 >>> system("./zdaemon -Cconf status") program running; pid=1149 >>> system("./zdaemon -Cconf stop") . . daemon process stopped Environment Variables ===================== Sometimes, it is necessary to set environment variables before running a program. Perhaps the most common case for this is setting LD_LIBRARY_PATH so that dynamically loaded libraries can be found. >>> open('conf', 'w').write( ... ''' ... ... program env ... socket-name /tmp/demo.zdsock ... ... ... LD_LIBRARY_PATH /home/foo/lib ... HOME /home/foo ... ... '''.replace('/tmp', tmpdir)) >>> system("./zdaemon -Cconf fg") env USER=jim HOME=/home/foo LOGNAME=jim USERNAME=jim TERM=dumb PATH=/home/jim/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin EMACS=t LANG=en_US.UTF-8 SHELL=/bin/bash EDITOR=emacs LD_LIBRARY_PATH=/home/foo/lib Transcript log ============== When zdaemon run a program in daemon mode, it disconnects the program's standard input, standard output, and standard error from the controlling terminal. It can optionally redirect the output to standard error and standard output to a file. This is done with the transcript option. This is, of course, useful for logging output from long-running applications. Let's look at an example. We'll have a long-running process that simple tails a data file: >>> f = open('data', 'w', 0) >>> import os >>> f.write('rec 1\n'); os.fsync(f.fileno()) >>> open('conf', 'w').write( ... ''' ... ... program tail -f data ... transcript log ... ... ''') >>> system("./zdaemon -Cconf start") . . daemon process started, pid=7963 .. Wait a little bit to make sure tail has a chance to work >>> import time >>> time.sleep(0.1) Now, if we look at the log file, it contains the tail output: >>> open('log').read() 'rec 1\n' We can rotate the transcript log by renaming it and telling zdaemon to reopen it: >>> import os >>> os.rename('log', 'log.1') If we generate more output: >>> f.write('rec 2\n'); os.fsync(f.fileno()) .. Wait a little bit to make sure tail has a chance to work >>> time.sleep(1) The output will appear in the old file, because zdaemon still has it open: >>> open('log.1').read() 'rec 1\nrec 2\n' Now, if we tell zdaemon to reopen the file: >>> system("./zdaemon -Cconf reopen_transcript") and generate some output: >>> f.write('rec 3\n'); os.fsync(f.fileno()) .. Wait a little bit to make sure tail has a chance to work >>> time.sleep(1) the output will show up in the new file, not the old: >>> open('log').read() 'rec 3\n' >>> open('log.1').read() 'rec 1\nrec 2\n' Reference Documentation ======================= The following options are available for use in the runner section of configuration files and as command-line options. program Command-line option: -p or --program This option gives the command used to start the subprocess managed by zdaemon. This is currently a simple list of whitespace-delimited words. The first word is the program file, subsequent words are its command line arguments. If the program file contains no slashes, it is searched using $PATH. (Note that there is no way to to include whitespace in the program file or an argument, and under certain circumstances other shell metacharacters are also a problem.) socket-name Command-line option: -s or --socket-name. The pathname of the Unix domain socket used for communication between the zdaemon command-line tool and a deamon-management process. The default is relative to the current directory in which zdaemon is started. You want to specify an absolute pathname here. This defaults to "zdsock", which is created in the directory in which zdrun is started. daemon Command-line option: -d or --daemon. If this option is true, zdaemon runs in the background as a true daemon. It forks a child process which becomes the subprocess manager, while the parent exits (making the shell that started it believe it is done). The child process also does the following: - if the directory option is set, change into that directory - redirect stdin, stdout and stderr to /dev/null - call setsid() so it becomes a session leader - call umask() with specified value The default for this option is on by default. The command-line option therefore has no effect. To disable daemon mode, you must use a configuration file:: program sleep 1 daemon off directory Command-line option: -z or --directory. If the daemon option is true (default), this option can specify a directory into which zdrun.py changes as part of the "daemonizing". If the daemon option is false, this option is ignored. backoff-limit Command-line option: -b or --backoff-limit. When the subprocess crashes, zdaemon inserts a one-second delay before it restarts it. When the subprocess crashes again right away, the delay is incremented by one second, and so on. What happens when the delay has reached the value of backoff-limit (in seconds), depends on the value of the forever option. If forever is false, zdaemon gives up at this point, and exits. An always-crashing subprocess will have been restarted exactly backoff-limit times in this case. If forever is true, zdaemon continues to attempt to restart the process, keeping the delay at backoff-limit seconds. If the subprocess stays up for more than backoff-limit seconds, the delay is reset to 1 second. This defaults to 10. forever Command-line option: -f or --forever. If this option is true, zdaemon will keep restarting a crashing subprocess forever. If it is false, it will give up after backoff-limit crashes in a row. See the description of backoff-limit for details. This is disabled by default. exit-codes Command-line option: -x or --exit-codes. This defaults to 0,2. If the subprocess exits with an exit status that is equal to one of the integers in this list, zdaemon will not restart it. The default list requires some explanation. Exit status 0 is considered a willful successful exit; the ZEO and Zope server processes use this exit status when they want to stop without being restarted. (Including in response to a SIGTERM.) Exit status 2 is typically issued for command line syntax errors; in this case, restarting the program will not help! NOTE: this mechanism overrides the backoff-limit and forever options; i.e. even if forever is true, a subprocess exit status code in this list makes zdaemon give up. To disable this, change the value to an empty list. user Command-line option: -u or --user. When zdaemon is started by root, this option specifies the user as who the the zdaemon process (and hence the daemon subprocess) will run. This can be a user name or a numeric user id. Both the user and the group are set from the corresponding password entry, using setuid() and setgid(). This is done before zdaemon does anything else besides parsing its command line arguments. NOTE: when zdaemon is not started by root, specifying this option is an error. (XXX This may be a mistake.) XXX The zdaemon event log file may be opened *before* setuid() is called. Is this good or bad? umask Command-line option: -m or --umask. When daemon mode is used, this option specifies the octal umask of the subprocess. default-to-interactive If this option is true, zdaemon enters interactive mode when it is invoked without a positional command argument. If it is false, you must use the -i or --interactive command line option to zdaemon to enter interactive mode. This is enabled by default. logfile This option specifies a log file that is the default target of the "logtail" zdaemon command. NOTE: This is NOT the log file to which zdaemon writes its logging messages! That log file is specified by the section described below. transcript The name of a file in which a transcript of all output from the command being run will be written to when daemonized. If not specified, output from the command will be discarded. This only takes effect when the "daemon" option is enabled. prompt The prompt shown by the controller program. The default must be provided by the application. (Note that a few other options are available to support old configuration files, but aren't needed any more and can generally be ignored.) In addition to the runner section, you can use an eventlog section that specified one or more logfile subsections:: path /var/log/foo/foo.log path STDOUT In this example, log output is sent to a file and to standard out. Log output from zdaemon usually isn't very interesting but can be handy for debugging. zope2.13-2.13.21/source/zdaemon/src/zdaemon/schema.xml0000644000175000017500000000176212214017516021307 0ustar arnauarnau This schema describes various options that control zdctl.py and zdrun.py. zdrun.py is the "daemon process manager"; it runs a subprocess in the background and restarts it when it crashes. zdctl.py is the user interface to zdrun.py; it can tell zdrun.py to start, stop or restart the subprocess, send it a signal, etc. There are two sections: <runner> defines options unique zdctl.py and zdrun.py, and <eventlog> defines a standard event logging section used by zdrun.py. More information about zdctl.py and zdrun.py can be found in the file Doc/zdctl.txt. This all is specific to Unix/Linux.
zope2.13-2.13.21/source/zdaemon/src/zdaemon/zdrun.py0000644000175000017500000006362412214017516021046 0ustar arnauarnau#!python ############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """zrdun -- run an application as a daemon. Usage: python zrdun.py [zrdun-options] program [program-arguments] Options: -C/--configure URL -- configuration file or URL -S/--schema XML Schema -- XML schema for configuration file -b/--backoff-limit SECONDS -- set backoff limit to SECONDS (default 10) -d/--daemon -- run as a proper daemon; fork a subprocess, setsid(), etc. -f/--forever -- run forever (by default, exit when backoff limit is exceeded) -h/--help -- print this usage message and exit -s/--socket-name SOCKET -- Unix socket name for client (default "zdsock") -u/--user USER -- run as this user (or numeric uid) -m/--umask UMASK -- use this umask for daemon subprocess (default is 022) -t/--transcript FILE -- transript of output from daemon-mode program -x/--exit-codes LIST -- list of fatal exit codes (default "0,2") -z/--directory DIRECTORY -- directory to chdir to when using -d (default off) program [program-arguments] -- an arbitrary application to run This daemon manager has two purposes: it restarts the application when it dies, and (when requested to do so with the -d option) it runs the application in the background, detached from the foreground tty session that started it (if any). Exit codes: if at any point the application exits with an exit status listed by the -x option, it is not restarted. Any other form of termination (either being killed by a signal or exiting with an exit status not listed in the -x option) causes it to be restarted. Backoff limit: when the application exits (nearly) immediately after a restart, the daemon manager starts slowing down by delaying between restarts. The delay starts at 1 second and is increased by one on each restart up to the backoff limit given by the -b option; it is reset when the application runs for more than the backoff limit seconds. By default, when the delay reaches the backoff limit, the daemon manager exits (under the assumption that the application has a persistent fault). The -f (forever) option prevents this exit; use it when you expect that a temporary external problem (such as a network outage or an overfull disk) may prevent the application from starting but you want the daemon manager to keep trying. """ import os import sys import time import errno import logging import socket import select import signal import threading from stat import ST_MODE if __name__ == "__main__": # Add the parent of the script directory to the module search path # (but only when the script is run from inside the zdaemon package) from os.path import dirname, basename, abspath, normpath scriptdir = dirname(normpath(abspath(sys.argv[0]))) if basename(scriptdir).lower() == "zdaemon": sys.path.append(dirname(scriptdir)) here = os.path.dirname(os.path.realpath(__file__)) swhome = os.path.dirname(here) for parts in [("src",), ("lib", "python"), ("Lib", "site-packages")]: d = os.path.join(swhome, *(parts + ("zdaemon",))) if os.path.isdir(d): d = os.path.join(swhome, *parts) sys.path.insert(0, d) break from zdaemon.zdoptions import RunnerOptions class ZDRunOptions(RunnerOptions): __doc__ = __doc__ positional_args_allowed = 1 logsectionname = "runner.eventlog" program = None def __init__(self): RunnerOptions.__init__(self) self.add("schemafile", short="S:", long="schema=", default="schema.xml", handler=self.set_schemafile) self.add("transcript", "runner.transcript", "t:", "transcript=", default="/dev/null") def set_schemafile(self, file): self.schemafile = file def realize(self, *args, **kwds): RunnerOptions.realize(self, *args, **kwds) if self.args: self.program = self.args if not self.program: self.usage("no program specified (use -C or positional args)") if self.sockname: # Convert socket name to absolute path self.sockname = os.path.abspath(self.sockname) if self.config_logger is None: # This doesn't perform any configuration of the logging # package, but that's reasonable in this case. self.logger = logging.getLogger() else: self.logger = self.config_logger() def load_logconf(self, sectname): """Load alternate eventlog if the specified section isn't present.""" RunnerOptions.load_logconf(self, sectname) if self.config_logger is None and sectname != "eventlog": RunnerOptions.load_logconf(self, "eventlog") class Subprocess: """A class to manage a subprocess.""" # Initial state; overridden by instance variables pid = 0 # Subprocess pid; 0 when not running lasttime = 0 # Last time the subprocess was started; 0 if never def __init__(self, options, args=None): """Constructor. Arguments are a ZDRunOptions instance and a list of program arguments; the latter's first item must be the program name. """ if args is None: args = options.args if not args: options.usage("missing 'program' argument") self.options = options self.args = args self._set_filename(args[0]) def _set_filename(self, program): """Internal: turn a program name into a file name, using $PATH.""" if "/" in program: filename = program try: st = os.stat(filename) except os.error: self.options.usage("can't stat program %r" % program) else: path = get_path() for dir in path: filename = os.path.join(dir, program) try: st = os.stat(filename) except os.error: continue mode = st[ST_MODE] if mode & 0111: break else: self.options.usage("can't find program %r on PATH %s" % (program, path)) if not os.access(filename, os.X_OK): self.options.usage("no permission to run program %r" % filename) self.filename = filename def spawn(self): """Start the subprocess. It must not be running already. Return the process id. If the fork() call fails, return 0. """ assert not self.pid self.lasttime = time.time() try: pid = os.fork() except os.error: return 0 if pid != 0: # Parent self.pid = pid self.options.logger.info("spawned process pid=%d" % pid) return pid else: # Child try: # Close file descriptors except std{in,out,err}. # XXX We don't know how many to close; hope 100 is plenty. for i in range(3, 100): try: os.close(i) except os.error: pass try: os.execv(self.filename, self.args) except os.error, err: sys.stderr.write("can't exec %r: %s\n" % (self.filename, err)) finally: os._exit(127) # Does not return def kill(self, sig): """Send a signal to the subprocess. This may or may not kill it. Return None if the signal was sent, or an error message string if an error occurred or if the subprocess is not running. """ if not self.pid: return "no subprocess running" try: os.kill(self.pid, sig) except os.error, msg: return str(msg) return None def setstatus(self, sts): """Set process status returned by wait() or waitpid(). This simply notes the fact that the subprocess is no longer running by setting self.pid to 0. """ self.pid = 0 class Daemonizer: def main(self, args=None): self.options = ZDRunOptions() self.options.realize(args) self.logger = self.options.logger self.run() def run(self): self.proc = Subprocess(self.options) self.opensocket() try: self.setsignals() if self.options.daemon: self.daemonize() self.runforever() finally: try: os.unlink(self.options.sockname) except os.error: pass mastersocket = None commandsocket = None def opensocket(self): sockname = self.options.sockname tempname = "%s.%d" % (sockname, os.getpid()) self.unlink_quietly(tempname) while 1: sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: sock.bind(tempname) os.chmod(tempname, 0700) try: os.link(tempname, sockname) break except os.error: # Lock contention, or stale socket. self.checkopen() # Stale socket -- delete, sleep, and try again. msg = "Unlinking stale socket %s; sleep 1" % sockname sys.stderr.write(msg + "\n") self.logger.warn(msg) self.unlink_quietly(sockname) sock.close() time.sleep(1) continue finally: self.unlink_quietly(tempname) sock.listen(1) sock.setblocking(0) self.mastersocket = sock def unlink_quietly(self, filename): try: os.unlink(filename) except os.error: pass def checkopen(self): s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: s.connect(self.options.sockname) s.send("status\n") data = s.recv(1000) s.close() except socket.error: pass else: while data.endswith("\n"): data = data[:-1] msg = ("Another zrdun is already up using socket %r:\n%s" % (self.options.sockname, data)) sys.stderr.write(msg + "\n") self.logger.critical(msg) sys.exit(1) def setsignals(self): signal.signal(signal.SIGTERM, self.sigexit) signal.signal(signal.SIGHUP, self.sigexit) signal.signal(signal.SIGINT, self.sigexit) signal.signal(signal.SIGCHLD, self.sigchild) def sigexit(self, sig, frame): self.logger.critical("daemon manager killed by %s" % signame(sig)) sys.exit(1) waitstatus = None def sigchild(self, sig, frame): try: pid, sts = os.waitpid(-1, os.WNOHANG) except os.error: return if pid: self.waitstatus = pid, sts transcript = None def daemonize(self): # To daemonize, we need to become the leader of our own session # (process) group. If we do not, signals sent to our # parent process will also be sent to us. This might be bad because # signals such as SIGINT can be sent to our parent process during # normal (uninteresting) operations such as when we press Ctrl-C in the # parent terminal window to escape from a logtail command. # To disassociate ourselves from our parent's session group we use # os.setsid. It means "set session id", which has the effect of # disassociating a process from is current session and process group # and setting itself up as a new session leader. # # Unfortunately we cannot call setsid if we're already a session group # leader, so we use "fork" to make a copy of ourselves that is # guaranteed to not be a session group leader. # # We also change directories, set stderr and stdout to null, and # change our umask. # # This explanation was (gratefully) garnered from # http://www.hawklord.uklinux.net/system/daemons/d3.htm pid = os.fork() if pid != 0: # Parent self.logger.debug("daemon manager forked; parent exiting") os._exit(0) # Child self.logger.info("daemonizing the process") if self.options.directory: try: os.chdir(self.options.directory) except os.error, err: self.logger.warn("can't chdir into %r: %s" % (self.options.directory, err)) else: self.logger.info("set current directory: %r" % self.options.directory) os.close(0) sys.stdin = sys.__stdin__ = open("/dev/null") self.transcript = Transcript(self.options.transcript) os.setsid() os.umask(self.options.umask) # XXX Stevens, in his Advanced Unix book, section 13.3 (page # 417) recommends calling umask(0) and closing unused # file descriptors. In his Network Programming book, he # additionally recommends ignoring SIGHUP and forking again # after the setsid() call, for obscure SVR4 reasons. mood = 1 # 1: up, 0: down, -1: suicidal delay = 0 # If nonzero, delay starting or killing until this time killing = 0 # If true, send SIGKILL when delay expires proc = None # Subprocess instance def runforever(self): self.logger.info("daemon manager started") min_mood = not self.options.hang_around while self.mood >= min_mood or self.proc.pid: if self.mood > 0 and not self.proc.pid and not self.delay: pid = self.proc.spawn() if not pid: # Can't fork. Try again later... self.delay = time.time() + self.backofflimit if self.waitstatus: self.reportstatus() r, w, x = [self.mastersocket], [], [] if self.commandsocket: r.append(self.commandsocket) timeout = self.options.backofflimit if self.delay: timeout = max(0, min(timeout, self.delay - time.time())) if timeout <= 0: self.delay = 0 if self.killing and self.proc.pid: self.proc.kill(signal.SIGKILL) self.delay = time.time() + self.options.backofflimit try: r, w, x = select.select(r, w, x, timeout) except select.error, err: if err[0] != errno.EINTR: raise r = w = x = [] if self.waitstatus: self.reportstatus() if self.commandsocket and self.commandsocket in r: try: self.dorecv() except socket.error, msg: self.logger.exception("socket.error in dorecv(): %s" % str(msg)) self.commandsocket = None if self.mastersocket in r: try: self.doaccept() except socket.error, msg: self.logger.exception("socket.error in doaccept(): %s" % str(msg)) self.commandsocket = None self.logger.info("Exiting") sys.exit(0) def reportstatus(self): pid, sts = self.waitstatus self.waitstatus = None es, msg = decode_wait_status(sts) msg = "pid %d: " % pid + msg if pid != self.proc.pid: msg = "unknown " + msg self.logger.warn(msg) else: killing = self.killing if killing: self.killing = 0 self.delay = 0 else: self.governor() self.proc.setstatus(sts) if es in self.options.exitcodes and not killing: msg = msg + "; exiting now" self.logger.info(msg) sys.exit(es) self.logger.info(msg) backoff = 0 def governor(self): # Back off if respawning too frequently now = time.time() if not self.proc.lasttime: pass elif now - self.proc.lasttime < self.options.backofflimit: # Exited rather quickly; slow down the restarts self.backoff += 1 if self.backoff >= self.options.backofflimit: if self.options.forever: self.backoff = self.options.backofflimit else: self.logger.critical("restarting too frequently; quit") sys.exit(1) self.logger.info("sleep %s to avoid rapid restarts" % self.backoff) self.delay = now + self.backoff else: # Reset the backoff timer self.backoff = 0 self.delay = 0 def doaccept(self): if self.commandsocket: # Give up on previous command socket! self.sendreply("Command superseded by new command") self.commandsocket.close() self.commandsocket = None self.commandsocket, addr = self.mastersocket.accept() self.commandbuffer = "" def dorecv(self): data = self.commandsocket.recv(1000) if not data: self.sendreply("Command not terminated by newline") self.commandsocket.close() self.commandsocket = None self.commandbuffer += data if "\n" in self.commandbuffer: self.docommand() self.commandsocket.close() self.commandsocket = None elif len(self.commandbuffer) > 10000: self.sendreply("Command exceeds 10 KB") self.commandsocket.close() self.commandsocket = None def docommand(self): lines = self.commandbuffer.split("\n") args = lines[0].split() if not args: self.sendreply("Empty command") return command = args[0] methodname = "cmd_" + command method = getattr(self, methodname, None) if method: method(args) else: self.sendreply("Unknown command %r; 'help' for a list" % args[0]) def cmd_start(self, args): self.mood = 1 # Up self.backoff = 0 self.delay = 0 self.killing = 0 if not self.proc.pid: self.proc.spawn() self.sendreply("Application started") else: self.sendreply("Application already started") def cmd_stop(self, args): self.mood = 0 # Down self.backoff = 0 self.delay = 0 self.killing = 0 if self.proc.pid: self.proc.kill(signal.SIGTERM) self.sendreply("Sent SIGTERM") self.killing = 1 self.delay = time.time() + self.options.backofflimit else: self.sendreply("Application already stopped") def cmd_restart(self, args): self.mood = 1 # Up self.backoff = 0 self.delay = 0 self.killing = 0 if self.proc.pid: self.proc.kill(signal.SIGTERM) self.sendreply("Sent SIGTERM; will restart later") self.killing = 1 self.delay = time.time() + self.options.backofflimit else: self.proc.spawn() self.sendreply("Application started") def cmd_exit(self, args): self.mood = -1 # Suicidal self.backoff = 0 self.delay = 0 self.killing = 0 if self.proc.pid: self.proc.kill(signal.SIGTERM) self.sendreply("Sent SIGTERM; will exit later") self.killing = 1 self.delay = time.time() + self.options.backofflimit else: self.sendreply("Exiting now") self.logger.info("Exiting") sys.exit(0) def cmd_kill(self, args): if args[1:]: try: sig = int(args[1]) except: self.sendreply("Bad signal %r" % args[1]) return else: sig = signal.SIGTERM if not self.proc.pid: self.sendreply("Application not running") else: msg = self.proc.kill(sig) if msg: self.sendreply("Kill %d failed: %s" % (sig, msg)) else: self.sendreply("Signal %d sent" % sig) def cmd_status(self, args): if not self.proc.pid: status = "stopped" else: status = "running" self.sendreply("status=%s\n" % status + "now=%r\n" % time.time() + "mood=%d\n" % self.mood + "delay=%r\n" % self.delay + "backoff=%r\n" % self.backoff + "lasttime=%r\n" % self.proc.lasttime + "application=%r\n" % self.proc.pid + "manager=%r\n" % os.getpid() + "backofflimit=%r\n" % self.options.backofflimit + "filename=%r\n" % self.proc.filename + "args=%r\n" % self.proc.args) def cmd_reopen_transcript(self, args): if self.transcript is not None: self.transcript.reopen() def cmd_help(self, args): self.sendreply( "Available commands:\n" " help -- return command help\n" " status -- report application status (default command)\n" " kill [signal] -- send a signal to the application\n" " (default signal is SIGTERM)\n" " start -- start the application if not already running\n" " stop -- stop the application if running\n" " (the daemon manager keeps running)\n" " restart -- stop followed by start\n" " exit -- stop the application and exit\n" ) def sendreply(self, msg): try: if not msg.endswith("\n"): msg = msg + "\n" if hasattr(self.commandsocket, "sendall"): self.commandsocket.sendall(msg) else: # This is quadratic, but msg is rarely more than 100 bytes :-) while msg: sent = self.commandsocket.send(msg) msg = msg[sent:] except socket.error, msg: self.logger.warn("Error sending reply: %s" % str(msg)) class Transcript: def __init__(self, filename): self.read_from, w = os.pipe() os.dup2(w, 1) sys.stdout = sys.__stdout__ = os.fdopen(1, "a", 0) os.dup2(w, 2) sys.stderr = sys.__stderr__ = os.fdopen(2, "a", 0) self.filename = filename self.file = open(filename, 'a', 0) self.write = self.file.write self.lock = threading.Lock() thread = threading.Thread(target=self.copy) thread.setDaemon(True) thread.start() def copy(self): lock = self.lock i = [self.read_from] o = e = [] while 1: ii, oo, ee = select.select(i, o, e) lock.acquire() for fd in ii: self.write(os.read(fd, 8192)) lock.release() def reopen(self): self.lock.acquire() self.file.close() self.file = open(self.filename, 'a', 0) self.write = self.file.write self.lock.release() # Helpers for dealing with signals and exit status def decode_wait_status(sts): """Decode the status returned by wait() or waitpid(). Return a tuple (exitstatus, message) where exitstatus is the exit status, or -1 if the process was killed by a signal; and message is a message telling what happened. It is the caller's responsibility to display the message. """ if os.WIFEXITED(sts): es = os.WEXITSTATUS(sts) & 0xffff msg = "exit status %s" % es return es, msg elif os.WIFSIGNALED(sts): sig = os.WTERMSIG(sts) msg = "terminated by %s" % signame(sig) if hasattr(os, "WCOREDUMP"): iscore = os.WCOREDUMP(sts) else: iscore = sts & 0x80 if iscore: msg += " (core dumped)" return -1, msg else: msg = "unknown termination cause 0x%04x" % sts return -1, msg _signames = None def signame(sig): """Return a symbolic name for a signal. Return "signal NNN" if there is no corresponding SIG name in the signal module. """ if _signames is None: _init_signames() return _signames.get(sig) or "signal %d" % sig def _init_signames(): global _signames d = {} for k, v in signal.__dict__.items(): k_startswith = getattr(k, "startswith", None) if k_startswith is None: continue if k_startswith("SIG") and not k_startswith("SIG_"): d[v] = k _signames = d def get_path(): """Return a list corresponding to $PATH, or a default.""" path = ["/bin", "/usr/bin", "/usr/local/bin"] if os.environ.has_key("PATH"): p = os.environ["PATH"] if p: path = p.split(os.pathsep) return path # Main program def main(args=None): assert os.name == "posix", "This code makes many Unix-specific assumptions" d = Daemonizer() d.main(args) if __name__ == "__main__": main() zope2.13-2.13.21/source/zdaemon/src/zdaemon/zdctl.py0000644000175000017500000005555412214017516021027 0ustar arnauarnau#!python ############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """zdctl -- control an application run by zdaemon. Usage: python zdctl.py [-C URL] [-S schema.xml] [-h] [-p PROGRAM] [zdrun-options] [action [arguments]] Options: -C/--configure URL -- configuration file or URL -S/--schema XML Schema -- XML schema for configuration file -h/--help -- print usage message and exit -b/--backoff-limit SECONDS -- set backoff limit to SECONDS (default 10) -d/--daemon -- run as a proper daemon; fork a subprocess, close files etc. -f/--forever -- run forever (by default, exit when backoff limit is exceeded) -h/--help -- print this usage message and exit -i/--interactive -- start an interactive shell after executing commands -l/--logfile -- log file to be read by logtail command -p/--program PROGRAM -- the program to run -s/--socket-name SOCKET -- Unix socket name for client (default "zdsock") -u/--user USER -- run as this user (or numeric uid) -m/--umask UMASK -- use this umask for daemon subprocess (default is 022) -x/--exit-codes LIST -- list of fatal exit codes (default "0,2") -z/--directory DIRECTORY -- directory to chdir to when using -d (default off) action [arguments] -- see below Actions are commands like "start", "stop" and "status". If -i is specified or no action is specified on the command line, a "shell" interpreting actions typed interactively is started (unless the configuration option default_to_interactive is set to false). Use the action "help" to find out about available actions. """ import os import os.path import re import cmd import sys import time import signal import socket import stat if __name__ == "__main__": # Add the parent of the script directory to the module search path # (but only when the script is run from inside the zdaemon package) from os.path import dirname, basename, abspath, normpath scriptdir = dirname(normpath(abspath(sys.argv[0]))) if basename(scriptdir).lower() == "zdaemon": sys.path.append(dirname(scriptdir)) here = os.path.dirname(os.path.realpath(__file__)) swhome = os.path.dirname(here) for parts in [("src",), ("lib", "python"), ("Lib", "site-packages")]: d = os.path.join(swhome, *(parts + ("zdaemon",))) if os.path.isdir(d): d = os.path.join(swhome, *parts) sys.path.insert(0, d) break from zdaemon.zdoptions import RunnerOptions def string_list(arg): return arg.split() class ZDCtlOptions(RunnerOptions): __doc__ = __doc__ positional_args_allowed = True def __init__(self): RunnerOptions.__init__(self) self.add("schemafile", short="S:", long="schema=", default="schema.xml", handler=self.set_schemafile) self.add("interactive", None, "i", "interactive", flag=1) self.add("default_to_interactive", "runner.default_to_interactive", default=1) self.add("program", "runner.program", "p:", "program=", handler=string_list, required="no program specified; use -p or -C") self.add("logfile", "runner.logfile", "l:", "logfile=") self.add("python", "runner.python") self.add("zdrun", "runner.zdrun") programname = os.path.basename(sys.argv[0]) base, ext = os.path.splitext(programname) if ext == ".py": programname = base self.add("prompt", "runner.prompt", default=(programname + ">")) def realize(self, *args, **kwds): RunnerOptions.realize(self, *args, **kwds) # Maybe the config file requires -i or positional args if not self.args and not self.interactive: if not self.default_to_interactive: self.usage("either -i or an action argument is required") self.interactive = 1 # Where's python? if not self.python: self.python = sys.executable def set_schemafile(self, file): self.schemafile = file class ZDCmd(cmd.Cmd): def __init__(self, options): self.options = options self.prompt = self.options.prompt + ' ' cmd.Cmd.__init__(self) self.get_status() if self.zd_status: m = re.search("(?m)^args=(.*)$", self.zd_status) if m: s = m.group(1) args = eval(s, {"__builtins__": {}}) program = self.options.program if args[:len(program)] != program: print "WARNING! zdrun is managing a different program!" print "our program =", program print "daemon's args =", args if options.configroot is not None: env = getattr(options.configroot, 'environment', None) if env is not None: if getattr(env, 'mapping', None) is not None: for k, v in env.mapping.items(): os.environ[k] = v elif type(env) is type({}): for k, v in env.items(): os.environ[k] = v self.create_rundir() self.create_socket_dir() self.set_uid() def create_rundir(self): if self.options.directory is None: return self.create_directory(self.options.directory) def create_socket_dir(self): dir = os.path.dirname(self.options.sockname) if not dir: return self.create_directory(dir) def create_directory(self, directory): if os.path.isdir(directory): return os.mkdir(directory) uid = os.geteuid() if uid == 0 and uid != self.options.uid: # Change owner of directory to target os.chown(directory, self.options.uid, self.options.gid) def set_uid(self): user = self.options.user if user is None: return import pwd try: uid = int(user) except ValueError: try: pwrec = pwd.getpwnam(user) except KeyError: self.options.usage("username %r not found" % user) uid = pwrec.pw_uid else: try: pwrec = pwd.getpwuid(uid) except KeyError: self.options.usage("uid %r not found" % user) # See if we're already that user: euid = os.geteuid() if euid != 0: if euid != uid: self.options.usage("only root can use -u USER to change users") return # OK, we have to set user and groups: os.setgid(pwrec.pw_gid) import grp user = pwrec.pw_name os.setgroups( sorted(g.gr_gid for g in grp.getgrall() # sort for tests if user in g.gr_mem) ) os.setuid(uid) def emptyline(self): # We don't want a blank line to repeat the last command. # Showing status is a nice alternative. self.do_status() def send_action(self, action): """Send an action to the zdrun server and return the response. Return None if the server is not up or any other error happened. """ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: sock.connect(self.options.sockname) sock.send(action + "\n") sock.shutdown(1) # We're not writing any more response = "" while 1: data = sock.recv(1000) if not data: break response += data sock.close() return response except socket.error, msg: return None def get_status(self): self.zd_up = 0 self.zd_pid = 0 self.zd_status = None resp = self.send_action("status") if not resp: return resp m = re.search("(?m)^application=(\d+)$", resp) if not m: return resp self.zd_up = 1 self.zd_pid = int(m.group(1)) self.zd_status = resp return resp def awhile(self, cond, msg): n = 0 was_running = False try: if self.get_status(): was_running = True while not cond(): sys.stdout.write(". ") sys.stdout.flush() time.sleep(1) n += 1 if self.get_status(): was_running = True elif (was_running or n > 10) and not cond(): print "\ndaemon manager not running" return except KeyboardInterrupt: print "^C" print "\n" + msg % self.__dict__ def help_help(self): print "help -- Print a list of available actions." print "help -- Print help for ." def do_EOF(self, arg): print return 1 def help_EOF(self): print "To quit, type ^D or use the quit command." def do_start(self, arg): self.get_status() if not self.zd_up: if self.options.zdrun: args = [self.options.python, self.options.zdrun] else: args = [self.options.python, sys.argv[0]] os.environ['DAEMON_MANAGER_MODE'] = '1' args += self._get_override("-S", "schemafile") args += self._get_override("-C", "configfile") args += self._get_override("-b", "backofflimit") args += self._get_override("-f", "forever", flag=1) args += self._get_override("-s", "sockname") args += self._get_override("-u", "user") if self.options.umask: args += self._get_override("-m", "umask", oct(self.options.umask)) args += self._get_override( "-x", "exitcodes", ",".join(map(str, self.options.exitcodes))) args += self._get_override("-z", "directory") args.extend(self.options.program) args.extend(self.options.args[1:]) if self.options.daemon: flag = os.P_NOWAIT else: flag = os.P_WAIT os.spawnvp(flag, args[0], args) elif not self.zd_pid: self.send_action("start") else: print "daemon process already running; pid=%d" % self.zd_pid return if self.options.daemon: self.awhile(lambda: self.zd_pid, "daemon process started, pid=%(zd_pid)d") def _get_override(self, opt, name, svalue=None, flag=0): value = getattr(self.options, name) if value is None: return [] configroot = self.options.configroot if configroot is not None: for n, cn in self.options.names_list: if n == name and cn: v = configroot for p in cn.split("."): v = getattr(v, p, None) if v is None: break if v == value: # We didn't override anything return [] break if flag: if value: args = [opt] else: args = [] else: if svalue is None: svalue = str(value) args = [opt, svalue] return args def help_start(self): print "start -- Start the daemon process." print " If it is already running, do nothing." def do_stop(self, arg): self.get_status() if not self.zd_up: print "daemon manager not running" elif not self.zd_pid: print "daemon process not running" else: self.send_action("stop") self.awhile(lambda: not self.zd_pid, "daemon process stopped") def do_reopen_transcript(self, arg): if not self.zd_up: print "daemon manager not running" elif not self.zd_pid: print "daemon process not running" else: self.send_action("reopen_transcript") def help_stop(self): print "stop -- Stop the daemon process." print " If it is not running, do nothing." def do_restart(self, arg): self.get_status() pid = self.zd_pid if not pid: self.do_start(arg) else: self.send_action("restart") self.awhile(lambda: self.zd_pid not in (0, pid), "daemon process restarted, pid=%(zd_pid)d") def help_restart(self): print "restart -- Stop and then start the daemon process." def do_kill(self, arg): if not arg: sig = signal.SIGTERM else: try: sig = int(arg) except: # int() can raise any number of exceptions print "invalid signal number", `arg` return self.get_status() if not self.zd_pid: print "daemon process not running" return print "kill(%d, %d)" % (self.zd_pid, sig) try: os.kill(self.zd_pid, sig) except os.error, msg: print "Error:", msg else: print "signal %d sent to process %d" % (sig, self.zd_pid) def help_kill(self): print "kill [sig] -- Send signal sig to the daemon process." print " The default signal is SIGTERM." def do_wait(self, arg): self.awhile(lambda: not self.zd_pid, "daemon process stopped") self.do_status() def help_wait(self): print "wait -- Wait for the daemon process to exit." def do_status(self, arg=""): if arg not in ["", "-l"]: print "status argument must be absent or -l" return self.get_status() if not self.zd_up: print "daemon manager not running" elif not self.zd_pid: print "daemon manager running; daemon process not running" else: print "program running; pid=%d" % self.zd_pid if arg == "-l" and self.zd_status: print self.zd_status def help_status(self): print "status [-l] -- Print status for the daemon process." print " With -l, show raw status output as well." def do_show(self, arg): if not arg: arg = "options" try: method = getattr(self, "show_" + arg) except AttributeError, err: print err self.help_show() return method() def show_options(self): print "zdctl/zdrun options:" print "schemafile: ", repr(self.options.schemafile) print "configfile: ", repr(self.options.configfile) print "interactive: ", repr(self.options.interactive) print "default_to_interactive:", print repr(self.options.default_to_interactive) print "zdrun: ", repr(self.options.zdrun) print "python: ", repr(self.options.python) print "program: ", repr(self.options.program) print "backofflimit:", repr(self.options.backofflimit) print "daemon: ", repr(self.options.daemon) print "forever: ", repr(self.options.forever) print "sockname: ", repr(self.options.sockname) print "exitcodes: ", repr(self.options.exitcodes) print "user: ", repr(self.options.user) umask = self.options.umask if not umask: # Here we're just getting the current umask so we can report it: umask = os.umask(0777) os.umask(umask) print "umask: ", oct(umask) print "directory: ", repr(self.options.directory) print "logfile: ", repr(self.options.logfile) print "hang_around: ", repr(self.options.hang_around) def show_python(self): print "Python info:" version = sys.version.replace("\n", "\n ") print "Version: ", version print "Platform: ", sys.platform print "Executable: ", repr(sys.executable) print "Arguments: ", repr(sys.argv) print "Directory: ", repr(os.getcwd()) print "Path:" for dir in sys.path: print " " + repr(dir) def show_all(self): self.show_options() print self.show_python() def help_show(self): print "show options -- show zdctl options" print "show python -- show Python version and details" print "show all -- show all of the above" def complete_show(self, text, *ignored): options = ["options", "python", "all"] return [x for x in options if x.startswith(text)] def do_logreopen(self, arg): self.do_kill(str(signal.SIGUSR2)) def help_logreopen(self): print "logreopen -- Send a SIGUSR2 signal to the daemon process." print " This is designed to reopen the log file." def do_logtail(self, arg): if not arg: arg = self.options.logfile if not arg: print "No default log file specified; use logtail " return try: helper = TailHelper(arg) helper.tailf() except KeyboardInterrupt: print except IOError, msg: print msg except OSError, msg: print msg def help_logtail(self): print "logtail [logfile] -- Run tail -f on the given logfile." print " A default file may exist." print " Hit ^C to exit this mode." def do_shell(self, arg): if not arg: arg = os.getenv("SHELL") or "/bin/sh" try: os.system(arg) except KeyboardInterrupt: print def help_shell(self): print "shell [command] -- Execute a shell command." print " Without a command, start an interactive sh." print "An alias for this command is ! [command]" def do_reload(self, arg): if arg: args = arg.split() if self.options.configfile: args = ["-C", self.options.configfile] + args else: args = None options = ZDCtlOptions() options.positional_args_allowed = 0 try: options.realize(args) except SystemExit: print "Configuration not reloaded" else: self.options = options if self.options.configfile: print "Configuration reloaded from", self.options.configfile else: print "Configuration reloaded without a config file" def help_reload(self): print "reload [options] -- Reload the configuration." print " Without options, this reparses the command line." print " With options, this substitutes 'options' for the" print " command line, except that if no -C option is given," print " the last configuration file is used." def do_foreground(self, arg): self.get_status() pid = self.zd_pid if pid: print "To run the program in the foreground, please stop it first." return program = self.options.program + self.options.args[1:] print " ".join(program) sys.stdout.flush() try: os.spawnlp(os.P_WAIT, program[0], *program) except KeyboardInterrupt: print def do_fg(self, arg): self.do_foreground(arg) def help_foreground(self): print "foreground -- Run the program in the forground." print "fg -- an alias for foreground." def help_fg(self): self.help_foreground() def do_quit(self, arg): self.get_status() if not self.zd_up: print "daemon manager not running" elif not self.zd_pid: print "daemon process not running; stopping daemon manager" self.send_action("exit") self.awhile(lambda: not self.zd_up, "daemon manager stopped") else: print "daemon process and daemon manager still running" return 1 def help_quit(self): print "quit -- Exit the zdctl shell." print " If the daemon process is not running," print " stop the daemon manager." class TailHelper: MAX_BUFFSIZE = 1024 def __init__(self, fname): self.f = open(fname, 'r') def tailf(self): sz, lines = self.tail(10) for line in lines: sys.stdout.write(line) sys.stdout.flush() while 1: newsz = self.fsize() bytes_added = newsz - sz if bytes_added < 0: sz = 0 print "==> File truncated <==" bytes_added = newsz if bytes_added > 0: self.f.seek(-bytes_added, 2) bytes = self.f.read(bytes_added) sys.stdout.write(bytes) sys.stdout.flush() sz = newsz time.sleep(1) def tail(self, max=10): self.f.seek(0, 2) pos = sz = self.f.tell() lines = [] bytes = [] num_bytes = 0 while 1: if pos == 0: break self.f.seek(pos) byte = self.f.read(1) if byte == '\n': if len(lines) == max: break bytes.reverse() line = ''.join(bytes) line and lines.append(line) bytes = [] bytes.append(byte) num_bytes = num_bytes + 1 if num_bytes > self.MAX_BUFFSIZE: break pos = pos - 1 lines.reverse() return sz, lines def fsize(self): return os.fstat(self.f.fileno())[stat.ST_SIZE] def main(args=None, options=None, cmdclass=ZDCmd): if args is None: args = sys.argv[1:] if os.environ.get('DAEMON_MANAGER_MODE'): import zdaemon.zdrun return zdaemon.zdrun.main(args) if options is None: options = ZDCtlOptions() options.realize(args) c = cmdclass(options) if options.args: c.onecmd(" ".join(options.args)) if options.interactive: try: import readline except ImportError: pass print "program:", " ".join(options.program) c.do_status() c.cmdloop() if __name__ == "__main__": main() zope2.13-2.13.21/source/zdaemon/src/zdaemon/zdoptions.py0000644000175000017500000004114712214017516021731 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Option processing for zdaemon and related code.""" import os import sys import getopt import ZConfig class ZDOptions: """a zdaemon script. Usage: python
 
1">

<< Items through of >>

There are currently no items in &dtml-title_or_id;

zope2.13-2.13.21/source/Products.BTreeFolder2/src/Products/BTreeFolder2/__init__.py0000644000175000017500000000162312214017664026407 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import BTreeFolder2 def initialize(context): context.registerClass( BTreeFolder2.BTreeFolder2, constructors=(BTreeFolder2.manage_addBTreeFolderForm, BTreeFolder2.manage_addBTreeFolder), icon='btreefolder2.gif', ) zope2.13-2.13.21/source/Products.BTreeFolder2/src/Products/BTreeFolder2/BTreeFolder2.py0000644000175000017500000004262112214017664027072 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """BTreeFolder2 """ from cgi import escape from logging import getLogger from random import randint import sys from urllib import quote from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.SecurityManagement import getSecurityManager from AccessControl.Permissions import access_contents_information from AccessControl.Permissions import view_management_screens from Acquisition import aq_base from App.special_dtml import DTMLFile from BTrees.Length import Length from BTrees.OIBTree import OIBTree from BTrees.OIBTree import union from BTrees.OOBTree import OOBTree from OFS.event import ObjectWillBeAddedEvent from OFS.event import ObjectWillBeRemovedEvent from OFS.Folder import Folder from OFS.ObjectManager import BadRequestException from OFS.subscribers import compatibilityCall from Persistence import Persistent from Products.ZCatalog.Lazy import LazyMap from zope.event import notify from zope.lifecycleevent import ObjectAddedEvent from zope.lifecycleevent import ObjectRemovedEvent from zope.container.contained import notifyContainerModified LOG = getLogger('BTreeFolder2') manage_addBTreeFolderForm = DTMLFile('folderAdd', globals()) def manage_addBTreeFolder(dispatcher, id, title='', REQUEST=None): """Adds a new BTreeFolder object with id *id*. """ id = str(id) ob = BTreeFolder2(id) ob.title = str(title) dispatcher._setObject(id, ob) ob = dispatcher._getOb(id) if REQUEST is not None: return dispatcher.manage_main(dispatcher, REQUEST, update_menu=1) listtext0 = ''' ''' _marker = [] # Create a new marker object. MAX_UNIQUEID_ATTEMPTS = 1000 class ExhaustedUniqueIdsError(Exception): pass class BTreeFolder2Base(Persistent): """Base for BTree-based folders. """ security = ClassSecurityInfo() manage_options=( ({'label': 'Contents', 'action': 'manage_main'}, ) + Folder.manage_options[1:] ) security.declareProtected(view_management_screens, 'manage_main') manage_main = DTMLFile('contents', globals()) _tree = None # OOBTree: { id -> object } _count = None # A BTrees.Length _v_nextid = 0 # The integer component of the next generated ID _mt_index = None # OOBTree: { meta_type -> OIBTree: { id -> 1 } } title = '' # superValues() looks for the _objects attribute, but the implementation # would be inefficient, so superValues() support is disabled. _objects = () def __init__(self, id=None): if id is not None: self.id = id self._initBTrees() def _initBTrees(self): self._tree = OOBTree() self._count = Length() self._mt_index = OOBTree() def _populateFromFolder(self, source): """Fill this folder with the contents of another folder. """ for name in source.objectIds(): value = source._getOb(name, None) if value is not None: self._setOb(name, aq_base(value)) security.declareProtected(view_management_screens, 'manage_fixCount') def manage_fixCount(self): """Calls self._fixCount() and reports the result as text. """ old, new = self._fixCount() path = '/'.join(self.getPhysicalPath()) if old == new: return "No count mismatch detected in BTreeFolder2 at %s." % path else: return ("Fixed count mismatch in BTreeFolder2 at %s. " "Count was %d; corrected to %d" % (path, old, new)) def _fixCount(self): """Checks if the value of self._count disagrees with len(self.objectIds()). If so, corrects self._count. Returns the old and new count values. If old==new, no correction was performed. """ old = self._count() new = len(self.objectIds()) if old != new: self._count.set(new) return old, new security.declareProtected(view_management_screens, 'manage_cleanup') def manage_cleanup(self): """Calls self._cleanup() and reports the result as text. """ v = self._cleanup() path = '/'.join(self.getPhysicalPath()) if v: return "No damage detected in BTreeFolder2 at %s." % path else: return ("Fixed BTreeFolder2 at %s. " "See the log for more details." % path) def _cleanup(self): """Cleans up errors in the BTrees. Certain ZODB bugs have caused BTrees to become slightly insane. Fortunately, there is a way to clean up damaged BTrees that always seems to work: make a new BTree containing the items() of the old one. Returns 1 if no damage was detected, or 0 if damage was detected and fixed. """ from BTrees.check import check path = '/'.join(self.getPhysicalPath()) try: check(self._tree) for key in self._tree.keys(): if key not in self._tree: raise AssertionError( "Missing value for key: %s" % repr(key)) check(self._mt_index) for key, value in self._mt_index.items(): if (key not in self._mt_index or self._mt_index[key] is not value): raise AssertionError( "Missing or incorrect meta_type index: %s" % repr(key)) check(value) for k in value.keys(): if k not in value: raise AssertionError( "Missing values for meta_type index: %s" % repr(key)) return 1 except AssertionError: LOG.warn('Detected damage to %s. Fixing now.' % path, exc_info=sys.exc_info()) try: self._tree = OOBTree(self._tree) mt_index = OOBTree() for key, value in self._mt_index.items(): mt_index[key] = OIBTree(value) self._mt_index = mt_index except: LOG.error('Failed to fix %s.' % path, exc_info=sys.exc_info()) raise else: LOG.info('Fixed %s.' % path) return 0 def _getOb(self, id, default=_marker): """Return the named object from the folder. """ try: return self._tree[id].__of__(self) except KeyError: if default is _marker: raise else: return default security.declareProtected(access_contents_information, 'get') def get(self, name, default=None): return self._getOb(name, default) def __getitem__(self, name): return self._getOb(name) def __getattr__(self, name): # Boo hoo hoo! Zope 2 prefers implicit acquisition over traversal # to subitems, and __bobo_traverse__ hooks don't work with # restrictedTraverse() unless __getattr__() is also present. # Oh well. try: return self._tree[name] except KeyError: raise AttributeError(name) def _setOb(self, id, object): """Store the named object in the folder. """ tree = self._tree if id in tree: raise KeyError('There is already an item named "%s".' % id) tree[id] = object self._count.change(1) # Update the meta type index. mti = self._mt_index meta_type = getattr(object, 'meta_type', None) if meta_type is not None: ids = mti.get(meta_type, None) if ids is None: ids = OIBTree() mti[meta_type] = ids ids[id] = 1 def _delOb(self, id): """Remove the named object from the folder. """ tree = self._tree meta_type = getattr(tree[id], 'meta_type', None) del tree[id] self._count.change(-1) # Update the meta type index. if meta_type is not None: mti = self._mt_index ids = mti.get(meta_type, None) if ids is not None and id in ids: del ids[id] if not ids: # Removed the last object of this meta_type. # Prune the index. del mti[meta_type] security.declareProtected(view_management_screens, 'getBatchObjectListing') def getBatchObjectListing(self, REQUEST=None): """Return a structure for a page template to show the list of objects. """ if REQUEST is None: REQUEST = {} pref_rows = int(REQUEST.get('dtpref_rows', 20)) b_start = int(REQUEST.get('b_start', 1)) b_count = int(REQUEST.get('b_count', 1000)) b_end = b_start + b_count - 1 url = self.absolute_url() + '/manage_main' idlist = self.objectIds() # Pre-sorted. count = self.objectCount() if b_end < count: next_url = url + '?b_start=%d' % (b_start + b_count) else: b_end = count next_url = '' if b_start > 1: prev_url = url + '?b_start=%d' % max(b_start - b_count, 1) else: prev_url = '' formatted = [] formatted.append(listtext0 % pref_rows) for i in range(b_start - 1, b_end): optID = escape(idlist[i]) formatted.append(listtext1 % (escape(optID, quote=1), optID)) formatted.append(listtext2) return {'b_start': b_start, 'b_end': b_end, 'prev_batch_url': prev_url, 'next_batch_url': next_url, 'formatted_list': ''.join(formatted)} security.declareProtected(view_management_screens, 'manage_object_workspace') def manage_object_workspace(self, ids=(), REQUEST=None): '''Redirects to the workspace of the first object in the list.''' if ids and REQUEST is not None: REQUEST.RESPONSE.redirect( '%s/%s/manage_workspace' % ( self.absolute_url(), quote(ids[0]))) else: return self.manage_main(self, REQUEST) security.declareProtected(access_contents_information, 'tpValues') def tpValues(self): """Ensures the items don't show up in the left pane. """ return () security.declareProtected(access_contents_information, 'objectCount') def objectCount(self): """Returns the number of items in the folder.""" return self._count() def __len__(self): return self.objectCount() def __nonzero__(self): return True security.declareProtected(access_contents_information, 'has_key') def has_key(self, id): """Indicates whether the folder has an item by ID. """ return id in self._tree # backward compatibility security.declareProtected(access_contents_information, 'hasObject') hasObject = has_key security.declareProtected(access_contents_information, 'objectIds') def objectIds(self, spec=None): # Returns a list of subobject ids of the current object. # If 'spec' is specified, returns objects whose meta_type # matches 'spec'. if spec is None: return self._tree.keys() if isinstance(spec, str): spec = [spec] set = None mti = self._mt_index for meta_type in spec: ids = mti.get(meta_type, None) if ids is not None: set = union(set, ids) if set is None: return () else: return set.keys() def __contains__(self, name): return name in self._tree def __iter__(self): return iter(self.objectIds()) security.declareProtected(access_contents_information, 'objectValues') def objectValues(self, spec=None): # Returns a list of actual subobjects of the current object. # If 'spec' is specified, returns only objects whose meta_type # match 'spec'. return LazyMap(self._getOb, self.objectIds(spec)) security.declareProtected(access_contents_information, 'objectItems') def objectItems(self, spec=None): # Returns a list of (id, subobject) tuples of the current object. # If 'spec' is specified, returns only objects whose meta_type match # 'spec' return LazyMap(lambda id, _getOb=self._getOb: (id, _getOb(id)), self.objectIds(spec)) security.declareProtected( access_contents_information, 'keys', 'items', 'values') keys = objectIds values = objectValues items = objectItems security.declareProtected(access_contents_information, 'objectMap') def objectMap(self): # Returns a tuple of mappings containing subobject meta-data. return LazyMap(lambda (k, v): {'id': k, 'meta_type': getattr(v, 'meta_type', None)}, self._tree.items(), self._count()) security.declareProtected(access_contents_information, 'objectIds_d') def objectIds_d(self, t=None): ids = self.objectIds(t) res = {} for id in ids: res[id] = 1 return res security.declareProtected(access_contents_information, 'objectMap_d') def objectMap_d(self, t=None): return self.objectMap() def _checkId(self, id, allow_dup=0): if not allow_dup and id in self: raise BadRequestException('The id "%s" is invalid--' 'it is already in use.' % id) def _setObject(self, id, object, roles=None, user=None, set_owner=1, suppress_events=False): ob = object # better name, keep original function signature v = self._checkId(id) if v is not None: id = v # If an object by the given id already exists, remove it. if id in self: self._delObject(id) if not suppress_events: notify(ObjectWillBeAddedEvent(ob, self, id)) self._setOb(id, ob) ob = self._getOb(id) if set_owner: # TODO: eventify manage_fixupOwnershipAfterAdd # This will be called for a copy/clone, or a normal _setObject. ob.manage_fixupOwnershipAfterAdd() # Try to give user the local role "Owner", but only if # no local roles have been set on the object yet. if getattr(ob, '__ac_local_roles__', _marker) is None: user = getSecurityManager().getUser() if user is not None: userid = user.getId() if userid is not None: ob.manage_setLocalRoles(userid, ['Owner']) if not suppress_events: notify(ObjectAddedEvent(ob, self, id)) notifyContainerModified(self) compatibilityCall('manage_afterAdd', ob, ob, self) return id def __setitem__(self, key, value): return self._setObject(key, value) def _delObject(self, id, dp=1, suppress_events=False): ob = self._getOb(id) compatibilityCall('manage_beforeDelete', ob, ob, self) if not suppress_events: notify(ObjectWillBeRemovedEvent(ob, self, id)) self._delOb(id) if not suppress_events: notify(ObjectRemovedEvent(ob, self, id)) notifyContainerModified(self) def __delitem__(self, name): return self._delObject(id=name) # Utility for generating unique IDs. security.declareProtected(access_contents_information, 'generateId') def generateId(self, prefix='item', suffix='', rand_ceiling=999999999): """Returns an ID not used yet by this folder. The ID is unlikely to collide with other threads and clients. The IDs are sequential to optimize access to objects that are likely to have some relation. """ tree = self._tree n = self._v_nextid attempt = 0 while 1: if n % 4000 != 0 and n <= rand_ceiling: id = '%s%d%s' % (prefix, n, suffix) if id not in tree: break n = randint(1, rand_ceiling) attempt = attempt + 1 if attempt > MAX_UNIQUEID_ATTEMPTS: # Prevent denial of service raise ExhaustedUniqueIdsError self._v_nextid = n + 1 return id InitializeClass(BTreeFolder2Base) class BTreeFolder2 (BTreeFolder2Base, Folder): """BTreeFolder2 based on OFS.Folder. """ meta_type = 'BTreeFolder2' def _checkId(self, id, allow_dup=0): Folder._checkId(self, id, allow_dup) BTreeFolder2Base._checkId(self, id, allow_dup) InitializeClass(BTreeFolder2) zope2.13-2.13.21/source/Products.BTreeFolder2/src/Products/BTreeFolder2/folderAdd.dtml0000644000175000017500000000304212214017664027041 0ustar arnauarnau &dtml-form_title;

&dtml-form_title;

A Folder contains other objects. Use Folders to organize your web objects in to logical groups.

A BTreeFolder2 may be able to handle a larger number of objects than a standard folder because it does not need to activate other subobjects in order to access a single subobject. It is more efficient than the original BTreeFolder product, but does not provide attribute access.

Id
Title
zope2.13-2.13.21/source/Products.BTreeFolder2/src/Products/BTreeFolder2/tests/0000755000175000017500000000000012214017664025436 5ustar arnauarnauzope2.13-2.13.21/source/Products.BTreeFolder2/src/Products/BTreeFolder2/tests/__init__.py0000644000175000017500000000000112214017664027536 0ustar arnauarnau#zope2.13-2.13.21/source/Products.BTreeFolder2/src/Products/BTreeFolder2/tests/testBTreeFolder2.py0000644000175000017500000002341712214017664031136 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Unit tests for BTreeFolder2. """ import unittest from OFS.ObjectManager import BadRequestException from OFS.Folder import Folder from Acquisition import aq_base from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2 from Products.BTreeFolder2.BTreeFolder2 import ExhaustedUniqueIdsError class BTreeFolder2Tests(unittest.TestCase): def getBase(self, ob): # This is overridden in subclasses. return aq_base(ob) def setUp(self): self.f = BTreeFolder2('root') ff = BTreeFolder2('item') self.f._setOb(ff.id, ff) self.ff = self.f._getOb(ff.id) def testAdded(self): self.assertEqual(self.ff.id, 'item') def testSetItem(self): self.f['ff2'] = BTreeFolder2('item2') self.assertEqual(self.f.ff2.id, 'item2') def test_getattr_found(self): self.assertEqual(getattr(self.f, 'item'), self.ff) def test_getattr_notfound(self): self.assertRaises(AttributeError, getattr, self.f, 'none') def test_getattr_default(self): self.assertEqual(getattr(self.f, 'none', '1'), '1') def testCount(self): self.assertEqual(self.f.objectCount(), 1) self.assertEqual(self.ff.objectCount(), 0) def testLen(self): self.assertEqual(len(self.f), 1) self.assertEqual(len(self.ff), 0) def testNonZero(self): self.assertEqual(bool(self.f), True) self.assertEqual(bool(self.ff), True) def testObjectIds(self): self.assertEqual(list(self.f.objectIds()), ['item']) self.assertEqual(list(self.ff.objectIds()), []) f3 = BTreeFolder2('item3') self.f._setOb(f3.id, f3) lst = list(self.f.objectIds()) lst.sort() self.assertEqual(lst, ['item', 'item3']) def testKeys(self): self.assertEqual(list(self.f.keys()), ['item']) self.assertEqual(list(self.ff.keys()), []) f3 = BTreeFolder2('item3') self.f[f3.id] = f3 lst = list(self.f.keys()) lst.sort() self.assertEqual(lst, ['item', 'item3']) def testObjectIdsWithMetaType(self): f2 = Folder() f2.id = 'subfolder' self.f._setOb(f2.id, f2) mt1 = self.ff.meta_type mt2 = Folder.meta_type self.assertEqual(list(self.f.objectIds(mt1)), ['item']) self.assertEqual(list(self.f.objectIds((mt1, ))), ['item']) self.assertEqual(list(self.f.objectIds(mt2)), ['subfolder']) lst = list(self.f.objectIds([mt1, mt2])) lst.sort() self.assertEqual(lst, ['item', 'subfolder']) self.assertEqual(list(self.f.objectIds('blah')), []) def testObjectValues(self): values = self.f.objectValues() self.assertEqual(len(values), 1) self.assertEqual(values[0].id, 'item') self.assert_(values[0].aq_parent is self.f) def testValues(self): values = self.f.values() self.assertEqual(len(values), 1) self.assertEqual(values[0].id, 'item') def testObjectItems(self): items = self.f.objectItems() self.assertEqual(len(items), 1) id, val = items[0] self.assertEqual(id, 'item') self.assertEqual(val.id, 'item') self.assert_(val.aq_parent is self.f) def testItems(self): items = self.f.items() self.assertEqual(len(items), 1) id, val = items[0] self.assertEqual(id, 'item') self.assertEqual(val.id, 'item') def testHasKey(self): self.assert_(self.f.hasObject('item')) # Old spelling self.assert_(self.f.has_key('item')) # New spelling def testContains(self): self.assert_('item' in self.f) def testDelete(self): self.f._delOb('item') self.assertEqual(list(self.f.objectIds()), []) self.assertEqual(self.f.objectCount(), 0) def testDelItem(self): del self.f['item'] self.assert_('item' not in self.f) self.assertEqual(len(self.f), 0) def testIter(self): iterator = iter(self.f) first = iterator.next() self.assertEquals(first, 'item') self.assertRaises(StopIteration, iterator.next) def testObjectMap(self): map = self.f.objectMap() self.assertEqual(list(map), [{'id': 'item', 'meta_type': self.ff.meta_type}]) # I'm not sure why objectMap_d() exists, since it appears to be # the same as objectMap(), but it's implemented by Folder. self.assertEqual(list(self.f.objectMap_d()), list(self.f.objectMap())) def testObjectIds_d(self): self.assertEqual(self.f.objectIds_d(), {'item': 1}) def testCheckId(self): self.assertEqual(self.f._checkId('xyz'), None) self.assertRaises(BadRequestException, self.f._checkId, 'item') self.assertRaises(BadRequestException, self.f._checkId, 'REQUEST') def testSetObject(self): f2 = BTreeFolder2('item2') self.f._setObject(f2.id, f2) self.assert_('item2' in self.f) self.assertEqual(self.f.objectCount(), 2) def testWrapped(self): # Verify that the folder returns wrapped versions of objects. base = self.getBase(self.f._getOb('item')) self.assert_(self.f._getOb('item') is not base) self.assert_(self.f['item'] is not base) self.assert_(self.f.get('item') is not base) self.assert_(self.getBase(self.f._getOb('item')) is base) def testGenerateId(self): ids = {} for n in range(10): ids[self.f.generateId()] = 1 self.assertEqual(len(ids), 10) # All unique for id in ids.keys(): self.f._checkId(id) # Must all be valid def testGenerateIdDenialOfServicePrevention(self): for n in range(10): item = Folder() item.id = 'item%d' % n self.f._setOb(item.id, item) self.f.generateId('item', rand_ceiling=20) # Shouldn't be a problem self.assertRaises(ExhaustedUniqueIdsError, self.f.generateId, 'item', rand_ceiling=9) def testReplace(self): old_f = Folder() old_f.id = 'item' inner_f = BTreeFolder2('inner') old_f._setObject(inner_f.id, inner_f) self.ff._populateFromFolder(old_f) self.assertEqual(self.ff.objectCount(), 1) self.assert_('inner' in self.ff) self.assertEqual(self.getBase(self.ff._getOb('inner')), inner_f) def testObjectListing(self): f2 = BTreeFolder2('somefolder') self.f._setObject(f2.id, f2) # Hack in an absolute_url() method that works without context. self.f.absolute_url = lambda: '' info = self.f.getBatchObjectListing() self.assertEqual(info['b_start'], 1) self.assertEqual(info['b_end'], 2) self.assertEqual(info['prev_batch_url'], '') self.assertEqual(info['next_batch_url'], '') self.assert_(info['formatted_list'].find('') > 0) self.assert_(info['formatted_list'].find('item') > 0) self.assert_(info['formatted_list'].find('somefolder') > 0) # Ensure batching is working. info = self.f.getBatchObjectListing({'b_count': 1}) self.assertEqual(info['b_start'], 1) self.assertEqual(info['b_end'], 1) self.assertEqual(info['prev_batch_url'], '') self.assert_(info['next_batch_url'] != '') self.assert_(info['formatted_list'].find('item') > 0) self.assert_(info['formatted_list'].find('somefolder') < 0) info = self.f.getBatchObjectListing({'b_start': 2}) self.assertEqual(info['b_start'], 2) self.assertEqual(info['b_end'], 2) self.assert_(info['prev_batch_url'] != '') self.assertEqual(info['next_batch_url'], '') self.assert_(info['formatted_list'].find('item') < 0) self.assert_(info['formatted_list'].find('somefolder') > 0) def testObjectListingWithSpaces(self): # The option list must use value attributes to preserve spaces. name = " some folder " f2 = BTreeFolder2(name) self.f._setObject(f2.id, f2) self.f.absolute_url = lambda: '' info = self.f.getBatchObjectListing() expect = '' % (name, name) self.assert_(info['formatted_list'].find(expect) > 0) def testCleanup(self): self.assert_(self.f._cleanup()) key = TrojanKey('a') self.f._tree[key] = 'b' self.assert_(self.f._cleanup()) key.value = 'z' # With a key in the wrong place, there should now be damage. self.assert_(not self.f._cleanup()) # Now it's fixed. self.assert_(self.f._cleanup()) # Verify the management interface also works, # but don't test return values. self.f.manage_cleanup() key.value = 'a' self.f.manage_cleanup() class TrojanKey: """Pretends to be a consistent, immutable, humble citizen... then sweeps the rug out from under the BTree. """ def __init__(self, value): self.value = value def __cmp__(self, other): return cmp(self.value, other) def __hash__(self): return hash(self.value) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(BTreeFolder2Tests), )) zope2.13-2.13.21/source/Products.BTreeFolder2/src/Products/__init__.py0000644000175000017500000000007012214017664024163 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/Products.BTreeFolder2/src/Products.BTreeFolder2.egg-info/0000755000175000017500000000000012214017664025765 5ustar arnauarnauzope2.13-2.13.21/source/Products.BTreeFolder2/src/Products.BTreeFolder2.egg-info/PKG-INFO0000644000175000017500000001367312214017664027074 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.BTreeFolder2 Version: 2.13.4 Summary: A BTree based implementation for Zope 2's OFS. Home-page: http://pypi.python.org/pypi/Products.BTreeFolder2 Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== BTreeFolder2 is a Zope product that acts like a Zope 2 OFS folder but can store many more items. When you fill a Zope folder with too many items, both Zope and your browser get overwhelmed. Zope has to load and store a large folder object, and the browser has to render large HTML tables repeatedly. Zope can store a lot of objects, but it has trouble storing a lot of objects in a single standard folder. Zope Corporation once had an extensive discussion on the subject. It was decided that we would expand standard folders to handle large numbers of objects gracefully. Unfortunately, Zope folders are used and extended in so many ways today that it would be difficult to modify standard folders in a way that would be compatible with all Zope products. So the BTreeFolder product was born. It stored all subobjects in a ZODB BTree, a structure designed to allow many items without loading them all into memory. It also rendered the contents of the folder as a simple select list rather than a table. Most browsers have no trouble rendering large select lists. But there was still one issue remaining. BTreeFolders still stored the ID of all subobjects in a single database record. If you put tens of thousands of items in a single BTreeFolder, you would still be loading and storing a multi-megabyte folder object. Zope can do this, but not quickly, and not without bloating the database. BTreeFolder2 solves this issue. It stores not only the subobjects but also the IDs of the subobjects in a BTree. It also batches the list of items in the UI, showing only 1000 items at a time. So if you write your application carefully, you can use a BTreeFolder2 to store as many items as will fit in physical storage. There are products that depend on the internal structure of the original BTreeFolder, however. So rather than risk breaking those products, the product has been renamed. You can have both products installed at the same time. If you're developing new applications, you should use BTreeFolder2. Usage ===== The BTreeFolder2 user interface shows a list of items rather than a series of checkboxes. To visit an item, select it in the list and click the "edit" button. BTreeFolder2 objects provide Python dictionary-like methods to make them easier to use in Python code than standard folders:: has_key(key) keys() values() items() get(key, default=None) __len__() keys(), values(), and items() return sequences, but not necessarily tuples or lists. Use len(folder) to call the __len__() method. The objects returned by values() and items() have acquisition wrappers. BTreeFolder2 also provides a method for generating unique, non-overlapping IDs:: generateId(prefix='item', suffix='', rand_ceiling=999999999) The ID returned by this method is guaranteed to not clash with any other ID in the folder. Use the returned value as the ID for new objects. The generated IDs tend to be sequential so that objects that are likely related in some way get loaded together. BTreeFolder2 implements the full Folder interface, with the exception that the superValues() method does not return any items. To implement the method in the way the Zope codebase expects would undermine the performance benefits gained by using BTreeFolder2. Changelog ========= 2.13.4 (2011-12-12) ------------------- - Provide security declaration for `BTreeFolder2Base.hasObject` method. - Add some tests for correct `getattr` behavior. - Minor `__getattr__` and `_getOb` optimizations. 2.13.3 (2011-03-15) ------------------- - `keys`, `values` and `items` methods are now exactly the same as `objectIds`, `objectValues` and `objectItems`. They did the same before already but duplicated the code. 2.13.2 (2011-03-08) ------------------- - `objectValues` and `objectItems` no longer do a special handling when no special `spec` is requested as `objectIds` already does the correct handling. 2.13.1 (2010-08-04) ------------------- - Make sure that methods returning objects return them Acquisition wrapped. - Be more careful in calling our own keys, values and items methods, as sub-classes might have overridden some of them. 2.13.0 (2010-07-11) ------------------- - Changed the `objectIds`, `objectItems` and `objectValues` methods to use the internal OOBTree methods directly if no `spec` argument is passed. - Change implementation of `keys`, `items` and `values` method to access the `self._tree` OOBTree methods directly. This avoids lookups in the meta_types structures. - Implement the full dictionary protocol including `__getitem__`, `__delitem__`, `__setitem__`, `__nonzero__`, `__iter__` and `__contains__`. - Released as separate package. Platform: UNKNOWN ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.BTreeFolder2/src/Products.BTreeFolder2.egg-info/dependency_links.txtzope2.13-2.13.21/source/Products.BTreeFolder2/src/Products.BTreeFolder2.egg-info/dependency_links.tx0000644000175000017500000000000112214017664031647 0ustar arnauarnau zope2.13-2.13.21/source/Products.BTreeFolder2/src/Products.BTreeFolder2.egg-info/requires.txt0000644000175000017500000000016612214017664030370 0ustar arnauarnausetuptools AccessControl Acquisition Persistence ZODB3 Zope2 >= 2.13.0a1 zope.container zope.event zope.lifecycleevent././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/Products.BTreeFolder2/src/Products.BTreeFolder2.egg-info/namespace_packages.txtzope2.13-2.13.21/source/Products.BTreeFolder2/src/Products.BTreeFolder2.egg-info/namespace_packages.0000644000175000017500000000001112214017664031550 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.BTreeFolder2/src/Products.BTreeFolder2.egg-info/top_level.txt0000644000175000017500000000001112214017664030507 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.BTreeFolder2/src/Products.BTreeFolder2.egg-info/SOURCES.txt0000644000175000017500000000140212214017664027646 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt MANIFEST.in README.txt bootstrap.py buildout.cfg setup.py src/Products/__init__.py src/Products.BTreeFolder2.egg-info/PKG-INFO src/Products.BTreeFolder2.egg-info/SOURCES.txt src/Products.BTreeFolder2.egg-info/dependency_links.txt src/Products.BTreeFolder2.egg-info/namespace_packages.txt src/Products.BTreeFolder2.egg-info/not-zip-safe src/Products.BTreeFolder2.egg-info/requires.txt src/Products.BTreeFolder2.egg-info/top_level.txt src/Products/BTreeFolder2/BTreeFolder2.py src/Products/BTreeFolder2/__init__.py src/Products/BTreeFolder2/btreefolder2.gif src/Products/BTreeFolder2/contents.dtml src/Products/BTreeFolder2/folderAdd.dtml src/Products/BTreeFolder2/tests/__init__.py src/Products/BTreeFolder2/tests/testBTreeFolder2.pyzope2.13-2.13.21/source/Products.BTreeFolder2/src/Products.BTreeFolder2.egg-info/not-zip-safe0000644000175000017500000000000112214017664030213 0ustar arnauarnau zope2.13-2.13.21/source/zope.event/0000755000175000017500000000000012214017560015532 5ustar arnauarnauzope2.13-2.13.21/source/zope.event/setup.py0000644000175000017500000000471312214017560017251 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.event package """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup( name='zope.event', version='3.5.2', url='http://pypi.python.org/pypi/zope.event', license='ZPL 2.1', description='Very basic event publishing system', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=( read('README.txt') + '\n' + read('CHANGES.txt') ), classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.4", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Topic :: Software Development :: Libraries :: Python Modules", ], packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope',], include_package_data=True, install_requires=['setuptools'], zip_safe=False, test_suite='zope.event.tests.test_suite', extras_require={'docs': ['Sphinx'], 'testing': ['nose', 'coverage'], }, ) zope2.13-2.13.21/source/zope.event/PKG-INFO0000644000175000017500000000503612214017560016633 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.event Version: 3.5.2 Summary: Very basic event publishing system Home-page: http://pypi.python.org/pypi/zope.event Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ``zope.event`` README ===================== The ``zope.event`` package provides a simple event system, including: - An event publishing API, intended for use by applications which are unaware of any subscribers to their events. - A very simple event-dispatching system on which more sophisticated event dispatching systems can be built. For example, a type-based event dispatching system that builds on ``zope.event`` can be found in ``zope.component``. Please see ``doc/index.rst`` for the documentation. ``zope.event`` Changelog ======================== 3.5.2 (2012-03-30) ------------------ - This release is the last which will maintain support for Python 2.4 / Python 2.5. - Added support for continuous integration using ``tox`` and ``jenkins``. - Added 'setup.py dev' alias (runs ``setup.py develop`` plus installs ``nose`` and ``coverage``). - Added 'setup.py docs' alias (installs ``Sphinx`` and dependencies). 3.5.1 (2011-08-04) ------------------ - Added Sphinx documentation. 3.5.0 (2010-05-01) ------------------ - Added change log to ``long-description``. - Support for Python 3.x. 3.4.1 (2009-03-03) ------------------ - A few minor cleanups. 3.4.0 (2007-07-14) ------------------ - Initial release as a separate project. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Topic :: Software Development :: Libraries :: Python Modules zope2.13-2.13.21/source/zope.event/tox.ini0000644000175000017500000000027012214017560017044 0ustar arnauarnau[tox] envlist = py24,py25,py26,py27,py32,jython,pypy [testenv] commands = python setup.py test -q deps = zope.event [testenv:jython] commands = jython setup.py test -q zope2.13-2.13.21/source/zope.event/pip-egg-info/0000755000175000017500000000000012214017560020013 5ustar arnauarnauzope2.13-2.13.21/source/zope.event/pip-egg-info/zope.event.egg-info/0000755000175000017500000000000012214017560023602 5ustar arnauarnauzope2.13-2.13.21/source/zope.event/pip-egg-info/zope.event.egg-info/PKG-INFO0000644000175000017500000000503612214017560024703 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.event Version: 3.5.2 Summary: Very basic event publishing system Home-page: http://pypi.python.org/pypi/zope.event Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ``zope.event`` README ===================== The ``zope.event`` package provides a simple event system, including: - An event publishing API, intended for use by applications which are unaware of any subscribers to their events. - A very simple event-dispatching system on which more sophisticated event dispatching systems can be built. For example, a type-based event dispatching system that builds on ``zope.event`` can be found in ``zope.component``. Please see ``doc/index.rst`` for the documentation. ``zope.event`` Changelog ======================== 3.5.2 (2012-03-30) ------------------ - This release is the last which will maintain support for Python 2.4 / Python 2.5. - Added support for continuous integration using ``tox`` and ``jenkins``. - Added 'setup.py dev' alias (runs ``setup.py develop`` plus installs ``nose`` and ``coverage``). - Added 'setup.py docs' alias (installs ``Sphinx`` and dependencies). 3.5.1 (2011-08-04) ------------------ - Added Sphinx documentation. 3.5.0 (2010-05-01) ------------------ - Added change log to ``long-description``. - Support for Python 3.x. 3.4.1 (2009-03-03) ------------------ - A few minor cleanups. 3.4.0 (2007-07-14) ------------------ - Initial release as a separate project. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Topic :: Software Development :: Libraries :: Python Modules zope2.13-2.13.21/source/zope.event/pip-egg-info/zope.event.egg-info/dependency_links.txt0000644000175000017500000000000112214017560027650 0ustar arnauarnau zope2.13-2.13.21/source/zope.event/pip-egg-info/zope.event.egg-info/requires.txt0000644000175000017500000000006212214017560026200 0ustar arnauarnausetuptools [docs] Sphinx [testing] nose coveragezope2.13-2.13.21/source/zope.event/pip-egg-info/zope.event.egg-info/namespace_packages.txt0000644000175000017500000000000512214017560030130 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.event/pip-egg-info/zope.event.egg-info/top_level.txt0000644000175000017500000000000512214017560026327 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.event/pip-egg-info/zope.event.egg-info/SOURCES.txt0000644000175000017500000000065412214017560025473 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.event.egg-info/PKG-INFO pip-egg-info/zope.event.egg-info/SOURCES.txt pip-egg-info/zope.event.egg-info/dependency_links.txt pip-egg-info/zope.event.egg-info/namespace_packages.txt pip-egg-info/zope.event.egg-info/not-zip-safe pip-egg-info/zope.event.egg-info/requires.txt pip-egg-info/zope.event.egg-info/top_level.txt src/zope/__init__.py src/zope/event/__init__.py src/zope/event/tests.pyzope2.13-2.13.21/source/zope.event/pip-egg-info/zope.event.egg-info/not-zip-safe0000644000175000017500000000000112214017560026030 0ustar arnauarnau zope2.13-2.13.21/source/zope.event/LICENSE.txt0000644000175000017500000000402612214017560017357 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.event/README.txt0000644000175000017500000000100612214017560017225 0ustar arnauarnau``zope.event`` README ===================== The ``zope.event`` package provides a simple event system, including: - An event publishing API, intended for use by applications which are unaware of any subscribers to their events. - A very simple event-dispatching system on which more sophisticated event dispatching systems can be built. For example, a type-based event dispatching system that builds on ``zope.event`` can be found in ``zope.component``. Please see ``doc/index.rst`` for the documentation. zope2.13-2.13.21/source/zope.event/setup.cfg0000644000175000017500000000037512214017560017360 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 [nosetests] cover-package = zope.event nocapture = 1 cover-erase = 1 with-doctest = 0 where = src [aliases] dev = develop easy_install zope.event[testing] docs = easy_install zope.event[docs] zope2.13-2.13.21/source/zope.event/docs/0000755000175000017500000000000012214017560016462 5ustar arnauarnauzope2.13-2.13.21/source/zope.event/docs/theory.rst0000644000175000017500000000073312214017560020531 0ustar arnauarnauTheory of Operation =================== .. note:: This section explains both why an application or framework might publish events, and various ways the integrator might configure the subscribers to achieve different goals. Outline ------- - Events as decoupling mechanism. - Injecting policy into reusable applications. - Extending frameworks. - Event dispatch strategies - Type-based dispatch, as used in ZCA - Attribute-based / key-based dispatch zope2.13-2.13.21/source/zope.event/docs/make.bat0000644000175000017500000000600512214017560020070 0ustar arnauarnau@ECHO OFF REM Command file for Sphinx documentation set SPHINXBUILD=sphinx-build set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :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 over 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 goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "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. goto end ) if "%1" == "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\zopeevent.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\zopeevent.ghc goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "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. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end zope2.13-2.13.21/source/zope.event/docs/conf.py0000644000175000017500000001513712214017560017770 0ustar arnauarnau# -*- coding: utf-8 -*- # # zope.event documentation build configuration file, created by # sphinx-quickstart on Fri Apr 16 17:22:42 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os, pkg_resources # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path 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('.')) sys.path.append(os.path.abspath('../src')) rqmt = pkg_resources.require('zope.event')[0] # -- 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', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'zope.event' copyright = u'2010, Zope Foundation and Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '%s.%s' % tuple(map(int, rqmt.version.split('.')[:2])) # The full version, including alpha/beta/rc tags. release = rqmt.version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # 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 directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # 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' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # 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 (relative to this directory) to place at the top # of the sidebar. #html_logo = None # 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 = None # 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, links to the reST sources are added to the pages. #html_show_sourcelink = 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 = 'zopeeventdoc' # -- 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, documentclass [howto/manual]). latex_documents = [ ('index', 'zopeevent.tex', u'zope.event Documentation', u'Zope Foundation and Contributors', '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 # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} zope2.13-2.13.21/source/zope.event/docs/index.rst0000644000175000017500000000114012214017560020317 0ustar arnauarnau:mod:`zope.event` Documentation =============================== This package provides a simple event system on which application-specific event systems can be built. Application code can generate events without being concerned about the event-processing frameworks that might handle the events. Events are objects that represent something happening in a system. They are used to extend processing by providing processing plug points. Contents: .. toctree:: :maxdepth: 2 usage theory api hacking Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` zope2.13-2.13.21/source/zope.event/docs/hacking.rst0000644000175000017500000001535212214017560020626 0ustar arnauarnauHacking on :mod:`zope.event` ============================ Getting the Code ----------------- The main repository for :mod:`zope.event` is in the Zope Subversion repository: http://svn.zope.org/zope.event You can get a read-only Subversion checkout from there: .. code-block:: sh $ svn checkout svn://svn.zope.org/repos/main/zope.event/trunk zope.event The project also mirrors the trunk from the Subversion repository as a Bazaar branch on Launchpad: https://code.launchpad.net/zope.event You can branch the trunk from there using Bazaar: .. code-block:: sh $ bzr branch lp:zope.event Running the tests in a ``virtualenv`` ------------------------------------- If you use the ``virtualenv`` package to create lightweight Python development environments, you can run the tests using nothing more than the ``python`` binary in a virtualenv. First, create a scratch environment: .. code-block:: sh $ /path/to/virtualenv --no-site-packages /tmp/hack-zope.event Next, get this package registered as a "development egg" in the environment: .. code-block:: sh $ /tmp/hack-zope.event/bin/python setup.py develop Finally, run the tests using the build-in ``setuptools`` testrunner: .. code-block:: sh $ /tmp/hack-zope.event/bin/python setup.py test running test ... test_empty (zope.event.tests.Test_notify) ... ok test_not_empty (zope.event.tests.Test_notify) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK If you have the :mod:`nose` package installed in the virtualenv, you can use its testrunner too: .. code-block:: sh $ /tmp/hack-zope.event/bin/easy_install nose ... $ /tmp/hack-zope.event/bin/python setup.py nosetests running nosetests ... ---------------------------------------------------------------------- Ran 3 tests in 0.011s OK or: .. code-block:: sh $ /tmp/hack-zope.event/bin/nosetests ... ---------------------------------------------------------------------- Ran 3 tests in 0.011s OK If you have the :mod:`coverage` pacakge installed in the virtualenv, you can see how well the tests cover the code: .. code-block:: sh $ /tmp/hack-zope.event/bin/easy_install nose coverage ... $ /tmp/hack-zope.event/bin/python setup.py nosetests \ --with coverage --cover-package=zope.event running nosetests ... Name Stmts Exec Cover Missing ------------------------------------------ zope.event 5 5 100% ---------------------------------------------------------------------- Ran 3 tests in 0.019s OK Building the documentation in a ``virtualenv`` ---------------------------------------------- :mod:`zope.event` uses the nifty :mod:`Sphinx` documentation system for building its docs. Using the same virtualenv you set up to run the tests, you can build the docs: .. code-block:: sh $ /tmp/hack-zope.event/bin/easy_install Sphinx ... $ cd docs $ PATH=/tmp/hack-zope.event/bin:$PATH make html sphinx-build -b html -d _build/doctrees . _build/html ... build succeeded. Build finished. The HTML pages are in _build/html. You can also test the code snippets in the documentation: .. code-block:: sh $ PATH=/tmp/hack-zope.event/bin:$PATH make doctest sphinx-build -b doctest -d _build/doctrees . _build/doctest ... running tests... Document: index --------------- 1 items passed all tests: 17 tests in default 17 tests in 1 items. 17 passed and 0 failed. Test passed. Doctest summary =============== 17 tests 0 failures in tests 0 failures in setup code build succeeded. Testing of doctests in the sources finished, look at the \ results in _build/doctest/output.txt. Running the tests using :mod:`zc.buildout` ------------------------------------------- :mod:`zope.event` ships with its own :file:`buildout.cfg` file and :file:`bootstrap.py` for setting up a development buildout: .. code-block:: sh $ /path/to/python2.6 bootstrap.py ... Generated script '.../bin/buildout' $ bin/buildout Develop: '/home/tseaver/projects/Zope/BTK/event/.' ... Generated script '.../bin/sphinx-quickstart'. Generated script '.../bin/sphinx-build'. You can now run the tests: .. code-block:: sh $ bin/test --all Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in 0.000 seconds. Ran 2 tests with 0 failures and 0 errors in 0.000 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in 0.000 seconds. Building the documentation using :mod:`zc.buildout` --------------------------------------------------- The :mod:`zope.event` buildout installs the Sphinx scripts required to build the documentation, including testing its code snippets: .. code-block:: sh $ cd docs $ PATH=../bin:$PATH make doctest html .../bin/sphinx-build -b doctest -d .../docs/_build/doctrees .../docs .../docs/_build/doctest running tests... Document: index --------------- 1 items passed all tests: 17 tests in default 17 tests in 1 items. 17 passed and 0 failed. Test passed. Doctest summary =============== 17 tests 0 failures in tests 0 failures in setup code build succeeded. Testing of doctests in the sources finished, look at the results in .../docs/_build/doctest/output.txt. .../bin/sphinx-build -b html -d .../docs/_build/doctrees .../docs .../docs/_build/html ... build succeeded. Build finished. The HTML pages are in .../docs/_build/html. Submitting a Bug Report ----------------------- :mod:`zope.event` tracks its bugs on Launchpad: https://bugs.launchpad.net/zope.event Please submit bug reports and feature requests there. Sharing Your Changes -------------------- .. note:: Please ensure that all tests are passing before you submit your code. If possible, your submission should include new tests for new features or bug fixes, although it is possible that you may have tested your new code by updating existing tests. If you got a read-only checkout from the Subversion repository, and you have made a change you would like to share, the best route is to let Subversion help you make a patch file: .. code-block:: sh $ svn diff > zope.event-cool_feature.patch You can then upload that patch file as an attachment to a Launchpad bug report. If you branched the code from Launchpad using Bazaar, you have another option: you can "push" your branch to Launchpad: .. code-block:: sh $ bzr push lp:~tseaver/zope.event/cool_feature After pushing your branch, you can link it to a bug report on Launchpad, or request that the maintainers merge your branch using the Launchpad "merge request" feature. zope2.13-2.13.21/source/zope.event/docs/usage.rst0000644000175000017500000000340312214017560020320 0ustar arnauarnauUsing :mod:`zope.event` ======================= The :mod:`zope.event` package has a list of subscribers. Application code can manage subscriptions by manipulating this list. For the examples here, we'll save the current contents away and empty the list. We'll restore the contents when we're done with our examples. .. doctest:: >>> import zope.event >>> old_subscribers = zope.event.subscribers[:] >>> del zope.event.subscribers[:] The package provides a :func:`notify` function, which is used to notify subscribers that something has happened: .. doctest:: >>> class MyEvent: ... pass >>> event = MyEvent() >>> zope.event.notify(event) The notify function is called with a single object, which we call an event. Any object will do: .. doctest:: >>> zope.event.notify(None) >>> zope.event.notify(42) An extremely trivial subscription mechanism is provided. Subscribers are simply callback functions: .. doctest:: >>> def f(event): ... print 'got:', event that are put into the subscriptions list: .. doctest:: >>> zope.event.subscribers.append(f) >>> zope.event.notify(42) got: 42 >>> def g(event): ... print 'also got:', event >>> zope.event.subscribers.append(g) >>> zope.event.notify(42) got: 42 also got: 42 To unsubscribe, simply remove a subscriber from the list: .. doctest:: >>> zope.event.subscribers.remove(f) >>> zope.event.notify(42) also got: 42 Generally, application frameworks will provide more sophisticated subscription mechanisms that build on this simple mechanism. The frameworks will install subscribers that then dispatch to other subscribers based on event types or data. We're done, so we'll restore the subscribers: .. doctest:: >>> zope.event.subscribers[:] = old_subscribers zope2.13-2.13.21/source/zope.event/docs/Makefile0000644000175000017500000000607412214017560020131 0ustar arnauarnau# 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) . .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/zopeevent.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/zopeevent.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." zope2.13-2.13.21/source/zope.event/docs/api.rst0000644000175000017500000000033712214017560017770 0ustar arnauarnau:mod:`zope.event` API Reference =============================== The package exports the following API symbols. Data ---- .. autodata:: zope.event.subscribers Functions --------- .. autofunction:: zope.event.notify zope2.13-2.13.21/source/zope.event/COPYRIGHT.txt0000644000175000017500000000004012214017560017635 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.event/buildout.cfg0000644000175000017500000000025012214017560020037 0ustar arnauarnau[buildout] develop = . parts = test sphinx [test] recipe = zc.recipe.testrunner eggs = zope.event [sphinx] recipe = zc.recipe.egg eggs = zope.event[docs] zope2.13-2.13.21/source/zope.event/bootstrap.py0000644000175000017500000000413712214017560020126 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set is_jython = sys.platform.startswith('java') if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout'], env = dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.event/CHANGES.txt0000644000175000017500000000135612214017560017350 0ustar arnauarnau``zope.event`` Changelog ======================== 3.5.2 (2012-03-30) ------------------ - This release is the last which will maintain support for Python 2.4 / Python 2.5. - Added support for continuous integration using ``tox`` and ``jenkins``. - Added 'setup.py dev' alias (runs ``setup.py develop`` plus installs ``nose`` and ``coverage``). - Added 'setup.py docs' alias (installs ``Sphinx`` and dependencies). 3.5.1 (2011-08-04) ------------------ - Added Sphinx documentation. 3.5.0 (2010-05-01) ------------------ - Added change log to ``long-description``. - Support for Python 3.x. 3.4.1 (2009-03-03) ------------------ - A few minor cleanups. 3.4.0 (2007-07-14) ------------------ - Initial release as a separate project. zope2.13-2.13.21/source/zope.event/src/0000755000175000017500000000000012214017560016321 5ustar arnauarnauzope2.13-2.13.21/source/zope.event/src/zope/0000755000175000017500000000000012214017560017276 5ustar arnauarnauzope2.13-2.13.21/source/zope.event/src/zope/event/0000755000175000017500000000000012214017560020417 5ustar arnauarnauzope2.13-2.13.21/source/zope.event/src/zope/event/__init__.py0000644000175000017500000000210712214017560022530 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ Base event system implementation """ #: Applications may register for notification of events by appending a #: callable to the ``subscribers`` list. #: #: Each subscriber takes a single argument, which is the event object #: being published. #: #: Exceptions raised by subscribers will be propagated. subscribers = [] def notify(event): """ Notify all subscribers of ``event``. """ for subscriber in subscribers: subscriber(event) zope2.13-2.13.21/source/zope.event/src/zope/event/tests.py0000644000175000017500000000271112214017560022134 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ Test the event system """ import unittest class Test_notify(unittest.TestCase): def setUp(self): from zope.event import subscribers self._old_subscribers = subscribers[:] subscribers[:] = [] def tearDown(self): from zope.event import subscribers subscribers[:] = self._old_subscribers def _callFUT(self, event): from zope.event import notify notify(event) def test_empty(self): event = object() self._callFUT(event) def test_not_empty(self): from zope.event import subscribers dummy = [] subscribers.append(dummy.append) event = object() self._callFUT(event) self.assertEqual(dummy, [event]) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(Test_notify), )) zope2.13-2.13.21/source/zope.event/src/zope/__init__.py0000644000175000017500000000007012214017560021404 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.event/src/zope.event.egg-info/0000755000175000017500000000000012214017560022110 5ustar arnauarnauzope2.13-2.13.21/source/zope.event/src/zope.event.egg-info/PKG-INFO0000644000175000017500000000503612214017560023211 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.event Version: 3.5.2 Summary: Very basic event publishing system Home-page: http://pypi.python.org/pypi/zope.event Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ``zope.event`` README ===================== The ``zope.event`` package provides a simple event system, including: - An event publishing API, intended for use by applications which are unaware of any subscribers to their events. - A very simple event-dispatching system on which more sophisticated event dispatching systems can be built. For example, a type-based event dispatching system that builds on ``zope.event`` can be found in ``zope.component``. Please see ``doc/index.rst`` for the documentation. ``zope.event`` Changelog ======================== 3.5.2 (2012-03-30) ------------------ - This release is the last which will maintain support for Python 2.4 / Python 2.5. - Added support for continuous integration using ``tox`` and ``jenkins``. - Added 'setup.py dev' alias (runs ``setup.py develop`` plus installs ``nose`` and ``coverage``). - Added 'setup.py docs' alias (installs ``Sphinx`` and dependencies). 3.5.1 (2011-08-04) ------------------ - Added Sphinx documentation. 3.5.0 (2010-05-01) ------------------ - Added change log to ``long-description``. - Support for Python 3.x. 3.4.1 (2009-03-03) ------------------ - A few minor cleanups. 3.4.0 (2007-07-14) ------------------ - Initial release as a separate project. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Topic :: Software Development :: Libraries :: Python Modules zope2.13-2.13.21/source/zope.event/src/zope.event.egg-info/dependency_links.txt0000644000175000017500000000000112214017560026156 0ustar arnauarnau zope2.13-2.13.21/source/zope.event/src/zope.event.egg-info/requires.txt0000644000175000017500000000006212214017560024506 0ustar arnauarnausetuptools [docs] Sphinx [testing] nose coveragezope2.13-2.13.21/source/zope.event/src/zope.event.egg-info/namespace_packages.txt0000644000175000017500000000000512214017560026436 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.event/src/zope.event.egg-info/top_level.txt0000644000175000017500000000000512214017560024635 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.event/src/zope.event.egg-info/SOURCES.txt0000644000175000017500000000107612214017560024000 0ustar arnauarnau.bzrignore CHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.cfg setup.py tox.ini docs/Makefile docs/api.rst docs/conf.py docs/hacking.rst docs/index.rst docs/make.bat docs/theory.rst docs/usage.rst src/zope/__init__.py src/zope.event.egg-info/PKG-INFO src/zope.event.egg-info/SOURCES.txt src/zope.event.egg-info/dependency_links.txt src/zope.event.egg-info/namespace_packages.txt src/zope.event.egg-info/not-zip-safe src/zope.event.egg-info/requires.txt src/zope.event.egg-info/top_level.txt src/zope/event/__init__.py src/zope/event/tests.pyzope2.13-2.13.21/source/zope.event/src/zope.event.egg-info/not-zip-safe0000644000175000017500000000000112214017560024336 0ustar arnauarnau zope2.13-2.13.21/source/zope.event/.bzrignore0000644000175000017500000000015412214017560017534 0ustar arnauarnau./.installed.cfg ./bin ./eggs ./develop-eggs ./parts *.egg-info ./docs/_build/* .coverage build __pycache__ zope2.13-2.13.21/source/zope.interface/0000755000175000017500000000000012214017572016354 5ustar arnauarnauzope2.13-2.13.21/source/zope.interface/setup.py0000644000175000017500000001142712214017572020073 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.interface package """ import os, sys try: from setuptools import setup, Extension, Feature except ImportError: # do we need to support plain distutils for building when even # the package itself requires setuptools for installing? from distutils.core import setup, Extension if sys.version_info[:2] >= (2, 4): extra = dict( package_data={ 'zope.interface': ['*.txt'], 'zope.interface.tests': ['*.txt'], } ) else: extra = {} else: codeoptimization = Feature("Optional code optimizations", standard = True, ext_modules = [Extension( "zope.interface._zope_interface_coptimizations", [os.path.normcase( os.path.join('src', 'zope', 'interface', '_zope_interface_coptimizations.c') )] )]) extra = dict( namespace_packages=["zope"], include_package_data = True, zip_safe = False, tests_require = [], install_requires = ['setuptools'], extras_require={'docs': ['z3c.recipe.sphinxdoc']}, features = {'codeoptimization': codeoptimization} ) def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() long_description=( read('README.txt') + '\n' + 'Detailed Documentation\n' '**********************\n' + '\n.. contents::\n\n' + read('src', 'zope', 'interface', 'README.txt') + '\n' + read('src', 'zope', 'interface', 'adapter.txt') + '\n' + read('src', 'zope', 'interface', 'human.txt') + '\n' + read('CHANGES.txt') + '\n' + 'Download\n' '********\n' ) try: # Zope setuptools versions from build_ext_3 import optional_build_ext # This is Python 3. Setuptools is now required, and so is zope.fixers. extra['install_requires'] = ['setuptools'] extra['setup_requires'] = ['zope.fixers'] extra['use_2to3'] = True extra['convert_2to3_doctests'] = [ 'src/zope/interface/README.ru.txt', 'src/zope/interface/README.txt', 'src/zope/interface/adapter.ru.txt', 'src/zope/interface/adapter.txt', 'src/zope/interface/human.ru.txt', 'src/zope/interface/human.txt', 'src/zope/interface/index.txt', 'src/zope/interface/verify.txt', ] extra['use_2to3_fixers'] = ['zope.fixers'] except (ImportError, SyntaxError): from build_ext_2 import optional_build_ext setup(name='zope.interface', version='3.6.7', url='http://pypi.python.org/pypi/zope.interface', license='ZPL 2.1', description='Interfaces for Python', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=long_description, classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.4", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Topic :: Software Development :: Libraries :: Python Modules", ], packages = ['zope', 'zope.interface', 'zope.interface.tests'], package_dir = {'': 'src'}, cmdclass = {'build_ext': optional_build_ext, }, test_suite = 'zope.interface.tests', **extra) zope2.13-2.13.21/source/zope.interface/PKG-INFO0000644000175000017500000020443312214017572017457 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.interface Version: 3.6.7 Summary: Interfaces for Python Home-page: http://pypi.python.org/pypi/zope.interface Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: *This package is intended to be independently reusable in any Python project. It is maintained by the* `Zope Toolkit project `_. This package provides an implementation of `object interfaces` for Python. Interfaces are a mechanism for labeling objects as conforming to a given API or contract. So, this package can be considered as implementation of the `Design By Contract`_ methodology support in Python. .. _Design By Contract: http://en.wikipedia.org/wiki/Design_by_contract Detailed Documentation ********************** .. contents:: ========== Interfaces ========== Interfaces are objects that specify (document) the external behavior of objects that "provide" them. An interface specifies behavior through: - Informal documentation in a doc string - Attribute definitions - Invariants, which are conditions that must hold for objects that provide the interface Attribute definitions specify specific attributes. They define the attribute name and provide documentation and constraints of attribute values. Attribute definitions can take a number of forms, as we'll see below. Defining interfaces =================== Interfaces are defined using Python class statements:: >>> import zope.interface >>> class IFoo(zope.interface.Interface): ... """Foo blah blah""" ... ... x = zope.interface.Attribute("""X blah blah""") ... ... def bar(q, r=None): ... """bar blah blah""" In the example above, we've created an interface, `IFoo`. We subclassed `zope.interface.Interface`, which is an ancestor interface for all interfaces, much as `object` is an ancestor of all new-style classes [#create]_. The interface is not a class, it's an Interface, an instance of `InterfaceClass`:: >>> type(IFoo) We can ask for the interface's documentation:: >>> IFoo.__doc__ 'Foo blah blah' and its name:: >>> IFoo.__name__ 'IFoo' and even its module:: >>> IFoo.__module__ '__main__' The interface defined two attributes: `x` This is the simplest form of attribute definition. It has a name and a doc string. It doesn't formally specify anything else. `bar` This is a method. A method is defined via a function definition. A method is simply an attribute constrained to be a callable with a particular signature, as provided by the function definition. Note that `bar` doesn't take a `self` argument. Interfaces document how an object is *used*. When calling instance methods, you don't pass a `self` argument, so a `self` argument isn't included in the interface signature. The `self` argument in instance methods is really an implementation detail of Python instances. Other objects, besides instances can provide interfaces and their methods might not be instance methods. For example, modules can provide interfaces and their methods are usually just functions. Even instances can have methods that are not instance methods. You can access the attributes defined by an interface using mapping syntax:: >>> x = IFoo['x'] >>> type(x) >>> x.__name__ 'x' >>> x.__doc__ 'X blah blah' >>> IFoo.get('x').__name__ 'x' >>> IFoo.get('y') You can use `in` to determine if an interface defines a name:: >>> 'x' in IFoo True You can iterate over interfaces to get the names they define:: >>> names = list(IFoo) >>> names.sort() >>> names ['bar', 'x'] Remember that interfaces aren't classes. You can't access attribute definitions as attributes of interfaces:: >>> IFoo.x Traceback (most recent call last): File "", line 1, in ? AttributeError: 'InterfaceClass' object has no attribute 'x' Methods provide access to the method signature:: >>> bar = IFoo['bar'] >>> bar.getSignatureString() '(q, r=None)' TODO Methods really should have a better API. This is something that needs to be improved. Declaring interfaces ==================== Having defined interfaces, we can *declare* that objects provide them. Before we describe the details, lets define some terms: *provide* We say that objects *provide* interfaces. If an object provides an interface, then the interface specifies the behavior of the object. In other words, interfaces specify the behavior of the objects that provide them. *implement* We normally say that classes *implement* interfaces. If a class implements an interface, then the instances of the class provide the interface. Objects provide interfaces that their classes implement [#factory]_. (Objects can provide interfaces directly, in addition to what their classes implement.) It is important to note that classes don't usually provide the interfaces that they implement. We can generalize this to factories. For any callable object we can declare that it produces objects that provide some interfaces by saying that the factory implements the interfaces. Now that we've defined these terms, we can talk about the API for declaring interfaces. Declaring implemented interfaces -------------------------------- The most common way to declare interfaces is using the implements function in a class statement:: >>> class Foo: ... zope.interface.implements(IFoo) ... ... def __init__(self, x=None): ... self.x = x ... ... def bar(self, q, r=None): ... return q, r, self.x ... ... def __repr__(self): ... return "Foo(%s)" % self.x In this example, we declared that `Foo` implements `IFoo`. This means that instances of `Foo` provide `IFoo`. Having made this declaration, there are several ways we can introspect the declarations. First, we can ask an interface whether it is implemented by a class:: >>> IFoo.implementedBy(Foo) True And we can ask whether an interface is provided by an object:: >>> foo = Foo() >>> IFoo.providedBy(foo) True Of course, `Foo` doesn't provide `IFoo`, it implements it:: >>> IFoo.providedBy(Foo) False We can also ask what interfaces are implemented by an object:: >>> list(zope.interface.implementedBy(Foo)) [] It's an error to ask for interfaces implemented by a non-callable object:: >>> IFoo.implementedBy(foo) Traceback (most recent call last): ... TypeError: ('ImplementedBy called for non-factory', Foo(None)) >>> list(zope.interface.implementedBy(foo)) Traceback (most recent call last): ... TypeError: ('ImplementedBy called for non-factory', Foo(None)) Similarly, we can ask what interfaces are provided by an object:: >>> list(zope.interface.providedBy(foo)) [] >>> list(zope.interface.providedBy(Foo)) [] We can declare interfaces implemented by other factories (besides classes). We do this using a Python-2.4-style decorator named `implementer`. In versions of Python before 2.4, this looks like:: >>> def yfoo(y): ... foo = Foo() ... foo.y = y ... return foo >>> yfoo = zope.interface.implementer(IFoo)(yfoo) >>> list(zope.interface.implementedBy(yfoo)) [] Note that the implementer decorator may modify it's argument. Callers should not assume that a new object is created. Using implementer also works on callable objects. This is used by zope.formlib, as an example. >>> class yfactory: ... def __call__(self, y): ... foo = Foo() ... foo.y = y ... return foo >>> yfoo = yfactory() >>> yfoo = zope.interface.implementer(IFoo)(yfoo) >>> list(zope.interface.implementedBy(yfoo)) [] XXX: Double check and update these version numbers: In zope.interface 3.5.2 and lower, the implementor decorator can not be used for classes, but in 3.6.0 and higher it can: >>> Foo = zope.interface.implementer(IFoo)(Foo) >>> list(zope.interface.providedBy(Foo())) [] Note that class decorators using the @implementor(IFoo) syntax are only supported in Python 2.6 and later. Declaring provided interfaces ----------------------------- We can declare interfaces directly provided by objects. Suppose that we want to document what the `__init__` method of the `Foo` class does. It's not *really* part of `IFoo`. You wouldn't normally call the `__init__` method on Foo instances. Rather, the `__init__` method is part of the `Foo`'s `__call__` method:: >>> class IFooFactory(zope.interface.Interface): ... """Create foos""" ... ... def __call__(x=None): ... """Create a foo ... ... The argument provides the initial value for x ... ... """ It's the class that provides this interface, so we declare the interface on the class:: >>> zope.interface.directlyProvides(Foo, IFooFactory) And then, we'll see that Foo provides some interfaces:: >>> list(zope.interface.providedBy(Foo)) [] >>> IFooFactory.providedBy(Foo) True Declaring class interfaces is common enough that there's a special declaration function for it, `classProvides`, that allows the declaration from within a class statement:: >>> class Foo2: ... zope.interface.implements(IFoo) ... zope.interface.classProvides(IFooFactory) ... ... def __init__(self, x=None): ... self.x = x ... ... def bar(self, q, r=None): ... return q, r, self.x ... ... def __repr__(self): ... return "Foo(%s)" % self.x >>> list(zope.interface.providedBy(Foo2)) [] >>> IFooFactory.providedBy(Foo2) True There's a similar function, `moduleProvides`, that supports interface declarations from within module definitions. For example, see the use of `moduleProvides` call in `zope.interface.__init__`, which declares that the package `zope.interface` provides `IInterfaceDeclaration`. Sometimes, we want to declare interfaces on instances, even though those instances get interfaces from their classes. Suppose we create a new interface, `ISpecial`:: >>> class ISpecial(zope.interface.Interface): ... reason = zope.interface.Attribute("Reason why we're special") ... def brag(): ... "Brag about being special" We can make an existing foo instance special by providing `reason` and `brag` attributes:: >>> foo.reason = 'I just am' >>> def brag(): ... return "I'm special!" >>> foo.brag = brag >>> foo.reason 'I just am' >>> foo.brag() "I'm special!" and by declaring the interface:: >>> zope.interface.directlyProvides(foo, ISpecial) then the new interface is included in the provided interfaces:: >>> ISpecial.providedBy(foo) True >>> list(zope.interface.providedBy(foo)) [, ] We can find out what interfaces are directly provided by an object:: >>> list(zope.interface.directlyProvidedBy(foo)) [] >>> newfoo = Foo() >>> list(zope.interface.directlyProvidedBy(newfoo)) [] Inherited declarations ---------------------- Normally, declarations are inherited:: >>> class SpecialFoo(Foo): ... zope.interface.implements(ISpecial) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason >>> list(zope.interface.implementedBy(SpecialFoo)) [, ] >>> list(zope.interface.providedBy(SpecialFoo())) [, ] Sometimes, you don't want to inherit declarations. In that case, you can use `implementsOnly`, instead of `implements`:: >>> class Special(Foo): ... zope.interface.implementsOnly(ISpecial) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason >>> list(zope.interface.implementedBy(Special)) [] >>> list(zope.interface.providedBy(Special())) [] External declarations --------------------- Normally, we make implementation declarations as part of a class definition. Sometimes, we may want to make declarations from outside the class definition. For example, we might want to declare interfaces for classes that we didn't write. The function `classImplements` can be used for this purpose:: >>> class C: ... pass >>> zope.interface.classImplements(C, IFoo) >>> list(zope.interface.implementedBy(C)) [] We can use `classImplementsOnly` to exclude inherited interfaces:: >>> class C(Foo): ... pass >>> zope.interface.classImplementsOnly(C, ISpecial) >>> list(zope.interface.implementedBy(C)) [] Declaration Objects ------------------- When we declare interfaces, we create *declaration* objects. When we query declarations, declaration objects are returned:: >>> type(zope.interface.implementedBy(Special)) Declaration objects and interface objects are similar in many ways. In fact, they share a common base class. The important thing to realize about them is that they can be used where interfaces are expected in declarations. Here's a silly example:: >>> class Special2(Foo): ... zope.interface.implementsOnly( ... zope.interface.implementedBy(Foo), ... ISpecial, ... ) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason The declaration here is almost the same as ``zope.interface.implements(ISpecial)``, except that the order of interfaces in the resulting declaration is different:: >>> list(zope.interface.implementedBy(Special2)) [, ] Interface Inheritance ===================== Interfaces can extend other interfaces. They do this simply by listing the other interfaces as base interfaces:: >>> class IBlat(zope.interface.Interface): ... """Blat blah blah""" ... ... y = zope.interface.Attribute("y blah blah") ... def eek(): ... """eek blah blah""" >>> IBlat.__bases__ (,) >>> class IBaz(IFoo, IBlat): ... """Baz blah""" ... def eek(a=1): ... """eek in baz blah""" ... >>> IBaz.__bases__ (, ) >>> names = list(IBaz) >>> names.sort() >>> names ['bar', 'eek', 'x', 'y'] Note that `IBaz` overrides eek:: >>> IBlat['eek'].__doc__ 'eek blah blah' >>> IBaz['eek'].__doc__ 'eek in baz blah' We were careful to override eek in a compatible way. When extending an interface, the extending interface should be compatible [#compat]_ with the extended interfaces. We can ask whether one interface extends another:: >>> IBaz.extends(IFoo) True >>> IBlat.extends(IFoo) False Note that interfaces don't extend themselves:: >>> IBaz.extends(IBaz) False Sometimes we wish they did, but we can, instead use `isOrExtends`:: >>> IBaz.isOrExtends(IBaz) True >>> IBaz.isOrExtends(IFoo) True >>> IFoo.isOrExtends(IBaz) False When we iterate over an interface, we get all of the names it defines, including names defined by base interfaces. Sometimes, we want *just* the names defined by the interface directly. We bane use the `names` method for that:: >>> list(IBaz.names()) ['eek'] Inheritance of attribute specifications --------------------------------------- An interface may override attribute definitions from base interfaces. If two base interfaces define the same attribute, the attribute is inherited from the most specific interface. For example, with:: >>> class IBase(zope.interface.Interface): ... ... def foo(): ... "base foo doc" >>> class IBase1(IBase): ... pass >>> class IBase2(IBase): ... ... def foo(): ... "base2 foo doc" >>> class ISub(IBase1, IBase2): ... pass ISub's definition of foo is the one from IBase2, since IBase2 is more specific that IBase:: >>> ISub['foo'].__doc__ 'base2 foo doc' Note that this differs from a depth-first search. Sometimes, it's useful to ask whether an interface defines an attribute directly. You can use the direct method to get a directly defined definitions:: >>> IBase.direct('foo').__doc__ 'base foo doc' >>> ISub.direct('foo') Specifications -------------- Interfaces and declarations are both special cases of specifications. What we described above for interface inheritance applies to both declarations and specifications. Declarations actually extend the interfaces that they declare:: >>> class Baz(object): ... zope.interface.implements(IBaz) >>> baz_implements = zope.interface.implementedBy(Baz) >>> baz_implements.__bases__ (, ) >>> baz_implements.extends(IFoo) True >>> baz_implements.isOrExtends(IFoo) True >>> baz_implements.isOrExtends(baz_implements) True Specifications (interfaces and declarations) provide an `__sro__` that lists the specification and all of it's ancestors:: >>> baz_implements.__sro__ (, , , , , ) Tagged Values ============= Interfaces and attribute descriptions support an extension mechanism, borrowed from UML, called "tagged values" that lets us store extra data:: >>> IFoo.setTaggedValue('date-modified', '2004-04-01') >>> IFoo.setTaggedValue('author', 'Jim Fulton') >>> IFoo.getTaggedValue('date-modified') '2004-04-01' >>> IFoo.queryTaggedValue('date-modified') '2004-04-01' >>> IFoo.queryTaggedValue('datemodified') >>> tags = list(IFoo.getTaggedValueTags()) >>> tags.sort() >>> tags ['author', 'date-modified'] Function attributes are converted to tagged values when method attribute definitions are created:: >>> class IBazFactory(zope.interface.Interface): ... def __call__(): ... "create one" ... __call__.return_type = IBaz >>> IBazFactory['__call__'].getTaggedValue('return_type') Tagged values can also be defined from within an interface definition:: >>> class IWithTaggedValues(zope.interface.Interface): ... zope.interface.taggedValue('squish', 'squash') >>> IWithTaggedValues.getTaggedValue('squish') 'squash' Invariants ========== Interfaces can express conditions that must hold for objects that provide them. These conditions are expressed using one or more invariants. Invariants are callable objects that will be called with an object that provides an interface. An invariant raises an `Invalid` exception if the condition doesn't hold. Here's an example:: >>> class RangeError(zope.interface.Invalid): ... """A range has invalid limits""" ... def __repr__(self): ... return "RangeError(%r)" % self.args >>> def range_invariant(ob): ... if ob.max < ob.min: ... raise RangeError(ob) Given this invariant, we can use it in an interface definition:: >>> class IRange(zope.interface.Interface): ... min = zope.interface.Attribute("Lower bound") ... max = zope.interface.Attribute("Upper bound") ... ... zope.interface.invariant(range_invariant) Interfaces have a method for checking their invariants:: >>> class Range(object): ... zope.interface.implements(IRange) ... ... def __init__(self, min, max): ... self.min, self.max = min, max ... ... def __repr__(self): ... return "Range(%s, %s)" % (self.min, self.max) >>> IRange.validateInvariants(Range(1,2)) >>> IRange.validateInvariants(Range(1,1)) >>> IRange.validateInvariants(Range(2,1)) Traceback (most recent call last): ... RangeError: Range(2, 1) If you have multiple invariants, you may not want to stop checking after the first error. If you pass a list to `validateInvariants`, then a single `Invalid` exception will be raised with the list of exceptions as it's argument:: >>> from zope.interface.exceptions import Invalid >>> errors = [] >>> try: ... IRange.validateInvariants(Range(2,1), errors) ... except Invalid, e: ... str(e) '[RangeError(Range(2, 1))]' And the list will be filled with the individual exceptions:: >>> errors [RangeError(Range(2, 1))] >>> del errors[:] Adaptation ========== Interfaces can be called to perform adaptation. The semantics are based on those of the PEP 246 adapt function. If an object cannot be adapted, then a TypeError is raised:: >>> class I(zope.interface.Interface): ... pass >>> I(0) Traceback (most recent call last): ... TypeError: ('Could not adapt', 0, ) unless an alternate value is provided as a second positional argument:: >>> I(0, 'bob') 'bob' If an object already implements the interface, then it will be returned:: >>> class C(object): ... zope.interface.implements(I) >>> obj = C() >>> I(obj) is obj True If an object implements __conform__, then it will be used:: >>> class C(object): ... zope.interface.implements(I) ... def __conform__(self, proto): ... return 0 >>> I(C()) 0 Adapter hooks (see __adapt__) will also be used, if present:: >>> from zope.interface.interface import adapter_hooks >>> def adapt_0_to_42(iface, obj): ... if obj == 0: ... return 42 >>> adapter_hooks.append(adapt_0_to_42) >>> I(0) 42 >>> adapter_hooks.remove(adapt_0_to_42) >>> I(0) Traceback (most recent call last): ... TypeError: ('Could not adapt', 0, ) __adapt__ --------- >>> class I(zope.interface.Interface): ... pass Interfaces implement the PEP 246 __adapt__ method. This method is normally not called directly. It is called by the PEP 246 adapt framework and by the interface __call__ operator. The adapt method is responsible for adapting an object to the reciever. The default version returns None:: >>> I.__adapt__(0) unless the object given provides the interface:: >>> class C(object): ... zope.interface.implements(I) >>> obj = C() >>> I.__adapt__(obj) is obj True Adapter hooks can be provided (or removed) to provide custom adaptation. We'll install a silly hook that adapts 0 to 42. We install a hook by simply adding it to the adapter_hooks list:: >>> from zope.interface.interface import adapter_hooks >>> def adapt_0_to_42(iface, obj): ... if obj == 0: ... return 42 >>> adapter_hooks.append(adapt_0_to_42) >>> I.__adapt__(0) 42 Hooks must either return an adapter, or None if no adapter can be found. Hooks can be uninstalled by removing them from the list:: >>> adapter_hooks.remove(adapt_0_to_42) >>> I.__adapt__(0) .. [#create] The main reason we subclass `Interface` is to cause the Python class statement to create an interface, rather than a class. It's possible to create interfaces by calling a special interface class directly. Doing this, it's possible (and, on rare occasions, useful) to create interfaces that don't descend from `Interface`. Using this technique is beyond the scope of this document. .. [#factory] Classes are factories. They can be called to create their instances. We expect that we will eventually extend the concept of implementation to other kinds of factories, so that we can declare the interfaces provided by the objects created. .. [#compat] The goal is substitutability. An object that provides an extending interface should be substitutable for an object that provides the extended interface. In our example, an object that provides IBaz should be usable whereever an object that provides IBlat is expected. The interface implementation doesn't enforce this. but maybe it should do some checks. ================ Adapter Registry ================ Adapter registries provide a way to register objects that depend on one or more interface specifications and provide (perhaps indirectly) some interface. In addition, the registrations have names. (You can think of the names as qualifiers of the provided interfaces.) The term "interface specification" refers both to interfaces and to interface declarations, such as declarations of interfaces implemented by a class. Single Adapters =============== Let's look at a simple example, using a single required specification:: >>> from zope.interface.adapter import AdapterRegistry >>> import zope.interface >>> class IR1(zope.interface.Interface): ... pass >>> class IP1(zope.interface.Interface): ... pass >>> class IP2(IP1): ... pass >>> registry = AdapterRegistry() We'll register an object that depends on IR1 and "provides" IP2:: >>> registry.register([IR1], IP2, '', 12) Given the registration, we can look it up again:: >>> registry.lookup([IR1], IP2, '') 12 Note that we used an integer in the example. In real applications, one would use some objects that actually depend on or provide interfaces. The registry doesn't care about what gets registered, so we'll use integers and strings to keep the examples simple. There is one exception. Registering a value of None unregisters any previously-registered value. If an object depends on a specification, it can be looked up with a specification that extends the specification that it depends on:: >>> class IR2(IR1): ... pass >>> registry.lookup([IR2], IP2, '') 12 We can use a class implementation specification to look up the object:: >>> class C2: ... zope.interface.implements(IR2) >>> registry.lookup([zope.interface.implementedBy(C2)], IP2, '') 12 and it can be looked up for interfaces that its provided interface extends:: >>> registry.lookup([IR1], IP1, '') 12 >>> registry.lookup([IR2], IP1, '') 12 But if you require a specification that doesn't extend the specification the object depends on, you won't get anything:: >>> registry.lookup([zope.interface.Interface], IP1, '') By the way, you can pass a default value to lookup:: >>> registry.lookup([zope.interface.Interface], IP1, '', 42) 42 If you try to get an interface the object doesn't provide, you also won't get anything:: >>> class IP3(IP2): ... pass >>> registry.lookup([IR1], IP3, '') You also won't get anything if you use the wrong name:: >>> registry.lookup([IR1], IP1, 'bob') >>> registry.register([IR1], IP2, 'bob', "Bob's 12") >>> registry.lookup([IR1], IP1, 'bob') "Bob's 12" You can leave the name off when doing a lookup:: >>> registry.lookup([IR1], IP1) 12 If we register an object that provides IP1:: >>> registry.register([IR1], IP1, '', 11) then that object will be prefered over O(12):: >>> registry.lookup([IR1], IP1, '') 11 Also, if we register an object for IR2, then that will be prefered when using IR2:: >>> registry.register([IR2], IP1, '', 21) >>> registry.lookup([IR2], IP1, '') 21 Finding out what, if anything, is registered -------------------------------------------- We can ask if there is an adapter registered for a collection of interfaces. This is different than lookup, because it looks for an exact match. >>> print registry.registered([IR1], IP1) 11 >>> print registry.registered([IR1], IP2) 12 >>> print registry.registered([IR1], IP2, 'bob') Bob's 12 >>> print registry.registered([IR2], IP1) 21 >>> print registry.registered([IR2], IP2) None In the last example, None was returned because nothing was registered exactly for the given interfaces. lookup1 ------- Lookup of single adapters is common enough that there is a specialized version of lookup that takes a single required interface:: >>> registry.lookup1(IR2, IP1, '') 21 >>> registry.lookup1(IR2, IP1) 21 Actual Adaptation ----------------- The adapter registry is intended to support adaptation, where one object that implements an interface is adapted to another object that supports a different interface. The adapter registry supports the computation of adapters. In this case, we have to register adapter factories:: >>> class IR(zope.interface.Interface): ... pass >>> class X: ... zope.interface.implements(IR) >>> class Y: ... zope.interface.implements(IP1) ... def __init__(self, context): ... self.context = context >>> registry.register([IR], IP1, '', Y) In this case, we registered a class as the factory. Now we can call `queryAdapter` to get the adapted object:: >>> x = X() >>> y = registry.queryAdapter(x, IP1) >>> y.__class__.__name__ 'Y' >>> y.context is x True We can register and lookup by name too:: >>> class Y2(Y): ... pass >>> registry.register([IR], IP1, 'bob', Y2) >>> y = registry.queryAdapter(x, IP1, 'bob') >>> y.__class__.__name__ 'Y2' >>> y.context is x True When the adapter factory produces `None`, then this is treated as if no adapter has been found. This allows us to prevent adaptation (when desired) and let the adapter factory determine whether adaptation is possible based on the state of the object being adapted. >>> def factory(context): ... if context.name == 'object': ... return 'adapter' ... return None >>> class Object(object): ... zope.interface.implements(IR) ... name = 'object' >>> registry.register([IR], IP1, 'conditional', factory) >>> obj = Object() >>> registry.queryAdapter(obj, IP1, 'conditional') 'adapter' >>> obj.name = 'no object' >>> registry.queryAdapter(obj, IP1, 'conditional') is None True >>> registry.queryAdapter(obj, IP1, 'conditional', 'default') 'default' An alternate method that provides the same function as `queryAdapter()` is `adapter_hook()`:: >>> y = registry.adapter_hook(IP1, x) >>> y.__class__.__name__ 'Y' >>> y.context is x True >>> y = registry.adapter_hook(IP1, x, 'bob') >>> y.__class__.__name__ 'Y2' >>> y.context is x True The `adapter_hook()` simply switches the order of the object and interface arguments. It is used to hook into the interface call mechanism. Default Adapters ---------------- Sometimes, you want to provide an adapter that will adapt anything. For that, provide None as the required interface:: >>> registry.register([None], IP1, '', 1) then we can use that adapter for interfaces we don't have specific adapters for:: >>> class IQ(zope.interface.Interface): ... pass >>> registry.lookup([IQ], IP1, '') 1 Of course, specific adapters are still used when applicable:: >>> registry.lookup([IR2], IP1, '') 21 Class adapters -------------- You can register adapters for class declarations, which is almost the same as registering them for a class:: >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', 'C21') >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') 'C21' Dict adapters ------------- At some point it was impossible to register dictionary-based adapters due a bug. Let's make sure this works now: >>> adapter = {} >>> registry.register((), IQ, '', adapter) >>> registry.lookup((), IQ, '') is adapter True Unregistering ------------- You can unregister by registering None, rather than an object:: >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', None) >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') 21 Of course, this means that None can't be registered. This is an exception to the statement, made earlier, that the registry doesn't care what gets registered. Multi-adapters ============== You can adapt multiple specifications:: >>> registry.register([IR1, IQ], IP2, '', '1q2') >>> registry.lookup([IR1, IQ], IP2, '') '1q2' >>> registry.lookup([IR2, IQ], IP1, '') '1q2' >>> class IS(zope.interface.Interface): ... pass >>> registry.lookup([IR2, IS], IP1, '') >>> class IQ2(IQ): ... pass >>> registry.lookup([IR2, IQ2], IP1, '') '1q2' >>> registry.register([IR1, IQ2], IP2, '', '1q22') >>> registry.lookup([IR2, IQ2], IP1, '') '1q22' Multi-adaptation ---------------- You can adapt multiple objects:: >>> class Q: ... zope.interface.implements(IQ) As with single adapters, we register a factory, which is often a class:: >>> class IM(zope.interface.Interface): ... pass >>> class M: ... zope.interface.implements(IM) ... def __init__(self, x, q): ... self.x, self.q = x, q >>> registry.register([IR, IQ], IM, '', M) And then we can call `queryMultiAdapter` to compute an adapter:: >>> q = Q() >>> m = registry.queryMultiAdapter((x, q), IM) >>> m.__class__.__name__ 'M' >>> m.x is x and m.q is q True and, of course, we can use names:: >>> class M2(M): ... pass >>> registry.register([IR, IQ], IM, 'bob', M2) >>> m = registry.queryMultiAdapter((x, q), IM, 'bob') >>> m.__class__.__name__ 'M2' >>> m.x is x and m.q is q True Default Adapters ---------------- As with single adapters, you can define default adapters by specifying None for the *first* specification:: >>> registry.register([None, IQ], IP2, '', 'q2') >>> registry.lookup([IS, IQ], IP2, '') 'q2' Null Adapters ============= You can also adapt no specification:: >>> registry.register([], IP2, '', 2) >>> registry.lookup([], IP2, '') 2 >>> registry.lookup([], IP1, '') 2 Listing named adapters ---------------------- Adapters are named. Sometimes, it's useful to get all of the named adapters for given interfaces:: >>> adapters = list(registry.lookupAll([IR1], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', 11), (u'bob', "Bob's 12")] This works for multi-adapters too:: >>> registry.register([IR1, IQ2], IP2, 'bob', '1q2 for bob') >>> adapters = list(registry.lookupAll([IR2, IQ2], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', '1q22'), (u'bob', '1q2 for bob')] And even null adapters:: >>> registry.register([], IP2, 'bob', 3) >>> adapters = list(registry.lookupAll([], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', 2), (u'bob', 3)] Subscriptions ============= Normally, we want to look up an object that most-closely matches a specification. Sometimes, we want to get all of the objects that match some specification. We use subscriptions for this. We subscribe objects against specifications and then later find all of the subscribed objects:: >>> registry.subscribe([IR1], IP2, 'sub12 1') >>> registry.subscriptions([IR1], IP2) ['sub12 1'] Note that, unlike regular adapters, subscriptions are unnamed. You can have multiple subscribers for the same specification:: >>> registry.subscribe([IR1], IP2, 'sub12 2') >>> registry.subscriptions([IR1], IP2) ['sub12 1', 'sub12 2'] If subscribers are registered for the same required interfaces, they are returned in the order of definition. You can register subscribers for all specifications using None:: >>> registry.subscribe([None], IP1, 'sub_1') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2'] Note that the new subscriber is returned first. Subscribers defined for less general required interfaces are returned before subscribers for more general interfaces. Subscriptions may be combined over multiple compatible specifications:: >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2'] >>> registry.subscribe([IR1], IP1, 'sub11') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2', 'sub11'] >>> registry.subscribe([IR2], IP2, 'sub22') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2', 'sub11', 'sub22'] >>> registry.subscriptions([IR2], IP2) ['sub12 1', 'sub12 2', 'sub22'] Subscriptions can be on multiple specifications:: >>> registry.subscribe([IR1, IQ], IP2, 'sub1q2') >>> registry.subscriptions([IR1, IQ], IP2) ['sub1q2'] As with single subscriptions and non-subscription adapters, you can specify None for the first required interface, to specify a default:: >>> registry.subscribe([None, IQ], IP2, 'sub_q2') >>> registry.subscriptions([IS, IQ], IP2) ['sub_q2'] >>> registry.subscriptions([IR1, IQ], IP2) ['sub_q2', 'sub1q2'] You can have subscriptions that are indepenent of any specifications:: >>> list(registry.subscriptions([], IP1)) [] >>> registry.subscribe([], IP2, 'sub2') >>> registry.subscriptions([], IP1) ['sub2'] >>> registry.subscribe([], IP1, 'sub1') >>> registry.subscriptions([], IP1) ['sub2', 'sub1'] >>> registry.subscriptions([], IP2) ['sub2'] Unregistering subscribers ------------------------- We can unregister subscribers. When unregistering a subscriber, we can unregister a specific subscriber:: >>> registry.unsubscribe([IR1], IP1, 'sub11') >>> registry.subscriptions([IR1], IP1) ['sub_1', 'sub12 1', 'sub12 2'] If we don't specify a value, then all subscribers matching the given interfaces will be unsubscribed: >>> registry.unsubscribe([IR1], IP2) >>> registry.subscriptions([IR1], IP1) ['sub_1'] Subscription adapters --------------------- We normally register adapter factories, which then allow us to compute adapters, but with subscriptions, we get multiple adapters. Here's an example of multiple-object subscribers:: >>> registry.subscribe([IR, IQ], IM, M) >>> registry.subscribe([IR, IQ], IM, M2) >>> subscribers = registry.subscribers((x, q), IM) >>> len(subscribers) 2 >>> class_names = [s.__class__.__name__ for s in subscribers] >>> class_names.sort() >>> class_names ['M', 'M2'] >>> [(s.x is x and s.q is q) for s in subscribers] [True, True] adapter factory subcribers can't return None values:: >>> def M3(x, y): ... return None >>> registry.subscribe([IR, IQ], IM, M3) >>> subscribers = registry.subscribers((x, q), IM) >>> len(subscribers) 2 Handlers -------- A handler is a subscriber factory that doesn't produce any normal output. It returns None. A handler is unlike adapters in that it does all of its work when the factory is called. To register a handler, simply provide None as the provided interface:: >>> def handler(event): ... print 'handler', event >>> registry.subscribe([IR1], None, handler) >>> registry.subscriptions([IR1], None) == [handler] True ========================== Using the Adapter Registry ========================== This is a small demonstration of the ``zope.interface`` package including its adapter registry. It is intended to provide a concrete but narrow example on how to use interfaces and adapters outside of Zope 3. First we have to import the interface package:: >>> import zope.interface We now develop an interface for our object, which is a simple file in this case. For now we simply support one attribute, the body, which contains the actual file contents:: >>> class IFile(zope.interface.Interface): ... ... body = zope.interface.Attribute('Contents of the file.') ... For statistical reasons we often want to know the size of a file. However, it would be clumsy to implement the size directly in the file object, since the size really represents meta-data. Thus we create another interface that provides the size of something:: >>> class ISize(zope.interface.Interface): ... ... def getSize(): ... 'Return the size of an object.' ... Now we need to implement the file. It is essential that the object states that it implements the `IFile` interface. We also provide a default body value (just to make things simpler for this example):: >>> class File(object): ... ... zope.interface.implements(IFile) ... body = 'foo bar' ... Next we implement an adapter that can provide the `ISize` interface given any object providing `IFile`. By convention we use `__used_for__` to specify the interface that we expect the adapted object to provide, in our case `IFile`. However, this attribute is not used for anything. If you have multiple interfaces for which an adapter is used, just specify the interfaces via a tuple. Again by convention, the constructor of an adapter takes one argument, the context. The context in this case is an instance of `File` (providing `IFile`) that is used to extract the size from. Also by convention the context is stored in an attribute named `context` on the adapter. The twisted community refers to the context as the `original` object. However, you may feel free to use a specific argument name, such as `file`:: >>> class FileSize(object): ... ... zope.interface.implements(ISize) ... __used_for__ = IFile ... ... def __init__(self, context): ... self.context = context ... ... def getSize(self): ... return len(self.context.body) ... Now that we have written our adapter, we have to register it with an adapter registry, so that it can be looked up when needed. There is no such thing as a global registry; thus we have to instantiate one for our example manually:: >>> from zope.interface.adapter import AdapterRegistry >>> registry = AdapterRegistry() The registry keeps a map of what adapters implement based on another interface, the object already provides. Therefore, we next have to register an adapter that adapts from `IFile` to `ISize`. The first argument to the registry's `register()` method is a list of original interfaces.In our cause we have only one original interface, `IFile`. A list makes sense, since the interface package has the concept of multi-adapters, which are adapters that require multiple objects to adapt to a new interface. In these situations, your adapter constructor will require an argument for each specified interface. The second argument is the interface the adapter provides, in our case `ISize`. The third argument is the name of the adapter. Since we do not care about names, we simply leave it as an empty string. Names are commonly useful, if you have adapters for the same set of interfaces, but they are useful in different situations. The last argument is simply the adapter class:: >>> registry.register([IFile], ISize, '', FileSize) You can now use the the registry to lookup the adapter:: >>> registry.lookup1(IFile, ISize, '') Let's get a little bit more practical. Let's create a `File` instance and create the adapter using a registry lookup. Then we see whether the adapter returns the correct size by calling `getSize()`:: >>> file = File() >>> size = registry.lookup1(IFile, ISize, '')(file) >>> size.getSize() 7 However, this is not very practical, since I have to manually pass in the arguments to the lookup method. There is some syntactic candy that will allow us to get an adapter instance by simply calling `ISize(file)`. To make use of this functionality, we need to add our registry to the adapter_hooks list, which is a member of the adapters module. This list stores a collection of callables that are automatically invoked when IFoo(obj) is called; their purpose is to locate adapters that implement an interface for a certain context instance. You are required to implement your own adapter hook; this example covers one of the simplest hooks that use the registry, but you could implement one that used an adapter cache or persistent adapters, for instance. The helper hook is required to expect as first argument the desired output interface (for us `ISize`) and as the second argument the context of the adapter (here `file`). The function returns an adapter, i.e. a `FileSize` instance:: >>> def hook(provided, object): ... adapter = registry.lookup1(zope.interface.providedBy(object), ... provided, '') ... return adapter(object) ... We now just add the hook to an `adapter_hooks` list:: >>> from zope.interface.interface import adapter_hooks >>> adapter_hooks.append(hook) Once the hook is registered, you can use the desired syntax:: >>> size = ISize(file) >>> size.getSize() 7 Now we have to cleanup after ourselves, so that others after us have a clean `adapter_hooks` list:: >>> adapter_hooks.remove(hook) That's it. I have intentionally left out a discussion of named adapters and multi-adapters, since this text is intended as a practical and simple introduction to Zope 3 interfaces and adapters. You might want to read the `adapter.txt` in the `zope.interface` package for a more formal, referencial and complete treatment of the package. Warning: People have reported that `adapter.txt` makes their brain feel soft! ``zope.interface Changelog`` ============================ 3.6.7 (2011-08-20) ------------------ - Fix sporadic failures on x86-64 platforms in tests of rich comparisons of interfaces. 3.6.6 (2011-08-13) ------------------ - LP #570942: Now correctly compare interfaces from different modules but with the same names. N.B.: This is a less intrusive / destabilizing fix than the one applied in 3.6.3: we only fix the underlying cmp-alike function, rather than adding the other "rich comparison" functions. - Revert to software as released with 3.6.1 for "stable" 3.6 release branch. 3.6.5 (2011-08-11) ------------------ - LP #811792: work around buggy behavior in some subclasses of ``zope.interface.interface.InterfaceClass``, which invoke ``__hash__`` before initializing ``__module__`` and ``__name__``. The workaround returns a fixed constant hash in such cases, and issues a ``UserWarning``. - LP #804832: Under PyPy, ``zope.interface`` should not build its C extension. Also, prevent attempting to build it under Jython. - Add a tox.ini for easier xplatform testing. - Fix testing deprecation warnings issued when tested under Py3K. 3.6.4 (2011-07-04) ------------------ - LP 804951: InterfaceClass instances were unhashable under Python 3.x. 3.6.3 (2011-05-26) ------------------ - LP #570942: Now correctly compare interfaces from different modules but with the same names. 3.6.2 (2011-05-17) ------------------ - Moved detailed documentation out-of-line from PyPI page, linking instead to http://docs.zope.org/zope.interface . - Fixes for small issues when running tests under Python 3.2 using ``zope.testrunner``. - LP # 675064: Specify return value type for C optimizations module init under Python 3: undeclared value caused warnings, and segfaults on some 64 bit architectures. - setup.py now raises RuntimeError if you don't have Distutils installed when running under Python 3. 3.6.1 (2010-05-03) ------------------ - A non-ASCII character in the changelog made 3.6.0 uninstallable on Python 3 systems with another default encoding than UTF-8. - Fixed compiler warnings under GCC 4.3.3. 3.6.0 (2010-04-29) ------------------ - LP #185974: Clear the cache used by ``Specificaton.get`` inside ``Specification.changed``. Thanks to Jacob Holm for the patch. - Added support for Python 3.1. Contributors: Lennart Regebro Martin v Loewis Thomas Lotze Wolfgang Schnerring The 3.1 support is completely backwards compatible. However, the implements syntax used under Python 2.X does not work under 3.X, since it depends on how metaclasses are implemented and this has changed. Instead it now supports a decorator syntax (also under Python 2.X):: class Foo: implements(IFoo) ... can now also be written:: @implementor(IFoo): class Foo: ... There are 2to3 fixers available to do this change automatically in the zope.fixers package. - Python 2.3 is no longer supported. 3.5.4 (2009-12-23) ------------------ - Use the standard Python doctest module instead of zope.testing.doctest, which has been deprecated. 3.5.3 (2009-12-08) ------------------ - Fix an edge case: make providedBy() work when a class has '__provides__' in its __slots__ (see http://thread.gmane.org/gmane.comp.web.zope.devel/22490) 3.5.2 (2009-07-01) ------------------ - BaseAdapterRegistry.unregister, unsubscribe: Remove empty portions of the data structures when something is removed. This avoids leaving references to global objects (interfaces) that may be slated for removal from the calling application. 3.5.1 (2009-03-18) ------------------ - verifyObject: use getattr instead of hasattr to test for object attributes in order to let exceptions other than AttributeError raised by properties propagate to the caller - Add Sphinx-based documentation building to the package buildout configuration. Use the ``bin/docs`` command after buildout. - Improve package description a bit. Unify changelog entries formatting. - Change package's mailing list address to zope-dev at zope.org as zope3-dev at zope.org is now retired. 3.5.0 (2008-10-26) ------------------ - Fixed declaration of _zope_interface_coptimizations, it's not a top level package. - Add a DocTestSuite for odd.py module, so their tests are run. - Allow to bootstrap on Jython. - Fix https://bugs.launchpad.net/zope3/3.3/+bug/98388: ISpecification was missing a declaration for __iro__. - Added optional code optimizations support, which allows the building of C code optimizations to fail (Jython). - Replaced `_flatten` with a non-recursive implementation, effectively making it 3x faster. 3.4.1 (2007-10-02) ------------------ - Fixed a setup bug that prevented installation from source on systems without setuptools. 3.4.0 (2007-07-19) ------------------ - Final release for 3.4.0. 3.4.0b3 (2007-05-22) -------------------- - Objects with picky custom comparison methods couldn't be added to component registries. Now, when checking whether an object is already registered, identity comparison is used. 3.3.0.1 (2007-01-03) -------------------- - Made a reference to OverflowWarning, which disappeared in Python 2.5, conditional. 3.3.0 (2007/01/03) ------------------ New Features ++++++++++++ - The adapter-lookup algorithim was refactored to make it much simpler and faster. Also, more of the adapter-lookup logic is implemented in C, making debugging of application code easier, since there is less infrastructre code to step through. - We now treat objects without interface declarations as if they declared that they provide zope.interface.Interface. - There are a number of richer new adapter-registration interfaces that provide greater control and introspection. - Added a new interface decorator to zope.interface that allows the setting of tagged values on an interface at definition time (see zope.interface.taggedValue). Bug Fixes +++++++++ - A bug in multi-adapter lookup sometimes caused incorrect adapters to be returned. 3.2.0.2 (2006-04-15) -------------------- - Fix packaging bug: 'package_dir' must be a *relative* path. 3.2.0.1 (2006-04-14) -------------------- - Packaging change: suppress inclusion of 'setup.cfg' in 'sdist' builds. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope 3.2.0 release. 3.1.0 (2005-10-03) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope 3.1.0 release. - Made attribute resolution order consistent with component lookup order, i.e. new-style class MRO semantics. - Deprecated 'isImplementedBy' and 'isImplementedByInstancesOf' APIs in favor of 'implementedBy' and 'providedBy'. 3.0.1 (2005-07-27) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope X3.0.1 release. - Fixed a bug reported by James Knight, which caused adapter registries to fail occasionally to reflect declaration changes. 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope X3.0.0 release. Download ******** Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Topic :: Software Development :: Libraries :: Python Modules zope2.13-2.13.21/source/zope.interface/build_ext_2.py0000644000175000017500000000213312214017572021125 0ustar arnauarnauimport sys from distutils.errors import (CCompilerError, DistutilsExecError, DistutilsPlatformError) try: from setuptools.command.build_ext import build_ext except ImportError: from distutils.command.build_ext import build_ext class optional_build_ext(build_ext): """This class subclasses build_ext and allows the building of C extensions to fail. """ def run(self): try: build_ext.run(self) except DistutilsPlatformError, e: self._unavailable(e) def build_extension(self, ext): try: build_ext.build_extension(self, ext) except (CCompilerError, DistutilsExecError), e: self._unavailable(e) def _unavailable(self, e): print >> sys.stderr, '*' * 80 print >> sys.stderr, """WARNING: An optional code optimization (C extension) could not be compiled. Optimizations for this package will not be available!""" print >> sys.stderr print >> sys.stderr, e print >> sys.stderr, '*' * 80 zope2.13-2.13.21/source/zope.interface/pip-egg-info/0000755000175000017500000000000012214017572020635 5ustar arnauarnauzope2.13-2.13.21/source/zope.interface/pip-egg-info/zope.interface.egg-info/0000755000175000017500000000000012214017572025243 5ustar arnauarnauzope2.13-2.13.21/source/zope.interface/pip-egg-info/zope.interface.egg-info/PKG-INFO0000644000175000017500000020443312214017572026346 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.interface Version: 3.6.7 Summary: Interfaces for Python Home-page: http://pypi.python.org/pypi/zope.interface Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: *This package is intended to be independently reusable in any Python project. It is maintained by the* `Zope Toolkit project `_. This package provides an implementation of `object interfaces` for Python. Interfaces are a mechanism for labeling objects as conforming to a given API or contract. So, this package can be considered as implementation of the `Design By Contract`_ methodology support in Python. .. _Design By Contract: http://en.wikipedia.org/wiki/Design_by_contract Detailed Documentation ********************** .. contents:: ========== Interfaces ========== Interfaces are objects that specify (document) the external behavior of objects that "provide" them. An interface specifies behavior through: - Informal documentation in a doc string - Attribute definitions - Invariants, which are conditions that must hold for objects that provide the interface Attribute definitions specify specific attributes. They define the attribute name and provide documentation and constraints of attribute values. Attribute definitions can take a number of forms, as we'll see below. Defining interfaces =================== Interfaces are defined using Python class statements:: >>> import zope.interface >>> class IFoo(zope.interface.Interface): ... """Foo blah blah""" ... ... x = zope.interface.Attribute("""X blah blah""") ... ... def bar(q, r=None): ... """bar blah blah""" In the example above, we've created an interface, `IFoo`. We subclassed `zope.interface.Interface`, which is an ancestor interface for all interfaces, much as `object` is an ancestor of all new-style classes [#create]_. The interface is not a class, it's an Interface, an instance of `InterfaceClass`:: >>> type(IFoo) We can ask for the interface's documentation:: >>> IFoo.__doc__ 'Foo blah blah' and its name:: >>> IFoo.__name__ 'IFoo' and even its module:: >>> IFoo.__module__ '__main__' The interface defined two attributes: `x` This is the simplest form of attribute definition. It has a name and a doc string. It doesn't formally specify anything else. `bar` This is a method. A method is defined via a function definition. A method is simply an attribute constrained to be a callable with a particular signature, as provided by the function definition. Note that `bar` doesn't take a `self` argument. Interfaces document how an object is *used*. When calling instance methods, you don't pass a `self` argument, so a `self` argument isn't included in the interface signature. The `self` argument in instance methods is really an implementation detail of Python instances. Other objects, besides instances can provide interfaces and their methods might not be instance methods. For example, modules can provide interfaces and their methods are usually just functions. Even instances can have methods that are not instance methods. You can access the attributes defined by an interface using mapping syntax:: >>> x = IFoo['x'] >>> type(x) >>> x.__name__ 'x' >>> x.__doc__ 'X blah blah' >>> IFoo.get('x').__name__ 'x' >>> IFoo.get('y') You can use `in` to determine if an interface defines a name:: >>> 'x' in IFoo True You can iterate over interfaces to get the names they define:: >>> names = list(IFoo) >>> names.sort() >>> names ['bar', 'x'] Remember that interfaces aren't classes. You can't access attribute definitions as attributes of interfaces:: >>> IFoo.x Traceback (most recent call last): File "", line 1, in ? AttributeError: 'InterfaceClass' object has no attribute 'x' Methods provide access to the method signature:: >>> bar = IFoo['bar'] >>> bar.getSignatureString() '(q, r=None)' TODO Methods really should have a better API. This is something that needs to be improved. Declaring interfaces ==================== Having defined interfaces, we can *declare* that objects provide them. Before we describe the details, lets define some terms: *provide* We say that objects *provide* interfaces. If an object provides an interface, then the interface specifies the behavior of the object. In other words, interfaces specify the behavior of the objects that provide them. *implement* We normally say that classes *implement* interfaces. If a class implements an interface, then the instances of the class provide the interface. Objects provide interfaces that their classes implement [#factory]_. (Objects can provide interfaces directly, in addition to what their classes implement.) It is important to note that classes don't usually provide the interfaces that they implement. We can generalize this to factories. For any callable object we can declare that it produces objects that provide some interfaces by saying that the factory implements the interfaces. Now that we've defined these terms, we can talk about the API for declaring interfaces. Declaring implemented interfaces -------------------------------- The most common way to declare interfaces is using the implements function in a class statement:: >>> class Foo: ... zope.interface.implements(IFoo) ... ... def __init__(self, x=None): ... self.x = x ... ... def bar(self, q, r=None): ... return q, r, self.x ... ... def __repr__(self): ... return "Foo(%s)" % self.x In this example, we declared that `Foo` implements `IFoo`. This means that instances of `Foo` provide `IFoo`. Having made this declaration, there are several ways we can introspect the declarations. First, we can ask an interface whether it is implemented by a class:: >>> IFoo.implementedBy(Foo) True And we can ask whether an interface is provided by an object:: >>> foo = Foo() >>> IFoo.providedBy(foo) True Of course, `Foo` doesn't provide `IFoo`, it implements it:: >>> IFoo.providedBy(Foo) False We can also ask what interfaces are implemented by an object:: >>> list(zope.interface.implementedBy(Foo)) [] It's an error to ask for interfaces implemented by a non-callable object:: >>> IFoo.implementedBy(foo) Traceback (most recent call last): ... TypeError: ('ImplementedBy called for non-factory', Foo(None)) >>> list(zope.interface.implementedBy(foo)) Traceback (most recent call last): ... TypeError: ('ImplementedBy called for non-factory', Foo(None)) Similarly, we can ask what interfaces are provided by an object:: >>> list(zope.interface.providedBy(foo)) [] >>> list(zope.interface.providedBy(Foo)) [] We can declare interfaces implemented by other factories (besides classes). We do this using a Python-2.4-style decorator named `implementer`. In versions of Python before 2.4, this looks like:: >>> def yfoo(y): ... foo = Foo() ... foo.y = y ... return foo >>> yfoo = zope.interface.implementer(IFoo)(yfoo) >>> list(zope.interface.implementedBy(yfoo)) [] Note that the implementer decorator may modify it's argument. Callers should not assume that a new object is created. Using implementer also works on callable objects. This is used by zope.formlib, as an example. >>> class yfactory: ... def __call__(self, y): ... foo = Foo() ... foo.y = y ... return foo >>> yfoo = yfactory() >>> yfoo = zope.interface.implementer(IFoo)(yfoo) >>> list(zope.interface.implementedBy(yfoo)) [] XXX: Double check and update these version numbers: In zope.interface 3.5.2 and lower, the implementor decorator can not be used for classes, but in 3.6.0 and higher it can: >>> Foo = zope.interface.implementer(IFoo)(Foo) >>> list(zope.interface.providedBy(Foo())) [] Note that class decorators using the @implementor(IFoo) syntax are only supported in Python 2.6 and later. Declaring provided interfaces ----------------------------- We can declare interfaces directly provided by objects. Suppose that we want to document what the `__init__` method of the `Foo` class does. It's not *really* part of `IFoo`. You wouldn't normally call the `__init__` method on Foo instances. Rather, the `__init__` method is part of the `Foo`'s `__call__` method:: >>> class IFooFactory(zope.interface.Interface): ... """Create foos""" ... ... def __call__(x=None): ... """Create a foo ... ... The argument provides the initial value for x ... ... """ It's the class that provides this interface, so we declare the interface on the class:: >>> zope.interface.directlyProvides(Foo, IFooFactory) And then, we'll see that Foo provides some interfaces:: >>> list(zope.interface.providedBy(Foo)) [] >>> IFooFactory.providedBy(Foo) True Declaring class interfaces is common enough that there's a special declaration function for it, `classProvides`, that allows the declaration from within a class statement:: >>> class Foo2: ... zope.interface.implements(IFoo) ... zope.interface.classProvides(IFooFactory) ... ... def __init__(self, x=None): ... self.x = x ... ... def bar(self, q, r=None): ... return q, r, self.x ... ... def __repr__(self): ... return "Foo(%s)" % self.x >>> list(zope.interface.providedBy(Foo2)) [] >>> IFooFactory.providedBy(Foo2) True There's a similar function, `moduleProvides`, that supports interface declarations from within module definitions. For example, see the use of `moduleProvides` call in `zope.interface.__init__`, which declares that the package `zope.interface` provides `IInterfaceDeclaration`. Sometimes, we want to declare interfaces on instances, even though those instances get interfaces from their classes. Suppose we create a new interface, `ISpecial`:: >>> class ISpecial(zope.interface.Interface): ... reason = zope.interface.Attribute("Reason why we're special") ... def brag(): ... "Brag about being special" We can make an existing foo instance special by providing `reason` and `brag` attributes:: >>> foo.reason = 'I just am' >>> def brag(): ... return "I'm special!" >>> foo.brag = brag >>> foo.reason 'I just am' >>> foo.brag() "I'm special!" and by declaring the interface:: >>> zope.interface.directlyProvides(foo, ISpecial) then the new interface is included in the provided interfaces:: >>> ISpecial.providedBy(foo) True >>> list(zope.interface.providedBy(foo)) [, ] We can find out what interfaces are directly provided by an object:: >>> list(zope.interface.directlyProvidedBy(foo)) [] >>> newfoo = Foo() >>> list(zope.interface.directlyProvidedBy(newfoo)) [] Inherited declarations ---------------------- Normally, declarations are inherited:: >>> class SpecialFoo(Foo): ... zope.interface.implements(ISpecial) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason >>> list(zope.interface.implementedBy(SpecialFoo)) [, ] >>> list(zope.interface.providedBy(SpecialFoo())) [, ] Sometimes, you don't want to inherit declarations. In that case, you can use `implementsOnly`, instead of `implements`:: >>> class Special(Foo): ... zope.interface.implementsOnly(ISpecial) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason >>> list(zope.interface.implementedBy(Special)) [] >>> list(zope.interface.providedBy(Special())) [] External declarations --------------------- Normally, we make implementation declarations as part of a class definition. Sometimes, we may want to make declarations from outside the class definition. For example, we might want to declare interfaces for classes that we didn't write. The function `classImplements` can be used for this purpose:: >>> class C: ... pass >>> zope.interface.classImplements(C, IFoo) >>> list(zope.interface.implementedBy(C)) [] We can use `classImplementsOnly` to exclude inherited interfaces:: >>> class C(Foo): ... pass >>> zope.interface.classImplementsOnly(C, ISpecial) >>> list(zope.interface.implementedBy(C)) [] Declaration Objects ------------------- When we declare interfaces, we create *declaration* objects. When we query declarations, declaration objects are returned:: >>> type(zope.interface.implementedBy(Special)) Declaration objects and interface objects are similar in many ways. In fact, they share a common base class. The important thing to realize about them is that they can be used where interfaces are expected in declarations. Here's a silly example:: >>> class Special2(Foo): ... zope.interface.implementsOnly( ... zope.interface.implementedBy(Foo), ... ISpecial, ... ) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason The declaration here is almost the same as ``zope.interface.implements(ISpecial)``, except that the order of interfaces in the resulting declaration is different:: >>> list(zope.interface.implementedBy(Special2)) [, ] Interface Inheritance ===================== Interfaces can extend other interfaces. They do this simply by listing the other interfaces as base interfaces:: >>> class IBlat(zope.interface.Interface): ... """Blat blah blah""" ... ... y = zope.interface.Attribute("y blah blah") ... def eek(): ... """eek blah blah""" >>> IBlat.__bases__ (,) >>> class IBaz(IFoo, IBlat): ... """Baz blah""" ... def eek(a=1): ... """eek in baz blah""" ... >>> IBaz.__bases__ (, ) >>> names = list(IBaz) >>> names.sort() >>> names ['bar', 'eek', 'x', 'y'] Note that `IBaz` overrides eek:: >>> IBlat['eek'].__doc__ 'eek blah blah' >>> IBaz['eek'].__doc__ 'eek in baz blah' We were careful to override eek in a compatible way. When extending an interface, the extending interface should be compatible [#compat]_ with the extended interfaces. We can ask whether one interface extends another:: >>> IBaz.extends(IFoo) True >>> IBlat.extends(IFoo) False Note that interfaces don't extend themselves:: >>> IBaz.extends(IBaz) False Sometimes we wish they did, but we can, instead use `isOrExtends`:: >>> IBaz.isOrExtends(IBaz) True >>> IBaz.isOrExtends(IFoo) True >>> IFoo.isOrExtends(IBaz) False When we iterate over an interface, we get all of the names it defines, including names defined by base interfaces. Sometimes, we want *just* the names defined by the interface directly. We bane use the `names` method for that:: >>> list(IBaz.names()) ['eek'] Inheritance of attribute specifications --------------------------------------- An interface may override attribute definitions from base interfaces. If two base interfaces define the same attribute, the attribute is inherited from the most specific interface. For example, with:: >>> class IBase(zope.interface.Interface): ... ... def foo(): ... "base foo doc" >>> class IBase1(IBase): ... pass >>> class IBase2(IBase): ... ... def foo(): ... "base2 foo doc" >>> class ISub(IBase1, IBase2): ... pass ISub's definition of foo is the one from IBase2, since IBase2 is more specific that IBase:: >>> ISub['foo'].__doc__ 'base2 foo doc' Note that this differs from a depth-first search. Sometimes, it's useful to ask whether an interface defines an attribute directly. You can use the direct method to get a directly defined definitions:: >>> IBase.direct('foo').__doc__ 'base foo doc' >>> ISub.direct('foo') Specifications -------------- Interfaces and declarations are both special cases of specifications. What we described above for interface inheritance applies to both declarations and specifications. Declarations actually extend the interfaces that they declare:: >>> class Baz(object): ... zope.interface.implements(IBaz) >>> baz_implements = zope.interface.implementedBy(Baz) >>> baz_implements.__bases__ (, ) >>> baz_implements.extends(IFoo) True >>> baz_implements.isOrExtends(IFoo) True >>> baz_implements.isOrExtends(baz_implements) True Specifications (interfaces and declarations) provide an `__sro__` that lists the specification and all of it's ancestors:: >>> baz_implements.__sro__ (, , , , , ) Tagged Values ============= Interfaces and attribute descriptions support an extension mechanism, borrowed from UML, called "tagged values" that lets us store extra data:: >>> IFoo.setTaggedValue('date-modified', '2004-04-01') >>> IFoo.setTaggedValue('author', 'Jim Fulton') >>> IFoo.getTaggedValue('date-modified') '2004-04-01' >>> IFoo.queryTaggedValue('date-modified') '2004-04-01' >>> IFoo.queryTaggedValue('datemodified') >>> tags = list(IFoo.getTaggedValueTags()) >>> tags.sort() >>> tags ['author', 'date-modified'] Function attributes are converted to tagged values when method attribute definitions are created:: >>> class IBazFactory(zope.interface.Interface): ... def __call__(): ... "create one" ... __call__.return_type = IBaz >>> IBazFactory['__call__'].getTaggedValue('return_type') Tagged values can also be defined from within an interface definition:: >>> class IWithTaggedValues(zope.interface.Interface): ... zope.interface.taggedValue('squish', 'squash') >>> IWithTaggedValues.getTaggedValue('squish') 'squash' Invariants ========== Interfaces can express conditions that must hold for objects that provide them. These conditions are expressed using one or more invariants. Invariants are callable objects that will be called with an object that provides an interface. An invariant raises an `Invalid` exception if the condition doesn't hold. Here's an example:: >>> class RangeError(zope.interface.Invalid): ... """A range has invalid limits""" ... def __repr__(self): ... return "RangeError(%r)" % self.args >>> def range_invariant(ob): ... if ob.max < ob.min: ... raise RangeError(ob) Given this invariant, we can use it in an interface definition:: >>> class IRange(zope.interface.Interface): ... min = zope.interface.Attribute("Lower bound") ... max = zope.interface.Attribute("Upper bound") ... ... zope.interface.invariant(range_invariant) Interfaces have a method for checking their invariants:: >>> class Range(object): ... zope.interface.implements(IRange) ... ... def __init__(self, min, max): ... self.min, self.max = min, max ... ... def __repr__(self): ... return "Range(%s, %s)" % (self.min, self.max) >>> IRange.validateInvariants(Range(1,2)) >>> IRange.validateInvariants(Range(1,1)) >>> IRange.validateInvariants(Range(2,1)) Traceback (most recent call last): ... RangeError: Range(2, 1) If you have multiple invariants, you may not want to stop checking after the first error. If you pass a list to `validateInvariants`, then a single `Invalid` exception will be raised with the list of exceptions as it's argument:: >>> from zope.interface.exceptions import Invalid >>> errors = [] >>> try: ... IRange.validateInvariants(Range(2,1), errors) ... except Invalid, e: ... str(e) '[RangeError(Range(2, 1))]' And the list will be filled with the individual exceptions:: >>> errors [RangeError(Range(2, 1))] >>> del errors[:] Adaptation ========== Interfaces can be called to perform adaptation. The semantics are based on those of the PEP 246 adapt function. If an object cannot be adapted, then a TypeError is raised:: >>> class I(zope.interface.Interface): ... pass >>> I(0) Traceback (most recent call last): ... TypeError: ('Could not adapt', 0, ) unless an alternate value is provided as a second positional argument:: >>> I(0, 'bob') 'bob' If an object already implements the interface, then it will be returned:: >>> class C(object): ... zope.interface.implements(I) >>> obj = C() >>> I(obj) is obj True If an object implements __conform__, then it will be used:: >>> class C(object): ... zope.interface.implements(I) ... def __conform__(self, proto): ... return 0 >>> I(C()) 0 Adapter hooks (see __adapt__) will also be used, if present:: >>> from zope.interface.interface import adapter_hooks >>> def adapt_0_to_42(iface, obj): ... if obj == 0: ... return 42 >>> adapter_hooks.append(adapt_0_to_42) >>> I(0) 42 >>> adapter_hooks.remove(adapt_0_to_42) >>> I(0) Traceback (most recent call last): ... TypeError: ('Could not adapt', 0, ) __adapt__ --------- >>> class I(zope.interface.Interface): ... pass Interfaces implement the PEP 246 __adapt__ method. This method is normally not called directly. It is called by the PEP 246 adapt framework and by the interface __call__ operator. The adapt method is responsible for adapting an object to the reciever. The default version returns None:: >>> I.__adapt__(0) unless the object given provides the interface:: >>> class C(object): ... zope.interface.implements(I) >>> obj = C() >>> I.__adapt__(obj) is obj True Adapter hooks can be provided (or removed) to provide custom adaptation. We'll install a silly hook that adapts 0 to 42. We install a hook by simply adding it to the adapter_hooks list:: >>> from zope.interface.interface import adapter_hooks >>> def adapt_0_to_42(iface, obj): ... if obj == 0: ... return 42 >>> adapter_hooks.append(adapt_0_to_42) >>> I.__adapt__(0) 42 Hooks must either return an adapter, or None if no adapter can be found. Hooks can be uninstalled by removing them from the list:: >>> adapter_hooks.remove(adapt_0_to_42) >>> I.__adapt__(0) .. [#create] The main reason we subclass `Interface` is to cause the Python class statement to create an interface, rather than a class. It's possible to create interfaces by calling a special interface class directly. Doing this, it's possible (and, on rare occasions, useful) to create interfaces that don't descend from `Interface`. Using this technique is beyond the scope of this document. .. [#factory] Classes are factories. They can be called to create their instances. We expect that we will eventually extend the concept of implementation to other kinds of factories, so that we can declare the interfaces provided by the objects created. .. [#compat] The goal is substitutability. An object that provides an extending interface should be substitutable for an object that provides the extended interface. In our example, an object that provides IBaz should be usable whereever an object that provides IBlat is expected. The interface implementation doesn't enforce this. but maybe it should do some checks. ================ Adapter Registry ================ Adapter registries provide a way to register objects that depend on one or more interface specifications and provide (perhaps indirectly) some interface. In addition, the registrations have names. (You can think of the names as qualifiers of the provided interfaces.) The term "interface specification" refers both to interfaces and to interface declarations, such as declarations of interfaces implemented by a class. Single Adapters =============== Let's look at a simple example, using a single required specification:: >>> from zope.interface.adapter import AdapterRegistry >>> import zope.interface >>> class IR1(zope.interface.Interface): ... pass >>> class IP1(zope.interface.Interface): ... pass >>> class IP2(IP1): ... pass >>> registry = AdapterRegistry() We'll register an object that depends on IR1 and "provides" IP2:: >>> registry.register([IR1], IP2, '', 12) Given the registration, we can look it up again:: >>> registry.lookup([IR1], IP2, '') 12 Note that we used an integer in the example. In real applications, one would use some objects that actually depend on or provide interfaces. The registry doesn't care about what gets registered, so we'll use integers and strings to keep the examples simple. There is one exception. Registering a value of None unregisters any previously-registered value. If an object depends on a specification, it can be looked up with a specification that extends the specification that it depends on:: >>> class IR2(IR1): ... pass >>> registry.lookup([IR2], IP2, '') 12 We can use a class implementation specification to look up the object:: >>> class C2: ... zope.interface.implements(IR2) >>> registry.lookup([zope.interface.implementedBy(C2)], IP2, '') 12 and it can be looked up for interfaces that its provided interface extends:: >>> registry.lookup([IR1], IP1, '') 12 >>> registry.lookup([IR2], IP1, '') 12 But if you require a specification that doesn't extend the specification the object depends on, you won't get anything:: >>> registry.lookup([zope.interface.Interface], IP1, '') By the way, you can pass a default value to lookup:: >>> registry.lookup([zope.interface.Interface], IP1, '', 42) 42 If you try to get an interface the object doesn't provide, you also won't get anything:: >>> class IP3(IP2): ... pass >>> registry.lookup([IR1], IP3, '') You also won't get anything if you use the wrong name:: >>> registry.lookup([IR1], IP1, 'bob') >>> registry.register([IR1], IP2, 'bob', "Bob's 12") >>> registry.lookup([IR1], IP1, 'bob') "Bob's 12" You can leave the name off when doing a lookup:: >>> registry.lookup([IR1], IP1) 12 If we register an object that provides IP1:: >>> registry.register([IR1], IP1, '', 11) then that object will be prefered over O(12):: >>> registry.lookup([IR1], IP1, '') 11 Also, if we register an object for IR2, then that will be prefered when using IR2:: >>> registry.register([IR2], IP1, '', 21) >>> registry.lookup([IR2], IP1, '') 21 Finding out what, if anything, is registered -------------------------------------------- We can ask if there is an adapter registered for a collection of interfaces. This is different than lookup, because it looks for an exact match. >>> print registry.registered([IR1], IP1) 11 >>> print registry.registered([IR1], IP2) 12 >>> print registry.registered([IR1], IP2, 'bob') Bob's 12 >>> print registry.registered([IR2], IP1) 21 >>> print registry.registered([IR2], IP2) None In the last example, None was returned because nothing was registered exactly for the given interfaces. lookup1 ------- Lookup of single adapters is common enough that there is a specialized version of lookup that takes a single required interface:: >>> registry.lookup1(IR2, IP1, '') 21 >>> registry.lookup1(IR2, IP1) 21 Actual Adaptation ----------------- The adapter registry is intended to support adaptation, where one object that implements an interface is adapted to another object that supports a different interface. The adapter registry supports the computation of adapters. In this case, we have to register adapter factories:: >>> class IR(zope.interface.Interface): ... pass >>> class X: ... zope.interface.implements(IR) >>> class Y: ... zope.interface.implements(IP1) ... def __init__(self, context): ... self.context = context >>> registry.register([IR], IP1, '', Y) In this case, we registered a class as the factory. Now we can call `queryAdapter` to get the adapted object:: >>> x = X() >>> y = registry.queryAdapter(x, IP1) >>> y.__class__.__name__ 'Y' >>> y.context is x True We can register and lookup by name too:: >>> class Y2(Y): ... pass >>> registry.register([IR], IP1, 'bob', Y2) >>> y = registry.queryAdapter(x, IP1, 'bob') >>> y.__class__.__name__ 'Y2' >>> y.context is x True When the adapter factory produces `None`, then this is treated as if no adapter has been found. This allows us to prevent adaptation (when desired) and let the adapter factory determine whether adaptation is possible based on the state of the object being adapted. >>> def factory(context): ... if context.name == 'object': ... return 'adapter' ... return None >>> class Object(object): ... zope.interface.implements(IR) ... name = 'object' >>> registry.register([IR], IP1, 'conditional', factory) >>> obj = Object() >>> registry.queryAdapter(obj, IP1, 'conditional') 'adapter' >>> obj.name = 'no object' >>> registry.queryAdapter(obj, IP1, 'conditional') is None True >>> registry.queryAdapter(obj, IP1, 'conditional', 'default') 'default' An alternate method that provides the same function as `queryAdapter()` is `adapter_hook()`:: >>> y = registry.adapter_hook(IP1, x) >>> y.__class__.__name__ 'Y' >>> y.context is x True >>> y = registry.adapter_hook(IP1, x, 'bob') >>> y.__class__.__name__ 'Y2' >>> y.context is x True The `adapter_hook()` simply switches the order of the object and interface arguments. It is used to hook into the interface call mechanism. Default Adapters ---------------- Sometimes, you want to provide an adapter that will adapt anything. For that, provide None as the required interface:: >>> registry.register([None], IP1, '', 1) then we can use that adapter for interfaces we don't have specific adapters for:: >>> class IQ(zope.interface.Interface): ... pass >>> registry.lookup([IQ], IP1, '') 1 Of course, specific adapters are still used when applicable:: >>> registry.lookup([IR2], IP1, '') 21 Class adapters -------------- You can register adapters for class declarations, which is almost the same as registering them for a class:: >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', 'C21') >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') 'C21' Dict adapters ------------- At some point it was impossible to register dictionary-based adapters due a bug. Let's make sure this works now: >>> adapter = {} >>> registry.register((), IQ, '', adapter) >>> registry.lookup((), IQ, '') is adapter True Unregistering ------------- You can unregister by registering None, rather than an object:: >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', None) >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') 21 Of course, this means that None can't be registered. This is an exception to the statement, made earlier, that the registry doesn't care what gets registered. Multi-adapters ============== You can adapt multiple specifications:: >>> registry.register([IR1, IQ], IP2, '', '1q2') >>> registry.lookup([IR1, IQ], IP2, '') '1q2' >>> registry.lookup([IR2, IQ], IP1, '') '1q2' >>> class IS(zope.interface.Interface): ... pass >>> registry.lookup([IR2, IS], IP1, '') >>> class IQ2(IQ): ... pass >>> registry.lookup([IR2, IQ2], IP1, '') '1q2' >>> registry.register([IR1, IQ2], IP2, '', '1q22') >>> registry.lookup([IR2, IQ2], IP1, '') '1q22' Multi-adaptation ---------------- You can adapt multiple objects:: >>> class Q: ... zope.interface.implements(IQ) As with single adapters, we register a factory, which is often a class:: >>> class IM(zope.interface.Interface): ... pass >>> class M: ... zope.interface.implements(IM) ... def __init__(self, x, q): ... self.x, self.q = x, q >>> registry.register([IR, IQ], IM, '', M) And then we can call `queryMultiAdapter` to compute an adapter:: >>> q = Q() >>> m = registry.queryMultiAdapter((x, q), IM) >>> m.__class__.__name__ 'M' >>> m.x is x and m.q is q True and, of course, we can use names:: >>> class M2(M): ... pass >>> registry.register([IR, IQ], IM, 'bob', M2) >>> m = registry.queryMultiAdapter((x, q), IM, 'bob') >>> m.__class__.__name__ 'M2' >>> m.x is x and m.q is q True Default Adapters ---------------- As with single adapters, you can define default adapters by specifying None for the *first* specification:: >>> registry.register([None, IQ], IP2, '', 'q2') >>> registry.lookup([IS, IQ], IP2, '') 'q2' Null Adapters ============= You can also adapt no specification:: >>> registry.register([], IP2, '', 2) >>> registry.lookup([], IP2, '') 2 >>> registry.lookup([], IP1, '') 2 Listing named adapters ---------------------- Adapters are named. Sometimes, it's useful to get all of the named adapters for given interfaces:: >>> adapters = list(registry.lookupAll([IR1], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', 11), (u'bob', "Bob's 12")] This works for multi-adapters too:: >>> registry.register([IR1, IQ2], IP2, 'bob', '1q2 for bob') >>> adapters = list(registry.lookupAll([IR2, IQ2], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', '1q22'), (u'bob', '1q2 for bob')] And even null adapters:: >>> registry.register([], IP2, 'bob', 3) >>> adapters = list(registry.lookupAll([], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', 2), (u'bob', 3)] Subscriptions ============= Normally, we want to look up an object that most-closely matches a specification. Sometimes, we want to get all of the objects that match some specification. We use subscriptions for this. We subscribe objects against specifications and then later find all of the subscribed objects:: >>> registry.subscribe([IR1], IP2, 'sub12 1') >>> registry.subscriptions([IR1], IP2) ['sub12 1'] Note that, unlike regular adapters, subscriptions are unnamed. You can have multiple subscribers for the same specification:: >>> registry.subscribe([IR1], IP2, 'sub12 2') >>> registry.subscriptions([IR1], IP2) ['sub12 1', 'sub12 2'] If subscribers are registered for the same required interfaces, they are returned in the order of definition. You can register subscribers for all specifications using None:: >>> registry.subscribe([None], IP1, 'sub_1') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2'] Note that the new subscriber is returned first. Subscribers defined for less general required interfaces are returned before subscribers for more general interfaces. Subscriptions may be combined over multiple compatible specifications:: >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2'] >>> registry.subscribe([IR1], IP1, 'sub11') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2', 'sub11'] >>> registry.subscribe([IR2], IP2, 'sub22') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2', 'sub11', 'sub22'] >>> registry.subscriptions([IR2], IP2) ['sub12 1', 'sub12 2', 'sub22'] Subscriptions can be on multiple specifications:: >>> registry.subscribe([IR1, IQ], IP2, 'sub1q2') >>> registry.subscriptions([IR1, IQ], IP2) ['sub1q2'] As with single subscriptions and non-subscription adapters, you can specify None for the first required interface, to specify a default:: >>> registry.subscribe([None, IQ], IP2, 'sub_q2') >>> registry.subscriptions([IS, IQ], IP2) ['sub_q2'] >>> registry.subscriptions([IR1, IQ], IP2) ['sub_q2', 'sub1q2'] You can have subscriptions that are indepenent of any specifications:: >>> list(registry.subscriptions([], IP1)) [] >>> registry.subscribe([], IP2, 'sub2') >>> registry.subscriptions([], IP1) ['sub2'] >>> registry.subscribe([], IP1, 'sub1') >>> registry.subscriptions([], IP1) ['sub2', 'sub1'] >>> registry.subscriptions([], IP2) ['sub2'] Unregistering subscribers ------------------------- We can unregister subscribers. When unregistering a subscriber, we can unregister a specific subscriber:: >>> registry.unsubscribe([IR1], IP1, 'sub11') >>> registry.subscriptions([IR1], IP1) ['sub_1', 'sub12 1', 'sub12 2'] If we don't specify a value, then all subscribers matching the given interfaces will be unsubscribed: >>> registry.unsubscribe([IR1], IP2) >>> registry.subscriptions([IR1], IP1) ['sub_1'] Subscription adapters --------------------- We normally register adapter factories, which then allow us to compute adapters, but with subscriptions, we get multiple adapters. Here's an example of multiple-object subscribers:: >>> registry.subscribe([IR, IQ], IM, M) >>> registry.subscribe([IR, IQ], IM, M2) >>> subscribers = registry.subscribers((x, q), IM) >>> len(subscribers) 2 >>> class_names = [s.__class__.__name__ for s in subscribers] >>> class_names.sort() >>> class_names ['M', 'M2'] >>> [(s.x is x and s.q is q) for s in subscribers] [True, True] adapter factory subcribers can't return None values:: >>> def M3(x, y): ... return None >>> registry.subscribe([IR, IQ], IM, M3) >>> subscribers = registry.subscribers((x, q), IM) >>> len(subscribers) 2 Handlers -------- A handler is a subscriber factory that doesn't produce any normal output. It returns None. A handler is unlike adapters in that it does all of its work when the factory is called. To register a handler, simply provide None as the provided interface:: >>> def handler(event): ... print 'handler', event >>> registry.subscribe([IR1], None, handler) >>> registry.subscriptions([IR1], None) == [handler] True ========================== Using the Adapter Registry ========================== This is a small demonstration of the ``zope.interface`` package including its adapter registry. It is intended to provide a concrete but narrow example on how to use interfaces and adapters outside of Zope 3. First we have to import the interface package:: >>> import zope.interface We now develop an interface for our object, which is a simple file in this case. For now we simply support one attribute, the body, which contains the actual file contents:: >>> class IFile(zope.interface.Interface): ... ... body = zope.interface.Attribute('Contents of the file.') ... For statistical reasons we often want to know the size of a file. However, it would be clumsy to implement the size directly in the file object, since the size really represents meta-data. Thus we create another interface that provides the size of something:: >>> class ISize(zope.interface.Interface): ... ... def getSize(): ... 'Return the size of an object.' ... Now we need to implement the file. It is essential that the object states that it implements the `IFile` interface. We also provide a default body value (just to make things simpler for this example):: >>> class File(object): ... ... zope.interface.implements(IFile) ... body = 'foo bar' ... Next we implement an adapter that can provide the `ISize` interface given any object providing `IFile`. By convention we use `__used_for__` to specify the interface that we expect the adapted object to provide, in our case `IFile`. However, this attribute is not used for anything. If you have multiple interfaces for which an adapter is used, just specify the interfaces via a tuple. Again by convention, the constructor of an adapter takes one argument, the context. The context in this case is an instance of `File` (providing `IFile`) that is used to extract the size from. Also by convention the context is stored in an attribute named `context` on the adapter. The twisted community refers to the context as the `original` object. However, you may feel free to use a specific argument name, such as `file`:: >>> class FileSize(object): ... ... zope.interface.implements(ISize) ... __used_for__ = IFile ... ... def __init__(self, context): ... self.context = context ... ... def getSize(self): ... return len(self.context.body) ... Now that we have written our adapter, we have to register it with an adapter registry, so that it can be looked up when needed. There is no such thing as a global registry; thus we have to instantiate one for our example manually:: >>> from zope.interface.adapter import AdapterRegistry >>> registry = AdapterRegistry() The registry keeps a map of what adapters implement based on another interface, the object already provides. Therefore, we next have to register an adapter that adapts from `IFile` to `ISize`. The first argument to the registry's `register()` method is a list of original interfaces.In our cause we have only one original interface, `IFile`. A list makes sense, since the interface package has the concept of multi-adapters, which are adapters that require multiple objects to adapt to a new interface. In these situations, your adapter constructor will require an argument for each specified interface. The second argument is the interface the adapter provides, in our case `ISize`. The third argument is the name of the adapter. Since we do not care about names, we simply leave it as an empty string. Names are commonly useful, if you have adapters for the same set of interfaces, but they are useful in different situations. The last argument is simply the adapter class:: >>> registry.register([IFile], ISize, '', FileSize) You can now use the the registry to lookup the adapter:: >>> registry.lookup1(IFile, ISize, '') Let's get a little bit more practical. Let's create a `File` instance and create the adapter using a registry lookup. Then we see whether the adapter returns the correct size by calling `getSize()`:: >>> file = File() >>> size = registry.lookup1(IFile, ISize, '')(file) >>> size.getSize() 7 However, this is not very practical, since I have to manually pass in the arguments to the lookup method. There is some syntactic candy that will allow us to get an adapter instance by simply calling `ISize(file)`. To make use of this functionality, we need to add our registry to the adapter_hooks list, which is a member of the adapters module. This list stores a collection of callables that are automatically invoked when IFoo(obj) is called; their purpose is to locate adapters that implement an interface for a certain context instance. You are required to implement your own adapter hook; this example covers one of the simplest hooks that use the registry, but you could implement one that used an adapter cache or persistent adapters, for instance. The helper hook is required to expect as first argument the desired output interface (for us `ISize`) and as the second argument the context of the adapter (here `file`). The function returns an adapter, i.e. a `FileSize` instance:: >>> def hook(provided, object): ... adapter = registry.lookup1(zope.interface.providedBy(object), ... provided, '') ... return adapter(object) ... We now just add the hook to an `adapter_hooks` list:: >>> from zope.interface.interface import adapter_hooks >>> adapter_hooks.append(hook) Once the hook is registered, you can use the desired syntax:: >>> size = ISize(file) >>> size.getSize() 7 Now we have to cleanup after ourselves, so that others after us have a clean `adapter_hooks` list:: >>> adapter_hooks.remove(hook) That's it. I have intentionally left out a discussion of named adapters and multi-adapters, since this text is intended as a practical and simple introduction to Zope 3 interfaces and adapters. You might want to read the `adapter.txt` in the `zope.interface` package for a more formal, referencial and complete treatment of the package. Warning: People have reported that `adapter.txt` makes their brain feel soft! ``zope.interface Changelog`` ============================ 3.6.7 (2011-08-20) ------------------ - Fix sporadic failures on x86-64 platforms in tests of rich comparisons of interfaces. 3.6.6 (2011-08-13) ------------------ - LP #570942: Now correctly compare interfaces from different modules but with the same names. N.B.: This is a less intrusive / destabilizing fix than the one applied in 3.6.3: we only fix the underlying cmp-alike function, rather than adding the other "rich comparison" functions. - Revert to software as released with 3.6.1 for "stable" 3.6 release branch. 3.6.5 (2011-08-11) ------------------ - LP #811792: work around buggy behavior in some subclasses of ``zope.interface.interface.InterfaceClass``, which invoke ``__hash__`` before initializing ``__module__`` and ``__name__``. The workaround returns a fixed constant hash in such cases, and issues a ``UserWarning``. - LP #804832: Under PyPy, ``zope.interface`` should not build its C extension. Also, prevent attempting to build it under Jython. - Add a tox.ini for easier xplatform testing. - Fix testing deprecation warnings issued when tested under Py3K. 3.6.4 (2011-07-04) ------------------ - LP 804951: InterfaceClass instances were unhashable under Python 3.x. 3.6.3 (2011-05-26) ------------------ - LP #570942: Now correctly compare interfaces from different modules but with the same names. 3.6.2 (2011-05-17) ------------------ - Moved detailed documentation out-of-line from PyPI page, linking instead to http://docs.zope.org/zope.interface . - Fixes for small issues when running tests under Python 3.2 using ``zope.testrunner``. - LP # 675064: Specify return value type for C optimizations module init under Python 3: undeclared value caused warnings, and segfaults on some 64 bit architectures. - setup.py now raises RuntimeError if you don't have Distutils installed when running under Python 3. 3.6.1 (2010-05-03) ------------------ - A non-ASCII character in the changelog made 3.6.0 uninstallable on Python 3 systems with another default encoding than UTF-8. - Fixed compiler warnings under GCC 4.3.3. 3.6.0 (2010-04-29) ------------------ - LP #185974: Clear the cache used by ``Specificaton.get`` inside ``Specification.changed``. Thanks to Jacob Holm for the patch. - Added support for Python 3.1. Contributors: Lennart Regebro Martin v Loewis Thomas Lotze Wolfgang Schnerring The 3.1 support is completely backwards compatible. However, the implements syntax used under Python 2.X does not work under 3.X, since it depends on how metaclasses are implemented and this has changed. Instead it now supports a decorator syntax (also under Python 2.X):: class Foo: implements(IFoo) ... can now also be written:: @implementor(IFoo): class Foo: ... There are 2to3 fixers available to do this change automatically in the zope.fixers package. - Python 2.3 is no longer supported. 3.5.4 (2009-12-23) ------------------ - Use the standard Python doctest module instead of zope.testing.doctest, which has been deprecated. 3.5.3 (2009-12-08) ------------------ - Fix an edge case: make providedBy() work when a class has '__provides__' in its __slots__ (see http://thread.gmane.org/gmane.comp.web.zope.devel/22490) 3.5.2 (2009-07-01) ------------------ - BaseAdapterRegistry.unregister, unsubscribe: Remove empty portions of the data structures when something is removed. This avoids leaving references to global objects (interfaces) that may be slated for removal from the calling application. 3.5.1 (2009-03-18) ------------------ - verifyObject: use getattr instead of hasattr to test for object attributes in order to let exceptions other than AttributeError raised by properties propagate to the caller - Add Sphinx-based documentation building to the package buildout configuration. Use the ``bin/docs`` command after buildout. - Improve package description a bit. Unify changelog entries formatting. - Change package's mailing list address to zope-dev at zope.org as zope3-dev at zope.org is now retired. 3.5.0 (2008-10-26) ------------------ - Fixed declaration of _zope_interface_coptimizations, it's not a top level package. - Add a DocTestSuite for odd.py module, so their tests are run. - Allow to bootstrap on Jython. - Fix https://bugs.launchpad.net/zope3/3.3/+bug/98388: ISpecification was missing a declaration for __iro__. - Added optional code optimizations support, which allows the building of C code optimizations to fail (Jython). - Replaced `_flatten` with a non-recursive implementation, effectively making it 3x faster. 3.4.1 (2007-10-02) ------------------ - Fixed a setup bug that prevented installation from source on systems without setuptools. 3.4.0 (2007-07-19) ------------------ - Final release for 3.4.0. 3.4.0b3 (2007-05-22) -------------------- - Objects with picky custom comparison methods couldn't be added to component registries. Now, when checking whether an object is already registered, identity comparison is used. 3.3.0.1 (2007-01-03) -------------------- - Made a reference to OverflowWarning, which disappeared in Python 2.5, conditional. 3.3.0 (2007/01/03) ------------------ New Features ++++++++++++ - The adapter-lookup algorithim was refactored to make it much simpler and faster. Also, more of the adapter-lookup logic is implemented in C, making debugging of application code easier, since there is less infrastructre code to step through. - We now treat objects without interface declarations as if they declared that they provide zope.interface.Interface. - There are a number of richer new adapter-registration interfaces that provide greater control and introspection. - Added a new interface decorator to zope.interface that allows the setting of tagged values on an interface at definition time (see zope.interface.taggedValue). Bug Fixes +++++++++ - A bug in multi-adapter lookup sometimes caused incorrect adapters to be returned. 3.2.0.2 (2006-04-15) -------------------- - Fix packaging bug: 'package_dir' must be a *relative* path. 3.2.0.1 (2006-04-14) -------------------- - Packaging change: suppress inclusion of 'setup.cfg' in 'sdist' builds. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope 3.2.0 release. 3.1.0 (2005-10-03) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope 3.1.0 release. - Made attribute resolution order consistent with component lookup order, i.e. new-style class MRO semantics. - Deprecated 'isImplementedBy' and 'isImplementedByInstancesOf' APIs in favor of 'implementedBy' and 'providedBy'. 3.0.1 (2005-07-27) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope X3.0.1 release. - Fixed a bug reported by James Knight, which caused adapter registries to fail occasionally to reflect declaration changes. 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope X3.0.0 release. Download ******** Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Topic :: Software Development :: Libraries :: Python Modules zope2.13-2.13.21/source/zope.interface/pip-egg-info/zope.interface.egg-info/dependency_links.txt0000644000175000017500000000000112214017572031311 0ustar arnauarnau zope2.13-2.13.21/source/zope.interface/pip-egg-info/zope.interface.egg-info/requires.txt0000644000175000017500000000004712214017572027644 0ustar arnauarnausetuptools [docs] z3c.recipe.sphinxdoczope2.13-2.13.21/source/zope.interface/pip-egg-info/zope.interface.egg-info/namespace_packages.txt0000644000175000017500000000000512214017572031571 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.interface/pip-egg-info/zope.interface.egg-info/top_level.txt0000644000175000017500000000000512214017572027770 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.interface/pip-egg-info/zope.interface.egg-info/SOURCES.txt0000644000175000017500000000266212214017572027135 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.interface.egg-info/PKG-INFO pip-egg-info/zope.interface.egg-info/SOURCES.txt pip-egg-info/zope.interface.egg-info/dependency_links.txt pip-egg-info/zope.interface.egg-info/namespace_packages.txt pip-egg-info/zope.interface.egg-info/not-zip-safe pip-egg-info/zope.interface.egg-info/requires.txt pip-egg-info/zope.interface.egg-info/top_level.txt src/zope/__init__.py src/zope/interface/__init__.py src/zope/interface/_flatten.py src/zope/interface/_zope_interface_coptimizations.c src/zope/interface/adapter.py src/zope/interface/advice.py src/zope/interface/declarations.py src/zope/interface/document.py src/zope/interface/exceptions.py src/zope/interface/interface.py src/zope/interface/interfaces.py src/zope/interface/ro.py src/zope/interface/verify.py src/zope/interface/tests/__init__.py src/zope/interface/tests/dummy.py src/zope/interface/tests/ifoo.py src/zope/interface/tests/ifoo_other.py src/zope/interface/tests/m1.py src/zope/interface/tests/m2.py src/zope/interface/tests/odd.py src/zope/interface/tests/test_adapter.py src/zope/interface/tests/test_advice.py src/zope/interface/tests/test_declarations.py src/zope/interface/tests/test_document.py src/zope/interface/tests/test_element.py src/zope/interface/tests/test_interface.py src/zope/interface/tests/test_odd_declarations.py src/zope/interface/tests/test_sorting.py src/zope/interface/tests/test_verify.py src/zope/interface/tests/unitfixtures.pyzope2.13-2.13.21/source/zope.interface/pip-egg-info/zope.interface.egg-info/not-zip-safe0000644000175000017500000000000112214017572027471 0ustar arnauarnau zope2.13-2.13.21/source/zope.interface/LICENSE.txt0000644000175000017500000000402612214017572020201 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.interface/README.txt0000644000175000017500000000100412214017572020045 0ustar arnauarnau*This package is intended to be independently reusable in any Python project. It is maintained by the* `Zope Toolkit project `_. This package provides an implementation of `object interfaces` for Python. Interfaces are a mechanism for labeling objects as conforming to a given API or contract. So, this package can be considered as implementation of the `Design By Contract`_ methodology support in Python. .. _Design By Contract: http://en.wikipedia.org/wiki/Design_by_contract zope2.13-2.13.21/source/zope.interface/setup.cfg0000644000175000017500000000007312214017572020175 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.interface/build_ext_2.pyc0000644000175000017500000000277012214017572021277 0ustar arnauarnauó z0Rc@szddlZddlmZmZmZyddlmZWn!ek r_ddlmZnXdefd„ƒYZ dS(iÿÿÿÿN(tCCompilerErrortDistutilsExecErrortDistutilsPlatformError(t build_exttoptional_build_extcBs)eZdZd„Zd„Zd„ZRS(s\This class subclasses build_ext and allows the building of C extensions to fail. cCs8ytj|ƒWn tk r3}|j|ƒnXdS(N(RtrunRt _unavailable(tselfte((sbuild_ext_2.pyRscCsAytj||ƒWn&ttfk r<}|j|ƒnXdS(N(Rtbuild_extensionRRR(RtextR((sbuild_ext_2.pyR scCsGtjddIJtjdIJtjJtj|IJtjddIJdS(Nt*iPs“WARNING: An optional code optimization (C extension) could not be compiled. Optimizations for this package will not be available!(tsyststderr(RR((sbuild_ext_2.pyRs  (t__name__t __module__t__doc__RR R(((sbuild_ext_2.pyR s  ( R tdistutils.errorsRRRtsetuptools.command.build_extRt ImportErrortdistutils.command.build_extR(((sbuild_ext_2.pyts  zope2.13-2.13.21/source/zope.interface/build_ext_3.py0000644000175000017500000000235612214017572021135 0ustar arnauarnauimport os import sys from distutils.errors import (CCompilerError, DistutilsExecError, DistutilsPlatformError) try: from setuptools.command.build_ext import build_ext from pkg_resources import (normalize_path, working_set, add_activation_listener, require) except ImportError: from distutils.command.build_ext import build_ext class optional_build_ext(build_ext): """This class subclasses build_ext and allows the building of C extensions to fail. """ def run(self): try: build_ext.run(self) except DistutilsPlatformError as e: self._unavailable(e) def build_extension(self, ext): try: build_ext.build_extension(self, ext) except (CCompilerError, DistutilsExecError) as e: self._unavailable(e) def _unavailable(self, e): print('*' * 80, file=sys.stderr) print("""WARNING: An optional code optimization (C extension) could not be compiled. Optimizations for this package will not be available!""", file=sys.stderr) print(file=sys.stderr) print(e, file=sys.stderr) print('*' * 80, file=sys.stderr) zope2.13-2.13.21/source/zope.interface/COPYRIGHT.txt0000644000175000017500000000004012214017572020457 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.interface/buildout.cfg0000644000175000017500000000047612214017572020673 0ustar arnauarnau[buildout] develop = . parts = test python docs [test] recipe = zc.recipe.testrunner eggs = zope.interface [python] recipe = zc.recipe.egg eggs = zope.interface interpreter = python [docs] recipe = z3c.recipe.sphinxdoc eggs = zope.interface [docs] build-dir = ${buildout:directory}/docs default.css = layout.html = zope2.13-2.13.21/source/zope.interface/bootstrap.py0000644000175000017500000000416712214017572020753 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 72703 2007-02-20 11:49:26Z jim $ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources is_jython = sys.platform.startswith('java') if is_jython: import subprocess cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set if is_jython: assert subprocess.Popen([sys.executable] + ['-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout'], env = dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.interface/CHANGES.txt0000644000175000017500000001620712214017572020173 0ustar arnauarnau``zope.interface Changelog`` ============================ 3.6.7 (2011-08-20) ------------------ - Fix sporadic failures on x86-64 platforms in tests of rich comparisons of interfaces. 3.6.6 (2011-08-13) ------------------ - LP #570942: Now correctly compare interfaces from different modules but with the same names. N.B.: This is a less intrusive / destabilizing fix than the one applied in 3.6.3: we only fix the underlying cmp-alike function, rather than adding the other "rich comparison" functions. - Revert to software as released with 3.6.1 for "stable" 3.6 release branch. 3.6.5 (2011-08-11) ------------------ - LP #811792: work around buggy behavior in some subclasses of ``zope.interface.interface.InterfaceClass``, which invoke ``__hash__`` before initializing ``__module__`` and ``__name__``. The workaround returns a fixed constant hash in such cases, and issues a ``UserWarning``. - LP #804832: Under PyPy, ``zope.interface`` should not build its C extension. Also, prevent attempting to build it under Jython. - Add a tox.ini for easier xplatform testing. - Fix testing deprecation warnings issued when tested under Py3K. 3.6.4 (2011-07-04) ------------------ - LP 804951: InterfaceClass instances were unhashable under Python 3.x. 3.6.3 (2011-05-26) ------------------ - LP #570942: Now correctly compare interfaces from different modules but with the same names. 3.6.2 (2011-05-17) ------------------ - Moved detailed documentation out-of-line from PyPI page, linking instead to http://docs.zope.org/zope.interface . - Fixes for small issues when running tests under Python 3.2 using ``zope.testrunner``. - LP # 675064: Specify return value type for C optimizations module init under Python 3: undeclared value caused warnings, and segfaults on some 64 bit architectures. - setup.py now raises RuntimeError if you don't have Distutils installed when running under Python 3. 3.6.1 (2010-05-03) ------------------ - A non-ASCII character in the changelog made 3.6.0 uninstallable on Python 3 systems with another default encoding than UTF-8. - Fixed compiler warnings under GCC 4.3.3. 3.6.0 (2010-04-29) ------------------ - LP #185974: Clear the cache used by ``Specificaton.get`` inside ``Specification.changed``. Thanks to Jacob Holm for the patch. - Added support for Python 3.1. Contributors: Lennart Regebro Martin v Loewis Thomas Lotze Wolfgang Schnerring The 3.1 support is completely backwards compatible. However, the implements syntax used under Python 2.X does not work under 3.X, since it depends on how metaclasses are implemented and this has changed. Instead it now supports a decorator syntax (also under Python 2.X):: class Foo: implements(IFoo) ... can now also be written:: @implementor(IFoo): class Foo: ... There are 2to3 fixers available to do this change automatically in the zope.fixers package. - Python 2.3 is no longer supported. 3.5.4 (2009-12-23) ------------------ - Use the standard Python doctest module instead of zope.testing.doctest, which has been deprecated. 3.5.3 (2009-12-08) ------------------ - Fix an edge case: make providedBy() work when a class has '__provides__' in its __slots__ (see http://thread.gmane.org/gmane.comp.web.zope.devel/22490) 3.5.2 (2009-07-01) ------------------ - BaseAdapterRegistry.unregister, unsubscribe: Remove empty portions of the data structures when something is removed. This avoids leaving references to global objects (interfaces) that may be slated for removal from the calling application. 3.5.1 (2009-03-18) ------------------ - verifyObject: use getattr instead of hasattr to test for object attributes in order to let exceptions other than AttributeError raised by properties propagate to the caller - Add Sphinx-based documentation building to the package buildout configuration. Use the ``bin/docs`` command after buildout. - Improve package description a bit. Unify changelog entries formatting. - Change package's mailing list address to zope-dev at zope.org as zope3-dev at zope.org is now retired. 3.5.0 (2008-10-26) ------------------ - Fixed declaration of _zope_interface_coptimizations, it's not a top level package. - Add a DocTestSuite for odd.py module, so their tests are run. - Allow to bootstrap on Jython. - Fix https://bugs.launchpad.net/zope3/3.3/+bug/98388: ISpecification was missing a declaration for __iro__. - Added optional code optimizations support, which allows the building of C code optimizations to fail (Jython). - Replaced `_flatten` with a non-recursive implementation, effectively making it 3x faster. 3.4.1 (2007-10-02) ------------------ - Fixed a setup bug that prevented installation from source on systems without setuptools. 3.4.0 (2007-07-19) ------------------ - Final release for 3.4.0. 3.4.0b3 (2007-05-22) -------------------- - Objects with picky custom comparison methods couldn't be added to component registries. Now, when checking whether an object is already registered, identity comparison is used. 3.3.0.1 (2007-01-03) -------------------- - Made a reference to OverflowWarning, which disappeared in Python 2.5, conditional. 3.3.0 (2007/01/03) ------------------ New Features ++++++++++++ - The adapter-lookup algorithim was refactored to make it much simpler and faster. Also, more of the adapter-lookup logic is implemented in C, making debugging of application code easier, since there is less infrastructre code to step through. - We now treat objects without interface declarations as if they declared that they provide zope.interface.Interface. - There are a number of richer new adapter-registration interfaces that provide greater control and introspection. - Added a new interface decorator to zope.interface that allows the setting of tagged values on an interface at definition time (see zope.interface.taggedValue). Bug Fixes +++++++++ - A bug in multi-adapter lookup sometimes caused incorrect adapters to be returned. 3.2.0.2 (2006-04-15) -------------------- - Fix packaging bug: 'package_dir' must be a *relative* path. 3.2.0.1 (2006-04-14) -------------------- - Packaging change: suppress inclusion of 'setup.cfg' in 'sdist' builds. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope 3.2.0 release. 3.1.0 (2005-10-03) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope 3.1.0 release. - Made attribute resolution order consistent with component lookup order, i.e. new-style class MRO semantics. - Deprecated 'isImplementedBy' and 'isImplementedByInstancesOf' APIs in favor of 'implementedBy' and 'providedBy'. 3.0.1 (2005-07-27) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope X3.0.1 release. - Fixed a bug reported by James Knight, which caused adapter registries to fail occasionally to reflect declaration changes. 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope X3.0.0 release. zope2.13-2.13.21/source/zope.interface/src/0000755000175000017500000000000012214017572017143 5ustar arnauarnauzope2.13-2.13.21/source/zope.interface/src/zope/0000755000175000017500000000000012214017572020120 5ustar arnauarnauzope2.13-2.13.21/source/zope.interface/src/zope/__init__.py0000644000175000017500000000031012214017572022223 0ustar arnauarnau# this is a namespace package try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: import pkgutil __path__ = pkgutil.extend_path(__path__, __name__) zope2.13-2.13.21/source/zope.interface/src/zope/interface/0000755000175000017500000000000012214017572022060 5ustar arnauarnauzope2.13-2.13.21/source/zope.interface/src/zope/interface/_flatten.py0000644000175000017500000000213012214017572024222 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Adapter-style interface registry See Adapter class. $Id: _flatten.py 110536 2010-04-06 02:59:44Z tseaver $ """ from zope.interface import Declaration def _flatten(implements, include_None=0): try: r = implements.flattened() except AttributeError: if implements is None: r=() else: r = Declaration(implements).flattened() if not include_None: return r r = list(r) r.append(None) return r zope2.13-2.13.21/source/zope.interface/src/zope/interface/interface.py0000644000175000017500000005566512214017572024413 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interface object implementation """ from __future__ import generators import sys from types import FunctionType import weakref from zope.interface.exceptions import Invalid from zope.interface.ro import ro CO_VARARGS = 4 CO_VARKEYWORDS = 8 TAGGED_DATA = '__interface_tagged_values__' _decorator_non_return = object() def invariant(call): f_locals = sys._getframe(1).f_locals tags = f_locals.setdefault(TAGGED_DATA, {}) invariants = tags.setdefault('invariants', []) invariants.append(call) return _decorator_non_return def taggedValue(key, value): """Attaches a tagged value to an interface at definition time.""" f_locals = sys._getframe(1).f_locals tagged_values = f_locals.setdefault(TAGGED_DATA, {}) tagged_values[key] = value return _decorator_non_return class Element(object): # We can't say this yet because we don't have enough # infrastructure in place. # #implements(IElement) def __init__(self, __name__, __doc__=''): """Create an 'attribute' description """ if not __doc__ and __name__.find(' ') >= 0: __doc__ = __name__ __name__ = None self.__name__=__name__ self.__doc__=__doc__ self.__tagged_values = {} def getName(self): """ Returns the name of the object. """ return self.__name__ def getDoc(self): """ Returns the documentation for the object. """ return self.__doc__ def getTaggedValue(self, tag): """ Returns the value associated with 'tag'. """ return self.__tagged_values[tag] def queryTaggedValue(self, tag, default=None): """ Returns the value associated with 'tag'. """ return self.__tagged_values.get(tag, default) def getTaggedValueTags(self): """ Returns a list of all tags. """ return self.__tagged_values.keys() def setTaggedValue(self, tag, value): """ Associates 'value' with 'key'. """ self.__tagged_values[tag] = value class SpecificationBasePy(object): def providedBy(self, ob): """Is the interface implemented by an object >>> from zope.interface import * >>> class I1(Interface): ... pass >>> class C(object): ... implements(I1) >>> c = C() >>> class X(object): ... pass >>> x = X() >>> I1.providedBy(x) False >>> I1.providedBy(C) False >>> I1.providedBy(c) True >>> directlyProvides(x, I1) >>> I1.providedBy(x) True >>> directlyProvides(C, I1) >>> I1.providedBy(C) True """ spec = providedBy(ob) return self in spec._implied def implementedBy(self, cls): """Test whether the specification is implemented by a class or factory. Raise TypeError if argument is neither a class nor a callable.""" spec = implementedBy(cls) return self in spec._implied def isOrExtends(self, interface): """Is the interface the same as or extend the given interface Examples:: >>> from zope.interface import Interface >>> from zope.interface.declarations import Declaration >>> class I1(Interface): pass ... >>> class I2(I1): pass ... >>> class I3(Interface): pass ... >>> class I4(I3): pass ... >>> spec = Declaration() >>> int(spec.extends(Interface)) 1 >>> spec = Declaration(I2) >>> int(spec.extends(Interface)) 1 >>> int(spec.extends(I1)) 1 >>> int(spec.extends(I2)) 1 >>> int(spec.extends(I3)) 0 >>> int(spec.extends(I4)) 0 """ return interface in self._implied __call__ = isOrExtends SpecificationBase = SpecificationBasePy try: from _zope_interface_coptimizations import SpecificationBase except ImportError: pass _marker = object() class InterfaceBasePy(object): """Base class that wants to be replaced with a C base :) """ def __call__(self, obj, alternate=_marker): """Adapt an object to the interface """ conform = getattr(obj, '__conform__', None) if conform is not None: adapter = self._call_conform(conform) if adapter is not None: return adapter adapter = self.__adapt__(obj) if adapter is not None: return adapter elif alternate is not _marker: return alternate else: raise TypeError("Could not adapt", obj, self) def __adapt__(self, obj): """Adapt an object to the reciever """ if self.providedBy(obj): return obj for hook in adapter_hooks: adapter = hook(self, obj) if adapter is not None: return adapter InterfaceBase = InterfaceBasePy try: from _zope_interface_coptimizations import InterfaceBase except ImportError: pass adapter_hooks = [] try: from _zope_interface_coptimizations import adapter_hooks except ImportError: pass class Specification(SpecificationBase): """Specifications An interface specification is used to track interface declarations and component registrations. This class is a base class for both interfaces themselves and for interface specifications (declarations). Specifications are mutable. If you reassign their cases, their relations with other specifications are adjusted accordingly. For example: >>> from zope.interface import Interface >>> class I1(Interface): ... pass >>> class I2(I1): ... pass >>> class I3(I2): ... pass >>> [i.__name__ for i in I1.__bases__] ['Interface'] >>> [i.__name__ for i in I2.__bases__] ['I1'] >>> I3.extends(I1) 1 >>> I2.__bases__ = (Interface, ) >>> [i.__name__ for i in I2.__bases__] ['Interface'] >>> I3.extends(I1) 0 """ # Copy some base class methods for speed isOrExtends = SpecificationBase.isOrExtends providedBy = SpecificationBase.providedBy def __init__(self, bases=()): self._implied = {} self.dependents = weakref.WeakKeyDictionary() self.__bases__ = tuple(bases) def subscribe(self, dependent): self.dependents[dependent] = self.dependents.get(dependent, 0) + 1 def unsubscribe(self, dependent): n = self.dependents.get(dependent, 0) - 1 if not n: del self.dependents[dependent] elif n > 0: self.dependents[dependent] = n else: raise KeyError(dependent) def __setBases(self, bases): # Register ourselves as a dependent of our old bases for b in self.__bases__: b.unsubscribe(self) # Register ourselves as a dependent of our bases self.__dict__['__bases__'] = bases for b in bases: b.subscribe(self) self.changed(self) __bases__ = property( lambda self: self.__dict__.get('__bases__', ()), __setBases, ) def changed(self, originally_changed): """We, or something we depend on, have changed """ try: del self._v_attrs except AttributeError: pass implied = self._implied implied.clear() ancestors = ro(self) try: if Interface not in ancestors: ancestors.append(Interface) except NameError: pass # defining Interface itself self.__sro__ = tuple(ancestors) self.__iro__ = tuple([ancestor for ancestor in ancestors if isinstance(ancestor, InterfaceClass) ]) for ancestor in ancestors: # We directly imply our ancestors: implied[ancestor] = () # Now, advise our dependents of change: for dependent in self.dependents.keys(): dependent.changed(originally_changed) def interfaces(self): """Return an iterator for the interfaces in the specification for example:: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(I1): pass ... >>> class I3(Interface): pass ... >>> class I4(I3): pass ... >>> spec = Specification((I2, I3)) >>> spec = Specification((I4, spec)) >>> i = spec.interfaces() >>> [x.getName() for x in i] ['I4', 'I2', 'I3'] >>> list(i) [] """ seen = {} for base in self.__bases__: for interface in base.interfaces(): if interface not in seen: seen[interface] = 1 yield interface def extends(self, interface, strict=True): """Does the specification extend the given interface? Test whether an interface in the specification extends the given interface Examples:: >>> from zope.interface import Interface >>> from zope.interface.declarations import Declaration >>> class I1(Interface): pass ... >>> class I2(I1): pass ... >>> class I3(Interface): pass ... >>> class I4(I3): pass ... >>> spec = Declaration() >>> int(spec.extends(Interface)) 1 >>> spec = Declaration(I2) >>> int(spec.extends(Interface)) 1 >>> int(spec.extends(I1)) 1 >>> int(spec.extends(I2)) 1 >>> int(spec.extends(I3)) 0 >>> int(spec.extends(I4)) 0 >>> I2.extends(I2) 0 >>> I2.extends(I2, False) 1 >>> I2.extends(I2, strict=False) 1 """ return ((interface in self._implied) and ((not strict) or (self != interface)) ) def weakref(self, callback=None): return weakref.ref(self, callback) def get(self, name, default=None): """Query for an attribute description """ try: attrs = self._v_attrs except AttributeError: attrs = self._v_attrs = {} attr = attrs.get(name) if attr is None: for iface in self.__iro__: attr = iface.direct(name) if attr is not None: attrs[name] = attr break if attr is None: return default else: return attr class InterfaceClass(Element, InterfaceBase, Specification): """Prototype (scarecrow) Interfaces Implementation.""" # We can't say this yet because we don't have enough # infrastructure in place. # #implements(IInterface) def __init__(self, name, bases=(), attrs=None, __doc__=None, __module__=None): if attrs is None: attrs = {} if __module__ is None: __module__ = attrs.get('__module__') if isinstance(__module__, str): del attrs['__module__'] else: try: # Figure out what module defined the interface. # This is how cPython figures out the module of # a class, but of course it does it in C. :-/ __module__ = sys._getframe(1).f_globals['__name__'] except (AttributeError, KeyError): pass self.__module__ = __module__ d = attrs.get('__doc__') if d is not None: if not isinstance(d, Attribute): if __doc__ is None: __doc__ = d del attrs['__doc__'] if __doc__ is None: __doc__ = '' Element.__init__(self, name, __doc__) tagged_data = attrs.pop(TAGGED_DATA, None) if tagged_data is not None: for key, val in tagged_data.items(): self.setTaggedValue(key, val) for base in bases: if not isinstance(base, InterfaceClass): raise TypeError('Expected base interfaces') Specification.__init__(self, bases) # Make sure that all recorded attributes (and methods) are of type # `Attribute` and `Method` for name, attr in attrs.items(): if name == '__locals__': # This happens under Python 3 sometimes, not sure why. /regebro continue if isinstance(attr, Attribute): attr.interface = self if not attr.__name__: attr.__name__ = name elif isinstance(attr, FunctionType): attrs[name] = fromFunction(attr, self, name=name) elif attr is _decorator_non_return: del attrs[name] else: raise InvalidInterface("Concrete attribute, " + name) self.__attrs = attrs self.__identifier__ = "%s.%s" % (self.__module__, self.__name__) def interfaces(self): """Return an iterator for the interfaces in the specification for example:: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> >>> i = I1.interfaces() >>> [x.getName() for x in i] ['I1'] >>> list(i) [] """ yield self def getBases(self): return self.__bases__ def isEqualOrExtendedBy(self, other): """Same interface or extends?""" return self == other or other.extends(self) def names(self, all=False): """Return the attribute names defined by the interface.""" if not all: return self.__attrs.keys() r = self.__attrs.copy() for base in self.__bases__: r.update(dict.fromkeys(base.names(all))) return r.keys() def __iter__(self): return iter(self.names(all=True)) def namesAndDescriptions(self, all=False): """Return attribute names and descriptions defined by interface.""" if not all: return self.__attrs.items() r = {} for base in self.__bases__[::-1]: r.update(dict(base.namesAndDescriptions(all))) r.update(self.__attrs) return r.items() def getDescriptionFor(self, name): """Return the attribute description for the given name.""" r = self.get(name) if r is not None: return r raise KeyError(name) __getitem__ = getDescriptionFor def __contains__(self, name): return self.get(name) is not None def direct(self, name): return self.__attrs.get(name) def queryDescriptionFor(self, name, default=None): return self.get(name, default) def deferred(self): """Return a defered class corresponding to the interface.""" if hasattr(self, "_deferred"): return self._deferred klass={} exec "class %s: pass" % self.__name__ in klass klass=klass[self.__name__] self.__d(klass) self._deferred=klass return klass def validateInvariants(self, obj, errors=None): """validate object to defined invariants.""" for call in self.queryTaggedValue('invariants', []): try: call(obj) except Invalid, e: if errors is None: raise else: errors.append(e) for base in self.__bases__: try: base.validateInvariants(obj, errors) except Invalid: if errors is None: raise if errors: raise Invalid(errors) def _getInterface(self, ob, name): """Retrieve a named interface.""" return None def __d(self, klass): for k, v in self.__attrs.items(): if isinstance(v, Method) and not (k in klass.__dict__): setattr(klass, k, v) for b in self.__bases__: b.__d(klass) def __repr__(self): try: return self._v_repr except AttributeError: name = self.__name__ m = self.__module__ if m: name = '%s.%s' % (m, name) r = "<%s %s>" % (self.__class__.__name__, name) self._v_repr = r return r def _call_conform(self, conform): try: return conform(self) except TypeError: # We got a TypeError. It might be an error raised by # the __conform__ implementation, or *we* may have # made the TypeError by calling an unbound method # (object is a class). In the later case, we behave # as though there is no __conform__ method. We can # detect this case by checking whether there is more # than one traceback object in the traceback chain: if sys.exc_info()[2].tb_next is not None: # There is more than one entry in the chain, so # reraise the error: raise # This clever trick is from Phillip Eby return None def __reduce__(self): return self.__name__ def __cmp(self, o1, o2): # Yes, I did mean to name this __cmp, rather than __cmp__. # It is a private method used by __lt__ and __gt__. # I don't want to override __eq__ because I want the default # __eq__, which is really fast. """Make interfaces sortable TODO: It would ne nice if: More specific interfaces should sort before less specific ones. Otherwise, sort on name and module. But this is too complicated, and we're going to punt on it for now. For now, sort on interface and module name. None is treated as a pseudo interface that implies the loosest contact possible, no contract. For that reason, all interfaces sort before None. """ if o1 == o2: return 0 if o1 is None: return 1 if o2 is None: return -1 n1 = (getattr(o1, '__name__', ''), getattr(o1, '__module__', None)) n2 = (getattr(o2, '__name__', ''), getattr(o2, '__module__', None)) return (n1 > n2) - (n1 < n2) def __lt__(self, other): c = self.__cmp(self, other) #print '<', self, other, c < 0, c return c < 0 def __le__(self, other): c = self.__cmp(self, other) #print '<', self, other, c < 0, c return c <= 0 def __ge__(self, other): c = self.__cmp(self, other) #print '>', self, other, c > 0, c return c >= 0 def __gt__(self, other): c = self.__cmp(self, other) #print '>', self, other, c > 0, c return c > 0 Interface = InterfaceClass("Interface", __module__ = 'zope.interface') class Attribute(Element): """Attribute descriptions """ # We can't say this yet because we don't have enough # infrastructure in place. # # implements(IAttribute) interface = None class Method(Attribute): """Method interfaces The idea here is that you have objects that describe methods. This provides an opportunity for rich meta-data. """ # We can't say this yet because we don't have enough # infrastructure in place. # # implements(IMethod) def __call__(self, *args, **kw): raise BrokenImplementation(self.interface, self.__name__) def getSignatureInfo(self): return {'positional': self.positional, 'required': self.required, 'optional': self.optional, 'varargs': self.varargs, 'kwargs': self.kwargs, } def getSignatureString(self): sig = [] for v in self.positional: sig.append(v) if v in self.optional.keys(): sig[-1] += "=" + `self.optional[v]` if self.varargs: sig.append("*" + self.varargs) if self.kwargs: sig.append("**" + self.kwargs) return "(%s)" % ", ".join(sig) def fromFunction(func, interface=None, imlevel=0, name=None): name = name or func.__name__ method = Method(name, func.__doc__) defaults = func.func_defaults or () code = func.func_code # Number of positional arguments na = code.co_argcount-imlevel names = code.co_varnames[imlevel:] opt = {} # Number of required arguments nr = na-len(defaults) if nr < 0: defaults=defaults[-nr:] nr = 0 # Determine the optional arguments. opt.update(dict(zip(names[nr:], defaults))) method.positional = names[:na] method.required = names[:nr] method.optional = opt argno = na # Determine the function's variable argument's name (i.e. *args) if code.co_flags & CO_VARARGS: method.varargs = names[argno] argno = argno + 1 else: method.varargs = None # Determine the function's keyword argument's name (i.e. **kw) if code.co_flags & CO_VARKEYWORDS: method.kwargs = names[argno] else: method.kwargs = None method.interface = interface for key, value in func.__dict__.items(): method.setTaggedValue(key, value) return method def fromMethod(meth, interface=None, name=None): func = meth.im_func return fromFunction(func, interface, imlevel=1, name=name) # Now we can create the interesting interfaces and wire them up: def _wire(): from zope.interface.declarations import classImplements from zope.interface.interfaces import IAttribute classImplements(Attribute, IAttribute) from zope.interface.interfaces import IMethod classImplements(Method, IMethod) from zope.interface.interfaces import IInterface classImplements(InterfaceClass, IInterface) from zope.interface.interfaces import ISpecification classImplements(Specification, ISpecification) # We import this here to deal with module dependencies. from zope.interface.declarations import implementedBy from zope.interface.declarations import providedBy from zope.interface.exceptions import InvalidInterface from zope.interface.exceptions import BrokenImplementation zope2.13-2.13.21/source/zope.interface/src/zope/interface/advice.py0000644000175000017500000001611512214017572023671 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Class advice. This module was adapted from 'protocols.advice', part of the Python Enterprise Application Kit (PEAK). Please notify the PEAK authors (pje@telecommunity.com and tsarna@sarna.org) if bugs are found or Zope-specific changes are required, so that the PEAK version of this module can be kept in sync. PEAK is a Python application framework that interoperates with (but does not require) Zope 3 and Twisted. It provides tools for manipulating UML models, object-relational persistence, aspect-oriented programming, and more. Visit the PEAK home page at http://peak.telecommunity.com for more information. $Id: advice.py 110699 2010-04-09 08:16:17Z regebro $ """ from types import FunctionType try: from types import ClassType __python3 = False except ImportError: __python3 = True import sys def getFrameInfo(frame): """Return (kind,module,locals,globals) for a frame 'kind' is one of "exec", "module", "class", "function call", or "unknown". """ f_locals = frame.f_locals f_globals = frame.f_globals sameNamespace = f_locals is f_globals hasModule = '__module__' in f_locals hasName = '__name__' in f_globals sameName = hasModule and hasName sameName = sameName and f_globals['__name__']==f_locals['__module__'] module = hasName and sys.modules.get(f_globals['__name__']) or None namespaceIsModule = module and module.__dict__ is f_globals if not namespaceIsModule: # some kind of funky exec kind = "exec" elif sameNamespace and not hasModule: kind = "module" elif sameName and not sameNamespace: kind = "class" elif not sameNamespace: kind = "function call" else: # How can you have f_locals is f_globals, and have '__module__' set? # This is probably module-level code, but with a '__module__' variable. kind = "unknown" return kind, module, f_locals, f_globals def addClassAdvisor(callback, depth=2): """Set up 'callback' to be passed the containing class upon creation This function is designed to be called by an "advising" function executed in a class suite. The "advising" function supplies a callback that it wishes to have executed when the containing class is created. The callback will be given one argument: the newly created containing class. The return value of the callback will be used in place of the class, so the callback should return the input if it does not wish to replace the class. The optional 'depth' argument to this function determines the number of frames between this function and the targeted class suite. 'depth' defaults to 2, since this skips this function's frame and one calling function frame. If you use this function from a function called directly in the class suite, the default will be correct, otherwise you will need to determine the correct depth yourself. This function works by installing a special class factory function in place of the '__metaclass__' of the containing class. Therefore, only callbacks *after* the last '__metaclass__' assignment in the containing class will be executed. Be sure that classes using "advising" functions declare any '__metaclass__' *first*, to ensure all callbacks are run.""" frame = sys._getframe(depth) kind, module, caller_locals, caller_globals = getFrameInfo(frame) # This causes a problem when zope interfaces are used from doctest. # In these cases, kind == "exec". # #if kind != "class": # raise SyntaxError( # "Advice must be in the body of a class statement" # ) previousMetaclass = caller_locals.get('__metaclass__') if __python3: defaultMetaclass = caller_globals.get('__metaclass__', type) else: defaultMetaclass = caller_globals.get('__metaclass__', ClassType) def advise(name, bases, cdict): if '__metaclass__' in cdict: del cdict['__metaclass__'] if previousMetaclass is None: if bases: # find best metaclass or use global __metaclass__ if no bases meta = determineMetaclass(bases) else: meta = defaultMetaclass elif isClassAdvisor(previousMetaclass): # special case: we can't compute the "true" metaclass here, # so we need to invoke the previous metaclass and let it # figure it out for us (and apply its own advice in the process) meta = previousMetaclass else: meta = determineMetaclass(bases, previousMetaclass) newClass = meta(name,bases,cdict) # this lets the callback replace the class completely, if it wants to return callback(newClass) # introspection data only, not used by inner function advise.previousMetaclass = previousMetaclass advise.callback = callback # install the advisor caller_locals['__metaclass__'] = advise def isClassAdvisor(ob): """True if 'ob' is a class advisor function""" return isinstance(ob,FunctionType) and hasattr(ob,'previousMetaclass') def determineMetaclass(bases, explicit_mc=None): """Determine metaclass from 1+ bases and optional explicit __metaclass__""" meta = [getattr(b,'__class__',type(b)) for b in bases] if explicit_mc is not None: # The explicit metaclass needs to be verified for compatibility # as well, and allowed to resolve the incompatible bases, if any meta.append(explicit_mc) if len(meta)==1: # easy case return meta[0] candidates = minimalBases(meta) # minimal set of metaclasses if not candidates: # they're all "classic" classes assert(not __python3) # This should not happen under Python 3 return ClassType elif len(candidates)>1: # We could auto-combine, but for now we won't... raise TypeError("Incompatible metatypes",bases) # Just one, return it return candidates[0] def minimalBases(classes): """Reduce a list of base classes to its ordered minimum equivalent""" if not __python3: classes = [c for c in classes if c is not ClassType] candidates = [] for m in classes: for n in classes: if issubclass(n,m) and m is not n: break else: # m has no subclasses in 'classes' if m in candidates: candidates.remove(m) # ensure that we're later in the list candidates.append(m) return candidates zope2.13-2.13.21/source/zope.interface/src/zope/interface/adapter.txt0000644000175000017500000003425612214017572024253 0ustar arnauarnau================ Adapter Registry ================ Adapter registries provide a way to register objects that depend on one or more interface specifications and provide (perhaps indirectly) some interface. In addition, the registrations have names. (You can think of the names as qualifiers of the provided interfaces.) The term "interface specification" refers both to interfaces and to interface declarations, such as declarations of interfaces implemented by a class. Single Adapters =============== Let's look at a simple example, using a single required specification:: >>> from zope.interface.adapter import AdapterRegistry >>> import zope.interface >>> class IR1(zope.interface.Interface): ... pass >>> class IP1(zope.interface.Interface): ... pass >>> class IP2(IP1): ... pass >>> registry = AdapterRegistry() We'll register an object that depends on IR1 and "provides" IP2:: >>> registry.register([IR1], IP2, '', 12) Given the registration, we can look it up again:: >>> registry.lookup([IR1], IP2, '') 12 Note that we used an integer in the example. In real applications, one would use some objects that actually depend on or provide interfaces. The registry doesn't care about what gets registered, so we'll use integers and strings to keep the examples simple. There is one exception. Registering a value of None unregisters any previously-registered value. If an object depends on a specification, it can be looked up with a specification that extends the specification that it depends on:: >>> class IR2(IR1): ... pass >>> registry.lookup([IR2], IP2, '') 12 We can use a class implementation specification to look up the object:: >>> class C2: ... zope.interface.implements(IR2) >>> registry.lookup([zope.interface.implementedBy(C2)], IP2, '') 12 and it can be looked up for interfaces that its provided interface extends:: >>> registry.lookup([IR1], IP1, '') 12 >>> registry.lookup([IR2], IP1, '') 12 But if you require a specification that doesn't extend the specification the object depends on, you won't get anything:: >>> registry.lookup([zope.interface.Interface], IP1, '') By the way, you can pass a default value to lookup:: >>> registry.lookup([zope.interface.Interface], IP1, '', 42) 42 If you try to get an interface the object doesn't provide, you also won't get anything:: >>> class IP3(IP2): ... pass >>> registry.lookup([IR1], IP3, '') You also won't get anything if you use the wrong name:: >>> registry.lookup([IR1], IP1, 'bob') >>> registry.register([IR1], IP2, 'bob', "Bob's 12") >>> registry.lookup([IR1], IP1, 'bob') "Bob's 12" You can leave the name off when doing a lookup:: >>> registry.lookup([IR1], IP1) 12 If we register an object that provides IP1:: >>> registry.register([IR1], IP1, '', 11) then that object will be prefered over O(12):: >>> registry.lookup([IR1], IP1, '') 11 Also, if we register an object for IR2, then that will be prefered when using IR2:: >>> registry.register([IR2], IP1, '', 21) >>> registry.lookup([IR2], IP1, '') 21 Finding out what, if anything, is registered -------------------------------------------- We can ask if there is an adapter registered for a collection of interfaces. This is different than lookup, because it looks for an exact match. >>> print registry.registered([IR1], IP1) 11 >>> print registry.registered([IR1], IP2) 12 >>> print registry.registered([IR1], IP2, 'bob') Bob's 12 >>> print registry.registered([IR2], IP1) 21 >>> print registry.registered([IR2], IP2) None In the last example, None was returned because nothing was registered exactly for the given interfaces. lookup1 ------- Lookup of single adapters is common enough that there is a specialized version of lookup that takes a single required interface:: >>> registry.lookup1(IR2, IP1, '') 21 >>> registry.lookup1(IR2, IP1) 21 Actual Adaptation ----------------- The adapter registry is intended to support adaptation, where one object that implements an interface is adapted to another object that supports a different interface. The adapter registry supports the computation of adapters. In this case, we have to register adapter factories:: >>> class IR(zope.interface.Interface): ... pass >>> class X: ... zope.interface.implements(IR) >>> class Y: ... zope.interface.implements(IP1) ... def __init__(self, context): ... self.context = context >>> registry.register([IR], IP1, '', Y) In this case, we registered a class as the factory. Now we can call `queryAdapter` to get the adapted object:: >>> x = X() >>> y = registry.queryAdapter(x, IP1) >>> y.__class__.__name__ 'Y' >>> y.context is x True We can register and lookup by name too:: >>> class Y2(Y): ... pass >>> registry.register([IR], IP1, 'bob', Y2) >>> y = registry.queryAdapter(x, IP1, 'bob') >>> y.__class__.__name__ 'Y2' >>> y.context is x True When the adapter factory produces `None`, then this is treated as if no adapter has been found. This allows us to prevent adaptation (when desired) and let the adapter factory determine whether adaptation is possible based on the state of the object being adapted. >>> def factory(context): ... if context.name == 'object': ... return 'adapter' ... return None >>> class Object(object): ... zope.interface.implements(IR) ... name = 'object' >>> registry.register([IR], IP1, 'conditional', factory) >>> obj = Object() >>> registry.queryAdapter(obj, IP1, 'conditional') 'adapter' >>> obj.name = 'no object' >>> registry.queryAdapter(obj, IP1, 'conditional') is None True >>> registry.queryAdapter(obj, IP1, 'conditional', 'default') 'default' An alternate method that provides the same function as `queryAdapter()` is `adapter_hook()`:: >>> y = registry.adapter_hook(IP1, x) >>> y.__class__.__name__ 'Y' >>> y.context is x True >>> y = registry.adapter_hook(IP1, x, 'bob') >>> y.__class__.__name__ 'Y2' >>> y.context is x True The `adapter_hook()` simply switches the order of the object and interface arguments. It is used to hook into the interface call mechanism. Default Adapters ---------------- Sometimes, you want to provide an adapter that will adapt anything. For that, provide None as the required interface:: >>> registry.register([None], IP1, '', 1) then we can use that adapter for interfaces we don't have specific adapters for:: >>> class IQ(zope.interface.Interface): ... pass >>> registry.lookup([IQ], IP1, '') 1 Of course, specific adapters are still used when applicable:: >>> registry.lookup([IR2], IP1, '') 21 Class adapters -------------- You can register adapters for class declarations, which is almost the same as registering them for a class:: >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', 'C21') >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') 'C21' Dict adapters ------------- At some point it was impossible to register dictionary-based adapters due a bug. Let's make sure this works now: >>> adapter = {} >>> registry.register((), IQ, '', adapter) >>> registry.lookup((), IQ, '') is adapter True Unregistering ------------- You can unregister by registering None, rather than an object:: >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', None) >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') 21 Of course, this means that None can't be registered. This is an exception to the statement, made earlier, that the registry doesn't care what gets registered. Multi-adapters ============== You can adapt multiple specifications:: >>> registry.register([IR1, IQ], IP2, '', '1q2') >>> registry.lookup([IR1, IQ], IP2, '') '1q2' >>> registry.lookup([IR2, IQ], IP1, '') '1q2' >>> class IS(zope.interface.Interface): ... pass >>> registry.lookup([IR2, IS], IP1, '') >>> class IQ2(IQ): ... pass >>> registry.lookup([IR2, IQ2], IP1, '') '1q2' >>> registry.register([IR1, IQ2], IP2, '', '1q22') >>> registry.lookup([IR2, IQ2], IP1, '') '1q22' Multi-adaptation ---------------- You can adapt multiple objects:: >>> class Q: ... zope.interface.implements(IQ) As with single adapters, we register a factory, which is often a class:: >>> class IM(zope.interface.Interface): ... pass >>> class M: ... zope.interface.implements(IM) ... def __init__(self, x, q): ... self.x, self.q = x, q >>> registry.register([IR, IQ], IM, '', M) And then we can call `queryMultiAdapter` to compute an adapter:: >>> q = Q() >>> m = registry.queryMultiAdapter((x, q), IM) >>> m.__class__.__name__ 'M' >>> m.x is x and m.q is q True and, of course, we can use names:: >>> class M2(M): ... pass >>> registry.register([IR, IQ], IM, 'bob', M2) >>> m = registry.queryMultiAdapter((x, q), IM, 'bob') >>> m.__class__.__name__ 'M2' >>> m.x is x and m.q is q True Default Adapters ---------------- As with single adapters, you can define default adapters by specifying None for the *first* specification:: >>> registry.register([None, IQ], IP2, '', 'q2') >>> registry.lookup([IS, IQ], IP2, '') 'q2' Null Adapters ============= You can also adapt no specification:: >>> registry.register([], IP2, '', 2) >>> registry.lookup([], IP2, '') 2 >>> registry.lookup([], IP1, '') 2 Listing named adapters ---------------------- Adapters are named. Sometimes, it's useful to get all of the named adapters for given interfaces:: >>> adapters = list(registry.lookupAll([IR1], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', 11), (u'bob', "Bob's 12")] This works for multi-adapters too:: >>> registry.register([IR1, IQ2], IP2, 'bob', '1q2 for bob') >>> adapters = list(registry.lookupAll([IR2, IQ2], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', '1q22'), (u'bob', '1q2 for bob')] And even null adapters:: >>> registry.register([], IP2, 'bob', 3) >>> adapters = list(registry.lookupAll([], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', 2), (u'bob', 3)] Subscriptions ============= Normally, we want to look up an object that most-closely matches a specification. Sometimes, we want to get all of the objects that match some specification. We use subscriptions for this. We subscribe objects against specifications and then later find all of the subscribed objects:: >>> registry.subscribe([IR1], IP2, 'sub12 1') >>> registry.subscriptions([IR1], IP2) ['sub12 1'] Note that, unlike regular adapters, subscriptions are unnamed. You can have multiple subscribers for the same specification:: >>> registry.subscribe([IR1], IP2, 'sub12 2') >>> registry.subscriptions([IR1], IP2) ['sub12 1', 'sub12 2'] If subscribers are registered for the same required interfaces, they are returned in the order of definition. You can register subscribers for all specifications using None:: >>> registry.subscribe([None], IP1, 'sub_1') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2'] Note that the new subscriber is returned first. Subscribers defined for less general required interfaces are returned before subscribers for more general interfaces. Subscriptions may be combined over multiple compatible specifications:: >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2'] >>> registry.subscribe([IR1], IP1, 'sub11') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2', 'sub11'] >>> registry.subscribe([IR2], IP2, 'sub22') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2', 'sub11', 'sub22'] >>> registry.subscriptions([IR2], IP2) ['sub12 1', 'sub12 2', 'sub22'] Subscriptions can be on multiple specifications:: >>> registry.subscribe([IR1, IQ], IP2, 'sub1q2') >>> registry.subscriptions([IR1, IQ], IP2) ['sub1q2'] As with single subscriptions and non-subscription adapters, you can specify None for the first required interface, to specify a default:: >>> registry.subscribe([None, IQ], IP2, 'sub_q2') >>> registry.subscriptions([IS, IQ], IP2) ['sub_q2'] >>> registry.subscriptions([IR1, IQ], IP2) ['sub_q2', 'sub1q2'] You can have subscriptions that are indepenent of any specifications:: >>> list(registry.subscriptions([], IP1)) [] >>> registry.subscribe([], IP2, 'sub2') >>> registry.subscriptions([], IP1) ['sub2'] >>> registry.subscribe([], IP1, 'sub1') >>> registry.subscriptions([], IP1) ['sub2', 'sub1'] >>> registry.subscriptions([], IP2) ['sub2'] Unregistering subscribers ------------------------- We can unregister subscribers. When unregistering a subscriber, we can unregister a specific subscriber:: >>> registry.unsubscribe([IR1], IP1, 'sub11') >>> registry.subscriptions([IR1], IP1) ['sub_1', 'sub12 1', 'sub12 2'] If we don't specify a value, then all subscribers matching the given interfaces will be unsubscribed: >>> registry.unsubscribe([IR1], IP2) >>> registry.subscriptions([IR1], IP1) ['sub_1'] Subscription adapters --------------------- We normally register adapter factories, which then allow us to compute adapters, but with subscriptions, we get multiple adapters. Here's an example of multiple-object subscribers:: >>> registry.subscribe([IR, IQ], IM, M) >>> registry.subscribe([IR, IQ], IM, M2) >>> subscribers = registry.subscribers((x, q), IM) >>> len(subscribers) 2 >>> class_names = [s.__class__.__name__ for s in subscribers] >>> class_names.sort() >>> class_names ['M', 'M2'] >>> [(s.x is x and s.q is q) for s in subscribers] [True, True] adapter factory subcribers can't return None values:: >>> def M3(x, y): ... return None >>> registry.subscribe([IR, IQ], IM, M3) >>> subscribers = registry.subscribers((x, q), IM) >>> len(subscribers) 2 Handlers -------- A handler is a subscriber factory that doesn't produce any normal output. It returns None. A handler is unlike adapters in that it does all of its work when the factory is called. To register a handler, simply provide None as the provided interface:: >>> def handler(event): ... print 'handler', event >>> registry.subscribe([IR1], None, handler) >>> registry.subscriptions([IR1], None) == [handler] True zope2.13-2.13.21/source/zope.interface/src/zope/interface/human.txt0000644000175000017500000001417212214017572023736 0ustar arnauarnau========================== Using the Adapter Registry ========================== This is a small demonstration of the ``zope.interface`` package including its adapter registry. It is intended to provide a concrete but narrow example on how to use interfaces and adapters outside of Zope 3. First we have to import the interface package:: >>> import zope.interface We now develop an interface for our object, which is a simple file in this case. For now we simply support one attribute, the body, which contains the actual file contents:: >>> class IFile(zope.interface.Interface): ... ... body = zope.interface.Attribute('Contents of the file.') ... For statistical reasons we often want to know the size of a file. However, it would be clumsy to implement the size directly in the file object, since the size really represents meta-data. Thus we create another interface that provides the size of something:: >>> class ISize(zope.interface.Interface): ... ... def getSize(): ... 'Return the size of an object.' ... Now we need to implement the file. It is essential that the object states that it implements the `IFile` interface. We also provide a default body value (just to make things simpler for this example):: >>> class File(object): ... ... zope.interface.implements(IFile) ... body = 'foo bar' ... Next we implement an adapter that can provide the `ISize` interface given any object providing `IFile`. By convention we use `__used_for__` to specify the interface that we expect the adapted object to provide, in our case `IFile`. However, this attribute is not used for anything. If you have multiple interfaces for which an adapter is used, just specify the interfaces via a tuple. Again by convention, the constructor of an adapter takes one argument, the context. The context in this case is an instance of `File` (providing `IFile`) that is used to extract the size from. Also by convention the context is stored in an attribute named `context` on the adapter. The twisted community refers to the context as the `original` object. However, you may feel free to use a specific argument name, such as `file`:: >>> class FileSize(object): ... ... zope.interface.implements(ISize) ... __used_for__ = IFile ... ... def __init__(self, context): ... self.context = context ... ... def getSize(self): ... return len(self.context.body) ... Now that we have written our adapter, we have to register it with an adapter registry, so that it can be looked up when needed. There is no such thing as a global registry; thus we have to instantiate one for our example manually:: >>> from zope.interface.adapter import AdapterRegistry >>> registry = AdapterRegistry() The registry keeps a map of what adapters implement based on another interface, the object already provides. Therefore, we next have to register an adapter that adapts from `IFile` to `ISize`. The first argument to the registry's `register()` method is a list of original interfaces.In our cause we have only one original interface, `IFile`. A list makes sense, since the interface package has the concept of multi-adapters, which are adapters that require multiple objects to adapt to a new interface. In these situations, your adapter constructor will require an argument for each specified interface. The second argument is the interface the adapter provides, in our case `ISize`. The third argument is the name of the adapter. Since we do not care about names, we simply leave it as an empty string. Names are commonly useful, if you have adapters for the same set of interfaces, but they are useful in different situations. The last argument is simply the adapter class:: >>> registry.register([IFile], ISize, '', FileSize) You can now use the the registry to lookup the adapter:: >>> registry.lookup1(IFile, ISize, '') Let's get a little bit more practical. Let's create a `File` instance and create the adapter using a registry lookup. Then we see whether the adapter returns the correct size by calling `getSize()`:: >>> file = File() >>> size = registry.lookup1(IFile, ISize, '')(file) >>> size.getSize() 7 However, this is not very practical, since I have to manually pass in the arguments to the lookup method. There is some syntactic candy that will allow us to get an adapter instance by simply calling `ISize(file)`. To make use of this functionality, we need to add our registry to the adapter_hooks list, which is a member of the adapters module. This list stores a collection of callables that are automatically invoked when IFoo(obj) is called; their purpose is to locate adapters that implement an interface for a certain context instance. You are required to implement your own adapter hook; this example covers one of the simplest hooks that use the registry, but you could implement one that used an adapter cache or persistent adapters, for instance. The helper hook is required to expect as first argument the desired output interface (for us `ISize`) and as the second argument the context of the adapter (here `file`). The function returns an adapter, i.e. a `FileSize` instance:: >>> def hook(provided, object): ... adapter = registry.lookup1(zope.interface.providedBy(object), ... provided, '') ... return adapter(object) ... We now just add the hook to an `adapter_hooks` list:: >>> from zope.interface.interface import adapter_hooks >>> adapter_hooks.append(hook) Once the hook is registered, you can use the desired syntax:: >>> size = ISize(file) >>> size.getSize() 7 Now we have to cleanup after ourselves, so that others after us have a clean `adapter_hooks` list:: >>> adapter_hooks.remove(hook) That's it. I have intentionally left out a discussion of named adapters and multi-adapters, since this text is intended as a practical and simple introduction to Zope 3 interfaces and adapters. You might want to read the `adapter.txt` in the `zope.interface` package for a more formal, referencial and complete treatment of the package. Warning: People have reported that `adapter.txt` makes their brain feel soft! zope2.13-2.13.21/source/zope.interface/src/zope/interface/ro.py0000644000175000017500000000417312214017572023057 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Compute a resolution order for an object and its bases $Id: ro.py 110536 2010-04-06 02:59:44Z tseaver $ """ __docformat__ = 'restructuredtext' def ro(object): """Compute a "resolution order" for an object """ return mergeOrderings([_flatten(object)]) def mergeOrderings(orderings, seen=None): """Merge multiple orderings so that within-ordering order is preserved Orderings are constrained in such a way that if an object appears in two or more orderings, then the suffix that begins with the object must be in both orderings. For example: >>> _mergeOrderings([ ... ['x', 'y', 'z'], ... ['q', 'z'], ... [1, 3, 5], ... ['z'] ... ]) ['x', 'y', 'q', 1, 3, 5, 'z'] """ if seen is None: seen = {} result = [] orderings.reverse() for ordering in orderings: ordering = list(ordering) ordering.reverse() for o in ordering: if o not in seen: seen[o] = 1 result.append(o) result.reverse() return result def _flatten(ob): result = [ob] i = 0 for ob in iter(result): i += 1 # The recursive calls can be avoided by inserting the base classes # into the dynamically growing list directly after the currently # considered object; the iterator makes sure this will keep working # in the future, since it cannot rely on the length of the list # by definition. result[i:i] = ob.__bases__ return result zope2.13-2.13.21/source/zope.interface/src/zope/interface/interfaces.py0000644000175000017500000006013512214017572024562 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interface Package Interfaces $Id: interfaces.py 110699 2010-04-09 08:16:17Z regebro $ """ __docformat__ = 'restructuredtext' from zope.interface import Interface from zope.interface.interface import Attribute class IElement(Interface): """Objects that have basic documentation and tagged values. """ __name__ = Attribute('__name__', 'The object name') __doc__ = Attribute('__doc__', 'The object doc string') def getTaggedValue(tag): """Returns the value associated with `tag`. Raise a `KeyError` of the tag isn't set. """ def queryTaggedValue(tag, default=None): """Returns the value associated with `tag`. Return the default value of the tag isn't set. """ def getTaggedValueTags(): """Returns a list of all tags.""" def setTaggedValue(tag, value): """Associates `value` with `key`.""" class IAttribute(IElement): """Attribute descriptors""" interface = Attribute('interface', 'Stores the interface instance in which the ' 'attribute is located.') class IMethod(IAttribute): """Method attributes""" def getSignatureInfo(): """Returns the signature information. This method returns a dictionary with the following keys: o `positional` - All positional arguments. o `required` - A list of all required arguments. o `optional` - A list of all optional arguments. o `varargs` - The name of the varargs argument. o `kwargs` - The name of the kwargs argument. """ def getSignatureString(): """Return a signature string suitable for inclusion in documentation. This method returns the function signature string. For example, if you have `func(a, b, c=1, d='f')`, then the signature string is `(a, b, c=1, d='f')`. """ class ISpecification(Interface): """Object Behavioral specifications""" def extends(other, strict=True): """Test whether a specification extends another The specification extends other if it has other as a base interface or if one of it's bases extends other. If strict is false, then the specification extends itself. """ def isOrExtends(other): """Test whether the specification is or extends another """ def weakref(callback=None): """Return a weakref to the specification This method is, regrettably, needed to allow weakrefs to be computed to security-proxied specifications. While the zope.interface package does not require zope.security or zope.proxy, it has to be able to coexist with it. """ __bases__ = Attribute("""Base specifications A tuple if specifications from which this specification is directly derived. """) __sro__ = Attribute("""Specification-resolution order A tuple of the specification and all of it's ancestor specifications from most specific to least specific. (This is similar to the method-resolution order for new-style classes.) """) __iro__ = Attribute("""Interface-resolution order A tuple of the of the specification's ancestor interfaces from most specific to least specific. The specification itself is included if it is an interface. (This is similar to the method-resolution order for new-style classes.) """) def get(name, default=None): """Look up the description for a name If the named attribute is not defined, the default is returned. """ class IInterface(ISpecification, IElement): """Interface objects Interface objects describe the behavior of an object by containing useful information about the object. This information includes: o Prose documentation about the object. In Python terms, this is called the "doc string" of the interface. In this element, you describe how the object works in prose language and any other useful information about the object. o Descriptions of attributes. Attribute descriptions include the name of the attribute and prose documentation describing the attributes usage. o Descriptions of methods. Method descriptions can include: - Prose "doc string" documentation about the method and its usage. - A description of the methods arguments; how many arguments are expected, optional arguments and their default values, the position or arguments in the signature, whether the method accepts arbitrary arguments and whether the method accepts arbitrary keyword arguments. o Optional tagged data. Interface objects (and their attributes and methods) can have optional, application specific tagged data associated with them. Examples uses for this are examples, security assertions, pre/post conditions, and other possible information you may want to associate with an Interface or its attributes. Not all of this information is mandatory. For example, you may only want the methods of your interface to have prose documentation and not describe the arguments of the method in exact detail. Interface objects are flexible and let you give or take any of these components. Interfaces are created with the Python class statement using either Interface.Interface or another interface, as in:: from zope.interface import Interface class IMyInterface(Interface): '''Interface documentation''' def meth(arg1, arg2): '''Documentation for meth''' # Note that there is no self argument class IMySubInterface(IMyInterface): '''Interface documentation''' def meth2(): '''Documentation for meth2''' You use interfaces in two ways: o You assert that your object implement the interfaces. There are several ways that you can assert that an object implements an interface: 1. Call zope.interface.implements in your class definition. 2. Call zope.interfaces.directlyProvides on your object. 3. Call 'zope.interface.classImplements' to assert that instances of a class implement an interface. For example:: from zope.interface import classImplements classImplements(some_class, some_interface) This approach is useful when it is not an option to modify the class source. Note that this doesn't affect what the class itself implements, but only what its instances implement. o You query interface meta-data. See the IInterface methods and attributes for details. """ def providedBy(object): """Test whether the interface is implemented by the object Return true of the object asserts that it implements the interface, including asserting that it implements an extended interface. """ def implementedBy(class_): """Test whether the interface is implemented by instances of the class Return true of the class asserts that its instances implement the interface, including asserting that they implement an extended interface. """ def names(all=False): """Get the interface attribute names Return a sequence of the names of the attributes, including methods, included in the interface definition. Normally, only directly defined attributes are included. If a true positional or keyword argument is given, then attributes defined by base classes will be included. """ def namesAndDescriptions(all=False): """Get the interface attribute names and descriptions Return a sequence of the names and descriptions of the attributes, including methods, as name-value pairs, included in the interface definition. Normally, only directly defined attributes are included. If a true positional or keyword argument is given, then attributes defined by base classes will be included. """ def __getitem__(name): """Get the description for a name If the named attribute is not defined, a KeyError is raised. """ def direct(name): """Get the description for the name if it was defined by the interface If the interface doesn't define the name, returns None. """ def validateInvariants(obj, errors=None): """Validate invariants Validate object to defined invariants. If errors is None, raises first Invalid error; if errors is a list, appends all errors to list, then raises Invalid with the errors as the first element of the "args" tuple.""" def __contains__(name): """Test whether the name is defined by the interface""" def __iter__(): """Return an iterator over the names defined by the interface The names iterated include all of the names defined by the interface directly and indirectly by base interfaces. """ __module__ = Attribute("""The name of the module defining the interface""") class IDeclaration(ISpecification): """Interface declaration Declarations are used to express the interfaces implemented by classes or provided by objects. """ def __contains__(interface): """Test whether an interface is in the specification Return true if the given interface is one of the interfaces in the specification and false otherwise. """ def __iter__(): """Return an iterator for the interfaces in the specification """ def flattened(): """Return an iterator of all included and extended interfaces An iterator is returned for all interfaces either included in or extended by interfaces included in the specifications without duplicates. The interfaces are in "interface resolution order". The interface resolution order is such that base interfaces are listed after interfaces that extend them and, otherwise, interfaces are included in the order that they were defined in the specification. """ def __sub__(interfaces): """Create an interface specification with some interfaces excluded The argument can be an interface or an interface specifications. The interface or interfaces given in a specification are subtracted from the interface specification. Removing an interface that is not in the specification does not raise an error. Doing so has no effect. Removing an interface also removes sub-interfaces of the interface. """ def __add__(interfaces): """Create an interface specification with some interfaces added The argument can be an interface or an interface specifications. The interface or interfaces given in a specification are added to the interface specification. Adding an interface that is already in the specification does not raise an error. Doing so has no effect. """ def __nonzero__(): """Return a true value of the interface specification is non-empty """ class IInterfaceDeclaration(Interface): """Declare and check the interfaces of objects The functions defined in this interface are used to declare the interfaces that objects provide and to query the interfaces that have been declared. Interfaces can be declared for objects in two ways: - Interfaces are declared for instances of the object's class - Interfaces are declared for the object directly. The interfaces declared for an object are, therefore, the union of interfaces declared for the object directly and the interfaces declared for instances of the object's class. Note that we say that a class implements the interfaces provided by it's instances. An instance can also provide interfaces directly. The interfaces provided by an object are the union of the interfaces provided directly and the interfaces implemented by the class. """ def providedBy(ob): """Return the interfaces provided by an object This is the union of the interfaces directly provided by an object and interfaces implemented by it's class. The value returned is an IDeclaration. """ def implementedBy(class_): """Return the interfaces implemented for a class' instances The value returned is an IDeclaration. """ def classImplements(class_, *interfaces): """Declare additional interfaces implemented for instances of a class The arguments after the class are one or more interfaces or interface specifications (IDeclaration objects). The interfaces given (including the interfaces in the specifications) are added to any interfaces previously declared. Consider the following example:: class C(A, B): ... classImplements(C, I1, I2) Instances of ``C`` provide ``I1``, ``I2``, and whatever interfaces instances of ``A`` and ``B`` provide. """ def implementer(*interfaces): """Create a decorator for declaring interfaces implemented by a facory A callable is returned that makes an implements declaration on objects passed to it. """ def classImplementsOnly(class_, *interfaces): """Declare the only interfaces implemented by instances of a class The arguments after the class are one or more interfaces or interface specifications (IDeclaration objects). The interfaces given (including the interfaces in the specifications) replace any previous declarations. Consider the following example:: class C(A, B): ... classImplements(C, IA, IB. IC) classImplementsOnly(C. I1, I2) Instances of ``C`` provide only ``I1``, ``I2``, and regardless of whatever interfaces instances of ``A`` and ``B`` implement. """ def implementer_only(*interfaces): """Create a decorator for declaring the only interfaces implemented A callable is returned that makes an implements declaration on objects passed to it. """ def directlyProvidedBy(object): """Return the interfaces directly provided by the given object The value returned is an IDeclaration. """ def directlyProvides(object, *interfaces): """Declare interfaces declared directly for an object The arguments after the object are one or more interfaces or interface specifications (IDeclaration objects). The interfaces given (including the interfaces in the specifications) replace interfaces previously declared for the object. Consider the following example:: class C(A, B): ... ob = C() directlyProvides(ob, I1, I2) The object, ``ob`` provides ``I1``, ``I2``, and whatever interfaces instances have been declared for instances of ``C``. To remove directly provided interfaces, use ``directlyProvidedBy`` and subtract the unwanted interfaces. For example:: directlyProvides(ob, directlyProvidedBy(ob)-I2) removes I2 from the interfaces directly provided by ``ob``. The object, ``ob`` no longer directly provides ``I2``, although it might still provide ``I2`` if it's class implements ``I2``. To add directly provided interfaces, use ``directlyProvidedBy`` and include additional interfaces. For example:: directlyProvides(ob, directlyProvidedBy(ob), I2) adds I2 to the interfaces directly provided by ob. """ def alsoProvides(object, *interfaces): """Declare additional interfaces directly for an object:: alsoProvides(ob, I1) is equivalent to:: directlyProvides(ob, directlyProvidedBy(ob), I1) """ def noLongerProvides(object, interface): """Remove an interface from the list of an object's directly provided interfaces:: noLongerProvides(ob, I1) is equivalent to:: directlyProvides(ob, directlyProvidedBy(ob)-I1) with the exception that if ``I1`` is an interface that is provided by ``ob`` through the class's implementation, ValueError is raised. """ def implements(*interfaces): """Declare interfaces implemented by instances of a class This function is called in a class definition. The arguments are one or more interfaces or interface specifications (IDeclaration objects). The interfaces given (including the interfaces in the specifications) are added to any interfaces previously declared. Previous declarations include declarations for base classes unless implementsOnly was used. This function is provided for convenience. It provides a more convenient way to call classImplements. For example:: implements(I1) is equivalent to calling:: classImplements(C, I1) after the class has been created. Consider the following example:: class C(A, B): implements(I1, I2) Instances of ``C`` implement ``I1``, ``I2``, and whatever interfaces instances of ``A`` and ``B`` implement. """ def implementsOnly(*interfaces): """Declare the only interfaces implemented by instances of a class This function is called in a class definition. The arguments are one or more interfaces or interface specifications (IDeclaration objects). Previous declarations including declarations for base classes are overridden. This function is provided for convenience. It provides a more convenient way to call classImplementsOnly. For example:: implementsOnly(I1) is equivalent to calling:: classImplementsOnly(I1) after the class has been created. Consider the following example:: class C(A, B): implementsOnly(I1, I2) Instances of ``C`` implement ``I1``, ``I2``, regardless of what instances of ``A`` and ``B`` implement. """ def classProvides(*interfaces): """Declare interfaces provided directly by a class This function is called in a class definition. The arguments are one or more interfaces or interface specifications (IDeclaration objects). The given interfaces (including the interfaces in the specifications) are used to create the class's direct-object interface specification. An error will be raised if the module class has an direct interface specification. In other words, it is an error to call this function more than once in a class definition. Note that the given interfaces have nothing to do with the interfaces implemented by instances of the class. This function is provided for convenience. It provides a more convenient way to call directlyProvides for a class. For example:: classProvides(I1) is equivalent to calling:: directlyProvides(theclass, I1) after the class has been created. """ def provider(*interfaces): """A class decorator version of classProvides""" def moduleProvides(*interfaces): """Declare interfaces provided by a module This function is used in a module definition. The arguments are one or more interfaces or interface specifications (IDeclaration objects). The given interfaces (including the interfaces in the specifications) are used to create the module's direct-object interface specification. An error will be raised if the module already has an interface specification. In other words, it is an error to call this function more than once in a module definition. This function is provided for convenience. It provides a more convenient way to call directlyProvides for a module. For example:: moduleImplements(I1) is equivalent to:: directlyProvides(sys.modules[__name__], I1) """ def Declaration(*interfaces): """Create an interface specification The arguments are one or more interfaces or interface specifications (IDeclaration objects). A new interface specification (IDeclaration) with the given interfaces is returned. """ class IAdapterRegistry(Interface): """Provide an interface-based registry for adapters This registry registers objects that are in some sense "from" a sequence of specification to an interface and a name. No specific semantics are assumed for the registered objects, however, the most common application will be to register factories that adapt objects providing required specifications to a provided interface. """ def register(required, provided, name, value): """Register a value A value is registered for a *sequence* of required specifications, a provided interface, and a name. """ def registered(required, provided, name=u''): """Return the component registered for the given interfaces and name Unlike the lookup method, this methods won't retrieve components registered for more specific required interfaces or less specific provided interfaces. If no component was registered exactly for the given interfaces and name, then None is returned. """ def lookup(required, provided, name='', default=None): """Lookup a value A value is looked up based on a *sequence* of required specifications, a provided interface, and a name. """ def queryMultiAdapter(objects, provided, name=u'', default=None): """Adapt a sequence of objects to a named, provided, interface """ def lookup1(required, provided, name=u'', default=None): """Lookup a value using a single required interface A value is looked up based on a single required specifications, a provided interface, and a name. """ def queryAdapter(object, provided, name=u'', default=None): """Adapt an object using a registered adapter factory. """ def adapter_hook(provided, object, name=u'', default=None): """Adapt an object using a registered adapter factory. """ def lookupAll(required, provided): """Find all adapters from the required to the provided interfaces An iterable object is returned that provides name-value two-tuples. """ def names(required, provided): """Return the names for which there are registered objects """ def subscribe(required, provided, subscriber, name=u''): """Register a subscriber A subscriber is registered for a *sequence* of required specifications, a provided interface, and a name. Multiple subscribers may be registered for the same (or equivalent) interfaces. """ def subscriptions(required, provided, name=u''): """Get a sequence of subscribers Subscribers for a *sequence* of required interfaces, and a provided interface are returned. """ def subscribers(objects, provided, name=u''): """Get a sequence of subscription adapters """ zope2.13-2.13.21/source/zope.interface/src/zope/interface/README.ru.txt0000644000175000017500000010227012214017572024205 0ustar arnauarnau========== ИнтерфейÑÑ‹ ========== .. contents:: ИнтерфейÑÑ‹ - Ñто объекты Ñпецифицирующие (документирующие) внешнее поведение объектов которые их "предоÑтавлÑÑŽÑ‚". ИнтерфейÑÑ‹ определÑÑŽÑ‚ поведение через Ñледующие ÑоÑтавлÑющие: - Ðеформальную документацию в Ñтроках документации - ÐžÐ¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð°Ñ‚Ñ€Ð¸Ð±ÑƒÑ‚Ð¾Ð² - Инварианты - уÑловиÑ, которые должны ÑоблюдатьÑÑ Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð² предоÑтавлÑющих Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ ÐžÐ¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð°Ñ‚Ñ€Ð¸Ð±ÑƒÑ‚Ð¾Ð² опиÑывают конкретные атрибуты. Они определÑÑŽÑ‚ Ð¸Ð¼Ñ Ð°Ñ‚Ñ€Ð¸Ð±ÑƒÑ‚Ð° и предоÑтавлÑÑŽÑ‚ документацию и Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ð¹ атрибута. ÐžÐ¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð°Ñ‚Ñ€Ð¸Ð±ÑƒÑ‚Ð¾Ð² могут быть заданы неÑколькими путÑми как мы увидим ниже. Определение интерфейÑов ======================= ИнтерфейÑÑ‹ определÑÑŽÑ‚ÑÑ Ñ Ð¸Ñпользованием ключевого Ñлова class:: >>> import zope.interface >>> class IFoo(zope.interface.Interface): ... """Foo blah blah""" ... ... x = zope.interface.Attribute("""X blah blah""") ... ... def bar(q, r=None): ... """bar blah blah""" Ð’ примере выше мы Ñоздали Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ `IFoo`. Мы наÑледуем его от клаÑÑа `zope.interface.Interface`, который ÑвлÑетÑÑ Ñ€Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŒÑким интерфейÑом Ð´Ð»Ñ Ð²Ñех интерфейÑов, как `object` - Ñто родительÑкий клаÑÑ Ð´Ð»Ñ Ð²Ñех новых клаÑÑов [#create]_. Данный Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð½Ðµ ÑвлÑетÑÑ ÐºÐ»Ð°ÑÑом, а ÑвлÑетÑÑ Ð˜Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñом, ÑкземплÑром `InterfaceClass`:: >>> type(IFoo) Мы можем запроÑить у интерфейÑа его документацию:: >>> IFoo.__doc__ 'Foo blah blah' и его имÑ:: >>> IFoo.__name__ 'IFoo' и даже модуль в котором он определен:: >>> IFoo.__module__ '__main__' Ðаш Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ñет два атрибута: `x` Это проÑÑ‚ÐµÐ¹ÑˆÐ°Ñ Ñ„Ð¾Ñ€Ð¼Ð° Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð°Ñ‚Ñ€Ð¸Ð±ÑƒÑ‚Ð¾Ð². ОпределÑÑŽÑ‚ÑÑ Ð¸Ð¼Ñ Ð¸ Ñтрока документации. Формально здеÑÑŒ не определÑетÑÑ Ð½Ð¸Ñ‡ÐµÐ³Ð¾ более. `bar` Это метод. Методы определÑÑŽÑ‚ÑÑ ÐºÐ°Ðº обычные функции. Метод - Ñто проÑто атрибут который должен быть вызываемым Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸ÐµÐ¼ Ñигнатуры, предоÑтавлÑемой определением функции. Ðадо отметить, что аргумент `self` не указываетÑÑ Ð´Ð»Ñ `bar`. Ð˜Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð¸Ñ€ÑƒÐµÑ‚ как объект *иÑпользуетÑÑ*. Когда методы ÑкземплÑров клаÑÑов вызываютÑÑ Ð¼Ñ‹ не передаем аргумент `self`, таким образом аргумент `self` не включаетÑÑ Ð¸ в Ñигнатуру интерфейÑа. Ðргумент `self` в методах ÑкземплÑров клаÑÑов на Ñамом деле деталь реализации ÑкземплÑров клаÑÑов в Python. Другие объекты кроме ÑкземплÑров клаÑÑов могут предоÑтавлÑть интерфейÑÑ‹ и их методы могут не быть методами ÑкземплÑров клаÑÑов. Ð”Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÑ€Ð° модули могут предоÑтавлÑть интерфейÑÑ‹ и их методы обычно проÑто функции. Даже ÑкземплÑры могут иметь методы не ÑвлÑющиеÑÑ Ð¼ÐµÑ‚Ð¾Ð´Ð°Ð¼Ð¸ ÑкземплÑров клаÑÑа. Мы можем получить доÑтуп к атрибутам определенным интерфейÑом иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ ÑинтакÑÐ¸Ñ Ð´Ð¾Ñтупа к Ñлементам маÑÑива:: >>> x = IFoo['x'] >>> type(x) >>> x.__name__ 'x' >>> x.__doc__ 'X blah blah' >>> IFoo.get('x').__name__ 'x' >>> IFoo.get('y') Можно иÑпользовать `in` Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ñодержит ли Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð½Ð¾Ðµ имÑ:: >>> 'x' in IFoo True Мы можем иÑпользовать итератор Ð´Ð»Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñов что бы получить вÑе имена которые интерфейÑÑ‹ определÑÑŽÑ‚:: >>> names = list(IFoo) >>> names.sort() >>> names ['bar', 'x'] Ðадо помнить, что интерфейÑÑ‹ не ÑвлÑÑŽÑ‚ÑÑ ÐºÐ»Ð°ÑÑами. Мы не можем получить доÑтуп к определениÑм атрибутов через доÑтуп к атрибутам интерфейÑов:: >>> IFoo.x Traceback (most recent call last): File "", line 1, in ? AttributeError: 'InterfaceClass' object has no attribute 'x' Методы также предоÑтавлÑÑŽÑ‚ доÑтуп к Ñигнатуре метода:: >>> bar = IFoo['bar'] >>> bar.getSignatureString() '(q, r=None)' ОбъÑвление интерфейÑов ====================== Определив Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð¼Ñ‹ можем теперь *объÑвить*, что объекты предоÑтавлÑÑŽÑ‚ их. Перед опиÑанием деталей определим некоторые термины: *предоÑтавлÑть* Мы говорим, что объекты *предоÑтавлÑÑŽÑ‚* интерфейÑÑ‹. ЕÑли объект предоÑтавлÑет интерфейÑ, тогда Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ñпецифицирует поведение объекта. Другими Ñловами, интерфейÑÑ‹ Ñпецифицируют поведение объектов которые предоÑтавлÑÑŽÑ‚ их. *реализовать* Мы обычно говорим что клаÑÑÑ‹ *реализуют* интерфейÑÑ‹. ЕÑли клаÑÑ Ñ€ÐµÐ°Ð»Ð¸Ð·ÑƒÐµÑ‚ интерфейÑ, тогда ÑкземплÑры Ñтого клаÑÑа предоÑтавлÑÑŽÑ‚ данный интерфейÑ. Объекты предоÑтавлÑÑŽÑ‚ интерфейÑÑ‹ которые их клаÑÑÑ‹ реализуют [#factory]_. (Объекты также могут предоÑтавлÑть интерфейÑÑ‹ напрÑмую Ð¿Ð»ÑŽÑ Ðº тем которые реализуют их клаÑÑÑ‹.) Важно помнить, что клаÑÑÑ‹ обычно не предоÑтавлÑÑŽÑ‚ интерфейÑÑ‹ которые они реализуют. Мы можем обобщить Ñто до фабрик. Ð”Ð»Ñ Ð»ÑŽÐ±Ð¾Ð³Ð¾ вызываемого объекта мы можем объÑвить что он производит объекты которые предоÑтавлÑÑŽÑ‚ какие-либо интерфейÑÑ‹ Ñказав, что фабрика реализует данные интерфейÑÑ‹. Теперь поÑле того как мы определили Ñти термины мы можем поговорить об API Ð´Ð»Ñ Ð¾Ð±ÑŠÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñов. ОбъÑвление реализуемых интерфейÑов ---------------------------------- Ðаиболее чаÑто иÑпользуемый путь Ð´Ð»Ñ Ð¾Ð±ÑŠÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñов - Ñто иÑпользование функции implements в определении клаÑÑа:: >>> class Foo: ... zope.interface.implements(IFoo) ... ... def __init__(self, x=None): ... self.x = x ... ... def bar(self, q, r=None): ... return q, r, self.x ... ... def __repr__(self): ... return "Foo(%s)" % self.x Ð’ Ñтом примере мы объÑвили, что `Foo` реализует `IFoo`. Это значит, что ÑкземплÑры `Foo` предоÑтавлÑÑŽÑ‚ `IFoo`. ПоÑле данного объÑÐ²Ð»ÐµÐ½Ð¸Ñ ÐµÑть неÑколько путей Ð´Ð»Ñ Ð°Ð½Ð°Ð»Ð¸Ð·Ð° объÑвлений. Во-первых мы можем ÑпроÑить что Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ñ€ÐµÐ°Ð»Ð¸Ð·Ð¾Ð²Ð°Ð½ клаÑÑом:: >>> IFoo.implementedBy(Foo) True Также мы можем ÑпроÑить еÑли Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð¿Ñ€ÐµÐ´Ð¾ÑтавлÑетÑÑ Ð¾Ð±ÑŠÐµÐºÑ‚Ð°Ð¼Ð¸ клаÑÑа:: >>> foo = Foo() >>> IFoo.providedBy(foo) True Конечно `Foo` не предоÑтавлÑет `IFoo`, он реализует его:: >>> IFoo.providedBy(Foo) False Мы можем также узнать какие интерфейÑÑ‹ реализуютÑÑ Ð¾Ð±ÑŠÐµÐºÑ‚Ð°Ð¼Ð¸:: >>> list(zope.interface.implementedBy(Foo)) [] Это ошибка Ñпрашивать про интерфейÑÑ‹ реализуемые не вызываемым объектом:: >>> IFoo.implementedBy(foo) Traceback (most recent call last): ... TypeError: ('ImplementedBy called for non-factory', Foo(None)) >>> list(zope.interface.implementedBy(foo)) Traceback (most recent call last): ... TypeError: ('ImplementedBy called for non-factory', Foo(None)) Также можно узнать какие интерфейÑÑ‹ предоÑтавлÑÑŽÑ‚ÑÑ Ð¾Ð±ÑŠÐµÐºÑ‚Ð°Ð¼Ð¸:: >>> list(zope.interface.providedBy(foo)) [] >>> list(zope.interface.providedBy(Foo)) [] Мы можем объÑвить интерфейÑÑ‹ реализуемые другими фабриками (кроме клаÑÑов). Это можно Ñделать иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ð´ÐµÐºÐ¾Ñ€Ð°Ñ‚Ð¾Ñ€ `implementer` (в Ñтиле Python 2.4). Ð”Ð»Ñ Ð²ÐµÑ€Ñий Python ниже 2.4 Ñто будет выглÑдеть Ñледующим образом:: >>> def yfoo(y): ... foo = Foo() ... foo.y = y ... return foo >>> yfoo = zope.interface.implementer(IFoo)(yfoo) >>> list(zope.interface.implementedBy(yfoo)) [] Ðадо заметить, что декоратор implementer может модифицировать Ñвои аргументы. Ð’Ñ‹Ð·Ñ‹Ð²Ð°ÑŽÑ‰Ð°Ñ Ñторона не должна предполагать, что вÑегда будет ÑоздаватьÑÑ Ð½Ð¾Ð²Ñ‹Ð¹ объект. XXX: Double check and update these version numbers, and translate to russian: In zope.interface 3.5.1 and lower, the implementor decorator can not be used for classes, but in 3.5.2 and higher it can: >>> Foo = zope.interface.implementer(IFoo)(Foo) >>> list(zope.interface.providedBy(Foo())) [] Note that class decorators using the @implementor(IFoo) syntax are only supported in Python 2.6 and later. ОбъÑвление предоÑтавлÑемых интерфейÑов -------------------------------------- Мы можем объÑвлÑть интерфейÑÑ‹ напрÑмую предоÑтавлÑемые объектами. Предположим что мы хотим документировать что делает метод `__init__` клаÑÑа `Foo`. Это *точно* не чаÑть `IFoo`. Обычно мы не должны напрÑмую вызывать метод `__init__` Ð´Ð»Ñ ÑкземплÑров Foo. Скорее метод `__init__` ÑвлÑетÑÑ Ñ‡Ð°Ñтью метода `__call__` клаÑÑа `Foo`:: >>> class IFooFactory(zope.interface.Interface): ... """Create foos""" ... ... def __call__(x=None): ... """Create a foo ... ... The argument provides the initial value for x ... ... """ У Ð½Ð°Ñ ÐµÑть клаÑÑ Ð¿Ñ€ÐµÐ´Ð¾ÑтавлÑющий данный интерфейÑ, таким образом мы можем объÑвить Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ ÐºÐ»Ð°ÑÑа:: >>> zope.interface.directlyProvides(Foo, IFooFactory) Теперь мы видим, что Foo уже предоÑтавлÑет интерфейÑÑ‹:: >>> list(zope.interface.providedBy(Foo)) [] >>> IFooFactory.providedBy(Foo) True ОбъÑвление интерфейÑов клаÑÑа доÑтаточно чаÑÑ‚Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¸ Ð´Ð»Ñ Ð½ÐµÐµ еÑть ÑÐ¿ÐµÑ†Ð¸Ð°Ð»ÑŒÐ½Ð°Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ Ð¾Ð±ÑŠÑÐ²Ð»ÐµÐ½Ð¸Ñ `classProvides`, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»Ñет объÑвлÑть интерфейÑÑ‹ при определении клаÑÑа:: >>> class Foo2: ... zope.interface.implements(IFoo) ... zope.interface.classProvides(IFooFactory) ... ... def __init__(self, x=None): ... self.x = x ... ... def bar(self, q, r=None): ... return q, r, self.x ... ... def __repr__(self): ... return "Foo(%s)" % self.x >>> list(zope.interface.providedBy(Foo2)) [] >>> IFooFactory.providedBy(Foo2) True ÐŸÐ¾Ñ…Ð¾Ð¶Ð°Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ `moduleProvides` поддерживает объÑвление интерфейÑов при определении модулÑ. Ð”Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÑ€Ð° Ñмотрите иÑпользование вызова `moduleProvides` в `zope.interface.__init__`, который объÑвлÑет, что пакет `zope.interface` предоÑтавлÑет `IInterfaceDeclaration`. Иногда мы хотим объÑвить интерфейÑÑ‹ ÑкземплÑров, даже еÑли Ñти ÑкземплÑры уже берут интерфейÑÑ‹ от Ñвоих клаÑÑов. Предположим, что мы Ñоздаем новый Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ `ISpecial`:: >>> class ISpecial(zope.interface.Interface): ... reason = zope.interface.Attribute("Reason why we're special") ... def brag(): ... "Brag about being special" Мы можем Ñделать Ñозданный ÑкземплÑÑ€ foo Ñпециальным предоÑтавив атрибуты `reason` и `brag`:: >>> foo.reason = 'I just am' >>> def brag(): ... return "I'm special!" >>> foo.brag = brag >>> foo.reason 'I just am' >>> foo.brag() "I'm special!" и объÑвив интерфейÑ:: >>> zope.interface.directlyProvides(foo, ISpecial) таким образом новый Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð²ÐºÐ»ÑŽÑ‡Ð°ÐµÑ‚ÑÑ Ð² ÑпиÑок предоÑтавлÑемых интерфейÑов:: >>> ISpecial.providedBy(foo) True >>> list(zope.interface.providedBy(foo)) [, ] Мы также можем определить, что интерфейÑÑ‹ напрÑмую предоÑтавлÑÑŽÑ‚ÑÑ Ð¾Ð±ÑŠÐµÐºÑ‚Ð°Ð¼Ð¸:: >>> list(zope.interface.directlyProvidedBy(foo)) [] >>> newfoo = Foo() >>> list(zope.interface.directlyProvidedBy(newfoo)) [] ÐаÑледуемые объÑÐ²Ð»ÐµÐ½Ð¸Ñ ---------------------- Обычно объÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð½Ð°ÑледуютÑÑ:: >>> class SpecialFoo(Foo): ... zope.interface.implements(ISpecial) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason >>> list(zope.interface.implementedBy(SpecialFoo)) [, ] >>> list(zope.interface.providedBy(SpecialFoo())) [, ] Иногда мы не хотим наÑледовать объÑвлениÑ. Ð’ Ñтом Ñлучае мы можем иÑпользовать `implementsOnly` вмеÑто `implements`:: >>> class Special(Foo): ... zope.interface.implementsOnly(ISpecial) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason >>> list(zope.interface.implementedBy(Special)) [] >>> list(zope.interface.providedBy(Special())) [] Внешние объÑÐ²Ð»ÐµÐ½Ð¸Ñ ------------------ Обычно мы Ñоздаем объÑÐ²Ð»ÐµÐ½Ð¸Ñ Ñ€ÐµÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ð¸ как чаÑть объÑÐ²Ð»ÐµÐ½Ð¸Ñ ÐºÐ»Ð°ÑÑа. Иногда мы можем захотеть Ñоздать объÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð²Ð½Ðµ объÑÐ²Ð»ÐµÐ½Ð¸Ñ ÐºÐ»Ð°ÑÑа. Ð”Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÑ€Ð°, мы можем хотеть объÑвить интерфейÑÑ‹ Ð´Ð»Ñ ÐºÐ»Ð°ÑÑов которые пиÑали не мы. Ð”Ð»Ñ Ñтого может иÑпользоватьÑÑ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ `classImplements`:: >>> class C: ... pass >>> zope.interface.classImplements(C, IFoo) >>> list(zope.interface.implementedBy(C)) [] Мы можем иÑпользовать `classImplementsOnly` Ð´Ð»Ñ Ð¸ÑÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð½Ð°Ñледуемых интерфейÑов:: >>> class C(Foo): ... pass >>> zope.interface.classImplementsOnly(C, ISpecial) >>> list(zope.interface.implementedBy(C)) [] Объекты объÑвлений ------------------ Когда мы объÑвлÑем интерфейÑÑ‹ мы Ñоздаем объект *объÑвлениÑ*. Когда мы запрашиваем объÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð²Ð¾Ð·Ð²Ñ€Ð°Ñ‰Ð°ÐµÑ‚ÑÑ Ð¾Ð±ÑŠÐµÐºÑ‚ объÑвлениÑ:: >>> type(zope.interface.implementedBy(Special)) Объекты объÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð¸ объекты интерфейÑов во многом похожи друг на друга. Ðа Ñамом деле они даже имеют общий базовый клаÑÑ. Важно понÑть, что они могут иÑпользоватьÑÑ Ñ‚Ð°Ð¼ где в объÑвлениÑÑ… ожидаютÑÑ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹ÑÑ‹. Вот проÑтой пример:: >>> class Special2(Foo): ... zope.interface.implementsOnly( ... zope.interface.implementedBy(Foo), ... ISpecial, ... ) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason ОбъÑвление здеÑÑŒ практичеÑки такое же как ``zope.interface.implements(ISpecial)``, отличие только в порÑдке интерфейÑов в итоговом объÑвлениÑ:: >>> list(zope.interface.implementedBy(Special2)) [, ] ÐаÑледование интерфейÑов ======================== ИнтерфейÑÑ‹ могут раÑширÑть другие интерфейÑÑ‹. Они делают Ñто проÑто Ð¿Ð¾ÐºÐ°Ð·Ñ‹Ð²Ð°Ñ Ñти интерфейÑÑ‹ как базовые:: >>> class IBlat(zope.interface.Interface): ... """Blat blah blah""" ... ... y = zope.interface.Attribute("y blah blah") ... def eek(): ... """eek blah blah""" >>> IBlat.__bases__ (,) >>> class IBaz(IFoo, IBlat): ... """Baz blah""" ... def eek(a=1): ... """eek in baz blah""" ... >>> IBaz.__bases__ (, ) >>> names = list(IBaz) >>> names.sort() >>> names ['bar', 'eek', 'x', 'y'] Заметим, что `IBaz` переопределÑет eek:: >>> IBlat['eek'].__doc__ 'eek blah blah' >>> IBaz['eek'].__doc__ 'eek in baz blah' Мы были оÑторожны переопределÑÑ eek ÑовмеÑтимым путем. Когда Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ñ€Ð°ÑширÑетÑÑ, раÑширенный Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð´Ð¾Ð»Ð¶ÐµÐ½ быть ÑовмеÑтимым [#compat]_ Ñ Ñ€Ð°ÑширÑемыми интерфейÑами. Мы можем запроÑить раÑширÑет ли один из интерфейÑов другой:: >>> IBaz.extends(IFoo) True >>> IBlat.extends(IFoo) False Заметим, что интерфейÑÑ‹ не раÑширÑÑŽÑ‚ Ñами ÑебÑ:: >>> IBaz.extends(IBaz) False Иногда мы можем хотеть что бы они раÑширÑли Ñами ÑебÑ, но вмеÑто Ñтого мы можем иÑпользовать `isOrExtends`:: >>> IBaz.isOrExtends(IBaz) True >>> IBaz.isOrExtends(IFoo) True >>> IFoo.isOrExtends(IBaz) False Когда мы применÑем итерацию к интерфейÑу мы получаем вÑе имена которые он определÑет Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð¸Ð¼ÐµÐ½Ð° определенные Ð´Ð»Ñ Ð±Ð°Ð·Ð¾Ð²Ñ‹Ñ… интерфейÑов. Иногда мы хотим получить *только* имена определенные интерфейÑом напрÑмую. Ð”Ð»Ñ Ñтого мы иÑпользуем метод `names`:: >>> list(IBaz.names()) ['eek'] ÐаÑледование в Ñлучае Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð°Ñ‚Ñ€Ð¸Ð±ÑƒÑ‚Ð¾Ð² -------------------------------------------- Ð˜Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð¼Ð¾Ð¶ÐµÑ‚ переопределÑть Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð°Ñ‚Ñ€Ð¸Ð±ÑƒÑ‚Ð¾Ð² из базовых интерфейÑов. ЕÑли два базовых интерфейÑа определÑÑŽÑ‚ один и тот же атрибут атрибут наÑледуетÑÑ Ð¾Ñ‚ более Ñпецифичного интерфейÑа. Ð”Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÑ€Ð°:: >>> class IBase(zope.interface.Interface): ... ... def foo(): ... "base foo doc" >>> class IBase1(IBase): ... pass >>> class IBase2(IBase): ... ... def foo(): ... "base2 foo doc" >>> class ISub(IBase1, IBase2): ... pass Определение ISub Ð´Ð»Ñ foo будет из IBase2 Ñ‚.к. IBase2 более Ñпецифичен Ð´Ð»Ñ IBase:: >>> ISub['foo'].__doc__ 'base2 foo doc' Заметим, что Ñто отличаетÑÑ Ð¾Ñ‚ поиÑка в глубину. Иногда полезно узнать, что Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ñет атрибут напрÑмую. Мы можем иÑпользовать метод direct Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð½Ð°Ð¿Ñ€Ñмую определенных атрибутов:: >>> IBase.direct('foo').__doc__ 'base foo doc' >>> ISub.direct('foo') Спецификации ------------ ИнтерфейÑÑ‹ и объÑÐ²Ð»ÐµÐ½Ð¸Ñ - Ñто Ñпециальные Ñлучаи Ñпецификаций. ОпиÑание выше Ð´Ð»Ñ Ð½Ð°ÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñов можно применить и к объÑвлениÑм и к ÑпецификациÑм. ОбъÑÐ²Ð»ÐµÐ½Ð¸Ñ Ñ„Ð°ÐºÑ‚Ð¸Ñ‡ÐµÑки раÑширÑÑŽÑ‚ интерфейÑÑ‹ которые они объÑвлÑÑŽÑ‚:: >>> class Baz(object): ... zope.interface.implements(IBaz) >>> baz_implements = zope.interface.implementedBy(Baz) >>> baz_implements.__bases__ (, ) >>> baz_implements.extends(IFoo) True >>> baz_implements.isOrExtends(IFoo) True >>> baz_implements.isOrExtends(baz_implements) True Спецификации (интерфейÑÑ‹ и объÑвлениÑ) предоÑтавлÑÑŽÑ‚ атрибут `__sro__` который опиÑывает Ñпецификацию и вÑех ее предков:: >>> baz_implements.__sro__ (, , , , , ) Помеченные Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ =================== ИнтерфейÑÑ‹ и опиÑÐ°Ð½Ð¸Ñ Ð°Ñ‚Ñ€Ð¸Ð±ÑƒÑ‚Ð¾Ð² поддерживают механизм раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð·Ð°Ð¸Ð¼Ñтвованный из UML и называемый "помеченные значениÑ" который позволÑет ÑохранÑть дополнительные данные:: >>> IFoo.setTaggedValue('date-modified', '2004-04-01') >>> IFoo.setTaggedValue('author', 'Jim Fulton') >>> IFoo.getTaggedValue('date-modified') '2004-04-01' >>> IFoo.queryTaggedValue('date-modified') '2004-04-01' >>> IFoo.queryTaggedValue('datemodified') >>> tags = list(IFoo.getTaggedValueTags()) >>> tags.sort() >>> tags ['author', 'date-modified'] Ðтрибуты функций конвертируютÑÑ Ð² помеченные Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÐºÐ¾Ð³Ð´Ð° ÑоздаютÑÑ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð°Ñ‚Ñ€Ð¸Ð±ÑƒÑ‚Ð¾Ð² метода:: >>> class IBazFactory(zope.interface.Interface): ... def __call__(): ... "create one" ... __call__.return_type = IBaz >>> IBazFactory['__call__'].getTaggedValue('return_type') Помеченные Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ñ‚Ð°ÐºÐ¶Ðµ могут быть определены внутри Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñа:: >>> class IWithTaggedValues(zope.interface.Interface): ... zope.interface.taggedValue('squish', 'squash') >>> IWithTaggedValues.getTaggedValue('squish') 'squash' Инварианты ========== ИнтерфейÑÑ‹ могут опиÑывать уÑÐ»Ð¾Ð²Ð¸Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ðµ должны быть Ñоблюдены Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð² которые их предоÑтавлÑÑŽÑ‚. Эти уÑÐ»Ð¾Ð²Ð¸Ñ Ð¾Ð¿Ð¸ÑываютÑÑ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ð¾Ð´Ð¸Ð½ или более инвариантов. Инварианты - Ñто вызываемые объекты которые будут вызваны Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð¼ предоÑтавлÑющим Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð² качеÑтве параметра. Инвариант должен выкинуть иÑключение `Invalid` еÑли уÑловие не Ñоблюдено. Ðапример:: >>> class RangeError(zope.interface.Invalid): ... """A range has invalid limits""" ... def __repr__(self): ... return "RangeError(%r)" % self.args >>> def range_invariant(ob): ... if ob.max < ob.min: ... raise RangeError(ob) Определив Ñтот инвариант мы можем иÑпользовать его в определении интерфейÑов:: >>> class IRange(zope.interface.Interface): ... min = zope.interface.Attribute("Lower bound") ... max = zope.interface.Attribute("Upper bound") ... ... zope.interface.invariant(range_invariant) ИнтерфейÑÑ‹ имеют метод Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸ Ñвоих инвариантов:: >>> class Range(object): ... zope.interface.implements(IRange) ... ... def __init__(self, min, max): ... self.min, self.max = min, max ... ... def __repr__(self): ... return "Range(%s, %s)" % (self.min, self.max) >>> IRange.validateInvariants(Range(1,2)) >>> IRange.validateInvariants(Range(1,1)) >>> IRange.validateInvariants(Range(2,1)) Traceback (most recent call last): ... RangeError: Range(2, 1) Ð’ Ñлучае неÑкольких инвариантов мы можем захотеть оÑтановить проверку поÑле первой ошибки. ЕÑли мы передадим в `validateInvariants` пуÑтой ÑпиÑок тогда будет выкинуто единÑтвенное иÑключение `Invalid` Ñо ÑпиÑком иÑключений как аргументом:: >>> from zope.interface.exceptions import Invalid >>> errors = [] >>> try: ... IRange.validateInvariants(Range(2,1), errors) ... except Invalid, e: ... str(e) '[RangeError(Range(2, 1))]' И ÑпиÑок будет заполнен индивидуальными иÑключениÑми:: >>> errors [RangeError(Range(2, 1))] >>> del errors[:] ÐÐ´Ð°Ð¿Ñ‚Ð°Ñ†Ð¸Ñ ========= ИнтерфейÑÑ‹ могут быть вызваны Ð´Ð»Ñ Ð¾ÑущеÑÑ‚Ð²Ð»ÐµÐ½Ð¸Ñ Ð°Ð´Ð°Ð¿Ñ‚Ð°Ñ†Ð¸Ð¸. Эта Ñемантика оÑнована на функции adapt из PEP 246. ЕÑли объект не может быть адаптирован будет выкинут TypeError:: >>> class I(zope.interface.Interface): ... pass >>> I(0) Traceback (most recent call last): ... TypeError: ('Could not adapt', 0, ) только еÑли альтернативное значение не передано как второй аргумент:: >>> I(0, 'bob') 'bob' ЕÑли объект уже реализует нужный Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð¾Ð½ будет возвращен:: >>> class C(object): ... zope.interface.implements(I) >>> obj = C() >>> I(obj) is obj True ЕÑли объект реализует __conform__, тогда она будет иÑпользована:: >>> class C(object): ... zope.interface.implements(I) ... def __conform__(self, proto): ... return 0 >>> I(C()) 0 Также еÑли приÑутÑтвуют функции Ð´Ð»Ñ Ð²Ñ‹Ð·Ð¾Ð²Ð° адаптации (Ñм. __adapt__) они будут иÑпользованы:: >>> from zope.interface.interface import adapter_hooks >>> def adapt_0_to_42(iface, obj): ... if obj == 0: ... return 42 >>> adapter_hooks.append(adapt_0_to_42) >>> I(0) 42 >>> adapter_hooks.remove(adapt_0_to_42) >>> I(0) Traceback (most recent call last): ... TypeError: ('Could not adapt', 0, ) __adapt__ --------- >>> class I(zope.interface.Interface): ... pass ИнтерфейÑÑ‹ реализуют метод __adapt__ из PEP 246. Этот метод обычно не вызываетÑÑ Ð½Ð°Ð¿Ñ€Ñмую. Он вызываетÑÑ Ð°Ñ€Ñ…Ð¸Ñ‚ÐµÐºÑ‚ÑƒÑ€Ð¾Ð¹ адаптации из PEP 246 и методом __call__ интерфейÑов. Метод адаптации отвечает за адаптацию объекта к получателю. ВерÑÐ¸Ñ Ð¿Ð¾ умолчанию возвращает None:: >>> I.__adapt__(0) еÑли только переданный объект не предоÑтавлÑет нужный интерфейÑ:: >>> class C(object): ... zope.interface.implements(I) >>> obj = C() >>> I.__adapt__(obj) is obj True Функции Ð´Ð»Ñ Ð²Ñ‹Ð·Ð¾Ð²Ð° адаптации могут быть добавлены (или удалены) Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´Ð¾ÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð°Ð´Ð°Ð¿Ñ‚Ð°Ñ†Ð¸Ð¸ "на заказ". Мы уÑтановим глупую функцию ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð°Ð´Ð°Ð¿Ñ‚Ð¸Ñ€ÑƒÐµÑ‚ 0 к 42. Мы уÑтанавливаем функцию проÑто добавлÑÑ ÐµÐµ к ÑпиÑку adapter_hooks:: >>> from zope.interface.interface import adapter_hooks >>> def adapt_0_to_42(iface, obj): ... if obj == 0: ... return 42 >>> adapter_hooks.append(adapt_0_to_42) >>> I.__adapt__(0) 42 Функции должны возвращать либо адаптер, либо None еÑли адаптер не найден. Функции могут быть удалены удалением их из ÑпиÑка:: >>> adapter_hooks.remove(adapt_0_to_42) >>> I.__adapt__(0) .. [#create] ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð° по которой мы наÑледуемÑÑ Ð¾Ñ‚ `Interface` - Ñто что бы быть уверенными в том, что ключевое Ñлово class будет Ñоздавать интерфейÑ, а не клаÑÑ. ЕÑть возможноÑть Ñоздать интерфейÑÑ‹ вызвав Ñпециальный клаÑÑ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñа напрÑмую. Ð”ÐµÐ»Ð°Ñ Ñто, возможно (и в редких ÑлучаÑÑ… полезно) Ñоздать интерфейÑÑ‹ которые не наÑледуютÑÑ Ð¾Ñ‚ `Interface`. Однако иÑпользование Ñтой техники выходит за рамки данного документа. .. [#factory] КлаÑÑÑ‹ - Ñто фабрики. Они могут быть вызваны Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñвоих ÑкземплÑров. Мы ожидаем что в итоге мы раÑширим концепцию реализации на другие типы фабрик, таким образом мы Ñможем объÑвлÑть интерфейÑÑ‹ предоÑтавлÑемые Ñозданными фабриками объектами. .. [#compat] Цель - заменÑемоÑть. Объект который предоÑтавлÑет раÑширенный Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð´Ð¾Ð»Ð¶ÐµÐ½ быть заменÑем в качеÑтве объектов которые предоÑтавлÑÑŽÑ‚ раÑширÑемый интерфейÑ. Ð’ нашем примере объект который предоÑтавлÑет IBaz должен быть иÑпользуемым и в Ñлучае еÑли ожидаетÑÑ Ð¾Ð±ÑŠÐµÐºÑ‚ который предоÑтавлÑет IBlat. Ð ÐµÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñа не требует Ñтого. Ðо возможно в дальнейшем она должна будет делать какие-либо проверки. zope2.13-2.13.21/source/zope.interface/src/zope/interface/__init__.py0000644000175000017500000000567112214017572024202 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interfaces This package implements the Python "scarecrow" proposal. The package exports two objects, `Interface` and `Attribute` directly. It also exports several helper methods. Interface is used to create an interface with a class statement, as in: class IMyInterface(Interface): '''Interface documentation ''' def meth(arg1, arg2): '''Documentation for meth ''' # Note that there is no self argument To find out what you can do with interfaces, see the interface interface, `IInterface` in the `interfaces` module. The package has several public modules: o `declarations` provides utilities to declare interfaces on objects. It also provides a wide range of helpful utilities that aid in managing declared interfaces. Most of its public names are however imported here. o `document` has a utility for documenting an interface as structured text. o `exceptions` has the interface-defined exceptions o `interfaces` contains a list of all public interfaces for this package. o `verify` has utilities for verifying implementations of interfaces. See the module doc strings for more information. $Id: __init__.py 110699 2010-04-09 08:16:17Z regebro $ """ __docformat__ = 'restructuredtext' from zope.interface.interface import Interface, _wire # Need to actually get the interface elements to implement the right interfaces _wire() del _wire from zope.interface.interface import Attribute, invariant, taggedValue from zope.interface.declarations import providedBy, implementedBy from zope.interface.declarations import classImplements, classImplementsOnly from zope.interface.declarations import directlyProvidedBy, directlyProvides from zope.interface.declarations import alsoProvides, provider from zope.interface.declarations import implementer, implementer_only from zope.interface.declarations import implements, implementsOnly from zope.interface.declarations import classProvides, moduleProvides from zope.interface.declarations import noLongerProvides, Declaration from zope.interface.exceptions import Invalid # The following are to make spec pickles cleaner from zope.interface.declarations import Provides from zope.interface.interfaces import IInterfaceDeclaration moduleProvides(IInterfaceDeclaration) __all__ = ('Interface', 'Attribute') + tuple(IInterfaceDeclaration) zope2.13-2.13.21/source/zope.interface/src/zope/interface/README.txt0000644000175000017500000005656312214017572023575 0ustar arnauarnau========== Interfaces ========== Interfaces are objects that specify (document) the external behavior of objects that "provide" them. An interface specifies behavior through: - Informal documentation in a doc string - Attribute definitions - Invariants, which are conditions that must hold for objects that provide the interface Attribute definitions specify specific attributes. They define the attribute name and provide documentation and constraints of attribute values. Attribute definitions can take a number of forms, as we'll see below. Defining interfaces =================== Interfaces are defined using Python class statements:: >>> import zope.interface >>> class IFoo(zope.interface.Interface): ... """Foo blah blah""" ... ... x = zope.interface.Attribute("""X blah blah""") ... ... def bar(q, r=None): ... """bar blah blah""" In the example above, we've created an interface, `IFoo`. We subclassed `zope.interface.Interface`, which is an ancestor interface for all interfaces, much as `object` is an ancestor of all new-style classes [#create]_. The interface is not a class, it's an Interface, an instance of `InterfaceClass`:: >>> type(IFoo) We can ask for the interface's documentation:: >>> IFoo.__doc__ 'Foo blah blah' and its name:: >>> IFoo.__name__ 'IFoo' and even its module:: >>> IFoo.__module__ '__main__' The interface defined two attributes: `x` This is the simplest form of attribute definition. It has a name and a doc string. It doesn't formally specify anything else. `bar` This is a method. A method is defined via a function definition. A method is simply an attribute constrained to be a callable with a particular signature, as provided by the function definition. Note that `bar` doesn't take a `self` argument. Interfaces document how an object is *used*. When calling instance methods, you don't pass a `self` argument, so a `self` argument isn't included in the interface signature. The `self` argument in instance methods is really an implementation detail of Python instances. Other objects, besides instances can provide interfaces and their methods might not be instance methods. For example, modules can provide interfaces and their methods are usually just functions. Even instances can have methods that are not instance methods. You can access the attributes defined by an interface using mapping syntax:: >>> x = IFoo['x'] >>> type(x) >>> x.__name__ 'x' >>> x.__doc__ 'X blah blah' >>> IFoo.get('x').__name__ 'x' >>> IFoo.get('y') You can use `in` to determine if an interface defines a name:: >>> 'x' in IFoo True You can iterate over interfaces to get the names they define:: >>> names = list(IFoo) >>> names.sort() >>> names ['bar', 'x'] Remember that interfaces aren't classes. You can't access attribute definitions as attributes of interfaces:: >>> IFoo.x Traceback (most recent call last): File "", line 1, in ? AttributeError: 'InterfaceClass' object has no attribute 'x' Methods provide access to the method signature:: >>> bar = IFoo['bar'] >>> bar.getSignatureString() '(q, r=None)' TODO Methods really should have a better API. This is something that needs to be improved. Declaring interfaces ==================== Having defined interfaces, we can *declare* that objects provide them. Before we describe the details, lets define some terms: *provide* We say that objects *provide* interfaces. If an object provides an interface, then the interface specifies the behavior of the object. In other words, interfaces specify the behavior of the objects that provide them. *implement* We normally say that classes *implement* interfaces. If a class implements an interface, then the instances of the class provide the interface. Objects provide interfaces that their classes implement [#factory]_. (Objects can provide interfaces directly, in addition to what their classes implement.) It is important to note that classes don't usually provide the interfaces that they implement. We can generalize this to factories. For any callable object we can declare that it produces objects that provide some interfaces by saying that the factory implements the interfaces. Now that we've defined these terms, we can talk about the API for declaring interfaces. Declaring implemented interfaces -------------------------------- The most common way to declare interfaces is using the implements function in a class statement:: >>> class Foo: ... zope.interface.implements(IFoo) ... ... def __init__(self, x=None): ... self.x = x ... ... def bar(self, q, r=None): ... return q, r, self.x ... ... def __repr__(self): ... return "Foo(%s)" % self.x In this example, we declared that `Foo` implements `IFoo`. This means that instances of `Foo` provide `IFoo`. Having made this declaration, there are several ways we can introspect the declarations. First, we can ask an interface whether it is implemented by a class:: >>> IFoo.implementedBy(Foo) True And we can ask whether an interface is provided by an object:: >>> foo = Foo() >>> IFoo.providedBy(foo) True Of course, `Foo` doesn't provide `IFoo`, it implements it:: >>> IFoo.providedBy(Foo) False We can also ask what interfaces are implemented by an object:: >>> list(zope.interface.implementedBy(Foo)) [] It's an error to ask for interfaces implemented by a non-callable object:: >>> IFoo.implementedBy(foo) Traceback (most recent call last): ... TypeError: ('ImplementedBy called for non-factory', Foo(None)) >>> list(zope.interface.implementedBy(foo)) Traceback (most recent call last): ... TypeError: ('ImplementedBy called for non-factory', Foo(None)) Similarly, we can ask what interfaces are provided by an object:: >>> list(zope.interface.providedBy(foo)) [] >>> list(zope.interface.providedBy(Foo)) [] We can declare interfaces implemented by other factories (besides classes). We do this using a Python-2.4-style decorator named `implementer`. In versions of Python before 2.4, this looks like:: >>> def yfoo(y): ... foo = Foo() ... foo.y = y ... return foo >>> yfoo = zope.interface.implementer(IFoo)(yfoo) >>> list(zope.interface.implementedBy(yfoo)) [] Note that the implementer decorator may modify it's argument. Callers should not assume that a new object is created. Using implementer also works on callable objects. This is used by zope.formlib, as an example. >>> class yfactory: ... def __call__(self, y): ... foo = Foo() ... foo.y = y ... return foo >>> yfoo = yfactory() >>> yfoo = zope.interface.implementer(IFoo)(yfoo) >>> list(zope.interface.implementedBy(yfoo)) [] XXX: Double check and update these version numbers: In zope.interface 3.5.2 and lower, the implementor decorator can not be used for classes, but in 3.6.0 and higher it can: >>> Foo = zope.interface.implementer(IFoo)(Foo) >>> list(zope.interface.providedBy(Foo())) [] Note that class decorators using the @implementor(IFoo) syntax are only supported in Python 2.6 and later. Declaring provided interfaces ----------------------------- We can declare interfaces directly provided by objects. Suppose that we want to document what the `__init__` method of the `Foo` class does. It's not *really* part of `IFoo`. You wouldn't normally call the `__init__` method on Foo instances. Rather, the `__init__` method is part of the `Foo`'s `__call__` method:: >>> class IFooFactory(zope.interface.Interface): ... """Create foos""" ... ... def __call__(x=None): ... """Create a foo ... ... The argument provides the initial value for x ... ... """ It's the class that provides this interface, so we declare the interface on the class:: >>> zope.interface.directlyProvides(Foo, IFooFactory) And then, we'll see that Foo provides some interfaces:: >>> list(zope.interface.providedBy(Foo)) [] >>> IFooFactory.providedBy(Foo) True Declaring class interfaces is common enough that there's a special declaration function for it, `classProvides`, that allows the declaration from within a class statement:: >>> class Foo2: ... zope.interface.implements(IFoo) ... zope.interface.classProvides(IFooFactory) ... ... def __init__(self, x=None): ... self.x = x ... ... def bar(self, q, r=None): ... return q, r, self.x ... ... def __repr__(self): ... return "Foo(%s)" % self.x >>> list(zope.interface.providedBy(Foo2)) [] >>> IFooFactory.providedBy(Foo2) True There's a similar function, `moduleProvides`, that supports interface declarations from within module definitions. For example, see the use of `moduleProvides` call in `zope.interface.__init__`, which declares that the package `zope.interface` provides `IInterfaceDeclaration`. Sometimes, we want to declare interfaces on instances, even though those instances get interfaces from their classes. Suppose we create a new interface, `ISpecial`:: >>> class ISpecial(zope.interface.Interface): ... reason = zope.interface.Attribute("Reason why we're special") ... def brag(): ... "Brag about being special" We can make an existing foo instance special by providing `reason` and `brag` attributes:: >>> foo.reason = 'I just am' >>> def brag(): ... return "I'm special!" >>> foo.brag = brag >>> foo.reason 'I just am' >>> foo.brag() "I'm special!" and by declaring the interface:: >>> zope.interface.directlyProvides(foo, ISpecial) then the new interface is included in the provided interfaces:: >>> ISpecial.providedBy(foo) True >>> list(zope.interface.providedBy(foo)) [, ] We can find out what interfaces are directly provided by an object:: >>> list(zope.interface.directlyProvidedBy(foo)) [] >>> newfoo = Foo() >>> list(zope.interface.directlyProvidedBy(newfoo)) [] Inherited declarations ---------------------- Normally, declarations are inherited:: >>> class SpecialFoo(Foo): ... zope.interface.implements(ISpecial) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason >>> list(zope.interface.implementedBy(SpecialFoo)) [, ] >>> list(zope.interface.providedBy(SpecialFoo())) [, ] Sometimes, you don't want to inherit declarations. In that case, you can use `implementsOnly`, instead of `implements`:: >>> class Special(Foo): ... zope.interface.implementsOnly(ISpecial) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason >>> list(zope.interface.implementedBy(Special)) [] >>> list(zope.interface.providedBy(Special())) [] External declarations --------------------- Normally, we make implementation declarations as part of a class definition. Sometimes, we may want to make declarations from outside the class definition. For example, we might want to declare interfaces for classes that we didn't write. The function `classImplements` can be used for this purpose:: >>> class C: ... pass >>> zope.interface.classImplements(C, IFoo) >>> list(zope.interface.implementedBy(C)) [] We can use `classImplementsOnly` to exclude inherited interfaces:: >>> class C(Foo): ... pass >>> zope.interface.classImplementsOnly(C, ISpecial) >>> list(zope.interface.implementedBy(C)) [] Declaration Objects ------------------- When we declare interfaces, we create *declaration* objects. When we query declarations, declaration objects are returned:: >>> type(zope.interface.implementedBy(Special)) Declaration objects and interface objects are similar in many ways. In fact, they share a common base class. The important thing to realize about them is that they can be used where interfaces are expected in declarations. Here's a silly example:: >>> class Special2(Foo): ... zope.interface.implementsOnly( ... zope.interface.implementedBy(Foo), ... ISpecial, ... ) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason The declaration here is almost the same as ``zope.interface.implements(ISpecial)``, except that the order of interfaces in the resulting declaration is different:: >>> list(zope.interface.implementedBy(Special2)) [, ] Interface Inheritance ===================== Interfaces can extend other interfaces. They do this simply by listing the other interfaces as base interfaces:: >>> class IBlat(zope.interface.Interface): ... """Blat blah blah""" ... ... y = zope.interface.Attribute("y blah blah") ... def eek(): ... """eek blah blah""" >>> IBlat.__bases__ (,) >>> class IBaz(IFoo, IBlat): ... """Baz blah""" ... def eek(a=1): ... """eek in baz blah""" ... >>> IBaz.__bases__ (, ) >>> names = list(IBaz) >>> names.sort() >>> names ['bar', 'eek', 'x', 'y'] Note that `IBaz` overrides eek:: >>> IBlat['eek'].__doc__ 'eek blah blah' >>> IBaz['eek'].__doc__ 'eek in baz blah' We were careful to override eek in a compatible way. When extending an interface, the extending interface should be compatible [#compat]_ with the extended interfaces. We can ask whether one interface extends another:: >>> IBaz.extends(IFoo) True >>> IBlat.extends(IFoo) False Note that interfaces don't extend themselves:: >>> IBaz.extends(IBaz) False Sometimes we wish they did, but we can, instead use `isOrExtends`:: >>> IBaz.isOrExtends(IBaz) True >>> IBaz.isOrExtends(IFoo) True >>> IFoo.isOrExtends(IBaz) False When we iterate over an interface, we get all of the names it defines, including names defined by base interfaces. Sometimes, we want *just* the names defined by the interface directly. We bane use the `names` method for that:: >>> list(IBaz.names()) ['eek'] Inheritance of attribute specifications --------------------------------------- An interface may override attribute definitions from base interfaces. If two base interfaces define the same attribute, the attribute is inherited from the most specific interface. For example, with:: >>> class IBase(zope.interface.Interface): ... ... def foo(): ... "base foo doc" >>> class IBase1(IBase): ... pass >>> class IBase2(IBase): ... ... def foo(): ... "base2 foo doc" >>> class ISub(IBase1, IBase2): ... pass ISub's definition of foo is the one from IBase2, since IBase2 is more specific that IBase:: >>> ISub['foo'].__doc__ 'base2 foo doc' Note that this differs from a depth-first search. Sometimes, it's useful to ask whether an interface defines an attribute directly. You can use the direct method to get a directly defined definitions:: >>> IBase.direct('foo').__doc__ 'base foo doc' >>> ISub.direct('foo') Specifications -------------- Interfaces and declarations are both special cases of specifications. What we described above for interface inheritance applies to both declarations and specifications. Declarations actually extend the interfaces that they declare:: >>> class Baz(object): ... zope.interface.implements(IBaz) >>> baz_implements = zope.interface.implementedBy(Baz) >>> baz_implements.__bases__ (, ) >>> baz_implements.extends(IFoo) True >>> baz_implements.isOrExtends(IFoo) True >>> baz_implements.isOrExtends(baz_implements) True Specifications (interfaces and declarations) provide an `__sro__` that lists the specification and all of it's ancestors:: >>> baz_implements.__sro__ (, , , , , ) Tagged Values ============= Interfaces and attribute descriptions support an extension mechanism, borrowed from UML, called "tagged values" that lets us store extra data:: >>> IFoo.setTaggedValue('date-modified', '2004-04-01') >>> IFoo.setTaggedValue('author', 'Jim Fulton') >>> IFoo.getTaggedValue('date-modified') '2004-04-01' >>> IFoo.queryTaggedValue('date-modified') '2004-04-01' >>> IFoo.queryTaggedValue('datemodified') >>> tags = list(IFoo.getTaggedValueTags()) >>> tags.sort() >>> tags ['author', 'date-modified'] Function attributes are converted to tagged values when method attribute definitions are created:: >>> class IBazFactory(zope.interface.Interface): ... def __call__(): ... "create one" ... __call__.return_type = IBaz >>> IBazFactory['__call__'].getTaggedValue('return_type') Tagged values can also be defined from within an interface definition:: >>> class IWithTaggedValues(zope.interface.Interface): ... zope.interface.taggedValue('squish', 'squash') >>> IWithTaggedValues.getTaggedValue('squish') 'squash' Invariants ========== Interfaces can express conditions that must hold for objects that provide them. These conditions are expressed using one or more invariants. Invariants are callable objects that will be called with an object that provides an interface. An invariant raises an `Invalid` exception if the condition doesn't hold. Here's an example:: >>> class RangeError(zope.interface.Invalid): ... """A range has invalid limits""" ... def __repr__(self): ... return "RangeError(%r)" % self.args >>> def range_invariant(ob): ... if ob.max < ob.min: ... raise RangeError(ob) Given this invariant, we can use it in an interface definition:: >>> class IRange(zope.interface.Interface): ... min = zope.interface.Attribute("Lower bound") ... max = zope.interface.Attribute("Upper bound") ... ... zope.interface.invariant(range_invariant) Interfaces have a method for checking their invariants:: >>> class Range(object): ... zope.interface.implements(IRange) ... ... def __init__(self, min, max): ... self.min, self.max = min, max ... ... def __repr__(self): ... return "Range(%s, %s)" % (self.min, self.max) >>> IRange.validateInvariants(Range(1,2)) >>> IRange.validateInvariants(Range(1,1)) >>> IRange.validateInvariants(Range(2,1)) Traceback (most recent call last): ... RangeError: Range(2, 1) If you have multiple invariants, you may not want to stop checking after the first error. If you pass a list to `validateInvariants`, then a single `Invalid` exception will be raised with the list of exceptions as it's argument:: >>> from zope.interface.exceptions import Invalid >>> errors = [] >>> try: ... IRange.validateInvariants(Range(2,1), errors) ... except Invalid, e: ... str(e) '[RangeError(Range(2, 1))]' And the list will be filled with the individual exceptions:: >>> errors [RangeError(Range(2, 1))] >>> del errors[:] Adaptation ========== Interfaces can be called to perform adaptation. The semantics are based on those of the PEP 246 adapt function. If an object cannot be adapted, then a TypeError is raised:: >>> class I(zope.interface.Interface): ... pass >>> I(0) Traceback (most recent call last): ... TypeError: ('Could not adapt', 0, ) unless an alternate value is provided as a second positional argument:: >>> I(0, 'bob') 'bob' If an object already implements the interface, then it will be returned:: >>> class C(object): ... zope.interface.implements(I) >>> obj = C() >>> I(obj) is obj True If an object implements __conform__, then it will be used:: >>> class C(object): ... zope.interface.implements(I) ... def __conform__(self, proto): ... return 0 >>> I(C()) 0 Adapter hooks (see __adapt__) will also be used, if present:: >>> from zope.interface.interface import adapter_hooks >>> def adapt_0_to_42(iface, obj): ... if obj == 0: ... return 42 >>> adapter_hooks.append(adapt_0_to_42) >>> I(0) 42 >>> adapter_hooks.remove(adapt_0_to_42) >>> I(0) Traceback (most recent call last): ... TypeError: ('Could not adapt', 0, ) __adapt__ --------- >>> class I(zope.interface.Interface): ... pass Interfaces implement the PEP 246 __adapt__ method. This method is normally not called directly. It is called by the PEP 246 adapt framework and by the interface __call__ operator. The adapt method is responsible for adapting an object to the reciever. The default version returns None:: >>> I.__adapt__(0) unless the object given provides the interface:: >>> class C(object): ... zope.interface.implements(I) >>> obj = C() >>> I.__adapt__(obj) is obj True Adapter hooks can be provided (or removed) to provide custom adaptation. We'll install a silly hook that adapts 0 to 42. We install a hook by simply adding it to the adapter_hooks list:: >>> from zope.interface.interface import adapter_hooks >>> def adapt_0_to_42(iface, obj): ... if obj == 0: ... return 42 >>> adapter_hooks.append(adapt_0_to_42) >>> I.__adapt__(0) 42 Hooks must either return an adapter, or None if no adapter can be found. Hooks can be uninstalled by removing them from the list:: >>> adapter_hooks.remove(adapt_0_to_42) >>> I.__adapt__(0) .. [#create] The main reason we subclass `Interface` is to cause the Python class statement to create an interface, rather than a class. It's possible to create interfaces by calling a special interface class directly. Doing this, it's possible (and, on rare occasions, useful) to create interfaces that don't descend from `Interface`. Using this technique is beyond the scope of this document. .. [#factory] Classes are factories. They can be called to create their instances. We expect that we will eventually extend the concept of implementation to other kinds of factories, so that we can declare the interfaces provided by the objects created. .. [#compat] The goal is substitutability. An object that provides an extending interface should be substitutable for an object that provides the extended interface. In our example, an object that provides IBaz should be usable whereever an object that provides IBlat is expected. The interface implementation doesn't enforce this. but maybe it should do some checks. zope2.13-2.13.21/source/zope.interface/src/zope/interface/document.py0000644000175000017500000000672012214017572024255 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ Pretty-Print an Interface object as structured text (Yum) This module provides a function, asStructuredText, for rendering an interface as structured text. $Id: document.py 110536 2010-04-06 02:59:44Z tseaver $ """ import zope.interface def asStructuredText(I, munge=0): """ Output structured text format. Note, this will whack any existing 'structured' format of the text. """ r = [I.getName()] outp = r.append level = 1 if I.getDoc(): outp(_justify_and_indent(_trim_doc_string(I.getDoc()), level)) bases = [base for base in I.__bases__ if base is not zope.interface.Interface ] if bases: outp(_justify_and_indent("This interface extends:", level, munge)) level += 1 for b in bases: item = "o %s" % b.getName() outp(_justify_and_indent(_trim_doc_string(item), level, munge)) level -= 1 namesAndDescriptions = I.namesAndDescriptions() namesAndDescriptions.sort() outp(_justify_and_indent("Attributes:", level, munge)) level += 1 for name, desc in namesAndDescriptions: if not hasattr(desc, 'getSignatureString'): # ugh... item = "%s -- %s" % (desc.getName(), desc.getDoc() or 'no documentation') outp(_justify_and_indent(_trim_doc_string(item), level, munge)) level -= 1 outp(_justify_and_indent("Methods:", level, munge)) level += 1 for name, desc in namesAndDescriptions: if hasattr(desc, 'getSignatureString'): # ugh... item = "%s%s -- %s" % (desc.getName(), desc.getSignatureString(), desc.getDoc() or 'no documentation') outp(_justify_and_indent(_trim_doc_string(item), level, munge)) return "\n\n".join(r) + "\n\n" def _trim_doc_string(text): """ Trims a doc string to make it format correctly with structured text. """ lines = text.replace('\r\n', '\n').split('\n') nlines = [lines.pop(0)] if lines: min_indent = min([len(line) - len(line.lstrip()) for line in lines]) for line in lines: nlines.append(line[min_indent:]) return '\n'.join(nlines) def _justify_and_indent(text, level, munge=0, width=72): """ indent and justify text, rejustify (munge) if specified """ indent = " " * level if munge: lines = [] line = indent text = text.split() for word in text: line = ' '.join([line, word]) if len(line) > width: lines.append(line) line = indent else: lines.append(line) return '\n'.join(lines) else: return indent + \ text.strip().replace("\r\n", "\n") .replace("\n", "\n" + indent) zope2.13-2.13.21/source/zope.interface/src/zope/interface/verify.py0000644000175000017500000001066612214017572023747 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Verify interface implementations $Id: verify.py 110699 2010-04-09 08:16:17Z regebro $ """ from zope.interface.exceptions import BrokenImplementation, DoesNotImplement from zope.interface.exceptions import BrokenMethodImplementation from types import FunctionType, MethodType from zope.interface.interface import fromMethod, fromFunction, Method import sys # This will be monkey-patched when running under Zope 2, so leave this # here: MethodTypes = (MethodType, ) def _verify(iface, candidate, tentative=0, vtype=None): """Verify that 'candidate' might correctly implements 'iface'. This involves: o Making sure the candidate defines all the necessary methods o Making sure the methods have the correct signature o Making sure the candidate asserts that it implements the interface Note that this isn't the same as verifying that the class does implement the interface. If optional tentative is true, suppress the "is implemented by" test. """ if vtype == 'c': tester = iface.implementedBy else: tester = iface.providedBy if not tentative and not tester(candidate): raise DoesNotImplement(iface) # Here the `desc` is either an `Attribute` or `Method` instance for name, desc in iface.namesAndDescriptions(1): try: attr = getattr(candidate, name) except AttributeError: if (not isinstance(desc, Method)) and vtype == 'c': # We can't verify non-methods on classes, since the # class may provide attrs in it's __init__. continue raise BrokenImplementation(iface, name) if not isinstance(desc, Method): # If it's not a method, there's nothing else we can test continue if isinstance(attr, FunctionType): if sys.version[0] == '3' and isinstance(candidate, type): # This is an "unbound method" in Python 3. meth = fromFunction(attr, iface, name=name, imlevel=1) else: # Nope, just a normal function meth = fromFunction(attr, iface, name=name) elif (isinstance(attr, MethodTypes) and type(attr.im_func) is FunctionType): meth = fromMethod(attr, iface, name) else: if not callable(attr): raise BrokenMethodImplementation(name, "Not a method") # sigh, it's callable, but we don't know how to intrspect it, so # we have to give it a pass. continue # Make sure that the required and implemented method signatures are # the same. desc = desc.getSignatureInfo() meth = meth.getSignatureInfo() mess = _incompat(desc, meth) if mess: raise BrokenMethodImplementation(name, mess) return True def verifyClass(iface, candidate, tentative=0): return _verify(iface, candidate, tentative, vtype='c') def verifyObject(iface, candidate, tentative=0): return _verify(iface, candidate, tentative, vtype='o') def _incompat(required, implemented): #if (required['positional'] != # implemented['positional'][:len(required['positional'])] # and implemented['kwargs'] is None): # return 'imlementation has different argument names' if len(implemented['required']) > len(required['required']): return 'implementation requires too many arguments' if ((len(implemented['positional']) < len(required['positional'])) and not implemented['varargs']): return "implementation doesn't allow enough arguments" if required['kwargs'] and not implemented['kwargs']: return "implementation doesn't support keyword arguments" if required['varargs'] and not implemented['varargs']: return "implementation doesn't support variable arguments" zope2.13-2.13.21/source/zope.interface/src/zope/interface/verify.txt0000644000175000017500000000641412214017572024132 0ustar arnauarnau=================================== Verifying interface implementations =================================== The ``zope.interface.verify`` module provides functions that test whether a given interface is implemented by a class or provided by an object, resp. Verifying classes ================= This is covered by unit tests defined in ``zope.interface.tests.test_verify``. Verifying objects ================= An object provides an interface if - either its class declares that it implements the interfaces, or the object declares that it directly provides the interface - the object defines all the methods required by the interface - all the methods have the correct signature - the object defines all non-method attributes required by the interface This doctest currently covers only the latter item. Testing for attributes ---------------------- Attributes of the object, be they defined by its class or added by its ``__init__`` method, will be recognized: >>> from zope.interface import Interface, Attribute, implements >>> from zope.interface.exceptions import BrokenImplementation >>> class IFoo(Interface): ... x = Attribute("The X attribute") ... y = Attribute("The Y attribute") >>> class Foo(object): ... implements(IFoo) ... x = 1 ... def __init__(self): ... self.y = 2 >>> from zope.interface.verify import verifyObject >>> verifyObject(IFoo, Foo()) True If either attribute is missing, verification will fail: >>> class Foo(object): ... implements(IFoo) ... x = 1 >>> try: #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS ... verifyObject(IFoo, Foo()) ... except BrokenImplementation, e: ... print str(e) An object has failed to implement interface The y attribute was not provided. >>> class Foo(object): ... implements(IFoo) ... def __init__(self): ... self.y = 2 >>> try: #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS ... verifyObject(IFoo, Foo()) ... except BrokenImplementation, e: ... print str(e) An object has failed to implement interface The x attribute was not provided. If an attribute is implemented as a property that raises an AttributeError when trying to get its value, the attribute is considered missing: >>> class IFoo(Interface): ... x = Attribute('The X attribute') >>> class Foo(object): ... implements(IFoo) ... @property ... def x(self): ... raise AttributeError >>> try: #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS ... verifyObject(IFoo, Foo()) ... except BrokenImplementation, e: ... print str(e) An object has failed to implement interface The x attribute was not provided. Any other exception raised by a property will propagate to the caller of ``verifyObject``: >>> class Foo(object): ... implements(IFoo) ... @property ... def x(self): ... raise Exception >>> verifyObject(IFoo, Foo()) Traceback (most recent call last): Exception Of course, broken properties that are not required by the interface don't do any harm: >>> class Foo(object): ... implements(IFoo) ... x = 1 ... @property ... def y(self): ... raise Exception >>> verifyObject(IFoo, Foo()) True zope2.13-2.13.21/source/zope.interface/src/zope/interface/human.ru.txt0000644000175000017500000002467112214017572024370 0ustar arnauarnau=============================== ИÑпользование рееÑтра адаптеров =============================== Данный документ Ñодержит небольшую демонÑтрацию пакета ``zope.interface`` и его рееÑтра адаптеров. Документ раÑÑчитывалÑÑ ÐºÐ°Ðº конкретный, но более узкий пример того как иÑпользовать интерфейÑÑ‹ и адаптеры вне Zope 3. Сначала нам необходимо импортировать пакет Ð´Ð»Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñами:: >>> import zope.interface Теперь мы разработаем Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð´Ð»Ñ Ð½Ð°ÑˆÐµÐ³Ð¾ объекта - проÑтого файла. Ðаш файл будет Ñодержать вÑего один атрибут - body, в котором фактичеÑки будет Ñохранено Ñодержимое файла:: >>> class IFile(zope.interface.Interface): ... ... body = zope.interface.Attribute(u'Содержимое файла.') ... Ð”Ð»Ñ ÑтатиÑтики нам чаÑто необходимо знать размер файла. Ðо было бы неÑколько топорно реализовывать определение размера прÑмо Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð° файла, Ñ‚.к. размер больше отноÑитÑÑ Ðº мета-данным. Таким образом мы Ñоздаем еще один Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´ÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€Ð° какого-либо объекта:: >>> class ISize(zope.interface.Interface): ... ... def getSize(): ... 'Return the size of an object.' ... Теперь мы должны Ñоздать клаÑÑ Ñ€ÐµÐ°Ð»Ð¸Ð·ÑƒÑŽÑ‰Ð¸Ð¹ наш файл. Ðеобходимо что бы наш объект хранил информацию о том, что он реализует Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ `IFile`. Мы также Ñоздаем атрибут Ñ Ñодержимым файла по умолчанию (Ð´Ð»Ñ ÑƒÐ¿Ñ€Ð¾Ñ‰ÐµÐ½Ð¸Ñ Ð½Ð°ÑˆÐµÐ³Ð¾ примера):: >>> class File(object): ... ... zope.interface.implements(IFile) ... body = 'foo bar' ... Дальше мы Ñоздаем адаптер, который будет предоÑтавлÑть Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ `ISize` Ð¿Ð¾Ð»ÑƒÑ‡Ð°Ñ Ð»ÑŽÐ±Ð¾Ð¹ объект предоÑтавлÑющий Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ `IFile`. По Ñоглашению мы иÑпользуем атрибут `__used_for__` Ð´Ð»Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñа который как мы ожидаем предоÑтавлÑет адаптируемый объект, `IFile` в нашем Ñлучае. Ðа Ñамом деле Ñтот атрибут иÑпользуетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ Ð´Ð»Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ. Ð’ Ñлучае еÑли адаптер иÑпользуетÑÑ Ð´Ð»Ñ Ð½ÐµÑкольких интерфейÑов можно указать их вÑе в виде кортежа. ОпÑть же по Ñоглашению конÑтруктор адаптера получает один аргумент - context (контекÑÑ‚). Ð’ нашем Ñлучае контекÑÑ‚ - Ñто ÑкземплÑÑ€ `IFile` (объект, предоÑтавлÑющий `IFile`) который иÑпользуетÑÑ Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¸Ð· него размера. Так же по Ñоглашению контекÑÑ‚ ÑохранÑетÑÑ Ð° адаптере в атрибуте Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ `context`. Twisted комьюнити ÑÑылаетÑÑ Ð½Ð° контекÑÑ‚ как на объект `original`. Таким образом можно также дать аргументу любое подходÑщее имÑ, например `file`:: >>> class FileSize(object): ... ... zope.interface.implements(ISize) ... __used_for__ = IFile ... ... def __init__(self, context): ... self.context = context ... ... def getSize(self): ... return len(self.context.body) ... Теперь когда мы напиÑали наш адаптер мы должны зарегиÑтрировать его в рееÑтре адаптеров, что бы его можно было запроÑить когда он понадобитÑÑ. ЗдеÑÑŒ нет какого-либо глобального рееÑтра адаптеров, таким образом мы должны ÑамоÑтоÑтельно Ñоздать Ð´Ð»Ñ Ð½Ð°ÑˆÐµÐ³Ð¾ примера рееÑтр:: >>> from zope.interface.adapter import AdapterRegistry >>> registry = AdapterRegistry() РееÑтр Ñодержит отображение того, что адаптер реализует на оÑнове другого интерфейÑа который предоÑтавлÑет объект. ПоÑтому дальше мы региÑтрируем адаптер который адаптирует Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ `IFile` к интерфейÑу `ISize`. Первый аргумент к методу `register()` рееÑтра - Ñто ÑпиÑок адаптируемых интерфейÑов. Ð’ нашем Ñлучае мы имеем только один адаптируемый Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ - `IFile`. СпиÑок интерфейÑов имеет ÑмыÑл Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÐºÐ¾Ð½Ñ†ÐµÐ¿Ñ†Ð¸Ð¸ мульти-адаптеров, которые требуют неÑкольких оригинальных объектов Ð´Ð»Ñ Ð°Ð´Ð°Ð¿Ñ‚Ð°Ñ†Ð¸Ð¸ к новому интерфейÑу. Ð’ Ñтой Ñитуации конÑтруктор адаптера будет требовать новый аргумент Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ оригинального интерфейÑа. Второй аргумент метода `register()` - Ñто Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¹ предоÑтавлÑет адаптер, в нашем Ñлучае `ISize`. Третий аргумент - Ð¸Ð¼Ñ Ð°Ð´Ð°Ð¿Ñ‚ÐµÑ€Ð°. Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð½Ð°Ð¼ не важно Ð¸Ð¼Ñ Ð°Ð´Ð°Ð¿Ñ‚ÐµÑ€Ð° и мы передаем его как пуÑтую Ñтроку. Обычно имена полезны еÑли иÑпользуютÑÑ Ð°Ð´Ð°Ð¿Ñ‚ÐµÑ€Ñ‹ Ð´Ð»Ñ Ð¾Ð´Ð¸Ð½Ð°ÐºÐ¾Ð²Ð¾Ð³Ð¾ набора интерфейÑов, но в различных ÑитуациÑÑ…. ПоÑледний аргумент - Ñто клаÑÑ Ð°Ð´Ð°Ð¿Ñ‚ÐµÑ€Ð°:: >>> registry.register([IFile], ISize, '', FileSize) Теперь мы можем иÑпользовать рееÑтр Ð´Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа адаптера:: >>> registry.lookup1(IFile, ISize, '') Попробуем более практичный пример. Создадим ÑкземплÑÑ€ `File` и Ñоздадим адаптер иÑпользующий Ð·Ð°Ð¿Ñ€Ð¾Ñ Ñ€ÐµÐµÑтра. Затем мы увидим возвращает ли адаптер корректный размер при вызове `getSize()`:: >>> file = File() >>> size = registry.lookup1(IFile, ISize, '')(file) >>> size.getSize() 7 Ðа Ñамом деле Ñто не очень практично, Ñ‚.к. нам нужно Ñамим передавать вÑе аргументы методу запроÑа. СущеÑтвует некоторый ÑинтакÑичеÑкий леденец который позволÑет нам получить ÑкземплÑÑ€ адаптера проÑто вызвав `ISize(file)`. Что бы иÑпользовать Ñту функциональноÑть нам понадобитÑÑ Ð´Ð¾Ð±Ð°Ð²Ð¸Ñ‚ÑŒ наш рееÑтр к ÑпиÑку adapter_hooks, который находитÑÑ Ð² модуле Ñ Ð°Ð´Ð°Ð¿Ñ‚ÐµÑ€Ð°Ð¼Ð¸. Этот ÑпиÑок хранит коллекцию вызываемых объектов которые вызываютÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки когда вызываетÑÑ IFoo(obj); их предназначение - найти адаптеры которые реализуют Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð½Ð¾Ð³Ð¾ ÑкземплÑра контекÑта. Ðеобходимо реализовать Ñвою ÑобÑтвенную функцию Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка адаптера; данный пример опиÑывает одну из проÑтейших функций Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ Ñ€ÐµÐµÑтром, но также можно реализовать поиÑковые функции которые, например, иÑпользуют кÑширование, или адаптеры ÑохранÑемые в базе. Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð¿Ð¾Ð¸Ñка должна принимать желаемый на выходе Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ (в нашем Ñлучае `ISize`) как первый аргумент и контекÑÑ‚ Ð´Ð»Ñ Ð°Ð´Ð°Ð¿Ñ‚Ð°Ñ†Ð¸Ð¸ (`file`) как второй. Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð´Ð¾Ð»Ð¶Ð½Ð° вернуть адаптер, Ñ‚.е. ÑкземплÑÑ€ `FileSize`:: >>> def hook(provided, object): ... adapter = registry.lookup1(zope.interface.providedBy(object), ... provided, '') ... return adapter(object) ... Теперь мы проÑто добавлÑем нашу функцию к ÑпиÑку `adapter_hooks`:: >>> from zope.interface.interface import adapter_hooks >>> adapter_hooks.append(hook) Как только Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ Ð·Ð°Ñ€ÐµÐ³Ð¸Ñтрирована мы можем иÑпользовать желаемый ÑинтакÑиÑ:: >>> size = ISize(file) >>> size.getSize() 7 ПоÑле нам нужно прибратьÑÑ Ð·Ð° Ñобой, что бы другие получили чиÑтый ÑпиÑок `adaper_hooks` поÑле наÑ:: >>> adapter_hooks.remove(hook) Это вÑе. ЗдеÑÑŒ намеренно отложена диÑкуÑÑÐ¸Ñ Ð¾Ð± именованных и мульти-адаптерах, Ñ‚.к. данный текÑÑ‚ раÑÑчитан как практичеÑкое и проÑтое введение в интерфейÑÑ‹ и адаптеры Zope 3. Ð”Ð»Ñ Ð±Ð¾Ð»ÐµÐµ подробной информации имеет ÑмыÑл прочитать `adapter.txt` из пакета `zope.interface`, что бы получить более формальное, Ñправочное и полное трактование пакета. Внимание: многие жаловалиÑÑŒ, что `adapter.txt` приводит их мозг к раÑплавленному ÑоÑтоÑнию! zope2.13-2.13.21/source/zope.interface/src/zope/interface/adapter.ru.txt0000644000175000017500000005126112214017572024673 0ustar arnauarnau================ РееÑтр адаптеров ================ .. contents:: РееÑтры адаптеров предоÑтавлÑÑŽÑ‚ возможноÑть Ð´Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации объектов которые завиÑÑÑ‚ от одной, или неÑкольких Ñпецификаций интерфейÑов и предоÑтавлÑÑŽÑ‚ (возможно не напрÑмую) какой-либо интерфейÑ. Ð’ дополнение, региÑтрации имеют имена. (Можно думать об именах как о Ñпецификаторах предоÑтавлÑемого интерфейÑа.) Термин "ÑÐ¿ÐµÑ†Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñа" ÑÑылаетÑÑ Ð¸ на интерфейÑÑ‹ и на Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñов, такие как Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñов реализованных некоторым клаÑÑом. Одиночные адаптеры ================== Давайте раÑÑмотрим проÑтой пример иÑпользующий единÑтвенную требуемую Ñпецификацию:: >>> from zope.interface.adapter import AdapterRegistry >>> import zope.interface >>> class IR1(zope.interface.Interface): ... pass >>> class IP1(zope.interface.Interface): ... pass >>> class IP2(IP1): ... pass >>> registry = AdapterRegistry() Мы зарегиÑтрируем объект который завиÑит от IR1 и "предоÑтавлÑет" IP2:: >>> registry.register([IR1], IP2, '', 12) ПоÑле региÑтрации мы можем запроÑить объект Ñнова:: >>> registry.lookup([IR1], IP2, '') 12 Заметьте, что мы иÑпользуем целое в Ñтом примере. Ð’ реальных приложениÑÑ… вы можете иÑпользовать объекты которые на Ñамом деле завиÑÑÑ‚ или предоÑтавлÑÑŽÑ‚ интерфейÑÑ‹. РееÑтр не заботитьÑÑ Ð¾ том, что региÑтрируетÑÑ Ð¸ таким образом мы можем иÑпользовать целые, или Ñтроки что бы упроÑтить наши примеры. ЗдеÑÑŒ еÑть одно иÑключение. РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ None удалÑет региÑтрацию Ð´Ð»Ñ Ð»ÑŽÐ±Ð¾Ð³Ð¾ зарегиÑтрированного прежде значениÑ. ЕÑли объект завиÑит от Ñпецификации он может быть запрошен Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ Ñпецификации ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ñ€Ð°ÑширÑет Ñпецификацию от которой он завиÑит:: >>> class IR2(IR1): ... pass >>> registry.lookup([IR2], IP2, '') 12 Мы можем иÑпользовать клаÑÑ Ñ€ÐµÐ°Ð»Ð¸Ð·ÑƒÑŽÑ‰Ð¸Ð¹ Ñпецификацию Ð´Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа объекта:: >>> class C2: ... zope.interface.implements(IR2) >>> registry.lookup([zope.interface.implementedBy(C2)], IP2, '') 12 и объект может быть запрошен Ð´Ð»Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñов которые предоÑтавлÑемый объектом Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ñ€Ð°ÑширÑет:: >>> registry.lookup([IR1], IP1, '') 12 >>> registry.lookup([IR2], IP1, '') 12 Ðо еÑли вы требуете Ñпецификацию ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð½Ðµ раÑширÑет Ñпецификацию от которой завиÑит объект, вы не получите ничего:: >>> registry.lookup([zope.interface.Interface], IP1, '') Между прочим, вы можете передать значение по умолчанию при запроÑе:: >>> registry.lookup([zope.interface.Interface], IP1, '', 42) 42 ЕÑли вы пробуете получить Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¹ объект не предоÑтавлÑет вы также не получите ничего:: >>> class IP3(IP2): ... pass >>> registry.lookup([IR1], IP3, '') Ð’Ñ‹ также не получите ничего еÑли вы иÑпользуете неверное имÑ:: >>> registry.lookup([IR1], IP1, 'bob') >>> registry.register([IR1], IP2, 'bob', "Bob's 12") >>> registry.lookup([IR1], IP1, 'bob') "Bob's 12" Ð’Ñ‹ можете не иÑпользовать Ð¸Ð¼Ñ Ð¿Ñ€Ð¸ запроÑе:: >>> registry.lookup([IR1], IP1) 12 ЕÑли мы региÑтрируем объект который предоÑтавлÑет IP1:: >>> registry.register([IR1], IP1, '', 11) тогда Ñтот объект будет иметь преимущеÑтво перед O(12):: >>> registry.lookup([IR1], IP1, '') 11 Также, еÑли мы региÑтрируем объект Ð´Ð»Ñ IR2 тогда он будет иметь преимущеÑтво когда иÑпользуетÑÑ IR2:: >>> registry.register([IR2], IP1, '', 21) >>> registry.lookup([IR2], IP1, '') 21 ПоиÑк того, что (еÑли вообще что-то) зарегиÑтрировано ----------------------------------------------------- Мы можем ÑпроÑить еÑть-ли адаптер зарегиÑтрированный Ð´Ð»Ñ Ð½Ð°Ð±Ð¾Ñ€Ð° интерфейÑов. Это отличаетÑÑ Ð¾Ñ‚ обычного запроÑа так как здеÑÑŒ мы ищем точное Ñовпадение:: >>> print registry.registered([IR1], IP1) 11 >>> print registry.registered([IR1], IP2) 12 >>> print registry.registered([IR1], IP2, 'bob') Bob's 12 >>> print registry.registered([IR2], IP1) 21 >>> print registry.registered([IR2], IP2) None Ð’ поÑледнем примере, None был возвращен потому, что Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ интерфейÑа ничего не было зарегиÑтрировано. lookup1 ------- Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð¾Ð´Ð¸Ð½Ð¾Ñ‡Ð½Ð¾Ð³Ð¾ адаптера - Ñто наиболее чаÑÑ‚Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¸ Ð´Ð»Ñ Ð½ÐµÐµ еÑть ÑÐ¿ÐµÑ†Ð¸Ð°Ð»Ð¸Ð·Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð¿Ð¾Ð»ÑƒÑ‡Ð°ÐµÑ‚ на вход единÑтвенный требуемый интерфейÑ:: >>> registry.lookup1(IR2, IP1, '') 21 >>> registry.lookup1(IR2, IP1) 21 ÐÐ´Ð°Ð¿Ñ‚Ð°Ñ†Ð¸Ñ Ð½Ð° практике --------------------- РееÑтр адаптеров предназначен Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶ÐºÐ¸ адаптации когда один объект реализующий Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð°Ð´Ð°Ð¿Ñ‚Ð¸Ñ€ÑƒÐµÑ‚ÑÑ Ðº другому объекту который поддерживает другой интерфейÑ. РееÑтр адаптеров также поддерживает вычиÑление адаптеров. Ð’ Ñтом Ñлучае мы должны региÑтрировать фабрики Ð´Ð»Ñ Ð°Ð´Ð°Ð¿Ñ‚ÐµÑ€Ð¾Ð²:: >>> class IR(zope.interface.Interface): ... pass >>> class X: ... zope.interface.implements(IR) >>> class Y: ... zope.interface.implements(IP1) ... def __init__(self, context): ... self.context = context >>> registry.register([IR], IP1, '', Y) Ð’ Ñтом Ñлучае мы региÑтрируем клаÑÑ ÐºÐ°Ðº фабрику. Теперь мы можем вызвать `queryAdapter` Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð°Ð´Ð°Ð¿Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð¾Ð³Ð¾ объекта:: >>> x = X() >>> y = registry.queryAdapter(x, IP1) >>> y.__class__.__name__ 'Y' >>> y.context is x True Мы также можем региÑтрировать и запрашивать по имени:: >>> class Y2(Y): ... pass >>> registry.register([IR], IP1, 'bob', Y2) >>> y = registry.queryAdapter(x, IP1, 'bob') >>> y.__class__.__name__ 'Y2' >>> y.context is x True Когда фабрика Ð´Ð»Ñ Ð°Ð´Ð°Ð¿Ñ‚ÐµÑ€Ð° возвращает `None` - Ñто раÑÑматриваетÑÑ ÐºÐ°Ðº еÑли бы адаптер не был найден. Это позволÑет нам избежать адаптации (по желанию) и дает возможноÑть фабрике адаптера определить возможна ли Ð°Ð´Ð°Ð¿Ñ‚Ð°Ñ†Ð¸Ñ Ð¾ÑновываÑÑÑŒ на ÑоÑтоÑнии объекта который адаптируетÑÑ:: >>> def factory(context): ... if context.name == 'object': ... return 'adapter' ... return None >>> class Object(object): ... zope.interface.implements(IR) ... name = 'object' >>> registry.register([IR], IP1, 'conditional', factory) >>> obj = Object() >>> registry.queryAdapter(obj, IP1, 'conditional') 'adapter' >>> obj.name = 'no object' >>> registry.queryAdapter(obj, IP1, 'conditional') is None True >>> registry.queryAdapter(obj, IP1, 'conditional', 'default') 'default' Ðльтернативный метод Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´Ð¾ÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ‚Ð°ÐºÐ¾Ð¹ же функциональноÑти как и `queryAdapter()` - Ñто `adapter_hook()`:: >>> y = registry.adapter_hook(IP1, x) >>> y.__class__.__name__ 'Y' >>> y.context is x True >>> y = registry.adapter_hook(IP1, x, 'bob') >>> y.__class__.__name__ 'Y2' >>> y.context is x True `adapter_hook()` проÑто менÑет порÑдок аргументов Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð° и интерфейÑа. Это иÑпользуетÑÑ Ð´Ð»Ñ Ð²ÑÑ‚Ñ€Ð°Ð¸Ð²Ð°Ð½Ð¸Ñ Ð² механизм вызовов интерфейÑов. Ðдаптеры по умолчанию --------------------- Иногда вы можете захотеть предоÑтавить адаптер который не будет ничего адаптировать. Ð”Ð»Ñ Ñтого нужно передать None как требуемый интерфейÑ:: >>> registry.register([None], IP1, '', 1) поÑле Ñтого вы можете иÑпользовать Ñтот адаптер Ð´Ð»Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñов Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… у Ð²Ð°Ñ Ð½ÐµÑ‚ конкретного адаптера:: >>> class IQ(zope.interface.Interface): ... pass >>> registry.lookup([IQ], IP1, '') 1 Конечно, конкретные адаптеры вÑе еще иÑпользуютÑÑ ÐºÐ¾Ð³Ð´Ð° необходимо:: >>> registry.lookup([IR2], IP1, '') 21 Ðдаптеры клаÑÑов ---------------- Ð’Ñ‹ можете региÑтрировать адаптеры Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ð¹ клаÑÑов, что будет похоже на региÑтрацию их Ð´Ð»Ñ ÐºÐ»Ð°ÑÑов:: >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', 'C21') >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') 'C21' Ðдаптеры Ð´Ð»Ñ Ñловарей --------------------- Ð’ какой-то момент было невозможно региÑтрировать адаптеры оÑнованные на ÑловарÑÑ… из-за ошибки. Давайте удоÑтоверимÑÑ Ñ‡Ñ‚Ð¾ Ñто теперь работает:: >>> adapter = {} >>> registry.register((), IQ, '', adapter) >>> registry.lookup((), IQ, '') is adapter True Удаление региÑтрации -------------------- Ð’Ñ‹ можете удалить региÑтрацию региÑÑ‚Ñ€Ð¸Ñ€ÑƒÑ None вмеÑто объекта:: >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', None) >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') 21 Конечно Ñто значит, что None не может быть зарегиÑтрирован. Это иÑключение к утверждению выше о том, что рееÑтр не заботитьÑÑ Ð¾ том, что региÑтрируетÑÑ. Мульти-адаптеры =============== Ð’Ñ‹ можете адаптировать неÑколько Ñпецификаций:: >>> registry.register([IR1, IQ], IP2, '', '1q2') >>> registry.lookup([IR1, IQ], IP2, '') '1q2' >>> registry.lookup([IR2, IQ], IP1, '') '1q2' >>> class IS(zope.interface.Interface): ... pass >>> registry.lookup([IR2, IS], IP1, '') >>> class IQ2(IQ): ... pass >>> registry.lookup([IR2, IQ2], IP1, '') '1q2' >>> registry.register([IR1, IQ2], IP2, '', '1q22') >>> registry.lookup([IR2, IQ2], IP1, '') '1q22' Мульти-Ð°Ð´Ð°Ð¿Ñ‚Ð°Ñ†Ð¸Ñ ---------------- Ð’Ñ‹ можете адаптировать неÑколько объектов:: >>> class Q: ... zope.interface.implements(IQ) Как и Ñ Ð¾Ð´Ð¸Ð½Ð¾Ñ‡Ð½Ñ‹Ð¼Ð¸ адаптерами, мы региÑтрируем фабрику ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð²Ð¾Ð·Ð²Ñ€Ð°Ñ‰Ð°ÐµÑ‚ клаÑÑ:: >>> class IM(zope.interface.Interface): ... pass >>> class M: ... zope.interface.implements(IM) ... def __init__(self, x, q): ... self.x, self.q = x, q >>> registry.register([IR, IQ], IM, '', M) И затем мы можем вызвать `queryMultiAdapter` Ð´Ð»Ñ Ð²Ñ‹Ñ‡Ð¸ÑÐ»ÐµÐ½Ð¸Ñ Ð°Ð´Ð°Ð¿Ñ‚ÐµÑ€Ð°:: >>> q = Q() >>> m = registry.queryMultiAdapter((x, q), IM) >>> m.__class__.__name__ 'M' >>> m.x is x and m.q is q True и, конечно, мы можем иÑпользовать имена:: >>> class M2(M): ... pass >>> registry.register([IR, IQ], IM, 'bob', M2) >>> m = registry.queryMultiAdapter((x, q), IM, 'bob') >>> m.__class__.__name__ 'M2' >>> m.x is x and m.q is q True Ðдаптеры по умолчанию --------------------- Как и Ð´Ð»Ñ Ð¾Ð´Ð¸Ð½Ð¾Ñ‡Ð½Ñ‹Ñ… адаптеров вы можете определить адаптер по умолчанию передав None вмеÑто *первой* Ñпецификации:: >>> registry.register([None, IQ], IP2, '', 'q2') >>> registry.lookup([IS, IQ], IP2, '') 'q2' Ðулевые адаптеры ================ Ð’Ñ‹ можете также адаптировать без Ñпецификации:: >>> registry.register([], IP2, '', 2) >>> registry.lookup([], IP2, '') 2 >>> registry.lookup([], IP1, '') 2 ПеречиÑление именованных адаптеров ---------------------------------- Ðдаптеры имеют имена. Иногда Ñто полезно Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð²Ñех именованных адаптеров Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ интерфейÑа:: >>> adapters = list(registry.lookupAll([IR1], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', 11), (u'bob', "Bob's 12")] Это работает также и Ð´Ð»Ñ Ð¼ÑƒÐ»ÑŒÑ‚Ð¸-адаптеров:: >>> registry.register([IR1, IQ2], IP2, 'bob', '1q2 for bob') >>> adapters = list(registry.lookupAll([IR2, IQ2], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', '1q22'), (u'bob', '1q2 for bob')] И даже Ð´Ð»Ñ Ð½ÑƒÐ»ÐµÐ²Ñ‹Ñ… адаптеров:: >>> registry.register([], IP2, 'bob', 3) >>> adapters = list(registry.lookupAll([], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', 2), (u'bob', 3)] ПодпиÑки ======== Обычно мы хотим запроÑить объект который наиболее близко ÑоответÑтвует Ñпецификации. Иногда мы хотим получить вÑе объекты которые ÑоответÑтвуют какой-либо Ñпецификации. Мы иÑпользуем подпиÑки Ð´Ð»Ñ Ñтого. Мы подпиÑываем объекты Ð´Ð»Ñ Ñпецификаций и затем позже находим вÑе подпиÑанные объекты:: >>> registry.subscribe([IR1], IP2, 'sub12 1') >>> registry.subscriptions([IR1], IP2) ['sub12 1'] Заметьте, что в отличие от обычных адаптеров подпиÑки не имеют имен. Ð’Ñ‹ можете иметь неÑколько подпиÑчиков Ð´Ð»Ñ Ð¾Ð´Ð½Ð¾Ð¹ Ñпецификации:: >>> registry.subscribe([IR1], IP2, 'sub12 2') >>> registry.subscriptions([IR1], IP2) ['sub12 1', 'sub12 2'] ЕÑли подпиÑчики зарегиÑтрированы Ð´Ð»Ñ Ð¾Ð´Ð½Ð¸Ñ… и тех же требуемых интерфейÑов, они возвращаютÑÑ Ð² порÑдке определениÑ. Ð’Ñ‹ можете зарегиÑтрировать подпиÑчики Ð´Ð»Ñ Ð²Ñех Ñпецификаций иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ None:: >>> registry.subscribe([None], IP1, 'sub_1') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2'] Заметьте, что новый подпиÑчик возвращаетÑÑ Ð¿ÐµÑ€Ð²Ñ‹Ð¼. ПодпиÑчики определенные Ð´Ð»Ñ Ð¼ÐµÐ½ÐµÐµ общих требуемых интерфейÑов возвращаютÑÑ Ð¿ÐµÑ€ÐµÐ´ подпиÑчиками Ð´Ð»Ñ Ð±Ð¾Ð»ÐµÐµ общих интерфейÑов. ПодпиÑки могут ÑмешиватьÑÑ Ð¼ÐµÐ¶Ð´Ñƒ неÑколькими ÑовмеÑтимыми ÑпецификациÑми:: >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2'] >>> registry.subscribe([IR1], IP1, 'sub11') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2', 'sub11'] >>> registry.subscribe([IR2], IP2, 'sub22') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2', 'sub11', 'sub22'] >>> registry.subscriptions([IR2], IP2) ['sub12 1', 'sub12 2', 'sub22'] ПодпиÑки могут ÑущеÑтвовать Ð´Ð»Ñ Ð½ÐµÑкольких Ñпецификаций:: >>> registry.subscribe([IR1, IQ], IP2, 'sub1q2') >>> registry.subscriptions([IR1, IQ], IP2) ['sub1q2'] Как и Ñ Ð¾Ð´Ð¸Ð½Ð¾Ñ‡Ð½Ñ‹Ð¼Ð¸ подпиÑчиками и адаптерами без подпиÑок, вы можете определить None Ð´Ð»Ñ Ð¿ÐµÑ€Ð²Ð¾Ð³Ð¾ требуемого интерфейÑа, что бы задать значение по умолчанию:: >>> registry.subscribe([None, IQ], IP2, 'sub_q2') >>> registry.subscriptions([IS, IQ], IP2) ['sub_q2'] >>> registry.subscriptions([IR1, IQ], IP2) ['sub_q2', 'sub1q2'] Ð’Ñ‹ можете Ñоздать подпиÑки которые незавиÑимы от любых Ñпецификаций:: >>> list(registry.subscriptions([], IP1)) [] >>> registry.subscribe([], IP2, 'sub2') >>> registry.subscriptions([], IP1) ['sub2'] >>> registry.subscribe([], IP1, 'sub1') >>> registry.subscriptions([], IP1) ['sub2', 'sub1'] >>> registry.subscriptions([], IP2) ['sub2'] Удаление региÑтрации подпиÑчиков -------------------------------- Мы можем удалÑть региÑтрацию подпиÑчиков. При удалении региÑтрации подпиÑчика мы можем удалить региÑтрацию заданного адаптера:: >>> registry.unsubscribe([IR1], IP1, 'sub11') >>> registry.subscriptions([IR1], IP1) ['sub_1', 'sub12 1', 'sub12 2'] ЕÑли мы не задаем никакого Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ñ‚Ð¾Ð³Ð´Ð° подпиÑки будут удалены Ð´Ð»Ñ Ð²Ñех подпиÑчиков Ñовпадающих Ñ Ð·Ð°Ð´Ð°Ð½Ð½Ñ‹Ð¼ интерфейÑом:: >>> registry.unsubscribe([IR1], IP2) >>> registry.subscriptions([IR1], IP1) ['sub_1'] Ðдаптеры подпиÑки ----------------- Обычно мы региÑтрируем фабрики Ð´Ð»Ñ Ð°Ð´Ð°Ð¿Ñ‚ÐµÑ€Ð¾Ð² которые затем позволÑÑŽÑ‚ нам вычиÑлÑть адаптеры, но Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñками мы получаем неÑколько адаптеров. Это пример подпиÑчика Ð´Ð»Ñ Ð½ÐµÑкольких объектов:: >>> registry.subscribe([IR, IQ], IM, M) >>> registry.subscribe([IR, IQ], IM, M2) >>> subscribers = registry.subscribers((x, q), IM) >>> len(subscribers) 2 >>> class_names = [s.__class__.__name__ for s in subscribers] >>> class_names.sort() >>> class_names ['M', 'M2'] >>> [(s.x is x and s.q is q) for s in subscribers] [True, True] подпиÑчики фабрик адаптеров не могут возвращать None:: >>> def M3(x, y): ... return None >>> registry.subscribe([IR, IQ], IM, M3) >>> subscribers = registry.subscribers((x, q), IM) >>> len(subscribers) 2 Обработчики ----------- Обработчик - Ñто подпиÑÐ°Ð½Ð½Ð°Ñ Ñ„Ð°Ð±Ñ€Ð¸ÐºÐ° ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð½Ðµ возвращает нормального значениÑ. Она возвращает None. Обработчик отличаетÑÑ Ð¾Ñ‚ адаптеров тем, что он делает вÑÑŽ работу когда вызываетÑÑ Ñ„Ð°Ð±Ñ€Ð¸ÐºÐ°. Ð”Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации обработчика надо проÑто передать None как предоÑтавлÑемый интерфейÑ:: >>> def handler(event): ... print 'handler', event >>> registry.subscribe([IR1], None, handler) >>> registry.subscriptions([IR1], None) == [handler] True zope2.13-2.13.21/source/zope.interface/src/zope/interface/common/0000755000175000017500000000000012214017572023350 5ustar arnauarnauzope2.13-2.13.21/source/zope.interface/src/zope/interface/common/mapping.py0000644000175000017500000000667012214017572025366 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Mapping Interfaces $Id: mapping.py 110536 2010-04-06 02:59:44Z tseaver $ """ from zope.interface import Interface class IItemMapping(Interface): """Simplest readable mapping object """ def __getitem__(key): """Get a value for a key A KeyError is raised if there is no value for the key. """ class IReadMapping(IItemMapping): """Basic mapping interface """ def get(key, default=None): """Get a value for a key The default is returned if there is no value for the key. """ def __contains__(key): """Tell if a key exists in the mapping.""" class IWriteMapping(Interface): """Mapping methods for changing data""" def __delitem__(key): """Delete a value from the mapping using the key.""" def __setitem__(key, value): """Set a new item in the mapping.""" class IEnumerableMapping(IReadMapping): """Mapping objects whose items can be enumerated. """ def keys(): """Return the keys of the mapping object. """ def __iter__(): """Return an iterator for the keys of the mapping object. """ def values(): """Return the values of the mapping object. """ def items(): """Return the items of the mapping object. """ def __len__(): """Return the number of items. """ class IMapping(IWriteMapping, IEnumerableMapping): ''' Simple mapping interface ''' class IIterableMapping(IEnumerableMapping): def iterkeys(): "iterate over keys; equivalent to __iter__" def itervalues(): "iterate over values" def iteritems(): "iterate over items" class IClonableMapping(Interface): def copy(): "return copy of dict" class IExtendedReadMapping(IIterableMapping): def has_key(key): """Tell if a key exists in the mapping; equivalent to __contains__""" class IExtendedWriteMapping(IWriteMapping): def clear(): "delete all items" def update(d): " Update D from E: for k in E.keys(): D[k] = E[k]" def setdefault(key, default=None): "D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D" def pop(k, *args): """remove specified key and return the corresponding value *args may contain a single default value, or may not be supplied. If key is not found, default is returned if given, otherwise KeyError is raised""" def popitem(): """remove and return some (key, value) pair as a 2-tuple; but raise KeyError if mapping is empty""" class IFullMapping( IExtendedReadMapping, IExtendedWriteMapping, IClonableMapping, IMapping): ''' Full mapping interface ''' # IMapping included so tests for IMapping # succeed with IFullMapping zope2.13-2.13.21/source/zope.interface/src/zope/interface/common/idatetime.py0000644000175000017500000004722012214017572025674 0ustar arnauarnau############################################################################## # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. ############################################################################## """Datetime interfaces. This module is called idatetime because if it were called datetime the import of the real datetime would fail. $Id: idatetime.py 110736 2010-04-11 10:59:30Z regebro $ """ from zope.interface import Interface, Attribute from zope.interface import classImplements from datetime import timedelta, date, datetime, time, tzinfo class ITimeDeltaClass(Interface): """This is the timedelta class interface.""" min = Attribute("The most negative timedelta object") max = Attribute("The most positive timedelta object") resolution = Attribute( "The smallest difference between non-equal timedelta objects") class ITimeDelta(ITimeDeltaClass): """Represent the difference between two datetime objects. Supported operators: - add, subtract timedelta - unary plus, minus, abs - compare to timedelta - multiply, divide by int/long In addition, datetime supports subtraction of two datetime objects returning a timedelta, and addition or subtraction of a datetime and a timedelta giving a datetime. Representation: (days, seconds, microseconds). """ days = Attribute("Days between -999999999 and 999999999 inclusive") seconds = Attribute("Seconds between 0 and 86399 inclusive") microseconds = Attribute("Microseconds between 0 and 999999 inclusive") class IDateClass(Interface): """This is the date class interface.""" min = Attribute("The earliest representable date") max = Attribute("The latest representable date") resolution = Attribute( "The smallest difference between non-equal date objects") def today(): """Return the current local time. This is equivalent to date.fromtimestamp(time.time())""" def fromtimestamp(timestamp): """Return the local date from a POSIX timestamp (like time.time()) This may raise ValueError, if the timestamp is out of the range of values supported by the platform C localtime() function. It's common for this to be restricted to years from 1970 through 2038. Note that on non-POSIX systems that include leap seconds in their notion of a timestamp, leap seconds are ignored by fromtimestamp(). """ def fromordinal(ordinal): """Return the date corresponding to the proleptic Gregorian ordinal. January 1 of year 1 has ordinal 1. ValueError is raised unless 1 <= ordinal <= date.max.toordinal(). For any date d, date.fromordinal(d.toordinal()) == d. """ class IDate(IDateClass): """Represents a date (year, month and day) in an idealized calendar. Operators: __repr__, __str__ __cmp__, __hash__ __add__, __radd__, __sub__ (add/radd only with timedelta arg) """ year = Attribute("Between MINYEAR and MAXYEAR inclusive.") month = Attribute("Between 1 and 12 inclusive") day = Attribute( "Between 1 and the number of days in the given month of the given year.") def replace(year, month, day): """Return a date with the same value. Except for those members given new values by whichever keyword arguments are specified. For example, if d == date(2002, 12, 31), then d.replace(day=26) == date(2000, 12, 26). """ def timetuple(): """Return a 9-element tuple of the form returned by time.localtime(). The hours, minutes and seconds are 0, and the DST flag is -1. d.timetuple() is equivalent to (d.year, d.month, d.day, 0, 0, 0, d.weekday(), d.toordinal() - date(d.year, 1, 1).toordinal() + 1, -1) """ def toordinal(): """Return the proleptic Gregorian ordinal of the date January 1 of year 1 has ordinal 1. For any date object d, date.fromordinal(d.toordinal()) == d. """ def weekday(): """Return the day of the week as an integer. Monday is 0 and Sunday is 6. For example, date(2002, 12, 4).weekday() == 2, a Wednesday. See also isoweekday(). """ def isoweekday(): """Return the day of the week as an integer. Monday is 1 and Sunday is 7. For example, date(2002, 12, 4).isoweekday() == 3, a Wednesday. See also weekday(), isocalendar(). """ def isocalendar(): """Return a 3-tuple, (ISO year, ISO week number, ISO weekday). The ISO calendar is a widely used variant of the Gregorian calendar. See http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm for a good explanation. The ISO year consists of 52 or 53 full weeks, and where a week starts on a Monday and ends on a Sunday. The first week of an ISO year is the first (Gregorian) calendar week of a year containing a Thursday. This is called week number 1, and the ISO year of that Thursday is the same as its Gregorian year. For example, 2004 begins on a Thursday, so the first week of ISO year 2004 begins on Monday, 29 Dec 2003 and ends on Sunday, 4 Jan 2004, so that date(2003, 12, 29).isocalendar() == (2004, 1, 1) and date(2004, 1, 4).isocalendar() == (2004, 1, 7). """ def isoformat(): """Return a string representing the date in ISO 8601 format. This is 'YYYY-MM-DD'. For example, date(2002, 12, 4).isoformat() == '2002-12-04'. """ def __str__(): """For a date d, str(d) is equivalent to d.isoformat().""" def ctime(): """Return a string representing the date. For example date(2002, 12, 4).ctime() == 'Wed Dec 4 00:00:00 2002'. d.ctime() is equivalent to time.ctime(time.mktime(d.timetuple())) on platforms where the native C ctime() function (which time.ctime() invokes, but which date.ctime() does not invoke) conforms to the C standard. """ def strftime(format): """Return a string representing the date. Controlled by an explicit format string. Format codes referring to hours, minutes or seconds will see 0 values. """ class IDateTimeClass(Interface): """This is the datetime class interface.""" min = Attribute("The earliest representable datetime") max = Attribute("The latest representable datetime") resolution = Attribute( "The smallest possible difference between non-equal datetime objects") def today(): """Return the current local datetime, with tzinfo None. This is equivalent to datetime.fromtimestamp(time.time()). See also now(), fromtimestamp(). """ def now(tz=None): """Return the current local date and time. If optional argument tz is None or not specified, this is like today(), but, if possible, supplies more precision than can be gotten from going through a time.time() timestamp (for example, this may be possible on platforms supplying the C gettimeofday() function). Else tz must be an instance of a class tzinfo subclass, and the current date and time are converted to tz's time zone. In this case the result is equivalent to tz.fromutc(datetime.utcnow().replace(tzinfo=tz)). See also today(), utcnow(). """ def utcnow(): """Return the current UTC date and time, with tzinfo None. This is like now(), but returns the current UTC date and time, as a naive datetime object. See also now(). """ def fromtimestamp(timestamp, tz=None): """Return the local date and time corresponding to the POSIX timestamp. Same as is returned by time.time(). If optional argument tz is None or not specified, the timestamp is converted to the platform's local date and time, and the returned datetime object is naive. Else tz must be an instance of a class tzinfo subclass, and the timestamp is converted to tz's time zone. In this case the result is equivalent to tz.fromutc(datetime.utcfromtimestamp(timestamp).replace(tzinfo=tz)). fromtimestamp() may raise ValueError, if the timestamp is out of the range of values supported by the platform C localtime() or gmtime() functions. It's common for this to be restricted to years in 1970 through 2038. Note that on non-POSIX systems that include leap seconds in their notion of a timestamp, leap seconds are ignored by fromtimestamp(), and then it's possible to have two timestamps differing by a second that yield identical datetime objects. See also utcfromtimestamp(). """ def utcfromtimestamp(timestamp): """Return the UTC datetime from the POSIX timestamp with tzinfo None. This may raise ValueError, if the timestamp is out of the range of values supported by the platform C gmtime() function. It's common for this to be restricted to years in 1970 through 2038. See also fromtimestamp(). """ def fromordinal(ordinal): """Return the datetime from the proleptic Gregorian ordinal. January 1 of year 1 has ordinal 1. ValueError is raised unless 1 <= ordinal <= datetime.max.toordinal(). The hour, minute, second and microsecond of the result are all 0, and tzinfo is None. """ def combine(date, time): """Return a new datetime object. Its date members are equal to the given date object's, and whose time and tzinfo members are equal to the given time object's. For any datetime object d, d == datetime.combine(d.date(), d.timetz()). If date is a datetime object, its time and tzinfo members are ignored. """ class IDateTime(IDate, IDateTimeClass): """Object contains all the information from a date object and a time object. """ year = Attribute("Year between MINYEAR and MAXYEAR inclusive") month = Attribute("Month between 1 and 12 inclusive") day = Attribute( "Day between 1 and the number of days in the given month of the year") hour = Attribute("Hour in range(24)") minute = Attribute("Minute in range(60)") second = Attribute("Second in range(60)") microsecond = Attribute("Microsecond in range(1000000)") tzinfo = Attribute( """The object passed as the tzinfo argument to the datetime constructor or None if none was passed""") def date(): """Return date object with same year, month and day.""" def time(): """Return time object with same hour, minute, second, microsecond. tzinfo is None. See also method timetz(). """ def timetz(): """Return time object with same hour, minute, second, microsecond, and tzinfo. See also method time(). """ def replace(year, month, day, hour, minute, second, microsecond, tzinfo): """Return a datetime with the same members, except for those members given new values by whichever keyword arguments are specified. Note that tzinfo=None can be specified to create a naive datetime from an aware datetime with no conversion of date and time members. """ def astimezone(tz): """Return a datetime object with new tzinfo member tz, adjusting the date and time members so the result is the same UTC time as self, but in tz's local time. tz must be an instance of a tzinfo subclass, and its utcoffset() and dst() methods must not return None. self must be aware (self.tzinfo must not be None, and self.utcoffset() must not return None). If self.tzinfo is tz, self.astimezone(tz) is equal to self: no adjustment of date or time members is performed. Else the result is local time in time zone tz, representing the same UTC time as self: after astz = dt.astimezone(tz), astz - astz.utcoffset() will usually have the same date and time members as dt - dt.utcoffset(). The discussion of class tzinfo explains the cases at Daylight Saving Time transition boundaries where this cannot be achieved (an issue only if tz models both standard and daylight time). If you merely want to attach a time zone object tz to a datetime dt without adjustment of date and time members, use dt.replace(tzinfo=tz). If you merely want to remove the time zone object from an aware datetime dt without conversion of date and time members, use dt.replace(tzinfo=None). Note that the default tzinfo.fromutc() method can be overridden in a tzinfo subclass to effect the result returned by astimezone(). """ def utcoffset(): """Return the timezone offset in minutes east of UTC (negative west of UTC).""" def dst(): """Return 0 if DST is not in effect, or the DST offset (in minutes eastward) if DST is in effect. """ def tzname(): """Return the timezone name.""" def timetuple(): """Return a 9-element tuple of the form returned by time.localtime().""" def utctimetuple(): """Return UTC time tuple compatilble with time.gmtimr().""" def toordinal(): """Return the proleptic Gregorian ordinal of the date. The same as self.date().toordinal(). """ def weekday(): """Return the day of the week as an integer. Monday is 0 and Sunday is 6. The same as self.date().weekday(). See also isoweekday(). """ def isoweekday(): """Return the day of the week as an integer. Monday is 1 and Sunday is 7. The same as self.date().isoweekday. See also weekday(), isocalendar(). """ def isocalendar(): """Return a 3-tuple, (ISO year, ISO week number, ISO weekday). The same as self.date().isocalendar(). """ def isoformat(sep='T'): """Return a string representing the date and time in ISO 8601 format. YYYY-MM-DDTHH:MM:SS.mmmmmm or YYYY-MM-DDTHH:MM:SS if microsecond is 0 If utcoffset() does not return None, a 6-character string is appended, giving the UTC offset in (signed) hours and minutes: YYYY-MM-DDTHH:MM:SS.mmmmmm+HH:MM or YYYY-MM-DDTHH:MM:SS+HH:MM if microsecond is 0. The optional argument sep (default 'T') is a one-character separator, placed between the date and time portions of the result. """ def __str__(): """For a datetime instance d, str(d) is equivalent to d.isoformat(' '). """ def ctime(): """Return a string representing the date and time. datetime(2002, 12, 4, 20, 30, 40).ctime() == 'Wed Dec 4 20:30:40 2002'. d.ctime() is equivalent to time.ctime(time.mktime(d.timetuple())) on platforms where the native C ctime() function (which time.ctime() invokes, but which datetime.ctime() does not invoke) conforms to the C standard. """ def strftime(format): """Return a string representing the date and time. This is controlled by an explicit format string. """ class ITimeClass(Interface): """This is the time class interface.""" min = Attribute("The earliest representable time") max = Attribute("The latest representable time") resolution = Attribute( "The smallest possible difference between non-equal time objects") class ITime(ITimeClass): """Represent time with time zone. Operators: __repr__, __str__ __cmp__, __hash__ """ hour = Attribute("Hour in range(24)") minute = Attribute("Minute in range(60)") second = Attribute("Second in range(60)") microsecond = Attribute("Microsecond in range(1000000)") tzinfo = Attribute( """The object passed as the tzinfo argument to the time constructor or None if none was passed.""") def replace(hour, minute, second, microsecond, tzinfo): """Return a time with the same value. Except for those members given new values by whichever keyword arguments are specified. Note that tzinfo=None can be specified to create a naive time from an aware time, without conversion of the time members. """ def isoformat(): """Return a string representing the time in ISO 8601 format. That is HH:MM:SS.mmmmmm or, if self.microsecond is 0, HH:MM:SS If utcoffset() does not return None, a 6-character string is appended, giving the UTC offset in (signed) hours and minutes: HH:MM:SS.mmmmmm+HH:MM or, if self.microsecond is 0, HH:MM:SS+HH:MM """ def __str__(): """For a time t, str(t) is equivalent to t.isoformat().""" def strftime(format): """Return a string representing the time. This is controlled by an explicit format string. """ def utcoffset(): """Return the timezone offset in minutes east of UTC (negative west of UTC). If tzinfo is None, returns None, else returns self.tzinfo.utcoffset(None), and raises an exception if the latter doesn't return None or a timedelta object representing a whole number of minutes with magnitude less than one day. """ def dst(): """Return 0 if DST is not in effect, or the DST offset (in minutes eastward) if DST is in effect. If tzinfo is None, returns None, else returns self.tzinfo.dst(None), and raises an exception if the latter doesn't return None, or a timedelta object representing a whole number of minutes with magnitude less than one day. """ def tzname(): """Return the timezone name. If tzinfo is None, returns None, else returns self.tzinfo.tzname(None), or raises an exception if the latter doesn't return None or a string object. """ class ITZInfo(Interface): """Time zone info class. """ def utcoffset(dt): """Return offset of local time from UTC, in minutes east of UTC. If local time is west of UTC, this should be negative. Note that this is intended to be the total offset from UTC; for example, if a tzinfo object represents both time zone and DST adjustments, utcoffset() should return their sum. If the UTC offset isn't known, return None. Else the value returned must be a timedelta object specifying a whole number of minutes in the range -1439 to 1439 inclusive (1440 = 24*60; the magnitude of the offset must be less than one day). """ def dst(dt): """Return the daylight saving time (DST) adjustment, in minutes east of UTC, or None if DST information isn't known. """ def tzname(dt): """Return the time zone name corresponding to the datetime object as a string. """ def fromutc(dt): """Return an equivalent datetime in self's local time.""" classImplements(timedelta, ITimeDelta) classImplements(date, IDate) classImplements(datetime, IDateTime) classImplements(time, ITime) classImplements(tzinfo, ITZInfo) ## directlyProvides(timedelta, ITimeDeltaClass) ## directlyProvides(date, IDateClass) ## directlyProvides(datetime, IDateTimeClass) ## directlyProvides(time, ITimeClass) zope2.13-2.13.21/source/zope.interface/src/zope/interface/common/interfaces.py0000644000175000017500000001014612214017572026047 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interfaces for standard python exceptions $Id: interfaces.py 110536 2010-04-06 02:59:44Z tseaver $ """ from zope.interface import Interface from zope.interface import classImplements class IException(Interface): pass class IStandardError(IException): pass class IWarning(IException): pass class ISyntaxError(IStandardError): pass class ILookupError(IStandardError): pass class IValueError(IStandardError): pass class IRuntimeError(IStandardError): pass class IArithmeticError(IStandardError): pass class IAssertionError(IStandardError): pass class IAttributeError(IStandardError): pass class IDeprecationWarning(IWarning): pass class IEOFError(IStandardError): pass class IEnvironmentError(IStandardError): pass class IFloatingPointError(IArithmeticError): pass class IIOError(IEnvironmentError): pass class IImportError(IStandardError): pass class IIndentationError(ISyntaxError): pass class IIndexError(ILookupError): pass class IKeyError(ILookupError): pass class IKeyboardInterrupt(IStandardError): pass class IMemoryError(IStandardError): pass class INameError(IStandardError): pass class INotImplementedError(IRuntimeError): pass class IOSError(IEnvironmentError): pass class IOverflowError(IArithmeticError): pass class IOverflowWarning(IWarning): pass class IReferenceError(IStandardError): pass class IRuntimeWarning(IWarning): pass class IStopIteration(IException): pass class ISyntaxWarning(IWarning): pass class ISystemError(IStandardError): pass class ISystemExit(IException): pass class ITabError(IIndentationError): pass class ITypeError(IStandardError): pass class IUnboundLocalError(INameError): pass class IUnicodeError(IValueError): pass class IUserWarning(IWarning): pass class IZeroDivisionError(IArithmeticError): pass classImplements(ArithmeticError, IArithmeticError) classImplements(AssertionError, IAssertionError) classImplements(AttributeError, IAttributeError) classImplements(DeprecationWarning, IDeprecationWarning) classImplements(EnvironmentError, IEnvironmentError) classImplements(EOFError, IEOFError) classImplements(Exception, IException) classImplements(FloatingPointError, IFloatingPointError) classImplements(ImportError, IImportError) classImplements(IndentationError, IIndentationError) classImplements(IndexError, IIndexError) classImplements(IOError, IIOError) classImplements(KeyboardInterrupt, IKeyboardInterrupt) classImplements(KeyError, IKeyError) classImplements(LookupError, ILookupError) classImplements(MemoryError, IMemoryError) classImplements(NameError, INameError) classImplements(NotImplementedError, INotImplementedError) classImplements(OSError, IOSError) classImplements(OverflowError, IOverflowError) try: classImplements(OverflowWarning, IOverflowWarning) except NameError: pass # OverflowWarning was removed in Python 2.5 classImplements(ReferenceError, IReferenceError) classImplements(RuntimeError, IRuntimeError) classImplements(RuntimeWarning, IRuntimeWarning) classImplements(StandardError, IStandardError) classImplements(StopIteration, IStopIteration) classImplements(SyntaxError, ISyntaxError) classImplements(SyntaxWarning, ISyntaxWarning) classImplements(SystemError, ISystemError) classImplements(SystemExit, ISystemExit) classImplements(TabError, ITabError) classImplements(TypeError, ITypeError) classImplements(UnboundLocalError, IUnboundLocalError) classImplements(UnicodeError, IUnicodeError) classImplements(UserWarning, IUserWarning) classImplements(ValueError, IValueError) classImplements(Warning, IWarning) classImplements(ZeroDivisionError, IZeroDivisionError) zope2.13-2.13.21/source/zope.interface/src/zope/interface/common/__init__.py0000644000175000017500000000007512214017572025463 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.interface/src/zope/interface/common/sequence.py0000644000175000017500000001117712214017572025541 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sequence Interfaces $Id: sequence.py 110536 2010-04-06 02:59:44Z tseaver $ """ __docformat__ = 'restructuredtext' from zope import interface class IMinimalSequence(interface.Interface): """Most basic sequence interface. All sequences are iterable. This requires at least one of the following: - a `__getitem__()` method that takes a single argument; interger values starting at 0 must be supported, and `IndexError` should be raised for the first index for which there is no value, or - an `__iter__()` method that returns an iterator as defined in the Python documentation (http://docs.python.org/lib/typeiter.html). """ def __getitem__(index): """`x.__getitem__(index)` <==> `x[index]` Declaring this interface does not specify whether `__getitem__` supports slice objects.""" class IFiniteSequence(IMinimalSequence): def __len__(): """`x.__len__()` <==> `len(x)`""" class IReadSequence(IFiniteSequence): """read interface shared by tuple and list""" def __contains__(item): """`x.__contains__(item)` <==> `item in x`""" def __lt__(other): """`x.__lt__(other)` <==> `x < other`""" def __le__(other): """`x.__le__(other)` <==> `x <= other`""" def __eq__(other): """`x.__eq__(other)` <==> `x == other`""" def __ne__(other): """`x.__ne__(other)` <==> `x != other`""" def __gt__(other): """`x.__gt__(other)` <==> `x > other`""" def __ge__(other): """`x.__ge__(other)` <==> `x >= other`""" def __add__(other): """`x.__add__(other)` <==> `x + other`""" def __mul__(n): """`x.__mul__(n)` <==> `x * n`""" def __rmul__(n): """`x.__rmul__(n)` <==> `n * x`""" def __getslice__(i, j): """`x.__getslice__(i, j)` <==> `x[i:j]` Use of negative indices is not supported. Deprecated since Python 2.0 but still a part of `UserList`. """ class IExtendedReadSequence(IReadSequence): """Full read interface for lists""" def count(item): """Return number of occurrences of value""" def index(item, *args): """Return first index of value `L.index(value, [start, [stop]])` -> integer""" class IUniqueMemberWriteSequence(interface.Interface): """The write contract for a sequence that may enforce unique members""" def __setitem__(index, item): """`x.__setitem__(index, item)` <==> `x[index] = item` Declaring this interface does not specify whether `__setitem__` supports slice objects. """ def __delitem__(index): """`x.__delitem__(index)` <==> `del x[index]` Declaring this interface does not specify whether `__delitem__` supports slice objects. """ def __setslice__(i, j, other): """`x.__setslice__(i, j, other)` <==> `x[i:j]=other` Use of negative indices is not supported. Deprecated since Python 2.0 but still a part of `UserList`. """ def __delslice__(i, j): """`x.__delslice__(i, j)` <==> `del x[i:j]` Use of negative indices is not supported. Deprecated since Python 2.0 but still a part of `UserList`. """ def __iadd__(y): """`x.__iadd__(y)` <==> `x += y`""" def append(item): """Append item to end""" def insert(index, item): """Insert item before index""" def pop(index=-1): """Remove and return item at index (default last)""" def remove(item): """Remove first occurrence of value""" def reverse(): """Reverse *IN PLACE*""" def sort(cmpfunc=None): """Stable sort *IN PLACE*; `cmpfunc(x, y)` -> -1, 0, 1""" def extend(iterable): """Extend list by appending elements from the iterable""" class IWriteSequence(IUniqueMemberWriteSequence): """Full write contract for sequences""" def __imul__(n): """`x.__imul__(n)` <==> `x *= n`""" class ISequence(IReadSequence, IWriteSequence): """Full sequence contract""" zope2.13-2.13.21/source/zope.interface/src/zope/interface/common/tests/0000755000175000017500000000000012214017572024512 5ustar arnauarnauzope2.13-2.13.21/source/zope.interface/src/zope/interface/common/tests/__init__.py0000644000175000017500000000007512214017572026625 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.interface/src/zope/interface/common/tests/test_import_interfaces.py0000644000175000017500000000164012214017572031641 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import unittest def test_interface_import(): """ >>> import zope.interface.common.interfaces """ def test_suite(): return unittest.TestSuite(( doctest.DocTestSuite(), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.interface/src/zope/interface/common/tests/test_idatetime.py0000644000175000017500000000345512214017572030077 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test for datetime interfaces $Id: test_idatetime.py 110536 2010-04-06 02:59:44Z tseaver $ """ import unittest from zope.interface.verify import verifyObject, verifyClass from zope.interface.common.idatetime import ITimeDelta, ITimeDeltaClass from zope.interface.common.idatetime import IDate, IDateClass from zope.interface.common.idatetime import IDateTime, IDateTimeClass from zope.interface.common.idatetime import ITime, ITimeClass, ITZInfo from datetime import timedelta, date, datetime, time, tzinfo class TestDateTimeInterfaces(unittest.TestCase): def test_interfaces(self): verifyObject(ITimeDelta, timedelta(minutes=20)) verifyObject(IDate, date(2000, 1, 2)) verifyObject(IDateTime, datetime(2000, 1, 2, 10, 20)) verifyObject(ITime, time(20, 30, 15, 1234)) verifyObject(ITZInfo, tzinfo()) verifyClass(ITimeDeltaClass, timedelta) verifyClass(IDateClass, date) verifyClass(IDateTimeClass, datetime) verifyClass(ITimeClass, time) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestDateTimeInterfaces)) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.interface/src/zope/interface/common/tests/basemapping.py0000644000175000017500000000760512214017572027362 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Base Mapping tests $Id: basemapping.py 110736 2010-04-11 10:59:30Z regebro $ """ from operator import __getitem__ def testIReadMapping(self, inst, state, absent): for key in state: self.assertEqual(inst[key], state[key]) self.assertEqual(inst.get(key, None), state[key]) self.failUnless(key in inst) for key in absent: self.assertEqual(inst.get(key, None), None) self.assertEqual(inst.get(key), None) self.assertEqual(inst.get(key, self), self) self.assertRaises(KeyError, __getitem__, inst, key) def test_keys(self, inst, state): # Return the keys of the mapping object inst_keys = list(inst.keys()); inst_keys.sort() state_keys = list(state.keys()) ; state_keys.sort() self.assertEqual(inst_keys, state_keys) def test_iter(self, inst, state): # Return the keys of the mapping object inst_keys = list(inst); inst_keys.sort() state_keys = list(state.keys()) ; state_keys.sort() self.assertEqual(inst_keys, state_keys) def test_values(self, inst, state): # Return the values of the mapping object inst_values = list(inst.values()); inst_values.sort() state_values = list(state.values()) ; state_values.sort() self.assertEqual(inst_values, state_values) def test_items(self, inst, state): # Return the items of the mapping object inst_items = list(inst.items()); inst_items.sort() state_items = list(state.items()) ; state_items.sort() self.assertEqual(inst_items, state_items) def test___len__(self, inst, state): # Return the number of items self.assertEqual(len(inst), len(state)) def testIEnumerableMapping(self, inst, state): test_keys(self, inst, state) test_items(self, inst, state) test_values(self, inst, state) test___len__(self, inst, state) class BaseTestIReadMapping(object): def testIReadMapping(self): inst = self._IReadMapping__sample() state = self._IReadMapping__stateDict() absent = self._IReadMapping__absentKeys() testIReadMapping(self, inst, state, absent) class BaseTestIEnumerableMapping(BaseTestIReadMapping): # Mapping objects whose items can be enumerated def test_keys(self): # Return the keys of the mapping object inst = self._IEnumerableMapping__sample() state = self._IEnumerableMapping__stateDict() test_keys(self, inst, state) def test_values(self): # Return the values of the mapping object inst = self._IEnumerableMapping__sample() state = self._IEnumerableMapping__stateDict() test_values(self, inst, state) def test_items(self): # Return the items of the mapping object inst = self._IEnumerableMapping__sample() state = self._IEnumerableMapping__stateDict() test_items(self, inst, state) def test___len__(self): # Return the number of items inst = self._IEnumerableMapping__sample() state = self._IEnumerableMapping__stateDict() test___len__(self, inst, state) def _IReadMapping__stateDict(self): return self._IEnumerableMapping__stateDict() def _IReadMapping__sample(self): return self._IEnumerableMapping__sample() def _IReadMapping__absentKeys(self): return self._IEnumerableMapping__absentKeys() zope2.13-2.13.21/source/zope.interface/src/zope/interface/index.txt0000644000175000017500000000054712214017572023736 0ustar arnauarnauWelcome to zope.interface's documentation! ========================================== Contents: .. toctree:: :maxdepth: 2 README adapter human verify По-руÑÑки ========= .. toctree:: :maxdepth: 2 README.ru adapter.ru human.ru Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/0000755000175000017500000000000012214017572023222 5ustar arnauarnauzope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/test_declarations.py0000644000175000017500000002604412214017572027311 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test the new API for making and checking interface declarations $Id: test_declarations.py 110827 2010-04-13 20:33:31Z tseaver $ """ import doctest import unittest from zope.interface import Interface, implements from zope.interface import directlyProvides, providedBy from zope.interface import classImplements, implementedBy, implementsOnly class I1(Interface): pass class I2(Interface): pass class I3(Interface): pass class I4(Interface): pass class I5(Interface): pass class A(object): implements(I1) class B(object): implements(I2) class C(A, B): implements(I3) class COnly(A, B): implementsOnly(I3) class COnly_old(A, B): __implemented__ = I3 class D(COnly): implements(I5) def test_ObjectSpecification_Simple(): """ >>> c = C() >>> directlyProvides(c, I4) >>> [i.__name__ for i in providedBy(c)] ['I4', 'I3', 'I1', 'I2'] """ def test_ObjectSpecification_Simple_w_only(): """ >>> c = COnly() >>> directlyProvides(c, I4) >>> [i.__name__ for i in providedBy(c)] ['I4', 'I3'] """ def test_ObjectSpecification_Simple_old_style(): """ >>> c = COnly_old() >>> directlyProvides(c, I4) >>> [i.__name__ for i in providedBy(c)] ['I4', 'I3'] """ class Test(unittest.TestCase): # Note that most of the tests are in the doc strings of the # declarations module. def test_backward_compat(self): class C1(object): __implemented__ = I1 class C2(C1): __implemented__ = I2, I5 class C3(C2): __implemented__ = I3, C2.__implemented__ self.assert_(C3.__implemented__.__class__ is tuple) self.assertEqual( [i.getName() for i in providedBy(C3())], ['I3', 'I2', 'I5'], ) class C4(C3): implements(I4) self.assertEqual( [i.getName() for i in providedBy(C4())], ['I4', 'I3', 'I2', 'I5'], ) self.assertEqual( [i.getName() for i in C4.__implemented__], ['I4', 'I3', 'I2', 'I5'], ) # Note that C3.__implemented__ should now be a sequence of interfaces self.assertEqual( [i.getName() for i in C3.__implemented__], ['I3', 'I2', 'I5'], ) self.failIf(C3.__implemented__.__class__ is tuple) def test_module(self): from zope.interface.tests import m1, m2 #import zope.interface.tests.m2 directlyProvides(m2, m1.I1, m1.I2, ) self.assertEqual(list(providedBy(m1)), list(providedBy(m2)), ) def test_builtins(self): # Setup intspec = implementedBy(int) olddeclared = intspec.declared classImplements(int, I1) class myint(int): implements(I2) x = 42 self.assertEqual([i.getName() for i in providedBy(x)], ['I1']) x = myint(42) directlyProvides(x, I3) self.assertEqual([i.getName() for i in providedBy(x)], ['I3', 'I2', 'I1']) # cleanup intspec.declared = olddeclared classImplements(int) x = 42 self.assertEqual([i.getName() for i in providedBy(x)], []) def test_signature_w_no_class_interfaces(): """ >>> from zope.interface import * >>> class C(object): ... pass >>> c = C() >>> list(providedBy(c)) [] >>> class I(Interface): ... pass >>> directlyProvides(c, I) >>> list(providedBy(c)) == list(directlyProvidedBy(c)) 1 """ def test_classImplement_on_deeply_nested_classes(): """This test is in response to a bug found, which is why it's a bit contrived >>> from zope.interface import * >>> class B1(object): ... pass >>> class B2(B1): ... pass >>> class B3(B2): ... pass >>> class D(object): ... implements() >>> class S(B3, D): ... implements() This failed due to a bug in the code for finding __providedBy__ descriptors for old-style classes. """ def test_pickle_provides_specs(): """ >>> from pickle import dumps, loads >>> a = A() >>> I2.providedBy(a) 0 >>> directlyProvides(a, I2) >>> I2.providedBy(a) 1 >>> a2 = loads(dumps(a)) >>> I2.providedBy(a2) 1 """ def test_that_we_dont_inherit_class_provides(): """ >>> from zope.interface import classProvides >>> class X(object): ... classProvides(I1) >>> class Y(X): ... pass >>> [i.__name__ for i in X.__provides__] ['I1'] >>> Y.__provides__ Traceback (most recent call last): ... AttributeError: __provides__ """ def test_that_we_dont_inherit_provides_optimizations(): """ When we make a declaration for a class, we install a __provides__ descriptors that provides a default for instances that don't have instance-specific declarations: >>> class A(object): ... implements(I1) >>> class B(object): ... implements(I2) >>> [i.__name__ for i in A().__provides__] ['I1'] >>> [i.__name__ for i in B().__provides__] ['I2'] But it's important that we don't use this for subclasses without declarations. This would cause incorrect results: >>> class X(A, B): ... pass >>> X().__provides__ Traceback (most recent call last): ... AttributeError: __provides__ However, if we "induce" a declaration, by calling implementedBy (even indirectly through providedBy): >>> [i.__name__ for i in providedBy(X())] ['I1', 'I2'] then the optimization will work: >>> [i.__name__ for i in X().__provides__] ['I1', 'I2'] """ def test_classProvides_before_implements(): """Special descriptor for class __provides__ The descriptor caches the implementedBy info, so that we can get declarations for objects without instance-specific interfaces a bit quicker. For example:: >>> from zope.interface import Interface, classProvides >>> class IFooFactory(Interface): ... pass >>> class IFoo(Interface): ... pass >>> class C(object): ... classProvides(IFooFactory) ... implements(IFoo) >>> [i.getName() for i in C.__provides__] ['IFooFactory'] >>> [i.getName() for i in C().__provides__] ['IFoo'] """ def test_getting_spec_for_proxied_builtin_class(): """ In general, we should be able to get a spec for a proxied class if someone has declared or asked for a spec before. We don't want to depend on proxies in this (zope.interface) package, but we do want to work with proxies. Proxies have the effect that a class's __dict__ cannot be gotten. Further, for built-in classes, we can't save, and thus, cannot get, any class attributes. We'll emulate this by treating a plain object as a class: >>> cls = object() We'll create an implements specification: >>> import zope.interface.declarations >>> impl = zope.interface.declarations.Implements(I1, I2) Now, we'll emulate a declaration for a built-in type by putting it in BuiltinImplementationSpecifications: >>> zope.interface.declarations.BuiltinImplementationSpecifications[ ... cls] = impl Now, we should be able to get it back: >>> implementedBy(cls) is impl True Of course, we don't want to leave it there. :) >>> del zope.interface.declarations.BuiltinImplementationSpecifications[ ... cls] """ def test_declaration_get(): """ We can get definitions from a declaration: >>> import zope.interface >>> class I1(zope.interface.Interface): ... a11 = zope.interface.Attribute('a11') ... a12 = zope.interface.Attribute('a12') >>> class I2(zope.interface.Interface): ... a21 = zope.interface.Attribute('a21') ... a22 = zope.interface.Attribute('a22') ... a12 = zope.interface.Attribute('a212') >>> class I11(I1): ... a11 = zope.interface.Attribute('a111') >>> decl = zope.interface.Declaration(I11, I2) >>> decl.get('a11') is I11.get('a11') True >>> decl.get('a12') is I1.get('a12') True >>> decl.get('a21') is I2.get('a21') True >>> decl.get('a22') is I2.get('a22') True >>> decl.get('a') >>> decl.get('a', 42) 42 We get None even with no interfaces: >>> decl = zope.interface.Declaration() >>> decl.get('a11') >>> decl.get('a11', 42) 42 We get new data if e change interface bases: >>> decl.__bases__ = I11, I2 >>> decl.get('a11') is I11.get('a11') True """ def test_classImplements_after_classImplementsOnly_issue_402(): """http://www.zope.org/Collectors/Zope3-dev/402 >>> from zope.interface import * >>> class I1(Interface): ... pass >>> class I2(Interface): ... pass >>> class C: ... implements(I1) >>> class C2: ... implementsOnly(I2) >>> class I3(Interface): ... pass >>> [i.__name__ for i in providedBy(C2()).__iro__] ['I2', 'Interface'] >>> classImplements(C2, I3) >>> [i.__name__ for i in providedBy(C2()).__iro__] ['I2', 'I3', 'Interface'] >>> class I4(Interface): ... pass >>> classImplements(C2, I4) >>> [i.__name__ for i in providedBy(C2()).__iro__] ['I2', 'I3', 'I4', 'Interface'] """ def test_picklability_of_implements_specifications(): """ Sometimes, we need to pickle implements specs. We should be able to do so as long as the class is picklable. >>> import pickle >>> pickle.loads(pickle.dumps(implementedBy(C))) is implementedBy(C) True """ def test_provided_by_with_slots(): """ This is an edge case: if the __slots__ of a class contain '__provides__', using providedBy() on that class should still work (this occurs, for example, when providing an adapter for a concrete class.) >>> import zope.interface >>> class Slotted(object): ... __slots__ = ('__provides__') >>> class IFoo(zope.interface.Interface): ... pass >>> IFoo.providedBy(Slotted) False """ def test_suite(): return unittest.TestSuite(( unittest.makeSuite(Test), doctest.DocTestSuite("zope.interface.declarations"), doctest.DocTestSuite(), )) zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/m1.py0000644000175000017500000000153612214017572024116 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test module that declares an interface $Id: m1.py 110536 2010-04-06 02:59:44Z tseaver $ """ from zope.interface import Interface, moduleProvides class I1(Interface): pass class I2(Interface): pass moduleProvides(I1, I2) zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/test_sorting.py0000644000175000017500000000256612214017572026331 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test interface sorting $Id: test_sorting.py 110699 2010-04-09 08:16:17Z regebro $ """ from unittest import TestCase, TestSuite, main, makeSuite from zope.interface import Interface class I1(Interface): pass class I2(I1): pass class I3(I1): pass class I4(Interface): pass class I5(I4): pass class I6(I2): pass class Test(TestCase): def test(self): l = [I1, I3, I5, I6, I4, I2] l.sort() self.assertEqual(l, [I1, I2, I3, I4, I5, I6]) def test_w_None(self): l = [I1, None, I3, I5, I6, I4, I2] l.sort() self.assertEqual(l, [I1, I2, I3, I4, I5, I6, None]) def test_suite(): return TestSuite(( makeSuite(Test), )) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/test_odd_declarations.py0000644000175000017500000001501412214017572030132 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test interface declarations against ExtensionClass-like classes. These tests are to make sure we do something sane in the presence of classic ExtensionClass classes and instances. $Id: test_odd_declarations.py 110736 2010-04-11 10:59:30Z regebro $ """ import doctest import unittest from zope.interface.tests import odd from zope.interface import Interface, implements, classProvides from zope.interface import directlyProvides, providedBy, directlyProvidedBy from zope.interface import classImplements, classImplementsOnly, implementedBy class I1(Interface): pass class I2(Interface): pass class I3(Interface): pass class I31(I3): pass class I4(Interface): pass class I5(Interface): pass class Odd(object): __metaclass__ = odd.MetaClass class B(Odd): __implemented__ = I2 # TODO: We are going to need more magic to make classProvides work with odd # classes. This will work in the next iteration. For now, we'll use # a different mechanism. # from zope.interface import classProvides class A(Odd): pass classImplements(A, I1) class C(A, B): pass classImplements(C, I31) class Test(unittest.TestCase): def test_ObjectSpecification(self): c = C() directlyProvides(c, I4) self.assertEqual([i.getName() for i in providedBy(c)], ['I4', 'I31', 'I1', 'I2'] ) self.assertEqual([i.getName() for i in providedBy(c).flattened()], ['I4', 'I31', 'I3', 'I1', 'I2', 'Interface'] ) self.assert_(I1 in providedBy(c)) self.failIf(I3 in providedBy(c)) self.assert_(providedBy(c).extends(I3)) self.assert_(providedBy(c).extends(I31)) self.failIf(providedBy(c).extends(I5)) class COnly(A, B): pass classImplementsOnly(COnly, I31) class D(COnly): pass classImplements(D, I5) classImplements(D, I5) c = D() directlyProvides(c, I4) self.assertEqual([i.getName() for i in providedBy(c)], ['I4', 'I5', 'I31']) self.assertEqual([i.getName() for i in providedBy(c).flattened()], ['I4', 'I5', 'I31', 'I3', 'Interface']) self.failIf(I1 in providedBy(c)) self.failIf(I3 in providedBy(c)) self.assert_(providedBy(c).extends(I3)) self.failIf(providedBy(c).extends(I1)) self.assert_(providedBy(c).extends(I31)) self.assert_(providedBy(c).extends(I5)) class COnly(A, B): __implemented__ = I31 class D(COnly): pass classImplements(D, I5) classImplements(D, I5) c = D() directlyProvides(c, I4) self.assertEqual([i.getName() for i in providedBy(c)], ['I4', 'I5', 'I31']) self.assertEqual([i.getName() for i in providedBy(c).flattened()], ['I4', 'I5', 'I31', 'I3', 'Interface']) self.failIf(I1 in providedBy(c)) self.failIf(I3 in providedBy(c)) self.assert_(providedBy(c).extends(I3)) self.failIf(providedBy(c).extends(I1)) self.assert_(providedBy(c).extends(I31)) self.assert_(providedBy(c).extends(I5)) def test_classImplements(self): class A(Odd): implements(I3) class B(Odd): implements(I4) class C(A, B): pass classImplements(C, I1, I2) self.assertEqual([i.getName() for i in implementedBy(C)], ['I1', 'I2', 'I3', 'I4']) classImplements(C, I5) self.assertEqual([i.getName() for i in implementedBy(C)], ['I1', 'I2', 'I5', 'I3', 'I4']) def test_classImplementsOnly(self): class A(Odd): implements(I3) class B(Odd): implements(I4) class C(A, B): pass classImplementsOnly(C, I1, I2) self.assertEqual([i.__name__ for i in implementedBy(C)], ['I1', 'I2']) def test_directlyProvides(self): class IA1(Interface): pass class IA2(Interface): pass class IB(Interface): pass class IC(Interface): pass class A(Odd): pass classImplements(A, IA1, IA2) class B(Odd): pass classImplements(B, IB) class C(A, B): pass classImplements(C, IC) ob = C() directlyProvides(ob, I1, I2) self.assert_(I1 in providedBy(ob)) self.assert_(I2 in providedBy(ob)) self.assert_(IA1 in providedBy(ob)) self.assert_(IA2 in providedBy(ob)) self.assert_(IB in providedBy(ob)) self.assert_(IC in providedBy(ob)) directlyProvides(ob, directlyProvidedBy(ob)-I2) self.assert_(I1 in providedBy(ob)) self.failIf(I2 in providedBy(ob)) self.failIf(I2 in providedBy(ob)) directlyProvides(ob, directlyProvidedBy(ob), I2) self.assert_(I2 in providedBy(ob)) def test_directlyProvides_fails_for_odd_class(self): self.assertRaises(TypeError, directlyProvides, C, I5) # see above def TODO_test_classProvides_fails_for_odd_class(self): try: class A(Odd): classProvides(I1) except TypeError: pass # Sucess self.assert_(False, "Shouldn't be able to use directlyProvides on odd class." ) def test_implementedBy(self): class I2(I1): pass class C1(Odd): pass classImplements(C1, I2) class C2(C1): pass classImplements(C2, I3) self.assertEqual([i.getName() for i in implementedBy(C2)], ['I3', 'I2']) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(Test)) suite.addTest(doctest.DocTestSuite(odd)) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/ifoo.py0000644000175000017500000000160712214017572024534 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """IFoo test module $Id: ifoo.py 110536 2010-04-06 02:59:44Z tseaver $ """ from zope.interface import Interface class IFoo(Interface): """ Dummy interface for unit tests. """ def bar(baz): """ Just a note. """ zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/odd.py0000644000175000017500000000607212214017572024347 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Odd meta class that doesn't subclass type. This is used for testing support for ExtensionClass in new interfaces. >>> class A(object): ... __metaclass__ = MetaClass ... a = 1 ... >>> A.__name__ 'A' >>> A.__bases__ == (object,) True >>> class B(object): ... __metaclass__ = MetaClass ... b = 1 ... >>> class C(A, B): pass ... >>> C.__name__ 'C' >>> int(C.__bases__ == (A, B)) 1 >>> a = A() >>> aa = A() >>> a.a 1 >>> aa.a 1 >>> aa.a = 2 >>> a.a 1 >>> aa.a 2 >>> c = C() >>> c.a 1 >>> c.b 1 >>> c.b = 2 >>> c.b 2 >>> C.c = 1 >>> c.c 1 >>> import sys >>> if sys.version[0] == '2': # This test only makes sense under Python 2.x ... from types import ClassType ... assert not isinstance(C, (type, ClassType)) >>> int(C.__class__.__class__ is C.__class__) 1 $Id: odd.py 110699 2010-04-09 08:16:17Z regebro $ """ # class OddClass is an odd meta class class MetaMetaClass(type): def __getattribute__(self, name): if name == '__class__': return self return type.__getattribute__(self, name) class MetaClass(object): """Odd classes """ __metaclass__ = MetaMetaClass def __init__(self, name, bases, dict): self.__name__ = name self.__bases__ = bases self.__dict__.update(dict) def __call__(self): return OddInstance(self) def __getattr__(self, name): for b in self.__bases__: v = getattr(b, name, self) if v is not self: return v raise AttributeError(name) def __repr__(self): return "" % (self.__name__, hex(id(self))) class OddInstance(object): def __init__(self, cls): self.__dict__['__class__'] = cls def __getattribute__(self, name): dict = object.__getattribute__(self, '__dict__') if name == '__dict__': return dict v = dict.get(name, self) if v is not self: return v return getattr(dict['__class__'], name) def __setattr__(self, name, v): self.__dict__[name] = v def __delattr__(self, name): del self.__dict__[name] def __repr__(self): return "" % ( self.__class__.__name__, hex(id(self))) # DocTest: if __name__ == "__main__": import doctest, __main__ doctest.testmod(__main__, isprivate=lambda *a: False) zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/test_verify.py0000644000175000017500000001100412214017572026133 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interface Verify tests $Id: test_verify.py 110536 2010-04-06 02:59:44Z tseaver $ """ import doctest import unittest from zope.interface import Interface, implements, classImplements, Attribute from zope.interface.verify import verifyClass, verifyObject from zope.interface.exceptions import DoesNotImplement, BrokenImplementation from zope.interface.exceptions import BrokenMethodImplementation class Test(unittest.TestCase): def testNotImplemented(self): class C(object): pass class I(Interface): pass self.assertRaises(DoesNotImplement, verifyClass, I, C) classImplements(C, I) verifyClass(I, C) def testMissingAttr(self): class I(Interface): def f(): pass class C(object): implements(I) self.assertRaises(BrokenImplementation, verifyClass, I, C) C.f=lambda self: None verifyClass(I, C) def testMissingAttr_with_Extended_Interface(self): class II(Interface): def f(): pass class I(II): pass class C(object): implements(I) self.assertRaises(BrokenImplementation, verifyClass, I, C) C.f=lambda self: None verifyClass(I, C) def testWrongArgs(self): class I(Interface): def f(a): pass class C(object): def f(self, b): pass implements(I) # We no longer require names to match. #self.assertRaises(BrokenMethodImplementation, verifyClass, I, C) C.f=lambda self, a: None verifyClass(I, C) C.f=lambda self, **kw: None self.assertRaises(BrokenMethodImplementation, verifyClass, I, C) C.f=lambda self, a, *args: None verifyClass(I, C) C.f=lambda self, a, *args, **kw: None verifyClass(I, C) C.f=lambda self, *args: None verifyClass(I, C) def testExtraArgs(self): class I(Interface): def f(a): pass class C(object): def f(self, a, b): pass implements(I) self.assertRaises(BrokenMethodImplementation, verifyClass, I, C) C.f=lambda self, a: None verifyClass(I, C) C.f=lambda self, a, b=None: None verifyClass(I, C) def testNoVar(self): class I(Interface): def f(a, *args): pass class C(object): def f(self, a): pass implements(I) self.assertRaises(BrokenMethodImplementation, verifyClass, I, C) C.f=lambda self, a, *foo: None verifyClass(I, C) def testNoKW(self): class I(Interface): def f(a, **args): pass class C(object): def f(self, a): pass implements(I) self.assertRaises(BrokenMethodImplementation, verifyClass, I, C) C.f=lambda self, a, **foo: None verifyClass(I, C) def testModule(self): from zope.interface.tests.ifoo import IFoo from zope.interface.tests import dummy verifyObject(IFoo, dummy) def testMethodForAttr(self): class IFoo(Interface): foo = Attribute("The foo Attribute") class Foo: implements(IFoo) def foo(self): pass verifyClass(IFoo, Foo) def testNonMethodForMethod(self): class IBar(Interface): def foo(): pass class Bar: implements(IBar) foo = 1 self.assertRaises(BrokenMethodImplementation, verifyClass, IBar, Bar) def test_suite(): loader=unittest.TestLoader() return unittest.TestSuite(( doctest.DocFileSuite( '../verify.txt', optionflags=doctest.NORMALIZE_WHITESPACE), loader.loadTestsFromTestCase(Test), )) if __name__=='__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/test_element.py0000644000175000017500000000254412214017572026271 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test Element meta-class. $Id: test_element.py 110536 2010-04-06 02:59:44Z tseaver $ """ import unittest from zope.interface.interface import Element class TestElement(unittest.TestCase): def test_taggedValues(self): """Test that we can update tagged values of more than one element """ e1 = Element("foo") e2 = Element("bar") e1.setTaggedValue("x", 1) e2.setTaggedValue("x", 2) self.assertEqual(e1.getTaggedValue("x"), 1) self.assertEqual(e2.getTaggedValue("x"), 2) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestElement)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/m2.py0000644000175000017500000000134312214017572024113 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test module that doesn't declare an interface $Id: m2.py 110536 2010-04-06 02:59:44Z tseaver $ """ zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/__init__.py0000644000175000017500000000074112214017572025335 0ustar arnauarnauimport os import unittest def additional_tests(): suites = unittest.TestSuite() for file in os.listdir(os.path.dirname(__file__)): if file.endswith('.py') and file!='__init__.py': name = os.path.splitext(file)[0] module = __import__('.'.join((__name__, name)), globals(), locals(), [name]) if hasattr(module, 'test_suite'): suites.addTests(module.test_suite()) return suites zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/test_advice.py0000644000175000017500000001246212214017572026073 0ustar arnauarnau ############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for advice This module was adapted from 'protocols.tests.advice', part of the Python Enterprise Application Kit (PEAK). Please notify the PEAK authors (pje@telecommunity.com and tsarna@sarna.org) if bugs are found or Zope-specific changes are required, so that the PEAK version of this module can be kept in sync. PEAK is a Python application framework that interoperates with (but does not require) Zope 3 and Twisted. It provides tools for manipulating UML models, object-relational persistence, aspect-oriented programming, and more. Visit the PEAK home page at http://peak.telecommunity.com for more information. $Id: test_advice.py 110736 2010-04-11 10:59:30Z regebro $ """ import unittest from unittest import TestCase, makeSuite, TestSuite from zope.interface.advice import addClassAdvisor, determineMetaclass from zope.interface.advice import getFrameInfo import sys def ping(log, value): def pong(klass): log.append((value,klass)) return [klass] addClassAdvisor(pong) try: from types import ClassType class ClassicClass: __metaclass__ = ClassType classLevelFrameInfo = getFrameInfo(sys._getframe()) except ImportError: pass class NewStyleClass: __metaclass__ = type classLevelFrameInfo = getFrameInfo(sys._getframe()) moduleLevelFrameInfo = getFrameInfo(sys._getframe()) class FrameInfoTest(TestCase): classLevelFrameInfo = getFrameInfo(sys._getframe()) def checkModuleInfo(self): kind, module, f_locals, f_globals = moduleLevelFrameInfo self.assertEquals(kind, "module") for d in module.__dict__, f_locals, f_globals: self.assert_(d is globals()) def checkClassicClassInfo(self): kind, module, f_locals, f_globals = ClassicClass.classLevelFrameInfo self.assertEquals(kind, "class") self.assert_(f_locals is ClassicClass.__dict__) # ??? for d in module.__dict__, f_globals: self.assert_(d is globals()) def checkNewStyleClassInfo(self): kind, module, f_locals, f_globals = NewStyleClass.classLevelFrameInfo self.assertEquals(kind, "class") for d in module.__dict__, f_globals: self.assert_(d is globals()) def checkCallInfo(self): kind, module, f_locals, f_globals = getFrameInfo(sys._getframe()) self.assertEquals(kind, "function call") self.assert_(f_locals is locals()) # ??? for d in module.__dict__, f_globals: self.assert_(d is globals()) class AdviceTests(TestCase): def checkOrder(self): log = [] class Foo(object): ping(log, 1) ping(log, 2) ping(log, 3) # Strip the list nesting for i in 1,2,3: self.assert_(isinstance(Foo, list)) Foo, = Foo self.assertEquals(log, [(1, Foo), (2, [Foo]), (3, [[Foo]])]) def TODOcheckOutside(self): # Disabled because the check does not work with doctest tests. try: ping([], 1) except SyntaxError: pass else: raise AssertionError( "Should have detected advice outside class body" ) def checkDoubleType(self): if sys.hexversion >= 0x02030000: return # you can't duplicate bases in 2.3 class aType(type,type): ping([],1) aType, = aType self.assert_(aType.__class__ is type) def checkSingleExplicitMeta(self): class M(type): pass class C(M): __metaclass__ = M ping([],1) C, = C self.assert_(C.__class__ is M) def checkMixedMetas(self): class M1(type): pass class M2(type): pass class B1: __metaclass__ = M1 class B2: __metaclass__ = M2 try: class C(B1,B2): ping([],1) except TypeError: pass else: raise AssertionError("Should have gotten incompatibility error") class M3(M1,M2): pass class C(B1,B2): __metaclass__ = M3 ping([],1) self.assert_(isinstance(C,list)) C, = C self.assert_(isinstance(C,M3)) def checkMetaOfClass(self): class metameta(type): pass class meta(type): __metaclass__ = metameta self.assertEquals(determineMetaclass((meta, type)), metameta) TestClasses = (AdviceTests, FrameInfoTest) def test_suite(): if sys.version[0] == '2': return TestSuite([makeSuite(t,'check') for t in TestClasses]) else: # Advise metaclasses doesn't work in Python 3 return [] if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/foodforthought.txt0000644000175000017500000000321512214017572027025 0ustar arnauarnau================================ Food-based subscription examples ================================ This file gives more subscription examples using a cooking-based example:: >>> from zope.interface.adapter import AdapterRegistry >>> registry = AdapterRegistry() >>> import zope.interface >>> class IAnimal(zope.interface.Interface): ... pass >>> class IPoultry(IAnimal): ... pass >>> class IChicken(IPoultry): ... pass >>> class ISeafood(IAnimal): ... pass Adapting to some other interface for which there is no subscription adapter returns an empty sequence:: >>> class IRecipe(zope.interface.Interface): ... pass >>> class ISausages(IRecipe): ... pass >>> class INoodles(IRecipe): ... pass >>> class IKFC(IRecipe): ... pass >>> list(registry.subscriptions([IPoultry], IRecipe)) [] unless we define a subscription:: >>> registry.subscribe([IAnimal], ISausages, 'sausages') >>> list(registry.subscriptions([IPoultry], ISausages)) ['sausages'] And define another subscription adapter:: >>> registry.subscribe([IPoultry], INoodles, 'noodles') >>> meals = list(registry.subscriptions([IPoultry], IRecipe)) >>> meals.sort() >>> meals ['noodles', 'sausages'] >>> registry.subscribe([IChicken], IKFC, 'kfc') >>> meals = list(registry.subscriptions([IChicken], IRecipe)) >>> meals.sort() >>> meals ['kfc', 'noodles', 'sausages'] And the answer for poultry hasn't changed:: >>> meals = list(registry.subscriptions([IPoultry], IRecipe)) >>> meals.sort() >>> meals ['noodles', 'sausages'] zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/test_interface.py0000644000175000017500000004022012214017572026571 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test Interface implementation """ import doctest import unittest import sys class InterfaceTests(unittest.TestCase): def _makeDerivedInterface(self): from zope.interface import Interface from zope.interface import Attribute class _I1(Interface): a1 = Attribute("This is an attribute") def f11(): pass def f12(): pass f12.optional = 1 class _I1_(_I1): pass class _I1__(_I1_): pass class _I2(_I1__): def f21(): pass def f22(): pass f23 = f22 return _I2 def testInterfaceSetOnAttributes(self): from zope.interface.tests.unitfixtures import FooInterface self.assertEqual(FooInterface['foobar'].interface, FooInterface) self.assertEqual(FooInterface['aMethod'].interface, FooInterface) def testClassImplements(self): from zope.interface.tests.unitfixtures import A from zope.interface.tests.unitfixtures import B from zope.interface.tests.unitfixtures import C from zope.interface.tests.unitfixtures import D from zope.interface.tests.unitfixtures import E from zope.interface.tests.unitfixtures import I1 from zope.interface.tests.unitfixtures import I2 from zope.interface.tests.unitfixtures import IC self.assert_(IC.implementedBy(C)) self.assert_(I1.implementedBy(A)) self.assert_(I1.implementedBy(B)) self.assert_(not I1.implementedBy(C)) self.assert_(I1.implementedBy(D)) self.assert_(I1.implementedBy(E)) self.assert_(not I2.implementedBy(A)) self.assert_(I2.implementedBy(B)) self.assert_(not I2.implementedBy(C)) # No longer after interfacegeddon # self.assert_(not I2.implementedBy(D)) self.assert_(not I2.implementedBy(E)) def testUtil(self): from zope.interface import implementedBy from zope.interface import providedBy from zope.interface.tests.unitfixtures import A from zope.interface.tests.unitfixtures import B from zope.interface.tests.unitfixtures import C from zope.interface.tests.unitfixtures import I1 from zope.interface.tests.unitfixtures import I2 from zope.interface.tests.unitfixtures import IC self.assert_(IC in implementedBy(C)) self.assert_(I1 in implementedBy(A)) self.assert_(not I1 in implementedBy(C)) self.assert_(I2 in implementedBy(B)) self.assert_(not I2 in implementedBy(C)) self.assert_(IC in providedBy(C())) self.assert_(I1 in providedBy(A())) self.assert_(not I1 in providedBy(C())) self.assert_(I2 in providedBy(B())) self.assert_(not I2 in providedBy(C())) def testObjectImplements(self): from zope.interface.tests.unitfixtures import A from zope.interface.tests.unitfixtures import B from zope.interface.tests.unitfixtures import C from zope.interface.tests.unitfixtures import D from zope.interface.tests.unitfixtures import E from zope.interface.tests.unitfixtures import I1 from zope.interface.tests.unitfixtures import I2 from zope.interface.tests.unitfixtures import IC self.assert_(IC.providedBy(C())) self.assert_(I1.providedBy(A())) self.assert_(I1.providedBy(B())) self.assert_(not I1.providedBy(C())) self.assert_(I1.providedBy(D())) self.assert_(I1.providedBy(E())) self.assert_(not I2.providedBy(A())) self.assert_(I2.providedBy(B())) self.assert_(not I2.providedBy(C())) # Not after interface geddon # self.assert_(not I2.providedBy(D())) self.assert_(not I2.providedBy(E())) def testDeferredClass(self): from zope.interface.tests.unitfixtures import A from zope.interface.exceptions import BrokenImplementation a = A() self.assertRaises(BrokenImplementation, a.ma) def testInterfaceExtendsInterface(self): from zope.interface.tests.unitfixtures import BazInterface from zope.interface.tests.unitfixtures import BarInterface from zope.interface.tests.unitfixtures import BobInterface from zope.interface.tests.unitfixtures import FunInterface self.assert_(BazInterface.extends(BobInterface)) self.assert_(BazInterface.extends(BarInterface)) self.assert_(BazInterface.extends(FunInterface)) self.assert_(not BobInterface.extends(FunInterface)) self.assert_(not BobInterface.extends(BarInterface)) self.assert_(BarInterface.extends(FunInterface)) self.assert_(not BarInterface.extends(BazInterface)) def testVerifyImplementation(self): from zope.interface.verify import verifyClass from zope.interface import Interface from zope.interface.tests.unitfixtures import Foo from zope.interface.tests.unitfixtures import FooInterface from zope.interface.tests.unitfixtures import I1 self.assert_(verifyClass(FooInterface, Foo)) self.assert_(Interface.providedBy(I1)) def test_names(self): iface = self._makeDerivedInterface() names = list(iface.names()) names.sort() self.assertEqual(names, ['f21', 'f22', 'f23']) all = list(iface.names(all=True)) all.sort() self.assertEqual(all, ['a1', 'f11', 'f12', 'f21', 'f22', 'f23']) def test_namesAndDescriptions(self): iface = self._makeDerivedInterface() names = [nd[0] for nd in iface.namesAndDescriptions()] names.sort() self.assertEqual(names, ['f21', 'f22', 'f23']) names = [nd[0] for nd in iface.namesAndDescriptions(1)] names.sort() self.assertEqual(names, ['a1', 'f11', 'f12', 'f21', 'f22', 'f23']) for name, d in iface.namesAndDescriptions(1): self.assertEqual(name, d.__name__) def test_getDescriptionFor(self): iface = self._makeDerivedInterface() self.assertEqual(iface.getDescriptionFor('f11').__name__, 'f11') self.assertEqual(iface.getDescriptionFor('f22').__name__, 'f22') self.assertEqual(iface.queryDescriptionFor('f33', self), self) self.assertRaises(KeyError, iface.getDescriptionFor, 'f33') def test___getitem__(self): iface = self._makeDerivedInterface() self.assertEqual(iface['f11'].__name__, 'f11') self.assertEqual(iface['f22'].__name__, 'f22') self.assertEqual(iface.get('f33', self), self) self.assertRaises(KeyError, iface.__getitem__, 'f33') def test___contains__(self): iface = self._makeDerivedInterface() self.failUnless('f11' in iface) self.failIf('f33' in iface) def test___iter__(self): iface = self._makeDerivedInterface() names = list(iter(iface)) names.sort() self.assertEqual(names, ['a1', 'f11', 'f12', 'f21', 'f22', 'f23']) def testAttr(self): iface = self._makeDerivedInterface() description = iface.getDescriptionFor('a1') self.assertEqual(description.__name__, 'a1') self.assertEqual(description.__doc__, 'This is an attribute') def testFunctionAttributes(self): # Make sure function attributes become tagged values. from zope.interface import Interface class ITest(Interface): def method(): pass method.optional = 1 method = ITest['method'] self.assertEqual(method.getTaggedValue('optional'), 1) def testInvariant(self): from zope.interface.exceptions import Invalid from zope.interface import directlyProvides from zope.interface.tests.unitfixtures import BarGreaterThanFoo from zope.interface.tests.unitfixtures import ifFooThenBar from zope.interface.tests.unitfixtures import IInvariant from zope.interface.tests.unitfixtures import InvariantC from zope.interface.tests.unitfixtures import ISubInvariant # set up o = InvariantC() directlyProvides(o, IInvariant) # a helper def errorsEqual(self, o, error_len, error_msgs, iface=None): if iface is None: iface = IInvariant self.assertRaises(Invalid, iface.validateInvariants, o) e = [] try: iface.validateInvariants(o, e) except Invalid, error: self.assertEquals(error.args[0], e) else: self._assert(0) # validateInvariants should always raise # Invalid self.assertEquals(len(e), error_len) msgs = [error.args[0] for error in e] msgs.sort() for msg in msgs: self.assertEquals(msg, error_msgs.pop(0)) # the tests self.assertEquals(IInvariant.getTaggedValue('invariants'), [ifFooThenBar]) self.assertEquals(IInvariant.validateInvariants(o), None) o.bar = 27 self.assertEquals(IInvariant.validateInvariants(o), None) o.foo = 42 self.assertEquals(IInvariant.validateInvariants(o), None) del o.bar errorsEqual(self, o, 1, ['If Foo, then Bar!']) # nested interfaces with invariants: self.assertEquals(ISubInvariant.getTaggedValue('invariants'), [BarGreaterThanFoo]) o = InvariantC() directlyProvides(o, ISubInvariant) o.foo = 42 # even though the interface has changed, we should still only have one # error. errorsEqual(self, o, 1, ['If Foo, then Bar!'], ISubInvariant) # however, if we set foo to 0 (Boolean False) and bar to a negative # number then we'll get the new error o.foo = 2 o.bar = 1 errorsEqual(self, o, 1, ['Please, Boo MUST be greater than Foo!'], ISubInvariant) # and if we set foo to a positive number and boo to 0, we'll # get both errors! o.foo = 1 o.bar = 0 errorsEqual(self, o, 2, ['If Foo, then Bar!', 'Please, Boo MUST be greater than Foo!'], ISubInvariant) # for a happy ending, we'll make the invariants happy o.foo = 1 o.bar = 2 self.assertEquals(IInvariant.validateInvariants(o), None) # woohoo # now we'll do two invariants on the same interface, # just to make sure that a small # multi-invariant interface is at least minimally tested. o = InvariantC() directlyProvides(o, IInvariant) o.foo = 42 old_invariants = IInvariant.getTaggedValue('invariants') invariants = old_invariants[:] invariants.append(BarGreaterThanFoo) # if you really need to mutate, # then this would be the way to do it. Probably a bad idea, though. :-) IInvariant.setTaggedValue('invariants', invariants) # # even though the interface has changed, we should still only have one # error. errorsEqual(self, o, 1, ['If Foo, then Bar!']) # however, if we set foo to 0 (Boolean False) and bar to a negative # number then we'll get the new error o.foo = 2 o.bar = 1 errorsEqual(self, o, 1, ['Please, Boo MUST be greater than Foo!']) # and if we set foo to a positive number and boo to 0, we'll # get both errors! o.foo = 1 o.bar = 0 errorsEqual(self, o, 2, ['If Foo, then Bar!', 'Please, Boo MUST be greater than Foo!']) # for another happy ending, we'll make the invariants happy again o.foo = 1 o.bar = 2 self.assertEquals(IInvariant.validateInvariants(o), None) # bliss # clean up IInvariant.setTaggedValue('invariants', old_invariants) def test___doc___element(self): from zope.interface import Interface from zope.interface import Attribute class I(Interface): "xxx" self.assertEqual(I.__doc__, "xxx") self.assertEqual(list(I), []) class I(Interface): "xxx" __doc__ = Attribute('the doc') self.assertEqual(I.__doc__, "") self.assertEqual(list(I), ['__doc__']) def testIssue228(self): from zope.interface import Interface # Test for http://collector.zope.org/Zope3-dev/228 if sys.version[0] == '3': # No old style classes in Python 3, so the test becomes moot. return class I(Interface): "xxx" class Bad: __providedBy__ = None # Old style classes don't have a '__class__' attribute self.failUnlessRaises(AttributeError, I.providedBy, Bad) def test_comparison_with_same_named_instance_in_other_module(self): # See LP #570942 from zope.interface.tests.ifoo import IFoo as IFoo1 from zope.interface.tests.ifoo_other import IFoo as IFoo2 self.failUnless(IFoo1 < IFoo2) self.failUnless(IFoo1 <= IFoo2) self.failIf(IFoo1 == IFoo2) self.failUnless(IFoo1 != IFoo2) self.failIf(IFoo1 >= IFoo2) self.failIf(IFoo1 > IFoo2) if sys.version_info >= (2, 4): def test_invariant_as_decorator(): """Invaiants can be deined in line >>> from zope.interface.exceptions import Invalid >>> from zope.interface import Interface >>> from zope.interface import Attribute >>> from zope.interface import implements >>> from zope.interface import invariant >>> class IRange(Interface): ... min = Attribute("Lower bound") ... max = Attribute("Upper bound") ... ... @invariant ... def range_invariant(ob): ... if ob.max < ob.min: ... raise Invalid('max < min') >>> class Range(object): ... implements(IRange) ... ... def __init__(self, min, max): ... self.min, self.max = min, max >>> from zope.interface.exceptions import Invalid >>> IRange.validateInvariants(Range(1,2)) >>> IRange.validateInvariants(Range(1,1)) >>> try: ... IRange.validateInvariants(Range(2,1)) ... except Invalid, e: ... str(e) 'max < min' """ def test_description_cache_management(): """ See https://bugs.launchpad.net/zope.interface/+bug/185974 There was a bug where the cache used by Specification.get() was not cleared when the bases were changed. >>> from zope.interface import Interface >>> from zope.interface import Attribute >>> class I1(Interface): ... a = Attribute('a') >>> class I2(I1): ... pass >>> class I3(I2): ... pass >>> I3.get('a') is I1.get('a') True >>> I2.__bases__ = (Interface,) >>> I3.get('a') is None True """ def test_suite(): suite = unittest.makeSuite(InterfaceTests) suite.addTest(doctest.DocTestSuite("zope.interface.interface")) if sys.version_info >= (2, 4): suite.addTest(doctest.DocTestSuite()) suite.addTest(doctest.DocFileSuite( '../README.txt', globs={'__name__': '__main__'}, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS, )) suite.addTest(doctest.DocFileSuite( '../README.ru.txt', globs={'__name__': '__main__'}, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS, )) return suite zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/unitfixtures.py0000644000175000017500000000713112214017572026347 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unit Test Fixtures $Id: unitfixtures.py 110536 2010-04-06 02:59:44Z tseaver $ """ from zope.interface import Interface, invariant from zope.interface.interface import Attribute from zope.interface.exceptions import Invalid class mytest(Interface): pass class C(object): def m1(self, a, b): "return 1" return 1 def m2(self, a, b): "return 2" return 2 # testInstancesOfClassImplements # YAGNI IC=Interface.impliedInterface(C) class IC(Interface): def m1(a, b): "return 1" def m2(a, b): "return 2" C.__implemented__=IC class I1(Interface): def ma(): "blah" class I2(I1): pass class I3(Interface): pass class I4(Interface): pass class A(I1.deferred()): __implemented__=I1 class B(object): __implemented__=I2, I3 class D(A, B): pass class E(A, B): __implemented__ = A.__implemented__, C.__implemented__ class FooInterface(Interface): """ This is an Abstract Base Class """ foobar = Attribute("fuzzed over beyond all recognition") def aMethod(foo, bar, bingo): """ This is aMethod """ def anotherMethod(foo=6, bar="where you get sloshed", bingo=(1,3,)): """ This is anotherMethod """ def wammy(zip, *argues): """ yadda yadda """ def useless(**keywords): """ useless code is fun! """ class Foo(object): """ A concrete class """ __implemented__ = FooInterface, foobar = "yeah" def aMethod(self, foo, bar, bingo): """ This is aMethod """ return "barf!" def anotherMethod(self, foo=6, bar="where you get sloshed", bingo=(1,3,)): """ This is anotherMethod """ return "barf!" def wammy(self, zip, *argues): """ yadda yadda """ return "barf!" def useless(self, **keywords): """ useless code is fun! """ return "barf!" foo_instance = Foo() class Blah(object): pass new = Interface.__class__ FunInterface = new('FunInterface') BarInterface = new('BarInterface', [FunInterface]) BobInterface = new('BobInterface') BazInterface = new('BazInterface', [BobInterface, BarInterface]) # fixtures for invariant tests def ifFooThenBar(obj): if getattr(obj, 'foo', None) and not getattr(obj, 'bar', None): raise Invalid('If Foo, then Bar!') class IInvariant(Interface): foo = Attribute('foo') bar = Attribute('bar; must eval to Boolean True if foo does') invariant(ifFooThenBar) def BarGreaterThanFoo(obj): foo = getattr(obj, 'foo', None) bar = getattr(obj, 'bar', None) if foo is not None and isinstance(foo, type(bar)): # type checking should be handled elsewhere (like, say, # schema); these invariants should be intra-interface # constraints. This is a hacky way to do it, maybe, but you # get the idea if not bar > foo: raise Invalid('Please, Boo MUST be greater than Foo!') class ISubInvariant(IInvariant): invariant(BarGreaterThanFoo) class InvariantC(object): pass zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/test_adapter.py0000644000175000017500000002557512214017572026271 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Adapter registry tests $Id: test_adapter.py 110736 2010-04-11 10:59:30Z regebro $ """ import doctest import unittest import zope.interface from zope.interface.adapter import AdapterRegistry class IF0(zope.interface.Interface): pass class IF1(IF0): pass class IB0(zope.interface.Interface): pass class IB1(IB0): pass class IR0(zope.interface.Interface): pass class IR1(IR0): pass def test_multi_adapter_get_best_match(): """ >>> registry = AdapterRegistry() >>> class IB2(IB0): ... pass >>> class IB3(IB2, IB1): ... pass >>> class IB4(IB1, IB2): ... pass >>> registry.register([None, IB1], IR0, '', 'A1') >>> registry.register([None, IB0], IR0, '', 'A0') >>> registry.register([None, IB2], IR0, '', 'A2') >>> registry.lookup((IF1, IB1), IR0, '') 'A1' >>> registry.lookup((IF1, IB2), IR0, '') 'A2' >>> registry.lookup((IF1, IB0), IR0, '') 'A0' >>> registry.lookup((IF1, IB3), IR0, '') 'A2' >>> registry.lookup((IF1, IB4), IR0, '') 'A1' """ def test_multi_adapter_lookupAll_get_best_matches(): """ >>> registry = AdapterRegistry() >>> class IB2(IB0): ... pass >>> class IB3(IB2, IB1): ... pass >>> class IB4(IB1, IB2): ... pass >>> registry.register([None, IB1], IR0, '', 'A1') >>> registry.register([None, IB0], IR0, '', 'A0') >>> registry.register([None, IB2], IR0, '', 'A2') >>> tuple(registry.lookupAll((IF1, IB1), IR0))[0][1] 'A1' >>> tuple(registry.lookupAll((IF1, IB2), IR0))[0][1] 'A2' >>> tuple(registry.lookupAll((IF1, IB0), IR0))[0][1] 'A0' >>> tuple(registry.lookupAll((IF1, IB3), IR0))[0][1] 'A2' >>> tuple(registry.lookupAll((IF1, IB4), IR0))[0][1] 'A1' """ def test_multi_adapter_w_default(): """ >>> registry = AdapterRegistry() >>> registry.register([None, None], IB1, 'bob', 'A0') >>> registry.lookup((IF1, IR1), IB0, 'bob') 'A0' >>> registry.register([None, IR0], IB1, 'bob', 'A1') >>> registry.lookup((IF1, IR1), IB0, 'bob') 'A1' >>> registry.lookup((IF1, IR1), IB0, 'bruce') >>> registry.register([None, IR1], IB1, 'bob', 'A2') >>> registry.lookup((IF1, IR1), IB0, 'bob') 'A2' """ def test_multi_adapter_w_inherited_and_multiple_registrations(): """ >>> registry = AdapterRegistry() >>> class IX(zope.interface.Interface): ... pass >>> registry.register([IF0, IR0], IB1, 'bob', 'A1') >>> registry.register([IF1, IX], IB1, 'bob', 'AX') >>> registry.lookup((IF1, IR1), IB0, 'bob') 'A1' """ def test_named_adapter_with_default(): """Query a named simple adapter >>> registry = AdapterRegistry() If we ask for a named adapter, we won't get a result unless there is a named adapter, even if the object implements the interface: >>> registry.lookup([IF1], IF0, 'bob') >>> registry.register([None], IB1, 'bob', 'A1') >>> registry.lookup([IF1], IB0, 'bob') 'A1' >>> registry.lookup([IF1], IB0, 'bruce') >>> registry.register([None], IB0, 'bob', 'A2') >>> registry.lookup([IF1], IB0, 'bob') 'A2' """ def test_multi_adapter_gets_closest_provided(): """ >>> registry = AdapterRegistry() >>> registry.register([IF1, IR0], IB0, 'bob', 'A1') >>> registry.register((IF1, IR0), IB1, 'bob', 'A2') >>> registry.lookup((IF1, IR1), IB0, 'bob') 'A1' >>> registry = AdapterRegistry() >>> registry.register([IF1, IR0], IB1, 'bob', 'A2') >>> registry.register([IF1, IR0], IB0, 'bob', 'A1') >>> registry.lookup([IF1, IR0], IB0, 'bob') 'A1' >>> registry = AdapterRegistry() >>> registry.register([IF1, IR0], IB0, 'bob', 'A1') >>> registry.register([IF1, IR1], IB1, 'bob', 'A2') >>> registry.lookup([IF1, IR1], IB0, 'bob') 'A2' >>> registry = AdapterRegistry() >>> registry.register([IF1, IR1], IB1, 'bob', 2) >>> registry.register([IF1, IR0], IB0, 'bob', 1) >>> registry.lookup([IF1, IR1], IB0, 'bob') 2 """ def test_multi_adapter_check_non_default_dont_hide_default(): """ >>> registry = AdapterRegistry() >>> class IX(zope.interface.Interface): ... pass >>> registry.register([None, IR0], IB0, 'bob', 1) >>> registry.register([IF1, IX], IB0, 'bob', 2) >>> registry.lookup([IF1, IR1], IB0, 'bob') 1 """ def test_adapter_hook_with_factory_producing_None(): """ >>> registry = AdapterRegistry() >>> default = object() >>> class Object1(object): ... zope.interface.implements(IF0) >>> class Object2(object): ... zope.interface.implements(IF0) >>> def factory(context): ... if isinstance(context, Object1): ... return 'adapter' ... return None >>> registry.register([IF0], IB0, '', factory) >>> registry.adapter_hook(IB0, Object1()) 'adapter' >>> registry.adapter_hook(IB0, Object2()) is None True >>> registry.adapter_hook(IB0, Object2(), default=default) is default True """ def test_adapter_registry_update_upon_interface_bases_change(): """ Let's first create a adapter registry and a simple adaptation hook: >>> globalRegistry = AdapterRegistry() >>> def _hook(iface, ob, lookup=globalRegistry.lookup1): ... factory = lookup(zope.interface.providedBy(ob), iface) ... if factory is None: ... return None ... else: ... return factory(ob) >>> zope.interface.interface.adapter_hooks.append(_hook) Now we create some interfaces and an implementation: >>> class IX(zope.interface.Interface): ... pass >>> class IY(zope.interface.Interface): ... pass >>> class X(object): ... pass >>> class Y(object): ... zope.interface.implements(IY) ... def __init__(self, original): ... self.original=original and register an adapter: >>> globalRegistry.register((IX,), IY, '', Y) at first, we still expect the adapter lookup from `X` to `IY` to fail: >>> IY(X()) #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Traceback (most recent call last): ... TypeError: ('Could not adapt', , ) But after we declare an interface on the class `X`, it should pass: >>> zope.interface.classImplementsOnly(X, IX) >>> IY(X()) #doctest: +ELLIPSIS >>> hook = zope.interface.interface.adapter_hooks.pop() """ def test_changing_declarations(): """ If we change declarations for a class, those adapter lookup should eflect the changes: >>> class I1(zope.interface.Interface): ... pass >>> class I2(zope.interface.Interface): ... pass >>> registry = AdapterRegistry() >>> registry.register([I1], I2, '', 42) >>> class C: ... pass >>> registry.lookup([zope.interface.implementedBy(C)], I2, '') >>> zope.interface.classImplements(C, I1) >>> registry.lookup([zope.interface.implementedBy(C)], I2, '') 42 """ def test_correct_multi_adapter_lookup(): """ >>> registry = AdapterRegistry() >>> registry.register([IF0, IB1], IR0, '', 'A01') >>> registry.register([IF1, IB0], IR0, '', 'A10') >>> registry.lookup((IF1, IB1), IR0, '') 'A10' """ def test_duplicate_bases(): """ There was a bug that caused problems if a spec had multiple bases: >>> class I(zope.interface.Interface): ... pass >>> class I2(I, I): ... pass >>> registry = AdapterRegistry() >>> registry.register([I2], IR0, 'x', 'X') >>> registry.lookup((I2, ), IR0, 'x') 'X' >>> registry.register([I2], IR0, 'y', 'Y') >>> registry.lookup((I2, ), IR0, 'x') 'X' >>> registry.lookup((I2, ), IR0, 'y') 'Y' """ def test_register_objects_with_cmp(): """ The registry should never use == as that will tend to fail when objects are picky about what they are compared with: >>> class Picky: ... def __cmp__(self, other): ... raise TypeError("I\'m too picky for comparison!") >>> class I(zope.interface.Interface): ... pass >>> class I2(I, I): ... pass >>> registry = AdapterRegistry() >>> picky = Picky() >>> registry.register([I2], IR0, '', picky) >>> registry.unregister([I2], IR0, '', picky) >>> registry.subscribe([I2], IR0, picky) >>> registry.unsubscribe([I2], IR0, picky) """ def test_unregister_cleans_up_empties(): """ >>> class I(zope.interface.Interface): ... pass >>> class IP(zope.interface.Interface): ... pass >>> class C(object): ... pass >>> registry = AdapterRegistry() >>> registry.register([], IP, '', C) >>> registry.register([I], IP, '', C) >>> registry.register([I], IP, 'name', C) >>> registry.register([I, I], IP, '', C) >>> len(registry._adapters) 3 >>> map(len, registry._adapters) [1, 1, 1] >>> registry.unregister([], IP, '', C) >>> registry.unregister([I], IP, '', C) >>> registry.unregister([I], IP, 'name', C) >>> registry.unregister([I, I], IP, '', C) >>> registry._adapters [] """ def test_unsubscribe_cleans_up_empties(): """ >>> class I1(zope.interface.Interface): ... pass >>> class I2(zope.interface.Interface): ... pass >>> class IP(zope.interface.Interface): ... pass >>> registry = AdapterRegistry() >>> def handler(event): ... pass >>> registry.subscribe([I1], I1, handler) >>> registry.subscribe([I2], I1, handler) >>> len(registry._subscribers) 2 >>> map(len, registry._subscribers) [0, 2] >>> registry.unsubscribe([I1], I1, handler) >>> registry.unsubscribe([I2], I1, handler) >>> registry._subscribers [] """ def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('../adapter.txt', '../adapter.ru.txt', '../human.txt', '../human.ru.txt', 'foodforthought.txt', globs={'__name__': '__main__'}), doctest.DocTestSuite(), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/ifoo_other.py0000644000175000017500000000152312214017572025732 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """IFoo test module """ from zope.interface import Interface class IFoo(Interface): """ Dummy interface for unit tests. """ def bar(baz): """ Just a note. """ zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/dummy.py0000644000175000017500000000151612214017572024732 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Dummy Module $Id: dummy.py 110736 2010-04-11 10:59:30Z regebro $ """ from zope.interface import moduleProvides from zope.interface.tests.ifoo import IFoo moduleProvides(IFoo) def bar(baz): pass zope2.13-2.13.21/source/zope.interface/src/zope/interface/tests/test_document.py0000644000175000017500000000274712214017572026463 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Documentation tests. $Id: test_document.py 110536 2010-04-06 02:59:44Z tseaver $ """ from unittest import TestCase, main, makeSuite from zope.interface import Interface, Attribute class Test(TestCase): def testBlech(self): from zope.interface.document import asStructuredText self.assertEqual(asStructuredText(I2), '''\ I2 I2 doc This interface extends: o _I1 Attributes: a1 -- no documentation a2 -- a2 doc Methods: f21() -- f21 doc f22() -- no documentation f23() -- f23 doc ''') def test_suite(): return makeSuite(Test) class _I1(Interface): def f11(): pass def f12(): pass class I2(_I1): "I2 doc" a1 = Attribute('a1') a2 = Attribute('a2', 'a2 doc') def f21(): "f21 doc" def f22(): pass def f23(): "f23 doc" if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.interface/src/zope/interface/adapter.py0000644000175000017500000005355612214017572024070 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Adapter management $Id: adapter.py 110699 2010-04-09 08:16:17Z regebro $ """ import weakref from zope.interface import providedBy, Interface, ro _marker = object class BaseAdapterRegistry(object): # List of methods copied from lookup sub-objects: _delegated = ('lookup', 'queryMultiAdapter', 'lookup1', 'queryAdapter', 'adapter_hook', 'lookupAll', 'names', 'subscriptions', 'subscribers') # All registries maintain a generation that can be used by verifying # registries _generation = 0 def __init__(self, bases=()): # The comments here could be improved. Possibly this bit needs # explaining in a separate document, as the comments here can # be quite confusing. /regebro # {order -> {required -> {provided -> {name -> value}}}} # Here "order" is actually an index in a list, "required" and # "provided" are interfaces, and "required" is really a nested # key. So, for example: # for order == 0 (that is, self._adapters[0]), we have: # {provided -> {name -> value}} # but for order == 2 (that is, self._adapters[2]), we have: # {r1 -> {r2 -> {provided -> {name -> value}}}} # self._adapters = [] # {order -> {required -> {provided -> {name -> [value]}}}} # where the remarks about adapters above apply self._subscribers = [] # Set, with a reference count, keeping track of the interfaces # for which we have provided components: self._provided = {} # Create ``_v_lookup`` object to perform lookup. We make this a # separate object to to make it easier to implement just the # lookup functionality in C. This object keeps track of cache # invalidation data in two kinds of registries. # Invalidating registries have caches that are invalidated # when they or their base registies change. An invalidating # registry can only have invalidating registries as bases. # See LookupBasePy below for the pertinent logic. # Verifying registies can't rely on getting invalidation messages, # so have to check the generations of base registries to determine # if their cache data are current. See VerifyingBasePy below # for the pertinent object. self._createLookup() # Setting the bases causes the registries described above # to be initialized (self._setBases -> self.changed -> # self._v_lookup.changed). self.__bases__ = bases def _setBases(self, bases): self.__dict__['__bases__'] = bases self.ro = ro.ro(self) self.changed(self) __bases__ = property(lambda self: self.__dict__['__bases__'], lambda self, bases: self._setBases(bases), ) def _createLookup(self): self._v_lookup = self.LookupClass(self) for name in self._delegated: self.__dict__[name] = getattr(self._v_lookup, name) def changed(self, originally_changed): self._generation += 1 self._v_lookup.changed(originally_changed) def register(self, required, provided, name, value): if value is None: self.unregister(required, provided, name, value) return required = tuple(map(_convert_None_to_Interface, required)) name = _normalize_name(name) order = len(required) byorder = self._adapters while len(byorder) <= order: byorder.append({}) components = byorder[order] key = required + (provided,) for k in key: d = components.get(k) if d is None: d = {} components[k] = d components = d if components.get(name) is value: return components[name] = value n = self._provided.get(provided, 0) + 1 self._provided[provided] = n if n == 1: self._v_lookup.add_extendor(provided) self.changed(self) def registered(self, required, provided, name=u''): required = tuple(map(_convert_None_to_Interface, required)) name = _normalize_name(name) order = len(required) byorder = self._adapters if len(byorder) <= order: return None components = byorder[order] key = required + (provided,) for k in key: d = components.get(k) if d is None: return None components = d return components.get(name) def unregister(self, required, provided, name, value=None): required = tuple(map(_convert_None_to_Interface, required)) order = len(required) byorder = self._adapters if order >= len(byorder): return False components = byorder[order] key = required + (provided,) # Keep track of how we got to `components`: lookups = [] # Keep track of how we got to `components`: lookups = [] for k in key: d = components.get(k) if d is None: return lookups.append((components, k)) components = d old = components.get(name) if old is None: return if (value is not None) and (old is not value): return del components[name] if not components: # Clean out empty containers, since we don't want our keys # to reference global objects (interfaces) unnecessarily. # This is often a problem when an interface is slated for # removal; a hold-over entry in the registry can make it # difficult to remove such interfaces. for comp, k in reversed(lookups): d = comp[k] if d: break else: del comp[k] while byorder and not byorder[-1]: del byorder[-1] n = self._provided[provided] - 1 if n == 0: del self._provided[provided] self._v_lookup.remove_extendor(provided) else: self._provided[provided] = n self.changed(self) def subscribe(self, required, provided, value): required = tuple(map(_convert_None_to_Interface, required)) name = u'' order = len(required) byorder = self._subscribers while len(byorder) <= order: byorder.append({}) components = byorder[order] key = required + (provided,) for k in key: d = components.get(k) if d is None: d = {} components[k] = d components = d components[name] = components.get(name, ()) + (value, ) if provided is not None: n = self._provided.get(provided, 0) + 1 self._provided[provided] = n if n == 1: self._v_lookup.add_extendor(provided) self.changed(self) def unsubscribe(self, required, provided, value=None): required = tuple(map(_convert_None_to_Interface, required)) order = len(required) byorder = self._subscribers if order >= len(byorder): return components = byorder[order] key = required + (provided,) # Keep track of how we got to `components`: lookups = [] # Keep track of how we got to `components`: lookups = [] for k in key: d = components.get(k) if d is None: return lookups.append((components, k)) components = d old = components.get(u'') if not old: return if value is None: new = () else: new = tuple([v for v in old if v is not value]) if new == old: return if new: components[u''] = new else: # Instead of setting components[u''] = new, we clean out # empty containers, since we don't want our keys to # reference global objects (interfaces) unnecessarily. This # is often a problem when an interface is slated for # removal; a hold-over entry in the registry can make it # difficult to remove such interfaces. if u'' in components: del components[u''] for comp, k in reversed(lookups): d = comp[k] if d: break else: del comp[k] while byorder and not byorder[-1]: del byorder[-1] if provided is not None: n = self._provided[provided] + len(new) - len(old) if n == 0: del self._provided[provided] self._v_lookup.remove_extendor(provided) self.changed(self) # XXX hack to fake out twisted's use of a private api. We need to get them # to use the new registed method. def get(self, _): class XXXTwistedFakeOut: selfImplied = {} return XXXTwistedFakeOut _not_in_mapping = object() class LookupBasePy(object): def __init__(self): self._cache = {} self._mcache = {} self._scache = {} def changed(self, ignored=None): self._cache.clear() self._mcache.clear() self._scache.clear() def _getcache(self, provided, name): cache = self._cache.get(provided) if cache is None: cache = {} self._cache[provided] = cache if name: c = cache.get(name) if c is None: c = {} cache[name] = c cache = c return cache def lookup(self, required, provided, name=u'', default=None): cache = self._getcache(provided, name) if len(required) == 1: result = cache.get(required[0], _not_in_mapping) else: result = cache.get(tuple(required), _not_in_mapping) if result is _not_in_mapping: result = self._uncached_lookup(required, provided, name) if len(required) == 1: cache[required[0]] = result else: cache[tuple(required)] = result if result is None: return default return result def lookup1(self, required, provided, name=u'', default=None): cache = self._getcache(provided, name) result = cache.get(required, _not_in_mapping) if result is _not_in_mapping: return self.lookup((required, ), provided, name, default) if result is None: return default return result def queryAdapter(self, object, provided, name=u'', default=None): return self.adapter_hook(provided, object, name, default) def adapter_hook(self, provided, object, name=u'', default=None): required = providedBy(object) cache = self._getcache(provided, name) factory = cache.get(required, _not_in_mapping) if factory is _not_in_mapping: factory = self.lookup((required, ), provided, name) if factory is not None: result = factory(object) if result is not None: return result return default def lookupAll(self, required, provided): cache = self._mcache.get(provided) if cache is None: cache = {} self._mcache[provided] = cache required = tuple(required) result = cache.get(required, _not_in_mapping) if result is _not_in_mapping: result = self._uncached_lookupAll(required, provided) cache[required] = result return result def subscriptions(self, required, provided): cache = self._scache.get(provided) if cache is None: cache = {} self._scache[provided] = cache required = tuple(required) result = cache.get(required, _not_in_mapping) if result is _not_in_mapping: result = self._uncached_subscriptions(required, provided) cache[required] = result return result LookupBase = LookupBasePy class VerifyingBasePy(LookupBasePy): def changed(self, originally_changed): LookupBasePy.changed(self, originally_changed) self._verify_ro = self._registry.ro[1:] self._verify_generations = [r._generation for r in self._verify_ro] def _verify(self): if ([r._generation for r in self._verify_ro] != self._verify_generations): self.changed(None) def _getcache(self, provided, name): self._verify() return LookupBasePy._getcache(self, provided, name) def lookupAll(self, required, provided): self._verify() return LookupBasePy.lookupAll(self, required, provided) def subscriptions(self, required, provided): self._verify() return LookupBasePy.subscriptions(self, required, provided) VerifyingBase = VerifyingBasePy try: import _zope_interface_coptimizations except ImportError: pass else: from _zope_interface_coptimizations import LookupBase, VerifyingBase class AdapterLookupBase(object): def __init__(self, registry): self._registry = registry self._required = {} self.init_extendors() super(AdapterLookupBase, self).__init__() def changed(self, ignored=None): super(AdapterLookupBase, self).changed(None) for r in self._required.keys(): r = r() if r is not None: r.unsubscribe(self) self._required.clear() # Extendors # --------- # When given an target interface for an adapter lookup, we need to consider # adapters for interfaces that extend the target interface. This is # what the extendors dictionary is about. It tells us all of the # interfaces that extend an interface for which there are adapters # registered. # We could separate this by order and name, thus reducing the # number of provided interfaces to search at run time. The tradeoff, # however, is that we have to store more information. For example, # is the same interface is provided for multiple names and if the # interface extends many interfaces, we'll have to keep track of # a fair bit of information for each name. It's better to # be space efficient here and be time efficient in the cache # implementation. # TODO: add invalidation when a provided interface changes, in case # the interface's __iro__ has changed. This is unlikely enough that # we'll take our chances for now. def init_extendors(self): self._extendors = {} for p in self._registry._provided: self.add_extendor(p) def add_extendor(self, provided): _extendors = self._extendors for i in provided.__iro__: extendors = _extendors.get(i, ()) _extendors[i] = ( [e for e in extendors if provided.isOrExtends(e)] + [provided] + [e for e in extendors if not provided.isOrExtends(e)] ) def remove_extendor(self, provided): _extendors = self._extendors for i in provided.__iro__: _extendors[i] = [e for e in _extendors.get(i, ()) if e != provided] def _subscribe(self, *required): _refs = self._required for r in required: ref = r.weakref() if ref not in _refs: r.subscribe(self) _refs[ref] = 1 def _uncached_lookup(self, required, provided, name=u''): result = None order = len(required) for registry in self._registry.ro: byorder = registry._adapters if order >= len(byorder): continue extendors = registry._v_lookup._extendors.get(provided) if not extendors: continue components = byorder[order] result = _lookup(components, required, extendors, name, 0, order) if result is not None: break self._subscribe(*required) return result def queryMultiAdapter(self, objects, provided, name=u'', default=None): factory = self.lookup(map(providedBy, objects), provided, name) if factory is None: return default result = factory(*objects) if result is None: return default return result def _uncached_lookupAll(self, required, provided): order = len(required) result = {} for registry in reversed(self._registry.ro): byorder = registry._adapters if order >= len(byorder): continue extendors = registry._v_lookup._extendors.get(provided) if not extendors: continue components = byorder[order] _lookupAll(components, required, extendors, result, 0, order) self._subscribe(*required) return tuple(result.iteritems()) def names(self, required, provided): return [c[0] for c in self.lookupAll(required, provided)] def _uncached_subscriptions(self, required, provided): order = len(required) result = [] for registry in reversed(self._registry.ro): byorder = registry._subscribers if order >= len(byorder): continue if provided is None: extendors = (provided, ) else: extendors = registry._v_lookup._extendors.get(provided) if extendors is None: continue _subscriptions(byorder[order], required, extendors, u'', result, 0, order) self._subscribe(*required) return result def subscribers(self, objects, provided): subscriptions = self.subscriptions(map(providedBy, objects), provided) if provided is None: result = () for subscription in subscriptions: subscription(*objects) else: result = [] for subscription in subscriptions: subscriber = subscription(*objects) if subscriber is not None: result.append(subscriber) return result class AdapterLookup(AdapterLookupBase, LookupBase): pass class AdapterRegistry(BaseAdapterRegistry): LookupClass = AdapterLookup def __init__(self, bases=()): # AdapterRegisties are invalidating registries, so # we need to keep track of out invalidating subregistries. self._v_subregistries = weakref.WeakKeyDictionary() super(AdapterRegistry, self).__init__(bases) def _addSubregistry(self, r): self._v_subregistries[r] = 1 def _removeSubregistry(self, r): if r in self._v_subregistries: del self._v_subregistries[r] def _setBases(self, bases): old = self.__dict__.get('__bases__', ()) for r in old: if r not in bases: r._removeSubregistry(self) for r in bases: if r not in old: r._addSubregistry(self) super(AdapterRegistry, self)._setBases(bases) def changed(self, originally_changed): super(AdapterRegistry, self).changed(originally_changed) for sub in self._v_subregistries.keys(): sub.changed(originally_changed) class VerifyingAdapterLookup(AdapterLookupBase, VerifyingBase): pass class VerifyingAdapterRegistry(BaseAdapterRegistry): LookupClass = VerifyingAdapterLookup def _convert_None_to_Interface(x): if x is None: return Interface else: return x def _normalize_name(name): if isinstance(name, basestring): return unicode(name) raise TypeError("name must be a regular or unicode string") def _lookup(components, specs, provided, name, i, l): if i < l: for spec in specs[i].__sro__: comps = components.get(spec) if comps: r = _lookup(comps, specs, provided, name, i+1, l) if r is not None: return r else: for iface in provided: comps = components.get(iface) if comps: r = comps.get(name) if r is not None: return r return None def _lookupAll(components, specs, provided, result, i, l): if i < l: for spec in reversed(specs[i].__sro__): comps = components.get(spec) if comps: _lookupAll(comps, specs, provided, result, i+1, l) else: for iface in reversed(provided): comps = components.get(iface) if comps: result.update(comps) def _subscriptions(components, specs, provided, name, result, i, l): if i < l: for spec in reversed(specs[i].__sro__): comps = components.get(spec) if comps: _subscriptions(comps, specs, provided, name, result, i+1, l) else: for iface in reversed(provided): comps = components.get(iface) if comps: comps = comps.get(name) if comps: result.extend(comps) zope2.13-2.13.21/source/zope.interface/src/zope/interface/declarations.py0000644000175000017500000012106412214017572025106 0ustar arnauarnau############################################################################## # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. ############################################################################## """Implementation of interface declarations There are three flavors of declarations: - Declarations are used to simply name declared interfaces. - ImplementsDeclarations are used to express the interfaces that a class implements (that instances of the class provides). Implements specifications support inheriting interfaces. - ProvidesDeclarations are used to express interfaces directly provided by objects. $Id: declarations.py 110736 2010-04-11 10:59:30Z regebro $ """ __docformat__ = 'restructuredtext' import sys import weakref from zope.interface.interface import InterfaceClass, Specification from zope.interface.interface import SpecificationBase from types import ModuleType, MethodType, FunctionType from zope.interface.advice import addClassAdvisor # Registry of class-implementation specifications BuiltinImplementationSpecifications = {} class Declaration(Specification): """Interface declarations""" def __init__(self, *interfaces): Specification.__init__(self, _normalizeargs(interfaces)) def changed(self, originally_changed): Specification.changed(self, originally_changed) try: del self._v_attrs except AttributeError: pass def __contains__(self, interface): """Test whether an interface is in the specification for example: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(I1): pass ... >>> class I3(Interface): pass ... >>> class I4(I3): pass ... >>> spec = Declaration(I2, I3) >>> spec = Declaration(I4, spec) >>> int(I1 in spec) 0 >>> int(I2 in spec) 1 >>> int(I3 in spec) 1 >>> int(I4 in spec) 1 """ return self.extends(interface) and interface in self.interfaces() def __iter__(self): """Return an iterator for the interfaces in the specification for example: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(I1): pass ... >>> class I3(Interface): pass ... >>> class I4(I3): pass ... >>> spec = Declaration(I2, I3) >>> spec = Declaration(I4, spec) >>> i = iter(spec) >>> [x.getName() for x in i] ['I4', 'I2', 'I3'] >>> list(i) [] """ return self.interfaces() def flattened(self): """Return an iterator of all included and extended interfaces for example: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(I1): pass ... >>> class I3(Interface): pass ... >>> class I4(I3): pass ... >>> spec = Declaration(I2, I3) >>> spec = Declaration(I4, spec) >>> i = spec.flattened() >>> [x.getName() for x in i] ['I4', 'I2', 'I1', 'I3', 'Interface'] >>> list(i) [] """ return iter(self.__iro__) def __sub__(self, other): """Remove interfaces from a specification Examples: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(I1): pass ... >>> class I3(Interface): pass ... >>> class I4(I3): pass ... >>> spec = Declaration() >>> [iface.getName() for iface in spec] [] >>> spec -= I1 >>> [iface.getName() for iface in spec] [] >>> spec -= Declaration(I1, I2) >>> [iface.getName() for iface in spec] [] >>> spec = Declaration(I2, I4) >>> [iface.getName() for iface in spec] ['I2', 'I4'] >>> [iface.getName() for iface in spec - I4] ['I2'] >>> [iface.getName() for iface in spec - I1] ['I4'] >>> [iface.getName() for iface ... in spec - Declaration(I3, I4)] ['I2'] """ return Declaration( *[i for i in self.interfaces() if not [j for j in other.interfaces() if i.extends(j, 0)] ] ) def __add__(self, other): """Add two specifications or a specification and an interface Examples: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(I1): pass ... >>> class I3(Interface): pass ... >>> class I4(I3): pass ... >>> spec = Declaration() >>> [iface.getName() for iface in spec] [] >>> [iface.getName() for iface in spec+I1] ['I1'] >>> [iface.getName() for iface in I1+spec] ['I1'] >>> spec2 = spec >>> spec += I1 >>> [iface.getName() for iface in spec] ['I1'] >>> [iface.getName() for iface in spec2] [] >>> spec2 += Declaration(I3, I4) >>> [iface.getName() for iface in spec2] ['I3', 'I4'] >>> [iface.getName() for iface in spec+spec2] ['I1', 'I3', 'I4'] >>> [iface.getName() for iface in spec2+spec] ['I3', 'I4', 'I1'] """ seen = {} result = [] for i in self.interfaces(): if i not in seen: seen[i] = 1 result.append(i) for i in other.interfaces(): if i not in seen: seen[i] = 1 result.append(i) return Declaration(*result) __radd__ = __add__ ############################################################################## # # Implementation specifications # # These specify interfaces implemented by instances of classes class Implements(Declaration): # class whose specification should be used as additional base inherit = None # interfaces actually declared for a class declared = () __name__ = '?' def __repr__(self): return '' % (self.__name__) def __reduce__(self): return implementedBy, (self.inherit, ) def implementedByFallback(cls): """Return the interfaces implemented for a class' instances The value returned is an IDeclaration. for example: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(I1): pass ... >>> class I3(Interface): pass ... >>> class I4(I3): pass ... >>> class C1(object): ... implements(I2) >>> class C2(C1): ... implements(I3) >>> [i.getName() for i in implementedBy(C2)] ['I3', 'I2'] Really, any object should be able to receive a successful answer, even an instance: >>> class Callable(object): ... def __call__(self): ... return self >>> implementedBy(Callable()) Note that the name of the spec ends with a '?', because the `Callable` instance does not have a `__name__` attribute. """ # This also manages storage of implementation specifications try: spec = cls.__dict__.get('__implemented__') except AttributeError: # we can't get the class dict. This is probably due to a # security proxy. If this is the case, then probably no # descriptor was installed for the class. # We don't want to depend directly on zope.security in # zope.interface, but we'll try to make reasonable # accommodations in an indirect way. # We'll check to see if there's an implements: spec = getattr(cls, '__implemented__', None) if spec is None: # There's no spec stred in the class. Maybe its a builtin: spec = BuiltinImplementationSpecifications.get(cls) if spec is not None: return spec return _empty if spec.__class__ == Implements: # we defaulted to _empty or there was a spec. Good enough. # Return it. return spec # TODO: need old style __implements__ compatibility? # Hm, there's an __implemented__, but it's not a spec. Must be # an old-style declaration. Just compute a spec for it return Declaration(*_normalizeargs((spec, ))) if isinstance(spec, Implements): return spec if spec is None: spec = BuiltinImplementationSpecifications.get(cls) if spec is not None: return spec # TODO: need old style __implements__ compatibility? if spec is not None: # old-style __implemented__ = foo declaration spec = (spec, ) # tuplefy, as it might be just an int spec = Implements(*_normalizeargs(spec)) spec.inherit = None # old-style implies no inherit del cls.__implemented__ # get rid of the old-style declaration else: try: bases = cls.__bases__ except AttributeError: if not callable(cls): raise TypeError("ImplementedBy called for non-factory", cls) bases = () spec = Implements(*[implementedBy(c) for c in bases]) spec.inherit = cls spec.__name__ = (getattr(cls, '__module__', '?') or '?') + \ '.' + (getattr(cls, '__name__', '?') or '?') try: cls.__implemented__ = spec if not hasattr(cls, '__providedBy__'): cls.__providedBy__ = objectSpecificationDescriptor if (isinstance(cls, DescriptorAwareMetaClasses) and '__provides__' not in cls.__dict__): # Make sure we get a __provides__ descriptor cls.__provides__ = ClassProvides( cls, getattr(cls, '__class__', type(cls)), ) except TypeError: if not isinstance(cls, type): raise TypeError("ImplementedBy called for non-type", cls) BuiltinImplementationSpecifications[cls] = spec return spec implementedBy = implementedByFallback def classImplementsOnly(cls, *interfaces): """Declare the only interfaces implemented by instances of a class The arguments after the class are one or more interfaces or interface specifications (``IDeclaration`` objects). The interfaces given (including the interfaces in the specifications) replace any previous declarations. Consider the following example: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(Interface): pass ... >>> class I3(Interface): pass ... >>> class I4(Interface): pass ... >>> class A(object): ... implements(I3) >>> class B(object): ... implements(I4) >>> class C(A, B): ... pass >>> classImplementsOnly(C, I1, I2) >>> [i.getName() for i in implementedBy(C)] ['I1', 'I2'] Instances of ``C`` provide only ``I1``, ``I2``, and regardless of whatever interfaces instances of ``A`` and ``B`` implement. """ spec = implementedBy(cls) spec.declared = () spec.inherit = None classImplements(cls, *interfaces) def classImplements(cls, *interfaces): """Declare additional interfaces implemented for instances of a class The arguments after the class are one or more interfaces or interface specifications (``IDeclaration`` objects). The interfaces given (including the interfaces in the specifications) are added to any interfaces previously declared. Consider the following example: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(Interface): pass ... >>> class I3(Interface): pass ... >>> class I4(Interface): pass ... >>> class I5(Interface): pass ... >>> class A(object): ... implements(I3) >>> class B(object): ... implements(I4) >>> class C(A, B): ... pass >>> classImplements(C, I1, I2) >>> [i.getName() for i in implementedBy(C)] ['I1', 'I2', 'I3', 'I4'] >>> classImplements(C, I5) >>> [i.getName() for i in implementedBy(C)] ['I1', 'I2', 'I5', 'I3', 'I4'] Instances of ``C`` provide ``I1``, ``I2``, ``I5``, and whatever interfaces instances of ``A`` and ``B`` provide. """ spec = implementedBy(cls) spec.declared += tuple(_normalizeargs(interfaces)) # compute the bases bases = [] seen = {} for b in spec.declared: if b not in seen: seen[b] = 1 bases.append(b) if spec.inherit is not None: for c in spec.inherit.__bases__: b = implementedBy(c) if b not in seen: seen[b] = 1 bases.append(b) spec.__bases__ = tuple(bases) def _implements_advice(cls): interfaces, classImplements = cls.__dict__['__implements_advice_data__'] del cls.__implements_advice_data__ classImplements(cls, *interfaces) return cls class implementer: def __init__(self, *interfaces): self.interfaces = interfaces def __call__(self, ob): if isinstance(ob, DescriptorAwareMetaClasses): classImplements(ob, *self.interfaces) return ob spec = Implements(*self.interfaces) try: ob.__implemented__ = spec except AttributeError: raise TypeError("Can't declare implements", ob) return ob class implementer_only: def __init__(self, *interfaces): self.interfaces = interfaces def __call__(self, ob): if isinstance(ob, (FunctionType, MethodType)): # XXX Does this decorator make sense for anything but classes? # I don't think so. There can be no inheritance of interfaces # on a method pr function.... raise ValueError('The implementor_only decorator is not ' 'supported for methods or functions.') else: # Assume it's a class: classImplementsOnly(ob, *self.interfaces) return ob def _implements(name, interfaces, classImplements): frame = sys._getframe(2) locals = frame.f_locals # Try to make sure we were called from a class def. In 2.2.0 we can't # check for __module__ since it doesn't seem to be added to the locals # until later on. if (locals is frame.f_globals) or ( ('__module__' not in locals) and sys.version_info[:3] > (2, 2, 0)): raise TypeError(name+" can be used only from a class definition.") if '__implements_advice_data__' in locals: raise TypeError(name+" can be used only once in a class definition.") locals['__implements_advice_data__'] = interfaces, classImplements addClassAdvisor(_implements_advice, depth=3) def implements(*interfaces): """Declare interfaces implemented by instances of a class This function is called in a class definition. The arguments are one or more interfaces or interface specifications (IDeclaration objects). The interfaces given (including the interfaces in the specifications) are added to any interfaces previously declared. Previous declarations include declarations for base classes unless implementsOnly was used. This function is provided for convenience. It provides a more convenient way to call classImplements. For example:: implements(I1) is equivalent to calling:: classImplements(C, I1) after the class has been created. Consider the following example:: >>> from zope.interface import Interface >>> class IA1(Interface): pass ... >>> class IA2(Interface): pass ... >>> class IB(Interface): pass ... >>> class IC(Interface): pass ... >>> class A(object): ... implements(IA1, IA2) >>> class B(object): ... implements(IB) >>> class C(A, B): ... implements(IC) >>> ob = C() >>> int(IA1 in providedBy(ob)) 1 >>> int(IA2 in providedBy(ob)) 1 >>> int(IB in providedBy(ob)) 1 >>> int(IC in providedBy(ob)) 1 Instances of ``C`` implement ``I1``, ``I2``, and whatever interfaces instances of ``A`` and ``B`` implement. """ _implements("implements", interfaces, classImplements) def implementsOnly(*interfaces): """Declare the only interfaces implemented by instances of a class This function is called in a class definition. The arguments are one or more interfaces or interface specifications (IDeclaration objects). Previous declarations including declarations for base classes are overridden. This function is provided for convenience. It provides a more convenient way to call classImplementsOnly. For example:: implementsOnly(I1) is equivalent to calling:: classImplementsOnly(I1) after the class has been created. Consider the following example:: >>> from zope.interface import Interface >>> class IA1(Interface): pass ... >>> class IA2(Interface): pass ... >>> class IB(Interface): pass ... >>> class IC(Interface): pass ... >>> class A(object): ... implements(IA1, IA2) >>> class B(object): ... implements(IB) >>> class C(A, B): ... implementsOnly(IC) >>> ob = C() >>> int(IA1 in providedBy(ob)) 0 >>> int(IA2 in providedBy(ob)) 0 >>> int(IB in providedBy(ob)) 0 >>> int(IC in providedBy(ob)) 1 Instances of ``C`` implement ``IC``, regardless of what instances of ``A`` and ``B`` implement. """ _implements("implementsOnly", interfaces, classImplementsOnly) ############################################################################## # # Instance declarations class Provides(Declaration): # Really named ProvidesClass """Implement __provides__, the instance-specific specification When an object is pickled, we pickle the interfaces that it implements. """ def __init__(self, cls, *interfaces): self.__args = (cls, ) + interfaces self._cls = cls Declaration.__init__(self, *(interfaces + (implementedBy(cls), ))) def __reduce__(self): return Provides, self.__args __module__ = 'zope.interface' def __get__(self, inst, cls): """Make sure that a class __provides__ doesn't leak to an instance For example: >>> from zope.interface import Interface >>> class IFooFactory(Interface): pass ... >>> class C(object): ... pass >>> C.__provides__ = ProvidesClass(C, IFooFactory) >>> [i.getName() for i in C.__provides__] ['IFooFactory'] >>> getattr(C(), '__provides__', 0) 0 """ if inst is None and cls is self._cls: # We were accessed through a class, so we are the class' # provides spec. Just return this object, but only if we are # being called on the same class that we were defined for: return self raise AttributeError('__provides__') ProvidesClass = Provides # Registry of instance declarations # This is a memory optimization to allow objects to share specifications. InstanceDeclarations = weakref.WeakValueDictionary() def Provides(*interfaces): """Cache instance declarations Instance declarations are shared among instances that have the same declaration. The declarations are cached in a weak value dictionary. (Note that, in the examples below, we are going to make assertions about the size of the weakvalue dictionary. For the assertions to be meaningful, we need to force garbage collection to make sure garbage objects are, indeed, removed from the system. Depending on how Python is run, we may need to make multiple calls to be sure. We provide a collect function to help with this: >>> import gc >>> def collect(): ... for i in range(4): ... gc.collect() ) >>> collect() >>> before = len(InstanceDeclarations) >>> class C(object): ... pass >>> from zope.interface import Interface >>> class I(Interface): ... pass >>> c1 = C() >>> c2 = C() >>> len(InstanceDeclarations) == before 1 >>> directlyProvides(c1, I) >>> len(InstanceDeclarations) == before + 1 1 >>> directlyProvides(c2, I) >>> len(InstanceDeclarations) == before + 1 1 >>> del c1 >>> collect() >>> len(InstanceDeclarations) == before + 1 1 >>> del c2 >>> collect() >>> len(InstanceDeclarations) == before 1 """ spec = InstanceDeclarations.get(interfaces) if spec is None: spec = ProvidesClass(*interfaces) InstanceDeclarations[interfaces] = spec return spec Provides.__safe_for_unpickling__ = True try: from types import ClassType DescriptorAwareMetaClasses = ClassType, type except ImportError: # Python 3 DescriptorAwareMetaClasses = (type,) def directlyProvides(object, *interfaces): """Declare interfaces declared directly for an object The arguments after the object are one or more interfaces or interface specifications (``IDeclaration`` objects). The interfaces given (including the interfaces in the specifications) replace interfaces previously declared for the object. Consider the following example: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(Interface): pass ... >>> class IA1(Interface): pass ... >>> class IA2(Interface): pass ... >>> class IB(Interface): pass ... >>> class IC(Interface): pass ... >>> class A(object): ... implements(IA1, IA2) >>> class B(object): ... implements(IB) >>> class C(A, B): ... implements(IC) >>> ob = C() >>> directlyProvides(ob, I1, I2) >>> int(I1 in providedBy(ob)) 1 >>> int(I2 in providedBy(ob)) 1 >>> int(IA1 in providedBy(ob)) 1 >>> int(IA2 in providedBy(ob)) 1 >>> int(IB in providedBy(ob)) 1 >>> int(IC in providedBy(ob)) 1 The object, ``ob`` provides ``I1``, ``I2``, and whatever interfaces instances have been declared for instances of ``C``. To remove directly provided interfaces, use ``directlyProvidedBy`` and subtract the unwanted interfaces. For example: >>> directlyProvides(ob, directlyProvidedBy(ob)-I2) >>> int(I1 in providedBy(ob)) 1 >>> int(I2 in providedBy(ob)) 0 removes I2 from the interfaces directly provided by ``ob``. The object, ``ob`` no longer directly provides ``I2``, although it might still provide ``I2`` if it's class implements ``I2``. To add directly provided interfaces, use ``directlyProvidedBy`` and include additional interfaces. For example: >>> int(I2 in providedBy(ob)) 0 >>> directlyProvides(ob, directlyProvidedBy(ob), I2) adds ``I2`` to the interfaces directly provided by ob:: >>> int(I2 in providedBy(ob)) 1 """ # We need to avoid setting this attribute on meta classes that # don't support descriptors. # We can do away with this check when we get rid of the old EC cls = getattr(object, '__class__', None) if cls is not None and getattr(cls, '__class__', None) is cls: # It's a meta class (well, at least it it could be an extension class) if not isinstance(object, DescriptorAwareMetaClasses): raise TypeError("Attempt to make an interface declaration on a " "non-descriptor-aware class") interfaces = _normalizeargs(interfaces) if cls is None: cls = type(object) issub = False for damc in DescriptorAwareMetaClasses: if issubclass(cls, damc): issub = True break if issub: # we have a class or type. We'll use a special descriptor # that provides some extra caching object.__provides__ = ClassProvides(object, cls, *interfaces) else: object.__provides__ = Provides(cls, *interfaces) def alsoProvides(object, *interfaces): """Declare interfaces declared directly for an object The arguments after the object are one or more interfaces or interface specifications (``IDeclaration`` objects). The interfaces given (including the interfaces in the specifications) are added to the interfaces previously declared for the object. Consider the following example: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(Interface): pass ... >>> class IA1(Interface): pass ... >>> class IA2(Interface): pass ... >>> class IB(Interface): pass ... >>> class IC(Interface): pass ... >>> class A(object): ... implements(IA1, IA2) >>> class B(object): ... implements(IB) >>> class C(A, B): ... implements(IC) >>> ob = C() >>> directlyProvides(ob, I1) >>> int(I1 in providedBy(ob)) 1 >>> int(I2 in providedBy(ob)) 0 >>> int(IA1 in providedBy(ob)) 1 >>> int(IA2 in providedBy(ob)) 1 >>> int(IB in providedBy(ob)) 1 >>> int(IC in providedBy(ob)) 1 >>> alsoProvides(ob, I2) >>> int(I1 in providedBy(ob)) 1 >>> int(I2 in providedBy(ob)) 1 >>> int(IA1 in providedBy(ob)) 1 >>> int(IA2 in providedBy(ob)) 1 >>> int(IB in providedBy(ob)) 1 >>> int(IC in providedBy(ob)) 1 The object, ``ob`` provides ``I1``, ``I2``, and whatever interfaces instances have been declared for instances of ``C``. Notice that the alsoProvides just extends the provided interfaces. """ directlyProvides(object, directlyProvidedBy(object), *interfaces) def noLongerProvides(object, interface): """ This removes a directly provided interface from an object. Consider the following two interfaces: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(Interface): pass ... ``I1`` is provided through the class, ``I2`` is directly provided by the object: >>> class C(object): ... implements(I1) >>> c = C() >>> alsoProvides(c, I2) >>> I2.providedBy(c) True Remove I2 from c again: >>> noLongerProvides(c, I2) >>> I2.providedBy(c) False Removing an interface that is provided through the class is not possible: >>> noLongerProvides(c, I1) Traceback (most recent call last): ... ValueError: Can only remove directly provided interfaces. """ directlyProvides(object, directlyProvidedBy(object)-interface) if interface.providedBy(object): raise ValueError("Can only remove directly provided interfaces.") class ClassProvidesBasePy(object): def __get__(self, inst, cls): if cls is self._cls: # We only work if called on the class we were defined for if inst is None: # We were accessed through a class, so we are the class' # provides spec. Just return this object as is: return self return self._implements raise AttributeError('__provides__') ClassProvidesBase = ClassProvidesBasePy # Try to get C base: try: import _zope_interface_coptimizations except ImportError: pass else: from _zope_interface_coptimizations import ClassProvidesBase class ClassProvides(Declaration, ClassProvidesBase): """Special descriptor for class __provides__ The descriptor caches the implementedBy info, so that we can get declarations for objects without instance-specific interfaces a bit quicker. For example: >>> from zope.interface import Interface >>> class IFooFactory(Interface): ... pass >>> class IFoo(Interface): ... pass >>> class C(object): ... implements(IFoo) ... classProvides(IFooFactory) >>> [i.getName() for i in C.__provides__] ['IFooFactory'] >>> [i.getName() for i in C().__provides__] ['IFoo'] """ def __init__(self, cls, metacls, *interfaces): self._cls = cls self._implements = implementedBy(cls) self.__args = (cls, metacls, ) + interfaces Declaration.__init__(self, *(interfaces + (implementedBy(metacls), ))) def __reduce__(self): return self.__class__, self.__args # Copy base-class method for speed __get__ = ClassProvidesBase.__get__ def directlyProvidedBy(object): """Return the interfaces directly provided by the given object The value returned is an ``IDeclaration``. """ provides = getattr(object, "__provides__", None) if (provides is None # no spec or # We might have gotten the implements spec, as an # optimization. If so, it's like having only one base, that we # lop off to exclude class-supplied declarations: isinstance(provides, Implements) ): return _empty # Strip off the class part of the spec: return Declaration(provides.__bases__[:-1]) def classProvides(*interfaces): """Declare interfaces provided directly by a class This function is called in a class definition. The arguments are one or more interfaces or interface specifications (``IDeclaration`` objects). The given interfaces (including the interfaces in the specifications) are used to create the class's direct-object interface specification. An error will be raised if the module class has an direct interface specification. In other words, it is an error to call this function more than once in a class definition. Note that the given interfaces have nothing to do with the interfaces implemented by instances of the class. This function is provided for convenience. It provides a more convenient way to call directlyProvides for a class. For example:: classProvides(I1) is equivalent to calling:: directlyProvides(theclass, I1) after the class has been created. For example: >>> from zope.interface import Interface >>> class IFoo(Interface): pass ... >>> class IFooFactory(Interface): pass ... >>> class C(object): ... implements(IFoo) ... classProvides(IFooFactory) >>> [i.getName() for i in C.__providedBy__] ['IFooFactory'] >>> [i.getName() for i in C().__providedBy__] ['IFoo'] if equivalent to: >>> from zope.interface import Interface >>> class IFoo(Interface): pass ... >>> class IFooFactory(Interface): pass ... >>> class C(object): ... implements(IFoo) >>> directlyProvides(C, IFooFactory) >>> [i.getName() for i in C.__providedBy__] ['IFooFactory'] >>> [i.getName() for i in C().__providedBy__] ['IFoo'] """ frame = sys._getframe(1) locals = frame.f_locals # Try to make sure we were called from a class def if (locals is frame.f_globals) or ('__module__' not in locals): raise TypeError("classProvides can be used only from a class definition.") if '__provides__' in locals: raise TypeError( "classProvides can only be used once in a class definition.") locals["__provides__"] = _normalizeargs(interfaces) addClassAdvisor(_classProvides_advice, depth=2) def _classProvides_advice(cls): interfaces = cls.__dict__['__provides__'] del cls.__provides__ directlyProvides(cls, *interfaces) return cls class provider: """Class decorator version of classProvides""" def __init__(self, *interfaces): self.interfaces = interfaces def __call__(self, ob): directlyProvides(ob, *self.interfaces) return ob def moduleProvides(*interfaces): """Declare interfaces provided by a module This function is used in a module definition. The arguments are one or more interfaces or interface specifications (``IDeclaration`` objects). The given interfaces (including the interfaces in the specifications) are used to create the module's direct-object interface specification. An error will be raised if the module already has an interface specification. In other words, it is an error to call this function more than once in a module definition. This function is provided for convenience. It provides a more convenient way to call directlyProvides. For example:: moduleImplements(I1) is equivalent to:: directlyProvides(sys.modules[__name__], I1) """ frame = sys._getframe(1) locals = frame.f_locals # Try to make sure we were called from a class def if (locals is not frame.f_globals) or ('__name__' not in locals): raise TypeError( "moduleProvides can only be used from a module definition.") if '__provides__' in locals: raise TypeError( "moduleProvides can only be used once in a module definition.") locals["__provides__"] = Provides(ModuleType, *_normalizeargs(interfaces)) ############################################################################## # # Declaration querying support def ObjectSpecification(direct, cls): """Provide object specifications These combine information for the object and for it's classes. For example: >>> from zope.interface import Interface >>> class I1(Interface): pass ... >>> class I2(Interface): pass ... >>> class I3(Interface): pass ... >>> class I31(I3): pass ... >>> class I4(Interface): pass ... >>> class I5(Interface): pass ... >>> class A(object): ... implements(I1) >>> class B(object): __implemented__ = I2 ... >>> class C(A, B): ... implements(I31) >>> c = C() >>> directlyProvides(c, I4) >>> [i.getName() for i in providedBy(c)] ['I4', 'I31', 'I1', 'I2'] >>> [i.getName() for i in providedBy(c).flattened()] ['I4', 'I31', 'I3', 'I1', 'I2', 'Interface'] >>> int(I1 in providedBy(c)) 1 >>> int(I3 in providedBy(c)) 0 >>> int(providedBy(c).extends(I3)) 1 >>> int(providedBy(c).extends(I31)) 1 >>> int(providedBy(c).extends(I5)) 0 >>> class COnly(A, B): ... implementsOnly(I31) >>> class D(COnly): ... implements(I5) >>> c = D() >>> directlyProvides(c, I4) >>> [i.getName() for i in providedBy(c)] ['I4', 'I5', 'I31'] >>> [i.getName() for i in providedBy(c).flattened()] ['I4', 'I5', 'I31', 'I3', 'Interface'] >>> int(I1 in providedBy(c)) 0 >>> int(I3 in providedBy(c)) 0 >>> int(providedBy(c).extends(I3)) 1 >>> int(providedBy(c).extends(I1)) 0 >>> int(providedBy(c).extends(I31)) 1 >>> int(providedBy(c).extends(I5)) 1 """ return Provides(cls, direct) def getObjectSpecification(ob): provides = getattr(ob, '__provides__', None) if provides is not None: if isinstance(provides, SpecificationBase): return provides try: cls = ob.__class__ except AttributeError: # We can't get the class, so just consider provides return _empty return implementedBy(cls) def providedBy(ob): # Here we have either a special object, an old-style declaration # or a descriptor # Try to get __providedBy__ try: r = ob.__providedBy__ except AttributeError: # Not set yet. Fall back to lower-level thing that computes it return getObjectSpecification(ob) try: # We might have gotten a descriptor from an instance of a # class (like an ExtensionClass) that doesn't support # descriptors. We'll make sure we got one by trying to get # the only attribute, which all specs have. r.extends except AttributeError: # The object's class doesn't understand descriptors. # Sigh. We need to get an object descriptor, but we have to be # careful. We want to use the instance's __provides__, if # there is one, but only if it didn't come from the class. try: r = ob.__provides__ except AttributeError: # No __provides__, so just fall back to implementedBy return implementedBy(ob.__class__) # We need to make sure we got the __provides__ from the # instance. We'll do this by making sure we don't get the same # thing from the class: try: cp = ob.__class__.__provides__ except AttributeError: # The ob doesn't have a class or the class has no # provides, assume we're done: return r if r is cp: # Oops, we got the provides from the class. This means # the object doesn't have it's own. We should use implementedBy return implementedBy(ob.__class__) return r class ObjectSpecificationDescriptorPy(object): """Implement the `__providedBy__` attribute The `__providedBy__` attribute computes the interfaces peovided by an object. """ def __get__(self, inst, cls): """Get an object specification for an object For example: >>> from zope.interface import Interface >>> class IFoo(Interface): pass ... >>> class IFooFactory(Interface): pass ... >>> class C(object): ... implements(IFoo) ... classProvides(IFooFactory) >>> [i.getName() for i in C.__providedBy__] ['IFooFactory'] >>> [i.getName() for i in C().__providedBy__] ['IFoo'] """ # Get an ObjectSpecification bound to either an instance or a class, # depending on how we were accessed. if inst is None: return getObjectSpecification(cls) provides = getattr(inst, '__provides__', None) if provides is not None: return provides return implementedBy(cls) ObjectSpecificationDescriptor = ObjectSpecificationDescriptorPy ############################################################################## def _normalizeargs(sequence, output = None): """Normalize declaration arguments Normalization arguments might contain Declarions, tuples, or single interfaces. Anything but individial interfaces or implements specs will be expanded. """ if output is None: output = [] cls = sequence.__class__ if InterfaceClass in cls.__mro__ or Implements in cls.__mro__: output.append(sequence) else: for v in sequence: _normalizeargs(v, output) return output _empty = Declaration() try: import _zope_interface_coptimizations except ImportError: pass else: from _zope_interface_coptimizations import implementedBy, providedBy from _zope_interface_coptimizations import getObjectSpecification from _zope_interface_coptimizations import ObjectSpecificationDescriptor objectSpecificationDescriptor = ObjectSpecificationDescriptor() zope2.13-2.13.21/source/zope.interface/src/zope/interface/_zope_interface_coptimizations.c0000644000175000017500000013020412214017572030514 0ustar arnauarnau/*########################################################################### # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ #include "Python.h" #include "structmember.h" #define TYPE(O) ((PyTypeObject*)(O)) #define OBJECT(O) ((PyObject*)(O)) #define CLASSIC(O) ((PyClassObject*)(O)) #ifndef PyVarObject_HEAD_INIT #define PyVarObject_HEAD_INIT(a, b) PyObject_HEAD_INIT(a) b, #endif #ifndef Py_TYPE #define Py_TYPE(o) ((o)->ob_type) #endif static PyObject *str__dict__, *str__implemented__, *strextends; static PyObject *BuiltinImplementationSpecifications, *str__provides__; static PyObject *str__class__, *str__providedBy__; static PyObject *empty, *fallback, *str_implied, *str_cls, *str_implements; static PyObject *str__conform__, *str_call_conform, *adapter_hooks; static PyObject *str_uncached_lookup, *str_uncached_lookupAll; static PyObject *str_uncached_subscriptions; static PyObject *str_registry, *strro, *str_generation, *strchanged; static PyTypeObject *Implements; static int imported_declarations = 0; static int import_declarations(void) { PyObject *declarations, *i; declarations = PyImport_ImportModule("zope.interface.declarations"); if (declarations == NULL) return -1; BuiltinImplementationSpecifications = PyObject_GetAttrString( declarations, "BuiltinImplementationSpecifications"); if (BuiltinImplementationSpecifications == NULL) return -1; empty = PyObject_GetAttrString(declarations, "_empty"); if (empty == NULL) return -1; fallback = PyObject_GetAttrString(declarations, "implementedByFallback"); if (fallback == NULL) return -1; i = PyObject_GetAttrString(declarations, "Implements"); if (i == NULL) return -1; if (! PyType_Check(i)) { PyErr_SetString(PyExc_TypeError, "zope.interface.declarations.Implements is not a type"); return -1; } Implements = (PyTypeObject *)i; Py_DECREF(declarations); imported_declarations = 1; return 0; } static PyTypeObject SpecType; /* Forward */ static PyObject * implementedByFallback(PyObject *cls) { if (imported_declarations == 0 && import_declarations() < 0) return NULL; return PyObject_CallFunctionObjArgs(fallback, cls, NULL); } static PyObject * implementedBy(PyObject *ignored, PyObject *cls) { /* Fast retrieval of implements spec, if possible, to optimize common case. Use fallback code if we get stuck. */ PyObject *dict = NULL, *spec; if (PyType_Check(cls)) { dict = TYPE(cls)->tp_dict; Py_XINCREF(dict); } if (dict == NULL) dict = PyObject_GetAttr(cls, str__dict__); if (dict == NULL) { /* Probably a security proxied class, use more expensive fallback code */ PyErr_Clear(); return implementedByFallback(cls); } spec = PyObject_GetItem(dict, str__implemented__); Py_DECREF(dict); if (spec) { if (imported_declarations == 0 && import_declarations() < 0) return NULL; if (PyObject_TypeCheck(spec, Implements)) return spec; /* Old-style declaration, use more expensive fallback code */ Py_DECREF(spec); return implementedByFallback(cls); } PyErr_Clear(); /* Maybe we have a builtin */ if (imported_declarations == 0 && import_declarations() < 0) return NULL; spec = PyDict_GetItem(BuiltinImplementationSpecifications, cls); if (spec != NULL) { Py_INCREF(spec); return spec; } /* We're stuck, use fallback */ return implementedByFallback(cls); } static PyObject * getObjectSpecification(PyObject *ignored, PyObject *ob) { PyObject *cls, *result; result = PyObject_GetAttr(ob, str__provides__); if (result != NULL && PyObject_TypeCheck(result, &SpecType)) return result; PyErr_Clear(); /* We do a getattr here so as not to be defeated by proxies */ cls = PyObject_GetAttr(ob, str__class__); if (cls == NULL) { PyErr_Clear(); if (imported_declarations == 0 && import_declarations() < 0) return NULL; Py_INCREF(empty); return empty; } result = implementedBy(NULL, cls); Py_DECREF(cls); return result; } static PyObject * providedBy(PyObject *ignored, PyObject *ob) { PyObject *result, *cls, *cp; result = PyObject_GetAttr(ob, str__providedBy__); if (result == NULL) { PyErr_Clear(); return getObjectSpecification(NULL, ob); } /* We want to make sure we have a spec. We can't do a type check because we may have a proxy, so we'll just try to get the only attribute. */ if (PyObject_TypeCheck(result, &SpecType) || PyObject_HasAttr(result, strextends) ) return result; /* The object's class doesn't understand descriptors. Sigh. We need to get an object descriptor, but we have to be careful. We want to use the instance's __provides__,l if there is one, but only if it didn't come from the class. */ Py_DECREF(result); cls = PyObject_GetAttr(ob, str__class__); if (cls == NULL) return NULL; result = PyObject_GetAttr(ob, str__provides__); if (result == NULL) { /* No __provides__, so just fall back to implementedBy */ PyErr_Clear(); result = implementedBy(NULL, cls); Py_DECREF(cls); return result; } cp = PyObject_GetAttr(cls, str__provides__); if (cp == NULL) { /* The the class has no provides, assume we're done: */ PyErr_Clear(); Py_DECREF(cls); return result; } if (cp == result) { /* Oops, we got the provides from the class. This means the object doesn't have it's own. We should use implementedBy */ Py_DECREF(result); result = implementedBy(NULL, cls); } Py_DECREF(cls); Py_DECREF(cp); return result; } /* Get an attribute from an inst dict. Return a borrowed reference. This has a number of advantages: - It avoids layers of Python api - It doesn't waste time looking for descriptors - It fails wo raising an exception, although that shouldn't really matter. */ static PyObject * inst_attr(PyObject *self, PyObject *name) { PyObject **dictp, *v; dictp = _PyObject_GetDictPtr(self); if (dictp && *dictp && (v = PyDict_GetItem(*dictp, name))) return v; PyErr_SetObject(PyExc_AttributeError, name); return NULL; } static PyObject * Spec_extends(PyObject *self, PyObject *other) { PyObject *implied; implied = inst_attr(self, str_implied); if (implied == NULL) return NULL; #ifdef Py_True if (PyDict_GetItem(implied, other) != NULL) { Py_INCREF(Py_True); return Py_True; } Py_INCREF(Py_False); return Py_False; #else return PyInt_FromLong(PyDict_GetItem(implied, other) != NULL); #endif } static char Spec_extends__doc__[] = "Test whether a specification is or extends another" ; static char Spec_providedBy__doc__[] = "Test whether an interface is implemented by the specification" ; static PyObject * Spec_call(PyObject *self, PyObject *args, PyObject *kw) { PyObject *spec; if (! PyArg_ParseTuple(args, "O", &spec)) return NULL; return Spec_extends(self, spec); } static PyObject * Spec_providedBy(PyObject *self, PyObject *ob) { PyObject *decl, *item; decl = providedBy(NULL, ob); if (decl == NULL) return NULL; if (PyObject_TypeCheck(decl, &SpecType)) item = Spec_extends(decl, self); else /* decl is probably a security proxy. We have to go the long way around. */ item = PyObject_CallFunctionObjArgs(decl, self, NULL); Py_DECREF(decl); return item; } static char Spec_implementedBy__doc__[] = "Test whether the specification is implemented by a class or factory.\n" "Raise TypeError if argument is neither a class nor a callable." ; static PyObject * Spec_implementedBy(PyObject *self, PyObject *cls) { PyObject *decl, *item; decl = implementedBy(NULL, cls); if (decl == NULL) return NULL; if (PyObject_TypeCheck(decl, &SpecType)) item = Spec_extends(decl, self); else item = PyObject_CallFunctionObjArgs(decl, self, NULL); Py_DECREF(decl); return item; } static struct PyMethodDef Spec_methods[] = { {"providedBy", (PyCFunction)Spec_providedBy, METH_O, Spec_providedBy__doc__}, {"implementedBy", (PyCFunction)Spec_implementedBy, METH_O, Spec_implementedBy__doc__}, {"isOrExtends", (PyCFunction)Spec_extends, METH_O, Spec_extends__doc__}, {NULL, NULL} /* sentinel */ }; static PyTypeObject SpecType = { PyVarObject_HEAD_INIT(NULL, 0) /* tp_name */ "_interface_coptimizations." "SpecificationBase", /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ (destructor)0, /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, /* tp_compare */ 0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ (hashfunc)0, /* tp_call */ (ternaryfunc)Spec_call, /* tp_str */ (reprfunc)0, /* tp_getattro */ (getattrofunc)0, /* tp_setattro */ (setattrofunc)0, /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, "Base type for Specification objects", /* tp_traverse */ (traverseproc)0, /* tp_clear */ (inquiry)0, /* tp_richcompare */ (richcmpfunc)0, /* tp_weaklistoffset */ (long)0, /* tp_iter */ (getiterfunc)0, /* tp_iternext */ (iternextfunc)0, /* tp_methods */ Spec_methods, }; static PyObject * OSD_descr_get(PyObject *self, PyObject *inst, PyObject *cls) { PyObject *provides; if (inst == NULL) return getObjectSpecification(NULL, cls); provides = PyObject_GetAttr(inst, str__provides__); if (provides != NULL) return provides; PyErr_Clear(); return implementedBy(NULL, cls); } static PyTypeObject OSDType = { PyVarObject_HEAD_INIT(NULL, 0) /* tp_name */ "_interface_coptimizations." "ObjectSpecificationDescriptor", /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ (destructor)0, /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, /* tp_compare */ 0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ (hashfunc)0, /* tp_call */ (ternaryfunc)0, /* tp_str */ (reprfunc)0, /* tp_getattro */ (getattrofunc)0, /* tp_setattro */ (setattrofunc)0, /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE , "Object Specification Descriptor", /* tp_traverse */ (traverseproc)0, /* tp_clear */ (inquiry)0, /* tp_richcompare */ (richcmpfunc)0, /* tp_weaklistoffset */ (long)0, /* tp_iter */ (getiterfunc)0, /* tp_iternext */ (iternextfunc)0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* internal use */ /* tp_descr_get */ (descrgetfunc)OSD_descr_get, }; static PyObject * CPB_descr_get(PyObject *self, PyObject *inst, PyObject *cls) { PyObject *mycls, *implements; mycls = inst_attr(self, str_cls); if (mycls == NULL) return NULL; if (cls == mycls) { if (inst == NULL) { Py_INCREF(self); return OBJECT(self); } implements = inst_attr(self, str_implements); Py_XINCREF(implements); return implements; } PyErr_SetObject(PyExc_AttributeError, str__provides__); return NULL; } static PyTypeObject CPBType = { PyVarObject_HEAD_INIT(NULL, 0) /* tp_name */ "_interface_coptimizations." "ClassProvidesBase", /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ (destructor)0, /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, /* tp_compare */ 0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ (hashfunc)0, /* tp_call */ (ternaryfunc)0, /* tp_str */ (reprfunc)0, /* tp_getattro */ (getattrofunc)0, /* tp_setattro */ (setattrofunc)0, /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, "C Base class for ClassProvides", /* tp_traverse */ (traverseproc)0, /* tp_clear */ (inquiry)0, /* tp_richcompare */ (richcmpfunc)0, /* tp_weaklistoffset */ (long)0, /* tp_iter */ (getiterfunc)0, /* tp_iternext */ (iternextfunc)0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ &SpecType, /* tp_dict */ 0, /* internal use */ /* tp_descr_get */ (descrgetfunc)CPB_descr_get, }; /* ==================================================================== */ /* ========== Begin: __call__ and __adapt__ =========================== */ /* def __adapt__(self, obj): """Adapt an object to the reciever """ if self.providedBy(obj): return obj for hook in adapter_hooks: adapter = hook(self, obj) if adapter is not None: return adapter */ static PyObject * __adapt__(PyObject *self, PyObject *obj) { PyObject *decl, *args, *adapter; int implements, i, l; decl = providedBy(NULL, obj); if (decl == NULL) return NULL; if (PyObject_TypeCheck(decl, &SpecType)) { PyObject *implied; implied = inst_attr(decl, str_implied); if (implied == NULL) { Py_DECREF(decl); return NULL; } implements = PyDict_GetItem(implied, self) != NULL; Py_DECREF(decl); } else { /* decl is probably a security proxy. We have to go the long way around. */ PyObject *r; r = PyObject_CallFunctionObjArgs(decl, self, NULL); Py_DECREF(decl); if (r == NULL) return NULL; implements = PyObject_IsTrue(r); Py_DECREF(r); } if (implements) { Py_INCREF(obj); return obj; } l = PyList_GET_SIZE(adapter_hooks); args = PyTuple_New(2); if (args == NULL) return NULL; Py_INCREF(self); PyTuple_SET_ITEM(args, 0, self); Py_INCREF(obj); PyTuple_SET_ITEM(args, 1, obj); for (i = 0; i < l; i++) { adapter = PyObject_CallObject(PyList_GET_ITEM(adapter_hooks, i), args); if (adapter == NULL || adapter != Py_None) { Py_DECREF(args); return adapter; } Py_DECREF(adapter); } Py_DECREF(args); Py_INCREF(Py_None); return Py_None; } static struct PyMethodDef ib_methods[] = { {"__adapt__", (PyCFunction)__adapt__, METH_O, "Adapt an object to the reciever"}, {NULL, NULL} /* sentinel */ }; /* def __call__(self, obj, alternate=_marker): conform = getattr(obj, '__conform__', None) if conform is not None: adapter = self._call_conform(conform) if adapter is not None: return adapter adapter = self.__adapt__(obj) if adapter is not None: return adapter elif alternate is not _marker: return alternate else: raise TypeError("Could not adapt", obj, self) */ static PyObject * ib_call(PyObject *self, PyObject *args, PyObject *kwargs) { PyObject *conform, *obj, *alternate=NULL, *adapter; static char *kwlist[] = {"obj", "alternate", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, &obj, &alternate)) return NULL; conform = PyObject_GetAttr(obj, str__conform__); if (conform != NULL) { adapter = PyObject_CallMethodObjArgs(self, str_call_conform, conform, NULL); Py_DECREF(conform); if (adapter == NULL || adapter != Py_None) return adapter; Py_DECREF(adapter); } else PyErr_Clear(); adapter = __adapt__(self, obj); if (adapter == NULL || adapter != Py_None) return adapter; Py_DECREF(adapter); if (alternate != NULL) { Py_INCREF(alternate); return alternate; } adapter = Py_BuildValue("sOO", "Could not adapt", obj, self); if (adapter != NULL) { PyErr_SetObject(PyExc_TypeError, adapter); Py_DECREF(adapter); } return NULL; } static PyTypeObject InterfaceBase = { PyVarObject_HEAD_INIT(NULL, 0) /* tp_name */ "_zope_interface_coptimizations." "InterfaceBase", /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ (destructor)0, /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, /* tp_compare */ 0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ (hashfunc)0, /* tp_call */ (ternaryfunc)ib_call, /* tp_str */ (reprfunc)0, /* tp_getattro */ (getattrofunc)0, /* tp_setattro */ (setattrofunc)0, /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE , /* tp_doc */ "Interface base type providing __call__ and __adapt__", /* tp_traverse */ (traverseproc)0, /* tp_clear */ (inquiry)0, /* tp_richcompare */ (richcmpfunc)0, /* tp_weaklistoffset */ (long)0, /* tp_iter */ (getiterfunc)0, /* tp_iternext */ (iternextfunc)0, /* tp_methods */ ib_methods, }; /* =================== End: __call__ and __adapt__ ==================== */ /* ==================================================================== */ /* ==================================================================== */ /* ========================== Begin: Lookup Bases ===================== */ typedef struct { PyObject_HEAD PyObject *_cache; PyObject *_mcache; PyObject *_scache; } lookup; typedef struct { PyObject_HEAD PyObject *_cache; PyObject *_mcache; PyObject *_scache; PyObject *_verify_ro; PyObject *_verify_generations; } verify; static int lookup_traverse(lookup *self, visitproc visit, void *arg) { int vret; if (self->_cache) { vret = visit(self->_cache, arg); if (vret != 0) return vret; } if (self->_mcache) { vret = visit(self->_mcache, arg); if (vret != 0) return vret; } if (self->_scache) { vret = visit(self->_scache, arg); if (vret != 0) return vret; } return 0; } static int lookup_clear(lookup *self) { Py_CLEAR(self->_cache); Py_CLEAR(self->_mcache); Py_CLEAR(self->_scache); return 0; } static void lookup_dealloc(lookup *self) { lookup_clear(self); Py_TYPE(self)->tp_free((PyObject*)self); } /* def changed(self, ignored=None): self._cache.clear() self._mcache.clear() self._scache.clear() */ static PyObject * lookup_changed(lookup *self, PyObject *ignored) { lookup_clear(self); Py_INCREF(Py_None); return Py_None; } #define ASSURE_DICT(N) if (N == NULL) { N = PyDict_New(); \ if (N == NULL) return NULL; \ } /* def _getcache(self, provided, name): cache = self._cache.get(provided) if cache is None: cache = {} self._cache[provided] = cache if name: c = cache.get(name) if c is None: c = {} cache[name] = c cache = c return cache */ static PyObject * _subcache(PyObject *cache, PyObject *key) { PyObject *subcache; subcache = PyDict_GetItem(cache, key); if (subcache == NULL) { int status; subcache = PyDict_New(); if (subcache == NULL) return NULL; status = PyDict_SetItem(cache, key, subcache); Py_DECREF(subcache); if (status < 0) return NULL; } return subcache; } static PyObject * _getcache(lookup *self, PyObject *provided, PyObject *name) { PyObject *cache; ASSURE_DICT(self->_cache); cache = _subcache(self->_cache, provided); if (cache == NULL) return NULL; if (name != NULL && PyObject_IsTrue(name)) cache = _subcache(cache, name); return cache; } /* def lookup(self, required, provided, name=u'', default=None): cache = self._getcache(provided, name) if len(required) == 1: result = cache.get(required[0], _not_in_mapping) else: result = cache.get(tuple(required), _not_in_mapping) if result is _not_in_mapping: result = self._uncached_lookup(required, provided, name) if len(required) == 1: cache[required[0]] = result else: cache[tuple(required)] = result if result is None: return default return result */ static PyObject * tuplefy(PyObject *v) { if (! PyTuple_Check(v)) { v = PyObject_CallFunctionObjArgs(OBJECT(&PyTuple_Type), v, NULL); if (v == NULL) return NULL; } else Py_INCREF(v); return v; } static PyObject * _lookup(lookup *self, PyObject *required, PyObject *provided, PyObject *name, PyObject *default_) { PyObject *result, *key, *cache; cache = _getcache(self, provided, name); if (cache == NULL) return NULL; required = tuplefy(required); if (required == NULL) return NULL; if (PyTuple_GET_SIZE(required) == 1) key = PyTuple_GET_ITEM(required, 0); else key = required; result = PyDict_GetItem(cache, key); if (result == NULL) { int status; result = PyObject_CallMethodObjArgs(OBJECT(self), str_uncached_lookup, required, provided, name, NULL); if (result == NULL) { Py_DECREF(required); return NULL; } status = PyDict_SetItem(cache, key, result); Py_DECREF(required); if (status < 0) { Py_DECREF(result); return NULL; } } else { Py_INCREF(result); Py_DECREF(required); } if (result == Py_None && default_ != NULL) { Py_DECREF(Py_None); Py_INCREF(default_); return default_; } return result; } static PyObject * lookup_lookup(lookup *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"required", "provided", "name", "default", NULL}; PyObject *required, *provided, *name=NULL, *default_=NULL; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, &required, &provided, &name, &default_)) return NULL; return _lookup(self, required, provided, name, default_); } /* def lookup1(self, required, provided, name=u'', default=None): cache = self._getcache(provided, name) result = cache.get(required, _not_in_mapping) if result is _not_in_mapping: return self.lookup((required, ), provided, name, default) if result is None: return default return result */ static PyObject * _lookup1(lookup *self, PyObject *required, PyObject *provided, PyObject *name, PyObject *default_) { PyObject *result, *cache; cache = _getcache(self, provided, name); if (cache == NULL) return NULL; result = PyDict_GetItem(cache, required); if (result == NULL) { PyObject *tup; tup = PyTuple_New(1); if (tup == NULL) return NULL; Py_INCREF(required); PyTuple_SET_ITEM(tup, 0, required); result = _lookup(self, tup, provided, name, default_); Py_DECREF(tup); } else Py_INCREF(result); return result; } static PyObject * lookup_lookup1(lookup *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"required", "provided", "name", "default", NULL}; PyObject *required, *provided, *name=NULL, *default_=NULL; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, &required, &provided, &name, &default_)) return NULL; return _lookup1(self, required, provided, name, default_); } /* def adapter_hook(self, provided, object, name=u'', default=None): required = providedBy(object) cache = self._getcache(provided, name) factory = cache.get(required, _not_in_mapping) if factory is _not_in_mapping: factory = self.lookup((required, ), provided, name) if factory is not None: result = factory(object) if result is not None: return result return default */ static PyObject * _adapter_hook(lookup *self, PyObject *provided, PyObject *object, PyObject *name, PyObject *default_) { PyObject *required, *factory, *result; required = providedBy(NULL, object); if (required == NULL) return NULL; factory = _lookup1(self, required, provided, name, Py_None); Py_DECREF(required); if (factory == NULL) return NULL; if (factory != Py_None) { result = PyObject_CallFunctionObjArgs(factory, object, NULL); Py_DECREF(factory); if (result == NULL || result != Py_None) return result; } else result = factory; /* None */ if (default_ == NULL || default_ == result) /* No default specified, */ return result; /* Return None. result is owned None */ Py_DECREF(result); Py_INCREF(default_); return default_; } static PyObject * lookup_adapter_hook(lookup *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"provided", "object", "name", "default", NULL}; PyObject *object, *provided, *name=NULL, *default_=NULL; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, &provided, &object, &name, &default_)) return NULL; return _adapter_hook(self, provided, object, name, default_); } static PyObject * lookup_queryAdapter(lookup *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"object", "provided", "name", "default", NULL}; PyObject *object, *provided, *name=NULL, *default_=NULL; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, &object, &provided, &name, &default_)) return NULL; return _adapter_hook(self, provided, object, name, default_); } /* def lookupAll(self, required, provided): cache = self._mcache.get(provided) if cache is None: cache = {} self._mcache[provided] = cache required = tuple(required) result = cache.get(required, _not_in_mapping) if result is _not_in_mapping: result = self._uncached_lookupAll(required, provided) cache[required] = result return result */ static PyObject * _lookupAll(lookup *self, PyObject *required, PyObject *provided) { PyObject *cache, *result; ASSURE_DICT(self->_mcache); cache = _subcache(self->_mcache, provided); if (cache == NULL) return NULL; required = tuplefy(required); if (required == NULL) return NULL; result = PyDict_GetItem(cache, required); if (result == NULL) { int status; result = PyObject_CallMethodObjArgs(OBJECT(self), str_uncached_lookupAll, required, provided, NULL); if (result == NULL) { Py_DECREF(required); return NULL; } status = PyDict_SetItem(cache, required, result); Py_DECREF(required); if (status < 0) { Py_DECREF(result); return NULL; } } else { Py_INCREF(result); Py_DECREF(required); } return result; } static PyObject * lookup_lookupAll(lookup *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"required", "provided", NULL}; PyObject *required, *provided; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, &required, &provided)) return NULL; return _lookupAll(self, required, provided); } /* def subscriptions(self, required, provided): cache = self._scache.get(provided) if cache is None: cache = {} self._scache[provided] = cache required = tuple(required) result = cache.get(required, _not_in_mapping) if result is _not_in_mapping: result = self._uncached_subscriptions(required, provided) cache[required] = result return result */ static PyObject * _subscriptions(lookup *self, PyObject *required, PyObject *provided) { PyObject *cache, *result; ASSURE_DICT(self->_scache); cache = _subcache(self->_scache, provided); if (cache == NULL) return NULL; required = tuplefy(required); if (required == NULL) return NULL; result = PyDict_GetItem(cache, required); if (result == NULL) { int status; result = PyObject_CallMethodObjArgs( OBJECT(self), str_uncached_subscriptions, required, provided, NULL); if (result == NULL) { Py_DECREF(required); return NULL; } status = PyDict_SetItem(cache, required, result); Py_DECREF(required); if (status < 0) { Py_DECREF(result); return NULL; } } else { Py_INCREF(result); Py_DECREF(required); } return result; } static PyObject * lookup_subscriptions(lookup *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"required", "provided", NULL}; PyObject *required, *provided; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, &required, &provided)) return NULL; return _subscriptions(self, required, provided); } static struct PyMethodDef lookup_methods[] = { {"changed", (PyCFunction)lookup_changed, METH_O, ""}, {"lookup", (PyCFunction)lookup_lookup, METH_KEYWORDS, ""}, {"lookup1", (PyCFunction)lookup_lookup1, METH_KEYWORDS, ""}, {"queryAdapter", (PyCFunction)lookup_queryAdapter, METH_KEYWORDS, ""}, {"adapter_hook", (PyCFunction)lookup_adapter_hook, METH_KEYWORDS, ""}, {"lookupAll", (PyCFunction)lookup_lookupAll, METH_KEYWORDS, ""}, {"subscriptions", (PyCFunction)lookup_subscriptions, METH_KEYWORDS, ""}, {NULL, NULL} /* sentinel */ }; static PyTypeObject LookupBase = { PyVarObject_HEAD_INIT(NULL, 0) /* tp_name */ "_zope_interface_coptimizations." "LookupBase", /* tp_basicsize */ sizeof(lookup), /* tp_itemsize */ 0, /* tp_dealloc */ (destructor)&lookup_dealloc, /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, /* tp_compare */ 0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ (hashfunc)0, /* tp_call */ (ternaryfunc)0, /* tp_str */ (reprfunc)0, /* tp_getattro */ (getattrofunc)0, /* tp_setattro */ (setattrofunc)0, /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_doc */ "", /* tp_traverse */ (traverseproc)lookup_traverse, /* tp_clear */ (inquiry)lookup_clear, /* tp_richcompare */ (richcmpfunc)0, /* tp_weaklistoffset */ (long)0, /* tp_iter */ (getiterfunc)0, /* tp_iternext */ (iternextfunc)0, /* tp_methods */ lookup_methods, }; static int verifying_traverse(verify *self, visitproc visit, void *arg) { int vret; vret = lookup_traverse((lookup *)self, visit, arg); if (vret != 0) return vret; if (self->_verify_ro) { vret = visit(self->_verify_ro, arg); if (vret != 0) return vret; } if (self->_verify_generations) { vret = visit(self->_verify_generations, arg); if (vret != 0) return vret; } return 0; } static int verifying_clear(verify *self) { lookup_clear((lookup *)self); Py_CLEAR(self->_verify_generations); Py_CLEAR(self->_verify_ro); return 0; } static void verifying_dealloc(verify *self) { verifying_clear(self); Py_TYPE(self)->tp_free((PyObject*)self); } /* def changed(self, originally_changed): super(VerifyingBasePy, self).changed(originally_changed) self._verify_ro = self._registry.ro[1:] self._verify_generations = [r._generation for r in self._verify_ro] */ static PyObject * _generations_tuple(PyObject *ro) { int i, l; PyObject *generations; l = PyTuple_GET_SIZE(ro); generations = PyTuple_New(l); for (i=0; i < l; i++) { PyObject *generation; generation = PyObject_GetAttr(PyTuple_GET_ITEM(ro, i), str_generation); if (generation == NULL) { Py_DECREF(generations); return NULL; } PyTuple_SET_ITEM(generations, i, generation); } return generations; } static PyObject * verifying_changed(verify *self, PyObject *ignored) { PyObject *t, *ro; verifying_clear(self); t = PyObject_GetAttr(OBJECT(self), str_registry); if (t == NULL) return NULL; ro = PyObject_GetAttr(t, strro); Py_DECREF(t); if (ro == NULL) return NULL; t = PyObject_CallFunctionObjArgs(OBJECT(&PyTuple_Type), ro, NULL); Py_DECREF(ro); if (t == NULL) return NULL; ro = PyTuple_GetSlice(t, 1, PyTuple_GET_SIZE(t)); Py_DECREF(t); if (ro == NULL) return NULL; self->_verify_generations = _generations_tuple(ro); if (self->_verify_generations == NULL) { Py_DECREF(ro); return NULL; } self->_verify_ro = ro; Py_INCREF(Py_None); return Py_None; } /* def _verify(self): if ([r._generation for r in self._verify_ro] != self._verify_generations): self.changed(None) */ static int _verify(verify *self) { PyObject *changed_result; if (self->_verify_ro != NULL && self->_verify_generations != NULL) { PyObject *generations; int changed; generations = _generations_tuple(self->_verify_ro); if (generations == NULL) return -1; changed = PyObject_RichCompareBool(self->_verify_generations, generations, Py_NE); Py_DECREF(generations); if (changed == -1) return -1; if (changed == 0) return 0; } changed_result = PyObject_CallMethodObjArgs(OBJECT(self), strchanged, Py_None, NULL); if (changed_result == NULL) return -1; Py_DECREF(changed_result); return 0; } static PyObject * verifying_lookup(verify *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"required", "provided", "name", "default", NULL}; PyObject *required, *provided, *name=NULL, *default_=NULL; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, &required, &provided, &name, &default_)) return NULL; if (_verify(self) < 0) return NULL; return _lookup((lookup *)self, required, provided, name, default_); } static PyObject * verifying_lookup1(verify *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"required", "provided", "name", "default", NULL}; PyObject *required, *provided, *name=NULL, *default_=NULL; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, &required, &provided, &name, &default_)) return NULL; if (_verify(self) < 0) return NULL; return _lookup1((lookup *)self, required, provided, name, default_); } static PyObject * verifying_adapter_hook(verify *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"provided", "object", "name", "default", NULL}; PyObject *object, *provided, *name=NULL, *default_=NULL; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, &provided, &object, &name, &default_)) return NULL; if (_verify(self) < 0) return NULL; return _adapter_hook((lookup *)self, provided, object, name, default_); } static PyObject * verifying_queryAdapter(verify *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"object", "provided", "name", "default", NULL}; PyObject *object, *provided, *name=NULL, *default_=NULL; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, &object, &provided, &name, &default_)) return NULL; if (_verify(self) < 0) return NULL; return _adapter_hook((lookup *)self, provided, object, name, default_); } static PyObject * verifying_lookupAll(verify *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"required", "provided", NULL}; PyObject *required, *provided; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, &required, &provided)) return NULL; if (_verify(self) < 0) return NULL; return _lookupAll((lookup *)self, required, provided); } static PyObject * verifying_subscriptions(verify *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"required", "provided", NULL}; PyObject *required, *provided; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, &required, &provided)) return NULL; if (_verify(self) < 0) return NULL; return _subscriptions((lookup *)self, required, provided); } static struct PyMethodDef verifying_methods[] = { {"changed", (PyCFunction)verifying_changed, METH_O, ""}, {"lookup", (PyCFunction)verifying_lookup, METH_KEYWORDS, ""}, {"lookup1", (PyCFunction)verifying_lookup1, METH_KEYWORDS, ""}, {"queryAdapter", (PyCFunction)verifying_queryAdapter, METH_KEYWORDS, ""}, {"adapter_hook", (PyCFunction)verifying_adapter_hook, METH_KEYWORDS, ""}, {"lookupAll", (PyCFunction)verifying_lookupAll, METH_KEYWORDS, ""}, {"subscriptions", (PyCFunction)verifying_subscriptions, METH_KEYWORDS, ""}, {NULL, NULL} /* sentinel */ }; static PyTypeObject VerifyingBase = { PyVarObject_HEAD_INIT(NULL, 0) /* tp_name */ "_zope_interface_coptimizations." "VerifyingBase", /* tp_basicsize */ sizeof(verify), /* tp_itemsize */ 0, /* tp_dealloc */ (destructor)&verifying_dealloc, /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, /* tp_compare */ 0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ (hashfunc)0, /* tp_call */ (ternaryfunc)0, /* tp_str */ (reprfunc)0, /* tp_getattro */ (getattrofunc)0, /* tp_setattro */ (setattrofunc)0, /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_doc */ "", /* tp_traverse */ (traverseproc)verifying_traverse, /* tp_clear */ (inquiry)verifying_clear, /* tp_richcompare */ (richcmpfunc)0, /* tp_weaklistoffset */ (long)0, /* tp_iter */ (getiterfunc)0, /* tp_iternext */ (iternextfunc)0, /* tp_methods */ verifying_methods, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ &LookupBase, }; /* ========================== End: Lookup Bases ======================= */ /* ==================================================================== */ static struct PyMethodDef m_methods[] = { {"implementedBy", (PyCFunction)implementedBy, METH_O, "Interfaces implemented by a class or factory.\n" "Raises TypeError if argument is neither a class nor a callable."}, {"getObjectSpecification", (PyCFunction)getObjectSpecification, METH_O, "Get an object's interfaces (internal api)"}, {"providedBy", (PyCFunction)providedBy, METH_O, "Get an object's interfaces"}, {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; #if PY_MAJOR_VERSION >= 3 static char module_doc[] = "C optimizations for zope.interface\n\n" "$Id: _zope_interface_coptimizations.c 111871 2010-05-02 17:19:14Z tseaver $"; static struct PyModuleDef _zic_module = { PyModuleDef_HEAD_INIT, "_zope_interface_coptimizations", module_doc, -1, m_methods, NULL, NULL, NULL, NULL }; #endif #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif static PyObject * init(void) { PyObject *m; #if PY_MAJOR_VERSION < 3 #define DEFINE_STRING(S) \ if(! (str ## S = PyString_FromString(# S))) return NULL #else #define DEFINE_STRING(S) \ if(! (str ## S = PyUnicode_FromString(# S))) return NULL #endif DEFINE_STRING(__dict__); DEFINE_STRING(__implemented__); DEFINE_STRING(__provides__); DEFINE_STRING(__class__); DEFINE_STRING(__providedBy__); DEFINE_STRING(extends); DEFINE_STRING(_implied); DEFINE_STRING(_implements); DEFINE_STRING(_cls); DEFINE_STRING(__conform__); DEFINE_STRING(_call_conform); DEFINE_STRING(_uncached_lookup); DEFINE_STRING(_uncached_lookupAll); DEFINE_STRING(_uncached_subscriptions); DEFINE_STRING(_registry); DEFINE_STRING(_generation); DEFINE_STRING(ro); DEFINE_STRING(changed); #undef DEFINE_STRING adapter_hooks = PyList_New(0); if (adapter_hooks == NULL) return NULL; /* Initialize types: */ SpecType.tp_new = PyBaseObject_Type.tp_new; if (PyType_Ready(&SpecType) < 0) return NULL; OSDType.tp_new = PyBaseObject_Type.tp_new; if (PyType_Ready(&OSDType) < 0) return NULL; CPBType.tp_new = PyBaseObject_Type.tp_new; if (PyType_Ready(&CPBType) < 0) return NULL; InterfaceBase.tp_new = PyBaseObject_Type.tp_new; if (PyType_Ready(&InterfaceBase) < 0) return NULL; LookupBase.tp_new = PyBaseObject_Type.tp_new; if (PyType_Ready(&LookupBase) < 0) return NULL; VerifyingBase.tp_new = PyBaseObject_Type.tp_new; if (PyType_Ready(&VerifyingBase) < 0) return NULL; #if PY_MAJOR_VERSION < 3 /* Create the module and add the functions */ m = Py_InitModule3("_zope_interface_coptimizations", m_methods, "C optimizations for zope.interface\n\n" "$Id: _zope_interface_coptimizations.c 111871 2010-05-02 17:19:14Z tseaver $"); #else m = PyModule_Create(&_zic_module); #endif if (m == NULL) return NULL; /* Add types: */ if (PyModule_AddObject(m, "SpecificationBase", OBJECT(&SpecType)) < 0) return NULL; if (PyModule_AddObject(m, "ObjectSpecificationDescriptor", (PyObject *)&OSDType) < 0) return NULL; if (PyModule_AddObject(m, "ClassProvidesBase", OBJECT(&CPBType)) < 0) return NULL; if (PyModule_AddObject(m, "InterfaceBase", OBJECT(&InterfaceBase)) < 0) return NULL; if (PyModule_AddObject(m, "LookupBase", OBJECT(&LookupBase)) < 0) return NULL; if (PyModule_AddObject(m, "VerifyingBase", OBJECT(&VerifyingBase)) < 0) return NULL; if (PyModule_AddObject(m, "adapter_hooks", adapter_hooks) < 0) return NULL; return m; } #if PY_MAJOR_VERSION < 3 PyMODINIT_FUNC init_zope_interface_coptimizations(void) { init(); } #else PyInit__zope_interface_coptimizations(void) { return init(); } #endif zope2.13-2.13.21/source/zope.interface/src/zope/interface/exceptions.py0000644000175000017500000000401112214017572024607 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interface-specific exceptions $Id: exceptions.py 110536 2010-04-06 02:59:44Z tseaver $ """ class Invalid(Exception): """A specification is violated """ class DoesNotImplement(Invalid): """ This object does not implement """ def __init__(self, interface): self.interface = interface def __str__(self): return """An object does not implement interface %(interface)s """ % self.__dict__ class BrokenImplementation(Invalid): """An attribute is not completely implemented. """ def __init__(self, interface, name): self.interface=interface self.name=name def __str__(self): return """An object has failed to implement interface %(interface)s The %(name)s attribute was not provided. """ % self.__dict__ class BrokenMethodImplementation(Invalid): """An method is not completely implemented. """ def __init__(self, method, mess): self.method=method self.mess=mess def __str__(self): return """The implementation of %(method)s violates its contract because %(mess)s. """ % self.__dict__ class InvalidInterface(Exception): """The interface has invalid contents """ class BadImplements(TypeError): """An implementation assertion is invalid because it doesn't contain an interface or a sequence of valid implementation assertions. """ zope2.13-2.13.21/source/zope.interface/src/zope.interface.egg-info/0000755000175000017500000000000012214017572023551 5ustar arnauarnauzope2.13-2.13.21/source/zope.interface/src/zope.interface.egg-info/PKG-INFO0000644000175000017500000020443312214017572024654 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.interface Version: 3.6.7 Summary: Interfaces for Python Home-page: http://pypi.python.org/pypi/zope.interface Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: *This package is intended to be independently reusable in any Python project. It is maintained by the* `Zope Toolkit project `_. This package provides an implementation of `object interfaces` for Python. Interfaces are a mechanism for labeling objects as conforming to a given API or contract. So, this package can be considered as implementation of the `Design By Contract`_ methodology support in Python. .. _Design By Contract: http://en.wikipedia.org/wiki/Design_by_contract Detailed Documentation ********************** .. contents:: ========== Interfaces ========== Interfaces are objects that specify (document) the external behavior of objects that "provide" them. An interface specifies behavior through: - Informal documentation in a doc string - Attribute definitions - Invariants, which are conditions that must hold for objects that provide the interface Attribute definitions specify specific attributes. They define the attribute name and provide documentation and constraints of attribute values. Attribute definitions can take a number of forms, as we'll see below. Defining interfaces =================== Interfaces are defined using Python class statements:: >>> import zope.interface >>> class IFoo(zope.interface.Interface): ... """Foo blah blah""" ... ... x = zope.interface.Attribute("""X blah blah""") ... ... def bar(q, r=None): ... """bar blah blah""" In the example above, we've created an interface, `IFoo`. We subclassed `zope.interface.Interface`, which is an ancestor interface for all interfaces, much as `object` is an ancestor of all new-style classes [#create]_. The interface is not a class, it's an Interface, an instance of `InterfaceClass`:: >>> type(IFoo) We can ask for the interface's documentation:: >>> IFoo.__doc__ 'Foo blah blah' and its name:: >>> IFoo.__name__ 'IFoo' and even its module:: >>> IFoo.__module__ '__main__' The interface defined two attributes: `x` This is the simplest form of attribute definition. It has a name and a doc string. It doesn't formally specify anything else. `bar` This is a method. A method is defined via a function definition. A method is simply an attribute constrained to be a callable with a particular signature, as provided by the function definition. Note that `bar` doesn't take a `self` argument. Interfaces document how an object is *used*. When calling instance methods, you don't pass a `self` argument, so a `self` argument isn't included in the interface signature. The `self` argument in instance methods is really an implementation detail of Python instances. Other objects, besides instances can provide interfaces and their methods might not be instance methods. For example, modules can provide interfaces and their methods are usually just functions. Even instances can have methods that are not instance methods. You can access the attributes defined by an interface using mapping syntax:: >>> x = IFoo['x'] >>> type(x) >>> x.__name__ 'x' >>> x.__doc__ 'X blah blah' >>> IFoo.get('x').__name__ 'x' >>> IFoo.get('y') You can use `in` to determine if an interface defines a name:: >>> 'x' in IFoo True You can iterate over interfaces to get the names they define:: >>> names = list(IFoo) >>> names.sort() >>> names ['bar', 'x'] Remember that interfaces aren't classes. You can't access attribute definitions as attributes of interfaces:: >>> IFoo.x Traceback (most recent call last): File "", line 1, in ? AttributeError: 'InterfaceClass' object has no attribute 'x' Methods provide access to the method signature:: >>> bar = IFoo['bar'] >>> bar.getSignatureString() '(q, r=None)' TODO Methods really should have a better API. This is something that needs to be improved. Declaring interfaces ==================== Having defined interfaces, we can *declare* that objects provide them. Before we describe the details, lets define some terms: *provide* We say that objects *provide* interfaces. If an object provides an interface, then the interface specifies the behavior of the object. In other words, interfaces specify the behavior of the objects that provide them. *implement* We normally say that classes *implement* interfaces. If a class implements an interface, then the instances of the class provide the interface. Objects provide interfaces that their classes implement [#factory]_. (Objects can provide interfaces directly, in addition to what their classes implement.) It is important to note that classes don't usually provide the interfaces that they implement. We can generalize this to factories. For any callable object we can declare that it produces objects that provide some interfaces by saying that the factory implements the interfaces. Now that we've defined these terms, we can talk about the API for declaring interfaces. Declaring implemented interfaces -------------------------------- The most common way to declare interfaces is using the implements function in a class statement:: >>> class Foo: ... zope.interface.implements(IFoo) ... ... def __init__(self, x=None): ... self.x = x ... ... def bar(self, q, r=None): ... return q, r, self.x ... ... def __repr__(self): ... return "Foo(%s)" % self.x In this example, we declared that `Foo` implements `IFoo`. This means that instances of `Foo` provide `IFoo`. Having made this declaration, there are several ways we can introspect the declarations. First, we can ask an interface whether it is implemented by a class:: >>> IFoo.implementedBy(Foo) True And we can ask whether an interface is provided by an object:: >>> foo = Foo() >>> IFoo.providedBy(foo) True Of course, `Foo` doesn't provide `IFoo`, it implements it:: >>> IFoo.providedBy(Foo) False We can also ask what interfaces are implemented by an object:: >>> list(zope.interface.implementedBy(Foo)) [] It's an error to ask for interfaces implemented by a non-callable object:: >>> IFoo.implementedBy(foo) Traceback (most recent call last): ... TypeError: ('ImplementedBy called for non-factory', Foo(None)) >>> list(zope.interface.implementedBy(foo)) Traceback (most recent call last): ... TypeError: ('ImplementedBy called for non-factory', Foo(None)) Similarly, we can ask what interfaces are provided by an object:: >>> list(zope.interface.providedBy(foo)) [] >>> list(zope.interface.providedBy(Foo)) [] We can declare interfaces implemented by other factories (besides classes). We do this using a Python-2.4-style decorator named `implementer`. In versions of Python before 2.4, this looks like:: >>> def yfoo(y): ... foo = Foo() ... foo.y = y ... return foo >>> yfoo = zope.interface.implementer(IFoo)(yfoo) >>> list(zope.interface.implementedBy(yfoo)) [] Note that the implementer decorator may modify it's argument. Callers should not assume that a new object is created. Using implementer also works on callable objects. This is used by zope.formlib, as an example. >>> class yfactory: ... def __call__(self, y): ... foo = Foo() ... foo.y = y ... return foo >>> yfoo = yfactory() >>> yfoo = zope.interface.implementer(IFoo)(yfoo) >>> list(zope.interface.implementedBy(yfoo)) [] XXX: Double check and update these version numbers: In zope.interface 3.5.2 and lower, the implementor decorator can not be used for classes, but in 3.6.0 and higher it can: >>> Foo = zope.interface.implementer(IFoo)(Foo) >>> list(zope.interface.providedBy(Foo())) [] Note that class decorators using the @implementor(IFoo) syntax are only supported in Python 2.6 and later. Declaring provided interfaces ----------------------------- We can declare interfaces directly provided by objects. Suppose that we want to document what the `__init__` method of the `Foo` class does. It's not *really* part of `IFoo`. You wouldn't normally call the `__init__` method on Foo instances. Rather, the `__init__` method is part of the `Foo`'s `__call__` method:: >>> class IFooFactory(zope.interface.Interface): ... """Create foos""" ... ... def __call__(x=None): ... """Create a foo ... ... The argument provides the initial value for x ... ... """ It's the class that provides this interface, so we declare the interface on the class:: >>> zope.interface.directlyProvides(Foo, IFooFactory) And then, we'll see that Foo provides some interfaces:: >>> list(zope.interface.providedBy(Foo)) [] >>> IFooFactory.providedBy(Foo) True Declaring class interfaces is common enough that there's a special declaration function for it, `classProvides`, that allows the declaration from within a class statement:: >>> class Foo2: ... zope.interface.implements(IFoo) ... zope.interface.classProvides(IFooFactory) ... ... def __init__(self, x=None): ... self.x = x ... ... def bar(self, q, r=None): ... return q, r, self.x ... ... def __repr__(self): ... return "Foo(%s)" % self.x >>> list(zope.interface.providedBy(Foo2)) [] >>> IFooFactory.providedBy(Foo2) True There's a similar function, `moduleProvides`, that supports interface declarations from within module definitions. For example, see the use of `moduleProvides` call in `zope.interface.__init__`, which declares that the package `zope.interface` provides `IInterfaceDeclaration`. Sometimes, we want to declare interfaces on instances, even though those instances get interfaces from their classes. Suppose we create a new interface, `ISpecial`:: >>> class ISpecial(zope.interface.Interface): ... reason = zope.interface.Attribute("Reason why we're special") ... def brag(): ... "Brag about being special" We can make an existing foo instance special by providing `reason` and `brag` attributes:: >>> foo.reason = 'I just am' >>> def brag(): ... return "I'm special!" >>> foo.brag = brag >>> foo.reason 'I just am' >>> foo.brag() "I'm special!" and by declaring the interface:: >>> zope.interface.directlyProvides(foo, ISpecial) then the new interface is included in the provided interfaces:: >>> ISpecial.providedBy(foo) True >>> list(zope.interface.providedBy(foo)) [, ] We can find out what interfaces are directly provided by an object:: >>> list(zope.interface.directlyProvidedBy(foo)) [] >>> newfoo = Foo() >>> list(zope.interface.directlyProvidedBy(newfoo)) [] Inherited declarations ---------------------- Normally, declarations are inherited:: >>> class SpecialFoo(Foo): ... zope.interface.implements(ISpecial) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason >>> list(zope.interface.implementedBy(SpecialFoo)) [, ] >>> list(zope.interface.providedBy(SpecialFoo())) [, ] Sometimes, you don't want to inherit declarations. In that case, you can use `implementsOnly`, instead of `implements`:: >>> class Special(Foo): ... zope.interface.implementsOnly(ISpecial) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason >>> list(zope.interface.implementedBy(Special)) [] >>> list(zope.interface.providedBy(Special())) [] External declarations --------------------- Normally, we make implementation declarations as part of a class definition. Sometimes, we may want to make declarations from outside the class definition. For example, we might want to declare interfaces for classes that we didn't write. The function `classImplements` can be used for this purpose:: >>> class C: ... pass >>> zope.interface.classImplements(C, IFoo) >>> list(zope.interface.implementedBy(C)) [] We can use `classImplementsOnly` to exclude inherited interfaces:: >>> class C(Foo): ... pass >>> zope.interface.classImplementsOnly(C, ISpecial) >>> list(zope.interface.implementedBy(C)) [] Declaration Objects ------------------- When we declare interfaces, we create *declaration* objects. When we query declarations, declaration objects are returned:: >>> type(zope.interface.implementedBy(Special)) Declaration objects and interface objects are similar in many ways. In fact, they share a common base class. The important thing to realize about them is that they can be used where interfaces are expected in declarations. Here's a silly example:: >>> class Special2(Foo): ... zope.interface.implementsOnly( ... zope.interface.implementedBy(Foo), ... ISpecial, ... ) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason The declaration here is almost the same as ``zope.interface.implements(ISpecial)``, except that the order of interfaces in the resulting declaration is different:: >>> list(zope.interface.implementedBy(Special2)) [, ] Interface Inheritance ===================== Interfaces can extend other interfaces. They do this simply by listing the other interfaces as base interfaces:: >>> class IBlat(zope.interface.Interface): ... """Blat blah blah""" ... ... y = zope.interface.Attribute("y blah blah") ... def eek(): ... """eek blah blah""" >>> IBlat.__bases__ (,) >>> class IBaz(IFoo, IBlat): ... """Baz blah""" ... def eek(a=1): ... """eek in baz blah""" ... >>> IBaz.__bases__ (, ) >>> names = list(IBaz) >>> names.sort() >>> names ['bar', 'eek', 'x', 'y'] Note that `IBaz` overrides eek:: >>> IBlat['eek'].__doc__ 'eek blah blah' >>> IBaz['eek'].__doc__ 'eek in baz blah' We were careful to override eek in a compatible way. When extending an interface, the extending interface should be compatible [#compat]_ with the extended interfaces. We can ask whether one interface extends another:: >>> IBaz.extends(IFoo) True >>> IBlat.extends(IFoo) False Note that interfaces don't extend themselves:: >>> IBaz.extends(IBaz) False Sometimes we wish they did, but we can, instead use `isOrExtends`:: >>> IBaz.isOrExtends(IBaz) True >>> IBaz.isOrExtends(IFoo) True >>> IFoo.isOrExtends(IBaz) False When we iterate over an interface, we get all of the names it defines, including names defined by base interfaces. Sometimes, we want *just* the names defined by the interface directly. We bane use the `names` method for that:: >>> list(IBaz.names()) ['eek'] Inheritance of attribute specifications --------------------------------------- An interface may override attribute definitions from base interfaces. If two base interfaces define the same attribute, the attribute is inherited from the most specific interface. For example, with:: >>> class IBase(zope.interface.Interface): ... ... def foo(): ... "base foo doc" >>> class IBase1(IBase): ... pass >>> class IBase2(IBase): ... ... def foo(): ... "base2 foo doc" >>> class ISub(IBase1, IBase2): ... pass ISub's definition of foo is the one from IBase2, since IBase2 is more specific that IBase:: >>> ISub['foo'].__doc__ 'base2 foo doc' Note that this differs from a depth-first search. Sometimes, it's useful to ask whether an interface defines an attribute directly. You can use the direct method to get a directly defined definitions:: >>> IBase.direct('foo').__doc__ 'base foo doc' >>> ISub.direct('foo') Specifications -------------- Interfaces and declarations are both special cases of specifications. What we described above for interface inheritance applies to both declarations and specifications. Declarations actually extend the interfaces that they declare:: >>> class Baz(object): ... zope.interface.implements(IBaz) >>> baz_implements = zope.interface.implementedBy(Baz) >>> baz_implements.__bases__ (, ) >>> baz_implements.extends(IFoo) True >>> baz_implements.isOrExtends(IFoo) True >>> baz_implements.isOrExtends(baz_implements) True Specifications (interfaces and declarations) provide an `__sro__` that lists the specification and all of it's ancestors:: >>> baz_implements.__sro__ (, , , , , ) Tagged Values ============= Interfaces and attribute descriptions support an extension mechanism, borrowed from UML, called "tagged values" that lets us store extra data:: >>> IFoo.setTaggedValue('date-modified', '2004-04-01') >>> IFoo.setTaggedValue('author', 'Jim Fulton') >>> IFoo.getTaggedValue('date-modified') '2004-04-01' >>> IFoo.queryTaggedValue('date-modified') '2004-04-01' >>> IFoo.queryTaggedValue('datemodified') >>> tags = list(IFoo.getTaggedValueTags()) >>> tags.sort() >>> tags ['author', 'date-modified'] Function attributes are converted to tagged values when method attribute definitions are created:: >>> class IBazFactory(zope.interface.Interface): ... def __call__(): ... "create one" ... __call__.return_type = IBaz >>> IBazFactory['__call__'].getTaggedValue('return_type') Tagged values can also be defined from within an interface definition:: >>> class IWithTaggedValues(zope.interface.Interface): ... zope.interface.taggedValue('squish', 'squash') >>> IWithTaggedValues.getTaggedValue('squish') 'squash' Invariants ========== Interfaces can express conditions that must hold for objects that provide them. These conditions are expressed using one or more invariants. Invariants are callable objects that will be called with an object that provides an interface. An invariant raises an `Invalid` exception if the condition doesn't hold. Here's an example:: >>> class RangeError(zope.interface.Invalid): ... """A range has invalid limits""" ... def __repr__(self): ... return "RangeError(%r)" % self.args >>> def range_invariant(ob): ... if ob.max < ob.min: ... raise RangeError(ob) Given this invariant, we can use it in an interface definition:: >>> class IRange(zope.interface.Interface): ... min = zope.interface.Attribute("Lower bound") ... max = zope.interface.Attribute("Upper bound") ... ... zope.interface.invariant(range_invariant) Interfaces have a method for checking their invariants:: >>> class Range(object): ... zope.interface.implements(IRange) ... ... def __init__(self, min, max): ... self.min, self.max = min, max ... ... def __repr__(self): ... return "Range(%s, %s)" % (self.min, self.max) >>> IRange.validateInvariants(Range(1,2)) >>> IRange.validateInvariants(Range(1,1)) >>> IRange.validateInvariants(Range(2,1)) Traceback (most recent call last): ... RangeError: Range(2, 1) If you have multiple invariants, you may not want to stop checking after the first error. If you pass a list to `validateInvariants`, then a single `Invalid` exception will be raised with the list of exceptions as it's argument:: >>> from zope.interface.exceptions import Invalid >>> errors = [] >>> try: ... IRange.validateInvariants(Range(2,1), errors) ... except Invalid, e: ... str(e) '[RangeError(Range(2, 1))]' And the list will be filled with the individual exceptions:: >>> errors [RangeError(Range(2, 1))] >>> del errors[:] Adaptation ========== Interfaces can be called to perform adaptation. The semantics are based on those of the PEP 246 adapt function. If an object cannot be adapted, then a TypeError is raised:: >>> class I(zope.interface.Interface): ... pass >>> I(0) Traceback (most recent call last): ... TypeError: ('Could not adapt', 0, ) unless an alternate value is provided as a second positional argument:: >>> I(0, 'bob') 'bob' If an object already implements the interface, then it will be returned:: >>> class C(object): ... zope.interface.implements(I) >>> obj = C() >>> I(obj) is obj True If an object implements __conform__, then it will be used:: >>> class C(object): ... zope.interface.implements(I) ... def __conform__(self, proto): ... return 0 >>> I(C()) 0 Adapter hooks (see __adapt__) will also be used, if present:: >>> from zope.interface.interface import adapter_hooks >>> def adapt_0_to_42(iface, obj): ... if obj == 0: ... return 42 >>> adapter_hooks.append(adapt_0_to_42) >>> I(0) 42 >>> adapter_hooks.remove(adapt_0_to_42) >>> I(0) Traceback (most recent call last): ... TypeError: ('Could not adapt', 0, ) __adapt__ --------- >>> class I(zope.interface.Interface): ... pass Interfaces implement the PEP 246 __adapt__ method. This method is normally not called directly. It is called by the PEP 246 adapt framework and by the interface __call__ operator. The adapt method is responsible for adapting an object to the reciever. The default version returns None:: >>> I.__adapt__(0) unless the object given provides the interface:: >>> class C(object): ... zope.interface.implements(I) >>> obj = C() >>> I.__adapt__(obj) is obj True Adapter hooks can be provided (or removed) to provide custom adaptation. We'll install a silly hook that adapts 0 to 42. We install a hook by simply adding it to the adapter_hooks list:: >>> from zope.interface.interface import adapter_hooks >>> def adapt_0_to_42(iface, obj): ... if obj == 0: ... return 42 >>> adapter_hooks.append(adapt_0_to_42) >>> I.__adapt__(0) 42 Hooks must either return an adapter, or None if no adapter can be found. Hooks can be uninstalled by removing them from the list:: >>> adapter_hooks.remove(adapt_0_to_42) >>> I.__adapt__(0) .. [#create] The main reason we subclass `Interface` is to cause the Python class statement to create an interface, rather than a class. It's possible to create interfaces by calling a special interface class directly. Doing this, it's possible (and, on rare occasions, useful) to create interfaces that don't descend from `Interface`. Using this technique is beyond the scope of this document. .. [#factory] Classes are factories. They can be called to create their instances. We expect that we will eventually extend the concept of implementation to other kinds of factories, so that we can declare the interfaces provided by the objects created. .. [#compat] The goal is substitutability. An object that provides an extending interface should be substitutable for an object that provides the extended interface. In our example, an object that provides IBaz should be usable whereever an object that provides IBlat is expected. The interface implementation doesn't enforce this. but maybe it should do some checks. ================ Adapter Registry ================ Adapter registries provide a way to register objects that depend on one or more interface specifications and provide (perhaps indirectly) some interface. In addition, the registrations have names. (You can think of the names as qualifiers of the provided interfaces.) The term "interface specification" refers both to interfaces and to interface declarations, such as declarations of interfaces implemented by a class. Single Adapters =============== Let's look at a simple example, using a single required specification:: >>> from zope.interface.adapter import AdapterRegistry >>> import zope.interface >>> class IR1(zope.interface.Interface): ... pass >>> class IP1(zope.interface.Interface): ... pass >>> class IP2(IP1): ... pass >>> registry = AdapterRegistry() We'll register an object that depends on IR1 and "provides" IP2:: >>> registry.register([IR1], IP2, '', 12) Given the registration, we can look it up again:: >>> registry.lookup([IR1], IP2, '') 12 Note that we used an integer in the example. In real applications, one would use some objects that actually depend on or provide interfaces. The registry doesn't care about what gets registered, so we'll use integers and strings to keep the examples simple. There is one exception. Registering a value of None unregisters any previously-registered value. If an object depends on a specification, it can be looked up with a specification that extends the specification that it depends on:: >>> class IR2(IR1): ... pass >>> registry.lookup([IR2], IP2, '') 12 We can use a class implementation specification to look up the object:: >>> class C2: ... zope.interface.implements(IR2) >>> registry.lookup([zope.interface.implementedBy(C2)], IP2, '') 12 and it can be looked up for interfaces that its provided interface extends:: >>> registry.lookup([IR1], IP1, '') 12 >>> registry.lookup([IR2], IP1, '') 12 But if you require a specification that doesn't extend the specification the object depends on, you won't get anything:: >>> registry.lookup([zope.interface.Interface], IP1, '') By the way, you can pass a default value to lookup:: >>> registry.lookup([zope.interface.Interface], IP1, '', 42) 42 If you try to get an interface the object doesn't provide, you also won't get anything:: >>> class IP3(IP2): ... pass >>> registry.lookup([IR1], IP3, '') You also won't get anything if you use the wrong name:: >>> registry.lookup([IR1], IP1, 'bob') >>> registry.register([IR1], IP2, 'bob', "Bob's 12") >>> registry.lookup([IR1], IP1, 'bob') "Bob's 12" You can leave the name off when doing a lookup:: >>> registry.lookup([IR1], IP1) 12 If we register an object that provides IP1:: >>> registry.register([IR1], IP1, '', 11) then that object will be prefered over O(12):: >>> registry.lookup([IR1], IP1, '') 11 Also, if we register an object for IR2, then that will be prefered when using IR2:: >>> registry.register([IR2], IP1, '', 21) >>> registry.lookup([IR2], IP1, '') 21 Finding out what, if anything, is registered -------------------------------------------- We can ask if there is an adapter registered for a collection of interfaces. This is different than lookup, because it looks for an exact match. >>> print registry.registered([IR1], IP1) 11 >>> print registry.registered([IR1], IP2) 12 >>> print registry.registered([IR1], IP2, 'bob') Bob's 12 >>> print registry.registered([IR2], IP1) 21 >>> print registry.registered([IR2], IP2) None In the last example, None was returned because nothing was registered exactly for the given interfaces. lookup1 ------- Lookup of single adapters is common enough that there is a specialized version of lookup that takes a single required interface:: >>> registry.lookup1(IR2, IP1, '') 21 >>> registry.lookup1(IR2, IP1) 21 Actual Adaptation ----------------- The adapter registry is intended to support adaptation, where one object that implements an interface is adapted to another object that supports a different interface. The adapter registry supports the computation of adapters. In this case, we have to register adapter factories:: >>> class IR(zope.interface.Interface): ... pass >>> class X: ... zope.interface.implements(IR) >>> class Y: ... zope.interface.implements(IP1) ... def __init__(self, context): ... self.context = context >>> registry.register([IR], IP1, '', Y) In this case, we registered a class as the factory. Now we can call `queryAdapter` to get the adapted object:: >>> x = X() >>> y = registry.queryAdapter(x, IP1) >>> y.__class__.__name__ 'Y' >>> y.context is x True We can register and lookup by name too:: >>> class Y2(Y): ... pass >>> registry.register([IR], IP1, 'bob', Y2) >>> y = registry.queryAdapter(x, IP1, 'bob') >>> y.__class__.__name__ 'Y2' >>> y.context is x True When the adapter factory produces `None`, then this is treated as if no adapter has been found. This allows us to prevent adaptation (when desired) and let the adapter factory determine whether adaptation is possible based on the state of the object being adapted. >>> def factory(context): ... if context.name == 'object': ... return 'adapter' ... return None >>> class Object(object): ... zope.interface.implements(IR) ... name = 'object' >>> registry.register([IR], IP1, 'conditional', factory) >>> obj = Object() >>> registry.queryAdapter(obj, IP1, 'conditional') 'adapter' >>> obj.name = 'no object' >>> registry.queryAdapter(obj, IP1, 'conditional') is None True >>> registry.queryAdapter(obj, IP1, 'conditional', 'default') 'default' An alternate method that provides the same function as `queryAdapter()` is `adapter_hook()`:: >>> y = registry.adapter_hook(IP1, x) >>> y.__class__.__name__ 'Y' >>> y.context is x True >>> y = registry.adapter_hook(IP1, x, 'bob') >>> y.__class__.__name__ 'Y2' >>> y.context is x True The `adapter_hook()` simply switches the order of the object and interface arguments. It is used to hook into the interface call mechanism. Default Adapters ---------------- Sometimes, you want to provide an adapter that will adapt anything. For that, provide None as the required interface:: >>> registry.register([None], IP1, '', 1) then we can use that adapter for interfaces we don't have specific adapters for:: >>> class IQ(zope.interface.Interface): ... pass >>> registry.lookup([IQ], IP1, '') 1 Of course, specific adapters are still used when applicable:: >>> registry.lookup([IR2], IP1, '') 21 Class adapters -------------- You can register adapters for class declarations, which is almost the same as registering them for a class:: >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', 'C21') >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') 'C21' Dict adapters ------------- At some point it was impossible to register dictionary-based adapters due a bug. Let's make sure this works now: >>> adapter = {} >>> registry.register((), IQ, '', adapter) >>> registry.lookup((), IQ, '') is adapter True Unregistering ------------- You can unregister by registering None, rather than an object:: >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', None) >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') 21 Of course, this means that None can't be registered. This is an exception to the statement, made earlier, that the registry doesn't care what gets registered. Multi-adapters ============== You can adapt multiple specifications:: >>> registry.register([IR1, IQ], IP2, '', '1q2') >>> registry.lookup([IR1, IQ], IP2, '') '1q2' >>> registry.lookup([IR2, IQ], IP1, '') '1q2' >>> class IS(zope.interface.Interface): ... pass >>> registry.lookup([IR2, IS], IP1, '') >>> class IQ2(IQ): ... pass >>> registry.lookup([IR2, IQ2], IP1, '') '1q2' >>> registry.register([IR1, IQ2], IP2, '', '1q22') >>> registry.lookup([IR2, IQ2], IP1, '') '1q22' Multi-adaptation ---------------- You can adapt multiple objects:: >>> class Q: ... zope.interface.implements(IQ) As with single adapters, we register a factory, which is often a class:: >>> class IM(zope.interface.Interface): ... pass >>> class M: ... zope.interface.implements(IM) ... def __init__(self, x, q): ... self.x, self.q = x, q >>> registry.register([IR, IQ], IM, '', M) And then we can call `queryMultiAdapter` to compute an adapter:: >>> q = Q() >>> m = registry.queryMultiAdapter((x, q), IM) >>> m.__class__.__name__ 'M' >>> m.x is x and m.q is q True and, of course, we can use names:: >>> class M2(M): ... pass >>> registry.register([IR, IQ], IM, 'bob', M2) >>> m = registry.queryMultiAdapter((x, q), IM, 'bob') >>> m.__class__.__name__ 'M2' >>> m.x is x and m.q is q True Default Adapters ---------------- As with single adapters, you can define default adapters by specifying None for the *first* specification:: >>> registry.register([None, IQ], IP2, '', 'q2') >>> registry.lookup([IS, IQ], IP2, '') 'q2' Null Adapters ============= You can also adapt no specification:: >>> registry.register([], IP2, '', 2) >>> registry.lookup([], IP2, '') 2 >>> registry.lookup([], IP1, '') 2 Listing named adapters ---------------------- Adapters are named. Sometimes, it's useful to get all of the named adapters for given interfaces:: >>> adapters = list(registry.lookupAll([IR1], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', 11), (u'bob', "Bob's 12")] This works for multi-adapters too:: >>> registry.register([IR1, IQ2], IP2, 'bob', '1q2 for bob') >>> adapters = list(registry.lookupAll([IR2, IQ2], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', '1q22'), (u'bob', '1q2 for bob')] And even null adapters:: >>> registry.register([], IP2, 'bob', 3) >>> adapters = list(registry.lookupAll([], IP1)) >>> adapters.sort() >>> assert adapters == [(u'', 2), (u'bob', 3)] Subscriptions ============= Normally, we want to look up an object that most-closely matches a specification. Sometimes, we want to get all of the objects that match some specification. We use subscriptions for this. We subscribe objects against specifications and then later find all of the subscribed objects:: >>> registry.subscribe([IR1], IP2, 'sub12 1') >>> registry.subscriptions([IR1], IP2) ['sub12 1'] Note that, unlike regular adapters, subscriptions are unnamed. You can have multiple subscribers for the same specification:: >>> registry.subscribe([IR1], IP2, 'sub12 2') >>> registry.subscriptions([IR1], IP2) ['sub12 1', 'sub12 2'] If subscribers are registered for the same required interfaces, they are returned in the order of definition. You can register subscribers for all specifications using None:: >>> registry.subscribe([None], IP1, 'sub_1') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2'] Note that the new subscriber is returned first. Subscribers defined for less general required interfaces are returned before subscribers for more general interfaces. Subscriptions may be combined over multiple compatible specifications:: >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2'] >>> registry.subscribe([IR1], IP1, 'sub11') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2', 'sub11'] >>> registry.subscribe([IR2], IP2, 'sub22') >>> registry.subscriptions([IR2], IP1) ['sub_1', 'sub12 1', 'sub12 2', 'sub11', 'sub22'] >>> registry.subscriptions([IR2], IP2) ['sub12 1', 'sub12 2', 'sub22'] Subscriptions can be on multiple specifications:: >>> registry.subscribe([IR1, IQ], IP2, 'sub1q2') >>> registry.subscriptions([IR1, IQ], IP2) ['sub1q2'] As with single subscriptions and non-subscription adapters, you can specify None for the first required interface, to specify a default:: >>> registry.subscribe([None, IQ], IP2, 'sub_q2') >>> registry.subscriptions([IS, IQ], IP2) ['sub_q2'] >>> registry.subscriptions([IR1, IQ], IP2) ['sub_q2', 'sub1q2'] You can have subscriptions that are indepenent of any specifications:: >>> list(registry.subscriptions([], IP1)) [] >>> registry.subscribe([], IP2, 'sub2') >>> registry.subscriptions([], IP1) ['sub2'] >>> registry.subscribe([], IP1, 'sub1') >>> registry.subscriptions([], IP1) ['sub2', 'sub1'] >>> registry.subscriptions([], IP2) ['sub2'] Unregistering subscribers ------------------------- We can unregister subscribers. When unregistering a subscriber, we can unregister a specific subscriber:: >>> registry.unsubscribe([IR1], IP1, 'sub11') >>> registry.subscriptions([IR1], IP1) ['sub_1', 'sub12 1', 'sub12 2'] If we don't specify a value, then all subscribers matching the given interfaces will be unsubscribed: >>> registry.unsubscribe([IR1], IP2) >>> registry.subscriptions([IR1], IP1) ['sub_1'] Subscription adapters --------------------- We normally register adapter factories, which then allow us to compute adapters, but with subscriptions, we get multiple adapters. Here's an example of multiple-object subscribers:: >>> registry.subscribe([IR, IQ], IM, M) >>> registry.subscribe([IR, IQ], IM, M2) >>> subscribers = registry.subscribers((x, q), IM) >>> len(subscribers) 2 >>> class_names = [s.__class__.__name__ for s in subscribers] >>> class_names.sort() >>> class_names ['M', 'M2'] >>> [(s.x is x and s.q is q) for s in subscribers] [True, True] adapter factory subcribers can't return None values:: >>> def M3(x, y): ... return None >>> registry.subscribe([IR, IQ], IM, M3) >>> subscribers = registry.subscribers((x, q), IM) >>> len(subscribers) 2 Handlers -------- A handler is a subscriber factory that doesn't produce any normal output. It returns None. A handler is unlike adapters in that it does all of its work when the factory is called. To register a handler, simply provide None as the provided interface:: >>> def handler(event): ... print 'handler', event >>> registry.subscribe([IR1], None, handler) >>> registry.subscriptions([IR1], None) == [handler] True ========================== Using the Adapter Registry ========================== This is a small demonstration of the ``zope.interface`` package including its adapter registry. It is intended to provide a concrete but narrow example on how to use interfaces and adapters outside of Zope 3. First we have to import the interface package:: >>> import zope.interface We now develop an interface for our object, which is a simple file in this case. For now we simply support one attribute, the body, which contains the actual file contents:: >>> class IFile(zope.interface.Interface): ... ... body = zope.interface.Attribute('Contents of the file.') ... For statistical reasons we often want to know the size of a file. However, it would be clumsy to implement the size directly in the file object, since the size really represents meta-data. Thus we create another interface that provides the size of something:: >>> class ISize(zope.interface.Interface): ... ... def getSize(): ... 'Return the size of an object.' ... Now we need to implement the file. It is essential that the object states that it implements the `IFile` interface. We also provide a default body value (just to make things simpler for this example):: >>> class File(object): ... ... zope.interface.implements(IFile) ... body = 'foo bar' ... Next we implement an adapter that can provide the `ISize` interface given any object providing `IFile`. By convention we use `__used_for__` to specify the interface that we expect the adapted object to provide, in our case `IFile`. However, this attribute is not used for anything. If you have multiple interfaces for which an adapter is used, just specify the interfaces via a tuple. Again by convention, the constructor of an adapter takes one argument, the context. The context in this case is an instance of `File` (providing `IFile`) that is used to extract the size from. Also by convention the context is stored in an attribute named `context` on the adapter. The twisted community refers to the context as the `original` object. However, you may feel free to use a specific argument name, such as `file`:: >>> class FileSize(object): ... ... zope.interface.implements(ISize) ... __used_for__ = IFile ... ... def __init__(self, context): ... self.context = context ... ... def getSize(self): ... return len(self.context.body) ... Now that we have written our adapter, we have to register it with an adapter registry, so that it can be looked up when needed. There is no such thing as a global registry; thus we have to instantiate one for our example manually:: >>> from zope.interface.adapter import AdapterRegistry >>> registry = AdapterRegistry() The registry keeps a map of what adapters implement based on another interface, the object already provides. Therefore, we next have to register an adapter that adapts from `IFile` to `ISize`. The first argument to the registry's `register()` method is a list of original interfaces.In our cause we have only one original interface, `IFile`. A list makes sense, since the interface package has the concept of multi-adapters, which are adapters that require multiple objects to adapt to a new interface. In these situations, your adapter constructor will require an argument for each specified interface. The second argument is the interface the adapter provides, in our case `ISize`. The third argument is the name of the adapter. Since we do not care about names, we simply leave it as an empty string. Names are commonly useful, if you have adapters for the same set of interfaces, but they are useful in different situations. The last argument is simply the adapter class:: >>> registry.register([IFile], ISize, '', FileSize) You can now use the the registry to lookup the adapter:: >>> registry.lookup1(IFile, ISize, '') Let's get a little bit more practical. Let's create a `File` instance and create the adapter using a registry lookup. Then we see whether the adapter returns the correct size by calling `getSize()`:: >>> file = File() >>> size = registry.lookup1(IFile, ISize, '')(file) >>> size.getSize() 7 However, this is not very practical, since I have to manually pass in the arguments to the lookup method. There is some syntactic candy that will allow us to get an adapter instance by simply calling `ISize(file)`. To make use of this functionality, we need to add our registry to the adapter_hooks list, which is a member of the adapters module. This list stores a collection of callables that are automatically invoked when IFoo(obj) is called; their purpose is to locate adapters that implement an interface for a certain context instance. You are required to implement your own adapter hook; this example covers one of the simplest hooks that use the registry, but you could implement one that used an adapter cache or persistent adapters, for instance. The helper hook is required to expect as first argument the desired output interface (for us `ISize`) and as the second argument the context of the adapter (here `file`). The function returns an adapter, i.e. a `FileSize` instance:: >>> def hook(provided, object): ... adapter = registry.lookup1(zope.interface.providedBy(object), ... provided, '') ... return adapter(object) ... We now just add the hook to an `adapter_hooks` list:: >>> from zope.interface.interface import adapter_hooks >>> adapter_hooks.append(hook) Once the hook is registered, you can use the desired syntax:: >>> size = ISize(file) >>> size.getSize() 7 Now we have to cleanup after ourselves, so that others after us have a clean `adapter_hooks` list:: >>> adapter_hooks.remove(hook) That's it. I have intentionally left out a discussion of named adapters and multi-adapters, since this text is intended as a practical and simple introduction to Zope 3 interfaces and adapters. You might want to read the `adapter.txt` in the `zope.interface` package for a more formal, referencial and complete treatment of the package. Warning: People have reported that `adapter.txt` makes their brain feel soft! ``zope.interface Changelog`` ============================ 3.6.7 (2011-08-20) ------------------ - Fix sporadic failures on x86-64 platforms in tests of rich comparisons of interfaces. 3.6.6 (2011-08-13) ------------------ - LP #570942: Now correctly compare interfaces from different modules but with the same names. N.B.: This is a less intrusive / destabilizing fix than the one applied in 3.6.3: we only fix the underlying cmp-alike function, rather than adding the other "rich comparison" functions. - Revert to software as released with 3.6.1 for "stable" 3.6 release branch. 3.6.5 (2011-08-11) ------------------ - LP #811792: work around buggy behavior in some subclasses of ``zope.interface.interface.InterfaceClass``, which invoke ``__hash__`` before initializing ``__module__`` and ``__name__``. The workaround returns a fixed constant hash in such cases, and issues a ``UserWarning``. - LP #804832: Under PyPy, ``zope.interface`` should not build its C extension. Also, prevent attempting to build it under Jython. - Add a tox.ini for easier xplatform testing. - Fix testing deprecation warnings issued when tested under Py3K. 3.6.4 (2011-07-04) ------------------ - LP 804951: InterfaceClass instances were unhashable under Python 3.x. 3.6.3 (2011-05-26) ------------------ - LP #570942: Now correctly compare interfaces from different modules but with the same names. 3.6.2 (2011-05-17) ------------------ - Moved detailed documentation out-of-line from PyPI page, linking instead to http://docs.zope.org/zope.interface . - Fixes for small issues when running tests under Python 3.2 using ``zope.testrunner``. - LP # 675064: Specify return value type for C optimizations module init under Python 3: undeclared value caused warnings, and segfaults on some 64 bit architectures. - setup.py now raises RuntimeError if you don't have Distutils installed when running under Python 3. 3.6.1 (2010-05-03) ------------------ - A non-ASCII character in the changelog made 3.6.0 uninstallable on Python 3 systems with another default encoding than UTF-8. - Fixed compiler warnings under GCC 4.3.3. 3.6.0 (2010-04-29) ------------------ - LP #185974: Clear the cache used by ``Specificaton.get`` inside ``Specification.changed``. Thanks to Jacob Holm for the patch. - Added support for Python 3.1. Contributors: Lennart Regebro Martin v Loewis Thomas Lotze Wolfgang Schnerring The 3.1 support is completely backwards compatible. However, the implements syntax used under Python 2.X does not work under 3.X, since it depends on how metaclasses are implemented and this has changed. Instead it now supports a decorator syntax (also under Python 2.X):: class Foo: implements(IFoo) ... can now also be written:: @implementor(IFoo): class Foo: ... There are 2to3 fixers available to do this change automatically in the zope.fixers package. - Python 2.3 is no longer supported. 3.5.4 (2009-12-23) ------------------ - Use the standard Python doctest module instead of zope.testing.doctest, which has been deprecated. 3.5.3 (2009-12-08) ------------------ - Fix an edge case: make providedBy() work when a class has '__provides__' in its __slots__ (see http://thread.gmane.org/gmane.comp.web.zope.devel/22490) 3.5.2 (2009-07-01) ------------------ - BaseAdapterRegistry.unregister, unsubscribe: Remove empty portions of the data structures when something is removed. This avoids leaving references to global objects (interfaces) that may be slated for removal from the calling application. 3.5.1 (2009-03-18) ------------------ - verifyObject: use getattr instead of hasattr to test for object attributes in order to let exceptions other than AttributeError raised by properties propagate to the caller - Add Sphinx-based documentation building to the package buildout configuration. Use the ``bin/docs`` command after buildout. - Improve package description a bit. Unify changelog entries formatting. - Change package's mailing list address to zope-dev at zope.org as zope3-dev at zope.org is now retired. 3.5.0 (2008-10-26) ------------------ - Fixed declaration of _zope_interface_coptimizations, it's not a top level package. - Add a DocTestSuite for odd.py module, so their tests are run. - Allow to bootstrap on Jython. - Fix https://bugs.launchpad.net/zope3/3.3/+bug/98388: ISpecification was missing a declaration for __iro__. - Added optional code optimizations support, which allows the building of C code optimizations to fail (Jython). - Replaced `_flatten` with a non-recursive implementation, effectively making it 3x faster. 3.4.1 (2007-10-02) ------------------ - Fixed a setup bug that prevented installation from source on systems without setuptools. 3.4.0 (2007-07-19) ------------------ - Final release for 3.4.0. 3.4.0b3 (2007-05-22) -------------------- - Objects with picky custom comparison methods couldn't be added to component registries. Now, when checking whether an object is already registered, identity comparison is used. 3.3.0.1 (2007-01-03) -------------------- - Made a reference to OverflowWarning, which disappeared in Python 2.5, conditional. 3.3.0 (2007/01/03) ------------------ New Features ++++++++++++ - The adapter-lookup algorithim was refactored to make it much simpler and faster. Also, more of the adapter-lookup logic is implemented in C, making debugging of application code easier, since there is less infrastructre code to step through. - We now treat objects without interface declarations as if they declared that they provide zope.interface.Interface. - There are a number of richer new adapter-registration interfaces that provide greater control and introspection. - Added a new interface decorator to zope.interface that allows the setting of tagged values on an interface at definition time (see zope.interface.taggedValue). Bug Fixes +++++++++ - A bug in multi-adapter lookup sometimes caused incorrect adapters to be returned. 3.2.0.2 (2006-04-15) -------------------- - Fix packaging bug: 'package_dir' must be a *relative* path. 3.2.0.1 (2006-04-14) -------------------- - Packaging change: suppress inclusion of 'setup.cfg' in 'sdist' builds. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope 3.2.0 release. 3.1.0 (2005-10-03) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope 3.1.0 release. - Made attribute resolution order consistent with component lookup order, i.e. new-style class MRO semantics. - Deprecated 'isImplementedBy' and 'isImplementedByInstancesOf' APIs in favor of 'implementedBy' and 'providedBy'. 3.0.1 (2005-07-27) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope X3.0.1 release. - Fixed a bug reported by James Knight, which caused adapter registries to fail occasionally to reflect declaration changes. 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the zope.interface package shipped as part of the Zope X3.0.0 release. Download ******** Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Topic :: Software Development :: Libraries :: Python Modules zope2.13-2.13.21/source/zope.interface/src/zope.interface.egg-info/dependency_links.txt0000644000175000017500000000000112214017572027617 0ustar arnauarnau zope2.13-2.13.21/source/zope.interface/src/zope.interface.egg-info/requires.txt0000644000175000017500000000004712214017572026152 0ustar arnauarnausetuptools [docs] z3c.recipe.sphinxdoczope2.13-2.13.21/source/zope.interface/src/zope.interface.egg-info/namespace_packages.txt0000644000175000017500000000000512214017572030077 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.interface/src/zope.interface.egg-info/top_level.txt0000644000175000017500000000000512214017572026276 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.interface/src/zope.interface.egg-info/SOURCES.txt0000644000175000017500000000420612214017572025437 0ustar arnauarnau.bzrignore CHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py build_ext_2.py build_ext_3.py buildout.cfg setup.py src/zope/__init__.py src/zope.interface.egg-info/PKG-INFO src/zope.interface.egg-info/SOURCES.txt src/zope.interface.egg-info/dependency_links.txt src/zope.interface.egg-info/namespace_packages.txt src/zope.interface.egg-info/not-zip-safe src/zope.interface.egg-info/requires.txt src/zope.interface.egg-info/top_level.txt src/zope/interface/README.ru.txt src/zope/interface/README.txt src/zope/interface/__init__.py src/zope/interface/_flatten.py src/zope/interface/_zope_interface_coptimizations.c src/zope/interface/adapter.py src/zope/interface/adapter.ru.txt src/zope/interface/adapter.txt src/zope/interface/advice.py src/zope/interface/declarations.py src/zope/interface/document.py src/zope/interface/exceptions.py src/zope/interface/human.ru.txt src/zope/interface/human.txt src/zope/interface/index.txt src/zope/interface/interface.py src/zope/interface/interfaces.py src/zope/interface/ro.py src/zope/interface/verify.py src/zope/interface/verify.txt src/zope/interface/common/__init__.py src/zope/interface/common/idatetime.py src/zope/interface/common/interfaces.py src/zope/interface/common/mapping.py src/zope/interface/common/sequence.py src/zope/interface/common/tests/__init__.py src/zope/interface/common/tests/basemapping.py src/zope/interface/common/tests/test_idatetime.py src/zope/interface/common/tests/test_import_interfaces.py src/zope/interface/tests/__init__.py src/zope/interface/tests/dummy.py src/zope/interface/tests/foodforthought.txt src/zope/interface/tests/ifoo.py src/zope/interface/tests/ifoo_other.py src/zope/interface/tests/m1.py src/zope/interface/tests/m2.py src/zope/interface/tests/odd.py src/zope/interface/tests/test_adapter.py src/zope/interface/tests/test_advice.py src/zope/interface/tests/test_declarations.py src/zope/interface/tests/test_document.py src/zope/interface/tests/test_element.py src/zope/interface/tests/test_interface.py src/zope/interface/tests/test_odd_declarations.py src/zope/interface/tests/test_sorting.py src/zope/interface/tests/test_verify.py src/zope/interface/tests/unitfixtures.pyzope2.13-2.13.21/source/zope.interface/src/zope.interface.egg-info/not-zip-safe0000644000175000017500000000000112214017572025777 0ustar arnauarnau zope2.13-2.13.21/source/zope.interface/.bzrignore0000644000175000017500000000011712214017572020355 0ustar arnauarnau./.installed.cfg ./bin ./eggs ./develop-eggs ./docs ./parts *.egg-info ./build zope2.13-2.13.21/source/zope.contentprovider/0000755000175000017500000000000012214017551017636 5ustar arnauarnauzope2.13-2.13.21/source/zope.contentprovider/setup.py0000644000175000017500000000533712214017551021360 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.contentprovider package $Id: setup.py 112694 2010-05-25 14:08:28Z tseaver $ """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.contentprovider', version = '3.7.2', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description='Content Provider Framework for Zope Templates', long_description=( read('src', 'zope', 'contentprovider', 'README.txt') + '\n\n' + read('CHANGES.txt') ), keywords = "zope3 content provider", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.contentprovider', license='ZPL 2.1', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope'], extras_require = dict( test=['zope.browserpage>=3.12', 'zope.testing', ]), install_requires=['setuptools', 'zope.component', 'zope.event', 'zope.interface', 'zope.location', 'zope.publisher', 'zope.schema', 'zope.tales', ], include_package_data = True, zip_safe = False, ) zope2.13-2.13.21/source/zope.contentprovider/PKG-INFO0000644000175000017500000007022312214017551020737 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.contentprovider Version: 3.7.2 Summary: Content Provider Framework for Zope Templates Home-page: http://pypi.python.org/pypi/zope.contentprovider Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ================= Content Providers ================= This package provides a framework to develop componentized Web GUI applications. Instead of describing the content of a page using a single template or static system of templates and METAL macros, content provider objects are dynamically looked up based on the setup/configuration of the application. .. contents:: Motivation and Design Goals --------------------------- Before diving into the features of this package let me take up a few bytes of text to explain the use cases that drove us to develop this package (also others) and how the API documented below fulfills/solves those use cases. When we started developing Zope 3, it was from a desire to decentralize functionality and thus the complexity of the Python code. And we were successful! The component architecture is a marvelous piece of software that hopefully will allow us to build scalable solutions for a very long time. However, when it comes to user interface design, in this case specifically HTML pages, we have failed to provide the features and patterns of assembeling a page from configured components. Looking up views for a particular content component and a request just simply does not work by itself. The content inside the page is still monolithic. One attempt to solve this problem are METAL macros, which allow you to insert other TAL snippets into your main template. But macros have two shortcomings. For one there is a "hard-coded" one-to-one mapping between a slot and the macro that fills that slot, which makes it impossible to register several macros for a given location. The second problem is that macros are not views in their own right; thus they cannot provide functionality that is independent of the main template's view. A second approach to modular UI design are rendering pipes. Rendering pipes have the great advantage that they can reach all regions of the page during every step of the rendering process. For example, if we have a widget in the middle of the page that requires some additional Javascript, then it is easy for a rendering unit to insert the Javascript file link in the HTML header of the page. This type of use case is very hard to solve using page templates. However, pipes are not the answer to componentized user interface, since they cannot simply deal with registering random content for a given page region. In fact, I propose that pipelines are orthogonal to content providers, the concept introducted below. A pipeline framework could easily use functionality provided by this and other packages to provide component-driven UI design. So our goal is clear: Bring the pluggability of the component architecture into page templates and user interface design. Zope is commonly known to reinvent the wheel, develop its own terminology and misuse other's terms. For example, the Plone community has a very different understanding of what a "portlet" is compared to the commonly accepted meaning in the corporate world, which derives its definition from JSR 168. Therefore an additional use case of the design of this package was to stick with common terms and use them in their original meaning -- well, given a little extra twist. The most basic user interface component in the Web application Java world is the "content provider" [1]_. A content provider is simply responsible for providing HTML content for a page. This is equivalent to a view that does not provide a full page, but just a snippet, much like widgets or macros. Once there is a way to configure those content providers, we need a way to insert them into our page templates. In our implementation this is accomplished using a new TALES namespace that allows to insert content providers by name. But how, you might wonder, does this provide a componentized user interface? On the Zope 3 level, each content provider is registered as a presentation component discriminated by the context, request and view it will appear in. Thus different content providers will be picked for different configurations. Okay, that's pretty much everything there is to say about content providers. What, we are done? Hold on, what about defining regions of pages and filling them configured UI snippets. The short answer is: See the ``zope.viewlet`` pacakge. But let me also give you the long answer. This and the other pacakges were developed using real world use cases. While doing this, we noticed that not every project would need, for example, all the features of a portlet, but would still profit from lower-level features. Thus we decided to declare clear boundaries of functionality and providing each level in a different package. This particualr package is only meant to provide the interface between the content provider world and page templates. .. [1] Note that this is a bit different from the role named content provider, which refers to a service that provides content; the content provider we are talking about here are the software components the service would provide to an application. Content Providers ----------------- Content Provider is a term from the Java world that refers to components that can provide HTML content. It means nothing more! How the content is found and returned is totally up to the implementation. The Zope 3 touch to the concept is that content providers are multi-adapters that are looked up by the context, request (and thus the layer/skin), and view they are displayed in. The second important concept of content providers are their two-phase rendering design. In the first phase the state of the content provider is prepared and, if applicable, any data the provider is responsible for is updated. >>> from zope.contentprovider import interfaces So let's create a simple content provider: >>> import zope.interface >>> import zope.component >>> from zope.publisher.interfaces import browser >>> class MessageBox(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... message = u'My Message' ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
%s
' %self.message The ``update()`` method is executed during phase one. Since no state needs to be calculated and no data is modified by this simple content provider, it is an empty implementation. The ``render()`` method implements phase 2 of the process. We can now instantiate the content provider (manually) and render it: >>> box = MessageBox(None, None, None) >>> box.render() u'
My Message
' Since our content provider did not require the context, request or view to create its HTML content, we were able to pass trivial dummy values into the constructor. Also note that the provider must have a parent (using the ``__parent__`` attribute) specified at all times. The parent must be the view the provider appears in. I agree, this functionally does not seem very useful now. The constructor and the ``update()`` method seem useless and the returned content is totally static. However, we implemented a contract for content providers that other code can rely on. Content providers are (commonly) instantiated using the context, request and view they appear in and are required to always generate its HTML using those three components. Two-Phased Content Providers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Let's now have a look at a content provider that actively uses the two-phase rendering process. The simpler scenario is the case where the content provider updates a content component without affecting anything else. So let's create a content component to be updated, >>> class Article(object): ... title = u'initial' >>> article = Article() and the content provider that is updating the title: >>> class ChangeTitle(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... fieldName = 'ChangeTitle.title' ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... self.context, self.request = context, request ... ... def update(self): ... if self.fieldName in self.request: ... self.context.title = self.request[self.fieldName] ... ... def render(self): ... return u'' % (self.fieldName, ... self.context.title) Using a request, let's now instantiate the content provider and go through the two-phase rendering process: >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> changer = ChangeTitle(article, request, None) >>> changer.update() >>> changer.render() u'' Let's now enter a new title and render the provider: >>> request = TestRequest(form={'ChangeTitle.title': u'new title'}) >>> changer = ChangeTitle(article, request, None) >>> changer.update() >>> changer.render() u'' >>> article.title u'new title' So this was easy. Let's now look at a case where one content provider's update influences the content of another. Let's say we have a content provider that displays the article's title: >>> class ViewTitle(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... ... def __init__(self, context, request, view): ... self.context, self.__parent__ = context, view ... ... def update(self): ... pass ... ... def render(self): ... return u'

Title: %s

' % self.context.title Let's now say that the `ShowTitle` content provider is shown on a page *before* the `ChangeTitle` content provider. If we do the full rendering process for each provider in sequence, we get the wrong result: >>> request = TestRequest(form={'ChangeTitle.title': u'newer title'}) >>> viewer = ViewTitle(article, request, None) >>> viewer.update() >>> viewer.render() u'

Title: new title

' >>> changer = ChangeTitle(article, request, None) >>> changer.update() >>> changer.render() u'' So the correct way of doing this is to first complete phase 1 (update) for all providers, before executing phase 2 (render): >>> request = TestRequest(form={'ChangeTitle.title': u'newest title'}) >>> viewer = ViewTitle(article, request, None) >>> changer = ChangeTitle(article, request, None) >>> viewer.update() >>> changer.update() >>> viewer.render() u'

Title: newest title

' >>> changer.render() u'' ``UpdateNotCalled`` Errors ~~~~~~~~~~~~~~~~~~~~~~~~~~ Since calling ``update()`` before any other method that mutates the provider or any other data is so important to the correct functioning of the API, the developer has the choice to raise the ``UpdateNotCalled`` error, if any method is called before ``update()`` (with exception of the constructor): >>> class InfoBox(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... self.__updated = False ... ... def update(self): ... self.__updated = True ... ... def render(self): ... if not self.__updated: ... raise interfaces.UpdateNotCalled ... return u'
Some information
' >>> info = InfoBox(None, None, None) >>> info.render() Traceback (most recent call last): ... UpdateNotCalled: ``update()`` was not called yet. >>> info.update() >>> info.render() u'
Some information
' The TALES ``provider`` Expression --------------------------------- The ``provider`` expression will look up the name of the content provider, call it and return the HTML content. The first step, however, will be to register our content provider with the component architecture: >>> zope.component.provideAdapter(MessageBox, name='mypage.MessageBox') The content provider must be registered by name, since the TALES expression uses the name to look up the provider at run time. Let's now create a view using a page template: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> templateFileName = os.path.join(temp_dir, 'template.pt') >>> open(templateFileName, 'w').write(''' ... ... ...

My Web Page

...
... ...
...
... Content here ...
... ... ... ''') As you can see, we exprect the ``provider`` expression to simply look up the content provider and insert the HTML content at this place. Next we register the template as a view (browser page) for all objects: >>> from zope.browserpage.simpleviewclass import SimpleViewClass >>> FrontPage = SimpleViewClass(templateFileName, name='main.html') >>> zope.component.provideAdapter( ... FrontPage, ... (zope.interface.Interface, browser.IDefaultBrowserLayer), ... zope.interface.Interface, ... name='main.html') Let's create a content object that can be viewed: >>> class Content(object): ... zope.interface.implements(zope.interface.Interface) >>> content = Content() Finally we look up the view and render it. Note that a BeforeUpdateEvent is fired - this event should always be fired before any contentprovider is updated. >>> from zope.publisher.browser import TestRequest >>> events = [] >>> zope.component.provideHandler(events.append, (None, )) >>> request = TestRequest() >>> view = zope.component.getMultiAdapter((content, request), ... name='main.html') >>> print view().strip()

My Web Page

My Message
Content here
>>> events [] The event holds the provider and the request. >>> events[0].request >>> events[0].object Failure to lookup a Content Provider ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If the name is not found, an error is raised. To demonstrate this behavior let's create another template: >>> errorFileName = os.path.join(temp_dir, 'error.pt') >>> open(errorFileName, 'w').write(''' ... ... ... ... ... ... ''') >>> ErrorPage = SimpleViewClass(errorFileName, name='error.html') >>> zope.component.provideAdapter( ... ErrorPage, ... (zope.interface.Interface, browser.IDefaultBrowserLayer), ... zope.interface.Interface, ... name='main.html') >>> errorview = zope.component.getMultiAdapter((content, request), ... name='main.html') >>> print errorview() Traceback (most recent call last): ... ContentProviderLookupError: mypage.UnknownName Additional Data from TAL ~~~~~~~~~~~~~~~~~~~~~~~~ The ``provider`` expression allows also for transferring data from the TAL context into the content provider. This is accomplished by having the content provider implement an interface that specifies the attributes and provides ``ITALNamespaceData``: >>> import zope.schema >>> class IMessageText(zope.interface.Interface): ... message = zope.schema.Text(title=u'Text of the message box') >>> zope.interface.directlyProvides(IMessageText, ... interfaces.ITALNamespaceData) Now the message box can receive its text from the TAL environment: >>> class DynamicMessageBox(MessageBox): ... zope.interface.implements(IMessageText) >>> zope.component.provideAdapter( ... DynamicMessageBox, provides=interfaces.IContentProvider, ... name='mypage.DynamicMessageBox') We are now updating our original template to provide the message text: >>> open(templateFileName, 'w').write(''' ... ... ...

My Web Page

...
... ... ...
...
... Content here ...
... ... ... ''') Now we should get two message boxes with different text: >>> print view().strip()

My Web Page

Hello World!
Hello World again!
Content here
Finally, a content provider can also implement several ``ITALNamespaceData``: >>> class IMessageType(zope.interface.Interface): ... type = zope.schema.TextLine(title=u'The type of the message box') >>> zope.interface.directlyProvides(IMessageType, ... interfaces.ITALNamespaceData) We'll change our message box content provider implementation a bit, so the new information is used: >>> class BetterDynamicMessageBox(DynamicMessageBox): ... zope.interface.implements(IMessageType) ... type = None ... ... def render(self): ... return u'
%s
' %(self.type, self.message) >>> zope.component.provideAdapter( ... BetterDynamicMessageBox, provides=interfaces.IContentProvider, ... name='mypage.MessageBox') Of course, we also have to make our tempalte a little bit more dynamic as well: >>> open(templateFileName, 'w').write(''' ... ... ...

My Web Page

...
... ... ...
...
... Content here ...
... ... ... ''') Now we should get two message boxes with different text and types: >>> print view().strip()

My Web Page

Hello World!
Hello World again!
Content here
Base class ---------- The ``zope.contentprovider.provider`` module provides an useful base class for implementing content providers. It has all boilerplate code and it's only required to override the ``render`` method to make it work: >>> from zope.contentprovider.provider import ContentProviderBase >>> class MyProvider(ContentProviderBase): ... def render(self, *args, **kwargs): ... return 'Hi there' >>> provider = MyProvider(None, None, None) >>> interfaces.IContentProvider.providedBy(provider) True >>> provider.update() >>> print provider.render() Hi there Note, that it can't be used as is, without providing the ``render`` method: >>> bad = ContentProviderBase(None, None, None) >>> bad.update() >>> print bad.render() Traceback (most recent call last): ... NotImplementedError: ``render`` method must be implemented by subclass You can add the update logic into the ``update`` method as with any content provider and you can implement more complex rendering patterns, based on templates, using this ContentProviderBase class as a base. You might also want to look at the ``zope.viewlet`` package for a more featureful API. Let's remove all temporary data we created during this README. >>> import shutil >>> shutil.rmtree(temp_dir) ======= CHANGES ======= 3.7.2 (2010-05-25) ------------------ - Fixed unit tests broken under Python 2.4 by the switch to the standard library ``doctest`` module. 3.7.1 (2010-04-30) ------------------ - Prefer the standard library's ``doctest`` module to the one from ``zope.testing.`` 3.7 (2010-04-27) ---------------- - Since ``tales:expressiontype`` is now in ``zope.browserpage``, update conditional ZCML accordingly so it doesn't depend on the presence of ``zope.app.pagetemplate`` anymore. 3.6.1 (2009-12-23) ------------------ - Ensure that our ``configure.zcml`` can be loaded without requiring further dependencies. It uses a ``tales:expressiontype`` directive defined in ``zope.app.pagetemplate.`` We keep that dependency optional, as not all consumers of this package use ZCML to configure the expression type. 3.6.0 (2009-12-22) ------------------ - Updated test dependency to use ``zope.browserpage``. 3.5.0 (2009-03-18) ------------------ - Add very simple, but useful base class for implementing content providers, see ``zope.contentprovider.provider.ContentProviderBase``. - Remove unneeded testing dependencies. We only need ``zope.testing`` and ``zope.app.pagetemplate``. - Remove zcml slug and old zpkg-related files. - Added setuptools dependency to setup.py. - Clean up package's description and documentation a bit. Remove duplicate text in README. - Change mailing list address to zope-dev at zope.org instead of retired one. - Change ``cheeseshop`` to ``pypi`` in the package url. 3.4.0 (2007-10-02) ------------------ - Initial release independent of the main Zope tree. Keywords: zope3 content provider Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.contentprovider/pip-egg-info/0000755000175000017500000000000012214017551022117 5ustar arnauarnauzope2.13-2.13.21/source/zope.contentprovider/pip-egg-info/zope.contentprovider.egg-info/0000755000175000017500000000000012214017551030012 5ustar arnauarnauzope2.13-2.13.21/source/zope.contentprovider/pip-egg-info/zope.contentprovider.egg-info/PKG-INFO0000644000175000017500000007022312214017551031113 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.contentprovider Version: 3.7.2 Summary: Content Provider Framework for Zope Templates Home-page: http://pypi.python.org/pypi/zope.contentprovider Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ================= Content Providers ================= This package provides a framework to develop componentized Web GUI applications. Instead of describing the content of a page using a single template or static system of templates and METAL macros, content provider objects are dynamically looked up based on the setup/configuration of the application. .. contents:: Motivation and Design Goals --------------------------- Before diving into the features of this package let me take up a few bytes of text to explain the use cases that drove us to develop this package (also others) and how the API documented below fulfills/solves those use cases. When we started developing Zope 3, it was from a desire to decentralize functionality and thus the complexity of the Python code. And we were successful! The component architecture is a marvelous piece of software that hopefully will allow us to build scalable solutions for a very long time. However, when it comes to user interface design, in this case specifically HTML pages, we have failed to provide the features and patterns of assembeling a page from configured components. Looking up views for a particular content component and a request just simply does not work by itself. The content inside the page is still monolithic. One attempt to solve this problem are METAL macros, which allow you to insert other TAL snippets into your main template. But macros have two shortcomings. For one there is a "hard-coded" one-to-one mapping between a slot and the macro that fills that slot, which makes it impossible to register several macros for a given location. The second problem is that macros are not views in their own right; thus they cannot provide functionality that is independent of the main template's view. A second approach to modular UI design are rendering pipes. Rendering pipes have the great advantage that they can reach all regions of the page during every step of the rendering process. For example, if we have a widget in the middle of the page that requires some additional Javascript, then it is easy for a rendering unit to insert the Javascript file link in the HTML header of the page. This type of use case is very hard to solve using page templates. However, pipes are not the answer to componentized user interface, since they cannot simply deal with registering random content for a given page region. In fact, I propose that pipelines are orthogonal to content providers, the concept introducted below. A pipeline framework could easily use functionality provided by this and other packages to provide component-driven UI design. So our goal is clear: Bring the pluggability of the component architecture into page templates and user interface design. Zope is commonly known to reinvent the wheel, develop its own terminology and misuse other's terms. For example, the Plone community has a very different understanding of what a "portlet" is compared to the commonly accepted meaning in the corporate world, which derives its definition from JSR 168. Therefore an additional use case of the design of this package was to stick with common terms and use them in their original meaning -- well, given a little extra twist. The most basic user interface component in the Web application Java world is the "content provider" [1]_. A content provider is simply responsible for providing HTML content for a page. This is equivalent to a view that does not provide a full page, but just a snippet, much like widgets or macros. Once there is a way to configure those content providers, we need a way to insert them into our page templates. In our implementation this is accomplished using a new TALES namespace that allows to insert content providers by name. But how, you might wonder, does this provide a componentized user interface? On the Zope 3 level, each content provider is registered as a presentation component discriminated by the context, request and view it will appear in. Thus different content providers will be picked for different configurations. Okay, that's pretty much everything there is to say about content providers. What, we are done? Hold on, what about defining regions of pages and filling them configured UI snippets. The short answer is: See the ``zope.viewlet`` pacakge. But let me also give you the long answer. This and the other pacakges were developed using real world use cases. While doing this, we noticed that not every project would need, for example, all the features of a portlet, but would still profit from lower-level features. Thus we decided to declare clear boundaries of functionality and providing each level in a different package. This particualr package is only meant to provide the interface between the content provider world and page templates. .. [1] Note that this is a bit different from the role named content provider, which refers to a service that provides content; the content provider we are talking about here are the software components the service would provide to an application. Content Providers ----------------- Content Provider is a term from the Java world that refers to components that can provide HTML content. It means nothing more! How the content is found and returned is totally up to the implementation. The Zope 3 touch to the concept is that content providers are multi-adapters that are looked up by the context, request (and thus the layer/skin), and view they are displayed in. The second important concept of content providers are their two-phase rendering design. In the first phase the state of the content provider is prepared and, if applicable, any data the provider is responsible for is updated. >>> from zope.contentprovider import interfaces So let's create a simple content provider: >>> import zope.interface >>> import zope.component >>> from zope.publisher.interfaces import browser >>> class MessageBox(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... message = u'My Message' ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
%s
' %self.message The ``update()`` method is executed during phase one. Since no state needs to be calculated and no data is modified by this simple content provider, it is an empty implementation. The ``render()`` method implements phase 2 of the process. We can now instantiate the content provider (manually) and render it: >>> box = MessageBox(None, None, None) >>> box.render() u'
My Message
' Since our content provider did not require the context, request or view to create its HTML content, we were able to pass trivial dummy values into the constructor. Also note that the provider must have a parent (using the ``__parent__`` attribute) specified at all times. The parent must be the view the provider appears in. I agree, this functionally does not seem very useful now. The constructor and the ``update()`` method seem useless and the returned content is totally static. However, we implemented a contract for content providers that other code can rely on. Content providers are (commonly) instantiated using the context, request and view they appear in and are required to always generate its HTML using those three components. Two-Phased Content Providers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Let's now have a look at a content provider that actively uses the two-phase rendering process. The simpler scenario is the case where the content provider updates a content component without affecting anything else. So let's create a content component to be updated, >>> class Article(object): ... title = u'initial' >>> article = Article() and the content provider that is updating the title: >>> class ChangeTitle(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... fieldName = 'ChangeTitle.title' ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... self.context, self.request = context, request ... ... def update(self): ... if self.fieldName in self.request: ... self.context.title = self.request[self.fieldName] ... ... def render(self): ... return u'' % (self.fieldName, ... self.context.title) Using a request, let's now instantiate the content provider and go through the two-phase rendering process: >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> changer = ChangeTitle(article, request, None) >>> changer.update() >>> changer.render() u'' Let's now enter a new title and render the provider: >>> request = TestRequest(form={'ChangeTitle.title': u'new title'}) >>> changer = ChangeTitle(article, request, None) >>> changer.update() >>> changer.render() u'' >>> article.title u'new title' So this was easy. Let's now look at a case where one content provider's update influences the content of another. Let's say we have a content provider that displays the article's title: >>> class ViewTitle(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... ... def __init__(self, context, request, view): ... self.context, self.__parent__ = context, view ... ... def update(self): ... pass ... ... def render(self): ... return u'

Title: %s

' % self.context.title Let's now say that the `ShowTitle` content provider is shown on a page *before* the `ChangeTitle` content provider. If we do the full rendering process for each provider in sequence, we get the wrong result: >>> request = TestRequest(form={'ChangeTitle.title': u'newer title'}) >>> viewer = ViewTitle(article, request, None) >>> viewer.update() >>> viewer.render() u'

Title: new title

' >>> changer = ChangeTitle(article, request, None) >>> changer.update() >>> changer.render() u'' So the correct way of doing this is to first complete phase 1 (update) for all providers, before executing phase 2 (render): >>> request = TestRequest(form={'ChangeTitle.title': u'newest title'}) >>> viewer = ViewTitle(article, request, None) >>> changer = ChangeTitle(article, request, None) >>> viewer.update() >>> changer.update() >>> viewer.render() u'

Title: newest title

' >>> changer.render() u'' ``UpdateNotCalled`` Errors ~~~~~~~~~~~~~~~~~~~~~~~~~~ Since calling ``update()`` before any other method that mutates the provider or any other data is so important to the correct functioning of the API, the developer has the choice to raise the ``UpdateNotCalled`` error, if any method is called before ``update()`` (with exception of the constructor): >>> class InfoBox(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... self.__updated = False ... ... def update(self): ... self.__updated = True ... ... def render(self): ... if not self.__updated: ... raise interfaces.UpdateNotCalled ... return u'
Some information
' >>> info = InfoBox(None, None, None) >>> info.render() Traceback (most recent call last): ... UpdateNotCalled: ``update()`` was not called yet. >>> info.update() >>> info.render() u'
Some information
' The TALES ``provider`` Expression --------------------------------- The ``provider`` expression will look up the name of the content provider, call it and return the HTML content. The first step, however, will be to register our content provider with the component architecture: >>> zope.component.provideAdapter(MessageBox, name='mypage.MessageBox') The content provider must be registered by name, since the TALES expression uses the name to look up the provider at run time. Let's now create a view using a page template: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> templateFileName = os.path.join(temp_dir, 'template.pt') >>> open(templateFileName, 'w').write(''' ... ... ...

My Web Page

...
... ...
...
... Content here ...
... ... ... ''') As you can see, we exprect the ``provider`` expression to simply look up the content provider and insert the HTML content at this place. Next we register the template as a view (browser page) for all objects: >>> from zope.browserpage.simpleviewclass import SimpleViewClass >>> FrontPage = SimpleViewClass(templateFileName, name='main.html') >>> zope.component.provideAdapter( ... FrontPage, ... (zope.interface.Interface, browser.IDefaultBrowserLayer), ... zope.interface.Interface, ... name='main.html') Let's create a content object that can be viewed: >>> class Content(object): ... zope.interface.implements(zope.interface.Interface) >>> content = Content() Finally we look up the view and render it. Note that a BeforeUpdateEvent is fired - this event should always be fired before any contentprovider is updated. >>> from zope.publisher.browser import TestRequest >>> events = [] >>> zope.component.provideHandler(events.append, (None, )) >>> request = TestRequest() >>> view = zope.component.getMultiAdapter((content, request), ... name='main.html') >>> print view().strip()

My Web Page

My Message
Content here
>>> events [] The event holds the provider and the request. >>> events[0].request >>> events[0].object Failure to lookup a Content Provider ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If the name is not found, an error is raised. To demonstrate this behavior let's create another template: >>> errorFileName = os.path.join(temp_dir, 'error.pt') >>> open(errorFileName, 'w').write(''' ... ... ... ... ... ... ''') >>> ErrorPage = SimpleViewClass(errorFileName, name='error.html') >>> zope.component.provideAdapter( ... ErrorPage, ... (zope.interface.Interface, browser.IDefaultBrowserLayer), ... zope.interface.Interface, ... name='main.html') >>> errorview = zope.component.getMultiAdapter((content, request), ... name='main.html') >>> print errorview() Traceback (most recent call last): ... ContentProviderLookupError: mypage.UnknownName Additional Data from TAL ~~~~~~~~~~~~~~~~~~~~~~~~ The ``provider`` expression allows also for transferring data from the TAL context into the content provider. This is accomplished by having the content provider implement an interface that specifies the attributes and provides ``ITALNamespaceData``: >>> import zope.schema >>> class IMessageText(zope.interface.Interface): ... message = zope.schema.Text(title=u'Text of the message box') >>> zope.interface.directlyProvides(IMessageText, ... interfaces.ITALNamespaceData) Now the message box can receive its text from the TAL environment: >>> class DynamicMessageBox(MessageBox): ... zope.interface.implements(IMessageText) >>> zope.component.provideAdapter( ... DynamicMessageBox, provides=interfaces.IContentProvider, ... name='mypage.DynamicMessageBox') We are now updating our original template to provide the message text: >>> open(templateFileName, 'w').write(''' ... ... ...

My Web Page

...
... ... ...
...
... Content here ...
... ... ... ''') Now we should get two message boxes with different text: >>> print view().strip()

My Web Page

Hello World!
Hello World again!
Content here
Finally, a content provider can also implement several ``ITALNamespaceData``: >>> class IMessageType(zope.interface.Interface): ... type = zope.schema.TextLine(title=u'The type of the message box') >>> zope.interface.directlyProvides(IMessageType, ... interfaces.ITALNamespaceData) We'll change our message box content provider implementation a bit, so the new information is used: >>> class BetterDynamicMessageBox(DynamicMessageBox): ... zope.interface.implements(IMessageType) ... type = None ... ... def render(self): ... return u'
%s
' %(self.type, self.message) >>> zope.component.provideAdapter( ... BetterDynamicMessageBox, provides=interfaces.IContentProvider, ... name='mypage.MessageBox') Of course, we also have to make our tempalte a little bit more dynamic as well: >>> open(templateFileName, 'w').write(''' ... ... ...

My Web Page

...
... ... ...
...
... Content here ...
... ... ... ''') Now we should get two message boxes with different text and types: >>> print view().strip()

My Web Page

Hello World!
Hello World again!
Content here
Base class ---------- The ``zope.contentprovider.provider`` module provides an useful base class for implementing content providers. It has all boilerplate code and it's only required to override the ``render`` method to make it work: >>> from zope.contentprovider.provider import ContentProviderBase >>> class MyProvider(ContentProviderBase): ... def render(self, *args, **kwargs): ... return 'Hi there' >>> provider = MyProvider(None, None, None) >>> interfaces.IContentProvider.providedBy(provider) True >>> provider.update() >>> print provider.render() Hi there Note, that it can't be used as is, without providing the ``render`` method: >>> bad = ContentProviderBase(None, None, None) >>> bad.update() >>> print bad.render() Traceback (most recent call last): ... NotImplementedError: ``render`` method must be implemented by subclass You can add the update logic into the ``update`` method as with any content provider and you can implement more complex rendering patterns, based on templates, using this ContentProviderBase class as a base. You might also want to look at the ``zope.viewlet`` package for a more featureful API. Let's remove all temporary data we created during this README. >>> import shutil >>> shutil.rmtree(temp_dir) ======= CHANGES ======= 3.7.2 (2010-05-25) ------------------ - Fixed unit tests broken under Python 2.4 by the switch to the standard library ``doctest`` module. 3.7.1 (2010-04-30) ------------------ - Prefer the standard library's ``doctest`` module to the one from ``zope.testing.`` 3.7 (2010-04-27) ---------------- - Since ``tales:expressiontype`` is now in ``zope.browserpage``, update conditional ZCML accordingly so it doesn't depend on the presence of ``zope.app.pagetemplate`` anymore. 3.6.1 (2009-12-23) ------------------ - Ensure that our ``configure.zcml`` can be loaded without requiring further dependencies. It uses a ``tales:expressiontype`` directive defined in ``zope.app.pagetemplate.`` We keep that dependency optional, as not all consumers of this package use ZCML to configure the expression type. 3.6.0 (2009-12-22) ------------------ - Updated test dependency to use ``zope.browserpage``. 3.5.0 (2009-03-18) ------------------ - Add very simple, but useful base class for implementing content providers, see ``zope.contentprovider.provider.ContentProviderBase``. - Remove unneeded testing dependencies. We only need ``zope.testing`` and ``zope.app.pagetemplate``. - Remove zcml slug and old zpkg-related files. - Added setuptools dependency to setup.py. - Clean up package's description and documentation a bit. Remove duplicate text in README. - Change mailing list address to zope-dev at zope.org instead of retired one. - Change ``cheeseshop`` to ``pypi`` in the package url. 3.4.0 (2007-10-02) ------------------ - Initial release independent of the main Zope tree. Keywords: zope3 content provider Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.contentprovider/pip-egg-info/zope.contentprovider.egg-info/dependency_links.txtzope2.13-2.13.21/source/zope.contentprovider/pip-egg-info/zope.contentprovider.egg-info/dependency_l0000644000175000017500000000000112214017551032355 0ustar arnauarnau zope2.13-2.13.21/source/zope.contentprovider/pip-egg-info/zope.contentprovider.egg-info/requires.txt0000644000175000017500000000022312214017551032407 0ustar arnauarnausetuptools zope.component zope.event zope.interface zope.location zope.publisher zope.schema zope.tales [test] zope.browserpage>=3.12 zope.testing././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.contentprovider/pip-egg-info/zope.contentprovider.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.contentprovider/pip-egg-info/zope.contentprovider.egg-info/namespace_pa0000644000175000017500000000000512214017551032344 0ustar arnauarnauzope ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.contentprovider/pip-egg-info/zope.contentprovider.egg-info/top_level.txtzope2.13-2.13.21/source/zope.contentprovider/pip-egg-info/zope.contentprovider.egg-info/top_level.tx0000644000175000017500000000000512214017551032353 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.contentprovider/pip-egg-info/zope.contentprovider.egg-info/SOURCES.txt0000644000175000017500000000116412214017551031700 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.contentprovider.egg-info/PKG-INFO pip-egg-info/zope.contentprovider.egg-info/SOURCES.txt pip-egg-info/zope.contentprovider.egg-info/dependency_links.txt pip-egg-info/zope.contentprovider.egg-info/namespace_packages.txt pip-egg-info/zope.contentprovider.egg-info/not-zip-safe pip-egg-info/zope.contentprovider.egg-info/requires.txt pip-egg-info/zope.contentprovider.egg-info/top_level.txt src/zope/__init__.py src/zope/contentprovider/__init__.py src/zope/contentprovider/interfaces.py src/zope/contentprovider/provider.py src/zope/contentprovider/tales.py src/zope/contentprovider/tests.pyzope2.13-2.13.21/source/zope.contentprovider/pip-egg-info/zope.contentprovider.egg-info/not-zip-safe0000644000175000017500000000000112214017551032240 0ustar arnauarnau zope2.13-2.13.21/source/zope.contentprovider/LICENSE.txt0000644000175000017500000000402612214017551021463 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.contentprovider/README.txt0000644000175000017500000000005112214017551021330 0ustar arnauarnauSee src/zope/contentprovider/README.txt. zope2.13-2.13.21/source/zope.contentprovider/setup.cfg0000644000175000017500000000007312214017551021457 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.contentprovider/COPYRIGHT.txt0000644000175000017500000000004012214017551021741 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.contentprovider/buildout.cfg0000644000175000017500000000015512214017551022147 0ustar arnauarnau[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = zope.contentprovider [test] zope2.13-2.13.21/source/zope.contentprovider/bootstrap.py0000644000175000017500000000742212214017551022232 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 111717 2010-04-30 20:46:02Z hannosch $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.contentprovider/CHANGES.txt0000644000175000017500000000315412214017551021452 0ustar arnauarnau======= CHANGES ======= 3.7.2 (2010-05-25) ------------------ - Fixed unit tests broken under Python 2.4 by the switch to the standard library ``doctest`` module. 3.7.1 (2010-04-30) ------------------ - Prefer the standard library's ``doctest`` module to the one from ``zope.testing.`` 3.7 (2010-04-27) ---------------- - Since ``tales:expressiontype`` is now in ``zope.browserpage``, update conditional ZCML accordingly so it doesn't depend on the presence of ``zope.app.pagetemplate`` anymore. 3.6.1 (2009-12-23) ------------------ - Ensure that our ``configure.zcml`` can be loaded without requiring further dependencies. It uses a ``tales:expressiontype`` directive defined in ``zope.app.pagetemplate.`` We keep that dependency optional, as not all consumers of this package use ZCML to configure the expression type. 3.6.0 (2009-12-22) ------------------ - Updated test dependency to use ``zope.browserpage``. 3.5.0 (2009-03-18) ------------------ - Add very simple, but useful base class for implementing content providers, see ``zope.contentprovider.provider.ContentProviderBase``. - Remove unneeded testing dependencies. We only need ``zope.testing`` and ``zope.app.pagetemplate``. - Remove zcml slug and old zpkg-related files. - Added setuptools dependency to setup.py. - Clean up package's description and documentation a bit. Remove duplicate text in README. - Change mailing list address to zope-dev at zope.org instead of retired one. - Change ``cheeseshop`` to ``pypi`` in the package url. 3.4.0 (2007-10-02) ------------------ - Initial release independent of the main Zope tree. zope2.13-2.13.21/source/zope.contentprovider/src/0000755000175000017500000000000012214017551020425 5ustar arnauarnauzope2.13-2.13.21/source/zope.contentprovider/src/zope.contentprovider.egg-info/0000755000175000017500000000000012214017551026320 5ustar arnauarnauzope2.13-2.13.21/source/zope.contentprovider/src/zope.contentprovider.egg-info/PKG-INFO0000644000175000017500000007022312214017551027421 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.contentprovider Version: 3.7.2 Summary: Content Provider Framework for Zope Templates Home-page: http://pypi.python.org/pypi/zope.contentprovider Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ================= Content Providers ================= This package provides a framework to develop componentized Web GUI applications. Instead of describing the content of a page using a single template or static system of templates and METAL macros, content provider objects are dynamically looked up based on the setup/configuration of the application. .. contents:: Motivation and Design Goals --------------------------- Before diving into the features of this package let me take up a few bytes of text to explain the use cases that drove us to develop this package (also others) and how the API documented below fulfills/solves those use cases. When we started developing Zope 3, it was from a desire to decentralize functionality and thus the complexity of the Python code. And we were successful! The component architecture is a marvelous piece of software that hopefully will allow us to build scalable solutions for a very long time. However, when it comes to user interface design, in this case specifically HTML pages, we have failed to provide the features and patterns of assembeling a page from configured components. Looking up views for a particular content component and a request just simply does not work by itself. The content inside the page is still monolithic. One attempt to solve this problem are METAL macros, which allow you to insert other TAL snippets into your main template. But macros have two shortcomings. For one there is a "hard-coded" one-to-one mapping between a slot and the macro that fills that slot, which makes it impossible to register several macros for a given location. The second problem is that macros are not views in their own right; thus they cannot provide functionality that is independent of the main template's view. A second approach to modular UI design are rendering pipes. Rendering pipes have the great advantage that they can reach all regions of the page during every step of the rendering process. For example, if we have a widget in the middle of the page that requires some additional Javascript, then it is easy for a rendering unit to insert the Javascript file link in the HTML header of the page. This type of use case is very hard to solve using page templates. However, pipes are not the answer to componentized user interface, since they cannot simply deal with registering random content for a given page region. In fact, I propose that pipelines are orthogonal to content providers, the concept introducted below. A pipeline framework could easily use functionality provided by this and other packages to provide component-driven UI design. So our goal is clear: Bring the pluggability of the component architecture into page templates and user interface design. Zope is commonly known to reinvent the wheel, develop its own terminology and misuse other's terms. For example, the Plone community has a very different understanding of what a "portlet" is compared to the commonly accepted meaning in the corporate world, which derives its definition from JSR 168. Therefore an additional use case of the design of this package was to stick with common terms and use them in their original meaning -- well, given a little extra twist. The most basic user interface component in the Web application Java world is the "content provider" [1]_. A content provider is simply responsible for providing HTML content for a page. This is equivalent to a view that does not provide a full page, but just a snippet, much like widgets or macros. Once there is a way to configure those content providers, we need a way to insert them into our page templates. In our implementation this is accomplished using a new TALES namespace that allows to insert content providers by name. But how, you might wonder, does this provide a componentized user interface? On the Zope 3 level, each content provider is registered as a presentation component discriminated by the context, request and view it will appear in. Thus different content providers will be picked for different configurations. Okay, that's pretty much everything there is to say about content providers. What, we are done? Hold on, what about defining regions of pages and filling them configured UI snippets. The short answer is: See the ``zope.viewlet`` pacakge. But let me also give you the long answer. This and the other pacakges were developed using real world use cases. While doing this, we noticed that not every project would need, for example, all the features of a portlet, but would still profit from lower-level features. Thus we decided to declare clear boundaries of functionality and providing each level in a different package. This particualr package is only meant to provide the interface between the content provider world and page templates. .. [1] Note that this is a bit different from the role named content provider, which refers to a service that provides content; the content provider we are talking about here are the software components the service would provide to an application. Content Providers ----------------- Content Provider is a term from the Java world that refers to components that can provide HTML content. It means nothing more! How the content is found and returned is totally up to the implementation. The Zope 3 touch to the concept is that content providers are multi-adapters that are looked up by the context, request (and thus the layer/skin), and view they are displayed in. The second important concept of content providers are their two-phase rendering design. In the first phase the state of the content provider is prepared and, if applicable, any data the provider is responsible for is updated. >>> from zope.contentprovider import interfaces So let's create a simple content provider: >>> import zope.interface >>> import zope.component >>> from zope.publisher.interfaces import browser >>> class MessageBox(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... message = u'My Message' ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
%s
' %self.message The ``update()`` method is executed during phase one. Since no state needs to be calculated and no data is modified by this simple content provider, it is an empty implementation. The ``render()`` method implements phase 2 of the process. We can now instantiate the content provider (manually) and render it: >>> box = MessageBox(None, None, None) >>> box.render() u'
My Message
' Since our content provider did not require the context, request or view to create its HTML content, we were able to pass trivial dummy values into the constructor. Also note that the provider must have a parent (using the ``__parent__`` attribute) specified at all times. The parent must be the view the provider appears in. I agree, this functionally does not seem very useful now. The constructor and the ``update()`` method seem useless and the returned content is totally static. However, we implemented a contract for content providers that other code can rely on. Content providers are (commonly) instantiated using the context, request and view they appear in and are required to always generate its HTML using those three components. Two-Phased Content Providers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Let's now have a look at a content provider that actively uses the two-phase rendering process. The simpler scenario is the case where the content provider updates a content component without affecting anything else. So let's create a content component to be updated, >>> class Article(object): ... title = u'initial' >>> article = Article() and the content provider that is updating the title: >>> class ChangeTitle(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... fieldName = 'ChangeTitle.title' ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... self.context, self.request = context, request ... ... def update(self): ... if self.fieldName in self.request: ... self.context.title = self.request[self.fieldName] ... ... def render(self): ... return u'' % (self.fieldName, ... self.context.title) Using a request, let's now instantiate the content provider and go through the two-phase rendering process: >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> changer = ChangeTitle(article, request, None) >>> changer.update() >>> changer.render() u'' Let's now enter a new title and render the provider: >>> request = TestRequest(form={'ChangeTitle.title': u'new title'}) >>> changer = ChangeTitle(article, request, None) >>> changer.update() >>> changer.render() u'' >>> article.title u'new title' So this was easy. Let's now look at a case where one content provider's update influences the content of another. Let's say we have a content provider that displays the article's title: >>> class ViewTitle(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... ... def __init__(self, context, request, view): ... self.context, self.__parent__ = context, view ... ... def update(self): ... pass ... ... def render(self): ... return u'

Title: %s

' % self.context.title Let's now say that the `ShowTitle` content provider is shown on a page *before* the `ChangeTitle` content provider. If we do the full rendering process for each provider in sequence, we get the wrong result: >>> request = TestRequest(form={'ChangeTitle.title': u'newer title'}) >>> viewer = ViewTitle(article, request, None) >>> viewer.update() >>> viewer.render() u'

Title: new title

' >>> changer = ChangeTitle(article, request, None) >>> changer.update() >>> changer.render() u'' So the correct way of doing this is to first complete phase 1 (update) for all providers, before executing phase 2 (render): >>> request = TestRequest(form={'ChangeTitle.title': u'newest title'}) >>> viewer = ViewTitle(article, request, None) >>> changer = ChangeTitle(article, request, None) >>> viewer.update() >>> changer.update() >>> viewer.render() u'

Title: newest title

' >>> changer.render() u'' ``UpdateNotCalled`` Errors ~~~~~~~~~~~~~~~~~~~~~~~~~~ Since calling ``update()`` before any other method that mutates the provider or any other data is so important to the correct functioning of the API, the developer has the choice to raise the ``UpdateNotCalled`` error, if any method is called before ``update()`` (with exception of the constructor): >>> class InfoBox(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... self.__updated = False ... ... def update(self): ... self.__updated = True ... ... def render(self): ... if not self.__updated: ... raise interfaces.UpdateNotCalled ... return u'
Some information
' >>> info = InfoBox(None, None, None) >>> info.render() Traceback (most recent call last): ... UpdateNotCalled: ``update()`` was not called yet. >>> info.update() >>> info.render() u'
Some information
' The TALES ``provider`` Expression --------------------------------- The ``provider`` expression will look up the name of the content provider, call it and return the HTML content. The first step, however, will be to register our content provider with the component architecture: >>> zope.component.provideAdapter(MessageBox, name='mypage.MessageBox') The content provider must be registered by name, since the TALES expression uses the name to look up the provider at run time. Let's now create a view using a page template: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> templateFileName = os.path.join(temp_dir, 'template.pt') >>> open(templateFileName, 'w').write(''' ... ... ...

My Web Page

...
... ...
...
... Content here ...
... ... ... ''') As you can see, we exprect the ``provider`` expression to simply look up the content provider and insert the HTML content at this place. Next we register the template as a view (browser page) for all objects: >>> from zope.browserpage.simpleviewclass import SimpleViewClass >>> FrontPage = SimpleViewClass(templateFileName, name='main.html') >>> zope.component.provideAdapter( ... FrontPage, ... (zope.interface.Interface, browser.IDefaultBrowserLayer), ... zope.interface.Interface, ... name='main.html') Let's create a content object that can be viewed: >>> class Content(object): ... zope.interface.implements(zope.interface.Interface) >>> content = Content() Finally we look up the view and render it. Note that a BeforeUpdateEvent is fired - this event should always be fired before any contentprovider is updated. >>> from zope.publisher.browser import TestRequest >>> events = [] >>> zope.component.provideHandler(events.append, (None, )) >>> request = TestRequest() >>> view = zope.component.getMultiAdapter((content, request), ... name='main.html') >>> print view().strip()

My Web Page

My Message
Content here
>>> events [] The event holds the provider and the request. >>> events[0].request >>> events[0].object Failure to lookup a Content Provider ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If the name is not found, an error is raised. To demonstrate this behavior let's create another template: >>> errorFileName = os.path.join(temp_dir, 'error.pt') >>> open(errorFileName, 'w').write(''' ... ... ... ... ... ... ''') >>> ErrorPage = SimpleViewClass(errorFileName, name='error.html') >>> zope.component.provideAdapter( ... ErrorPage, ... (zope.interface.Interface, browser.IDefaultBrowserLayer), ... zope.interface.Interface, ... name='main.html') >>> errorview = zope.component.getMultiAdapter((content, request), ... name='main.html') >>> print errorview() Traceback (most recent call last): ... ContentProviderLookupError: mypage.UnknownName Additional Data from TAL ~~~~~~~~~~~~~~~~~~~~~~~~ The ``provider`` expression allows also for transferring data from the TAL context into the content provider. This is accomplished by having the content provider implement an interface that specifies the attributes and provides ``ITALNamespaceData``: >>> import zope.schema >>> class IMessageText(zope.interface.Interface): ... message = zope.schema.Text(title=u'Text of the message box') >>> zope.interface.directlyProvides(IMessageText, ... interfaces.ITALNamespaceData) Now the message box can receive its text from the TAL environment: >>> class DynamicMessageBox(MessageBox): ... zope.interface.implements(IMessageText) >>> zope.component.provideAdapter( ... DynamicMessageBox, provides=interfaces.IContentProvider, ... name='mypage.DynamicMessageBox') We are now updating our original template to provide the message text: >>> open(templateFileName, 'w').write(''' ... ... ...

My Web Page

...
... ... ...
...
... Content here ...
... ... ... ''') Now we should get two message boxes with different text: >>> print view().strip()

My Web Page

Hello World!
Hello World again!
Content here
Finally, a content provider can also implement several ``ITALNamespaceData``: >>> class IMessageType(zope.interface.Interface): ... type = zope.schema.TextLine(title=u'The type of the message box') >>> zope.interface.directlyProvides(IMessageType, ... interfaces.ITALNamespaceData) We'll change our message box content provider implementation a bit, so the new information is used: >>> class BetterDynamicMessageBox(DynamicMessageBox): ... zope.interface.implements(IMessageType) ... type = None ... ... def render(self): ... return u'
%s
' %(self.type, self.message) >>> zope.component.provideAdapter( ... BetterDynamicMessageBox, provides=interfaces.IContentProvider, ... name='mypage.MessageBox') Of course, we also have to make our tempalte a little bit more dynamic as well: >>> open(templateFileName, 'w').write(''' ... ... ...

My Web Page

...
... ... ...
...
... Content here ...
... ... ... ''') Now we should get two message boxes with different text and types: >>> print view().strip()

My Web Page

Hello World!
Hello World again!
Content here
Base class ---------- The ``zope.contentprovider.provider`` module provides an useful base class for implementing content providers. It has all boilerplate code and it's only required to override the ``render`` method to make it work: >>> from zope.contentprovider.provider import ContentProviderBase >>> class MyProvider(ContentProviderBase): ... def render(self, *args, **kwargs): ... return 'Hi there' >>> provider = MyProvider(None, None, None) >>> interfaces.IContentProvider.providedBy(provider) True >>> provider.update() >>> print provider.render() Hi there Note, that it can't be used as is, without providing the ``render`` method: >>> bad = ContentProviderBase(None, None, None) >>> bad.update() >>> print bad.render() Traceback (most recent call last): ... NotImplementedError: ``render`` method must be implemented by subclass You can add the update logic into the ``update`` method as with any content provider and you can implement more complex rendering patterns, based on templates, using this ContentProviderBase class as a base. You might also want to look at the ``zope.viewlet`` package for a more featureful API. Let's remove all temporary data we created during this README. >>> import shutil >>> shutil.rmtree(temp_dir) ======= CHANGES ======= 3.7.2 (2010-05-25) ------------------ - Fixed unit tests broken under Python 2.4 by the switch to the standard library ``doctest`` module. 3.7.1 (2010-04-30) ------------------ - Prefer the standard library's ``doctest`` module to the one from ``zope.testing.`` 3.7 (2010-04-27) ---------------- - Since ``tales:expressiontype`` is now in ``zope.browserpage``, update conditional ZCML accordingly so it doesn't depend on the presence of ``zope.app.pagetemplate`` anymore. 3.6.1 (2009-12-23) ------------------ - Ensure that our ``configure.zcml`` can be loaded without requiring further dependencies. It uses a ``tales:expressiontype`` directive defined in ``zope.app.pagetemplate.`` We keep that dependency optional, as not all consumers of this package use ZCML to configure the expression type. 3.6.0 (2009-12-22) ------------------ - Updated test dependency to use ``zope.browserpage``. 3.5.0 (2009-03-18) ------------------ - Add very simple, but useful base class for implementing content providers, see ``zope.contentprovider.provider.ContentProviderBase``. - Remove unneeded testing dependencies. We only need ``zope.testing`` and ``zope.app.pagetemplate``. - Remove zcml slug and old zpkg-related files. - Added setuptools dependency to setup.py. - Clean up package's description and documentation a bit. Remove duplicate text in README. - Change mailing list address to zope-dev at zope.org instead of retired one. - Change ``cheeseshop`` to ``pypi`` in the package url. 3.4.0 (2007-10-02) ------------------ - Initial release independent of the main Zope tree. Keywords: zope3 content provider Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.contentprovider/src/zope.contentprovider.egg-info/dependency_links.txt0000644000175000017500000000000112214017551032366 0ustar arnauarnau zope2.13-2.13.21/source/zope.contentprovider/src/zope.contentprovider.egg-info/requires.txt0000644000175000017500000000022312214017551030715 0ustar arnauarnausetuptools zope.component zope.event zope.interface zope.location zope.publisher zope.schema zope.tales [test] zope.browserpage>=3.12 zope.testing././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.contentprovider/src/zope.contentprovider.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.contentprovider/src/zope.contentprovider.egg-info/namespace_packages.tx0000644000175000017500000000000512214017551032462 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.contentprovider/src/zope.contentprovider.egg-info/top_level.txt0000644000175000017500000000000512214017551031045 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.contentprovider/src/zope.contentprovider.egg-info/SOURCES.txt0000644000175000017500000000130012214017551030176 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.contentprovider.egg-info/PKG-INFO src/zope.contentprovider.egg-info/SOURCES.txt src/zope.contentprovider.egg-info/dependency_links.txt src/zope.contentprovider.egg-info/namespace_packages.txt src/zope.contentprovider.egg-info/not-zip-safe src/zope.contentprovider.egg-info/requires.txt src/zope.contentprovider.egg-info/top_level.txt src/zope/contentprovider/README.txt src/zope/contentprovider/__init__.py src/zope/contentprovider/configure.zcml src/zope/contentprovider/interfaces.py src/zope/contentprovider/provider.py src/zope/contentprovider/tales.py src/zope/contentprovider/tests.pyzope2.13-2.13.21/source/zope.contentprovider/src/zope.contentprovider.egg-info/not-zip-safe0000644000175000017500000000000112214017551030546 0ustar arnauarnau zope2.13-2.13.21/source/zope.contentprovider/src/zope/0000755000175000017500000000000012214017551021402 5ustar arnauarnauzope2.13-2.13.21/source/zope.contentprovider/src/zope/__init__.py0000644000175000017500000000007012214017551023510 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.contentprovider/src/zope/contentprovider/0000755000175000017500000000000012214017551024627 5ustar arnauarnauzope2.13-2.13.21/source/zope.contentprovider/src/zope/contentprovider/configure.zcml0000644000175000017500000000121412214017551027475 0ustar arnauarnau zope2.13-2.13.21/source/zope.contentprovider/src/zope/contentprovider/provider.py0000644000175000017500000000273012214017551027035 0ustar arnauarnau############################################################################## # # Copyright (c) 2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Simple base class for implementing content providers $Id: provider.py 98173 2009-03-16 22:47:55Z nadako $ """ from zope.component import adapts from zope.interface import Interface, implements from zope.publisher.browser import BrowserView from zope.publisher.interfaces.browser import IBrowserRequest from zope.contentprovider.interfaces import IContentProvider class ContentProviderBase(BrowserView): """Base class for content providers""" implements(IContentProvider) adapts(Interface, IBrowserRequest, Interface) def __init__(self, context, request, view): super(ContentProviderBase, self).__init__(context, request) self.__parent__ = view def update(self): pass def render(self, *args, **kwargs): raise NotImplementedError( '``render`` method must be implemented by subclass') zope2.13-2.13.21/source/zope.contentprovider/src/zope/contentprovider/interfaces.py0000644000175000017500000001273212214017551027331 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Content provider interfaces $Id: interfaces.py 112004 2010-05-05 17:54:28Z tseaver $ """ __docformat__ = 'restructuredtext' import zope.component import zope.interface from zope.component.interfaces import ObjectEvent, IObjectEvent from zope.publisher.interfaces import browser from zope.tales import interfaces class IUpdateNotCalled(zope.interface.common.interfaces.IRuntimeError): """Update Not Called An error that is raised when any content provider method is called before the ``update()`` method. """ class UpdateNotCalled(RuntimeError): pass # Make it a singelton UpdateNotCalled = UpdateNotCalled('``update()`` was not called yet.') class IBeforeUpdateEvent(IObjectEvent): """A Contentprovider will be updated""" request = zope.interface.Attribute( """The request in which the object is udpated, might also be None""") class BeforeUpdateEvent(ObjectEvent): """A Contentprovider will be updated""" zope.interface.implements(IBeforeUpdateEvent) def __init__(self, provider, request=None): super(BeforeUpdateEvent, self).__init__(provider) self.request = request class IContentProvider(browser.IBrowserView): """A piece of content to be shown on a page. Objects implementing this interface are providing HTML content when they are rendered. It is up to the implementation to decide how to lookup necessary data to complete the job. Content Providers use a two-stage process to fulfill their contract: (1) The first stage is responsible to calculate the state of the content provider and, if applicable, edit the data. This stage is executed using the ``update()`` method. (2) During the second stage the provider constructs/renders its HTML output based on the state that was calculated in the first stage. This stage is executed using the ``render()`` method. Content Providers are discriminated by three components: the context, the request and the view. This allows great control over the selection of the provider. """ __parent__ = zope.interface.Attribute( """The view the provider appears in. The view is the third discriminator of the content provider. It allows that the content can be controlled for different views. Having it stored as the parent is also very important for the security context to be kept. """) def update(): """Initialize the content provider. This method should perform all state calculation and *not* refer it to the rendering stage. In this method, all state must be calculated from the current interaction (e.g., the browser request); all contained or managed content providers must have ``update()`` be called as well; any additional stateful API for contained or managed content providers must be handled; and persistent objects should be modified, if the provider is going to do it. Do *not* store state about persistent objects: the rendering process should actually use the persistent objects for the data, in case other components modify the object between the update and render stages. This method *must* be called before any other method that mutates the instance (besides the class constructor). Non-mutating methods and attributes may raise an error if used before ``update()`` is called. The view may rely on this order but is *not required* to explicitly enforce this. Implementations *may* enforce it as a developer aid. """ def render(*args, **kw): """Return the content provided by this content provider. Calling this method before ``update()`` *may* (but is not required to) raise an ``UpdateNotCalled`` error. """ class IContentProviderType(zope.interface.interfaces.IInterface): """Type interface for content provider types (interfaces derived from IContentProvider). """ class ITALNamespaceData(zope.interface.interfaces.IInterface): """A type interface that marks an interface as a TAL data specification. All fields specified in an interface that provides `ITALNamespaceData` will be looked up in the TAL context and stored on the content provider. A content provider can have multiple interfaces that are of this type. """ class ContentProviderLookupError(zope.component.ComponentLookupError): """No content provider was found.""" class ITALESProviderExpression(interfaces.ITALESExpression): """Return the HTML content of the named provider. To call a content provider in a view use the the following syntax in a page template:: The content provider is looked up by the (context, request, view) objects and the name (`provider.name`). """ zope2.13-2.13.21/source/zope.contentprovider/src/zope/contentprovider/tales.py0000644000175000017500000000572512214017551026322 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Provider TALES expression $Id: tales.py 112004 2010-05-05 17:54:28Z tseaver $ """ __docformat__ = 'restructuredtext' import zope.component import zope.interface import zope.schema import zope.event from zope.location.interfaces import ILocation from zope.tales import expressions from zope.contentprovider import interfaces def addTALNamespaceData(provider, context): """Add the requested TAL attributes to the provider""" data = {} for interface in zope.interface.providedBy(provider): if interfaces.ITALNamespaceData.providedBy(interface): for name, field in zope.schema.getFields(interface).items(): data[name] = context.vars.get(name, field.default) provider.__dict__.update(data) class TALESProviderExpression(expressions.StringExpr): """Collect content provider via a TAL namespace. Note that this implementation of the TALES `provider` namespace does not work with interdependent content providers, since each content-provider's stage one call is made just before the second stage is executed. If you want to implement interdependent content providers, you need to consider a TAL-independent view implementation that will complete all content providers' stage one before rendering any of them. """ zope.interface.implements(interfaces.ITALESProviderExpression) def __call__(self, econtext): name = super(TALESProviderExpression, self).__call__(econtext) context = econtext.vars['context'] request = econtext.vars['request'] view = econtext.vars['view'] # Try to look up the provider. provider = zope.component.queryMultiAdapter( (context, request, view), interfaces.IContentProvider, name) # Provide a useful error message, if the provider was not found. if provider is None: raise interfaces.ContentProviderLookupError(name) # add the __name__ attribute if it implements ILocation if ILocation.providedBy(provider): provider.__name__ = name # Insert the data gotten from the context addTALNamespaceData(provider, econtext) # Stage 1: Do the state update. zope.event.notify(interfaces.BeforeUpdateEvent(provider, request)) provider.update() # Stage 2: Render the HTML content. return provider.render() zope2.13-2.13.21/source/zope.contentprovider/src/zope/contentprovider/__init__.py0000644000175000017500000000127312214017551026743 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ $Id: __init__.py 112004 2010-05-05 17:54:28Z tseaver $ """ zope2.13-2.13.21/source/zope.contentprovider/src/zope/contentprovider/README.txt0000644000175000017500000005147212214017551026336 0ustar arnauarnau================= Content Providers ================= This package provides a framework to develop componentized Web GUI applications. Instead of describing the content of a page using a single template or static system of templates and METAL macros, content provider objects are dynamically looked up based on the setup/configuration of the application. .. contents:: Motivation and Design Goals --------------------------- Before diving into the features of this package let me take up a few bytes of text to explain the use cases that drove us to develop this package (also others) and how the API documented below fulfills/solves those use cases. When we started developing Zope 3, it was from a desire to decentralize functionality and thus the complexity of the Python code. And we were successful! The component architecture is a marvelous piece of software that hopefully will allow us to build scalable solutions for a very long time. However, when it comes to user interface design, in this case specifically HTML pages, we have failed to provide the features and patterns of assembeling a page from configured components. Looking up views for a particular content component and a request just simply does not work by itself. The content inside the page is still monolithic. One attempt to solve this problem are METAL macros, which allow you to insert other TAL snippets into your main template. But macros have two shortcomings. For one there is a "hard-coded" one-to-one mapping between a slot and the macro that fills that slot, which makes it impossible to register several macros for a given location. The second problem is that macros are not views in their own right; thus they cannot provide functionality that is independent of the main template's view. A second approach to modular UI design are rendering pipes. Rendering pipes have the great advantage that they can reach all regions of the page during every step of the rendering process. For example, if we have a widget in the middle of the page that requires some additional Javascript, then it is easy for a rendering unit to insert the Javascript file link in the HTML header of the page. This type of use case is very hard to solve using page templates. However, pipes are not the answer to componentized user interface, since they cannot simply deal with registering random content for a given page region. In fact, I propose that pipelines are orthogonal to content providers, the concept introducted below. A pipeline framework could easily use functionality provided by this and other packages to provide component-driven UI design. So our goal is clear: Bring the pluggability of the component architecture into page templates and user interface design. Zope is commonly known to reinvent the wheel, develop its own terminology and misuse other's terms. For example, the Plone community has a very different understanding of what a "portlet" is compared to the commonly accepted meaning in the corporate world, which derives its definition from JSR 168. Therefore an additional use case of the design of this package was to stick with common terms and use them in their original meaning -- well, given a little extra twist. The most basic user interface component in the Web application Java world is the "content provider" [1]_. A content provider is simply responsible for providing HTML content for a page. This is equivalent to a view that does not provide a full page, but just a snippet, much like widgets or macros. Once there is a way to configure those content providers, we need a way to insert them into our page templates. In our implementation this is accomplished using a new TALES namespace that allows to insert content providers by name. But how, you might wonder, does this provide a componentized user interface? On the Zope 3 level, each content provider is registered as a presentation component discriminated by the context, request and view it will appear in. Thus different content providers will be picked for different configurations. Okay, that's pretty much everything there is to say about content providers. What, we are done? Hold on, what about defining regions of pages and filling them configured UI snippets. The short answer is: See the ``zope.viewlet`` pacakge. But let me also give you the long answer. This and the other pacakges were developed using real world use cases. While doing this, we noticed that not every project would need, for example, all the features of a portlet, but would still profit from lower-level features. Thus we decided to declare clear boundaries of functionality and providing each level in a different package. This particualr package is only meant to provide the interface between the content provider world and page templates. .. [1] Note that this is a bit different from the role named content provider, which refers to a service that provides content; the content provider we are talking about here are the software components the service would provide to an application. Content Providers ----------------- Content Provider is a term from the Java world that refers to components that can provide HTML content. It means nothing more! How the content is found and returned is totally up to the implementation. The Zope 3 touch to the concept is that content providers are multi-adapters that are looked up by the context, request (and thus the layer/skin), and view they are displayed in. The second important concept of content providers are their two-phase rendering design. In the first phase the state of the content provider is prepared and, if applicable, any data the provider is responsible for is updated. >>> from zope.contentprovider import interfaces So let's create a simple content provider: >>> import zope.interface >>> import zope.component >>> from zope.publisher.interfaces import browser >>> class MessageBox(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... message = u'My Message' ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
%s
' %self.message The ``update()`` method is executed during phase one. Since no state needs to be calculated and no data is modified by this simple content provider, it is an empty implementation. The ``render()`` method implements phase 2 of the process. We can now instantiate the content provider (manually) and render it: >>> box = MessageBox(None, None, None) >>> box.render() u'
My Message
' Since our content provider did not require the context, request or view to create its HTML content, we were able to pass trivial dummy values into the constructor. Also note that the provider must have a parent (using the ``__parent__`` attribute) specified at all times. The parent must be the view the provider appears in. I agree, this functionally does not seem very useful now. The constructor and the ``update()`` method seem useless and the returned content is totally static. However, we implemented a contract for content providers that other code can rely on. Content providers are (commonly) instantiated using the context, request and view they appear in and are required to always generate its HTML using those three components. Two-Phased Content Providers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Let's now have a look at a content provider that actively uses the two-phase rendering process. The simpler scenario is the case where the content provider updates a content component without affecting anything else. So let's create a content component to be updated, >>> class Article(object): ... title = u'initial' >>> article = Article() and the content provider that is updating the title: >>> class ChangeTitle(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... fieldName = 'ChangeTitle.title' ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... self.context, self.request = context, request ... ... def update(self): ... if self.fieldName in self.request: ... self.context.title = self.request[self.fieldName] ... ... def render(self): ... return u'' % (self.fieldName, ... self.context.title) Using a request, let's now instantiate the content provider and go through the two-phase rendering process: >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> changer = ChangeTitle(article, request, None) >>> changer.update() >>> changer.render() u'' Let's now enter a new title and render the provider: >>> request = TestRequest(form={'ChangeTitle.title': u'new title'}) >>> changer = ChangeTitle(article, request, None) >>> changer.update() >>> changer.render() u'' >>> article.title u'new title' So this was easy. Let's now look at a case where one content provider's update influences the content of another. Let's say we have a content provider that displays the article's title: >>> class ViewTitle(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... ... def __init__(self, context, request, view): ... self.context, self.__parent__ = context, view ... ... def update(self): ... pass ... ... def render(self): ... return u'

Title: %s

' % self.context.title Let's now say that the `ShowTitle` content provider is shown on a page *before* the `ChangeTitle` content provider. If we do the full rendering process for each provider in sequence, we get the wrong result: >>> request = TestRequest(form={'ChangeTitle.title': u'newer title'}) >>> viewer = ViewTitle(article, request, None) >>> viewer.update() >>> viewer.render() u'

Title: new title

' >>> changer = ChangeTitle(article, request, None) >>> changer.update() >>> changer.render() u'' So the correct way of doing this is to first complete phase 1 (update) for all providers, before executing phase 2 (render): >>> request = TestRequest(form={'ChangeTitle.title': u'newest title'}) >>> viewer = ViewTitle(article, request, None) >>> changer = ChangeTitle(article, request, None) >>> viewer.update() >>> changer.update() >>> viewer.render() u'

Title: newest title

' >>> changer.render() u'' ``UpdateNotCalled`` Errors ~~~~~~~~~~~~~~~~~~~~~~~~~~ Since calling ``update()`` before any other method that mutates the provider or any other data is so important to the correct functioning of the API, the developer has the choice to raise the ``UpdateNotCalled`` error, if any method is called before ``update()`` (with exception of the constructor): >>> class InfoBox(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... self.__updated = False ... ... def update(self): ... self.__updated = True ... ... def render(self): ... if not self.__updated: ... raise interfaces.UpdateNotCalled ... return u'
Some information
' >>> info = InfoBox(None, None, None) >>> info.render() Traceback (most recent call last): ... UpdateNotCalled: ``update()`` was not called yet. >>> info.update() >>> info.render() u'
Some information
' The TALES ``provider`` Expression --------------------------------- The ``provider`` expression will look up the name of the content provider, call it and return the HTML content. The first step, however, will be to register our content provider with the component architecture: >>> zope.component.provideAdapter(MessageBox, name='mypage.MessageBox') The content provider must be registered by name, since the TALES expression uses the name to look up the provider at run time. Let's now create a view using a page template: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> templateFileName = os.path.join(temp_dir, 'template.pt') >>> open(templateFileName, 'w').write(''' ... ... ...

My Web Page

...
... ...
...
... Content here ...
... ... ... ''') As you can see, we exprect the ``provider`` expression to simply look up the content provider and insert the HTML content at this place. Next we register the template as a view (browser page) for all objects: >>> from zope.browserpage.simpleviewclass import SimpleViewClass >>> FrontPage = SimpleViewClass(templateFileName, name='main.html') >>> zope.component.provideAdapter( ... FrontPage, ... (zope.interface.Interface, browser.IDefaultBrowserLayer), ... zope.interface.Interface, ... name='main.html') Let's create a content object that can be viewed: >>> class Content(object): ... zope.interface.implements(zope.interface.Interface) >>> content = Content() Finally we look up the view and render it. Note that a BeforeUpdateEvent is fired - this event should always be fired before any contentprovider is updated. >>> from zope.publisher.browser import TestRequest >>> events = [] >>> zope.component.provideHandler(events.append, (None, )) >>> request = TestRequest() >>> view = zope.component.getMultiAdapter((content, request), ... name='main.html') >>> print view().strip()

My Web Page

My Message
Content here
>>> events [] The event holds the provider and the request. >>> events[0].request >>> events[0].object Failure to lookup a Content Provider ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If the name is not found, an error is raised. To demonstrate this behavior let's create another template: >>> errorFileName = os.path.join(temp_dir, 'error.pt') >>> open(errorFileName, 'w').write(''' ... ... ... ... ... ... ''') >>> ErrorPage = SimpleViewClass(errorFileName, name='error.html') >>> zope.component.provideAdapter( ... ErrorPage, ... (zope.interface.Interface, browser.IDefaultBrowserLayer), ... zope.interface.Interface, ... name='main.html') >>> errorview = zope.component.getMultiAdapter((content, request), ... name='main.html') >>> print errorview() Traceback (most recent call last): ... ContentProviderLookupError: mypage.UnknownName Additional Data from TAL ~~~~~~~~~~~~~~~~~~~~~~~~ The ``provider`` expression allows also for transferring data from the TAL context into the content provider. This is accomplished by having the content provider implement an interface that specifies the attributes and provides ``ITALNamespaceData``: >>> import zope.schema >>> class IMessageText(zope.interface.Interface): ... message = zope.schema.Text(title=u'Text of the message box') >>> zope.interface.directlyProvides(IMessageText, ... interfaces.ITALNamespaceData) Now the message box can receive its text from the TAL environment: >>> class DynamicMessageBox(MessageBox): ... zope.interface.implements(IMessageText) >>> zope.component.provideAdapter( ... DynamicMessageBox, provides=interfaces.IContentProvider, ... name='mypage.DynamicMessageBox') We are now updating our original template to provide the message text: >>> open(templateFileName, 'w').write(''' ... ... ...

My Web Page

...
... ... ...
...
... Content here ...
... ... ... ''') Now we should get two message boxes with different text: >>> print view().strip()

My Web Page

Hello World!
Hello World again!
Content here
Finally, a content provider can also implement several ``ITALNamespaceData``: >>> class IMessageType(zope.interface.Interface): ... type = zope.schema.TextLine(title=u'The type of the message box') >>> zope.interface.directlyProvides(IMessageType, ... interfaces.ITALNamespaceData) We'll change our message box content provider implementation a bit, so the new information is used: >>> class BetterDynamicMessageBox(DynamicMessageBox): ... zope.interface.implements(IMessageType) ... type = None ... ... def render(self): ... return u'
%s
' %(self.type, self.message) >>> zope.component.provideAdapter( ... BetterDynamicMessageBox, provides=interfaces.IContentProvider, ... name='mypage.MessageBox') Of course, we also have to make our tempalte a little bit more dynamic as well: >>> open(templateFileName, 'w').write(''' ... ... ...

My Web Page

...
... ... ...
...
... Content here ...
... ... ... ''') Now we should get two message boxes with different text and types: >>> print view().strip()

My Web Page

Hello World!
Hello World again!
Content here
Base class ---------- The ``zope.contentprovider.provider`` module provides an useful base class for implementing content providers. It has all boilerplate code and it's only required to override the ``render`` method to make it work: >>> from zope.contentprovider.provider import ContentProviderBase >>> class MyProvider(ContentProviderBase): ... def render(self, *args, **kwargs): ... return 'Hi there' >>> provider = MyProvider(None, None, None) >>> interfaces.IContentProvider.providedBy(provider) True >>> provider.update() >>> print provider.render() Hi there Note, that it can't be used as is, without providing the ``render`` method: >>> bad = ContentProviderBase(None, None, None) >>> bad.update() >>> print bad.render() Traceback (most recent call last): ... NotImplementedError: ``render`` method must be implemented by subclass You can add the update logic into the ``update`` method as with any content provider and you can implement more complex rendering patterns, based on templates, using this ContentProviderBase class as a base. You might also want to look at the ``zope.viewlet`` package for a more featureful API. Let's remove all temporary data we created during this README. >>> import shutil >>> shutil.rmtree(temp_dir) zope2.13-2.13.21/source/zope.contentprovider/src/zope/contentprovider/tests.py0000644000175000017500000000344012214017551026344 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Content provider tests $Id: tests.py 112004 2010-05-05 17:54:28Z tseaver $ """ __docformat__ = 'restructuredtext' import doctest import os.path import unittest from zope.component import eventtesting from zope.testing import cleanup counter = 0 mtime_func = None def setUp(test): cleanup.setUp() eventtesting.setUp() from zope.browserpage.metaconfigure import registerType from zope.contentprovider import tales registerType('provider', tales.TALESProviderExpression) # Make sure we are always reloading page template files ;-) global mtime_func mtime_func = os.path.getmtime def number(x): global counter counter += 1 return counter os.path.getmtime = number def tearDown(test): cleanup.tearDown() os.path.getmtime = mtime_func global counter counter = 0 def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('README.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS, globs = {'__file__': os.path.join( os.path.dirname(__file__), 'README.txt')} ), )) zope2.13-2.13.21/source/zope.broken/0000755000175000017500000000000012214017703015670 5ustar arnauarnauzope2.13-2.13.21/source/zope.broken/setup.py0000644000175000017500000000372712214017703017413 0ustar arnauarnau############################################################################## # # Copyright (c) 2009 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Setup for zope.broken package $Id: setup.py 96059 2009-02-04 08:09:40Z wosc $ """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.broken', version = '3.6.0', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', description='Zope Broken Object Interfaces', long_description=( read('README.txt') + '\n\n' + read('CHANGES.txt') ), keywords = "zope3 broken", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.broken', license='ZPL 2.1', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope'], install_requires=['setuptools', 'zope.interface', ], include_package_data = True, zip_safe = False, ) zope2.13-2.13.21/source/zope.broken/PKG-INFO0000644000175000017500000000315712214017703016773 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.broken Version: 3.6.0 Summary: Zope Broken Object Interfaces Home-page: http://pypi.python.org/pypi/zope.broken Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== This package is obsolete and its functionality has been merged into the ZODB3 distribution itself. If you use version 3.10 or later of ZODB3, please change your imports of the IBroken interface to a direct:: from ZODB.interfaces import IBroken You can use this package with older versions of the ZODB3, which didn't have the IBroken interface yet. Changelog ========= 3.6.0 (2010-01-09) ------------------ - The IBroken interface has been merged into the ZODB3 distribution. Import the interface from there, while providing a copy for backwards compatibility with older versions of the ZODB3. 3.5.0 (2009-02-04) ------------------ - Created ``zope.broken`` to hold depended upon bits of ``zope.app.broken``. Keywords: zope3 broken Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.broken/pip-egg-info/0000755000175000017500000000000012214017703020151 5ustar arnauarnauzope2.13-2.13.21/source/zope.broken/pip-egg-info/zope.broken.egg-info/0000755000175000017500000000000012214017703024077 5ustar arnauarnauzope2.13-2.13.21/source/zope.broken/pip-egg-info/zope.broken.egg-info/PKG-INFO0000644000175000017500000000316512214017703025201 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.broken Version: 3.6.0 Summary: Zope Broken Object Interfaces Home-page: http://pypi.python.org/pypi/zope.broken Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== This package is obsolete and its functionality has been merged into the ZODB3 distribution itself. If you use version 3.10 or later of ZODB3, please change your imports of the IBroken interface to a direct:: from ZODB.interfaces import IBroken You can use this package with older versions of the ZODB3, which didn't have the IBroken interface yet. Changelog ========= 3.6.0 (2010-01-09) ------------------ - The IBroken interface has been merged into the ZODB3 distribution. Import the interface from there, while providing a copy for backwards compatibility with older versions of the ZODB3. 3.5.0 (2009-02-04) ------------------ - Created ``zope.broken`` to hold depended upon bits of ``zope.app.broken``. Keywords: zope3 broken Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.broken/pip-egg-info/zope.broken.egg-info/dependency_links.txt0000644000175000017500000000000112214017703030145 0ustar arnauarnau zope2.13-2.13.21/source/zope.broken/pip-egg-info/zope.broken.egg-info/requires.txt0000644000175000017500000000003112214017703026471 0ustar arnauarnausetuptools zope.interfacezope2.13-2.13.21/source/zope.broken/pip-egg-info/zope.broken.egg-info/namespace_packages.txt0000644000175000017500000000000512214017703030425 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.broken/pip-egg-info/zope.broken.egg-info/top_level.txt0000644000175000017500000000000512214017703026624 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.broken/pip-egg-info/zope.broken.egg-info/SOURCES.txt0000644000175000017500000000067212214017703025770 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.broken.egg-info/PKG-INFO pip-egg-info/zope.broken.egg-info/SOURCES.txt pip-egg-info/zope.broken.egg-info/dependency_links.txt pip-egg-info/zope.broken.egg-info/namespace_packages.txt pip-egg-info/zope.broken.egg-info/not-zip-safe pip-egg-info/zope.broken.egg-info/requires.txt pip-egg-info/zope.broken.egg-info/top_level.txt src/zope/__init__.py src/zope/broken/__init__.py src/zope/broken/interfaces.pyzope2.13-2.13.21/source/zope.broken/pip-egg-info/zope.broken.egg-info/not-zip-safe0000644000175000017500000000000112214017703026325 0ustar arnauarnau zope2.13-2.13.21/source/zope.broken/README.txt0000644000175000017500000000056312214017703017372 0ustar arnauarnauOverview ======== This package is obsolete and its functionality has been merged into the ZODB3 distribution itself. If you use version 3.10 or later of ZODB3, please change your imports of the IBroken interface to a direct:: from ZODB.interfaces import IBroken You can use this package with older versions of the ZODB3, which didn't have the IBroken interface yet. zope2.13-2.13.21/source/zope.broken/setup.cfg0000644000175000017500000000007312214017703017511 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.broken/buildout.cfg0000644000175000017500000000003012214017703020171 0ustar arnauarnau[buildout] develop = . zope2.13-2.13.21/source/zope.broken/bootstrap.py0000644000175000017500000000337012214017703020262 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 73528 2007-03-25 07:03:34Z dobe $ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.broken/CHANGES.txt0000644000175000017500000000056012214017703017502 0ustar arnauarnauChangelog ========= 3.6.0 (2010-01-09) ------------------ - The IBroken interface has been merged into the ZODB3 distribution. Import the interface from there, while providing a copy for backwards compatibility with older versions of the ZODB3. 3.5.0 (2009-02-04) ------------------ - Created ``zope.broken`` to hold depended upon bits of ``zope.app.broken``. zope2.13-2.13.21/source/zope.broken/src/0000755000175000017500000000000012214017703016457 5ustar arnauarnauzope2.13-2.13.21/source/zope.broken/src/zope/0000755000175000017500000000000012214017703017434 5ustar arnauarnauzope2.13-2.13.21/source/zope.broken/src/zope/broken/0000755000175000017500000000000012214017703020714 5ustar arnauarnauzope2.13-2.13.21/source/zope.broken/src/zope/broken/interfaces.py0000644000175000017500000000167112214017703023416 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """zope.broken interfaces. $Id: interfaces.py 72735 2007-02-21 05:02:33Z baijum $ """ __docformat__ = "reStructuredText" import zope.interface try: from ZODB.interfaces import IBroken except ImportError: class IBroken(zope.interface.Interface): """Marker interface for broken objects """ zope2.13-2.13.21/source/zope.broken/src/zope/broken/__init__.py0000644000175000017500000000004112214017703023020 0ustar arnauarnau# Make this directory a package. zope2.13-2.13.21/source/zope.broken/src/zope/__init__.py0000644000175000017500000000031112214017703021540 0ustar arnauarnau# this is a namespace package try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: import pkgutil __path__ = pkgutil.extend_path(__path__, __name__) zope2.13-2.13.21/source/zope.broken/src/zope.broken.egg-info/0000755000175000017500000000000012214017703022405 5ustar arnauarnauzope2.13-2.13.21/source/zope.broken/src/zope.broken.egg-info/PKG-INFO0000644000175000017500000000315712214017703023510 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.broken Version: 3.6.0 Summary: Zope Broken Object Interfaces Home-page: http://pypi.python.org/pypi/zope.broken Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== This package is obsolete and its functionality has been merged into the ZODB3 distribution itself. If you use version 3.10 or later of ZODB3, please change your imports of the IBroken interface to a direct:: from ZODB.interfaces import IBroken You can use this package with older versions of the ZODB3, which didn't have the IBroken interface yet. Changelog ========= 3.6.0 (2010-01-09) ------------------ - The IBroken interface has been merged into the ZODB3 distribution. Import the interface from there, while providing a copy for backwards compatibility with older versions of the ZODB3. 3.5.0 (2009-02-04) ------------------ - Created ``zope.broken`` to hold depended upon bits of ``zope.app.broken``. Keywords: zope3 broken Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.broken/src/zope.broken.egg-info/dependency_links.txt0000644000175000017500000000000112214017703026453 0ustar arnauarnau zope2.13-2.13.21/source/zope.broken/src/zope.broken.egg-info/requires.txt0000644000175000017500000000003112214017703024777 0ustar arnauarnausetuptools zope.interfacezope2.13-2.13.21/source/zope.broken/src/zope.broken.egg-info/namespace_packages.txt0000644000175000017500000000000512214017703026733 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.broken/src/zope.broken.egg-info/top_level.txt0000644000175000017500000000000512214017703025132 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.broken/src/zope.broken.egg-info/SOURCES.txt0000644000175000017500000000064012214017703024271 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.broken.egg-info/PKG-INFO src/zope.broken.egg-info/SOURCES.txt src/zope.broken.egg-info/dependency_links.txt src/zope.broken.egg-info/namespace_packages.txt src/zope.broken.egg-info/not-zip-safe src/zope.broken.egg-info/requires.txt src/zope.broken.egg-info/top_level.txt src/zope/broken/__init__.py src/zope/broken/interfaces.pyzope2.13-2.13.21/source/zope.broken/src/zope.broken.egg-info/not-zip-safe0000644000175000017500000000000112214017703024633 0ustar arnauarnau zope2.13-2.13.21/source/Products.MailHost/0000755000175000017500000000000012214017670016761 5ustar arnauarnauzope2.13-2.13.21/source/Products.MailHost/setup.py0000644000175000017500000000247012214017670020476 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from setuptools import setup, find_packages setup(name='Products.MailHost', version = '2.13.1', url='http://pypi.python.org/pypi/Products.MailHost', license='ZPL 2.1', description="zope.sendmail integration for Zope 2.", author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=open('README.txt').read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), namespace_packages=['Products'], package_dir={'': 'src'}, install_requires=[ 'setuptools', 'Zope2 >= 2.13.0a1', ], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/Products.MailHost/PKG-INFO0000644000175000017500000000352512214017670020063 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.MailHost Version: 2.13.1 Summary: zope.sendmail integration for Zope 2. Home-page: http://pypi.python.org/pypi/Products.MailHost Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== The MailHost product provides support for sending email from within the Zope environment using MailHost objects. An optional character set can be specified to automatically encode unicode input, and perform appropriate RFC 2822 header and body encoding for the specified character set. Full python email.Message.Message objects may be sent. Email can optionally be encoded using Base64, Quoted-Printable or UUEncode encoding (though automatic body encoding will be applied if a character set is specified). MailHost provides integration with the Zope transaction system and optional support for asynchronous mail delivery. Asynchronous mail delivery is implemented using a queue and a dedicated thread processing the queue. The thread is (re)-started automatically when sending an email. The thread can be started manually (in case of restart) by calling its manage_restartQueueThread?action=start method through HTTP. There is currently no possibility to start the thread at Zope startup time. Supports TLS/SSL encryption (requires Python compiled with SSL support). Changelog ========= 2.13.1 (2010-09-25) ------------------- - LP #642728: Fixed TypeError on nested multi part messages in MailHost. 2.13.0 (2010-07-13) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Products.MailHost/pip-egg-info/0000755000175000017500000000000012214017670021242 5ustar arnauarnauzope2.13-2.13.21/source/Products.MailHost/pip-egg-info/Products.MailHost.egg-info/0000755000175000017500000000000012214017670026256 5ustar arnauarnauzope2.13-2.13.21/source/Products.MailHost/pip-egg-info/Products.MailHost.egg-info/PKG-INFO0000644000175000017500000000352512214017670027360 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.MailHost Version: 2.13.1 Summary: zope.sendmail integration for Zope 2. Home-page: http://pypi.python.org/pypi/Products.MailHost Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== The MailHost product provides support for sending email from within the Zope environment using MailHost objects. An optional character set can be specified to automatically encode unicode input, and perform appropriate RFC 2822 header and body encoding for the specified character set. Full python email.Message.Message objects may be sent. Email can optionally be encoded using Base64, Quoted-Printable or UUEncode encoding (though automatic body encoding will be applied if a character set is specified). MailHost provides integration with the Zope transaction system and optional support for asynchronous mail delivery. Asynchronous mail delivery is implemented using a queue and a dedicated thread processing the queue. The thread is (re)-started automatically when sending an email. The thread can be started manually (in case of restart) by calling its manage_restartQueueThread?action=start method through HTTP. There is currently no possibility to start the thread at Zope startup time. Supports TLS/SSL encryption (requires Python compiled with SSL support). Changelog ========= 2.13.1 (2010-09-25) ------------------- - LP #642728: Fixed TypeError on nested multi part messages in MailHost. 2.13.0 (2010-07-13) ------------------- - Released as separate package. Platform: UNKNOWN ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/Products.MailHost/pip-egg-info/Products.MailHost.egg-info/dependency_links.txtzope2.13-2.13.21/source/Products.MailHost/pip-egg-info/Products.MailHost.egg-info/dependency_links.t0000644000175000017500000000000112214017670031750 0ustar arnauarnau zope2.13-2.13.21/source/Products.MailHost/pip-egg-info/Products.MailHost.egg-info/requires.txt0000644000175000017500000000003412214017670030653 0ustar arnauarnausetuptools Zope2 >= 2.13.0a1././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.MailHost/pip-egg-info/Products.MailHost.egg-info/namespace_packages.txtzope2.13-2.13.21/source/Products.MailHost/pip-egg-info/Products.MailHost.egg-info/namespace_packages0000644000175000017500000000001112214017670031763 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.MailHost/pip-egg-info/Products.MailHost.egg-info/top_level.txt0000644000175000017500000000001112214017670031000 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.MailHost/pip-egg-info/Products.MailHost.egg-info/SOURCES.txt0000644000175000017500000000132212214017670030140 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/Products.MailHost.egg-info/PKG-INFO pip-egg-info/Products.MailHost.egg-info/SOURCES.txt pip-egg-info/Products.MailHost.egg-info/dependency_links.txt pip-egg-info/Products.MailHost.egg-info/namespace_packages.txt pip-egg-info/Products.MailHost.egg-info/not-zip-safe pip-egg-info/Products.MailHost.egg-info/requires.txt pip-egg-info/Products.MailHost.egg-info/top_level.txt src/Products/__init__.py src/Products/MailHost/MailHost.py src/Products/MailHost/SendMailTag.py src/Products/MailHost/__init__.py src/Products/MailHost/decorator.py src/Products/MailHost/interfaces.py src/Products/MailHost/mailer.py src/Products/MailHost/tests/__init__.py src/Products/MailHost/tests/testMailHost.pyzope2.13-2.13.21/source/Products.MailHost/pip-egg-info/Products.MailHost.egg-info/not-zip-safe0000644000175000017500000000000112214017670030504 0ustar arnauarnau zope2.13-2.13.21/source/Products.MailHost/LICENSE.txt0000644000175000017500000000402612214017670020606 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/Products.MailHost/README.txt0000644000175000017500000000210312214017670020453 0ustar arnauarnauOverview ======== The MailHost product provides support for sending email from within the Zope environment using MailHost objects. An optional character set can be specified to automatically encode unicode input, and perform appropriate RFC 2822 header and body encoding for the specified character set. Full python email.Message.Message objects may be sent. Email can optionally be encoded using Base64, Quoted-Printable or UUEncode encoding (though automatic body encoding will be applied if a character set is specified). MailHost provides integration with the Zope transaction system and optional support for asynchronous mail delivery. Asynchronous mail delivery is implemented using a queue and a dedicated thread processing the queue. The thread is (re)-started automatically when sending an email. The thread can be started manually (in case of restart) by calling its manage_restartQueueThread?action=start method through HTTP. There is currently no possibility to start the thread at Zope startup time. Supports TLS/SSL encryption (requires Python compiled with SSL support). zope2.13-2.13.21/source/Products.MailHost/setup.cfg0000644000175000017500000000007312214017670020602 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/Products.MailHost/COPYRIGHT.txt0000644000175000017500000000004012214017670021064 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/Products.MailHost/buildout.cfg0000644000175000017500000000030312214017670021265 0ustar arnauarnau[buildout] develop = . parts = interpreter test [interpreter] recipe = zc.recipe.egg interpreter = python eggs = Products.MailHost [test] recipe = zc.recipe.testrunner eggs = Products.MailHost zope2.13-2.13.21/source/Products.MailHost/bootstrap.py0000644000175000017500000000742012214017670021353 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 108946 2010-02-12 02:40:18Z yusei $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/Products.MailHost/CHANGES.txt0000644000175000017500000000032112214017670020566 0ustar arnauarnauChangelog ========= 2.13.1 (2010-09-25) ------------------- - LP #642728: Fixed TypeError on nested multi part messages in MailHost. 2.13.0 (2010-07-13) ------------------- - Released as separate package. zope2.13-2.13.21/source/Products.MailHost/src/0000755000175000017500000000000012214017670017550 5ustar arnauarnauzope2.13-2.13.21/source/Products.MailHost/src/Products.MailHost.egg-info/0000755000175000017500000000000012214017670024564 5ustar arnauarnauzope2.13-2.13.21/source/Products.MailHost/src/Products.MailHost.egg-info/PKG-INFO0000644000175000017500000000352512214017670025666 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.MailHost Version: 2.13.1 Summary: zope.sendmail integration for Zope 2. Home-page: http://pypi.python.org/pypi/Products.MailHost Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== The MailHost product provides support for sending email from within the Zope environment using MailHost objects. An optional character set can be specified to automatically encode unicode input, and perform appropriate RFC 2822 header and body encoding for the specified character set. Full python email.Message.Message objects may be sent. Email can optionally be encoded using Base64, Quoted-Printable or UUEncode encoding (though automatic body encoding will be applied if a character set is specified). MailHost provides integration with the Zope transaction system and optional support for asynchronous mail delivery. Asynchronous mail delivery is implemented using a queue and a dedicated thread processing the queue. The thread is (re)-started automatically when sending an email. The thread can be started manually (in case of restart) by calling its manage_restartQueueThread?action=start method through HTTP. There is currently no possibility to start the thread at Zope startup time. Supports TLS/SSL encryption (requires Python compiled with SSL support). Changelog ========= 2.13.1 (2010-09-25) ------------------- - LP #642728: Fixed TypeError on nested multi part messages in MailHost. 2.13.0 (2010-07-13) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Products.MailHost/src/Products.MailHost.egg-info/dependency_links.txt0000644000175000017500000000000112214017670030632 0ustar arnauarnau zope2.13-2.13.21/source/Products.MailHost/src/Products.MailHost.egg-info/requires.txt0000644000175000017500000000003412214017670027161 0ustar arnauarnausetuptools Zope2 >= 2.13.0a1zope2.13-2.13.21/source/Products.MailHost/src/Products.MailHost.egg-info/namespace_packages.txt0000644000175000017500000000001112214017670031107 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.MailHost/src/Products.MailHost.egg-info/top_level.txt0000644000175000017500000000001112214017670027306 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.MailHost/src/Products.MailHost.egg-info/SOURCES.txt0000644000175000017500000000201112214017670026442 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/Products/__init__.py src/Products.MailHost.egg-info/PKG-INFO src/Products.MailHost.egg-info/SOURCES.txt src/Products.MailHost.egg-info/dependency_links.txt src/Products.MailHost.egg-info/namespace_packages.txt src/Products.MailHost.egg-info/not-zip-safe src/Products.MailHost.egg-info/requires.txt src/Products.MailHost.egg-info/top_level.txt src/Products/MailHost/MailHost.py src/Products/MailHost/SendMailTag.py src/Products/MailHost/__init__.py src/Products/MailHost/decorator.py src/Products/MailHost/interfaces.py src/Products/MailHost/mailer.py src/Products/MailHost/dtml/addMailHost_form.dtml src/Products/MailHost/dtml/manageMailHost.dtml src/Products/MailHost/help/Mail-Host.stx src/Products/MailHost/help/Mail-Host_Add.stx src/Products/MailHost/help/Mail-Host_Edit.stx src/Products/MailHost/help/MailHost.py src/Products/MailHost/tests/__init__.py src/Products/MailHost/tests/testMailHost.py src/Products/MailHost/www/MailHost_icon.gifzope2.13-2.13.21/source/Products.MailHost/src/Products.MailHost.egg-info/not-zip-safe0000644000175000017500000000000112214017670027012 0ustar arnauarnau zope2.13-2.13.21/source/Products.MailHost/src/Products/0000755000175000017500000000000012214017670021353 5ustar arnauarnauzope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/0000755000175000017500000000000012214017670023073 5ustar arnauarnauzope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/decorator.py0000644000175000017500000000165312214017670025434 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ Decorator(s) """ def synchronized(lock): """ Decorator for method synchronization. """ def wrapper(f): def method(*args, **kw): lock.acquire() try: return f(*args, **kw) finally: lock.release() return method return wrapper zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/www/0000755000175000017500000000000012214017670023717 5ustar arnauarnauzope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/www/MailHost_icon.gif0000644000175000017500000000160012214017670027133 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,@]H° @*\˜@"JœHbB0jÐð¡Ájdøà@‡lÉñJbÊœ)Ó#È›8 "$Éð%O…øü©p(Q—w} ©Ó§O;zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/mailer.py0000644000175000017500000000023512214017670024716 0ustar arnauarnauimport zope.deferredimport zope.deferredimport.deprecatedFrom( "Import from zope.sendmail instead", 'zope.sendmail.mailer', 'SMTPMailer', ) zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/interfaces.py0000644000175000017500000000153612214017670025575 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """MailHost interfaces. """ from zope.interface import Interface class IMailHost(Interface): def send(messageText, mto=None, mfrom=None, subject=None, encode=None, charset=None, msg_type=None): """Send mail. """ zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/__init__.py0000644000175000017500000000174212214017670025210 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import MailHost import SendMailTag def initialize(context): context.registerClass( MailHost.MailHost, permission='Add MailHost objects', constructors=(MailHost.manage_addMailHostForm, MailHost.manage_addMailHost), icon='www/MailHost_icon.gif', ) context.registerHelp() context.registerHelpTitle('Zope Help') zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/SendMailTag.py0000644000175000017500000001017312214017670025577 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from DocumentTemplate.DT_Util import parse_params from DocumentTemplate.DT_Util import render_blocks from DocumentTemplate.DT_String import String from Products.MailHost.MailHost import MailBase, MailHostError class SendMailTag: '''the send mail tag, used like thus: to: person@their.machine.com from: me@mymachine.net subject: just called to say... boy howdy! Text between the sendmail and /sendmail tags is processed by the MailHost machinery and delivered. There must be at least one blank line seperating the headers (to/from/etc..) from the body of the message. Instead of specifying a MailHost, an smtphost may be specified ala 'smtphost="mail.mycompany.com" port=25' (port defaults to 25 automatically). Other parameters are * mailto -- person (or comma-seperated list of persons) to send the mail to. If not specified, there **must** be a to: header in the message. * mailfrom -- person sending the mail (basically who the recipient can reply to). If not specified, there **must** be a from: header in the message. * subject -- optional subject. If not specified, there **must** be a subject: header in the message. * encode -- optional encoding. Possible values are: 'base64', 'quoted-printable' and 'uuencode'. ''' name='sendmail' blockContinuations=() encode=None def __init__(self, blocks): tname, args, section=blocks[0] args=parse_params(args, mailhost=None, mailto=None, mailfrom=None, subject=None, smtphost=None, port='25', encode=None) smtphost=None has_key=args.has_key if has_key('mailhost'): mailhost = args['mailhost'] elif has_key('smtphost'): mailhost = smtphost = args['smtphost'] elif has_key(''): mailhost = args['mailhost'] = args[''] else: raise MailHostError('No mailhost was specified in tag') for key in ('mailto', 'mailfrom', 'subject', 'port'): if not key in args: args[key] = '' if has_key('encode') and args['encode'] not in \ ('base64', 'quoted-printable', 'uuencode', 'x-uuencode', 'uue', 'x-uue'): raise MailHostError( 'An unsupported encoding was specified in tag') if not smtphost: self.__name__=self.mailhost=mailhost self.smtphost=None else: self.__name__=self.smtphost=smtphost self.mailhost=None self.section=section self.args=args self.mailto=args['mailto'] self.mailfrom=args['mailfrom'] self.subject=None or args['subject'] if args['port'] and type(args['port']) is type('s'): self.port=args['port']=int(args['port']) elif args['port']=='': self.port=args['port']=25 else: self.port=args['port'] if has_key('encode'): self.encode=args['encode'] else: self.encode=None def render(self, md): if self.mailhost: mhost = md[self.mailhost] elif self.smtphost: mhost = MailBase(smtp_host=self.smtphost, smtp_port=self.port) mhost.send(render_blocks(self.section.blocks, md), self.mailto, self.mailfrom, self.subject, self.encode) return ' ' __call__ = render String.commands['sendmail'] = SendMailTag zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/help/0000755000175000017500000000000012214017670024023 5ustar arnauarnauzope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/help/Mail-Host_Add.stx0000644000175000017500000000056012214017670027131 0ustar arnauarnauMailHost - Add: Create a new MailHost Description Create a new MailHost object. Controls 'ID' -- The id of the MailHost object. 'Title' -- The title of the MailHost. 'SMTP host' -- The domain name or address of the SMTP mail server to relay mail through. 'SMTP port' -- The port of the SMTP mail server to relay mail through. zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/help/MailHost.py0000644000175000017500000000477012214017670026125 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## def manage_addMailHost(id, title='', smtp_host=None, localhost='localhost', smtp_port=25, timeout=1.0): """ Add a mailhost object to an ObjectManager. """ class MailHost: """ MailHost objects work as adapters to Simple Mail Transfer Protocol (SMTP) servers. MailHosts are used by DTML 'sendmail' tags to find the proper host to deliver mail to. """ def send(messageText, mto=None, mfrom=None, subject=None, encode=None): """ Sends an email message where the messageText is an rfc822 formatted message. This allows you complete control over the message headers, including setting any extra headers such as Cc: and Reply-To:. The arguments are: messageText -- The mail message. It can either be a rfc822 formed text with header fields, or just a body without any header fields. The other arguments given will override the header fields in the message, if they exist. mto -- A commaseparated string or list of recipient(s) of the message. mfrom -- The address of the message sender. subject -- The subject of the message. encode -- The rfc822 defined encoding of the message. The default of 'None' means no encoding is done. Valid values are 'base64', 'quoted-printable' and 'uuencode'. """ def simple_send(self, mto, mfrom, subject, body): """ Sends a message. Only To:, From: and Subject: headers can be set. Note that simple_send does not process or validate its arguments in any way. The arguments are: mto -- A commaseparated string of recipient(s) of the message. mfrom -- The address of the message sender. subject -- The subject of the message. body -- The body of the message. """ zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/help/Mail-Host.stx0000644000175000017500000000315312214017670026362 0ustar arnauarnauMailHost: Sends mail through an SMTP server. MailHosts allow you to send mail via the Simple Mail Transfer Protocol (SMTP). This object can be used deliver mail by the tag or via the send() and simple_send() methods. 'send(messageText, mto=None, mfrom=None, subject=None, encode=None)' Sends an email message where the messageText is an rfc822 formatted message. This allows you complete control over the message headers, including setting any extra headers such as Cc: and Reply-To:. The arguments are: messageText -- The mail message. It can either be a rfc822 formed text with header fields, or just a body without any header fields. The other arguments given will override the header fields in the message, if they exist. mto -- A commaseparated string or list of recipient(s) of the message. mfrom -- The address of the message sender. subject -- The subject of the message. encode -- The rfc822 defined encoding of the message. The default of 'None' means no encoding is done. Valid values are 'base64', 'quoted-printable' and 'uuencode'. 'simple_send(self, mto, mfrom, subject, body)' Sends a message. Only To:, From: and Subject: headers can be set. Note that simple_send does not process or validate its arguments in any way. The arguments are: mto -- A commaseparated string of recipient(s) of the message. mfrom -- The address of the message sender. subject -- The subject of the message. body -- The body of the message. zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/help/Mail-Host_Edit.stx0000644000175000017500000000057312214017670027332 0ustar arnauarnauMailHost - Edit: Edit mail host properties Description This view allows you edit the MailHost. Controls 'ID' -- The id of the MailHost. 'Title' -- The title of the MailHost. 'SMTP host' -- The domain name or address of the SMTP mail server to relay mail through. 'SMTP port' -- The port of the SMTP mail server to relay mail through. zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/MailHost.py0000644000175000017500000004410012214017670025164 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """SMTP mail objects """ import logging from os.path import realpath import re from cStringIO import StringIO from copy import deepcopy from email.Header import Header from email.Charset import Charset from email import message_from_string from email.Message import Message from email import Encoders try: import email.utils as emailutils except ImportError: import email.Utils as emailutils import email.Charset # We import from a private module here because the email module # doesn't provide a good public address list parser import uu from threading import Lock import time from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.Permissions import change_configuration, view from AccessControl.Permissions import use_mailhost_services from Acquisition import Implicit from App.special_dtml import DTMLFile from DateTime.DateTime import DateTime from Persistence import Persistent from OFS.role import RoleManager from OFS.SimpleItem import Item from zope.interface import implements from zope.sendmail.mailer import SMTPMailer from zope.sendmail.maildir import Maildir from zope.sendmail.delivery import DirectMailDelivery, QueuedMailDelivery, \ QueueProcessorThread from interfaces import IMailHost from decorator import synchronized queue_threads = {} # maps MailHost path -> queue processor threada LOG = logging.getLogger('MailHost') # Encode utf-8 emails as Quoted Printable by default email.Charset.add_charset("utf-8", email.Charset.QP, email.Charset.QP, "utf-8") formataddr = emailutils.formataddr parseaddr = emailutils.parseaddr getaddresses = emailutils.getaddresses CHARSET_RE = re.compile('charset=[\'"]?([\w-]+)[\'"]?', re.IGNORECASE) class MailHostError(Exception): pass manage_addMailHostForm = DTMLFile('dtml/addMailHost_form', globals()) def manage_addMailHost(self, id, title='', smtp_host='localhost', localhost='localhost', smtp_port=25, timeout=1.0, REQUEST=None, ): """ Add a MailHost into the system. """ i = MailHost(id, title, smtp_host, smtp_port) self._setObject(id, i) if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main') add = manage_addMailHost class MailBase(Implicit, Item, RoleManager): """a mailhost...?""" implements(IMailHost) meta_type = 'Mail Host' manage = manage_main = DTMLFile('dtml/manageMailHost', globals()) manage_main._setName('manage_main') index_html = None security = ClassSecurityInfo() smtp_uid = '' # Class attributes for smooth upgrades smtp_pwd = '' smtp_queue = False smtp_queue_directory = '/tmp' force_tls = False lock = Lock() manage_options = ( ( {'icon': '', 'label': 'Edit', 'action': 'manage_main', 'help': ('MailHost', 'Mail-Host_Edit.stx')}, ) + RoleManager.manage_options + Item.manage_options ) def __init__(self, id='', title='', smtp_host='localhost', smtp_port=25, force_tls=False, smtp_uid='', smtp_pwd='', smtp_queue=False, smtp_queue_directory='/tmp', ): """Initialize a new MailHost instance. """ self.id = id self.title = title self.smtp_host = str(smtp_host) self.smtp_port = int(smtp_port) self.smtp_uid = smtp_uid self.smtp_pwd = smtp_pwd self.force_tls = force_tls self.smtp_queue = smtp_queue self.smtp_queue_directory = smtp_queue_directory # staying for now... (backwards compatibility) def _init(self, smtp_host, smtp_port): self.smtp_host = smtp_host self.smtp_port = smtp_port security.declareProtected(change_configuration, 'manage_makeChanges') def manage_makeChanges(self, title, smtp_host, smtp_port, smtp_uid='', smtp_pwd='', smtp_queue=False, smtp_queue_directory='/tmp', force_tls=False, REQUEST=None, ): """Make the changes. """ title = str(title) smtp_host = str(smtp_host) smtp_port = int(smtp_port) self.title = title self.smtp_host = smtp_host self.smtp_port = smtp_port self.smtp_uid = smtp_uid self.smtp_pwd = smtp_pwd self.force_tls = force_tls self.smtp_queue = smtp_queue self.smtp_queue_directory = smtp_queue_directory # restart queue processor thread if self.smtp_queue: self._stopQueueProcessorThread() self._startQueueProcessorThread() else: self._stopQueueProcessorThread() if REQUEST is not None: msg = 'MailHost %s updated' % self.id return self.manage_main(self, REQUEST, manage_tabs_message=msg) security.declareProtected(use_mailhost_services, 'sendTemplate') def sendTemplate(trueself, self, messageTemplate, statusTemplate=None, mto=None, mfrom=None, encode=None, REQUEST=None, immediate=False, charset=None, msg_type=None, ): """Render a mail template, then send it... """ mtemplate = getattr(self, messageTemplate) messageText = mtemplate(self, trueself.REQUEST) trueself.send(messageText, mto=mto, mfrom=mfrom, encode=encode, immediate=immediate, charset=charset, msg_type=msg_type) if not statusTemplate: return "SEND OK" try: stemplate = getattr(self, statusTemplate) return stemplate(self, trueself.REQUEST) except: return "SEND OK" security.declareProtected(use_mailhost_services, 'send') def send(self, messageText, mto=None, mfrom=None, subject=None, encode=None, immediate=False, charset=None, msg_type=None, ): messageText, mto, mfrom = _mungeHeaders(messageText, mto, mfrom, subject, charset, msg_type) # This encode step is mainly for BBB, encoding should be # automatic if charset is passed. The automated charset-based # encoding will be preferred if both encode and charset are # provided. messageText = _encode(messageText, encode) self._send(mfrom, mto, messageText, immediate) # This is here for backwards compatibility only. Possibly it could # be used to send messages at a scheduled future time, or via a mail queue? security.declareProtected(use_mailhost_services, 'scheduledSend') scheduledSend = send security.declareProtected(use_mailhost_services, 'simple_send') def simple_send(self, mto, mfrom, subject, body, immediate=False): body = "From: %s\nTo: %s\nSubject: %s\n\n%s" % ( mfrom, mto, subject, body) self._send(mfrom, mto, body, immediate) def _makeMailer(self): """ Create a SMTPMailer """ return SMTPMailer(hostname=self.smtp_host, port=int(self.smtp_port), username=self.smtp_uid or None, password=self.smtp_pwd or None, force_tls=self.force_tls) security.declarePrivate('_getThreadKey') def _getThreadKey(self): """ Return the key used to find our processor thread. """ return realpath(self.smtp_queue_directory) @synchronized(lock) def _stopQueueProcessorThread(self): """ Stop thread for processing the mail queue. """ key = self._getThreadKey() if key in queue_threads: thread = queue_threads[key] thread.stop() while thread.isAlive(): # wait until thread is really dead time.sleep(0.3) del queue_threads[key] LOG.info('Thread for %s stopped' % key) @synchronized(lock) def _startQueueProcessorThread(self): """ Start thread for processing the mail queue. """ key = self._getThreadKey() if key not in queue_threads: thread = QueueProcessorThread() thread.setMailer(self._makeMailer()) thread.setQueuePath(self.smtp_queue_directory) thread.start() queue_threads[key] = thread LOG.info('Thread for %s started' % key) security.declareProtected(view, 'queueLength') def queueLength(self): """ return length of mail queue """ try: maildir = Maildir(self.smtp_queue_directory) return len([item for item in maildir]) except ValueError: return 'n/a - %s is not a maildir - please verify your ' \ 'configuration' % self.smtp_queue_directory security.declareProtected(view, 'queueThreadAlive') def queueThreadAlive(self): """ return True/False is queue thread is working """ th = queue_threads.get(self._getThreadKey()) if th: return th.isAlive() return False security.declareProtected(change_configuration, 'manage_restartQueueThread') def manage_restartQueueThread(self, action='start', REQUEST=None): """ Restart the queue processor thread """ if action == 'stop': self._stopQueueProcessorThread() elif action == 'start': self._startQueueProcessorThread() else: raise ValueError('Unsupported action %s' % action) if REQUEST is not None: msg = 'Queue processor thread %s' % \ (action == 'stop' and 'stopped' or 'started') return self.manage_main(self, REQUEST, manage_tabs_message=msg) security.declarePrivate('_send') def _send(self, mfrom, mto, messageText, immediate=False): """ Send the message """ if immediate: self._makeMailer().send(mfrom, mto, messageText) else: if self.smtp_queue: # Start queue processor thread, if necessary self._startQueueProcessorThread() delivery = QueuedMailDelivery(self.smtp_queue_directory) else: delivery = DirectMailDelivery(self._makeMailer()) delivery.send(mfrom, mto, messageText) InitializeClass(MailBase) class MailHost(Persistent, MailBase): """persistent version""" def uu_encoder(msg): """For BBB only, don't send uuencoded emails""" orig = StringIO(msg.get_payload()) encdata = StringIO() uu.encode(orig, encdata) msg.set_payload(encdata.getvalue()) # All encodings supported by mimetools for BBB ENCODERS = { 'base64': Encoders.encode_base64, 'quoted-printable': Encoders.encode_quopri, '7bit': Encoders.encode_7or8bit, '8bit': Encoders.encode_7or8bit, 'x-uuencode': uu_encoder, 'uuencode': uu_encoder, 'x-uue': uu_encoder, 'uue': uu_encoder, } def _encode(body, encode=None): """Manually sets an encoding and encodes the message if not already encoded.""" if encode is None: return body mo = message_from_string(body) current_coding = mo['Content-Transfer-Encoding'] if current_coding == encode: # already encoded correctly, may have been automated return body if mo['Content-Transfer-Encoding'] not in ['7bit', None]: raise MailHostError('Message already encoded') if encode in ENCODERS: ENCODERS[encode](mo) if not mo['Content-Transfer-Encoding']: mo['Content-Transfer-Encoding'] = encode if not mo['Mime-Version']: mo['Mime-Version'] = '1.0' return mo.as_string() def _mungeHeaders(messageText, mto=None, mfrom=None, subject=None, charset=None, msg_type=None): """Sets missing message headers, and deletes Bcc. returns fixed message, fixed mto and fixed mfrom""" # If we have been given unicode fields, attempt to encode them if isinstance(messageText, unicode): messageText = _try_encode(messageText, charset) if isinstance(mto, unicode): mto = _try_encode(mto, charset) if isinstance(mfrom, unicode): mfrom = _try_encode(mfrom, charset) if isinstance(subject, unicode): subject = _try_encode(subject, charset) if isinstance(messageText, Message): # We already have a message, make a copy to operate on mo = deepcopy(messageText) else: # Otherwise parse the input message mo = message_from_string(messageText) if msg_type and not mo.get('Content-Type'): # we don't use get_content_type because that has a default # value of 'text/plain' mo.set_type(msg_type) charset = _set_recursive_charset(mo, charset=charset) # Parameters given will *always* override headers in the messageText. # This is so that you can't override or add to subscribers by adding # them to # the message text. if subject: # remove any existing header otherwise we get two del mo['Subject'] # Perhaps we should ignore errors here and pass 8bit strings # on encoding errors mo['Subject'] = Header(subject, charset, errors='replace') elif not mo.get('Subject'): mo['Subject'] = '[No Subject]' if mto: if isinstance(mto, basestring): mto = [formataddr(addr) for addr in getaddresses((mto, ))] if not mo.get('To'): mo['To'] = ', '.join(str(_encode_address_string(e, charset)) for e in mto) else: # If we don't have recipients, extract them from the message mto = [] for header in ('To', 'Cc', 'Bcc'): v = ','.join(mo.get_all(header) or []) if v: mto += [formataddr(addr) for addr in getaddresses((v, ))] if not mto: raise MailHostError("No message recipients designated") if mfrom: # XXX: do we really want to override an explicitly set From # header in the messageText del mo['From'] mo['From'] = _encode_address_string(mfrom, charset) else: if mo.get('From') is None: raise MailHostError("Message missing SMTP Header 'From'") mfrom = mo['From'] if mo.get('Bcc'): del mo['Bcc'] if not mo.get('Date'): mo['Date'] = DateTime().rfc822() return mo.as_string(), mto, mfrom def _set_recursive_charset(payload, charset=None): """Set charset for all parts of an multipart message.""" def _set_payload_charset(payload, charset=None, index=None): payload_from_string = False if not isinstance(payload, Message): payload = message_from_string(payload) payload_from_string = True charset_match = CHARSET_RE.search(payload['Content-Type'] or '') if charset and not charset_match: # Don't change the charset if already set # This encodes the payload automatically based on the default # encoding for the charset if payload_from_string: payload.get_payload()[index] = payload else: payload.set_charset(charset) elif charset_match and not charset: # If a charset parameter was provided use it for header encoding # below, otherwise, try to use the charset provided in the message. charset = charset_match.groups()[0] return charset if payload.is_multipart(): for index, payload in enumerate(payload.get_payload()): if payload.get_filename() is None: if not payload.is_multipart(): charset = _set_payload_charset(payload, charset=charset, index=index) else: _set_recursive_charset(payload, charset=charset) else: charset = _set_payload_charset(payload, charset=charset) return charset def _try_encode(text, charset): """Attempt to encode using the default charset if none is provided. Should we permit encoding errors?""" if charset: return text.encode(charset) else: return text.encode() def _encode_address_string(text, charset): """Split the email into parts and use header encoding on the name part if needed. We do this because the actual addresses need to be ASCII with no encoding for most SMTP servers, but the non-address parts should be encoded appropriately.""" header = Header() name, addr = parseaddr(text) try: name.decode('us-ascii') except UnicodeDecodeError: if charset: charset = Charset(charset) name = charset.header_encode(name) # We again replace rather than raise an error or pass an 8bit string header.append(formataddr((name, addr)), errors='replace') return header zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/tests/0000755000175000017500000000000012214017670024235 5ustar arnauarnauzope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/tests/testMailHost.py0000644000175000017500000006262212214017670027237 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """MailHost unit tests. """ import unittest from email import message_from_string from Products.MailHost.MailHost import MailHost from Products.MailHost.MailHost import MailHostError, _mungeHeaders class DummyMailHost(MailHost): meta_type = 'Dummy Mail Host' def __init__(self, id): self.id = id self.sent = '' def _send(self, mfrom, mto, messageText, immediate=False): self.sent = messageText self.immediate = immediate class FakeContent(object): def __init__(self, template_name, message): def template(self, context, REQUEST=None): return message setattr(self, template_name, template) @staticmethod def check_status(context, REQUEST=None): return 'Message Sent' class TestMailHost(unittest.TestCase): def _getTargetClass(self): return DummyMailHost def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def test_z3interfaces(self): from Products.MailHost.interfaces import IMailHost from zope.interface.verify import verifyClass verifyClass(IMailHost, self._getTargetClass()) def testAllHeaders(self): msg = """To: recipient@domain.com From: sender@domain.com Subject: This is the subject This is the message body.""" # No additional info resmsg, resto, resfrom = _mungeHeaders(msg) self.failUnless(resto == ['recipient@domain.com']) self.failUnless(resfrom == 'sender@domain.com') # Add duplicated info resmsg, resto, resfrom = _mungeHeaders(msg, 'recipient@domain.com', 'sender@domain.com', 'This is the subject') self.failUnlessEqual(resto, ['recipient@domain.com']) self.failUnlessEqual(resfrom, 'sender@domain.com') # Add extra info resmsg, resto, resfrom = _mungeHeaders(msg, 'recipient2@domain.com', 'sender2@domain.com', 'This is the real subject') self.failUnlessEqual(resto, ['recipient2@domain.com']) self.failUnlessEqual(resfrom, 'sender2@domain.com') def testMissingHeaders(self): msg = """X-Header: Dummy header This is the message body.""" # Doesn't specify to self.failUnlessRaises(MailHostError, _mungeHeaders, msg, mfrom='sender@domain.com') # Doesn't specify from self.failUnlessRaises(MailHostError, _mungeHeaders, msg, mto='recipient@domain.com') def testNoHeaders(self): msg = """This is the message body.""" # Doesn't specify to self.failUnlessRaises(MailHostError, _mungeHeaders, msg, mfrom='sender@domain.com') # Doesn't specify from self.failUnlessRaises(MailHostError, _mungeHeaders, msg, mto='recipient@domain.com') # Specify all resmsg, resto, resfrom = _mungeHeaders(msg, 'recipient2@domain.com', 'sender2@domain.com', 'This is the real subject') self.failUnlessEqual(resto, ['recipient2@domain.com']) self.failUnlessEqual(resfrom, 'sender2@domain.com') def testBCCHeader(self): msg = "From: me@example.com\nBcc: many@example.com\n\nMessage text" # Specify only the "Bcc" header. Useful for bulk emails. resmsg, resto, resfrom = _mungeHeaders(msg) self.failUnlessEqual(resto, ['many@example.com']) self.failUnlessEqual(resfrom, 'me@example.com') def test__getThreadKey_uses_fspath(self): mh1 = self._makeOne('mh1') mh1.smtp_queue_directory = '/abc' mh1.absolute_url = lambda self: 'http://example.com/mh1' mh2 = self._makeOne('mh2') mh2.smtp_queue_directory = '/abc' mh2.absolute_url = lambda self: 'http://example.com/mh2' self.assertEqual(mh1._getThreadKey(), mh2._getThreadKey()) def testAddressParser(self): msg = """\ To: "Name, Nick" , "Foo Bar" CC: "Web, Jack" From: sender@domain.com Subject: This is the subject This is the message body.""" # Test Address-Parser for To & CC given in messageText resmsg, resto, resfrom = _mungeHeaders(msg) self.failUnlessEqual(resto, ['"Name, Nick" ', 'Foo Bar ', '"Web, Jack" ']) self.failUnlessEqual(resfrom, 'sender@domain.com') # Test Address-Parser for a given mto-string resmsg, resto, resfrom = _mungeHeaders(msg, mto='"Public, Joe" , Foo Bar ') self.failUnlessEqual(resto, ['"Public, Joe" ', 'Foo Bar ']) self.failUnlessEqual(resfrom, 'sender@domain.com') def testSendMessageOnly(self): msg = """\ To: "Name, Nick" , "Foo Bar" From: sender@domain.com Subject: This is the subject Date: Sun, 27 Aug 2006 17:00:00 +0200 This is the message body.""" mailhost = self._makeOne('MailHost') mailhost.send(msg) self.assertEqual(mailhost.sent, msg) def testSendWithArguments(self): inmsg = """\ Date: Sun, 27 Aug 2006 17:00:00 +0200 This is the message body.""" outmsg = """\ Date: Sun, 27 Aug 2006 17:00:00 +0200 Subject: This is the subject To: "Name, Nick" , Foo Bar From: sender@domain.com This is the message body.""" mailhost = self._makeOne('MailHost') mailhost.send(messageText=inmsg, mto='"Name, Nick" , ' '"Foo Bar" ', mfrom='sender@domain.com', subject='This is the subject') self.assertEqual(mailhost.sent, outmsg) def testSendWithMtoList(self): inmsg = """\ Date: Sun, 27 Aug 2006 17:00:00 +0200 This is the message body.""" outmsg = """\ Date: Sun, 27 Aug 2006 17:00:00 +0200 Subject: This is the subject To: "Name, Nick" , Foo Bar From: sender@domain.com This is the message body.""" mailhost = self._makeOne('MailHost') mailhost.send(messageText=inmsg, mto=['"Name, Nick" ', '"Foo Bar" '], mfrom='sender@domain.com', subject='This is the subject') self.assertEqual(mailhost.sent, outmsg) def testSimpleSend(self): outmsg = """\ From: sender@domain.com To: "Name, Nick" , "Foo Bar" Subject: This is the subject This is the message body.""" mailhost = self._makeOne('MailHost') mailhost.simple_send(mto='"Name, Nick" , ' '"Foo Bar" ', mfrom='sender@domain.com', subject='This is the subject', body='This is the message body.') self.assertEqual(mailhost.sent, outmsg) self.assertEqual(mailhost.immediate, False) def testSendImmediate(self): outmsg = """\ From: sender@domain.com To: "Name, Nick" , "Foo Bar" Subject: This is the subject This is the message body.""" mailhost = self._makeOne('MailHost') mailhost.simple_send(mto='"Name, Nick" , ' '"Foo Bar" ', mfrom='sender@domain.com', subject='This is the subject', body='This is the message body.', immediate=True) self.assertEqual(mailhost.sent, outmsg) self.assertEqual(mailhost.immediate, True) def testSendBodyWithUrl(self): # The implementation of rfc822.Message reacts poorly to # message bodies containing ':' characters as in a url msg = "Here's a nice link: http://www.zope.org/" mailhost = self._makeOne('MailHost') mailhost.send(messageText=msg, mto='"Name, Nick" , ' '"Foo Bar" ', mfrom='sender@domain.com', subject='This is the subject') out = message_from_string(mailhost.sent) self.failUnlessEqual(out.get_payload(), msg) self.failUnlessEqual(out['To'], '"Name, Nick" , Foo Bar ') self.failUnlessEqual(out['From'], 'sender@domain.com') def testSendEncodedBody(self): # If a charset is specified the correct headers for content # encoding will be set if not already set. Additionally, if # there is a default transfer encoding for the charset, then # the content will be encoded and the transfer encoding header # will be set. msg = "Here's some encoded t\xc3\xa9xt." mailhost = self._makeOne('MailHost') mailhost.send(messageText=msg, mto='"Name, Nick" , ' '"Foo Bar" ', mfrom='sender@domain.com', subject='This is the subject', charset='utf-8') out = message_from_string(mailhost.sent) self.failUnlessEqual(out['To'], '"Name, Nick" , Foo Bar ') self.failUnlessEqual(out['From'], 'sender@domain.com') # utf-8 will default to Quoted Printable encoding self.failUnlessEqual(out['Content-Transfer-Encoding'], 'quoted-printable') self.failUnlessEqual(out['Content-Type'], 'text/plain; charset="utf-8"') self.failUnlessEqual(out.get_payload(), "Here's some encoded t=C3=A9xt.") def testEncodedHeaders(self): # Headers are encoded automatically, email headers are encoded # piece-wise to ensure the adresses remain ASCII mfrom = "Jos\xc3\xa9 Andr\xc3\xa9s " mto = "Ferran Adri\xc3\xa0 " subject = "\xc2\xbfEsferificaci\xc3\xb3n?" mailhost = self._makeOne('MailHost') mailhost.send(messageText='A message.', mto=mto, mfrom=mfrom, subject=subject, charset='utf-8') out = message_from_string(mailhost.sent) self.failUnlessEqual(out['To'], '=?utf-8?q?Ferran_Adri=C3=A0?= ') self.failUnlessEqual(out['From'], '=?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= ') self.failUnlessEqual(out['Subject'], '=?utf-8?q?=C2=BFEsferificaci=C3=B3n=3F?=') # utf-8 will default to Quoted Printable encoding self.failUnlessEqual(out['Content-Transfer-Encoding'], 'quoted-printable') self.failUnlessEqual(out['Content-Type'], 'text/plain; charset="utf-8"') self.failUnlessEqual(out.get_payload(), "A message.") def testAlreadyEncodedMessage(self): # If the message already specifies encodings, it is # essentially not altered this is true even if charset or # msg_type is specified msg = """\ From: =?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= To: =?utf-8?q?Ferran_Adri=C3=A0?= Subject: =?utf-8?q?=C2=BFEsferificaci=C3=B3n=3F?= Date: Sun, 27 Aug 2006 17:00:00 +0200 Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: base64 MIME-Version: 1.0 (Generated by testMailHost.py) wqFVbiB0cnVjbyA8c3Ryb25nPmZhbnTDoXN0aWNvPC9zdHJvbmc+IQ=3D=3D """ mailhost = self._makeOne('MailHost') mailhost.send(messageText=msg) self.failUnlessEqual(mailhost.sent, msg) mailhost.send(messageText=msg, msg_type='text/plain') # The msg_type is ignored if already set self.failUnlessEqual(mailhost.sent, msg) def testAlreadyEncodedMessageWithCharset(self): # If the message already specifies encodings, it is # essentially not altered this is true even if charset or # msg_type is specified msg = """\ From: =?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= To: =?utf-8?q?Ferran_Adri=C3=A0?= Subject: =?utf-8?q?=C2=BFEsferificaci=C3=B3n=3F?= Date: Sun, 27 Aug 2006 17:00:00 +0200 Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: base64 MIME-Version: 1.0 (Generated by testMailHost.py) wqFVbiB0cnVjbyA8c3Ryb25nPmZhbnTDoXN0aWNvPC9zdHJvbmc+IQ=3D=3D """ mailhost = self._makeOne('MailHost') # Pass a different charset, which will apply to any explicitly # set headers mailhost.send(messageText=msg, subject='\xbfEsferificaci\xf3n?', charset='iso-8859-1', msg_type='text/plain') # The charset for the body should remain the same, but any # headers passed into the method will be encoded using the # specified charset out = message_from_string(mailhost.sent) self.failUnlessEqual(out['Content-Type'], 'text/html; charset="utf-8"') self.failUnlessEqual(out['Content-Transfer-Encoding'], 'base64') # Headers set by parameter will be set using charset parameter self.failUnlessEqual(out['Subject'], '=?iso-8859-1?q?=BFEsferificaci=F3n=3F?=') # original headers will be unaltered self.failUnlessEqual(out['From'], '=?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= ') def testUnicodeMessage(self): # unicode messages and headers are decoded using the given charset msg = unicode("Here's some unencoded t\xc3\xa9xt.", 'utf-8') mfrom = unicode('Ferran Adri\xc3\xa0 ', 'utf-8') subject = unicode('\xc2\xa1Andr\xc3\xa9s!', 'utf-8') mailhost = self._makeOne('MailHost') mailhost.send(messageText=msg, mto='"Name, Nick" ', mfrom=mfrom, subject=subject, charset='utf-8', msg_type='text/html') out = message_from_string(mailhost.sent) self.failUnlessEqual(out['To'], '"Name, Nick" ') self.failUnlessEqual(out['From'], '=?utf-8?q?Ferran_Adri=C3=A0?= ') self.failUnlessEqual(out['Subject'], '=?utf-8?q?=C2=A1Andr=C3=A9s!?=') self.failUnlessEqual(out['Content-Transfer-Encoding'], 'quoted-printable') self.failUnlessEqual(out['Content-Type'], 'text/html; charset="utf-8"') self.failUnlessEqual(out.get_payload(), "Here's some unencoded t=C3=A9xt.") def testUnicodeNoEncodingErrors(self): # Unicode messages and headers raise errors if no charset is passed to # send msg = unicode("Here's some unencoded t\xc3\xa9xt.", 'utf-8') subject = unicode('\xc2\xa1Andr\xc3\xa9s!', 'utf-8') mailhost = self._makeOne('MailHost') self.assertRaises(UnicodeEncodeError, mailhost.send, msg, mto='"Name, Nick" ', mfrom='Foo Bar ', subject=subject) def testUnicodeDefaultEncoding(self): # However if we pass unicode that can be encoded to the # default encoding (generally 'us-ascii'), no error is raised. # We include a date in the messageText to make inspecting the # results more convenient. msg = u"""\ Date: Sun, 27 Aug 2006 17:00:00 +0200 Here's some unencoded text.""" subject = u'Andres!' mailhost = self._makeOne('MailHost') mailhost.send(msg, mto=u'"Name, Nick" ', mfrom=u'Foo Bar ', subject=subject) out = mailhost.sent # Ensure the results are not unicode self.failUnlessEqual(out, """\ Date: Sun, 27 Aug 2006 17:00:00 +0200 Subject: Andres! To: "Name, Nick" From: Foo Bar Here's some unencoded text.""") self.failUnlessEqual(type(out), str) def testSendMessageObject(self): # send will accept an email.Message.Message object directly msg = message_from_string("""\ From: =?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= To: =?utf-8?q?Ferran_Adri=C3=A0?= Subject: =?utf-8?q?=C2=BFEsferificaci=C3=B3n=3F?= Date: Sun, 27 Aug 2006 17:00:00 +0200 Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: base64 MIME-Version: 1.1 wqFVbiB0cnVjbyA8c3Ryb25nPmZhbnTDoXN0aWNvPC9zdHJvbmc+IQ=3D=3D """) mailhost = self._makeOne('MailHost') mailhost.send(msg) out = message_from_string(mailhost.sent) self.failUnlessEqual(out.as_string(), msg.as_string()) # we can even alter a from and subject headers without affecting the # original object mailhost.send(msg, mfrom='Foo Bar ', subject='Changed!') out = message_from_string(mailhost.sent) # We need to make sure we didn't mutate the message we were passed self.failIfEqual(out.as_string(), msg.as_string()) self.failUnlessEqual(out['From'], 'Foo Bar ') self.failUnlessEqual(msg['From'], '=?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= ') # The subject is encoded with the body encoding since no # explicit encoding was specified self.failUnlessEqual(out['Subject'], '=?utf-8?q?Changed!?=') self.failUnlessEqual(msg['Subject'], '=?utf-8?q?=C2=BFEsferificaci=C3=B3n=3F?=') def testExplicitUUEncoding(self): # We can request a payload encoding explicitly, though this # should probably be considered deprecated functionality. mailhost = self._makeOne('MailHost') # uuencoding mailhost.send('Date: Sun, 27 Aug 2006 17:00:00 +0200\n\nA Message', mfrom='sender@domain.com', mto='Foo Bar ', encode='uue') out = message_from_string(mailhost.sent) self.failUnlessEqual(mailhost.sent, """\ Date: Sun, 27 Aug 2006 17:00:00 +0200 Subject: [No Subject] To: Foo Bar From: sender@domain.com Content-Transfer-Encoding: uue Mime-Version: 1.0 begin 666 - )02!-97-S86=E end """) def testExplicitBase64Encoding(self): mailhost = self._makeOne('MailHost') mailhost.send('Date: Sun, 27 Aug 2006 17:00:00 +0200\n\nA Message', mfrom='sender@domain.com', mto='Foo Bar ', encode='base64') out = message_from_string(mailhost.sent) self.failUnlessEqual(mailhost.sent, """\ Date: Sun, 27 Aug 2006 17:00:00 +0200 Subject: [No Subject] To: Foo Bar From: sender@domain.com Content-Transfer-Encoding: base64 Mime-Version: 1.0 QSBNZXNzYWdl""") def testExplicit7bitEncoding(self): mailhost = self._makeOne('MailHost') mailhost.send('Date: Sun, 27 Aug 2006 17:00:00 +0200\n\nA Message', mfrom='sender@domain.com', mto='Foo Bar ', encode='7bit') out = message_from_string(mailhost.sent) self.failUnlessEqual(mailhost.sent, """\ Date: Sun, 27 Aug 2006 17:00:00 +0200 Subject: [No Subject] To: Foo Bar From: sender@domain.com Content-Transfer-Encoding: 7bit Mime-Version: 1.0 A Message""") def testExplicit8bitEncoding(self): mailhost = self._makeOne('MailHost') # We pass an encoded string with unspecified charset, it should be # encoded 8bit mailhost.send('Date: Sun, 27 Aug 2006 17:00:00 +0200\n\n' 'A M\xc3\xa9ssage', mfrom='sender@domain.com', mto='Foo Bar ', encode='8bit') out = message_from_string(mailhost.sent) self.failUnlessEqual(mailhost.sent, """\ Date: Sun, 27 Aug 2006 17:00:00 +0200 Subject: [No Subject] To: Foo Bar From: sender@domain.com Content-Transfer-Encoding: 8bit Mime-Version: 1.0 A M\xc3\xa9ssage""") def testSendTemplate(self): content = FakeContent('my_template', 'A Message') mailhost = self._makeOne('MailHost') result = mailhost.sendTemplate(content, 'my_template', mto='Foo Bar ', mfrom='sender@domain.com') self.failUnlessEqual(result, 'SEND OK') result = mailhost.sendTemplate(content, 'my_template', mto='Foo Bar ', mfrom='sender@domain.com', statusTemplate='wrong_name') self.failUnlessEqual(result, 'SEND OK') result = mailhost.sendTemplate(content, 'my_template', mto='Foo Bar ', mfrom='sender@domain.com', statusTemplate='check_status') self.failUnlessEqual(result, 'Message Sent') def testSendMultiPartAlternativeMessage(self): msg = ("""\ Content-Type: multipart/alternative; boundary="===============0490954888==" MIME-Version: 1.0 Date: Sun, 27 Aug 2006 17:00:00 +0200 Subject: My multipart email To: Foo Bar From: sender@domain.com --===============0490954888== Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable This is plain text. --===============0490954888== Content-Type: multipart/related; boundary="===============2078950065==" MIME-Version: 1.0 --===============2078950065== Content-Type: text/html; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable

This is html.

--===============2078950065==-- --===============0490954888==-- """) mailhost = self._makeOne('MailHost') # Specifying a charset for the header may have unwanted side # effects in the case of multipart mails. # (TypeError: expected string or buffer) mailhost.send(msg, charset='utf-8') self.assertEqual(mailhost.sent, msg) def testSendMultiPartMixedMessage(self): msg = ("""\ Content-Type: multipart/mixed; boundary="XOIedfhf+7KOe/yw" Content-Disposition: inline MIME-Version: 1.0 Date: Sun, 27 Aug 2006 17:00:00 +0200 Subject: My multipart email To: Foo Bar From: sender@domain.com --XOIedfhf+7KOe/yw Content-Type: text/plain; charset=us-ascii Content-Disposition: inline This is a test with as attachment OFS/www/new.gif. --XOIedfhf+7KOe/yw Content-Type: image/gif Content-Disposition: attachment; filename="new.gif" Content-Transfer-Encoding: base64 R0lGODlhCwAQAPcAAP8A/wAAAFBQUICAgMDAwP8AAIAAQAAAoABAgIAAgEAAQP//AP//gACA gECAgP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAALABAAAAg7AAEIFKhgoEGC CwoeRKhwoYKEBhVIfLgg4UQAFCtqbJixYkOEHg9SHDmQJEmMEBkS/IiR5cKXMGPKDAgAOw== --XOIedfhf+7KOe/yw Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: attachment; filename="test.txt" Content-Transfer-Encoding: quoted-printable D=EDt =EFs =E9=E9n test --XOIedfhf+7KOe/yw-- """) mailhost = self._makeOne('MailHost') # Specifying a charset for the header may have unwanted side # effects in the case of multipart mails. # (TypeError: expected string or buffer) mailhost.send(msg, charset='utf-8') self.assertEqual(mailhost.sent, msg) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestMailHost)) return suite zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/tests/__init__.py0000644000175000017500000000117412214017670026351 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/dtml/0000755000175000017500000000000012214017670024033 5ustar arnauarnauzope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/dtml/addMailHost_form.dtml0000644000175000017500000000326112214017670030133 0ustar arnauarnau

MailHost object provide a way to send email from Zope code in DTML or Python Scripts. SMTP host is the name of the mail server machine. SMTP port is the port on which the mail server is running the SMTP service.

Id
Title
SMTP Host
SMTP Port
zope2.13-2.13.21/source/Products.MailHost/src/Products/MailHost/dtml/manageMailHost.dtml0000644000175000017500000001060612214017670027611 0ustar arnauarnau
Id
&dtml-id;
Title
SMTP Host
SMTP Port
Username
(optional for SMTP AUTH)
Password
(optional for SMTP AUTH)
Force TLS
checked (enforce the use of an encrypted connection to the SMTP server. Mail delivery fails if the SMTP server does not support encryption)
Use mail queue
checked (asynchronous mail delivery if checked)
Queue directory
(directory on the filesystem where the mails will be spooled. Only used if 'Use mail queue' is checked.)

Mails in queue
Status of queue processor thread
Running
Stop queue processor thread (this may take some seconds)
Stopped
Start queue processor thread (this may take some seconds)
zope2.13-2.13.21/source/Products.MailHost/src/Products/__init__.py0000644000175000017500000000007012214017670023461 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.testbrowser/0000755000175000017500000000000012214017652016776 5ustar arnauarnauzope2.13-2.13.21/source/zope.testbrowser/setup.py0000644000175000017500000000516212214017652020514 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Setup for zope.testbrowser package """ import os from setuptools import setup, find_packages long_description = ( '.. contents::\n\n' + open('README.txt').read() + '\n\n' + open(os.path.join('src', 'zope', 'testbrowser', 'README.txt')).read() + '\n\n' + open('CHANGES.txt').read() ) setup( name = 'zope.testbrowser', version='3.11.1', url = 'http://pypi.python.org/pypi/zope.testbrowser', license = 'ZPL 2.1', description = 'Programmable browser for functional black-box tests', author = 'Zope Corporation and Contributors', author_email = 'zope-dev@zope.org', long_description = long_description, classifiers=[ 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Topic :: Software Development :: Testing', 'Topic :: Internet :: WWW/HTTP', ], packages = find_packages('src'), package_dir = {'': 'src'}, namespace_packages = ['zope',], tests_require = ['zope.testing'], install_requires = [ # mechanize 0.2.0 folds in ClientForm, makes incompatible API changes 'mechanize>=0.2.0', 'setuptools', 'zope.interface', 'zope.schema', 'pytz', ], extras_require = { 'test': [ 'zope.browserpage', 'zope.browserresource', 'zope.component', 'zope.container', 'zope.principalregistry', 'zope.ptresource', 'zope.publisher', 'zope.security', 'zope.site', 'zope.traversing', 'zope.app.appsetup', 'zope.app.publication', 'zope.app.testing >= 3.8.1', ], 'zope-functional-testing': [ 'zope.app.testing', ], 'wsgi': [ 'wsgi_intercept', ] }, include_package_data = True, zip_safe = False, ) zope2.13-2.13.21/source/zope.testbrowser/PKG-INFO0000644000175000017500000017061712214017652020107 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.testbrowser Version: 3.11.1 Summary: Programmable browser for functional black-box tests Home-page: http://pypi.python.org/pypi/zope.testbrowser Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: .. contents:: ``zope.testbrowser`` provides an easy-to-use programmable web browser with special focus on testing. It is used in Zope, but it's not Zope specific at all. For instance, it can be used to test or otherwise interact with any web site. ====================== Detailed Documentation ====================== Different Browsers ------------------ HTTP Browser ~~~~~~~~~~~~ The ``zope.testbrowser.browser`` module exposes a ``Browser`` class that simulates a web browser similar to Mozilla Firefox or IE. >>> from zope.testbrowser.browser import Browser >>> browser = Browser() This version of the browser object can be used to access any web site just as you would do using a normal web browser. WSGI Test Browser ~~~~~~~~~~~~~~~~~ There is also a special version of the ``Browser`` class which uses `wsgi_intercept`_ and can be used to do functional testing of WSGI applications, it can be imported from ``zope.testbrowser.wsgi``: >>> from zope.testbrowser.wsgi import Browser >>> browser = Browser() .. _`wsgi_intercept`: http://pypi.python.org/pypi/wsgi_intercept To use this browser you have to: * use the `wsgi` extra of the ``zope.testbrowser`` egg, * write a subclass of ``zope.testbrowser.wsgi.Layer`` and override the ``make_wsgi_app`` method, * use an instance of the class as the test layer of your test. Example: >>> import zope.testbrowser.wsgi >>> class SimpleLayer(zope.testbrowser.wsgi.Layer): ... def make_wsgi_app(self): ... return simple_app Where ``simple_app`` is the callable of your WSGI application. Zope 3 Test Browser ~~~~~~~~~~~~~~~~~~~ There is also a special version of the ``Browser`` class used to do functional testing of Zope 3 applications, it can be imported from ``zope.testbrowser.testing``: >>> from zope.testbrowser.testing import Browser >>> browser = Browser() Bowser Usage ------------ All browsers are used the same way. An initial page to load can be passed to the ``Browser`` constructor: >>> browser = Browser('http://localhost/@@/testbrowser/simple.html') >>> browser.url 'http://localhost/@@/testbrowser/simple.html' The browser can send arbitrary headers; this is helpful for setting the "Authorization" header or a language value, so that your tests format values the way you expect in your tests, if you rely on zope.i18n locale-based formatting or a similar approach. >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw') >>> browser.addHeader('Accept-Language', 'en-US') An existing browser instance can also `open` web pages: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.url 'http://localhost/@@/testbrowser/simple.html' Once you have opened a web page initially, best practice for writing testbrowser doctests suggests using 'click' to navigate further (as discussed below), except in unusual circumstances. The test browser complies with the IBrowser interface; see ``zope.testbrowser.interfaces`` for full details on the interface. >>> from zope.testbrowser import interfaces >>> from zope.interface.verify import verifyObject >>> verifyObject(interfaces.IBrowser, browser) True Page Contents ------------- The contents of the current page are available: >>> print browser.contents Simple Page

Simple Page

Making assertions about page contents is easy. >>> '

Simple Page

' in browser.contents True Utilizing the doctest facilities, it also possible to do: >>> browser.contents '...

Simple Page

...' Note: Unfortunately, ellipsis (...) cannot be used at the beginning of the output (this is a limitation of doctest). Checking for HTML ----------------- Not all URLs return HTML. Of course our simple page does: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.isHtml True But if we load an image (or other binary file), we do not get HTML: >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif') >>> browser.isHtml False HTML Page Title ---------------- Another useful helper property is the title: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.title 'Simple Page' If a page does not provide a title, it is simply ``None``: >>> browser.open('http://localhost/@@/testbrowser/notitle.html') >>> browser.title However, if the output is not HTML, then an error will occur trying to access the title: >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif') >>> browser.title Traceback (most recent call last): ... BrowserStateError: not viewing HTML Headers ------- As you can see, the `contents` of the browser does not return any HTTP headers. The headers are accessible via a separate attribute, which is an ``httplib.HTTPMessage`` instance (httplib is a part of Python's standard library): >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.headers The headers can be accessed as a string: >>> print browser.headers Status: 200 OK Content-Length: 123 Content-Type: text/html;charset=utf-8 X-Powered-By: Zope (www.zope.org), Python (www.python.org) Or as a mapping: >>> browser.headers['content-type'] 'text/html;charset=utf-8' Cookies ------- When a Set-Cookie header is available, it can be found in the headers, as seen above. Here, we use a view that will make the server set cookies with the values we provide. >>> browser.open('http://localhost/set_cookie.html?name=foo&value=bar') >>> browser.headers['set-cookie'].replace(';', '') 'foo=bar' It is also available in the browser's ``cookies`` attribute. This is an extended mapping interface that allows getting, setting, and deleting the cookies that the browser is remembering *for the current url*. Here are a few examples. >>> browser.cookies['foo'] 'bar' >>> browser.cookies.keys() ['foo'] >>> browser.cookies.values() ['bar'] >>> browser.cookies.items() [('foo', 'bar')] >>> 'foo' in browser.cookies True >>> 'bar' in browser.cookies False >>> len(browser.cookies) 1 >>> print(dict(browser.cookies)) {'foo': 'bar'} >>> browser.cookies['sha'] = 'zam' >>> len(browser.cookies) 2 >>> sorted(browser.cookies.items()) [('foo', 'bar'), ('sha', 'zam')] >>> browser.open('http://localhost/get_cookie.html') >>> print browser.headers.get('set-cookie') None >>> print browser.contents # server got the cookie change foo: bar sha: zam >>> sorted(browser.cookies.items()) [('foo', 'bar'), ('sha', 'zam')] >>> browser.cookies.clearAll() >>> len(browser.cookies) 0 Many more examples, and a discussion of the additional methods available, can be found in cookies.txt. Navigation and Link Objects --------------------------- If you want to simulate clicking on a link, get the link and `click` on it. In the `navigate.html` file there are several links set up to demonstrate the capabilities of the link objects and their `click` method. The simplest way to get a link is via the anchor text. In other words the text you would see in a browser (text and url searches are substring searches): >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...Link Text...' >>> link = browser.getLink('Link Text') >>> link Link objects comply with the ILink interface. >>> verifyObject(interfaces.ILink, link) True Links expose several attributes for easy access. >>> link.text 'Link Text' >>> link.tag # links can also be image maps. 'a' >>> link.url # it's normalized 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text' >>> link.attrs {'href': 'navigate.html?message=By+Link+Text'} Links can be "clicked" and the browser will navigate to the referenced URL. >>> link.click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text' >>> browser.contents '...Message: By Link Text...' When finding a link by its text, whitespace is normalized. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...> Link Text \n with Whitespace\tNormalization (and parens) >> link = browser.getLink('Link Text with Whitespace Normalization ' ... '(and parens)') >>> link >>> link.text 'Link Text with Whitespace Normalization (and parens)' >>> link.click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text+with+Normalization' >>> browser.contents '...Message: By Link Text with Normalization...' When a link text matches more than one link, by default the first one is chosen. You can, however, specify the index of the link and thus retrieve a later matching link: >>> browser.getLink('Link Text') >>> browser.getLink('Link Text', index=1) Note that clicking a link object after its browser page has expired will generate an error. >>> link.click() Traceback (most recent call last): ... ExpiredError You can also find the link by its URL, >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...Using the URL...' >>> browser.getLink(url='?message=By+URL').click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+URL' >>> browser.contents '...Message: By URL...' or its id: >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...By Anchor Id...' >>> browser.getLink(id='anchorid').click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Id' >>> browser.contents '...Message: By Id...' You thought we were done here? Not so quickly. The `getLink` method also supports image maps, though not by specifying the coordinates, but using the area's id: >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> link = browser.getLink(id='zope3') >>> link.tag 'area' >>> link.click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name' >>> browser.contents '...Message: Zope 3 Name...' Getting a nonexistent link raises an exception. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.getLink('This does not exist') Traceback (most recent call last): ... LinkNotFoundError A convenience method is provided to follow links; this uses the same arguments as `getLink`, but clicks on the link instead of returning the link object. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...Link Text...' >>> browser.follow('Link Text') >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text' >>> browser.contents '...Message: By Link Text...' >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.follow(url='?message=By+URL') >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+URL' >>> browser.contents '...Message: By URL...' >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.follow(id='zope3') >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name' >>> browser.contents '...Message: Zope 3 Name...' Attempting to follow links that don't exist raises the same exception as asking for the link object: >>> browser.follow('This does not exist') Traceback (most recent call last): ... LinkNotFoundError Other Navigation ---------------- Like in any normal browser, you can reload a page: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.url 'http://localhost/@@/testbrowser/simple.html' >>> browser.reload() >>> browser.url 'http://localhost/@@/testbrowser/simple.html' You can also go back: >>> browser.open('http://localhost/@@/testbrowser/notitle.html') >>> browser.url 'http://localhost/@@/testbrowser/notitle.html' >>> browser.goBack() >>> browser.url 'http://localhost/@@/testbrowser/simple.html' Controls -------- One of the most important features of the browser is the ability to inspect and fill in values for the controls of input forms. To do so, let's first open a page that has a bunch of controls: >>> browser.open('http://localhost/@@/testbrowser/controls.html') Obtaining a Control ~~~~~~~~~~~~~~~~~~~ You look up browser controls with the 'getControl' method. The default first argument is 'label', and looks up the form on the basis of any associated label. >>> control = browser.getControl('Text Control') >>> control >>> browser.getControl(label='Text Control') # equivalent If you request a control that doesn't exist, the code raises a LookupError: >>> browser.getControl('Does Not Exist') Traceback (most recent call last): ... LookupError: label 'Does Not Exist' If you request a control with an ambiguous lookup, the code raises an AmbiguityError. >>> browser.getControl('Ambiguous Control') Traceback (most recent call last): ... AmbiguityError: label 'Ambiguous Control' This is also true if an option in a control is ambiguous in relation to the control itself. >>> browser.getControl('Sub-control Ambiguity') Traceback (most recent call last): ... AmbiguityError: label 'Sub-control Ambiguity' Ambiguous controls may be specified using an index value. We use the control's value attribute to show the two controls; this attribute is properly introduced below. >>> browser.getControl('Ambiguous Control', index=0) >>> browser.getControl('Ambiguous Control', index=0).value 'First' >>> browser.getControl('Ambiguous Control', index=1).value 'Second' >>> browser.getControl('Sub-control Ambiguity', index=0) >>> browser.getControl('Sub-control Ambiguity', index=1).optionValue 'ambiguous' Label searches are against stripped, whitespace-normalized, no-tag versions of the text. Text applied to searches is also stripped and whitespace normalized. The search finds results if the text search finds the whole words of your text in a label. Thus, for instance, a search for 'Add' will match the label 'Add a Client' but not 'Address'. Case is honored. >>> browser.getControl('Label Needs Whitespace Normalization') >>> browser.getControl('label needs whitespace normalization') Traceback (most recent call last): ... LookupError: label 'label needs whitespace normalization' >>> browser.getControl(' Label Needs Whitespace ') >>> browser.getControl('Whitespace') >>> browser.getControl('hitespace') Traceback (most recent call last): ... LookupError: label 'hitespace' >>> browser.getControl('[non word characters should not confuse]') Multiple labels can refer to the same control (simply because that is possible in the HTML 4.0 spec). >>> browser.getControl('Multiple labels really') >>> browser.getControl('really are possible') >>> browser.getControl('really') # OK: ambiguous labels, but not ambiguous control A label can be connected with a control using the 'for' attribute and also by containing a control. >>> browser.getControl( ... 'Labels can be connected by containing their respective fields') Get also accepts one other search argument, 'name'. Only one of 'label' and 'name' may be used at a time. The 'name' keyword searches form field names. >>> browser.getControl(name='text-value') >>> browser.getControl(name='ambiguous-control-name') Traceback (most recent call last): ... AmbiguityError: name 'ambiguous-control-name' >>> browser.getControl(name='does-not-exist') Traceback (most recent call last): ... LookupError: name 'does-not-exist' >>> browser.getControl(name='ambiguous-control-name', index=1).value 'Second' Combining 'label' and 'name' raises a ValueError, as does supplying neither of them. >>> browser.getControl(label='Ambiguous Control', name='ambiguous-control-name') Traceback (most recent call last): ... ValueError: Supply one and only one of "label" and "name" as arguments >>> browser.getControl() Traceback (most recent call last): ... ValueError: Supply one and only one of "label" and "name" as arguments Radio and checkbox fields are unusual in that their labels and names may point to different objects: names point to logical collections of radio buttons or checkboxes, but labels may only be used for individual choices within the logical collection. This means that obtaining a radio button by label gets a different object than obtaining the radio collection by name. Select options may also be searched by label. >>> browser.getControl(name='radio-value') >>> browser.getControl('Zwei') >>> browser.getControl('One') >>> browser.getControl('Tres') Characteristics of controls and subcontrols are discussed below. Control Objects ~~~~~~~~~~~~~~~ Controls provide IControl. >>> ctrl = browser.getControl('Text Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True They have several useful attributes: - the name as which the control is known to the form: >>> ctrl.name 'text-value' - the value of the control, which may also be set: >>> ctrl.value 'Some Text' >>> ctrl.value = 'More Text' >>> ctrl.value 'More Text' - the type of the control: >>> ctrl.type 'text' - a flag describing whether the control is disabled: >>> ctrl.disabled False - and a flag to tell us whether the control can have multiple values: >>> ctrl.multiple False Additionally, controllers for select, radio, and checkbox provide IListControl. These fields have four other attributes and an additional method: >>> ctrl = browser.getControl('Multiple Select Control') >>> ctrl >>> ctrl.disabled False >>> ctrl.multiple True >>> verifyObject(interfaces.IListControl, ctrl) True - 'options' lists all available value options. >>> ctrl.options ['1', '2', '3'] - 'displayOptions' lists all available options by label. The 'label' attribute on an option has precedence over its contents, which is why our last option is 'Third' in the display. >>> ctrl.displayOptions ['Un', 'Deux', 'Third'] - 'displayValue' lets you get and set the displayed values of the control of the select box, rather than the actual values. >>> ctrl.value [] >>> ctrl.displayValue [] >>> ctrl.displayValue = ['Un', 'Deux'] >>> ctrl.displayValue ['Un', 'Deux'] >>> ctrl.value ['1', '2'] - 'controls' gives you a list of the subcontrol objects in the control (subcontrols are discussed below). >>> ctrl.controls [, , ] - The 'getControl' method lets you get subcontrols by their label or their value. >>> ctrl.getControl('Un') >>> ctrl.getControl('Deux') >>> ctrl.getControl('Trois') # label attribute >>> ctrl.getControl('Third') # contents >>> browser.getControl('Third') # ambiguous in the browser, so useful Traceback (most recent call last): ... AmbiguityError: label 'Third' Finally, submit controls provide ISubmitControl, and image controls provide IImageSubmitControl, which extents ISubmitControl. These both simply add a 'click' method. For image submit controls, you may also provide a coordinates argument, which is a tuple of (x, y). These submit the forms, and are demonstrated below as we examine each control individually. ItemControl Objects ~~~~~~~~~~~~~~~~~~~ As introduced briefly above, using labels to obtain elements of a logical radio button or checkbox collection returns item controls, which are parents. Manipulating the value of these controls affects the parent control. >>> browser.getControl(name='radio-value').value ['2'] >>> browser.getControl('Zwei').optionValue # read-only. '2' >>> browser.getControl('Zwei').selected True >>> verifyObject(interfaces.IItemControl, browser.getControl('Zwei')) True >>> browser.getControl('Ein').selected = True >>> browser.getControl('Ein').selected True >>> browser.getControl('Zwei').selected False >>> browser.getControl(name='radio-value').value ['1'] >>> browser.getControl('Ein').selected = False >>> browser.getControl(name='radio-value').value [] >>> browser.getControl('Zwei').selected = True Checkbox collections behave similarly, as shown below. Controls with subcontrols-- Various Controls ~~~~~~~~~~~~~~~~ The various types of controls are demonstrated here. - Text Control The text control we already introduced above. - Password Control >>> ctrl = browser.getControl('Password Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value 'Password' >>> ctrl.value = 'pass now' >>> ctrl.value 'pass now' >>> ctrl.disabled False >>> ctrl.multiple False - Hidden Control >>> ctrl = browser.getControl(name='hidden-value') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value 'Hidden' >>> ctrl.value = 'More Hidden' >>> ctrl.disabled False >>> ctrl.multiple False - Text Area Control >>> ctrl = browser.getControl('Text Area Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value ' Text inside\n area!\n ' >>> ctrl.value = 'A lot of\n text.' >>> ctrl.disabled False >>> ctrl.multiple False - File Control File controls are used when a form has a file-upload field. To specify data, call the add_file method, passing: - A file-like object - a content type, and - a file name >>> ctrl = browser.getControl('File Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value is None True >>> import cStringIO >>> ctrl.add_file(cStringIO.StringIO('File contents'), ... 'text/plain', 'test.txt') The file control (like the other controls) also knows if it is disabled or if it can have multiple values. >>> ctrl.disabled False >>> ctrl.multiple False - Selection Control (Single-Valued) >>> ctrl = browser.getControl('Single Select Control') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value ['1'] >>> ctrl.value = ['2'] >>> ctrl.disabled False >>> ctrl.multiple False >>> ctrl.options ['1', '2', '3'] >>> ctrl.displayOptions ['Uno', 'Dos', 'Third'] >>> ctrl.displayValue ['Dos'] >>> ctrl.displayValue = ['Tres'] >>> ctrl.displayValue ['Third'] >>> ctrl.displayValue = ['Dos'] >>> ctrl.displayValue ['Dos'] >>> ctrl.displayValue = ['Third'] >>> ctrl.displayValue ['Third'] >>> ctrl.value ['3'] - Selection Control (Multi-Valued) This was already demonstrated in the introduction to control objects above. - Checkbox Control (Single-Valued; Unvalued) >>> ctrl = browser.getControl(name='single-unvalued-checkbox-value') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value True >>> ctrl.value = False >>> ctrl.disabled False >>> ctrl.multiple True >>> ctrl.options [True] >>> ctrl.displayOptions ['Single Unvalued Checkbox'] >>> ctrl.displayValue [] >>> verifyObject( ... interfaces.IItemControl, ... browser.getControl('Single Unvalued Checkbox')) True >>> browser.getControl('Single Unvalued Checkbox').optionValue 'on' >>> browser.getControl('Single Unvalued Checkbox').selected False >>> ctrl.displayValue = ['Single Unvalued Checkbox'] >>> ctrl.displayValue ['Single Unvalued Checkbox'] >>> browser.getControl('Single Unvalued Checkbox').selected True >>> browser.getControl('Single Unvalued Checkbox').selected = False >>> browser.getControl('Single Unvalued Checkbox').selected False >>> ctrl.displayValue [] >>> browser.getControl( ... name='single-disabled-unvalued-checkbox-value').disabled True - Checkbox Control (Single-Valued, Valued) >>> ctrl = browser.getControl(name='single-valued-checkbox-value') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value ['1'] >>> ctrl.value = [] >>> ctrl.disabled False >>> ctrl.multiple True >>> ctrl.options ['1'] >>> ctrl.displayOptions ['Single Valued Checkbox'] >>> ctrl.displayValue [] >>> verifyObject( ... interfaces.IItemControl, ... browser.getControl('Single Valued Checkbox')) True >>> browser.getControl('Single Valued Checkbox').selected False >>> browser.getControl('Single Valued Checkbox').optionValue '1' >>> ctrl.displayValue = ['Single Valued Checkbox'] >>> ctrl.displayValue ['Single Valued Checkbox'] >>> browser.getControl('Single Valued Checkbox').selected True >>> browser.getControl('Single Valued Checkbox').selected = False >>> browser.getControl('Single Valued Checkbox').selected False >>> ctrl.displayValue [] - Checkbox Control (Multi-Valued) >>> ctrl = browser.getControl(name='multi-checkbox-value') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value ['1', '3'] >>> ctrl.value = ['1', '2'] >>> ctrl.disabled False >>> ctrl.multiple True >>> ctrl.options ['1', '2', '3'] >>> ctrl.displayOptions ['One', 'Two', 'Three'] >>> ctrl.displayValue ['One', 'Two'] >>> ctrl.displayValue = ['Two'] >>> ctrl.value ['2'] >>> browser.getControl('Two').optionValue '2' >>> browser.getControl('Two').selected True >>> verifyObject(interfaces.IItemControl, browser.getControl('Two')) True >>> browser.getControl('Three').selected = True >>> browser.getControl('Three').selected True >>> browser.getControl('Two').selected True >>> ctrl.value ['2', '3'] >>> browser.getControl('Two').selected = False >>> ctrl.value ['3'] >>> browser.getControl('Three').selected = False >>> ctrl.value [] - Radio Control This is how you get a radio button based control: >>> ctrl = browser.getControl(name='radio-value') This shows the existing value of the control, as it was in the HTML received from the server: >>> ctrl.value ['2'] We can then unselect it: >>> ctrl.value = [] >>> ctrl.value [] We can also reselect it: >>> ctrl.value = ['2'] >>> ctrl.value ['2'] displayValue shows the text the user would see next to the control: >>> ctrl.displayValue ['Zwei'] This is just unit testing: >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.disabled False >>> ctrl.multiple False >>> ctrl.options ['1', '2', '3'] >>> ctrl.displayOptions ['Ein', 'Zwei', 'Drei'] >>> ctrl.displayValue = ['Ein'] >>> ctrl.value ['1'] >>> ctrl.displayValue ['Ein'] The radio control subcontrols were illustrated above. - Image Control >>> ctrl = browser.getControl(name='image-value') >>> ctrl >>> verifyObject(interfaces.IImageSubmitControl, ctrl) True >>> ctrl.value '' >>> ctrl.disabled False >>> ctrl.multiple False - Submit Control >>> ctrl = browser.getControl(name='submit-value') >>> ctrl >>> browser.getControl('Submit This') # value of submit button is a label >>> browser.getControl('Standard Submit Control') # label tag is legal >>> browser.getControl('Submit') # multiple labels, but same control >>> verifyObject(interfaces.ISubmitControl, ctrl) True >>> ctrl.value 'Submit This' >>> ctrl.disabled False >>> ctrl.multiple False Using Submitting Controls ~~~~~~~~~~~~~~~~~~~~~~~~~ Both the submit and image type should be clickable and submit the form: >>> browser.getControl('Text Control').value = 'Other Text' >>> browser.getControl('Submit').click() >>> print browser.contents ... Other Text ... Submit This ... Note that if you click a submit object after the associated page has expired, you will get an error. >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> ctrl = browser.getControl('Submit') >>> ctrl.click() >>> ctrl.click() Traceback (most recent call last): ... ExpiredError All the above also holds true for the image control: >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> browser.getControl('Text Control').value = 'Other Text' >>> browser.getControl(name='image-value').click() >>> print browser.contents ... Other Text ... 1 1 ... >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> ctrl = browser.getControl(name='image-value') >>> ctrl.click() >>> ctrl.click() Traceback (most recent call last): ... ExpiredError But when sending an image, you can also specify the coordinate you clicked: >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> browser.getControl(name='image-value').click((50,25)) >>> print browser.contents ... 50 25 ... Forms ----- Because pages can have multiple forms with like-named controls, it is sometimes necessary to access forms by name or id. The browser's `forms` attribute can be used to do so. The key value is the form's name or id. If more than one form has the same name or id, the first one will be returned. >>> browser.open('http://localhost/@@/testbrowser/forms.html') >>> form = browser.getForm(name='one') Form instances conform to the IForm interface. >>> verifyObject(interfaces.IForm, form) True The form exposes several attributes related to forms: - The name of the form: >>> form.name 'one' - The id of the form: >>> form.id '1' - The action (target URL) when the form is submitted: >>> form.action 'http://localhost/@@/testbrowser/forms.html' - The method (HTTP verb) used to transmit the form data: >>> form.method 'GET' Besides those attributes, you have also a couple of methods. Like for the browser, you can get control objects, but limited to the current form... >>> form.getControl(name='text-value') ...and submit the form. >>> form.submit('Submit') >>> print browser.contents ... First Text ... Submitting also works without specifying a control, as shown below, which is it's primary reason for existing in competition with the control submission discussed above. Now let me show you briefly that looking up forms is sometimes important. In the `forms.html` template, we have four forms all having a text control named `text-value`. Now, if I use the browser's `get` method, >>> browser.getControl(name='text-value') Traceback (most recent call last): ... AmbiguityError: name 'text-value' >>> browser.getControl('Text Control') Traceback (most recent call last): ... AmbiguityError: label 'Text Control' I'll always get an ambiguous form field. I can use the index argument, or with the `getForm` method I can disambiguate by searching only within a given form: >>> form = browser.getForm('2') >>> form.getControl(name='text-value').value 'Second Text' >>> form.submit('Submit') >>> browser.contents '...Second Text...' >>> form = browser.getForm('2') >>> form.getControl('Submit').click() >>> browser.contents '...Second Text...' >>> browser.getForm('3').getControl('Text Control').value 'Third Text' The last form on the page does not have a name, an id, or a submit button. Working with it is still easy, thanks to a index attribute that guarantees order. (Forms without submit buttons are sometimes useful for JavaScript.) >>> form = browser.getForm(index=3) >>> form.submit() >>> browser.contents '...Fourth Text...Submitted without the submit button....' If a form is requested that does not exists, an exception will be raised. >>> form = browser.getForm('does-not-exist') Traceback (most recent call last): LookupError If the HTML page contains only one form, no arguments to `getForm` are needed: >>> oneform = Browser() >>> oneform.open('http://localhost/@@/testbrowser/oneform.html') >>> form = oneform.getForm() If the HTML page contains more than one form, `index` is needed to disambiguate if no other arguments are provided: >>> browser.getForm() Traceback (most recent call last): ValueError: if no other arguments are given, index is required. Submitting a posts body directly -------------------------------- In addition to the open method, zope.testbrowser.testing.Browser has a ``post`` method that allows a request body to be supplied. This method is particularly helpful when testing Ajax methods. Let's visit a page that echos it's request: >>> browser.open('http://localhost/@@echo.html') >>> print browser.contents, HTTP_USER_AGENT: Python-urllib/2.4 HTTP_CONNECTION: close HTTP_COOKIE: REMOTE_ADDR: 127.0.0.1 HTTP_ACCEPT_LANGUAGE: en-US REQUEST_METHOD: GET HTTP_HOST: localhost PATH_INFO: /@@echo.html SERVER_PROTOCOL: HTTP/1.1 QUERY_STRING: Body: '' Now, we'll try a post. The post method takes a URL, a data string, and an optional content type. If we just pass a string, then a URL-encoded query string is assumed: >>> browser.post('http://localhost/@@echo.html', 'x=1&y=2') >>> print browser.contents, CONTENT_LENGTH: 7 HTTP_USER_AGENT: Python-urllib/2.4 HTTP_CONNECTION: close HTTP_COOKIE: REMOTE_ADDR: 127.0.0.1 HTTP_ACCEPT_LANGUAGE: en-US y: 2 REQUEST_METHOD: POST HTTP_HOST: localhost PATH_INFO: /@@echo.html CONTENT_TYPE: application/x-www-form-urlencoded SERVER_PROTOCOL: HTTP/1.1 QUERY_STRING: x: 1 Body: '' The body is empty because it is consumed to get form data. We can pass a content-type explicitly: >>> browser.post('http://localhost/@@echo.html', ... '{"x":1,"y":2}', 'application/x-javascript') >>> print browser.contents, CONTENT_LENGTH: 13 HTTP_USER_AGENT: Python-urllib/2.4 HTTP_CONNECTION: close HTTP_COOKIE: REMOTE_ADDR: 127.0.0.1 HTTP_ACCEPT_LANGUAGE: en-US REQUEST_METHOD: POST HTTP_HOST: localhost PATH_INFO: /@@echo.html CONTENT_TYPE: application/x-javascript SERVER_PROTOCOL: HTTP/1.1 Body: '{"x":1,"y":2}' Here, the body is left in place because it isn't form data. Performance Testing ------------------- Browser objects keep up with how much time each request takes. This can be used to ensure a particular request's performance is within a tolerable range. Be very careful using raw seconds, cross-machine differences can be huge, pystones is usually a better choice. >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.lastRequestSeconds < 10 # really big number for safety True >>> browser.lastRequestPystones < 10000 # really big number for safety True Handling Errors when using Zope 3's Publisher --------------------------------------------- A very useful feature of the publisher is the automatic graceful handling of application errors, such as invalid URLs: >>> browser.open('http://localhost/invalid') Traceback (most recent call last): ... HTTPError: HTTP Error 404: Not Found Note that the above error was thrown by ``mechanize`` and not by the publisher. For debugging purposes, however, it can be very useful to see the original exception caused by the application. In those cases you can set the ``handleErrors`` property of the browser to ``False``. It is defaulted to ``True``: >>> browser.handleErrors True So when we tell the publisher not to handle the errors, >>> browser.handleErrors = False we get a different, Zope internal error: >>> browser.open('http://localhost/invalid') Traceback (most recent call last): ... NotFound: Object: , name: u'invalid' NB: Setting the handleErrors attribute to False will only change anything if the http server you're testing is using Zope 3's publisher or can otherwise respond appropriately to an 'X-zope-handle-errors' header in requests. When the testbrowser is raising HttpErrors, the errors still hit the test. Sometimes we don't want that to happen, in situations where there are edge cases that will cause the error to be predictably but infrequently raised. Time is a primary cause of this. To get around this, one can set the raiseHttpErrors to False. >>> browser.handleErrors = True >>> browser.raiseHttpErrors = False This will cause HttpErrors not to propagate. >>> browser.open('http://localhost/invalid') The headers are still there, though. >>> '404 Not Found' in str(browser.headers) True If we don't handle the errors, and allow internal ones to propagate, however, this flag doesn't affect things. >>> browser.handleErrors = False >>> browser.open('http://localhost/invalid') Traceback (most recent call last): ... NotFound: Object: , name: u'invalid' >>> browser.raiseHttpErrors = True Hand-Holding ------------ Instances of the various objects ensure that users don't set incorrect instance attributes accidentally. >>> browser.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Browser' object has no attribute 'nonexistant' >>> form.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Form' object has no attribute 'nonexistant' >>> control.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Control' object has no attribute 'nonexistant' >>> link.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Link' object has no attribute 'nonexistant' ======= CHANGES ======= 3.11.1 (2011-01-24) ------------------- - Fixing brown bag release 3.11.0. 3.11.0 (2011-01-24) ------------------- - Added `wsgi_intercept` support (came from ``zope.app.wsgi.testlayer``). 3.10.4 (2011-01-14) ------------------- - Move the over-the-wire.txt doctest out of the TestBrowserLayer as it doesn't need or use it. - Fix test compatibility with zope.app.testing 3.8.1. 3.10.3 (2010-10-15) ------------------- - Fixed backwards compatibility with ``zope.app.wsgi.testlayer``. 3.10.2 (2010-10-15) ------------------- - Fixed Python 2.7 compatibility in Browser.handleErrors. 3.10.1 (2010-09-21) ------------------- - Fixed a bug that caused the ``Browser`` to keep it's previous ``contents`` The places are: - Link.click() - SubmitControl.click() - ImageControl.click() - Form.submit() - Also adjusted exception messages at the above places to match pre version 3.4.1 messages. 3.10.0 (2010-09-14) ------------------- - LP #98437: use mechanize's built-in ``submit()`` to submit forms, allowing mechanize to set the "Referer:" (sic) header appropriately. - Fixed tests to run with ``zope.app.testing`` 3.8 and above. 3.9.0 (2010-05-17) ------------------ - LP #568806: Update dependency ``mechanize >= 0.2.0``, which now includes the ``ClientForm`` APIs. Remove use of ``urllib2`` APIs (incompatible with ``mechanize 0.2.0``) in favor of ``mechanize`` equivalents. Thanks to John J. Lee for the patch. - Use stdlib ``doctest`` module, instead of ``zope.testing.doctest``. - **Caution:** This version is no longer fully compatible with Python 2.4: ``handleErrors = False`` no longer works. 3.8.1 (2010-04-19) ------------------ - Pinned dependency on mechanize to prevent use of the upcoming 0.2.0 release before we have time to adjust to its API changes. - LP #98396: testbrowser resolves relative URLs incorrectly. 3.8.0 (2010-03-05) ------------------ - Added ``follow`` convenience method which gets and follows a link. 3.7.0 (2009-12-17) ------------------ - Moved zope.app.testing dependency into the scope of the PublisherConnection class. Zope2 specifies its own PublisherConnection which isn't dependent on zope.app.testing. - Fixed LP #419119: return None when the browser has no contents instead of raising an exception. 3.7.0a1 (2009-08-29) -------------------- - Remove dependency on zope.app.publisher in favor of zope.browserpage, zope.browserresource and zope.ptresource. - Remove dependencies on zope.app.principalannotation and zope.securitypolicy by using the simple PermissiveSecurityPolicy. We aren't testing security in our tests. - Replaced the testing dependency on zope.app.zcmlfiles with explicit dependencies of a minimal set of packages. - Remove unneeded zope.app.authentication from ftesting.zcml. - Test dependency on zope.securitypolicy instead of its app variant. 3.6.0a2 (2009-01-31) -------------------- - Test dependency on zope.site.folder instead of zope.app.folder. - Remove useless test dependency in zope.app.component. 3.6.0a1 (2009-01-08) -------------------- - Author e-mail to zope-dev rather than zope3-dev. - New lines are no longer stripped in XML and HTML code contained in a textarea; fix requires ClientForm >= 0.2.10 (LP #268139). - Added ``cookies`` attribute to browser for easy manipulation of browser cookies. See brief example in main documentation, plus new ``cookies.txt`` documentation. 3.5.1 (2008-10-10) ------------------ - Provide a work around for a mechanize/urllib2 bug on Python 2.6 missing 'timeout' attribute on 'Request' base class. - Provide a work around for a mechanize/urllib2 bug in creating request objects that won't handle fragment URLs correctly. 3.5.0 (2008-03-30) ------------------ - Added a zope.testbrowser.testing.Browser.post method that allows tests to supply a body and a content type. This is handy for testing Ajax requests with non-form input (e.g. JSON). - Remove vendor import of mechanize. - Fix bug that caused HTTP exception tracebacks to differ between version 3.4.0 and 3.4.1. - Workaround for bug in Python Cookie.SimpleCookie when handling unicode strings. - Fix bug introduced in 3.4.1 that created incompatible tracebacks in doctests. This necessitated adding a patched mechanize to the source tree; patches have been sent to the mechanize project. - Fix https://bugs.launchpad.net/bugs/149517 by adding zope.interface and zope.schema as real dependencies - Fix browser.getLink documentation that was not updated since the last API modification. - Move tests for fixed bugs to a separate file. - Removed non-functional and undocumented code intended to help test servers using virtual hosting. 3.4.2 (2007-10-31) ------------------ - Resolve ``ZopeSecurityPolicy`` deprecation warning. 3.4.1 (2007-09-01) ------------------ * Updated to mechanize 0.1.7b and ClientForm 0.2.7. These are now pulled in via egg dependencies. * ``zope.testbrowser`` now works on Python 2.5. 3.4.0 (2007-06-04) ------------------ * Added the ability to suppress raising exceptions on HTTP errors (``raiseHttpErrors`` attribute). * Made the tests more resilient to HTTP header formatting changes with the REnormalizer. 3.4.0a1 (2007-04-22) -------------------- Initial release as a separate project, corresponds to zope.testbrowser from Zope 3.4.0a1 Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Internet :: WWW/HTTP zope2.13-2.13.21/source/zope.testbrowser/pip-egg-info/0000755000175000017500000000000012214017652021257 5ustar arnauarnauzope2.13-2.13.21/source/zope.testbrowser/pip-egg-info/zope.testbrowser.egg-info/0000755000175000017500000000000012214017652026310 5ustar arnauarnauzope2.13-2.13.21/source/zope.testbrowser/pip-egg-info/zope.testbrowser.egg-info/PKG-INFO0000644000175000017500000017061712214017652027421 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.testbrowser Version: 3.11.1 Summary: Programmable browser for functional black-box tests Home-page: http://pypi.python.org/pypi/zope.testbrowser Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: .. contents:: ``zope.testbrowser`` provides an easy-to-use programmable web browser with special focus on testing. It is used in Zope, but it's not Zope specific at all. For instance, it can be used to test or otherwise interact with any web site. ====================== Detailed Documentation ====================== Different Browsers ------------------ HTTP Browser ~~~~~~~~~~~~ The ``zope.testbrowser.browser`` module exposes a ``Browser`` class that simulates a web browser similar to Mozilla Firefox or IE. >>> from zope.testbrowser.browser import Browser >>> browser = Browser() This version of the browser object can be used to access any web site just as you would do using a normal web browser. WSGI Test Browser ~~~~~~~~~~~~~~~~~ There is also a special version of the ``Browser`` class which uses `wsgi_intercept`_ and can be used to do functional testing of WSGI applications, it can be imported from ``zope.testbrowser.wsgi``: >>> from zope.testbrowser.wsgi import Browser >>> browser = Browser() .. _`wsgi_intercept`: http://pypi.python.org/pypi/wsgi_intercept To use this browser you have to: * use the `wsgi` extra of the ``zope.testbrowser`` egg, * write a subclass of ``zope.testbrowser.wsgi.Layer`` and override the ``make_wsgi_app`` method, * use an instance of the class as the test layer of your test. Example: >>> import zope.testbrowser.wsgi >>> class SimpleLayer(zope.testbrowser.wsgi.Layer): ... def make_wsgi_app(self): ... return simple_app Where ``simple_app`` is the callable of your WSGI application. Zope 3 Test Browser ~~~~~~~~~~~~~~~~~~~ There is also a special version of the ``Browser`` class used to do functional testing of Zope 3 applications, it can be imported from ``zope.testbrowser.testing``: >>> from zope.testbrowser.testing import Browser >>> browser = Browser() Bowser Usage ------------ All browsers are used the same way. An initial page to load can be passed to the ``Browser`` constructor: >>> browser = Browser('http://localhost/@@/testbrowser/simple.html') >>> browser.url 'http://localhost/@@/testbrowser/simple.html' The browser can send arbitrary headers; this is helpful for setting the "Authorization" header or a language value, so that your tests format values the way you expect in your tests, if you rely on zope.i18n locale-based formatting or a similar approach. >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw') >>> browser.addHeader('Accept-Language', 'en-US') An existing browser instance can also `open` web pages: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.url 'http://localhost/@@/testbrowser/simple.html' Once you have opened a web page initially, best practice for writing testbrowser doctests suggests using 'click' to navigate further (as discussed below), except in unusual circumstances. The test browser complies with the IBrowser interface; see ``zope.testbrowser.interfaces`` for full details on the interface. >>> from zope.testbrowser import interfaces >>> from zope.interface.verify import verifyObject >>> verifyObject(interfaces.IBrowser, browser) True Page Contents ------------- The contents of the current page are available: >>> print browser.contents Simple Page

Simple Page

Making assertions about page contents is easy. >>> '

Simple Page

' in browser.contents True Utilizing the doctest facilities, it also possible to do: >>> browser.contents '...

Simple Page

...' Note: Unfortunately, ellipsis (...) cannot be used at the beginning of the output (this is a limitation of doctest). Checking for HTML ----------------- Not all URLs return HTML. Of course our simple page does: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.isHtml True But if we load an image (or other binary file), we do not get HTML: >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif') >>> browser.isHtml False HTML Page Title ---------------- Another useful helper property is the title: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.title 'Simple Page' If a page does not provide a title, it is simply ``None``: >>> browser.open('http://localhost/@@/testbrowser/notitle.html') >>> browser.title However, if the output is not HTML, then an error will occur trying to access the title: >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif') >>> browser.title Traceback (most recent call last): ... BrowserStateError: not viewing HTML Headers ------- As you can see, the `contents` of the browser does not return any HTTP headers. The headers are accessible via a separate attribute, which is an ``httplib.HTTPMessage`` instance (httplib is a part of Python's standard library): >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.headers The headers can be accessed as a string: >>> print browser.headers Status: 200 OK Content-Length: 123 Content-Type: text/html;charset=utf-8 X-Powered-By: Zope (www.zope.org), Python (www.python.org) Or as a mapping: >>> browser.headers['content-type'] 'text/html;charset=utf-8' Cookies ------- When a Set-Cookie header is available, it can be found in the headers, as seen above. Here, we use a view that will make the server set cookies with the values we provide. >>> browser.open('http://localhost/set_cookie.html?name=foo&value=bar') >>> browser.headers['set-cookie'].replace(';', '') 'foo=bar' It is also available in the browser's ``cookies`` attribute. This is an extended mapping interface that allows getting, setting, and deleting the cookies that the browser is remembering *for the current url*. Here are a few examples. >>> browser.cookies['foo'] 'bar' >>> browser.cookies.keys() ['foo'] >>> browser.cookies.values() ['bar'] >>> browser.cookies.items() [('foo', 'bar')] >>> 'foo' in browser.cookies True >>> 'bar' in browser.cookies False >>> len(browser.cookies) 1 >>> print(dict(browser.cookies)) {'foo': 'bar'} >>> browser.cookies['sha'] = 'zam' >>> len(browser.cookies) 2 >>> sorted(browser.cookies.items()) [('foo', 'bar'), ('sha', 'zam')] >>> browser.open('http://localhost/get_cookie.html') >>> print browser.headers.get('set-cookie') None >>> print browser.contents # server got the cookie change foo: bar sha: zam >>> sorted(browser.cookies.items()) [('foo', 'bar'), ('sha', 'zam')] >>> browser.cookies.clearAll() >>> len(browser.cookies) 0 Many more examples, and a discussion of the additional methods available, can be found in cookies.txt. Navigation and Link Objects --------------------------- If you want to simulate clicking on a link, get the link and `click` on it. In the `navigate.html` file there are several links set up to demonstrate the capabilities of the link objects and their `click` method. The simplest way to get a link is via the anchor text. In other words the text you would see in a browser (text and url searches are substring searches): >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...Link Text...' >>> link = browser.getLink('Link Text') >>> link Link objects comply with the ILink interface. >>> verifyObject(interfaces.ILink, link) True Links expose several attributes for easy access. >>> link.text 'Link Text' >>> link.tag # links can also be image maps. 'a' >>> link.url # it's normalized 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text' >>> link.attrs {'href': 'navigate.html?message=By+Link+Text'} Links can be "clicked" and the browser will navigate to the referenced URL. >>> link.click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text' >>> browser.contents '...Message: By Link Text...' When finding a link by its text, whitespace is normalized. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...> Link Text \n with Whitespace\tNormalization (and parens) >> link = browser.getLink('Link Text with Whitespace Normalization ' ... '(and parens)') >>> link >>> link.text 'Link Text with Whitespace Normalization (and parens)' >>> link.click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text+with+Normalization' >>> browser.contents '...Message: By Link Text with Normalization...' When a link text matches more than one link, by default the first one is chosen. You can, however, specify the index of the link and thus retrieve a later matching link: >>> browser.getLink('Link Text') >>> browser.getLink('Link Text', index=1) Note that clicking a link object after its browser page has expired will generate an error. >>> link.click() Traceback (most recent call last): ... ExpiredError You can also find the link by its URL, >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...Using the URL...' >>> browser.getLink(url='?message=By+URL').click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+URL' >>> browser.contents '...Message: By URL...' or its id: >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...By Anchor Id...' >>> browser.getLink(id='anchorid').click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Id' >>> browser.contents '...Message: By Id...' You thought we were done here? Not so quickly. The `getLink` method also supports image maps, though not by specifying the coordinates, but using the area's id: >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> link = browser.getLink(id='zope3') >>> link.tag 'area' >>> link.click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name' >>> browser.contents '...Message: Zope 3 Name...' Getting a nonexistent link raises an exception. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.getLink('This does not exist') Traceback (most recent call last): ... LinkNotFoundError A convenience method is provided to follow links; this uses the same arguments as `getLink`, but clicks on the link instead of returning the link object. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...Link Text...' >>> browser.follow('Link Text') >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text' >>> browser.contents '...Message: By Link Text...' >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.follow(url='?message=By+URL') >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+URL' >>> browser.contents '...Message: By URL...' >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.follow(id='zope3') >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name' >>> browser.contents '...Message: Zope 3 Name...' Attempting to follow links that don't exist raises the same exception as asking for the link object: >>> browser.follow('This does not exist') Traceback (most recent call last): ... LinkNotFoundError Other Navigation ---------------- Like in any normal browser, you can reload a page: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.url 'http://localhost/@@/testbrowser/simple.html' >>> browser.reload() >>> browser.url 'http://localhost/@@/testbrowser/simple.html' You can also go back: >>> browser.open('http://localhost/@@/testbrowser/notitle.html') >>> browser.url 'http://localhost/@@/testbrowser/notitle.html' >>> browser.goBack() >>> browser.url 'http://localhost/@@/testbrowser/simple.html' Controls -------- One of the most important features of the browser is the ability to inspect and fill in values for the controls of input forms. To do so, let's first open a page that has a bunch of controls: >>> browser.open('http://localhost/@@/testbrowser/controls.html') Obtaining a Control ~~~~~~~~~~~~~~~~~~~ You look up browser controls with the 'getControl' method. The default first argument is 'label', and looks up the form on the basis of any associated label. >>> control = browser.getControl('Text Control') >>> control >>> browser.getControl(label='Text Control') # equivalent If you request a control that doesn't exist, the code raises a LookupError: >>> browser.getControl('Does Not Exist') Traceback (most recent call last): ... LookupError: label 'Does Not Exist' If you request a control with an ambiguous lookup, the code raises an AmbiguityError. >>> browser.getControl('Ambiguous Control') Traceback (most recent call last): ... AmbiguityError: label 'Ambiguous Control' This is also true if an option in a control is ambiguous in relation to the control itself. >>> browser.getControl('Sub-control Ambiguity') Traceback (most recent call last): ... AmbiguityError: label 'Sub-control Ambiguity' Ambiguous controls may be specified using an index value. We use the control's value attribute to show the two controls; this attribute is properly introduced below. >>> browser.getControl('Ambiguous Control', index=0) >>> browser.getControl('Ambiguous Control', index=0).value 'First' >>> browser.getControl('Ambiguous Control', index=1).value 'Second' >>> browser.getControl('Sub-control Ambiguity', index=0) >>> browser.getControl('Sub-control Ambiguity', index=1).optionValue 'ambiguous' Label searches are against stripped, whitespace-normalized, no-tag versions of the text. Text applied to searches is also stripped and whitespace normalized. The search finds results if the text search finds the whole words of your text in a label. Thus, for instance, a search for 'Add' will match the label 'Add a Client' but not 'Address'. Case is honored. >>> browser.getControl('Label Needs Whitespace Normalization') >>> browser.getControl('label needs whitespace normalization') Traceback (most recent call last): ... LookupError: label 'label needs whitespace normalization' >>> browser.getControl(' Label Needs Whitespace ') >>> browser.getControl('Whitespace') >>> browser.getControl('hitespace') Traceback (most recent call last): ... LookupError: label 'hitespace' >>> browser.getControl('[non word characters should not confuse]') Multiple labels can refer to the same control (simply because that is possible in the HTML 4.0 spec). >>> browser.getControl('Multiple labels really') >>> browser.getControl('really are possible') >>> browser.getControl('really') # OK: ambiguous labels, but not ambiguous control A label can be connected with a control using the 'for' attribute and also by containing a control. >>> browser.getControl( ... 'Labels can be connected by containing their respective fields') Get also accepts one other search argument, 'name'. Only one of 'label' and 'name' may be used at a time. The 'name' keyword searches form field names. >>> browser.getControl(name='text-value') >>> browser.getControl(name='ambiguous-control-name') Traceback (most recent call last): ... AmbiguityError: name 'ambiguous-control-name' >>> browser.getControl(name='does-not-exist') Traceback (most recent call last): ... LookupError: name 'does-not-exist' >>> browser.getControl(name='ambiguous-control-name', index=1).value 'Second' Combining 'label' and 'name' raises a ValueError, as does supplying neither of them. >>> browser.getControl(label='Ambiguous Control', name='ambiguous-control-name') Traceback (most recent call last): ... ValueError: Supply one and only one of "label" and "name" as arguments >>> browser.getControl() Traceback (most recent call last): ... ValueError: Supply one and only one of "label" and "name" as arguments Radio and checkbox fields are unusual in that their labels and names may point to different objects: names point to logical collections of radio buttons or checkboxes, but labels may only be used for individual choices within the logical collection. This means that obtaining a radio button by label gets a different object than obtaining the radio collection by name. Select options may also be searched by label. >>> browser.getControl(name='radio-value') >>> browser.getControl('Zwei') >>> browser.getControl('One') >>> browser.getControl('Tres') Characteristics of controls and subcontrols are discussed below. Control Objects ~~~~~~~~~~~~~~~ Controls provide IControl. >>> ctrl = browser.getControl('Text Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True They have several useful attributes: - the name as which the control is known to the form: >>> ctrl.name 'text-value' - the value of the control, which may also be set: >>> ctrl.value 'Some Text' >>> ctrl.value = 'More Text' >>> ctrl.value 'More Text' - the type of the control: >>> ctrl.type 'text' - a flag describing whether the control is disabled: >>> ctrl.disabled False - and a flag to tell us whether the control can have multiple values: >>> ctrl.multiple False Additionally, controllers for select, radio, and checkbox provide IListControl. These fields have four other attributes and an additional method: >>> ctrl = browser.getControl('Multiple Select Control') >>> ctrl >>> ctrl.disabled False >>> ctrl.multiple True >>> verifyObject(interfaces.IListControl, ctrl) True - 'options' lists all available value options. >>> ctrl.options ['1', '2', '3'] - 'displayOptions' lists all available options by label. The 'label' attribute on an option has precedence over its contents, which is why our last option is 'Third' in the display. >>> ctrl.displayOptions ['Un', 'Deux', 'Third'] - 'displayValue' lets you get and set the displayed values of the control of the select box, rather than the actual values. >>> ctrl.value [] >>> ctrl.displayValue [] >>> ctrl.displayValue = ['Un', 'Deux'] >>> ctrl.displayValue ['Un', 'Deux'] >>> ctrl.value ['1', '2'] - 'controls' gives you a list of the subcontrol objects in the control (subcontrols are discussed below). >>> ctrl.controls [, , ] - The 'getControl' method lets you get subcontrols by their label or their value. >>> ctrl.getControl('Un') >>> ctrl.getControl('Deux') >>> ctrl.getControl('Trois') # label attribute >>> ctrl.getControl('Third') # contents >>> browser.getControl('Third') # ambiguous in the browser, so useful Traceback (most recent call last): ... AmbiguityError: label 'Third' Finally, submit controls provide ISubmitControl, and image controls provide IImageSubmitControl, which extents ISubmitControl. These both simply add a 'click' method. For image submit controls, you may also provide a coordinates argument, which is a tuple of (x, y). These submit the forms, and are demonstrated below as we examine each control individually. ItemControl Objects ~~~~~~~~~~~~~~~~~~~ As introduced briefly above, using labels to obtain elements of a logical radio button or checkbox collection returns item controls, which are parents. Manipulating the value of these controls affects the parent control. >>> browser.getControl(name='radio-value').value ['2'] >>> browser.getControl('Zwei').optionValue # read-only. '2' >>> browser.getControl('Zwei').selected True >>> verifyObject(interfaces.IItemControl, browser.getControl('Zwei')) True >>> browser.getControl('Ein').selected = True >>> browser.getControl('Ein').selected True >>> browser.getControl('Zwei').selected False >>> browser.getControl(name='radio-value').value ['1'] >>> browser.getControl('Ein').selected = False >>> browser.getControl(name='radio-value').value [] >>> browser.getControl('Zwei').selected = True Checkbox collections behave similarly, as shown below. Controls with subcontrols-- Various Controls ~~~~~~~~~~~~~~~~ The various types of controls are demonstrated here. - Text Control The text control we already introduced above. - Password Control >>> ctrl = browser.getControl('Password Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value 'Password' >>> ctrl.value = 'pass now' >>> ctrl.value 'pass now' >>> ctrl.disabled False >>> ctrl.multiple False - Hidden Control >>> ctrl = browser.getControl(name='hidden-value') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value 'Hidden' >>> ctrl.value = 'More Hidden' >>> ctrl.disabled False >>> ctrl.multiple False - Text Area Control >>> ctrl = browser.getControl('Text Area Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value ' Text inside\n area!\n ' >>> ctrl.value = 'A lot of\n text.' >>> ctrl.disabled False >>> ctrl.multiple False - File Control File controls are used when a form has a file-upload field. To specify data, call the add_file method, passing: - A file-like object - a content type, and - a file name >>> ctrl = browser.getControl('File Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value is None True >>> import cStringIO >>> ctrl.add_file(cStringIO.StringIO('File contents'), ... 'text/plain', 'test.txt') The file control (like the other controls) also knows if it is disabled or if it can have multiple values. >>> ctrl.disabled False >>> ctrl.multiple False - Selection Control (Single-Valued) >>> ctrl = browser.getControl('Single Select Control') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value ['1'] >>> ctrl.value = ['2'] >>> ctrl.disabled False >>> ctrl.multiple False >>> ctrl.options ['1', '2', '3'] >>> ctrl.displayOptions ['Uno', 'Dos', 'Third'] >>> ctrl.displayValue ['Dos'] >>> ctrl.displayValue = ['Tres'] >>> ctrl.displayValue ['Third'] >>> ctrl.displayValue = ['Dos'] >>> ctrl.displayValue ['Dos'] >>> ctrl.displayValue = ['Third'] >>> ctrl.displayValue ['Third'] >>> ctrl.value ['3'] - Selection Control (Multi-Valued) This was already demonstrated in the introduction to control objects above. - Checkbox Control (Single-Valued; Unvalued) >>> ctrl = browser.getControl(name='single-unvalued-checkbox-value') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value True >>> ctrl.value = False >>> ctrl.disabled False >>> ctrl.multiple True >>> ctrl.options [True] >>> ctrl.displayOptions ['Single Unvalued Checkbox'] >>> ctrl.displayValue [] >>> verifyObject( ... interfaces.IItemControl, ... browser.getControl('Single Unvalued Checkbox')) True >>> browser.getControl('Single Unvalued Checkbox').optionValue 'on' >>> browser.getControl('Single Unvalued Checkbox').selected False >>> ctrl.displayValue = ['Single Unvalued Checkbox'] >>> ctrl.displayValue ['Single Unvalued Checkbox'] >>> browser.getControl('Single Unvalued Checkbox').selected True >>> browser.getControl('Single Unvalued Checkbox').selected = False >>> browser.getControl('Single Unvalued Checkbox').selected False >>> ctrl.displayValue [] >>> browser.getControl( ... name='single-disabled-unvalued-checkbox-value').disabled True - Checkbox Control (Single-Valued, Valued) >>> ctrl = browser.getControl(name='single-valued-checkbox-value') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value ['1'] >>> ctrl.value = [] >>> ctrl.disabled False >>> ctrl.multiple True >>> ctrl.options ['1'] >>> ctrl.displayOptions ['Single Valued Checkbox'] >>> ctrl.displayValue [] >>> verifyObject( ... interfaces.IItemControl, ... browser.getControl('Single Valued Checkbox')) True >>> browser.getControl('Single Valued Checkbox').selected False >>> browser.getControl('Single Valued Checkbox').optionValue '1' >>> ctrl.displayValue = ['Single Valued Checkbox'] >>> ctrl.displayValue ['Single Valued Checkbox'] >>> browser.getControl('Single Valued Checkbox').selected True >>> browser.getControl('Single Valued Checkbox').selected = False >>> browser.getControl('Single Valued Checkbox').selected False >>> ctrl.displayValue [] - Checkbox Control (Multi-Valued) >>> ctrl = browser.getControl(name='multi-checkbox-value') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value ['1', '3'] >>> ctrl.value = ['1', '2'] >>> ctrl.disabled False >>> ctrl.multiple True >>> ctrl.options ['1', '2', '3'] >>> ctrl.displayOptions ['One', 'Two', 'Three'] >>> ctrl.displayValue ['One', 'Two'] >>> ctrl.displayValue = ['Two'] >>> ctrl.value ['2'] >>> browser.getControl('Two').optionValue '2' >>> browser.getControl('Two').selected True >>> verifyObject(interfaces.IItemControl, browser.getControl('Two')) True >>> browser.getControl('Three').selected = True >>> browser.getControl('Three').selected True >>> browser.getControl('Two').selected True >>> ctrl.value ['2', '3'] >>> browser.getControl('Two').selected = False >>> ctrl.value ['3'] >>> browser.getControl('Three').selected = False >>> ctrl.value [] - Radio Control This is how you get a radio button based control: >>> ctrl = browser.getControl(name='radio-value') This shows the existing value of the control, as it was in the HTML received from the server: >>> ctrl.value ['2'] We can then unselect it: >>> ctrl.value = [] >>> ctrl.value [] We can also reselect it: >>> ctrl.value = ['2'] >>> ctrl.value ['2'] displayValue shows the text the user would see next to the control: >>> ctrl.displayValue ['Zwei'] This is just unit testing: >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.disabled False >>> ctrl.multiple False >>> ctrl.options ['1', '2', '3'] >>> ctrl.displayOptions ['Ein', 'Zwei', 'Drei'] >>> ctrl.displayValue = ['Ein'] >>> ctrl.value ['1'] >>> ctrl.displayValue ['Ein'] The radio control subcontrols were illustrated above. - Image Control >>> ctrl = browser.getControl(name='image-value') >>> ctrl >>> verifyObject(interfaces.IImageSubmitControl, ctrl) True >>> ctrl.value '' >>> ctrl.disabled False >>> ctrl.multiple False - Submit Control >>> ctrl = browser.getControl(name='submit-value') >>> ctrl >>> browser.getControl('Submit This') # value of submit button is a label >>> browser.getControl('Standard Submit Control') # label tag is legal >>> browser.getControl('Submit') # multiple labels, but same control >>> verifyObject(interfaces.ISubmitControl, ctrl) True >>> ctrl.value 'Submit This' >>> ctrl.disabled False >>> ctrl.multiple False Using Submitting Controls ~~~~~~~~~~~~~~~~~~~~~~~~~ Both the submit and image type should be clickable and submit the form: >>> browser.getControl('Text Control').value = 'Other Text' >>> browser.getControl('Submit').click() >>> print browser.contents ... Other Text ... Submit This ... Note that if you click a submit object after the associated page has expired, you will get an error. >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> ctrl = browser.getControl('Submit') >>> ctrl.click() >>> ctrl.click() Traceback (most recent call last): ... ExpiredError All the above also holds true for the image control: >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> browser.getControl('Text Control').value = 'Other Text' >>> browser.getControl(name='image-value').click() >>> print browser.contents ... Other Text ... 1 1 ... >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> ctrl = browser.getControl(name='image-value') >>> ctrl.click() >>> ctrl.click() Traceback (most recent call last): ... ExpiredError But when sending an image, you can also specify the coordinate you clicked: >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> browser.getControl(name='image-value').click((50,25)) >>> print browser.contents ... 50 25 ... Forms ----- Because pages can have multiple forms with like-named controls, it is sometimes necessary to access forms by name or id. The browser's `forms` attribute can be used to do so. The key value is the form's name or id. If more than one form has the same name or id, the first one will be returned. >>> browser.open('http://localhost/@@/testbrowser/forms.html') >>> form = browser.getForm(name='one') Form instances conform to the IForm interface. >>> verifyObject(interfaces.IForm, form) True The form exposes several attributes related to forms: - The name of the form: >>> form.name 'one' - The id of the form: >>> form.id '1' - The action (target URL) when the form is submitted: >>> form.action 'http://localhost/@@/testbrowser/forms.html' - The method (HTTP verb) used to transmit the form data: >>> form.method 'GET' Besides those attributes, you have also a couple of methods. Like for the browser, you can get control objects, but limited to the current form... >>> form.getControl(name='text-value') ...and submit the form. >>> form.submit('Submit') >>> print browser.contents ... First Text ... Submitting also works without specifying a control, as shown below, which is it's primary reason for existing in competition with the control submission discussed above. Now let me show you briefly that looking up forms is sometimes important. In the `forms.html` template, we have four forms all having a text control named `text-value`. Now, if I use the browser's `get` method, >>> browser.getControl(name='text-value') Traceback (most recent call last): ... AmbiguityError: name 'text-value' >>> browser.getControl('Text Control') Traceback (most recent call last): ... AmbiguityError: label 'Text Control' I'll always get an ambiguous form field. I can use the index argument, or with the `getForm` method I can disambiguate by searching only within a given form: >>> form = browser.getForm('2') >>> form.getControl(name='text-value').value 'Second Text' >>> form.submit('Submit') >>> browser.contents '...Second Text...' >>> form = browser.getForm('2') >>> form.getControl('Submit').click() >>> browser.contents '...Second Text...' >>> browser.getForm('3').getControl('Text Control').value 'Third Text' The last form on the page does not have a name, an id, or a submit button. Working with it is still easy, thanks to a index attribute that guarantees order. (Forms without submit buttons are sometimes useful for JavaScript.) >>> form = browser.getForm(index=3) >>> form.submit() >>> browser.contents '...Fourth Text...Submitted without the submit button....' If a form is requested that does not exists, an exception will be raised. >>> form = browser.getForm('does-not-exist') Traceback (most recent call last): LookupError If the HTML page contains only one form, no arguments to `getForm` are needed: >>> oneform = Browser() >>> oneform.open('http://localhost/@@/testbrowser/oneform.html') >>> form = oneform.getForm() If the HTML page contains more than one form, `index` is needed to disambiguate if no other arguments are provided: >>> browser.getForm() Traceback (most recent call last): ValueError: if no other arguments are given, index is required. Submitting a posts body directly -------------------------------- In addition to the open method, zope.testbrowser.testing.Browser has a ``post`` method that allows a request body to be supplied. This method is particularly helpful when testing Ajax methods. Let's visit a page that echos it's request: >>> browser.open('http://localhost/@@echo.html') >>> print browser.contents, HTTP_USER_AGENT: Python-urllib/2.4 HTTP_CONNECTION: close HTTP_COOKIE: REMOTE_ADDR: 127.0.0.1 HTTP_ACCEPT_LANGUAGE: en-US REQUEST_METHOD: GET HTTP_HOST: localhost PATH_INFO: /@@echo.html SERVER_PROTOCOL: HTTP/1.1 QUERY_STRING: Body: '' Now, we'll try a post. The post method takes a URL, a data string, and an optional content type. If we just pass a string, then a URL-encoded query string is assumed: >>> browser.post('http://localhost/@@echo.html', 'x=1&y=2') >>> print browser.contents, CONTENT_LENGTH: 7 HTTP_USER_AGENT: Python-urllib/2.4 HTTP_CONNECTION: close HTTP_COOKIE: REMOTE_ADDR: 127.0.0.1 HTTP_ACCEPT_LANGUAGE: en-US y: 2 REQUEST_METHOD: POST HTTP_HOST: localhost PATH_INFO: /@@echo.html CONTENT_TYPE: application/x-www-form-urlencoded SERVER_PROTOCOL: HTTP/1.1 QUERY_STRING: x: 1 Body: '' The body is empty because it is consumed to get form data. We can pass a content-type explicitly: >>> browser.post('http://localhost/@@echo.html', ... '{"x":1,"y":2}', 'application/x-javascript') >>> print browser.contents, CONTENT_LENGTH: 13 HTTP_USER_AGENT: Python-urllib/2.4 HTTP_CONNECTION: close HTTP_COOKIE: REMOTE_ADDR: 127.0.0.1 HTTP_ACCEPT_LANGUAGE: en-US REQUEST_METHOD: POST HTTP_HOST: localhost PATH_INFO: /@@echo.html CONTENT_TYPE: application/x-javascript SERVER_PROTOCOL: HTTP/1.1 Body: '{"x":1,"y":2}' Here, the body is left in place because it isn't form data. Performance Testing ------------------- Browser objects keep up with how much time each request takes. This can be used to ensure a particular request's performance is within a tolerable range. Be very careful using raw seconds, cross-machine differences can be huge, pystones is usually a better choice. >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.lastRequestSeconds < 10 # really big number for safety True >>> browser.lastRequestPystones < 10000 # really big number for safety True Handling Errors when using Zope 3's Publisher --------------------------------------------- A very useful feature of the publisher is the automatic graceful handling of application errors, such as invalid URLs: >>> browser.open('http://localhost/invalid') Traceback (most recent call last): ... HTTPError: HTTP Error 404: Not Found Note that the above error was thrown by ``mechanize`` and not by the publisher. For debugging purposes, however, it can be very useful to see the original exception caused by the application. In those cases you can set the ``handleErrors`` property of the browser to ``False``. It is defaulted to ``True``: >>> browser.handleErrors True So when we tell the publisher not to handle the errors, >>> browser.handleErrors = False we get a different, Zope internal error: >>> browser.open('http://localhost/invalid') Traceback (most recent call last): ... NotFound: Object: , name: u'invalid' NB: Setting the handleErrors attribute to False will only change anything if the http server you're testing is using Zope 3's publisher or can otherwise respond appropriately to an 'X-zope-handle-errors' header in requests. When the testbrowser is raising HttpErrors, the errors still hit the test. Sometimes we don't want that to happen, in situations where there are edge cases that will cause the error to be predictably but infrequently raised. Time is a primary cause of this. To get around this, one can set the raiseHttpErrors to False. >>> browser.handleErrors = True >>> browser.raiseHttpErrors = False This will cause HttpErrors not to propagate. >>> browser.open('http://localhost/invalid') The headers are still there, though. >>> '404 Not Found' in str(browser.headers) True If we don't handle the errors, and allow internal ones to propagate, however, this flag doesn't affect things. >>> browser.handleErrors = False >>> browser.open('http://localhost/invalid') Traceback (most recent call last): ... NotFound: Object: , name: u'invalid' >>> browser.raiseHttpErrors = True Hand-Holding ------------ Instances of the various objects ensure that users don't set incorrect instance attributes accidentally. >>> browser.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Browser' object has no attribute 'nonexistant' >>> form.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Form' object has no attribute 'nonexistant' >>> control.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Control' object has no attribute 'nonexistant' >>> link.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Link' object has no attribute 'nonexistant' ======= CHANGES ======= 3.11.1 (2011-01-24) ------------------- - Fixing brown bag release 3.11.0. 3.11.0 (2011-01-24) ------------------- - Added `wsgi_intercept` support (came from ``zope.app.wsgi.testlayer``). 3.10.4 (2011-01-14) ------------------- - Move the over-the-wire.txt doctest out of the TestBrowserLayer as it doesn't need or use it. - Fix test compatibility with zope.app.testing 3.8.1. 3.10.3 (2010-10-15) ------------------- - Fixed backwards compatibility with ``zope.app.wsgi.testlayer``. 3.10.2 (2010-10-15) ------------------- - Fixed Python 2.7 compatibility in Browser.handleErrors. 3.10.1 (2010-09-21) ------------------- - Fixed a bug that caused the ``Browser`` to keep it's previous ``contents`` The places are: - Link.click() - SubmitControl.click() - ImageControl.click() - Form.submit() - Also adjusted exception messages at the above places to match pre version 3.4.1 messages. 3.10.0 (2010-09-14) ------------------- - LP #98437: use mechanize's built-in ``submit()`` to submit forms, allowing mechanize to set the "Referer:" (sic) header appropriately. - Fixed tests to run with ``zope.app.testing`` 3.8 and above. 3.9.0 (2010-05-17) ------------------ - LP #568806: Update dependency ``mechanize >= 0.2.0``, which now includes the ``ClientForm`` APIs. Remove use of ``urllib2`` APIs (incompatible with ``mechanize 0.2.0``) in favor of ``mechanize`` equivalents. Thanks to John J. Lee for the patch. - Use stdlib ``doctest`` module, instead of ``zope.testing.doctest``. - **Caution:** This version is no longer fully compatible with Python 2.4: ``handleErrors = False`` no longer works. 3.8.1 (2010-04-19) ------------------ - Pinned dependency on mechanize to prevent use of the upcoming 0.2.0 release before we have time to adjust to its API changes. - LP #98396: testbrowser resolves relative URLs incorrectly. 3.8.0 (2010-03-05) ------------------ - Added ``follow`` convenience method which gets and follows a link. 3.7.0 (2009-12-17) ------------------ - Moved zope.app.testing dependency into the scope of the PublisherConnection class. Zope2 specifies its own PublisherConnection which isn't dependent on zope.app.testing. - Fixed LP #419119: return None when the browser has no contents instead of raising an exception. 3.7.0a1 (2009-08-29) -------------------- - Remove dependency on zope.app.publisher in favor of zope.browserpage, zope.browserresource and zope.ptresource. - Remove dependencies on zope.app.principalannotation and zope.securitypolicy by using the simple PermissiveSecurityPolicy. We aren't testing security in our tests. - Replaced the testing dependency on zope.app.zcmlfiles with explicit dependencies of a minimal set of packages. - Remove unneeded zope.app.authentication from ftesting.zcml. - Test dependency on zope.securitypolicy instead of its app variant. 3.6.0a2 (2009-01-31) -------------------- - Test dependency on zope.site.folder instead of zope.app.folder. - Remove useless test dependency in zope.app.component. 3.6.0a1 (2009-01-08) -------------------- - Author e-mail to zope-dev rather than zope3-dev. - New lines are no longer stripped in XML and HTML code contained in a textarea; fix requires ClientForm >= 0.2.10 (LP #268139). - Added ``cookies`` attribute to browser for easy manipulation of browser cookies. See brief example in main documentation, plus new ``cookies.txt`` documentation. 3.5.1 (2008-10-10) ------------------ - Provide a work around for a mechanize/urllib2 bug on Python 2.6 missing 'timeout' attribute on 'Request' base class. - Provide a work around for a mechanize/urllib2 bug in creating request objects that won't handle fragment URLs correctly. 3.5.0 (2008-03-30) ------------------ - Added a zope.testbrowser.testing.Browser.post method that allows tests to supply a body and a content type. This is handy for testing Ajax requests with non-form input (e.g. JSON). - Remove vendor import of mechanize. - Fix bug that caused HTTP exception tracebacks to differ between version 3.4.0 and 3.4.1. - Workaround for bug in Python Cookie.SimpleCookie when handling unicode strings. - Fix bug introduced in 3.4.1 that created incompatible tracebacks in doctests. This necessitated adding a patched mechanize to the source tree; patches have been sent to the mechanize project. - Fix https://bugs.launchpad.net/bugs/149517 by adding zope.interface and zope.schema as real dependencies - Fix browser.getLink documentation that was not updated since the last API modification. - Move tests for fixed bugs to a separate file. - Removed non-functional and undocumented code intended to help test servers using virtual hosting. 3.4.2 (2007-10-31) ------------------ - Resolve ``ZopeSecurityPolicy`` deprecation warning. 3.4.1 (2007-09-01) ------------------ * Updated to mechanize 0.1.7b and ClientForm 0.2.7. These are now pulled in via egg dependencies. * ``zope.testbrowser`` now works on Python 2.5. 3.4.0 (2007-06-04) ------------------ * Added the ability to suppress raising exceptions on HTTP errors (``raiseHttpErrors`` attribute). * Made the tests more resilient to HTTP header formatting changes with the REnormalizer. 3.4.0a1 (2007-04-22) -------------------- Initial release as a separate project, corresponds to zope.testbrowser from Zope 3.4.0a1 Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Internet :: WWW/HTTP zope2.13-2.13.21/source/zope.testbrowser/pip-egg-info/zope.testbrowser.egg-info/dependency_links.txt0000644000175000017500000000000112214017652032356 0ustar arnauarnau zope2.13-2.13.21/source/zope.testbrowser/pip-egg-info/zope.testbrowser.egg-info/requires.txt0000644000175000017500000000055112214017652030711 0ustar arnauarnaumechanize>=0.2.0 setuptools zope.interface zope.schema pytz [test] zope.browserpage zope.browserresource zope.component zope.container zope.principalregistry zope.ptresource zope.publisher zope.security zope.site zope.traversing zope.app.appsetup zope.app.publication zope.app.testing >= 3.8.1 [zope-functional-testing] zope.app.testing [wsgi] wsgi_intercept././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.testbrowser/pip-egg-info/zope.testbrowser.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.testbrowser/pip-egg-info/zope.testbrowser.egg-info/namespace_packages.t0000644000175000017500000000000512214017652032262 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.testbrowser/pip-egg-info/zope.testbrowser.egg-info/top_level.txt0000644000175000017500000000000512214017652031035 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.testbrowser/pip-egg-info/zope.testbrowser.egg-info/SOURCES.txt0000644000175000017500000000152712214017652030201 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.testbrowser.egg-info/PKG-INFO pip-egg-info/zope.testbrowser.egg-info/SOURCES.txt pip-egg-info/zope.testbrowser.egg-info/dependency_links.txt pip-egg-info/zope.testbrowser.egg-info/namespace_packages.txt pip-egg-info/zope.testbrowser.egg-info/not-zip-safe pip-egg-info/zope.testbrowser.egg-info/requires.txt pip-egg-info/zope.testbrowser.egg-info/top_level.txt src/zope/__init__.py src/zope/testbrowser/__init__.py src/zope/testbrowser/browser.py src/zope/testbrowser/cookies.py src/zope/testbrowser/interfaces.py src/zope/testbrowser/testing.py src/zope/testbrowser/wsgi.py src/zope/testbrowser/ftests/__init__.py src/zope/testbrowser/tests/__init__.py src/zope/testbrowser/tests/helper.py src/zope/testbrowser/tests/test_browser.py src/zope/testbrowser/tests/test_doctests.py src/zope/testbrowser/tests/test_wsgi.pyzope2.13-2.13.21/source/zope.testbrowser/pip-egg-info/zope.testbrowser.egg-info/not-zip-safe0000644000175000017500000000000112214017652030536 0ustar arnauarnau zope2.13-2.13.21/source/zope.testbrowser/LICENSE.txt0000644000175000017500000000402612214017652020623 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.testbrowser/README.txt0000644000175000017500000000035512214017652020477 0ustar arnauarnau``zope.testbrowser`` provides an easy-to-use programmable web browser with special focus on testing. It is used in Zope, but it's not Zope specific at all. For instance, it can be used to test or otherwise interact with any web site. zope2.13-2.13.21/source/zope.testbrowser/setup.cfg0000644000175000017500000000007312214017652020617 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.testbrowser/COPYRIGHT.txt0000644000175000017500000000004012214017652021101 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.testbrowser/buildout.cfg0000644000175000017500000000036712214017652021314 0ustar arnauarnau[buildout] develop = . parts = test interpreter [test] recipe = zc.recipe.testrunner defaults = ['--tests-pattern', '^f?tests$'] eggs = zope.testbrowser [test, wsgi] [interpreter] recipe = zc.recipe.egg eggs = zope.testbrowser interpreter = py zope2.13-2.13.21/source/zope.testbrowser/bootstrap.py0000644000175000017500000000725612214017652021377 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] try: import pkg_resources import setuptools if not hasattr(pkg_resources, '_distribute'): raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) reload(sys.modules['pkg_resources']) import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.testbrowser/CHANGES.txt0000644000175000017500000001245512214017652020616 0ustar arnauarnau======= CHANGES ======= 3.11.1 (2011-01-24) ------------------- - Fixing brown bag release 3.11.0. 3.11.0 (2011-01-24) ------------------- - Added `wsgi_intercept` support (came from ``zope.app.wsgi.testlayer``). 3.10.4 (2011-01-14) ------------------- - Move the over-the-wire.txt doctest out of the TestBrowserLayer as it doesn't need or use it. - Fix test compatibility with zope.app.testing 3.8.1. 3.10.3 (2010-10-15) ------------------- - Fixed backwards compatibility with ``zope.app.wsgi.testlayer``. 3.10.2 (2010-10-15) ------------------- - Fixed Python 2.7 compatibility in Browser.handleErrors. 3.10.1 (2010-09-21) ------------------- - Fixed a bug that caused the ``Browser`` to keep it's previous ``contents`` The places are: - Link.click() - SubmitControl.click() - ImageControl.click() - Form.submit() - Also adjusted exception messages at the above places to match pre version 3.4.1 messages. 3.10.0 (2010-09-14) ------------------- - LP #98437: use mechanize's built-in ``submit()`` to submit forms, allowing mechanize to set the "Referer:" (sic) header appropriately. - Fixed tests to run with ``zope.app.testing`` 3.8 and above. 3.9.0 (2010-05-17) ------------------ - LP #568806: Update dependency ``mechanize >= 0.2.0``, which now includes the ``ClientForm`` APIs. Remove use of ``urllib2`` APIs (incompatible with ``mechanize 0.2.0``) in favor of ``mechanize`` equivalents. Thanks to John J. Lee for the patch. - Use stdlib ``doctest`` module, instead of ``zope.testing.doctest``. - **Caution:** This version is no longer fully compatible with Python 2.4: ``handleErrors = False`` no longer works. 3.8.1 (2010-04-19) ------------------ - Pinned dependency on mechanize to prevent use of the upcoming 0.2.0 release before we have time to adjust to its API changes. - LP #98396: testbrowser resolves relative URLs incorrectly. 3.8.0 (2010-03-05) ------------------ - Added ``follow`` convenience method which gets and follows a link. 3.7.0 (2009-12-17) ------------------ - Moved zope.app.testing dependency into the scope of the PublisherConnection class. Zope2 specifies its own PublisherConnection which isn't dependent on zope.app.testing. - Fixed LP #419119: return None when the browser has no contents instead of raising an exception. 3.7.0a1 (2009-08-29) -------------------- - Remove dependency on zope.app.publisher in favor of zope.browserpage, zope.browserresource and zope.ptresource. - Remove dependencies on zope.app.principalannotation and zope.securitypolicy by using the simple PermissiveSecurityPolicy. We aren't testing security in our tests. - Replaced the testing dependency on zope.app.zcmlfiles with explicit dependencies of a minimal set of packages. - Remove unneeded zope.app.authentication from ftesting.zcml. - Test dependency on zope.securitypolicy instead of its app variant. 3.6.0a2 (2009-01-31) -------------------- - Test dependency on zope.site.folder instead of zope.app.folder. - Remove useless test dependency in zope.app.component. 3.6.0a1 (2009-01-08) -------------------- - Author e-mail to zope-dev rather than zope3-dev. - New lines are no longer stripped in XML and HTML code contained in a textarea; fix requires ClientForm >= 0.2.10 (LP #268139). - Added ``cookies`` attribute to browser for easy manipulation of browser cookies. See brief example in main documentation, plus new ``cookies.txt`` documentation. 3.5.1 (2008-10-10) ------------------ - Provide a work around for a mechanize/urllib2 bug on Python 2.6 missing 'timeout' attribute on 'Request' base class. - Provide a work around for a mechanize/urllib2 bug in creating request objects that won't handle fragment URLs correctly. 3.5.0 (2008-03-30) ------------------ - Added a zope.testbrowser.testing.Browser.post method that allows tests to supply a body and a content type. This is handy for testing Ajax requests with non-form input (e.g. JSON). - Remove vendor import of mechanize. - Fix bug that caused HTTP exception tracebacks to differ between version 3.4.0 and 3.4.1. - Workaround for bug in Python Cookie.SimpleCookie when handling unicode strings. - Fix bug introduced in 3.4.1 that created incompatible tracebacks in doctests. This necessitated adding a patched mechanize to the source tree; patches have been sent to the mechanize project. - Fix https://bugs.launchpad.net/bugs/149517 by adding zope.interface and zope.schema as real dependencies - Fix browser.getLink documentation that was not updated since the last API modification. - Move tests for fixed bugs to a separate file. - Removed non-functional and undocumented code intended to help test servers using virtual hosting. 3.4.2 (2007-10-31) ------------------ - Resolve ``ZopeSecurityPolicy`` deprecation warning. 3.4.1 (2007-09-01) ------------------ * Updated to mechanize 0.1.7b and ClientForm 0.2.7. These are now pulled in via egg dependencies. * ``zope.testbrowser`` now works on Python 2.5. 3.4.0 (2007-06-04) ------------------ * Added the ability to suppress raising exceptions on HTTP errors (``raiseHttpErrors`` attribute). * Made the tests more resilient to HTTP header formatting changes with the REnormalizer. 3.4.0a1 (2007-04-22) -------------------- Initial release as a separate project, corresponds to zope.testbrowser from Zope 3.4.0a1 zope2.13-2.13.21/source/zope.testbrowser/src/0000755000175000017500000000000012214017652017565 5ustar arnauarnauzope2.13-2.13.21/source/zope.testbrowser/src/zope/0000755000175000017500000000000012214017652020542 5ustar arnauarnauzope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/0000755000175000017500000000000012214017652023125 5ustar arnauarnauzope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/browser.py0000644000175000017500000006337112214017652025174 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Mechanize-based Functional Doctest interfaces """ __docformat__ = "reStructuredText" import cStringIO import re import sys import time import mechanize import zope.interface import zope.testbrowser.cookies import zope.testbrowser.interfaces RegexType = type(re.compile('')) _compress_re = re.compile(r"\s+") compressText = lambda text: _compress_re.sub(' ', text.strip()) def disambiguate(intermediate, msg, index): if intermediate: if index is None: if len(intermediate) > 1: raise mechanize.AmbiguityError(msg) else: return intermediate[0] else: try: return intermediate[index] except KeyError: msg = '%s index %d' % (msg, index) raise LookupError(msg) def controlFactory(control, form, browser): if isinstance(control, mechanize.Item): # it is a subcontrol return ItemControl(control, form, browser) else: t = control.type if t in ('checkbox', 'select', 'radio'): return ListControl(control, form, browser) elif t in ('submit', 'submitbutton'): return SubmitControl(control, form, browser) elif t=='image': return ImageControl(control, form, browser) else: return Control(control, form, browser) def any(items): return bool(sum([bool(i) for i in items])) def onlyOne(items, description): total = sum([bool(i) for i in items]) if total == 0 or total > 1: raise ValueError( "Supply one and only one of %s as arguments" % description) def zeroOrOne(items, description): if sum([bool(i) for i in items]) > 1: raise ValueError( "Supply no more than one of %s as arguments" % description) def fix_exception_name(e): # mechanize unceremoniously changed the repr of HTTPErrors, in # in order not to break existing doctests, we have to undo that if hasattr(e, '_exc_class_name'): name = e._exc_class_name name = name.rsplit('.', 1)[-1] e.__class__.__name__ = name class SetattrErrorsMixin(object): _enable_setattr_errors = False def __setattr__(self, name, value): if self._enable_setattr_errors: # cause an attribute error if the attribute doesn't already exist getattr(self, name) # set the value object.__setattr__(self, name, value) class PystoneTimer(object): start_time = 0 end_time = 0 _pystones_per_second = None @property def pystonesPerSecond(self): """How many pystones are equivalent to one second on this machine""" # deferred import as workaround for Zope 2 testrunner issue: # http://www.zope.org/Collectors/Zope/2268 from test import pystone if self._pystones_per_second == None: self._pystones_per_second = pystone.pystones(pystone.LOOPS/10)[1] return self._pystones_per_second def _getTime(self): if sys.platform.startswith('win'): # Windows' time.clock gives us high-resolution wall-time return time.clock() else: # everyone else uses time.time return time.time() def start(self): """Begin a timing period""" self.start_time = self._getTime() self.end_time = None def stop(self): """End a timing period""" self.end_time = self._getTime() @property def elapsedSeconds(self): """Elapsed time from calling `start` to calling `stop` or present time If `stop` has been called, the timing period stopped then, otherwise the end is the current time. """ if self.end_time is None: end_time = self._getTime() else: end_time = self.end_time return end_time - self.start_time @property def elapsedPystones(self): """Elapsed pystones in timing period See elapsed_seconds for definition of timing period. """ return self.elapsedSeconds * self.pystonesPerSecond class Browser(SetattrErrorsMixin): """A web user agent.""" zope.interface.implements(zope.testbrowser.interfaces.IBrowser) _contents = None _counter = 0 def __init__(self, url=None, mech_browser=None): if mech_browser is None: mech_browser = mechanize.Browser() self.mech_browser = mech_browser self.timer = PystoneTimer() self.raiseHttpErrors = True self.cookies = zope.testbrowser.cookies.Cookies(self.mech_browser) self._enable_setattr_errors = True if url is not None: self.open(url) @property def url(self): """See zope.testbrowser.interfaces.IBrowser""" return self.mech_browser.geturl() @property def isHtml(self): """See zope.testbrowser.interfaces.IBrowser""" return self.mech_browser.viewing_html() @property def title(self): """See zope.testbrowser.interfaces.IBrowser""" return self.mech_browser.title() @property def contents(self): """See zope.testbrowser.interfaces.IBrowser""" if self._contents is not None: return self._contents response = self.mech_browser.response() if response is None: return None old_location = response.tell() response.seek(0) self._contents = response.read() response.seek(old_location) return self._contents @property def headers(self): """See zope.testbrowser.interfaces.IBrowser""" return self.mech_browser.response().info() @apply def handleErrors(): """See zope.testbrowser.interfaces.IBrowser""" header_key = 'X-zope-handle-errors' def get(self): headers = self.mech_browser.addheaders value = dict(headers).get(header_key, True) return {'False': False}.get(value, True) def set(self, value): headers = self.mech_browser.addheaders current_value = get(self) if current_value == value: return # Remove the current header... for key, header_value in headers[:]: if key == header_key: headers.remove((key, header_value)) # ... Before adding the new one. headers.append((header_key, {False: 'False'}.get(value, 'True'))) return property(get, set) def open(self, url, data=None): """See zope.testbrowser.interfaces.IBrowser""" url = str(url) self._start_timer() try: try: try: self.mech_browser.open(url, data) except Exception, e: fix_exception_name(e) raise except mechanize.HTTPError, e: if e.code >= 200 and e.code <= 299: # 200s aren't really errors pass elif self.raiseHttpErrors: raise finally: self._stop_timer() self._changed() # if the headers don't have a status, I suppose there can't be an error if 'Status' in self.headers: code, msg = self.headers['Status'].split(' ', 1) code = int(code) if self.raiseHttpErrors and code >= 400: raise mechanize.HTTPError(url, code, msg, self.headers, fp=None) def post(self, url, data, content_type=None): if content_type is not None: data = {'body': data, 'content-type': content_type} return self.open(url, data) def _start_timer(self): self.timer.start() def _stop_timer(self): self.timer.stop() @property def lastRequestPystones(self): """See zope.testbrowser.interfaces.IBrowser""" return self.timer.elapsedPystones @property def lastRequestSeconds(self): """See zope.testbrowser.interfaces.IBrowser""" return self.timer.elapsedSeconds def reload(self): """See zope.testbrowser.interfaces.IBrowser""" self._start_timer() self.mech_browser.reload() self._stop_timer() self._changed() def goBack(self, count=1): """See zope.testbrowser.interfaces.IBrowser""" self._start_timer() self.mech_browser.back(count) self._stop_timer() self._changed() def addHeader(self, key, value): """See zope.testbrowser.interfaces.IBrowser""" if (self.mech_browser.request is not None and key.lower() in ('cookie', 'cookie2') and self.cookies.header): # to prevent unpleasant intermittent errors, only set cookies with # the browser headers OR the cookies mapping. raise ValueError('cookies are already set in `cookies` attribute') self.mech_browser.addheaders.append( (str(key), str(value)) ) def getLink(self, text=None, url=None, id=None, index=0): """See zope.testbrowser.interfaces.IBrowser""" if id is not None: def predicate(link): return dict(link.attrs).get('id') == id args = {'predicate': predicate} else: if isinstance(text, RegexType): text_regex = text elif text is not None: text_regex = re.compile(re.escape(text), re.DOTALL) else: text_regex = None if isinstance(url, RegexType): url_regex = url elif url is not None: url_regex = re.compile(re.escape(url), re.DOTALL) else: url_regex = None args = {'text_regex': text_regex, 'url_regex': url_regex} args['nr'] = index return Link(self.mech_browser.find_link(**args), self) def follow(self, *args, **kw): """Select a link and follow it.""" self.getLink(*args, **kw).click() def _findByLabel(self, label, forms, include_subcontrols=False): # forms are iterable of mech_forms matches = re.compile(r'(^|\b|\W)%s(\b|\W|$)' % re.escape(compressText(label))).search found = [] for f in forms: for control in f.controls: phantom = control.type in ('radio', 'checkbox') if not phantom: for l in control.get_labels(): if matches(l.text): found.append((control, f)) break if include_subcontrols and ( phantom or control.type=='select'): for i in control.items: for l in i.get_labels(): if matches(l.text): found.append((i, f)) found_one = True break return found def _findByName(self, name, forms): found = [] for f in forms: for control in f.controls: if control.name==name: found.append((control, f)) return found def getControl(self, label=None, name=None, index=None): """See zope.testbrowser.interfaces.IBrowser""" intermediate, msg = self._get_all_controls( label, name, self.mech_browser.forms(), include_subcontrols=True) control, form = disambiguate(intermediate, msg, index) return controlFactory(control, form, self) def _get_all_controls(self, label, name, forms, include_subcontrols=False): onlyOne([label, name], '"label" and "name"') if label is not None: res = self._findByLabel(label, forms, include_subcontrols) msg = 'label %r' % label elif name is not None: res = self._findByName(name, forms) msg = 'name %r' % name return res, msg def getForm(self, id=None, name=None, action=None, index=None): zeroOrOne([id, name, action], '"id", "name", and "action"') matching_forms = [] for form in self.mech_browser.forms(): if ((id is not None and form.attrs.get('id') == id) or (name is not None and form.name == name) or (action is not None and re.search(action, str(form.action))) or id == name == action == None): matching_forms.append(form) if index is None and not any([id, name, action]): if len(matching_forms) == 1: index = 0 else: raise ValueError( 'if no other arguments are given, index is required.') form = disambiguate(matching_forms, '', index) self.mech_browser.form = form return Form(self, form) def _clickSubmit(self, form, control, coord): labels = control.get_labels() if labels: label = labels[0].text else: label = None try: self._start_timer() try: self.mech_browser.form = form self.mech_browser.submit(id=control.id, name=control.name, label=label, coord=coord) except Exception, e: fix_exception_name(e) raise finally: self._stop_timer() def _changed(self): self._counter += 1 self._contents = None class Link(SetattrErrorsMixin): zope.interface.implements(zope.testbrowser.interfaces.ILink) def __init__(self, link, browser): self.mech_link = link self.browser = browser self._browser_counter = self.browser._counter self._enable_setattr_errors = True def click(self): if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError self.browser._start_timer() try: try: self.browser.mech_browser.follow_link(self.mech_link) except Exception, e: fix_exception_name(e) raise finally: self.browser._stop_timer() self.browser._changed() @property def url(self): return self.mech_link.absolute_url @property def text(self): return self.mech_link.text @property def tag(self): return self.mech_link.tag @property def attrs(self): return dict(self.mech_link.attrs) def __repr__(self): return "<%s text=%r url=%r>" % ( self.__class__.__name__, self.text, self.url) class Control(SetattrErrorsMixin): """A control of a form.""" zope.interface.implements(zope.testbrowser.interfaces.IControl) _enable_setattr_errors = False def __init__(self, control, form, browser): self.mech_control = control self.mech_form = form self.browser = browser self._browser_counter = self.browser._counter if self.mech_control.type == 'file': self.filename = None self.content_type = None # for some reason mechanize thinks we shouldn't be able to modify # hidden fields, but while testing it is sometimes very important if self.mech_control.type == 'hidden': self.mech_control.readonly = False # disable addition of further attributes self._enable_setattr_errors = True @property def disabled(self): return bool(getattr(self.mech_control, 'disabled', False)) @property def type(self): return getattr(self.mech_control, 'type', None) @property def name(self): return getattr(self.mech_control, 'name', None) @property def multiple(self): return bool(getattr(self.mech_control, 'multiple', False)) @apply def value(): """See zope.testbrowser.interfaces.IControl""" def fget(self): if (self.type == 'checkbox' and len(self.mech_control.items) == 1 and self.mech_control.items[0].name == 'on'): return self.mech_control.items[0].selected return self.mech_control.value def fset(self, value): if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError if self.mech_control.type == 'file': self.mech_control.add_file(value, content_type=self.content_type, filename=self.filename) elif self.type == 'checkbox' and len(self.mech_control.items) == 1: self.mech_control.items[0].selected = bool(value) else: self.mech_control.value = value return property(fget, fset) def add_file(self, file, content_type, filename): if not self.mech_control.type == 'file': raise TypeError("Can't call add_file on %s controls" % self.mech_control.type) if isinstance(file, str): file = cStringIO.StringIO(file) self.mech_control.add_file(file, content_type, filename) def clear(self): if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError self.mech_control.clear() def __repr__(self): return "<%s name=%r type=%r>" % ( self.__class__.__name__, self.name, self.type) class ListControl(Control): zope.interface.implements(zope.testbrowser.interfaces.IListControl) @apply def displayValue(): """See zope.testbrowser.interfaces.IListControl""" # not implemented for anything other than select; # would be nice if mechanize implemented for checkbox and radio. # attribute error for all others. def fget(self): return self.mech_control.get_value_by_label() def fset(self, value): if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError self.mech_control.set_value_by_label(value) return property(fget, fset) @property def displayOptions(self): """See zope.testbrowser.interfaces.IListControl""" res = [] for item in self.mech_control.items: if not item.disabled: for label in item.get_labels(): if label.text: res.append(label.text) break else: res.append(None) return res @property def options(self): """See zope.testbrowser.interfaces.IListControl""" if (self.type == 'checkbox' and len(self.mech_control.items) == 1 and self.mech_control.items[0].name == 'on'): return [True] return [i.name for i in self.mech_control.items if not i.disabled] @property def disabled(self): if self.type == 'checkbox' and len(self.mech_control.items) == 1: return bool(getattr(self.mech_control.items[0], 'disabled', False)) return bool(getattr(self.mech_control, 'disabled', False)) @property def controls(self): if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError res = [controlFactory(i, self.mech_form, self.browser) for i in self.mech_control.items] for s in res: s.__dict__['control'] = self return res def getControl(self, label=None, value=None, index=None): if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError onlyOne([label, value], '"label" and "value"') if label is not None: options = self.mech_control.get_items(label=label) msg = 'label %r' % label elif value is not None: options = self.mech_control.get_items(name=value) msg = 'value %r' % value res = controlFactory( disambiguate(options, msg, index), self.mech_form, self.browser) res.__dict__['control'] = self return res class SubmitControl(Control): zope.interface.implements(zope.testbrowser.interfaces.ISubmitControl) def click(self): if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError try: self.browser._clickSubmit(self.mech_form, self.mech_control, (1,1)) finally: self.browser._changed() class ImageControl(Control): zope.interface.implements(zope.testbrowser.interfaces.IImageSubmitControl) def click(self, coord=(1,1)): if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError try: self.browser._clickSubmit(self.mech_form, self.mech_control, coord) finally: self.browser._changed() class ItemControl(SetattrErrorsMixin): zope.interface.implements(zope.testbrowser.interfaces.IItemControl) def __init__(self, item, form, browser): self.mech_item = item self.mech_form = form self.browser = browser self._browser_counter = self.browser._counter self._enable_setattr_errors = True @property def control(self): if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError res = controlFactory( self.mech_item._control, self.mech_form, self.browser) self.__dict__['control'] = res return res @property def disabled(self): return self.mech_item.disabled @apply def selected(): """See zope.testbrowser.interfaces.IControl""" def fget(self): return self.mech_item.selected def fset(self, value): if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError self.mech_item.selected = value return property(fget, fset) @property def optionValue(self): return self.mech_item.attrs.get('value') def click(self): if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError self.mech_item.selected = not self.mech_item.selected def __repr__(self): return "<%s name=%r type=%r optionValue=%r selected=%r>" % ( self.__class__.__name__, self.mech_item._control.name, self.mech_item._control.type, self.optionValue, self.mech_item.selected) class Form(SetattrErrorsMixin): """HTML Form""" zope.interface.implements(zope.testbrowser.interfaces.IForm) def __init__(self, browser, form): """Initialize the Form browser - a Browser instance form - a mechanize.HTMLForm instance """ self.browser = browser self.mech_form = form self._browser_counter = self.browser._counter self._enable_setattr_errors = True @property def action(self): return self.mech_form.action @property def method(self): return self.mech_form.method @property def enctype(self): return self.mech_form.enctype @property def name(self): return self.mech_form.name @property def id(self): """See zope.testbrowser.interfaces.IForm""" return self.mech_form.attrs.get('id') def submit(self, label=None, name=None, index=None, coord=(1,1)): """See zope.testbrowser.interfaces.IForm""" if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError form = self.mech_form try: if label is not None or name is not None: intermediate, msg = self.browser._get_all_controls( label, name, (form,)) intermediate = [ (control, form) for (control, form) in intermediate if control.type in ('submit', 'submitbutton', 'image')] control, form = disambiguate(intermediate, msg, index) self.browser._clickSubmit(form, control, coord) else: # JavaScript sort of submit if index is not None or coord != (1,1): raise ValueError( 'May not use index or coord without a control') request = self.mech_form._switch_click("request", mechanize.Request) self.browser._start_timer() try: try: self.browser.mech_browser.open(request) except Exception, e: fix_exception_name(e) raise finally: self.browser._stop_timer() finally: self.browser._changed() def getControl(self, label=None, name=None, index=None): """See zope.testbrowser.interfaces.IBrowser""" if self._browser_counter != self.browser._counter: raise zope.testbrowser.interfaces.ExpiredError intermediate, msg = self.browser._get_all_controls( label, name, (self.mech_form,), include_subcontrols=True) control, form = disambiguate(intermediate, msg, index) return controlFactory(control, form, self.browser) zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/over_the_wire.txt0000644000175000017500000000240712214017652026532 0ustar arnauarnau================================= Using testbrowser On the Internet ================================= The ``zope.testbrowser`` module exposes a ``Browser`` class that simulates a web browser similar to Mozilla Firefox or IE. >>> from zope.testbrowser.browser import Browser >>> browser = Browser() It can send arbitrary headers; this is helpful for setting the language value, so that your tests format values the way you expect in your tests, if you rely on zope.i18n locale-based formatting or a similar approach. >>> browser.addHeader('Accept-Language', 'en-US') The browser can `open` web pages: >>> # This is tricky, since in Germany I am forwarded to google.de usually; >>> # The `ncr` forces to really go to google.com. >>> browser.open('http://google.com/ncr') >>> browser.url 'http://www.google.com/' >>> 'html' in browser.contents.lower() True We'll put some text in the query box... >>> browser.getControl(name='q').value = 'zope.testbrowser' ...and then click the search button. >>> browser.getControl('Google Search').click() Traceback (most recent call last): ... RobotExclusionError: HTTP Error 403: request disallowed by robots.txt Oops! Google doesn't let robots use their search engine. Oh well. zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/0000755000175000017500000000000012214017652024435 5ustar arnauarnauzope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/ftesting.zcml0000644000175000017500000000311412214017652027146 0ustar arnauarnau zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/oneform.html0000644000175000017500000000056512214017652026776 0ustar arnauarnau

Single Form Tests

zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/forms.html0000644000175000017500000000254112214017652026453 0ustar arnauarnau

Forms Tests

Submitted without the submit button.
zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/radio.html0000644000175000017500000000065212214017652026424 0ustar arnauarnau

Radio Button Tests



zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/navigate.html0000644000175000017500000000233612214017652027125 0ustar arnauarnau

Navigation Tests

Message: Message

Link Text Link Text with Whitespace Normalization (and parens) Using the URL By Anchor Name By Anchor Id Spaces in the URL
Zope3 zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/zope3logo.gif0000644000175000017500000000274212214017652027052 0ustar arnauarnauGIF89a‘*ÕÅÕÝ¥½ÉÓßåèïòºÍÖ•²À¨ÀÌùûû÷ùúëð󓱿ðôöÕàæ­¼·ËÔž¸Å±ÇÑÚåé¾ÐØÁÒÚÌÚᠺƵÉÓ˜´Â­ÄÏ¢¼ÈÊØàâêîÝæëš¶Ã»Îרâ诽óöø¬ÃΪÂÍÐÝã›¶ÄÏÜã©ÁÌ???¿¿¿ïïïÏÏÏ///ßß߯¯¯ŸŸŸ___OOOooo¯ÅÐÃÔÜ¡»Ç§¿Ëœ·Ä³ÈÒŒ¬»ÿÿÿ!ù,‘*ÿÀŸpH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°x¸ 92—ô%Ñ$Æðø•¡+¤s‹ÃrÚ•.$r„…I !F†–† 6&{'¢:8;—®a v9 #6»6#:©­G5(ÉÊËÌɯÏB6= 9$ ƒB Û Á; ÅG(>êëìí>3ÐQ/*,Y= 8oK8":2VÔ¨G%B šø@À‚ãÊ5 %]FŽFX¨HA“fŒI²˜·PNÿ‡=9¶`ˆ„E:U¬ÈØP =4 ‡<0xAˆ¨PR¬SqäÅ u+”aôáÆwëV¤€)®E$gWÀüÑBŒ)z°\" px0y‚« = <髮ƑêÚ:˜›?0 Hd°¨6x@¡®‡Á™ «±Îvú™ÿÎ 4¤0ƒl2Ð&D.8 (£qæB´L´jß4×ÊjÝ4±«úÀ/ç:áÒ Ÿf¤L»ü–j…R>q0zøPs¶ÅÁD° ¢ŸIäéð½ "a±Þ‰çÄ=Øð×=ä<‹"ÄœF”Æ„§Ml1vJ¼\ ë,œÄˆtóÔ³\À„ÊáÂLxç«”9×ò¥÷^!Û–KH#ÍÔSgðmm=H„‰(+1ã MpqÄ2çT¶gCÁ1Û90é÷ÑÜuÕ}e}»Ä„ÒT0­ŽÓO^¸PQÝ@š º õÛsrý„l(ÁBE+0+y€'ÁÿÂ\HÄ,E`š÷î»ïa‘0 =õ„PM4 ð¤Qå­ÃR±±V~eˤBëC˜LèÜ*¢ÍDÔSw0úøä‹žœï |k²]ì#ÝFìÖTÃZ3¬Ú°uÙu„2ÄÀÑí*Â^¡>ò6) *  jÂÀ:ÐqB€2à§<ÿ!¡&“{ ç’T­˜kB`rp8' 9Á“ÈZpƒšÜÄq“JNتð†R¸‡¨1¨‡@œ©…„  gø ¢›°€#ÆMD¼Ηƒ(.ñŠE¼ÇÔ :©MÍ>â¿ à|âs€ 8Æ6ºñpŒ£ç‡ ;zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/fragment.html0000644000175000017500000000016012214017652027123 0ustar arnauarnau Follow me zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/__init__.py0000644000175000017500000000321112214017652026543 0ustar arnauarnau############################################################################## # # Copyright (c) Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## class View: def __init__(self, context, request): self.context = context self.request = request class Echo(View): """Simply echo the contents of the request""" def __call__(self): return ('\n'.join('%s: %s' % x for x in self.request.items()) + '\nBody: %r' % self.request.bodyStream.read()) class GetCookie(View): """Gets cookie value""" def __call__(self): return '\n'.join( ('%s: %s' % (k, v)) for k, v in sorted( self.request.cookies.items())) class SetCookie(View): """Sets cookie value""" def __call__(self): self.request.response.setCookie( **dict((str(k), str(v)) for k, v in self.request.form.items())) class SetStatus(View): """Sets HTTP status""" def __call__(self): status = self.request.get('status') if status: self.request.response.setStatus(int(status)) return 'Just set a status of %s' % status else: return 'Everything fine' zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/simple.html0000644000175000017500000000015512214017652026615 0ustar arnauarnau Simple Page

Simple Page

zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/controls.html0000644000175000017500000001655712214017652027204 0ustar arnauarnau

Controls Tests

(label: hee hee)
(Multi checkbox: options have the labels)
(Radio: options have the labels)
If you have a select field with a label that overlaps with one of its options' labels, that is ambiguous.
zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/status_lead.html0000644000175000017500000000103412214017652027631 0ustar arnauarnau

This page leads to status setting

403
zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/notitle.html0000644000175000017500000000006712214017652027004 0ustar arnauarnau

No Title

zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/textarea.html0000644000175000017500000000066712214017652027151 0ustar arnauarnau

Textarea Tests

zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/ftests/cookies.html0000644000175000017500000000006712214017652026762 0ustar arnauarnau

No Title

zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/cookies.txt0000644000175000017500000006246312214017652025335 0ustar arnauarnau======= Cookies ======= Getting started =============== The cookies mapping has an extended mapping interface that allows getting, setting, and deleting the cookies that the browser is remembering for the current url, or for an explicitly provided URL. >>> from zope.testbrowser.testing import Browser >>> browser = Browser() Initially the browser does not point to a URL, and the cookies cannot be used. >>> len(browser.cookies) Traceback (most recent call last): ... RuntimeError: no request found >>> browser.cookies.keys() Traceback (most recent call last): ... RuntimeError: no request found Once you send the browser to a URL, the cookies attribute can be used. >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> len(browser.cookies) 0 >>> browser.cookies.keys() [] >>> browser.url 'http://localhost/@@/testbrowser/simple.html' >>> browser.cookies.url 'http://localhost/@@/testbrowser/simple.html' >>> import zope.testbrowser.interfaces >>> from zope.interface.verify import verifyObject >>> verifyObject(zope.testbrowser.interfaces.ICookies, browser.cookies) True Alternatively, you can use the ``forURL`` method to get another instance of the cookies mapping for the given URL. >>> len(browser.cookies.forURL('http://www.example.com')) 0 >>> browser.cookies.forURL('http://www.example.com').keys() [] >>> browser.cookies.forURL('http://www.example.com').url 'http://www.example.com' >>> browser.url 'http://localhost/@@/testbrowser/simple.html' >>> browser.cookies.url 'http://localhost/@@/testbrowser/simple.html' Here, we use a view that will make the server set cookies with the values we provide. >>> browser.open('http://localhost/set_cookie.html?name=foo&value=bar') >>> browser.headers['set-cookie'].replace(';', '') 'foo=bar' Basic Mapping Interface ======================= Now the cookies for localhost have a value. These are examples of just the basic accessor operators and methods. >>> browser.cookies['foo'] 'bar' >>> browser.cookies.keys() ['foo'] >>> browser.cookies.values() ['bar'] >>> browser.cookies.items() [('foo', 'bar')] >>> 'foo' in browser.cookies True >>> 'bar' in browser.cookies False >>> len(browser.cookies) 1 >>> print(dict(browser.cookies)) {'foo': 'bar'} As you would expect, the cookies attribute can also be used to examine cookies that have already been set in a previous request. To demonstrate this, we use another view that does not set cookies but reports on the cookies it receives from the browser. >>> browser.open('http://localhost/get_cookie.html') >>> print browser.headers.get('set-cookie') None >>> browser.contents 'foo: bar' >>> browser.cookies['foo'] 'bar' The standard mapping mutation methods and operators are also available, as seen here. >>> browser.cookies['sha'] = 'zam' >>> len(browser.cookies) 2 >>> import pprint >>> pprint.pprint(sorted(browser.cookies.items())) [('foo', 'bar'), ('sha', 'zam')] >>> browser.open('http://localhost/get_cookie.html') >>> print browser.headers.get('set-cookie') None >>> print browser.contents # server got the cookie change foo: bar sha: zam >>> browser.cookies.update({'va': 'voom', 'tweedle': 'dee'}) >>> pprint.pprint(sorted(browser.cookies.items())) [('foo', 'bar'), ('sha', 'zam'), ('tweedle', 'dee'), ('va', 'voom')] >>> browser.open('http://localhost/get_cookie.html') >>> print browser.headers.get('set-cookie') None >>> print browser.contents foo: bar sha: zam tweedle: dee va: voom >>> del browser.cookies['foo'] >>> del browser.cookies['tweedle'] >>> browser.open('http://localhost/get_cookie.html') >>> print browser.contents sha: zam va: voom Headers ======= You can see the Cookies header that will be sent to the browser in the ``header`` attribute and the repr and str. >>> browser.cookies.header 'sha=zam; va=voom' >>> browser.cookies # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE >>> str(browser.cookies) 'sha=zam; va=voom' Extended Mapping Interface ========================== ------------------------------------------ Read Methods: ``getinfo`` and ``iterinfo`` ------------------------------------------ ``getinfo`` ----------- The ``cookies`` mapping also has an extended interface to get and set extra information about each cookie. ``getinfo`` returns a dictionary. Here is the interface description. :: def getinfo(name): """returns dict of settings for the given cookie name. This includes only the following cookie values: - name (str) - value (str), - port (int or None), - domain (str), - path (str or None), - secure (bool), and - expires (datetime.datetime with pytz.UTC timezone or None), - comment (str or None), - commenturl (str or None). """ Here are some examples. >>> browser.open('http://localhost/set_cookie.html?name=foo&value=bar') >>> pprint.pprint(browser.cookies.getinfo('foo')) {'comment': None, 'commenturl': None, 'domain': 'localhost.local', 'expires': None, 'name': 'foo', 'path': '/', 'port': None, 'secure': False, 'value': 'bar'} >>> pprint.pprint(browser.cookies.getinfo('sha')) {'comment': None, 'commenturl': None, 'domain': 'localhost.local', 'expires': None, 'name': 'sha', 'path': '/', 'port': None, 'secure': False, 'value': 'zam'} >>> import datetime >>> expires = datetime.datetime(2030, 1, 1).strftime( ... '%a, %d %b %Y %H:%M:%S GMT') >>> browser.open( ... 'http://localhost/set_cookie.html?name=wow&value=wee&' ... 'expires=%s' % ... (expires,)) >>> pprint.pprint(browser.cookies.getinfo('wow')) {'comment': None, 'commenturl': None, 'domain': 'localhost.local', 'expires': datetime.datetime(2030, 1, 1, 0, 0, tzinfo=), 'name': 'wow', 'path': '/', 'port': None, 'secure': False, 'value': 'wee'} Max-age is converted to an "expires" value. >>> browser.open( ... 'http://localhost/set_cookie.html?name=max&value=min&' ... 'max-age=3000&&comment=silly+billy') >>> pprint.pprint(browser.cookies.getinfo('max')) # doctest: +ELLIPSIS {'comment': 'silly%20billy', 'commenturl': None, 'domain': 'localhost.local', 'expires': datetime.datetime(..., tzinfo=), 'name': 'max', 'path': '/', 'port': None, 'secure': False, 'value': 'min'} ``iterinfo`` ------------ You can iterate over all of the information about the cookies for the current page using the ``iterinfo`` method. >>> pprint.pprint(sorted(browser.cookies.iterinfo(), ... key=lambda info: info['name'])) ... # doctest: +ELLIPSIS [{'comment': None, 'commenturl': None, 'domain': 'localhost.local', 'expires': None, 'name': 'foo', 'path': '/', 'port': None, 'secure': False, 'value': 'bar'}, {'comment': 'silly%20billy', 'commenturl': None, 'domain': 'localhost.local', 'expires': datetime.datetime(..., tzinfo=), 'name': 'max', 'path': '/', 'port': None, 'secure': False, 'value': 'min'}, {'comment': None, 'commenturl': None, 'domain': 'localhost.local', 'expires': None, 'name': 'sha', 'path': '/', 'port': None, 'secure': False, 'value': 'zam'}, {'comment': None, 'commenturl': None, 'domain': 'localhost.local', 'expires': None, 'name': 'va', 'path': '/', 'port': None, 'secure': False, 'value': 'voom'}, {'comment': None, 'commenturl': None, 'domain': 'localhost.local', 'expires': datetime.datetime(2030, 1, 1, 0, 0, tzinfo=), 'name': 'wow', 'path': '/', 'port': None, 'secure': False, 'value': 'wee'}] Extended Examples ----------------- If you want to look at the cookies for another page, you can either navigate to the other page in the browser, or, as already mentioned, you can use the ``forURL`` method, which returns an ICookies instance for the new URL. >>> sorted(browser.cookies.forURL( ... 'http://localhost/inner/set_cookie.html').keys()) ['foo', 'max', 'sha', 'va', 'wow'] >>> extra_cookie = browser.cookies.forURL( ... 'http://localhost/inner/set_cookie.html') >>> extra_cookie['gew'] = 'gaw' >>> extra_cookie.getinfo('gew')['path'] '/inner' >>> sorted(extra_cookie.keys()) ['foo', 'gew', 'max', 'sha', 'va', 'wow'] >>> sorted(browser.cookies.keys()) ['foo', 'max', 'sha', 'va', 'wow'] >>> import zope.site.folder >>> getRootFolder()['inner'] = zope.site.folder.Folder() >>> getRootFolder()['inner']['path'] = zope.site.folder.Folder() >>> import transaction >>> transaction.commit() >>> browser.open('http://localhost/inner/get_cookie.html') >>> print browser.contents # has gewgaw foo: bar gew: gaw max: min sha: zam va: voom wow: wee >>> browser.open('http://localhost/inner/path/get_cookie.html') >>> print browser.contents # has gewgaw foo: bar gew: gaw max: min sha: zam va: voom wow: wee >>> browser.open('http://localhost/get_cookie.html') >>> print browser.contents # NO gewgaw foo: bar max: min sha: zam va: voom wow: wee Here's an example of the server setting a cookie that is only available on an inner page. >>> browser.open( ... 'http://localhost/inner/path/set_cookie.html?name=big&value=kahuna' ... ) >>> browser.cookies['big'] 'kahuna' >>> browser.cookies.getinfo('big')['path'] '/inner/path' >>> browser.cookies.getinfo('gew')['path'] '/inner' >>> browser.cookies.getinfo('foo')['path'] '/' >>> print browser.cookies.forURL('http://localhost/').get('big') None ---------------------------------------- Write Methods: ``create`` and ``change`` ---------------------------------------- The basic mapping API only allows setting values. If a cookie already exists for the given name, it's value will be changed; or else a new cookie will be created for the current request's domain and a path of '/', set to last for only this browser session (a "session" cookie). To create or change cookies with different additional information, use the ``create`` and ``change`` methods, respectively. Here is an example of ``create``. >>> from pytz import UTC >>> browser.cookies.create( ... 'bling', value='blang', path='/inner', ... expires=datetime.datetime(2020, 1, 1, tzinfo=UTC), ... comment='follow swallow') >>> pprint.pprint(browser.cookies.getinfo('bling')) {'comment': 'follow%20swallow', 'commenturl': None, 'domain': 'localhost.local', 'expires': datetime.datetime(2020, 1, 1, 0, 0, tzinfo=), 'name': 'bling', 'path': '/inner', 'port': None, 'secure': False, 'value': 'blang'} In these further examples of ``create``, note that the testbrowser sends all domains to Zope, and both http and https. >>> browser.open('https://dev.example.com/inner/path/get_cookie.html') >>> browser.cookies.keys() # a different domain [] >>> browser.cookies.create('tweedle', 'dee') >>> pprint.pprint(browser.cookies.getinfo('tweedle')) {'comment': None, 'commenturl': None, 'domain': 'dev.example.com', 'expires': None, 'name': 'tweedle', 'path': '/inner/path', 'port': None, 'secure': False, 'value': 'dee'} >>> browser.cookies.create( ... 'boo', 'yah', domain='.example.com', path='/inner', secure=True) >>> pprint.pprint(browser.cookies.getinfo('boo')) {'comment': None, 'commenturl': None, 'domain': '.example.com', 'expires': None, 'name': 'boo', 'path': '/inner', 'port': None, 'secure': True, 'value': 'yah'} >>> sorted(browser.cookies.keys()) ['boo', 'tweedle'] >>> browser.open('https://dev.example.com/inner/path/get_cookie.html') >>> print browser.contents boo: yah tweedle: dee >>> browser.open( # not https, so not secure, so not 'boo' ... 'http://dev.example.com/inner/path/get_cookie.html') >>> sorted(browser.cookies.keys()) ['tweedle'] >>> print browser.contents tweedle: dee >>> browser.open( # not tweedle's domain ... 'https://prod.example.com/inner/path/get_cookie.html') >>> sorted(browser.cookies.keys()) ['boo'] >>> print browser.contents boo: yah >>> browser.open( # not tweedle's domain ... 'https://example.com/inner/path/get_cookie.html') >>> sorted(browser.cookies.keys()) ['boo'] >>> print browser.contents boo: yah >>> browser.open( # not tweedle's path ... 'https://dev.example.com/inner/get_cookie.html') >>> sorted(browser.cookies.keys()) ['boo'] >>> print browser.contents boo: yah Masking by Path --------------- The API allows creation of cookies that mask existing cookies, but it does not allow creating a cookie that will be immediately masked upon creation. Having multiple cookies with the same name for a given URL is rare, and is a pathological case for using a mapping API to work with cookies, but it is supported to some degree, as demonstrated below. Note that the Cookie RFCs (2109, 2965) specify that all matching cookies be sent to the server, but with an ordering so that more specific paths come first. We also prefer more specific domains, though the RFCs state that the ordering of cookies with the same path is indeterminate. The best-matching cookie is the one that the mapping API uses. Also note that ports, as sent by RFC 2965's Cookie2 and Set-Cookie2 headers, are parsed and stored by this API but are not used for filtering as of this writing. This is an example of making one cookie that masks another because of path. First, unless you pass an explicit path, you will be modifying the existing cookie. >>> browser.open('https://dev.example.com/inner/path/get_cookie.html') >>> print browser.contents boo: yah tweedle: dee >>> browser.cookies.getinfo('boo')['path'] '/inner' >>> browser.cookies['boo'] = 'hoo' >>> browser.cookies.getinfo('boo')['path'] '/inner' >>> browser.cookies.getinfo('boo')['secure'] True Now we mask the cookie, using the path. >>> browser.cookies.create('boo', 'boo', path='/inner/path') >>> browser.cookies['boo'] 'boo' >>> browser.cookies.getinfo('boo')['path'] '/inner/path' >>> browser.cookies.getinfo('boo')['secure'] False >>> browser.cookies['boo'] 'boo' >>> sorted(browser.cookies.keys()) ['boo', 'tweedle'] To identify the additional cookies, you can change the URL... >>> extra_cookies = browser.cookies.forURL( ... 'https://dev.example.com/inner/get_cookie.html') >>> extra_cookies['boo'] 'hoo' >>> extra_cookies.getinfo('boo')['path'] '/inner' >>> extra_cookies.getinfo('boo')['secure'] True ...or use ``iterinfo`` and pass in a name. >>> pprint.pprint(list(browser.cookies.iterinfo('boo'))) [{'comment': None, 'commenturl': None, 'domain': 'dev.example.com', 'expires': None, 'name': 'boo', 'path': '/inner/path', 'port': None, 'secure': False, 'value': 'boo'}, {'comment': None, 'commenturl': None, 'domain': '.example.com', 'expires': None, 'name': 'boo', 'path': '/inner', 'port': None, 'secure': True, 'value': 'hoo'}] An odd situation in this case is that deleting a cookie can sometimes reveal another one. >>> browser.open('https://dev.example.com/inner/path/get_cookie.html') >>> browser.cookies['boo'] 'boo' >>> del browser.cookies['boo'] >>> browser.cookies['boo'] 'hoo' Creating a cookie that will be immediately masked within the current url is not allowed. >>> browser.cookies.getinfo('tweedle')['path'] '/inner/path' >>> browser.cookies.create('tweedle', 'dum', path='/inner') ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ValueError: cannot set a cookie that will be hidden by another cookie for this url (https://dev.example.com/inner/path/get_cookie.html) >>> browser.cookies['tweedle'] 'dee' Masking by Domain ----------------- All of the same behavior is also true for domains. The only difference is a theoretical one: while the behavior of masking cookies via paths is defined by the relevant IRCs, it is not defined for domains. Here, we simply follow a "best match" policy. We initialize by setting some cookies for example.org. >>> browser.open('https://dev.example.org/get_cookie.html') >>> browser.cookies.keys() # a different domain [] >>> browser.cookies.create('tweedle', 'dee') >>> browser.cookies.create('boo', 'yah', domain='example.org', ... secure=True) Before we look at the examples, note that the default behavior of the cookies is to be liberal in the matching of domains. >>> browser.cookies.strict_domain_policy False According to the RFCs, a domain of 'example.com' can only be set implicitly from the server, and implies an exact match, so example.com URLs will get the cookie, but not *.example.com (i.e., dev.example.com). Real browsers vary in their behavior in this regard. The cookies collection, by default, has a looser interpretation of this, such that domains are always interpreted as effectively beginning with a ".", so dev.example.com will include a cookie from the "example.com" domain filter as if it were a ".example.com" filter. Here's an example. If we go to dev.example.org, we should only see the "tweedle" cookie if we are using strict rules. But right now we are using loose rules, so 'boo' is around too. >>> browser.open('https://dev.example.org/get_cookie.html') >>> sorted(browser.cookies) ['boo', 'tweedle'] >>> print browser.contents boo: yah tweedle: dee If we set strict_domain_policy to True, then only tweedle is included. >>> browser.cookies.strict_domain_policy = True >>> sorted(browser.cookies) ['tweedle'] >>> browser.open('https://dev.example.org/get_cookie.html') >>> print browser.contents tweedle: dee If we set the "boo" domain to ".example.org" (as it would be set under the more recent Cookie RFC if a server sent the value) then maybe we get the "boo" value again. >>> browser.cookies.forURL('https://example.org').change( ... 'boo', domain=".example.org") Traceback (most recent call last): ... ValueError: policy does not allow this cookie Whoa! Why couldn't we do that? Well, the strict_domain_policy affects what cookies we can set also. With strict rules, ".example.org" can only be set by "*.example.org" domains, *not* example.org itself. OK, we'll create a new cookie then. >>> browser.cookies.forURL('https://snoo.example.org').create( ... 'snoo', 'kums', domain=".example.org") >>> sorted(browser.cookies) ['snoo', 'tweedle'] >>> browser.open('https://dev.example.org/get_cookie.html') >>> print browser.contents snoo: kums tweedle: dee Let's set things back to the way they were. >>> del browser.cookies['snoo'] >>> browser.cookies.strict_domain_policy = False >>> browser.open('https://dev.example.org/get_cookie.html') >>> sorted(browser.cookies) ['boo', 'tweedle'] >>> print browser.contents boo: yah tweedle: dee Now back to the the examples of masking by domain. First, unless you pass an explicit domain, you will be modifying the existing cookie. >>> browser.cookies.getinfo('boo')['domain'] 'example.org' >>> browser.cookies['boo'] = 'hoo' >>> browser.cookies.getinfo('boo')['domain'] 'example.org' >>> browser.cookies.getinfo('boo')['secure'] True Now we mask the cookie, using the domain. >>> browser.cookies.create('boo', 'boo', domain='dev.example.org') >>> browser.cookies['boo'] 'boo' >>> browser.cookies.getinfo('boo')['domain'] 'dev.example.org' >>> browser.cookies.getinfo('boo')['secure'] False >>> browser.cookies['boo'] 'boo' >>> sorted(browser.cookies.keys()) ['boo', 'tweedle'] To identify the additional cookies, you can change the URL... >>> extra_cookies = browser.cookies.forURL( ... 'https://example.org/get_cookie.html') >>> extra_cookies['boo'] 'hoo' >>> extra_cookies.getinfo('boo')['domain'] 'example.org' >>> extra_cookies.getinfo('boo')['secure'] True ...or use ``iterinfo`` and pass in a name. >>> pprint.pprint(list(browser.cookies.iterinfo('boo'))) [{'comment': None, 'commenturl': None, 'domain': 'dev.example.org', 'expires': None, 'name': 'boo', 'path': '/', 'port': None, 'secure': False, 'value': 'boo'}, {'comment': None, 'commenturl': None, 'domain': 'example.org', 'expires': None, 'name': 'boo', 'path': '/', 'port': None, 'secure': True, 'value': 'hoo'}] An odd situation in this case is that deleting a cookie can sometimes reveal another one. >>> browser.open('https://dev.example.org/get_cookie.html') >>> browser.cookies['boo'] 'boo' >>> del browser.cookies['boo'] >>> browser.cookies['boo'] 'hoo' Setting a cookie with a foreign domain from the current URL is not allowed (use forURL to get around this). >>> browser.cookies.create('tweedle', 'dum', domain='locahost.local') Traceback (most recent call last): ... ValueError: current url must match given domain >>> browser.cookies['tweedle'] 'dee' Setting a cookie that will be immediately masked within the current url is also not allowed. >>> browser.cookies.getinfo('tweedle')['domain'] 'dev.example.org' >>> browser.cookies.create('tweedle', 'dum', domain='.example.org') ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ValueError: cannot set a cookie that will be hidden by another cookie for this url (https://dev.example.org/get_cookie.html) >>> browser.cookies['tweedle'] 'dee' ``change`` ---------- So far all of our examples in this section have centered on ``create``. ``change`` allows making changes to existing cookies. Changing expiration is a good example. >>> browser.open("http://localhost/@@/testbrowser/cookies.html") >>> browser.cookies['foo'] = 'bar' >>> browser.cookies.change('foo', expires=datetime.datetime(2021, 1, 1)) >>> browser.cookies.getinfo('foo')['expires'] datetime.datetime(2021, 1, 1, 0, 0, tzinfo=) That's the main story. Now here are some edge cases. >>> browser.cookies.change( ... 'foo', ... expires=zope.testbrowser.cookies.expiration_string( ... datetime.datetime(2020, 1, 1))) >>> browser.cookies.getinfo('foo')['expires'] datetime.datetime(2020, 1, 1, 0, 0, tzinfo=) >>> browser.cookies.forURL( ... 'http://localhost/@@/testbrowser/cookies.html').change( ... 'foo', ... expires=zope.testbrowser.cookies.expiration_string( ... datetime.datetime(2019, 1, 1))) >>> browser.cookies.getinfo('foo')['expires'] datetime.datetime(2019, 1, 1, 0, 0, tzinfo=) >>> browser.cookies['foo'] 'bar' >>> browser.cookies.change('foo', expires=datetime.datetime(1999, 1, 1)) >>> len(browser.cookies) 4 While we are at it, it is worth noting that trying to create a cookie that has already expired raises an error. >>> browser.cookies.create('foo', 'bar', ... expires=datetime.datetime(1999, 1, 1)) Traceback (most recent call last): ... AlreadyExpiredError: May not create a cookie that is immediately expired Clearing cookies ---------------- clear, clearAll, clearAllSession allow various clears of the cookies. The ``clear`` method clears all of the cookies for the current page. >>> browser.open('http://localhost/@@/testbrowser/cookies.html') >>> len(browser.cookies) 4 >>> browser.cookies.clear() >>> len(browser.cookies) 0 The ``clearAllSession`` method clears *all* session cookies (for all domains and paths, not just the current URL), as if the browser had been restarted. >>> browser.cookies.clearAllSession() >>> len(browser.cookies) 0 The ``clearAll`` removes all cookies for the browser. >>> browser.cookies.clearAll() >>> len(browser.cookies) 0 Note that explicitly setting a Cookie header is an error if the ``cookies`` mapping has any values; and adding a new cookie to the ``cookies`` mapping is an error if the Cookie header is already set. This is to prevent hard-to- diagnose intermittent errors when one header or the other wins. >>> browser.cookies['boo'] = 'yah' >>> browser.addHeader('Cookie', 'gee=gaw') Traceback (most recent call last): ... ValueError: cookies are already set in `cookies` attribute >>> browser.cookies.clearAll() >>> browser.addHeader('Cookie', 'gee=gaw') >>> browser.cookies['fee'] = 'fi' Traceback (most recent call last): ... ValueError: cookies are already set in `Cookie` header zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/fixed-bugs.txt0000644000175000017500000001366012214017652025731 0ustar arnauarnau========== Fixed Bugs ========== This file includes tests for bugs that were found and then fixed that don't fit into the more documentation-centric sections above. Unicode URLs ============ Unicode URLs or headers cause the entire constructed request to be unicode, and (as of Python 2.4.4) Cookie.SimpleCookie checks the type of the input against type(""), so it handles the value inappropriately, causing exceptions that ended with:: File "/home/benji/Python-2.4.4/lib/python2.4/Cookie.py", line 623, in load self.update(rawdata) ValueError: dictionary update sequence element #0 has length 1; 2 is required As a work-around, unicode strings passed to Browser.open() are now converted to ASCII before being passed on, as well as the key and value passed to Browser.addHeader(). The tests below failed before the change was put in place. >>> from zope.testbrowser.testing import Browser >>> browser = Browser() >>> browser.addHeader('Cookie', 'test') >>> browser.open(u'http://localhost/@@/testbrowser/simple.html') >>> from zope.testbrowser.testing import Browser >>> browser = Browser() >>> browser.addHeader(u'Cookie', 'test') >>> browser.open('http://localhost/@@/testbrowser/simple.html') Spaces in URL ============= When URLs have spaces in them, they're handled correctly (before the bug was fixed, you'd get "ValueError: too many values to unpack"): >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.getLink('Spaces in the URL').click() .goBack() Truncation ==================== The .goBack() method used to truncate the .contents. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> actual_length = len(browser.contents) >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.goBack() >>> len(browser.contents) == actual_length True Labeled Radio Buttons ===================== The .getControl() method was sometimes unable to find radio buttons by label. >>> # import mechanize._form; mechanize._form._show_debug_messages() >>> browser.open('http://localhost/@@/testbrowser/radio.html') >>> browser.getControl('One').optionValue '1' >>> browser.getControl('Two').optionValue '2' >>> browser.getControl('Three').optionValue '3' Fragment URLs ============= Earlier versions of mechanize used to incorrectly follow links containing fragments. We upgraded our dependency to a newer version of mechanize and make sure this regression doesn't come back: >>> browser.open('http://localhost/@@/testbrowser/fragment.html#asdf') >>> browser.url 'http://localhost/@@/testbrowser/fragment.html#asdf' >>> browser.getLink('Follow me') >>> browser.getLink('Follow me').click() Textareas with HTML/XML ======================= >>> browser.open('http://localhost/@@/testbrowser/textarea.html') >>> browser.getControl('Text Area').value '\r\n \r\n &\r\n' .click() with non-200 status ============================ The problem was that with the below controls testbrowser forgot to do after-processing after an exception in mechanize. That means ``_stop_timer()`` and ``_changed()`` were not executed if an exception was raised. Not calling ``_changed()`` resulted in not refreshing ``contents``. The ``contents`` property gets cached on any first access and should be reset on any navigation. The problem is that e.g. a simple 403 status raises an exception. This is how it works with a simple open(): >>> browser.handleErrors=False >>> browser.open('http://localhost/set_status.html') >>> print browser.contents Everything fine >>> browser.open('http://localhost/set_status.html?status=403') Traceback (most recent call last): ... HTTPError: HTTP Error 403: Forbidden >>> print browser.contents Just set a status of 403 These are the various controls: A link: >>> browser.open('http://localhost/@@/testbrowser/status_lead.html') >>> print browser.contents ... >>> browser.getLink('403').click() Traceback (most recent call last): ... HTTPError: HTTP Error 403: Forbidden >>> print browser.contents Just set a status of 403 A submit button: >>> browser.open('http://localhost/@@/testbrowser/status_lead.html') >>> print browser.contents ... >>> browser.getControl(name='status').value = '404' >>> browser.getControl('Submit This').click() Traceback (most recent call last): ... HTTPError: HTTP Error 404: Not Found >>> print browser.contents Just set a status of 404 A submit image control: >>> browser.open('http://localhost/@@/testbrowser/status_lead.html') >>> print browser.contents ... >>> browser.getControl(name='status').value = '403' >>> browser.getControl(name='image-value').click() Traceback (most recent call last): ... HTTPError: HTTP Error 403: Forbidden >>> print browser.contents Just set a status of 403 A javascript-ish form submit: >>> browser.open('http://localhost/@@/testbrowser/status_lead.html') >>> print browser.contents ... >>> browser.getControl(name='status').value = '404' >>> browser.getForm(name='theform').submit() Traceback (most recent call last): ... HTTPError: HTTP Error 404: Not Found >>> print browser.contents Just set a status of 404 A non-javascript-ish form submit: >>> browser.open('http://localhost/@@/testbrowser/status_lead.html') >>> print browser.contents ... >>> browser.getControl(name='status').value = '403' >>> browser.getForm(name='theform').submit(name='submit-value') Traceback (most recent call last): ... HTTPError: HTTP Error 403: Forbidden >>> print browser.contents Just set a status of 403 zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/interfaces.py0000644000175000017500000003717712214017652025641 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Browser-like functional doctest interfaces """ __docformat__ = "reStructuredText" import zope.interface import zope.schema import zope.interface.common.mapping class AlreadyExpiredError(ValueError): pass class ICookies(zope.interface.common.mapping.IExtendedReadMapping, zope.interface.common.mapping.IExtendedWriteMapping, zope.interface.common.mapping.IMapping): # NOT copy """A mapping of cookies for a given url""" url = zope.schema.URI( title=u"URL", description=u"The URL the mapping is currently exposing.", required=True) header = zope.schema.TextLine( title=u"Header", description=u"The current value for the Cookie header for the URL", required=True) def forURL(url): """Returns another ICookies instance for the given URL.""" def getinfo(name): """returns dict of settings for the given cookie name. This includes only the following cookie values: - name (str) - value (str), - port (int or None), - domain (str), - path (str or None), - secure (bool), and - expires (datetime.datetime with pytz.UTC timezone or None), - comment (str or None), - commenturl (str or None). (Method name is not camelCase because it is intended to feel like an extension to the mapping interface, which uses all lower case, e.g. iterkeys.) """ def iterinfo(name=None): """iterate over the information about all the cookies for the URL. Each result is a dictionary as described for ``getinfo``. If name is given, iterates over all cookies for given name. (Method name is not camelCase because it is intended to feel like an extension to the mapping interface, which uses all lower case, e.g. iterkeys.) """ def create(name, value, domain=None, expires=None, path=None, secure=None, comment=None, commenturl=None, port=None): """Create a new cookie with the given values. If cookie of the same name, domain, and path exists, raises a ValueError. Expires is a string or a datetime.datetime. timezone-naive datetimes are interpreted as in UTC. If expires is before now, raises AlreadyExpiredError. If the domain or path do not generally match the current URL, raises ValueError. """ def change(name, value=None, domain=None, expires=None, path=None, secure=None, comment=None, commenturl=None, port=None): """Change an attribute of an existing cookie. If cookie does not exist, raises a KeyError.""" def clearAll(): """Clear all cookies for the associated browser, irrespective of URL """ def clearAllSession(): """Clear session cookies for associated browser, irrespective of URL """ class IBrowser(zope.interface.Interface): """A Programmatic Web Browser.""" cookies = zope.schema.Field( title=u"Cookies", description=(u"An ICookies mapping for the browser's current URL."), required=True) url = zope.schema.URI( title=u"URL", description=u"The URL the browser is currently showing.", required=True) headers = zope.schema.Field( title=u"Headers", description=(u"Headers of the HTTP response; a " "``httplib.HTTPMessage``."), required=True) contents = zope.schema.Text( title=u"Contents", description=u"The complete response body of the HTTP request.", required=True) isHtml = zope.schema.Bool( title=u"Is HTML", description=u"Tells whether the output is HTML or not.", required=True) title = zope.schema.TextLine( title=u"Title", description=u"Title of the displayed page", required=False) handleErrors = zope.schema.Bool( title=u"Handle Errors", description=(u"Describes whether server-side errors will be handled " u"by the publisher. If set to ``False``, the error will " u"progress all the way to the test, which is good for " u"debugging."), default=True, required=True) def addHeader(key, value): """Adds a header to each HTTP request. Adding additional headers can be useful in many ways, from setting the credentials token to specifying the browser identification string. """ def open(url, data=None): """Open a URL in the browser. The URL must be fully qualified. However, note that the server name and port is arbitrary for Zope 3 functional tests, since the request is sent to the publisher directly. The ``data`` argument describes the data that will be sent as the body of the request. """ def reload(): """Reload the current page. Like a browser reload, if the past request included a form submission, the form data will be resubmitted.""" def goBack(count=1): """Go back in history by a certain amount of visisted pages. The ``count`` argument specifies how far to go back. It is set to 1 by default. """ def getLink(text=None, url=None, id=None, index=0): """Return an ILink from the page. The link is found by the arguments of the method. One or more may be used together. o ``text`` -- A regular expression trying to match the link's text, in other words everything between and or the value of the submit button. o ``url`` -- The URL the link is going to. This is either the ``href`` attribute of an anchor tag or the action of a form. o ``id`` -- The id attribute of the anchor tag submit button. o ``index`` -- When there's more than one link that matches the text/URL, you can specify which one you want. """ lastRequestSeconds = zope.schema.Field( title=u"Seconds to Process Last Request", description=( u"""Return how many seconds (or fractions) the last request took. The values returned have the same resolution as the results from ``time.clock``. """), required=True, readonly=True) lastRequestPystones = zope.schema.Field( title= u"Approximate System-Independent Effort of Last Request (Pystones)", description=( u"""Return how many pystones the last request took. This number is found by multiplying the number of pystones/second at which this system benchmarks and the result of ``lastRequestSeconds``. """), required=True, readonly=True) def getControl(label=None, name=None, index=None): """Get a control from the page. Only one of ``label`` and ``name`` may be provided. ``label`` searches form labels (including submit button values, per the HTML 4.0 spec), and ``name`` searches form field names. Label value is searched as case-sensitive whole words within the labels for each control--that is, a search for 'Add' will match 'Add a contact' but not 'Address'. A word is defined as one or more alphanumeric characters or the underline. If no values are found, the code raises a LookupError. If ``index`` is None (the default) and more than one field matches the search, the code raises an AmbiguityError. If an index is provided, it is used to choose the index from the ambiguous choices. If the index does not exist, the code raises a LookupError. """ def getForm(id=None, name=None, action=None, index=None): """Get a form from the page. Zero or one of ``id``, ``name``, and ``action`` may be provided. If none are provided the index alone is used to determine the return value. If no values are found, the code raises a LookupError. If ``index`` is None (the default) and more than one form matches the search, the code raises an AmbiguityError. If an index is provided, it is used to choose the index from the ambiguous choices. If the index does not exist, the code raises a LookupError. """ class ExpiredError(Exception): """The browser page to which this was attached is no longer active""" class IControl(zope.interface.Interface): """A control (input field) of a page.""" name = zope.schema.TextLine( title=u"Name", description=u"The name of the control.", required=True) value = zope.schema.Field( title=u"Value", description=u"The value of the control", default=None, required=True) type = zope.schema.Choice( title=u"Type", description=u"The type of the control", values=['text', 'password', 'hidden', 'submit', 'checkbox', 'select', 'radio', 'image', 'file'], required=True) disabled = zope.schema.Bool( title=u"Disabled", description=u"Describes whether a control is disabled.", default=False, required=False) multiple = zope.schema.Bool( title=u"Multiple", description=u"Describes whether this control can hold multiple values.", default=False, required=False) def clear(): """Clear the value of the control.""" class IListControl(IControl): """A radio button, checkbox, or select control""" options = zope.schema.List( title=u"Options", description=u"""\ A list of possible values for the control.""", required=True) displayOptions = zope.schema.List( # TODO: currently only implemented for select by mechanize title=u"Options", description=u"""\ A list of possible display values for the control.""", required=True) displayValue = zope.schema.Field( # TODO: currently only implemented for select by mechanize title=u"Value", description=u"The value of the control, as rendered by the display", default=None, required=True) def getControl(label=None, value=None, index=None): """return subcontrol for given label or value, disambiguated by index if given. Label value is searched as case-sensitive whole words within the labels for each item--that is, a search for 'Add' will match 'Add a contact' but not 'Address'. A word is defined as one or more alphanumeric characters or the underline.""" controls = zope.interface.Attribute( """a list of subcontrols for the control. mutating list has no effect on control (although subcontrols may be changed as usual).""") class ISubmitControl(IControl): def click(): "click the submit button" class IImageSubmitControl(ISubmitControl): def click(coord=(1,1,)): "click the submit button with optional coordinates" class IItemControl(zope.interface.Interface): """a radio button or checkbox within a larger multiple-choice control""" control = zope.schema.Object( title=u"Control", description=(u"The parent control element."), schema=IControl, required=True) disabled = zope.schema.Bool( title=u"Disabled", description=u"Describes whether a subcontrol is disabled.", default=False, required=False) selected = zope.schema.Bool( title=u"Selected", description=u"Whether the subcontrol is selected", default=None, required=True) optionValue = zope.schema.TextLine( title=u"Value", description=u"The value of the subcontrol", default=None, required=False) class ILink(zope.interface.Interface): def click(): """click the link, going to the URL referenced""" url = zope.schema.TextLine( title=u"URL", description=u"The normalized URL of the link", required=False) attrs = zope.schema.Dict( title=u'Attributes', description=u'The attributes of the link tag', required=False) text = zope.schema.TextLine( title=u'Text', description=u'The contained text of the link', required=False) tag = zope.schema.TextLine( title=u'Tag', description=u'The tag name of the link (a or area, typically)', required=True) class IForm(zope.interface.Interface): """An HTML form of the page.""" action = zope.schema.TextLine( title=u"Action", description=u"The action (or URI) that is opened upon submittance.", required=True) method = zope.schema.Choice( title=u"Method", description=u"The method used to submit the form.", values=['post', 'get', 'put'], required=True) enctype = zope.schema.TextLine( title=u"Encoding Type", description=u"The type of encoding used to encode the form data.", required=True) name = zope.schema.TextLine( title=u"Name", description=u"The value of the `name` attribute in the form tag, " u"if specified.", required=True) id = zope.schema.TextLine( title=u"Id", description=u"The value of the `id` attribute in the form tag, " u"if specified.", required=True) def getControl(label=None, name=None, index=None): """Get a control in the page. Only one of ``label`` and ``name`` may be provided. ``label`` searches form labels (including submit button values, per the HTML 4.0 spec), and ``name`` searches form field names. Label value is searched as case-sensitive whole words within the labels for each control--that is, a search for 'Add' will match 'Add a contact' but not 'Address'. A word is defined as one or more alphanumeric characters or the underline. If no values are found, the code raises a LookupError. If ``index`` is None (the default) and more than one field matches the search, the code raises an AmbiguityError. If an index is provided, it is used to choose the index from the ambiguous choices. If the index does not exist, the code raises a LookupError. """ def submit(label=None, name=None, index=None, coord=(1,1)): """Submit this form. The `label`, `name`, and `index` arguments select the submit button to use to submit the form. You may label or name, with index to disambiguate. Label value is searched as case-sensitive whole words within the labels for each control--that is, a search for 'Add' will match 'Add a contact' but not 'Address'. A word is defined as one or more alphanumeric characters or the underline. The control code works identically to 'get' except that searches are filtered to find only submit and image controls. """ zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/__init__.py0000644000175000017500000000125512214017652025241 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Browser Simulator for Functional DocTests """ zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/README.txt0000644000175000017500000012275112214017652024633 0ustar arnauarnau====================== Detailed Documentation ====================== Different Browsers ------------------ HTTP Browser ~~~~~~~~~~~~ The ``zope.testbrowser.browser`` module exposes a ``Browser`` class that simulates a web browser similar to Mozilla Firefox or IE. >>> from zope.testbrowser.browser import Browser >>> browser = Browser() This version of the browser object can be used to access any web site just as you would do using a normal web browser. WSGI Test Browser ~~~~~~~~~~~~~~~~~ There is also a special version of the ``Browser`` class which uses `wsgi_intercept`_ and can be used to do functional testing of WSGI applications, it can be imported from ``zope.testbrowser.wsgi``: >>> from zope.testbrowser.wsgi import Browser >>> browser = Browser() .. _`wsgi_intercept`: http://pypi.python.org/pypi/wsgi_intercept To use this browser you have to: * use the `wsgi` extra of the ``zope.testbrowser`` egg, * write a subclass of ``zope.testbrowser.wsgi.Layer`` and override the ``make_wsgi_app`` method, * use an instance of the class as the test layer of your test. Example: >>> import zope.testbrowser.wsgi >>> class SimpleLayer(zope.testbrowser.wsgi.Layer): ... def make_wsgi_app(self): ... return simple_app Where ``simple_app`` is the callable of your WSGI application. Zope 3 Test Browser ~~~~~~~~~~~~~~~~~~~ There is also a special version of the ``Browser`` class used to do functional testing of Zope 3 applications, it can be imported from ``zope.testbrowser.testing``: >>> from zope.testbrowser.testing import Browser >>> browser = Browser() Bowser Usage ------------ All browsers are used the same way. An initial page to load can be passed to the ``Browser`` constructor: >>> browser = Browser('http://localhost/@@/testbrowser/simple.html') >>> browser.url 'http://localhost/@@/testbrowser/simple.html' The browser can send arbitrary headers; this is helpful for setting the "Authorization" header or a language value, so that your tests format values the way you expect in your tests, if you rely on zope.i18n locale-based formatting or a similar approach. >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw') >>> browser.addHeader('Accept-Language', 'en-US') An existing browser instance can also `open` web pages: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.url 'http://localhost/@@/testbrowser/simple.html' Once you have opened a web page initially, best practice for writing testbrowser doctests suggests using 'click' to navigate further (as discussed below), except in unusual circumstances. The test browser complies with the IBrowser interface; see ``zope.testbrowser.interfaces`` for full details on the interface. >>> from zope.testbrowser import interfaces >>> from zope.interface.verify import verifyObject >>> verifyObject(interfaces.IBrowser, browser) True Page Contents ------------- The contents of the current page are available: >>> print browser.contents Simple Page

Simple Page

Making assertions about page contents is easy. >>> '

Simple Page

' in browser.contents True Utilizing the doctest facilities, it also possible to do: >>> browser.contents '...

Simple Page

...' Note: Unfortunately, ellipsis (...) cannot be used at the beginning of the output (this is a limitation of doctest). Checking for HTML ----------------- Not all URLs return HTML. Of course our simple page does: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.isHtml True But if we load an image (or other binary file), we do not get HTML: >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif') >>> browser.isHtml False HTML Page Title ---------------- Another useful helper property is the title: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.title 'Simple Page' If a page does not provide a title, it is simply ``None``: >>> browser.open('http://localhost/@@/testbrowser/notitle.html') >>> browser.title However, if the output is not HTML, then an error will occur trying to access the title: >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif') >>> browser.title Traceback (most recent call last): ... BrowserStateError: not viewing HTML Headers ------- As you can see, the `contents` of the browser does not return any HTTP headers. The headers are accessible via a separate attribute, which is an ``httplib.HTTPMessage`` instance (httplib is a part of Python's standard library): >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.headers The headers can be accessed as a string: >>> print browser.headers Status: 200 OK Content-Length: 123 Content-Type: text/html;charset=utf-8 X-Powered-By: Zope (www.zope.org), Python (www.python.org) Or as a mapping: >>> browser.headers['content-type'] 'text/html;charset=utf-8' Cookies ------- When a Set-Cookie header is available, it can be found in the headers, as seen above. Here, we use a view that will make the server set cookies with the values we provide. >>> browser.open('http://localhost/set_cookie.html?name=foo&value=bar') >>> browser.headers['set-cookie'].replace(';', '') 'foo=bar' It is also available in the browser's ``cookies`` attribute. This is an extended mapping interface that allows getting, setting, and deleting the cookies that the browser is remembering *for the current url*. Here are a few examples. >>> browser.cookies['foo'] 'bar' >>> browser.cookies.keys() ['foo'] >>> browser.cookies.values() ['bar'] >>> browser.cookies.items() [('foo', 'bar')] >>> 'foo' in browser.cookies True >>> 'bar' in browser.cookies False >>> len(browser.cookies) 1 >>> print(dict(browser.cookies)) {'foo': 'bar'} >>> browser.cookies['sha'] = 'zam' >>> len(browser.cookies) 2 >>> sorted(browser.cookies.items()) [('foo', 'bar'), ('sha', 'zam')] >>> browser.open('http://localhost/get_cookie.html') >>> print browser.headers.get('set-cookie') None >>> print browser.contents # server got the cookie change foo: bar sha: zam >>> sorted(browser.cookies.items()) [('foo', 'bar'), ('sha', 'zam')] >>> browser.cookies.clearAll() >>> len(browser.cookies) 0 Many more examples, and a discussion of the additional methods available, can be found in cookies.txt. Navigation and Link Objects --------------------------- If you want to simulate clicking on a link, get the link and `click` on it. In the `navigate.html` file there are several links set up to demonstrate the capabilities of the link objects and their `click` method. The simplest way to get a link is via the anchor text. In other words the text you would see in a browser (text and url searches are substring searches): >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...Link Text...' >>> link = browser.getLink('Link Text') >>> link Link objects comply with the ILink interface. >>> verifyObject(interfaces.ILink, link) True Links expose several attributes for easy access. >>> link.text 'Link Text' >>> link.tag # links can also be image maps. 'a' >>> link.url # it's normalized 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text' >>> link.attrs {'href': 'navigate.html?message=By+Link+Text'} Links can be "clicked" and the browser will navigate to the referenced URL. >>> link.click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text' >>> browser.contents '...Message: By Link Text...' When finding a link by its text, whitespace is normalized. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...> Link Text \n with Whitespace\tNormalization (and parens) >> link = browser.getLink('Link Text with Whitespace Normalization ' ... '(and parens)') >>> link >>> link.text 'Link Text with Whitespace Normalization (and parens)' >>> link.click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text+with+Normalization' >>> browser.contents '...Message: By Link Text with Normalization...' When a link text matches more than one link, by default the first one is chosen. You can, however, specify the index of the link and thus retrieve a later matching link: >>> browser.getLink('Link Text') >>> browser.getLink('Link Text', index=1) Note that clicking a link object after its browser page has expired will generate an error. >>> link.click() Traceback (most recent call last): ... ExpiredError You can also find the link by its URL, >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...Using the URL...' >>> browser.getLink(url='?message=By+URL').click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+URL' >>> browser.contents '...Message: By URL...' or its id: >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...By Anchor Id...' >>> browser.getLink(id='anchorid').click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Id' >>> browser.contents '...Message: By Id...' You thought we were done here? Not so quickly. The `getLink` method also supports image maps, though not by specifying the coordinates, but using the area's id: >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> link = browser.getLink(id='zope3') >>> link.tag 'area' >>> link.click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name' >>> browser.contents '...Message: Zope 3 Name...' Getting a nonexistent link raises an exception. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.getLink('This does not exist') Traceback (most recent call last): ... LinkNotFoundError A convenience method is provided to follow links; this uses the same arguments as `getLink`, but clicks on the link instead of returning the link object. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...Link Text...' >>> browser.follow('Link Text') >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text' >>> browser.contents '...Message: By Link Text...' >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.follow(url='?message=By+URL') >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+URL' >>> browser.contents '...Message: By URL...' >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.follow(id='zope3') >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name' >>> browser.contents '...Message: Zope 3 Name...' Attempting to follow links that don't exist raises the same exception as asking for the link object: >>> browser.follow('This does not exist') Traceback (most recent call last): ... LinkNotFoundError Other Navigation ---------------- Like in any normal browser, you can reload a page: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.url 'http://localhost/@@/testbrowser/simple.html' >>> browser.reload() >>> browser.url 'http://localhost/@@/testbrowser/simple.html' You can also go back: >>> browser.open('http://localhost/@@/testbrowser/notitle.html') >>> browser.url 'http://localhost/@@/testbrowser/notitle.html' >>> browser.goBack() >>> browser.url 'http://localhost/@@/testbrowser/simple.html' Controls -------- One of the most important features of the browser is the ability to inspect and fill in values for the controls of input forms. To do so, let's first open a page that has a bunch of controls: >>> browser.open('http://localhost/@@/testbrowser/controls.html') Obtaining a Control ~~~~~~~~~~~~~~~~~~~ You look up browser controls with the 'getControl' method. The default first argument is 'label', and looks up the form on the basis of any associated label. >>> control = browser.getControl('Text Control') >>> control >>> browser.getControl(label='Text Control') # equivalent If you request a control that doesn't exist, the code raises a LookupError: >>> browser.getControl('Does Not Exist') Traceback (most recent call last): ... LookupError: label 'Does Not Exist' If you request a control with an ambiguous lookup, the code raises an AmbiguityError. >>> browser.getControl('Ambiguous Control') Traceback (most recent call last): ... AmbiguityError: label 'Ambiguous Control' This is also true if an option in a control is ambiguous in relation to the control itself. >>> browser.getControl('Sub-control Ambiguity') Traceback (most recent call last): ... AmbiguityError: label 'Sub-control Ambiguity' Ambiguous controls may be specified using an index value. We use the control's value attribute to show the two controls; this attribute is properly introduced below. >>> browser.getControl('Ambiguous Control', index=0) >>> browser.getControl('Ambiguous Control', index=0).value 'First' >>> browser.getControl('Ambiguous Control', index=1).value 'Second' >>> browser.getControl('Sub-control Ambiguity', index=0) >>> browser.getControl('Sub-control Ambiguity', index=1).optionValue 'ambiguous' Label searches are against stripped, whitespace-normalized, no-tag versions of the text. Text applied to searches is also stripped and whitespace normalized. The search finds results if the text search finds the whole words of your text in a label. Thus, for instance, a search for 'Add' will match the label 'Add a Client' but not 'Address'. Case is honored. >>> browser.getControl('Label Needs Whitespace Normalization') >>> browser.getControl('label needs whitespace normalization') Traceback (most recent call last): ... LookupError: label 'label needs whitespace normalization' >>> browser.getControl(' Label Needs Whitespace ') >>> browser.getControl('Whitespace') >>> browser.getControl('hitespace') Traceback (most recent call last): ... LookupError: label 'hitespace' >>> browser.getControl('[non word characters should not confuse]') Multiple labels can refer to the same control (simply because that is possible in the HTML 4.0 spec). >>> browser.getControl('Multiple labels really') >>> browser.getControl('really are possible') >>> browser.getControl('really') # OK: ambiguous labels, but not ambiguous control A label can be connected with a control using the 'for' attribute and also by containing a control. >>> browser.getControl( ... 'Labels can be connected by containing their respective fields') Get also accepts one other search argument, 'name'. Only one of 'label' and 'name' may be used at a time. The 'name' keyword searches form field names. >>> browser.getControl(name='text-value') >>> browser.getControl(name='ambiguous-control-name') Traceback (most recent call last): ... AmbiguityError: name 'ambiguous-control-name' >>> browser.getControl(name='does-not-exist') Traceback (most recent call last): ... LookupError: name 'does-not-exist' >>> browser.getControl(name='ambiguous-control-name', index=1).value 'Second' Combining 'label' and 'name' raises a ValueError, as does supplying neither of them. >>> browser.getControl(label='Ambiguous Control', name='ambiguous-control-name') Traceback (most recent call last): ... ValueError: Supply one and only one of "label" and "name" as arguments >>> browser.getControl() Traceback (most recent call last): ... ValueError: Supply one and only one of "label" and "name" as arguments Radio and checkbox fields are unusual in that their labels and names may point to different objects: names point to logical collections of radio buttons or checkboxes, but labels may only be used for individual choices within the logical collection. This means that obtaining a radio button by label gets a different object than obtaining the radio collection by name. Select options may also be searched by label. >>> browser.getControl(name='radio-value') >>> browser.getControl('Zwei') >>> browser.getControl('One') >>> browser.getControl('Tres') Characteristics of controls and subcontrols are discussed below. Control Objects ~~~~~~~~~~~~~~~ Controls provide IControl. >>> ctrl = browser.getControl('Text Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True They have several useful attributes: - the name as which the control is known to the form: >>> ctrl.name 'text-value' - the value of the control, which may also be set: >>> ctrl.value 'Some Text' >>> ctrl.value = 'More Text' >>> ctrl.value 'More Text' - the type of the control: >>> ctrl.type 'text' - a flag describing whether the control is disabled: >>> ctrl.disabled False - and a flag to tell us whether the control can have multiple values: >>> ctrl.multiple False Additionally, controllers for select, radio, and checkbox provide IListControl. These fields have four other attributes and an additional method: >>> ctrl = browser.getControl('Multiple Select Control') >>> ctrl >>> ctrl.disabled False >>> ctrl.multiple True >>> verifyObject(interfaces.IListControl, ctrl) True - 'options' lists all available value options. >>> ctrl.options ['1', '2', '3'] - 'displayOptions' lists all available options by label. The 'label' attribute on an option has precedence over its contents, which is why our last option is 'Third' in the display. >>> ctrl.displayOptions ['Un', 'Deux', 'Third'] - 'displayValue' lets you get and set the displayed values of the control of the select box, rather than the actual values. >>> ctrl.value [] >>> ctrl.displayValue [] >>> ctrl.displayValue = ['Un', 'Deux'] >>> ctrl.displayValue ['Un', 'Deux'] >>> ctrl.value ['1', '2'] - 'controls' gives you a list of the subcontrol objects in the control (subcontrols are discussed below). >>> ctrl.controls [, , ] - The 'getControl' method lets you get subcontrols by their label or their value. >>> ctrl.getControl('Un') >>> ctrl.getControl('Deux') >>> ctrl.getControl('Trois') # label attribute >>> ctrl.getControl('Third') # contents >>> browser.getControl('Third') # ambiguous in the browser, so useful Traceback (most recent call last): ... AmbiguityError: label 'Third' Finally, submit controls provide ISubmitControl, and image controls provide IImageSubmitControl, which extents ISubmitControl. These both simply add a 'click' method. For image submit controls, you may also provide a coordinates argument, which is a tuple of (x, y). These submit the forms, and are demonstrated below as we examine each control individually. ItemControl Objects ~~~~~~~~~~~~~~~~~~~ As introduced briefly above, using labels to obtain elements of a logical radio button or checkbox collection returns item controls, which are parents. Manipulating the value of these controls affects the parent control. >>> browser.getControl(name='radio-value').value ['2'] >>> browser.getControl('Zwei').optionValue # read-only. '2' >>> browser.getControl('Zwei').selected True >>> verifyObject(interfaces.IItemControl, browser.getControl('Zwei')) True >>> browser.getControl('Ein').selected = True >>> browser.getControl('Ein').selected True >>> browser.getControl('Zwei').selected False >>> browser.getControl(name='radio-value').value ['1'] >>> browser.getControl('Ein').selected = False >>> browser.getControl(name='radio-value').value [] >>> browser.getControl('Zwei').selected = True Checkbox collections behave similarly, as shown below. Controls with subcontrols-- Various Controls ~~~~~~~~~~~~~~~~ The various types of controls are demonstrated here. - Text Control The text control we already introduced above. - Password Control >>> ctrl = browser.getControl('Password Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value 'Password' >>> ctrl.value = 'pass now' >>> ctrl.value 'pass now' >>> ctrl.disabled False >>> ctrl.multiple False - Hidden Control >>> ctrl = browser.getControl(name='hidden-value') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value 'Hidden' >>> ctrl.value = 'More Hidden' >>> ctrl.disabled False >>> ctrl.multiple False - Text Area Control >>> ctrl = browser.getControl('Text Area Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value ' Text inside\n area!\n ' >>> ctrl.value = 'A lot of\n text.' >>> ctrl.disabled False >>> ctrl.multiple False - File Control File controls are used when a form has a file-upload field. To specify data, call the add_file method, passing: - A file-like object - a content type, and - a file name >>> ctrl = browser.getControl('File Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value is None True >>> import cStringIO >>> ctrl.add_file(cStringIO.StringIO('File contents'), ... 'text/plain', 'test.txt') The file control (like the other controls) also knows if it is disabled or if it can have multiple values. >>> ctrl.disabled False >>> ctrl.multiple False - Selection Control (Single-Valued) >>> ctrl = browser.getControl('Single Select Control') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value ['1'] >>> ctrl.value = ['2'] >>> ctrl.disabled False >>> ctrl.multiple False >>> ctrl.options ['1', '2', '3'] >>> ctrl.displayOptions ['Uno', 'Dos', 'Third'] >>> ctrl.displayValue ['Dos'] >>> ctrl.displayValue = ['Tres'] >>> ctrl.displayValue ['Third'] >>> ctrl.displayValue = ['Dos'] >>> ctrl.displayValue ['Dos'] >>> ctrl.displayValue = ['Third'] >>> ctrl.displayValue ['Third'] >>> ctrl.value ['3'] - Selection Control (Multi-Valued) This was already demonstrated in the introduction to control objects above. - Checkbox Control (Single-Valued; Unvalued) >>> ctrl = browser.getControl(name='single-unvalued-checkbox-value') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value True >>> ctrl.value = False >>> ctrl.disabled False >>> ctrl.multiple True >>> ctrl.options [True] >>> ctrl.displayOptions ['Single Unvalued Checkbox'] >>> ctrl.displayValue [] >>> verifyObject( ... interfaces.IItemControl, ... browser.getControl('Single Unvalued Checkbox')) True >>> browser.getControl('Single Unvalued Checkbox').optionValue 'on' >>> browser.getControl('Single Unvalued Checkbox').selected False >>> ctrl.displayValue = ['Single Unvalued Checkbox'] >>> ctrl.displayValue ['Single Unvalued Checkbox'] >>> browser.getControl('Single Unvalued Checkbox').selected True >>> browser.getControl('Single Unvalued Checkbox').selected = False >>> browser.getControl('Single Unvalued Checkbox').selected False >>> ctrl.displayValue [] >>> browser.getControl( ... name='single-disabled-unvalued-checkbox-value').disabled True - Checkbox Control (Single-Valued, Valued) >>> ctrl = browser.getControl(name='single-valued-checkbox-value') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value ['1'] >>> ctrl.value = [] >>> ctrl.disabled False >>> ctrl.multiple True >>> ctrl.options ['1'] >>> ctrl.displayOptions ['Single Valued Checkbox'] >>> ctrl.displayValue [] >>> verifyObject( ... interfaces.IItemControl, ... browser.getControl('Single Valued Checkbox')) True >>> browser.getControl('Single Valued Checkbox').selected False >>> browser.getControl('Single Valued Checkbox').optionValue '1' >>> ctrl.displayValue = ['Single Valued Checkbox'] >>> ctrl.displayValue ['Single Valued Checkbox'] >>> browser.getControl('Single Valued Checkbox').selected True >>> browser.getControl('Single Valued Checkbox').selected = False >>> browser.getControl('Single Valued Checkbox').selected False >>> ctrl.displayValue [] - Checkbox Control (Multi-Valued) >>> ctrl = browser.getControl(name='multi-checkbox-value') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value ['1', '3'] >>> ctrl.value = ['1', '2'] >>> ctrl.disabled False >>> ctrl.multiple True >>> ctrl.options ['1', '2', '3'] >>> ctrl.displayOptions ['One', 'Two', 'Three'] >>> ctrl.displayValue ['One', 'Two'] >>> ctrl.displayValue = ['Two'] >>> ctrl.value ['2'] >>> browser.getControl('Two').optionValue '2' >>> browser.getControl('Two').selected True >>> verifyObject(interfaces.IItemControl, browser.getControl('Two')) True >>> browser.getControl('Three').selected = True >>> browser.getControl('Three').selected True >>> browser.getControl('Two').selected True >>> ctrl.value ['2', '3'] >>> browser.getControl('Two').selected = False >>> ctrl.value ['3'] >>> browser.getControl('Three').selected = False >>> ctrl.value [] - Radio Control This is how you get a radio button based control: >>> ctrl = browser.getControl(name='radio-value') This shows the existing value of the control, as it was in the HTML received from the server: >>> ctrl.value ['2'] We can then unselect it: >>> ctrl.value = [] >>> ctrl.value [] We can also reselect it: >>> ctrl.value = ['2'] >>> ctrl.value ['2'] displayValue shows the text the user would see next to the control: >>> ctrl.displayValue ['Zwei'] This is just unit testing: >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.disabled False >>> ctrl.multiple False >>> ctrl.options ['1', '2', '3'] >>> ctrl.displayOptions ['Ein', 'Zwei', 'Drei'] >>> ctrl.displayValue = ['Ein'] >>> ctrl.value ['1'] >>> ctrl.displayValue ['Ein'] The radio control subcontrols were illustrated above. - Image Control >>> ctrl = browser.getControl(name='image-value') >>> ctrl >>> verifyObject(interfaces.IImageSubmitControl, ctrl) True >>> ctrl.value '' >>> ctrl.disabled False >>> ctrl.multiple False - Submit Control >>> ctrl = browser.getControl(name='submit-value') >>> ctrl >>> browser.getControl('Submit This') # value of submit button is a label >>> browser.getControl('Standard Submit Control') # label tag is legal >>> browser.getControl('Submit') # multiple labels, but same control >>> verifyObject(interfaces.ISubmitControl, ctrl) True >>> ctrl.value 'Submit This' >>> ctrl.disabled False >>> ctrl.multiple False Using Submitting Controls ~~~~~~~~~~~~~~~~~~~~~~~~~ Both the submit and image type should be clickable and submit the form: >>> browser.getControl('Text Control').value = 'Other Text' >>> browser.getControl('Submit').click() >>> print browser.contents ... Other Text ... Submit This ... Note that if you click a submit object after the associated page has expired, you will get an error. >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> ctrl = browser.getControl('Submit') >>> ctrl.click() >>> ctrl.click() Traceback (most recent call last): ... ExpiredError All the above also holds true for the image control: >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> browser.getControl('Text Control').value = 'Other Text' >>> browser.getControl(name='image-value').click() >>> print browser.contents ... Other Text ... 1 1 ... >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> ctrl = browser.getControl(name='image-value') >>> ctrl.click() >>> ctrl.click() Traceback (most recent call last): ... ExpiredError But when sending an image, you can also specify the coordinate you clicked: >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> browser.getControl(name='image-value').click((50,25)) >>> print browser.contents ... 50 25 ... Forms ----- Because pages can have multiple forms with like-named controls, it is sometimes necessary to access forms by name or id. The browser's `forms` attribute can be used to do so. The key value is the form's name or id. If more than one form has the same name or id, the first one will be returned. >>> browser.open('http://localhost/@@/testbrowser/forms.html') >>> form = browser.getForm(name='one') Form instances conform to the IForm interface. >>> verifyObject(interfaces.IForm, form) True The form exposes several attributes related to forms: - The name of the form: >>> form.name 'one' - The id of the form: >>> form.id '1' - The action (target URL) when the form is submitted: >>> form.action 'http://localhost/@@/testbrowser/forms.html' - The method (HTTP verb) used to transmit the form data: >>> form.method 'GET' Besides those attributes, you have also a couple of methods. Like for the browser, you can get control objects, but limited to the current form... >>> form.getControl(name='text-value') ...and submit the form. >>> form.submit('Submit') >>> print browser.contents ... First Text ... Submitting also works without specifying a control, as shown below, which is it's primary reason for existing in competition with the control submission discussed above. Now let me show you briefly that looking up forms is sometimes important. In the `forms.html` template, we have four forms all having a text control named `text-value`. Now, if I use the browser's `get` method, >>> browser.getControl(name='text-value') Traceback (most recent call last): ... AmbiguityError: name 'text-value' >>> browser.getControl('Text Control') Traceback (most recent call last): ... AmbiguityError: label 'Text Control' I'll always get an ambiguous form field. I can use the index argument, or with the `getForm` method I can disambiguate by searching only within a given form: >>> form = browser.getForm('2') >>> form.getControl(name='text-value').value 'Second Text' >>> form.submit('Submit') >>> browser.contents '...Second Text...' >>> form = browser.getForm('2') >>> form.getControl('Submit').click() >>> browser.contents '...Second Text...' >>> browser.getForm('3').getControl('Text Control').value 'Third Text' The last form on the page does not have a name, an id, or a submit button. Working with it is still easy, thanks to a index attribute that guarantees order. (Forms without submit buttons are sometimes useful for JavaScript.) >>> form = browser.getForm(index=3) >>> form.submit() >>> browser.contents '...Fourth Text...Submitted without the submit button....' If a form is requested that does not exists, an exception will be raised. >>> form = browser.getForm('does-not-exist') Traceback (most recent call last): LookupError If the HTML page contains only one form, no arguments to `getForm` are needed: >>> oneform = Browser() >>> oneform.open('http://localhost/@@/testbrowser/oneform.html') >>> form = oneform.getForm() If the HTML page contains more than one form, `index` is needed to disambiguate if no other arguments are provided: >>> browser.getForm() Traceback (most recent call last): ValueError: if no other arguments are given, index is required. Submitting a posts body directly -------------------------------- In addition to the open method, zope.testbrowser.testing.Browser has a ``post`` method that allows a request body to be supplied. This method is particularly helpful when testing Ajax methods. Let's visit a page that echos it's request: >>> browser.open('http://localhost/@@echo.html') >>> print browser.contents, HTTP_USER_AGENT: Python-urllib/2.4 HTTP_CONNECTION: close HTTP_COOKIE: REMOTE_ADDR: 127.0.0.1 HTTP_ACCEPT_LANGUAGE: en-US REQUEST_METHOD: GET HTTP_HOST: localhost PATH_INFO: /@@echo.html SERVER_PROTOCOL: HTTP/1.1 QUERY_STRING: Body: '' Now, we'll try a post. The post method takes a URL, a data string, and an optional content type. If we just pass a string, then a URL-encoded query string is assumed: >>> browser.post('http://localhost/@@echo.html', 'x=1&y=2') >>> print browser.contents, CONTENT_LENGTH: 7 HTTP_USER_AGENT: Python-urllib/2.4 HTTP_CONNECTION: close HTTP_COOKIE: REMOTE_ADDR: 127.0.0.1 HTTP_ACCEPT_LANGUAGE: en-US y: 2 REQUEST_METHOD: POST HTTP_HOST: localhost PATH_INFO: /@@echo.html CONTENT_TYPE: application/x-www-form-urlencoded SERVER_PROTOCOL: HTTP/1.1 QUERY_STRING: x: 1 Body: '' The body is empty because it is consumed to get form data. We can pass a content-type explicitly: >>> browser.post('http://localhost/@@echo.html', ... '{"x":1,"y":2}', 'application/x-javascript') >>> print browser.contents, CONTENT_LENGTH: 13 HTTP_USER_AGENT: Python-urllib/2.4 HTTP_CONNECTION: close HTTP_COOKIE: REMOTE_ADDR: 127.0.0.1 HTTP_ACCEPT_LANGUAGE: en-US REQUEST_METHOD: POST HTTP_HOST: localhost PATH_INFO: /@@echo.html CONTENT_TYPE: application/x-javascript SERVER_PROTOCOL: HTTP/1.1 Body: '{"x":1,"y":2}' Here, the body is left in place because it isn't form data. Performance Testing ------------------- Browser objects keep up with how much time each request takes. This can be used to ensure a particular request's performance is within a tolerable range. Be very careful using raw seconds, cross-machine differences can be huge, pystones is usually a better choice. >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.lastRequestSeconds < 10 # really big number for safety True >>> browser.lastRequestPystones < 10000 # really big number for safety True Handling Errors when using Zope 3's Publisher --------------------------------------------- A very useful feature of the publisher is the automatic graceful handling of application errors, such as invalid URLs: >>> browser.open('http://localhost/invalid') Traceback (most recent call last): ... HTTPError: HTTP Error 404: Not Found Note that the above error was thrown by ``mechanize`` and not by the publisher. For debugging purposes, however, it can be very useful to see the original exception caused by the application. In those cases you can set the ``handleErrors`` property of the browser to ``False``. It is defaulted to ``True``: >>> browser.handleErrors True So when we tell the publisher not to handle the errors, >>> browser.handleErrors = False we get a different, Zope internal error: >>> browser.open('http://localhost/invalid') Traceback (most recent call last): ... NotFound: Object: , name: u'invalid' NB: Setting the handleErrors attribute to False will only change anything if the http server you're testing is using Zope 3's publisher or can otherwise respond appropriately to an 'X-zope-handle-errors' header in requests. When the testbrowser is raising HttpErrors, the errors still hit the test. Sometimes we don't want that to happen, in situations where there are edge cases that will cause the error to be predictably but infrequently raised. Time is a primary cause of this. To get around this, one can set the raiseHttpErrors to False. >>> browser.handleErrors = True >>> browser.raiseHttpErrors = False This will cause HttpErrors not to propagate. >>> browser.open('http://localhost/invalid') The headers are still there, though. >>> '404 Not Found' in str(browser.headers) True If we don't handle the errors, and allow internal ones to propagate, however, this flag doesn't affect things. >>> browser.handleErrors = False >>> browser.open('http://localhost/invalid') Traceback (most recent call last): ... NotFound: Object: , name: u'invalid' >>> browser.raiseHttpErrors = True Hand-Holding ------------ Instances of the various objects ensure that users don't set incorrect instance attributes accidentally. >>> browser.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Browser' object has no attribute 'nonexistant' >>> form.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Form' object has no attribute 'nonexistant' >>> control.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Control' object has no attribute 'nonexistant' >>> link.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Link' object has no attribute 'nonexistant' zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/wsgi.py0000644000175000017500000001003112214017652024443 0ustar arnauarnau############################################################################## # # Copyright (c) 2010-2011 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import base64 import re import wsgi_intercept import wsgi_intercept.mechanize_intercept import zope.testbrowser.browser # List of hostname where the test browser/http function replies to TEST_HOSTS = ['localhost', '127.0.0.1'] class InterceptBrowser(wsgi_intercept.mechanize_intercept.Browser): default_schemes = ['http'] default_others = ['_http_error', '_http_default_error'] default_features = ['_redirect', '_cookies', '_referer', '_refresh', '_equiv', '_basicauth', '_digestauth'] class Browser(zope.testbrowser.browser.Browser): """Override the zope.testbrowser.browser.Browser interface so that it uses InterceptBrowser. """ def __init__(self, *args, **kw): kw['mech_browser'] = InterceptBrowser() super(Browser, self).__init__(*args, **kw) # Compatibility helpers to behave like zope.app.testing basicre = re.compile('Basic (.+)?:(.+)?$') def auth_header(header): """This function takes an authorization HTTP header and encode the couple user, password into base 64 like the HTTP protocol wants it. """ match = basicre.match(header) if match: u, p = match.group(1, 2) if u is None: u = '' if p is None: p = '' auth = base64.encodestring('%s:%s' % (u, p)) return 'Basic %s' % auth[:-1] return header def is_wanted_header(header): """Return True if the given HTTP header key is wanted. """ key, value = header return key.lower() not in ('x-content-type-warning', 'x-powered-by') class AuthorizationMiddleware(object): """This middleware makes the WSGI application compatible with the HTTPCaller behavior defined in zope.app.testing.functional: - It modifies the HTTP Authorization header to encode user and password into base64 if it is Basic authentication. """ def __init__(self, wsgi_stack): self.wsgi_stack = wsgi_stack def __call__(self, environ, start_response): # Handle authorization auth_key = 'HTTP_AUTHORIZATION' if auth_key in environ: environ[auth_key] = auth_header(environ[auth_key]) # Remove unwanted headers def application_start_response(status, headers, exc_info=None): headers = filter(is_wanted_header, headers) start_response(status, headers) for entry in self.wsgi_stack(environ, application_start_response): yield entry class Layer(object): """Test layer which sets up WSGI application for use with wsgi_intercept/testbrowser. """ __bases__ = () __name__ = 'Layer' def make_wsgi_app(self): # Override this method in subclasses of this layer in order to set up # the WSGI application. raise NotImplementedError def cooperative_super(self, method_name): # Calling `super` for multiple inheritance: method = getattr(super(Layer, self), method_name, None) if method is not None: method() def setUp(self): self.cooperative_super('setUp') self.app = self.make_wsgi_app() factory = lambda: AuthorizationMiddleware(self.app) for host in TEST_HOSTS: wsgi_intercept.add_wsgi_intercept(host, 80, factory) def tearDown(self): for host in TEST_HOSTS: wsgi_intercept.remove_wsgi_intercept(host, 80) self.cooperative_super('tearDown') zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/cookies.py0000644000175000017500000003164012214017652025137 0ustar arnauarnau############################################################################## # # Copyright (c) 2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import Cookie import datetime import time import urllib import urlparse import UserDict import mechanize import pytz import zope.interface from zope.testbrowser import interfaces # Cookies class helpers class _StubHTTPMessage(object): def __init__(self, cookies): self._cookies = cookies def getheaders(self, name): if name.lower() != 'set-cookie': return [] else: return self._cookies class _StubResponse(object): def __init__(self, cookies): self.message = _StubHTTPMessage(cookies) def info(self): return self.message def expiration_string(expires): # this is not protected so usable in tests. if isinstance(expires, datetime.datetime): if expires.tzinfo is not None: expires = expires.astimezone(pytz.UTC) expires = expires.strftime('%a, %d %b %Y %H:%M:%S GMT') return expires if getattr(property, 'setter', None) is None: # hack on Python 2.6 spelling of the only part we use here class property(property): __slots__ = () def setter(self, f): return property(self.fget, f, self.fdel, self.__doc__) # end Cookies class helpers class Cookies(object, UserDict.DictMixin): """Cookies for mechanize browser. """ zope.interface.implements(interfaces.ICookies) def __init__(self, mech_browser, url=None): self.mech_browser = mech_browser self._url = url for handler in self.mech_browser.handlers: if getattr(handler, 'cookiejar', None) is not None: self._jar = handler.cookiejar break else: raise RuntimeError('no cookiejar found') @property def strict_domain_policy(self): policy = self._jar.get_policy() flags = (policy.DomainStrictNoDots | policy.DomainRFC2965Match | policy.DomainStrictNonDomain) return policy.strict_ns_domain & flags == flags @strict_domain_policy.setter def strict_domain_policy(self, value): jar = self._jar policy = jar.get_policy() flags = (policy.DomainStrictNoDots | policy.DomainRFC2965Match | policy.DomainStrictNonDomain) policy.strict_ns_domain |= flags if not value: policy.strict_ns_domain ^= flags def forURL(self, url): return self.__class__(self.mech_browser, url) @property def url(self): if self._url is not None: return self._url else: return self.mech_browser.geturl() @property def _request(self): if self._url is not None: return self.mech_browser.request_class(self._url) else: request = self.mech_browser.request if request is None: raise RuntimeError('no request found') return request @property def header(self): request = self.mech_browser.request_class(self.url) self._jar.add_cookie_header(request) return request.get_header('Cookie') def __str__(self): return self.header def __repr__(self): # get the cookies for the current url return '<%s.%s object at %r for %s (%s)>' % ( self.__class__.__module__, self.__class__.__name__, id(self), self.url, self.header) def _raw_cookies(self): return self._jar.cookies_for_request(self._request) def _get_cookies(self, key=None): if key is None: seen = set() for ck in self._raw_cookies(): if ck.name not in seen: yield ck seen.add(ck.name) else: for ck in self._raw_cookies(): if ck.name == key: yield ck _marker = object() def _get(self, key, default=_marker): for ck in self._raw_cookies(): if ck.name == key: return ck if default is self._marker: raise KeyError(key) return default def __getitem__(self, key): return self._get(key).value def getinfo(self, key): return self._getinfo(self._get(key)) def _getinfo(self, ck): res = {'name': ck.name, 'value': ck.value, 'port': ck.port, 'domain': ck.domain, 'path': ck.path, 'secure': ck.secure, 'expires': None, 'comment': ck.comment, 'commenturl': ck.comment_url} if ck.expires is not None: res['expires'] = datetime.datetime.fromtimestamp( ck.expires, pytz.UTC) return res def keys(self): return [ck.name for ck in self._get_cookies()] def __iter__(self): return (ck.name for ck in self._get_cookies()) iterkeys = __iter__ def iterinfo(self, key=None): return (self._getinfo(ck) for ck in self._get_cookies(key)) def iteritems(self): return ((ck.name, ck.value) for ck in self._get_cookies()) def has_key(self, key): return self._get(key, None) is not None __contains__ = has_key def __len__(self): return len(list(self._get_cookies())) def __delitem__(self, key): ck = self._get(key) self._jar.clear(ck.domain, ck.path, ck.name) def create(self, name, value, domain=None, expires=None, path=None, secure=None, comment=None, commenturl=None, port=None): if value is None: raise ValueError('must provide value') ck = self._get(name, None) if (ck is not None and (path is None or ck.path == path) and (domain is None or ck.domain == domain or ck.domain == domain) and (port is None or ck.port == port)): # cookie already exists raise ValueError('cookie already exists') if domain is not None: self._verifyDomain(domain, ck) if path is not None: self._verifyPath(path, ck) now = int(time.time()) if expires is not None and self._is_expired(expires, now): raise zope.testbrowser.interfaces.AlreadyExpiredError( 'May not create a cookie that is immediately expired') self._setCookie(name, value, domain, expires, path, secure, comment, commenturl, port, now=now) def change(self, name, value=None, domain=None, expires=None, path=None, secure=None, comment=None, commenturl=None, port=None): now = int(time.time()) if expires is not None and self._is_expired(expires, now): # shortcut del self[name] else: self._change(self._get(name), value, domain, expires, path, secure, comment, commenturl, port, now) def _change(self, ck, value=None, domain=None, expires=None, path=None, secure=None, comment=None, commenturl=None, port=None, now=None): if value is None: value = ck.value if domain is None: domain = ck.domain else: self._verifyDomain(domain, None) if expires is None: expires = ck.expires if path is None: path = ck.path else: self._verifyPath(domain, None) if secure is None: secure = ck.secure if comment is None: comment = ck.comment if commenturl is None: commenturl = ck.comment_url if port is None: port = ck.port self._setCookie(ck.name, value, domain, expires, path, secure, comment, commenturl, port, ck.version, ck=ck, now=now) def _verifyDomain(self, domain, ck): tmp_domain = domain if domain is not None and domain.startswith('.'): tmp_domain = domain[1:] self_host = mechanize.effective_request_host(self._request) if (self_host != tmp_domain and not self_host.endswith('.' + tmp_domain)): raise ValueError('current url must match given domain') if (ck is not None and ck.domain != tmp_domain and ck.domain.endswith(tmp_domain)): raise ValueError( 'cannot set a cookie that will be hidden by another ' 'cookie for this url (%s)' % (self.url,)) def _verifyPath(self, path, ck): self_path = urlparse.urlparse(self.url)[2] if not self_path.startswith(path): raise ValueError('current url must start with path, if given') if ck is not None and ck.path != path and ck.path.startswith(path): raise ValueError( 'cannot set a cookie that will be hidden by another ' 'cookie for this url (%s)' % (self.url,)) def _setCookie(self, name, value, domain, expires, path, secure, comment, commenturl, port, version=None, ck=None, now=None): for nm, val in self.mech_browser.addheaders: if nm.lower() in ('cookie', 'cookie2'): raise ValueError('cookies are already set in `Cookie` header') if domain and not domain.startswith('.'): # we do a dance here so that we keep names that have been passed # in consistent (i.e., if we get an explicit 'example.com' it stays # 'example.com', rather than converting to '.example.com'). tmp_domain = domain domain = None if secure: protocol = 'https' else: protocol = 'http' url = '%s://%s%s' % (protocol, tmp_domain, path or '/') request = self.mech_browser.request_class(url) else: request = self._request if request is None: raise mechanize.BrowserStateError( 'cannot create cookie without request or domain') c = Cookie.SimpleCookie() name = str(name) c[name] = value.encode('utf8') if secure: c[name]['secure'] = True if domain: c[name]['domain'] = domain if path: c[name]['path'] = path if expires: c[name]['expires'] = expiration_string(expires) if comment: c[name]['comment'] = urllib.quote( comment.encode('utf-8'), safe="/?:@&+") if port: c[name]['port'] = port if commenturl: c[name]['commenturl'] = commenturl if version: c[name]['version'] = version # this use of objects like _StubResponse and _StubHTTPMessage is in # fact supported by the documented client cookie API. cookies = self._jar.make_cookies( _StubResponse([c.output(header='').strip()]), request) assert len(cookies) == 1, ( 'programmer error: %d cookies made' % (len(cookies),)) policy = self._jar._policy if now is None: now = int(time.time()) policy._now = self._jar._now = now # TODO get mechanize to expose this if not policy.set_ok(cookies[0], request): raise ValueError('policy does not allow this cookie') if ck is not None: self._jar.clear(ck.domain, ck.path, ck.name) self._jar.set_cookie(cookies[0]) def __setitem__(self, key, value): ck = self._get(key, None) if ck is None: self.create(key, value) else: self._change(ck, value) def _is_expired(self, value, now): # now = int(time.time()) dnow = datetime.datetime.fromtimestamp(now, pytz.UTC) if isinstance(value, datetime.datetime): if value.tzinfo is None: if value <= dnow.replace(tzinfo=None): return True elif value <= dnow: return True elif isinstance(value, basestring): if datetime.datetime.fromtimestamp( mechanize.str2time(value), pytz.UTC) <= dnow: return True return False def clear(self): # to give expected mapping behavior of resulting in an empty dict, # we use _raw_cookies rather than _get_cookies. for ck in self._raw_cookies(): self._jar.clear(ck.domain, ck.path, ck.name) def clearAllSession(self): self._jar.clear_session_cookies() def clearAll(self): self._jar.clear() zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/tests/0000755000175000017500000000000012214017652024267 5ustar arnauarnauzope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/tests/helper.py0000644000175000017500000000335412214017652026125 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import re import zope.testing.renormalizing class win32CRLFtransformer(object): def sub(self, replacement, text): return text.replace(r'\r', '') checker = zope.testing.renormalizing.RENormalizing([ (re.compile(r'^--\S+\.\S+\.\S+', re.M), '-' * 30), (re.compile(r'boundary=\S+\.\S+\.\S+'), 'boundary=' + '-' * 30), (re.compile(r'^---{10}.*', re.M), '-' * 30), (re.compile(r'boundary=-{10}.*'), 'boundary=' + '-' * 30), (re.compile(r'User-agent:\s+\S+'), 'User-agent: Python-urllib/2.4'), (re.compile(r'HTTP_USER_AGENT:\s+\S+'), 'HTTP_USER_AGENT: Python-urllib/2.4'), (re.compile(r'Content-[Ll]ength:.*'), 'Content-Length: 123'), (re.compile(r'Status: 200.*'), 'Status: 200 OK'), (win32CRLFtransformer(), None), (re.compile(r'User-Agent: Python-urllib/2.5'), 'User-agent: Python-urllib/2.4'), (re.compile(r'User-Agent: Python-urllib/2.6'), 'User-agent: Python-urllib/2.4'), (re.compile(r'Host: localhost'), 'Connection: close'), (re.compile(r'Content-Type: '), 'Content-type: '), (re.compile(r'Content-Disposition: '), 'Content-disposition: '), ]) zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/tests/test_wsgi.py0000644000175000017500000000255012214017652026653 0ustar arnauarnau############################################################################## # # Copyright (c) 2011 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest import zope.testbrowser.wsgi # Copied from PEP #333 def simple_app(environ, start_response): """Simplest possible application object""" status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return ['Hello world!\n'] class SimpleLayer(zope.testbrowser.wsgi.Layer): def make_wsgi_app(self): return simple_app SIMPLE_LAYER = SimpleLayer() class TestWSGI(unittest.TestCase): layer = SIMPLE_LAYER def test_(self): browser = zope.testbrowser.wsgi.Browser() browser.open('http://localhost') self.assertEqual('Hello world!\n', browser.contents) # XXX test for authorization header munging is missing zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/tests/test_browser.py0000644000175000017500000003066612214017652027376 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Real test for file-upload and beginning of a better internal test framework """ import cStringIO import doctest import httplib import mechanize import socket import sys import zope.testbrowser.browser import zope.testbrowser.tests.helper def set_next_response(body, headers=None, status='200', reason='OK'): global next_response_body global next_response_headers global next_response_status global next_response_reason if headers is None: headers = ( 'Content-Type: text/html\r\n' 'Content-Length: %s\r\n' % len(body)) next_response_body = body next_response_headers = headers next_response_status = status next_response_reason = reason class FauxConnection(object): """A ``mechanize`` compatible connection object.""" def __init__(self, host, timeout=None): pass def set_debuglevel(self, level): pass def _quote(self, url): # the publisher expects to be able to split on whitespace, so we have # to make sure there is none in the URL return url.replace(' ', '%20') def request(self, method, url, body=None, headers=None): if body is None: body = '' if url == '': url = '/' url = self._quote(url) # Construct the headers. header_chunks = [] if headers is not None: for header in headers.items(): header_chunks.append('%s: %s' % header) headers = '\n'.join(header_chunks) + '\n' else: headers = '' # Construct the full HTTP request string, since that is what the # ``HTTPCaller`` wants. request_string = (method + ' ' + url + ' HTTP/1.1\n' + headers + '\n' + body) print request_string.replace('\r', '') def getresponse(self): """Return a ``mechanize`` compatible response. The goal of this method is to convert the Zope Publisher's response to a ``mechanize`` compatible response, which is also understood by mechanize. """ return FauxResponse(next_response_body, next_response_headers, next_response_status, next_response_reason, ) class FauxResponse(object): def __init__(self, content, headers, status, reason): self.content = content self.status = status self.reason = reason self.msg = httplib.HTTPMessage(cStringIO.StringIO(headers), 0) self.content_as_file = cStringIO.StringIO(self.content) def read(self, amt=None): return self.content_as_file.read(amt) def close(self): """To overcome changes in mechanize and socket in python2.5""" pass class FauxHTTPHandler(mechanize.HTTPHandler): http_request = mechanize.HTTPHandler.do_request_ def http_open(self, req): """Open an HTTP connection having a ``mechanize`` request.""" # Here we connect to the publisher. if sys.version_info > (2, 6) and not hasattr(req, 'timeout'): # Workaround mechanize incompatibility with Python # 2.6. See: LP #280334 req.timeout = socket._GLOBAL_DEFAULT_TIMEOUT return self.do_open(FauxConnection, req) class FauxMechanizeBrowser(mechanize.Browser): handler_classes = { # scheme handlers "http": FauxHTTPHandler, "_http_error": mechanize.HTTPErrorProcessor, "_http_default_error": mechanize.HTTPDefaultErrorHandler, # feature handlers "_authen": mechanize.HTTPBasicAuthHandler, "_redirect": mechanize.HTTPRedirectHandler, "_cookies": mechanize.HTTPCookieProcessor, "_refresh": mechanize.HTTPRefreshProcessor, "_referer": mechanize.Browser.handler_classes['_referer'], "_equiv": mechanize.HTTPEquivProcessor, } default_schemes = ["http"] default_others = ["_http_error", "_http_default_error"] default_features = ["_authen", "_redirect", "_cookies"] class Browser(zope.testbrowser.browser.Browser): def __init__(self, url=None): mech_browser = FauxMechanizeBrowser() super(Browser, self).__init__(url=url, mech_browser=mech_browser) def open(self, body, headers=None, status=200, reason='OK', url='http://localhost/'): set_next_response(body, headers, status, reason) zope.testbrowser.browser.Browser.open(self, url) def test_submit_duplicate_name(): """ This test was inspired by bug #723 as testbrowser would pick up the wrong button when having the same name twice in a form. >>> browser = Browser() When given a form with two submit buttons that have the same name: >>> browser.open('''\ ... ...
... ... ...
... ''') # doctest: +ELLIPSIS GET / HTTP/1.1 ... We can specify the second button through it's label/value: >>> browser.getControl('BAD') >>> browser.getControl('BAD').value 'BAD' >>> browser.getControl('BAD').click() # doctest: +REPORT_NDIFF +ELLIPSIS POST / HTTP/1.1 ... Content-type: multipart/form-data; ... Content-disposition: form-data; name="submit_me" BAD ... This also works if the labels have whitespace around them (this tests a regression caused by the original fix for the above): >>> browser.open('''\ ... ...
... ... ...
... ''') # doctest: +ELLIPSIS GET / HTTP/1.1 ... >>> browser.getControl('BAD') >>> browser.getControl('BAD').value ' BAD ' >>> browser.getControl('BAD').click() # doctest: +REPORT_NDIFF +ELLIPSIS POST / HTTP/1.1 ... Content-type: multipart/form-data; ... Content-disposition: form-data; name="submit_me" BAD ... """ def test_file_upload(): """ >>> browser = Browser() When given a form with a file-upload >>> browser.open('''\ ... ...
... ... ...
... ''') # doctest: +ELLIPSIS GET / HTTP/1.1 ... Fill in the form value using add_file: >>> browser.getControl(name='foo').add_file( ... cStringIO.StringIO('sample_data'), 'text/foo', 'x.foo') >>> browser.getControl('OK').click() # doctest: +REPORT_NDIFF +ELLIPSIS POST / HTTP/1.1 ... Content-type: multipart/form-data; ... Content-disposition: form-data; name="foo"; filename="x.foo" Content-type: text/foo sample_data ... You can pass a string to add_file: >>> browser.getControl(name='foo').add_file( ... 'blah blah blah', 'text/blah', 'x.blah') >>> browser.getControl('OK').click() # doctest: +REPORT_NDIFF +ELLIPSIS POST / HTTP/1.1 ... Content-type: multipart/form-data; ... Content-disposition: form-data; name="foo"; filename="x.blah" Content-type: text/blah blah blah blah ... """ def test_submit_gets_referrer(): """ Test for bug #98437: No HTTP_REFERER was sent when submitting a form. >>> browser = Browser() A simple form for testing, like abobe. >>> browser.open('''\ ... ...
... ...
... ''') # doctest: +ELLIPSIS GET / HTTP/1.1 ... Now submit the form, and see that we get an referrer along: >>> form = browser.getForm(id='form') >>> form.submit(name='submit_me') # doctest: +ELLIPSIS POST / HTTP/1.1 ... Referer: http://localhost/ ... """ def test_new_instance_no_contents_should_not_fail(self): """ When first instantiated, the browser has no contents. (Regression test for ) >>> browser = Browser() >>> print browser.contents None """ def test_strip_linebreaks_from_textarea(self): """ >>> browser = Browser() According to http://www.w3.org/TR/html4/appendix/notes.html#h-B.3.1 line break immediately after start tags or immediately before end tags must be ignored, but real browsers only ignore a line break after a start tag. So if we give the following form: >>> browser.open(''' ... ...
... ...
... ''') # doctest: +ELLIPSIS GET / HTTP/1.1 ... The value of the textarea won't contain the first line break: >>> browser.getControl(name='textarea').value 'Foo\\n' Of course, if we add line breaks, so that there are now two line breaks after the start tag, the textarea value will start and end with a line break. >>> browser.open(''' ... ...
... ...
... ''') # doctest: +ELLIPSIS GET / HTTP/1.1 ... >>> browser.getControl(name='textarea').value '\\nFoo\\n' Also, if there is some other whitespace after the start tag, it will be preserved. >>> browser.open(''' ... ...
... ...
... ''') # doctest: +ELLIPSIS GET / HTTP/1.1 ... >>> browser.getControl(name='textarea').value ' Foo ' """ def test_relative_link(): """ RFC 1808 specifies how relative URLs should be resolved, let's see that we conform to it. Let's start with a simple example. >>> browser = Browser() >>> browser.open('''\ ... ... link ... ... ''', url='http://localhost/bar') # doctest: +ELLIPSIS GET /bar HTTP/1.1 ... >>> link = browser.getLink('link') >>> link.url 'http://localhost/foo' It's possible to have a relative URL consisting of only a query part. In that case it should simply be appended to the base URL. >>> browser.open('''\ ... ... link ... ... ''', url='http://localhost/bar') # doctest: +ELLIPSIS GET /bar HTTP/1.1 ... >>> link = browser.getLink('link') >>> link.url 'http://localhost/bar?key=value' In the example above, the base URL was the page URL, but we can also specify a base URL using a tag. >>> browser.open('''\ ... ... link ... ... ''', url='http://localhost/base/bar') # doctest: +ELLIPSIS GET /base/bar HTTP/1.1 ... >>> link = browser.getLink('link') >>> link.url 'http://localhost/base?key=value' """ def test_suite(): return doctest.DocTestSuite( checker=zope.testbrowser.tests.helper.checker, optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS) zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/tests/__init__.py0000644000175000017500000000000012214017652026366 0ustar arnauarnauzope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/tests/test_doctests.py0000644000175000017500000000273312214017652027535 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2011 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import pkg_resources import unittest import zope.app.testing.functional TestBrowserLayer = zope.app.testing.functional.ZCMLLayer( pkg_resources.resource_filename( 'zope.testbrowser', 'ftests/ftesting.zcml'), __name__, 'TestBrowserLayer', allow_teardown=True) def test_suite(): flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS suite = zope.app.testing.functional.FunctionalDocFileSuite( 'README.txt', 'cookies.txt', 'fixed-bugs.txt', optionflags=flags, checker=zope.testbrowser.tests.helper.checker, package='zope.testbrowser') suite.layer = TestBrowserLayer wire = doctest.DocFileSuite('over_the_wire.txt', optionflags=flags, package='zope.testbrowser') wire.level = 2 return unittest.TestSuite((suite, wire)) zope2.13-2.13.21/source/zope.testbrowser/src/zope/testbrowser/testing.py0000644000175000017500000001404312214017652025156 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Zope 3-specific testing code """ import cStringIO import httplib import mechanize import socket import sys import zope.testbrowser.browser class PublisherConnection(object): """A ``mechanize`` compatible connection object.""" def __init__(self, host, timeout=None): from zope.app.testing.functional import HTTPCaller self.caller = HTTPCaller() self.host = host def set_debuglevel(self, level): pass def _quote(self, url): # the publisher expects to be able to split on whitespace, so we have # to make sure there is none in the URL return url.replace(' ', '%20') def request(self, method, url, body=None, headers=None): """Send a request to the publisher. The response will be stored in ``self.response``. """ if body is None: body = '' if url == '': url = '/' url = self._quote(url) # Extract the handle_error option header if sys.version_info >= (2,5): handle_errors_key = 'X-Zope-Handle-Errors' else: handle_errors_key = 'X-zope-handle-errors' handle_errors_header = headers.get(handle_errors_key, True) if handle_errors_key in headers: del headers[handle_errors_key] # Translate string to boolean. handle_errors = {'False': False}.get(handle_errors_header, True) # Construct the headers. header_chunks = [] if headers is not None: for header in headers.items(): header_chunks.append('%s: %s' % header) headers = '\n'.join(header_chunks) + '\n' else: headers = '' # Construct the full HTTP request string, since that is what the # ``HTTPCaller`` wants. request_string = (method + ' ' + url + ' HTTP/1.1\n' + headers + '\n' + body) self.response = self.caller(request_string, handle_errors) def getresponse(self): """Return a ``mechanize`` compatible response. The goal of ths method is to convert the Zope Publisher's reseponse to a ``mechanize`` compatible response, which is also understood by mechanize. """ real_response = self.response._response status = real_response.getStatus() reason = real_response._reason # XXX add a getReason method headers = real_response.getHeaders() headers.sort() headers.insert(0, ('Status', real_response.getStatusString())) headers = '\r\n'.join('%s: %s' % h for h in headers) content = real_response.consumeBody() return PublisherResponse(content, headers, status, reason) class PublisherResponse(object): """``mechanize`` compatible response object.""" def __init__(self, content, headers, status, reason): self.content = content self.status = status self.reason = reason self.msg = httplib.HTTPMessage(cStringIO.StringIO(headers), 0) self.content_as_file = cStringIO.StringIO(self.content) def read(self, amt=None): return self.content_as_file.read(amt) def close(self): """To overcome changes in mechanize and socket in python2.5""" pass class PublisherHTTPHandler(mechanize.HTTPHandler): """Special HTTP handler to use the Zope Publisher.""" def http_request(self, req): # look at data and set content type if req.has_data(): data = req.get_data() if isinstance(data, dict): req.add_data(data['body']) req.add_unredirected_header('Content-type', data['content-type']) return mechanize.HTTPHandler.do_request_(self, req) https_request = http_request def http_open(self, req): """Open an HTTP connection having a ``mechanize`` request.""" # Here we connect to the publisher. if sys.version_info > (2, 6) and not hasattr(req, 'timeout'): # Workaround mechanize incompatibility with Python # 2.6. See: LP #280334 req.timeout = socket._GLOBAL_DEFAULT_TIMEOUT return self.do_open(PublisherConnection, req) https_open = http_open class PublisherMechanizeBrowser(mechanize.Browser): """Special ``mechanize`` browser using the Zope Publisher HTTP handler.""" default_schemes = ['http'] default_others = ['_http_error', '_http_default_error'] default_features = ['_redirect', '_cookies', '_referer', '_refresh', '_equiv', '_basicauth', '_digestauth'] def __init__(self, *args, **kws): inherited_handlers = ['_unknown', '_http_error', '_http_default_error', '_basicauth', '_digestauth', '_redirect', '_cookies', '_referer', '_refresh', '_equiv', '_gzip'] self.handler_classes = {"http": PublisherHTTPHandler} for name in inherited_handlers: self.handler_classes[name] = mechanize.Browser.handler_classes[name] kws['request_class'] = kws.get('request_class', mechanize._request.Request) mechanize.Browser.__init__(self, *args, **kws) class Browser(zope.testbrowser.browser.Browser): """A Zope `testbrowser` Browser that uses the Zope Publisher.""" def __init__(self, url=None): mech_browser = PublisherMechanizeBrowser() super(Browser, self).__init__(url=url, mech_browser=mech_browser) zope2.13-2.13.21/source/zope.testbrowser/src/zope/__init__.py0000644000175000017500000000007012214017652022650 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.testbrowser/src/zope.testbrowser.egg-info/0000755000175000017500000000000012214017652024616 5ustar arnauarnauzope2.13-2.13.21/source/zope.testbrowser/src/zope.testbrowser.egg-info/PKG-INFO0000644000175000017500000017061712214017652025727 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.testbrowser Version: 3.11.1 Summary: Programmable browser for functional black-box tests Home-page: http://pypi.python.org/pypi/zope.testbrowser Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: .. contents:: ``zope.testbrowser`` provides an easy-to-use programmable web browser with special focus on testing. It is used in Zope, but it's not Zope specific at all. For instance, it can be used to test or otherwise interact with any web site. ====================== Detailed Documentation ====================== Different Browsers ------------------ HTTP Browser ~~~~~~~~~~~~ The ``zope.testbrowser.browser`` module exposes a ``Browser`` class that simulates a web browser similar to Mozilla Firefox or IE. >>> from zope.testbrowser.browser import Browser >>> browser = Browser() This version of the browser object can be used to access any web site just as you would do using a normal web browser. WSGI Test Browser ~~~~~~~~~~~~~~~~~ There is also a special version of the ``Browser`` class which uses `wsgi_intercept`_ and can be used to do functional testing of WSGI applications, it can be imported from ``zope.testbrowser.wsgi``: >>> from zope.testbrowser.wsgi import Browser >>> browser = Browser() .. _`wsgi_intercept`: http://pypi.python.org/pypi/wsgi_intercept To use this browser you have to: * use the `wsgi` extra of the ``zope.testbrowser`` egg, * write a subclass of ``zope.testbrowser.wsgi.Layer`` and override the ``make_wsgi_app`` method, * use an instance of the class as the test layer of your test. Example: >>> import zope.testbrowser.wsgi >>> class SimpleLayer(zope.testbrowser.wsgi.Layer): ... def make_wsgi_app(self): ... return simple_app Where ``simple_app`` is the callable of your WSGI application. Zope 3 Test Browser ~~~~~~~~~~~~~~~~~~~ There is also a special version of the ``Browser`` class used to do functional testing of Zope 3 applications, it can be imported from ``zope.testbrowser.testing``: >>> from zope.testbrowser.testing import Browser >>> browser = Browser() Bowser Usage ------------ All browsers are used the same way. An initial page to load can be passed to the ``Browser`` constructor: >>> browser = Browser('http://localhost/@@/testbrowser/simple.html') >>> browser.url 'http://localhost/@@/testbrowser/simple.html' The browser can send arbitrary headers; this is helpful for setting the "Authorization" header or a language value, so that your tests format values the way you expect in your tests, if you rely on zope.i18n locale-based formatting or a similar approach. >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw') >>> browser.addHeader('Accept-Language', 'en-US') An existing browser instance can also `open` web pages: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.url 'http://localhost/@@/testbrowser/simple.html' Once you have opened a web page initially, best practice for writing testbrowser doctests suggests using 'click' to navigate further (as discussed below), except in unusual circumstances. The test browser complies with the IBrowser interface; see ``zope.testbrowser.interfaces`` for full details on the interface. >>> from zope.testbrowser import interfaces >>> from zope.interface.verify import verifyObject >>> verifyObject(interfaces.IBrowser, browser) True Page Contents ------------- The contents of the current page are available: >>> print browser.contents Simple Page

Simple Page

Making assertions about page contents is easy. >>> '

Simple Page

' in browser.contents True Utilizing the doctest facilities, it also possible to do: >>> browser.contents '...

Simple Page

...' Note: Unfortunately, ellipsis (...) cannot be used at the beginning of the output (this is a limitation of doctest). Checking for HTML ----------------- Not all URLs return HTML. Of course our simple page does: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.isHtml True But if we load an image (or other binary file), we do not get HTML: >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif') >>> browser.isHtml False HTML Page Title ---------------- Another useful helper property is the title: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.title 'Simple Page' If a page does not provide a title, it is simply ``None``: >>> browser.open('http://localhost/@@/testbrowser/notitle.html') >>> browser.title However, if the output is not HTML, then an error will occur trying to access the title: >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif') >>> browser.title Traceback (most recent call last): ... BrowserStateError: not viewing HTML Headers ------- As you can see, the `contents` of the browser does not return any HTTP headers. The headers are accessible via a separate attribute, which is an ``httplib.HTTPMessage`` instance (httplib is a part of Python's standard library): >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.headers The headers can be accessed as a string: >>> print browser.headers Status: 200 OK Content-Length: 123 Content-Type: text/html;charset=utf-8 X-Powered-By: Zope (www.zope.org), Python (www.python.org) Or as a mapping: >>> browser.headers['content-type'] 'text/html;charset=utf-8' Cookies ------- When a Set-Cookie header is available, it can be found in the headers, as seen above. Here, we use a view that will make the server set cookies with the values we provide. >>> browser.open('http://localhost/set_cookie.html?name=foo&value=bar') >>> browser.headers['set-cookie'].replace(';', '') 'foo=bar' It is also available in the browser's ``cookies`` attribute. This is an extended mapping interface that allows getting, setting, and deleting the cookies that the browser is remembering *for the current url*. Here are a few examples. >>> browser.cookies['foo'] 'bar' >>> browser.cookies.keys() ['foo'] >>> browser.cookies.values() ['bar'] >>> browser.cookies.items() [('foo', 'bar')] >>> 'foo' in browser.cookies True >>> 'bar' in browser.cookies False >>> len(browser.cookies) 1 >>> print(dict(browser.cookies)) {'foo': 'bar'} >>> browser.cookies['sha'] = 'zam' >>> len(browser.cookies) 2 >>> sorted(browser.cookies.items()) [('foo', 'bar'), ('sha', 'zam')] >>> browser.open('http://localhost/get_cookie.html') >>> print browser.headers.get('set-cookie') None >>> print browser.contents # server got the cookie change foo: bar sha: zam >>> sorted(browser.cookies.items()) [('foo', 'bar'), ('sha', 'zam')] >>> browser.cookies.clearAll() >>> len(browser.cookies) 0 Many more examples, and a discussion of the additional methods available, can be found in cookies.txt. Navigation and Link Objects --------------------------- If you want to simulate clicking on a link, get the link and `click` on it. In the `navigate.html` file there are several links set up to demonstrate the capabilities of the link objects and their `click` method. The simplest way to get a link is via the anchor text. In other words the text you would see in a browser (text and url searches are substring searches): >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...Link Text...' >>> link = browser.getLink('Link Text') >>> link Link objects comply with the ILink interface. >>> verifyObject(interfaces.ILink, link) True Links expose several attributes for easy access. >>> link.text 'Link Text' >>> link.tag # links can also be image maps. 'a' >>> link.url # it's normalized 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text' >>> link.attrs {'href': 'navigate.html?message=By+Link+Text'} Links can be "clicked" and the browser will navigate to the referenced URL. >>> link.click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text' >>> browser.contents '...Message: By Link Text...' When finding a link by its text, whitespace is normalized. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...> Link Text \n with Whitespace\tNormalization (and parens) >> link = browser.getLink('Link Text with Whitespace Normalization ' ... '(and parens)') >>> link >>> link.text 'Link Text with Whitespace Normalization (and parens)' >>> link.click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text+with+Normalization' >>> browser.contents '...Message: By Link Text with Normalization...' When a link text matches more than one link, by default the first one is chosen. You can, however, specify the index of the link and thus retrieve a later matching link: >>> browser.getLink('Link Text') >>> browser.getLink('Link Text', index=1) Note that clicking a link object after its browser page has expired will generate an error. >>> link.click() Traceback (most recent call last): ... ExpiredError You can also find the link by its URL, >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...Using the URL...' >>> browser.getLink(url='?message=By+URL').click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+URL' >>> browser.contents '...Message: By URL...' or its id: >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...By Anchor Id...' >>> browser.getLink(id='anchorid').click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Id' >>> browser.contents '...Message: By Id...' You thought we were done here? Not so quickly. The `getLink` method also supports image maps, though not by specifying the coordinates, but using the area's id: >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> link = browser.getLink(id='zope3') >>> link.tag 'area' >>> link.click() >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name' >>> browser.contents '...Message: Zope 3 Name...' Getting a nonexistent link raises an exception. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.getLink('This does not exist') Traceback (most recent call last): ... LinkNotFoundError A convenience method is provided to follow links; this uses the same arguments as `getLink`, but clicks on the link instead of returning the link object. >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.contents '...Link Text...' >>> browser.follow('Link Text') >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text' >>> browser.contents '...Message: By Link Text...' >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.follow(url='?message=By+URL') >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=By+URL' >>> browser.contents '...Message: By URL...' >>> browser.open('http://localhost/@@/testbrowser/navigate.html') >>> browser.follow(id='zope3') >>> browser.url 'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name' >>> browser.contents '...Message: Zope 3 Name...' Attempting to follow links that don't exist raises the same exception as asking for the link object: >>> browser.follow('This does not exist') Traceback (most recent call last): ... LinkNotFoundError Other Navigation ---------------- Like in any normal browser, you can reload a page: >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.url 'http://localhost/@@/testbrowser/simple.html' >>> browser.reload() >>> browser.url 'http://localhost/@@/testbrowser/simple.html' You can also go back: >>> browser.open('http://localhost/@@/testbrowser/notitle.html') >>> browser.url 'http://localhost/@@/testbrowser/notitle.html' >>> browser.goBack() >>> browser.url 'http://localhost/@@/testbrowser/simple.html' Controls -------- One of the most important features of the browser is the ability to inspect and fill in values for the controls of input forms. To do so, let's first open a page that has a bunch of controls: >>> browser.open('http://localhost/@@/testbrowser/controls.html') Obtaining a Control ~~~~~~~~~~~~~~~~~~~ You look up browser controls with the 'getControl' method. The default first argument is 'label', and looks up the form on the basis of any associated label. >>> control = browser.getControl('Text Control') >>> control >>> browser.getControl(label='Text Control') # equivalent If you request a control that doesn't exist, the code raises a LookupError: >>> browser.getControl('Does Not Exist') Traceback (most recent call last): ... LookupError: label 'Does Not Exist' If you request a control with an ambiguous lookup, the code raises an AmbiguityError. >>> browser.getControl('Ambiguous Control') Traceback (most recent call last): ... AmbiguityError: label 'Ambiguous Control' This is also true if an option in a control is ambiguous in relation to the control itself. >>> browser.getControl('Sub-control Ambiguity') Traceback (most recent call last): ... AmbiguityError: label 'Sub-control Ambiguity' Ambiguous controls may be specified using an index value. We use the control's value attribute to show the two controls; this attribute is properly introduced below. >>> browser.getControl('Ambiguous Control', index=0) >>> browser.getControl('Ambiguous Control', index=0).value 'First' >>> browser.getControl('Ambiguous Control', index=1).value 'Second' >>> browser.getControl('Sub-control Ambiguity', index=0) >>> browser.getControl('Sub-control Ambiguity', index=1).optionValue 'ambiguous' Label searches are against stripped, whitespace-normalized, no-tag versions of the text. Text applied to searches is also stripped and whitespace normalized. The search finds results if the text search finds the whole words of your text in a label. Thus, for instance, a search for 'Add' will match the label 'Add a Client' but not 'Address'. Case is honored. >>> browser.getControl('Label Needs Whitespace Normalization') >>> browser.getControl('label needs whitespace normalization') Traceback (most recent call last): ... LookupError: label 'label needs whitespace normalization' >>> browser.getControl(' Label Needs Whitespace ') >>> browser.getControl('Whitespace') >>> browser.getControl('hitespace') Traceback (most recent call last): ... LookupError: label 'hitespace' >>> browser.getControl('[non word characters should not confuse]') Multiple labels can refer to the same control (simply because that is possible in the HTML 4.0 spec). >>> browser.getControl('Multiple labels really') >>> browser.getControl('really are possible') >>> browser.getControl('really') # OK: ambiguous labels, but not ambiguous control A label can be connected with a control using the 'for' attribute and also by containing a control. >>> browser.getControl( ... 'Labels can be connected by containing their respective fields') Get also accepts one other search argument, 'name'. Only one of 'label' and 'name' may be used at a time. The 'name' keyword searches form field names. >>> browser.getControl(name='text-value') >>> browser.getControl(name='ambiguous-control-name') Traceback (most recent call last): ... AmbiguityError: name 'ambiguous-control-name' >>> browser.getControl(name='does-not-exist') Traceback (most recent call last): ... LookupError: name 'does-not-exist' >>> browser.getControl(name='ambiguous-control-name', index=1).value 'Second' Combining 'label' and 'name' raises a ValueError, as does supplying neither of them. >>> browser.getControl(label='Ambiguous Control', name='ambiguous-control-name') Traceback (most recent call last): ... ValueError: Supply one and only one of "label" and "name" as arguments >>> browser.getControl() Traceback (most recent call last): ... ValueError: Supply one and only one of "label" and "name" as arguments Radio and checkbox fields are unusual in that their labels and names may point to different objects: names point to logical collections of radio buttons or checkboxes, but labels may only be used for individual choices within the logical collection. This means that obtaining a radio button by label gets a different object than obtaining the radio collection by name. Select options may also be searched by label. >>> browser.getControl(name='radio-value') >>> browser.getControl('Zwei') >>> browser.getControl('One') >>> browser.getControl('Tres') Characteristics of controls and subcontrols are discussed below. Control Objects ~~~~~~~~~~~~~~~ Controls provide IControl. >>> ctrl = browser.getControl('Text Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True They have several useful attributes: - the name as which the control is known to the form: >>> ctrl.name 'text-value' - the value of the control, which may also be set: >>> ctrl.value 'Some Text' >>> ctrl.value = 'More Text' >>> ctrl.value 'More Text' - the type of the control: >>> ctrl.type 'text' - a flag describing whether the control is disabled: >>> ctrl.disabled False - and a flag to tell us whether the control can have multiple values: >>> ctrl.multiple False Additionally, controllers for select, radio, and checkbox provide IListControl. These fields have four other attributes and an additional method: >>> ctrl = browser.getControl('Multiple Select Control') >>> ctrl >>> ctrl.disabled False >>> ctrl.multiple True >>> verifyObject(interfaces.IListControl, ctrl) True - 'options' lists all available value options. >>> ctrl.options ['1', '2', '3'] - 'displayOptions' lists all available options by label. The 'label' attribute on an option has precedence over its contents, which is why our last option is 'Third' in the display. >>> ctrl.displayOptions ['Un', 'Deux', 'Third'] - 'displayValue' lets you get and set the displayed values of the control of the select box, rather than the actual values. >>> ctrl.value [] >>> ctrl.displayValue [] >>> ctrl.displayValue = ['Un', 'Deux'] >>> ctrl.displayValue ['Un', 'Deux'] >>> ctrl.value ['1', '2'] - 'controls' gives you a list of the subcontrol objects in the control (subcontrols are discussed below). >>> ctrl.controls [, , ] - The 'getControl' method lets you get subcontrols by their label or their value. >>> ctrl.getControl('Un') >>> ctrl.getControl('Deux') >>> ctrl.getControl('Trois') # label attribute >>> ctrl.getControl('Third') # contents >>> browser.getControl('Third') # ambiguous in the browser, so useful Traceback (most recent call last): ... AmbiguityError: label 'Third' Finally, submit controls provide ISubmitControl, and image controls provide IImageSubmitControl, which extents ISubmitControl. These both simply add a 'click' method. For image submit controls, you may also provide a coordinates argument, which is a tuple of (x, y). These submit the forms, and are demonstrated below as we examine each control individually. ItemControl Objects ~~~~~~~~~~~~~~~~~~~ As introduced briefly above, using labels to obtain elements of a logical radio button or checkbox collection returns item controls, which are parents. Manipulating the value of these controls affects the parent control. >>> browser.getControl(name='radio-value').value ['2'] >>> browser.getControl('Zwei').optionValue # read-only. '2' >>> browser.getControl('Zwei').selected True >>> verifyObject(interfaces.IItemControl, browser.getControl('Zwei')) True >>> browser.getControl('Ein').selected = True >>> browser.getControl('Ein').selected True >>> browser.getControl('Zwei').selected False >>> browser.getControl(name='radio-value').value ['1'] >>> browser.getControl('Ein').selected = False >>> browser.getControl(name='radio-value').value [] >>> browser.getControl('Zwei').selected = True Checkbox collections behave similarly, as shown below. Controls with subcontrols-- Various Controls ~~~~~~~~~~~~~~~~ The various types of controls are demonstrated here. - Text Control The text control we already introduced above. - Password Control >>> ctrl = browser.getControl('Password Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value 'Password' >>> ctrl.value = 'pass now' >>> ctrl.value 'pass now' >>> ctrl.disabled False >>> ctrl.multiple False - Hidden Control >>> ctrl = browser.getControl(name='hidden-value') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value 'Hidden' >>> ctrl.value = 'More Hidden' >>> ctrl.disabled False >>> ctrl.multiple False - Text Area Control >>> ctrl = browser.getControl('Text Area Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value ' Text inside\n area!\n ' >>> ctrl.value = 'A lot of\n text.' >>> ctrl.disabled False >>> ctrl.multiple False - File Control File controls are used when a form has a file-upload field. To specify data, call the add_file method, passing: - A file-like object - a content type, and - a file name >>> ctrl = browser.getControl('File Control') >>> ctrl >>> verifyObject(interfaces.IControl, ctrl) True >>> ctrl.value is None True >>> import cStringIO >>> ctrl.add_file(cStringIO.StringIO('File contents'), ... 'text/plain', 'test.txt') The file control (like the other controls) also knows if it is disabled or if it can have multiple values. >>> ctrl.disabled False >>> ctrl.multiple False - Selection Control (Single-Valued) >>> ctrl = browser.getControl('Single Select Control') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value ['1'] >>> ctrl.value = ['2'] >>> ctrl.disabled False >>> ctrl.multiple False >>> ctrl.options ['1', '2', '3'] >>> ctrl.displayOptions ['Uno', 'Dos', 'Third'] >>> ctrl.displayValue ['Dos'] >>> ctrl.displayValue = ['Tres'] >>> ctrl.displayValue ['Third'] >>> ctrl.displayValue = ['Dos'] >>> ctrl.displayValue ['Dos'] >>> ctrl.displayValue = ['Third'] >>> ctrl.displayValue ['Third'] >>> ctrl.value ['3'] - Selection Control (Multi-Valued) This was already demonstrated in the introduction to control objects above. - Checkbox Control (Single-Valued; Unvalued) >>> ctrl = browser.getControl(name='single-unvalued-checkbox-value') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value True >>> ctrl.value = False >>> ctrl.disabled False >>> ctrl.multiple True >>> ctrl.options [True] >>> ctrl.displayOptions ['Single Unvalued Checkbox'] >>> ctrl.displayValue [] >>> verifyObject( ... interfaces.IItemControl, ... browser.getControl('Single Unvalued Checkbox')) True >>> browser.getControl('Single Unvalued Checkbox').optionValue 'on' >>> browser.getControl('Single Unvalued Checkbox').selected False >>> ctrl.displayValue = ['Single Unvalued Checkbox'] >>> ctrl.displayValue ['Single Unvalued Checkbox'] >>> browser.getControl('Single Unvalued Checkbox').selected True >>> browser.getControl('Single Unvalued Checkbox').selected = False >>> browser.getControl('Single Unvalued Checkbox').selected False >>> ctrl.displayValue [] >>> browser.getControl( ... name='single-disabled-unvalued-checkbox-value').disabled True - Checkbox Control (Single-Valued, Valued) >>> ctrl = browser.getControl(name='single-valued-checkbox-value') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value ['1'] >>> ctrl.value = [] >>> ctrl.disabled False >>> ctrl.multiple True >>> ctrl.options ['1'] >>> ctrl.displayOptions ['Single Valued Checkbox'] >>> ctrl.displayValue [] >>> verifyObject( ... interfaces.IItemControl, ... browser.getControl('Single Valued Checkbox')) True >>> browser.getControl('Single Valued Checkbox').selected False >>> browser.getControl('Single Valued Checkbox').optionValue '1' >>> ctrl.displayValue = ['Single Valued Checkbox'] >>> ctrl.displayValue ['Single Valued Checkbox'] >>> browser.getControl('Single Valued Checkbox').selected True >>> browser.getControl('Single Valued Checkbox').selected = False >>> browser.getControl('Single Valued Checkbox').selected False >>> ctrl.displayValue [] - Checkbox Control (Multi-Valued) >>> ctrl = browser.getControl(name='multi-checkbox-value') >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.value ['1', '3'] >>> ctrl.value = ['1', '2'] >>> ctrl.disabled False >>> ctrl.multiple True >>> ctrl.options ['1', '2', '3'] >>> ctrl.displayOptions ['One', 'Two', 'Three'] >>> ctrl.displayValue ['One', 'Two'] >>> ctrl.displayValue = ['Two'] >>> ctrl.value ['2'] >>> browser.getControl('Two').optionValue '2' >>> browser.getControl('Two').selected True >>> verifyObject(interfaces.IItemControl, browser.getControl('Two')) True >>> browser.getControl('Three').selected = True >>> browser.getControl('Three').selected True >>> browser.getControl('Two').selected True >>> ctrl.value ['2', '3'] >>> browser.getControl('Two').selected = False >>> ctrl.value ['3'] >>> browser.getControl('Three').selected = False >>> ctrl.value [] - Radio Control This is how you get a radio button based control: >>> ctrl = browser.getControl(name='radio-value') This shows the existing value of the control, as it was in the HTML received from the server: >>> ctrl.value ['2'] We can then unselect it: >>> ctrl.value = [] >>> ctrl.value [] We can also reselect it: >>> ctrl.value = ['2'] >>> ctrl.value ['2'] displayValue shows the text the user would see next to the control: >>> ctrl.displayValue ['Zwei'] This is just unit testing: >>> ctrl >>> verifyObject(interfaces.IListControl, ctrl) True >>> ctrl.disabled False >>> ctrl.multiple False >>> ctrl.options ['1', '2', '3'] >>> ctrl.displayOptions ['Ein', 'Zwei', 'Drei'] >>> ctrl.displayValue = ['Ein'] >>> ctrl.value ['1'] >>> ctrl.displayValue ['Ein'] The radio control subcontrols were illustrated above. - Image Control >>> ctrl = browser.getControl(name='image-value') >>> ctrl >>> verifyObject(interfaces.IImageSubmitControl, ctrl) True >>> ctrl.value '' >>> ctrl.disabled False >>> ctrl.multiple False - Submit Control >>> ctrl = browser.getControl(name='submit-value') >>> ctrl >>> browser.getControl('Submit This') # value of submit button is a label >>> browser.getControl('Standard Submit Control') # label tag is legal >>> browser.getControl('Submit') # multiple labels, but same control >>> verifyObject(interfaces.ISubmitControl, ctrl) True >>> ctrl.value 'Submit This' >>> ctrl.disabled False >>> ctrl.multiple False Using Submitting Controls ~~~~~~~~~~~~~~~~~~~~~~~~~ Both the submit and image type should be clickable and submit the form: >>> browser.getControl('Text Control').value = 'Other Text' >>> browser.getControl('Submit').click() >>> print browser.contents ... Other Text ... Submit This ... Note that if you click a submit object after the associated page has expired, you will get an error. >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> ctrl = browser.getControl('Submit') >>> ctrl.click() >>> ctrl.click() Traceback (most recent call last): ... ExpiredError All the above also holds true for the image control: >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> browser.getControl('Text Control').value = 'Other Text' >>> browser.getControl(name='image-value').click() >>> print browser.contents ... Other Text ... 1 1 ... >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> ctrl = browser.getControl(name='image-value') >>> ctrl.click() >>> ctrl.click() Traceback (most recent call last): ... ExpiredError But when sending an image, you can also specify the coordinate you clicked: >>> browser.open('http://localhost/@@/testbrowser/controls.html') >>> browser.getControl(name='image-value').click((50,25)) >>> print browser.contents ... 50 25 ... Forms ----- Because pages can have multiple forms with like-named controls, it is sometimes necessary to access forms by name or id. The browser's `forms` attribute can be used to do so. The key value is the form's name or id. If more than one form has the same name or id, the first one will be returned. >>> browser.open('http://localhost/@@/testbrowser/forms.html') >>> form = browser.getForm(name='one') Form instances conform to the IForm interface. >>> verifyObject(interfaces.IForm, form) True The form exposes several attributes related to forms: - The name of the form: >>> form.name 'one' - The id of the form: >>> form.id '1' - The action (target URL) when the form is submitted: >>> form.action 'http://localhost/@@/testbrowser/forms.html' - The method (HTTP verb) used to transmit the form data: >>> form.method 'GET' Besides those attributes, you have also a couple of methods. Like for the browser, you can get control objects, but limited to the current form... >>> form.getControl(name='text-value') ...and submit the form. >>> form.submit('Submit') >>> print browser.contents ... First Text ... Submitting also works without specifying a control, as shown below, which is it's primary reason for existing in competition with the control submission discussed above. Now let me show you briefly that looking up forms is sometimes important. In the `forms.html` template, we have four forms all having a text control named `text-value`. Now, if I use the browser's `get` method, >>> browser.getControl(name='text-value') Traceback (most recent call last): ... AmbiguityError: name 'text-value' >>> browser.getControl('Text Control') Traceback (most recent call last): ... AmbiguityError: label 'Text Control' I'll always get an ambiguous form field. I can use the index argument, or with the `getForm` method I can disambiguate by searching only within a given form: >>> form = browser.getForm('2') >>> form.getControl(name='text-value').value 'Second Text' >>> form.submit('Submit') >>> browser.contents '...Second Text...' >>> form = browser.getForm('2') >>> form.getControl('Submit').click() >>> browser.contents '...Second Text...' >>> browser.getForm('3').getControl('Text Control').value 'Third Text' The last form on the page does not have a name, an id, or a submit button. Working with it is still easy, thanks to a index attribute that guarantees order. (Forms without submit buttons are sometimes useful for JavaScript.) >>> form = browser.getForm(index=3) >>> form.submit() >>> browser.contents '...Fourth Text...Submitted without the submit button....' If a form is requested that does not exists, an exception will be raised. >>> form = browser.getForm('does-not-exist') Traceback (most recent call last): LookupError If the HTML page contains only one form, no arguments to `getForm` are needed: >>> oneform = Browser() >>> oneform.open('http://localhost/@@/testbrowser/oneform.html') >>> form = oneform.getForm() If the HTML page contains more than one form, `index` is needed to disambiguate if no other arguments are provided: >>> browser.getForm() Traceback (most recent call last): ValueError: if no other arguments are given, index is required. Submitting a posts body directly -------------------------------- In addition to the open method, zope.testbrowser.testing.Browser has a ``post`` method that allows a request body to be supplied. This method is particularly helpful when testing Ajax methods. Let's visit a page that echos it's request: >>> browser.open('http://localhost/@@echo.html') >>> print browser.contents, HTTP_USER_AGENT: Python-urllib/2.4 HTTP_CONNECTION: close HTTP_COOKIE: REMOTE_ADDR: 127.0.0.1 HTTP_ACCEPT_LANGUAGE: en-US REQUEST_METHOD: GET HTTP_HOST: localhost PATH_INFO: /@@echo.html SERVER_PROTOCOL: HTTP/1.1 QUERY_STRING: Body: '' Now, we'll try a post. The post method takes a URL, a data string, and an optional content type. If we just pass a string, then a URL-encoded query string is assumed: >>> browser.post('http://localhost/@@echo.html', 'x=1&y=2') >>> print browser.contents, CONTENT_LENGTH: 7 HTTP_USER_AGENT: Python-urllib/2.4 HTTP_CONNECTION: close HTTP_COOKIE: REMOTE_ADDR: 127.0.0.1 HTTP_ACCEPT_LANGUAGE: en-US y: 2 REQUEST_METHOD: POST HTTP_HOST: localhost PATH_INFO: /@@echo.html CONTENT_TYPE: application/x-www-form-urlencoded SERVER_PROTOCOL: HTTP/1.1 QUERY_STRING: x: 1 Body: '' The body is empty because it is consumed to get form data. We can pass a content-type explicitly: >>> browser.post('http://localhost/@@echo.html', ... '{"x":1,"y":2}', 'application/x-javascript') >>> print browser.contents, CONTENT_LENGTH: 13 HTTP_USER_AGENT: Python-urllib/2.4 HTTP_CONNECTION: close HTTP_COOKIE: REMOTE_ADDR: 127.0.0.1 HTTP_ACCEPT_LANGUAGE: en-US REQUEST_METHOD: POST HTTP_HOST: localhost PATH_INFO: /@@echo.html CONTENT_TYPE: application/x-javascript SERVER_PROTOCOL: HTTP/1.1 Body: '{"x":1,"y":2}' Here, the body is left in place because it isn't form data. Performance Testing ------------------- Browser objects keep up with how much time each request takes. This can be used to ensure a particular request's performance is within a tolerable range. Be very careful using raw seconds, cross-machine differences can be huge, pystones is usually a better choice. >>> browser.open('http://localhost/@@/testbrowser/simple.html') >>> browser.lastRequestSeconds < 10 # really big number for safety True >>> browser.lastRequestPystones < 10000 # really big number for safety True Handling Errors when using Zope 3's Publisher --------------------------------------------- A very useful feature of the publisher is the automatic graceful handling of application errors, such as invalid URLs: >>> browser.open('http://localhost/invalid') Traceback (most recent call last): ... HTTPError: HTTP Error 404: Not Found Note that the above error was thrown by ``mechanize`` and not by the publisher. For debugging purposes, however, it can be very useful to see the original exception caused by the application. In those cases you can set the ``handleErrors`` property of the browser to ``False``. It is defaulted to ``True``: >>> browser.handleErrors True So when we tell the publisher not to handle the errors, >>> browser.handleErrors = False we get a different, Zope internal error: >>> browser.open('http://localhost/invalid') Traceback (most recent call last): ... NotFound: Object: , name: u'invalid' NB: Setting the handleErrors attribute to False will only change anything if the http server you're testing is using Zope 3's publisher or can otherwise respond appropriately to an 'X-zope-handle-errors' header in requests. When the testbrowser is raising HttpErrors, the errors still hit the test. Sometimes we don't want that to happen, in situations where there are edge cases that will cause the error to be predictably but infrequently raised. Time is a primary cause of this. To get around this, one can set the raiseHttpErrors to False. >>> browser.handleErrors = True >>> browser.raiseHttpErrors = False This will cause HttpErrors not to propagate. >>> browser.open('http://localhost/invalid') The headers are still there, though. >>> '404 Not Found' in str(browser.headers) True If we don't handle the errors, and allow internal ones to propagate, however, this flag doesn't affect things. >>> browser.handleErrors = False >>> browser.open('http://localhost/invalid') Traceback (most recent call last): ... NotFound: Object: , name: u'invalid' >>> browser.raiseHttpErrors = True Hand-Holding ------------ Instances of the various objects ensure that users don't set incorrect instance attributes accidentally. >>> browser.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Browser' object has no attribute 'nonexistant' >>> form.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Form' object has no attribute 'nonexistant' >>> control.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Control' object has no attribute 'nonexistant' >>> link.nonexistant = None Traceback (most recent call last): ... AttributeError: 'Link' object has no attribute 'nonexistant' ======= CHANGES ======= 3.11.1 (2011-01-24) ------------------- - Fixing brown bag release 3.11.0. 3.11.0 (2011-01-24) ------------------- - Added `wsgi_intercept` support (came from ``zope.app.wsgi.testlayer``). 3.10.4 (2011-01-14) ------------------- - Move the over-the-wire.txt doctest out of the TestBrowserLayer as it doesn't need or use it. - Fix test compatibility with zope.app.testing 3.8.1. 3.10.3 (2010-10-15) ------------------- - Fixed backwards compatibility with ``zope.app.wsgi.testlayer``. 3.10.2 (2010-10-15) ------------------- - Fixed Python 2.7 compatibility in Browser.handleErrors. 3.10.1 (2010-09-21) ------------------- - Fixed a bug that caused the ``Browser`` to keep it's previous ``contents`` The places are: - Link.click() - SubmitControl.click() - ImageControl.click() - Form.submit() - Also adjusted exception messages at the above places to match pre version 3.4.1 messages. 3.10.0 (2010-09-14) ------------------- - LP #98437: use mechanize's built-in ``submit()`` to submit forms, allowing mechanize to set the "Referer:" (sic) header appropriately. - Fixed tests to run with ``zope.app.testing`` 3.8 and above. 3.9.0 (2010-05-17) ------------------ - LP #568806: Update dependency ``mechanize >= 0.2.0``, which now includes the ``ClientForm`` APIs. Remove use of ``urllib2`` APIs (incompatible with ``mechanize 0.2.0``) in favor of ``mechanize`` equivalents. Thanks to John J. Lee for the patch. - Use stdlib ``doctest`` module, instead of ``zope.testing.doctest``. - **Caution:** This version is no longer fully compatible with Python 2.4: ``handleErrors = False`` no longer works. 3.8.1 (2010-04-19) ------------------ - Pinned dependency on mechanize to prevent use of the upcoming 0.2.0 release before we have time to adjust to its API changes. - LP #98396: testbrowser resolves relative URLs incorrectly. 3.8.0 (2010-03-05) ------------------ - Added ``follow`` convenience method which gets and follows a link. 3.7.0 (2009-12-17) ------------------ - Moved zope.app.testing dependency into the scope of the PublisherConnection class. Zope2 specifies its own PublisherConnection which isn't dependent on zope.app.testing. - Fixed LP #419119: return None when the browser has no contents instead of raising an exception. 3.7.0a1 (2009-08-29) -------------------- - Remove dependency on zope.app.publisher in favor of zope.browserpage, zope.browserresource and zope.ptresource. - Remove dependencies on zope.app.principalannotation and zope.securitypolicy by using the simple PermissiveSecurityPolicy. We aren't testing security in our tests. - Replaced the testing dependency on zope.app.zcmlfiles with explicit dependencies of a minimal set of packages. - Remove unneeded zope.app.authentication from ftesting.zcml. - Test dependency on zope.securitypolicy instead of its app variant. 3.6.0a2 (2009-01-31) -------------------- - Test dependency on zope.site.folder instead of zope.app.folder. - Remove useless test dependency in zope.app.component. 3.6.0a1 (2009-01-08) -------------------- - Author e-mail to zope-dev rather than zope3-dev. - New lines are no longer stripped in XML and HTML code contained in a textarea; fix requires ClientForm >= 0.2.10 (LP #268139). - Added ``cookies`` attribute to browser for easy manipulation of browser cookies. See brief example in main documentation, plus new ``cookies.txt`` documentation. 3.5.1 (2008-10-10) ------------------ - Provide a work around for a mechanize/urllib2 bug on Python 2.6 missing 'timeout' attribute on 'Request' base class. - Provide a work around for a mechanize/urllib2 bug in creating request objects that won't handle fragment URLs correctly. 3.5.0 (2008-03-30) ------------------ - Added a zope.testbrowser.testing.Browser.post method that allows tests to supply a body and a content type. This is handy for testing Ajax requests with non-form input (e.g. JSON). - Remove vendor import of mechanize. - Fix bug that caused HTTP exception tracebacks to differ between version 3.4.0 and 3.4.1. - Workaround for bug in Python Cookie.SimpleCookie when handling unicode strings. - Fix bug introduced in 3.4.1 that created incompatible tracebacks in doctests. This necessitated adding a patched mechanize to the source tree; patches have been sent to the mechanize project. - Fix https://bugs.launchpad.net/bugs/149517 by adding zope.interface and zope.schema as real dependencies - Fix browser.getLink documentation that was not updated since the last API modification. - Move tests for fixed bugs to a separate file. - Removed non-functional and undocumented code intended to help test servers using virtual hosting. 3.4.2 (2007-10-31) ------------------ - Resolve ``ZopeSecurityPolicy`` deprecation warning. 3.4.1 (2007-09-01) ------------------ * Updated to mechanize 0.1.7b and ClientForm 0.2.7. These are now pulled in via egg dependencies. * ``zope.testbrowser`` now works on Python 2.5. 3.4.0 (2007-06-04) ------------------ * Added the ability to suppress raising exceptions on HTTP errors (``raiseHttpErrors`` attribute). * Made the tests more resilient to HTTP header formatting changes with the REnormalizer. 3.4.0a1 (2007-04-22) -------------------- Initial release as a separate project, corresponds to zope.testbrowser from Zope 3.4.0a1 Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Internet :: WWW/HTTP zope2.13-2.13.21/source/zope.testbrowser/src/zope.testbrowser.egg-info/dependency_links.txt0000644000175000017500000000000112214017652030664 0ustar arnauarnau zope2.13-2.13.21/source/zope.testbrowser/src/zope.testbrowser.egg-info/requires.txt0000644000175000017500000000055112214017652027217 0ustar arnauarnaumechanize>=0.2.0 setuptools zope.interface zope.schema pytz [test] zope.browserpage zope.browserresource zope.component zope.container zope.principalregistry zope.ptresource zope.publisher zope.security zope.site zope.traversing zope.app.appsetup zope.app.publication zope.app.testing >= 3.8.1 [zope-functional-testing] zope.app.testing [wsgi] wsgi_interceptzope2.13-2.13.21/source/zope.testbrowser/src/zope.testbrowser.egg-info/namespace_packages.txt0000644000175000017500000000000512214017652031144 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.testbrowser/src/zope.testbrowser.egg-info/top_level.txt0000644000175000017500000000000512214017652027343 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.testbrowser/src/zope.testbrowser.egg-info/SOURCES.txt0000644000175000017500000000277512214017652026515 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.testbrowser.egg-info/PKG-INFO src/zope.testbrowser.egg-info/SOURCES.txt src/zope.testbrowser.egg-info/dependency_links.txt src/zope.testbrowser.egg-info/namespace_packages.txt src/zope.testbrowser.egg-info/not-zip-safe src/zope.testbrowser.egg-info/requires.txt src/zope.testbrowser.egg-info/top_level.txt src/zope/testbrowser/README.txt src/zope/testbrowser/__init__.py src/zope/testbrowser/browser.py src/zope/testbrowser/cookies.py src/zope/testbrowser/cookies.txt src/zope/testbrowser/fixed-bugs.txt src/zope/testbrowser/interfaces.py src/zope/testbrowser/over_the_wire.txt src/zope/testbrowser/testing.py src/zope/testbrowser/wsgi.py src/zope/testbrowser/ftests/__init__.py src/zope/testbrowser/ftests/controls.html src/zope/testbrowser/ftests/cookies.html src/zope/testbrowser/ftests/forms.html src/zope/testbrowser/ftests/fragment.html src/zope/testbrowser/ftests/ftesting.zcml src/zope/testbrowser/ftests/navigate.html src/zope/testbrowser/ftests/notitle.html src/zope/testbrowser/ftests/oneform.html src/zope/testbrowser/ftests/radio.html src/zope/testbrowser/ftests/simple.html src/zope/testbrowser/ftests/status_lead.html src/zope/testbrowser/ftests/textarea.html src/zope/testbrowser/ftests/zope3logo.gif src/zope/testbrowser/tests/__init__.py src/zope/testbrowser/tests/helper.py src/zope/testbrowser/tests/test_browser.py src/zope/testbrowser/tests/test_doctests.py src/zope/testbrowser/tests/test_wsgi.pyzope2.13-2.13.21/source/zope.testbrowser/src/zope.testbrowser.egg-info/not-zip-safe0000644000175000017500000000000112214017652027044 0ustar arnauarnau zope2.13-2.13.21/source/zope.exceptions/0000755000175000017500000000000012214017562016574 5ustar arnauarnauzope2.13-2.13.21/source/zope.exceptions/setup.py0000644000175000017500000000551112214017562020310 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.exceptions package """ import os from setuptools import setup, find_packages import sys if sys.version_info < (3, ): extra = {} else: # Python 3 support: extra = dict( use_2to3=True, setup_requires=['zope.fixers'], use_2to3_fixers = ['zope.fixers'], ) def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.exceptions', version='3.6.2', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description='Zope Exceptions', long_description=(read('README.txt') + '\n\n' + read('CHANGES.txt')), keywords = 'zope exceptions', classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Programming Language :: Python :: 2.4', 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.1', 'Programming Language :: Python :: 3.2', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://cheeseshop.python.org/pypi/zope.exceptions', license='ZPL 2.1', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope'], install_requires=['setuptools', 'zope.interface', ], test_suite = 'zope.exceptions.tests', include_package_data = True, zip_safe = False, **extra) zope2.13-2.13.21/source/zope.exceptions/PKG-INFO0000644000175000017500000000740412214017562017676 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.exceptions Version: 3.6.2 Summary: Zope Exceptions Home-page: http://cheeseshop.python.org/pypi/zope.exceptions Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ======================= General Zope Exceptions ======================= This package contains exception interfaces and implementations which are so general purpose that they don't belong in Zope application-specific packages. ======= Changes ======= 3.6.2 (2012-03-28) ------------------ - Fallback to traceback.format_tb when the formatter is called recursively. i.e. Don't let errors in the formatter pass silently. - Fix deprecated unittest functions: assert_ and assertEquals. 3.6.1 (2010-07-06) ------------------ - Fixed tests to work under Python 2.7. - PEP8 cleanup and removed obsolete build infrastructure files. 3.6.0 (2010-05-02) ------------------ - Added support to bootstrap on Jython. - Added Python 3 support. - The dependency on zope.testing seemed spurious, possibly a rest of a real dependency that is gone now. I removed it. 3.5.2 (2008-04-30) ------------------ - Updated CHANGES.txt. 3.5.1 (2008-04-28) ------------------ - Reverted changes in 3.5.0. 3.5.0 ----- - Added the capability for exceptions to be formatted line-by-line. Unfortunately, also introduced a bug cause each line of the exception to be its own log message. 3.4.0 (2007-10-02) ------------------ - Updated package meta-data. 3.4.0b2 (2007-08-14) -------------------- - Removed superfluous dependency on ``zope.deprecation``. 3.4.0b1 (2007-07-09) -------------------- - Corresponds to the version of the ``zope.exceptions`` package shipped as part of the Zope 3.4.0b1 release. 3.2.0 (2006-01-05) ------------------ - Corresponds to the version of the ``zope.exceptions`` package shipped as part of the Zope 3.2.0 release. - Deprecated the ``INotFoundError`` interface and the corresponding ``NotFoundError`` exception class, in favor of "standard" exceptions ``AttributeError``, ``KeyError``). The deprecated items will be removed in Zope 3.3. 3.0.0 (2004-11-07) ------------------ - Corresponds to the version of the zope.exceptions package shipped as part of the Zope X3.0.0 release. Keywords: zope exceptions Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.exceptions/pip-egg-info/0000755000175000017500000000000012214017563021056 5ustar arnauarnauzope2.13-2.13.21/source/zope.exceptions/pip-egg-info/zope.exceptions.egg-info/0000755000175000017500000000000012214017563025705 5ustar arnauarnauzope2.13-2.13.21/source/zope.exceptions/pip-egg-info/zope.exceptions.egg-info/PKG-INFO0000644000175000017500000000740412214017563027007 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.exceptions Version: 3.6.2 Summary: Zope Exceptions Home-page: http://cheeseshop.python.org/pypi/zope.exceptions Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ======================= General Zope Exceptions ======================= This package contains exception interfaces and implementations which are so general purpose that they don't belong in Zope application-specific packages. ======= Changes ======= 3.6.2 (2012-03-28) ------------------ - Fallback to traceback.format_tb when the formatter is called recursively. i.e. Don't let errors in the formatter pass silently. - Fix deprecated unittest functions: assert_ and assertEquals. 3.6.1 (2010-07-06) ------------------ - Fixed tests to work under Python 2.7. - PEP8 cleanup and removed obsolete build infrastructure files. 3.6.0 (2010-05-02) ------------------ - Added support to bootstrap on Jython. - Added Python 3 support. - The dependency on zope.testing seemed spurious, possibly a rest of a real dependency that is gone now. I removed it. 3.5.2 (2008-04-30) ------------------ - Updated CHANGES.txt. 3.5.1 (2008-04-28) ------------------ - Reverted changes in 3.5.0. 3.5.0 ----- - Added the capability for exceptions to be formatted line-by-line. Unfortunately, also introduced a bug cause each line of the exception to be its own log message. 3.4.0 (2007-10-02) ------------------ - Updated package meta-data. 3.4.0b2 (2007-08-14) -------------------- - Removed superfluous dependency on ``zope.deprecation``. 3.4.0b1 (2007-07-09) -------------------- - Corresponds to the version of the ``zope.exceptions`` package shipped as part of the Zope 3.4.0b1 release. 3.2.0 (2006-01-05) ------------------ - Corresponds to the version of the ``zope.exceptions`` package shipped as part of the Zope 3.2.0 release. - Deprecated the ``INotFoundError`` interface and the corresponding ``NotFoundError`` exception class, in favor of "standard" exceptions ``AttributeError``, ``KeyError``). The deprecated items will be removed in Zope 3.3. 3.0.0 (2004-11-07) ------------------ - Corresponds to the version of the zope.exceptions package shipped as part of the Zope X3.0.0 release. Keywords: zope exceptions Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.exceptions/pip-egg-info/zope.exceptions.egg-info/dependency_links.txt0000644000175000017500000000000112214017563031753 0ustar arnauarnau zope2.13-2.13.21/source/zope.exceptions/pip-egg-info/zope.exceptions.egg-info/requires.txt0000644000175000017500000000003112214017563030277 0ustar arnauarnausetuptools zope.interfacezope2.13-2.13.21/source/zope.exceptions/pip-egg-info/zope.exceptions.egg-info/namespace_packages.txt0000644000175000017500000000000512214017563032233 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.exceptions/pip-egg-info/zope.exceptions.egg-info/top_level.txt0000644000175000017500000000000512214017563030432 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.exceptions/pip-egg-info/zope.exceptions.egg-info/SOURCES.txt0000644000175000017500000000117612214017563027576 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.exceptions.egg-info/PKG-INFO pip-egg-info/zope.exceptions.egg-info/SOURCES.txt pip-egg-info/zope.exceptions.egg-info/dependency_links.txt pip-egg-info/zope.exceptions.egg-info/namespace_packages.txt pip-egg-info/zope.exceptions.egg-info/not-zip-safe pip-egg-info/zope.exceptions.egg-info/requires.txt pip-egg-info/zope.exceptions.egg-info/top_level.txt src/zope/__init__.py src/zope/exceptions/__init__.py src/zope/exceptions/exceptionformatter.py src/zope/exceptions/interfaces.py src/zope/exceptions/log.py src/zope/exceptions/tests/__init__.py src/zope/exceptions/tests/test_exceptionformatter.pyzope2.13-2.13.21/source/zope.exceptions/pip-egg-info/zope.exceptions.egg-info/not-zip-safe0000644000175000017500000000000112214017563030133 0ustar arnauarnau zope2.13-2.13.21/source/zope.exceptions/LICENSE.txt0000644000175000017500000000402612214017562020421 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.exceptions/README.txt0000644000175000017500000000034312214017562020272 0ustar arnauarnau======================= General Zope Exceptions ======================= This package contains exception interfaces and implementations which are so general purpose that they don't belong in Zope application-specific packages. zope2.13-2.13.21/source/zope.exceptions/setup.cfg0000644000175000017500000000007312214017562020415 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.exceptions/COPYRIGHT.txt0000644000175000017500000000004012214017562020677 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.exceptions/buildout.cfg0000644000175000017500000000014112214017562021100 0ustar arnauarnau[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = zope.exceptions zope2.13-2.13.21/source/zope.exceptions/bootstrap.py0000644000175000017500000000735012214017562021170 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 114246 2010-07-06 17:13:08Z hannosch $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] try: import pkg_resources import setuptools if not hasattr(pkg_resources, '_distribute'): raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) reload(sys.modules['pkg_resources']) import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.exceptions/CHANGES.txt0000644000175000017500000000342112214017562020405 0ustar arnauarnau======= Changes ======= 3.6.2 (2012-03-28) ------------------ - Fallback to traceback.format_tb when the formatter is called recursively. i.e. Don't let errors in the formatter pass silently. - Fix deprecated unittest functions: assert_ and assertEquals. 3.6.1 (2010-07-06) ------------------ - Fixed tests to work under Python 2.7. - PEP8 cleanup and removed obsolete build infrastructure files. 3.6.0 (2010-05-02) ------------------ - Added support to bootstrap on Jython. - Added Python 3 support. - The dependency on zope.testing seemed spurious, possibly a rest of a real dependency that is gone now. I removed it. 3.5.2 (2008-04-30) ------------------ - Updated CHANGES.txt. 3.5.1 (2008-04-28) ------------------ - Reverted changes in 3.5.0. 3.5.0 ----- - Added the capability for exceptions to be formatted line-by-line. Unfortunately, also introduced a bug cause each line of the exception to be its own log message. 3.4.0 (2007-10-02) ------------------ - Updated package meta-data. 3.4.0b2 (2007-08-14) -------------------- - Removed superfluous dependency on ``zope.deprecation``. 3.4.0b1 (2007-07-09) -------------------- - Corresponds to the version of the ``zope.exceptions`` package shipped as part of the Zope 3.4.0b1 release. 3.2.0 (2006-01-05) ------------------ - Corresponds to the version of the ``zope.exceptions`` package shipped as part of the Zope 3.2.0 release. - Deprecated the ``INotFoundError`` interface and the corresponding ``NotFoundError`` exception class, in favor of "standard" exceptions ``AttributeError``, ``KeyError``). The deprecated items will be removed in Zope 3.3. 3.0.0 (2004-11-07) ------------------ - Corresponds to the version of the zope.exceptions package shipped as part of the Zope X3.0.0 release. zope2.13-2.13.21/source/zope.exceptions/src/0000755000175000017500000000000012214017562017363 5ustar arnauarnauzope2.13-2.13.21/source/zope.exceptions/src/zope/0000755000175000017500000000000012214017562020340 5ustar arnauarnauzope2.13-2.13.21/source/zope.exceptions/src/zope/__init__.py0000644000175000017500000000007012214017562022446 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.exceptions/src/zope/exceptions/0000755000175000017500000000000012214017562022521 5ustar arnauarnauzope2.13-2.13.21/source/zope.exceptions/src/zope/exceptions/interfaces.py0000644000175000017500000000675012214017562025226 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ITracebackSupplement interface definition. When zope.exceptionformatter generates a traceback, it looks for local variables named __traceback_info__ or __traceback_supplement__. It includes the information provided by those local variables in the traceback. __traceback_info__ is for arbitrary information. repr(__traceback_info__) gets dumped to the traceback. __traceback_supplement__ is more structured. It should be a tuple. The first item of the tuple is a callable that produces an object that implements ITracebackSupplement, and the rest of the tuple contains arguments to pass to the factory. The traceback formatter makes an effort to clearly present the information provided by the ITracebackSupplement. """ from zope.interface import Interface, Attribute, implements class IDuplicationError(Interface): pass class DuplicationError(Exception): """A duplicate registration was attempted""" implements(IDuplicationError) class IUserError(Interface): """User error exceptions """ class UserError(Exception): """User errors These exceptions should generally be displayed to users unless they are handled. """ implements(IUserError) class ITracebackSupplement(Interface): """Provides valuable information to supplement an exception traceback. The interface is geared toward providing meaningful feedback when exceptions occur in user code written in mini-languages like Zope page templates and restricted Python scripts. """ source_url = Attribute( 'source_url', """Optional. Set to URL of the script where the exception occurred. Normally this generates a URL in the traceback that the user can visit to manage the object. Set to None if unknown or not available. """) line = Attribute( 'line', """Optional. Set to the line number (>=1) where the exception occurred. Set to 0 or None if the line number is unknown. """) column = Attribute( 'column', """Optional. Set to the column offset (>=0) where the exception occurred. Set to None if the column number is unknown. """) expression = Attribute( 'expression', """Optional. Set to the expression that was being evaluated. Set to None if not available or not applicable. """) warnings = Attribute( 'warnings', """Optional. Set to a sequence of warning messages. Set to None if not available, not applicable, or if the exception itself provides enough information. """) def getInfo(as_html=0): """Optional. Returns a string containing any other useful info. If as_html is set, the implementation must HTML-quote the result (normally using cgi.escape()). Returns None to provide no extra info. """ zope2.13-2.13.21/source/zope.exceptions/src/zope/exceptions/__init__.py0000644000175000017500000000251512214017562024635 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """General exceptions that wish they were standard exceptions These exceptions are so general purpose that they don't belong in Zope application-specific packages. """ from zope.exceptions.interfaces import DuplicationError, IDuplicationError from zope.exceptions.interfaces import UserError, IUserError # avoid dependency on zope.security: try: import zope.security except ImportError, v: # "ImportError: No module named security" if not str(v).endswith('security'): raise else: from zope.security.interfaces import IUnauthorized, Unauthorized from zope.security.interfaces import IForbidden, IForbiddenAttribute from zope.security.interfaces import Forbidden, ForbiddenAttribute zope2.13-2.13.21/source/zope.exceptions/src/zope/exceptions/exceptionformatter.py0000644000175000017500000001745612214017562027032 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """An exception formatter that shows traceback supplements and traceback info, optionally in HTML. """ import sys import cgi import linecache import traceback DEBUG_EXCEPTION_FORMATTER = 1 class TextExceptionFormatter(object): line_sep = '\n' show_revisions = 0 def __init__(self, limit=None, with_filenames=False): self.limit = limit self.with_filenames = with_filenames def escape(self, s): return s def getPrefix(self): return 'Traceback (most recent call last):' def getLimit(self): limit = self.limit if limit is None: limit = getattr(sys, 'tracebacklimit', 200) return limit def formatSupplementLine(self, line): return ' - %s' % line def formatSourceURL(self, url): return [self.formatSupplementLine(url)] def formatSupplement(self, supplement, tb): result = [] fmtLine = self.formatSupplementLine url = getattr(supplement, 'source_url', None) if url is not None: result.extend(self.formatSourceURL(url)) line = getattr(supplement, 'line', 0) if line == -1: line = tb.tb_lineno col = getattr(supplement, 'column', -1) if line: if col is not None and col >= 0: result.append(fmtLine('Line %s, Column %s' % ( line, col))) else: result.append(fmtLine('Line %s' % line)) elif col is not None and col >= 0: result.append(fmtLine('Column %s' % col)) expr = getattr(supplement, 'expression', None) if expr: result.append(fmtLine('Expression: %s' % expr)) warnings = getattr(supplement, 'warnings', None) if warnings: for warning in warnings: result.append(fmtLine('Warning: %s' % warning)) getInfo = getattr(supplement, 'getInfo', None) if getInfo is not None: try: extra = getInfo() if extra: extra = self.escape(extra) if self.line_sep != "\n": extra = extra.replace(" ", " ") extra = extra.replace("\n", self.line_sep) result.append(extra) except: if DEBUG_EXCEPTION_FORMATTER: traceback.print_exc() # else just swallow the exception. return result def formatTracebackInfo(self, tbi): return self.formatSupplementLine('__traceback_info__: %s' % (tbi, )) def formatLine(self, tb): f = tb.tb_frame lineno = tb.tb_lineno co = f.f_code filename = co.co_filename name = co.co_name locals = f.f_locals globals = f.f_globals if self.with_filenames: s = ' File "%s", line %d' % (filename, lineno) else: modname = globals.get('__name__', filename) s = ' Module %s, line %d' % (modname, lineno) s = s + ', in %s' % name result = [] result.append(self.escape(s)) # Append the source line, if available line = linecache.getline(filename, lineno) if line: result.append(" " + self.escape(line.strip())) # Output a traceback supplement, if any. if '__traceback_supplement__' in locals: # Use the supplement defined in the function. tbs = locals['__traceback_supplement__'] elif '__traceback_supplement__' in globals: # Use the supplement defined in the module. # This is used by Scripts (Python). tbs = globals['__traceback_supplement__'] else: tbs = None if tbs is not None: factory = tbs[0] args = tbs[1:] try: supp = factory(*args) result.extend(self.formatSupplement(supp, tb)) except: if DEBUG_EXCEPTION_FORMATTER: traceback.print_exc() # else just swallow the exception. try: tbi = locals.get('__traceback_info__', None) if tbi is not None: result.append(self.formatTracebackInfo(tbi)) except: if DEBUG_EXCEPTION_FORMATTER: traceback.print_exc() # else just swallow the exception. return self.line_sep.join(result) def formatExceptionOnly(self, etype, value): result = ''.join(traceback.format_exception_only(etype, value)) return result.replace('\n', self.line_sep) def formatLastLine(self, exc_line): return self.escape(exc_line) def formatException(self, etype, value, tb): # The next line provides a way to detect recursion. __exception_formatter__ = 1 result = [self.getPrefix() + '\n'] limit = self.getLimit() n = 0 while tb is not None and (limit is None or n < limit): if tb.tb_frame.f_locals.get('__exception_formatter__'): # Stop recursion. result.append('(Recursive formatException() stopped, trying traceback.format_tb)\n') result.extend(traceback.format_tb(tb)) break line = self.formatLine(tb) result.append(line + '\n') tb = tb.tb_next n = n + 1 exc_line = self.formatExceptionOnly(etype, value) result.append(self.formatLastLine(exc_line)) return result class HTMLExceptionFormatter(TextExceptionFormatter): line_sep = '
\r\n' def escape(self, s): return cgi.escape(s) def getPrefix(self): return '

Traceback (most recent call last):\r\n

    ' def formatSupplementLine(self, line): return '%s' % self.escape(str(line)) def formatTracebackInfo(self, tbi): s = self.escape(str(tbi)) s = s.replace('\n', self.line_sep) return '__traceback_info__: %s' % (s, ) def formatLine(self, tb): line = TextExceptionFormatter.formatLine(self, tb) return '
  • %s
  • ' % line def formatLastLine(self, exc_line): return '
%s

' % self.escape(exc_line) def format_exception(t, v, tb, limit=None, as_html=False, with_filenames=False): """Format a stack trace and the exception information. Similar to 'traceback.format_exception', but adds supplemental information to the traceback and accepts two options, 'as_html' and 'with_filenames'. """ if as_html: fmt = HTMLExceptionFormatter(limit, with_filenames) else: fmt = TextExceptionFormatter(limit, with_filenames) return fmt.formatException(t, v, tb) def print_exception(t, v, tb, limit=None, file=None, as_html=False, with_filenames=True): """Print exception up to 'limit' stack trace entries from 'tb' to 'file'. Similar to 'traceback.print_exception', but adds supplemental information to the traceback and accepts two options, 'as_html' and 'with_filenames'. """ if file is None: file = sys.stderr lines = format_exception(t, v, tb, limit, as_html, with_filenames) for line in lines: file.write(line) zope2.13-2.13.21/source/zope.exceptions/src/zope/exceptions/tests/0000755000175000017500000000000012214017562023663 5ustar arnauarnauzope2.13-2.13.21/source/zope.exceptions/src/zope/exceptions/tests/test_exceptionformatter.py0000644000175000017500000001331512214017562031221 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ExceptionFormatter tests. """ import sys from unittest import TestCase, makeSuite from zope.exceptions.exceptionformatter import format_exception def tb(as_html=0): t, v, b = sys.exc_info() try: return ''.join(format_exception(t, v, b, as_html=as_html)) finally: del b class ExceptionForTesting (Exception): pass class TestingTracebackSupplement(object): source_url = '/somepath' line = 634 column = 57 warnings = ['Repent, for the end is nigh'] def __init__(self, expression): self.expression = expression class Test(TestCase): def testBasicNamesText(self, as_html=0): try: raise ExceptionForTesting except ExceptionForTesting: s = tb(as_html) # The traceback should include the name of this function. self.assertTrue(s.find('testBasicNamesText') >= 0) # The traceback should include the name of the exception. self.assertTrue(s.find('ExceptionForTesting') >= 0) else: self.fail('no exception occurred') def testBasicNamesHTML(self): self.testBasicNamesText(1) def testSupplement(self, as_html=0): try: __traceback_supplement__ = (TestingTracebackSupplement, "You're one in a million") raise ExceptionForTesting except ExceptionForTesting: s = tb(as_html) # The source URL self.assertTrue(s.find('/somepath') >= 0, s) # The line number self.assertTrue(s.find('634') >= 0, s) # The column number self.assertTrue(s.find('57') >= 0, s) # The expression self.assertTrue(s.find("You're one in a million") >= 0, s) # The warning self.assertTrue(s.find("Repent, for the end is nigh") >= 0, s) else: self.fail('no exception occurred') def testSupplementHTML(self): self.testSupplement(1) def testTracebackInfo(self, as_html=0): try: __traceback_info__ = "Adam & Eve" raise ExceptionForTesting except ExceptionForTesting: s = tb(as_html) if as_html: # Be sure quoting is happening. self.assertTrue(s.find('Adam & Eve') >= 0, s) else: self.assertTrue(s.find('Adam & Eve') >= 0, s) else: self.fail('no exception occurred') def testTracebackInfoHTML(self): self.testTracebackInfo(1) def testTracebackInfoTuple(self): try: __traceback_info__ = ("Adam", "Eve") raise ExceptionForTesting except ExceptionForTesting: s = tb() self.assertTrue(s.find('Adam') >= 0, s) self.assertTrue(s.find('Eve') >= 0, s) else: self.fail('no exception occurred') def testMultipleLevels(self): # Makes sure many levels are shown in a traceback. def f(n): """Produces a (n + 1)-level traceback.""" __traceback_info__ = 'level%d' % n if n > 0: f(n - 1) else: raise ExceptionForTesting try: f(10) except ExceptionForTesting: s = tb() for n in range(11): self.assertTrue(s.find('level%d' % n) >= 0, s) else: self.fail('no exception occurred') def testQuoteLastLine(self): class C(object): pass try: raise TypeError(C()) except: s = tb(1) else: self.fail('no exception occurred') self.assertTrue(s.find('<') >= 0, s) self.assertTrue(s.find('>') >= 0, s) def testMultilineException(self): try: exec 'syntax error\n' except Exception: s = tb() self.assertEqual(s.splitlines()[-3:], [' syntax error', ' ^', 'SyntaxError: invalid syntax']) def testRecursionFailure(self): from zope.exceptions.exceptionformatter import TextExceptionFormatter class FormatterException(Exception): pass class FailingFormatter(TextExceptionFormatter): def formatLine(self, tb): raise FormatterException("Formatter failed") fmt = FailingFormatter() try: raise ExceptionForTesting except ExceptionForTesting: try: fmt.formatException(*sys.exc_info()) except FormatterException: s = tb() # Recursion was detected self.assertTrue('(Recursive formatException() stopped, trying traceback.format_tb)' in s, s) # and we fellback to the stdlib rather than hid the real error self.assertEqual(s.splitlines()[-2], ' raise FormatterException("Formatter failed")') self.assertTrue('FormatterException: Formatter failed' in s.splitlines()[-1]) def test_suite(): return makeSuite(Test) zope2.13-2.13.21/source/zope.exceptions/src/zope/exceptions/tests/__init__.py0000644000175000017500000000000112214017562025763 0ustar arnauarnau#zope2.13-2.13.21/source/zope.exceptions/src/zope/exceptions/log.py0000644000175000017500000000235012214017562023654 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Log formatter that enhances tracebacks with extra information. """ import logging import cStringIO from zope.exceptions.exceptionformatter import print_exception class Formatter(logging.Formatter): def formatException(self, ei): """Format and return the specified exception information as a string. Uses zope.exceptions.exceptionformatter to generate the traceback. """ sio = cStringIO.StringIO() print_exception(ei[0], ei[1], ei[2], file=sio, with_filenames=True) s = sio.getvalue() if s.endswith("\n"): s = s[:-1] return s zope2.13-2.13.21/source/zope.exceptions/src/zope.exceptions.egg-info/0000755000175000017500000000000012214017562024212 5ustar arnauarnauzope2.13-2.13.21/source/zope.exceptions/src/zope.exceptions.egg-info/PKG-INFO0000644000175000017500000000740412214017562025314 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.exceptions Version: 3.6.2 Summary: Zope Exceptions Home-page: http://cheeseshop.python.org/pypi/zope.exceptions Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ======================= General Zope Exceptions ======================= This package contains exception interfaces and implementations which are so general purpose that they don't belong in Zope application-specific packages. ======= Changes ======= 3.6.2 (2012-03-28) ------------------ - Fallback to traceback.format_tb when the formatter is called recursively. i.e. Don't let errors in the formatter pass silently. - Fix deprecated unittest functions: assert_ and assertEquals. 3.6.1 (2010-07-06) ------------------ - Fixed tests to work under Python 2.7. - PEP8 cleanup and removed obsolete build infrastructure files. 3.6.0 (2010-05-02) ------------------ - Added support to bootstrap on Jython. - Added Python 3 support. - The dependency on zope.testing seemed spurious, possibly a rest of a real dependency that is gone now. I removed it. 3.5.2 (2008-04-30) ------------------ - Updated CHANGES.txt. 3.5.1 (2008-04-28) ------------------ - Reverted changes in 3.5.0. 3.5.0 ----- - Added the capability for exceptions to be formatted line-by-line. Unfortunately, also introduced a bug cause each line of the exception to be its own log message. 3.4.0 (2007-10-02) ------------------ - Updated package meta-data. 3.4.0b2 (2007-08-14) -------------------- - Removed superfluous dependency on ``zope.deprecation``. 3.4.0b1 (2007-07-09) -------------------- - Corresponds to the version of the ``zope.exceptions`` package shipped as part of the Zope 3.4.0b1 release. 3.2.0 (2006-01-05) ------------------ - Corresponds to the version of the ``zope.exceptions`` package shipped as part of the Zope 3.2.0 release. - Deprecated the ``INotFoundError`` interface and the corresponding ``NotFoundError`` exception class, in favor of "standard" exceptions ``AttributeError``, ``KeyError``). The deprecated items will be removed in Zope 3.3. 3.0.0 (2004-11-07) ------------------ - Corresponds to the version of the zope.exceptions package shipped as part of the Zope X3.0.0 release. Keywords: zope exceptions Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.exceptions/src/zope.exceptions.egg-info/dependency_links.txt0000644000175000017500000000000112214017562030260 0ustar arnauarnau zope2.13-2.13.21/source/zope.exceptions/src/zope.exceptions.egg-info/requires.txt0000644000175000017500000000003112214017562026604 0ustar arnauarnausetuptools zope.interfacezope2.13-2.13.21/source/zope.exceptions/src/zope.exceptions.egg-info/namespace_packages.txt0000644000175000017500000000000512214017562030540 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.exceptions/src/zope.exceptions.egg-info/top_level.txt0000644000175000017500000000000512214017562026737 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.exceptions/src/zope.exceptions.egg-info/SOURCES.txt0000644000175000017500000000121112214017562026071 0ustar arnauarnau.bzrignore CHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.exceptions.egg-info/PKG-INFO src/zope.exceptions.egg-info/SOURCES.txt src/zope.exceptions.egg-info/dependency_links.txt src/zope.exceptions.egg-info/namespace_packages.txt src/zope.exceptions.egg-info/not-zip-safe src/zope.exceptions.egg-info/requires.txt src/zope.exceptions.egg-info/top_level.txt src/zope/exceptions/__init__.py src/zope/exceptions/exceptionformatter.py src/zope/exceptions/interfaces.py src/zope/exceptions/log.py src/zope/exceptions/tests/__init__.py src/zope/exceptions/tests/test_exceptionformatter.pyzope2.13-2.13.21/source/zope.exceptions/src/zope.exceptions.egg-info/not-zip-safe0000644000175000017500000000000112214017562026440 0ustar arnauarnau zope2.13-2.13.21/source/zope.exceptions/.bzrignore0000644000175000017500000000010012214017562020565 0ustar arnauarnau./.installed.cfg ./bin ./develop-eggs ./eggs ./parts *.egg-info zope2.13-2.13.21/source/zope.configuration/0000755000175000017500000000000012214017543017261 5ustar arnauarnauzope2.13-2.13.21/source/zope.configuration/setup.py0000644000175000017500000000721412214017543020777 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() def _modname(path, base, name=''): if path == base: return name dirname, basename = os.path.split(path) return _modname(dirname, base, basename + '.' + name) def alltests(): import logging import pkg_resources import unittest class NullHandler(logging.Handler): level = 50 def emit(self, record): pass logging.getLogger().addHandler(NullHandler()) suite = unittest.TestSuite() base = pkg_resources.working_set.find( pkg_resources.Requirement.parse('zope.configuration')).location for dirpath, dirnames, filenames in os.walk(base): if os.path.basename(dirpath) == 'tests': for filename in filenames: if ( filename.endswith('.py') and filename.startswith('test') ): mod = __import__( _modname(dirpath, base, os.path.splitext(filename)[0]), {}, {}, ['*']) suite.addTest(mod.test_suite()) return suite setup(name='zope.configuration', version = '3.7.4', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description='Zope Configuration Markup Language (ZCML)', long_description=( read('README.txt') + '\n\n' + 'Detailed Documentation\n' + '----------------------\n' + '\n\n' + read('src', 'zope', 'configuration', 'README.txt') + '\n\n' + read('CHANGES.txt') ), keywords = "zope configuration zcml", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.configuration', license='ZPL 2.1', packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope'], extras_require=dict( test=['zope.testing']), install_requires=['zope.i18nmessageid', 'zope.interface', 'zope.schema', 'setuptools', ], include_package_data=True, zip_safe=False, tests_require = 'zope.testing', test_suite='__main__.alltests', ) zope2.13-2.13.21/source/zope.configuration/PKG-INFO0000644000175000017500000002022412214017543020356 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.configuration Version: 3.7.4 Summary: Zope Configuration Markup Language (ZCML) Home-page: http://pypi.python.org/pypi/zope.configuration Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: zope.configuration ================== Overview -------- The zope configuration system provides an extensible system for supporting various kinds of configurations. It is based on the idea of configuration directives. Users of the configuration system provide configuration directives in some language that express configuration choices. The intent is that the language be pluggable. An XML language is provided by default. Detailed Documentation ---------------------- ========================== Zope configuration system ========================== The zope configuration system provides an extensible system for supporting variouse kinds of configurations. It is based on the idea of configuration directives. Users of the configuration system provide configuration directives in some language that express configuration choices. The intent is that the language be pluggable. An XML language is provided by default. Configuration is performed in three stages. In the first stage, directives are processed to compute configuration actions. Configuration actions consist of: - A discriminator - A callable - Positional arguments - Keyword arguments The actions are essentially delayed function calls. Two or more actions conflict if they have the same discriminator. The configuration system has rules for resolving conflicts. If conflicts cannot be resolved, an error will result. Conflict resolution typically discards all but one of the conflicting actions, so that the remaining action of the originally-conflicting actions no longer conflicts. Non-conflicting actions are executed in the order that they were created by passing the positional and non-positional arguments to the action callable. The system is extensible. There is a meta-configuration language for defining configuration directives. A directive is defined by providing meta data about the directive and handler code to process the directive. There are four kinds of directives: - Simple directives compute configuration actions. Their handlers are typically functions that take a context and zero or more keyword arguments and return a sequence of configuration actions. To learn how to create simple directives, see `tests/test_simple.py`. - Grouping directives collect information to be used by nested directives. They are called with a context object which they adapt to some interface that extends IConfigurationContext. To learn how to create grouping directives, look at the documentation in zopeconfigure.py, which provides the implementation of the zope `configure` directive. Other directives can be nested in grouping directives. To learn how to implement nested directives, look at the documentation in `tests/test_nested.py`. - Complex directives are directives that have subdirectives. Subdirectives have handlers that are simply methods of complex directives. Complex diretives are handled by factories, typically classes, that create objects that have methods for handling subdirectives. These objects also have __call__ methods that are called when processing of subdirectives is finished. Complex directives only exist to support old directive handlers. They will probably be deprecated in the future. - Subdirectives are nested in complex directives. They are like simple directives except that they hane handlers that are complex directive methods. Subdirectives, like complex directives only exist to support old directive handlers. They will probably be deprecated in the future. ======= Changes ======= 3.7.4 (2011-04-03) ------------------ - Test fixes for Windows. 3.7.3 (2011-03-11) ------------------ - Correctly locate packages with a __path__ attribute but no __file__ attribute (such as namespace packages installed with setup.py install --single-version-externally-managed). - Allow "info" and "includepath" to be passed optionally to context.action. 3.7.2 (2010-04-30) ------------------ - Prefer the standard libraries doctest module over zope.testing.doctest. 3.7.1 (2010-01-05) ------------------ - Jython support: use ``__builtin__`` module import rather than assuming ``__builtins__`` is available. - Jython support: deal with the fact that the Jython SAX parser returns attribute sets that have an empty string indicating no namespace instead of ``None``. - Allow ``setup.py test`` to run at least a subset of the tests that would be run when using the zope testrunner: ``setup.py test`` runs 53 tests, while ``bin/test`` runs 156. 3.7.0 (2009-12-22) ------------------ - Adjust testing output to newer zope.schema. - Prefer zope.testing.doctest over doctestunit. 3.6.0 (2009-04-01) ------------------ - Removed dependency of `zope.deprecation` package. - Don't suppress deprecation warnings any more in 'zope.configuration' package level. This makes it more likely other packages will generate deprecation warnings now, which will allow us to remove more outdated ones. - Don't fail when zope.testing is not installed. - Added missing ``processFile`` method to ``IConfigurationContext``. It is already implemented in the mix-in class, ``zope.configuration.config.ConfigurationContext``, and used by implementations of ``include`` and ``exclude`` directives. 3.5.0 (2009-02-26) ------------------ - Added the ``exclude`` directive to standard directives. It was previously available via ``zc.configuration`` package and now it's merged into ``zope.configuration``. - Changed package's mailing list address to zope-dev at zope.org, change "cheeseshop" to "pypi" in the package's url. 3.4.1 (2008-12-11) ------------------ - Use built-in 'set' type, rather than importin the 'sets' module, which is deprecated in Python 2.6. - Added support to bootstrap on Jython. 3.4.0 (2007-10-02) ------------------ - Initial release as a standalone package. Before 3.4.0 ------------ This package was part of the Zope 3 distribution and did not have its own CHANGES.txt. For earlier changes please refer to either our subversion log or the CHANGES.txt of earlier Zope 3 releases. Keywords: zope configuration zcml Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.configuration/pip-egg-info/0000755000175000017500000000000012214017543021542 5ustar arnauarnauzope2.13-2.13.21/source/zope.configuration/pip-egg-info/zope.configuration.egg-info/0000755000175000017500000000000012214017543027057 5ustar arnauarnauzope2.13-2.13.21/source/zope.configuration/pip-egg-info/zope.configuration.egg-info/PKG-INFO0000644000175000017500000002022412214017543030154 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.configuration Version: 3.7.4 Summary: Zope Configuration Markup Language (ZCML) Home-page: http://pypi.python.org/pypi/zope.configuration Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: zope.configuration ================== Overview -------- The zope configuration system provides an extensible system for supporting various kinds of configurations. It is based on the idea of configuration directives. Users of the configuration system provide configuration directives in some language that express configuration choices. The intent is that the language be pluggable. An XML language is provided by default. Detailed Documentation ---------------------- ========================== Zope configuration system ========================== The zope configuration system provides an extensible system for supporting variouse kinds of configurations. It is based on the idea of configuration directives. Users of the configuration system provide configuration directives in some language that express configuration choices. The intent is that the language be pluggable. An XML language is provided by default. Configuration is performed in three stages. In the first stage, directives are processed to compute configuration actions. Configuration actions consist of: - A discriminator - A callable - Positional arguments - Keyword arguments The actions are essentially delayed function calls. Two or more actions conflict if they have the same discriminator. The configuration system has rules for resolving conflicts. If conflicts cannot be resolved, an error will result. Conflict resolution typically discards all but one of the conflicting actions, so that the remaining action of the originally-conflicting actions no longer conflicts. Non-conflicting actions are executed in the order that they were created by passing the positional and non-positional arguments to the action callable. The system is extensible. There is a meta-configuration language for defining configuration directives. A directive is defined by providing meta data about the directive and handler code to process the directive. There are four kinds of directives: - Simple directives compute configuration actions. Their handlers are typically functions that take a context and zero or more keyword arguments and return a sequence of configuration actions. To learn how to create simple directives, see `tests/test_simple.py`. - Grouping directives collect information to be used by nested directives. They are called with a context object which they adapt to some interface that extends IConfigurationContext. To learn how to create grouping directives, look at the documentation in zopeconfigure.py, which provides the implementation of the zope `configure` directive. Other directives can be nested in grouping directives. To learn how to implement nested directives, look at the documentation in `tests/test_nested.py`. - Complex directives are directives that have subdirectives. Subdirectives have handlers that are simply methods of complex directives. Complex diretives are handled by factories, typically classes, that create objects that have methods for handling subdirectives. These objects also have __call__ methods that are called when processing of subdirectives is finished. Complex directives only exist to support old directive handlers. They will probably be deprecated in the future. - Subdirectives are nested in complex directives. They are like simple directives except that they hane handlers that are complex directive methods. Subdirectives, like complex directives only exist to support old directive handlers. They will probably be deprecated in the future. ======= Changes ======= 3.7.4 (2011-04-03) ------------------ - Test fixes for Windows. 3.7.3 (2011-03-11) ------------------ - Correctly locate packages with a __path__ attribute but no __file__ attribute (such as namespace packages installed with setup.py install --single-version-externally-managed). - Allow "info" and "includepath" to be passed optionally to context.action. 3.7.2 (2010-04-30) ------------------ - Prefer the standard libraries doctest module over zope.testing.doctest. 3.7.1 (2010-01-05) ------------------ - Jython support: use ``__builtin__`` module import rather than assuming ``__builtins__`` is available. - Jython support: deal with the fact that the Jython SAX parser returns attribute sets that have an empty string indicating no namespace instead of ``None``. - Allow ``setup.py test`` to run at least a subset of the tests that would be run when using the zope testrunner: ``setup.py test`` runs 53 tests, while ``bin/test`` runs 156. 3.7.0 (2009-12-22) ------------------ - Adjust testing output to newer zope.schema. - Prefer zope.testing.doctest over doctestunit. 3.6.0 (2009-04-01) ------------------ - Removed dependency of `zope.deprecation` package. - Don't suppress deprecation warnings any more in 'zope.configuration' package level. This makes it more likely other packages will generate deprecation warnings now, which will allow us to remove more outdated ones. - Don't fail when zope.testing is not installed. - Added missing ``processFile`` method to ``IConfigurationContext``. It is already implemented in the mix-in class, ``zope.configuration.config.ConfigurationContext``, and used by implementations of ``include`` and ``exclude`` directives. 3.5.0 (2009-02-26) ------------------ - Added the ``exclude`` directive to standard directives. It was previously available via ``zc.configuration`` package and now it's merged into ``zope.configuration``. - Changed package's mailing list address to zope-dev at zope.org, change "cheeseshop" to "pypi" in the package's url. 3.4.1 (2008-12-11) ------------------ - Use built-in 'set' type, rather than importin the 'sets' module, which is deprecated in Python 2.6. - Added support to bootstrap on Jython. 3.4.0 (2007-10-02) ------------------ - Initial release as a standalone package. Before 3.4.0 ------------ This package was part of the Zope 3 distribution and did not have its own CHANGES.txt. For earlier changes please refer to either our subversion log or the CHANGES.txt of earlier Zope 3 releases. Keywords: zope configuration zcml Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/zope.configuration/pip-egg-info/zope.configuration.egg-info/dependency_links.txtzope2.13-2.13.21/source/zope.configuration/pip-egg-info/zope.configuration.egg-info/dependency_links0000644000175000017500000000000112214017543032307 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/pip-egg-info/zope.configuration.egg-info/requires.txt0000644000175000017500000000011512214017543031454 0ustar arnauarnauzope.i18nmessageid zope.interface zope.schema setuptools [test] zope.testing././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootzope2.13-2.13.21/source/zope.configuration/pip-egg-info/zope.configuration.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.configuration/pip-egg-info/zope.configuration.egg-info/namespace_packag0000644000175000017500000000000512214017543032237 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.configuration/pip-egg-info/zope.configuration.egg-info/top_level.txt0000644000175000017500000000000512214017543031604 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.configuration/pip-egg-info/zope.configuration.egg-info/SOURCES.txt0000644000175000017500000000262712214017543030752 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.configuration.egg-info/PKG-INFO pip-egg-info/zope.configuration.egg-info/SOURCES.txt pip-egg-info/zope.configuration.egg-info/dependency_links.txt pip-egg-info/zope.configuration.egg-info/namespace_packages.txt pip-egg-info/zope.configuration.egg-info/not-zip-safe pip-egg-info/zope.configuration.egg-info/requires.txt pip-egg-info/zope.configuration.egg-info/top_level.txt src/zope/__init__.py src/zope/configuration/__init__.py src/zope/configuration/config.py src/zope/configuration/docutils.py src/zope/configuration/exceptions.py src/zope/configuration/fields.py src/zope/configuration/interfaces.py src/zope/configuration/name.py src/zope/configuration/stxdocs.py src/zope/configuration/xmlconfig.py src/zope/configuration/zopeconfigure.py src/zope/configuration/tests/__init__.py src/zope/configuration/tests/bad.py src/zope/configuration/tests/directives.py src/zope/configuration/tests/test_conditions.py src/zope/configuration/tests/test_config.py src/zope/configuration/tests/test_docutils.py src/zope/configuration/tests/test_nested.py src/zope/configuration/tests/test_simple.py src/zope/configuration/tests/test_xmlconfig.py src/zope/configuration/tests/victim.py src/zope/configuration/tests/excludedemo/__init__.py src/zope/configuration/tests/excludedemo/sub/__init__.py src/zope/configuration/tests/samplepackage/__init__.py src/zope/configuration/tests/samplepackage/foo.pyzope2.13-2.13.21/source/zope.configuration/pip-egg-info/zope.configuration.egg-info/not-zip-safe0000644000175000017500000000000112214017543031305 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/LICENSE.txt0000644000175000017500000000402612214017543021106 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.configuration/README.txt0000644000175000017500000000065312214017543020763 0ustar arnauarnauzope.configuration ================== Overview -------- The zope configuration system provides an extensible system for supporting various kinds of configurations. It is based on the idea of configuration directives. Users of the configuration system provide configuration directives in some language that express configuration choices. The intent is that the language be pluggable. An XML language is provided by default. zope2.13-2.13.21/source/zope.configuration/setup.cfg0000644000175000017500000000007312214017543021102 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.configuration/COPYRIGHT.txt0000644000175000017500000000004012214017543021364 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.configuration/buildout.cfg0000644000175000017500000000030212214017543021564 0ustar arnauarnau[buildout] develop = . parts = test python [test] recipe = zc.recipe.testrunner eggs = zope.configuration [test] [python] recipe = zc.recipe.egg eggs = zope.configuration interpreter = python zope2.13-2.13.21/source/zope.configuration/bootstrap.py0000644000175000017500000000742212214017543021655 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 111664 2010-04-30 17:35:35Z hannosch $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.configuration/CHANGES.txt0000644000175000017500000000500512214017543021072 0ustar arnauarnau======= Changes ======= 3.7.4 (2011-04-03) ------------------ - Test fixes for Windows. 3.7.3 (2011-03-11) ------------------ - Correctly locate packages with a __path__ attribute but no __file__ attribute (such as namespace packages installed with setup.py install --single-version-externally-managed). - Allow "info" and "includepath" to be passed optionally to context.action. 3.7.2 (2010-04-30) ------------------ - Prefer the standard libraries doctest module over zope.testing.doctest. 3.7.1 (2010-01-05) ------------------ - Jython support: use ``__builtin__`` module import rather than assuming ``__builtins__`` is available. - Jython support: deal with the fact that the Jython SAX parser returns attribute sets that have an empty string indicating no namespace instead of ``None``. - Allow ``setup.py test`` to run at least a subset of the tests that would be run when using the zope testrunner: ``setup.py test`` runs 53 tests, while ``bin/test`` runs 156. 3.7.0 (2009-12-22) ------------------ - Adjust testing output to newer zope.schema. - Prefer zope.testing.doctest over doctestunit. 3.6.0 (2009-04-01) ------------------ - Removed dependency of `zope.deprecation` package. - Don't suppress deprecation warnings any more in 'zope.configuration' package level. This makes it more likely other packages will generate deprecation warnings now, which will allow us to remove more outdated ones. - Don't fail when zope.testing is not installed. - Added missing ``processFile`` method to ``IConfigurationContext``. It is already implemented in the mix-in class, ``zope.configuration.config.ConfigurationContext``, and used by implementations of ``include`` and ``exclude`` directives. 3.5.0 (2009-02-26) ------------------ - Added the ``exclude`` directive to standard directives. It was previously available via ``zc.configuration`` package and now it's merged into ``zope.configuration``. - Changed package's mailing list address to zope-dev at zope.org, change "cheeseshop" to "pypi" in the package's url. 3.4.1 (2008-12-11) ------------------ - Use built-in 'set' type, rather than importin the 'sets' module, which is deprecated in Python 2.6. - Added support to bootstrap on Jython. 3.4.0 (2007-10-02) ------------------ - Initial release as a standalone package. Before 3.4.0 ------------ This package was part of the Zope 3 distribution and did not have its own CHANGES.txt. For earlier changes please refer to either our subversion log or the CHANGES.txt of earlier Zope 3 releases. zope2.13-2.13.21/source/zope.configuration/src/0000755000175000017500000000000012214017543020050 5ustar arnauarnauzope2.13-2.13.21/source/zope.configuration/src/zope/0000755000175000017500000000000012214017543021025 5ustar arnauarnauzope2.13-2.13.21/source/zope.configuration/src/zope/__init__.py0000644000175000017500000000007012214017543023133 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/0000755000175000017500000000000012214017543023674 5ustar arnauarnauzope2.13-2.13.21/source/zope.configuration/src/zope/configuration/zopeconfigure.py0000644000175000017500000001364412214017543027135 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Zope configure directive This file contains the implementation of the Zope configure directive. It is broken out in a separate file to provide an example of a grouping directive. The zope configuration directive is a pure grouping directive. It doesn't compute any actions on it's own. Instead, it allows a package to be specified, affecting the interpretation of relative dotted names and file paths. It also allows an i18n domain to be specified. The information collected is used by subdirectives. To define a grouping directive, we need to do three things: - Define a schema for the parameters passed to the directive - Define a handler class. - Register the class The parameter schema is given by IZopeConfigure. It specifies a package parameter and an i18n_domain parameter. The package parameter is specified as a ``GlobalObject``. This means it must be given as a dotted name that can be resolved through import. The i18n domain is just a plain (not unicode) string. The handler class has a constructor that takes a context to be adapted and zero or more arguments (depending on the paramter schema). The handler class must implement ``zope.configuration.interfaces.IGroupingContext``, which defines hooks ``before`` and ``after``, that are called with no arguments before and after nested directives are processed. If a grouping directive handler creates any actions, or does any computation, this is normally done in either the ``before`` or ``after`` hooks. Grouping handlers are normally decorators. The base class, ``zope.configuration.config.GroupingContextDecorator``, is normally used to define grouping directive handlers. It provides: - An implementation of IConfigurationContext, which grouping directive handlers should normally implement, - A default implementation of ``IGroupingContext`` that provides empty hooks. - Decorator support that uses a ``__getattr__`` method to delegate attribute accesses to adapted contexts, and - A constructor that sets the ``context`` attribute to the adapted context and assigns keyword arguments to attributes. The ``ZopeConfigure`` provides handling for the ``configure`` directive. It subclasses GroupingContextDecorator, and overrides the constructor to set the ``basepath`` attribute if a ``package`` argument is provided. Note that it delegates the job of assigning paramters to attribute to the ``GroupingContextDecorator`` constructor. The last step is to register the directive using the meta configuration directive. If we wanted to register the Zope ``configure`` directive for the ``zope`` namespace, we'd use a meta-configuration directive like:: Zope configure The ``configure`` node is normally used as the root node for a configuration file. It can also be used to specify a package or internationalization domain for a group of directives within a file by grouping those directives. We use the groupingDirective meta-directive to register a grouping directive. The parameters are self explanatory. The textual contents of the directive provide documentation text, excluding parameter documentation, which is provided by the schema. (The Zope ``configuration`` directive is actually registered using a lower-level Python API because it is registered for all namespaces, which isn't supported using the meta-configuration directives.) """ __docformat__ = 'restructuredtext' import os import zope.configuration.config as config from zope import schema from zope.interface import Interface class IZopeConfigure(Interface): """The ``zope:configure`` Directive The zope configuration directive is a pure grouping directive. It doesn't compute any actions on it's own. Instead, it allows a package to be specified, affecting the interpretation of relative dotted names and file paths. It also allows an i18n domain to be specified. The information collected is used by subdirectives. It may seem that this directive can only be used once per file, but it can be applied whereever it is convenient. """ package = config.fields.GlobalObject( title=u"Package", description=u"The package to be used for evaluating relative imports " u"and file names.", required=False) i18n_domain = schema.BytesLine( title=u"Internationalization domain", description=u"This is a name for the software project. It must be a " u"legal file-system name as it will be used to contruct " u"names for directories containing translation data. " u"\n" u"The domain defines a namespace for the message ids " u"used by a project.", required=False) class ZopeConfigure(config.GroupingContextDecorator): __doc__ = __doc__ def __init__(self, context, **kw): super(ZopeConfigure, self).__init__(context, **kw) if 'package' in kw: # if we have a package, we want to also define basepath # so we don't acquire one self.basepath = os.path.dirname(self.package.__file__) zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/name.py0000644000175000017500000000450012214017543025165 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Provide configuration object name resolution """ import os from types import ModuleType def resolve(name, package='zopeproducts', _silly=('__doc__',), _globals={}): name = name.strip() if name.startswith('.'): name=package+name if name.endswith('.') or name.endswith('+'): name = name[:-1] repeat = 1 else: repeat = 0 names=name.split('.') last=names[-1] mod='.'.join(names[:-1]) if not mod: return __import__(name, _globals, _globals, _silly) while 1: m=__import__(mod, _globals, _globals, _silly) try: a=getattr(m, last) except AttributeError: if not repeat: return __import__(name, _globals, _globals, _silly) else: if not repeat or (not isinstance(a, ModuleType)): return a mod += '.' + last def getNormalizedName(name, package): name=name.strip() if name.startswith('.'): name=package+name if name.endswith('.') or name.endswith('+'): name = name[:-1] repeat = 1 else: repeat = 0 name=name.split(".") while len(name)>1 and name[-1]==name[-2]: name.pop() repeat=1 name=".".join(name) if repeat: name+="+" return name def path(file='', package = 'zopeproducts', _silly=('__doc__',), _globals={}): try: package = __import__(package, _globals, _globals, _silly) except ImportError: if file and os.path.abspath(file) == file: # The package didn't matter return file raise path = os.path.dirname(package.__file__) if file: path = os.path.join(path, file) return path zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/config.py0000644000175000017500000014647712214017543025536 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Configuration processor See README.txt. """ __docformat__ = 'restructuredtext' import __builtin__ import os.path import sys import zope.schema from keyword import iskeyword from zope.configuration.exceptions import ConfigurationError from zope.configuration.interfaces import IConfigurationContext from zope.configuration.interfaces import IGroupingContext from zope.interface.adapter import AdapterRegistry from zope.interface import Interface, implements, providedBy from zope.configuration import fields zopens = 'http://namespaces.zope.org/zope' metans = 'http://namespaces.zope.org/meta' testns = 'http://namespaces.zope.org/test' _import_chickens = {}, {}, ("*",) # dead chickens needed by __import__ class ConfigurationContext(object): """Mix-in that implements IConfigurationContext Subclasses provide a ``package`` attribute and a ``basepath`` attribute. If the base path is not None, relative paths are converted to absolute paths using the the base path. If the package is not none, relative imports are performed relative to the package. In general, the basepath and package attributes should be consistent. When a package is provided, the base path should be set to the path of the package directory. Subclasses also provide an ``actions`` attribute, which is a list of actions, an ``includepath`` attribute, and an ``info`` attribute. The include path is appended to each action and is used when resolving conflicts among actions. Normally, only the a ConfigurationMachine provides the actions attribute. Decorators simply use the actions of the context they decorate. The ``includepath`` attribute is a tuple of names. Each name is typically the name of an included configuration file. The ``info`` attribute contains descriptive information helpful when reporting errors. If not set, it defaults to an empty string. The actions attribute is a sequence of tuples with items: - discriminator, a value that identifies the action. Two actions that have the same (non None) discriminator conflict. - an object that is called to execute the action, - positional arguments for the action - keyword arguments for the action - a tuple of include file names (defaults to ()) - an object that has descriptive information about the action (defaults to '') For brevity, trailing items after the callable in the tuples are ommitted if they are empty. """ def __init__(self): super(ConfigurationContext, self).__init__() self._seen_files = set() self._features = set() def resolve(self, dottedname): """Resolve a dotted name to an object Examples: >>> c = ConfigurationContext() >>> import zope, zope.interface >>> c.resolve('zope') is zope 1 >>> c.resolve('zope.interface') is zope.interface 1 >>> c.resolve('zope.configuration.eek') #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ConfigurationError: ImportError: Module zope.configuration has no global eek >>> c.resolve('.config.ConfigurationContext') Traceback (most recent call last): ... AttributeError: 'ConfigurationContext' object has no attribute """ \ """'package' >>> import zope.configuration >>> c.package = zope.configuration >>> c.resolve('.') is zope.configuration 1 >>> c.resolve('.config.ConfigurationContext') is ConfigurationContext 1 >>> c.resolve('..interface') is zope.interface 1 >>> c.resolve('unicode') """ name = dottedname.strip() if not name: raise ValueError("The given name is blank") if name == '.': return self.package names = name.split('.') if not names[-1]: raise ValueError( "Trailing dots are no longer supported in dotted names") if len(names) == 1: # Check for built-in objects marker = object() obj = getattr(__builtin__, names[0], marker) if obj is not marker: return obj if not names[0]: # Got a relative name. Convert it to abs using package info if self.package is None: raise ConfigurationError( "Can't use leading dots in dotted names, " "no package has been set.") pnames = self.package.__name__.split(".") pnames.append('') while names and not names[0]: try: names.pop(0) except IndexError: raise ConfigurationError("Invalid global name", name) try: pnames.pop() except IndexError: raise ConfigurationError("Invalid global name", name) names[0:0] = pnames # Now we should have an absolute dotted name # Split off object name: oname, mname = names[-1], '.'.join(names[:-1]) # Import the module if not mname: # Just got a single name. Must me a module mname = oname oname = '' try: mod = __import__(mname, *_import_chickens) except ImportError, v: if sys.exc_info()[2].tb_next is not None: # ImportError was caused deeper raise raise ConfigurationError( "ImportError: Couldn't import %s, %s" % (mname, v)) if not oname: # see not mname case above return mod try: obj = getattr(mod, oname) return obj except AttributeError: # No such name, maybe it's a module that we still need to import try: return __import__(mname+'.'+oname, *_import_chickens) except ImportError: if sys.exc_info()[2].tb_next is not None: # ImportError was caused deeper raise raise ConfigurationError( "ImportError: Module %s has no global %s" % (mname, oname)) def path(self, filename): """ Examples: >>> c = ConfigurationContext() >>> c.path("/x/y/z") == os.path.normpath("/x/y/z") 1 >>> c.path("y/z") Traceback (most recent call last): ... AttributeError: 'ConfigurationContext' object has no attribute """ \ """'package' >>> import zope.configuration >>> c.package = zope.configuration >>> import os >>> d = os.path.dirname(zope.configuration.__file__) >>> c.path("y/z") == d + os.path.normpath("/y/z") 1 >>> c.path("y/./z") == d + os.path.normpath("/y/z") 1 >>> c.path("y/../z") == d + os.path.normpath("/z") 1 """ filename = os.path.normpath(filename) if os.path.isabs(filename): return filename # Got a relative path, combine with base path. # If we have no basepath, compute the base path from the package # path. basepath = getattr(self, 'basepath', '') if not basepath: if self.package is None: basepath = os.getcwd() else: if hasattr(self.package, '__path__'): basepath = self.package.__path__[0] else: basepath = os.path.dirname(self.package.__file__) basepath = os.path.abspath(basepath) self.basepath = basepath return os.path.join(basepath, filename) def checkDuplicate(self, filename): """Check for duplicate imports of the same file. Raises an exception if this file had been processed before. This is better than an unlimited number of conflict errors. >>> c = ConfigurationContext() >>> c.checkDuplicate('/foo.zcml') >>> try: ... c.checkDuplicate('/foo.zcml') ... except ConfigurationError, e: ... # On Linux the exact msg has /foo, on Windows \foo. ... str(e).endswith("foo.zcml' included more than once") True You may use different ways to refer to the same file: >>> import zope.configuration >>> c.package = zope.configuration >>> import os >>> d = os.path.dirname(zope.configuration.__file__) >>> c.checkDuplicate('bar.zcml') >>> try: ... c.checkDuplicate(d + os.path.normpath('/bar.zcml')) ... except ConfigurationError, e: ... str(e).endswith("bar.zcml' included more than once") ... True """ #' <-- bow to font-lock path = self.path(filename) if path in self._seen_files: raise ConfigurationError('%r included more than once' % path) self._seen_files.add(path) def processFile(self, filename): """Check whether a file needs to be processed Return True if processing is needed and False otherwise. If the file needs to be processed, it will be marked as processed, assuming that the caller will procces the file if it needs to be procssed. >>> c = ConfigurationContext() >>> c.processFile('/foo.zcml') True >>> c.processFile('/foo.zcml') False You may use different ways to refer to the same file: >>> import zope.configuration >>> c.package = zope.configuration >>> import os >>> d = os.path.dirname(zope.configuration.__file__) >>> c.processFile('bar.zcml') True >>> c.processFile('bar.zcml') False """ #' <-- bow to font-lock path = self.path(filename) if path in self._seen_files: return False self._seen_files.add(path) return True def action(self, discriminator, callable=None, args=(), kw={}, order=0, includepath=None, info=None): """Add an action with the given discriminator, callable and arguments For testing purposes, the callable and arguments may be omitted. In that case, a default noop callable is used. The discriminator must be given, but it can be None, to indicate that the action never conflicts. Let's look at some examples: >>> c = ConfigurationContext() Normally, the context gets actions from subclasses. We'll provide an actions attribute ourselves: >>> c.actions = [] We'll use a test callable that has a convenient string representation >>> from zope.configuration.tests.directives import f >>> c.action(1, f, (1, ), {'x': 1}) >>> c.actions [(1, f, (1,), {'x': 1})] >>> c.action(None) >>> c.actions [(1, f, (1,), {'x': 1}), (None, None)] Now set the include path and info: >>> c.includepath = ('foo.zcml',) >>> c.info = "?" >>> c.action(None) >>> c.actions[-1] (None, None, (), {}, ('foo.zcml',), '?') We can add an order argument to crudely control the order of execution: >>> c.action(None, order=99999) >>> c.actions[-1] (None, None, (), {}, ('foo.zcml',), '?', 99999) We can also pass an includepath argument, which will be used as the the includepath for the action. (if includepath is None, self.includepath will be used): >>> c.action(None, includepath=('abc',)) >>> c.actions[-1] (None, None, (), {}, ('abc',), '?') We can also pass an info argument, which will be used as the the source line info for the action. (if info is None, self.info will be used): >>> c.action(None, info='abc') >>> c.actions[-1] (None, None, (), {}, ('foo.zcml',), 'abc') """ if info is None: info = getattr(self, 'info', '') if includepath is None: includepath = getattr(self, 'includepath', ()) action = (discriminator, callable, args, kw, includepath, info, order) # remove trailing false items while (len(action) > 2) and not action[-1]: action = action[:-1] self.actions.append(action) def hasFeature(self, feature): """Check whether a named feature has been provided. Initially no features are provided >>> c = ConfigurationContext() >>> c.hasFeature('onlinehelp') False You can declare that a feature is provided >>> c.provideFeature('onlinehelp') and it becomes available >>> c.hasFeature('onlinehelp') True """ return feature in self._features def provideFeature(self, feature): """Declare thata named feature has been provided. See `hasFeature` for examples. """ self._features.add(feature) class ConfigurationAdapterRegistry(object): """Simple adapter registry that manages directives as adapters >>> r = ConfigurationAdapterRegistry() >>> c = ConfigurationMachine() >>> r.factory(c, ('http://www.zope.com','xxx')) Traceback (most recent call last): ... ConfigurationError: ('Unknown directive', 'http://www.zope.com', 'xxx') >>> from zope.configuration.interfaces import IConfigurationContext >>> def f(): ... pass >>> r.register(IConfigurationContext, ('http://www.zope.com', 'xxx'), f) >>> r.factory(c, ('http://www.zope.com','xxx')) is f 1 >>> r.factory(c, ('http://www.zope.com','yyy')) is f Traceback (most recent call last): ... ConfigurationError: ('Unknown directive', 'http://www.zope.com', 'yyy') >>> r.register(IConfigurationContext, 'yyy', f) >>> r.factory(c, ('http://www.zope.com','yyy')) is f 1 Test the documentation feature: >>> r._docRegistry [] >>> r.document(('ns', 'dir'), IFullInfo, IConfigurationContext, None, ... 'inf', None) >>> r._docRegistry[0][0] == ('ns', 'dir') 1 >>> r._docRegistry[0][1] is IFullInfo 1 >>> r._docRegistry[0][2] is IConfigurationContext 1 >>> r._docRegistry[0][3] is None 1 >>> r._docRegistry[0][4] == 'inf' 1 >>> r._docRegistry[0][5] is None 1 >>> r.document('all-dir', None, None, None, None) >>> r._docRegistry[1][0] ('', 'all-dir') """ def __init__(self): super(ConfigurationAdapterRegistry, self).__init__() self._registry = {} # Stores tuples of form: # (namespace, name), schema, usedIn, info, parent self._docRegistry = [] def register(self, interface, name, factory): r = self._registry.get(name) if r is None: r = AdapterRegistry() self._registry[name] = r r.register([interface], Interface, '', factory) def document(self, name, schema, usedIn, handler, info, parent=None): if isinstance(name, (str, unicode)): name = ('', name) self._docRegistry.append((name, schema, usedIn, handler, info, parent)) def factory(self, context, name): r = self._registry.get(name) if r is None: # Try namespace-independent name ns, n = name r = self._registry.get(n) if r is None: raise ConfigurationError("Unknown directive", ns, n) f = r.lookup1(providedBy(context), Interface) if f is None: raise ConfigurationError( "The directive %s cannot be used in this context" % (name, )) return f class ConfigurationMachine(ConfigurationAdapterRegistry, ConfigurationContext): """Configuration machine Example: >>> machine = ConfigurationMachine() >>> ns = "http://www.zope.org/testing" Register a directive: >>> machine((metans, "directive"), ... namespace=ns, name="simple", ... schema="zope.configuration.tests.directives.ISimple", ... handler="zope.configuration.tests.directives.simple") and try it out: >>> machine((ns, "simple"), a=u"aa", c=u"cc") >>> machine.actions [(('simple', u'aa', u'xxx', 'cc'), f, (u'aa', u'xxx', 'cc'))] A more extensive example can be found in the unit tests. """ implements(IConfigurationContext) package = None basepath = None includepath = () info = '' def __init__(self): super(ConfigurationMachine, self).__init__() self.actions = [] self.stack = [RootStackItem(self)] self.i18n_strings = {} _bootstrap(self) def begin(self, __name, __data=None, __info=None, **kw): if __data: if kw: raise TypeError("Can't provide a mapping object and keyword " "arguments") else: __data = kw self.stack.append(self.stack[-1].contained(__name, __data, __info)) def end(self): self.stack.pop().finish() def __call__(self, __name, __info=None, **__kw): self.begin(__name, __kw, __info) self.end() def getInfo(self): return self.stack[-1].context.info def setInfo(self, info): self.stack[-1].context.info = info def execute_actions(self, clear=True, testing=False): """Execute the configuration actions This calls the action callables after resolving conflicts For example: >>> output = [] >>> def f(*a, **k): ... output.append(('f', a, k)) >>> context = ConfigurationMachine() >>> context.actions = [ ... (1, f, (1,)), ... (1, f, (11,), {}, ('x', )), ... (2, f, (2,)), ... ] >>> context.execute_actions() >>> output [('f', (1,), {}), ('f', (2,), {})] If the action raises an error, we convert it to a ConfigurationExecutionError. >>> output = [] >>> def bad(): ... bad.xxx >>> context.actions = [ ... (1, f, (1,)), ... (1, f, (11,), {}, ('x', )), ... (2, f, (2,)), ... (3, bad, (), {}, (), 'oops') ... ] >>> try: ... v = context.execute_actions() ... except ConfigurationExecutionError, v: ... pass >>> print v exceptions.AttributeError: 'function' object has no attribute 'xxx' in: oops Note that actions executed before the error still have an effect: >>> output [('f', (1,), {}), ('f', (2,), {})] """ try: for action in resolveConflicts(self.actions): (discriminator, callable, args, kw, includepath, info, order ) = expand_action(*action) if callable is None: continue try: callable(*args, **kw) except (KeyboardInterrupt, SystemExit): raise except: if testing: raise t, v, tb = sys.exc_info() raise ConfigurationExecutionError(t, v, info), None, tb finally: if clear: del self.actions[:] class ConfigurationExecutionError(ConfigurationError): """An error occurred during execution of a configuration action """ def __init__(self, etype, evalue, info): self.etype, self.evalue, self.info = etype, evalue, info def __str__(self): return "%s: %s\n in:\n %s" % (self.etype, self.evalue, self.info) ############################################################################## # Stack items class IStackItem(Interface): """Configuration machine stack items Stack items are created when a directive is being processed. A stack item is created for each directive use. """ def contained(name, data, info): """Begin processing a contained directive The data are a dictionary of attribute names mapped to unicode strings. The info argument is an object that can be converted to a string and that contains information about the directive. The begin method returns the next item to be placed on the stack. """ def finish(): """Finish processing a directive """ class SimpleStackItem(object): """Simple stack item A simple stack item can't have anything added after it. It can only be removed. It is used for simple directives and subdirectives, which can't contain other directives. It also defers any computation until the end of the directive has been reached. """ implements(IStackItem) def __init__(self, context, handler, info, *argdata): newcontext = GroupingContextDecorator(context) newcontext.info = info self.context = newcontext self.handler = handler self.argdata = argdata def contained(self, name, data, info): raise ConfigurationError("Invalid directive %s" % str(name)) def finish(self): # We're going to use the context that was passed to us, which wasn't # created for the directive. We want to set it's info to the one # passed to us while we make the call, so we'll save the old one # and restore it. context = self.context args = toargs(context, *self.argdata) actions = self.handler(context, **args) if actions: # we allow the handler to return nothing for action in actions: context.action(*action) class RootStackItem(object): def __init__(self, context): self.context = context def contained(self, name, data, info): """Handle a contained directive We have to compute a new stack item by getting a named adapter for the current context object. """ factory = self.context.factory(self.context, name) if factory is None: raise ConfigurationError("Invalid directive", name) adapter = factory(self.context, data, info) return adapter def finish(self): pass class GroupingStackItem(RootStackItem): """Stack item for a grouping directive A grouping stack item is in the stack when a grouping directive is being processed. Grouping directives group other directives. Often, they just manage common data, but they may also take actions, either before or after contained directives are executed. A grouping stack item is created with a grouping directive definition, a configuration context, and directive data. To see how this works, let's look at an example: We need a context. We'll just use a configuration machine >>> context = ConfigurationMachine() We need a callable to use in configuration actions. We'll use a convenient one from the tests: >>> from zope.configuration.tests.directives import f We need a handler for the grouping directive. This is a class that implements a context decorator. The decorator must also provide ``before`` and ``after`` methods that are called before and after any contained directives are processed. We'll typically subclass ``GroupingContextDecorator``, which provides context decoration, and default ``before`` and ``after`` methods. >>> class SampleGrouping(GroupingContextDecorator): ... def before(self): ... self.action(('before', self.x, self.y), f) ... def after(self): ... self.action(('after'), f) We'll use our decorator to decorate our initial context, providing keyword arguments x and y: >>> dec = SampleGrouping(context, x=1, y=2) Note that the keyword arguments are made attributes of the decorator. Now we'll create the stack item. >>> item = GroupingStackItem(dec) We still haven't called the before action yet, which we can verify by looking at the context actions: >>> context.actions [] Subdirectives will get looked up as adapters of the context. We'll create a simple handler: >>> def simple(context, data, info): ... context.action(("simple", context.x, context.y, data), f) ... return info and register it with the context: >>> context.register(IConfigurationContext, (testns, 'simple'), simple) This handler isn't really a propert handler, because it doesn't return a new context. It will do for this example. Now we'll call the contained method on the stack item: >>> item.contained((testns, 'simple'), {'z': 'zope'}, "someinfo") 'someinfo' We can verify thet the simple method was called by looking at the context actions. Note that the before method was called before handling the contained directive. >>> from pprint import PrettyPrinter >>> pprint=PrettyPrinter(width=60).pprint >>> pprint(context.actions) [(('before', 1, 2), f), (('simple', 1, 2, {'z': 'zope'}), f)] Finally, we call finish, which calls the decorator after method: >>> item.finish() >>> pprint(context.actions) [(('before', 1, 2), f), (('simple', 1, 2, {'z': 'zope'}), f), ('after', f)] If there were no nested directives: >>> context = ConfigurationMachine() >>> dec = SampleGrouping(context, x=1, y=2) >>> item = GroupingStackItem(dec) >>> item.finish() Then before will be when we call finish: >>> pprint(context.actions) [(('before', 1, 2), f), ('after', f)] """ implements(IStackItem) def __init__(self, context): super(GroupingStackItem, self).__init__(context) def __callBefore(self): actions = self.context.before() if actions: for action in actions: self.context.action(*action) self.__callBefore = noop def contained(self, name, data, info): self.__callBefore() return RootStackItem.contained(self, name, data, info) def finish(self): self.__callBefore() actions = self.context.after() if actions: for action in actions: self.context.action(*action) def noop(): pass class ComplexStackItem(object): """Complex stack item A complex stack item is in the stack when a complex directive is being processed. It only allows subdirectives to be used. A complex stack item is created with a complex directive definition (IComplexDirectiveContext), a configuration context, and directive data. To see how this works, let's look at an example: We need a context. We'll just use a configuration machine >>> context = ConfigurationMachine() We need a callable to use in configuration actions. We'll use a convenient one from the tests: >>> from zope.configuration.tests.directives import f We need a handler for the complex directive. This is a class with a method for each subdirective: >>> class Handler(object): ... def __init__(self, context, x, y): ... self.context, self.x, self.y = context, x, y ... context.action('init', f) ... def sub(self, context, a, b): ... context.action(('sub', a, b), f) ... def __call__(self): ... self.context.action(('call', self.x, self.y), f) We need a complex directive definition: >>> class Ixy(Interface): ... x = zope.schema.TextLine() ... y = zope.schema.TextLine() >>> definition = ComplexDirectiveDefinition( ... context, name="test", schema=Ixy, ... handler=Handler) >>> class Iab(Interface): ... a = zope.schema.TextLine() ... b = zope.schema.TextLine() >>> definition['sub'] = Iab, '' OK, now that we have the context, handler and definition, we're ready to use a stack item. >>> item = ComplexStackItem(definition, context, {'x': u'xv', 'y': u'yv'}, ... 'foo') When we created the definition, the handler (factory) was called. >>> context.actions [('init', f, (), {}, (), 'foo')] If a subdirective is provided, the ``contained`` method of the stack item is called. It will lookup the subdirective schema and call the corresponding method on the handler instance: >>> simple = item.contained(('somenamespace', 'sub'), ... {'a': u'av', 'b': u'bv'}, 'baz') >>> simple.finish() Note that the name passed to ``contained`` is a 2-part name, consisting of a namespace and a name within the namespace. >>> from pprint import PrettyPrinter >>> pprint=PrettyPrinter(width=60).pprint >>> pprint(context.actions) [('init', f, (), {}, (), 'foo'), (('sub', u'av', u'bv'), f, (), {}, (), 'baz')] The new stack item returned by contained is one that doesn't allow any more subdirectives, When all of the subdirectives have been provided, the ``finish`` method is called: >>> item.finish() The stack item will call the handler if it is callable. >>> pprint(context.actions) [('init', f, (), {}, (), 'foo'), (('sub', u'av', u'bv'), f, (), {}, (), 'baz'), (('call', u'xv', u'yv'), f, (), {}, (), 'foo')] """ implements(IStackItem) def __init__(self, meta, context, data, info): newcontext = GroupingContextDecorator(context) newcontext.info = info self.context = newcontext self.meta = meta # Call the handler contructor args = toargs(newcontext, meta.schema, data) self.handler = self.meta.handler(newcontext, **args) def contained(self, name, data, info): """Handle a subdirective """ # Look up the subdirective meta data on our meta object ns, name = name schema = self.meta.get(name) if schema is None: raise ConfigurationError("Invalid directive", name) schema = schema[0] # strip off info handler = getattr(self.handler, name) return SimpleStackItem(self.context, handler, info, schema, data) def finish(self): # when we're done, we call the handler, which might return more actions # Need to save and restore old info try: actions = self.handler() except AttributeError, v: if v[0] == '__call__': return # noncallable raise except TypeError: return # non callable if actions: # we allow the handler to return nothing for action in actions: self.context.action(*action) ############################################################################## # Helper classes class GroupingContextDecorator(ConfigurationContext): """Helper mix-in class for building grouping directives See the discussion (and test) in GroupingStackItem. """ implements(IConfigurationContext, IGroupingContext) def __init__(self, context, **kw): self.context = context for name, v in kw.items(): setattr(self, name, v) def __getattr__(self, name, getattr=getattr, setattr=setattr): v = getattr(self.context, name) # cache result in self setattr(self, name, v) return v def before(self): pass def after(self): pass ############################################################################## # Directive-definition class DirectiveSchema(fields.GlobalInterface): """A field that contains a global variable value that must be a schema """ class IDirectivesInfo(Interface): """Schema for the ``directives`` directive """ namespace = zope.schema.URI( title=u"Namespace", description=u"The namespace in which directives' names will be defined", ) class IDirectivesContext(IDirectivesInfo, IConfigurationContext): pass class DirectivesHandler(GroupingContextDecorator): """Handler for the directives directive This is just a grouping directive that adds a namespace attribute to the normal directive context. """ implements(IDirectivesContext) class IDirectiveInfo(Interface): """Information common to all directive definitions have """ name = zope.schema.TextLine( title = u"Directive name", description = u"The name of the directive being defined", ) schema = DirectiveSchema( title = u"Directive handler", description = u"The dotted name of the directive handler", ) class IFullInfo(IDirectiveInfo): """Information that all top-level directives (not subdirectives) have """ handler = fields.GlobalObject( title = u"Directive handler", description = u"The dotted name of the directive handler", ) usedIn = fields.GlobalInterface( title = u"The directive types the directive can be used in", description = (u"The interface of the directives that can contain " u"the directive" ), default = IConfigurationContext, ) class IStandaloneDirectiveInfo(IDirectivesInfo, IFullInfo): """Info for full directives defined outside a directives directives """ def defineSimpleDirective(context, name, schema, handler, namespace='', usedIn=IConfigurationContext): """Define a simple directive Define and register a factory that invokes the simple directive and returns a new stack item, which is always the same simple stack item. If the namespace is '*', the directive is registered for all namespaces. for example: >>> context = ConfigurationMachine() >>> from zope.configuration.tests.directives import f >>> class Ixy(Interface): ... x = zope.schema.TextLine() ... y = zope.schema.TextLine() >>> def s(context, x, y): ... context.action(('s', x, y), f) >>> defineSimpleDirective(context, 's', Ixy, s, testns) >>> context((testns, "s"), x=u"vx", y=u"vy") >>> context.actions [(('s', u'vx', u'vy'), f)] >>> context(('http://www.zope.com/t1', "s"), x=u"vx", y=u"vy") Traceback (most recent call last): ... ConfigurationError: ('Unknown directive', 'http://www.zope.com/t1', 's') >>> context = ConfigurationMachine() >>> defineSimpleDirective(context, 's', Ixy, s, "*") >>> context(('http://www.zope.com/t1', "s"), x=u"vx", y=u"vy") >>> context.actions [(('s', u'vx', u'vy'), f)] """ namespace = namespace or context.namespace if namespace != '*': name = namespace, name def factory(context, data, info): return SimpleStackItem(context, handler, info, schema, data) factory.schema = schema context.register(usedIn, name, factory) context.document(name, schema, usedIn, handler, context.info) def defineGroupingDirective(context, name, schema, handler, namespace='', usedIn=IConfigurationContext): """Define a grouping directive Define and register a factory that sets up a grouping directive. If the namespace is '*', the directive is registered for all namespaces. for example: >>> context = ConfigurationMachine() >>> from zope.configuration.tests.directives import f >>> class Ixy(Interface): ... x = zope.schema.TextLine() ... y = zope.schema.TextLine() We won't bother creating a special grouping directive class. We'll just use GroupingContextDecorator, which simply sets up a grouping context that has extra attributes defined by a schema: >>> defineGroupingDirective(context, 'g', Ixy, ... GroupingContextDecorator, testns) >>> context.begin((testns, "g"), x=u"vx", y=u"vy") >>> context.stack[-1].context.x u'vx' >>> context.stack[-1].context.y u'vy' >>> context(('http://www.zope.com/t1', "g"), x=u"vx", y=u"vy") Traceback (most recent call last): ... ConfigurationError: ('Unknown directive', 'http://www.zope.com/t1', 'g') >>> context = ConfigurationMachine() >>> defineGroupingDirective(context, 'g', Ixy, ... GroupingContextDecorator, "*") >>> context.begin(('http://www.zope.com/t1', "g"), x=u"vx", y=u"vy") >>> context.stack[-1].context.x u'vx' >>> context.stack[-1].context.y u'vy' """ namespace = namespace or context.namespace if namespace != '*': name = namespace, name def factory(context, data, info): args = toargs(context, schema, data) newcontext = handler(context, **args) newcontext.info = info return GroupingStackItem(newcontext) factory.schema = schema context.register(usedIn, name, factory) context.document(name, schema, usedIn, handler, context.info) class IComplexDirectiveContext(IFullInfo, IConfigurationContext): pass class ComplexDirectiveDefinition(GroupingContextDecorator, dict): """Handler for defining complex directives See the description and tests for ComplexStackItem. """ implements(IComplexDirectiveContext) def before(self): def factory(context, data, info): return ComplexStackItem(self, context, data, info) factory.schema = self.schema self.register(self.usedIn, (self.namespace, self.name), factory) self.document((self.namespace, self.name), self.schema, self.usedIn, self.handler, self.info) def subdirective(context, name, schema): context.document((context.namespace, name), schema, context.usedIn, getattr(context.handler, name, context.handler), context.info, context.context) context.context[name] = schema, context.info ############################################################################## # Features class IProvidesDirectiveInfo(Interface): """Information for a directive""" feature = zope.schema.TextLine( title = u"Feature name", description = u"""The name of the feature being provided You can test available features with zcml:condition="have featurename". """, ) def provides(context, feature): """Declare that a feature is provided in context. >>> c = ConfigurationContext() >>> provides(c, 'apidoc') >>> c.hasFeature('apidoc') True Spaces are not allowed in feature names (this is reserved for providing many features with a single directive in the futute). >>> provides(c, 'apidoc onlinehelp') Traceback (most recent call last): ... ValueError: Only one feature name allowed >>> c.hasFeature('apidoc onlinehelp') False """ if len(feature.split()) > 1: raise ValueError("Only one feature name allowed") context.provideFeature(feature) ############################################################################## # Argument conversion def toargs(context, schema, data): """Marshal data to an argument dictionary using a schema Names that are python keywords have an underscore added as a suffix in the schema and in the argument list, but are used without the underscore in the data. The fields in the schema must all implement IFromUnicode. All of the items in the data must have corresponding fields in the schema unless the schema has a true tagged value named 'keyword_arguments'. Here's an example: >>> from zope import schema >>> class schema(Interface): ... in_ = zope.schema.Int(constraint=lambda v: v > 0) ... f = zope.schema.Float() ... n = zope.schema.TextLine(min_length=1, default=u"rob") ... x = zope.schema.BytesLine(required=False) ... u = zope.schema.URI() >>> context = ConfigurationMachine() >>> from pprint import PrettyPrinter >>> pprint=PrettyPrinter(width=50).pprint >>> pprint(toargs(context, schema, ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', ... 'u': u'http://www.zope.org' })) {'f': 1.2, 'in_': 1, 'n': u'bob', 'u': 'http://www.zope.org', 'x': 'x.y.z'} If we have extra data, we'll get an error: >>> toargs(context, schema, ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', ... 'u': u'http://www.zope.org', 'a': u'1'}) Traceback (most recent call last): ... ConfigurationError: ('Unrecognized parameters:', 'a') Unless we set a tagged value to say that extra arguments are ok: >>> schema.setTaggedValue('keyword_arguments', True) >>> pprint(toargs(context, schema, ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', ... 'u': u'http://www.zope.org', 'a': u'1'})) {'a': u'1', 'f': 1.2, 'in_': 1, 'n': u'bob', 'u': 'http://www.zope.org', 'x': 'x.y.z'} If we ommit required data we get an error telling us what was omitted: >>> pprint(toargs(context, schema, ... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z'})) Traceback (most recent call last): ... ConfigurationError: ('Missing parameter:', 'u') Although we can omit not-required data: >>> pprint(toargs(context, schema, ... {'in': u'1', 'f': u'1.2', 'n': u'bob', ... 'u': u'http://www.zope.org', 'a': u'1'})) {'a': u'1', 'f': 1.2, 'in_': 1, 'n': u'bob', 'u': 'http://www.zope.org'} And we can ommit required fields if they have valid defaults (defaults that are valid values): >>> pprint(toargs(context, schema, ... {'in': u'1', 'f': u'1.2', ... 'u': u'http://www.zope.org', 'a': u'1'})) {'a': u'1', 'f': 1.2, 'in_': 1, 'n': u'rob', 'u': 'http://www.zope.org'} We also get an error if any data was invalid: >>> pprint(toargs(context, schema, ... {'in': u'0', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z', ... 'u': u'http://www.zope.org', 'a': u'1'})) Traceback (most recent call last): ... ConfigurationError: ('Invalid value for', 'in', '0') """ data = dict(data) args = {} for name, field in schema.namesAndDescriptions(True): field = field.bind(context) n = name if n.endswith('_') and iskeyword(n[:-1]): n = n[:-1] s = data.get(n, data) if s is not data: s = unicode(s) del data[n] try: args[str(name)] = field.fromUnicode(s) except zope.schema.ValidationError, v: raise ConfigurationError( "Invalid value for", n, str(v)), None, sys.exc_info()[2] elif field.required: # if the default is valid, we can use that: default = field.default try: field.validate(default) except zope.schema.ValidationError: raise ConfigurationError("Missing parameter:", n) args[str(name)] = default if data: # we had data left over try: keyword_arguments = schema.getTaggedValue('keyword_arguments') except KeyError: keyword_arguments = False if not keyword_arguments: raise ConfigurationError("Unrecognized parameters:", *data) for name in data: args[str(name)] = data[name] return args ############################################################################## # Conflict resolution def expand_action(discriminator, callable=None, args=(), kw={}, includepath=(), info='', order=0): return (discriminator, callable, args, kw, includepath, info, order) def resolveConflicts(actions): """Resolve conflicting actions Given an actions list, identify and try to resolve conflicting actions. Actions conflict if they have the same non-null discriminator. Conflicting actions can be resolved if the include path of one of the actions is a prefix of the includepaths of the other conflicting actions and is unequal to the include paths in the other conflicting actions. Here are some examples to illustrate how this works: >>> from zope.configuration.tests.directives import f >>> from pprint import PrettyPrinter >>> pprint=PrettyPrinter(width=60).pprint >>> pprint(resolveConflicts([ ... (None, f), ... (1, f, (1,), {}, (), 'first'), ... (1, f, (2,), {}, ('x',), 'second'), ... (1, f, (3,), {}, ('y',), 'third'), ... (4, f, (4,), {}, ('y',), 'should be last', 99999), ... (3, f, (3,), {}, ('y',)), ... (None, f, (5,), {}, ('y',)), ... ])) [(None, f), (1, f, (1,), {}, (), 'first'), (3, f, (3,), {}, ('y',)), (None, f, (5,), {}, ('y',)), (4, f, (4,), {}, ('y',), 'should be last')] >>> try: ... v = resolveConflicts([ ... (None, f), ... (1, f, (2,), {}, ('x',), 'eek'), ... (1, f, (3,), {}, ('y',), 'ack'), ... (4, f, (4,), {}, ('y',)), ... (3, f, (3,), {}, ('y',)), ... (None, f, (5,), {}, ('y',)), ... ]) ... except ConfigurationConflictError, v: ... pass >>> print v Conflicting configuration actions For: 1 eek ack """ # organize actions by discriminators unique = {} output = [] for i in range(len(actions)): (discriminator, callable, args, kw, includepath, info, order ) = expand_action(*(actions[i])) order = order or i if discriminator is None: # The discriminator is None, so this directive can # never conflict. We can add it directly to the # configuration actions. output.append( (order, discriminator, callable, args, kw, includepath, info) ) continue a = unique.setdefault(discriminator, []) a.append( (includepath, order, callable, args, kw, info) ) # Check for conflicts conflicts = {} for discriminator, dups in unique.items(): # We need to sort the actions by the paths so that the shortest # path with a given prefix comes first: dups.sort() (basepath, i, callable, args, kw, baseinfo) = dups[0] output.append( (i, discriminator, callable, args, kw, basepath, baseinfo) ) for includepath, i, callable, args, kw, info in dups[1:]: # Test whether path is a prefix of opath if (includepath[:len(basepath)] != basepath # not a prefix or (includepath == basepath) ): if discriminator not in conflicts: conflicts[discriminator] = [baseinfo] conflicts[discriminator].append(info) if conflicts: raise ConfigurationConflictError(conflicts) # Now put the output back in the original order, and return it: output.sort() r = [] for o in output: action = o[1:] while len(action) > 2 and not action[-1]: action = action[:-1] r.append(action) return r class ConfigurationConflictError(ConfigurationError): def __init__(self, conflicts): self._conflicts = conflicts def __str__(self): r = ["Conflicting configuration actions"] items = self._conflicts.items() items.sort() for discriminator, infos in items: r.append(" For: %s" % (discriminator, )) for info in infos: for line in unicode(info).rstrip().split(u'\n'): r.append(u" "+line) return "\n".join(r) ############################################################################## # Bootstap code def _bootstrap(context): # Set enough machinery to register other directives # Define the directive (simple directive) directive by calling it's # handler directly info = 'Manually registered in zope/configuration/config.py' context.info = info defineSimpleDirective( context, namespace=metans, name='directive', schema=IStandaloneDirectiveInfo, handler=defineSimpleDirective) context.info = '' # OK, now that we have that, we can use the machine to define the # other directives. This isn't the easiest way to proceed, but it lets # us eat our own dogfood. :) # Standalone groupingDirective context((metans, 'directive'), info, name='groupingDirective', namespace=metans, handler="zope.configuration.config.defineGroupingDirective", schema="zope.configuration.config.IStandaloneDirectiveInfo" ) # Now we can use the grouping directive to define the directives directive context((metans, 'groupingDirective'), info, name='directives', namespace=metans, handler="zope.configuration.config.DirectivesHandler", schema="zope.configuration.config.IDirectivesInfo" ) # directive and groupingDirective inside directives context((metans, 'directive'), info, name='directive', namespace=metans, usedIn="zope.configuration.config.IDirectivesContext", handler="zope.configuration.config.defineSimpleDirective", schema="zope.configuration.config.IFullInfo" ) context((metans, 'directive'), info, name='groupingDirective', namespace=metans, usedIn="zope.configuration.config.IDirectivesContext", handler="zope.configuration.config.defineGroupingDirective", schema="zope.configuration.config.IFullInfo" ) # Setup complex directive directive, both standalone, and in # directives directive context((metans, 'groupingDirective'), info, name='complexDirective', namespace=metans, handler="zope.configuration.config.ComplexDirectiveDefinition", schema="zope.configuration.config.IStandaloneDirectiveInfo" ) context((metans, 'groupingDirective'), info, name='complexDirective', namespace=metans, usedIn="zope.configuration.config.IDirectivesContext", handler="zope.configuration.config.ComplexDirectiveDefinition", schema="zope.configuration.config.IFullInfo" ) # Finally, setup subdirective directive context((metans, 'directive'), info, name='subdirective', namespace=metans, usedIn="zope.configuration.config.IComplexDirectiveContext", handler="zope.configuration.config.subdirective", schema="zope.configuration.config.IDirectiveInfo" ) # meta:provides context((metans, 'directive'), info, name='provides', namespace=metans, handler="zope.configuration.config.provides", schema="zope.configuration.config.IProvidesDirectiveInfo" ) zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/interfaces.py0000644000175000017500000001065712214017543026402 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Zope Configuration (ZCML) interfaces """ from zope.interface import Interface from zope.schema import BytesLine from zope.schema.interfaces import ValidationError class InvalidToken(ValidationError): """Invaid token in list.""" class IConfigurationContext(Interface): """Configuration Context The configuration context manages information about the state of the configuration system, such as the package containing the configuration file. More importantly, it provides methods for importing objects and opening files relative to the package. """ package = BytesLine( title=u"The current package name", description=u"""\ This is the name of the package containing the configuration file being executed. If the configuration file was not included by package, then this is None. """, required=False, ) def resolve(dottedname): """Resolve a dotted name to an object A dotted name is constructed by concatenating a dotted module name with a global name within the module using a dot. For example, the object named "spam" in the foo.bar module has a dotted name of foo.bar.spam. If the current package is a prefix of a dotted name, then the package name can be relaced with a leading dot, So, for example, if the configuration file is in the foo package, then the dotted name foo.bar.spam can be shortened to .bar.spam. If the current package is multiple levels deep, multiple leading dots can be used to refer to higher-level modules. For example, if the current package is x.y.z, the dotted object name ..foo refers to x.y.foo. """ def path(filename): """Compute a full file name for the given file If the filename is relative to the package, then the returned name will include the package path, otherwise, the original file name is returned. """ def checkDuplicate(filename): """Check for duplicate imports of the same file. Raises an exception if this file had been processed before. This is better than an unlimited number of conflict errors. """ def processFile(filename): """Check whether a file needs to be processed. Return True if processing is needed and False otherwise. If the file needs to be processed, it will be marked as processed, assuming that the caller will procces the file if it needs to be procssed. """ def action(self, discriminator, callable, args=(), kw={}, order=0, includepath=None, info=None): """Record a configuration action The job of most directives is to compute actions for later processing. The action method is used to record those actions. The discriminator is used to to find actions that conflict. Actions conflict if they have the same discriminator. The exception to this is the special case of the discriminator with the value None. An actions with a discriminator of None never conflicts with other actions. This is possible to add an order argument to crudely control the order of execution. 'info' is optional source line information, 'includepath' is None (the default) or a tuple of include paths for this action. """ def provideFeature(name): """Record that a named feature is available in this context.""" def hasFeature(name): """Check whether a named feature is available in this context.""" class IGroupingContext(Interface): def before(): """Do something before processing nested directives """ def after(): """Do something after processing nested directives """ zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/__init__.py0000644000175000017500000000151312214017543026005 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Zope configuration support Software that wants to provide new config directives calls zope.configuration.meta.register. """ def namespace(suffix): return 'http://namespaces.zope.org/'+suffix zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/README.txt0000644000175000017500000000600412214017543025372 0ustar arnauarnau========================== Zope configuration system ========================== The zope configuration system provides an extensible system for supporting variouse kinds of configurations. It is based on the idea of configuration directives. Users of the configuration system provide configuration directives in some language that express configuration choices. The intent is that the language be pluggable. An XML language is provided by default. Configuration is performed in three stages. In the first stage, directives are processed to compute configuration actions. Configuration actions consist of: - A discriminator - A callable - Positional arguments - Keyword arguments The actions are essentially delayed function calls. Two or more actions conflict if they have the same discriminator. The configuration system has rules for resolving conflicts. If conflicts cannot be resolved, an error will result. Conflict resolution typically discards all but one of the conflicting actions, so that the remaining action of the originally-conflicting actions no longer conflicts. Non-conflicting actions are executed in the order that they were created by passing the positional and non-positional arguments to the action callable. The system is extensible. There is a meta-configuration language for defining configuration directives. A directive is defined by providing meta data about the directive and handler code to process the directive. There are four kinds of directives: - Simple directives compute configuration actions. Their handlers are typically functions that take a context and zero or more keyword arguments and return a sequence of configuration actions. To learn how to create simple directives, see `tests/test_simple.py`. - Grouping directives collect information to be used by nested directives. They are called with a context object which they adapt to some interface that extends IConfigurationContext. To learn how to create grouping directives, look at the documentation in zopeconfigure.py, which provides the implementation of the zope `configure` directive. Other directives can be nested in grouping directives. To learn how to implement nested directives, look at the documentation in `tests/test_nested.py`. - Complex directives are directives that have subdirectives. Subdirectives have handlers that are simply methods of complex directives. Complex diretives are handled by factories, typically classes, that create objects that have methods for handling subdirectives. These objects also have __call__ methods that are called when processing of subdirectives is finished. Complex directives only exist to support old directive handlers. They will probably be deprecated in the future. - Subdirectives are nested in complex directives. They are like simple directives except that they hane handlers that are complex directive methods. Subdirectives, like complex directives only exist to support old directive handlers. They will probably be deprecated in the future. zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/fields.py0000644000175000017500000002750412214017543025524 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Configuration-specific schema fields """ __docformat__ = 'restructuredtext' import os, re, warnings from zope import schema from zope.schema.interfaces import IFromUnicode from zope.schema.interfaces import ConstraintNotSatisfied from zope.configuration.exceptions import ConfigurationError from zope.interface import implements from zope.configuration.interfaces import InvalidToken PYIDENTIFIER_REGEX = u'\\A[a-zA-Z_]+[a-zA-Z0-9_]*\\Z' pyidentifierPattern = re.compile(PYIDENTIFIER_REGEX) class PythonIdentifier(schema.TextLine): r"""This field describes a python identifier, i.e. a variable name. Let's look at an example: >>> class FauxContext(object): ... pass >>> context = FauxContext() >>> field = PythonIdentifier().bind(context) Let's test the fromUnicode method: >>> field.fromUnicode(u'foo') u'foo' >>> field.fromUnicode(u'foo3') u'foo3' >>> field.fromUnicode(u'_foo3') u'_foo3' Now let's see whether validation works alright >>> for value in (u'foo', u'foo3', u'foo_', u'_foo3', u'foo_3', u'foo3_'): ... field._validate(value) >>> >>> from zope import schema >>> >>> for value in (u'3foo', u'foo:', u'\\', u''): ... try: ... field._validate(value) ... except schema.ValidationError: ... print 'Validation Error' Validation Error Validation Error Validation Error Validation Error """ implements(IFromUnicode) def fromUnicode(self, u): return u.strip() def _validate(self, value): super(PythonIdentifier, self)._validate(value) if pyidentifierPattern.match(value) is None: raise schema.ValidationError(value) class GlobalObject(schema.Field): """An object that can be accessed as a module global. Examples: First, we need to set up a stub name resolver: >>> d = {'x': 1, 'y': 42, 'z': 'zope'} >>> class fakeresolver(dict): ... def resolve(self, n): ... return self[n] >>> fake = fakeresolver(d) >>> g = GlobalObject(value_type=schema.Int()) >>> gg = g.bind(fake) >>> gg.fromUnicode("x") 1 >>> gg.fromUnicode(" x \\n ") 1 >>> gg.fromUnicode("y") 42 >>> gg.fromUnicode("z") Traceback (most recent call last): ... WrongType: ('zope', (, ), '') >>> g = GlobalObject(constraint=lambda x: x%2 == 0) >>> gg = g.bind(fake) >>> gg.fromUnicode("x") Traceback (most recent call last): ... ConstraintNotSatisfied: 1 >>> gg.fromUnicode("y") 42 >>> g = GlobalObject() >>> gg = g.bind(fake) >>> gg.fromUnicode('*') >>> """ implements(IFromUnicode) def __init__(self, value_type=None, **kw): self.value_type = value_type super(GlobalObject, self).__init__(**kw) def _validate(self, value): super(GlobalObject, self)._validate(value) if self.value_type is not None: self.value_type.validate(value) def fromUnicode(self, u): name = str(u.strip()) # special case, mostly for interfaces if name == '*': return None try: value = self.context.resolve(name) except ConfigurationError, v: raise schema.ValidationError(v) self.validate(value) return value class GlobalInterface(GlobalObject): """An interface that can be accessed from a module. First, we need to set up a stub name resolver: >>> class Foo(object): pass >>> from zope.interface import Interface >>> class IFoo(Interface): pass >>> d = {'Foo': Foo, 'IFoo': IFoo} >>> class fakeresolver(dict): ... def resolve(self, n): ... return self[n] >>> fake = fakeresolver(d) Now verify constraints are checked correctly. >>> g = GlobalInterface() >>> gg = g.bind(fake) >>> gg.fromUnicode('IFoo') >>> gg.fromUnicode(' IFoo ') >>> gg.fromUnicode('Foo') Traceback (most recent call last): ... WrongType: ('An interface is required', , '') """ def __init__(self, **kw): super(GlobalInterface, self).__init__(schema.InterfaceField(), **kw) class Tokens(schema.List): """A list that can be read from a space-separated string Consider GlobalObject tokens: Examples: First, we need to set up a stub name resolver: >>> d = {'x': 1, 'y': 42, 'z': 'zope', 'x.y.x': 'foo'} >>> class fakeresolver(dict): ... def resolve(self, n): ... return self[n] >>> fake = fakeresolver(d) >>> g = Tokens(value_type=GlobalObject()) >>> gg = g.bind(fake) >>> gg.fromUnicode(" \\n x y z \\n") [1, 42, 'zope'] >>> g = Tokens(value_type= ... GlobalObject(value_type= ... schema.Int(constraint=lambda x: x%2 == 0))) >>> gg = g.bind(fake) >>> gg.fromUnicode("x y") Traceback (most recent call last): ... InvalidToken: 1 in x y >>> gg.fromUnicode("z y") Traceback (most recent call last): ... InvalidToken: ('zope', (, ), '') in z y >>> gg.fromUnicode("y y") [42, 42] >>> """ implements(IFromUnicode) def fromUnicode(self, u): u = u.strip() if u: vt = self.value_type.bind(self.context) values = [] for s in u.split(): try: v = vt.fromUnicode(s) except schema.ValidationError, v: raise InvalidToken("%s in %s" % (v, u)) else: values.append(v) else: values = [] self.validate(values) return values class Path(schema.Text): r"""A file path name, which may be input as a relative path Input paths are converted to absolute paths and normalized. Let's look at an example: First, we need a "context" for the field that has a path function for converting relative path to an absolute path. We'll be careful to do this in an os-independent fashion. >>> class FauxContext(object): ... def path(self, p): ... return os.path.join(os.sep, 'faux', 'context', p) >>> context = FauxContext() >>> field = Path().bind(context) Lets try an absolute path first: >>> p = unicode(os.path.join(os.sep, 'a', 'b')) >>> n = field.fromUnicode(p) >>> n.split(os.sep) [u'', u'a', u'b'] This should also work with extra spaces around the path: >>> p = " \n %s \n\n " % p >>> n = field.fromUnicode(p) >>> n.split(os.sep) [u'', u'a', u'b'] Now try a relative path: >>> p = unicode(os.path.join('a', 'b')) >>> n = field.fromUnicode(p) >>> n.split(os.sep) [u'', u'faux', u'context', u'a', u'b'] """ implements(IFromUnicode) def fromUnicode(self, u): u = u.strip() if os.path.isabs(u): return os.path.normpath(u) return self.context.path(u) class Bool(schema.Bool): """A boolean value Values may be input (in upper or lower case) as any of: yes, no, y, n, true, false, t, or f. >>> Bool().fromUnicode(u"yes") 1 >>> Bool().fromUnicode(u"y") 1 >>> Bool().fromUnicode(u"true") 1 >>> Bool().fromUnicode(u"no") 0 """ implements(IFromUnicode) def fromUnicode(self, u): u = u.lower() if u in ('1', 'true', 'yes', 't', 'y'): return True if u in ('0', 'false', 'no', 'f', 'n'): return False raise schema.ValidationError class MessageID(schema.Text): """Text string that should be translated. When a string is converted to a message ID, it is also recorded in the context. >>> class Info(object): ... file = 'file location' ... line = 8 >>> class FauxContext(object): ... i18n_strings = {} ... info = Info() >>> context = FauxContext() >>> field = MessageID().bind(context) There is a fallback domain when no domain has been specified. Exchange the warn function so we can make test whether the warning has been issued >>> warned = None >>> def fakewarn(*args, **kw): ... global warned ... warned = args >>> import warnings >>> realwarn = warnings.warn >>> warnings.warn = fakewarn >>> i = field.fromUnicode(u"Hello world!") >>> i u'Hello world!' >>> i.domain 'untranslated' >>> warned ("You did not specify an i18n translation domain for the '' """ \ """field in file location",) >>> warnings.warn = realwarn With the domain specified: >>> context.i18n_strings = {} >>> context.i18n_domain = 'testing' We can get a message id: >>> i = field.fromUnicode(u"Hello world!") >>> i u'Hello world!' >>> i.domain 'testing' In addition, the string has been registered with the context: >>> context.i18n_strings {'testing': {u'Hello world!': [('file location', 8)]}} >>> i = field.fromUnicode(u"Foo Bar") >>> i = field.fromUnicode(u"Hello world!") >>> from pprint import PrettyPrinter >>> pprint=PrettyPrinter(width=70).pprint >>> pprint(context.i18n_strings) {'testing': {u'Foo Bar': [('file location', 8)], u'Hello world!': [('file location', 8), ('file location', 8)]}} >>> from zope.i18nmessageid import Message >>> isinstance(context.i18n_strings['testing'].keys()[0], Message) 1 Explicit Message IDs >>> i = field.fromUnicode(u'[View-Permission] View') >>> i u'View-Permission' >>> i.default u'View' >>> i = field.fromUnicode(u'[] [Some] text') >>> i u'[Some] text' >>> i.default is None True """ implements(IFromUnicode) __factories = {} def fromUnicode(self, u): context = self.context domain = getattr(context, 'i18n_domain', '') if not domain: domain = 'untranslated' warnings.warn( "You did not specify an i18n translation domain for the "\ "'%s' field in %s" % (self.getName(), context.info.file ) ) v = super(MessageID, self).fromUnicode(u) # Check whether there is an explicit message is specified default = None if v.startswith('[]'): v = v[2:].lstrip() elif v.startswith('['): end = v.find(']') default = v[end+2:] v = v[1:end] # Convert to a message id, importing the factory, if necessary factory = self.__factories.get(domain) if factory is None: import zope.i18nmessageid factory = zope.i18nmessageid.MessageFactory(domain) self.__factories[domain] = factory msgid = factory(v, default) # Record the string we got for the domain i18n_strings = context.i18n_strings strings = i18n_strings.get(domain) if strings is None: strings = i18n_strings[domain] = {} locations = strings.setdefault(msgid, []) locations.append((context.info.file, context.info.line)) return msgid zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/0000755000175000017500000000000012214017543025036 5ustar arnauarnauzope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/test_conditions.py0000644000175000017500000000671712214017543030633 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## r'''How to conditionalize specific directives There is a "condition" attribute in the "http://namespaces.zope.org/zcml" namespace which is honored on all elements in ZCML. The value of the attribute is an expression which is used to determine if that element and its descendents are used. If the condition is true, processing continues normally, otherwise that element and its descendents are ignored. Currently the expression is always of the form "have featurename", and it checks for the presence of a . Our demonstration uses a trivial registry; each registration consists of a simple id inserted in the global `registry` in this module. We can checked that a registration was made by checking whether the id is present in `registry`. We start by loading the example ZCML file, *conditions.zcml*:: >>> import zope.configuration.tests >>> import zope.configuration.xmlconfig >>> context = zope.configuration.xmlconfig.file("conditions.zcml", ... zope.configuration.tests) To show that our sample directive works, we see that the unqualified registration was successful:: >>> "unqualified.registration" in registry True When the expression specified with ``zcml:condition`` evaluates to true, the element it is attached to and all contained elements (not otherwise conditioned) should be processed normally:: >>> "direct.true.condition" in registry True >>> "nested.true.condition" in registry True However, when the expression evaluates to false, the conditioned element and all contained elements should be ignored:: >>> "direct.false.condition" in registry False >>> "nested.false.condition" in registry False Conditions on container elements affect the conditions in nested elements in a reasonable way. If an "outer" condition is true, nested conditions are processed normally:: >>> "true.condition.nested.in.true" in registry True >>> "false.condition.nested.in.true" in registry False If the outer condition is false, inner conditions are not even evaluated, and the nested elements are ignored:: >>> "true.condition.nested.in.false" in registry False >>> "false.condition.nested.in.false" in registry False Now we need to clean up after ourselves:: >>> del registry[:] ''' __docformat__ = "reStructuredText" import doctest import zope.interface import zope.schema class IRegister(zope.interface.Interface): """Trivial sample registry.""" id = zope.schema.Id( title=u"Identifier", description=u"Some identifier that can be checked.", required=True, ) registry = [] def register(context, id): context.action(discriminator=('Register', id), callable=registry.append, args=(id,) ) def test_suite(): return doctest.DocTestSuite() zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/bad.py0000644000175000017500000000011512214017543026133 0ustar arnauarnau# I'm bad. I want to be bad. Don't try to change me. import bad_to_the_bone zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/test_config.py0000644000175000017500000002304312214017543027716 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test configuration machinery. """ import sys import unittest import re from doctest import DocTestSuite from zope.testing import renormalizing from zope.configuration.config import metans, ConfigurationMachine from zope.configuration import config def test_config_extended_example(): """Configuration machine Examples: >>> machine = ConfigurationMachine() >>> ns = "http://www.zope.org/testing" Register some test directives: Start with a grouping directive that sets a package: >>> machine((metans, "groupingDirective"), ... name="package", namespace=ns, ... schema="zope.configuration.tests.directives.IPackaged", ... handler="zope.configuration.tests.directives.Packaged", ... ) Now we can set the package: >>> machine.begin((ns, "package"), ... package="zope.configuration.tests.directives", ... ) Which makes it easier to define the other directives: First, define some simple directives: >>> machine((metans, "directive"), ... namespace=ns, name="simple", ... schema=".ISimple", handler=".simple") >>> machine((metans, "directive"), ... namespace=ns, name="newsimple", ... schema=".ISimple", handler=".newsimple") and try them out: >>> machine((ns, "simple"), "first", a=u"aa", c=u"cc") >>> machine((ns, "newsimple"), "second", a=u"naa", c=u"ncc", b=u"nbb") >>> from pprint import PrettyPrinter >>> pprint=PrettyPrinter(width=50).pprint >>> pprint(machine.actions) [(('simple', u'aa', u'xxx', 'cc'), f, (u'aa', u'xxx', 'cc'), {}, (), 'first'), (('newsimple', u'naa', u'nbb', 'ncc'), f, (u'naa', u'nbb', 'ncc'), {}, (), 'second')] Define and try a simple directive that uses a component: >>> machine((metans, "directive"), ... namespace=ns, name="factory", ... schema=".IFactory", handler=".factory") >>> machine((ns, "factory"), factory=u".f") >>> pprint(machine.actions[-1:]) [(('factory', 1, 2), f)] Define and try a complex directive: >>> machine.begin((metans, "complexDirective"), ... namespace=ns, name="testc", ... schema=".ISimple", handler=".Complex") >>> machine((metans, "subdirective"), ... name="factory", schema=".IFactory") >>> machine.end() >>> machine.begin((ns, "testc"), None, "third", a=u'ca', c='cc') >>> machine((ns, "factory"), "fourth", factory=".f") Note that we can't call a complex method unless there is a directive for it: >>> machine((ns, "factory2"), factory=".f") Traceback (most recent call last): ... ConfigurationError: ('Invalid directive', 'factory2') >>> machine.end() >>> pprint(machine.actions) [(('simple', u'aa', u'xxx', 'cc'), f, (u'aa', u'xxx', 'cc'), {}, (), 'first'), (('newsimple', u'naa', u'nbb', 'ncc'), f, (u'naa', u'nbb', 'ncc'), {}, (), 'second'), (('factory', 1, 2), f), ('Complex.__init__', None, (), {}, (), 'third'), (('Complex.factory', 1, 2), f, (u'ca',), {}, (), 'fourth'), (('Complex', 1, 2), f, (u'xxx', 'cc'), {}, (), 'third')] Done with the package >>> machine.end() Verify that we can use a simple directive outside of the package: >>> machine((ns, "simple"), a=u"oaa", c=u"occ", b=u"obb") But we can't use the factory directive, because it's only valid inside a package directive: >>> machine((ns, "factory"), factory=u".F") Traceback (most recent call last): ... ConfigurationError: ('Invalid value for', 'factory',""" \ """ "Can't use leading dots in dotted names, no package has been set.") >>> pprint(machine.actions) [(('simple', u'aa', u'xxx', 'cc'), f, (u'aa', u'xxx', 'cc'), {}, (), 'first'), (('newsimple', u'naa', u'nbb', 'ncc'), f, (u'naa', u'nbb', 'ncc'), {}, (), 'second'), (('factory', 1, 2), f), ('Complex.__init__', None, (), {}, (), 'third'), (('Complex.factory', 1, 2), f, (u'ca',), {}, (), 'fourth'), (('Complex', 1, 2), f, (u'xxx', 'cc'), {}, (), 'third'), (('simple', u'oaa', u'obb', 'occ'), f, (u'oaa', u'obb', 'occ'))] """ #' def test_keyword_handling(): """ >>> machine = ConfigurationMachine() >>> ns = "http://www.zope.org/testing" Register some test directives: Start with a grouping directive that sets a package: >>> machine((metans, "groupingDirective"), ... name="package", namespace=ns, ... schema="zope.configuration.tests.directives.IPackaged", ... handler="zope.configuration.tests.directives.Packaged", ... ) Now we can set the package: >>> machine.begin((ns, "package"), ... package="zope.configuration.tests.directives", ... ) Which makes it easier to define the other directives: >>> machine((metans, "directive"), ... namespace=ns, name="k", ... schema=".Ik", handler=".k") >>> machine((ns, "k"), "yee ha", **{"for": u"f", "class": u"c", "x": u"x"}) >>> machine.actions [(('k', 'f'), f, ('f', 'c', 'x'), {}, (), 'yee ha')] """ def test_basepath_absolute(): """Path must always return an absolute path. >>> import os >>> class stub: ... __file__ = os.path.join('relative', 'path') >>> c = config.ConfigurationContext() >>> c.package = stub() >>> os.path.isabs(c.path('y/z')) True """ def test_basepath_uses_dunder_path(): """Determine package path using __path__ if __file__ isn't available. (i.e. namespace package installed with --single-version-externally-managed) >>> import os >>> class stub: ... __path__ = [os.path.join('relative', 'path')] >>> c = config.ConfigurationContext() >>> c.package = stub() >>> os.path.isabs(c.path('y/z')) True """ def test_trailing_dot_in_resolve(): """Dotted names are no longer allowed to end in dots >>> c = config.ConfigurationContext() >>> c.resolve('zope.') Traceback (most recent call last): ... ValueError: Trailing dots are no longer supported in dotted names >>> c.resolve(' ') Traceback (most recent call last): ... ValueError: The given name is blank """ def test_bad_dotted_last_import(): """ >>> c = config.ConfigurationContext() Import error caused by a bad last component in the dotted name. >>> c.resolve('zope.configuration.tests.nosuch') Traceback (most recent call last): ... ConfigurationError: ImportError: Module zope.configuration.tests""" \ """ has no global nosuch """ def test_bad_dotted_import(): """ >>> c = config.ConfigurationContext() Import error caused by a totally wrong dotted name. >>> c.resolve('zope.configuration.nosuch.noreally') Traceback (most recent call last): ... ConfigurationError: ImportError: Couldn't import""" \ """ zope.configuration.nosuch, No module named nosuch """ def test_bad_sub_last_import(): """ >>> c = config.ConfigurationContext() Import error caused by a bad sub import inside the referenced dotted name. Here we keep the standard traceback. >>> c.resolve('zope.configuration.tests.victim') Traceback (most recent call last): ... File "...bad.py", line 3 in ? import bad_to_the_bone ImportError: No module named bad_to_the_bone Cleanup: >>> for name in ('zope.configuration.tests.victim', ... 'zope.configuration.tests.bad'): ... if name in sys.modules: ... del sys.modules[name] """ def test_bad_sub_import(): """ >>> c = config.ConfigurationContext() Import error caused by a bad sub import inside part of the referenced dotted name. Here we keep the standard traceback. >>> c.resolve('zope.configuration.tests.victim.nosuch') Traceback (most recent call last): ... File "...bad.py", line 3 in ? import bad_to_the_bone ImportError: No module named bad_to_the_bone Cleanup: >>> for name in ('zope.configuration.tests.victim', ... 'zope.configuration.tests.bad'): ... if name in sys.modules: ... del sys.modules[name] """ def test_suite(): checker = renormalizing.RENormalizing([ (re.compile(r":"), r'exceptions.\1Error:'), ]) return unittest.TestSuite(( DocTestSuite('zope.configuration.fields'), DocTestSuite('zope.configuration.config',checker=checker), DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/0000755000175000017500000000000012214017543027633 5ustar arnauarnauzope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/foo.py0000644000175000017500000000246012214017543030772 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample module used for testing """ from zope.interface import Interface from zope import schema data = [] class S1(Interface): x = schema.BytesLine() y = schema.Int() class stuff(object): def __init__(self, args, info, basepath, package, includepath): (self.args, self.info, self.basepath, self.package, self.includepath ) = args, info, basepath, package, includepath def handler(_context, **kw): args = kw.items() args.sort() args = tuple(args) discriminator = args args = (stuff(args, _context.info, _context.basepath, _context.package, _context.includepath), ) _context.action(discriminator, data.append, args) zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/bar1.zcml0000644000175000017500000000020412214017543031343 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/baro.zcml0000644000175000017500000000014512214017543031445 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/foo.zcml.in0000644000175000017500000000067212214017543031717 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/configure.zcml0000644000175000017500000000067312214017543032511 0ustar arnauarnau ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/configure.zcml.inzope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/configure.zcml0000644000175000017500000000010112214017543032473 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/bar.zcml0000644000175000017500000000013412214017543031264 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/baz2.zcml0000644000175000017500000000023712214017543031362 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/bar21.zcml0000644000175000017500000000017012214017543031427 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/__init__.py0000644000175000017500000000007512214017543031746 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/bar2.zcml0000644000175000017500000000023112214017543031344 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/baz1.zcml0000644000175000017500000000063412214017543031362 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/baro2.zcml0000644000175000017500000000014412214017543031526 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/samplepackage/baz3.zcml0000644000175000017500000000023712214017543031363 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/simple.zcml0000644000175000017500000000160212214017543027215 0ustar arnauarnau Register a file with the file registry Blah blah blah :) Describes how to implement a simple directive Shows the ZCML directives needed to register a simple directive. Also show some usage examples, zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/directives.py0000644000175000017500000000456012214017543027556 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test directives """ from zope.interface import Interface, implements from zope.schema import Text, BytesLine from zope.configuration.config import GroupingContextDecorator from zope.configuration.interfaces import IConfigurationContext from zope.configuration.fields import GlobalObject class F(object): def __repr__(self): return 'f' def __call__(self, *a, **k): pass f = F() class ISimple(Interface): a = Text() b = Text(required=False) c = BytesLine() def simple(context, a=None, c=None, b=u"xxx"): return [(('simple', a, b, c), f, (a, b, c))] def newsimple(context, a, c, b=u"xxx"): context.action(('newsimple', a, b, c), f, (a, b, c)) class IPackaged(Interface): package = GlobalObject() class IPackagedContext(IPackaged, IConfigurationContext): pass class Packaged(GroupingContextDecorator): implements(IPackagedContext) class IFactory(Interface): factory = GlobalObject() def factory(context, factory): context.action(('factory', 1,2), factory) class Complex(object): def __init__(self, context, a, c, b=u"xxx"): self.a, self.b, self.c = a, b, c context.action("Complex.__init__") def factory(self, context, factory): return [(('Complex.factory', 1,2), factory, (self.a, ))] def factory2(self, context, factory): return [(('Complex.factory', 1,2), factory, (self.a, ))] def __call__(self): return [(('Complex', 1,2), f, (self.b, self.c))] class Ik(Interface): for_ = BytesLine() class_ = BytesLine() x = BytesLine() def k(context, for_, class_, x): context.action(('k', for_), f, (for_, class_, x)) def kkw(context, for_, class_, x, **kw): context.action(('k', for_), f, (for_, class_, x, kw)) zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/victim.py0000644000175000017500000000001312214017543026675 0ustar arnauarnauimport bad zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/sample.zcml0000644000175000017500000000044312214017543027207 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/test_simple.py0000644000175000017500000001623512214017543027747 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## r"""How to write a simple directive This module documents how to write a simple directive. A simple directive is a directive that doesn't contain other directives. It can be implemented via a fairly simple function. To implement a simple directive, you need to do 3 things: - You need to create a schema to describe the directive parameters, - You need to write a directive handler, and - You need to register the directive. In this module, we'll implement a contrived example that records information about files in a file registry. The file registry is just the list, ``file_registry``. Our registry will contain tuples with: - file path - file title - description - Information about where the file was defined Our schema is defined in IRegisterFile (see below). Our schema lists the path and title. We'll get the description and other information for free, as we'll see later. The title is not required, and may be ommmitted. The job of a configuration handler is to compute one or more configuration actions. Configuration actions are defered function calls. The handler doesn't perform the actions. It just computes actions, which may be performed later if they are not overridden by other directives. Out handler is given in the function, ``registerFile``. It takes a context, a path and a title. All directive handlers take the directive context as the first argument. A directive context, at a minimim, implements, ``zope.configuration.IConfigurationContext``. (Specialized contexts can implement more specific interfaces. We'll say more about that when we talk about grouping directives.) The title argument must have a default value, because we indicated that the title was not required in the schema. (Alternatively, we could have made the title required, but provided a default value in the schema. In the first line of function `registerFile`, we get the context information object. This object contains information about the configuration directive, such as the file and location within the file of the directive. The context information object also has a text attribute that contains the textual data contained by the configuration directive. (This is the concatenation of all of the xml text nodes directly contained by the directive.) We use this for our description in the second line of the handler. The last thing the handler does is to compute an action by calling the action method of the context. It passes the action method 3 keyword arguments: - discriminator The discriminator is used to identify the action to be performed so that duplicate actions can be detected. Two actions are duplicated, and this conflict, if they have the same discriminator values and the values are not ``None``. Conflicting actions can be resolved if one of the conflicting actions is from a configuration file that directly or indirectly includes the files containing the other conflicting actions. In function ``registerFile``, we a tuple with the string ``'RegisterFile'`` and the path to be registered. - callable The callable is the object to be called to perform the action. - args The args argument contains positinal arguments to be passed to the callable. In function ``registerFile``, we pass a tuple containing a ``FileInfo`` object. (Note that there's nothing special about the FileInfo class. It has nothing to do with creating simple directives. It's just used in this example to organize the application data.) The final step in implementing the simple directive is to register it. We do that with the zcml ``meta:directive`` directive. This is given in the file simple.zcml. Here we specify the name, namespace, schema, and handler for the directive. We also provide a documentation for the directive as text between the start and end tags. The file simple.zcml also includes some directives that use the new directive to register some files. Now let's try it all out: >>> from zope.configuration import tests >>> context = xmlconfig.file("simple.zcml", tests) Now we should see some file information in the registry: >>> from zope.configuration.tests.test_xmlconfig import clean_text_w_paths >>> from zope.configuration.tests.test_xmlconfig import clean_path >>> for i in file_registry: ... print "path:", clean_path(i.path) ... print "title:", i.title ... print "description:", '\n'.join( ... [l.rstrip() ... for l in i.description.strip().split('\n') ... if l.rstrip()]) ... print "info:" ... print clean_text_w_paths(i.info) path: tests/test_simple.py title: How to create a simple directive description: Describes how to implement a simple directive info: File "tests/simple.zcml", line 19.2-24.2 Describes how to implement a simple directive path: tests/simple.zcml title: description: Shows the ZCML directives needed to register a simple directive. Also show some usage examples, info: File "tests/simple.zcml", line 26.2-30.2 Shows the ZCML directives needed to register a simple directive. Also show some usage examples, path: tests/__init__.py title: Make this a package description: info: File "tests/simple.zcml", line 32.2-32.67 We'll clean up after ourselves: >>> del file_registry[:] """ file_registry = [] import unittest from doctest import DocTestSuite from zope import interface from zope import schema from zope.configuration import fields, xmlconfig class IRegisterFile(interface.Interface): path = fields.Path( title=u"File path", description=u"This is the path name of the file to be registered." ) title = schema.Text( title=u"Short summary of the file", description=u"This will be used in file listings", required = False ) class FileInfo(object): def __init__(self, path, title, description, info): (self.path, self.title, self.description, self.info ) = path, title, description, info def registerFile(context, path, title=u""): info = context.info description = info.text.strip() context.action(discriminator=('RegisterFile', path), callable=file_registry.append, args=(FileInfo(path, title, description, info),) ) def test_suite(): return unittest.TestSuite(( DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/__init__.py0000644000175000017500000000007512214017543027151 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/test_nested.py0000644000175000017500000002511712214017543027737 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## r"""Creating nested directives When using ZCML, you sometimes nest ZCML directives. This is typically done either to: - Avoid repetative input. Information shared among multiple directives is provided in a surrounding directive. - Put together information that is too complex or structured to express with a single set of directive parameters. Grouping directives are used to handle both of these cases. See the documentation in ``../zopeconfigure.py``. This file describes the implementation of the zope ``configure`` directive, which groups directives that use a common package or internationalization domain. The documentation in ``../zopeconfigure.py`` provides background for the documentation here. You should also have read the documentation in ``test_simple.py``, which documents how to create simple directives. This file shows you how to handle the second case above. In this case, we have grouping directives that are meant to collaborate with specific contained directives. To do this, you have the grouping directives declare a more specific (or alternate) interface to ``IConfigurationContext``. Directives designed to work with those grouping directives are registered for the new interface. Let's look at example. Suppose we wanted to be able to define schema using ZCML. We'd use a grouping directive to specify schemas and contained directives to specify fields within the schema. We'll use a schema registry to hold the defined schemas. A schema has a name, an id, some documentation, and some fields. We'll provide the name and the id as parameters. We'll define fields as subdirectives and documentation as text contained in the schema directive. The schema directive uses the schema, ``ISchemaInfo`` for it's parameters. We also define the schema, ISchema, that specifies an attribute that nested field directives will use to store the fields they define. The class, ``Schema``, provides the handler for the schema directive. (If you haven't read the documentation in ``zopeconfigure.py``, you need to do so now.) The constructor saves its arguments as attributes and initializes its ``fields`` attribute. The ``after`` method of the ``Schema`` class creates a schema and computes an action to register the schema in the schema registry. The discriminator prevents two schema directives from registering the same schema. It's important to note that when we call the ``action`` method on ``self``, rather than on ``self.context``. This is because, in a grouping directive handler, the handler instance is itself a context. When we call the ``action`` method, the method stores additional meta data associated with the context it was called on. This meta data includes an include path, used when resolving conflicting actions, and an object that contains information about the XML source used to invole the directive. If we called the action method on ``self.context``, the wrong meta data would be associated with the configuration action. The file ``schema.zcml`` contains the meta-configuration directive that defines the schema directive. To define fields, we'll create directives to define the fields. Let's start with a ``text`` field. ``ITextField`` defines the schema for text field parameters. It extends ``IFieldInfo``, which defines data common to all fields. We also define a simple handler method, textField, that takes a context and keyword arguments. (For information on writing simple directives, see ``test_simple.py``.) We've abstracted most of the logic into the function ``field``. The ``field`` function computes a field instance using the constructor, and the keyword arguments passed to it. It also uses the context information object to get the text content of the directive, which it uses for the field description. After computing the field instance, it gets the ``Schema`` instance, which is the context of the context passed to the function. The function checks to see if there is already a field with that name. If there is, it raises an error. Otherwise, it saves the field. We also define an ``IIntInfo`` schema and ``intField`` handler function to support defining integer fields. We register the ``text`` and ``int`` directives in ``schema.zcml``. These are like the simple directive definition we saw in ``test_simple.py`` with an important exception. We provide a ``usedIn`` parameter to say that these directives can *only* ne used in a ``ISchema`` context. In other words, these can only be used inside of ``schema`` directives. The ``schema.zcml`` file also contains some sample ``schema`` directives. We can execute the file: >>> from zope.configuration import tests >>> context = xmlconfig.file("schema.zcml", tests) And verify that the schema registery has the schemas we expect: >>> from pprint import PrettyPrinter >>> pprint=PrettyPrinter(width=70).pprint >>> pprint(list(schema_registry)) ['zope.configuration.tests.test_nested.I1', 'zope.configuration.tests.test_nested.I2'] >>> def sorted(x): ... r = list(x) ... r.sort() ... return r >>> i1 = schema_registry['zope.configuration.tests.test_nested.I1'] >>> sorted(i1) ['a', 'b'] >>> i1['a'].__class__.__name__ 'Text' >>> i1['a'].description.strip() u'A\n\n Blah blah' >>> i1['a'].min_length 1 >>> i1['b'].__class__.__name__ 'Int' >>> i1['b'].description.strip() u'B\n\n Not feeling very creative' >>> i1['b'].min 1 >>> i1['b'].max 10 >>> i2 = schema_registry['zope.configuration.tests.test_nested.I2'] >>> sorted(i2) ['x', 'y'] Now let's look at some error situations. For example, let's see what happens if we use a field directive outside of a schema dorective. (Note that we used the context we created above, so we don't have to redefine our directives: >>> try: ... v = xmlconfig.string( ... '', ... context) ... except xmlconfig.ZopeXMLConfigurationError, v: ... pass >>> print v File "", line 1.0 ConfigurationError: The directive """ \ """(u'http://sample.namespaces.zope.org/schema', u'text') """ \ """cannot be used in this context Let's see what happens if we declare duplicate fields: >>> try: ... v = xmlconfig.string( ... ''' ... ... ... ... ... ''', ... context) ... except xmlconfig.ZopeXMLConfigurationError, v: ... pass >>> print v File "", line 5.7-5.24 ValueError: ('Duplicate field', 'x') """ import unittest from doctest import DocTestSuite from zope import interface, schema from zope.configuration import config, xmlconfig, fields schema_registry = {} class ISchemaInfo(interface.Interface): """Parameter schema for the schema directive """ name = schema.TextLine( title=u"The schema name", description=u"This is a descriptive name for the schema." ) id = schema.Id( title=u"The unique id for the schema" ) class ISchema(interface.Interface): """Interface that distinguishes the schema directive """ fields = interface.Attribute("Dictionary of field definitions" ) class Schema(config.GroupingContextDecorator): """Handle schema directives """ interface.implements(config.IConfigurationContext, ISchema) def __init__(self, context, name, id): self.context, self.name, self.id = context, name, id self.fields = {} def after(self): schema = interface.Interface.__class__( self.name, (interface.Interface, ), self.fields ) schema.__doc__ = self.info.text.strip() self.action( discriminator=('schema', self.id), callable=schema_registry.__setitem__, args=(self.id, schema), ) class IFieldInfo(interface.Interface): name = schema.BytesLine( title=u"The field name" ) title = schema.TextLine( title=u"Title", description=u"A short summary or label", default=u"", required=False, ) required = fields.Bool( title=u"Required", description=u"Determines whether a value is required.", default=True) readonly = fields.Bool( title=u"Read Only", description=u"Can the value be modified?", required=False, default=False) class ITextInfo(IFieldInfo): min_length = schema.Int( title=u"Minimum length", description=u"Value after whitespace processing cannot have less than " u"min_length characters. If min_length is None, there is " u"no minimum.", required=False, min=0, # needs to be a positive number default=0) max_length = schema.Int( title=u"Maximum length", description=u"Value after whitespace processing cannot have greater " u"or equal than max_length characters. If max_length is " u"None, there is no maximum.", required=False, min=0, # needs to be a positive number default=None) def field(context, constructor, name, **kw): # Compute the field field = constructor(description=context.info.text.strip(), **kw) # Save it in the schema's field dictionary schema = context.context if name in schema.fields: raise ValueError("Duplicate field", name) schema.fields[name] = field def textField(context, **kw): field(context, schema.Text, **kw) class IIntInfo(IFieldInfo): min = schema.Int( title=u"Start of the range", required=False, default=None ) max = schema.Int( title=u"End of the range (excluding the value itself)", required=False, default=None ) def intField(context, **kw): field(context, schema.Int, **kw) def test_suite(): return unittest.TestSuite(( DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/excludedemo/0000755000175000017500000000000012214017543027334 5ustar arnauarnauzope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/excludedemo/configure.zcml0000644000175000017500000000012312214017543032200 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/excludedemo/__init__.py0000644000175000017500000000000212214017543031435 0ustar arnauarnau# zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/excludedemo/spam.zcml0000644000175000017500000000001612214017543031160 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/excludedemo/sub/0000755000175000017500000000000012214017543030125 5ustar arnauarnau././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/excludedemo/sub/configure.zcmlzope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/excludedemo/sub/configure.zc0000644000175000017500000000001612214017543032441 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/excludedemo/sub/__init__.py0000644000175000017500000000000212214017543032226 0ustar arnauarnau# zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/schema.zcml0000644000175000017500000000313412214017543027166 0ustar arnauarnau Define a schema Use field directives (e.g. text and int directives) to define the schema fields. Define a text field Define an integer field Sample interface I1 A Blah blah B Not feeling very creative Sample interface I2 X zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/conditions.zcml0000644000175000017500000000377612214017543030113 0ustar arnauarnau This registers a directive which creates registrations we can test. ZCML directives inside here should be included. This registration should be included. ZCML directives inside here should be ignored. This registration should be ignored. zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/test_xmlconfig.py0000644000175000017500000004775212214017543030454 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test XML configuration (ZCML) machinery. """ import unittest import os import re from doctest import DocTestSuite, DocFileSuite from zope.testing import renormalizing from zope.configuration import xmlconfig, config from zope.configuration.tests.samplepackage import foo from pprint import PrettyPrinter, pprint class FauxLocator(object): def __init__(self, file, line, column): self.file, self.line, self.column = file, line, column def getSystemId(self): return self.file def getLineNumber(self): return self.line def getColumnNumber(self): return self.column class FauxContext(object): def setInfo(self, info): self.info = info def getInfo(self): return self.info def begin(self, name, data, info): self.begin_args = name, data self.info = info def end(self): self.end_called = 1 def path(*p): return os.path.join(os.path.dirname(__file__), *p) def test_ConfigurationHandler_normal(): """ >>> context = FauxContext() >>> locator = FauxLocator('tests//sample.zcml', 1, 1) >>> handler = xmlconfig.ConfigurationHandler(context) >>> handler.setDocumentLocator(locator) >>> handler.startElementNS((u"ns", u"foo"), u"foo", ... {(u"xxx", u"splat"): u"splatv", ... (None, u"a"): u"avalue", ... (None, u"b"): u"bvalue", ... }) >>> context.info File "tests//sample.zcml", line 1.1 >>> from pprint import PrettyPrinter >>> pprint=PrettyPrinter(width=50).pprint >>> pprint(context.begin_args) ((u'ns', u'foo'), {'a': u'avalue', 'b': u'bvalue'}) >>> getattr(context, "end_called", 0) 0 >>> locator.line, locator.column = 7, 16 >>> handler.endElementNS((u"ns", u"foo"), u"foo") >>> context.info File "tests//sample.zcml", line 1.1-7.16 >>> context.end_called 1 """ def test_ConfigurationHandler_err_start(): """ >>> class FauxContext(FauxContext): ... def begin(self, *args): ... raise AttributeError("xxx") >>> context = FauxContext() >>> locator = FauxLocator('tests//sample.zcml', 1, 1) >>> handler = xmlconfig.ConfigurationHandler(context) >>> handler.setDocumentLocator(locator) >>> try: ... v = handler.startElementNS((u"ns", u"foo"), u"foo", ... {(u"xxx", u"splat"): u"splatv", ... (None, u"a"): u"avalue", ... (None, u"b"): u"bvalue", ... }) ... except xmlconfig.ZopeXMLConfigurationError, v: ... pass >>> print v File "tests//sample.zcml", line 1.1 AttributeError: xxx """ def test_ConfigurationHandler_err_end(): """ >>> class FauxContext(FauxContext): ... def end(self): ... raise AttributeError("xxx") >>> context = FauxContext() >>> locator = FauxLocator('tests//sample.zcml', 1, 1) >>> handler = xmlconfig.ConfigurationHandler(context) >>> handler.setDocumentLocator(locator) >>> handler.startElementNS((u"ns", u"foo"), u"foo", ... {(u"xxx", u"splat"): u"splatv", ... (None, u"a"): u"avalue", ... (None, u"b"): u"bvalue", ... }) >>> locator.line, locator.column = 7, 16 >>> try: ... v = handler.endElementNS((u"ns", u"foo"), u"foo") ... except xmlconfig.ZopeXMLConfigurationError, v: ... pass >>> print v File "tests//sample.zcml", line 1.1-7.16 AttributeError: xxx """ def clean_info_path(s): part1 = s[:6] part2 = s[6:s.find('"', 6)] part2 = part2[part2.rfind("tests"):] part2 = part2.replace(os.sep, '/') part3 = s[s.find('"', 6):].rstrip() return part1+part2+part3 def clean_path(s): s = s[s.rfind("tests"):] s = s.replace(os.sep, '/') return s def test_processxmlfile(): """ >>> file = open(path("samplepackage", "configure.zcml")) >>> context = config.ConfigurationMachine() >>> xmlconfig.registerCommonDirectives(context) >>> xmlconfig.processxmlfile(file, context) >>> foo.data [] >>> context.execute_actions() >>> data = foo.data.pop() >>> data.args (('x', 'blah'), ('y', 0)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/configure.zcml", line 12.2-12.29 >>> print clean_info_path(str(data.info)) File "tests/samplepackage/configure.zcml", line 12.2-12.29 >>> data.package >>> data.basepath """ def test_file(): """ >>> file_name = path("samplepackage", "configure.zcml") >>> context = xmlconfig.file(file_name) >>> data = foo.data.pop() >>> data.args (('x', 'blah'), ('y', 0)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/configure.zcml", line 12.2-12.29 >>> print clean_info_path(str(data.info)) File "tests/samplepackage/configure.zcml", line 12.2-12.29 >>> data.package >>> print clean_path(data.basepath) tests/samplepackage """ def test_include_by_package(): """ >>> context = config.ConfigurationMachine() >>> xmlconfig.registerCommonDirectives(context) >>> import zope.configuration.tests.samplepackage as package >>> xmlconfig.include(context, 'configure.zcml', package) >>> context.execute_actions() >>> data = foo.data.pop() >>> data.args (('x', 'blah'), ('y', 0)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/configure.zcml", line 12.2-12.29 >>> print clean_info_path(str(data.info)) File "tests/samplepackage/configure.zcml", line 12.2-12.29 >>> data.package is package 1 >>> data.basepath[-13:] 'samplepackage' >>> [clean_path(p) for p in data.includepath] ['tests/samplepackage/configure.zcml'] """ # Not any more ## Including the same file more than once produces an error: ## >>> try: ## ... xmlconfig.include(context, 'configure.zcml', package) ## ... except xmlconfig.ConfigurationError, e: ## ... 'OK' ## ... ## 'OK' def test_include_by_file(): """ >>> context = config.ConfigurationMachine() >>> xmlconfig.registerCommonDirectives(context) >>> here = os.path.dirname(__file__) >>> path = os.path.join(here, "samplepackage", "foo.zcml") >>> xmlconfig.include(context, path) >>> context.execute_actions() >>> data = foo.data.pop() >>> data.args (('x', 'foo'), ('y', 2)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/foo.zcml.in", line 12.2-12.28 >>> print clean_info_path(str(data.info)) File "tests/samplepackage/foo.zcml.in", line 12.2-12.28 >>> data.package >>> data.basepath[-13:] 'samplepackage' >>> [clean_path(p) for p in data.includepath] ['tests/samplepackage/foo.zcml.in'] """ def test_include_by_file_glob(): """ >>> context = config.ConfigurationMachine() >>> xmlconfig.registerCommonDirectives(context) >>> here = os.path.dirname(__file__) >>> path = os.path.join(here, "samplepackage/baz*.zcml") >>> xmlconfig.include(context, files=path) >>> context.execute_actions() >>> data = foo.data.pop() >>> data.args (('x', 'foo'), ('y', 3)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/baz3.zcml", line 5.2-5.28 >>> print clean_info_path(str(data.info)) File "tests/samplepackage/baz3.zcml", line 5.2-5.28 >>> data.package >>> data.basepath[-13:] 'samplepackage' >>> [clean_path(p) for p in data.includepath] ['tests/samplepackage/baz3.zcml'] >>> data = foo.data.pop() >>> data.args (('x', 'foo'), ('y', 2)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/baz2.zcml", line 5.2-5.28 >>> print clean_info_path(str(data.info)) File "tests/samplepackage/baz2.zcml", line 5.2-5.28 >>> data.package >>> data.basepath[-13:] 'samplepackage' >>> [clean_path(p) for p in data.includepath] ['tests/samplepackage/baz2.zcml'] """ def clean_actions(actions): return [ {'discriminator': discriminator, 'info': clean_info_path(`info`), 'includepath': [clean_path(p) for p in includepath], } for (discriminator, callable, args, kw, includepath, info, order) in [config.expand_action(*action) for action in actions] ] def clean_text_w_paths(error): r = [] for line in unicode(error).split("\n"): line = line.rstrip() if not line: continue l = line.find('File "') if l >= 0: line = line[:l] + clean_info_path(line[l:]) r.append(line) return '\n'.join(r) def test_includeOverrides(): """ When we have conflicting directives, we can resolve them if one of the conflicting directives was from a file that included all of the others. The problem with this is that this requires that all of the overriding directives be in one file, typically the top-most including file. This isn't very convenient. Fortunately, we can overcome this with the includeOverrides directive. Let's look at an example to see how this works. Look at the file bar.zcml. It includes bar1.zcml and bar2.zcml. bar2.zcml includes configure.zcml and has a foo directive. bar2.zcml includes bar21.zcml. bar2.zcml has a foo directive that conflicts with one in bar1.zcml. bar2.zcml also overrides a foo directive in bar21.zcml. bar21.zcml has a foo directive that conflicts with one in in configure.zcml. Whew! Let's see what happens when we try to process bar.zcml. >>> context = config.ConfigurationMachine() >>> xmlconfig.registerCommonDirectives(context) >>> here = os.path.dirname(__file__) >>> path = os.path.join(here, "samplepackage", "bar.zcml") >>> xmlconfig.include(context, path) So far so good, let's look at the configuration actions: >>> pprint=PrettyPrinter(width=70).pprint >>> pprint(clean_actions(context.actions)) [{'discriminator': (('x', 'blah'), ('y', 0)), 'includepath': ['tests/samplepackage/bar.zcml', 'tests/samplepackage/bar1.zcml', 'tests/samplepackage/configure.zcml'], 'info': 'File "tests/samplepackage/configure.zcml", line 12.2-12.29'}, {'discriminator': (('x', 'blah'), ('y', 1)), 'includepath': ['tests/samplepackage/bar.zcml', 'tests/samplepackage/bar1.zcml'], 'info': 'File "tests/samplepackage/bar1.zcml", line 5.2-5.24'}, {'discriminator': (('x', 'blah'), ('y', 0)), 'includepath': ['tests/samplepackage/bar.zcml', 'tests/samplepackage/bar2.zcml', 'tests/samplepackage/bar21.zcml'], 'info': 'File "tests/samplepackage/bar21.zcml", line 3.2-3.24'}, {'discriminator': (('x', 'blah'), ('y', 2)), 'includepath': ['tests/samplepackage/bar.zcml', 'tests/samplepackage/bar2.zcml', 'tests/samplepackage/bar21.zcml'], 'info': 'File "tests/samplepackage/bar21.zcml", line 4.2-4.24'}, {'discriminator': (('x', 'blah'), ('y', 2)), 'includepath': ['tests/samplepackage/bar.zcml', 'tests/samplepackage/bar2.zcml'], 'info': 'File "tests/samplepackage/bar2.zcml", line 5.2-5.24'}, {'discriminator': (('x', 'blah'), ('y', 1)), 'includepath': ['tests/samplepackage/bar.zcml', 'tests/samplepackage/bar2.zcml'], 'info': 'File "tests/samplepackage/bar2.zcml", line 6.2-6.24'}] As you can see, there are a number of conflicts (actions with the same discriminator). Some of these can be resolved, but many can't, as we'll find if we try to execuse the actions: >>> try: ... v = context.execute_actions() ... except config.ConfigurationConflictError, v: ... pass >>> print clean_text_w_paths(str(v)) Conflicting configuration actions For: (('x', 'blah'), ('y', 0)) File "tests/samplepackage/configure.zcml", line 12.2-12.29 File "tests/samplepackage/bar21.zcml", line 3.2-3.24 For: (('x', 'blah'), ('y', 1)) File "tests/samplepackage/bar1.zcml", line 5.2-5.24 File "tests/samplepackage/bar2.zcml", line 6.2-6.24 Note that the conflicts for (('x', 'blah'), ('y', 2)) aren't included in the error because they could be resolved. Let's try this again using includeOverrides. We'll include baro.zcml which includes bar2.zcml as overrides. >>> context = config.ConfigurationMachine() >>> xmlconfig.registerCommonDirectives(context) >>> path = os.path.join(here, "samplepackage", "baro.zcml") >>> xmlconfig.include(context, path) Now, if we look at the actions: >>> pprint(clean_actions(context.actions)) [{'discriminator': (('x', 'blah'), ('y', 0)), 'includepath': ['tests/samplepackage/baro.zcml', 'tests/samplepackage/bar1.zcml', 'tests/samplepackage/configure.zcml'], 'info': 'File "tests/samplepackage/configure.zcml", line 12.2-12.29'}, {'discriminator': (('x', 'blah'), ('y', 1)), 'includepath': ['tests/samplepackage/baro.zcml', 'tests/samplepackage/bar1.zcml'], 'info': 'File "tests/samplepackage/bar1.zcml", line 5.2-5.24'}, {'discriminator': (('x', 'blah'), ('y', 0)), 'includepath': ['tests/samplepackage/baro.zcml'], 'info': 'File "tests/samplepackage/bar21.zcml", line 3.2-3.24'}, {'discriminator': (('x', 'blah'), ('y', 2)), 'includepath': ['tests/samplepackage/baro.zcml'], 'info': 'File "tests/samplepackage/bar2.zcml", line 5.2-5.24'}, {'discriminator': (('x', 'blah'), ('y', 1)), 'includepath': ['tests/samplepackage/baro.zcml'], 'info': 'File "tests/samplepackage/bar2.zcml", line 6.2-6.24'}] We see that: - The conflicting actions between bar2.zcml and bar21.zcml have been resolved, and - The remaining (after conflict resolution) actions from bar2.zcml and bar21.zcml have the includepath that they would have if they were defined in baro.zcml and this override the actions from bar1.zcml and configure.zcml. We can now execute the actions without problem, since the remaining conflicts are resolvable: >>> context.execute_actions() We should now have three entries in foo.data: >>> len(foo.data) 3 >>> data = foo.data.pop(0) >>> data.args (('x', 'blah'), ('y', 0)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/bar21.zcml", line 3.2-3.24 >>> data = foo.data.pop(0) >>> data.args (('x', 'blah'), ('y', 2)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/bar2.zcml", line 5.2-5.24 >>> data = foo.data.pop(0) >>> data.args (('x', 'blah'), ('y', 1)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/bar2.zcml", line 6.2-6.24 We expect the exact same results when using includeOverrides with the ``files`` argument instead of the ``file`` argument. The baro2.zcml file uses the former: >>> context = config.ConfigurationMachine() >>> xmlconfig.registerCommonDirectives(context) >>> path = os.path.join(here, "samplepackage", "baro2.zcml") >>> xmlconfig.include(context, path) Actions look like above: >>> pprint(clean_actions(context.actions)) [{'discriminator': (('x', 'blah'), ('y', 0)), 'includepath': ['tests/samplepackage/baro2.zcml', 'tests/samplepackage/bar1.zcml', 'tests/samplepackage/configure.zcml'], 'info': 'File "tests/samplepackage/configure.zcml", line 12.2-12.29'}, {'discriminator': (('x', 'blah'), ('y', 1)), 'includepath': ['tests/samplepackage/baro2.zcml', 'tests/samplepackage/bar1.zcml'], 'info': 'File "tests/samplepackage/bar1.zcml", line 5.2-5.24'}, {'discriminator': (('x', 'blah'), ('y', 0)), 'includepath': ['tests/samplepackage/baro2.zcml'], 'info': 'File "tests/samplepackage/bar21.zcml", line 3.2-3.24'}, {'discriminator': (('x', 'blah'), ('y', 2)), 'includepath': ['tests/samplepackage/baro2.zcml'], 'info': 'File "tests/samplepackage/bar2.zcml", line 5.2-5.24'}, {'discriminator': (('x', 'blah'), ('y', 1)), 'includepath': ['tests/samplepackage/baro2.zcml'], 'info': 'File "tests/samplepackage/bar2.zcml", line 6.2-6.24'}] >>> context.execute_actions() >>> len(foo.data) 3 >>> del foo.data[:] """ def test_XMLConfig(): """Test processing a configuration file. We'll use the same example from test_includeOverrides: >>> here = os.path.dirname(__file__) >>> path = os.path.join(here, "samplepackage", "baro.zcml") First, process the configuration file: >>> x = xmlconfig.XMLConfig(path) Second, call the resulting object to process the actions: >>> x() And verify the data as above: >>> len(foo.data) 3 >>> data = foo.data.pop(0) >>> data.args (('x', 'blah'), ('y', 0)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/bar21.zcml", line 3.2-3.24 >>> data = foo.data.pop(0) >>> data.args (('x', 'blah'), ('y', 2)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/bar2.zcml", line 5.2-5.24 >>> data = foo.data.pop(0) >>> data.args (('x', 'blah'), ('y', 1)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/bar2.zcml", line 6.2-6.24 Finally, clean up. >>> from zope.testing.cleanup import CleanUp >>> CleanUp().cleanUp() """ def test_XMLConfig_w_module(): """Test processing a configuration file for a module. We'll use the same example from test_includeOverrides: >>> import zope.configuration.tests.samplepackage as module First, process the configuration file: >>> x = xmlconfig.XMLConfig("baro.zcml", module) Second, call the resulting object to process the actions: >>> x() And verify the data as above: >>> len(foo.data) 3 >>> data = foo.data.pop(0) >>> data.args (('x', 'blah'), ('y', 0)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/bar21.zcml", line 3.2-3.24 >>> data = foo.data.pop(0) >>> data.args (('x', 'blah'), ('y', 2)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/bar2.zcml", line 5.2-5.24 >>> data = foo.data.pop(0) >>> data.args (('x', 'blah'), ('y', 1)) >>> print clean_info_path(`data.info`) File "tests/samplepackage/bar2.zcml", line 6.2-6.24 Finally, clean up. >>> from zope.testing.cleanup import CleanUp >>> CleanUp().cleanUp() """ def test_suite(): return unittest.TestSuite(( DocTestSuite('zope.configuration.xmlconfig'), DocTestSuite(), DocFileSuite('../exclude.txt', checker=renormalizing.RENormalizing([ (re.compile('include [^\n]+zope.configuration[\S+]'), 'include /zope.configuration\2'), (re.compile(r'\\'), '/'), ])) )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/tests/test_docutils.py0000644000175000017500000000160112214017543030273 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Doc Tests for for zope.configuration.docutils """ import unittest from doctest import DocTestSuite def test_suite(): return unittest.TestSuite(( DocTestSuite('zope.configuration.docutils'), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/stxdocs.py0000644000175000017500000001317712214017543025746 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """STX Configuration Documentation Renderer Usage: stxdocs.py [options] Options: -h / --help Print this message and exit. -f Specifies the root ZCML meta directives file, relative to the current location. All included files will be considered as well -o Specifies a directory, relative to the current location in which the documentation is stored. Note that this tool will create sub-directories with files in them. """ import sys, os, getopt import zope.configuration from zope.schema import getFieldsInOrder from zope.configuration import config, xmlconfig from zope.configuration.docutils import wrap, makeDocStructures def usage(code, msg=''): # Python 2.1 required print >> sys.stderr, __doc__ if msg: print >> sys.stderr, msg sys.exit(code) def _directiveDocs(name, schema, handler, info, indent_offset=0): """Generate the documentation for one directive.""" # Write out the name of the directive text = ' '*indent_offset text += '%s\n\n' %name # Specify the file and location it has been declared if isinstance(info, xmlconfig.ParserInfo): # We do not want to specify the whole path; starting at the 'zope' # package is enough. base_dir = os.path.dirname(os.path.dirname( zope.configuration.__file__))[:-4] file = info.file.replace(base_dir, '') info_text = 'File %s, lines %i - %i.' %(file, info.line, info.eline) text += wrap(info_text, 78, indent_offset+2) elif isinstance(info, (str, unicode)) and info: text += wrap(info, 78, indent_offset+2) # Insert Handler information if handler is not None: handler_path = handler.__module__ + '.' + handler.__name__ text += wrap('Handler: %s' %handler_path, 78, indent_offset+2) # Use the schema documentation string as main documentation text for the # directive. text += wrap(schema.getDoc(), 78, indent_offset+2) text += ' '*indent_offset + ' Attributes\n\n' # Create directive attribute documentation for name, field in getFieldsInOrder(schema): name = name.strip('_') if field.required: opt = 'required' else: opt = 'optional, default=%s' %repr(field.default) text += ' '*indent_offset text += ' %s -- %s (%s)\n\n' %(name, field.__class__.__name__, opt) text += wrap(field.title, 78, indent_offset+6) text += wrap(field.description, 78, indent_offset+6) return text def _subDirectiveDocs(subdirs, namespace, name): """Appends a list of sub-directives and their full specification.""" if subdirs.has_key((namespace, name)): text = '\n Subdirectives\n\n' sub_dirs = [] # Simply walk through all sub-directives here. subs = subdirs[(namespace, name)] for sd_ns, sd_name, sd_schema, sd_handler, sd_info in subs: sub_dirs.append(_directiveDocs( sd_name, sd_schema, sd_handler, sd_info, 4)) return text + '\n\n'.join(sub_dirs) return '' def makedocs(target_dir, zcml_file): """Generate the documentation tree. All we need for this is a starting ZCML file and a directory in which to put the documentation. """ context = xmlconfig.file(zcml_file, execute=False) namespaces, subdirs = makeDocStructures(context) for namespace, directives in namespaces.items(): ns_dir = os.path.join(target_dir, namespace.split('/')[-1]) # Create a directory for the namespace, if necessary if not os.path.exists(ns_dir): os.mkdir(ns_dir) # Create a file for each directive for name, (schema, handler, info) in directives.items(): dir_file = os.path.join(ns_dir, name+'.stx') text = _directiveDocs(name, schema, handler, info) text += _subDirectiveDocs(subdirs, namespace, name) open(dir_file, 'w').write(text) def _makeabs(path): """Make an absolute path from the possibly relative path.""" if not path == os.path.abspath(path): cwd = os.getcwd() # This is for symlinks. if os.environ.has_key('PWD'): cwd = os.environ['PWD'] path = os.path.normpath(os.path.join(cwd, path)) return path def main(argv=sys.argv): try: opts, args = getopt.getopt( sys.argv[1:], 'h:f:o:', ['help']) except getopt.error, msg: usage(1, msg) zcml_file = None output_dir = None for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt in ('-o', ): output_dir = arg elif opt in ('-f', ): zcml_file = _makeabs(arg) if not os.path.exists(zcml_file): usage(1, 'The specified zcml file does not exist.') if zcml_file is None or output_dir is None: usage(0, "Both, the '-f' and '-o' option are required") # Generate the docs makedocs(output_dir, zcml_file) if __name__ == '__main__': main() zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/exclude.txt0000644000175000017500000000461012214017543026067 0ustar arnauarnauFiltering and Inhibiting Configuration ====================================== The ``exclude`` standard directive is provided for inhibiting unwanted configuration. It is used to exclude processing of configuration files. It is useful when including a configuration that includes some other configuration that you don't want. It must be used BEFORE including the files to be excluded. First, let's look at an example. The zope.configuration.tests.excludedemo package has a ZCML configuration that includes some other configuration files. We'll set a log handler so we can see what's going on: >>> import logging, sys >>> logger = logging.getLogger('config') >>> oldlevel = logger.level >>> logger.setLevel(logging.DEBUG) >>> handler = logging.StreamHandler(sys.stdout) >>> logger.addHandler(handler) Now, we'll include the zope.configuration.tests.excludedemo config: >>> from zope.configuration import xmlconfig >>> _ = xmlconfig.string('') include /zope.configuration/src/zope/configuration/tests/excludedemo/configure.zcml include /zope.configuration/src/zope/configuration/tests/excludedemo/sub/configure.zcml include /zope.configuration/src/zope/configuration/tests/excludedemo/spam.zcml Each run of the configuration machinery runs with fresh state, so rerunning gives the same thing: >>> _ = xmlconfig.string('') include /zope.configuration/src/zope/configuration/tests/excludedemo/configure.zcml include /zope.configuration/src/zope/configuration/tests/excludedemo/sub/configure.zcml include /zope.configuration/src/zope/configuration/tests/excludedemo/spam.zcml Now, we'll use the exclude directive to exclude the two files included by the configuration file in zope.configuration.tests.excludedemo: >>> _ = xmlconfig.string( ... ''' ... ... ... ... ... ... ''') include /zope.configuration/src/zope/configuration/tests/excludedemo/configure.zcml .. cleanup >>> logger.setLevel(oldlevel) >>> logger.removeHandler(handler) zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/xmlconfig.py0000644000175000017500000005471712214017543026252 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Support for the XML configuration file format Note, for a detailed description of the way that conflicting configuration actions are resolved, see the detailed example in test_includeOverrides in tests/test_xmlconfig.py """ __docformat__ = 'restructuredtext' import errno import os import sys import logging import zope.configuration.config as config from glob import glob from xml.sax import make_parser from xml.sax.xmlreader import InputSource from xml.sax.handler import ContentHandler, feature_namespaces from xml.sax import SAXParseException from zope import schema from zope.configuration.exceptions import ConfigurationError from zope.configuration.zopeconfigure import IZopeConfigure, ZopeConfigure from zope.interface import Interface logger = logging.getLogger("config") ZCML_NAMESPACE = "http://namespaces.zope.org/zcml" ZCML_CONDITION = (ZCML_NAMESPACE, u"condition") class ZopeXMLConfigurationError(ConfigurationError): """Zope XML Configuration error These errors are wrappers for other errors. The include configuration info and the wrapped error type and value: >>> v = ZopeXMLConfigurationError("blah", AttributeError, "xxx") >>> print v 'blah' AttributeError: xxx """ def __init__(self, info, etype, evalue): self.info, self.etype, self.evalue = info, etype, evalue def __str__(self): # Only use the repr of the info. This is because we expect to # get a parse info and we only want the location information. return "%s\n %s: %s" % ( `self.info`, self.etype.__name__, self.evalue) class ZopeSAXParseException(ConfigurationError): """Sax Parser errors, reformatted in an emacs friendly way >>> v = ZopeSAXParseException("foo.xml:12:3:Not well formed") >>> print v File "foo.xml", line 12.3, Not well formed """ def __init__(self, v): self._v = v def __str__(self): v = self._v s = tuple(str(v).split(':')) if len(s) == 4: return 'File "%s", line %s.%s, %s' % s else: return str(v) class ParserInfo(object): """Information about a directive based on parser data This includes the directive location, as well as text data contained in the directive. >>> info = ParserInfo('tests//sample.zcml', 1, 0) >>> info File "tests//sample.zcml", line 1.0 >>> print info File "tests//sample.zcml", line 1.0 >>> info.characters("blah\\n") >>> info.characters("blah") >>> info.text u'blah\\nblah' >>> info.end(7, 0) >>> info File "tests//sample.zcml", line 1.0-7.0 >>> print info File "tests//sample.zcml", line 1.0-7.0 """ text = u'' def __init__(self, file, line, column): self.file, self.line, self.column = file, line, column self.eline, self.ecolumn = line, column def end(self, line, column): self.eline, self.ecolumn = line, column def __repr__(self): if (self.line, self.column) == (self.eline, self.ecolumn): return 'File "%s", line %s.%s' % ( self.file, self.line, self.column) return 'File "%s", line %s.%s-%s.%s' % ( self.file, self.line, self.column, self.eline, self.ecolumn) def __str__(self): if (self.line, self.column) == (self.eline, self.ecolumn): return 'File "%s", line %s.%s' % ( self.file, self.line, self.column) file = self.file if file == 'tests//sample.zcml': # special case for testing file = os.path.join(os.path.dirname(__file__), 'tests', 'sample.zcml') try: f = open(file) except IOError: src = " Could not read source." else: lines = f.readlines()[self.line-1:self.eline] ecolumn = self.ecolumn if lines[-1][ecolumn:ecolumn+2] == '', ecolumn) if l >= 0: lines[-1] = lines[-1][:l+1] else: lines[-1] = lines[-1][:ecolumn+1] column = self.column if lines[0][:column].strip(): # Remove text before start if it's noy whitespace lines[0] = lines[0][self.column:] try: src = u''.join([u" "+l for l in lines]) except UnicodeDecodeError: # XXX: # I hope so most internation zcml will use UTF-8 as encoding # otherwise this code must be made more clever src = u''.join([u" "+l.decode('utf-8') for l in lines]) # unicode won't be printable, at least on my console src = src.encode('ascii','replace') return "%s\n%s" % (`self`, src) def characters(self, characters): self.text += characters class ConfigurationHandler(ContentHandler): """Interface to the xml parser Translate parser events into calls into the configuration system. """ def __init__(self, context, testing=0): self.context = context self.testing = testing self.ignore_depth = 0 def setDocumentLocator(self, locator): self.locator = locator def characters(self, text): self.context.getInfo().characters(text) def startElementNS(self, name, qname, attrs): if self.ignore_depth: self.ignore_depth += 1 return data = {} for (ns, aname), value in attrs.items(): # NB: even though on CPython, 'ns' will be ``None`` always, # do not change the below to "if ns is None" because Jython's # sax parser generates attrs that have empty strings for # the namepace instead of ``None``. if not ns: aname = str(aname) data[aname] = value if (ns, aname) == ZCML_CONDITION: # need to process the expression to determine if we # use this element and it's descendents use = self.evaluateCondition(value) if not use: self.ignore_depth = 1 return info = ParserInfo( self.locator.getSystemId(), self.locator.getLineNumber(), self.locator.getColumnNumber(), ) try: self.context.begin(name, data, info) except (KeyboardInterrupt, SystemExit): raise except: if self.testing: raise raise ZopeXMLConfigurationError(info, sys.exc_info()[0], sys.exc_info()[1]), None, sys.exc_info()[2] self.context.setInfo(info) def evaluateCondition(self, expression): """Evaluate a ZCML condition. `expression` is a string of the form "verb arguments". Currently the supported verbs are 'have', 'not-have', 'installed' and 'not-installed'. The 'have' verb takes one argument: the name of a feature. >>> from zope.configuration.config import ConfigurationContext >>> context = ConfigurationContext() >>> context.provideFeature('apidoc') >>> c = ConfigurationHandler(context, testing=True) >>> c.evaluateCondition("have apidoc") True >>> c.evaluateCondition("not-have apidoc") False >>> c.evaluateCondition("have onlinehelp") False >>> c.evaluateCondition("not-have onlinehelp") True Ill-formed expressions raise an error >>> c.evaluateCondition("want apidoc") Traceback (most recent call last): ... ValueError: Invalid ZCML condition: 'want apidoc' >>> c.evaluateCondition("have x y") Traceback (most recent call last): ... ValueError: Only one feature allowed: 'have x y' >>> c.evaluateCondition("have") Traceback (most recent call last): ... ValueError: Feature name missing: 'have' The 'installed' verb takes one argument: the dotted name of a pacakge. If the pacakge is found, in other words, can be imported, then the condition will return true. >>> from zope.configuration.config import ConfigurationContext >>> context = ConfigurationContext() >>> c = ConfigurationHandler(context, testing=True) >>> c.evaluateCondition('installed zope.interface') True >>> c.evaluateCondition('not-installed zope.interface') False >>> c.evaluateCondition('installed zope.foo') False >>> c.evaluateCondition('not-installed zope.foo') True Ill-formed expressions raise an error >>> c.evaluateCondition("installed foo bar") Traceback (most recent call last): ... ValueError: Only one package allowed: 'installed foo bar' >>> c.evaluateCondition("installed") Traceback (most recent call last): ... ValueError: Package name missing: 'installed' """ arguments = expression.split(None) verb = arguments.pop(0) if verb in ('have', 'not-have'): if not arguments: raise ValueError("Feature name missing: %r" % expression) if len(arguments) > 1: raise ValueError("Only one feature allowed: %r" % expression) if verb == 'have': return self.context.hasFeature(arguments[0]) elif verb == 'not-have': return not self.context.hasFeature(arguments[0]) elif verb in ('installed', 'not-installed'): if not arguments: raise ValueError("Package name missing: %r" % expression) if len(arguments) > 1: raise ValueError("Only one package allowed: %r" % expression) try: __import__(arguments[0]) installed = True except ImportError: installed = False if verb == 'installed': return installed elif verb == 'not-installed': return not installed else: raise ValueError("Invalid ZCML condition: %r" % expression) def endElementNS(self, name, qname): # If ignore_depth is set, this element will be ignored, even # if this this decrements ignore_depth to 0. if self.ignore_depth: self.ignore_depth -= 1 return info = self.context.getInfo() info.end( self.locator.getLineNumber(), self.locator.getColumnNumber(), ) try: self.context.end() except (KeyboardInterrupt, SystemExit): raise except: if self.testing: raise raise ZopeXMLConfigurationError(info, sys.exc_info()[0], sys.exc_info()[1]), None, sys.exc_info()[2] def processxmlfile(file, context, testing=False): """Process a configuration file See examples in tests/text_xmlconfig.py """ src = InputSource(getattr(file, 'name', '')) src.setByteStream(file) parser = make_parser() parser.setContentHandler(ConfigurationHandler(context, testing=testing)) parser.setFeature(feature_namespaces, True) try: parser.parse(src) except SAXParseException: raise ZopeSAXParseException(sys.exc_info()[1]), None, sys.exc_info()[2] def openInOrPlain(filename): """Open a file, falling back to filename.in. If the requested file does not exist and filename.in does, fall back to filename.in. If opening the original filename fails for any other reason, allow the failure to propogate. For example, the tests/samplepackage dirextory has files: configure.zcml configure.zcml.in foo.zcml.in If we open configure.zcml, we'll get that file: >>> here = os.path.dirname(__file__) >>> path = os.path.join(here, 'tests', 'samplepackage', 'configure.zcml') >>> f = openInOrPlain(path) >>> f.name[-14:] 'configure.zcml' But if we open foo.zcml, we'll get foo.zcml.in, since there isn't a foo.zcml: >>> path = os.path.join(here, 'tests', 'samplepackage', 'foo.zcml') >>> f = openInOrPlain(path) >>> f.name[-11:] 'foo.zcml.in' Make sure other IOErrors are re-raised. We need to do this in a try-except block because different errors are raised on Windows and on Linux. >>> try: ... f = openInOrPlain('.') ... except IOError: ... print "passed" ... else: ... print "failed" ... passed """ try: fp = open(filename) except IOError, (code, msg): if code == errno.ENOENT: fn = filename + ".in" if os.path.exists(fn): fp = open(fn) else: raise else: raise return fp class IInclude(Interface): """The ``include``, ``includeOverrides`` and ``exclude`` directives These directives allows you to include or preserve including of another ZCML file in the configuration. This enables you to write configuration files in each package and then link them together. """ file = schema.BytesLine( title=u"Configuration file name", description=u"The name of a configuration file to be included/excluded, " u"relative to the directive containing the " u"including configuration file.", required=False, ) files = schema.BytesLine( title=u"Configuration file name pattern", description=u""" The names of multiple configuration files to be included/excluded, expressed as a file-name pattern, relative to the directive containing the including or excluding configuration file. The pattern can include: - ``*`` matches 0 or more characters - ``?`` matches a single character - ``[]`` matches any character in seq - ``[!]`` matches any character not in seq The file names are included in sorted order, where sorting is without regard to case. """, required=False, ) package = config.fields.GlobalObject( title=u"Include or exclude package", description=u""" Include or exclude the named file (or configure.zcml) from the directory of this package. """, required=False, ) def include(_context, file=None, package=None, files=None): """Include a zcml file See examples in tests/text_xmlconfig.py """ if files: if file: raise ValueError("Must specify only one of file or files") elif not file: file = 'configure.zcml' # BBB 2006/12/19 -- to be removed after 12 months # This is a backward-compatibility support for old site.conf if package and (package.__name__ == 'zope.app'): try: import zope.app.zcmlfiles except ImportError: pass # maybe this is an old zope without zope.app.zcmlfiles else: dirpath, filename = os.path.split(file) # be careful, because zope.app is a namespace package # we can't assume that zcmlfiles is a subdirectory of the # zope.app package dirpath = os.path.dirname(zope.app.zcmlfiles.__file__) file = os.path.join(dirpath, filename) import warnings warnings.warn('In configuration file: %s ' 'replace: ' 'with: ' 'This will go away in Zope 3.6.' % os.path.abspath(file), DeprecationWarning, 2) # This is a tad tricky. We want to behave as a grouping directive. context = config.GroupingContextDecorator(_context) if package is not None: context.package = package context.basepath = None if files: paths = glob(context.path(files)) paths = zip([path.lower() for path in paths], paths) paths.sort() paths = [path for (l, path) in paths] else: paths = [context.path(file)] for path in paths: if context.processFile(path): f = openInOrPlain(path) logger.debug("include %s" % f.name) context.basepath = os.path.dirname(path) context.includepath = _context.includepath + (f.name, ) _context.stack.append(config.GroupingStackItem(context)) processxmlfile(f, context) f.close() assert _context.stack[-1].context is context _context.stack.pop() def exclude(_context, file=None, package=None, files=None): """Exclude a zcml file This directive should be used before any ZML that includes configuration you want to exclude. """ if files: if file: raise ValueError("Must specify only one of file or files") elif not file: file = 'configure.zcml' context = config.GroupingContextDecorator(_context) if package is not None: context.package = package context.basepath = None if files: paths = glob(context.path(files)) paths = zip([path.lower() for path in paths], paths) paths.sort() paths = [path for (l, path) in paths] else: paths = [context.path(file)] for path in paths: # processFile returns a boolean indicating if the file has been # processed or not, it *also* marks the file as having been processed, # here the side effect is used to keep the given file from being # processed in the future context.processFile(path) def includeOverrides(_context, file=None, package=None, files=None): """Include zcml file containing overrides The actions in the included file are added to the context as if they were in the including file directly. See the detailed example in test_includeOverrides in tests/text_xmlconfig.py """ # We need to remember how many actions we had before nactions = len(_context.actions) # We'll give the new actions this include path includepath = _context.includepath # Now we'll include the file. We'll munge the actions after include(_context, file, package, files) # Now we'll grab the new actions, resolve conflicts, # and munge the includepath: newactions = [] for action in config.resolveConflicts(_context.actions[nactions:]): (discriminator, callable, args, kw, oldincludepath, info, order ) = config.expand_action(*action) newactions.append( (discriminator, callable, args, kw, includepath, info, order) ) # and replace the new actions with the munched new actions: _context.actions[nactions:] = newactions def registerCommonDirectives(context): # We have to use the direct definition functions to define # a directive for all namespaces. config.defineSimpleDirective( context, "include", IInclude, include, namespace="*") config.defineSimpleDirective( context, "exclude", IInclude, exclude, namespace="*") config.defineSimpleDirective( context, "includeOverrides", IInclude, includeOverrides, namespace="*") config.defineGroupingDirective( context, name="configure", namespace="*", schema=IZopeConfigure, handler=ZopeConfigure, ) def file(name, package=None, context=None, execute=True): """Execute a zcml file """ if context is None: context = config.ConfigurationMachine() registerCommonDirectives(context) context.package = package include(context, name, package) if execute: context.execute_actions() return context def string(s, context=None, name="", execute=True): """Execute a zcml string """ from StringIO import StringIO if context is None: context = config.ConfigurationMachine() registerCommonDirectives(context) f = StringIO(s) f.name = name processxmlfile(f, context) if execute: context.execute_actions() return context ############################################################################## # Backward compatability, mainly for tests _context = None def _clearContext(): global _context _context = config.ConfigurationMachine() registerCommonDirectives(_context) def _getContext(): global _context if _context is None: _clearContext() try: from zope.testing.cleanup import addCleanUp except ImportError: pass else: addCleanUp(_clearContext) del addCleanUp return _context class XMLConfig(object): """Provide high-level handling of configuration files. See examples in tests/text_xmlconfig.py """ def __init__(self, file_name, module=None): context = _getContext() include(context, file_name, module) self.context = context def __call__(self): self.context.execute_actions() def xmlconfig(file, testing=False): context = _getContext() processxmlfile(file, context, testing=testing) context.execute_actions(testing=testing) def testxmlconfig(file, context=None): """xmlconfig that doesn't raise configuration errors This is useful for testing, as it doesn't mask exception types. """ context = _getContext() processxmlfile(file, context, testing=True) context.execute_actions(testing=True) zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/docutils.py0000644000175000017500000000534312214017543026101 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Helper Utility to wrap a text to a set width of characters """ __docformat__ = 'restructuredtext' import re para_sep = re.compile('\n{2,}') whitespace=re.compile('[ \t\n\r]+') def wrap(text, width=78, indent=0): """Makes sure that we keep a line length of a certain width. Examples: >>> print wrap('foo bar')[:-2] foo bar >>> print wrap('foo bar', indent=2)[:-2] foo bar >>> print wrap('foo bar, more foo bar', 10)[:-2] foo bar, more foo bar >>> print wrap('foo bar, more foo bar', 10, 2)[:-2] foo bar, more foo bar """ paras = para_sep.split(text.strip()) new_paras = [] for par in paras: words= filter(None, whitespace.split(par)) lines = [] line = [] length = indent for word in words: if length + len(word) <= width: line.append(word) length += len(word) + 1 else: lines.append(' '*indent + ' '.join(line)) line = [word] length = len(word) + 1 + indent lines.append(' '*indent + ' '.join(line)) new_paras.append('\n'.join(lines)) return '\n\n'.join(new_paras) + '\n\n' def makeDocStructures(context): """Creates two structures that provide a friendly format for documentation. 'namespaces' is a dictionary that maps namespaces to a directives dictionary with the key being the name of the directive and the value is a tuple: (schema, handler, info). 'subdirs' maps a (namespace, name) pair to a list of subdirectives that have the form (namespace, name, schema, info). """ namespaces = {} subdirs = {} registry = context._docRegistry for (namespace, name), schema, usedIn, handler, info, parent in registry: if not parent: ns_entry = namespaces.setdefault(namespace, {}) ns_entry[name] = (schema, handler, info) else: sd_entry = subdirs.setdefault((parent.namespace, parent.name), []) sd_entry.append((namespace, name, schema, handler, info)) return namespaces, subdirs zope2.13-2.13.21/source/zope.configuration/src/zope/configuration/exceptions.py0000644000175000017500000000137412214017543026434 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Standard configuration errors """ class ConfigurationError(Exception): """There was an error in a configuration """ zope2.13-2.13.21/source/zope.configuration/src/zope.configuration.egg-info/0000755000175000017500000000000012214017543025365 5ustar arnauarnauzope2.13-2.13.21/source/zope.configuration/src/zope.configuration.egg-info/PKG-INFO0000644000175000017500000002022412214017543026462 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.configuration Version: 3.7.4 Summary: Zope Configuration Markup Language (ZCML) Home-page: http://pypi.python.org/pypi/zope.configuration Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: zope.configuration ================== Overview -------- The zope configuration system provides an extensible system for supporting various kinds of configurations. It is based on the idea of configuration directives. Users of the configuration system provide configuration directives in some language that express configuration choices. The intent is that the language be pluggable. An XML language is provided by default. Detailed Documentation ---------------------- ========================== Zope configuration system ========================== The zope configuration system provides an extensible system for supporting variouse kinds of configurations. It is based on the idea of configuration directives. Users of the configuration system provide configuration directives in some language that express configuration choices. The intent is that the language be pluggable. An XML language is provided by default. Configuration is performed in three stages. In the first stage, directives are processed to compute configuration actions. Configuration actions consist of: - A discriminator - A callable - Positional arguments - Keyword arguments The actions are essentially delayed function calls. Two or more actions conflict if they have the same discriminator. The configuration system has rules for resolving conflicts. If conflicts cannot be resolved, an error will result. Conflict resolution typically discards all but one of the conflicting actions, so that the remaining action of the originally-conflicting actions no longer conflicts. Non-conflicting actions are executed in the order that they were created by passing the positional and non-positional arguments to the action callable. The system is extensible. There is a meta-configuration language for defining configuration directives. A directive is defined by providing meta data about the directive and handler code to process the directive. There are four kinds of directives: - Simple directives compute configuration actions. Their handlers are typically functions that take a context and zero or more keyword arguments and return a sequence of configuration actions. To learn how to create simple directives, see `tests/test_simple.py`. - Grouping directives collect information to be used by nested directives. They are called with a context object which they adapt to some interface that extends IConfigurationContext. To learn how to create grouping directives, look at the documentation in zopeconfigure.py, which provides the implementation of the zope `configure` directive. Other directives can be nested in grouping directives. To learn how to implement nested directives, look at the documentation in `tests/test_nested.py`. - Complex directives are directives that have subdirectives. Subdirectives have handlers that are simply methods of complex directives. Complex diretives are handled by factories, typically classes, that create objects that have methods for handling subdirectives. These objects also have __call__ methods that are called when processing of subdirectives is finished. Complex directives only exist to support old directive handlers. They will probably be deprecated in the future. - Subdirectives are nested in complex directives. They are like simple directives except that they hane handlers that are complex directive methods. Subdirectives, like complex directives only exist to support old directive handlers. They will probably be deprecated in the future. ======= Changes ======= 3.7.4 (2011-04-03) ------------------ - Test fixes for Windows. 3.7.3 (2011-03-11) ------------------ - Correctly locate packages with a __path__ attribute but no __file__ attribute (such as namespace packages installed with setup.py install --single-version-externally-managed). - Allow "info" and "includepath" to be passed optionally to context.action. 3.7.2 (2010-04-30) ------------------ - Prefer the standard libraries doctest module over zope.testing.doctest. 3.7.1 (2010-01-05) ------------------ - Jython support: use ``__builtin__`` module import rather than assuming ``__builtins__`` is available. - Jython support: deal with the fact that the Jython SAX parser returns attribute sets that have an empty string indicating no namespace instead of ``None``. - Allow ``setup.py test`` to run at least a subset of the tests that would be run when using the zope testrunner: ``setup.py test`` runs 53 tests, while ``bin/test`` runs 156. 3.7.0 (2009-12-22) ------------------ - Adjust testing output to newer zope.schema. - Prefer zope.testing.doctest over doctestunit. 3.6.0 (2009-04-01) ------------------ - Removed dependency of `zope.deprecation` package. - Don't suppress deprecation warnings any more in 'zope.configuration' package level. This makes it more likely other packages will generate deprecation warnings now, which will allow us to remove more outdated ones. - Don't fail when zope.testing is not installed. - Added missing ``processFile`` method to ``IConfigurationContext``. It is already implemented in the mix-in class, ``zope.configuration.config.ConfigurationContext``, and used by implementations of ``include`` and ``exclude`` directives. 3.5.0 (2009-02-26) ------------------ - Added the ``exclude`` directive to standard directives. It was previously available via ``zc.configuration`` package and now it's merged into ``zope.configuration``. - Changed package's mailing list address to zope-dev at zope.org, change "cheeseshop" to "pypi" in the package's url. 3.4.1 (2008-12-11) ------------------ - Use built-in 'set' type, rather than importin the 'sets' module, which is deprecated in Python 2.6. - Added support to bootstrap on Jython. 3.4.0 (2007-10-02) ------------------ - Initial release as a standalone package. Before 3.4.0 ------------ This package was part of the Zope 3 distribution and did not have its own CHANGES.txt. For earlier changes please refer to either our subversion log or the CHANGES.txt of earlier Zope 3 releases. Keywords: zope configuration zcml Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.configuration/src/zope.configuration.egg-info/dependency_links.txt0000644000175000017500000000000112214017543031433 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/src/zope.configuration.egg-info/requires.txt0000644000175000017500000000011512214017543027762 0ustar arnauarnauzope.i18nmessageid zope.interface zope.schema setuptools [test] zope.testingzope2.13-2.13.21/source/zope.configuration/src/zope.configuration.egg-info/namespace_packages.txt0000644000175000017500000000000512214017543031713 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.configuration/src/zope.configuration.egg-info/top_level.txt0000644000175000017500000000000512214017543030112 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.configuration/src/zope.configuration.egg-info/SOURCES.txt0000644000175000017500000000470212214017543027254 0ustar arnauarnau.bzrignore CHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.configuration.egg-info/PKG-INFO src/zope.configuration.egg-info/SOURCES.txt src/zope.configuration.egg-info/dependency_links.txt src/zope.configuration.egg-info/namespace_packages.txt src/zope.configuration.egg-info/not-zip-safe src/zope.configuration.egg-info/requires.txt src/zope.configuration.egg-info/top_level.txt src/zope/configuration/README.txt src/zope/configuration/__init__.py src/zope/configuration/config.py src/zope/configuration/docutils.py src/zope/configuration/exceptions.py src/zope/configuration/exclude.txt src/zope/configuration/fields.py src/zope/configuration/interfaces.py src/zope/configuration/name.py src/zope/configuration/stxdocs.py src/zope/configuration/xmlconfig.py src/zope/configuration/zopeconfigure.py src/zope/configuration/tests/__init__.py src/zope/configuration/tests/bad.py src/zope/configuration/tests/conditions.zcml src/zope/configuration/tests/directives.py src/zope/configuration/tests/sample.zcml src/zope/configuration/tests/schema.zcml src/zope/configuration/tests/simple.zcml src/zope/configuration/tests/test_conditions.py src/zope/configuration/tests/test_config.py src/zope/configuration/tests/test_docutils.py src/zope/configuration/tests/test_nested.py src/zope/configuration/tests/test_simple.py src/zope/configuration/tests/test_xmlconfig.py src/zope/configuration/tests/victim.py src/zope/configuration/tests/excludedemo/__init__.py src/zope/configuration/tests/excludedemo/configure.zcml src/zope/configuration/tests/excludedemo/spam.zcml src/zope/configuration/tests/excludedemo/sub/__init__.py src/zope/configuration/tests/excludedemo/sub/configure.zcml src/zope/configuration/tests/samplepackage/__init__.py src/zope/configuration/tests/samplepackage/bar.zcml src/zope/configuration/tests/samplepackage/bar1.zcml src/zope/configuration/tests/samplepackage/bar2.zcml src/zope/configuration/tests/samplepackage/bar21.zcml src/zope/configuration/tests/samplepackage/baro.zcml src/zope/configuration/tests/samplepackage/baro2.zcml src/zope/configuration/tests/samplepackage/baz1.zcml src/zope/configuration/tests/samplepackage/baz2.zcml src/zope/configuration/tests/samplepackage/baz3.zcml src/zope/configuration/tests/samplepackage/configure.zcml src/zope/configuration/tests/samplepackage/configure.zcml.in src/zope/configuration/tests/samplepackage/foo.py src/zope/configuration/tests/samplepackage/foo.zcml.inzope2.13-2.13.21/source/zope.configuration/src/zope.configuration.egg-info/not-zip-safe0000644000175000017500000000000112214017543027613 0ustar arnauarnau zope2.13-2.13.21/source/zope.configuration/.bzrignore0000644000175000017500000000010012214017543021252 0ustar arnauarnau./.installed.cfg ./bin ./develop-eggs ./eggs ./parts *.egg-info zope2.13-2.13.21/source/ExtensionClass/0000755000175000017500000000000012214017441016375 5ustar arnauarnauzope2.13-2.13.21/source/ExtensionClass/setup.py0000644000175000017500000000403212214017441020106 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Setup for the ExtensionClass distribution """ import os from setuptools import setup, find_packages, Extension README = open('README.txt').read() CHANGES = open('CHANGES.txt').read() setup(name='ExtensionClass', version = '2.13.2', url='http://pypi.python.org/pypi/ExtensionClass', license='ZPL 2.1', description='Metaclass for subclassable extension types', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description='\n\n'.join([README, CHANGES]), packages=find_packages('src'), package_dir={'': 'src'}, ext_modules=[Extension("ExtensionClass._ExtensionClass", [os.path.join('src', 'ExtensionClass', '_ExtensionClass.c')], include_dirs=['src']), Extension("ComputedAttribute._ComputedAttribute", [os.path.join('src', 'ComputedAttribute', '_ComputedAttribute.c')], include_dirs=['src']), Extension("MethodObject._MethodObject", [os.path.join('src', 'MethodObject', '_MethodObject.c')], include_dirs=['src']), ], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/ExtensionClass/PKG-INFO0000644000175000017500000000627612214017441017505 0ustar arnauarnauMetadata-Version: 1.0 Name: ExtensionClass Version: 2.13.2 Summary: Metaclass for subclassable extension types Home-page: http://pypi.python.org/pypi/ExtensionClass Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ExtensionClass and ExtensionClass-related packages ================================================== ExtensionClass -------------- This package provides a metaclass that allows classes implemented in extension modules to be subclassed in Python. Unless you need ExtensionClasses for legacy applications (e.g. Zope 2), you probably want to use Python's new-style classes (available since Python 2.2). ComputedAttribute ----------------- This package provides a way to attach attributes to an ``ExtensionClass`` or instance that are computed by calling a callable. This works very much like ``property`` known from new-style classes, except that a ``ComputedAttribute`` can also be attached to an instance and that it honours ExtensionClass semantics (which is useful for retaining Acquisition wrappers, for example). MethodObject ------------ This package lets you attach additional "methods" to ExtensionClasses. These "methods" are actually implemented by subclassing the ``MethodObject.Method`` class and implementing the ``__call__`` method there. Instances of those classes will be bound to the instances they're attached to and will receive that instance object as a first parameter (after ``self``). Changelog ========= 2.13.2 (2010-06-16) ------------------- - LP #587760: Handle tp_basicsize correctly. 2.13.1 (2010-04-03) ------------------- - Removed undeclared testing dependency on zope.testing. - Removed cruft in ``pickle/pickle.c`` related to removed ``__getnewargs__``. 2.13.0 (2010-02-22) ------------------- - Avoid defining ``__getnewargs__`` as not to defeat the ZODB persistent reference optimization. Refs https://bugs.launchpad.net/zope2/+bug/143657. In order to take advantage of this optimization, you need to re-save your objects. 2.12.0 (2010-02-14) ------------------- - Removed old build artifacts and some metadata cleanup. - Added support for method cache in ExtensionClass. Patch contributed by Yoshinori K. Okuji. See https://bugs.launchpad.net/zope2/+bug/486182. 2.11.3 (2009-08-02) ------------------- - Further 64-bit fixes (Python 2.4 compatibility). 2.11.2 (2009-08-02) ------------------- - Fixed 64-bit compatibility issues for Python 2.5.x / 2.6.x. See http://www.python.org/dev/peps/pep-0353/ for details. 2.11.1 (2009-02-19) ------------------- - Initial egg release. Platform: UNKNOWN zope2.13-2.13.21/source/ExtensionClass/pip-egg-info/0000755000175000017500000000000012214017441020656 5ustar arnauarnauzope2.13-2.13.21/source/ExtensionClass/pip-egg-info/ExtensionClass.egg-info/0000755000175000017500000000000012214017441025312 5ustar arnauarnauzope2.13-2.13.21/source/ExtensionClass/pip-egg-info/ExtensionClass.egg-info/PKG-INFO0000644000175000017500000000627612214017441026422 0ustar arnauarnauMetadata-Version: 1.0 Name: ExtensionClass Version: 2.13.2 Summary: Metaclass for subclassable extension types Home-page: http://pypi.python.org/pypi/ExtensionClass Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ExtensionClass and ExtensionClass-related packages ================================================== ExtensionClass -------------- This package provides a metaclass that allows classes implemented in extension modules to be subclassed in Python. Unless you need ExtensionClasses for legacy applications (e.g. Zope 2), you probably want to use Python's new-style classes (available since Python 2.2). ComputedAttribute ----------------- This package provides a way to attach attributes to an ``ExtensionClass`` or instance that are computed by calling a callable. This works very much like ``property`` known from new-style classes, except that a ``ComputedAttribute`` can also be attached to an instance and that it honours ExtensionClass semantics (which is useful for retaining Acquisition wrappers, for example). MethodObject ------------ This package lets you attach additional "methods" to ExtensionClasses. These "methods" are actually implemented by subclassing the ``MethodObject.Method`` class and implementing the ``__call__`` method there. Instances of those classes will be bound to the instances they're attached to and will receive that instance object as a first parameter (after ``self``). Changelog ========= 2.13.2 (2010-06-16) ------------------- - LP #587760: Handle tp_basicsize correctly. 2.13.1 (2010-04-03) ------------------- - Removed undeclared testing dependency on zope.testing. - Removed cruft in ``pickle/pickle.c`` related to removed ``__getnewargs__``. 2.13.0 (2010-02-22) ------------------- - Avoid defining ``__getnewargs__`` as not to defeat the ZODB persistent reference optimization. Refs https://bugs.launchpad.net/zope2/+bug/143657. In order to take advantage of this optimization, you need to re-save your objects. 2.12.0 (2010-02-14) ------------------- - Removed old build artifacts and some metadata cleanup. - Added support for method cache in ExtensionClass. Patch contributed by Yoshinori K. Okuji. See https://bugs.launchpad.net/zope2/+bug/486182. 2.11.3 (2009-08-02) ------------------- - Further 64-bit fixes (Python 2.4 compatibility). 2.11.2 (2009-08-02) ------------------- - Fixed 64-bit compatibility issues for Python 2.5.x / 2.6.x. See http://www.python.org/dev/peps/pep-0353/ for details. 2.11.1 (2009-02-19) ------------------- - Initial egg release. Platform: UNKNOWN zope2.13-2.13.21/source/ExtensionClass/pip-egg-info/ExtensionClass.egg-info/dependency_links.txt0000644000175000017500000000000112214017441031360 0ustar arnauarnau zope2.13-2.13.21/source/ExtensionClass/pip-egg-info/ExtensionClass.egg-info/top_level.txt0000644000175000017500000000005612214017441030045 0ustar arnauarnauExtensionClass ComputedAttribute MethodObject zope2.13-2.13.21/source/ExtensionClass/pip-egg-info/ExtensionClass.egg-info/SOURCES.txt0000644000175000017500000000106612214017441027201 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/ExtensionClass.egg-info/PKG-INFO pip-egg-info/ExtensionClass.egg-info/SOURCES.txt pip-egg-info/ExtensionClass.egg-info/dependency_links.txt pip-egg-info/ExtensionClass.egg-info/not-zip-safe pip-egg-info/ExtensionClass.egg-info/top_level.txt src/ComputedAttribute/_ComputedAttribute.c src/ComputedAttribute/__init__.py src/ComputedAttribute/tests.py src/ExtensionClass/_ExtensionClass.c src/ExtensionClass/__init__.py src/ExtensionClass/tests.py src/MethodObject/_MethodObject.c src/MethodObject/__init__.py src/MethodObject/tests.pyzope2.13-2.13.21/source/ExtensionClass/pip-egg-info/ExtensionClass.egg-info/not-zip-safe0000644000175000017500000000000112214017441027540 0ustar arnauarnau zope2.13-2.13.21/source/ExtensionClass/LICENSE.txt0000644000175000017500000000402612214017441020222 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/ExtensionClass/README.txt0000644000175000017500000000230012214017441020066 0ustar arnauarnauExtensionClass and ExtensionClass-related packages ================================================== ExtensionClass -------------- This package provides a metaclass that allows classes implemented in extension modules to be subclassed in Python. Unless you need ExtensionClasses for legacy applications (e.g. Zope 2), you probably want to use Python's new-style classes (available since Python 2.2). ComputedAttribute ----------------- This package provides a way to attach attributes to an ``ExtensionClass`` or instance that are computed by calling a callable. This works very much like ``property`` known from new-style classes, except that a ``ComputedAttribute`` can also be attached to an instance and that it honours ExtensionClass semantics (which is useful for retaining Acquisition wrappers, for example). MethodObject ------------ This package lets you attach additional "methods" to ExtensionClasses. These "methods" are actually implemented by subclassing the ``MethodObject.Method`` class and implementing the ``__call__`` method there. Instances of those classes will be bound to the instances they're attached to and will receive that instance object as a first parameter (after ``self``). zope2.13-2.13.21/source/ExtensionClass/setup.cfg0000644000175000017500000000007312214017441020216 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/ExtensionClass/COPYRIGHT.txt0000644000175000017500000000004012214017441020500 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/ExtensionClass/buildout.cfg0000644000175000017500000000025312214017441020705 0ustar arnauarnau[buildout] develop = . parts = test versions = versions [test] recipe = zc.recipe.testrunner eggs = ExtensionClass [versions] zc.buildout = 1.4.3 zc.recipe.egg = 1.2.2 zope2.13-2.13.21/source/ExtensionClass/bootstrap.py0000644000175000017500000000733612214017441020775 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id$ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/ExtensionClass/CHANGES.txt0000644000175000017500000000215512214017441020211 0ustar arnauarnauChangelog ========= 2.13.2 (2010-06-16) ------------------- - LP #587760: Handle tp_basicsize correctly. 2.13.1 (2010-04-03) ------------------- - Removed undeclared testing dependency on zope.testing. - Removed cruft in ``pickle/pickle.c`` related to removed ``__getnewargs__``. 2.13.0 (2010-02-22) ------------------- - Avoid defining ``__getnewargs__`` as not to defeat the ZODB persistent reference optimization. Refs https://bugs.launchpad.net/zope2/+bug/143657. In order to take advantage of this optimization, you need to re-save your objects. 2.12.0 (2010-02-14) ------------------- - Removed old build artifacts and some metadata cleanup. - Added support for method cache in ExtensionClass. Patch contributed by Yoshinori K. Okuji. See https://bugs.launchpad.net/zope2/+bug/486182. 2.11.3 (2009-08-02) ------------------- - Further 64-bit fixes (Python 2.4 compatibility). 2.11.2 (2009-08-02) ------------------- - Fixed 64-bit compatibility issues for Python 2.5.x / 2.6.x. See http://www.python.org/dev/peps/pep-0353/ for details. 2.11.1 (2009-02-19) ------------------- - Initial egg release. zope2.13-2.13.21/source/ExtensionClass/src/0000755000175000017500000000000012214017441017164 5ustar arnauarnauzope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass/0000755000175000017500000000000012214017441022126 5ustar arnauarnauzope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass/pickle/0000755000175000017500000000000012214017441023375 5ustar arnauarnauzope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass/pickle/pickle.c0000644000175000017500000002154512214017441025017 0ustar arnauarnau/* Copyright (c) 2003 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. */ /* Reusable pickle support code This is "includeware", meant to be used through a C include */ /* It's a dang shame we can't inherit __get/setstate__ from object :( */ static PyObject *str__slotnames__, *copy_reg_slotnames, *__newobj__; static PyObject *str__getnewargs__, *str__getstate__; #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) typedef int Py_ssize_t; #define PY_SSIZE_T_MAX INT_MAX #define PY_SSIZE_T_MIN INT_MIN #endif static int pickle_setup(void) { PyObject *copy_reg; int r = -1; #define DEFINE_STRING(S) \ if(! (str ## S = PyString_FromString(# S))) return -1 DEFINE_STRING(__slotnames__); DEFINE_STRING(__getnewargs__); DEFINE_STRING(__getstate__); #undef DEFINE_STRING copy_reg = PyImport_ImportModule("copy_reg"); if (copy_reg == NULL) return -1; copy_reg_slotnames = PyObject_GetAttrString(copy_reg, "_slotnames"); if (copy_reg_slotnames == NULL) goto end; __newobj__ = PyObject_GetAttrString(copy_reg, "__newobj__"); if (__newobj__ == NULL) goto end; r = 0; end: Py_DECREF(copy_reg); return r; } static PyObject * pickle_slotnames(PyTypeObject *cls) { PyObject *slotnames; slotnames = PyDict_GetItem(cls->tp_dict, str__slotnames__); if (slotnames != NULL) { Py_INCREF(slotnames); return slotnames; } slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames, (PyObject*)cls, NULL); if (slotnames != NULL && slotnames != Py_None && ! PyList_Check(slotnames)) { PyErr_SetString(PyExc_TypeError, "copy_reg._slotnames didn't return a list or None"); Py_DECREF(slotnames); slotnames = NULL; } return slotnames; } static PyObject * pickle_copy_dict(PyObject *state) { PyObject *copy, *key, *value; char *ckey; Py_ssize_t pos = 0; Py_ssize_t nr; copy = PyDict_New(); if (copy == NULL) return NULL; if (state == NULL) return copy; while ((nr = PyDict_Next(state, &pos, &key, &value))) { if (nr < 0) goto err; if (key && PyString_Check(key)) { ckey = PyString_AS_STRING(key); if (*ckey == '_' && (ckey[1] == 'v' || ckey[1] == 'p') && ckey[2] == '_') /* skip volatile and persistent */ continue; } if (key != NULL && value != NULL && (PyObject_SetItem(copy, key, value) < 0) ) goto err; } return copy; err: Py_DECREF(copy); return NULL; } static char pickle___getstate__doc[] = "Get the object serialization state\n" "\n" "If the object has no assigned slots and has no instance dictionary, then \n" "None is returned.\n" "\n" "If the object has no assigned slots and has an instance dictionary, then \n" "the a copy of the instance dictionary is returned. The copy has any items \n" "with names starting with '_v_' or '_p_' ommitted.\n" "\n" "If the object has assigned slots, then a two-element tuple is returned. \n" "The first element is either None or a copy of the instance dictionary, \n" "as described above. The second element is a dictionary with items \n" "for each of the assigned slots.\n" ; static PyObject * pickle___getstate__(PyObject *self) { PyObject *slotnames=NULL, *slots=NULL, *state=NULL; PyObject **dictp; int n=0; slotnames = pickle_slotnames(self->ob_type); if (slotnames == NULL) return NULL; dictp = _PyObject_GetDictPtr(self); if (dictp) state = pickle_copy_dict(*dictp); else { state = Py_None; Py_INCREF(state); } if (slotnames != Py_None) { int i; slots = PyDict_New(); if (slots == NULL) goto end; for (i = 0; i < PyList_GET_SIZE(slotnames); i++) { PyObject *name, *value; char *cname; name = PyList_GET_ITEM(slotnames, i); if (PyString_Check(name)) { cname = PyString_AS_STRING(name); if (*cname == '_' && (cname[1] == 'v' || cname[1] == 'p') && cname[2] == '_') /* skip volatile and persistent */ continue; } value = PyObject_GetAttr(self, name); if (value == NULL) PyErr_Clear(); else { int err = PyDict_SetItem(slots, name, value); Py_DECREF(value); if (err) goto end; n++; } } } if (n) state = Py_BuildValue("(NO)", state, slots); end: Py_XDECREF(slotnames); Py_XDECREF(slots); return state; } static int pickle_setattrs_from_dict(PyObject *self, PyObject *dict) { PyObject *key, *value; Py_ssize_t pos = 0; if (! PyDict_Check(dict)) { PyErr_SetString(PyExc_TypeError, "Expected dictionary"); return -1; } while (PyDict_Next(dict, &pos, &key, &value)) { if (key != NULL && value != NULL && (PyObject_SetAttr(self, key, value) < 0) ) return -1; } return 0; } static char pickle___setstate__doc[] = "Set the object serialization state\n" "\n" "The state should be in one of 3 forms:\n" "\n" "- None\n" "\n" " Ignored\n" "\n" "- A dictionary\n" "\n" " In this case, the object's instance dictionary will be cleared and \n" " updated with the new state.\n" "\n" "- A two-tuple with a string as the first element. \n" "\n" " In this case, the method named by the string in the first element will be\n" " called with the second element.\n" "\n" " This form supports migration of data formats.\n" "\n" "- A two-tuple with None or a Dictionary as the first element and\n" " with a dictionary as the second element.\n" "\n" " If the first element is not None, then the object's instance dictionary \n" " will be cleared and updated with the value.\n" "\n" " The items in the second element will be assigned as attributes.\n" ; static PyObject * pickle___setstate__(PyObject *self, PyObject *state) { PyObject *slots=NULL; if (PyTuple_Check(state)) { if (! PyArg_ParseTuple(state, "OO", &state, &slots)) return NULL; } if (state != Py_None) { PyObject **dict; dict = _PyObject_GetDictPtr(self); if (dict) { if (*dict == NULL) { *dict = PyDict_New(); if (*dict == NULL) return NULL; } } if (*dict != NULL) { PyDict_Clear(*dict); if (PyDict_Update(*dict, state) < 0) return NULL; } else if (pickle_setattrs_from_dict(self, state) < 0) return NULL; } if (slots != NULL && pickle_setattrs_from_dict(self, slots) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static char pickle___reduce__doc[] = "Reduce an object to contituent parts for serialization\n" ; static PyObject * pickle___reduce__(PyObject *self) { PyObject *args=NULL, *bargs=0, *state=NULL; int l, i; /* we no longer require '__getnewargs__' to be defined but use it if it is */ PyObject *getnewargs=NULL; getnewargs = PyObject_GetAttr(self, str__getnewargs__); if (getnewargs) bargs = PyEval_CallObject(getnewargs, (PyObject *)NULL); else { PyErr_Clear(); bargs = PyTuple_New(0); } l = PyTuple_Size(bargs); if (l < 0) goto end; args = PyTuple_New(l+1); if (args == NULL) goto end; Py_INCREF(self->ob_type); PyTuple_SET_ITEM(args, 0, (PyObject*)(self->ob_type)); for (i = 0; i < l; i++) { Py_INCREF(PyTuple_GET_ITEM(bargs, i)); PyTuple_SET_ITEM(args, i+1, PyTuple_GET_ITEM(bargs, i)); } state = PyObject_CallMethodObjArgs(self, str__getstate__, NULL); if (state == NULL) goto end; state = Py_BuildValue("(OON)", __newobj__, args, state); end: Py_XDECREF(bargs); Py_XDECREF(args); Py_XDECREF(getnewargs); return state; } #define PICKLE_GETSTATE_DEF \ {"__getstate__", (PyCFunction)pickle___getstate__, METH_NOARGS, \ pickle___getstate__doc}, #define PICKLE_SETSTATE_DEF \ {"__setstate__", (PyCFunction)pickle___setstate__, METH_O, \ pickle___setstate__doc}, #define PICKLE_GETNEWARGS_DEF #define PICKLE_REDUCE_DEF \ {"__reduce__", (PyCFunction)pickle___reduce__, METH_NOARGS, \ pickle___reduce__doc}, #define PICKLE_METHODS PICKLE_GETSTATE_DEF PICKLE_SETSTATE_DEF \ PICKLE_GETNEWARGS_DEF PICKLE_REDUCE_DEF zope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass/_ExtensionClass.c0000644000175000017500000006133012214017441025376 0ustar arnauarnau/* Copyright (c) 2003 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. */ static char _extensionclass_module_documentation[] = "ExtensionClass\n" "\n" "$Id: _ExtensionClass.c 113545 2010-06-16 14:17:54Z hannosch $\n" ; #include "ExtensionClass/ExtensionClass.h" #define EC PyTypeObject static PyObject *str__of__, *str__get__, *str__class_init__, *str__init__; static PyObject *str__bases__, *str__mro__, *str__new__; #define OBJECT(O) ((PyObject *)(O)) #define TYPE(O) ((PyTypeObject *)(O)) static PyTypeObject ExtensionClassType; static PyTypeObject BaseType; static PyObject * of_get(PyObject *self, PyObject *inst, PyObject *cls) { /* Descriptor slot function that calls __of__ */ if (inst && PyExtensionInstance_Check(inst)) return PyObject_CallMethodObjArgs(self, str__of__, inst, NULL); Py_INCREF(self); return self; } PyObject * Base_getattro(PyObject *obj, PyObject *name) { /* This is a modified copy of PyObject_GenericGetAttr. See the change note below. */ PyTypeObject *tp = obj->ob_type; PyObject *descr = NULL; PyObject *res = NULL; descrgetfunc f; long dictoffset; PyObject **dictptr; if (!PyString_Check(name)){ #ifdef Py_USING_UNICODE /* The Unicode to string conversion is done here because the existing tp_setattro slots expect a string object as name and we wouldn't want to break those. */ if (PyUnicode_Check(name)) { name = PyUnicode_AsEncodedString(name, NULL, NULL); if (name == NULL) return NULL; } else #endif { PyErr_SetString(PyExc_TypeError, "attribute name must be string"); return NULL; } } else Py_INCREF(name); if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) goto done; } #if !defined(Py_TPFLAGS_HAVE_VERSION_TAG) /* Inline _PyType_Lookup */ /* this is not quite _PyType_Lookup anymore */ { int i, n; PyObject *mro, *base, *dict; /* Look in tp_dict of types in MRO */ mro = tp->tp_mro; assert(mro != NULL); assert(PyTuple_Check(mro)); n = PyTuple_GET_SIZE(mro); for (i = 0; i < n; i++) { base = PyTuple_GET_ITEM(mro, i); if (PyClass_Check(base)) dict = ((PyClassObject *)base)->cl_dict; else { assert(PyType_Check(base)); dict = ((PyTypeObject *)base)->tp_dict; } assert(dict && PyDict_Check(dict)); descr = PyDict_GetItem(dict, name); if (descr != NULL) break; } } #else descr = _PyType_Lookup(tp, name); #endif Py_XINCREF(descr); f = NULL; if (descr != NULL && PyType_HasFeature(descr->ob_type, Py_TPFLAGS_HAVE_CLASS)) { f = descr->ob_type->tp_descr_get; if (f != NULL && PyDescr_IsData(descr)) { res = f(descr, obj, (PyObject *)obj->ob_type); Py_DECREF(descr); goto done; } } /* Inline _PyObject_GetDictPtr */ dictoffset = tp->tp_dictoffset; if (dictoffset != 0) { PyObject *dict; if (dictoffset < 0) { int tsize; size_t size; tsize = ((PyVarObject *)obj)->ob_size; if (tsize < 0) tsize = -tsize; size = _PyObject_VAR_SIZE(tp, tsize); dictoffset += (long)size; assert(dictoffset > 0); assert(dictoffset % SIZEOF_VOID_P == 0); } dictptr = (PyObject **) ((char *)obj + dictoffset); dict = *dictptr; if (dict != NULL) { Py_INCREF(dict); res = PyDict_GetItem(dict, name); if (res != NULL) { Py_INCREF(res); Py_XDECREF(descr); Py_DECREF(dict); /* CHANGED! If the tp_descr_get of res is of_get, then call it. */ if (PyObject_TypeCheck(res->ob_type, &ExtensionClassType) && res->ob_type->tp_descr_get != NULL) { PyObject *tres; tres = res->ob_type->tp_descr_get( res, obj, OBJECT(obj->ob_type)); Py_DECREF(res); res = tres; } goto done; } Py_DECREF(dict); } } if (f != NULL) { res = f(descr, obj, (PyObject *)obj->ob_type); Py_DECREF(descr); goto done; } if (descr != NULL) { res = descr; /* descr was already increfed above */ goto done; } /* CHANGED: Just use the name. Don't format. */ PyErr_SetObject(PyExc_AttributeError, name); done: Py_DECREF(name); return res; } #include "pickle/pickle.c" static struct PyMethodDef Base_methods[] = { PICKLE_METHODS {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static EC BaseType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "Base", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* tp_getattro */ (getattrofunc)Base_getattro, 0, 0, (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif ), "Standard ExtensionClass base type", 0, 0, 0, 0, 0, 0, Base_methods, }; static EC NoInstanceDictionaryBaseType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "NoInstanceDictionaryBase", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif ), "Base types for subclasses without instance dictionaries", }; static PyObject * EC_new(PyTypeObject *self, PyObject *args, PyObject *kw) { PyObject *name, *bases=NULL, *dict=NULL; PyObject *new_bases=NULL, *new_args, *result; int have_base = 0, i; if (kw && PyObject_IsTrue(kw)) { PyErr_SetString(PyExc_TypeError, "Keyword arguments are not supported"); return NULL; } if (!PyArg_ParseTuple(args, "O|O!O!", &name, &PyTuple_Type, &bases, &PyDict_Type, &dict)) return NULL; /* Make sure Base is in bases */ if (bases) { for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { if (PyObject_TypeCheck(PyTuple_GET_ITEM(bases, i), &ExtensionClassType)) { have_base = 1; break; } } if (! have_base) { new_bases = PyTuple_New(PyTuple_GET_SIZE(bases) + 1); if (new_bases == NULL) return NULL; for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { Py_XINCREF(PyTuple_GET_ITEM(bases, i)); PyTuple_SET_ITEM(new_bases, i, PyTuple_GET_ITEM(bases, i)); } Py_INCREF(OBJECT(&BaseType)); PyTuple_SET_ITEM(new_bases, PyTuple_GET_SIZE(bases), OBJECT(&BaseType)); } } else { new_bases = Py_BuildValue("(O)", &BaseType); if (new_bases == NULL) return NULL; } if (new_bases) { if (dict) new_args = Py_BuildValue("OOO", name, new_bases, dict); else new_args = Py_BuildValue("OO", name, new_bases); Py_DECREF(new_bases); if (new_args == NULL) return NULL; result = PyType_Type.tp_new(self, new_args, kw); Py_DECREF(new_args); } else { result = PyType_Type.tp_new(self, args, kw); /* We didn't have to add Base, so maybe NoInstanceDictionaryBase is in the bases. We need to check if it was. If it was, we need to suppress instance dictionary support. */ for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { if ( PyObject_TypeCheck(PyTuple_GET_ITEM(bases, i), &ExtensionClassType) && PyType_IsSubtype(TYPE(PyTuple_GET_ITEM(bases, i)), &NoInstanceDictionaryBaseType) ) { TYPE(result)->tp_dictoffset = 0; break; } } } return result; } /* set up __get__, if necessary */ static int EC_init_of(PyTypeObject *self) { PyObject *__of__; __of__ = PyObject_GetAttr(OBJECT(self), str__of__); if (__of__) { Py_DECREF(__of__); if (self->tp_descr_get) { if (self->tp_descr_get != of_get) { PyErr_SetString(PyExc_TypeError, "Can't mix __of__ and descriptors"); return -1; } } else self->tp_descr_get = of_get; } else { PyErr_Clear(); if (self->tp_descr_get == of_get) self->tp_descr_get = NULL; } return 0; } static int EC_init(PyTypeObject *self, PyObject *args, PyObject *kw) { PyObject *__class_init__, *r; if (PyType_Type.tp_init(OBJECT(self), args, kw) < 0) return -1; if (self->tp_dict != NULL) { r = PyDict_GetItemString(self->tp_dict, "__doc__"); if ((r == Py_None) && (PyDict_DelItemString(self->tp_dict, "__doc__") < 0) ) return -1; } if (EC_init_of(self) < 0) return -1; /* Call __class_init__ */ __class_init__ = PyObject_GetAttr(OBJECT(self), str__class_init__); if (__class_init__ == NULL) { PyErr_Clear(); return 0; } if (! (PyMethod_Check(__class_init__) && PyMethod_GET_FUNCTION(__class_init__) ) ) { Py_DECREF(__class_init__); PyErr_SetString(PyExc_TypeError, "Invalid type for __class_init__"); return -1; } r = PyObject_CallFunctionObjArgs(PyMethod_GET_FUNCTION(__class_init__), OBJECT(self), NULL); Py_DECREF(__class_init__); if (! r) return -1; Py_DECREF(r); return 0; } static int EC_setattro(PyTypeObject *type, PyObject *name, PyObject *value) { /* We want to allow setting attributes of builti-in types, because EC did in the past and there's code that relies on it. We can't really set slots though, but I don't think we need to. There's no good way to spot slots. We could use a lame rule like names that begin and end with __s and have just 4 _s smell too much like slots. */ if (! (type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { char *cname; int l; cname = PyString_AsString(name); if (cname == NULL) return -1; l = PyString_GET_SIZE(name); if (l > 4 && cname[0] == '_' && cname[1] == '_' && cname[l-1] == '_' && cname[l-2] == '_' ) { char *c; c = strchr(cname+2, '_'); if (c != NULL && (c - cname) >= (l-2)) { PyErr_Format (PyExc_TypeError, "can't set attributes of built-in/extension type '%s' if the " "attribute name begins and ends with __ and contains only " "4 _ characters", type->tp_name ); return -1; } } if (PyObject_GenericSetAttr(OBJECT(type), name, value) < 0) return -1; } else if (PyType_Type.tp_setattro(OBJECT(type), name, value) < 0) return -1; #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(type); #endif return 0; } static PyObject * inheritedAttribute(PyTypeObject *self, PyObject *name) { int i; PyObject *d, *cls; for (i = 1; i < PyTuple_GET_SIZE(self->tp_mro); i++) { cls = PyTuple_GET_ITEM(self->tp_mro, i); if (PyType_Check(cls)) d = ((PyTypeObject *)cls)->tp_dict; else if (PyClass_Check(cls)) d = ((PyClassObject *)cls)->cl_dict; else /* Unrecognized thing, punt */ d = NULL; if ((d == NULL) || (PyDict_GetItem(d, name) == NULL)) continue; return PyObject_GetAttr(cls, name); } PyErr_SetObject(PyExc_AttributeError, name); return NULL; } static PyObject * __basicnew__(PyObject *self) { return PyObject_CallMethodObjArgs(self, str__new__, self, NULL); } static int append_new(PyObject *result, PyObject *v) { int contains; if (v == OBJECT(&BaseType) || v == OBJECT(&PyBaseObject_Type)) return 0; /* Don't add these until end */ contains = PySequence_Contains(result, v); if (contains != 0) return contains; return PyList_Append(result, v); } static int copy_mro(PyObject *mro, PyObject *result) { PyObject *base; int i, l; l = PyTuple_Size(mro); if (l < 0) return -1; for (i=0; i < l; i++) { base = PyTuple_GET_ITEM(mro, i); if (append_new(result, base) < 0) return -1; } return 0; } static int copy_classic(PyObject *base, PyObject *result) { PyObject *bases, *basebase; int i, l, err=-1; if (append_new(result, base) < 0) return -1; bases = PyObject_GetAttr(base, str__bases__); if (bases == NULL) return -1; l = PyTuple_Size(bases); if (l < 0) goto end; for (i=0; i < l; i++) { basebase = PyTuple_GET_ITEM(bases, i); if (copy_classic(basebase, result) < 0) goto end; } err = 0; end: Py_DECREF(bases); return err; } static PyObject * mro(PyTypeObject *self) { /* Compute an MRO for a class */ PyObject *result, *base, *basemro, *mro=NULL; int i, l, err; result = PyList_New(0); if (result == NULL) return NULL; if (PyList_Append(result, OBJECT(self)) < 0) goto end; l = PyTuple_Size(self->tp_bases); if (l < 0) goto end; for (i=0; i < l; i++) { base = PyTuple_GET_ITEM(self->tp_bases, i); if (base == NULL) continue; basemro = PyObject_GetAttr(base, str__mro__); if (basemro != NULL) { /* Type */ err = copy_mro(basemro, result); Py_DECREF(basemro); if (err < 0) goto end; } else { PyErr_Clear(); if (copy_classic(base, result) < 0) goto end; } } if (self != &BaseType && PyList_Append(result, OBJECT(&BaseType)) < 0) goto end; if (PyList_Append(result, OBJECT(&PyBaseObject_Type)) < 0) goto end; l = PyList_GET_SIZE(result); mro = PyTuple_New(l); if (mro == NULL) goto end; for (i=0; i < l; i++) { Py_INCREF(PyList_GET_ITEM(result, i)); PyTuple_SET_ITEM(mro, i, PyList_GET_ITEM(result, i)); } end: Py_DECREF(result); return mro; } static struct PyMethodDef EC_methods[] = { {"__basicnew__", (PyCFunction)__basicnew__, METH_NOARGS, "Create a new empty object"}, {"inheritedAttribute", (PyCFunction)inheritedAttribute, METH_O, "Look up an inherited attribute"}, {"mro", (PyCFunction)mro, METH_NOARGS, "Compute an mro using the 'encalsulated base' scheme"}, {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static PyTypeObject ExtensionClassType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "ExtensionClass", /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ (destructor)0, /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, /* tp_compare */ (cmpfunc)0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ (hashfunc)0, /* tp_call */ (ternaryfunc)0, /* tp_str */ (reprfunc)0, /* tp_getattro */ (getattrofunc)0, /* tp_setattro */ (setattrofunc)EC_setattro, /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif , /* tp_doc */ "Meta-class for extension classes", /* tp_traverse */ (traverseproc)0, /* tp_clear */ (inquiry)0, /* tp_richcompare */ (richcmpfunc)0, /* tp_weaklistoffset */ (long)0, /* tp_iter */ (getiterfunc)0, /* tp_iternext */ (iternextfunc)0, /* tp_methods */ EC_methods, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* internal use */ /* tp_descr_get */ (descrgetfunc)0, /* tp_descr_set */ (descrsetfunc)0, /* tp_dictoffset */ 0, /* tp_init */ (initproc)EC_init, /* tp_alloc */ (allocfunc)0, /* tp_new */ (newfunc)EC_new, /* tp_free */ 0, /* Low-level free-mem routine */ /* tp_is_gc */ (inquiry)0, /* For PyObject_IS_GC */ }; static PyObject * debug(PyObject *self, PyObject *o) { Py_INCREF(Py_None); return Py_None; } static PyObject * pmc_init_of(PyObject *self, PyObject *args) { PyObject *o; if (! PyArg_ParseTuple(args, "O!", (PyObject *)&ExtensionClassType, &o)) return NULL; if (EC_init_of((PyTypeObject *)o) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } /* List of methods defined in the module */ static struct PyMethodDef ec_methods[] = { {"debug", (PyCFunction)debug, METH_O, ""}, {"pmc_init_of", (PyCFunction)pmc_init_of, METH_VARARGS, "Initialize __get__ for classes that define __of__"}, {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static PyObject * EC_findiattrs_(PyObject *self, char *cname) { PyObject *name, *r; name = PyString_FromString(cname); if (name == NULL) return NULL; r = ECBaseType->tp_getattro(self, name); Py_DECREF(name); return r; } static PyObject * ec_new_for_custom_dealloc(PyTypeObject *type, PyObject *args, PyObject *kw) { /* This is for EC's that have deallocs. For these, we need to incref the type when we create an instance, because the deallocs will decref the type. */ PyObject *r; r = PyType_GenericNew(type, args, kw); if (r) { Py_INCREF(type); } return r; } static int ec_init(PyObject *self, PyObject *args, PyObject *kw) { PyObject *r, *__init__; __init__ = PyObject_GetAttr(self, str__init__); if (__init__ == NULL) return -1; r = PyObject_Call(__init__, args, kw); Py_DECREF(__init__); if (r == NULL) return -1; Py_DECREF(r); return 0; } static int PyExtensionClass_Export_(PyObject *dict, char *name, PyTypeObject *typ) { long ecflags = 0; PyMethodDef *pure_methods = NULL, *mdef = NULL; PyObject *m; if (typ->tp_flags == 0) { /* Old-style EC */ if (typ->tp_traverse) { /* ExtensionClasses stick there methods in the tp_traverse slot */ mdef = (PyMethodDef *)typ->tp_traverse; if (typ->tp_basicsize <= sizeof(_emptyobject)) /* Pure mixin. We want rebindable methods */ pure_methods = mdef; else typ->tp_methods = mdef; typ->tp_traverse = NULL; /* Look for __init__ method */ for (; mdef->ml_name; mdef++) { if (strcmp(mdef->ml_name, "__init__") == 0) { /* we have an old-style __init__, install a special slot */ typ->tp_init = ec_init; break; } } } if (typ->tp_clear) { /* ExtensionClasses stick there flags in the tp_clear slot */ ecflags = (long)(typ->tp_clear); /* Some old-style flags were set */ if ((ecflags & EXTENSIONCLASS_BINDABLE_FLAG) && typ->tp_descr_get == NULL) /* We have __of__-style binding */ typ->tp_descr_get = of_get; } typ->tp_clear = NULL; typ->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; if (typ->tp_dealloc != NULL) typ->tp_new = ec_new_for_custom_dealloc; } typ->ob_type = ECExtensionClassType; if (ecflags & EXTENSIONCLASS_NOINSTDICT_FLAG) typ->tp_base = &NoInstanceDictionaryBaseType; else typ->tp_base = &BaseType; typ->tp_basicsize += typ->tp_base->tp_basicsize; if (typ->tp_new == NULL) typ->tp_new = PyType_GenericNew; if (PyType_Ready(typ) < 0) return -1; if (pure_methods) { /* We had pure methods. We want to be able to rebind these, so we'll make them ordinary method wrappers around method descrs */ for (; pure_methods->ml_name; pure_methods++) { m = PyDescr_NewMethod(ECBaseType, pure_methods); if (! m) return -1; m = PyMethod_New((PyObject *)m, NULL, (PyObject *)ECBaseType); if (! m) return -1; if (PyDict_SetItemString(typ->tp_dict, pure_methods->ml_name, m) < 0) return -1; } #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(typ); #endif } else if (mdef && mdef->ml_name) { /* Blast, we have to stick __init__ in the dict ourselves because PyType_Ready probably stuck a wrapper for ec_init in instead. */ m = PyDescr_NewMethod(typ, mdef); if (! m) return -1; if (PyDict_SetItemString(typ->tp_dict, mdef->ml_name, m) < 0) return -1; #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(typ); #endif } if (PyMapping_SetItemString(dict, name, (PyObject*)typ) < 0) return -1; return 0; } PyObject * PyECMethod_New_(PyObject *callable, PyObject *inst) { if (! PyExtensionInstance_Check(inst)) { PyErr_SetString(PyExc_TypeError, "Can't bind non-ExtensionClass instance."); return NULL; } if (PyMethod_Check(callable)) { if (callable->ob_refcnt == 1) { Py_XDECREF(((PyMethodObject*)callable)->im_self); Py_INCREF(inst); ((PyMethodObject*)callable)->im_self = inst; Py_INCREF(callable); return callable; } else return callable->ob_type->tp_descr_get( callable, inst, ((PyMethodObject*)callable)->im_class); } else return PyMethod_New(callable, inst, (PyObject*)(ECBaseType)); } static struct ExtensionClassCAPIstruct TrueExtensionClassCAPI = { EC_findiattrs_, PyExtensionClass_Export_, PyECMethod_New_, &BaseType, &ExtensionClassType, }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC init_ExtensionClass(void) { PyObject *m, *s; if (pickle_setup() < 0) return; #define DEFINE_STRING(S) \ if(! (str ## S = PyString_FromString(# S))) return DEFINE_STRING(__of__); DEFINE_STRING(__get__); DEFINE_STRING(__class_init__); DEFINE_STRING(__init__); DEFINE_STRING(__bases__); DEFINE_STRING(__mro__); DEFINE_STRING(__new__); #undef DEFINE_STRING PyExtensionClassCAPI = &TrueExtensionClassCAPI; ExtensionClassType.ob_type = &PyType_Type; ExtensionClassType.tp_base = &PyType_Type; ExtensionClassType.tp_basicsize = PyType_Type.tp_basicsize; ExtensionClassType.tp_traverse = PyType_Type.tp_traverse; ExtensionClassType.tp_clear = PyType_Type.tp_clear; /* Initialize types: */ if (PyType_Ready(&ExtensionClassType) < 0) return; BaseType.ob_type = &ExtensionClassType; BaseType.tp_base = &PyBaseObject_Type; BaseType.tp_basicsize = PyBaseObject_Type.tp_basicsize; BaseType.tp_new = PyType_GenericNew; if (PyType_Ready(&BaseType) < 0) return; NoInstanceDictionaryBaseType.ob_type = &ExtensionClassType; NoInstanceDictionaryBaseType.tp_base = &BaseType; NoInstanceDictionaryBaseType.tp_basicsize = BaseType.tp_basicsize; NoInstanceDictionaryBaseType.tp_new = PyType_GenericNew; if (PyType_Ready(&NoInstanceDictionaryBaseType) < 0) return; /* Create the module and add the functions */ m = Py_InitModule3("_ExtensionClass", ec_methods, _extensionclass_module_documentation); if (m == NULL) return; s = PyCObject_FromVoidPtr(PyExtensionClassCAPI, NULL); if (PyModule_AddObject(m, "CAPI2", s) < 0) return; /* Add types: */ if (PyModule_AddObject(m, "ExtensionClass", (PyObject *)&ExtensionClassType) < 0) return; if (PyModule_AddObject(m, "Base", (PyObject *)&BaseType) < 0) return; if (PyModule_AddObject(m, "NoInstanceDictionaryBase", (PyObject *)&NoInstanceDictionaryBaseType) < 0) return; } zope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass/__init__.py0000644000175000017500000000523312214017441024242 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ExtensionClass Extension Class exists to support types derived from the old ExtensionType meta-class that preceeded Python 2.2 and new-style classes. As a meta-class, ExtensionClass provides the following features: - Support for a class initialiser: >>> from ExtensionClass import ExtensionClass, Base >>> class C(Base): ... def __class_init__(self): ... print 'class init called' ... print self.__name__ ... def bar(self): ... return 'bar called' class init called C >>> c = C() >>> int(c.__class__ is C) 1 >>> int(c.__class__ is type(c)) 1 - Making sure that every instance of the meta-class has Base as a base class: >>> class X: ... __metaclass__ = ExtensionClass >>> Base in X.__mro__ 1 - Provide an inheritedAttribute method for looking up attributes in base classes: >>> class C2(C): ... def bar(*a): ... return C2.inheritedAttribute('bar')(*a), 42 class init called C2 >>> o = C2() >>> o.bar() ('bar called', 42) This is for compatability with old code. New code should use super instead. The base class, Base, exists mainly to support the __of__ protocol. The __of__ protocol is similar to __get__ except that __of__ is called when an implementor is retrieved from an instance as well as from a class: >>> class O(Base): ... def __of__(*a): ... return a >>> o1 = O() >>> o2 = O() >>> C.o1 = o1 >>> c.o2 = o2 >>> c.o1 == (o1, c) 1 >>> C.o1 == o1 1 >>> int(c.o2 == (o2, c)) 1 We accomplish this by making a class that implements __of__ a descriptor and treating all descriptor ExtensionClasses this way. That is, if an extension class is a descriptor, it's __get__ method will be called even when it is retrieved from an instance. >>> class O(Base): ... def __get__(*a): ... return a ... >>> o1 = O() >>> o2 = O() >>> C.o1 = o1 >>> c.o2 = o2 >>> int(c.o1 == (o1, c, type(c))) 1 >>> int(C.o1 == (o1, None, type(c))) 1 >>> int(c.o2 == (o2, c, type(c))) 1 $Id: __init__.py 110581 2010-04-07 15:04:20Z tseaver $ """ from _ExtensionClass import * zope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass/tests.py0000644000175000017500000004736612214017441023662 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from ExtensionClass import * def print_dict(d): d = d.items() d.sort() print '{%s}' % (', '.join( [('%r: %r' % (k, v)) for (k, v) in d] )) def test_mixing(): """Test working with a classic class >>> class Classic: ... def x(self): ... return 42 >>> class O(Base): ... def __of__(*a): ... return a >>> class O2(Classic, O): ... def __of__(*a): ... return (O2.inheritedAttribute('__of__')(*a), ... O2.inheritedAttribute('x')(a[0])) >>> class C(Base): ... def __class_init__(self): ... print 'class init called' ... print self.__name__ ... def bar(self): ... return 'bar called' class init called C >>> c = C() >>> o2 = O2() >>> c.o2 = o2 >>> int(c.o2 == ((o2, c), 42)) 1 Test working with a new style >>> class Modern(object): ... def x(self): ... return 42 >>> class O2(Modern, O): ... def __of__(*a): ... return (O2.inheritedAttribute('__of__')(*a), ... O2.inheritedAttribute('x')(a[0])) >>> o2 = O2() >>> c.o2 = o2 >>> int(c.o2 == ((o2, c), 42)) 1 """ def test_class_creation_under_stress(): """ >>> for i in range(100): ... class B(Base): ... print i, ... if i and i%20 == 0: ... print 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 >>> import gc >>> x = gc.collect() """ def old_test_add(): """test_add.py from old EC >>> class foo(Base): ... def __add__(self,other): print 'add called' >>> foo()+foo() add called """ def proper_error_on_deleattr(): """ Florent Guillaume wrote: ... Excellent. Will it also fix this particularity of ExtensionClass: >>> class A(Base): ... def foo(self): ... self.gee ... def bar(self): ... del self.gee >>> a=A() >>> a.foo() Traceback (most recent call last): ... AttributeError: gee >>> a.bar() Traceback (most recent call last): ... AttributeError: 'A' object has no attribute 'gee' I.e., the fact that KeyError is raised whereas a normal class would raise AttributeError. """ def test_NoInstanceDictionaryBase(): """ >>> class B(NoInstanceDictionaryBase): pass ... >>> B().__dict__ Traceback (most recent call last): ... AttributeError: This object has no __dict__ >>> class B(NoInstanceDictionaryBase): ... __slots__ = ('a', 'b') ... >>> class BB(B): pass ... >>> b = BB() >>> b.__dict__ Traceback (most recent call last): ... AttributeError: This object has no __dict__ >>> b.a = 1 >>> b.b = 2 >>> b.a 1 >>> b.b 2 """ def test__basicnew__(): """ >>> x = Simple.__basicnew__() >>> x.__dict__ {} """ def cmpattrs(self, other, *attrs): for attr in attrs: if attr[:3] in ('_v_', '_p_'): continue c = cmp(getattr(self, attr, None), getattr(other, attr, None)) if c: return c return 0 class Simple(Base): def __init__(self, name, **kw): self.__name__ = name self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', *(self.__dict__.keys())) def test_basic_pickling(): """ >>> x = Simple('x', aaa=1, bbb='foo') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> print_dict(x.__getstate__()) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> f, (c,), state = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Simple' >>> print_dict(state) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> import pickle >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.__setstate__({'z': 1}) >>> x.__dict__ {'z': 1} """ class Custom(Simple): def __new__(cls, x, y): r = Base.__new__(cls) r.x, r.y = x, y return r def __init__(self, x, y): self.a = 42 def __getnewargs__(self): return self.x, self.y def __getstate__(self): return self.a def __setstate__(self, a): self.a = a def test_pickling_w_overrides(): """ >>> x = Custom('x', 'y') >>> x.a = 99 >>> (f, (c, ax, ay), a) = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Custom' >>> ax, ay, a ('x', 'y', 99) >>> import pickle >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class Slotted(Base): __slots__ = 's1', 's2', '_p_splat', '_v_eek' def __init__(self, s1, s2): self.s1, self.s2 = s1, s2 self._v_eek = 1 self._p_splat = 2 class SubSlotted(Slotted): __slots__ = 's3', 's4' def __init__(self, s1, s2, s3): Slotted.__init__(self, s1, s2) self.s3 = s3 def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4') def test_pickling_w_slots_only(): """ >>> x = SubSlotted('x', 'y', 'z') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> import pickle >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class SubSubSlotted(SubSlotted): def __init__(self, s1, s2, s3, **kw): SubSlotted.__init__(self, s1, s2, s3) self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4', *(self.__dict__.keys())) def test_pickling_w_slots(): """ >>> x = SubSubSlotted('x', 'y', 'z', aaa=1, bbb='foo') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> import pickle >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ def test_pickling_w_slots_w_empty_dict(): """ >>> x = SubSubSlotted('x', 'y', 'z') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> import pickle >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ def test_setattr_on_extension_type(): """ >>> for name in 'x', '_x', 'x_', '__x_y__', '___x__', '__x___', '_x_': ... setattr(Base, name, 1) ... print getattr(Base, name) ... delattr(Base, name) ... print getattr(Base, name, 0) 1 0 1 0 1 0 1 0 1 0 1 0 1 0 >>> Base.__foo__ = 1 Traceback (most recent call last): ... TypeError: can't set attributes of built-in/extension type """ \ """'ExtensionClass.Base' if the attribute name begins """ \ """and ends with __ and contains only 4 _ characters >>> Base.__foo__ Traceback (most recent call last): ... AttributeError: type object 'ExtensionClass.Base' """ \ """has no attribute '__foo__' >>> del Base.__foo__ Traceback (most recent call last): ... TypeError: can't set attributes of built-in/extension type """ \ """'ExtensionClass.Base' if the attribute name begins """ \ """and ends with __ and contains only 4 _ characters """ def test_mro(): """ExtensionClass method-resolution order The EC MRO is chosen to maximize backward compatibility and provide a model that is easy to reason about. The basic idea is: I'll call this the "encapsulated base" scheme. Consider: >>> class X(Base): ... pass >>> class Y(Base): ... pass >>> class Z(Base): ... pass >>> class C(X, Y, Z): ... def foo(self): ... return 42 When we look up an attribute, we do the following: - Look in C's dictionary first. - Look up the attribute in X. We don't care how we get the attribute from X. If X is a new-style-class, we use the new algorithm. If X is a classic class, we use left-to-right depth-first. If X is an nsEC, use the "encapsulated base" algorithm. If we don't find the attribute in X, look in Y and then in Z, using the same approach. This algorithm will produce backward compatible results, providing the equivalent of left-to-right depth-first for nsECs and classic classes. We'll actually do something less abstract. We'll use a simple algorthm to merge the __mro__ of the base classes, computing an __mro__ for classic classes using the left-to-right depth-first algorithm. We'll basically lay the mros end-to-end left-to-right and remove repeats, keeping the first occurence of each class. >>> [c.__name__ for c in C.__mro__] ['C', 'X', 'Y', 'Z', 'Base', 'object'] For backward-compatability's sake, we actually depart from the above description a bit. We always put Base and object last in the mro, as shown in the example above. The primary reason for this is that object provides a do-nothing __init__ method. It is common practice to mix a C-implemented base class that implements a few methods with a Python class that implements those methods and others. The idea is that the C implementation overrides selected methods in C, so the C subclass is listed first. Unfortunately, because all extension classes are required to subclass Base, and thus, object, the C subclass brings along the __init__ object from objects, which would hide any __init__ method provided by the Python mix-in. Base and object are special in that they are implied by their meta classes. For example, a new-style class always has object as an ancestor, even if it isn't listed as a base: >>> class O: ... __metaclass__ = type >>> [c.__name__ for c in O.__bases__] ['object'] >>> [c.__name__ for c in O.__mro__] ['O', 'object'] Similarly, Base is always an ancestor of an extension class: >>> class E: ... __metaclass__ = ExtensionClass >>> [c.__name__ for c in E.__bases__] ['Base'] >>> [c.__name__ for c in E.__mro__] ['E', 'Base', 'object'] Base and object are generally added soley to get a particular meta class. They aren't used to provide application functionality and really shouldn't be considered when reasoning about where attributes come from. They do provide some useful default functionality and should be included at the end of the mro. Here are more examples: >>> from ExtensionClass import Base >>> class NA(object): ... pass >>> class NB(NA): ... pass >>> class NC(NA): ... pass >>> class ND(NB, NC): ... pass >>> [c.__name__ for c in ND.__mro__] ['ND', 'NB', 'NC', 'NA', 'object'] >>> class EA(Base): ... pass >>> class EB(EA): ... pass >>> class EC(EA): ... pass >>> class ED(EB, EC): ... pass >>> [c.__name__ for c in ED.__mro__] ['ED', 'EB', 'EA', 'EC', 'Base', 'object'] >>> class EE(ED, ND): ... pass >>> [c.__name__ for c in EE.__mro__] ['EE', 'ED', 'EB', 'EA', 'EC', 'ND', 'NB', 'NC', 'NA', 'Base', 'object'] >>> class EF(ND, ED): ... pass >>> [c.__name__ for c in EF.__mro__] ['EF', 'ND', 'NB', 'NC', 'NA', 'ED', 'EB', 'EA', 'EC', 'Base', 'object'] >>> class CA: ... pass >>> class CB(CA): ... pass >>> class CC(CA): ... pass >>> class CD(CB, CC): ... pass >>> class ECD(Base, CD): ... pass >>> [c.__name__ for c in ECD.__mro__] ['ECD', 'CD', 'CB', 'CA', 'CC', 'Base', 'object'] >>> class CDE(CD, Base): ... pass >>> [c.__name__ for c in CDE.__mro__] ['CDE', 'CD', 'CB', 'CA', 'CC', 'Base', 'object'] >>> class CEND(CD, ED, ND): ... pass >>> [c.__name__ for c in CEND.__mro__] ['CEND', 'CD', 'CB', 'CA', 'CC', """ \ """'ED', 'EB', 'EA', 'EC', 'ND', 'NB', 'NC', 'NA', 'Base', 'object'] """ def test_avoiding___init__decoy_w_inheritedAttribute(): """ >>> class Decoy(Base): ... pass >>> class B(Base): ... def __init__(self, a, b): ... print '__init__', a, b >>> class C(Decoy, B): ... def __init__(self): ... print 'C init' ... C.inheritedAttribute('__init__')(self, 1, 2) >>> x = C() C init __init__ 1 2 """ def test_of_not_called_when_not_accessed_through_EC_instance(): """ >>> class Eek(Base): ... def __of__(self, parent): ... return self, parent If I define an EC instance as an attr of an ordinary class: >>> class O(object): ... eek = Eek() >>> class C: ... eek = Eek() I get the instance, without calling __of__, when I get it from either tha class: >>> O.eek is O.__dict__['eek'] True >>> C.eek is C.__dict__['eek'] True or an instance of the class: >>> O().eek is O.__dict__['eek'] True >>> C().eek is C.__dict__['eek'] True If I define an EC instance as an attr of an extension class: >>> class E(Base): ... eek = Eek() I get the instance, without calling __of__, when I get it from tha class: >>> E.eek is E.__dict__['eek'] True But __of__ is called if I go through the instance: >>> e = E() >>> e.eek == (E.__dict__['eek'], e) True """ def test_inheriting___doc__(): """Old-style ExtensionClass inherited __doc__ from base classes. >>> class E(Base): ... "eek" >>> class EE(E): ... pass >>> EE.__doc__ 'eek' >>> EE().__doc__ 'eek' """ def test___of___w_metaclass_instance(): """When looking for extension class instances, need to handle meta classes >>> class C(Base): ... pass >>> class O(Base): ... def __of__(self, parent): ... print '__of__ called on an O' >>> class M(ExtensionClass): ... pass >>> class X: ... __metaclass__ = M ... >>> class S(X, O): ... pass >>> c = C() >>> c.s = S() >>> c.s __of__ called on an O """ def test___of__set_after_creation(): """We may need to set __of__ after a class is created. Normally, in a class's __init__, the initialization code checks for an __of__ method and, if it isn't already set, sets __get__. If a class is persistent and loaded from the database, we want this to happen in __setstate__. The pmc_init_of function allws us to do that. We'll create an extension class without a __of__. We'll also give it a special meta class, just to make sure that this works with funny metaclasses too: >>> import ExtensionClass >>> class M(ExtensionClass.ExtensionClass): ... "A meta class" >>> class B(ExtensionClass.Base): ... __metaclass__ = M ... def __init__(self, name): ... self.name = name ... def __repr__(self): ... return self.name >>> B.__class__ is M True >>> x = B('x') >>> x.y = B('y') >>> x.y y We define a __of__ method for B after the fact: >>> def __of__(self, other): ... print '__of__(%r, %r)' % (self, other) ... return self >>> B.__of__ = __of__ We see that this has no effect: >>> x.y y Until we use pmc_init_of: >>> ExtensionClass.pmc_init_of(B) >>> x.y __of__(y, x) y Note that there is no harm in calling pmc_init_of multiple times: >>> ExtensionClass.pmc_init_of(B) >>> ExtensionClass.pmc_init_of(B) >>> ExtensionClass.pmc_init_of(B) >>> x.y __of__(y, x) y If we remove __of__, we'll go back to the behavior we had before: >>> del B.__of__ >>> ExtensionClass.pmc_init_of(B) >>> x.y y """ def test_Basic_gc(): """Test to make sure that EC instances participate in GC >>> from ExtensionClass import Base >>> import gc >>> class C1(Base): ... pass ... >>> class C2(Base): ... def __del__(self): ... print 'removed' ... >>> a=C1() >>> a.b = C1() >>> a.b.a = a >>> a.b.c = C2() >>> thresholds = gc.get_threshold() >>> gc.set_threshold(0) >>> ignore = gc.collect() >>> del a >>> ignored = gc.collect() removed >>> ignored > 0 True >>> gc.set_threshold(*thresholds) """ from doctest import DocTestSuite import unittest def test_suite(): return unittest.TestSuite(( DocTestSuite('ExtensionClass'), DocTestSuite(), )) zope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass/ExtensionClass.h0000644000175000017500000002367412214017441025255 0ustar arnauarnau/***************************************************************************** Copyright (c) 1996-2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /* $Id: ExtensionClass.h 110581 2010-04-07 15:04:20Z tseaver $ Extension Class Definitions Implementing base extension classes A base extension class is implemented in much the same way that an extension type is implemented, except: - The include file, 'ExtensionClass.h', must be included. - The type structure is declared to be of type 'PyExtensionClass', rather than of type 'PyTypeObject'. - The type structure has an additional member that must be defined after the documentation string. This extra member is a method chain ('PyMethodChain') containing a linked list of method definition ('PyMethodDef') lists. Method chains can be used to implement method inheritance in C. Most extensions don't use method chains, but simply define method lists, which are null-terminated arrays of method definitions. A macro, 'METHOD_CHAIN' is defined in 'ExtensionClass.h' that converts a method list to a method chain. (See the example below.) - Module functions that create new instances must be replaced by an '__init__' method that initializes, but does not create storage for instances. - The extension class must be initialized and exported to the module with:: PyExtensionClass_Export(d,"name",type); where 'name' is the module name and 'type' is the extension class type object. Attribute lookup Attribute lookup is performed by calling the base extension class 'getattr' operation for the base extension class that includes C data, or for the first base extension class, if none of the base extension classes include C data. 'ExtensionClass.h' defines a macro 'Py_FindAttrString' that can be used to find an object's attributes that are stored in the object's instance dictionary or in the object's class or base classes:: v = Py_FindAttrString(self,name); In addition, a macro is provided that replaces 'Py_FindMethod' calls with logic to perform the same sort of lookup that is provided by 'Py_FindAttrString'. Linking The extension class mechanism was designed to be useful with dynamically linked extension modules. Modules that implement extension classes do not have to be linked against an extension class library. The macro 'PyExtensionClass_Export' imports the 'ExtensionClass' module and uses objects imported from this module to initialize an extension class with necessary behavior. */ #ifndef EXTENSIONCLASS_H #define EXTENSIONCLASS_H #include "Python.h" #include "import.h" /* Declarations for objects of type ExtensionClass */ #define EC PyTypeObject #define PyExtensionClass PyTypeObject #define EXTENSIONCLASS_BINDABLE_FLAG 1 << 2 #define EXTENSIONCLASS_NOINSTDICT_FLAG 1 << 5 typedef struct { PyObject_HEAD } _emptyobject; static struct ExtensionClassCAPIstruct { /***************************************************************************** WARNING: THIS STRUCT IS PRIVATE TO THE EXTENSION CLASS INTERFACE IMPLEMENTATION AND IS SUBJECT TO CHANGE !!! *****************************************************************************/ PyObject *(*EC_findiattrs_)(PyObject *self, char *cname); int (*PyExtensionClass_Export_)(PyObject *dict, char *name, PyTypeObject *typ); PyObject *(*PyECMethod_New_)(PyObject *callable, PyObject *inst); PyExtensionClass *ECBaseType_; PyExtensionClass *ECExtensionClassType_; } *PyExtensionClassCAPI = NULL; #define ECBaseType (PyExtensionClassCAPI->ECBaseType_) #define ECExtensionClassType (PyExtensionClassCAPI->ECExtensionClassType_) /* Following are macros that are needed or useful for defining extension classes: */ /* This macro redefines Py_FindMethod to do attribute for an attribute name given by a C string lookup using extension class meta-data. This is used by older getattr implementations. This macro is used in base class implementations of tp_getattr to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. Note that in Python 1.4, a getattr operation may be provided that uses an object argument. Classes that support this new operation should use Py_FindAttr. */ #define EC_findiattrs (PyExtensionClassCAPI->EC_findiattrs_) #define Py_FindMethod(M,SELF,NAME) (EC_findiattrs((SELF),(NAME))) /* Do method or attribute lookup for an attribute name given by a C string using extension class meta-data. This macro is used in base class implementations of tp_getattro to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. Note that in Python 1.4, a getattr operation may be provided that uses an object argument. Classes that support this new operation should use Py_FindAttr. */ #define Py_FindAttrString(SELF,NAME) (EC_findiattrs((SELF),(NAME))) /* Do method or attribute lookup using extension class meta-data. This macro is used in base class implementations of tp_getattr to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. */ #define Py_FindAttr (ECBaseType->tp_getattro) /* Do attribute assignment for an attribute. This macro is used in base class implementations of tp_setattro to set attributes that are not managed by the base type directly. The macro is generally used to assign attributes after other attribute attempts to assign attributes have failed. */ #define PyEC_SetAttr(SELF,NAME,V) (ECBaseType->tp_setattro(SELF, NAME, V)) /* Convert a method list to a method chain. */ #define METHOD_CHAIN(DEF) (traverseproc)(DEF) /* The following macro checks whether a type is an extension class: */ #define PyExtensionClass_Check(TYPE) \ PyObject_TypeCheck((PyObject*)(TYPE), ECExtensionClassType) /* The following macro checks whether an instance is an extension instance: */ #define PyExtensionInstance_Check(INST) \ PyObject_TypeCheck(((PyObject*)(INST))->ob_type, ECExtensionClassType) #define CHECK_FOR_ERRORS(MESS) /* The following macro can be used to define an extension base class that only provides method and that is used as a pure mix-in class. */ #define PURE_MIXIN_CLASS(NAME,DOC,METHODS) \ static PyExtensionClass NAME ## Type = { PyObject_HEAD_INIT(NULL) 0, # NAME, \ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \ 0 , DOC, (traverseproc)METHODS, } /* The following macros provide limited access to extension-class method facilities. */ /* Test for an ExtensionClass method: */ #define PyECMethod_Check(O) PyMethod_Check((O)) /* Create a method object that wraps a callable object and an instance. Note that if the callable object is an extension class method, then the new method will wrap the callable object that is wrapped by the extension class method. Also note that if the callable object is an extension class method with a reference count of 1, then the callable object will be rebound to the instance and returned with an incremented reference count. */ #define PyECMethod_New(CALLABLE, INST) \ PyExtensionClassCAPI->PyECMethod_New_((CALLABLE),(INST)) /* Return the instance that is bound by an extension class method. */ #define PyECMethod_Self(M) \ (PyMethod_Check((M)) ? ((PyMethodObject*)(M))->im_self : NULL) /* Check whether an object has an __of__ method for returning itself in the context of it's container. */ #define has__of__(O) (PyObject_TypeCheck((O)->ob_type, ECExtensionClassType) \ && (O)->ob_type->tp_descr_get != NULL) /* The following macros are used to check whether an instance or a class' instanses have instance dictionaries: */ #define HasInstDict(O) (_PyObject_GetDictPtr(O) != NULL) #define ClassHasInstDict(C) ((C)->tp_dictoffset > 0)) /* Get an object's instance dictionary. Use with caution */ #define INSTANCE_DICT(inst) (_PyObject_GetDictPtr(O)) /* Test whether an ExtensionClass, S, is a subclass of ExtensionClass C. */ #define ExtensionClassSubclass_Check(S,C) PyType_IsSubtype((S), (C)) /* Test whether an ExtensionClass instance , I, is a subclass of ExtensionClass C. */ #define ExtensionClassSubclassInstance_Check(I,C) PyObject_TypeCheck((I), (C)) /* Export an Extension Base class in a given module dictionary with a given name and ExtensionClass structure. */ #define PyExtensionClass_Export(D,N,T) \ if (! ExtensionClassImported || \ PyExtensionClassCAPI->PyExtensionClass_Export_((D),(N),&(T)) < 0) return; #define ExtensionClassImported \ ((PyExtensionClassCAPI != NULL) || \ (PyExtensionClassCAPI = PyCObject_Import("ExtensionClass","CAPI2"))) /* These are being overridded to use tp_free when used with new-style classes. This is to allow old extention-class code to work. */ #undef PyMem_DEL #undef PyObject_DEL #define PyMem_DEL(O) \ if (((O)->ob_type->tp_flags & Py_TPFLAGS_HAVE_CLASS) \ && ((O)->ob_type->tp_free != NULL)) \ (O)->ob_type->tp_free((PyObject*)(O)); \ else \ PyObject_FREE((O)); #define PyObject_DEL(O) PyMem_DEL(O) #endif /* EXTENSIONCLASS_H */ zope2.13-2.13.21/source/ExtensionClass/src/MethodObject/0000755000175000017500000000000012214017441021533 5ustar arnauarnauzope2.13-2.13.21/source/ExtensionClass/src/MethodObject/__init__.py0000644000175000017500000000003412214017441023641 0ustar arnauarnaufrom _MethodObject import * zope2.13-2.13.21/source/ExtensionClass/src/MethodObject/tests.py0000644000175000017500000000233712214017441023254 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## def test_methodobject(): """ >>> from ExtensionClass import Base >>> from MethodObject import Method >>> class foo(Method): ... def __call__(self, ob, *args, **kw): ... print 'called', ob, args, kw >>> class bar(Base): ... def __repr__(self): ... return "bar()" ... hi = foo() >>> x = bar() >>> hi = x.hi >>> hi(1,2,3,name='spam') called bar() (1, 2, 3) {'name': 'spam'} """ def test_suite(): import unittest from doctest import DocTestSuite return unittest.TestSuite(( DocTestSuite(), )) zope2.13-2.13.21/source/ExtensionClass/src/MethodObject/_MethodObject.c0000644000175000017500000000363412214017441024413 0ustar arnauarnau/***************************************************************************** Copyright (c) 1996-2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #include "ExtensionClass/ExtensionClass.h" static PyObject * of(PyObject *self, PyObject *args) { PyObject *inst; if(PyArg_Parse(args,"O",&inst)) return PyECMethod_New(self,inst); else return NULL; } struct PyMethodDef Method_methods[] = { {"__of__",(PyCFunction)of,0,""}, {NULL, NULL} /* sentinel */ }; static struct PyMethodDef methods[] = {{NULL, NULL}}; void init_MethodObject(void) { PyObject *m, *d; PURE_MIXIN_CLASS(Method, "Base class for objects that want to be treated as methods\n" "\n" "The method class provides a method, __of__, that\n" "binds an object to an instance. If a method is a subobject\n" "of an extension-class instance, the the method will be bound\n" "to the instance and when the resulting object is called, it\n" "will call the method and pass the instance in addition to\n" "other arguments. It is the responsibility of Method objects\n" "to implement (or inherit) a __call__ method.\n", Method_methods); /* Create the module and add the functions */ m = Py_InitModule4("_MethodObject", methods, "Method-object mix-in class module\n\n" "$Id: _MethodObject.c 110581 2010-04-07 15:04:20Z tseaver $\n", (PyObject*)NULL,PYTHON_API_VERSION); d = PyModule_GetDict(m); PyExtensionClass_Export(d,"Method",MethodType); } zope2.13-2.13.21/source/ExtensionClass/src/ComputedAttribute/0000755000175000017500000000000012214017441022630 5ustar arnauarnauzope2.13-2.13.21/source/ExtensionClass/src/ComputedAttribute/_ComputedAttribute.c0000644000175000017500000000552712214017441026610 0ustar arnauarnau/***************************************************************************** Copyright (c) 1996-2003 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #include "ExtensionClass/ExtensionClass.h" #define UNLESS(E) if(!(E)) #define OBJECT(O) ((PyObject*)(O)) typedef struct { PyObject_HEAD PyObject *callable; int level; } CA; static PyObject * CA__init__(CA *self, PyObject *args) { PyObject *callable; int level=0; UNLESS(PyArg_ParseTuple(args,"O|i",&callable, &level)) return NULL; if (level > 0) { callable=PyObject_CallFunction(OBJECT(self->ob_type), "Oi", callable, level-1); UNLESS (callable) return NULL; self->level=level; } else { Py_INCREF(callable); self->level=0; } self->callable=callable; Py_INCREF(Py_None); return Py_None; } static void CA_dealloc(CA *self) { Py_DECREF(self->callable); Py_DECREF(self->ob_type); PyObject_DEL(self); } static PyObject * CA_of(CA *self, PyObject *args) { if (self->level > 0) { Py_INCREF(self->callable); return self->callable; } if (PyString_Check(self->callable)) { /* Special case string as simple alias. */ PyObject *o; UNLESS (PyArg_ParseTuple(args,"O", &o)) return NULL; return PyObject_GetAttr(o, self->callable); } return PyObject_CallObject(self->callable, args); } static struct PyMethodDef CA_methods[] = { {"__init__",(PyCFunction)CA__init__, METH_VARARGS, ""}, {"__of__", (PyCFunction)CA_of, METH_VARARGS, ""}, {NULL, NULL} /* sentinel */ }; static PyExtensionClass ComputedAttributeType = { PyObject_HEAD_INIT(NULL) 0, "ComputedAttribute", sizeof(CA), 0, (destructor)CA_dealloc, 0,0,0,0,0, 0,0,0, 0,0,0,0,0, 0,0, "ComputedAttribute(callable) -- Create a computed attribute", METHOD_CHAIN(CA_methods), (void*)(EXTENSIONCLASS_BINDABLE_FLAG) }; static struct PyMethodDef methods[] = { {NULL, NULL} }; void init_ComputedAttribute(void) { PyObject *m, *d; UNLESS(ExtensionClassImported) return; /* Create the module and add the functions */ m = Py_InitModule4("_ComputedAttribute", methods, "Provide Computed Attributes\n\n" "$Id: _ComputedAttribute.c 110581 2010-04-07 15:04:20Z tseaver $\n", OBJECT(NULL),PYTHON_API_VERSION); d = PyModule_GetDict(m); PyExtensionClass_Export(d,"ComputedAttribute",ComputedAttributeType); } zope2.13-2.13.21/source/ExtensionClass/src/ComputedAttribute/__init__.py0000644000175000017500000000004112214017441024734 0ustar arnauarnaufrom _ComputedAttribute import * zope2.13-2.13.21/source/ExtensionClass/src/ComputedAttribute/tests.py0000644000175000017500000000433312214017441024347 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Computed Attributes Computed attributes work much like properties: >>> import math >>> from ComputedAttribute import ComputedAttribute >>> from ExtensionClass import Base >>> class Point(Base): ... def __init__(self, x, y): ... self.x, self.y = x, y ... length = ComputedAttribute(lambda self: math.sqrt(self.x**2+self.y**2)) >>> p = Point(3, 4) >>> "%.1f" % p.length '5.0' Except that you can also use computed attributes with instances: >>> p.angle = ComputedAttribute(lambda self: math.atan(self.y*1.0/self.x)) >>> "%.2f" % p.angle '0.93' $Id: tests.py 111623 2010-04-30 13:55:00Z hannosch $ """ def test_wrapper_support(): """Wrapper support To support acquisition wrappers, computed attributes have a level. The computation is only done when the level is zero. Retrieving a computed attribute with a level > 0 returns a computed attribute with a decremented level. >>> from ExtensionClass import Base >>> class X(Base): ... pass >>> x = X() >>> x.n = 1 >>> from ComputedAttribute import ComputedAttribute >>> x.n2 = ComputedAttribute(lambda self: self.n*2) >>> x.n2 2 >>> x.n2.__class__.__name__ 'int' >>> x.n2 = ComputedAttribute(lambda self: self.n*2, 2) >>> x.n2.__class__.__name__ 'ComputedAttribute' >>> x.n2 = x.n2 >>> x.n2.__class__.__name__ 'ComputedAttribute' >>> x.n2 = x.n2 >>> x.n2.__class__.__name__ 'int' """ import unittest from doctest import DocTestSuite def test_suite(): return unittest.TestSuite((DocTestSuite(),)) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass.egg-info/0000755000175000017500000000000012214017441023620 5ustar arnauarnauzope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass.egg-info/PKG-INFO0000644000175000017500000000627612214017441024730 0ustar arnauarnauMetadata-Version: 1.0 Name: ExtensionClass Version: 2.13.2 Summary: Metaclass for subclassable extension types Home-page: http://pypi.python.org/pypi/ExtensionClass Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ExtensionClass and ExtensionClass-related packages ================================================== ExtensionClass -------------- This package provides a metaclass that allows classes implemented in extension modules to be subclassed in Python. Unless you need ExtensionClasses for legacy applications (e.g. Zope 2), you probably want to use Python's new-style classes (available since Python 2.2). ComputedAttribute ----------------- This package provides a way to attach attributes to an ``ExtensionClass`` or instance that are computed by calling a callable. This works very much like ``property`` known from new-style classes, except that a ``ComputedAttribute`` can also be attached to an instance and that it honours ExtensionClass semantics (which is useful for retaining Acquisition wrappers, for example). MethodObject ------------ This package lets you attach additional "methods" to ExtensionClasses. These "methods" are actually implemented by subclassing the ``MethodObject.Method`` class and implementing the ``__call__`` method there. Instances of those classes will be bound to the instances they're attached to and will receive that instance object as a first parameter (after ``self``). Changelog ========= 2.13.2 (2010-06-16) ------------------- - LP #587760: Handle tp_basicsize correctly. 2.13.1 (2010-04-03) ------------------- - Removed undeclared testing dependency on zope.testing. - Removed cruft in ``pickle/pickle.c`` related to removed ``__getnewargs__``. 2.13.0 (2010-02-22) ------------------- - Avoid defining ``__getnewargs__`` as not to defeat the ZODB persistent reference optimization. Refs https://bugs.launchpad.net/zope2/+bug/143657. In order to take advantage of this optimization, you need to re-save your objects. 2.12.0 (2010-02-14) ------------------- - Removed old build artifacts and some metadata cleanup. - Added support for method cache in ExtensionClass. Patch contributed by Yoshinori K. Okuji. See https://bugs.launchpad.net/zope2/+bug/486182. 2.11.3 (2009-08-02) ------------------- - Further 64-bit fixes (Python 2.4 compatibility). 2.11.2 (2009-08-02) ------------------- - Fixed 64-bit compatibility issues for Python 2.5.x / 2.6.x. See http://www.python.org/dev/peps/pep-0353/ for details. 2.11.1 (2009-02-19) ------------------- - Initial egg release. Platform: UNKNOWN zope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass.egg-info/dependency_links.txt0000644000175000017500000000000112214017441027666 0ustar arnauarnau zope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass.egg-info/top_level.txt0000644000175000017500000000005612214017441026353 0ustar arnauarnauExtensionClass MethodObject ComputedAttribute zope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass.egg-info/SOURCES.txt0000644000175000017500000000121712214017441025505 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/ComputedAttribute/_ComputedAttribute.c src/ComputedAttribute/__init__.py src/ComputedAttribute/tests.py src/ExtensionClass/ExtensionClass.h src/ExtensionClass/_ExtensionClass.c src/ExtensionClass/__init__.py src/ExtensionClass/tests.py src/ExtensionClass.egg-info/PKG-INFO src/ExtensionClass.egg-info/SOURCES.txt src/ExtensionClass.egg-info/dependency_links.txt src/ExtensionClass.egg-info/not-zip-safe src/ExtensionClass.egg-info/top_level.txt src/ExtensionClass/pickle/pickle.c src/MethodObject/_MethodObject.c src/MethodObject/__init__.py src/MethodObject/tests.pyzope2.13-2.13.21/source/ExtensionClass/src/ExtensionClass.egg-info/not-zip-safe0000644000175000017500000000000112214017441026046 0ustar arnauarnau zope2.13-2.13.21/source/Products.ZCTextIndex/0000755000175000017500000000000012214017454017412 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCTextIndex/setup.py0000644000175000017500000000351612214017454021131 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from os.path import join from setuptools import setup, find_packages, Extension setup(name='Products.ZCTextIndex', version = '2.13.4', url='http://pypi.python.org/pypi/Products.ZCTextIndex', license='ZPL 2.1', description="Full text indexing for ZCatalog / Zope 2.", author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=open('README.txt').read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), namespace_packages=['Products'], package_dir={'': 'src'}, ext_modules=[ Extension( name='Products.ZCTextIndex.stopper', sources=[join('src', 'Products', 'ZCTextIndex', 'stopper.c')]), Extension( name='Products.ZCTextIndex.okascore', sources=[join('src', 'Products', 'ZCTextIndex', 'okascore.c')]), ], install_requires=[ 'setuptools', 'AccessControl', 'Acquisition', 'transaction', 'Persistence', 'zExceptions', 'ZODB3', 'Zope2 >= 2.13.0dev', 'zope.interface', ], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/Products.ZCTextIndex/PKG-INFO0000644000175000017500000000356012214017454020513 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.ZCTextIndex Version: 2.13.4 Summary: Full text indexing for ZCatalog / Zope 2. Home-page: http://pypi.python.org/pypi/Products.ZCTextIndex Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== This distribution contains a full text indexing facility for Zope 2 and more specifically for Products.ZCatalog. Changelog ========= 2.13.4 (2012-12-03) ------------------- - Fixed problem where the index was not reindexed if the new value was an empty string leading to inconsistence between the object attribute (that is empty) and the index that still contains the old indexed value. 2.13.3 (2011-07-28) ------------------- - Fixed problem in reindex document optimization, which could lead to negative document counts when reindexing unchanged documents. 2.13.2 (2011-05-04) ------------------- - Avoid changing data, if the indexed values stayed the same. 2.13.1 (2010-10-02) ------------------- - Changed word id creation algorithm in Lexicon. Instead of relying on an increasing length counter, we use a number from a randomized range. This avoids conflict errors while adding new words in multiple parallel transactions. Inspired by code from ``enfold.fixes``. - Lexicon: Added clear method. - Lexicon: Removed BBB code for instances created with Zope < 2.6.2. - Added missing namespace_packages declaration to setup.py. 2.13.0 (2010-06-19) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Products.ZCTextIndex/pip-egg-info/0000755000175000017500000000000012214017454021673 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCTextIndex/pip-egg-info/Products.ZCTextIndex.egg-info/0000755000175000017500000000000012214017454027340 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCTextIndex/pip-egg-info/Products.ZCTextIndex.egg-info/PKG-INFO0000644000175000017500000000356012214017454030441 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.ZCTextIndex Version: 2.13.4 Summary: Full text indexing for ZCatalog / Zope 2. Home-page: http://pypi.python.org/pypi/Products.ZCTextIndex Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== This distribution contains a full text indexing facility for Zope 2 and more specifically for Products.ZCatalog. Changelog ========= 2.13.4 (2012-12-03) ------------------- - Fixed problem where the index was not reindexed if the new value was an empty string leading to inconsistence between the object attribute (that is empty) and the index that still contains the old indexed value. 2.13.3 (2011-07-28) ------------------- - Fixed problem in reindex document optimization, which could lead to negative document counts when reindexing unchanged documents. 2.13.2 (2011-05-04) ------------------- - Avoid changing data, if the indexed values stayed the same. 2.13.1 (2010-10-02) ------------------- - Changed word id creation algorithm in Lexicon. Instead of relying on an increasing length counter, we use a number from a randomized range. This avoids conflict errors while adding new words in multiple parallel transactions. Inspired by code from ``enfold.fixes``. - Lexicon: Added clear method. - Lexicon: Removed BBB code for instances created with Zope < 2.6.2. - Added missing namespace_packages declaration to setup.py. 2.13.0 (2010-06-19) ------------------- - Released as separate package. Platform: UNKNOWN ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.ZCTextIndex/pip-egg-info/Products.ZCTextIndex.egg-info/dependency_links.txtzope2.13-2.13.21/source/Products.ZCTextIndex/pip-egg-info/Products.ZCTextIndex.egg-info/dependency_l0000644000175000017500000000000112214017454031703 0ustar arnauarnau zope2.13-2.13.21/source/Products.ZCTextIndex/pip-egg-info/Products.ZCTextIndex.egg-info/requires.txt0000644000175000017500000000016012214017454031735 0ustar arnauarnausetuptools AccessControl Acquisition transaction Persistence zExceptions ZODB3 Zope2 >= 2.13.0dev zope.interface././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/Products.ZCTextIndex/pip-egg-info/Products.ZCTextIndex.egg-info/namespace_packages.txtzope2.13-2.13.21/source/Products.ZCTextIndex/pip-egg-info/Products.ZCTextIndex.egg-info/namespace_pa0000644000175000017500000000001112214017454031667 0ustar arnauarnauProducts ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.ZCTextIndex/pip-egg-info/Products.ZCTextIndex.egg-info/top_level.txtzope2.13-2.13.21/source/Products.ZCTextIndex/pip-egg-info/Products.ZCTextIndex.egg-info/top_level.tx0000644000175000017500000000001112214017454031676 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.ZCTextIndex/pip-egg-info/Products.ZCTextIndex.egg-info/SOURCES.txt0000644000175000017500000000546512214017454031236 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt MANIFEST.in README.txt setup.cfg pip-egg-info/Products.ZCTextIndex.egg-info/PKG-INFO pip-egg-info/Products.ZCTextIndex.egg-info/SOURCES.txt pip-egg-info/Products.ZCTextIndex.egg-info/dependency_links.txt pip-egg-info/Products.ZCTextIndex.egg-info/namespace_packages.txt pip-egg-info/Products.ZCTextIndex.egg-info/not-zip-safe pip-egg-info/Products.ZCTextIndex.egg-info/requires.txt pip-egg-info/Products.ZCTextIndex.egg-info/top_level.txt src/Products/__init__.py src/Products/ZCTextIndex/BaseIndex.py src/Products/ZCTextIndex/CosineIndex.py src/Products/ZCTextIndex/HTMLSplitter.py src/Products/ZCTextIndex/IIndex.py src/Products/ZCTextIndex/INBest.py src/Products/ZCTextIndex/IPipelineElement.py src/Products/ZCTextIndex/IPipelineElementFactory.py src/Products/ZCTextIndex/IQueryParseTree.py src/Products/ZCTextIndex/IQueryParser.py src/Products/ZCTextIndex/ISplitter.py src/Products/ZCTextIndex/Lexicon.py src/Products/ZCTextIndex/NBest.py src/Products/ZCTextIndex/OkapiIndex.py src/Products/ZCTextIndex/ParseTree.py src/Products/ZCTextIndex/PipelineFactory.py src/Products/ZCTextIndex/QueryParser.py src/Products/ZCTextIndex/README.txt src/Products/ZCTextIndex/RiceCode.py src/Products/ZCTextIndex/SETUP.cfg src/Products/ZCTextIndex/SetOps.py src/Products/ZCTextIndex/Setup src/Products/ZCTextIndex/StopDict.py src/Products/ZCTextIndex/WidCode.py src/Products/ZCTextIndex/ZCTextIndex.py src/Products/ZCTextIndex/__init__.py src/Products/ZCTextIndex/interfaces.py src/Products/ZCTextIndex/okascore.c src/Products/ZCTextIndex/stopper.c src/Products/ZCTextIndex/dtml/addLexicon.dtml src/Products/ZCTextIndex/dtml/addZCTextIndex.dtml src/Products/ZCTextIndex/dtml/manageLexicon.dtml src/Products/ZCTextIndex/dtml/manageZCTextIndex.dtml src/Products/ZCTextIndex/dtml/queryLexicon.dtml src/Products/ZCTextIndex/help/Lexicon_Add.stx src/Products/ZCTextIndex/help/ZCTextIndex_Add.stx src/Products/ZCTextIndex/tests/__init__.py src/Products/ZCTextIndex/tests/hs-tool.py src/Products/ZCTextIndex/tests/indexhtml.py src/Products/ZCTextIndex/tests/mailtest.py src/Products/ZCTextIndex/tests/mhindex.py src/Products/ZCTextIndex/tests/python.txt src/Products/ZCTextIndex/tests/queryhtml.py src/Products/ZCTextIndex/tests/testHTMLSplitter.py src/Products/ZCTextIndex/tests/testIndex.py src/Products/ZCTextIndex/tests/testLexicon.py src/Products/ZCTextIndex/tests/testNBest.py src/Products/ZCTextIndex/tests/testParseTree.py src/Products/ZCTextIndex/tests/testPipelineFactory.py src/Products/ZCTextIndex/tests/testQueryEngine.py src/Products/ZCTextIndex/tests/testQueryParser.py src/Products/ZCTextIndex/tests/testSetOps.py src/Products/ZCTextIndex/tests/testStopper.py src/Products/ZCTextIndex/tests/testZCTextIndex.py src/Products/ZCTextIndex/tests/wordstats.py src/Products/ZCTextIndex/www/index.gif src/Products/ZCTextIndex/www/lexicon.gifzope2.13-2.13.21/source/Products.ZCTextIndex/pip-egg-info/Products.ZCTextIndex.egg-info/not-zip-safe0000644000175000017500000000000112214017454031566 0ustar arnauarnau zope2.13-2.13.21/source/Products.ZCTextIndex/LICENSE.txt0000644000175000017500000000402612214017454021237 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/Products.ZCTextIndex/README.txt0000644000175000017500000000020412214017454021104 0ustar arnauarnauOverview ======== This distribution contains a full text indexing facility for Zope 2 and more specifically for Products.ZCatalog. zope2.13-2.13.21/source/Products.ZCTextIndex/setup.cfg0000644000175000017500000000007312214017454021233 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/Products.ZCTextIndex/MANIFEST.in0000644000175000017500000000020412214017454021144 0ustar arnauarnauinclude *.txt recursive-include src/Products * global-exclude *.dll global-exclude *.pyc global-exclude *.pyo global-exclude *.so zope2.13-2.13.21/source/Products.ZCTextIndex/COPYRIGHT.txt0000644000175000017500000000004012214017454021515 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/Products.ZCTextIndex/buildout.cfg0000644000175000017500000000050512214017454021722 0ustar arnauarnau[buildout] extends = http://download.zope.org/Zope2/index/2.13.19/versions.cfg develop = . parts = interpreter test versions = versions [versions] Products.ZCTextIndex = [interpreter] recipe = zc.recipe.egg interpreter = python eggs = Products.ZCTextIndex [test] recipe = zc.recipe.testrunner eggs = Products.ZCTextIndex zope2.13-2.13.21/source/Products.ZCTextIndex/bootstrap.py0000644000175000017500000000742012214017454022004 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 108946 2010-02-12 02:40:18Z yusei $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/Products.ZCTextIndex/CHANGES.txt0000644000175000017500000000213112214017454021220 0ustar arnauarnauChangelog ========= 2.13.4 (2012-12-03) ------------------- - Fixed problem where the index was not reindexed if the new value was an empty string leading to inconsistence between the object attribute (that is empty) and the index that still contains the old indexed value. 2.13.3 (2011-07-28) ------------------- - Fixed problem in reindex document optimization, which could lead to negative document counts when reindexing unchanged documents. 2.13.2 (2011-05-04) ------------------- - Avoid changing data, if the indexed values stayed the same. 2.13.1 (2010-10-02) ------------------- - Changed word id creation algorithm in Lexicon. Instead of relying on an increasing length counter, we use a number from a randomized range. This avoids conflict errors while adding new words in multiple parallel transactions. Inspired by code from ``enfold.fixes``. - Lexicon: Added clear method. - Lexicon: Removed BBB code for instances created with Zope < 2.6.2. - Added missing namespace_packages declaration to setup.py. 2.13.0 (2010-06-19) ------------------- - Released as separate package. zope2.13-2.13.21/source/Products.ZCTextIndex/src/0000755000175000017500000000000012214017454020201 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCTextIndex/src/Products.ZCTextIndex.egg-info/0000755000175000017500000000000012214017454025646 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCTextIndex/src/Products.ZCTextIndex.egg-info/PKG-INFO0000644000175000017500000000356012214017454026747 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.ZCTextIndex Version: 2.13.4 Summary: Full text indexing for ZCatalog / Zope 2. Home-page: http://pypi.python.org/pypi/Products.ZCTextIndex Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== This distribution contains a full text indexing facility for Zope 2 and more specifically for Products.ZCatalog. Changelog ========= 2.13.4 (2012-12-03) ------------------- - Fixed problem where the index was not reindexed if the new value was an empty string leading to inconsistence between the object attribute (that is empty) and the index that still contains the old indexed value. 2.13.3 (2011-07-28) ------------------- - Fixed problem in reindex document optimization, which could lead to negative document counts when reindexing unchanged documents. 2.13.2 (2011-05-04) ------------------- - Avoid changing data, if the indexed values stayed the same. 2.13.1 (2010-10-02) ------------------- - Changed word id creation algorithm in Lexicon. Instead of relying on an increasing length counter, we use a number from a randomized range. This avoids conflict errors while adding new words in multiple parallel transactions. Inspired by code from ``enfold.fixes``. - Lexicon: Added clear method. - Lexicon: Removed BBB code for instances created with Zope < 2.6.2. - Added missing namespace_packages declaration to setup.py. 2.13.0 (2010-06-19) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products.ZCTextIndex.egg-info/dependency_links.txt0000644000175000017500000000000112214017454031714 0ustar arnauarnau zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products.ZCTextIndex.egg-info/requires.txt0000644000175000017500000000016012214017454030243 0ustar arnauarnausetuptools AccessControl Acquisition transaction Persistence zExceptions ZODB3 Zope2 >= 2.13.0dev zope.interface././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.ZCTextIndex/src/Products.ZCTextIndex.egg-info/namespace_packages.txtzope2.13-2.13.21/source/Products.ZCTextIndex/src/Products.ZCTextIndex.egg-info/namespace_packages.tx0000644000175000017500000000001112214017454032005 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products.ZCTextIndex.egg-info/top_level.txt0000644000175000017500000000001112214017454030370 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products.ZCTextIndex.egg-info/SOURCES.txt0000644000175000017500000000541712214017454027541 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt MANIFEST.in README.txt bootstrap.py buildout.cfg setup.py src/Products/__init__.py src/Products.ZCTextIndex.egg-info/PKG-INFO src/Products.ZCTextIndex.egg-info/SOURCES.txt src/Products.ZCTextIndex.egg-info/dependency_links.txt src/Products.ZCTextIndex.egg-info/namespace_packages.txt src/Products.ZCTextIndex.egg-info/not-zip-safe src/Products.ZCTextIndex.egg-info/requires.txt src/Products.ZCTextIndex.egg-info/top_level.txt src/Products/ZCTextIndex/BaseIndex.py src/Products/ZCTextIndex/CosineIndex.py src/Products/ZCTextIndex/HTMLSplitter.py src/Products/ZCTextIndex/IIndex.py src/Products/ZCTextIndex/INBest.py src/Products/ZCTextIndex/IPipelineElement.py src/Products/ZCTextIndex/IPipelineElementFactory.py src/Products/ZCTextIndex/IQueryParseTree.py src/Products/ZCTextIndex/IQueryParser.py src/Products/ZCTextIndex/ISplitter.py src/Products/ZCTextIndex/Lexicon.py src/Products/ZCTextIndex/NBest.py src/Products/ZCTextIndex/OkapiIndex.py src/Products/ZCTextIndex/ParseTree.py src/Products/ZCTextIndex/PipelineFactory.py src/Products/ZCTextIndex/QueryParser.py src/Products/ZCTextIndex/README.txt src/Products/ZCTextIndex/RiceCode.py src/Products/ZCTextIndex/SETUP.cfg src/Products/ZCTextIndex/SetOps.py src/Products/ZCTextIndex/Setup src/Products/ZCTextIndex/StopDict.py src/Products/ZCTextIndex/WidCode.py src/Products/ZCTextIndex/ZCTextIndex.py src/Products/ZCTextIndex/__init__.py src/Products/ZCTextIndex/interfaces.py src/Products/ZCTextIndex/okascore.c src/Products/ZCTextIndex/stopper.c src/Products/ZCTextIndex/dtml/addLexicon.dtml src/Products/ZCTextIndex/dtml/addZCTextIndex.dtml src/Products/ZCTextIndex/dtml/manageLexicon.dtml src/Products/ZCTextIndex/dtml/manageZCTextIndex.dtml src/Products/ZCTextIndex/dtml/queryLexicon.dtml src/Products/ZCTextIndex/help/Lexicon_Add.stx src/Products/ZCTextIndex/help/ZCTextIndex_Add.stx src/Products/ZCTextIndex/tests/__init__.py src/Products/ZCTextIndex/tests/hs-tool.py src/Products/ZCTextIndex/tests/indexhtml.py src/Products/ZCTextIndex/tests/mailtest.py src/Products/ZCTextIndex/tests/mhindex.py src/Products/ZCTextIndex/tests/python.txt src/Products/ZCTextIndex/tests/queryhtml.py src/Products/ZCTextIndex/tests/testHTMLSplitter.py src/Products/ZCTextIndex/tests/testIndex.py src/Products/ZCTextIndex/tests/testLexicon.py src/Products/ZCTextIndex/tests/testNBest.py src/Products/ZCTextIndex/tests/testParseTree.py src/Products/ZCTextIndex/tests/testPipelineFactory.py src/Products/ZCTextIndex/tests/testQueryEngine.py src/Products/ZCTextIndex/tests/testQueryParser.py src/Products/ZCTextIndex/tests/testSetOps.py src/Products/ZCTextIndex/tests/testStopper.py src/Products/ZCTextIndex/tests/testZCTextIndex.py src/Products/ZCTextIndex/tests/wordstats.py src/Products/ZCTextIndex/www/index.gif src/Products/ZCTextIndex/www/lexicon.gifzope2.13-2.13.21/source/Products.ZCTextIndex/src/Products.ZCTextIndex.egg-info/not-zip-safe0000644000175000017500000000000112214017454030074 0ustar arnauarnau zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/0000755000175000017500000000000012214017454022004 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/0000755000175000017500000000000012214017454024155 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/okascore.c0000644000175000017500000000716612214017454026141 0ustar arnauarnau/***************************************************************************** Copyright (c) 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /* okascore.c * * The inner scoring loop of OkapiIndex._search_wids() coded in C. * * Example from an indexed Python-Dev archive, where "python" shows up in all * but 2 of the 19,058 messages. With the Python scoring loop, * * query: python * # results: 10 of 19056 in 534.77 ms * query: python * # results: 10 of 19056 in 277.52 ms * * The first timing is cold, the second timing from an immediate repeat of * the same query. With the scoring loop here in C: * * query: python * # results: 10 of 19056 in 380.74 ms -- 40% speedup * query: python * # results: 10 of 19056 in 118.96 ms -- 133% speedup */ #include "Python.h" #define K1 1.2 #define B 0.75 #ifndef PyTuple_CheckExact #define PyTuple_CheckExact PyTuple_Check #endif static PyObject * score(PyObject *self, PyObject *args) { /* Believe it or not, floating these common subexpressions "by hand" gets better code out of MSVC 6. */ const double B_FROM1 = 1.0 - B; const double K1_PLUS1 = K1 + 1.0; /* Inputs */ PyObject *result; /* IIBucket result, maps d to score */ PyObject *d2fitems; /* ._wordinfo[t].items(), maps d to f(d, t) */ PyObject *d2len; /* ._docweight, maps d to # words in d */ double idf; /* inverse doc frequency of t */ double meandoclen; /* average number of words in a doc */ int n, i; if (!PyArg_ParseTuple(args, "OOOdd:score", &result, &d2fitems, &d2len, &idf, &meandoclen)) return NULL; idf *= 1024.0; /* float out part of the scaled_int computation */ n = PyObject_Length(d2fitems); for (i = 0; i < n; ++i) { PyObject *d_and_f; /* d2f[i], a (d, f) pair */ PyObject *d; double f; PyObject *doclen; /* ._docweight[d] */ double lenweight; double tf; PyObject *scaled_int; int status; d_and_f = PySequence_GetItem(d2fitems, i); if (d_and_f == NULL) return NULL; if (!(PyTuple_CheckExact(d_and_f) && PyTuple_GET_SIZE(d_and_f) == 2)) { PyErr_SetString(PyExc_TypeError, "d2fitems must produce 2-item tuples"); Py_DECREF(d_and_f); return NULL; } d = PyTuple_GET_ITEM(d_and_f, 0); f = (double)PyInt_AsLong(PyTuple_GET_ITEM(d_and_f, 1)); doclen = PyObject_GetItem(d2len, d); if (doclen == NULL) { Py_DECREF(d_and_f); return NULL; } lenweight = B_FROM1 + B * PyInt_AS_LONG(doclen) / meandoclen; tf = f * K1_PLUS1 / (f + K1 * lenweight); scaled_int = PyInt_FromLong((long)(tf * idf + 0.5)); if (scaled_int == NULL) status = -1; else status = PyObject_SetItem(result, d, scaled_int); Py_DECREF(d_and_f); Py_DECREF(doclen); Py_XDECREF(scaled_int); if (status < 0) return NULL; } Py_INCREF(Py_None); return Py_None; } static char score__doc__[] = "score(result, d2fitems, d2len, idf, meandoclen)\n" "\n" "Do the inner scoring loop for an Okapi index.\n"; static PyMethodDef okascore_functions[] = { {"score", score, METH_VARARGS, score__doc__}, {NULL} }; void initokascore(void) { PyObject *m; m = Py_InitModule3("okascore", okascore_functions, "inner scoring loop for Okapi rank"); } zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/PipelineFactory.py0000644000175000017500000000334712214017454027633 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from zope.interface import implements from Products.ZCTextIndex.interfaces import IPipelineElementFactory class PipelineElementFactory: implements(IPipelineElementFactory) def __init__(self): self._groups = {} def registerFactory(self, group, name, factory): if self._groups.has_key(group) and \ self._groups[group].has_key(name): raise ValueError('ZCTextIndex lexicon element "%s" ' 'already registered in group "%s"' % (name, group)) elements = self._groups.get(group) if elements is None: elements = self._groups[group] = {} elements[name] = factory def getFactoryGroups(self): groups = self._groups.keys() groups.sort() return groups def getFactoryNames(self, group): names = self._groups[group].keys() names.sort() return names def instantiate(self, group, name): factory = self._groups[group][name] if factory is not None: return factory() element_factory = PipelineElementFactory() zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/ZCTextIndex.py0000644000175000017500000003342212214017454026704 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Plug in text index for ZCatalog with relevance ranking. $Id: ZCTextIndex.py 128486 2012-12-03 10:08:37Z hannosch $ """ from cgi import escape from AccessControl.class_init import InitializeClass from AccessControl.Permissions import manage_vocabulary from AccessControl.Permissions import manage_zcatalog_indexes from AccessControl.Permissions import query_vocabulary from AccessControl.Permissions import search_zcatalog from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import aq_base from Acquisition import aq_inner from Acquisition import aq_parent from Acquisition import Implicit from App.special_dtml import DTMLFile from OFS.SimpleItem import SimpleItem from Persistence import Persistent from zope.interface import implements from Products.PluginIndexes.common.util import parseIndexRequest from Products.PluginIndexes.common import safe_callable from Products.PluginIndexes.interfaces import IPluggableIndex from Products.ZCTextIndex.Lexicon import CaseNormalizer from Products.ZCTextIndex.Lexicon import Lexicon from Products.ZCTextIndex.Lexicon import Splitter from Products.ZCTextIndex.Lexicon import StopWordRemover from Products.ZCTextIndex.NBest import NBest from Products.ZCTextIndex.QueryParser import QueryParser from Products.ZCTextIndex.CosineIndex import CosineIndex from Products.ZCTextIndex.interfaces import ILexicon from Products.ZCTextIndex.interfaces import IZCLexicon from Products.ZCTextIndex.interfaces import IZCTextIndex from Products.ZCTextIndex.OkapiIndex import OkapiIndex from Products.ZCTextIndex.PipelineFactory import element_factory index_types = {'Okapi BM25 Rank':OkapiIndex, 'Cosine Measure':CosineIndex} class ZCTextIndex(Persistent, Implicit, SimpleItem): """Persistent text index. """ implements(IZCTextIndex, IPluggableIndex) ## Magic class attributes ## meta_type = 'ZCTextIndex' query_options = ('query',) manage_options = ( {'label': 'Overview', 'action': 'manage_main'}, ) security = ClassSecurityInfo() security.declareObjectProtected(manage_zcatalog_indexes) ## Constructor ## def __init__(self, id, extra=None, caller=None, index_factory=None, field_name=None, lexicon_id=None): self.id = id # Arguments can be passed directly to the constructor or # via the silly "extra" record. self._fieldname = field_name or getattr(extra, 'doc_attr', '') or id self._indexed_attrs = self._fieldname.split(',') self._indexed_attrs = [ attr.strip() for attr in self._indexed_attrs if attr ] lexicon_id = lexicon_id or getattr(extra, 'lexicon_id', '') lexicon = getattr(caller, lexicon_id, None) if lexicon is None: raise LookupError, 'Lexicon "%s" not found' % escape(lexicon_id) if not ILexicon.providedBy(lexicon): raise ValueError('Object "%s" does not implement ' 'ZCTextIndex Lexicon interface' % lexicon.getId()) self.lexicon_id = lexicon.getId() self._v_lexicon = lexicon if index_factory is None: if extra.index_type not in index_types.keys(): raise ValueError, 'Invalid index type "%s"' % escape( extra.index_type) self._index_factory = index_types[extra.index_type] self._index_type = extra.index_type else: self._index_factory = index_factory self.index = self._index_factory(aq_base(self.getLexicon())) ## Private Methods ## security.declarePrivate('getLexicon') def getLexicon(self): """Get the lexicon for this index """ if hasattr(aq_base(self), 'lexicon'): # Fix up old ZCTextIndexes by removing direct lexicon ref # and changing it to an ID lexicon = getattr(aq_parent(aq_inner(self)), self.lexicon.getId()) self.lexicon_id = lexicon.getId() del self.lexicon if getattr(aq_base(self), 'lexicon_path', None): # Fix up slightly less old ZCTextIndexes by removing # the physical path and changing it to an ID. # There's no need to use a physical path, which otherwise # makes it difficult to move or rename ZCatalogs. self.lexicon_id = self.lexicon_path[-1] del self.lexicon_path try: return self._v_lexicon except AttributeError: lexicon = getattr(aq_parent(aq_inner(self)), self.lexicon_id) if not ILexicon.providedBy(lexicon): raise TypeError('Object "%s" is not a ZCTextIndex Lexicon' % repr(lexicon)) self._v_lexicon = lexicon return lexicon ## External methods not in the Pluggable Index API ## security.declareProtected(search_zcatalog, 'query') def query(self, query, nbest=10): """Return pair (mapping from docids to scores, num results). The num results is the total number of results before trimming to the nbest results. """ tree = QueryParser(self.getLexicon()).parseQuery(query) results = tree.executeQuery(self.index) if results is None: return [], 0 chooser = NBest(nbest) chooser.addmany(results.items()) return chooser.getbest(), len(results) ## Pluggable Index APIs ## def index_object(self, documentId, obj, threshold=None): """Wrapper for index_doc() handling indexing of multiple attributes. Enter the document with the specified documentId in the index under the terms extracted from the indexed text attributes, each of which should yield either a string or a list of strings (Unicode or otherwise) to be passed to index_doc(). """ # TODO we currently ignore subtransaction threshold # needed for backward compatibility fields = getattr(self, '_indexed_attrs', [self._fieldname]) all_texts = [] for attr in fields: text = getattr(obj, attr, None) if text is None: continue if safe_callable(text): text = text() if text is not None: if isinstance(text, (list, tuple, set)): all_texts.extend(text) else: all_texts.append(text) # Check that we're sending only strings all_texts = [t for t in all_texts if isinstance(t, basestring)] if all_texts: return self.index.index_doc(documentId, all_texts) return 0 def unindex_object(self, docid): if self.index.has_doc(docid): self.index.unindex_doc(docid) def _apply_index(self, request): """Apply query specified by request, a mapping containing the query. Returns two object on success, the resultSet containing the matching record numbers and a tuple containing the names of the fields used Returns None if request is not valid for this index. """ record = parseIndexRequest(request, self.id, self.query_options) if record.keys is None: return None query_str = ' '.join(record.keys) if not query_str: return None tree = QueryParser(self.getLexicon()).parseQuery(query_str) results = tree.executeQuery(self.index) return results, (self.id,) def getEntryForObject(self, documentId, default=None): """Return the list of words indexed for documentId""" try: word_ids = self.index.get_words(documentId) except KeyError: return default get_word = self.getLexicon().get_word return [get_word(wid) for wid in word_ids] def uniqueValues(self, name=None, withLengths=0): raise NotImplementedError ## The ZCatalog Index management screen uses these methods ## def numObjects(self): """Return number of unique words in the index""" return self.index.length() def indexSize(self): """Return the number of indexes objects """ return self.index.document_count() def clear(self): """reinitialize the index (but not the lexicon)""" try: # Remove the cached reference to the lexicon # So that it is refreshed del self._v_lexicon except (AttributeError, KeyError): pass self.index = self._index_factory(aq_base(self.getLexicon())) ## User Interface Methods ## manage_main = DTMLFile('dtml/manageZCTextIndex', globals()) def getIndexSourceNames(self): """Return sequence of names of indexed attributes""" try: return self._indexed_attrs except: return [self._fieldname] def getIndexType(self): """Return index type string""" return getattr(self, '_index_type', self._index_factory.__name__) def getLexiconURL(self): """Return the url of the lexicon used by the index""" try: lex = self.getLexicon() except (KeyError, AttributeError): return None else: return lex.absolute_url() InitializeClass(ZCTextIndex) def manage_addZCTextIndex(self, id, extra=None, REQUEST=None, RESPONSE=None): """Add a text index""" if REQUEST is None: URL3 = None else: URL3 = REQUEST.URL3 return self.manage_addIndex(id, 'ZCTextIndex', extra, REQUEST, RESPONSE, URL3) manage_addZCTextIndexForm = DTMLFile('dtml/addZCTextIndex', globals()) manage_addLexiconForm = DTMLFile('dtml/addLexicon', globals()) def manage_addLexicon(self, id, title='', elements=[], REQUEST=None): """Add ZCTextIndex Lexicon""" pipeline = [] for el_record in elements: if not hasattr(el_record, 'name'): continue # Skip over records that only specify element group element = element_factory.instantiate(el_record.group, el_record.name) if element is not None: if el_record.group == 'Word Splitter': # I don't like hardcoding this, but its a simple solution # to get the splitter element first in the pipeline pipeline.insert(0, element) else: pipeline.append(element) lexicon = PLexicon(id, title, *pipeline) self._setObject(id, lexicon) if REQUEST is not None: return self.manage_main(self, REQUEST, update_menu=1) # I am borrowing the existing vocabulary permissions for now to avoid # adding new permissions. This may change when old style Vocabs go away LexiconQueryPerm = query_vocabulary LexiconMgmtPerm = manage_vocabulary class PLexicon(Lexicon, Implicit, SimpleItem): """Lexicon for ZCTextIndex. """ implements(IZCLexicon) meta_type = 'ZCTextIndex Lexicon' manage_options = ({'label':'Overview', 'action':'manage_main'}, {'label':'Query', 'action':'queryLexicon'}, ) + SimpleItem.manage_options security = ClassSecurityInfo() security.declareObjectProtected(LexiconQueryPerm) def __init__(self, id, title='', *pipeline): self.id = str(id) self.title = str(title) PLexicon.inheritedAttribute('__init__')(self, *pipeline) ## User Interface Methods ## def getPipelineNames(self): """Return list of names of pipeline element classes""" return [element.__class__.__name__ for element in self._pipeline] _queryLexicon = DTMLFile('dtml/queryLexicon', globals()) security.declareProtected(LexiconQueryPerm, 'queryLexicon') def queryLexicon(self, REQUEST, words=None, page=0, rows=20, cols=4): """Lexicon browser/query user interface """ if words: wids = [] for word in self.parseTerms(words): wids.extend(self.globToWordIds(word)) words = [self.get_word(wid) for wid in wids] else: words = self.words() word_count = len(words) rows = max(min(rows, 500), 1) cols = max(min(cols, 12), 1) page_count = word_count / (rows * cols) + \ (word_count % (rows * cols) > 0) page = max(min(page, page_count - 1), 0) start = rows * cols * page end = min(rows * cols * (page + 1), word_count) if word_count: words = list(words[start:end]) else: words = [] columns = [] i = 0 while i < len(words): columns.append(words[i:i + rows]) i += rows info = dict(page=page, rows=rows, cols=cols, start_word=start+1, end_word=end, word_count=word_count, page_count=page_count, page_range=xrange(page_count), page_columns=columns) if REQUEST is not None: return self._queryLexicon(self, REQUEST, **info) return info security.declareProtected(LexiconMgmtPerm, 'manage_main') manage_main = DTMLFile('dtml/manageLexicon', globals()) InitializeClass(PLexicon) zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/SETUP.cfg0000644000175000017500000000015312214017454025535 0ustar arnauarnau source okascore.c source stopper.c zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/RiceCode.py0000644000175000017500000001400112214017454026200 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Rice coding (a variation of Golomb coding) Based on a Java implementation by Glen McCluskey described in a Usenix ;login: article at http://www.usenix.org/publications/login/2000-4/features/java.html McCluskey's article explains the approach as follows. The encoding for a value x is represented as a unary part and a binary part. The unary part is a sequence of 1 bits followed by a 0 bit. The binary part encodes some of the lower bits of x-1. The encoding is parameterized by a value m that describes how many bits to store in the binary part. If most of the values are smaller than 2**m then they can be stored in only m+1 bits. Compute the length of the unary part, q, where q = math.floor((x-1)/ 2 ** m) Emit q 1 bits followed by a 0 bit. Emit the lower m bits of x-1, treating x-1 as a binary value. """ import array class BitArray: def __init__(self, buf=None): self.bytes = array.array('B') self.nbits = 0 self.bitsleft = 0 self.tostring = self.bytes.tostring def __getitem__(self, i): byte, offset = divmod(i, 8) mask = 2 ** offset if self.bytes[byte] & mask: return 1 else: return 0 def __setitem__(self, i, val): byte, offset = divmod(i, 8) mask = 2 ** offset if val: self.bytes[byte] |= mask else: self.bytes[byte] &= ~mask def __len__(self): return self.nbits def append(self, bit): """Append a 1 if bit is true or 1 if it is false.""" if self.bitsleft == 0: self.bytes.append(0) self.bitsleft = 8 self.__setitem__(self.nbits, bit) self.nbits += 1 self.bitsleft -= 1 def __getstate__(self): return self.nbits, self.bitsleft, self.tostring() def __setstate__(self, (nbits, bitsleft, s)): self.bytes = array.array('B', s) self.nbits = nbits self.bitsleft = bitsleft class RiceCode: def __init__(self, m): """Constructor a RiceCode for m-bit values.""" if not (0 <= m <= 16): raise ValueError, "m must be between 0 and 16" self.init(m) self.bits = BitArray() self.len = 0 def init(self, m): self.m = m self.lower = (1 << m) - 1 self.mask = 1 << (m - 1) def append(self, val): """Append an item to the list.""" if val < 1: raise ValueError, "value >= 1 expected, got %s" % `val` val -= 1 # emit the unary part of the code q = val >> self.m for i in range(q): self.bits.append(1) self.bits.append(0) # emit the binary part r = val & self.lower mask = self.mask while mask: self.bits.append(r & mask) mask >>= 1 self.len += 1 def __len__(self): return self.len def tolist(self): """Return the items as a list.""" l = [] i = 0 # bit offset binary_range = range(self.m) for j in range(self.len): unary = 0 while self.bits[i] == 1: unary += 1 i += 1 assert self.bits[i] == 0 i += 1 binary = 0 for k in binary_range: binary = (binary << 1) | self.bits[i] i += 1 l.append((unary << self.m) + (binary + 1)) return l def tostring(self): """Return a binary string containing the encoded data. The binary string may contain some extra zeros at the end. """ return self.bits.tostring() def __getstate__(self): return self.m, self.bits def __setstate__(self, (m, bits)): self.init(m) self.bits = bits def encode(m, l): c = RiceCode(m) for elt in l: c.append(elt) assert c.tolist() == l return c def encode_deltas(l): if len(l) == 1: return l[0], [] deltas = RiceCode(6) deltas.append(l[1] - l[0]) for i in range(2, len(l)): deltas.append(l[i] - l[i - 1]) return l[0], deltas def decode_deltas(start, enc_deltas): deltas = enc_deltas.tolist() l = [start] for i in range(1, len(deltas)): l.append(l[i-1] + deltas[i]) l.append(l[-1] + deltas[-1]) return l def test(): import random for size in [10, 20, 50, 100, 200]: l = [random.randint(1, size) for i in range(50)] c = encode(random.randint(1, 16), l) assert c.tolist() == l for size in [10, 20, 50, 100, 200]: l = range(random.randint(1, size), size + random.randint(1, size)) t = encode_deltas(l) l2 = decode_deltas(*t) assert l == l2 if l != l2: print l print l2 def pickle_efficiency(): import pickle import random for m in [4, 8, 12]: for size in [10, 20, 50, 100, 200, 500, 1000, 2000, 5000]: for elt_range in [10, 20, 50, 100, 200, 500, 1000]: l = [random.randint(1, elt_range) for i in range(size)] raw = pickle.dumps(l, 1) enc = pickle.dumps(encode(m, l), 1) print "m=%2d size=%4d range=%4d" % (m, size, elt_range), print "%5d %5d" % (len(raw), len(enc)), if len(raw) > len(enc): print "win" else: print "lose" if __name__ == "__main__": test() zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/WidCode.py0000644000175000017500000000770612214017454026057 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # A byte-aligned encoding for lists of non-negative ints, using fewer bytes # for smaller ints. This is intended for lists of word ids (wids). The # ordinary string .find() method can be used to find the encoded form of a # desired wid-string in an encoded wid-string. As in UTF-8, the initial byte # of an encoding can't appear in the interior of an encoding, so find() can't # be fooled into starting a match "in the middle" of an encoding. Unlike # UTF-8, the initial byte does not tell you how many continuation bytes # follow; and there's no ASCII superset property. # Details: # # + Only the first byte of an encoding has the sign bit set. # # + The first byte has 7 bits of data. # # + Bytes beyond the first in an encoding have the sign bit clear, followed # by 7 bits of data. # # + The first byte doesn't tell you how many continuation bytes are # following. You can tell by searching for the next byte with the # high bit set (or the end of the string). # # The int to be encoded can contain no more than 28 bits. # # If it contains no more than 7 bits, 0abcdefg, the encoding is # 1abcdefg # # If it contains 8 thru 14 bits, # 00abcdef ghijkLmn # the encoding is # 1abcdefg 0hijkLmn # # Static tables _encoding and _decoding capture all encodes and decodes for # 14 or fewer bits. # # If it contains 15 thru 21 bits, # 000abcde fghijkLm nopqrstu # the encoding is # 1abcdefg 0hijkLmn 0opqrstu # # If it contains 22 thru 28 bits, # 0000abcd efghijkL mnopqrst uvwxyzAB # the encoding is # 1abcdefg 0hijkLmn 0opqrstu 0vwxyzAB assert 0x80**2 == 0x4000 assert 0x80**4 == 0x10000000 import re def encode(wids): # Encode a list of wids as a string. wid2enc = _encoding n = len(wid2enc) return "".join([w < n and wid2enc[w] or _encode(w) for w in wids]) _encoding = [None] * 0x4000 # Filled later, and converted to a tuple def _encode(w): assert 0x4000 <= w < 0x10000000 b, c = divmod(w, 0x80) a, b = divmod(b, 0x80) s = chr(b) + chr(c) if a < 0x80: # no more than 21 data bits return chr(a + 0x80) + s a, b = divmod(a, 0x80) assert a < 0x80, (w, a, b, s) # else more than 28 data bits return (chr(a + 0x80) + chr(b)) + s _prog = re.compile(r"[\x80-\xFF][\x00-\x7F]*") def decode(code): # Decode a string into a list of wids. get = _decoding.get # Obscure: while _decoding does have the key '\x80', its value is 0, # so the "or" here calls _decode('\x80') anyway. return [get(p) or _decode(p) for p in _prog.findall(code)] _decoding = {} # Filled later def _decode(s): if s == '\x80': # See comment in decode(). This is here to allow a trick to work. return 0 if len(s) == 3: a, b, c = map(ord, s) assert a & 0x80 == 0x80 and not b & 0x80 and not c & 0x80 return ((a & 0x7F) << 14) | (b << 7) | c assert len(s) == 4, `s` a, b, c, d = map(ord, s) assert a & 0x80 == 0x80 and not b & 0x80 and not c & 0x80 and not d & 0x80 return ((a & 0x7F) << 21) | (b << 14) | (c << 7) | d def _fill(): global _encoding for i in range(0x80): s = chr(i + 0x80) _encoding[i] = s _decoding[s] = i for i in range(0x80, 0x4000): hi, lo = divmod(i, 0x80) s = chr(hi + 0x80) + chr(lo) _encoding[i] = s _decoding[s] = i _encoding = tuple(_encoding) _fill() zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/ParseTree.py0000644000175000017500000000721112214017454026422 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Generic parser support: exception and parse tree nodes.""" from BTrees.IIBTree import difference from zope.interface import implements from Products.ZCTextIndex.interfaces import IQueryParseTree from Products.ZCTextIndex.SetOps import mass_weightedIntersection from Products.ZCTextIndex.SetOps import mass_weightedUnion class QueryError(Exception): pass class ParseError(Exception): pass class ParseTreeNode: implements(IQueryParseTree) _nodeType = None def __init__(self, value): self._value = value def nodeType(self): return self._nodeType def getValue(self): return self._value def __repr__(self): return "%s(%r)" % (self.__class__.__name__, self.getValue()) def terms(self): t = [] for v in self.getValue(): t.extend(v.terms()) return t def executeQuery(self, index): raise NotImplementedError class NotNode(ParseTreeNode): _nodeType = "NOT" def terms(self): return [] def executeQuery(self, index): raise QueryError, "NOT parse tree node cannot be executed directly" class AndNode(ParseTreeNode): _nodeType = "AND" def executeQuery(self, index): L = [] Nots = [] for subnode in self.getValue(): if subnode.nodeType() == "NOT": r = subnode.getValue().executeQuery(index) # If None, technically it matches every doc, but we treat # it as if it matched none (we want # real_word AND NOT stop_word # to act like plain real_word). if r is not None: Nots.append((r, 1)) else: r = subnode.executeQuery(index) # If None, technically it matches every doc, so needn't be # included. if r is not None: L.append((r, 1)) set = mass_weightedIntersection(L) if Nots: notset = mass_weightedUnion(Nots) set = difference(set, notset) return set class OrNode(ParseTreeNode): _nodeType = "OR" def executeQuery(self, index): weighted = [] for node in self.getValue(): r = node.executeQuery(index) # If None, technically it matches every doc, but we treat # it as if it matched none (we want # real_word OR stop_word # to act like plain real_word). if r is not None: weighted.append((r, 1)) return mass_weightedUnion(weighted) class AtomNode(ParseTreeNode): _nodeType = "ATOM" def terms(self): return [self.getValue()] def executeQuery(self, index): return index.search(self.getValue()) class PhraseNode(AtomNode): _nodeType = "PHRASE" def executeQuery(self, index): return index.search_phrase(self.getValue()) class GlobNode(AtomNode): _nodeType = "GLOB" def executeQuery(self, index): return index.search_glob(self.getValue()) zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/Lexicon.py0000644000175000017500000001652212214017454026136 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Lexicon. """ from random import randrange import re from BTrees.IOBTree import IOBTree from BTrees.OIBTree import OIBTree from BTrees.Length import Length from Persistence import Persistent from zope.interface import implements from Products.ZCTextIndex.interfaces import ILexicon from Products.ZCTextIndex.StopDict import get_stopdict from Products.ZCTextIndex.ParseTree import QueryError from Products.ZCTextIndex.PipelineFactory import element_factory class Lexicon(Persistent): implements(ILexicon) _v_nextid = None _wid_length_based = True # Flag to distinguish new and old lexica def __init__(self, *pipeline): self.clear() self._pipeline = pipeline def clear(self): """Empty the lexicon. """ self.length = Length() self._wid_length_based = False self._wids = OIBTree() # word -> wid self._words = IOBTree() # wid -> word # wid 0 is reserved for words that aren't in the lexicon (OOV -- out # of vocabulary). This can happen, e.g., if a query contains a word # we never saw before, and that isn't a known stopword (or otherwise # filtered out). Returning a special wid value for OOV words is a # way to let clients know when an OOV word appears. def length(self): """Return the number of unique terms in the lexicon. """ # Overridden in instances with a BTrees.Length.Length raise NotImplementedError def words(self): return self._wids.keys() def wids(self): return self._words.keys() def items(self): return self._wids.items() def sourceToWordIds(self, text): last = _text2list(text) for element in self._pipeline: last = element.process(last) return map(self._getWordIdCreate, last) def termToWordIds(self, text): last = _text2list(text) for element in self._pipeline: process = getattr(element, "process_post_glob", element.process) last = process(last) wids = [] for word in last: wids.append(self._wids.get(word, 0)) return wids def parseTerms(self, text): last = _text2list(text) for element in self._pipeline: process = getattr(element, "processGlob", element.process) last = process(last) return last def isGlob(self, word): return "*" in word or "?" in word def get_word(self, wid): return self._words[wid] def get_wid(self, word): return self._wids.get(word, 0) def globToWordIds(self, pattern): # Implement * and ? just as in the shell, except the pattern # must not start with either of these prefix = "" while pattern and pattern[0] not in "*?": prefix += pattern[0] pattern = pattern[1:] if not pattern: # There were no globbing characters in the pattern wid = self._wids.get(prefix, 0) if wid: return [wid] else: return [] if not prefix: # The pattern starts with a globbing character. # This is too efficient, so we raise an exception. raise QueryError( "pattern %r shouldn't start with glob character" % pattern) pat = prefix for c in pattern: if c == "*": pat += ".*" elif c == "?": pat += "." else: pat += re.escape(c) pat += "$" prog = re.compile(pat) keys = self._wids.keys(prefix) # Keys starting at prefix wids = [] for key in keys: if not key.startswith(prefix): break if prog.match(key): wids.append(self._wids[key]) return wids def _getWordIdCreate(self, word): wid = self._wids.get(word) if wid is None: # WidCode requires us to use at least 0x4000 as a base number. # The algorithm in versions before 2.13 used the length as a base # number. So we don't even try to generate numbers below the # length as they are likely all taken minimum = 0x4000 if self._wid_length_based: minimum = max(self.length(), 0x4000) while True: if self._v_nextid is None: self._v_nextid = randrange(minimum, 0x10000000) wid = self._v_nextid self._v_nextid += 1 if wid not in self._words: break self._v_nextid = None self.length.change(1) self._wids[word] = wid self._words[wid] = word return wid def _text2list(text): # Helper: splitter input may be a string or a list of strings try: text + "" except Exception: return text else: return [text] # Sample pipeline elements class Splitter: import re rx = re.compile(r"(?L)\w+") rxGlob = re.compile(r"(?L)\w+[\w*?]*") # See globToWordIds() above def process(self, lst): result = [] for s in lst: result += self.rx.findall(s) return result def processGlob(self, lst): result = [] for s in lst: result += self.rxGlob.findall(s) return result element_factory.registerFactory('Word Splitter', 'Whitespace splitter', Splitter) class CaseNormalizer: def process(self, lst): return [w.lower() for w in lst] element_factory.registerFactory('Case Normalizer', 'Case Normalizer', CaseNormalizer) element_factory.registerFactory('Stop Words', ' Don\'t remove stop words', None) class StopWordRemover: dict = get_stopdict().copy() try: from Products.ZCTextIndex.stopper import process as _process except ImportError: def process(self, lst): has_key = self.dict.has_key return [w for w in lst if not has_key(w)] else: def process(self, lst): return self._process(self.dict, lst) element_factory.registerFactory('Stop Words', 'Remove listed stop words only', StopWordRemover) class StopWordAndSingleCharRemover(StopWordRemover): dict = get_stopdict().copy() for c in range(255): dict[chr(c)] = None element_factory.registerFactory('Stop Words', 'Remove listed and single char words', StopWordAndSingleCharRemover) zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/CosineIndex.py0000644000175000017500000001127612214017454026746 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Full text index with relevance ranking, using a cosine measure.""" import math from BTrees.IIBTree import IIBucket from zope.interface import implements from Products.ZCTextIndex.interfaces import IIndex from Products.ZCTextIndex.BaseIndex import BaseIndex from Products.ZCTextIndex.BaseIndex import inverse_doc_frequency from Products.ZCTextIndex.BaseIndex import scaled_int from Products.ZCTextIndex.BaseIndex import SCALE_FACTOR class CosineIndex(BaseIndex): implements(IIndex) def __init__(self, lexicon): BaseIndex.__init__(self, lexicon) # ._wordinfo for cosine is wid -> {docid -> weight}; # t -> D -> w(d, t)/W(d) # ._docweight for cosine is # docid -> W(docid) # Most of the computation for computing a relevance score for the # document occurs in the _search_wids() method. The code currently # implements the cosine similarity function described in Managing # Gigabytes, eq. 4.3, p. 187. The index_object() method # precomputes some values that are independent of the particular # query. # The equation is # # sum(for t in I(d,q): w(d,t) * w(q,t)) # cosine(d, q) = ------------------------------------- # W(d) * W(q) # # where # I(d, q) = the intersection of the terms in d and q. # # w(d, t) = 1 + log f(d, t) # computed by doc_term_weight(); for a given word t, # self._wordinfo[t] is a map from d to w(d, t). # # w(q, t) = log(1 + N/f(t)) # computed by inverse_doc_frequency() # # W(d) = sqrt(sum(for t in d: w(d, t) ** 2)) # computed by _get_frequencies(), and remembered in # self._docweight[d] # # W(q) = sqrt(sum(for t in q: w(q, t) ** 2)) # computed by self.query_weight() def _search_wids(self, wids): if not wids: return [] N = float(self.document_count()) L = [] DictType = type({}) for wid in wids: assert self._wordinfo.has_key(wid) # caller responsible for OOV d2w = self._wordinfo[wid] # maps docid to w(docid, wid) idf = inverse_doc_frequency(len(d2w), N) # an unscaled float #print "idf = %.3f" % idf if isinstance(d2w, DictType): d2w = IIBucket(d2w) L.append((d2w, scaled_int(idf))) return L def query_weight(self, terms): wids = [] for term in terms: wids += self._lexicon.termToWordIds(term) N = float(self.document_count()) sum = 0.0 for wid in self._remove_oov_wids(wids): wt = inverse_doc_frequency(len(self._wordinfo[wid]), N) sum += wt ** 2.0 return scaled_int(math.sqrt(sum)) def _get_frequencies(self, wids): d = {} dget = d.get for wid in wids: d[wid] = dget(wid, 0) + 1 Wsquares = 0.0 for wid, count in d.items(): w = doc_term_weight(count) Wsquares += w * w d[wid] = w W = math.sqrt(Wsquares) #print "W = %.3f" % W for wid, weight in d.items(): #print i, ":", "%.3f" % weight, d[wid] = scaled_int(weight / W) #print "->", d[wid] return d, scaled_int(W) # The rest are helper methods to support unit tests def _get_wdt(self, d, t): wid, = self._lexicon.termToWordIds(t) map = self._wordinfo[wid] return map.get(d, 0) * self._docweight[d] / SCALE_FACTOR def _get_Wd(self, d): return self._docweight[d] def _get_ft(self, t): wid, = self._lexicon.termToWordIds(t) return len(self._wordinfo[wid]) def _get_wt(self, t): wid, = self._lexicon.termToWordIds(t) map = self._wordinfo[wid] return scaled_int(math.log(1 + len(self._docweight) / float(len(map)))) def doc_term_weight(count): """Return the doc-term weight for a term that appears count times.""" # implements w(d, t) = 1 + log f(d, t) return 1.0 + math.log(count) zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/ISplitter.py0000644000175000017500000000127012214017454026446 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from Products.ZCTextIndex.interfaces import ISplitter # BBB zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/www/0000755000175000017500000000000012214017454025001 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/www/index.gif0000644000175000017500000000015712214017454026602 0ustar arnauarnauGIF89aÂÿÿÿfffÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ,4º²0‚0ž”"ßk7È`Ø-#T6åä@TÚšZÊÛ(ÂóGš;ªZ>Ôë5©CŸØ,ç‰$;zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/www/lexicon.gif0000644000175000017500000000055412214017454027135 0ustar arnauarnauGIF89a¥@{ù÷¶ºf~‚‚‚ ¾ ƒ~VFBN¶¾^®Â^úü~íFGÙ u}‚Šöòøú‚åù÷YcW˜†BB¾BF~ŠJg÷úúy³º´^B³¾´¯¶¿~&*µ uB<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ,‰@ˆpÙdˆÈ!çSð“ óBõl’›Iza0.NbFâÙ2$ßê2¸Þ —Z¸B—…¤9çÖï o}jn|\Bto` C ŽD~¥ k© I© PÄÆPA;zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/IIndex.py0000644000175000017500000000131612214017454025710 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Index Interface.""" from Products.ZCTextIndex.interfaces import IIndex # BBB zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/interfaces.py0000644000175000017500000002373412214017454026663 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZCTextIndex interfaces. """ from zope.interface import Interface class IZCTextIndex(Interface): """Persistent text index. """ class ILexicon(Interface): """Object responsible for converting text to word identifiers. """ def clear(): """Empty the lexicon. """ def termToWordIds(text): """Return a sequence of ids of the words parsed from the text. The input text may be either a string or a list of strings. Parse the text as if they are search terms, and skips words that aren't in the lexicon. """ def sourceToWordIds(text): """Return a sequence of ids of the words parsed from the text. The input text may be either a string or a list of strings. Parse the text as if they come from a source document, and creates new word ids for words that aren't (yet) in the lexicon. """ def globToWordIds(pattern): """Return a sequence of ids of words matching the pattern. The argument should be a single word using globbing syntax, e.g. 'foo*' meaning anything starting with 'foo'. Return the wids for all words in the lexicon that match the pattern. """ def length(): """Return the number of unique terms in the lexicon. """ def get_word(wid): """Return the word for the given word id. Raise KeyError if the word id is not in the lexicon. """ def get_wid(word): """Return the wird id for the given word. Return 0 of the word is not in the lexicon. """ def parseTerms(text): """Pass the text through the pipeline. Return a list of words, normalized by the pipeline (e.g. stopwords removed, case normalized etc.). """ def isGlob(word): """Return true if the word is a globbing pattern. The word should be one of the words returned by parseTerm(). """ class IZCLexicon(Interface): """Lexicon for ZCTextIndex. """ class ISplitter(Interface): """A splitter.""" def process(text): """Run the splitter over the input text, returning a list of terms. """ class IPipelineElement(Interface): def process(source): """Provide a text processing step. Process a source sequence of words into a result sequence. """ def processGlob(source): """Process, passing through globbing metacharaters. This is an optional method; if it is not used, process() is used. """ class IPipelineElementFactory(Interface): """Class for creating pipeline elements by name""" def registerFactory(group, name, factory): """Registers a pipeline factory by name and element group. Each name can be registered only once for a given group. Duplicate registrations will raise a ValueError """ def getFactoryGroups(): """Returns a sorted list of element group names """ def getFactoryNames(group): """Returns a sorted list of registered pipeline factory names in the specified element group """ def instantiate(group, name): """Instantiates a pipeline element by group and name. If name is not registered raise a KeyError. """ class IQueryParseTree(Interface): """Interface for parse trees returned by parseQuery().""" def nodeType(): """Return the node type. This is one of 'AND', 'OR', 'NOT', 'ATOM', 'PHRASE' or 'GLOB'. """ def getValue(): """Return a node-type specific value. For node type: Return: 'AND' a list of parse trees 'OR' a list of parse trees 'NOT' a parse tree 'ATOM' a string (representing a single search term) 'PHRASE' a string (representing a search phrase) 'GLOB' a string (representing a pattern, e.g. "foo*") """ def terms(): """Return a list of all terms in this node, excluding NOT subtrees.""" def executeQuery(index): """Execute the query represented by this node against the index. The index argument must implement the IIndex interface. Return an IIBucket or IIBTree mapping document ids to scores (higher scores mean better results). May raise ParseTree.QueryError. """ class IQueryParser(Interface): """Interface for Query Parsers.""" def parseQuery(query): """Parse a query string. Return a parse tree (which implements IQueryParseTree). Some of the query terms may be ignored because they are stopwords; use getIgnored() to find out which terms were ignored. But if the entire query consists only of stop words, or of stopwords and one or more negated terms, an exception is raised. May raise ParseTree.ParseError. """ def getIgnored(): """Return the list of ignored terms. Return the list of terms that were ignored by the most recent call to parseQuery() because they were stopwords. If parseQuery() was never called this returns None. """ def parseQueryEx(query): """Parse a query string. Return a tuple (tree, ignored) where 'tree' is the parse tree as returned by parseQuery(), and 'ignored' is a list of ignored terms as returned by getIgnored(). May raise ParseTree.ParseError. """ class IIndex(Interface): """Interface for an Index.""" def length(): """Return the number of words in the index.""" def document_count(): """Return the number of documents in the index.""" def get_words(docid): """Return a list of wordids for the given docid.""" def search(term): """Execute a search on a single term given as a string. Return an IIBTree mapping docid to score, or None if all docs match due to the lexicon returning no wids for the term (e.g., if the term is entirely composed of stopwords). """ def search_phrase(phrase): """Execute a search on a phrase given as a string. Return an IIBtree mapping docid to score. """ def search_glob(pattern): """Execute a pattern search. The pattern represents a set of words by using * and ?. For example, "foo*" represents the set of all words in the lexicon starting with "foo". Return an IIBTree mapping docid to score. """ def query_weight(terms): """Return the weight for a set of query terms. 'terms' is a sequence of all terms included in the query, although not terms with a not. If a term appears more than once in a query, it should appear more than once in terms. Nothing is defined about what "weight" means, beyond that the result is an upper bound on document scores returned for the query. """ def index_doc(docid, text): """Add a document with the specified id and text to the index. If a document by that id already exists, replace its text with the new text provided text may be either a string (Unicode or otherwise) or a list of strings from which to extract the terms under which to index the source document. """ def unindex_doc(docid): """Remove the document with the specified id from the index""" def has_doc(docid): """Returns true if docid is an id of a document in the index""" class INBest(Interface): """NBest chooser Interface. An NBest object remembers the N best-scoring items ever passed to its .add(item, score) method. If .add() is called M times, the worst-case number of comparisons performed overall is M * log2(N). """ def add(item, score): """Record that item 'item' has score 'score'. No return value. The N best-scoring items are remembered, where N was passed to the constructor. 'item' can by anything. 'score' should be a number, and larger numbers are considered better. """ def addmany(sequence): """Like "for item, score in sequence: self.add(item, score)". This is simply faster than calling add() len(seq) times. """ def getbest(): """Return the (at most) N best-scoring items as a sequence. The return value is a sequence of 2-tuples, (item, score), with the largest score first. If .add() has been called fewer than N times, this sequence will contain fewer than N pairs. """ def pop_smallest(): """Return and remove the (item, score) pair with lowest score. If len(self) is 0, raise IndexError. To be cleaer, this is the lowest score among the N best-scoring seen so far. This is most useful if the capacity of the NBest object is never exceeded, in which case pop_smallest() allows using the object as an ordinary smallest-in-first-out priority queue. """ def __len__(): """Return the number of (item, score) pairs currently known. This is N (the value passed to the constructor), unless .add() has been called fewer than N times. """ def capacity(): """Return the maximum number of (item, score) pairs. This is N (the value passed to the constructor). """ zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/NBest.py0000644000175000017500000000505312214017454025545 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """NBest An NBest object remembers the N best-scoring items ever passed to its .add(item, score) method. If .add() is called M times, the worst-case number of comparisons performed overall is M * log2(N). """ from bisect import bisect from zope.interface import implements from Products.ZCTextIndex.interfaces import INBest class NBest: implements(INBest) def __init__(self, N): "Build an NBest object to remember the N best-scoring objects." if N < 1: raise ValueError("NBest() argument must be at least 1") self._capacity = N # This does a very simple thing with sorted lists. For large # N, a min-heap can be unboundedly better in terms of data # movement time. self._scores = [] self._items = [] def __len__(self): return len(self._scores) def capacity(self): return self._capacity def add(self, item, score): self.addmany([(item, score)]) def addmany(self, sequence): scores, items, capacity = self._scores, self._items, self._capacity n = len(scores) for item, score in sequence: # When we're in steady-state, the usual case is that we're filled # to capacity, and that an incoming item is worse than any of # the best-seen so far. if n >= capacity and score <= scores[0]: continue i = bisect(scores, score) scores.insert(i, score) items.insert(i, item) if n == capacity: del items[0], scores[0] else: n += 1 assert n == len(scores) def getbest(self): result = zip(self._items, self._scores) result.reverse() return result def pop_smallest(self): if self._scores: return self._items.pop(0), self._scores.pop(0) raise IndexError("pop_smallest() called on empty NBest object") zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/HTMLSplitter.py0000644000175000017500000000335212214017454027025 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import re from zope.interface import implements from Products.ZCTextIndex.interfaces import ISplitter from Products.ZCTextIndex.PipelineFactory import element_factory class HTMLWordSplitter: implements(ISplitter) def process(self, text, wordpat=r"(?L)\w+"): splat = [] for t in text: splat += self._split(t, wordpat) return splat def processGlob(self, text): # see Lexicon.globToWordIds() return self.process(text, r"(?L)\w+[\w*?]*") def _split(self, text, wordpat): text = text.lower() remove = [r"<[^<>]*>", r"&[A-Za-z]+;"] for pat in remove: text = re.sub(pat, " ", text) return re.findall(wordpat, text) element_factory.registerFactory('Word Splitter', 'HTML aware splitter', HTMLWordSplitter) if __name__ == "__main__": import sys splitter = HTMLWordSplitter() for path in sys.argv[1:]: f = open(path, "rb") buf = f.read() f.close() print path print splitter.process([buf]) zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/SetOps.py0000644000175000017500000000473112214017454025751 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """SetOps -- Weighted intersections and unions applied to many inputs.""" from BTrees.IIBTree import IIBucket from BTrees.IIBTree import weightedIntersection from BTrees.IIBTree import weightedUnion from Products.ZCTextIndex.NBest import NBest def mass_weightedIntersection(L): "A list of (mapping, weight) pairs -> their weightedIntersection IIBucket." L = [(x, wx) for (x, wx) in L if x is not None] if len(L) < 2: return _trivial(L) # Intersect with smallest first. We expect the input maps to be # IIBuckets, so it doesn't hurt to get their lengths repeatedly # (len(Bucket) is fast; len(BTree) is slow). L.sort(lambda x, y: cmp(len(x[0]), len(y[0]))) (x, wx), (y, wy) = L[:2] dummy, result = weightedIntersection(x, y, wx, wy) for x, wx in L[2:]: dummy, result = weightedIntersection(result, x, 1, wx) return result def mass_weightedUnion(L): "A list of (mapping, weight) pairs -> their weightedUnion IIBucket." if len(L) < 2: return _trivial(L) # Balance unions as closely as possible, smallest to largest. merge = NBest(len(L)) for x, weight in L: merge.add((x, weight), len(x)) while len(merge) > 1: # Merge the two smallest so far, and add back to the queue. (x, wx), dummy = merge.pop_smallest() (y, wy), dummy = merge.pop_smallest() dummy, z = weightedUnion(x, y, wx, wy) merge.add((z, 1), len(z)) (result, weight), dummy = merge.pop_smallest() return result def _trivial(L): # L is empty or has only one (mapping, weight) pair. If there is a # pair, we may still need to multiply the mapping by its weight. assert len(L) <= 1 if len(L) == 0: return IIBucket() [(result, weight)] = L if weight != 1: dummy, result = weightedUnion(IIBucket(), result, 0, weight) return result zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/stopper.c0000644000175000017500000000440512214017454026020 0ustar arnauarnau/***************************************************************************** Copyright (c) 2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /* stopper.c * * Fast version of the StopWordRemover object. */ #include "Python.h" static PyObject * stopper_process(PyObject *unused, PyObject *args) { PyObject *result = NULL; PyObject *dict; PyObject *seq; int len, i; if (!PyArg_ParseTuple(args, "O!O:process", &PyDict_Type, &dict, &seq)) return NULL; seq = PySequence_Fast(seq, "process() requires a sequence as argument 2"); if (seq == NULL) return NULL; result = PyList_New(0); if (result == NULL) goto finally; #if PY_VERSION_HEX >= 0x02020000 /* Only available in Python 2.2 and newer. */ len = PySequence_Fast_GET_SIZE(seq); #else len = PyObject_Length(seq); #endif for (i = 0; i < len; ++i) { PyObject *s = PySequence_Fast_GET_ITEM(seq, i); /* * PyDict_GetItem() returns NULL if there isn't a matching * item, but without setting an exception, so this does what * we want. */ if (PyDict_GetItem(dict, s) == NULL) { if (PyList_Append(result, s) < 0) { Py_DECREF(result); result = NULL; goto finally; } } } finally: Py_DECREF(seq); return result; } static PyMethodDef stopper_functions[] = { {"process", stopper_process, METH_VARARGS, "process(dict, [str, ...]) --> [str, ...]\n" "Remove stop words (the keys of dict) from the input list of strings\n" " to create a new list."}, {NULL} }; void initstopper(void) { Py_InitModule3("stopper", stopper_functions, "Fast StopWordRemover implementation."); } zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/__init__.py0000644000175000017500000000425712214017454026276 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ZCatalog Text Index Plugin text index for ZCatalog. """ from PipelineFactory import element_factory from Products.ZCTextIndex import ZCTextIndex, HTMLSplitter def initialize(context): context.registerClass( ZCTextIndex.ZCTextIndex, permission = 'Add Pluggable Index', constructors = (ZCTextIndex.manage_addZCTextIndexForm, ZCTextIndex.manage_addZCTextIndex, getIndexTypes), icon='www/index.gif', visibility=None ) context.registerClass( ZCTextIndex.PLexicon, permission = 'Add Vocabularies', constructors = (ZCTextIndex.manage_addLexiconForm, ZCTextIndex.manage_addLexicon, getElementGroups, getElementNames), icon='www/lexicon.gif' ) context.registerHelp() context.registerHelpTitle("Zope Help") ## Functions below are for use in the ZMI constructor forms ## def getElementGroups(self): return element_factory.getFactoryGroups() def getElementNames(self, group): return element_factory.getFactoryNames(group) def getIndexTypes(self): return ZCTextIndex.index_types.keys() ## Allow relevent exceptions to be caught in untrusted code from AccessControl import ModuleSecurityInfo ModuleSecurityInfo('Products').declarePublic('ZCTextIndex') ModuleSecurityInfo('Products.ZCTextIndex').declarePublic('ParseTree') ModuleSecurityInfo('Products.ZCTextIndex.ParseTree').declarePublic('QueryError') ModuleSecurityInfo('Products.ZCTextIndex.ParseTree').declarePublic('ParseError') zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/help/0000755000175000017500000000000012214017454025105 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/help/Lexicon_Add.stx0000644000175000017500000000321212214017454030014 0ustar arnauarnauZCTextIndex Lexicon - Add: Create a new ZCTextIndex Lexicon Description This view allows you to create a new ZCTextIndex Lexicon object. ZCTextIndex Lexicons store the words indexed by ZCTextIndexes in a ZCatalog. Controls 'Id' -- Allows you to specify the id of the ZCTextIndex Lexicon. 'Title' -- Allows you to specify the title of the ZCTextIndex Lexicon. Pipeline Stages The remaining controls allow you to select the desired processing of text to index by selecting pipeline stages. The default available stages are: - **Word Splitter** This is the only mandatory stage. The word splitter breaks the text up into a list of words. Included is a simple whitespace splitter, and a splitter that removes HTML tags. The HTML aware splitter gives best results when all of the incoming content to index is HTML. - **Stop Words** To conserve space in the vocabulary, and possibly increase performance, you can select a stop word remover which subtracts very common or single letter words from the Lexicon. Bear in mind that you will not be able to search on removed stop words, and they will also be removed from queries passed to search ZCTextIndexes using the Lexicon. - **Case Normalizer** The case normalizer removes case information from the words in the Lexicon. If case-sensitive searching is desires, then omit this element from the pipeline. zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/help/ZCTextIndex_Add.stx0000644000175000017500000000317512214017454030574 0ustar arnauarnauZCTextIndex Add: Create a new ZCTextIndex Description A ZCTextIndex is an index for performing full text searches over bodies of text. It includes the following features: - Boolean query operators with parenthetical grouping - Globbing (partial word) and phrase matching - Two selectable relevance scoring algorithms ZCTextIndex is designed as a replacement for standard TextIndex, and has several advantages over it. Controls 'Id' -- The id of the ZCTextIndex, must be unique for this ZCatalog. 'Field Name' -- The name of the field (object attribute) to be indexed. 'Ranking Strategy' - **Okapi BM25 Rank** A relevance scoring technique that seems to work well when the document text is considerably longer than the query string, which is often the case with user specified query strings. - **Cosine Measure** A relevance scoring technique derived from the "*Managing Gigabytes*":http://www.cs.mu.oz.au/mg/ book. It seems to work best when the queries are similar in size and content to the text they are searching. 'Lexicon' -- The ZCTextIndex Lexicon to be used by this ZCTextIndex. Lexicons process and store the words from the text and help in processing queries. You must define a ZCTextIndex Lexicon before you can create a ZCTextIndex. Several ZCTextIndexes can share the same Lexicon if desired. zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/README.txt0000644000175000017500000000717512214017454025665 0ustar arnauarnauZCTextIndex =========== This product is a replacement for the full text indexing facility of ZCatalog. Specifically, it is an alternative to PluginIndexes/TextIndex. Advantages of using ZCTextIndex over TextIndex: - A new query language, supporting both explicit and implicit Boolean operators, parentheses, globbing, and phrase searching. Apart from explicit operators and globbing, the syntax is roughly the same as that popularized by Google. - A more refined scoring algorithm, resulting in better selectiveness: it's much more likely that you'll find the document you are looking for among the first few highest-ranked results. - Actually, ZCTextIndex gives you a choice of two scoring algorithms from recent literature: the Cosine ranking from the Managing Gigabytes book, and Okapi from more recent research papers. Okapi usually does better, so it is the default (but your milage may vary). - A redesigned Lexicon, using a pipeline architecture to split the input text into words. This makes it possible to mix and match pipeline components, e.g. you can choose between an HTML-aware splitter and a plain text splitter, and additional components can be added to the pipeline for case folding, stopword removal, and other features. Enough example pipeline components are provided to get you started, and it is very easy to write new components. Performance is roughly the same as for TextIndex, and we're expecting to make tweaks to the code that will make it faster. This code can be used outside of Zope too; all you need is a standalone ZODB installation to make your index persistent. Several functional test programs in the tests subdirectory show how to do this, for example mhindex.py, mailtest.py, indexhtml.py, and queryhtml.py. See the online help for how to use ZCTextIndex within Zope. (Included in the subdirectory "help".) Code overview ------------- ZMI interface: __init__.py ZMI publishing code ZCTextIndex.py pluggable index class PipelineFactory.py ZMI helper to configure the pipeline Indexing: BaseIndex.py common code for Cosine and Okapi index CosineIndex.py Cosine index implementation OkapiIndex.py Okapi index implementation okascore.c C implementation of scoring loop Lexicon: Lexicon.py lexicon and sample pipeline elements HTMLSplitter.py HTML-aware splitter StopDict.py list of English stopwords stopper.c C implementation of stop word remover Query parser: QueryParser.py parse a query into a parse tree ParseTree.py parse tree node classes and exceptions Utilities: NBest.py find N best items in a list without sorting SetOps.py efficient weighted set operations WidCode.py list compression allowing phrase searches RiceCode.py list compression code (as yet unused) Interfaces (these speak for themselves): IIndex.py ILexicon.py INBest.py IPipelineElement.py IPipelineElementFactory.py IQueryParseTree.py IQueryParser.py ISplitter.py Subdirectories: dtml ZMI templates help ZMI help files tests unittests and some functional tests/examples www images used in the ZMI Tests ----- Functional tests and helpers: hs-tool.py helper to interpret hotshot profiler logs indexhtml.py index a collection of HTML files mailtest.py index and query a Unix mailbox file mhindex.py index and query a set of MH folders python.txt output from benchmark queries queryhtml.py query an index created by indexhtml.py wordstats.py dump statistics about each indexed word Unit tests (these speak for themselves): testIndex.py testLexicon.py testNBest.py testPipelineFactory.py testQueryEngine.py testQueryParser.py testSetOps.py testStopper.py testZCTextIndex.py zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/Setup0000644000175000017500000000005712214017454025202 0ustar arnauarnau*shared* stopper stopper.c okascore okascore.c zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/OkapiIndex.py0000644000175000017500000003362212214017454026570 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Full text index with relevance ranking, using an Okapi BM25 rank.""" # Lots of comments are at the bottom of this file. Read them to # understand what's going on. from BTrees.IIBTree import IIBucket from BTrees.Length import Length from zope.interface import implements from Products.ZCTextIndex.interfaces import IIndex from Products.ZCTextIndex.BaseIndex import BaseIndex from Products.ZCTextIndex.BaseIndex import inverse_doc_frequency from Products.ZCTextIndex.BaseIndex import scaled_int from Products.ZCTextIndex.okascore import score class OkapiIndex(BaseIndex): implements(IIndex) # BM25 free parameters. K1 = 1.2 B = 0.75 assert K1 >= 0.0 assert 0.0 <= B <= 1.0 def __init__(self, lexicon): BaseIndex.__init__(self, lexicon) # ._wordinfo for Okapi is # wid -> {docid -> frequency}; t -> D -> f(D, t) # ._docweight for Okapi is # docid -> # of words in the doc # This is just len(self._docwords[docid]), but _docwords is stored # in compressed form, so uncompressing it just to count the list # length would be ridiculously expensive. # sum(self._docweight.values()), the total # of words in all docs # This is a long for "better safe than sorry" reasons. It isn't # used often enough that speed should matter. # Use a BTree.Length.Length object to avoid concurrent write conflicts self._totaldoclen = Length(0L) def index_doc(self, docid, text): count = BaseIndex.index_doc(self, docid, text) self._change_doc_len(count) return count def _reindex_doc(self, docid, text): self._change_doc_len(-self._docweight[docid]) return BaseIndex._reindex_doc(self, docid, text) def unindex_doc(self, docid): self._change_doc_len(-self._docweight[docid]) BaseIndex.unindex_doc(self, docid) def _change_doc_len(self, delta): # Change total doc length used for scoring if delta == 0: return try: self._totaldoclen.change(delta) except AttributeError: # Opportunistically upgrade _totaldoclen attribute to Length object self._totaldoclen = Length(long(self._totaldoclen + delta)) # The workhorse. Return a list of (IIBucket, weight) pairs, one pair # for each wid t in wids. The IIBucket, times the weight, maps D to # TF(D,t) * IDF(t) for every docid D containing t. # As currently written, the weights are always 1, and the IIBucket maps # D to TF(D,t)*IDF(t) directly, where the product is computed as a float # but stored as a scaled_int. # NOTE: This is overridden below, by a function that computes the # same thing but with the inner scoring loop in C. def _search_wids(self, wids): if not wids: return [] N = float(self.document_count()) # total # of docs try: doclen = self._totaldoclen() except TypeError: # _totaldoclen has not yet been upgraded doclen = self._totaldoclen meandoclen = doclen / N K1 = self.K1 B = self.B K1_plus1 = K1 + 1.0 B_from1 = 1.0 - B # f(D, t) * (k1 + 1) # TF(D, t) = ------------------------------------------- # f(D, t) + k1 * ((1-b) + b*len(D)/E(len(D))) L = [] docid2len = self._docweight for t in wids: d2f = self._wordinfo[t] # map {docid -> f(docid, t)} idf = inverse_doc_frequency(len(d2f), N) # an unscaled float result = IIBucket() for docid, f in d2f.items(): lenweight = B_from1 + B * docid2len[docid] / meandoclen tf = f * K1_plus1 / (f + K1 * lenweight) result[docid] = scaled_int(tf * idf) L.append((result, 1)) return L # Note about the above: the result is tf * idf. tf is small -- it # can't be larger than k1+1 = 2.2. idf is formally unbounded, but # is less than 14 for a term that appears in only 1 of a million # documents. So the product is probably less than 32, or 5 bits # before the radix point. If we did the scaled-int business on # both of them, we'd be up to 25 bits. Add 64 of those and we'd # be in overflow territory. That's pretty unlikely, so we *could* # just store scaled_int(tf) in result[docid], and use scaled_int(idf) # as an invariant weight across the whole result. But besides # skating near the edge, it's not a speed cure, since the computation # of tf would still be done at Python speed, and it's a lot more # work than just multiplying by idf. # The same function as _search_wids above, but with the inner scoring # loop written in C (module okascore, function score()). # Cautions: okascore hardcodes the values of K, B1, and the scaled_int # function. def _search_wids(self, wids): if not wids: return [] N = float(self.document_count()) # total # of docs try: doclen = self._totaldoclen() except TypeError: # _totaldoclen has not yet been upgraded doclen = self._totaldoclen meandoclen = doclen / N #K1 = self.K1 #B = self.B #K1_plus1 = K1 + 1.0 #B_from1 = 1.0 - B # f(D, t) * (k1 + 1) # TF(D, t) = ------------------------------------------- # f(D, t) + k1 * ((1-b) + b*len(D)/E(len(D))) L = [] docid2len = self._docweight for t in wids: d2f = self._wordinfo[t] # map {docid -> f(docid, t)} idf = inverse_doc_frequency(len(d2f), N) # an unscaled float result = IIBucket() score(result, d2f.items(), docid2len, idf, meandoclen) L.append((result, 1)) return L def query_weight(self, terms): # Get the wids. wids = [] for term in terms: termwids = self._lexicon.termToWordIds(term) wids.extend(termwids) # The max score for term t is the maximum value of # TF(D, t) * IDF(Q, t) # We can compute IDF directly, and as noted in the comments below # TF(D, t) is bounded above by 1+K1. N = float(len(self._docweight)) tfmax = 1.0 + self.K1 sum = 0 for t in self._remove_oov_wids(wids): idf = inverse_doc_frequency(len(self._wordinfo[t]), N) sum += scaled_int(idf * tfmax) return sum def _get_frequencies(self, wids): d = {} dget = d.get for wid in wids: d[wid] = dget(wid, 0) + 1 return d, len(wids) """ "Okapi" (much like "cosine rule" also) is a large family of scoring gimmicks. It's based on probability arguments about how words are distributed in documents, not on an abstract vector space model. A long paper by its principal inventors gives an excellent overview of how it was derived: A probabilistic model of information retrieval: development and status K. Sparck Jones, S. Walker, S.E. Robertson http://citeseer.nj.nec.com/jones98probabilistic.html Spellings that ignore relevance information (which we don't have) are of this high-level form: score(D, Q) = sum(for t in D&Q: TF(D, t) * IDF(Q, t)) where D a specific document Q a specific query t a term (word, atomic phrase, whatever) D&Q the terms common to D and Q TF(D, t) a measure of t's importance in D -- a kind of term frequency weight IDF(Q, t) a measure of t's importance in the query and in the set of documents as a whole -- a kind of inverse document frequency weight The IDF(Q, t) here is identical to the one used for our cosine measure. Since queries are expected to be short, it ignores Q entirely: IDF(Q, t) = log(1.0 + N / f(t)) where N the total number of documents f(t) the number of documents in which t appears Most Okapi literature seems to use log(N/f(t)) instead. We don't, because that becomes 0 for a term that's in every document, and, e.g., if someone is searching for "documentation" on python.org (a term that may well show up on every page, due to the top navigation bar), we still want to find the pages that use the word a lot (which is TF's job to find, not IDF's -- we just want to stop IDF from considering this t to be irrelevant). The TF(D, t) spellings are more interesting. With lots of variations, the most basic spelling is of the form f(D, t) TF(D, t) = --------------- f(D, t) + K(D) where f(D, t) the number of times t appears in D K(D) a measure of the length of D, normalized to mean doc length The functional *form* f/(f+K) is clever. It's a gross approximation to a mixture of two distinct Poisson distributions, based on the idea that t probably appears in D for one of two reasons: 1. More or less at random. 2. Because it's important to D's purpose in life ("eliteness" in papers). Note that f/(f+K) is always between 0 and 1. If f is very large compared to K, it approaches 1. If K is very large compared to f, it approaches 0. If t appears in D more or less "for random reasons", f is likely to be small, and so K will dominate unless it's a very small doc, and the ratio will be small. OTOH, if t appears a lot in D, f will dominate unless it's a very large doc, and the ratio will be close to 1. We use a variation on that simple theme, a simplification of what's called BM25 in the literature (it was the 25th stab at a Best Match function from the Okapi group; "a simplification" means we're setting some of BM25's more esoteric free parameters to 0): f(D, t) * (k1 + 1) TF(D, t) = -------------------- f(D, t) + k1 * K(D) where k1 a "tuning factor", typically between 1.0 and 2.0. We use 1.2, the usual default value. This constant adjusts the curve to look more like a theoretical 2-Poisson curve. Note that as f(D, t) increases, TF(D, t) increases monotonically, approaching an asymptote of k1+1 from below. Finally, we use K(D) = (1-b) + b * len(D)/E(len(D)) where b is another free parameter, discussed below. We use 0.75. len(D) the length of D in words E(len(D)) the expected value of len(D) across the whole document set; or, IOW, the average document length b is a free parameter between 0.0 and 1.0, and adjusts for the expected effect of the "Verbosity Hypothesis". Suppose b is 1, and some word t appears 10 times as often in document d2 than in document d1. If document d2 is also 10 times as long as d1, TF(d1, t) and TF(d2, t) are identical: f(d2, t) * (k1 + 1) TF(d2, t) = --------------------------------- = f(d2, t) + k1 * len(d2)/E(len(D)) 10 * f(d1, t) * (k1 + 1) ----------------------------------------------- = TF(d1, t) 10 * f(d1, t) + k1 * (10 * len(d1))/E(len(D)) because the 10's cancel out. This is appropriate if we believe that a word appearing 10x more often in a doc 10x as long is simply due to that the longer doc is more verbose. If we do believe that, the longer doc and the shorter doc are probably equally relevant. OTOH, it *could* be that the longer doc is talking about t in greater depth too, in which case it's probably more relevant than the shorter doc. At the other extreme, if we set b to 0, the len(D)/E(len(D)) term vanishes completely, and a doc scores higher for having more occurences of a word regardless of the doc's length. Reality is between these extremes, and probably varies by document and word too. Reports in the literature suggest that b=0.75 is a good compromise "in general", favoring the "verbosity hypothesis" end of the scale. Putting it all together, the final TF function is f(D, t) * (k1 + 1) TF(D, t) = -------------------------------------------- f(D, t) + k1 * ((1-b) + b*len(D)/E(len(D))) with k1=1.2 and b=0.75. Query Term Weighting -------------------- I'm ignoring the query adjustment part of Okapi BM25 because I expect our queries are very short. Full BM25 takes them into account by adding the following to every score(D, Q); it depends on the lengths of D and Q, but not on the specific words in Q, or even on whether they appear in D(!): E(len(D)) - len(D) k2 * len(Q) * ------------------- E(len(D)) + len(D) Here k2 is another "tuning constant", len(Q) is the number of words in Q, and len(D) & E(len(D)) were defined above. The Okapi group set k2 to 0 in TREC-9, so it apparently doesn't do much good (or may even hurt). Full BM25 *also* multiplies the following factor into IDF(Q, t): f(Q, t) * (k3 + 1) ------------------ f(Q, t) + k3 where k3 is yet another free parameter, and f(Q,t) is the number of times t appears in Q. Since we're using short "web style" queries, I expect f(Q,t) to always be 1, and then that quotient is 1 * (k3 + 1) ------------ = 1 1 + k3 regardless of k3's value. So, in a trivial sense, we are incorporating this measure (and optimizing it by not bothering to multiply by 1 ). """ zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/INBest.py0000644000175000017500000000126612214017454025660 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from Products.ZCTextIndex.interfaces import INBest # BBB zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/StopDict.py0000644000175000017500000000233312214017454026261 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Provide a default list of stop words for the index. The specific splitter and lexicon are customizable, but the default ZCTextIndex should do something useful. """ def get_stopdict(): """Return a dictionary of stopwords.""" return _dict # This list of English stopwords comes from Lucene _words = [ "a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with" ] _dict = {} for w in _words: _dict[w] = None zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/IQueryParser.py0000644000175000017500000000127412214017454027126 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from Products.ZCTextIndex.interfaces import IQueryParser # BBB zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/IPipelineElementFactory.py0000644000175000017500000000130612214017454031247 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from Products.ZCTextIndex.interfaces import IPipelineElementFactory # BBB zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/BaseIndex.py0000644000175000017500000003170012214017454026372 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Abstract base class for full text index with relevance ranking.""" import math from BTrees.IOBTree import IOBTree from BTrees.IIBTree import IIBTree from BTrees.IIBTree import IITreeSet from BTrees.IIBTree import difference from BTrees.IIBTree import intersection from BTrees.Length import Length from Persistence import Persistent from zope.interface import implements from Products.ZCTextIndex import WidCode from Products.ZCTextIndex.interfaces import IIndex from Products.ZCTextIndex.SetOps import mass_weightedIntersection from Products.ZCTextIndex.SetOps import mass_weightedUnion # Instead of storing floats, we generally store scaled ints. Binary pickles # can store those more efficiently. The default SCALE_FACTOR of 1024 # is large enough to get about 3 decimal digits of fractional info, and # small enough so that scaled values should almost always fit in a signed # 16-bit int (we're generally storing logs, so a few bits before the radix # point goes a long way; on the flip side, for reasonably small numbers x # most of the info in log(x) is in the fractional bits, so we do want to # save a lot of those). SCALE_FACTOR = 1024.0 def scaled_int(f, scale=SCALE_FACTOR): # We expect only positive inputs, so "add a half and chop" is the # same as round(). Surprising, calling round() is significantly more # expensive. return int(f * scale + 0.5) def unique(L): """Return a list of the unique elements in L.""" return IITreeSet(L).keys() class BaseIndex(Persistent): implements(IIndex) def __init__(self, lexicon): self._lexicon = lexicon # wid -> {docid -> weight}; t -> D -> w(D, t) # Different indexers have different notions of term weight, but we # expect each indexer to use ._wordinfo to map wids to its notion # of a docid-to-weight map. # There are two kinds of OOV words: wid 0 is explicitly OOV, # and it's possible that the lexicon will return a non-zero wid # for a word we don't currently know about. For example, if we # unindex the last doc containing a particular word, that wid # remains in the lexicon, but is no longer in our _wordinfo map; # lexicons can also be shared across indices, and some other index # may introduce a lexicon word we've never seen. # A word is in-vocabulary for this index if and only if # _wordinfo.has_key(wid). Note that wid 0 must not be a key. self._wordinfo = IOBTree() # docid -> weight # Different indexers have different notions of doc weight, but we # expect each indexer to use ._docweight to map docids to its # notion of what a doc weight is. self._docweight = IIBTree() # docid -> WidCode'd list of wids # Used for un-indexing, and for phrase search. self._docwords = IOBTree() # Use a BTree length for efficient length computation w/o conflicts self.length = Length() self.document_count = Length() def length(self): """Return the number of words in the index.""" # This is overridden per instance return len(self._wordinfo) def document_count(self): """Return the number of documents in the index""" # This is overridden per instance return len(self._docweight) def get_words(self, docid): """Return a list of the wordids for a given docid.""" # Note this is overridden in the instance return WidCode.decode(self._docwords[docid]) # A subclass may wish to extend or override this. def index_doc(self, docid, text): if self._docwords.has_key(docid): return self._reindex_doc(docid, text) wids = self._lexicon.sourceToWordIds(text) wid2weight, docweight = self._get_frequencies(wids) self._mass_add_wordinfo(wid2weight, docid) self._docweight[docid] = docweight self._docwords[docid] = WidCode.encode(wids) try: self.document_count.change(1) except AttributeError: # Upgrade document_count to Length object self.document_count = Length(self.document_count()) return len(wids) # A subclass may wish to extend or override this. This is for adjusting # to a new version of a doc that already exists. The goal is to be # faster than simply unindexing the old version in its entirety and then # adding the new version in its entirety. def _reindex_doc(self, docid, text): # Touch as few docid->w(docid, score) maps in ._wordinfo as possible. old_wids = self.get_words(docid) new_wids = self._lexicon.sourceToWordIds(text) if old_wids == new_wids: return len(new_wids) old_wid2w, old_docw = self._get_frequencies(old_wids) new_wid2w, new_docw = self._get_frequencies(new_wids) old_widset = IITreeSet(old_wid2w.keys()) new_widset = IITreeSet(new_wid2w.keys()) in_both_widset = intersection(old_widset, new_widset) only_old_widset = difference(old_widset, in_both_widset) only_new_widset = difference(new_widset, in_both_widset) del old_widset, new_widset for wid in only_old_widset.keys(): self._del_wordinfo(wid, docid) for wid in only_new_widset.keys(): self._add_wordinfo(wid, new_wid2w[wid], docid) for wid in in_both_widset.keys(): # For the Okapi indexer, the "if" will trigger only for words # whose counts have changed. For the cosine indexer, the "if" # may trigger for every wid, since W(d) probably changed and # W(d) is divided into every score. newscore = new_wid2w[wid] if old_wid2w[wid] != newscore: self._add_wordinfo(wid, newscore, docid) self._docweight[docid] = new_docw self._docwords[docid] = WidCode.encode(new_wids) return len(new_wids) # Subclass must override. def _get_frequencies(self, wids): # Compute term frequencies and a doc weight, whatever those mean # to an indexer. # Return pair: # {wid0: w(d, wid0), wid1: w(d, wid1), ...], # docweight # The wid->weight mappings are fed into _add_wordinfo, and docweight # becomes the value of _docweight[docid]. raise NotImplementedError def has_doc(self, docid): return self._docwords.has_key(docid) # A subclass may wish to extend or override this. def unindex_doc(self, docid): for wid in unique(self.get_words(docid)): self._del_wordinfo(wid, docid) del self._docwords[docid] del self._docweight[docid] try: self.document_count.change(-1) except AttributeError: # Upgrade document_count to Length object self.document_count = Length(self.document_count()) def search(self, term): wids = self._lexicon.termToWordIds(term) if not wids: return None # All docs match wids = self._remove_oov_wids(wids) return mass_weightedUnion(self._search_wids(wids)) def search_glob(self, pattern): wids = self._lexicon.globToWordIds(pattern) wids = self._remove_oov_wids(wids) return mass_weightedUnion(self._search_wids(wids)) def search_phrase(self, phrase): wids = self._lexicon.termToWordIds(phrase) cleaned_wids = self._remove_oov_wids(wids) if len(wids) != len(cleaned_wids): # At least one wid was OOV: can't possibly find it. return IIBTree() scores = self._search_wids(wids) hits = mass_weightedIntersection(scores) if not hits: return hits code = WidCode.encode(wids) result = IIBTree() for docid, weight in hits.items(): docwords = self._docwords[docid] if docwords.find(code) >= 0: result[docid] = weight return result def _remove_oov_wids(self, wids): return filter(self._wordinfo.has_key, wids) # Subclass must override. # The workhorse. Return a list of (IIBucket, weight) pairs, one pair # for each wid t in wids. The IIBucket, times the weight, maps D to # TF(D,t) * IDF(t) for every docid D containing t. wids must not # contain any OOV words. def _search_wids(self, wids): raise NotImplementedError # Subclass must override. # It's not clear what it should do. It must return an upper bound on # document scores for the query. It would be nice if a document score # divided by the query's query_weight gave the proabability that a # document was relevant, but nobody knows how to do that. For # CosineIndex, the ratio is the cosine of the angle between the document # and query vectors. For OkapiIndex, the ratio is a (probably # unachievable) upper bound with no "intuitive meaning" beyond that. def query_weight(self, terms): raise NotImplementedError DICT_CUTOFF = 10 def _add_wordinfo(self, wid, f, docid): # Store a wordinfo in a dict as long as there are less than # DICT_CUTOFF docids in the dict. Otherwise use an IIBTree. # The pickle of a dict is smaller than the pickle of an # IIBTree, substantially so for small mappings. Thus, we use # a dictionary until the mapping reaches DICT_CUTOFF elements. # The cutoff is chosen based on the implementation # characteristics of Python dictionaries. The dict hashtable # always has 2**N slots and is resized whenever it is 2/3s # full. A pickled dict with 10 elts is half the size of an # IIBTree with 10 elts, and 10 happens to be 2/3s of 2**4. So # choose 10 as the cutoff for now. # The IIBTree has a smaller in-memory representation than a # dictionary, so pickle size isn't the only consideration when # choosing the threshold. The pickle of a 500-elt dict is 92% # of the size of the same IIBTree, but the dict uses more # space when it is live in memory. An IIBTree stores two C # arrays of ints, one for the keys and one for the values. It # holds up to 120 key-value pairs in a single bucket. doc2score = self._wordinfo.get(wid) if doc2score is None: doc2score = {} self.length.change(1) else: # _add_wordinfo() is called for each update. If the map # size exceeds the DICT_CUTOFF, convert to an IIBTree. # Obscure: First check the type. If it's not a dict, it # can't need conversion, and then we can avoid an expensive # len(IIBTree). if (isinstance(doc2score, type({})) and len(doc2score) == self.DICT_CUTOFF): doc2score = IIBTree(doc2score) doc2score[docid] = f self._wordinfo[wid] = doc2score # not redundant: Persistency! # self._mass_add_wordinfo(wid2weight, docid) # # is the same as # # for wid, weight in wid2weight.items(): # self._add_wordinfo(wid, weight, docid) # # except that _mass_add_wordinfo doesn't require so many function calls. def _mass_add_wordinfo(self, wid2weight, docid): dicttype = type({}) get_doc2score = self._wordinfo.get new_word_count = 0 for wid, weight in wid2weight.items(): doc2score = get_doc2score(wid) if doc2score is None: doc2score = {} new_word_count += 1 elif (isinstance(doc2score, dicttype) and len(doc2score) == self.DICT_CUTOFF): doc2score = IIBTree(doc2score) doc2score[docid] = weight self._wordinfo[wid] = doc2score # not redundant: Persistency! self.length.change(new_word_count) def _del_wordinfo(self, wid, docid): doc2score = self._wordinfo[wid] del doc2score[docid] if doc2score: self._wordinfo[wid] = doc2score # not redundant: Persistency! else: del self._wordinfo[wid] self.length.change(-1) def inverse_doc_frequency(term_count, num_items): """Return the inverse doc frequency for a term, that appears in term_count items in a collection with num_items total items. """ # implements IDF(q, t) = log(1 + N/f(t)) return math.log(1.0 + float(num_items) / term_count) zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/0000755000175000017500000000000012214017454025317 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/testZCTextIndex.py0000644000175000017500000007265412214017454030760 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZCTextIndex unit tests. $Id: testZCTextIndex.py 128273 2012-11-13 15:56:32Z gauthier.bastien $ """ import unittest import re import Acquisition from zExceptions import NotFound from Products.ZCTextIndex.ZCTextIndex import ZCTextIndex, PLexicon from Products.ZCTextIndex.tests import \ testIndex, testQueryEngine, testQueryParser from Products.ZCTextIndex.BaseIndex import \ scaled_int, SCALE_FACTOR, inverse_doc_frequency from Products.ZCTextIndex.CosineIndex import CosineIndex from Products.ZCTextIndex.OkapiIndex import OkapiIndex from Products.ZCTextIndex.Lexicon import Splitter from Products.ZCTextIndex.Lexicon import CaseNormalizer, StopWordRemover from Products.ZCTextIndex.QueryParser import QueryParser from Products.ZCTextIndex.StopDict import get_stopdict from Products.ZCTextIndex.ParseTree import ParseError class Indexable: def __init__(self, text): self.text = text class Indexable2: def __init__(self, text1, text2): self.text1 = text1 self.text2 = text2 class LexiconHolder(Acquisition.Implicit): def __init__(self, lexicon): self.lexicon = lexicon def getPhysicalPath(self): return ('',) # Pretend to be the root def dummyUnrestrictedTraverse(self, path): if path == ('', 'lexicon',): return self.lexicon raise NotFound, path # The tests classes below create a ZCTextIndex(). Then they create # instance variables that point to the internal components used by # ZCTextIndex. These tests run the individual module unit tests with # the fully integrated ZCTextIndex. def eq(scaled1, scaled2, epsilon=scaled_int(0.01)): if abs(scaled1 - scaled2) > epsilon: raise AssertionError, "%s != %s" % (scaled1, scaled2) # A series of text chunks to use for the re-index tests (testDocUpdate). text = [ """Here's a knocking indeed! If a man were porter of hell-gate, he should have old turning the key. knock (that made sure sure there's at least one word in common).""" """Knock, knock, knock! Who's there, i' the name of Beelzebub? Here's a farmer, that hanged himself on the expectation of plenty: come in time; have napkins enow about you; here you'll sweat for't.""", """Knock, knock! Who's there, in the other devil's name? Faith, here's an equivocator, that could swear in both the scales against either scale; who committed treason enough for God's sake, yet could not equivocate to heaven: O, come in, equivocator.""", """Knock, knock, knock! Who's there? Faith, here's an English tailor come hither, for stealing out of a French hose: come in, tailor; here you may roast your goose.""", """Knock, knock; never at quiet! What are you? But this place is too cold for hell. I'll devil-porter it no further: I had thought to have let in some of all professions that go the primrose way to the everlasting bonfire.""" ] # Subclasses should derive from one of testIndex.{CosineIndexTest, # OkapiIndexTest} too. class ZCIndexTestsBase: def setUp(self): self.lexicon = PLexicon('lexicon', '', Splitter(), CaseNormalizer(), StopWordRemover()) caller = LexiconHolder(self.lexicon) self.zc_index = ZCTextIndex('name', None, caller, self.IndexFactory, 'text', 'lexicon') self.index = self.zc_index.index def parserFailure(self, query): self.assertRaises(ParseError, self.zc_index.query, query) def parserSuccess(self, query, n): r, num = self.zc_index.query(query) self.assertEqual(num, n) if n: self.assertEqual(r[0][0], 1) def testMultipleAttributes(self): lexicon = PLexicon('lexicon', '', Splitter(), CaseNormalizer(), StopWordRemover()) caller = LexiconHolder(self.lexicon) zc_index = ZCTextIndex('name', None, caller, self.IndexFactory, 'text1,text2', 'lexicon') doc = Indexable2('foo bar', 'alpha omega') zc_index.index_object(1, doc) nbest, total = zc_index.query('foo') self.assertEqual(len(nbest), 1) nbest, total = zc_index.query('foo alpha') self.assertEqual(len(nbest), 1) nbest, total = zc_index.query('foo alpha gamma') self.assertEqual(len(nbest), 0) def testListAttributes(self): lexicon = PLexicon('lexicon', '', Splitter(), CaseNormalizer(), StopWordRemover()) caller = LexiconHolder(self.lexicon) zc_index = ZCTextIndex('name', None, caller, self.IndexFactory, 'text1,text2', 'lexicon') doc = Indexable2('Hello Tim', \ ['Now is the winter of our discontent', 'Made glorious summer by this sun of York', ]) zc_index.index_object(1, doc) nbest, total = zc_index.query('glorious') self.assertEqual(len(nbest), 1) nbest, total = zc_index.query('York Tim') self.assertEqual(len(nbest), 1) nbest, total = zc_index.query('Tuesday Tim York') self.assertEqual(len(nbest), 0) def testReindex(self): lexicon = PLexicon('lexicon', '', Splitter(), CaseNormalizer(), StopWordRemover()) caller = LexiconHolder(self.lexicon) zc_index = ZCTextIndex('name', None, caller, self.IndexFactory, 'text', 'lexicon') doc = Indexable('Hello Tim') zc_index.index_object(1, doc) nbest, total = zc_index.query('glorious') self.assertEqual(len(nbest), 0) nbest, total = zc_index.query('Tim') self.assertEqual(len(nbest), 1) # reindex with another value doc.text = 'Goodbye George' zc_index.index_object(1, doc) nbest, total = zc_index.query('Tim') self.assertEqual(len(nbest), 0) nbest, total = zc_index.query('Goodbye') self.assertEqual(len(nbest), 1) # reindex with an empty value doc.text = '' zc_index.index_object(1, doc) nbest, total = zc_index.query('George') self.assertEqual(len(nbest), 0) def testStopWords(self): # the only non-stopword is question text = ("to be or not to be " "that is the question") doc = Indexable(text) self.zc_index.index_object(1, doc) for word in text.split(): if word != "question": wids = self.lexicon.termToWordIds(word) self.assertEqual(wids, []) self.assertEqual(len(self.index.get_words(1)), 1) self.parserSuccess('question', 1) self.parserSuccess('question AND to AND be', 1) self.parserSuccess('to AND question AND be', 1) self.parserSuccess('question AND NOT gardenia', 1) self.parserSuccess('question AND gardenia', 0) self.parserSuccess('gardenia', 0) self.parserSuccess('question OR gardenia', 1) self.parserSuccess('question AND NOT to AND NOT be', 1) self.parserSuccess('question OR to OR be', 1) self.parserSuccess('question to be', 1) self.parserFailure('to be') self.parserFailure('to AND be') self.parserFailure('to OR be') self.parserFailure('to AND NOT be') self.parserFailure('to AND NOT question') self.parserFailure('to AND NOT gardenia') def testDocUpdate(self): docid = 1 # doesn't change -- we index the same doc repeatedly N = len(text) stop = get_stopdict() d = {} # word -> list of version numbers containing that word for version, i in zip(text, range(N)): # use a simple splitter rather than an official one words = [w for w in re.split("\W+", version.lower()) if len(w) > 1 and not stop.has_key(w)] word_seen = {} for w in words: if not word_seen.has_key(w): d.setdefault(w, []).append(i) word_seen[w] = 1 unique = {} # version number -> list of words unique to that version common = [] # list of words common to all versions for w, versionlist in d.items(): if len(versionlist) == 1: unique.setdefault(versionlist[0], []).append(w) elif len(versionlist) == N: common.append(w) self.assert_(len(common) > 0) self.assert_(len(unique) > 0) for version, i in zip(text, range(N)): doc = Indexable(version) self.zc_index.index_object(docid, doc) for w in common: nbest, total = self.zc_index.query(w) self.assertEqual(total, 1, "did not find %s" % w) for k, v in unique.items(): if k == i: continue for w in v: nbest, total = self.zc_index.query(w) self.assertEqual(total, 0, "did not expect to find %s" % w) class CosineIndexTests(ZCIndexTestsBase, testIndex.CosineIndexTest): # A fairly involved test of the ranking calculations based on # an example set of documents in queries in Managing # Gigabytes, pp. 180-188. This test peeks into many internals of the # cosine indexer. def test_z3interfaces(self): from Products.PluginIndexes.interfaces import IPluggableIndex from Products.ZCTextIndex.interfaces import IZCTextIndex from zope.interface.verify import verifyClass verifyClass(IPluggableIndex, ZCTextIndex) verifyClass(IZCTextIndex, ZCTextIndex) def testRanking(self): self.words = ["cold", "days", "eat", "hot", "lot", "nine", "old", "pease", "porridge", "pot"] self.docs = ["Pease porridge hot, pease porridge cold,", "Pease porridge in the pot,", "Nine days old.", "In the pot cold, in the pot hot,", "Pease porridge, pease porridge,", "Eat the lot."] self._ranking_index() self._ranking_tf() self._ranking_idf() self._ranking_queries() # A digression to exercise re-indexing. docs = self.docs for variant in "hot cold porridge python", "pease hot pithy": self.zc_index.index_object(len(docs), Indexable(variant)) try: self._ranking_tf() except (AssertionError, KeyError): pass else: self.fail("expected _ranking_tf() to fail -- reindex") try: self._ranking_idf() except (AssertionError, KeyError): pass else: self.fail("expected _ranking_idf() to fail -- reindex") try: self._ranking_queries() except AssertionError: pass else: self.fail("expected _ranking_queries() to fail -- reindex") # This should leave things exactly as they were. self.zc_index.index_object(len(docs), Indexable(docs[-1])) self._ranking_tf() self._ranking_idf() self._ranking_queries() def _ranking_index(self): docs = self.docs for i in range(len(docs)): self.zc_index.index_object(i + 1, Indexable(docs[i])) def _ranking_tf(self): # matrix of term weights for the rows are docids # and the columns are indexes into this list: l_wdt = [(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.7, 1.7, 0.0), (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0), (1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.7), (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.7, 1.7, 0.0), (0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)] l_Wd = [2.78, 1.73, 1.73, 2.21, 2.39, 1.41] for i in range(len(l_Wd)): docid = i + 1 scaled_Wd = scaled_int(l_Wd[i]) eq(scaled_Wd, self.index._get_Wd(docid)) wdts = [scaled_int(t) for t in l_wdt[i]] for j in range(len(wdts)): wdt = self.index._get_wdt(docid, self.words[j]) eq(wdts[j], wdt) def _ranking_idf(self): word_freqs = [2, 1, 1, 2, 1, 1, 1, 3, 3, 2] idfs = [1.39, 1.95, 1.95, 1.39, 1.95, 1.95, 1.95, 1.10, 1.10, 1.39] for i in range(len(self.words)): word = self.words[i] eq(word_freqs[i], self.index._get_ft(word)) eq(scaled_int(idfs[i]), self.index._get_wt(word)) def _ranking_queries(self): queries = ["eat", "porridge", "hot OR porridge", "eat OR nine OR day OR old OR porridge"] wqs = [1.95, 1.10, 1.77, 3.55] results = [[(6, 0.71)], [(1, 0.61), (2, 0.58), (5, 0.71)], [(1, 0.66), (2, 0.36), (4, 0.36), (5, 0.44)], [(1, 0.19), (2, 0.18), (3, 0.63), (5, 0.22), (6, 0.39)]] for i in range(len(queries)): raw = queries[i] q = QueryParser(self.lexicon).parseQuery(raw) wq = self.index.query_weight(q.terms()) eq(wq, scaled_int(wqs[i])) r, n = self.zc_index.query(raw) self.assertEqual(len(r), len(results[i])) # convert the results to a dict for each checking d = {} for doc, score in results[i]: d[doc] = scaled_int(score) for doc, score in r: score = scaled_int(float(score / SCALE_FACTOR) / wq) self.assert_(0 <= score <= SCALE_FACTOR) eq(d[doc], score) class OkapiIndexTests(ZCIndexTestsBase, testIndex.OkapiIndexTest): # A white-box test. def testAbsoluteScores(self): docs = ["one", "one two", "one two three"] for i in range(len(docs)): self.zc_index.index_object(i + 1, Indexable(docs[i])) self._checkAbsoluteScores() # Exercise re-indexing. for variant in "one xyz", "xyz two three", "abc def": self.zc_index.index_object(len(docs), Indexable(variant)) try: self._checkAbsoluteScores() except AssertionError: pass else: self.fail("expected _checkAbsoluteScores() to fail -- reindex") # This should leave things exactly as they were. self.zc_index.index_object(len(docs), Indexable(docs[-1])) self._checkAbsoluteScores() def _checkAbsoluteScores(self): self.assertEqual(self.index._totaldoclen(), 6) # So the mean doc length is 2. We use that later. r, num = self.zc_index.query("one") self.assertEqual(num, 3) self.assertEqual(len(r), 3) # Because our Okapi's B parameter is > 0, and "one" only appears # once in each doc, the verbosity hypothesis favors shorter docs. self.assertEqual([doc for doc, score in r], [1, 2, 3]) # The way the Okapi math works, a word that appears exactly once in # an average (length) doc gets tf score 1. Our second doc has # an average length, so its score should by 1 (tf) times the # inverse doc frequency of "one". But "one" appears in every # doc, so its IDF is log(1 + 3/3) = log(2). self.assertEqual(r[1][1], scaled_int(inverse_doc_frequency(3, 3))) # Similarly for "two". r, num = self.zc_index.query("two") self.assertEqual(num, 2) self.assertEqual(len(r), 2) self.assertEqual([doc for doc, score in r], [2, 3]) self.assertEqual(r[0][1], scaled_int(inverse_doc_frequency(2, 3))) # And "three", except that doesn't appear in an average-size doc, so # the math is much more involved. r, num = self.zc_index.query("three") self.assertEqual(num, 1) self.assertEqual(len(r), 1) self.assertEqual([doc for doc, score in r], [3]) idf = inverse_doc_frequency(1, 3) meandoclen = 2.0 lengthweight = 1.0 - OkapiIndex.B + OkapiIndex.B * 3 / meandoclen tf = (1.0 + OkapiIndex.K1) / (1.0 + OkapiIndex.K1 * lengthweight) self.assertEqual(r[0][1], scaled_int(tf * idf)) # More of a black-box test, but based on insight into how Okapi is trying # to think. def testRelativeScores(self): # Create 9 10-word docs. # All contain one instance of "one". # Doc #i contains i instances of "two" and 9-i of "xyz". for i in range(1, 10): doc = "one " + "two " * i + "xyz " * (9 - i) self.zc_index.index_object(i, Indexable(doc)) self._checkRelativeScores() # Exercise re-indexing. self.zc_index.index_object(9, Indexable("two xyz")) try: self._checkRelativeScores() except AssertionError: pass else: self.fail("expected _checkRelativeScores() to fail after reindex") # This should leave things exactly as they were. self.zc_index.index_object(9, Indexable(doc)) self._checkRelativeScores() def _checkRelativeScores(self): r, num = self.zc_index.query("one two") self.assertEqual(num, 9) self.assertEqual(len(r), 9) # The more twos in a doc, the better the score should be. self.assertEqual([doc for doc, score in r], range(9, 0, -1)) # Search for "two" alone shouldn't make any difference to relative # results. r, num = self.zc_index.query("two") self.assertEqual(num, 9) self.assertEqual(len(r), 9) self.assertEqual([doc for doc, score in r], range(9, 0, -1)) # Searching for xyz should skip doc 9, and favor the lower-numbered # docs (they have more instances of xyz). r, num = self.zc_index.query("xyz") self.assertEqual(num, 8) self.assertEqual(len(r), 8) self.assertEqual([doc for doc, score in r], range(1, 9)) # And relative results shouldn't change if we add "one". r, num = self.zc_index.query("xyz one") self.assertEqual(num, 8) self.assertEqual(len(r), 8) self.assertEqual([doc for doc, score in r], range(1, 9)) # But if we search for all the words, it's much muddier. The boost # in going from i instances to i+1 of a given word is smaller than # the boost in going from i-1 to i, so the winner will be the one # that balances the # of twos and xyzs best. But the test is nasty # that way: doc 4 has 4 two and 5 xyz, while doc 5 has the reverse. # However, xyz is missing from doc 9, so xyz has a larger idf than # two has. Since all the doc lengths are the same, doc lengths don't # matter. So doc 4 should win, and doc 5 should come in second. # The loser will be the most unbalanced, but is that doc 1 (1 two 8 # xyz) or doc 8 (8 two 1 xyz)? Again xyz has a higher idf, so doc 1 # is more valuable, and doc 8 is the loser. r, num = self.zc_index.query("xyz one two") self.assertEqual(num, 8) self.assertEqual(len(r), 8) self.assertEqual(r[0][0], 4) # winner self.assertEqual(r[1][0], 5) # runner up self.assertEqual(r[-1][0], 8) # loser self.assertEqual(r[-2][0], 1) # penultimate loser # And nothing about the relative results in the last test should # change if we leave "one" out of the search (it appears in all # docs, so it's a wash). r, num = self.zc_index.query("two xyz") self.assertEqual(num, 8) self.assertEqual(len(r), 8) self.assertEqual(r[0][0], 4) # winner self.assertEqual(r[1][0], 5) # runner up self.assertEqual(r[-1][0], 8) # loser self.assertEqual(r[-2][0], 1) # penultimate loser ############################################################################ # Subclasses of QueryTestsBase must set a class variable IndexFactory to # the kind of index to be constructed. class QueryTestsBase(testQueryEngine.TestQueryEngine, testQueryParser.TestQueryParser): # The FauxIndex in testQueryEngine contains four documents. # docid 1: foo, bar, ham # docid 2: bar, ham # docid 3: foo, ham # docid 4: ham docs = ["foo bar ham", "bar ham", "foo ham", "ham"] def setUp(self): self.lexicon = PLexicon('lexicon', '', Splitter(), CaseNormalizer(), StopWordRemover()) caller = LexiconHolder(self.lexicon) self.zc_index = ZCTextIndex('name', None, caller, self.IndexFactory, 'text', 'lexicon') self.parser = QueryParser(self.lexicon) self.index = self.zc_index.index self.add_docs() def add_docs(self): for i in range(len(self.docs)): text = self.docs[i] obj = Indexable(text) self.zc_index.index_object(i + 1, obj) def compareSet(self, set, dict): # XXX The FauxIndex and the real Index score documents very # differently. The set comparison can't actually compare the # items, but it can compare the keys. That will have to do for now. setkeys = list(set.keys()) dictkeys = dict.keys() setkeys.sort() dictkeys.sort() self.assertEqual(setkeys, dictkeys) class CosineQueryTests(QueryTestsBase): IndexFactory = CosineIndex class OkapiQueryTests(QueryTestsBase): IndexFactory = OkapiIndex class PLexiconTests(unittest.TestCase): def _getTargetClass(self): from Products.ZCTextIndex.ZCTextIndex import PLexicon return PLexicon def _makeOne(self, id='testing', title='Testing', *pipeline): return self._getTargetClass()(id, title, *pipeline) def test_class_conforms_to_ILexicon(self): from Products.ZCTextIndex.interfaces import ILexicon from zope.interface.verify import verifyClass verifyClass(ILexicon, self._getTargetClass()) def test_instance_conforms_to_ILexicon(self): from Products.ZCTextIndex.interfaces import ILexicon from zope.interface.verify import verifyObject verifyObject(ILexicon, self._makeOne()) def test_class_conforms_to_IZCLexicon(self): from Products.ZCTextIndex.interfaces import IZCLexicon from zope.interface.verify import verifyClass verifyClass(IZCLexicon, self._getTargetClass()) def test_instance_conforms_to_IZCLexicon(self): from Products.ZCTextIndex.interfaces import IZCLexicon from zope.interface.verify import verifyObject verifyObject(IZCLexicon, self._makeOne()) def test_queryLexicon_defaults_empty(self): lexicon = self._makeOne() info = lexicon.queryLexicon(REQUEST=None, words=None) self.assertEqual(info['page'], 0) self.assertEqual(info['rows'], 20) self.assertEqual(info['cols'], 4) self.assertEqual(info['start_word'], 1) self.assertEqual(info['end_word'], 0) self.assertEqual(info['word_count'], 0) self.assertEqual(list(info['page_range']), []) self.assertEqual(info['page_columns'], []) def test_queryLexicon_defaults_non_empty(self): WORDS = 'aaa bbb ccc ddd eee fff ggg'.split() lexicon = self._makeOne() lexicon.sourceToWordIds(WORDS) info = lexicon.queryLexicon(REQUEST=None, words=None) self.assertEqual(info['page'], 0) self.assertEqual(info['rows'], 20) self.assertEqual(info['cols'], 4) self.assertEqual(info['start_word'], 1) self.assertEqual(info['end_word'], 7) self.assertEqual(info['word_count'], 7) self.assertEqual(list(info['page_range']), [0]) self.assertEqual(info['page_columns'], [WORDS]) def test_queryLexicon_row_breaks(self): WORDS = 'aaa bbb ccc ddd eee fff ggg'.split() lexicon = self._makeOne() lexicon.sourceToWordIds(WORDS) info = lexicon.queryLexicon(REQUEST=None, words=None, rows=4) self.assertEqual(info['page'], 0) self.assertEqual(info['rows'], 4) self.assertEqual(info['cols'], 4) self.assertEqual(info['start_word'], 1) self.assertEqual(info['end_word'], 7) self.assertEqual(info['word_count'], 7) self.assertEqual(list(info['page_range']), [0]) self.assertEqual(info['page_columns'], [WORDS[0:4], WORDS[4:]]) def test_queryLexicon_page_breaks(self): WORDS = 'aaa bbb ccc ddd eee fff ggg'.split() lexicon = self._makeOne() lexicon.sourceToWordIds(WORDS) info = lexicon.queryLexicon(REQUEST=None, words=None, rows=2, cols=2) self.assertEqual(info['page'], 0) self.assertEqual(info['rows'], 2) self.assertEqual(info['cols'], 2) self.assertEqual(info['start_word'], 1) self.assertEqual(info['end_word'], 4) self.assertEqual(info['word_count'], 7) self.assertEqual(list(info['page_range']), [0, 1]) self.assertEqual(info['page_columns'], [WORDS[0:2], WORDS[2:4]]) def test_queryLexicon_page_break_not_first(self): WORDS = 'aaa bbb ccc ddd eee fff ggg'.split() lexicon = self._makeOne() lexicon.sourceToWordIds(WORDS) info = lexicon.queryLexicon(REQUEST=None, words=None, page=1, rows=2, cols=2) self.assertEqual(info['page'], 1) self.assertEqual(info['rows'], 2) self.assertEqual(info['cols'], 2) self.assertEqual(info['start_word'], 5) self.assertEqual(info['end_word'], 7) self.assertEqual(info['word_count'], 7) self.assertEqual(list(info['page_range']), [0, 1]) self.assertEqual(info['page_columns'], [WORDS[4:6], WORDS[6:]]) def test_queryLexicon_words_no_globbing(self): WORDS = 'aaa bbb ccc ddd eee fff ggg'.split() lexicon = self._makeOne() lexicon.sourceToWordIds(WORDS) info = lexicon.queryLexicon(REQUEST=None, words=['aaa', 'bbb']) self.assertEqual(info['page'], 0) self.assertEqual(info['rows'], 20) self.assertEqual(info['cols'], 4) self.assertEqual(info['start_word'], 1) self.assertEqual(info['end_word'], 2) self.assertEqual(info['word_count'], 2) self.assertEqual(list(info['page_range']), [0]) self.assertEqual(info['page_columns'], [['aaa', 'bbb']]) def test_queryLexicon_words_w_globbing(self): WORDS = 'aaa bbb ccc ddd eee fff ggg'.split() lexicon = self._makeOne() lexicon.sourceToWordIds(WORDS) info = lexicon.queryLexicon(REQUEST=None, words=['aa*', 'bbb*']) self.assertEqual(info['page'], 0) self.assertEqual(info['rows'], 20) self.assertEqual(info['cols'], 4) self.assertEqual(info['start_word'], 1) self.assertEqual(info['end_word'], 2) self.assertEqual(info['word_count'], 2) self.assertEqual(list(info['page_range']), [0]) self.assertEqual(info['page_columns'], [['aaa', 'bbb']]) def test_queryLexicon_uses_pipeline_for_normalization(self): from Products.ZCTextIndex.Lexicon import CaseNormalizer WORDS = 'aaa bbb ccc ddd eee fff ggg'.split() lexicon = self._makeOne('test', 'Testing', CaseNormalizer()) lexicon.sourceToWordIds(WORDS) info = lexicon.queryLexicon(REQUEST=None, words=['AA*', 'Bbb*']) self.assertEqual(info['page'], 0) self.assertEqual(info['rows'], 20) self.assertEqual(info['cols'], 4) self.assertEqual(info['start_word'], 1) self.assertEqual(info['end_word'], 2) self.assertEqual(info['word_count'], 2) self.assertEqual(list(info['page_range']), [0]) self.assertEqual(info['page_columns'], [['aaa', 'bbb']]) def test_suite(): s = unittest.TestSuite() for klass in (CosineIndexTests, OkapiIndexTests, CosineQueryTests, OkapiQueryTests, PLexiconTests): s.addTest(unittest.makeSuite(klass)) return s if __name__=='__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/testPipelineFactory.py0000644000175000017500000000356412214017454031676 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from unittest import TestCase, TestSuite, main, makeSuite from Products.ZCTextIndex.interfaces import IPipelineElement from Products.ZCTextIndex.PipelineFactory import PipelineElementFactory from zope.interface import implements class NullPipelineElement: implements(IPipelineElement) def process(source): pass class PipelineFactoryTest(TestCase): def setUp(self): self.huey = NullPipelineElement() self.dooey = NullPipelineElement() self.louie = NullPipelineElement() self.daffy = NullPipelineElement() def testPipeline(self): pf = PipelineElementFactory() pf.registerFactory('donald', 'huey', self.huey) pf.registerFactory('donald', 'dooey', self.dooey) pf.registerFactory('donald', 'louie', self.louie) pf.registerFactory('looney', 'daffy', self.daffy) self.assertRaises(ValueError, pf.registerFactory,'donald', 'huey', self.huey) self.assertEqual(pf.getFactoryGroups(), ['donald', 'looney']) self.assertEqual(pf.getFactoryNames('donald'), ['dooey', 'huey', 'louie']) def test_suite(): return makeSuite(PipelineFactoryTest) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/indexhtml.py0000644000175000017500000000775412214017454027702 0ustar arnauarnau#! /usr/bin/env python """Index a collection of HTML files on the filesystem. usage: indexhtml.py [options] dir Will create an index of all files in dir or its subdirectories. options: -f data.fs -- the path to the filestorage datafile """ # XXX: Products.PluginIndexes.TextIndex no longer exists from __future__ import nested_scopes import os from time import clock import ZODB from ZODB.FileStorage import FileStorage from BTrees.IOBTree import IOBTree import transaction from Products.ZCTextIndex.ZCTextIndex import ZCTextIndex from Products.ZCTextIndex.HTMLSplitter import HTMLWordSplitter from Products.ZCTextIndex.Lexicon import Lexicon, StopWordRemover def make_zc_index(): # there's an elaborate dance necessary to construct an index class Struct: pass extra = Struct() extra.doc_attr = "read" extra.lexicon_id = "lexicon" caller = Struct() caller.lexicon = Lexicon(HTMLWordSplitter(), StopWordRemover()) return ZCTextIndex("read", extra, caller) # XXX make a splitter more like the HTMLSplitter for TextIndex # signature is # Splitter(string, stop_words, encoding, # singlechar, indexnumbers, casefolding) class MySplitter: def __init__(self): self._v_splitter = HTMLWordSplitter() def __call__(self, text, stopdict, *args, **kwargs): words = self._v_splitter._split(text) def lookup(w): return stopdict.get(w, w) return filter(None, map(lookup, words)) def main(db, root, dir): rt["index"] = index = INDEX() rt["files"] = paths = IOBTree() transaction.commit() zodb_time = 0.0 pack_time = 0.0 files = [os.path.join(dir, file) for file in os.listdir(dir)] docid = 0 t0 = clock() for file in files: if os.path.isdir(file): files += [os.path.join(file, sub) for sub in os.listdir(file)] else: if not file.endswith(".html"): continue docid += 1 if LIMIT is not None and docid > LIMIT: break if VERBOSE: print "%5d" % docid, file f = open(file, "rb") paths[docid] = file index.index_object(docid, f) f.close() if docid % TXN_INTERVAL == 0: z0 = clock() transaction.commit() z1 = clock() zodb_time += z1 - z0 if VERBOSE: print "commit took", z1 - z0, zodb_time if docid % PACK_INTERVAL == 0: p0 = clock() db.pack() p1 = clock() zodb_time += p1 - p0 pack_time += p1 - p0 if VERBOSE: print "pack took", p1 - p0, pack_time z0 = clock() transaction.commit() z1 = t1 = clock() total_time = t1 - t0 zodb_time += z1 - z0 if VERBOSE: print "Total index time", total_time print "Non-pack time", total_time - pack_time print "Non-ZODB time", total_time - zodb_time if __name__ == "__main__": import sys import getopt VERBOSE = 0 FSPATH = "Data.fs" TXN_INTERVAL = 100 PACK_INTERVAL = 500 LIMIT = None INDEX = make_zc_index try: opts, args = getopt.getopt(sys.argv[1:], 'vf:t:p:n:T') except getopt.error, msg: print msg print __doc__ sys.exit(2) for o, v in opts: if o == '-v': VERBOSE += 1 if o == '-f': FSPATH = v if o == '-t': TXN_INTERVAL = int(v) if o == '-p': PACK_INTERVAL = int(v) if o == '-n': LIMIT = int(v) # if o == '-T': # INDEX = make_old_index if len(args) != 1: print "Expected on argument" print __doc__ sys.exit(2) dir = args[0] fs = FileStorage(FSPATH) db = ZODB.DB(fs) cn = db.open() rt = cn.root() dir = os.path.join(os.getcwd(), dir) print dir main(db, rt, dir) cn.close() fs.close() zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/testParseTree.py0000644000175000017500000000413512214017454030466 0ustar arnauarnau############################################################################## # # Copyright (c) 2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest class ParseTreeTests(unittest.TestCase): def _conforms(self, klass): from zope.interface.verify import verifyClass from Products.ZCTextIndex.interfaces import IQueryParseTree verifyClass(IQueryParseTree, klass) def test_ParseTreeNode_conforms_to_IQueryParseTree(self): from Products.ZCTextIndex.ParseTree import ParseTreeNode self._conforms(ParseTreeNode) def test_OrNode_conforms_to_IQueryParseTree(self): from Products.ZCTextIndex.ParseTree import OrNode self._conforms(OrNode) def test_AndNode_conforms_to_IQueryParseTree(self): from Products.ZCTextIndex.ParseTree import AndNode self._conforms(AndNode) def test_NotNode_conforms_to_IQueryParseTree(self): from Products.ZCTextIndex.ParseTree import NotNode self._conforms(NotNode) def test_GlobNode_conforms_to_IQueryParseTree(self): from Products.ZCTextIndex.ParseTree import GlobNode self._conforms(GlobNode) def test_AtomNode_conforms_to_IQueryParseTree(self): from Products.ZCTextIndex.ParseTree import AtomNode self._conforms(AtomNode) def test_PhraseNode_conforms_to_IQueryParseTree(self): from Products.ZCTextIndex.ParseTree import PhraseNode self._conforms(PhraseNode) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(ParseTreeTests), )) if __name__=="__main__": unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/testIndex.py0000644000175000017500000002646512214017454027655 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import os from unittest import TestCase, TestSuite, main, makeSuite import transaction from BTrees.Length import Length from Products.ZCTextIndex.Lexicon import Lexicon, Splitter from Products.ZCTextIndex.CosineIndex import CosineIndex from Products.ZCTextIndex.OkapiIndex import OkapiIndex # Subclasses must set a class variable IndexFactory to the appropriate # index object constructor. class IndexTest(TestCase): def setUp(self): self.lexicon = Lexicon(Splitter()) self.index = self.IndexFactory(self.lexicon) def test_index_document(self, DOCID=1): doc = "simple document contains five words" self.assert_(not self.index.has_doc(DOCID)) self.index.index_doc(DOCID, doc) self.assert_(self.index.has_doc(DOCID)) self.assert_(self.index._docweight[DOCID]) self.assertEqual(len(self.index._docweight), 1) self.assertEqual( len(self.index._docweight), self.index.document_count()) self.assertEqual(len(self.index._wordinfo), 5) self.assertEqual(len(self.index._docwords), 1) self.assertEqual(len(self.index.get_words(DOCID)), 5) self.assertEqual(len(self.index._wordinfo), self.index.length()) for map in self.index._wordinfo.values(): self.assertEqual(len(map), 1) self.assert_(map.has_key(DOCID)) def test_unindex_document(self): DOCID = 1 self.test_index_document(DOCID) self.index.unindex_doc(DOCID) self.assertEqual(len(self.index._docweight), 0) self.assertEqual( len(self.index._docweight), self.index.document_count()) self.assertEqual(len(self.index._wordinfo), 0) self.assertEqual(len(self.index._docwords), 0) self.assertEqual(len(self.index._wordinfo), self.index.length()) def test_index_two_documents(self): self.test_index_document() doc = "another document just four" DOCID = 2 self.index.index_doc(DOCID, doc) self.assert_(self.index._docweight[DOCID]) self.assertEqual(len(self.index._docweight), 2) self.assertEqual( len(self.index._docweight), self.index.document_count()) self.assertEqual(len(self.index._wordinfo), 8) self.assertEqual(len(self.index._docwords), 2) self.assertEqual(len(self.index.get_words(DOCID)), 4) self.assertEqual(len(self.index._wordinfo), self.index.length()) wids = self.lexicon.termToWordIds("document") self.assertEqual(len(wids), 1) document_wid = wids[0] for wid, map in self.index._wordinfo.items(): if wid == document_wid: self.assertEqual(len(map), 2) self.assert_(map.has_key(1)) self.assert_(map.has_key(DOCID)) else: self.assertEqual(len(map), 1) def test_index_two_unindex_one(self): # index two documents, unindex one, and test the results self.test_index_two_documents() self.index.unindex_doc(1) DOCID = 2 self.assertEqual(len(self.index._docweight), 1) self.assertEqual( len(self.index._docweight), self.index.document_count()) self.assert_(self.index._docweight[DOCID]) self.assertEqual(len(self.index._wordinfo), 4) self.assertEqual(len(self.index._docwords), 1) self.assertEqual(len(self.index.get_words(DOCID)), 4) self.assertEqual(len(self.index._wordinfo), self.index.length()) for map in self.index._wordinfo.values(): self.assertEqual(len(map), 1) self.assert_(map.has_key(DOCID)) def test_index_duplicated_words(self, DOCID=1): doc = "very simple repeat repeat repeat document test" self.index.index_doc(DOCID, doc) self.assert_(self.index._docweight[DOCID]) self.assertEqual(len(self.index._wordinfo), 5) self.assertEqual(len(self.index._docwords), 1) self.assertEqual(len(self.index.get_words(DOCID)), 7) self.assertEqual(len(self.index._wordinfo), self.index.length()) self.assertEqual( len(self.index._docweight), self.index.document_count()) wids = self.lexicon.termToWordIds("repeat") self.assertEqual(len(wids), 1) repititive_wid = wids[0] for wid, map in self.index._wordinfo.items(): self.assertEqual(len(map), 1) self.assert_(map.has_key(DOCID)) def test_simple_query_oneresult(self): self.index.index_doc(1, 'not the same document') results = self.index.search("document") self.assertEqual(list(results.keys()), [1]) def test_simple_query_noresults(self): self.index.index_doc(1, 'not the same document') results = self.index.search("frobnicate") self.assertEqual(list(results.keys()), []) def test_query_oneresult(self): self.index.index_doc(1, 'not the same document') self.index.index_doc(2, 'something about something else') results = self.index.search("document") self.assertEqual(list(results.keys()), [1]) def test_search_phrase(self): self.index.index_doc(1, "the quick brown fox jumps over the lazy dog") self.index.index_doc(2, "the quick fox jumps lazy over the brown dog") results = self.index.search_phrase("quick brown fox") self.assertEqual(list(results.keys()), [1]) def test_search_glob(self): self.index.index_doc(1, "how now brown cow") self.index.index_doc(2, "hough nough browne cough") self.index.index_doc(3, "bar brawl") results = self.index.search_glob("bro*") self.assertEqual(list(results.keys()), [1, 2]) results = self.index.search_glob("b*") self.assertEqual(list(results.keys()), [1, 2, 3]) class CosineIndexTest(IndexTest): IndexFactory = CosineIndex class OkapiIndexTest(IndexTest): IndexFactory = OkapiIndex class TestIndexConflict(TestCase): db = None def tearDown(self): if self.db is not None: self.db.close() self.storage.cleanup() def openDB(self): from ZODB.FileStorage import FileStorage from ZODB.DB import DB n = 'fs_tmp__%s' % os.getpid() self.storage = FileStorage(n) self.db = DB(self.storage) def test_index_doc_conflict(self): self.index = OkapiIndex(Lexicon()) self.openDB() r1 = self.db.open().root() r1['i'] = self.index transaction.commit() r2 = self.db.open().root() copy = r2['i'] # Make sure the data is loaded list(copy._docweight.items()) list(copy._docwords.items()) list(copy._wordinfo.items()) list(copy._lexicon._wids.items()) list(copy._lexicon._words.items()) self.assertEqual(self.index._p_serial, copy._p_serial) self.index.index_doc(0, 'The time has come') transaction.commit() copy.index_doc(1, 'That time has gone') transaction.commit() def test_reindex_doc_conflict(self): self.index = OkapiIndex(Lexicon()) self.index.index_doc(0, 'Sometimes change is good') self.index.index_doc(1, 'Then again, who asked') self.openDB() r1 = self.db.open().root() r1['i'] = self.index transaction.commit() r2 = self.db.open().root() copy = r2['i'] # Make sure the data is loaded list(copy._docweight.items()) list(copy._docwords.items()) list(copy._wordinfo.items()) list(copy._lexicon._wids.items()) list(copy._lexicon._words.items()) self.assertEqual(self.index._p_serial, copy._p_serial) self.index.index_doc(0, 'Sometimes change isn\'t bad') transaction.commit() copy.index_doc(1, 'Then again, who asked you?') transaction.commit() class TestUpgrade(TestCase): def test_query_before_totaldoclen_upgrade(self): self.index1 = OkapiIndex(Lexicon(Splitter())) self.index1.index_doc(0, 'The quiet of night') # Revert index1 back to a long to simulate an older index instance self.index1._totaldoclen = long(self.index1._totaldoclen()) self.assertEqual(len(self.index1.search('night')), 1) def test_upgrade_totaldoclen(self): self.index1 = OkapiIndex(Lexicon()) self.index2 = OkapiIndex(Lexicon()) self.index1.index_doc(0, 'The quiet of night') self.index2.index_doc(0, 'The quiet of night') # Revert index1 back to a long to simulate an older index instance self.index1._totaldoclen = long(self.index1._totaldoclen()) self.index1.index_doc(1, 'gazes upon my shadow') self.index2.index_doc(1, 'gazes upon my shadow') self.assertEqual( self.index1._totaldoclen(), self.index2._totaldoclen()) self.index1._totaldoclen = long(self.index1._totaldoclen()) self.index1.unindex_doc(0) self.index2.unindex_doc(0) self.assertEqual( self.index1._totaldoclen(), self.index2._totaldoclen()) def test_query_before_document_count_upgrade(self): self.index1 = OkapiIndex(Lexicon(Splitter())) self.index1.index_doc(0, 'The quiet of night') # Revert index1 back to a long to simulate an older index instance del self.index1.document_count self.assertEqual(len(self.index1.search('night')), 1) def test_upgrade_document_count(self): self.index1 = OkapiIndex(Lexicon()) self.index2 = OkapiIndex(Lexicon()) self.index1.index_doc(0, 'The quiet of night') self.index2.index_doc(0, 'The quiet of night') # Revert index1 back to simulate an older index instance del self.index1.document_count self.index1.index_doc(1, 'gazes upon my shadow') self.index2.index_doc(1, 'gazes upon my shadow') self.assert_(self.index1.document_count.__class__ is Length) self.assertEqual( self.index1.document_count(), self.index2.document_count()) del self.index1.document_count self.index1.unindex_doc(0) self.index2.unindex_doc(0) self.assert_(self.index1.document_count.__class__ is Length) self.assertEqual( self.index1.document_count(), self.index2.document_count()) def test_suite(): return TestSuite((makeSuite(CosineIndexTest), makeSuite(OkapiIndexTest), makeSuite(TestIndexConflict), makeSuite(TestUpgrade), )) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/testLexicon.py0000644000175000017500000002217212214017454030176 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Lexicon unit tests. """ import unittest import os import sys import transaction class StupidPipelineElement: def __init__(self, fromword, toword): self.__fromword = fromword self.__toword = toword def process(self, seq): res = [] for term in seq: if term == self.__fromword: res.append(self.__toword) else: res.append(term) return res class WackyReversePipelineElement: def __init__(self, revword): self.__revword = revword def process(self, seq): res = [] for term in seq: if term == self.__revword: x = list(term) x.reverse() res.append(''.join(x)) else: res.append(term) return res class StopWordPipelineElement: def __init__(self, stopdict={}): self.__stopdict = stopdict def process(self, seq): res = [] for term in seq: if self.__stopdict.get(term): continue else: res.append(term) return res class LexiconTests(unittest.TestCase): def _getTargetClass(self): from Products.ZCTextIndex.Lexicon import Lexicon return Lexicon def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def test_interfaces(self): from Products.ZCTextIndex.interfaces import ILexicon from zope.interface.verify import verifyClass verifyClass(ILexicon, self._getTargetClass()) def test_clear(self): lexicon = self._makeOne() lexicon.sourceToWordIds('foo') self.assertEqual(len(lexicon._wids), 1) self.assertEqual(len(lexicon._words), 1) self.assertEqual(lexicon.length(), 1) lexicon.clear() self.assertEqual(len(lexicon._wids), 0) self.assertEqual(len(lexicon._words), 0) self.assertEqual(lexicon.length(), 0) def testSourceToWordIds(self): from Products.ZCTextIndex.Lexicon import Splitter lexicon = self._makeOne(Splitter()) wids = lexicon.sourceToWordIds('cats and dogs') self.assertEqual(len(wids), 3) first = wids[0] self.assertEqual(wids, [first, first+1, first+2]) def testTermToWordIds(self): from Products.ZCTextIndex.Lexicon import Splitter lexicon = self._makeOne(Splitter()) wids = lexicon.sourceToWordIds('cats and dogs') wids = lexicon.termToWordIds('dogs') self.assertEqual(len(wids), 1) self.assert_(wids[0] > 0) def testMissingTermToWordIds(self): from Products.ZCTextIndex.Lexicon import Splitter lexicon = self._makeOne(Splitter()) wids = lexicon.sourceToWordIds('cats and dogs') wids = lexicon.termToWordIds('boxes') self.assertEqual(wids, [0]) def testTermToWordIdsWithProcess_post_glob(self): """This test is for added process_post_glob""" from Products.ZCTextIndex.Lexicon import Splitter class AddedSplitter(Splitter): def process_post_glob(self, lst): assert lst == ['dogs'] return ['dogs'] lexicon = self._makeOne(AddedSplitter()) wids = lexicon.sourceToWordIds('cats and dogs') wids = lexicon.termToWordIds('dogs') self.assertEqual(len(wids), 1) self.assert_(wids[0] > 0) def testMissingTermToWordIdsWithProcess_post_glob(self): """This test is for added process_post_glob""" from Products.ZCTextIndex.Lexicon import Splitter class AddedSplitter(Splitter): def process_post_glob(self, lst): assert lst == ['dogs'] return ['fox'] lexicon = self._makeOne(AddedSplitter()) wids = lexicon.sourceToWordIds('cats and dogs') wids = lexicon.termToWordIds('dogs') self.assertEqual(wids, [0]) def testOnePipelineElement(self): from Products.ZCTextIndex.Lexicon import Splitter lexicon = self._makeOne(Splitter(), StupidPipelineElement('dogs', 'fish')) wids = lexicon.sourceToWordIds('cats and dogs') wids = lexicon.termToWordIds('fish') self.assertEqual(len(wids), 1) self.assert_(wids[0] > 0) def testSplitterAdaptorFold(self): from Products.ZCTextIndex.Lexicon import CaseNormalizer from Products.ZCTextIndex.Lexicon import Splitter lexicon = self._makeOne(Splitter(), CaseNormalizer()) wids = lexicon.sourceToWordIds('CATS and dogs') wids = lexicon.termToWordIds('cats and dogs') self.assertEqual(len(wids), 3) first = wids[0] self.assertEqual(wids, [first, first+1, first+2]) def testSplitterAdaptorNofold(self): from Products.ZCTextIndex.Lexicon import Splitter lexicon = self._makeOne(Splitter()) wids = lexicon.sourceToWordIds('CATS and dogs') wids = lexicon.termToWordIds('cats and dogs') self.assertEqual(len(wids), 3) second = wids[1] self.assertEqual(wids, [0, second, second+1]) def testTwoElementPipeline(self): from Products.ZCTextIndex.Lexicon import Splitter lexicon = self._makeOne(Splitter(), StupidPipelineElement('cats', 'fish'), WackyReversePipelineElement('fish')) wids = lexicon.sourceToWordIds('cats and dogs') wids = lexicon.termToWordIds('hsif') self.assertEqual(len(wids), 1) self.assert_(wids[0] > 0) def testThreeElementPipeline(self): from Products.ZCTextIndex.Lexicon import Splitter lexicon = self._makeOne(Splitter(), StopWordPipelineElement({'and':1}), StupidPipelineElement('dogs', 'fish'), WackyReversePipelineElement('fish')) wids = lexicon.sourceToWordIds('cats and dogs') wids = lexicon.termToWordIds('hsif') self.assertEqual(len(wids), 1) self.assert_(wids[0] > 0) def testSplitterLocaleAwareness(self): import locale from Products.ZCTextIndex.Lexicon import Splitter from Products.ZCTextIndex.HTMLSplitter import HTMLWordSplitter loc = locale.setlocale(locale.LC_ALL) # get current locale # set German locale try: if sys.platform != 'win32': locale.setlocale(locale.LC_ALL, 'de_DE.ISO8859-1') else: locale.setlocale(locale.LC_ALL, 'German_Germany.1252') except locale.Error: return # This test doesn't work here :-( expected = ['m\xfclltonne', 'waschb\xe4r', 'beh\xf6rde', '\xfcberflieger'] words = [" ".join(expected)] words = Splitter().process(words) self.assertEqual(words, expected) words = HTMLWordSplitter().process(words) self.assertEqual(words, expected) locale.setlocale(locale.LC_ALL, loc) # restore saved locale class LexiconConflictTests(unittest.TestCase): db = None def _getTargetClass(self): from Products.ZCTextIndex.Lexicon import Lexicon return Lexicon def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def tearDown(self): if self.db is not None: self.db.close() self.storage.cleanup() def openDB(self): from ZODB.DB import DB from ZODB.FileStorage import FileStorage n = 'fs_tmp__%s' % os.getpid() self.storage = FileStorage(n) self.db = DB(self.storage) def testAddWordConflict(self): from Products.ZCTextIndex.Lexicon import Splitter self.l = self._makeOne(Splitter()) self.openDB() r1 = self.db.open().root() r1['l'] = self.l transaction.commit() r2 = self.db.open().root() copy = r2['l'] # Make sure the data is loaded list(copy._wids.items()) list(copy._words.items()) copy.length() self.assertEqual(self.l._p_serial, copy._p_serial) self.l.sourceToWordIds('mary had a little lamb') transaction.commit() copy.sourceToWordIds('whose fleece was') copy.sourceToWordIds('white as snow') transaction.commit() self.assertEqual(copy.length(), 11) self.assertEqual(copy.length(), len(copy._words)) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(LexiconTests)) suite.addTest(unittest.makeSuite(LexiconConflictTests)) return suite zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/testQueryParser.py0000644000175000017500000003145012214017454031056 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from unittest import TestCase, TestSuite, main, makeSuite class TestInterfaces(TestCase): def testInterfaces(self): from zope.interface.verify import verifyClass from Products.ZCTextIndex.interfaces import IQueryParser from Products.ZCTextIndex.QueryParser import QueryParser verifyClass(IQueryParser, QueryParser) class TestQueryParserBase(TestCase): def setUp(self): from Products.ZCTextIndex.QueryParser import QueryParser from Products.ZCTextIndex.Lexicon import Lexicon from Products.ZCTextIndex.Lexicon import Splitter self.lexicon = Lexicon(Splitter()) self.parser = QueryParser(self.lexicon) def expect(self, input, output, expected_ignored=[]): tree = self.parser.parseQuery(input) ignored = self.parser.getIgnored() self.compareParseTrees(tree, output) self.assertEqual(ignored, expected_ignored) # Check that parseQueryEx() == (parseQuery(), getIgnored()) ex_tree, ex_ignored = self.parser.parseQueryEx(input) self.compareParseTrees(ex_tree, tree) self.assertEqual(ex_ignored, expected_ignored) def failure(self, input): from Products.ZCTextIndex.ParseTree import ParseError self.assertRaises(ParseError, self.parser.parseQuery, input) self.assertRaises(ParseError, self.parser.parseQueryEx, input) def compareParseTrees(self, got, expected, msg=None): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode from Products.ZCTextIndex.ParseTree import GlobNode from Products.ZCTextIndex.ParseTree import NotNode from Products.ZCTextIndex.ParseTree import OrNode from Products.ZCTextIndex.ParseTree import ParseTreeNode from Products.ZCTextIndex.ParseTree import PhraseNode if msg is None: msg = repr(got) self.assertEqual(isinstance(got, ParseTreeNode), 1) self.assertEqual(got.__class__, expected.__class__, msg) if isinstance(got, PhraseNode): self.assertEqual(got.nodeType(), "PHRASE", msg) self.assertEqual(got.getValue(), expected.getValue(), msg) elif isinstance(got, GlobNode): self.assertEqual(got.nodeType(), "GLOB", msg) self.assertEqual(got.getValue(), expected.getValue(), msg) elif isinstance(got, AtomNode): self.assertEqual(got.nodeType(), "ATOM", msg) self.assertEqual(got.getValue(), expected.getValue(), msg) elif isinstance(got, NotNode): self.assertEqual(got.nodeType(), "NOT") self.compareParseTrees(got.getValue(), expected.getValue(), msg) elif isinstance(got, AndNode) or isinstance(got, OrNode): self.assertEqual(got.nodeType(), isinstance(got, AndNode) and "AND" or "OR", msg) list1 = got.getValue() list2 = expected.getValue() self.assertEqual(len(list1), len(list2), msg) for i in range(len(list1)): self.compareParseTrees(list1[i], list2[i], msg) class TestQueryParser(TestQueryParserBase): def test001(self): from Products.ZCTextIndex.ParseTree import AtomNode self.expect("foo", AtomNode("foo")) def test002(self): from Products.ZCTextIndex.ParseTree import AtomNode self.expect("note", AtomNode("note")) def test003(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode self.expect("aa and bb AND cc", AndNode([AtomNode("aa"), AtomNode("bb"), AtomNode("cc")])) def test004(self): from Products.ZCTextIndex.ParseTree import AtomNode from Products.ZCTextIndex.ParseTree import OrNode self.expect("aa OR bb or cc", OrNode([AtomNode("aa"), AtomNode("bb"), AtomNode("cc")])) def test005(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode from Products.ZCTextIndex.ParseTree import OrNode self.expect("aa AND bb OR cc AnD dd", OrNode([AndNode([AtomNode("aa"), AtomNode("bb")]), AndNode([AtomNode("cc"), AtomNode("dd")])])) def test006(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode from Products.ZCTextIndex.ParseTree import OrNode self.expect("(aa OR bb) AND (cc OR dd)", AndNode([OrNode([AtomNode("aa"), AtomNode("bb")]), OrNode([AtomNode("cc"), AtomNode("dd")])])) def test007(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode from Products.ZCTextIndex.ParseTree import NotNode self.expect("aa AND NOT bb", AndNode([AtomNode("aa"), NotNode(AtomNode("bb"))])) def test010(self): from Products.ZCTextIndex.ParseTree import PhraseNode self.expect('"foo bar"', PhraseNode(["foo", "bar"])) def test011(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode self.expect("foo bar", AndNode([AtomNode("foo"), AtomNode("bar")])) def test012(self): from Products.ZCTextIndex.ParseTree import PhraseNode self.expect('(("foo bar"))"', PhraseNode(["foo", "bar"])) def test013(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode self.expect("((foo bar))", AndNode([AtomNode("foo"), AtomNode("bar")])) def test014(self): from Products.ZCTextIndex.ParseTree import PhraseNode self.expect("foo-bar", PhraseNode(["foo", "bar"])) def test015(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode from Products.ZCTextIndex.ParseTree import NotNode self.expect("foo -bar", AndNode([AtomNode("foo"), NotNode(AtomNode("bar"))])) def test016(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode from Products.ZCTextIndex.ParseTree import NotNode self.expect("-foo bar", AndNode([AtomNode("bar"), NotNode(AtomNode("foo"))])) def test017(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode from Products.ZCTextIndex.ParseTree import NotNode from Products.ZCTextIndex.ParseTree import PhraseNode self.expect("booh -foo-bar", AndNode([AtomNode("booh"), NotNode(PhraseNode(["foo", "bar"]))])) def test018(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode from Products.ZCTextIndex.ParseTree import NotNode from Products.ZCTextIndex.ParseTree import PhraseNode self.expect('booh -"foo bar"', AndNode([AtomNode("booh"), NotNode(PhraseNode(["foo", "bar"]))])) def test019(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode self.expect('foo"bar"', AndNode([AtomNode("foo"), AtomNode("bar")])) def test020(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode self.expect('"foo"bar', AndNode([AtomNode("foo"), AtomNode("bar")])) def test021(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode self.expect('foo"bar"blech', AndNode([AtomNode("foo"), AtomNode("bar"), AtomNode("blech")])) def test022(self): from Products.ZCTextIndex.ParseTree import GlobNode self.expect("foo*", GlobNode("foo*")) def test023(self): from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode from Products.ZCTextIndex.ParseTree import GlobNode self.expect("foo* bar", AndNode([GlobNode("foo*"), AtomNode("bar")])) def test024(self): # Split by UTF-8 fullwidth space from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode self.expect("foo\xe3\x80\x80bar", AndNode([AtomNode("foo"), AtomNode("bar")])) def test025(self): # Split by Unicode fullwidth space from Products.ZCTextIndex.ParseTree import AndNode from Products.ZCTextIndex.ParseTree import AtomNode self.expect(u"foo\u3000bar", AndNode([AtomNode(u"foo"), AtomNode(u"bar")])) def test101(self): self.failure("") def test102(self): self.failure("not") def test103(self): self.failure("or") def test104(self): self.failure("and") def test105(self): self.failure("NOT") def test106(self): self.failure("OR") def test107(self): self.failure("AND") def test108(self): self.failure("NOT foo") def test109(self): self.failure(")") def test110(self): self.failure("(") def test111(self): self.failure("foo OR") def test112(self): self.failure("foo AND") def test113(self): self.failure("OR foo") def test114(self): self.failure("AND foo") def test115(self): self.failure("(foo) bar") def test116(self): self.failure("(foo OR)") def test117(self): self.failure("(foo AND)") def test118(self): self.failure("(NOT foo)") def test119(self): self.failure("-foo") def test120(self): self.failure("-foo -bar") def test121(self): self.failure("foo OR -bar") def test122(self): self.failure("foo AND -bar") class StopWordTestQueryParser(TestQueryParserBase): def setUp(self): from Products.ZCTextIndex.QueryParser import QueryParser from Products.ZCTextIndex.Lexicon import Lexicon from Products.ZCTextIndex.Lexicon import Splitter # Only 'stop' is a stopword (but 'and' is still an operator) self.lexicon = Lexicon(Splitter(), FakeStopWordRemover()) self.parser = QueryParser(self.lexicon) def test201(self): from Products.ZCTextIndex.ParseTree import AtomNode self.expect('and/', AtomNode("and")) def test202(self): from Products.ZCTextIndex.ParseTree import AtomNode self.expect('foo AND stop', AtomNode("foo"), ["stop"]) def test203(self): from Products.ZCTextIndex.ParseTree import AtomNode self.expect('foo AND NOT stop', AtomNode("foo"), ["stop"]) def test204(self): from Products.ZCTextIndex.ParseTree import AtomNode self.expect('stop AND foo', AtomNode("foo"), ["stop"]) def test205(self): from Products.ZCTextIndex.ParseTree import AtomNode self.expect('foo OR stop', AtomNode("foo"), ["stop"]) def test206(self): from Products.ZCTextIndex.ParseTree import AtomNode self.expect('stop OR foo', AtomNode("foo"), ["stop"]) def test301(self): self.failure('stop') def test302(self): self.failure('stop stop') def test303(self): self.failure('stop AND stop') def test304(self): self.failure('stop OR stop') def test305(self): self.failure('stop -foo') def test306(self): self.failure('stop AND NOT foo') class FakeStopWordRemover: def process(self, list): return [word for word in list if word != "stop"] def test_suite(): return TestSuite((makeSuite(TestQueryParser), makeSuite(StopWordTestQueryParser), makeSuite(TestInterfaces), )) if __name__=="__main__": main(defaultTest='test_suite') zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/mailtest.py0000644000175000017500000001746512214017454027530 0ustar arnauarnau"""Test an index with a Unix mailbox file. usage: python mailtest.py [options] options: -v -- verbose Index Generation -i mailbox -n NNN -- max number of messages to read from mailbox -t NNN -- commit a transaction every NNN messages (default: 1) -p NNN -- pack every NNN messages (default: 500), and at end -p 0 -- don't pack at all -x -- exclude the message text from the data.fs Queries -q query -b NNN -- return the NNN best matches (default: 10) -c NNN -- context; if -v, show the first NNN lines of results (default: 5) The script either indexes or queries depending on whether -q or -i is passed as an option. For -i mailbox, the script reads mail messages from the mailbox and indexes them. It indexes one message at a time, then commits the transaction. For -q query, it performs a query on an existing index. If both are specified, the index is performed first. You can also interact with the index after it is completed. Load the index from the database: import ZODB from ZODB.FileStorage import FileStorage fs = FileStorage( db = ZODB.DB(fs) index = cn.open().root()["index"] index.search("python AND unicode") """ import ZODB import ZODB.FileStorage import transaction from Products.ZCTextIndex.Lexicon import \ Lexicon, CaseNormalizer, Splitter, StopWordRemover from Products.ZCTextIndex.ZCTextIndex import ZCTextIndex from BTrees.IOBTree import IOBTree from Products.ZCTextIndex.QueryParser import QueryParser import sys import mailbox import time def usage(msg): print msg print __doc__ sys.exit(2) class Message: total_bytes = 0 def __init__(self, msg): subject = msg.getheader('subject', '') author = msg.getheader('from', '') if author: summary = "%s (%s)\n" % (subject, author) else: summary = "%s\n" % subject self.text = summary + msg.fp.read() Message.total_bytes += len(self.text) class Extra: pass def index(rt, mboxfile, db, profiler): global NUM idx_time = 0 pack_time = 0 start_time = time.time() lexicon = Lexicon(Splitter(), CaseNormalizer(), StopWordRemover()) extra = Extra() extra.lexicon_id = 'lexicon' extra.doc_attr = 'text' extra.index_type = 'Okapi BM25 Rank' caller = Extra() caller.lexicon = lexicon rt["index"] = idx = ZCTextIndex("index", extra, caller) if not EXCLUDE_TEXT: rt["documents"] = docs = IOBTree() else: docs = None transaction.commit() mbox = mailbox.UnixMailbox(open(mboxfile, 'rb')) if VERBOSE: print "opened", mboxfile if not NUM: NUM = sys.maxint if profiler: itime, ptime, i = profiler.runcall(indexmbox, mbox, idx, docs, db) else: itime, ptime, i = indexmbox(mbox, idx, docs, db) idx_time += itime pack_time += ptime transaction.commit() if PACK_INTERVAL and i % PACK_INTERVAL != 0: if VERBOSE >= 2: print "packing one last time..." p0 = time.clock() db.pack(time.time()) p1 = time.clock() if VERBOSE: print "pack took %s sec" % (p1 - p0) pack_time += p1 - p0 if VERBOSE: finish_time = time.time() print print "Index time", round(idx_time / 60, 3), "minutes" print "Pack time", round(pack_time / 60, 3), "minutes" print "Index bytes", Message.total_bytes rate = (Message.total_bytes / idx_time) / 1024 print "Index rate %.2f KB/sec" % rate print "Indexing began", time.ctime(start_time) print "Indexing ended", time.ctime(finish_time) print "Wall clock minutes", round((finish_time - start_time)/60, 3) def indexmbox(mbox, idx, docs, db): idx_time = 0 pack_time = 0 i = 0 while i < NUM: _msg = mbox.next() if _msg is None: break i += 1 msg = Message(_msg) if VERBOSE >= 2: print "indexing msg", i i0 = time.clock() idx.index_object(i, msg) if not EXCLUDE_TEXT: docs[i] = msg if i % TXN_SIZE == 0: transaction.commit() i1 = time.clock() idx_time += i1 - i0 if VERBOSE and i % 50 == 0: print i, "messages indexed" print "cache size", db.cacheSize() if PACK_INTERVAL and i % PACK_INTERVAL == 0: if VERBOSE >= 2: print "packing..." p0 = time.clock() db.pack(time.time()) p1 = time.clock() if VERBOSE: print "pack took %s sec" % (p1 - p0) pack_time += p1 - p0 return idx_time, pack_time, i def query(rt, query_str, profiler): idx = rt["index"] docs = rt["documents"] start = time.clock() if profiler is None: results, num_results = idx.query(query_str, BEST) else: if WARM_CACHE: print "Warming the cache..." idx.query(query_str, BEST) start = time.clock() results, num_results = profiler.runcall(idx.query, query_str, BEST) elapsed = time.clock() - start print "query:", query_str print "# results:", len(results), "of", num_results, \ "in %.2f ms" % (elapsed * 1000) tree = QueryParser(idx.lexicon).parseQuery(query_str) qw = idx.index.query_weight(tree.terms()) for docid, score in results: scaled = 100.0 * score / qw print "docid %7d score %6d scaled %5.2f%%" % (docid, score, scaled) if VERBOSE: msg = docs[docid] ctx = msg.text.split("\n", CONTEXT) del ctx[-1] print "-" * 60 print "message:" for l in ctx: print l print "-" * 60 def main(fs_path, mbox_path, query_str, profiler): f = ZODB.FileStorage.FileStorage(fs_path) db = ZODB.DB(f, cache_size=CACHE_SIZE) cn = db.open() rt = cn.root() if mbox_path is not None: index(rt, mbox_path, db, profiler) if query_str is not None: query(rt, query_str, profiler) cn.close() db.close() f.close() if __name__ == "__main__": import getopt NUM = 0 VERBOSE = 0 PACK_INTERVAL = 500 EXCLUDE_TEXT = 0 CACHE_SIZE = 10000 TXN_SIZE = 1 BEST = 10 CONTEXT = 5 WARM_CACHE = 0 query_str = None mbox_path = None profile = None old_profile = None try: opts, args = getopt.getopt(sys.argv[1:], 'vn:p:i:q:b:c:xt:w', ['profile=', 'old-profile=']) except getopt.error, msg: usage(msg) if len(args) != 1: usage("exactly 1 filename argument required") for o, v in opts: if o == '-n': NUM = int(v) elif o == '-v': VERBOSE += 1 elif o == '-p': PACK_INTERVAL = int(v) elif o == '-q': query_str = v elif o == '-i': mbox_path = v elif o == '-b': BEST = int(v) elif o == '-x': EXCLUDE_TEXT = 1 elif o == '-t': TXN_SIZE = int(v) elif o == '-c': CONTEXT = int(v) elif o == '-w': WARM_CACHE = 1 elif o == '--profile': profile = v elif o == '--old-profile': old_profile = v fs_path, = args if profile: import hotshot profiler = hotshot.Profile(profile, lineevents=1, linetimings=1) elif old_profile: import profile profiler = profile.Profile() else: profiler = None main(fs_path, mbox_path, query_str, profiler) if profile: profiler.close() elif old_profile: import pstats profiler.dump_stats(old_profile) stats = pstats.Stats(old_profile) stats.strip_dirs().sort_stats('time').print_stats(20) zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/hs-tool.py0000755000175000017500000000633012214017454027263 0ustar arnauarnau#! /usr/bin/env python import cPickle import os.path import sys from hotshot.log import LogReader def load_line_info(log): byline = {} prevloc = None for what, place, tdelta in log: if tdelta > 0: t, nhits = byline.get(prevloc, (0, 0)) byline[prevloc] = (tdelta + t), (nhits + 1) prevloc = place return byline def basename(path, cache={}): try: return cache[path] except KeyError: fn = os.path.split(path)[1] cache[path] = fn return fn def print_results(results): for info, place in results: if place is None: # This is the startup time for the profiler, and only # occurs at the very beginning. Just ignore it, since it # corresponds to frame setup of the outermost call, not # anything that's actually interesting. continue filename, line, funcname = place print '%8d %8d' % info, basename(filename), line def annotate_results(results): files = {} for stats, place in results: if not place: continue time, hits = stats file, line, func = place l = files.get(file) if l is None: l = files[file] = [] l.append((line, hits, time)) order = files.keys() order.sort() for k in order: if os.path.exists(k): v = files[k] v.sort() annotate(k, v) def annotate(file, lines): print "-" * 60 print file print "-" * 60 f = open(file) i = 1 match = lines[0][0] for line in f: if match == i: print "%6d %8d " % lines[0][1:], line, del lines[0] if lines: match = lines[0][0] else: match = None else: print " " * 16, line, i += 1 print def get_cache_name(filename): d, fn = os.path.split(filename) cache_dir = os.path.join(d, '.hs-tool') cache_file = os.path.join(cache_dir, fn) return cache_dir, cache_file def cache_results(filename, results): cache_dir, cache_file = get_cache_name(filename) if not os.path.exists(cache_dir): os.mkdir(cache_dir) fp = open(cache_file, 'wb') try: cPickle.dump(results, fp, 1) finally: fp.close() def main(filename, annotate): cache_dir, cache_file = get_cache_name(filename) if ( os.path.isfile(cache_file) and os.path.getmtime(cache_file) > os.path.getmtime(filename)): # cached data is up-to-date: fp = open(cache_file, 'rb') results = cPickle.load(fp) fp.close() else: log = LogReader(filename) byline = load_line_info(log) # Sort results = [(v, k) for k, v in byline.items()] results.sort() cache_results(filename, results) if annotate: annotate_results(results) else: print_results(results) if __name__ == "__main__": import getopt annotate_p = 0 opts, args = getopt.getopt(sys.argv[1:], 'A') for o, v in opts: if o == '-A': annotate_p = 1 if args: filename, = args else: filename = "profile.dat" main(filename, annotate_p) zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/testNBest.py0000644000175000017500000000571412214017454027613 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from unittest import TestCase, TestSuite, main, makeSuite from Products.ZCTextIndex.NBest import NBest class NBestTest(TestCase): def testConstructor(self): self.assertRaises(ValueError, NBest, 0) self.assertRaises(ValueError, NBest, -1) for n in range(1, 11): nb = NBest(n) self.assertEqual(len(nb), 0) self.assertEqual(nb.capacity(), n) def testOne(self): nb = NBest(1) nb.add('a', 0) self.assertEqual(nb.getbest(), [('a', 0)]) nb.add('b', 1) self.assertEqual(len(nb), 1) self.assertEqual(nb.capacity(), 1) self.assertEqual(nb.getbest(), [('b', 1)]) nb.add('c', -1) self.assertEqual(len(nb), 1) self.assertEqual(nb.capacity(), 1) self.assertEqual(nb.getbest(), [('b', 1)]) nb.addmany([('d', 3), ('e', -6), ('f', 5), ('g', 4)]) self.assertEqual(len(nb), 1) self.assertEqual(nb.capacity(), 1) self.assertEqual(nb.getbest(), [('f', 5)]) def testMany(self): import random inputs = [(-i, i) for i in range(50)] reversed_inputs = inputs[:] reversed_inputs.reverse() # Test the N-best for a variety of n (1, 6, 11, ... 50). for n in range(1, len(inputs)+1, 5): expected = inputs[-n:] expected.reverse() random_inputs = inputs[:] random.shuffle(random_inputs) for source in inputs, reversed_inputs, random_inputs: # Try feeding them one at a time. nb = NBest(n) for item, score in source: nb.add(item, score) self.assertEqual(len(nb), n) self.assertEqual(nb.capacity(), n) self.assertEqual(nb.getbest(), expected) # And again in one gulp. nb = NBest(n) nb.addmany(source) self.assertEqual(len(nb), n) self.assertEqual(nb.capacity(), n) self.assertEqual(nb.getbest(), expected) for i in range(1, n+1): self.assertEqual(nb.pop_smallest(), expected[-i]) self.assertRaises(IndexError, nb.pop_smallest) def test_suite(): return makeSuite(NBestTest) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/testHTMLSplitter.py0000644000175000017500000000571012214017454031067 0ustar arnauarnau############################################################################## # # Copyright (c) 2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test zope.index.text.htmlsplitter """ import unittest class HTMLWordSplitterTests(unittest.TestCase): # Subclasses must define '_getBTreesFamily' def _getTargetClass(self): from Products.ZCTextIndex.HTMLSplitter import HTMLWordSplitter return HTMLWordSplitter def _makeOne(self): return self._getTargetClass()() def test_class_conforms_to_ISplitter(self): from zope.interface.verify import verifyClass from Products.ZCTextIndex.interfaces import ISplitter verifyClass(ISplitter, self._getTargetClass()) def test_instance_conforms_to_ISplitter(self): from zope.interface.verify import verifyObject from Products.ZCTextIndex.interfaces import ISplitter verifyObject(ISplitter, self._makeOne()) def test_process_empty_string(self): splitter = self._makeOne() self.assertEqual(splitter.process(['']), []) def test_process_no_markup(self): splitter = self._makeOne() self.assertEqual(splitter.process(['abc def']), ['abc', 'def']) def test_process_w_markup(self): splitter = self._makeOne() self.assertEqual(splitter.process(['

abc

 

def

']), ['abc', 'def']) def test_process_no_markup_w_glob(self): splitter = self._makeOne() self.assertEqual(splitter.process(['abc?def hij*klm nop* qrs?']), ['abc', 'def', 'hij', 'klm', 'nop', 'qrs']) def test_processGlob_empty_string(self): splitter = self._makeOne() self.assertEqual(splitter.processGlob(['']), []) def test_processGlob_no_markup_no_glob(self): splitter = self._makeOne() self.assertEqual(splitter.processGlob(['abc def']), ['abc', 'def']) def test_processGlob_w_markup_no_glob(self): splitter = self._makeOne() self.assertEqual(splitter.processGlob(['

abc

  ' '

def

']), ['abc', 'def']) def test_processGlob_no_markup_w_glob(self): splitter = self._makeOne() self.assertEqual(splitter.processGlob(['abc?def hij*klm nop* qrs?']), ['abc?def', 'hij*klm', 'nop*', 'qrs?']) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(HTMLWordSplitterTests), )) zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/testSetOps.py0000644000175000017500000001176212214017454030015 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from unittest import TestCase, TestSuite, main, makeSuite from BTrees.IIBTree import IIBTree, IIBucket from Products.ZCTextIndex.SetOps import mass_weightedIntersection from Products.ZCTextIndex.SetOps import mass_weightedUnion class TestSetOps(TestCase): def testEmptyLists(self): self.assertEqual(len(mass_weightedIntersection([])), 0) self.assertEqual(len(mass_weightedUnion([])), 0) def testIdentity(self): t = IIBTree([(1, 2)]) b = IIBucket([(1, 2)]) for x in t, b: for func in mass_weightedUnion, mass_weightedIntersection: result = func([(x, 1)]) self.assertEqual(len(result), 1) self.assertEqual(list(result.items()), list(x.items())) def testScalarMultiply(self): t = IIBTree([(1, 2), (2, 3), (3, 4)]) allkeys = [1, 2, 3] b = IIBucket(t) for x in t, b: self.assertEqual(list(x.keys()), allkeys) for func in mass_weightedUnion, mass_weightedIntersection: for factor in 0, 1, 5, 10: result = func([(x, factor)]) self.assertEqual(allkeys, list(result.keys())) for key in x.keys(): self.assertEqual(x[key] * factor, result[key]) def testPairs(self): t1 = IIBTree([(1, 10), (3, 30), (7, 70)]) t2 = IIBTree([(3, 30), (5, 50), (7, 7), (9, 90)]) allkeys = [1, 3, 5, 7, 9] b1 = IIBucket(t1) b2 = IIBucket(t2) for x in t1, t2, b1, b2: for key in x.keys(): self.assertEqual(key in allkeys, 1) for y in t1, t2, b1, b2: for w1, w2 in (0, 0), (1, 10), (10, 1), (2, 3): # Test the union. expected = [] for key in allkeys: if x.has_key(key) or y.has_key(key): result = x.get(key, 0) * w1 + y.get(key, 0) * w2 expected.append((key, result)) expected.sort() got = mass_weightedUnion([(x, w1), (y, w2)]) self.assertEqual(expected, list(got.items())) got = mass_weightedUnion([(y, w2), (x, w1)]) self.assertEqual(expected, list(got.items())) # Test the intersection. expected = [] for key in allkeys: if x.has_key(key) and y.has_key(key): result = x[key] * w1 + y[key] * w2 expected.append((key, result)) expected.sort() got = mass_weightedIntersection([(x, w1), (y, w2)]) self.assertEqual(expected, list(got.items())) got = mass_weightedIntersection([(y, w2), (x, w1)]) self.assertEqual(expected, list(got.items())) def testMany(self): import random N = 15 # number of IIBTrees to feed in L = [] commonkey = N * 1000 allkeys = {commonkey: 1} for i in range(N): t = IIBTree() t[commonkey] = i for j in range(N-i): key = i + j allkeys[key] = 1 t[key] = N*i + j L.append((t, i+1)) random.shuffle(L) allkeys = allkeys.keys() allkeys.sort() # Test the union. expected = [] for key in allkeys: sum = 0 for t, w in L: if t.has_key(key): sum += t[key] * w expected.append((key, sum)) # print 'union', expected got = mass_weightedUnion(L) self.assertEqual(expected, list(got.items())) # Test the intersection. expected = [] for key in allkeys: sum = 0 for t, w in L: if t.has_key(key): sum += t[key] * w else: break else: # We didn't break out of the loop so it's in the intersection. expected.append((key, sum)) # print 'intersection', expected got = mass_weightedIntersection(L) self.assertEqual(expected, list(got.items())) def test_suite(): return makeSuite(TestSetOps) if __name__=="__main__": main(defaultTest='test_suite') zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/__init__.py0000644000175000017500000000121712214017454027431 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Test package.""" zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/python.txt0000644000175000017500000001060312214017454027401 0ustar arnauarnauSearch results for python.org query: "nested recursive functions" Ultraseek 83% http://www.python.org/dev/doc/maint22/whatsnew/node9.html 43% http://python.sourceforge.net/peps/pep-0227.txt 37% http://www.python.org/dev/doc/maint22/lib/module-pprint.html 37% http://www.python.org/doc/1.5.2p1/lib/module-pprint.html 37% http://www.python.org/doc/2.0.1/lib/module-pprint.html 37% http://www.python.org/doc/1.5.2/lib/module-pprint.html 37% http://www.python.org/doc/1.6/lib/module-pprint.html 37% http://www.python.org/doc/1.5.1/lib/module-pprint.html 37% http://www.python.org/doc/1.5/lib/node54.html 35% http://www.python.org/workshops/2000-01/proceedings/papers/tismers/ spcpaper.htm Google www.python.org/peps/pep-0227.html www.python.org/dev/doc/maint22/whatsnew/node9.html www.python.org/cgi-bin/faqw.py?req=recent&days=28 www.python.org/peps/pep-0255.html www.python.org/doc/current/lib/node558.html www.python.org/doc/1.5.2/lib/node52.html www.python.org/workshops/2000-01/proceedings/papers/tismers/spcpaper.pdf www.python.org/2.0/ www.python.org/2.0.1/NEWS.txt www.python.org/peps/pep-0266.html query: "explicit better than implicit" Ultraseek: http://www.python.org/dev/doc/maint22/lib/differ-examples.html http://www.python.org/doc/essays/ppt/python10/py10keynote.ppt http://www.python.org/dev/culture.html http://www.python.org/doc/Humor.html http://www.python.org/dev/doc/maint22/ref/implicit-joining.html http://www.python.org/dev/doc/maint22/ref/explicit-joining.html ttp://www.python.org/workshops/2000-01/proceedings/papers/ tigges-wyvill/tigges-wyvill.html http://www.python.org/peps/pep-0285.txt http://www.python.org/peps/pep-0285.html Google: www.python.org/doc/current/lib/differ-examples.html www.python.org/doc/essays/ppt/python10/py10keynote.pdf www.python.org/dev/culture.html www.python.org/doc/essays/ppt/python10/py10keynote.ppt www.python.org/peps/pep-0285.html www.python.org/peps/pep-0287.html www.python.org/peps/pep-0287.txt www.python.org/peps/pep-0209.html www.python.org/~guido/Proposal.txt www.python.org/~guido/Proposal.doc query: "build hpux" Ultraseek: 51% http://www.python.org/1.5/patches-1.5.1/configure.2.txt 47% http://www.python.org/dev/doc/devel/whatsnew/node5.html 43% http://www.python.org/1.5/patches-1.5.1/ 43% http://www.python.org/2.0/ 41% http://www.python.org/peps/pep-0243.html 41 % http://www.python.org/ftp/python/binaries-1.3/ python-HP-UX-A.09.05-full.README 39% http://www.python.org/ftp/python/binaries-1.3/ python-hppa1.1-hp-hpux10.10.README 39% http://www.python.org/ftp/python/binaries-1.3/ python-hppa1.1-hp-hpux10.10.README 35% http://www.python.org/peps/pep-0243.txt 35% http://www.python.org/2.0.1/NEWS.txt 35% http://python.sourceforge.net/peps/pep-0243.txt Google: www.python.org/2.1.1/NEWS.txt www.python.org/1.5/NEWS-152b2.txt query: "cannot create 'method-wrapper' instances" Ultraseek http://python.sourceforge.net/peps/pep-0007.txt http://www.python.org/workshops/1994-11/C++Python.txt http://www.python.org/peps/pep-0231.txt http://www.python.org/peps/pep-0231.html http://python.sourceforge.net/peps/pep-0231.txt http://www.python.org/dev/doc/maint22/lib/node383.html http://www.python.org/workshops/1994-11/BuiltInClasses/ BuiltInClasses_7.html http://www.python.org/workshops/1994-11/persistency.html http://www.python.org/dev/doc/maint22/lib/organizing-tests.html http://www.python.org/dev/doc/maint22/lib/module-SocketServer.html Google: no matches query: "extension module C++" http://www.python.org/dev/doc/devel/ext/building.html http://www.python.org/dev/doc/maint22/ext/module-defn-options.html http://www.python.org/dev/doc/maint21/ext/building-on-unix.html http://www.python.org/doc/1.6/ext/building-on-unix.html http://www.python.org/sigs/c++-sig/ http://www.python.org/dev/doc/maint22/ext/intro.html http://www.python.org/dev/doc/maint22/ext/cplusplus.html http://www.python.org/doc/1.4/ext/node18.html http://www.python.org/doc/1.6/ext/building-on-windows.html http://www.python.org/doc/1.6/dist/node12.html Google: www.python.org/doc/current/ext/building-on-unix.html www.python.org/doc/current/ext/intro.html www.python.org/doc/current/ext/ext.html www.python.org/sigs/c++-sig/ www.python.org/doc/current/ext/ module-defn-options.html www.python.org/doc/1.5.2p2/ext/building-on-unix.html www.python.org/doc/1.5.2p2/ext/contents.html www.python.org/doc/1.5.2p2/ext/ext.html www.python.org/doc/2.1.2/ext/building-on-unix.html www.python.org/doc/1.5.1/ext/intro.html zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/testStopper.py0000644000175000017500000000326512214017454030233 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for the C version of the StopWordRemover.""" import unittest from Products.ZCTextIndex import stopper class StopperTest(unittest.TestCase): def test_process_typeerror(self): self.assertRaises(TypeError, stopper.process, 42, []) self.assertRaises(TypeError, stopper.process, {}, 42) self.assertRaises(TypeError, stopper.process, {}) self.assertRaises(TypeError, stopper.process, {}, [], 'extra arg') def test_process_nostops(self): words = ['a', 'b', 'c', 'splat!'] self.assertEqual(words, stopper.process({}, words)) def test_process_somestops(self): d = {'b':1, 'splat!':1} words = ['a', 'b', 'c', 'splat!'] self.assertEqual(['a', 'c'], stopper.process(d, words)) def test_process_allstops(self): d = {'a':1, 'b':1, 'c':1, 'splat!':1} words = ['a', 'b', 'c', 'splat!'] self.assertEqual([], stopper.process(d, words)) def test_suite(): return unittest.makeSuite(StopperTest) if __name__ == "__main__": unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/mhindex.py0000644000175000017500000004501312214017454027330 0ustar arnauarnau"""MH mail indexer. To index messages from a single folder (messages defaults to 'all'): mhindex.py [options] -u +folder [messages ...] To bulk index all messages from several folders: mhindex.py [options] -b folder ...; the folder name ALL means all folders. To execute a single query: mhindex.py [options] query To enter interactive query mode: mhindex.py [options] Common options: -d FILE -- specify the Data.fs to use (default ~/.Data.fs) -w -- dump the word list in alphabetical order and exit -W -- dump the word list ordered by word id and exit Indexing options: -O -- do a prescan on the data to compute optimal word id assignments; this is only useful the first time the Data.fs is used -t N -- commit a transaction after every N messages (default 20000) -p N -- pack after every N commits (by default no packing is done) Querying options: -m N -- show at most N matching lines from the message (default 3) -n N -- show the N best matching messages (default 3) """ import os import re import sys import time import mhlib import getopt import traceback from StringIO import StringIO from stat import ST_MTIME DATAFS = "~/.Data.fs" ZOPECODE = "~/projects/Zope/lib/python" sys.path.append(os.path.expanduser(ZOPECODE)) from ZODB import DB from ZODB.FileStorage import FileStorage from Persistence import Persistent from BTrees.IOBTree import IOBTree from BTrees.OIBTree import OIBTree from BTrees.IIBTree import IIBTree import transaction from Products.ZCTextIndex.NBest import NBest from Products.ZCTextIndex.OkapiIndex import OkapiIndex from Products.ZCTextIndex.Lexicon import Lexicon, Splitter from Products.ZCTextIndex.Lexicon import CaseNormalizer, StopWordRemover from Products.ZCTextIndex.QueryParser import QueryParser from Products.ZCTextIndex.StopDict import get_stopdict NBEST = 3 MAXLINES = 3 def main(): try: opts, args = getopt.getopt(sys.argv[1:], "bd:fhm:n:Op:t:uwW") except getopt.error, msg: print msg print "use -h for help" return 2 update = 0 bulk = 0 optimize = 0 nbest = NBEST maxlines = MAXLINES datafs = os.path.expanduser(DATAFS) pack = 0 trans = 20000 dumpwords = dumpwids = dumpfreqs = 0 for o, a in opts: if o == "-b": bulk = 1 if o == "-d": datafs = a if o == "-f": dumpfreqs = 1 if o == "-h": print __doc__ return if o == "-m": maxlines = int(a) if o == "-n": nbest = int(a) if o == "-O": optimize = 1 if o == "-p": pack = int(a) if o == "-t": trans = int(a) if o == "-u": update = 1 if o == "-w": dumpwords = 1 if o == "-W": dumpwids = 1 ix = Indexer(datafs, writable=update or bulk, trans=trans, pack=pack) if dumpfreqs: ix.dumpfreqs() if dumpwords: ix.dumpwords() if dumpwids: ix.dumpwids() if dumpwords or dumpwids or dumpfreqs: return if bulk: if optimize: ix.optimize(args) ix.bulkupdate(args) elif update: ix.update(args) elif args: for i in range(len(args)): a = args[i] if " " in a: if a[0] == "-": args[i] = '-"' + a[1:] + '"' else: args[i] = '"' + a + '"' ix.query(" ".join(args), nbest, maxlines) else: ix.interact(nbest) if pack: ix.pack() class Indexer: filestorage = database = connection = root = None def __init__(self, datafs, writable=0, trans=0, pack=0): self.trans_limit = trans self.pack_limit = pack self.trans_count = 0 self.pack_count = 0 self.stopdict = get_stopdict() self.mh = mhlib.MH() self.filestorage = FileStorage(datafs, read_only=(not writable)) self.database = DB(self.filestorage) self.connection = self.database.open() self.root = self.connection.root() try: self.index = self.root["index"] except KeyError: self.index = self.root["index"] = TextIndex() try: self.docpaths = self.root["docpaths"] except KeyError: self.docpaths = self.root["docpaths"] = IOBTree() try: self.doctimes = self.root["doctimes"] except KeyError: self.doctimes = self.root["doctimes"] = IIBTree() try: self.watchfolders = self.root["watchfolders"] except KeyError: self.watchfolders = self.root["watchfolders"] = {} self.path2docid = OIBTree() for docid in self.docpaths.keys(): path = self.docpaths[docid] self.path2docid[path] = docid try: self.maxdocid = max(self.docpaths.keys()) except ValueError: self.maxdocid = 0 print len(self.docpaths), "Document ids" print len(self.path2docid), "Pathnames" print self.index.lexicon.length(), "Words" def dumpfreqs(self): lexicon = self.index.lexicon index = self.index.index assert isinstance(index, OkapiIndex) L = [] for wid in lexicon.wids(): freq = 0 for f in index._wordinfo.get(wid, {}).values(): freq += f L.append((freq, wid, lexicon.get_word(wid))) L.sort() L.reverse() for freq, wid, word in L: print "%10d %10d %s" % (wid, freq, word) def dumpwids(self): lexicon = self.index.lexicon index = self.index.index assert isinstance(index, OkapiIndex) for wid in lexicon.wids(): freq = 0 for f in index._wordinfo.get(wid, {}).values(): freq += f print "%10d %10d %s" % (wid, freq, lexicon.get_word(wid)) def dumpwords(self): lexicon = self.index.lexicon index = self.index.index assert isinstance(index, OkapiIndex) for word in lexicon.words(): wid = lexicon.get_wid(word) freq = 0 for f in index._wordinfo.get(wid, {}).values(): freq += f print "%10d %10d %s" % (wid, freq, word) def close(self): self.root = None if self.connection is not None: self.connection.close() self.connection = None if self.database is not None: self.database.close() self.database = None if self.filestorage is not None: self.filestorage.close() self.filestorage = None def interact(self, nbest=NBEST, maxlines=MAXLINES): try: import readline except ImportError: pass text = "" top = 0 results = [] while 1: try: line = raw_input("Query: ") except EOFError: print "\nBye." break line = line.strip() if line.startswith("/"): self.specialcommand(line, results, top - nbest) continue if line: text = line top = 0 else: if not text: continue try: results, n = self.timequery(text, top + nbest) except KeyboardInterrupt: raise except: reportexc() text = "" continue if len(results) <= top: if not n: print "No hits for %r." % text else: print "No more hits for %r." % text text = "" continue print "[Results %d-%d from %d" % (top+1, min(n, top+nbest), n), print "for query %s]" % repr(text) self.formatresults(text, results, maxlines, top, top+nbest) top += nbest def specialcommand(self, line, results, first): assert line.startswith("/") line = line[1:] if not line: n = first else: try: n = int(line) - 1 except: print "Huh?" return if n < 0 or n >= len(results): print "Out of range" return docid, score = results[n] path = self.docpaths[docid] i = path.rfind("/") assert i > 0 folder = path[:i] n = path[i+1:] cmd = "show +%s %s" % (folder, n) if os.getenv("DISPLAY"): os.system("xterm -e sh -c '%s | less' &" % cmd) else: os.system(cmd) def query(self, text, nbest=NBEST, maxlines=MAXLINES): results, n = self.timequery(text, nbest) if not n: print "No hits for %r." % text return print "[Results 1-%d from %d]" % (len(results), n) self.formatresults(text, results, maxlines) def timequery(self, text, nbest): t0 = time.time() c0 = time.clock() results, n = self.index.query(text, nbest) t1 = time.time() c1 = time.clock() print "[Query time: %.3f real, %.3f user]" % (t1-t0, c1-c0) return results, n def formatresults(self, text, results, maxlines=MAXLINES, lo=0, hi=sys.maxint): stop = self.stopdict.has_key words = [w for w in re.findall(r"\w+\*?", text.lower()) if not stop(w)] pattern = r"\b(" + "|".join(words) + r")\b" pattern = pattern.replace("*", ".*") # glob -> re syntax prog = re.compile(pattern, re.IGNORECASE) print '='*70 rank = lo qw = self.index.query_weight(text) for docid, score in results[lo:hi]: rank += 1 path = self.docpaths[docid] score = 100.0*score/qw print "Rank: %d Score: %d%% File: %s" % (rank, score, path) path = os.path.join(self.mh.getpath(), path) try: fp = open(path) except (IOError, OSError), msg: print "Can't open:", msg continue msg = mhlib.Message("", 0, fp) for header in "From", "To", "Cc", "Bcc", "Subject", "Date": h = msg.getheader(header) if h: print "%-8s %s" % (header+":", h) text = self.getmessagetext(msg) if text: print nleft = maxlines for part in text: for line in part.splitlines(): if prog.search(line): print line nleft -= 1 if nleft <= 0: break if nleft <= 0: break print '-'*70 def update(self, args): folder = None seqs = [] for arg in args: if arg.startswith("+"): if folder is None: folder = arg[1:] else: print "only one folder at a time" return else: seqs.append(arg) if not folder: folder = self.mh.getcontext() if not seqs: seqs = ['all'] try: f = self.mh.openfolder(folder) except mhlib.Error, msg: print msg return dict = {} for seq in seqs: try: nums = f.parsesequence(seq) except mhlib.Error, msg: print msg or "unparsable message sequence: %s" % `seq` return for n in nums: dict[n] = n msgs = dict.keys() msgs.sort() self.updatefolder(f, msgs) self.commit() def optimize(self, args): uniqwords = {} for folder in args: if folder.startswith("+"): folder = folder[1:] print "\nOPTIMIZE FOLDER", folder try: f = self.mh.openfolder(folder) except mhlib.Error, msg: print msg continue self.prescan(f, f.listmessages(), uniqwords) L = [(uniqwords[word], word) for word in uniqwords.keys()] L.sort() L.reverse() for i in range(100): print "%3d. %6d %s" % ((i+1,) + L[i]) self.index.lexicon.sourceToWordIds([word for (count, word) in L]) def prescan(self, f, msgs, uniqwords): pipeline = [Splitter(), CaseNormalizer(), StopWordRemover()] for n in msgs: print "prescanning", n m = f.openmessage(n) text = self.getmessagetext(m, f.name) for p in pipeline: text = p.process(text) for word in text: uniqwords[word] = uniqwords.get(word, 0) + 1 def bulkupdate(self, args): if not args: print "No folders specified; use ALL to bulk-index all folders" return if "ALL" in args: i = args.index("ALL") args[i:i+1] = self.mh.listfolders() for folder in args: if folder.startswith("+"): folder = folder[1:] print "\nFOLDER", folder try: f = self.mh.openfolder(folder) except mhlib.Error, msg: print msg continue self.updatefolder(f, f.listmessages()) print "Total", len(self.docpaths) self.commit() print len(self.index.lexicon._words), "unique words." def updatefolder(self, f, msgs): self.watchfolders[f.name] = self.getmtime(f.name) for n in msgs: path = "%s/%s" % (f.name, n) docid = self.path2docid.get(path, 0) if docid and self.getmtime(path) == self.doctimes.get(docid, 0): print "unchanged", docid, path continue docid = self.newdocid(path) try: m = f.openmessage(n) except IOError: print "disappeared", docid, path self.unindexpath(path) continue text = self.getmessagetext(m, f.name) if not text: self.unindexpath(path) continue print "indexing", docid, path self.index.index_text(docid, text) self.maycommit() # Remove messages from the folder that no longer exist for path in list(self.path2docid.keys(f.name)): if not path.startswith(f.name + "/"): break if self.getmtime(path) == 0: self.unindexpath(path) print "done." def unindexpath(self, path): if self.path2docid.has_key(path): docid = self.path2docid[path] print "unindexing", docid, path del self.docpaths[docid] del self.doctimes[docid] del self.path2docid[path] try: self.index.unindex(docid) except KeyError, msg: print "KeyError", msg self.maycommit() def getmessagetext(self, m, name=None): L = [] if name: L.append("_folder " + name) # To restrict search to a folder self.getheaders(m, L) try: self.getmsgparts(m, L, 0) except KeyboardInterrupt: raise except: print "(getmsgparts failed:)" reportexc() return L def getmsgparts(self, m, L, level): ctype = m.gettype() if level or ctype != "text/plain": print ". "*level + str(ctype) if ctype == "text/plain": L.append(m.getbodytext()) elif ctype in ("multipart/alternative", "multipart/mixed"): for part in m.getbodyparts(): self.getmsgparts(part, L, level+1) elif ctype == "message/rfc822": f = StringIO(m.getbodytext()) m = mhlib.Message("", 0, f) self.getheaders(m, L) self.getmsgparts(m, L, level+1) def getheaders(self, m, L): H = [] for key in "from", "to", "cc", "bcc", "subject": value = m.get(key) if value: H.append(value) if H: L.append("\n".join(H)) def newdocid(self, path): docid = self.path2docid.get(path) if docid is not None: self.doctimes[docid] = self.getmtime(path) return docid docid = self.maxdocid + 1 self.maxdocid = docid self.docpaths[docid] = path self.doctimes[docid] = self.getmtime(path) self.path2docid[path] = docid return docid def getmtime(self, path): path = os.path.join(self.mh.getpath(), path) try: st = os.stat(path) except os.error, msg: return 0 return int(st[ST_MTIME]) def maycommit(self): self.trans_count += 1 if self.trans_count >= self.trans_limit > 0: self.commit() def commit(self): if self.trans_count > 0: print "committing..." transaction.commit() self.trans_count = 0 self.pack_count += 1 if self.pack_count >= self.pack_limit > 0: self.pack() def pack(self): if self.pack_count > 0: print "packing..." self.database.pack() self.pack_count = 0 class TextIndex(Persistent): def __init__(self): self.lexicon = Lexicon(Splitter(), CaseNormalizer(), StopWordRemover()) self.index = OkapiIndex(self.lexicon) def index_text(self, docid, text): self.index.index_doc(docid, text) self._p_changed = 1 # XXX def unindex(self, docid): self.index.unindex_doc(docid) self._p_changed = 1 # XXX def query(self, query, nbest=10): # returns a total hit count and a mapping from docids to scores parser = QueryParser(self.lexicon) tree = parser.parseQuery(query) results = tree.executeQuery(self.index) if results is None: return [], 0 chooser = NBest(nbest) chooser.addmany(results.items()) return chooser.getbest(), len(results) def query_weight(self, query): parser = QueryParser(self.lexicon) tree = parser.parseQuery(query) terms = tree.terms() return self.index.query_weight(terms) def reportexc(): traceback.print_exc() if __name__ == "__main__": sys.exit(main()) zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/queryhtml.py0000644000175000017500000000642712214017454027734 0ustar arnauarnau# XXX: Products.PluginIndexes.TextIndex no longer exists import os from time import clock import ZODB from ZODB.FileStorage import FileStorage QUERIES = ["nested recursive functions", "explicit better than implicit", "build hpux", "cannot create 'method-wrapper' instances", "extension module C++", "class method", "instance variable", "articulate information", "import default files", "gopher ftp http", "documentation", ] def path2url(p): # convert the paths to a python.org URL # hack: only works for the way Jeremy indexed his copy of python.org marker = "www.python.org/." i = p.find(marker) if i == -1: return p i += len(marker) return "http://www.python.org" + p[i:] from Products.ZCTextIndex.tests.indexhtml import MySplitter from Products.ZCTextIndex.NBest import NBest def main(rt): index = rt["index"] files = rt["files"] times = {} ITERS = range(50) for i in range(11): for q in QUERIES: terms = q.split() for c in " OR ", " AND ": query = c.join(terms) t0 = clock() if TEXTINDEX: if c == " OR ": op = Or else: op = And _q = " ".join(terms) for _ in ITERS: b = index.query(_q, op).bucket() num = len(b) chooser = NBest(10) chooser.addmany(b.items()) results = chooser.getbest() else: try: for _ in ITERS: results, num = index.query(query) except: continue t1 = clock() print "

Query: \"%s\"" % query print "
Num results: %d" % num print "
time.clock(): %s" % (t1 - t0) key = query if i == 0: print "

    " for docid, score in results: url = path2url(files[docid]) fmt = '
  1. %s score = %s' print fmt % (url, url, score) print "
" continue l = times.setdefault(key, []) l.append(t1 - t0) l = times.keys() l.sort() print "
" for k in l: v = times[k] print "

Query: \"%s\"" % k print "
Min time: %s" % min(v) print "
All times: %s" % " ".join(map(str, v)) if __name__ == "__main__": import sys import getopt VERBOSE = 0 FSPATH = "Data.fs" TEXTINDEX = 0 try: opts, args = getopt.getopt(sys.argv[1:], 'vf:T') except getopt.error, msg: print msg print __doc__ sys.exit(2) for o, v in opts: if o == '-v': VERBOSE += 1 if o == '-f': FSPATH = v # if o == '-T': # TEXTINDEX = 1 fs = FileStorage(FSPATH, read_only=1) db = ZODB.DB(fs, cache_size=10000) cn = db.open() rt = cn.root() main(rt) zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/wordstats.py0000644000175000017500000000206412214017454027725 0ustar arnauarnau#! /usr/bin/env python """Dump statistics about each word in the index. usage: wordstats.py data.fs [index key] """ import ZODB from ZODB.FileStorage import FileStorage def main(fspath, key): fs = FileStorage(fspath, read_only=1) db = ZODB.DB(fs) rt = db.open().root() index = rt[key] lex = index.lexicon idx = index.index print "Words", lex.length() print "Documents", idx.length() print "Word frequencies: count, word, wid" for word, wid in lex.items(): docs = idx._wordinfo[wid] print len(docs), word, wid print "Per-doc scores: wid, (doc, score,)+" for wid in lex.wids(): print wid, docs = idx._wordinfo[wid] for docid, score in docs.items(): print docid, score, print if __name__ == "__main__": import sys args = sys.argv[1:] index_key = "index" if len(args) == 1: fspath = args[0] elif len(args) == 2: fspath, index_key = args else: print "Expected 1 or 2 args, got", len(args) main(fspath, index_key) zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/tests/testQueryEngine.py0000644000175000017500000000471712214017454031035 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from unittest import TestCase, TestSuite, main, makeSuite from BTrees.IIBTree import IIBucket from Products.ZCTextIndex.QueryParser import QueryParser from Products.ZCTextIndex.ParseTree import ParseError, QueryError from Products.ZCTextIndex.Lexicon import Lexicon, Splitter class FauxIndex: def search(self, term): b = IIBucket() if term == "foo": b[1] = b[3] = 1 elif term == "bar": b[1] = b[2] = 1 elif term == "ham": b[1] = b[2] = b[3] = b[4] = 1 return b class TestQueryEngine(TestCase): def setUp(self): self.lexicon = Lexicon(Splitter()) self.parser = QueryParser(self.lexicon) self.index = FauxIndex() def compareSet(self, set, dict): d = {} for k, v in set.items(): d[k] = v self.assertEqual(d, dict) def compareQuery(self, query, dict): tree = self.parser.parseQuery(query) set = tree.executeQuery(self.index) self.compareSet(set, dict) def testExecuteQuery(self): self.compareQuery("foo AND bar", {1: 2}) self.compareQuery("foo OR bar", {1: 2, 2: 1, 3:1}) self.compareQuery("foo AND NOT bar", {3: 1}) self.compareQuery("foo AND foo AND foo", {1: 3, 3: 3}) self.compareQuery("foo OR foo OR foo", {1: 3, 3: 3}) self.compareQuery("ham AND NOT foo AND NOT bar", {4: 1}) self.compareQuery("ham OR foo OR bar", {1: 3, 2: 2, 3: 2, 4: 1}) self.compareQuery("ham AND foo AND bar", {1: 3}) def testInvalidQuery(self): from Products.ZCTextIndex.ParseTree import NotNode, AtomNode tree = NotNode(AtomNode("foo")) self.assertRaises(QueryError, tree.executeQuery, self.index) def test_suite(): return makeSuite(TestQueryEngine) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/IQueryParseTree.py0000644000175000017500000000130712214017454027561 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from Products.ZCTextIndex.interfaces import IPipelineElementFactory # BBB zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/dtml/0000755000175000017500000000000012214017454025115 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/dtml/queryLexicon.dtml0000644000175000017500000000442112214017454030467 0ustar arnauarnau

Browse the words in the lexicon or enter the word(s) you are interested in below. Globbing characters (*, ?) are supported

Word(s)  Output Columns:  Rows:


&dtml-word_count; Words Found, Displaying &dtml-start_word;-&dtml-end_word; Page: of &dtml-page_count;
zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/dtml/manageZCTextIndex.dtml0000644000175000017500000000135012214017454031320 0ustar arnauarnau

Name(s) of attribute(s) indexed:

Index type: &dtml-getIndexType;

ZCTextIndex Lexicon used: &dtml-getLexiconURL; (Lexicon Not Found)

Note: The lexicon assigned to the index cannot be changed. To replace the existing lexicon, create a new lexicon in the same place and clear the index. This will make the index use the replacement lexicon.

zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/dtml/manageLexicon.dtml0000644000175000017500000000101712214017454030550 0ustar arnauarnau

The lexicon processes and stores the words found in objects indexed by one or more ZCTextIndexes.

Input Pipeline Stages

Text indexed through this lexicon is processed by the following pipeline stages

  1. &dtml-sequence-item;
zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/dtml/addZCTextIndex.dtml0000644000175000017500000000436412214017454030630 0ustar arnauarnau

Text Indexes break text up into individual words, and are often referred to as full-text indexes. Text indexes sort results by score, meaning they return hits in order from the most relevant to the least relevant.

Id
Indexed attributes
attribute1,attribute2,... or leave empty
Ranking Strategy
Lexicon
You must create a ZCTextIndex Lexicon first.
zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/dtml/addLexicon.dtml0000644000175000017500000000403712214017454030055 0ustar arnauarnau

A ZCTextIndex Lexicon processes and stores the words of documents indexed with a ZCTextIndex. Multiple ZCTextIndexes can share the same lexicon.

Id
Title
&dtml-group_item;
" checked />
zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/IPipelineElement.py0000644000175000017500000000127712214017454027726 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from Products.ZCTextIndex.interfaces import IPipelineElement # BBB zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/ZCTextIndex/QueryParser.py0000644000175000017500000001734412214017454027022 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Query Parser. This particular parser recognizes the following syntax: Start = OrExpr OrExpr = AndExpr ('OR' AndExpr)* AndExpr = Term ('AND' NotExpr)* NotExpr = ['NOT'] Term Term = '(' OrExpr ')' | ATOM+ The key words (AND, OR, NOT) are recognized in any mixture of case. An ATOM is either: + A sequence of characters not containing whitespace or parentheses or double quotes, and not equal (ignoring case) to one of the key words 'AND', 'OR', 'NOT'; or + A non-empty string enclosed in double quotes. The interior of the string can contain whitespace, parentheses and key words, but not quotes. + A hyphen followed by one of the two forms above, meaning that it must not be present. An unquoted ATOM may also contain globbing characters. Globbing syntax is defined by the lexicon; for example "foo*" could mean any word starting with "foo". When multiple consecutive ATOMs are found at the leaf level, they are connected by an implied AND operator, and an unquoted leading hyphen is interpreted as a NOT operator. Summarizing the default operator rules: - a sequence of words without operators implies AND, e.g. ``foo bar'' - double-quoted text implies phrase search, e.g. ``"foo bar"'' - words connected by punctuation implies phrase search, e.g. ``foo-bar'' - a leading hyphen implies NOT, e.g. ``foo -bar'' - these can be combined, e.g. ``foo -"foo bar"'' or ``foo -foo-bar'' - * and ? are used for globbing (i.e. prefix search), e.g. ``foo*'' """ import re from zope.interface import implements from Products.ZCTextIndex.interfaces import IQueryParser from Products.ZCTextIndex import ParseTree # Create unique symbols for token types. _AND = intern("AND") _OR = intern("OR") _NOT = intern("NOT") _LPAREN = intern("(") _RPAREN = intern(")") _ATOM = intern("ATOM") _EOF = intern("EOF") # Map keyword string to token type. _keywords = { _AND: _AND, _OR: _OR, _NOT: _NOT, _LPAREN: _LPAREN, _RPAREN: _RPAREN, } # Regular expression to tokenize. _tokenizer_regex = re.compile(r""" # a paren [()] # or an optional hyphen | -? # followed by (?: # a string inside double quotes (and not containing these) " [^"]* " # or a non-empty stretch w/o whitespace, parens or double quotes | [^()\s"]+ ) """, re.VERBOSE) # Use unicode regex to treat fullwidth space characters defined in Unicode # as valid whitespace. _tokenizer_unicode_regex = re.compile( _tokenizer_regex.pattern, _tokenizer_regex.flags|re.UNICODE) class QueryParser: implements(IQueryParser) # This class is not thread-safe; # each thread should have its own instance def __init__(self, lexicon): self._lexicon = lexicon self._ignored = None # Public API methods def parseQuery(self, query): # Lexical analysis. try: # Try to use unicode and treat fullwidth whitespace as valid one. if not isinstance(query, unicode): query = query.decode('utf-8') tokens = _tokenizer_unicode_regex.findall(query) except UnicodeDecodeError: tokens = _tokenizer_regex.findall(query) self._tokens = tokens # classify tokens self._tokentypes = [_keywords.get(token.upper(), _ATOM) for token in tokens] # add _EOF self._tokens.append(_EOF) self._tokentypes.append(_EOF) self._index = 0 # Syntactical analysis. self._ignored = [] # Ignored words in the query, for parseQueryEx tree = self._parseOrExpr() self._require(_EOF) if tree is None: raise ParseTree.ParseError( "Query contains only common words: %s" % repr(query)) return tree def getIgnored(self): return self._ignored def parseQueryEx(self, query): tree = self.parseQuery(query) ignored = self.getIgnored() return tree, ignored # Recursive descent parser def _require(self, tokentype): if not self._check(tokentype): t = self._tokens[self._index] msg = "Token %r required, %r found" % (tokentype, t) raise ParseTree.ParseError, msg def _check(self, tokentype): if self._tokentypes[self._index] is tokentype: self._index += 1 return 1 else: return 0 def _peek(self, tokentype): return self._tokentypes[self._index] is tokentype def _get(self, tokentype): t = self._tokens[self._index] self._require(tokentype) return t def _parseOrExpr(self): L = [] L.append(self._parseAndExpr()) while self._check(_OR): L.append(self._parseAndExpr()) L = filter(None, L) if not L: return None # Only stopwords elif len(L) == 1: return L[0] else: return ParseTree.OrNode(L) def _parseAndExpr(self): L = [] t = self._parseTerm() if t is not None: L.append(t) Nots = [] while self._check(_AND): t = self._parseNotExpr() if t is None: continue if isinstance(t, ParseTree.NotNode): Nots.append(t) else: L.append(t) if not L: return None # Only stopwords L.extend(Nots) if len(L) == 1: return L[0] else: return ParseTree.AndNode(L) def _parseNotExpr(self): if self._check(_NOT): t = self._parseTerm() if t is None: return None # Only stopwords return ParseTree.NotNode(t) else: return self._parseTerm() def _parseTerm(self): if self._check(_LPAREN): tree = self._parseOrExpr() self._require(_RPAREN) else: nodes = [] nodes = [self._parseAtom()] while self._peek(_ATOM): nodes.append(self._parseAtom()) nodes = filter(None, nodes) if not nodes: return None # Only stopwords structure = [(isinstance(nodes[i], ParseTree.NotNode), i, nodes[i]) for i in range(len(nodes))] structure.sort() nodes = [node for (bit, index, node) in structure] if isinstance(nodes[0], ParseTree.NotNode): raise ParseTree.ParseError( "a term must have at least one positive word") if len(nodes) == 1: return nodes[0] tree = ParseTree.AndNode(nodes) return tree def _parseAtom(self): term = self._get(_ATOM) words = self._lexicon.parseTerms(term) if not words: self._ignored.append(term) return None if len(words) > 1: tree = ParseTree.PhraseNode(words) elif self._lexicon.isGlob(words[0]): tree = ParseTree.GlobNode(words[0]) else: tree = ParseTree.AtomNode(words[0]) if term[0] == "-": tree = ParseTree.NotNode(tree) return tree zope2.13-2.13.21/source/Products.ZCTextIndex/src/Products/__init__.py0000644000175000017500000000007012214017454024112 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.component/0000755000175000017500000000000012214017426016414 5ustar arnauarnauzope2.13-2.13.21/source/zope.component/setup.py0000644000175000017500000000547212214017426020136 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.component package """ import os from setuptools import setup, find_packages tests_require = [ 'ZODB3', 'zope.hookable', 'zope.location', 'zope.proxy', 'zope.security', 'zope.testing', ] def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup( name='zope.component', version = '3.9.5', url='http://pypi.python.org/pypi/zope.component', license='ZPL 2.1', description='Zope Component Architecture', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=( read('README.txt') + '\n' + 'Detailed Documentation\n' '**********************\n' + '\n' + read('src', 'zope', 'component', 'README.txt') + '\n' + read('src', 'zope', 'component', 'event.txt') + '\n' + read('src', 'zope', 'component', 'factory.txt') + '\n' + read('src', 'zope', 'component', 'registry.txt') + '\n' + read('src', 'zope', 'component', 'persistentregistry.txt') + '\n' + read('src', 'zope', 'component', 'socketexample.txt') + '\n' + read('CHANGES.txt') + '\n' + 'Download\n' '********\n' ), packages = find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope',], tests_require = tests_require, install_requires=['setuptools', 'zope.interface', 'zope.event', ], include_package_data = True, zip_safe = False, extras_require = dict( hook = ['zope.hookable'], persistentregistry = ['ZODB3'], zcml = ['zope.configuration', 'zope.i18nmessageid', ], test = tests_require, docs = ['z3c.recipe.sphinxdoc'], ), ) zope2.13-2.13.21/source/zope.component/PKG-INFO0000644000175000017500000032223112214017426017514 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.component Version: 3.9.5 Summary: Zope Component Architecture Home-page: http://pypi.python.org/pypi/zope.component Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ***************************** zope.component Package Readme ***************************** *This package is intended to be independently reusable in any Python project. It is maintained by the* `Zope Toolkit project `_. This package represents the core of the Zope Component Architecture. Together with the 'zope.interface' package, it provides facilities for defining, registering and looking up components. .. contents:: Detailed Documentation ********************** Zope Component Architecture =========================== This package, together with `zope.interface`, provides facilities for defining, registering and looking up components. There are two basic kinds of components: adapters and utilities. Utilities --------- Utilities are just components that provide an interface and that are looked up by an interface and a name. Let's look at a trivial utility definition: >>> from zope import interface >>> class IGreeter(interface.Interface): ... def greet(): ... "say hello" >>> class Greeter: ... interface.implements(IGreeter) ... ... def __init__(self, other="world"): ... self.other = other ... ... def greet(self): ... print "Hello", self.other We can register an instance this class using `provideUtility` [1]_: >>> from zope import component >>> greet = Greeter('bob') >>> component.provideUtility(greet, IGreeter, 'robert') In this example we registered the utility as providing the `IGreeter` interface with a name of 'bob'. We can look the interface up with either `queryUtility` or `getUtility`: >>> component.queryUtility(IGreeter, 'robert').greet() Hello bob >>> component.getUtility(IGreeter, 'robert').greet() Hello bob `queryUtility` and `getUtility` differ in how failed lookups are handled: >>> component.queryUtility(IGreeter, 'ted') >>> component.queryUtility(IGreeter, 'ted', 42) 42 >>> component.getUtility(IGreeter, 'ted') ... # doctest: +ELLIPSIS Traceback (most recent call last): ... ComponentLookupError: (, 'ted') If a component provides only one interface, as in the example above, then we can omit the provided interface from the call to `provideUtility`: >>> ted = Greeter('ted') >>> component.provideUtility(ted, name='ted') >>> component.queryUtility(IGreeter, 'ted').greet() Hello ted The name defaults to an empty string: >>> world = Greeter() >>> component.provideUtility(world) >>> component.queryUtility(IGreeter).greet() Hello world Adapters -------- Adapters are components that are computed from other components to adapt them to some interface. Because they are computed from other objects, they are provided as factories, usually classes. Here, we'll create a greeter for persons, so we can provide personalized greetings for different people: >>> class IPerson(interface.Interface): ... name = interface.Attribute("Name") >>> class PersonGreeter: ... ... component.adapts(IPerson) ... interface.implements(IGreeter) ... ... def __init__(self, person): ... self.person = person ... ... def greet(self): ... print "Hello", self.person.name The class defines a constructor that takes an argument for every object adapted. We used `component.adapts` to declare what we adapt. We can find out if an object declares that it adapts anything using adaptedBy: >>> list(component.adaptedBy(PersonGreeter)) == [IPerson] True If an object makes no declaration, then None is returned: >>> component.adaptedBy(Greeter()) is None True If we declare the interfaces adapted and if we provide only one interface, as in the example above, then we can provide the adapter very simply [1]_: >>> component.provideAdapter(PersonGreeter) For adapters that adapt a single interface to a single interface without a name, we can get the adapter by simply calling the interface: >>> class Person: ... interface.implements(IPerson) ... ... def __init__(self, name): ... self.name = name >>> IGreeter(Person("Sally")).greet() Hello Sally We can also provide arguments to be very specific about what how to register the adapter. >>> class BobPersonGreeter(PersonGreeter): ... name = 'Bob' ... def greet(self): ... print "Hello", self.person.name, "my name is", self.name >>> component.provideAdapter( ... BobPersonGreeter, [IPerson], IGreeter, 'bob') The arguments can also be provided as keyword arguments: >>> class TedPersonGreeter(BobPersonGreeter): ... name = "Ted" >>> component.provideAdapter( ... factory=TedPersonGreeter, adapts=[IPerson], ... provides=IGreeter, name='ted') For named adapters, use `queryAdapter`, or `getAdapter`: >>> component.queryAdapter(Person("Sally"), IGreeter, 'bob').greet() Hello Sally my name is Bob >>> component.getAdapter(Person("Sally"), IGreeter, 'ted').greet() Hello Sally my name is Ted If an adapter can't be found, `queryAdapter` returns a default value and `getAdapter` raises an error: >>> component.queryAdapter(Person("Sally"), IGreeter, 'frank') >>> component.queryAdapter(Person("Sally"), IGreeter, 'frank', 42) 42 >>> component.getAdapter(Person("Sally"), IGreeter, 'frank') ... # doctest: +ELLIPSIS Traceback (most recent call last): ... ComponentLookupError: (...Person...>, <...IGreeter>, 'frank') Adapters can adapt multiple objects: >>> class TwoPersonGreeter: ... ... component.adapts(IPerson, IPerson) ... interface.implements(IGreeter) ... ... def __init__(self, person, greeter): ... self.person = person ... self.greeter = greeter ... ... def greet(self): ... print "Hello", self.person.name ... print "my name is", self.greeter.name >>> component.provideAdapter(TwoPersonGreeter) To look up a multi-adapter, use either `queryMultiAdapter` or `getMultiAdapter`: >>> component.queryMultiAdapter((Person("Sally"), Person("Bob")), ... IGreeter).greet() Hello Sally my name is Bob Adapters need not be classes. Any callable will do. We use the adapter decorator (in the Python 2.4 decorator sense) to declare that a callable object adapts some interfaces (or classes): >>> class IJob(interface.Interface): ... "A job" >>> class Job: ... interface.implements(IJob) >>> def personJob(person): ... return getattr(person, 'job', None) >>> personJob = interface.implementer(IJob)(personJob) >>> personJob = component.adapter(IPerson)(personJob) In Python 2.4, the example can be written: >>> @interface.implementer(IJob) ... @component.adapter(IPerson) ... def personJob(person): ... return getattr(person, 'job', None) which looks a bit nicer. In this example, the personJob function simply returns the person's `job` attribute if present, or None if it's not present. An adapter factory can return None to indicate that adaptation wasn't possible. Let's register this adapter and try it out: >>> component.provideAdapter(personJob) >>> sally = Person("Sally") >>> IJob(sally) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: ('Could not adapt', ... The adaptation failed because sally didn't have a job. Let's give her one: >>> job = Job() >>> sally.job = job >>> IJob(sally) is job True Subscription Adapters --------------------- Unlike regular adapters, subscription adapters are used when we want all of the adapters that adapt an object to a particular adapter. Consider a validation problem. We have objects and we want to assess whether they meet some sort of standards. We define a validation interface: >>> class IValidate(interface.Interface): ... def validate(ob): ... """Determine whether the object is valid ... ... Return a string describing a validation problem. ... An empty string is returned to indicate that the ... object is valid. ... """ Perhaps we have documents: >>> class IDocument(interface.Interface): ... summary = interface.Attribute("Document summary") ... body = interface.Attribute("Document text") >>> class Document: ... interface.implements(IDocument) ... def __init__(self, summary, body): ... self.summary, self.body = summary, body Now, we may want to specify various validation rules for documents. For example, we might require that the summary be a single line: >>> class SingleLineSummary: ... component.adapts(IDocument) ... interface.implements(IValidate) ... ... def __init__(self, doc): ... self.doc = doc ... ... def validate(self): ... if '\n' in self.doc.summary: ... return 'Summary should only have one line' ... else: ... return '' Or we might require the body to be at least 1000 characters in length: >>> class AdequateLength: ... component.adapts(IDocument) ... interface.implements(IValidate) ... ... def __init__(self, doc): ... self.doc = doc ... ... def validate(self): ... if len(self.doc.body) < 1000: ... return 'too short' ... else: ... return '' We can register these as subscription adapters [1]_: >>> component.provideSubscriptionAdapter(SingleLineSummary) >>> component.provideSubscriptionAdapter(AdequateLength) We can then use the subscribers to validate objects: >>> doc = Document("A\nDocument", "blah") >>> [adapter.validate() ... for adapter in component.subscribers([doc], IValidate) ... if adapter.validate()] ['Summary should only have one line', 'too short'] >>> doc = Document("A\nDocument", "blah" * 1000) >>> [adapter.validate() ... for adapter in component.subscribers([doc], IValidate) ... if adapter.validate()] ['Summary should only have one line'] >>> doc = Document("A Document", "blah") >>> [adapter.validate() ... for adapter in component.subscribers([doc], IValidate) ... if adapter.validate()] ['too short'] Handlers -------- Handlers are subscription adapter factories that don't produce anything. They do all of their work when called. Handlers are typically used to handle events. Event subscribers are different from other subscription adapters in that the caller of event subscribers doesn't expect to interact with them in any direct way. For example, an event publisher doesn't expect to get any return value. Because subscribers don't need to provide an API to their callers, it is more natural to define them with functions, rather than classes. For example, in a document-management system, we might want to record creation times for documents: >>> import datetime >>> def documentCreated(event): ... event.doc.created = datetime.datetime.utcnow() In this example, we have a function that takes an event and performs some processing. It doesn't actually return anything. This is a special case of a subscription adapter that adapts an event to nothing. All of the work is done when the adapter "factory" is called. We call subscribers that don't actually create anything "handlers". There are special APIs for registering and calling them. To register the subscriber above, we define a document-created event: >>> class IDocumentCreated(interface.Interface): ... doc = interface.Attribute("The document that was created") >>> class DocumentCreated: ... interface.implements(IDocumentCreated) ... ... def __init__(self, doc): ... self.doc = doc We'll also change our handler definition to: >>> def documentCreated(event): ... event.doc.created = datetime.datetime.utcnow() >>> documentCreated = component.adapter(IDocumentCreated)(documentCreated) Note that in Python 2.4, this can be written: >>> @component.adapter(IDocumentCreated) ... def documentCreated(event): ... event.doc.created = datetime.datetime.utcnow() This marks the handler as an adapter of `IDocumentCreated` events. Now we'll register the handler [1]_: >>> component.provideHandler(documentCreated) Now, if we can create an event and use the `handle` function to call handlers registered for the event: >>> component.handle(DocumentCreated(doc)) >>> doc.created.__class__.__name__ 'datetime' .. [1] CAUTION: This API should only be used from test or application-setup code. This API shouldn't be used by regular library modules, as component registration is a configuration activity. Events ====== The Component Architecture provides a way to dispatch events to event handlers. Event handlers are registered as *subscribers* a.k.a. *handlers*. Before we can start we need to import ``zope.component.event`` to make the dispatching effective: >>> import zope.component.event Consider two event classes: >>> class Event1(object): ... pass >>> class Event2(Event1): ... pass Now consider two handlers for these event classes: >>> called = [] >>> import zope.component >>> @zope.component.adapter(Event1) ... def handler1(event): ... called.append(1) >>> @zope.component.adapter(Event2) ... def handler2(event): ... called.append(2) We can register them with the Component Architecture: >>> zope.component.provideHandler(handler1) >>> zope.component.provideHandler(handler2) Now let's go through the events. We'll see that the handlers have been called accordingly: >>> from zope.event import notify >>> notify(Event1()) >>> called [1] >>> del called[:] >>> notify(Event2()) >>> called.sort() >>> called [1, 2] Object events ------------- The ``objectEventNotify`` function is a subscriber to dispatch ObjectEvents to interested adapters. First create an object class: >>> class IUseless(zope.interface.Interface): ... """Useless object""" >>> class UselessObject(object): ... """Useless object""" ... zope.interface.implements(IUseless) Then create an event class: >>> class IObjectThrownEvent(zope.component.interfaces.IObjectEvent): ... """An object has been thrown away""" >>> class ObjectThrownEvent(zope.component.interfaces.ObjectEvent): ... """An object has been thrown away""" ... zope.interface.implements(IObjectThrownEvent) Create an object and an event: >>> hammer = UselessObject() >>> event = ObjectThrownEvent(hammer) Then notify the event to the subscribers. Since the subscribers list is empty, nothing happens. >>> zope.component.event.objectEventNotify(event) Now create an handler for the event: >>> events = [] >>> def record(*args): ... events.append(args) >>> zope.component.provideHandler(record, [IUseless, IObjectThrownEvent]) The event is notified to the subscriber: >>> zope.component.event.objectEventNotify(event) >>> events == [(hammer, event)] True Following test demonstrates how a subscriber can raise an exception to prevent an action. >>> zope.component.provideHandler(zope.component.event.objectEventNotify) Let's create a container: >>> class ToolBox(dict): ... def __delitem__(self, key): ... notify(ObjectThrownEvent(self[key])) ... return super(ToolBox,self).__delitem__(key) >>> container = ToolBox() And put the object into the container: >>> container['Red Hammer'] = hammer Create an handler function that will raise an error when called: >>> class Veto(Exception): ... pass >>> def callback(item, event): ... assert(item == event.object) ... raise Veto Register the handler: >>> zope.component.provideHandler(callback, [IUseless, IObjectThrownEvent]) Then if we try to remove the object, an ObjectThrownEvent is fired: >>> del container['Red Hammer'] ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... raise Veto Veto Factories ========= The Factory Class ----------------- >>> from zope.interface import Interface >>> class IFunction(Interface): ... pass >>> class IKlass(Interface): ... pass >>> from zope.interface import implements >>> class Klass(object): ... implements(IKlass) ... ... def __init__(self, *args, **kw): ... self.args = args ... self.kw = kw >>> from zope.component.factory import Factory >>> factory = Factory(Klass, 'Klass', 'Klassier') >>> factory2 = Factory(lambda x: x, 'Func', 'Function') >>> factory3 = Factory(lambda x: x, 'Func', 'Function', (IFunction,)) Calling a Factory ~~~~~~~~~~~~~~~~~ Here we test whether the factory correctly creates the objects and including the correct handling of constructor elements. First we create a factory that creates instanace of the `Klass` class: >>> factory = Factory(Klass, 'Klass', 'Klassier') Now we use the factory to create the instance >>> kl = factory(1, 2, foo=3, bar=4) and make sure that the correct class was used to create the object: >>> kl.__class__ Since we passed in a couple positional and keyword arguments >>> kl.args (1, 2) >>> kl.kw {'foo': 3, 'bar': 4} >>> factory2(3) 3 >>> factory3(3) 3 Title and Description ~~~~~~~~~~~~~~~~~~~~~ >>> factory.title 'Klass' >>> factory.description 'Klassier' >>> factory2.title 'Func' >>> factory2.description 'Function' >>> factory3.title 'Func' >>> factory3.description 'Function' Provided Interfaces ~~~~~~~~~~~~~~~~~~~ >>> implemented = factory.getInterfaces() >>> implemented.isOrExtends(IKlass) True >>> list(implemented) [] >>> implemented2 = factory2.getInterfaces() >>> list(implemented2) [] >>> implemented3 = factory3.getInterfaces() >>> list(implemented3) [] The Component Architecture Factory API -------------------------------------- >>> import zope.component >>> factory = Factory(Klass, 'Klass', 'Klassier') >>> gsm = zope.component.getGlobalSiteManager() >>> from zope.component.interfaces import IFactory >>> gsm.registerUtility(factory, IFactory, 'klass') Creating an Object ~~~~~~~~~~~~~~~~~~ >>> kl = zope.component.createObject('klass', 1, 2, foo=3, bar=4) >>> isinstance(kl, Klass) True >>> kl.args (1, 2) >>> kl.kw {'foo': 3, 'bar': 4} Accessing Provided Interfaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >>> implemented = zope.component.getFactoryInterfaces('klass') >>> implemented.isOrExtends(IKlass) True >>> [iface for iface in implemented] [] List of All Factories ~~~~~~~~~~~~~~~~~~~~~ >>> [(name, fac.__class__) for name, fac in ... zope.component.getFactoriesFor(IKlass)] [(u'klass', )] Component-Management objects ============================ Component-management objects provide a higher-level component-management API over the basic adapter-registration API provided by the zope.interface package. In particular, it provides: - utilities - support for computing adapters, rather than just looking up adapter factories. - management of registration comments The zope.component.registry.Components class provides an implementation of zope.component.interfaces.IComponents that provides these features. >>> from zope.component import registry >>> from zope.component import tests >>> components = registry.Components('comps') As components are registered, events are generated. Let's register an event subscriber, so we can see the events generated: >>> import zope.event >>> def logevent(event): ... print event >>> zope.event.subscribers.append(logevent) Utilities --------- You can register Utilities using registerUtility: >>> components.registerUtility(tests.U1(1)) Registered event: UtilityRegistration(, I1, u'', 1, None, u'') Here we didn't specify an interface or name. An unnamed utility was registered for interface I1, since that is only interface implemented by the U1 class: >>> components.getUtility(tests.I1) U1(1) You can also register a utility using a factory instead of a utility instance: >>> def factory(): ... return tests.U1(1) >>> components.registerUtility(factory=factory) Unregistered event: UtilityRegistration(, I1, u'', 1, None, u'') Registered event: UtilityRegistration(, I1, u'', 1, >, u'') If a component implements other than one interface or no interface, then an error will be raised: >>> components.registerUtility(tests.U12(2)) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The utility doesn't provide a single interface and no provided interface was specified. >>> components.registerUtility(tests.A) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The utility doesn't provide a single interface and no provided interface was specified. We can provide an interface if desired: >>> components.registerUtility(tests.U12(2), tests.I2) Registered event: UtilityRegistration(, I2, u'', 2, None, u'') and we can specify a name: >>> components.registerUtility(tests.U12(3), tests.I2, u'three') Registered event: UtilityRegistration(, I2, u'three', 3, None, u'') >>> components.getUtility(tests.I2) U12(2) >>> components.getUtility(tests.I2, 'three') U12(3) If you try to get a utility that doesn't exist, you'll get a component lookup error: >>> components.getUtility(tests.I3) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, u'') Unless you use queryUtility: >>> components.queryUtility(tests.I3) >>> components.queryUtility(tests.I3, default=42) 42 You can get information about registered utilities with the registeredUtilities method: >>> for registration in sorted(components.registeredUtilities()): ... print registration.provided, registration.name ... print registration.component, registration.info U1(1) U12(2) three U12(3) Duplicate registrations replace existing ones: >>> components.registerUtility(tests.U1(4), info=u'use 4 now') Unregistered event: UtilityRegistration(, I1, u'', 1, >, u'') Registered event: UtilityRegistration(, I1, u'', 4, None, u'use 4 now') >>> components.getUtility(tests.I1) U1(4) >>> for registration in sorted(components.registeredUtilities()): ... print registration.provided, registration.name ... print registration.component, registration.info U1(4) use 4 now U12(2) three U12(3) As shown in the this example, you can provide an "info" argumemnt when registering utilities. This provides extra documentation about the registration itself that is shown when listing registrations. You can also unregister utilities: >>> components.unregisterUtility(provided=tests.I1) Unregistered event: UtilityRegistration(, I1, u'', 4, None, u'use 4 now') True A boolean is returned indicating whether anything changed: >>> components.queryUtility(tests.I1) >>> for registration in sorted(components.registeredUtilities()): ... print registration.provided, registration.name ... print registration.component, registration.info U12(2) three U12(3) When you unregister, you can specify a component. If the component doesn't match the one registered, then nothing happens: >>> u5 = tests.U1(5) >>> components.registerUtility(u5) Registered event: UtilityRegistration(, I1, u'', 5, None, u'') >>> components.unregisterUtility(tests.U1(6)) False >>> components.queryUtility(tests.I1) U1(5) >>> components.unregisterUtility(u5) Unregistered event: UtilityRegistration(, I1, u'', 5, None, u'') True >>> components.queryUtility(tests.I1) You can get the name and utility for all of the utilities that provide an interface using getUtilitiesFor: >>> sorted(components.getUtilitiesFor(tests.I2)) [(u'', U12(2)), (u'three', U12(3))] getAllUtilitiesRegisteredFor is similar to getUtilitiesFor except that it includes utilities that are overridden. For example, we'll register a utility that for an extending interface of I2: >>> util = tests.U('ext') >>> components.registerUtility(util, tests.I2e) Registered event: UtilityRegistration(, I2e, u'', ext, None, u'') We don't get the new utility for getUtilitiesFor: >>> sorted(components.getUtilitiesFor(tests.I2)) [(u'', U12(2)), (u'three', U12(3))] but we do get it from getAllUtilitiesRegisteredFor: >>> sorted(map(str, components.getAllUtilitiesRegisteredFor(tests.I2))) ['U(ext)', 'U12(2)', 'U12(3)'] Removing a utility also makes it disappear from getUtilitiesFor: >>> components.unregisterUtility(util, tests.I2e) Unregistered event: UtilityRegistration(, I2e, u'', ext, None, u'') True >>> list(components.getAllUtilitiesRegisteredFor(tests.I2e)) [] Adapters -------- You can register adapters with registerAdapter: >>> components.registerAdapter(tests.A12_1) Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Here, we didn't specify required interfaces, a provided interface, or a name. The required interfaces were determined from the factory s __component_adapts__ attribute and the provided interface was determined by introspecting what the factory implements. >>> components.getMultiAdapter((tests.U1(6), tests.U12(7)), tests.IA1) A12_1(U1(6), U12(7)) If a factory implements more than one interface, an exception will be raised: >>> components.registerAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.registerAdapter(tests.A1_12, provided=tests.IA2) Registered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') If a factory doesn't declare an implemented interface, an exception will be raised: >>> components.registerAdapter(tests.A12_) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.registerAdapter(tests.A12_, provided=tests.IA2) Registered event: AdapterRegistration(, [I1, I2], IA2, u'', A12_, u'') The required interface needs to be specified in the registration if the factory doesn't have a __component_adapts__ attribute: >>> components.registerAdapter(tests.A_2) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified Unless the required specifications specified: >>> components.registerAdapter(tests.A_2, required=[tests.I3]) Registered event: AdapterRegistration(, [I3], IA2, u'', A_2, u'') Classes can be specified in place of specifications, in which case the implementedBy specification for the class is used: >>> components.registerAdapter(tests.A_3, required=[tests.U], ... info="Really class specific") ... # doctest: +NORMALIZE_WHITESPACE Registered event: AdapterRegistration(, [zope.component.tests.U], IA3, u'', A_3, 'Really class specific') We can see the adapters that have been registered using the registeredAdapters method: >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (, ) zope.component.tests.A12_1 (, ) zope.component.tests.A12_ (,) zope.component.tests.A1_12 (,) zope.component.tests.A_2 (,) zope.component.tests.A_3 Really class specific As with utilities, we can provide registration information when registering adapters. If you try to fetch an adapter that isn't registered, you'll get a component-lookup error: >>> components.getMultiAdapter((tests.U(8), ), tests.IA1) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: ((U(8),), , u'') unless you use queryAdapter: >>> components.queryMultiAdapter((tests.U(8), ), tests.IA1) >>> components.queryMultiAdapter((tests.U(8), ), tests.IA1, default=42) 42 When looking up an adapter for a single object, you can use the slightly simpler getAdapter and queryAdapter calls: >>> components.getAdapter(tests.U1(9), tests.IA2) A1_12(U1(9)) >>> components.queryAdapter(tests.U1(9), tests.IA2) A1_12(U1(9)) >>> components.getAdapter(tests.U(8), tests.IA1) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (U(8), , u'') >>> components.queryAdapter(tests.U(8), tests.IA2) >>> components.queryAdapter(tests.U(8), tests.IA2, default=42) 42 You can unregister an adapter. If a factory is provided and if the rewuired and provided interfaces, can be infered, then they need not be provided: >>> components.unregisterAdapter(tests.A12_1) Unregistered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') True >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (, ) zope.component.tests.A12_ (,) zope.component.tests.A1_12 (,) zope.component.tests.A_2 (,) zope.component.tests.A_3 Really class specific A boolean is returned indicating whether a change was made. If a factory implements more than one interface, an exception will be raised: >>> components.unregisterAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.unregisterAdapter(tests.A1_12, provided=tests.IA2) Unregistered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') True If a factory doesn't declare an implemented interface, an exception will be raised: >>> components.unregisterAdapter(tests.A12_) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.unregisterAdapter(tests.A12_, provided=tests.IA2) Unregistered event: AdapterRegistration(, [I1, I2], IA2, u'', A12_, u'') True The required interface needs to be specified if the factory doesn't have a __component_adapts__ attribute: >>> components.unregisterAdapter(tests.A_2) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified >>> components.unregisterAdapter(tests.A_2, required=[tests.I3]) Unregistered event: AdapterRegistration(, [I3], IA2, u'', A_2, u'') True >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) zope.component.tests.A_3 Really class specific If a factory is unregistered that is not registered, False is returned: >>> components.unregisterAdapter(tests.A_2, required=[tests.I3]) False >>> components.unregisterAdapter(tests.A12_1, required=[tests.U]) False The factory can be omitted, to unregister *any* factory that matches specified required and provided interfaces: >>> components.unregisterAdapter(required=[tests.U], provided=tests.IA3) ... # doctest: +NORMALIZE_WHITESPACE Unregistered event: AdapterRegistration(, [zope.component.tests.U], IA3, u'', A_3, 'Really class specific') True >>> for registration in sorted(components.registeredAdapters()): ... print registration Adapters can be named: >>> components.registerAdapter(tests.A1_12, provided=tests.IA2, ... name=u'test') Registered event: AdapterRegistration(, [I1], IA2, u'test', A1_12, u'') >>> components.queryMultiAdapter((tests.U1(9), ), tests.IA2) >>> components.queryMultiAdapter((tests.U1(9), ), tests.IA2, name=u'test') A1_12(U1(9)) >>> components.queryAdapter(tests.U1(9), tests.IA2) >>> components.queryAdapter(tests.U1(9), tests.IA2, name=u'test') A1_12(U1(9)) >>> components.getAdapter(tests.U1(9), tests.IA2, name=u'test') A1_12(U1(9)) It is possible to look up all of the adapters that provide an interface: >>> components.registerAdapter(tests.A1_23, provided=tests.IA2, ... name=u'test 2') Registered event: AdapterRegistration(, [I1], IA2, u'test 2', A1_23, u'') >>> components.registerAdapter(tests.A1_12, provided=tests.IA2) Registered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') >>> for name, adapter in sorted(components.getAdapters((tests.U1(9), ), ... tests.IA2)): ... print name, adapter A1_12(U1(9)) test A1_12(U1(9)) test 2 A1_23(U1(9)) getAdapters is most commonly used as the basis of menu systems. If an adapter factory returns None, it is equivalent to there being no factory: >>> components.registerAdapter(tests.noop, ... required=[tests.IA1], provided=tests.IA2, ... name=u'test noop') ... # doctest: +NORMALIZE_WHITESPACE Registered event: AdapterRegistration(, [IA1], IA2, u'test noop', noop, u'') >>> components.queryAdapter(tests.U1(9), tests.IA2, name=u'test noop') >>> components.registerAdapter(tests.A1_12, provided=tests.IA2) Registered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') >>> for name, adapter in sorted(components.getAdapters((tests.U1(9), ), ... tests.IA2)): ... print name, adapter A1_12(U1(9)) test A1_12(U1(9)) test 2 A1_23(U1(9)) >>> components.unregisterAdapter(tests.A1_12, provided=tests.IA2, ... name=u'test') Unregistered event: AdapterRegistration(, [I1], IA2, u'test', A1_12, u'') True >>> components.unregisterAdapter(tests.A1_12, provided=tests.IA2) Unregistered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') True >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) test 2 zope.component.tests.A1_23 (,) test noop Subscribers ----------- Subscribers provide a way to get multiple adapters of a given type. In this regard, subscribers are like named adapters, except that there isn't any concept of the most specific adapter for a given name. Subscribers are registered by calling registerSubscriptionAdapter: >>> components.registerSubscriptionAdapter(tests.A1_2) ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') >>> components.registerSubscriptionAdapter( ... tests.A1_12, provided=tests.IA2) ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_12, u'') >>> components.registerSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2, ... info='a sample comment') ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A, 'a sample comment') The same rules, with regard to when required and provided interfaces have to be specified apply as with adapters: >>> components.registerSubscriptionAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.registerSubscriptionAdapter(tests.A) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.registerSubscriptionAdapter(tests.A, required=[tests.IA1]) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Note that we provided the info argument as a keyword argument above. That's because there is a name argument that's reserved for future use. We can give a name, as long as it is an empty string: >>> components.registerSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2, u'', 'a sample comment') ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A, 'a sample comment') >>> components.registerSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2, u'oops', 'a sample comment') Traceback (most recent call last): ... TypeError: Named subscribers are not yet supported Subscribers are looked up using the subscribers method: >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_2(U1(1)) A1_12(U1(1)) A(U1(1),) A(U1(1),) Note that, because we created multiple subscriptions for A, we got multiple subscriber instances. As with normal adapters, if a factory returns None, the result is skipped: >>> components.registerSubscriptionAdapter( ... tests.noop, [tests.I1], tests.IA2) Registered event: SubscriptionRegistration(, [I1], IA2, u'', noop, u'') >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_2(U1(1)) A1_12(U1(1)) A(U1(1),) A(U1(1),) We can get registration information for subscriptions: >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info (,) zope.component.tests.A a sample comment (,) zope.component.tests.A a sample comment (,) zope.component.tests.A1_12 (,) zope.component.tests.A1_2 (,) We can also unregister subscriptions in much the same way we can for adapters: >>> components.unregisterSubscriptionAdapter(tests.A1_2) ... # doctest: +NORMALIZE_WHITESPACE Unregistered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, '') True >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_12(U1(1)) A(U1(1),) A(U1(1),) >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info (,) zope.component.tests.A a sample comment (,) zope.component.tests.A a sample comment (,) zope.component.tests.A1_12 (,) >>> components.unregisterSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2) Unregistered event: SubscriptionRegistration(, [I1], IA2, u'', A, '') True >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_12(U1(1)) >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info (,) zope.component.tests.A1_12 (,) Note here that both registrations for A were removed. If we omit the factory, we must specify the required and provided interfaces: >>> components.unregisterSubscriptionAdapter(required=[tests.I1]) Traceback (most recent call last): ... TypeError: Must specify one of factory and provided >>> components.unregisterSubscriptionAdapter(provided=tests.IA2) Traceback (most recent call last): ... TypeError: Must specify one of factory and required >>> components.unregisterSubscriptionAdapter( ... required=[tests.I1], provided=tests.IA2) Unregistered event: SubscriptionRegistration(, [I1], IA2, u'', None, '') True >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.factory As when registering, an error is raised if the registration information can't be determined from the factory and isn't specified: >>> components.unregisterSubscriptionAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.unregisterSubscriptionAdapter(tests.A) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.unregisterSubscriptionAdapter(tests.A, required=[tests.IA1]) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. If you unregister something that's not registered, nothing will be changed and False will be returned: >>> components.unregisterSubscriptionAdapter( ... required=[tests.I1], provided=tests.IA2) False Handlers -------- Handlers are used when you want to perform some function in response to an event. Handlers aren't expected to return anything when called and are not registered to provide any interface. >>> from zope import component >>> @component.adapter(tests.I1) ... def handle1(x): ... print 'handle1', x >>> components.registerHandler(handle1, info="First handler") ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [I1], u'', handle1, 'First handler') >>> components.handle(tests.U1(1)) handle1 U1(1) >>> @component.adapter(tests.I1, tests.I2) ... def handle12(x, y): ... print 'handle12', x, y >>> components.registerHandler(handle12) Registered event: HandlerRegistration(, [I1, I2], u'', handle12, u'') >>> components.handle(tests.U1(1), tests.U12(2)) handle12 U1(1) U12(2) If a handler doesn't document interfaces it handles, then the required interfaces must be specified: >>> def handle(*objects): ... print 'handle', objects >>> components.registerHandler(handle) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified >>> components.registerHandler(handle, required=[tests.I1], ... info="a comment") Registered event: HandlerRegistration(, [I1], u'', handle, 'a comment') Handlers can also be registered for classes: >>> components.registerHandler(handle, required=[tests.U], ... info="handle a class") ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [zope.component.tests.U], u'', handle, 'handle a class') >>> components.handle(tests.U1(1)) handle (U1(1),) handle1 U1(1) handle (U1(1),) We can list the handler registrations: >>> for registration in components.registeredHandlers(): ... print registration.required ... print registration.handler, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) First handler (, ) (,) a comment (,) handle a class and we can unregister handlers: >>> components.unregisterHandler(required=[tests.U]) ... # doctest: +NORMALIZE_WHITESPACE Unregistered event: HandlerRegistration(, [zope.component.tests.U], u'', None, '') True >>> for registration in components.registeredHandlers(): ... print registration.required ... print registration.handler, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) First handler (, ) (,) a comment >>> components.unregisterHandler(handle12) Unregistered event: HandlerRegistration(, [I1, I2], u'', handle12, '') True >>> for registration in components.registeredHandlers(): ... print registration.required ... print registration.handler, registration.info (,) First handler (,) a comment >>> components.unregisterHandler(handle12) False >>> components.unregisterHandler() Traceback (most recent call last): ... TypeError: Must specify one of factory and required >>> components.registerHandler(handle) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified Extending --------- Component-management objects can extend other component-management objects. >>> c1 = registry.Components('1') >>> c1.__bases__ () >>> c2 = registry.Components('2', (c1, )) >>> c2.__bases__ == (c1, ) True >>> c1.registerUtility(tests.U1(1)) Registered event: UtilityRegistration(, I1, u'', 1, None, u'') >>> c1.queryUtility(tests.I1) U1(1) >>> c2.queryUtility(tests.I1) U1(1) >>> c1.registerUtility(tests.U1(2)) Unregistered event: UtilityRegistration(, I1, u'', 1, None, u'') Registered event: UtilityRegistration(, I1, u'', 2, None, u'') >>> c2.queryUtility(tests.I1) U1(2) We can use multiple inheritence: >>> c3 = registry.Components('3', (c1, )) >>> c4 = registry.Components('4', (c2, c3)) >>> c4.queryUtility(tests.I1) U1(2) >>> c1.registerUtility(tests.U12(1), tests.I2) Registered event: UtilityRegistration(, I2, u'', 1, None, u'') >>> c4.queryUtility(tests.I2) U12(1) >>> c3.registerUtility(tests.U12(3), tests.I2) Registered event: UtilityRegistration(, I2, u'', 3, None, u'') >>> c4.queryUtility(tests.I2) U12(3) >>> c1.registerHandler(handle1, info="First handler") Registered event: HandlerRegistration(, [I1], u'', handle1, 'First handler') >>> c2.registerHandler(handle, required=[tests.U]) ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [zope.component.tests.U], u'', handle, u'') >>> @component.adapter(tests.I1) ... def handle3(x): ... print 'handle3', x >>> c3.registerHandler(handle3) Registered event: HandlerRegistration(, [I1], u'', handle3, u'') >>> @component.adapter(tests.I1) ... def handle4(x): ... print 'handle4', x >>> c4.registerHandler(handle4) Registered event: HandlerRegistration(, [I1], u'', handle4, u'') >>> c4.handle(tests.U1(1)) handle1 U1(1) handle3 U1(1) handle (U1(1),) handle4 U1(1) Redispatch of registration events --------------------------------- Some handlers are available that, if registered, redispatch registration events to the objects being registered. They depend on being dispatched to by the object-event dispatcher: >>> from zope import component >>> import zope.component.event >>> zope.component.getGlobalSiteManager().registerHandler( ... zope.component.event.objectEventNotify) ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [IObjectEvent], u'', objectEventNotify, u'') To see this, we'll first register a multi-handler to show is when handlers are called on 2 objects: >>> @zope.component.adapter(None, None) ... def double_handler(o1, o2): ... print 'Double dispatch:' ... print ' ', o1 ... print ' ', o2 >>> zope.component.getGlobalSiteManager().registerHandler(double_handler) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: HandlerRegistration(, [Interface, Interface], u'', double_handler, u'') Registered event: HandlerRegistration(, [Interface, Interface], u'', double_handler, u'') Registered event: HandlerRegistration(, [Interface, Interface], u'', double_handler, u'') In the example above, the double_handler reported it's own registration. :) Now we'll register our handlers: >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchUtilityRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Double dispatch: ... >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchAdapterRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Double dispatch: ... >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchSubscriptionAdapterRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Double dispatch: ... >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchHandlerRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') Registered event: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') Double dispatch: Registered event: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') Registered event: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') In the last example above, we can see that the registration of dispatchHandlerRegistrationEvent was handled by dispatchHandlerRegistrationEvent and redispatched. This can be seen in the second double-dispatch output, where the first argument is the object being registered, which is dispatchHandlerRegistrationEvent. If we change some other registrations, we can the double dispatch taking place: >>> components.registerUtility(u5) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: UtilityRegistration(, I1, u'', 5, None, u'') Registered event: UtilityRegistration(, I1, u'', 5, None, u'') Double dispatch: U1(5) Registered event: UtilityRegistration(, I1, u'', 5, None, u'') Registered event: UtilityRegistration(, I1, u'', 5, None, u'') >>> components.registerAdapter(tests.A12_1) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Double dispatch: zope.component.tests.A12_1 Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') >>> components.registerSubscriptionAdapter(tests.A1_2) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Double dispatch: zope.component.tests.A1_2 Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Persistent Component Management =============================== Persistent component management allows persistent management of components. From a usage point of view, there shouldn't be any new behavior beyond what's described in registry.txt. The Zope 3 Component Architecture (Socket Example) ================================================== The component architecture provides an application framework that provides its functionality through loosely-connected components. A *component* can be any Python object and has a particular purpose associated with it. Thus, in a component-based applications you have many small component in contrast to classical object-oriented development, where you have a few big objects. Components communicate via specific APIs, which are formally defined by interfaces, which are provided by the `zope.interface` package. *Interfaces* describe the methods and properties that a component is expected to provide. They are also used as a primary mean to provide developer-level documentation for the components. For more details about interfaces see `zope/interface/README.txt`. The two main types of components are *adapters* and *utilities*. They will be discussed in detail later in this document. Both component types are managed by the *site manager*, with which you can register and access these components. However, most of the site manager's functionality is hidden behind the component architecture's public API, which is documented in `IComponentArchitecture`. Adapters -------- Adapters are a well-established pattern. An *adapter* uses an object providing one interface to produce an object that provides another interface. Here an example: Imagine that you purchased an electric shaver in the US, and thus you require the US socket type. You are now traveling in Germany, where another socket style is used. You will need a device, an adapter, that converts from the German to the US socket style. The functionality of adapters is actually natively provided by the `zope.interface` package and is thus well documented there. The `human.txt` file provides a gentle introduction to adapters, whereby `adapter.txt` is aimed at providing a comprehensive insight into adapters, but is too abstract for many as an initial read. Thus, we will only explain adapters in the context of the component architecture's API. So let's say that we have a German socket >>> from zope.interface import Interface, implements >>> class IGermanSocket(Interface): ... pass >>> class Socket(object): ... def __repr__(self): ... return '' %self.__class__.__name__ >>> class GermanSocket(Socket): ... """German wall socket.""" ... implements(IGermanSocket) and we want to convert it to an US socket >>> class IUSSocket(Interface): ... pass so that our shaver can be used in Germany. So we go to a German electronics store to look for an adapter that we can plug in the wall: >>> class GermanToUSSocketAdapter(Socket): ... implements(IUSSocket) ... __used_for__ = IGermanSocket ... ... def __init__(self, socket): ... self.context = socket Note that I could have called the passed in socket any way I like, but `context` is the standard name accepted. Single Adapters ~~~~~~~~~~~~~~~ Before we can use the adapter, we have to buy it and make it part of our inventory. In the component architecture we do this by registering the adapter with the framework, more specifically with the global site manager: >>> import zope.component >>> gsm = zope.component.getGlobalSiteManager() >>> gsm.registerAdapter(GermanToUSSocketAdapter, (IGermanSocket,), IUSSocket) `zope.component` is the component architecture API that is being presented by this file. You registered an adapter from `IGermanSocket` to `IUSSocket` having no name (thus the empty string). Anyways, you finally get back to your hotel room and shave, since you have not been able to shave in the plane. In the bathroom you discover a socket: >>> bathroomDE = GermanSocket() >>> IGermanSocket.providedBy(bathroomDE) True You now insert the adapter in the German socket >>> bathroomUS = zope.component.getAdapter(bathroomDE, IUSSocket, '') so that the socket now provides the US version: >>> IUSSocket.providedBy(bathroomUS) True Now you can insert your shaver and get on with your day. After a week you travel for a couple of days to the Prague and you notice that the Czech have yet another socket type: >>> class ICzechSocket(Interface): ... pass >>> class CzechSocket(Socket): ... implements(ICzechSocket) >>> czech = CzechSocket() You try to find an adapter for your shaver in your bag, but you fail, since you do not have one: >>> zope.component.getAdapter(czech, IUSSocket, '') \ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, , '') or the more graceful way: >>> marker = object() >>> socket = zope.component.queryAdapter(czech, IUSSocket, '', marker) >>> socket is marker True In the component architecture API any `get*` method will fail with a specific exception, if a query failed, whereby methods starting with `query*` will always return a `default` value after a failure. Named Adapters ~~~~~~~~~~~~~~ You are finally back in Germany. You also brought your DVD player and a couple DVDs with you, which you would like to watch. Your shaver was able to convert automatically from 110 volts to 240 volts, but your DVD player cannot. So you have to buy another adapter that also handles converting the voltage and the frequency of the AC current: >>> class GermanToUSSocketAdapterAndTransformer(object): ... implements(IUSSocket) ... __used_for__ = IGermanSocket ... ... def __init__(self, socket): ... self.context = socket Now, we need a way to keep the two adapters apart. Thus we register them with a name: >>> gsm.registerAdapter(GermanToUSSocketAdapter, ... (IGermanSocket,), IUSSocket, 'shaver',) >>> gsm.registerAdapter(GermanToUSSocketAdapterAndTransformer, ... (IGermanSocket,), IUSSocket, 'dvd') Now we simply look up the adapters using their labels (called *name*): >>> socket = zope.component.getAdapter(bathroomDE, IUSSocket, 'shaver') >>> socket.__class__ is GermanToUSSocketAdapter True >>> socket = zope.component.getAdapter(bathroomDE, IUSSocket, 'dvd') >>> socket.__class__ is GermanToUSSocketAdapterAndTransformer True Clearly, we do not have an adapter for the MP3 player >>> zope.component.getAdapter(bathroomDE, IUSSocket, 'mp3') \ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, , 'mp3') but you could use the 'dvd' adapter in this case of course. ;) Sometimes you want to know all adapters that are available. Let's say you want to know about all the adapters that convert a German to a US socket type: >>> sockets = list(zope.component.getAdapters((bathroomDE,), IUSSocket)) >>> len(sockets) 3 >>> names = [name for name, socket in sockets] >>> names.sort() >>> names [u'', u'dvd', u'shaver'] `zope.component.getAdapters()` returns a list of tuples. The first entry of the tuple is the name of the adapter and the second is the adapter itself. Multi-Adapters ~~~~~~~~~~~~~~ After watching all the DVDs you brought at least twice, you get tired of them and you want to listen to some music using your MP3 player. But darn, the MP3 player plug has a ground pin and all the adapters you have do not support that: >>> class IUSGroundedSocket(IUSSocket): ... pass So you go out another time to buy an adapter. This time, however, you do not buy yet another adapter, but a piece that provides the grounding plug: >>> class IGrounder(Interface): ... pass >>> class Grounder(object): ... implements(IGrounder) ... def __repr__(self): ... return '' Then together they will provided a grounded us socket: >>> class GroundedGermanToUSSocketAdapter(object): ... implements(IUSGroundedSocket) ... __used_for__ = (IGermanSocket, IGrounder) ... def __init__(self, socket, grounder): ... self.socket, self.grounder = socket, grounder You now register the combination, so that you know you can create a `IUSGroundedSocket`: >>> gsm.registerAdapter(GroundedGermanToUSSocketAdapter, ... (IGermanSocket, IGrounder), IUSGroundedSocket, 'mp3') Given the grounder >>> grounder = Grounder() and a German socket >>> livingroom = GermanSocket() we can now get a grounded US socket: >>> socket = zope.component.getMultiAdapter((livingroom, grounder), ... IUSGroundedSocket, 'mp3') >>> socket.__class__ is GroundedGermanToUSSocketAdapter True >>> socket.socket is livingroom True >>> socket.grounder is grounder True Of course, you do not have a 'dvd' grounded US socket available: >>> zope.component.getMultiAdapter((livingroom, grounder), ... IUSGroundedSocket, 'dvd') \ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: ((, ), , 'dvd') >>> socket = zope.component.queryMultiAdapter( ... (livingroom, grounder), IUSGroundedSocket, 'dvd', marker) >>> socket is marker True Again, you might want to read `adapter.txt` in `zope.interface` for a more comprehensive coverage of multi-adapters. Subscribers ----------- While subscribers are directly supported by the adapter registry and are adapters for all theoretical purposes, practically it might be better to think of them as separate components. Subscribers are particularly useful for events. Let's say one of our adapters overheated and caused a small fire: >>> class IFire(Interface): ... pass >>> class Fire(object): ... implements(IFire) >>> fire = Fire() We want to use all available objects to put out the fire: >>> class IFireExtinguisher(Interface): ... def extinguish(): ... pass >>> class FireExtinguisher(object): ... def __init__(self, fire): ... pass ... def extinguish(self): ... "Place extinguish code here." ... print 'Used ' + self.__class__.__name__ + '.' Here some specific methods to put out the fire: >>> class PowderExtinguisher(FireExtinguisher): ... pass >>> gsm.registerSubscriptionAdapter(PowderExtinguisher, ... (IFire,), IFireExtinguisher) >>> class Blanket(FireExtinguisher): ... pass >>> gsm.registerSubscriptionAdapter(Blanket, (IFire,), IFireExtinguisher) >>> class SprinklerSystem(FireExtinguisher): ... pass >>> gsm.registerSubscriptionAdapter(SprinklerSystem, ... (IFire,), IFireExtinguisher) Now let use all these things to put out the fire: >>> extinguishers = zope.component.subscribers((fire,), IFireExtinguisher) >>> extinguishers.sort() >>> for extinguisher in extinguishers: ... extinguisher.extinguish() Used Blanket. Used PowderExtinguisher. Used SprinklerSystem. If no subscribers are found for a particular object, then an empty list is returned: >>> zope.component.subscribers((object(),), IFireExtinguisher) [] Utilities --------- Utilities are the second type of component, the component architecture implements. *Utilities* are simply components that provide an interface. When you register an utility, you always register an instance (in contrast to a factory for adapters) since the initialization and setup process of a utility might be complex and is not well defined. In some ways a utility is much more fundamental than an adapter, because an adapter cannot be used without another component, but a utility is always self-contained. I like to think of utilities as the foundation of your application and adapters as components extending beyond this foundation. Back to our story... After your vacation is over you fly back home to Tampa, Florida. But it is August now, the middle of the Hurricane season. And, believe it or not, you are worried that you will not be able to shave when the power goes out for several days. (You just hate wet shavers.) So you decide to go to your favorite hardware store and by a Diesel-powered electric generator. The generator provides of course a US-style socket: >>> class Generator(object): ... implements(IUSSocket) ... def __repr__(self): ... return '' >>> generator = Generator() Like for adapters, we now have to add the newly-acquired generator to our inventory by registering it as a utility: >>> gsm.registerUtility(generator, IUSSocket) We can now get the utility using >>> utility = zope.component.getUtility(IUSSocket) >>> utility is generator True As you can see, it is very simple to register and retrieve utilities. If a utility does not exist for a particular interface, such as the German socket, then the lookup fails >>> zope.component.getUtility(IGermanSocket) Traceback (most recent call last): ... ComponentLookupError: (, '') or more gracefully when specifying a default value: >>> default = object() >>> utility = zope.component.queryUtility(IGermanSocket, default=default) >>> utility is default True Note: The only difference between `getUtility()` and `queryUtility()` is the fact that you can specify a default value for the latter function, so that it will never cause a `ComponentLookupError`. Named Utilities ~~~~~~~~~~~~~~~ It is often desirable to have several utilities providing the same interface per site. This way you can implement any sort of registry using utilities. For this reason, utilities -- like adapters -- can be named. In the context of our story, we might want to do the following: You really do not trust gas stations either. What if the roads are blocked after a hurricane and the gas stations run out of oil. So you look for another renewable power source. Then you think about solar panels! After a storm there is usually very nice weather, so why not? Via the Web you order a set of 110V/120W solar panels that provide a regular US-style socket as output: >>> class SolarPanel(object): ... implements(IUSSocket) ... def __repr__(self): ... return '' >>> panel = SolarPanel() Once it arrives, we add it to our inventory: >>> gsm.registerUtility(panel, IUSSocket, 'Solar Panel') You can now access the solar panel using >>> utility = zope.component.getUtility(IUSSocket, 'Solar Panel') >>> utility is panel True Of course, if a utility is not available, then the lookup will simply fail >>> zope.component.getUtility(IUSSocket, 'Wind Mill') Traceback (most recent call last): ... ComponentLookupError: (, 'Wind Mill') or more gracefully when specifying a default value: >>> default = object() >>> utility = zope.component.queryUtility(IUSSocket, 'Wind Mill', ... default=default) >>> utility is default True Now you want to look at all the utilities you have for a particular kind. The following API function will return a list of name/utility pairs: >>> utils = list(zope.component.getUtilitiesFor(IUSSocket)) >>> utils.sort() >>> utils #doctest: +NORMALIZE_WHITESPACE [(u'', ), (u'Solar Panel', )] Another method of looking up all utilities is by using `getAllUtilitiesRegisteredFor(iface)`. This function will return an iterable of utilities (without names); however, it will also return overridden utilities. If you are not using multiple site managers, you will not actually need this method. >>> utils = list(zope.component.getAllUtilitiesRegisteredFor(IUSSocket)) >>> utils.sort() >>> utils [, ] Factories ~~~~~~~~~ A *factory* is a special kind of utility that exists to create other components. A factory is always identified by a name. It also provides a title and description and is able to tell the developer what interfaces the created object will provide. The advantage of using a factory to create an object instead of directly instantiating a class or executing any other callable is that we can refer to the factory by name. As long as the name stays fixed, the implementation of the callable can be renamed or moved without a breakage in code. Let's say that our solar panel comes in parts and they have to be assembled. This assembly would be done by a factory, so let's create one for the solar panel. To do this, we can use a standard implementation of the `IFactory` interface: >>> from zope.component.factory import Factory >>> factory = Factory(SolarPanel, ... 'Solar Panel', ... 'This factory creates a solar panel.') Optionally, I could have also specified the interfaces that the created object will provide, but the factory class is smart enough to determine the implemented interface from the class. We now register the factory: >>> from zope.component.interfaces import IFactory >>> gsm.registerUtility(factory, IFactory, 'SolarPanel') We can now get a list of interfaces the produced object will provide: >>> ifaces = zope.component.getFactoryInterfaces('SolarPanel') >>> IUSSocket in ifaces True By the way, this is equivalent to >>> ifaces2 = factory.getInterfaces() >>> ifaces is ifaces2 True Of course you can also just create an object: >>> panel = zope.component.createObject('SolarPanel') >>> panel.__class__ is SolarPanel True Note: Ignore the first argument (`None`) for now; it is the context of the utility lookup, which is usually an optional argument, but cannot be in this case, since all other arguments beside the `name` are passed in as arguments to the specified callable. Once you register several factories >>> gsm.registerUtility(Factory(Generator), IFactory, 'Generator') you can also determine, which available factories will create objects providing a certain interface: >>> factories = zope.component.getFactoriesFor(IUSSocket) >>> factories = [(name, factory.__class__) for name, factory in factories] >>> factories.sort() >>> factories #doctest: +NORMALIZE_WHITESPACE [(u'Generator', ), (u'SolarPanel', )] Site Managers ------------- Why do we need site managers? Why is the component architecture API not sufficient? Some applications, including Zope 3, have a concept of locations. It is often desirable to have different configurations for these location; this can be done by overwriting existing or adding new component registrations. Site managers in locations below the root location, should be able to delegate requests to their parent locations. The root site manager is commonly known as *global site manager*, since it is always available. You can always get the global site manager using the API: >>> gsm = zope.component.getGlobalSiteManager() >>> from zope.component import globalSiteManager >>> gsm is globalSiteManager True >>> from zope.component.interfaces import IComponentLookup >>> IComponentLookup.providedBy(gsm) True >>> from zope.component.interfaces import IComponents >>> IComponents.providedBy(gsm) True You can also lookup at site manager in a given context. The only requirement is that the context can be adapted to a site manager. So let's create a special site manager: >>> from zope.component.globalregistry import BaseGlobalComponents >>> sm = BaseGlobalComponents() Now we create a context that adapts to the site manager via the `__conform__` method as specified in PEP 246. >>> class Context(object): ... def __init__(self, sm): ... self.sm = sm ... def __conform__(self, interface): ... if interface.isOrExtends(IComponentLookup): ... return self.sm We now instantiate the `Context` with our special site manager: >>> context = Context(sm) >>> context.sm is sm True We can now ask for the site manager of this context: >>> lsm = zope.component.getSiteManager(context) >>> lsm is sm True The site manager instance `lsm` is formally known as a *local site manager* of `context`. CHANGES ******* 3.9.5 (2010-07-09) ================== - Fix test requirements specification. 3.9.4 (2010-04-30) ================== - Prefer the standard libraries doctest module to the one from zope.testing. 3.9.3 (2010-03-08) ================== - The ZCML directives provided by zope.component now register the components in the registry returned by getSiteManager instead of the global registry. This allows the hooking of the getSiteManager method before the load of a ZCML file to register the components in a custom registry. 3.9.2 (2010-01-22) ================== - Fixed a bug introduced by recent refactoring, where passing CheckerPublic to securityAdapterFactory wrongly wrapped the factory into a LocatingUntrustedAdapterFactory. 3.9.1 (2010-01-21) ================== - The tested testrunner somehow gets influenced by options of the outer testrunner, such a the -v option. We modified the tests so that it avoids this. 3.9.0 (2010-01-21) ================== - Add testlayer support. It is now possible to load a ZCML file within tests more easily. See zope.component.testlayer.py and zope.component.testlayer.txt. 3.8.0 (2009-11-16) ================== - Removed the dependencies on zope.proxy and zope.security from the zcml extra: zope.component does not hard depend on them anymore; the support for security proxied components ZCML registrations is enabled only if zope.security and zope.proxy are available. - Moved the IPossibleSite and ISite interfaces here from zope.location as they are dealing with zope.component's concept of a site, but not with location. - Moved the zope.site.hooks functionality to zope.component.hooks as it isn't actually dealing with zope.site's concept of a site. 3.7.1 (2009-07-24) ================== - Fixed a problem, where ``queryNextUtility`` could fail if the context could not be adapted to a ``IComponentLookup``. - Fixed 2 related bugs: When a utility is registered and there was previously a utility registered for the same interface and name, then the old utility is unregistered. The 2 bugs related to this: - There was no ``Unregistered`` for the implicit unregistration. Now there is. - The old utility was still held and returned by getAllUtilitiesRegisteredFor. In other words, it was still considered registered, eeven though it wasn't. A particularly negative consequence of this is that the utility is held in memory or in the database even though it isn't used. 3.7.0 (2009-05-21) ================== - The HookableTests were not run by the testrunner. - Add in zope:view and zope:resource implementations into zope.component.zcml (dependency loaded with zope.component [zcml]). 3.6.0 (2009-03-12) ================== - IMPORTANT: the interfaces that were defined in the zope.component.bbb.interfaces and deprecated for years are now (re)moved. However, some packages, including part of zope framework were still using those interfaces. They will be adapted for this change. If you were using some of those interfaces, you need to adapt your code as well: - The IView and IDefaultViewName were moved to zope.publisher.interfaces. - The IResource was moved to zope.app.publisher.interfaces. - IContextDependent, IPresentation, IPresentationRequest, IResourceFactory, IViewFactory were removed completely. If you used IViewFactory in context of zope.app.form, there's now IWidgetFactory in the zope.app.form.interfaces instead. - Add getNextUtility/queryNextUtility functions that used to be in zope.site earlier (and in zope.app.component even more earlier). - Added a pure-Python 'hookable' implementation, for use when 'zope.hookable' is not present. - Removed use of 'zope.deferredimport' by breaking import cycles. - Cleanup package documentation and changelog a bit. Add sphinx-based documentation building command to the buildout. - Remove deprecated code. - Change package's mailing list address to zope-dev at zope.org, because zope3-dev at zope.org is now retired. 3.5.1 (2008-07-25) ================== - Fix bug introduced in 3.5.0: no longer supported interfaces declared in Python and always wanted an explicit provides="..." attribute. https://bugs.launchpad.net/zope3/+bug/251865 3.5.0 (2008-07-25) ================== - Support registration of utilities via factories through the component registry and return factory information in the registration information. This fixes https://bugs.launchpad.net/zope3/+bug/240631 - Optimized un/registerUtility via storing an optimized data structure for efficient retrieval of already registered utilities. This avoids looping over all utilities when registering a new one. 3.4.0 (2007-09-29) ================== No further changes since 3.4.0a1. 3.4.0a1 (2007-04-22) ==================== Corresponds to zope.component from Zope 3.4.0a1. - In the Zope 3.3.x series, ``zope.component`` was simplified yet once more. See http://wiki.zope.org/zope3/LocalComponentManagementSimplification for the proposal describing the changes. 3.2.0.2 (2006-04-15) ==================== - Fix packaging bug: 'package_dir' must be a *relative* path. 3.2.0.1 (2006-04-14) ==================== - Packaging change: suppress inclusion of 'setup.cfg' in 'sdist' builds. 3.2.0 (2006-01-05) ================== Corresponds to the verison of the zope.component package shipped as part of the Zope 3.2.0 release. - Deprecated services and related APIs. The adapter and utility registries are now available directly via the site manager's 'adapters' and 'utilities' attributes, respectively. Services are accessible, but deprecated, and will be removed in Zope 3.3. - Deprectaed all presentation-related APIs, including all view-related API functions. Use the adapter API functions instead. See http://dev.zope.org/Zope3/ImplementViewsAsAdapters` - Deprecated 'contextdependent' package: site managers are now looked up via a thread global, set during URL traversal. The 'context' argument is now always optional, and should no longer be passed. 3.0.0 (2004-11-07) ================== Corresponds to the verison of the zope.component package shipped as part of the Zope X3.0.0 release. Download ******** Platform: UNKNOWN zope2.13-2.13.21/source/zope.component/pip-egg-info/0000755000175000017500000000000012214017426020675 5ustar arnauarnauzope2.13-2.13.21/source/zope.component/pip-egg-info/zope.component.egg-info/0000755000175000017500000000000012214017426025345 5ustar arnauarnauzope2.13-2.13.21/source/zope.component/pip-egg-info/zope.component.egg-info/PKG-INFO0000644000175000017500000032223112214017426026445 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.component Version: 3.9.5 Summary: Zope Component Architecture Home-page: http://pypi.python.org/pypi/zope.component Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ***************************** zope.component Package Readme ***************************** *This package is intended to be independently reusable in any Python project. It is maintained by the* `Zope Toolkit project `_. This package represents the core of the Zope Component Architecture. Together with the 'zope.interface' package, it provides facilities for defining, registering and looking up components. .. contents:: Detailed Documentation ********************** Zope Component Architecture =========================== This package, together with `zope.interface`, provides facilities for defining, registering and looking up components. There are two basic kinds of components: adapters and utilities. Utilities --------- Utilities are just components that provide an interface and that are looked up by an interface and a name. Let's look at a trivial utility definition: >>> from zope import interface >>> class IGreeter(interface.Interface): ... def greet(): ... "say hello" >>> class Greeter: ... interface.implements(IGreeter) ... ... def __init__(self, other="world"): ... self.other = other ... ... def greet(self): ... print "Hello", self.other We can register an instance this class using `provideUtility` [1]_: >>> from zope import component >>> greet = Greeter('bob') >>> component.provideUtility(greet, IGreeter, 'robert') In this example we registered the utility as providing the `IGreeter` interface with a name of 'bob'. We can look the interface up with either `queryUtility` or `getUtility`: >>> component.queryUtility(IGreeter, 'robert').greet() Hello bob >>> component.getUtility(IGreeter, 'robert').greet() Hello bob `queryUtility` and `getUtility` differ in how failed lookups are handled: >>> component.queryUtility(IGreeter, 'ted') >>> component.queryUtility(IGreeter, 'ted', 42) 42 >>> component.getUtility(IGreeter, 'ted') ... # doctest: +ELLIPSIS Traceback (most recent call last): ... ComponentLookupError: (, 'ted') If a component provides only one interface, as in the example above, then we can omit the provided interface from the call to `provideUtility`: >>> ted = Greeter('ted') >>> component.provideUtility(ted, name='ted') >>> component.queryUtility(IGreeter, 'ted').greet() Hello ted The name defaults to an empty string: >>> world = Greeter() >>> component.provideUtility(world) >>> component.queryUtility(IGreeter).greet() Hello world Adapters -------- Adapters are components that are computed from other components to adapt them to some interface. Because they are computed from other objects, they are provided as factories, usually classes. Here, we'll create a greeter for persons, so we can provide personalized greetings for different people: >>> class IPerson(interface.Interface): ... name = interface.Attribute("Name") >>> class PersonGreeter: ... ... component.adapts(IPerson) ... interface.implements(IGreeter) ... ... def __init__(self, person): ... self.person = person ... ... def greet(self): ... print "Hello", self.person.name The class defines a constructor that takes an argument for every object adapted. We used `component.adapts` to declare what we adapt. We can find out if an object declares that it adapts anything using adaptedBy: >>> list(component.adaptedBy(PersonGreeter)) == [IPerson] True If an object makes no declaration, then None is returned: >>> component.adaptedBy(Greeter()) is None True If we declare the interfaces adapted and if we provide only one interface, as in the example above, then we can provide the adapter very simply [1]_: >>> component.provideAdapter(PersonGreeter) For adapters that adapt a single interface to a single interface without a name, we can get the adapter by simply calling the interface: >>> class Person: ... interface.implements(IPerson) ... ... def __init__(self, name): ... self.name = name >>> IGreeter(Person("Sally")).greet() Hello Sally We can also provide arguments to be very specific about what how to register the adapter. >>> class BobPersonGreeter(PersonGreeter): ... name = 'Bob' ... def greet(self): ... print "Hello", self.person.name, "my name is", self.name >>> component.provideAdapter( ... BobPersonGreeter, [IPerson], IGreeter, 'bob') The arguments can also be provided as keyword arguments: >>> class TedPersonGreeter(BobPersonGreeter): ... name = "Ted" >>> component.provideAdapter( ... factory=TedPersonGreeter, adapts=[IPerson], ... provides=IGreeter, name='ted') For named adapters, use `queryAdapter`, or `getAdapter`: >>> component.queryAdapter(Person("Sally"), IGreeter, 'bob').greet() Hello Sally my name is Bob >>> component.getAdapter(Person("Sally"), IGreeter, 'ted').greet() Hello Sally my name is Ted If an adapter can't be found, `queryAdapter` returns a default value and `getAdapter` raises an error: >>> component.queryAdapter(Person("Sally"), IGreeter, 'frank') >>> component.queryAdapter(Person("Sally"), IGreeter, 'frank', 42) 42 >>> component.getAdapter(Person("Sally"), IGreeter, 'frank') ... # doctest: +ELLIPSIS Traceback (most recent call last): ... ComponentLookupError: (...Person...>, <...IGreeter>, 'frank') Adapters can adapt multiple objects: >>> class TwoPersonGreeter: ... ... component.adapts(IPerson, IPerson) ... interface.implements(IGreeter) ... ... def __init__(self, person, greeter): ... self.person = person ... self.greeter = greeter ... ... def greet(self): ... print "Hello", self.person.name ... print "my name is", self.greeter.name >>> component.provideAdapter(TwoPersonGreeter) To look up a multi-adapter, use either `queryMultiAdapter` or `getMultiAdapter`: >>> component.queryMultiAdapter((Person("Sally"), Person("Bob")), ... IGreeter).greet() Hello Sally my name is Bob Adapters need not be classes. Any callable will do. We use the adapter decorator (in the Python 2.4 decorator sense) to declare that a callable object adapts some interfaces (or classes): >>> class IJob(interface.Interface): ... "A job" >>> class Job: ... interface.implements(IJob) >>> def personJob(person): ... return getattr(person, 'job', None) >>> personJob = interface.implementer(IJob)(personJob) >>> personJob = component.adapter(IPerson)(personJob) In Python 2.4, the example can be written: >>> @interface.implementer(IJob) ... @component.adapter(IPerson) ... def personJob(person): ... return getattr(person, 'job', None) which looks a bit nicer. In this example, the personJob function simply returns the person's `job` attribute if present, or None if it's not present. An adapter factory can return None to indicate that adaptation wasn't possible. Let's register this adapter and try it out: >>> component.provideAdapter(personJob) >>> sally = Person("Sally") >>> IJob(sally) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: ('Could not adapt', ... The adaptation failed because sally didn't have a job. Let's give her one: >>> job = Job() >>> sally.job = job >>> IJob(sally) is job True Subscription Adapters --------------------- Unlike regular adapters, subscription adapters are used when we want all of the adapters that adapt an object to a particular adapter. Consider a validation problem. We have objects and we want to assess whether they meet some sort of standards. We define a validation interface: >>> class IValidate(interface.Interface): ... def validate(ob): ... """Determine whether the object is valid ... ... Return a string describing a validation problem. ... An empty string is returned to indicate that the ... object is valid. ... """ Perhaps we have documents: >>> class IDocument(interface.Interface): ... summary = interface.Attribute("Document summary") ... body = interface.Attribute("Document text") >>> class Document: ... interface.implements(IDocument) ... def __init__(self, summary, body): ... self.summary, self.body = summary, body Now, we may want to specify various validation rules for documents. For example, we might require that the summary be a single line: >>> class SingleLineSummary: ... component.adapts(IDocument) ... interface.implements(IValidate) ... ... def __init__(self, doc): ... self.doc = doc ... ... def validate(self): ... if '\n' in self.doc.summary: ... return 'Summary should only have one line' ... else: ... return '' Or we might require the body to be at least 1000 characters in length: >>> class AdequateLength: ... component.adapts(IDocument) ... interface.implements(IValidate) ... ... def __init__(self, doc): ... self.doc = doc ... ... def validate(self): ... if len(self.doc.body) < 1000: ... return 'too short' ... else: ... return '' We can register these as subscription adapters [1]_: >>> component.provideSubscriptionAdapter(SingleLineSummary) >>> component.provideSubscriptionAdapter(AdequateLength) We can then use the subscribers to validate objects: >>> doc = Document("A\nDocument", "blah") >>> [adapter.validate() ... for adapter in component.subscribers([doc], IValidate) ... if adapter.validate()] ['Summary should only have one line', 'too short'] >>> doc = Document("A\nDocument", "blah" * 1000) >>> [adapter.validate() ... for adapter in component.subscribers([doc], IValidate) ... if adapter.validate()] ['Summary should only have one line'] >>> doc = Document("A Document", "blah") >>> [adapter.validate() ... for adapter in component.subscribers([doc], IValidate) ... if adapter.validate()] ['too short'] Handlers -------- Handlers are subscription adapter factories that don't produce anything. They do all of their work when called. Handlers are typically used to handle events. Event subscribers are different from other subscription adapters in that the caller of event subscribers doesn't expect to interact with them in any direct way. For example, an event publisher doesn't expect to get any return value. Because subscribers don't need to provide an API to their callers, it is more natural to define them with functions, rather than classes. For example, in a document-management system, we might want to record creation times for documents: >>> import datetime >>> def documentCreated(event): ... event.doc.created = datetime.datetime.utcnow() In this example, we have a function that takes an event and performs some processing. It doesn't actually return anything. This is a special case of a subscription adapter that adapts an event to nothing. All of the work is done when the adapter "factory" is called. We call subscribers that don't actually create anything "handlers". There are special APIs for registering and calling them. To register the subscriber above, we define a document-created event: >>> class IDocumentCreated(interface.Interface): ... doc = interface.Attribute("The document that was created") >>> class DocumentCreated: ... interface.implements(IDocumentCreated) ... ... def __init__(self, doc): ... self.doc = doc We'll also change our handler definition to: >>> def documentCreated(event): ... event.doc.created = datetime.datetime.utcnow() >>> documentCreated = component.adapter(IDocumentCreated)(documentCreated) Note that in Python 2.4, this can be written: >>> @component.adapter(IDocumentCreated) ... def documentCreated(event): ... event.doc.created = datetime.datetime.utcnow() This marks the handler as an adapter of `IDocumentCreated` events. Now we'll register the handler [1]_: >>> component.provideHandler(documentCreated) Now, if we can create an event and use the `handle` function to call handlers registered for the event: >>> component.handle(DocumentCreated(doc)) >>> doc.created.__class__.__name__ 'datetime' .. [1] CAUTION: This API should only be used from test or application-setup code. This API shouldn't be used by regular library modules, as component registration is a configuration activity. Events ====== The Component Architecture provides a way to dispatch events to event handlers. Event handlers are registered as *subscribers* a.k.a. *handlers*. Before we can start we need to import ``zope.component.event`` to make the dispatching effective: >>> import zope.component.event Consider two event classes: >>> class Event1(object): ... pass >>> class Event2(Event1): ... pass Now consider two handlers for these event classes: >>> called = [] >>> import zope.component >>> @zope.component.adapter(Event1) ... def handler1(event): ... called.append(1) >>> @zope.component.adapter(Event2) ... def handler2(event): ... called.append(2) We can register them with the Component Architecture: >>> zope.component.provideHandler(handler1) >>> zope.component.provideHandler(handler2) Now let's go through the events. We'll see that the handlers have been called accordingly: >>> from zope.event import notify >>> notify(Event1()) >>> called [1] >>> del called[:] >>> notify(Event2()) >>> called.sort() >>> called [1, 2] Object events ------------- The ``objectEventNotify`` function is a subscriber to dispatch ObjectEvents to interested adapters. First create an object class: >>> class IUseless(zope.interface.Interface): ... """Useless object""" >>> class UselessObject(object): ... """Useless object""" ... zope.interface.implements(IUseless) Then create an event class: >>> class IObjectThrownEvent(zope.component.interfaces.IObjectEvent): ... """An object has been thrown away""" >>> class ObjectThrownEvent(zope.component.interfaces.ObjectEvent): ... """An object has been thrown away""" ... zope.interface.implements(IObjectThrownEvent) Create an object and an event: >>> hammer = UselessObject() >>> event = ObjectThrownEvent(hammer) Then notify the event to the subscribers. Since the subscribers list is empty, nothing happens. >>> zope.component.event.objectEventNotify(event) Now create an handler for the event: >>> events = [] >>> def record(*args): ... events.append(args) >>> zope.component.provideHandler(record, [IUseless, IObjectThrownEvent]) The event is notified to the subscriber: >>> zope.component.event.objectEventNotify(event) >>> events == [(hammer, event)] True Following test demonstrates how a subscriber can raise an exception to prevent an action. >>> zope.component.provideHandler(zope.component.event.objectEventNotify) Let's create a container: >>> class ToolBox(dict): ... def __delitem__(self, key): ... notify(ObjectThrownEvent(self[key])) ... return super(ToolBox,self).__delitem__(key) >>> container = ToolBox() And put the object into the container: >>> container['Red Hammer'] = hammer Create an handler function that will raise an error when called: >>> class Veto(Exception): ... pass >>> def callback(item, event): ... assert(item == event.object) ... raise Veto Register the handler: >>> zope.component.provideHandler(callback, [IUseless, IObjectThrownEvent]) Then if we try to remove the object, an ObjectThrownEvent is fired: >>> del container['Red Hammer'] ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... raise Veto Veto Factories ========= The Factory Class ----------------- >>> from zope.interface import Interface >>> class IFunction(Interface): ... pass >>> class IKlass(Interface): ... pass >>> from zope.interface import implements >>> class Klass(object): ... implements(IKlass) ... ... def __init__(self, *args, **kw): ... self.args = args ... self.kw = kw >>> from zope.component.factory import Factory >>> factory = Factory(Klass, 'Klass', 'Klassier') >>> factory2 = Factory(lambda x: x, 'Func', 'Function') >>> factory3 = Factory(lambda x: x, 'Func', 'Function', (IFunction,)) Calling a Factory ~~~~~~~~~~~~~~~~~ Here we test whether the factory correctly creates the objects and including the correct handling of constructor elements. First we create a factory that creates instanace of the `Klass` class: >>> factory = Factory(Klass, 'Klass', 'Klassier') Now we use the factory to create the instance >>> kl = factory(1, 2, foo=3, bar=4) and make sure that the correct class was used to create the object: >>> kl.__class__ Since we passed in a couple positional and keyword arguments >>> kl.args (1, 2) >>> kl.kw {'foo': 3, 'bar': 4} >>> factory2(3) 3 >>> factory3(3) 3 Title and Description ~~~~~~~~~~~~~~~~~~~~~ >>> factory.title 'Klass' >>> factory.description 'Klassier' >>> factory2.title 'Func' >>> factory2.description 'Function' >>> factory3.title 'Func' >>> factory3.description 'Function' Provided Interfaces ~~~~~~~~~~~~~~~~~~~ >>> implemented = factory.getInterfaces() >>> implemented.isOrExtends(IKlass) True >>> list(implemented) [] >>> implemented2 = factory2.getInterfaces() >>> list(implemented2) [] >>> implemented3 = factory3.getInterfaces() >>> list(implemented3) [] The Component Architecture Factory API -------------------------------------- >>> import zope.component >>> factory = Factory(Klass, 'Klass', 'Klassier') >>> gsm = zope.component.getGlobalSiteManager() >>> from zope.component.interfaces import IFactory >>> gsm.registerUtility(factory, IFactory, 'klass') Creating an Object ~~~~~~~~~~~~~~~~~~ >>> kl = zope.component.createObject('klass', 1, 2, foo=3, bar=4) >>> isinstance(kl, Klass) True >>> kl.args (1, 2) >>> kl.kw {'foo': 3, 'bar': 4} Accessing Provided Interfaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >>> implemented = zope.component.getFactoryInterfaces('klass') >>> implemented.isOrExtends(IKlass) True >>> [iface for iface in implemented] [] List of All Factories ~~~~~~~~~~~~~~~~~~~~~ >>> [(name, fac.__class__) for name, fac in ... zope.component.getFactoriesFor(IKlass)] [(u'klass', )] Component-Management objects ============================ Component-management objects provide a higher-level component-management API over the basic adapter-registration API provided by the zope.interface package. In particular, it provides: - utilities - support for computing adapters, rather than just looking up adapter factories. - management of registration comments The zope.component.registry.Components class provides an implementation of zope.component.interfaces.IComponents that provides these features. >>> from zope.component import registry >>> from zope.component import tests >>> components = registry.Components('comps') As components are registered, events are generated. Let's register an event subscriber, so we can see the events generated: >>> import zope.event >>> def logevent(event): ... print event >>> zope.event.subscribers.append(logevent) Utilities --------- You can register Utilities using registerUtility: >>> components.registerUtility(tests.U1(1)) Registered event: UtilityRegistration(, I1, u'', 1, None, u'') Here we didn't specify an interface or name. An unnamed utility was registered for interface I1, since that is only interface implemented by the U1 class: >>> components.getUtility(tests.I1) U1(1) You can also register a utility using a factory instead of a utility instance: >>> def factory(): ... return tests.U1(1) >>> components.registerUtility(factory=factory) Unregistered event: UtilityRegistration(, I1, u'', 1, None, u'') Registered event: UtilityRegistration(, I1, u'', 1, >, u'') If a component implements other than one interface or no interface, then an error will be raised: >>> components.registerUtility(tests.U12(2)) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The utility doesn't provide a single interface and no provided interface was specified. >>> components.registerUtility(tests.A) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The utility doesn't provide a single interface and no provided interface was specified. We can provide an interface if desired: >>> components.registerUtility(tests.U12(2), tests.I2) Registered event: UtilityRegistration(, I2, u'', 2, None, u'') and we can specify a name: >>> components.registerUtility(tests.U12(3), tests.I2, u'three') Registered event: UtilityRegistration(, I2, u'three', 3, None, u'') >>> components.getUtility(tests.I2) U12(2) >>> components.getUtility(tests.I2, 'three') U12(3) If you try to get a utility that doesn't exist, you'll get a component lookup error: >>> components.getUtility(tests.I3) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, u'') Unless you use queryUtility: >>> components.queryUtility(tests.I3) >>> components.queryUtility(tests.I3, default=42) 42 You can get information about registered utilities with the registeredUtilities method: >>> for registration in sorted(components.registeredUtilities()): ... print registration.provided, registration.name ... print registration.component, registration.info U1(1) U12(2) three U12(3) Duplicate registrations replace existing ones: >>> components.registerUtility(tests.U1(4), info=u'use 4 now') Unregistered event: UtilityRegistration(, I1, u'', 1, >, u'') Registered event: UtilityRegistration(, I1, u'', 4, None, u'use 4 now') >>> components.getUtility(tests.I1) U1(4) >>> for registration in sorted(components.registeredUtilities()): ... print registration.provided, registration.name ... print registration.component, registration.info U1(4) use 4 now U12(2) three U12(3) As shown in the this example, you can provide an "info" argumemnt when registering utilities. This provides extra documentation about the registration itself that is shown when listing registrations. You can also unregister utilities: >>> components.unregisterUtility(provided=tests.I1) Unregistered event: UtilityRegistration(, I1, u'', 4, None, u'use 4 now') True A boolean is returned indicating whether anything changed: >>> components.queryUtility(tests.I1) >>> for registration in sorted(components.registeredUtilities()): ... print registration.provided, registration.name ... print registration.component, registration.info U12(2) three U12(3) When you unregister, you can specify a component. If the component doesn't match the one registered, then nothing happens: >>> u5 = tests.U1(5) >>> components.registerUtility(u5) Registered event: UtilityRegistration(, I1, u'', 5, None, u'') >>> components.unregisterUtility(tests.U1(6)) False >>> components.queryUtility(tests.I1) U1(5) >>> components.unregisterUtility(u5) Unregistered event: UtilityRegistration(, I1, u'', 5, None, u'') True >>> components.queryUtility(tests.I1) You can get the name and utility for all of the utilities that provide an interface using getUtilitiesFor: >>> sorted(components.getUtilitiesFor(tests.I2)) [(u'', U12(2)), (u'three', U12(3))] getAllUtilitiesRegisteredFor is similar to getUtilitiesFor except that it includes utilities that are overridden. For example, we'll register a utility that for an extending interface of I2: >>> util = tests.U('ext') >>> components.registerUtility(util, tests.I2e) Registered event: UtilityRegistration(, I2e, u'', ext, None, u'') We don't get the new utility for getUtilitiesFor: >>> sorted(components.getUtilitiesFor(tests.I2)) [(u'', U12(2)), (u'three', U12(3))] but we do get it from getAllUtilitiesRegisteredFor: >>> sorted(map(str, components.getAllUtilitiesRegisteredFor(tests.I2))) ['U(ext)', 'U12(2)', 'U12(3)'] Removing a utility also makes it disappear from getUtilitiesFor: >>> components.unregisterUtility(util, tests.I2e) Unregistered event: UtilityRegistration(, I2e, u'', ext, None, u'') True >>> list(components.getAllUtilitiesRegisteredFor(tests.I2e)) [] Adapters -------- You can register adapters with registerAdapter: >>> components.registerAdapter(tests.A12_1) Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Here, we didn't specify required interfaces, a provided interface, or a name. The required interfaces were determined from the factory s __component_adapts__ attribute and the provided interface was determined by introspecting what the factory implements. >>> components.getMultiAdapter((tests.U1(6), tests.U12(7)), tests.IA1) A12_1(U1(6), U12(7)) If a factory implements more than one interface, an exception will be raised: >>> components.registerAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.registerAdapter(tests.A1_12, provided=tests.IA2) Registered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') If a factory doesn't declare an implemented interface, an exception will be raised: >>> components.registerAdapter(tests.A12_) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.registerAdapter(tests.A12_, provided=tests.IA2) Registered event: AdapterRegistration(, [I1, I2], IA2, u'', A12_, u'') The required interface needs to be specified in the registration if the factory doesn't have a __component_adapts__ attribute: >>> components.registerAdapter(tests.A_2) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified Unless the required specifications specified: >>> components.registerAdapter(tests.A_2, required=[tests.I3]) Registered event: AdapterRegistration(, [I3], IA2, u'', A_2, u'') Classes can be specified in place of specifications, in which case the implementedBy specification for the class is used: >>> components.registerAdapter(tests.A_3, required=[tests.U], ... info="Really class specific") ... # doctest: +NORMALIZE_WHITESPACE Registered event: AdapterRegistration(, [zope.component.tests.U], IA3, u'', A_3, 'Really class specific') We can see the adapters that have been registered using the registeredAdapters method: >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (, ) zope.component.tests.A12_1 (, ) zope.component.tests.A12_ (,) zope.component.tests.A1_12 (,) zope.component.tests.A_2 (,) zope.component.tests.A_3 Really class specific As with utilities, we can provide registration information when registering adapters. If you try to fetch an adapter that isn't registered, you'll get a component-lookup error: >>> components.getMultiAdapter((tests.U(8), ), tests.IA1) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: ((U(8),), , u'') unless you use queryAdapter: >>> components.queryMultiAdapter((tests.U(8), ), tests.IA1) >>> components.queryMultiAdapter((tests.U(8), ), tests.IA1, default=42) 42 When looking up an adapter for a single object, you can use the slightly simpler getAdapter and queryAdapter calls: >>> components.getAdapter(tests.U1(9), tests.IA2) A1_12(U1(9)) >>> components.queryAdapter(tests.U1(9), tests.IA2) A1_12(U1(9)) >>> components.getAdapter(tests.U(8), tests.IA1) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (U(8), , u'') >>> components.queryAdapter(tests.U(8), tests.IA2) >>> components.queryAdapter(tests.U(8), tests.IA2, default=42) 42 You can unregister an adapter. If a factory is provided and if the rewuired and provided interfaces, can be infered, then they need not be provided: >>> components.unregisterAdapter(tests.A12_1) Unregistered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') True >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (, ) zope.component.tests.A12_ (,) zope.component.tests.A1_12 (,) zope.component.tests.A_2 (,) zope.component.tests.A_3 Really class specific A boolean is returned indicating whether a change was made. If a factory implements more than one interface, an exception will be raised: >>> components.unregisterAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.unregisterAdapter(tests.A1_12, provided=tests.IA2) Unregistered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') True If a factory doesn't declare an implemented interface, an exception will be raised: >>> components.unregisterAdapter(tests.A12_) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.unregisterAdapter(tests.A12_, provided=tests.IA2) Unregistered event: AdapterRegistration(, [I1, I2], IA2, u'', A12_, u'') True The required interface needs to be specified if the factory doesn't have a __component_adapts__ attribute: >>> components.unregisterAdapter(tests.A_2) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified >>> components.unregisterAdapter(tests.A_2, required=[tests.I3]) Unregistered event: AdapterRegistration(, [I3], IA2, u'', A_2, u'') True >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) zope.component.tests.A_3 Really class specific If a factory is unregistered that is not registered, False is returned: >>> components.unregisterAdapter(tests.A_2, required=[tests.I3]) False >>> components.unregisterAdapter(tests.A12_1, required=[tests.U]) False The factory can be omitted, to unregister *any* factory that matches specified required and provided interfaces: >>> components.unregisterAdapter(required=[tests.U], provided=tests.IA3) ... # doctest: +NORMALIZE_WHITESPACE Unregistered event: AdapterRegistration(, [zope.component.tests.U], IA3, u'', A_3, 'Really class specific') True >>> for registration in sorted(components.registeredAdapters()): ... print registration Adapters can be named: >>> components.registerAdapter(tests.A1_12, provided=tests.IA2, ... name=u'test') Registered event: AdapterRegistration(, [I1], IA2, u'test', A1_12, u'') >>> components.queryMultiAdapter((tests.U1(9), ), tests.IA2) >>> components.queryMultiAdapter((tests.U1(9), ), tests.IA2, name=u'test') A1_12(U1(9)) >>> components.queryAdapter(tests.U1(9), tests.IA2) >>> components.queryAdapter(tests.U1(9), tests.IA2, name=u'test') A1_12(U1(9)) >>> components.getAdapter(tests.U1(9), tests.IA2, name=u'test') A1_12(U1(9)) It is possible to look up all of the adapters that provide an interface: >>> components.registerAdapter(tests.A1_23, provided=tests.IA2, ... name=u'test 2') Registered event: AdapterRegistration(, [I1], IA2, u'test 2', A1_23, u'') >>> components.registerAdapter(tests.A1_12, provided=tests.IA2) Registered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') >>> for name, adapter in sorted(components.getAdapters((tests.U1(9), ), ... tests.IA2)): ... print name, adapter A1_12(U1(9)) test A1_12(U1(9)) test 2 A1_23(U1(9)) getAdapters is most commonly used as the basis of menu systems. If an adapter factory returns None, it is equivalent to there being no factory: >>> components.registerAdapter(tests.noop, ... required=[tests.IA1], provided=tests.IA2, ... name=u'test noop') ... # doctest: +NORMALIZE_WHITESPACE Registered event: AdapterRegistration(, [IA1], IA2, u'test noop', noop, u'') >>> components.queryAdapter(tests.U1(9), tests.IA2, name=u'test noop') >>> components.registerAdapter(tests.A1_12, provided=tests.IA2) Registered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') >>> for name, adapter in sorted(components.getAdapters((tests.U1(9), ), ... tests.IA2)): ... print name, adapter A1_12(U1(9)) test A1_12(U1(9)) test 2 A1_23(U1(9)) >>> components.unregisterAdapter(tests.A1_12, provided=tests.IA2, ... name=u'test') Unregistered event: AdapterRegistration(, [I1], IA2, u'test', A1_12, u'') True >>> components.unregisterAdapter(tests.A1_12, provided=tests.IA2) Unregistered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') True >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) test 2 zope.component.tests.A1_23 (,) test noop Subscribers ----------- Subscribers provide a way to get multiple adapters of a given type. In this regard, subscribers are like named adapters, except that there isn't any concept of the most specific adapter for a given name. Subscribers are registered by calling registerSubscriptionAdapter: >>> components.registerSubscriptionAdapter(tests.A1_2) ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') >>> components.registerSubscriptionAdapter( ... tests.A1_12, provided=tests.IA2) ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_12, u'') >>> components.registerSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2, ... info='a sample comment') ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A, 'a sample comment') The same rules, with regard to when required and provided interfaces have to be specified apply as with adapters: >>> components.registerSubscriptionAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.registerSubscriptionAdapter(tests.A) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.registerSubscriptionAdapter(tests.A, required=[tests.IA1]) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Note that we provided the info argument as a keyword argument above. That's because there is a name argument that's reserved for future use. We can give a name, as long as it is an empty string: >>> components.registerSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2, u'', 'a sample comment') ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A, 'a sample comment') >>> components.registerSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2, u'oops', 'a sample comment') Traceback (most recent call last): ... TypeError: Named subscribers are not yet supported Subscribers are looked up using the subscribers method: >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_2(U1(1)) A1_12(U1(1)) A(U1(1),) A(U1(1),) Note that, because we created multiple subscriptions for A, we got multiple subscriber instances. As with normal adapters, if a factory returns None, the result is skipped: >>> components.registerSubscriptionAdapter( ... tests.noop, [tests.I1], tests.IA2) Registered event: SubscriptionRegistration(, [I1], IA2, u'', noop, u'') >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_2(U1(1)) A1_12(U1(1)) A(U1(1),) A(U1(1),) We can get registration information for subscriptions: >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info (,) zope.component.tests.A a sample comment (,) zope.component.tests.A a sample comment (,) zope.component.tests.A1_12 (,) zope.component.tests.A1_2 (,) We can also unregister subscriptions in much the same way we can for adapters: >>> components.unregisterSubscriptionAdapter(tests.A1_2) ... # doctest: +NORMALIZE_WHITESPACE Unregistered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, '') True >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_12(U1(1)) A(U1(1),) A(U1(1),) >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info (,) zope.component.tests.A a sample comment (,) zope.component.tests.A a sample comment (,) zope.component.tests.A1_12 (,) >>> components.unregisterSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2) Unregistered event: SubscriptionRegistration(, [I1], IA2, u'', A, '') True >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_12(U1(1)) >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info (,) zope.component.tests.A1_12 (,) Note here that both registrations for A were removed. If we omit the factory, we must specify the required and provided interfaces: >>> components.unregisterSubscriptionAdapter(required=[tests.I1]) Traceback (most recent call last): ... TypeError: Must specify one of factory and provided >>> components.unregisterSubscriptionAdapter(provided=tests.IA2) Traceback (most recent call last): ... TypeError: Must specify one of factory and required >>> components.unregisterSubscriptionAdapter( ... required=[tests.I1], provided=tests.IA2) Unregistered event: SubscriptionRegistration(, [I1], IA2, u'', None, '') True >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.factory As when registering, an error is raised if the registration information can't be determined from the factory and isn't specified: >>> components.unregisterSubscriptionAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.unregisterSubscriptionAdapter(tests.A) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.unregisterSubscriptionAdapter(tests.A, required=[tests.IA1]) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. If you unregister something that's not registered, nothing will be changed and False will be returned: >>> components.unregisterSubscriptionAdapter( ... required=[tests.I1], provided=tests.IA2) False Handlers -------- Handlers are used when you want to perform some function in response to an event. Handlers aren't expected to return anything when called and are not registered to provide any interface. >>> from zope import component >>> @component.adapter(tests.I1) ... def handle1(x): ... print 'handle1', x >>> components.registerHandler(handle1, info="First handler") ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [I1], u'', handle1, 'First handler') >>> components.handle(tests.U1(1)) handle1 U1(1) >>> @component.adapter(tests.I1, tests.I2) ... def handle12(x, y): ... print 'handle12', x, y >>> components.registerHandler(handle12) Registered event: HandlerRegistration(, [I1, I2], u'', handle12, u'') >>> components.handle(tests.U1(1), tests.U12(2)) handle12 U1(1) U12(2) If a handler doesn't document interfaces it handles, then the required interfaces must be specified: >>> def handle(*objects): ... print 'handle', objects >>> components.registerHandler(handle) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified >>> components.registerHandler(handle, required=[tests.I1], ... info="a comment") Registered event: HandlerRegistration(, [I1], u'', handle, 'a comment') Handlers can also be registered for classes: >>> components.registerHandler(handle, required=[tests.U], ... info="handle a class") ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [zope.component.tests.U], u'', handle, 'handle a class') >>> components.handle(tests.U1(1)) handle (U1(1),) handle1 U1(1) handle (U1(1),) We can list the handler registrations: >>> for registration in components.registeredHandlers(): ... print registration.required ... print registration.handler, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) First handler (, ) (,) a comment (,) handle a class and we can unregister handlers: >>> components.unregisterHandler(required=[tests.U]) ... # doctest: +NORMALIZE_WHITESPACE Unregistered event: HandlerRegistration(, [zope.component.tests.U], u'', None, '') True >>> for registration in components.registeredHandlers(): ... print registration.required ... print registration.handler, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) First handler (, ) (,) a comment >>> components.unregisterHandler(handle12) Unregistered event: HandlerRegistration(, [I1, I2], u'', handle12, '') True >>> for registration in components.registeredHandlers(): ... print registration.required ... print registration.handler, registration.info (,) First handler (,) a comment >>> components.unregisterHandler(handle12) False >>> components.unregisterHandler() Traceback (most recent call last): ... TypeError: Must specify one of factory and required >>> components.registerHandler(handle) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified Extending --------- Component-management objects can extend other component-management objects. >>> c1 = registry.Components('1') >>> c1.__bases__ () >>> c2 = registry.Components('2', (c1, )) >>> c2.__bases__ == (c1, ) True >>> c1.registerUtility(tests.U1(1)) Registered event: UtilityRegistration(, I1, u'', 1, None, u'') >>> c1.queryUtility(tests.I1) U1(1) >>> c2.queryUtility(tests.I1) U1(1) >>> c1.registerUtility(tests.U1(2)) Unregistered event: UtilityRegistration(, I1, u'', 1, None, u'') Registered event: UtilityRegistration(, I1, u'', 2, None, u'') >>> c2.queryUtility(tests.I1) U1(2) We can use multiple inheritence: >>> c3 = registry.Components('3', (c1, )) >>> c4 = registry.Components('4', (c2, c3)) >>> c4.queryUtility(tests.I1) U1(2) >>> c1.registerUtility(tests.U12(1), tests.I2) Registered event: UtilityRegistration(, I2, u'', 1, None, u'') >>> c4.queryUtility(tests.I2) U12(1) >>> c3.registerUtility(tests.U12(3), tests.I2) Registered event: UtilityRegistration(, I2, u'', 3, None, u'') >>> c4.queryUtility(tests.I2) U12(3) >>> c1.registerHandler(handle1, info="First handler") Registered event: HandlerRegistration(, [I1], u'', handle1, 'First handler') >>> c2.registerHandler(handle, required=[tests.U]) ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [zope.component.tests.U], u'', handle, u'') >>> @component.adapter(tests.I1) ... def handle3(x): ... print 'handle3', x >>> c3.registerHandler(handle3) Registered event: HandlerRegistration(, [I1], u'', handle3, u'') >>> @component.adapter(tests.I1) ... def handle4(x): ... print 'handle4', x >>> c4.registerHandler(handle4) Registered event: HandlerRegistration(, [I1], u'', handle4, u'') >>> c4.handle(tests.U1(1)) handle1 U1(1) handle3 U1(1) handle (U1(1),) handle4 U1(1) Redispatch of registration events --------------------------------- Some handlers are available that, if registered, redispatch registration events to the objects being registered. They depend on being dispatched to by the object-event dispatcher: >>> from zope import component >>> import zope.component.event >>> zope.component.getGlobalSiteManager().registerHandler( ... zope.component.event.objectEventNotify) ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [IObjectEvent], u'', objectEventNotify, u'') To see this, we'll first register a multi-handler to show is when handlers are called on 2 objects: >>> @zope.component.adapter(None, None) ... def double_handler(o1, o2): ... print 'Double dispatch:' ... print ' ', o1 ... print ' ', o2 >>> zope.component.getGlobalSiteManager().registerHandler(double_handler) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: HandlerRegistration(, [Interface, Interface], u'', double_handler, u'') Registered event: HandlerRegistration(, [Interface, Interface], u'', double_handler, u'') Registered event: HandlerRegistration(, [Interface, Interface], u'', double_handler, u'') In the example above, the double_handler reported it's own registration. :) Now we'll register our handlers: >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchUtilityRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Double dispatch: ... >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchAdapterRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Double dispatch: ... >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchSubscriptionAdapterRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Double dispatch: ... >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchHandlerRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') Registered event: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') Double dispatch: Registered event: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') Registered event: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') In the last example above, we can see that the registration of dispatchHandlerRegistrationEvent was handled by dispatchHandlerRegistrationEvent and redispatched. This can be seen in the second double-dispatch output, where the first argument is the object being registered, which is dispatchHandlerRegistrationEvent. If we change some other registrations, we can the double dispatch taking place: >>> components.registerUtility(u5) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: UtilityRegistration(, I1, u'', 5, None, u'') Registered event: UtilityRegistration(, I1, u'', 5, None, u'') Double dispatch: U1(5) Registered event: UtilityRegistration(, I1, u'', 5, None, u'') Registered event: UtilityRegistration(, I1, u'', 5, None, u'') >>> components.registerAdapter(tests.A12_1) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Double dispatch: zope.component.tests.A12_1 Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') >>> components.registerSubscriptionAdapter(tests.A1_2) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Double dispatch: zope.component.tests.A1_2 Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Persistent Component Management =============================== Persistent component management allows persistent management of components. From a usage point of view, there shouldn't be any new behavior beyond what's described in registry.txt. The Zope 3 Component Architecture (Socket Example) ================================================== The component architecture provides an application framework that provides its functionality through loosely-connected components. A *component* can be any Python object and has a particular purpose associated with it. Thus, in a component-based applications you have many small component in contrast to classical object-oriented development, where you have a few big objects. Components communicate via specific APIs, which are formally defined by interfaces, which are provided by the `zope.interface` package. *Interfaces* describe the methods and properties that a component is expected to provide. They are also used as a primary mean to provide developer-level documentation for the components. For more details about interfaces see `zope/interface/README.txt`. The two main types of components are *adapters* and *utilities*. They will be discussed in detail later in this document. Both component types are managed by the *site manager*, with which you can register and access these components. However, most of the site manager's functionality is hidden behind the component architecture's public API, which is documented in `IComponentArchitecture`. Adapters -------- Adapters are a well-established pattern. An *adapter* uses an object providing one interface to produce an object that provides another interface. Here an example: Imagine that you purchased an electric shaver in the US, and thus you require the US socket type. You are now traveling in Germany, where another socket style is used. You will need a device, an adapter, that converts from the German to the US socket style. The functionality of adapters is actually natively provided by the `zope.interface` package and is thus well documented there. The `human.txt` file provides a gentle introduction to adapters, whereby `adapter.txt` is aimed at providing a comprehensive insight into adapters, but is too abstract for many as an initial read. Thus, we will only explain adapters in the context of the component architecture's API. So let's say that we have a German socket >>> from zope.interface import Interface, implements >>> class IGermanSocket(Interface): ... pass >>> class Socket(object): ... def __repr__(self): ... return '' %self.__class__.__name__ >>> class GermanSocket(Socket): ... """German wall socket.""" ... implements(IGermanSocket) and we want to convert it to an US socket >>> class IUSSocket(Interface): ... pass so that our shaver can be used in Germany. So we go to a German electronics store to look for an adapter that we can plug in the wall: >>> class GermanToUSSocketAdapter(Socket): ... implements(IUSSocket) ... __used_for__ = IGermanSocket ... ... def __init__(self, socket): ... self.context = socket Note that I could have called the passed in socket any way I like, but `context` is the standard name accepted. Single Adapters ~~~~~~~~~~~~~~~ Before we can use the adapter, we have to buy it and make it part of our inventory. In the component architecture we do this by registering the adapter with the framework, more specifically with the global site manager: >>> import zope.component >>> gsm = zope.component.getGlobalSiteManager() >>> gsm.registerAdapter(GermanToUSSocketAdapter, (IGermanSocket,), IUSSocket) `zope.component` is the component architecture API that is being presented by this file. You registered an adapter from `IGermanSocket` to `IUSSocket` having no name (thus the empty string). Anyways, you finally get back to your hotel room and shave, since you have not been able to shave in the plane. In the bathroom you discover a socket: >>> bathroomDE = GermanSocket() >>> IGermanSocket.providedBy(bathroomDE) True You now insert the adapter in the German socket >>> bathroomUS = zope.component.getAdapter(bathroomDE, IUSSocket, '') so that the socket now provides the US version: >>> IUSSocket.providedBy(bathroomUS) True Now you can insert your shaver and get on with your day. After a week you travel for a couple of days to the Prague and you notice that the Czech have yet another socket type: >>> class ICzechSocket(Interface): ... pass >>> class CzechSocket(Socket): ... implements(ICzechSocket) >>> czech = CzechSocket() You try to find an adapter for your shaver in your bag, but you fail, since you do not have one: >>> zope.component.getAdapter(czech, IUSSocket, '') \ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, , '') or the more graceful way: >>> marker = object() >>> socket = zope.component.queryAdapter(czech, IUSSocket, '', marker) >>> socket is marker True In the component architecture API any `get*` method will fail with a specific exception, if a query failed, whereby methods starting with `query*` will always return a `default` value after a failure. Named Adapters ~~~~~~~~~~~~~~ You are finally back in Germany. You also brought your DVD player and a couple DVDs with you, which you would like to watch. Your shaver was able to convert automatically from 110 volts to 240 volts, but your DVD player cannot. So you have to buy another adapter that also handles converting the voltage and the frequency of the AC current: >>> class GermanToUSSocketAdapterAndTransformer(object): ... implements(IUSSocket) ... __used_for__ = IGermanSocket ... ... def __init__(self, socket): ... self.context = socket Now, we need a way to keep the two adapters apart. Thus we register them with a name: >>> gsm.registerAdapter(GermanToUSSocketAdapter, ... (IGermanSocket,), IUSSocket, 'shaver',) >>> gsm.registerAdapter(GermanToUSSocketAdapterAndTransformer, ... (IGermanSocket,), IUSSocket, 'dvd') Now we simply look up the adapters using their labels (called *name*): >>> socket = zope.component.getAdapter(bathroomDE, IUSSocket, 'shaver') >>> socket.__class__ is GermanToUSSocketAdapter True >>> socket = zope.component.getAdapter(bathroomDE, IUSSocket, 'dvd') >>> socket.__class__ is GermanToUSSocketAdapterAndTransformer True Clearly, we do not have an adapter for the MP3 player >>> zope.component.getAdapter(bathroomDE, IUSSocket, 'mp3') \ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, , 'mp3') but you could use the 'dvd' adapter in this case of course. ;) Sometimes you want to know all adapters that are available. Let's say you want to know about all the adapters that convert a German to a US socket type: >>> sockets = list(zope.component.getAdapters((bathroomDE,), IUSSocket)) >>> len(sockets) 3 >>> names = [name for name, socket in sockets] >>> names.sort() >>> names [u'', u'dvd', u'shaver'] `zope.component.getAdapters()` returns a list of tuples. The first entry of the tuple is the name of the adapter and the second is the adapter itself. Multi-Adapters ~~~~~~~~~~~~~~ After watching all the DVDs you brought at least twice, you get tired of them and you want to listen to some music using your MP3 player. But darn, the MP3 player plug has a ground pin and all the adapters you have do not support that: >>> class IUSGroundedSocket(IUSSocket): ... pass So you go out another time to buy an adapter. This time, however, you do not buy yet another adapter, but a piece that provides the grounding plug: >>> class IGrounder(Interface): ... pass >>> class Grounder(object): ... implements(IGrounder) ... def __repr__(self): ... return '' Then together they will provided a grounded us socket: >>> class GroundedGermanToUSSocketAdapter(object): ... implements(IUSGroundedSocket) ... __used_for__ = (IGermanSocket, IGrounder) ... def __init__(self, socket, grounder): ... self.socket, self.grounder = socket, grounder You now register the combination, so that you know you can create a `IUSGroundedSocket`: >>> gsm.registerAdapter(GroundedGermanToUSSocketAdapter, ... (IGermanSocket, IGrounder), IUSGroundedSocket, 'mp3') Given the grounder >>> grounder = Grounder() and a German socket >>> livingroom = GermanSocket() we can now get a grounded US socket: >>> socket = zope.component.getMultiAdapter((livingroom, grounder), ... IUSGroundedSocket, 'mp3') >>> socket.__class__ is GroundedGermanToUSSocketAdapter True >>> socket.socket is livingroom True >>> socket.grounder is grounder True Of course, you do not have a 'dvd' grounded US socket available: >>> zope.component.getMultiAdapter((livingroom, grounder), ... IUSGroundedSocket, 'dvd') \ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: ((, ), , 'dvd') >>> socket = zope.component.queryMultiAdapter( ... (livingroom, grounder), IUSGroundedSocket, 'dvd', marker) >>> socket is marker True Again, you might want to read `adapter.txt` in `zope.interface` for a more comprehensive coverage of multi-adapters. Subscribers ----------- While subscribers are directly supported by the adapter registry and are adapters for all theoretical purposes, practically it might be better to think of them as separate components. Subscribers are particularly useful for events. Let's say one of our adapters overheated and caused a small fire: >>> class IFire(Interface): ... pass >>> class Fire(object): ... implements(IFire) >>> fire = Fire() We want to use all available objects to put out the fire: >>> class IFireExtinguisher(Interface): ... def extinguish(): ... pass >>> class FireExtinguisher(object): ... def __init__(self, fire): ... pass ... def extinguish(self): ... "Place extinguish code here." ... print 'Used ' + self.__class__.__name__ + '.' Here some specific methods to put out the fire: >>> class PowderExtinguisher(FireExtinguisher): ... pass >>> gsm.registerSubscriptionAdapter(PowderExtinguisher, ... (IFire,), IFireExtinguisher) >>> class Blanket(FireExtinguisher): ... pass >>> gsm.registerSubscriptionAdapter(Blanket, (IFire,), IFireExtinguisher) >>> class SprinklerSystem(FireExtinguisher): ... pass >>> gsm.registerSubscriptionAdapter(SprinklerSystem, ... (IFire,), IFireExtinguisher) Now let use all these things to put out the fire: >>> extinguishers = zope.component.subscribers((fire,), IFireExtinguisher) >>> extinguishers.sort() >>> for extinguisher in extinguishers: ... extinguisher.extinguish() Used Blanket. Used PowderExtinguisher. Used SprinklerSystem. If no subscribers are found for a particular object, then an empty list is returned: >>> zope.component.subscribers((object(),), IFireExtinguisher) [] Utilities --------- Utilities are the second type of component, the component architecture implements. *Utilities* are simply components that provide an interface. When you register an utility, you always register an instance (in contrast to a factory for adapters) since the initialization and setup process of a utility might be complex and is not well defined. In some ways a utility is much more fundamental than an adapter, because an adapter cannot be used without another component, but a utility is always self-contained. I like to think of utilities as the foundation of your application and adapters as components extending beyond this foundation. Back to our story... After your vacation is over you fly back home to Tampa, Florida. But it is August now, the middle of the Hurricane season. And, believe it or not, you are worried that you will not be able to shave when the power goes out for several days. (You just hate wet shavers.) So you decide to go to your favorite hardware store and by a Diesel-powered electric generator. The generator provides of course a US-style socket: >>> class Generator(object): ... implements(IUSSocket) ... def __repr__(self): ... return '' >>> generator = Generator() Like for adapters, we now have to add the newly-acquired generator to our inventory by registering it as a utility: >>> gsm.registerUtility(generator, IUSSocket) We can now get the utility using >>> utility = zope.component.getUtility(IUSSocket) >>> utility is generator True As you can see, it is very simple to register and retrieve utilities. If a utility does not exist for a particular interface, such as the German socket, then the lookup fails >>> zope.component.getUtility(IGermanSocket) Traceback (most recent call last): ... ComponentLookupError: (, '') or more gracefully when specifying a default value: >>> default = object() >>> utility = zope.component.queryUtility(IGermanSocket, default=default) >>> utility is default True Note: The only difference between `getUtility()` and `queryUtility()` is the fact that you can specify a default value for the latter function, so that it will never cause a `ComponentLookupError`. Named Utilities ~~~~~~~~~~~~~~~ It is often desirable to have several utilities providing the same interface per site. This way you can implement any sort of registry using utilities. For this reason, utilities -- like adapters -- can be named. In the context of our story, we might want to do the following: You really do not trust gas stations either. What if the roads are blocked after a hurricane and the gas stations run out of oil. So you look for another renewable power source. Then you think about solar panels! After a storm there is usually very nice weather, so why not? Via the Web you order a set of 110V/120W solar panels that provide a regular US-style socket as output: >>> class SolarPanel(object): ... implements(IUSSocket) ... def __repr__(self): ... return '' >>> panel = SolarPanel() Once it arrives, we add it to our inventory: >>> gsm.registerUtility(panel, IUSSocket, 'Solar Panel') You can now access the solar panel using >>> utility = zope.component.getUtility(IUSSocket, 'Solar Panel') >>> utility is panel True Of course, if a utility is not available, then the lookup will simply fail >>> zope.component.getUtility(IUSSocket, 'Wind Mill') Traceback (most recent call last): ... ComponentLookupError: (, 'Wind Mill') or more gracefully when specifying a default value: >>> default = object() >>> utility = zope.component.queryUtility(IUSSocket, 'Wind Mill', ... default=default) >>> utility is default True Now you want to look at all the utilities you have for a particular kind. The following API function will return a list of name/utility pairs: >>> utils = list(zope.component.getUtilitiesFor(IUSSocket)) >>> utils.sort() >>> utils #doctest: +NORMALIZE_WHITESPACE [(u'', ), (u'Solar Panel', )] Another method of looking up all utilities is by using `getAllUtilitiesRegisteredFor(iface)`. This function will return an iterable of utilities (without names); however, it will also return overridden utilities. If you are not using multiple site managers, you will not actually need this method. >>> utils = list(zope.component.getAllUtilitiesRegisteredFor(IUSSocket)) >>> utils.sort() >>> utils [, ] Factories ~~~~~~~~~ A *factory* is a special kind of utility that exists to create other components. A factory is always identified by a name. It also provides a title and description and is able to tell the developer what interfaces the created object will provide. The advantage of using a factory to create an object instead of directly instantiating a class or executing any other callable is that we can refer to the factory by name. As long as the name stays fixed, the implementation of the callable can be renamed or moved without a breakage in code. Let's say that our solar panel comes in parts and they have to be assembled. This assembly would be done by a factory, so let's create one for the solar panel. To do this, we can use a standard implementation of the `IFactory` interface: >>> from zope.component.factory import Factory >>> factory = Factory(SolarPanel, ... 'Solar Panel', ... 'This factory creates a solar panel.') Optionally, I could have also specified the interfaces that the created object will provide, but the factory class is smart enough to determine the implemented interface from the class. We now register the factory: >>> from zope.component.interfaces import IFactory >>> gsm.registerUtility(factory, IFactory, 'SolarPanel') We can now get a list of interfaces the produced object will provide: >>> ifaces = zope.component.getFactoryInterfaces('SolarPanel') >>> IUSSocket in ifaces True By the way, this is equivalent to >>> ifaces2 = factory.getInterfaces() >>> ifaces is ifaces2 True Of course you can also just create an object: >>> panel = zope.component.createObject('SolarPanel') >>> panel.__class__ is SolarPanel True Note: Ignore the first argument (`None`) for now; it is the context of the utility lookup, which is usually an optional argument, but cannot be in this case, since all other arguments beside the `name` are passed in as arguments to the specified callable. Once you register several factories >>> gsm.registerUtility(Factory(Generator), IFactory, 'Generator') you can also determine, which available factories will create objects providing a certain interface: >>> factories = zope.component.getFactoriesFor(IUSSocket) >>> factories = [(name, factory.__class__) for name, factory in factories] >>> factories.sort() >>> factories #doctest: +NORMALIZE_WHITESPACE [(u'Generator', ), (u'SolarPanel', )] Site Managers ------------- Why do we need site managers? Why is the component architecture API not sufficient? Some applications, including Zope 3, have a concept of locations. It is often desirable to have different configurations for these location; this can be done by overwriting existing or adding new component registrations. Site managers in locations below the root location, should be able to delegate requests to their parent locations. The root site manager is commonly known as *global site manager*, since it is always available. You can always get the global site manager using the API: >>> gsm = zope.component.getGlobalSiteManager() >>> from zope.component import globalSiteManager >>> gsm is globalSiteManager True >>> from zope.component.interfaces import IComponentLookup >>> IComponentLookup.providedBy(gsm) True >>> from zope.component.interfaces import IComponents >>> IComponents.providedBy(gsm) True You can also lookup at site manager in a given context. The only requirement is that the context can be adapted to a site manager. So let's create a special site manager: >>> from zope.component.globalregistry import BaseGlobalComponents >>> sm = BaseGlobalComponents() Now we create a context that adapts to the site manager via the `__conform__` method as specified in PEP 246. >>> class Context(object): ... def __init__(self, sm): ... self.sm = sm ... def __conform__(self, interface): ... if interface.isOrExtends(IComponentLookup): ... return self.sm We now instantiate the `Context` with our special site manager: >>> context = Context(sm) >>> context.sm is sm True We can now ask for the site manager of this context: >>> lsm = zope.component.getSiteManager(context) >>> lsm is sm True The site manager instance `lsm` is formally known as a *local site manager* of `context`. CHANGES ******* 3.9.5 (2010-07-09) ================== - Fix test requirements specification. 3.9.4 (2010-04-30) ================== - Prefer the standard libraries doctest module to the one from zope.testing. 3.9.3 (2010-03-08) ================== - The ZCML directives provided by zope.component now register the components in the registry returned by getSiteManager instead of the global registry. This allows the hooking of the getSiteManager method before the load of a ZCML file to register the components in a custom registry. 3.9.2 (2010-01-22) ================== - Fixed a bug introduced by recent refactoring, where passing CheckerPublic to securityAdapterFactory wrongly wrapped the factory into a LocatingUntrustedAdapterFactory. 3.9.1 (2010-01-21) ================== - The tested testrunner somehow gets influenced by options of the outer testrunner, such a the -v option. We modified the tests so that it avoids this. 3.9.0 (2010-01-21) ================== - Add testlayer support. It is now possible to load a ZCML file within tests more easily. See zope.component.testlayer.py and zope.component.testlayer.txt. 3.8.0 (2009-11-16) ================== - Removed the dependencies on zope.proxy and zope.security from the zcml extra: zope.component does not hard depend on them anymore; the support for security proxied components ZCML registrations is enabled only if zope.security and zope.proxy are available. - Moved the IPossibleSite and ISite interfaces here from zope.location as they are dealing with zope.component's concept of a site, but not with location. - Moved the zope.site.hooks functionality to zope.component.hooks as it isn't actually dealing with zope.site's concept of a site. 3.7.1 (2009-07-24) ================== - Fixed a problem, where ``queryNextUtility`` could fail if the context could not be adapted to a ``IComponentLookup``. - Fixed 2 related bugs: When a utility is registered and there was previously a utility registered for the same interface and name, then the old utility is unregistered. The 2 bugs related to this: - There was no ``Unregistered`` for the implicit unregistration. Now there is. - The old utility was still held and returned by getAllUtilitiesRegisteredFor. In other words, it was still considered registered, eeven though it wasn't. A particularly negative consequence of this is that the utility is held in memory or in the database even though it isn't used. 3.7.0 (2009-05-21) ================== - The HookableTests were not run by the testrunner. - Add in zope:view and zope:resource implementations into zope.component.zcml (dependency loaded with zope.component [zcml]). 3.6.0 (2009-03-12) ================== - IMPORTANT: the interfaces that were defined in the zope.component.bbb.interfaces and deprecated for years are now (re)moved. However, some packages, including part of zope framework were still using those interfaces. They will be adapted for this change. If you were using some of those interfaces, you need to adapt your code as well: - The IView and IDefaultViewName were moved to zope.publisher.interfaces. - The IResource was moved to zope.app.publisher.interfaces. - IContextDependent, IPresentation, IPresentationRequest, IResourceFactory, IViewFactory were removed completely. If you used IViewFactory in context of zope.app.form, there's now IWidgetFactory in the zope.app.form.interfaces instead. - Add getNextUtility/queryNextUtility functions that used to be in zope.site earlier (and in zope.app.component even more earlier). - Added a pure-Python 'hookable' implementation, for use when 'zope.hookable' is not present. - Removed use of 'zope.deferredimport' by breaking import cycles. - Cleanup package documentation and changelog a bit. Add sphinx-based documentation building command to the buildout. - Remove deprecated code. - Change package's mailing list address to zope-dev at zope.org, because zope3-dev at zope.org is now retired. 3.5.1 (2008-07-25) ================== - Fix bug introduced in 3.5.0: no longer supported interfaces declared in Python and always wanted an explicit provides="..." attribute. https://bugs.launchpad.net/zope3/+bug/251865 3.5.0 (2008-07-25) ================== - Support registration of utilities via factories through the component registry and return factory information in the registration information. This fixes https://bugs.launchpad.net/zope3/+bug/240631 - Optimized un/registerUtility via storing an optimized data structure for efficient retrieval of already registered utilities. This avoids looping over all utilities when registering a new one. 3.4.0 (2007-09-29) ================== No further changes since 3.4.0a1. 3.4.0a1 (2007-04-22) ==================== Corresponds to zope.component from Zope 3.4.0a1. - In the Zope 3.3.x series, ``zope.component`` was simplified yet once more. See http://wiki.zope.org/zope3/LocalComponentManagementSimplification for the proposal describing the changes. 3.2.0.2 (2006-04-15) ==================== - Fix packaging bug: 'package_dir' must be a *relative* path. 3.2.0.1 (2006-04-14) ==================== - Packaging change: suppress inclusion of 'setup.cfg' in 'sdist' builds. 3.2.0 (2006-01-05) ================== Corresponds to the verison of the zope.component package shipped as part of the Zope 3.2.0 release. - Deprecated services and related APIs. The adapter and utility registries are now available directly via the site manager's 'adapters' and 'utilities' attributes, respectively. Services are accessible, but deprecated, and will be removed in Zope 3.3. - Deprectaed all presentation-related APIs, including all view-related API functions. Use the adapter API functions instead. See http://dev.zope.org/Zope3/ImplementViewsAsAdapters` - Deprecated 'contextdependent' package: site managers are now looked up via a thread global, set during URL traversal. The 'context' argument is now always optional, and should no longer be passed. 3.0.0 (2004-11-07) ================== Corresponds to the verison of the zope.component package shipped as part of the Zope X3.0.0 release. Download ******** Platform: UNKNOWN zope2.13-2.13.21/source/zope.component/pip-egg-info/zope.component.egg-info/dependency_links.txt0000644000175000017500000000000112214017426031413 0ustar arnauarnau zope2.13-2.13.21/source/zope.component/pip-egg-info/zope.component.egg-info/requires.txt0000644000175000017500000000036112214017426027745 0ustar arnauarnausetuptools zope.interface zope.event [test] ZODB3 zope.hookable zope.location zope.proxy zope.security zope.testing [docs] z3c.recipe.sphinxdoc [zcml] zope.configuration zope.i18nmessageid [persistentregistry] ZODB3 [hook] zope.hookablezope2.13-2.13.21/source/zope.component/pip-egg-info/zope.component.egg-info/namespace_packages.txt0000644000175000017500000000000512214017426031673 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.component/pip-egg-info/zope.component.egg-info/top_level.txt0000644000175000017500000000000512214017426030072 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.component/pip-egg-info/zope.component.egg-info/SOURCES.txt0000644000175000017500000000226612214017426027237 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.component.egg-info/PKG-INFO pip-egg-info/zope.component.egg-info/SOURCES.txt pip-egg-info/zope.component.egg-info/dependency_links.txt pip-egg-info/zope.component.egg-info/namespace_packages.txt pip-egg-info/zope.component.egg-info/not-zip-safe pip-egg-info/zope.component.egg-info/requires.txt pip-egg-info/zope.component.egg-info/top_level.txt src/zope/__init__.py src/zope/component/__init__.py src/zope/component/_api.py src/zope/component/_declaration.py src/zope/component/event.py src/zope/component/eventtesting.py src/zope/component/factory.py src/zope/component/globalregistry.py src/zope/component/hookable.py src/zope/component/hooks.py src/zope/component/interface.py src/zope/component/interfaces.py src/zope/component/nexttesting.py src/zope/component/persistentregistry.py src/zope/component/registry.py src/zope/component/security.py src/zope/component/standalonetests.py src/zope/component/testing.py src/zope/component/testlayer.py src/zope/component/tests.py src/zope/component/zcml.py src/zope/component/testfiles/__init__.py src/zope/component/testfiles/adapter.py src/zope/component/testfiles/components.py src/zope/component/testfiles/views.pyzope2.13-2.13.21/source/zope.component/pip-egg-info/zope.component.egg-info/not-zip-safe0000644000175000017500000000000112214017426027573 0ustar arnauarnau zope2.13-2.13.21/source/zope.component/LICENSE.txt0000644000175000017500000000402612214017426020241 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.component/README.txt0000644000175000017500000000071412214017426020114 0ustar arnauarnau***************************** zope.component Package Readme ***************************** *This package is intended to be independently reusable in any Python project. It is maintained by the* `Zope Toolkit project `_. This package represents the core of the Zope Component Architecture. Together with the 'zope.interface' package, it provides facilities for defining, registering and looking up components. .. contents:: zope2.13-2.13.21/source/zope.component/setup.cfg0000644000175000017500000000007312214017426020235 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.component/COPYRIGHT.txt0000644000175000017500000000004012214017426020517 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.component/buildout.cfg0000644000175000017500000000132712214017426020727 0ustar arnauarnau[buildout] develop = . parts = test test_c_hookable python docs coverage-test coverage-report unzip = true [test] recipe = zc.recipe.testrunner eggs = zope.component [test,zcml] [coverage-test] recipe = zc.recipe.testrunner eggs = ${test:eggs} defaults = ['--coverage', '../../coverage'] [coverage-report] recipe = zc.recipe.egg eggs = z3c.coverage scripts = coverage=coverage-report arguments = ('coverage', 'coverage/report') [test_c_hookable] recipe = zc.recipe.testrunner eggs = zope.component [test,zcml,hook] [python] recipe = zc.recipe.egg interpreter = python eggs = ${test:eggs} [docs] recipe = z3c.recipe.sphinxdoc eggs = zope.component [docs] build-dir = ${buildout:directory}/docs default.css = layout.html = zope2.13-2.13.21/source/zope.component/bootstrap.py0000644000175000017500000000733012214017426021006 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.component/CHANGES.txt0000644000175000017500000001422212214017426020226 0ustar arnauarnauCHANGES ******* 3.9.5 (2010-07-09) ================== - Fix test requirements specification. 3.9.4 (2010-04-30) ================== - Prefer the standard libraries doctest module to the one from zope.testing. 3.9.3 (2010-03-08) ================== - The ZCML directives provided by zope.component now register the components in the registry returned by getSiteManager instead of the global registry. This allows the hooking of the getSiteManager method before the load of a ZCML file to register the components in a custom registry. 3.9.2 (2010-01-22) ================== - Fixed a bug introduced by recent refactoring, where passing CheckerPublic to securityAdapterFactory wrongly wrapped the factory into a LocatingUntrustedAdapterFactory. 3.9.1 (2010-01-21) ================== - The tested testrunner somehow gets influenced by options of the outer testrunner, such a the -v option. We modified the tests so that it avoids this. 3.9.0 (2010-01-21) ================== - Add testlayer support. It is now possible to load a ZCML file within tests more easily. See zope.component.testlayer.py and zope.component.testlayer.txt. 3.8.0 (2009-11-16) ================== - Removed the dependencies on zope.proxy and zope.security from the zcml extra: zope.component does not hard depend on them anymore; the support for security proxied components ZCML registrations is enabled only if zope.security and zope.proxy are available. - Moved the IPossibleSite and ISite interfaces here from zope.location as they are dealing with zope.component's concept of a site, but not with location. - Moved the zope.site.hooks functionality to zope.component.hooks as it isn't actually dealing with zope.site's concept of a site. 3.7.1 (2009-07-24) ================== - Fixed a problem, where ``queryNextUtility`` could fail if the context could not be adapted to a ``IComponentLookup``. - Fixed 2 related bugs: When a utility is registered and there was previously a utility registered for the same interface and name, then the old utility is unregistered. The 2 bugs related to this: - There was no ``Unregistered`` for the implicit unregistration. Now there is. - The old utility was still held and returned by getAllUtilitiesRegisteredFor. In other words, it was still considered registered, eeven though it wasn't. A particularly negative consequence of this is that the utility is held in memory or in the database even though it isn't used. 3.7.0 (2009-05-21) ================== - The HookableTests were not run by the testrunner. - Add in zope:view and zope:resource implementations into zope.component.zcml (dependency loaded with zope.component [zcml]). 3.6.0 (2009-03-12) ================== - IMPORTANT: the interfaces that were defined in the zope.component.bbb.interfaces and deprecated for years are now (re)moved. However, some packages, including part of zope framework were still using those interfaces. They will be adapted for this change. If you were using some of those interfaces, you need to adapt your code as well: - The IView and IDefaultViewName were moved to zope.publisher.interfaces. - The IResource was moved to zope.app.publisher.interfaces. - IContextDependent, IPresentation, IPresentationRequest, IResourceFactory, IViewFactory were removed completely. If you used IViewFactory in context of zope.app.form, there's now IWidgetFactory in the zope.app.form.interfaces instead. - Add getNextUtility/queryNextUtility functions that used to be in zope.site earlier (and in zope.app.component even more earlier). - Added a pure-Python 'hookable' implementation, for use when 'zope.hookable' is not present. - Removed use of 'zope.deferredimport' by breaking import cycles. - Cleanup package documentation and changelog a bit. Add sphinx-based documentation building command to the buildout. - Remove deprecated code. - Change package's mailing list address to zope-dev at zope.org, because zope3-dev at zope.org is now retired. 3.5.1 (2008-07-25) ================== - Fix bug introduced in 3.5.0: no longer supported interfaces declared in Python and always wanted an explicit provides="..." attribute. https://bugs.launchpad.net/zope3/+bug/251865 3.5.0 (2008-07-25) ================== - Support registration of utilities via factories through the component registry and return factory information in the registration information. This fixes https://bugs.launchpad.net/zope3/+bug/240631 - Optimized un/registerUtility via storing an optimized data structure for efficient retrieval of already registered utilities. This avoids looping over all utilities when registering a new one. 3.4.0 (2007-09-29) ================== No further changes since 3.4.0a1. 3.4.0a1 (2007-04-22) ==================== Corresponds to zope.component from Zope 3.4.0a1. - In the Zope 3.3.x series, ``zope.component`` was simplified yet once more. See http://wiki.zope.org/zope3/LocalComponentManagementSimplification for the proposal describing the changes. 3.2.0.2 (2006-04-15) ==================== - Fix packaging bug: 'package_dir' must be a *relative* path. 3.2.0.1 (2006-04-14) ==================== - Packaging change: suppress inclusion of 'setup.cfg' in 'sdist' builds. 3.2.0 (2006-01-05) ================== Corresponds to the verison of the zope.component package shipped as part of the Zope 3.2.0 release. - Deprecated services and related APIs. The adapter and utility registries are now available directly via the site manager's 'adapters' and 'utilities' attributes, respectively. Services are accessible, but deprecated, and will be removed in Zope 3.3. - Deprectaed all presentation-related APIs, including all view-related API functions. Use the adapter API functions instead. See http://dev.zope.org/Zope3/ImplementViewsAsAdapters` - Deprecated 'contextdependent' package: site managers are now looked up via a thread global, set during URL traversal. The 'context' argument is now always optional, and should no longer be passed. 3.0.0 (2004-11-07) ================== Corresponds to the verison of the zope.component package shipped as part of the Zope X3.0.0 release. zope2.13-2.13.21/source/zope.component/src/0000755000175000017500000000000012214017426017203 5ustar arnauarnauzope2.13-2.13.21/source/zope.component/src/zope/0000755000175000017500000000000012214017426020160 5ustar arnauarnauzope2.13-2.13.21/source/zope.component/src/zope/component/0000755000175000017500000000000012214017426022162 5ustar arnauarnauzope2.13-2.13.21/source/zope.component/src/zope/component/registry.txt0000644000175000017500000011570612214017426024605 0ustar arnauarnauComponent-Management objects ============================ Component-management objects provide a higher-level component-management API over the basic adapter-registration API provided by the zope.interface package. In particular, it provides: - utilities - support for computing adapters, rather than just looking up adapter factories. - management of registration comments The zope.component.registry.Components class provides an implementation of zope.component.interfaces.IComponents that provides these features. >>> from zope.component import registry >>> from zope.component import tests >>> components = registry.Components('comps') As components are registered, events are generated. Let's register an event subscriber, so we can see the events generated: >>> import zope.event >>> def logevent(event): ... print event >>> zope.event.subscribers.append(logevent) Utilities --------- You can register Utilities using registerUtility: >>> components.registerUtility(tests.U1(1)) Registered event: UtilityRegistration(, I1, u'', 1, None, u'') Here we didn't specify an interface or name. An unnamed utility was registered for interface I1, since that is only interface implemented by the U1 class: >>> components.getUtility(tests.I1) U1(1) You can also register a utility using a factory instead of a utility instance: >>> def factory(): ... return tests.U1(1) >>> components.registerUtility(factory=factory) Unregistered event: UtilityRegistration(, I1, u'', 1, None, u'') Registered event: UtilityRegistration(, I1, u'', 1, >, u'') If a component implements other than one interface or no interface, then an error will be raised: >>> components.registerUtility(tests.U12(2)) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The utility doesn't provide a single interface and no provided interface was specified. >>> components.registerUtility(tests.A) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The utility doesn't provide a single interface and no provided interface was specified. We can provide an interface if desired: >>> components.registerUtility(tests.U12(2), tests.I2) Registered event: UtilityRegistration(, I2, u'', 2, None, u'') and we can specify a name: >>> components.registerUtility(tests.U12(3), tests.I2, u'three') Registered event: UtilityRegistration(, I2, u'three', 3, None, u'') >>> components.getUtility(tests.I2) U12(2) >>> components.getUtility(tests.I2, 'three') U12(3) If you try to get a utility that doesn't exist, you'll get a component lookup error: >>> components.getUtility(tests.I3) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, u'') Unless you use queryUtility: >>> components.queryUtility(tests.I3) >>> components.queryUtility(tests.I3, default=42) 42 You can get information about registered utilities with the registeredUtilities method: >>> for registration in sorted(components.registeredUtilities()): ... print registration.provided, registration.name ... print registration.component, registration.info U1(1) U12(2) three U12(3) Duplicate registrations replace existing ones: >>> components.registerUtility(tests.U1(4), info=u'use 4 now') Unregistered event: UtilityRegistration(, I1, u'', 1, >, u'') Registered event: UtilityRegistration(, I1, u'', 4, None, u'use 4 now') >>> components.getUtility(tests.I1) U1(4) >>> for registration in sorted(components.registeredUtilities()): ... print registration.provided, registration.name ... print registration.component, registration.info U1(4) use 4 now U12(2) three U12(3) As shown in the this example, you can provide an "info" argumemnt when registering utilities. This provides extra documentation about the registration itself that is shown when listing registrations. You can also unregister utilities: >>> components.unregisterUtility(provided=tests.I1) Unregistered event: UtilityRegistration(, I1, u'', 4, None, u'use 4 now') True A boolean is returned indicating whether anything changed: >>> components.queryUtility(tests.I1) >>> for registration in sorted(components.registeredUtilities()): ... print registration.provided, registration.name ... print registration.component, registration.info U12(2) three U12(3) When you unregister, you can specify a component. If the component doesn't match the one registered, then nothing happens: >>> u5 = tests.U1(5) >>> components.registerUtility(u5) Registered event: UtilityRegistration(, I1, u'', 5, None, u'') >>> components.unregisterUtility(tests.U1(6)) False >>> components.queryUtility(tests.I1) U1(5) >>> components.unregisterUtility(u5) Unregistered event: UtilityRegistration(, I1, u'', 5, None, u'') True >>> components.queryUtility(tests.I1) You can get the name and utility for all of the utilities that provide an interface using getUtilitiesFor: >>> sorted(components.getUtilitiesFor(tests.I2)) [(u'', U12(2)), (u'three', U12(3))] getAllUtilitiesRegisteredFor is similar to getUtilitiesFor except that it includes utilities that are overridden. For example, we'll register a utility that for an extending interface of I2: >>> util = tests.U('ext') >>> components.registerUtility(util, tests.I2e) Registered event: UtilityRegistration(, I2e, u'', ext, None, u'') We don't get the new utility for getUtilitiesFor: >>> sorted(components.getUtilitiesFor(tests.I2)) [(u'', U12(2)), (u'three', U12(3))] but we do get it from getAllUtilitiesRegisteredFor: >>> sorted(map(str, components.getAllUtilitiesRegisteredFor(tests.I2))) ['U(ext)', 'U12(2)', 'U12(3)'] Removing a utility also makes it disappear from getUtilitiesFor: >>> components.unregisterUtility(util, tests.I2e) Unregistered event: UtilityRegistration(, I2e, u'', ext, None, u'') True >>> list(components.getAllUtilitiesRegisteredFor(tests.I2e)) [] Adapters -------- You can register adapters with registerAdapter: >>> components.registerAdapter(tests.A12_1) Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Here, we didn't specify required interfaces, a provided interface, or a name. The required interfaces were determined from the factory s __component_adapts__ attribute and the provided interface was determined by introspecting what the factory implements. >>> components.getMultiAdapter((tests.U1(6), tests.U12(7)), tests.IA1) A12_1(U1(6), U12(7)) If a factory implements more than one interface, an exception will be raised: >>> components.registerAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.registerAdapter(tests.A1_12, provided=tests.IA2) Registered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') If a factory doesn't declare an implemented interface, an exception will be raised: >>> components.registerAdapter(tests.A12_) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.registerAdapter(tests.A12_, provided=tests.IA2) Registered event: AdapterRegistration(, [I1, I2], IA2, u'', A12_, u'') The required interface needs to be specified in the registration if the factory doesn't have a __component_adapts__ attribute: >>> components.registerAdapter(tests.A_2) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified Unless the required specifications specified: >>> components.registerAdapter(tests.A_2, required=[tests.I3]) Registered event: AdapterRegistration(, [I3], IA2, u'', A_2, u'') Classes can be specified in place of specifications, in which case the implementedBy specification for the class is used: >>> components.registerAdapter(tests.A_3, required=[tests.U], ... info="Really class specific") ... # doctest: +NORMALIZE_WHITESPACE Registered event: AdapterRegistration(, [zope.component.tests.U], IA3, u'', A_3, 'Really class specific') We can see the adapters that have been registered using the registeredAdapters method: >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (, ) zope.component.tests.A12_1 (, ) zope.component.tests.A12_ (,) zope.component.tests.A1_12 (,) zope.component.tests.A_2 (,) zope.component.tests.A_3 Really class specific As with utilities, we can provide registration information when registering adapters. If you try to fetch an adapter that isn't registered, you'll get a component-lookup error: >>> components.getMultiAdapter((tests.U(8), ), tests.IA1) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: ((U(8),), , u'') unless you use queryAdapter: >>> components.queryMultiAdapter((tests.U(8), ), tests.IA1) >>> components.queryMultiAdapter((tests.U(8), ), tests.IA1, default=42) 42 When looking up an adapter for a single object, you can use the slightly simpler getAdapter and queryAdapter calls: >>> components.getAdapter(tests.U1(9), tests.IA2) A1_12(U1(9)) >>> components.queryAdapter(tests.U1(9), tests.IA2) A1_12(U1(9)) >>> components.getAdapter(tests.U(8), tests.IA1) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (U(8), , u'') >>> components.queryAdapter(tests.U(8), tests.IA2) >>> components.queryAdapter(tests.U(8), tests.IA2, default=42) 42 You can unregister an adapter. If a factory is provided and if the rewuired and provided interfaces, can be infered, then they need not be provided: >>> components.unregisterAdapter(tests.A12_1) Unregistered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') True >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (, ) zope.component.tests.A12_ (,) zope.component.tests.A1_12 (,) zope.component.tests.A_2 (,) zope.component.tests.A_3 Really class specific A boolean is returned indicating whether a change was made. If a factory implements more than one interface, an exception will be raised: >>> components.unregisterAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.unregisterAdapter(tests.A1_12, provided=tests.IA2) Unregistered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') True If a factory doesn't declare an implemented interface, an exception will be raised: >>> components.unregisterAdapter(tests.A12_) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.unregisterAdapter(tests.A12_, provided=tests.IA2) Unregistered event: AdapterRegistration(, [I1, I2], IA2, u'', A12_, u'') True The required interface needs to be specified if the factory doesn't have a __component_adapts__ attribute: >>> components.unregisterAdapter(tests.A_2) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified >>> components.unregisterAdapter(tests.A_2, required=[tests.I3]) Unregistered event: AdapterRegistration(, [I3], IA2, u'', A_2, u'') True >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) zope.component.tests.A_3 Really class specific If a factory is unregistered that is not registered, False is returned: >>> components.unregisterAdapter(tests.A_2, required=[tests.I3]) False >>> components.unregisterAdapter(tests.A12_1, required=[tests.U]) False The factory can be omitted, to unregister *any* factory that matches specified required and provided interfaces: >>> components.unregisterAdapter(required=[tests.U], provided=tests.IA3) ... # doctest: +NORMALIZE_WHITESPACE Unregistered event: AdapterRegistration(, [zope.component.tests.U], IA3, u'', A_3, 'Really class specific') True >>> for registration in sorted(components.registeredAdapters()): ... print registration Adapters can be named: >>> components.registerAdapter(tests.A1_12, provided=tests.IA2, ... name=u'test') Registered event: AdapterRegistration(, [I1], IA2, u'test', A1_12, u'') >>> components.queryMultiAdapter((tests.U1(9), ), tests.IA2) >>> components.queryMultiAdapter((tests.U1(9), ), tests.IA2, name=u'test') A1_12(U1(9)) >>> components.queryAdapter(tests.U1(9), tests.IA2) >>> components.queryAdapter(tests.U1(9), tests.IA2, name=u'test') A1_12(U1(9)) >>> components.getAdapter(tests.U1(9), tests.IA2, name=u'test') A1_12(U1(9)) It is possible to look up all of the adapters that provide an interface: >>> components.registerAdapter(tests.A1_23, provided=tests.IA2, ... name=u'test 2') Registered event: AdapterRegistration(, [I1], IA2, u'test 2', A1_23, u'') >>> components.registerAdapter(tests.A1_12, provided=tests.IA2) Registered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') >>> for name, adapter in sorted(components.getAdapters((tests.U1(9), ), ... tests.IA2)): ... print name, adapter A1_12(U1(9)) test A1_12(U1(9)) test 2 A1_23(U1(9)) getAdapters is most commonly used as the basis of menu systems. If an adapter factory returns None, it is equivalent to there being no factory: >>> components.registerAdapter(tests.noop, ... required=[tests.IA1], provided=tests.IA2, ... name=u'test noop') ... # doctest: +NORMALIZE_WHITESPACE Registered event: AdapterRegistration(, [IA1], IA2, u'test noop', noop, u'') >>> components.queryAdapter(tests.U1(9), tests.IA2, name=u'test noop') >>> components.registerAdapter(tests.A1_12, provided=tests.IA2) Registered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') >>> for name, adapter in sorted(components.getAdapters((tests.U1(9), ), ... tests.IA2)): ... print name, adapter A1_12(U1(9)) test A1_12(U1(9)) test 2 A1_23(U1(9)) >>> components.unregisterAdapter(tests.A1_12, provided=tests.IA2, ... name=u'test') Unregistered event: AdapterRegistration(, [I1], IA2, u'test', A1_12, u'') True >>> components.unregisterAdapter(tests.A1_12, provided=tests.IA2) Unregistered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') True >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) test 2 zope.component.tests.A1_23 (,) test noop Subscribers ----------- Subscribers provide a way to get multiple adapters of a given type. In this regard, subscribers are like named adapters, except that there isn't any concept of the most specific adapter for a given name. Subscribers are registered by calling registerSubscriptionAdapter: >>> components.registerSubscriptionAdapter(tests.A1_2) ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') >>> components.registerSubscriptionAdapter( ... tests.A1_12, provided=tests.IA2) ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_12, u'') >>> components.registerSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2, ... info='a sample comment') ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A, 'a sample comment') The same rules, with regard to when required and provided interfaces have to be specified apply as with adapters: >>> components.registerSubscriptionAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.registerSubscriptionAdapter(tests.A) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.registerSubscriptionAdapter(tests.A, required=[tests.IA1]) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Note that we provided the info argument as a keyword argument above. That's because there is a name argument that's reserved for future use. We can give a name, as long as it is an empty string: >>> components.registerSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2, u'', 'a sample comment') ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A, 'a sample comment') >>> components.registerSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2, u'oops', 'a sample comment') Traceback (most recent call last): ... TypeError: Named subscribers are not yet supported Subscribers are looked up using the subscribers method: >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_2(U1(1)) A1_12(U1(1)) A(U1(1),) A(U1(1),) Note that, because we created multiple subscriptions for A, we got multiple subscriber instances. As with normal adapters, if a factory returns None, the result is skipped: >>> components.registerSubscriptionAdapter( ... tests.noop, [tests.I1], tests.IA2) Registered event: SubscriptionRegistration(, [I1], IA2, u'', noop, u'') >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_2(U1(1)) A1_12(U1(1)) A(U1(1),) A(U1(1),) We can get registration information for subscriptions: >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info (,) zope.component.tests.A a sample comment (,) zope.component.tests.A a sample comment (,) zope.component.tests.A1_12 (,) zope.component.tests.A1_2 (,) We can also unregister subscriptions in much the same way we can for adapters: >>> components.unregisterSubscriptionAdapter(tests.A1_2) ... # doctest: +NORMALIZE_WHITESPACE Unregistered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, '') True >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_12(U1(1)) A(U1(1),) A(U1(1),) >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info (,) zope.component.tests.A a sample comment (,) zope.component.tests.A a sample comment (,) zope.component.tests.A1_12 (,) >>> components.unregisterSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2) Unregistered event: SubscriptionRegistration(, [I1], IA2, u'', A, '') True >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_12(U1(1)) >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info (,) zope.component.tests.A1_12 (,) Note here that both registrations for A were removed. If we omit the factory, we must specify the required and provided interfaces: >>> components.unregisterSubscriptionAdapter(required=[tests.I1]) Traceback (most recent call last): ... TypeError: Must specify one of factory and provided >>> components.unregisterSubscriptionAdapter(provided=tests.IA2) Traceback (most recent call last): ... TypeError: Must specify one of factory and required >>> components.unregisterSubscriptionAdapter( ... required=[tests.I1], provided=tests.IA2) Unregistered event: SubscriptionRegistration(, [I1], IA2, u'', None, '') True >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.factory As when registering, an error is raised if the registration information can't be determined from the factory and isn't specified: >>> components.unregisterSubscriptionAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.unregisterSubscriptionAdapter(tests.A) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.unregisterSubscriptionAdapter(tests.A, required=[tests.IA1]) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. If you unregister something that's not registered, nothing will be changed and False will be returned: >>> components.unregisterSubscriptionAdapter( ... required=[tests.I1], provided=tests.IA2) False Handlers -------- Handlers are used when you want to perform some function in response to an event. Handlers aren't expected to return anything when called and are not registered to provide any interface. >>> from zope import component >>> @component.adapter(tests.I1) ... def handle1(x): ... print 'handle1', x >>> components.registerHandler(handle1, info="First handler") ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [I1], u'', handle1, 'First handler') >>> components.handle(tests.U1(1)) handle1 U1(1) >>> @component.adapter(tests.I1, tests.I2) ... def handle12(x, y): ... print 'handle12', x, y >>> components.registerHandler(handle12) Registered event: HandlerRegistration(, [I1, I2], u'', handle12, u'') >>> components.handle(tests.U1(1), tests.U12(2)) handle12 U1(1) U12(2) If a handler doesn't document interfaces it handles, then the required interfaces must be specified: >>> def handle(*objects): ... print 'handle', objects >>> components.registerHandler(handle) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified >>> components.registerHandler(handle, required=[tests.I1], ... info="a comment") Registered event: HandlerRegistration(, [I1], u'', handle, 'a comment') Handlers can also be registered for classes: >>> components.registerHandler(handle, required=[tests.U], ... info="handle a class") ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [zope.component.tests.U], u'', handle, 'handle a class') >>> components.handle(tests.U1(1)) handle (U1(1),) handle1 U1(1) handle (U1(1),) We can list the handler registrations: >>> for registration in components.registeredHandlers(): ... print registration.required ... print registration.handler, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) First handler (, ) (,) a comment (,) handle a class and we can unregister handlers: >>> components.unregisterHandler(required=[tests.U]) ... # doctest: +NORMALIZE_WHITESPACE Unregistered event: HandlerRegistration(, [zope.component.tests.U], u'', None, '') True >>> for registration in components.registeredHandlers(): ... print registration.required ... print registration.handler, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) First handler (, ) (,) a comment >>> components.unregisterHandler(handle12) Unregistered event: HandlerRegistration(, [I1, I2], u'', handle12, '') True >>> for registration in components.registeredHandlers(): ... print registration.required ... print registration.handler, registration.info (,) First handler (,) a comment >>> components.unregisterHandler(handle12) False >>> components.unregisterHandler() Traceback (most recent call last): ... TypeError: Must specify one of factory and required >>> components.registerHandler(handle) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified Extending --------- Component-management objects can extend other component-management objects. >>> c1 = registry.Components('1') >>> c1.__bases__ () >>> c2 = registry.Components('2', (c1, )) >>> c2.__bases__ == (c1, ) True >>> c1.registerUtility(tests.U1(1)) Registered event: UtilityRegistration(, I1, u'', 1, None, u'') >>> c1.queryUtility(tests.I1) U1(1) >>> c2.queryUtility(tests.I1) U1(1) >>> c1.registerUtility(tests.U1(2)) Unregistered event: UtilityRegistration(, I1, u'', 1, None, u'') Registered event: UtilityRegistration(, I1, u'', 2, None, u'') >>> c2.queryUtility(tests.I1) U1(2) We can use multiple inheritence: >>> c3 = registry.Components('3', (c1, )) >>> c4 = registry.Components('4', (c2, c3)) >>> c4.queryUtility(tests.I1) U1(2) >>> c1.registerUtility(tests.U12(1), tests.I2) Registered event: UtilityRegistration(, I2, u'', 1, None, u'') >>> c4.queryUtility(tests.I2) U12(1) >>> c3.registerUtility(tests.U12(3), tests.I2) Registered event: UtilityRegistration(, I2, u'', 3, None, u'') >>> c4.queryUtility(tests.I2) U12(3) >>> c1.registerHandler(handle1, info="First handler") Registered event: HandlerRegistration(, [I1], u'', handle1, 'First handler') >>> c2.registerHandler(handle, required=[tests.U]) ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [zope.component.tests.U], u'', handle, u'') >>> @component.adapter(tests.I1) ... def handle3(x): ... print 'handle3', x >>> c3.registerHandler(handle3) Registered event: HandlerRegistration(, [I1], u'', handle3, u'') >>> @component.adapter(tests.I1) ... def handle4(x): ... print 'handle4', x >>> c4.registerHandler(handle4) Registered event: HandlerRegistration(, [I1], u'', handle4, u'') >>> c4.handle(tests.U1(1)) handle1 U1(1) handle3 U1(1) handle (U1(1),) handle4 U1(1) Redispatch of registration events --------------------------------- Some handlers are available that, if registered, redispatch registration events to the objects being registered. They depend on being dispatched to by the object-event dispatcher: >>> from zope import component >>> import zope.component.event >>> zope.component.getGlobalSiteManager().registerHandler( ... zope.component.event.objectEventNotify) ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [IObjectEvent], u'', objectEventNotify, u'') To see this, we'll first register a multi-handler to show is when handlers are called on 2 objects: >>> @zope.component.adapter(None, None) ... def double_handler(o1, o2): ... print 'Double dispatch:' ... print ' ', o1 ... print ' ', o2 >>> zope.component.getGlobalSiteManager().registerHandler(double_handler) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: HandlerRegistration(, [Interface, Interface], u'', double_handler, u'') Registered event: HandlerRegistration(, [Interface, Interface], u'', double_handler, u'') Registered event: HandlerRegistration(, [Interface, Interface], u'', double_handler, u'') In the example above, the double_handler reported it's own registration. :) Now we'll register our handlers: >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchUtilityRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Double dispatch: ... >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchAdapterRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Double dispatch: ... >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchSubscriptionAdapterRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Double dispatch: ... >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchHandlerRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') Registered event: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') Double dispatch: Registered event: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') Registered event: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') In the last example above, we can see that the registration of dispatchHandlerRegistrationEvent was handled by dispatchHandlerRegistrationEvent and redispatched. This can be seen in the second double-dispatch output, where the first argument is the object being registered, which is dispatchHandlerRegistrationEvent. If we change some other registrations, we can the double dispatch taking place: >>> components.registerUtility(u5) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: UtilityRegistration(, I1, u'', 5, None, u'') Registered event: UtilityRegistration(, I1, u'', 5, None, u'') Double dispatch: U1(5) Registered event: UtilityRegistration(, I1, u'', 5, None, u'') Registered event: UtilityRegistration(, I1, u'', 5, None, u'') >>> components.registerAdapter(tests.A12_1) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Double dispatch: zope.component.tests.A12_1 Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') >>> components.registerSubscriptionAdapter(tests.A1_2) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Double dispatch: zope.component.tests.A1_2 Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') zope2.13-2.13.21/source/zope.component/src/zope/component/persistentregistry.py0000644000175000017500000000374312214017426026534 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Persistent component managers """ import persistent.mapping import persistent.list import zope.interface.adapter import zope.component.registry class PersistentAdapterRegistry( zope.interface.adapter.VerifyingAdapterRegistry, persistent.Persistent, ): def changed(self, originally_changed): if originally_changed is self: self._p_changed = True super(PersistentAdapterRegistry, self).changed(originally_changed) def __getstate__(self): state = super(PersistentAdapterRegistry, self).__getstate__().copy() for name in self._delegated: state.pop(name, 0) return state def __setstate__(self, state): super(PersistentAdapterRegistry, self).__setstate__(state) self._createLookup() self._v_lookup.changed(self) class PersistentComponents(zope.component.registry.Components): def _init_registries(self): self.adapters = PersistentAdapterRegistry() self.utilities = PersistentAdapterRegistry() def _init_registrations(self): self._utility_registrations = persistent.mapping.PersistentMapping() self._adapter_registrations = persistent.mapping.PersistentMapping() self._subscription_registrations = persistent.list.PersistentList() self._handler_registrations = persistent.list.PersistentList() zope2.13-2.13.21/source/zope.component/src/zope/component/configure.zcml0000644000175000017500000000063412214017426025035 0ustar arnauarnau zope2.13-2.13.21/source/zope.component/src/zope/component/factory.py0000644000175000017500000000326112214017426024205 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Factory object """ from zope.interface import implements, implementedBy from zope.interface.declarations import Implements from zope.component.interfaces import IFactory class Factory(object): """Generic factory implementation. The purpose of this implementation is to provide a quick way of creating factories for classes, functions and other objects. """ implements(IFactory) def __init__(self, callable, title='', description='', interfaces=None): self._callable = callable self.title = title self.description = description self._interfaces = interfaces def __call__(self, *args, **kw): return self._callable(*args, **kw) def getInterfaces(self): if self._interfaces is not None: spec = Implements(*self._interfaces) spec.__name__ = getattr(self._callable, '__name__', '[callable]') return spec return implementedBy(self._callable) def __repr__(self): return '<%s for %s>' % (self.__class__.__name__, repr(self._callable)) zope2.13-2.13.21/source/zope.component/src/zope/component/interface.py0000644000175000017500000001755112214017426024505 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interface utility functions """ __docformat__ = 'restructuredtext' from types import ClassType import zope.component from zope.component.interfaces import ComponentLookupError from zope.interface import alsoProvides from zope.interface.interfaces import IInterface def provideInterface(id, interface, iface_type=None, info=''): """register Interface with global site manager as utility >>> gsm = zope.component.getGlobalSiteManager() >>> from zope.interface import Interface >>> from zope.interface.interfaces import IInterface >>> from zope.component.tests import ITestType >>> class I(Interface): ... pass >>> IInterface.providedBy(I) True >>> ITestType.providedBy(I) False >>> interfaces = gsm.getUtilitiesFor(ITestType) >>> list(interfaces) [] # provide first interface type >>> provideInterface('', I, ITestType) >>> ITestType.providedBy(I) True >>> interfaces = list(gsm.getUtilitiesFor(ITestType)) >>> [name for (name, iface) in interfaces] [u'zope.component.interface.I'] >>> [iface.__name__ for (name, iface) in interfaces] ['I'] # provide second interface type >>> class IOtherType(IInterface): ... pass >>> provideInterface('', I, IOtherType) >>> ITestType.providedBy(I) True >>> IOtherType.providedBy(I) True >>> interfaces = list(gsm.getUtilitiesFor(ITestType)) >>> [name for (name, iface) in interfaces] [u'zope.component.interface.I'] >>> interfaces = list(gsm.getUtilitiesFor(IOtherType)) >>> [name for (name, iface) in interfaces] [u'zope.component.interface.I'] >>> class I1(Interface): ... pass >>> provideInterface('', I1) >>> IInterface.providedBy(I1) True >>> ITestType.providedBy(I1) False >>> interfaces = list(gsm.getUtilitiesFor(ITestType)) >>> [name for (name, iface) in interfaces] [u'zope.component.interface.I'] >>> [iface.__name__ for (name, iface) in interfaces] ['I'] """ if not id: id = "%s.%s" % (interface.__module__, interface.__name__) if not IInterface.providedBy(interface): if not isinstance(interface, (type, ClassType)): raise TypeError(id, "is not an interface or class") return if iface_type is not None: if not iface_type.extends(IInterface): raise TypeError(iface_type, "is not an interface type") alsoProvides(interface, iface_type) else: iface_type = IInterface gsm = zope.component.getGlobalSiteManager() gsm.registerUtility(interface, iface_type, id, info) def getInterface(context, id): """Return interface or raise ComponentLookupError >>> from zope.interface import Interface >>> from zope.component.tests import ITestType >>> class I4(Interface): ... pass >>> IInterface.providedBy(I4) True >>> ITestType.providedBy(I4) False >>> getInterface(None, 'zope.component.interface.I4') Traceback (most recent call last): ... ComponentLookupError: zope.component.interface.I4 >>> provideInterface('', I4, ITestType) >>> ITestType.providedBy(I4) True >>> iface = queryInterface( """\ """ 'zope.component.interface.I4') >>> iface.__name__ 'I4' """ iface = queryInterface(id, None) if iface is None: raise ComponentLookupError(id) return iface def queryInterface(id, default=None): """return interface or ``None`` >>> from zope.interface import Interface >>> from zope.interface.interfaces import IInterface >>> from zope.component.tests import ITestType >>> class I3(Interface): ... pass >>> IInterface.providedBy(I3) True >>> ITestType.providedBy(I3) False >>> queryInterface('zope.component.interface.I3') >>> provideInterface('', I3, ITestType) >>> ITestType.providedBy(I3) True >>> iface = queryInterface('zope.component.interface.I3') >>> iface.__name__ 'I3' """ return zope.component.queryUtility(IInterface, id, default) def searchInterface(context, search_string=None, base=None): """Interfaces search >>> from zope.interface import Interface >>> from zope.interface.interfaces import IInterface >>> from zope.component.tests import ITestType >>> class I5(Interface): ... pass >>> IInterface.providedBy(I5) True >>> ITestType.providedBy(I5) False >>> searchInterface(None, 'zope.component.interface.I5') [] >>> provideInterface('', I5, ITestType) >>> ITestType.providedBy(I5) True >>> iface = searchInterface(None, 'zope.component.interface.I5') >>> iface[0].__name__ 'I5' """ return [iface_util[1] for iface_util in searchInterfaceUtilities(context, search_string, base)] def searchInterfaceIds(context, search_string=None, base=None): """Interfaces search >>> from zope.interface import Interface >>> from zope.interface.interfaces import IInterface >>> from zope.component.tests import ITestType >>> class I5(Interface): ... pass >>> IInterface.providedBy(I5) True >>> ITestType.providedBy(I5) False >>> searchInterface(None, 'zope.component.interface.I5') [] >>> provideInterface('', I5, ITestType) >>> ITestType.providedBy(I5) True >>> iface = searchInterfaceIds(None, 'zope.component.interface.I5') >>> iface [u'zope.component.interface.I5'] """ return [iface_util[0] for iface_util in searchInterfaceUtilities(context, search_string, base)] def searchInterfaceUtilities(context, search_string=None, base=None): gsm = zope.component.getGlobalSiteManager() iface_utilities = gsm.getUtilitiesFor(IInterface) if search_string: search_string = search_string.lower() iface_utilities = [iface_util for iface_util in iface_utilities if (getInterfaceAllDocs(iface_util[1]).\ find(search_string) >= 0)] if base: res = [iface_util for iface_util in iface_utilities if iface_util[1].extends(base)] else: res = [iface_util for iface_util in iface_utilities] return res def getInterfaceAllDocs(interface): iface_id = '%s.%s' %(interface.__module__, interface.__name__) docs = [str(iface_id).lower(), str(interface.__doc__).lower()] if IInterface.providedBy(interface): for name in interface: docs.append( str(interface.getDescriptionFor(name).__doc__).lower()) return '\n'.join(docs) def nameToInterface(context, id): if id == 'None': return None iface = getInterface(context, id) return iface def interfaceToName(context, interface): if interface is None: return 'None' items = searchInterface(context, base=interface) ids = [('%s.%s' %(iface.__module__, iface.__name__)) for iface in items if iface == interface] if not ids: # Do not fail badly, instead resort to the standard # way of getting the interface name, cause not all interfaces # may be registered as utilities. return interface.__module__ + '.' + interface.__name__ assert len(ids) == 1, "Ambiguous interface names: %s" % ids return ids[0] zope2.13-2.13.21/source/zope.component/src/zope/component/zcml_conditional.txt0000644000175000017500000004522212214017426026260 0ustar arnauarnauZCML directives without zope.security support ============================================= This tests run without zope.security available: >>> from zope.component.zcml import check_security_support >>> check_security_support() Traceback (most recent call last): ... ConfigurationError: security proxied components are not supported because zope.security is not available Components may be registered using the registration API exposed by ``zope.component`` (provideAdapter, provideUtility, etc.). They may also be registered using configuration files. The common way to do that is by using ZCML (Zope Configuration Markup Language), an XML spelling of component registration. In ZCML, each XML element is a *directive*. There are different top-level directives that let us register components. We will introduce them one by one here. This helper will let us easily execute ZCML snippets: >>> from cStringIO import StringIO >>> from zope.configuration.xmlconfig import xmlconfig >>> def runSnippet(snippet): ... template = """\ ... ... %s ... """ ... xmlconfig(StringIO(template % snippet)) adapter ------- Adapters play a key role in the Component Architecture. In ZCML, they are registered with the directive. >>> from zope.component.testfiles.adapter import A1, A2, A3, Handler >>> from zope.component.testfiles.adapter import I1, I2, I3, IS >>> from zope.component.testfiles.components import IContent, Content, Comp, comp Before we register the first test adapter, we can verify that adapter lookup doesn't work yet: >>> from zope.component.tests import clearZCML >>> clearZCML() >>> from zope.component.testfiles.components import IApp >>> IApp(Content(), None) is None True Then we register the adapter and see that the lookup works: >>> runSnippet(''' ... ''') >>> IApp(Content()).__class__ It is also possible to give adapters names. Then the combination of required interface, provided interface and name makes the adapter lookup unique. The name is supplied using the ``name`` argument to the directive: >>> from zope.component.tests import clearZCML >>> clearZCML() >>> import zope.component >>> zope.component.queryAdapter(Content(), IApp, 'test') is None True >>> runSnippet(''' ... ''') >>> zope.component.getAdapter(Content(), IApp, 'test').__class__ Adapter factories ~~~~~~~~~~~~~~~~~ It is possible to supply more than one adapter factory. In this case, during adapter lookup each factory will be called and the return value will be given to the next factory. The return value of the last factory is returned as the result of the adapter lookup. For examle: >>> clearZCML() >>> runSnippet(''' ... ''') The resulting adapter is an A3, around an A2, around an A1, around the adapted object: >>> content = Content() >>> a3 = IApp(content) >>> a3.__class__ is A3 True >>> a2 = a3.context[0] >>> a2.__class__ is A2 True >>> a1 = a2.context[0] >>> a1.__class__ is A1 True >>> a1.context[0] is content True Of course, if no factory is provided at all, we will get an error: >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-8.8 ValueError: No factory specified Declaring ``for`` and ``provides`` in Python ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The directive can figure out from the in-line Python declaration (using ``zope.component.adapts()`` or ``zope.component.adapter()`` as well as ``zope.interface.implements``) what the adapter should be registered for and what it provides:: >>> clearZCML() >>> IApp(Content(), None) is None True >>> runSnippet(''' ... ''') >>> IApp(Content()).__class__ Of course, if the adapter has no ``implements()`` declaration, ZCML can't figure out what it provides: >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-7.8 TypeError: Missing 'provides' attribute On the other hand, if the factory implements more than one interface, ZCML can't figure out what it should provide either: >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-7.8 TypeError: Missing 'provides' attribute A not so common edge case is registering adapters directly for classes, not for interfaces. For example: >>> clearZCML() >>> runSnippet(''' ... ''') >>> content = Content() >>> a1 = zope.component.getAdapter(content, I1, '') >>> isinstance(a1, A1) True This time, any object providing ``IContent`` won't work if it's not an instance of the ``Content`` class: >>> import zope.interface >>> class MyContent: ... zope.interface.implements(IContent) >>> zope.component.getAdapter(MyContent(), I1, '') # doctest: +ELLIPSIS Traceback (most recent call last): ... ComponentLookupError: ... Multi-adapters ~~~~~~~~~~~~~~ Conventional adapters adapt one object to provide another interface. Multi-adapters adapt several objects at once: >>> clearZCML() >>> runSnippet(''' ... ''') >>> content = Content() >>> a1 = A1() >>> a2 = A2() >>> a3 = zope.component.queryMultiAdapter((content, a1, a2), I3) >>> a3.__class__ is A3 True >>> a3.context == (content, a1, a2) True You can even adapt an empty list of objects (we call this a null-adapter): >>> clearZCML() >>> runSnippet(''' ... ''') >>> a3 = zope.component.queryMultiAdapter((), I3) >>> a3.__class__ is A3 True >>> a3.context == () True Even with multi-adapters, ZCML can figure out the ``for`` and ``provides`` parameters from the Python declarations: >>> clearZCML() >>> runSnippet(''' ... ''') >>> a3 = zope.component.queryMultiAdapter((content, a1, a2), I3) >>> a3.__class__ is A3 True >>> a3.context == (content, a1, a2) True Chained factories are not supported for multi-adapters, though: >>> clearZCML() >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-11.8 ValueError: Can't use multiple factories and multiple for And neither for null-adapters: >>> clearZCML() >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-9.8 ValueError: Can't use multiple factories and multiple for subscriber ---------- With the directive you can register subscription adapters or event subscribers with the adapter registry. Consider this very typical example of a directive: >>> clearZCML() >>> runSnippet(''' ... ''') >>> content = Content() >>> a1 = A1() >>> subscribers = zope.component.subscribers((content, a1), IS) >>> a3 = subscribers[0] >>> a3.__class__ is A3 True >>> a3.context == (content, a1) True Note how ZCML provides some additional information when registering components, such as the ZCML filename and line numbers: >>> sm = zope.component.getSiteManager() >>> doc = [reg.info for reg in sm.registeredSubscriptionAdapters() ... if reg.provided is IS][0] >>> print doc File "", line 4.2-9.8 Could not read source. The "fun" behind subscription adapters/subscribers is that when several ones are declared for the same for/provides, they are all found. With regular adapters, the most specific one (and in doubt the one registered last) wins. Consider these two subscribers: >>> clearZCML() >>> runSnippet(''' ... ... ''') >>> subscribers = zope.component.subscribers((content, a1), IS) >>> len(subscribers) 2 >>> sorted([a.__class__.__name__ for a in subscribers]) ['A2', 'A3'] Declaring ``for`` and ``provides`` in Python ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Like the directive, the directive can figure out from the in-line Python declaration (using ``zope.component.adapts()`` or ``zope.component.adapter()``) what the subscriber should be registered for: >>> clearZCML() >>> runSnippet(''' ... ''') >>> content = Content() >>> a2 = A2() >>> subscribers = zope.component.subscribers((content, a1, a2), IS) >>> a3 = subscribers[0] >>> a3.__class__ is A3 True >>> a3.context == (content, a1, a2) True In the same way the directive can figure out what a subscriber provides: >>> clearZCML() >>> runSnippet(''' ... ''') >>> sm = zope.component.getSiteManager() >>> a3 = sm.adapters.subscriptions((IContent, I1, I2), None)[0] >>> a3 is A3 True A not so common edge case is declaring subscribers directly for classes, not for interfaces. For example: >>> clearZCML() >>> runSnippet(''' ... ''') >>> subs = list(zope.component.subscribers((Content(),), I1)) >>> isinstance(subs[0], A1) True This time, any object providing ``IContent`` won't work if it's not an instance of the ``Content`` class: >>> list(zope.component.subscribers((MyContent(),), I1)) [] Event subscriber (handlers) ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sometimes, subscribers don't need to be adapters that actually provide anything. It's enough that a callable is called for a certain event. >>> clearZCML() >>> runSnippet(''' ... ''') In this case, simply getting the subscribers is enough to invoke them: >>> list(zope.component.subscribers((content, a1), None)) [] >>> content.args == ((a1,),) True utility ------- Apart from adapters (and subscription adapters), the Component Architecture knows a second kind of component: utilities. They are registered using the directive. Before we register the first test utility, we can verify that utility lookup doesn't work yet: >>> clearZCML() >>> zope.component.queryUtility(IApp) is None True Then we register the utility: >>> runSnippet(''' ... ''') >>> zope.component.getUtility(IApp) is comp True Like adapters, utilities can also have names. There can be more than one utility registered for a certain interface, as long as they each have a different name. First, we make sure that there's no utility yet: >>> clearZCML() >>> zope.component.queryUtility(IApp, 'test') is None True Then we register it: >>> runSnippet(''' ... ''') >>> zope.component.getUtility(IApp, 'test') is comp True Utilities can also be registered from a factory. In this case, the ZCML handler calls the factory (without any arguments) and registers the returned value as a utility. Typically, you'd pass a class for the factory: >>> clearZCML() >>> zope.component.queryUtility(IApp) is None True >>> runSnippet(''' ... ''') >>> zope.component.getUtility(IApp).__class__ is Comp True Declaring ``provides`` in Python ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Like other directives, can also figure out which interface a utility provides from the Python declaration: >>> clearZCML() >>> zope.component.queryUtility(IApp) is None True >>> runSnippet(''' ... ''') >>> zope.component.getUtility(IApp) is comp True It won't work if the component that is to be registered doesn't provide anything: >>> clearZCML() >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-4.61 TypeError: Missing 'provides' attribute Or if more than one interface is provided (then the ZCML directive handler doesn't know under which the utility should be registered): >>> clearZCML() >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-4.61 TypeError: Missing 'provides' attribute We can repeat the same drill for utility factories: >>> clearZCML() >>> runSnippet(''' ... ''') >>> zope.component.getUtility(IApp).__class__ is Comp True >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-4.59 TypeError: Missing 'provides' attribute >>> clearZCML() >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-4.59 TypeError: Missing 'provides' attribute interface --------- The directive lets us register an interface. Interfaces are registered as named utilities. We therefore needn't go though all the lookup details again, it is sufficient to see whether the directive handler emits the right actions. First we provide a stub configuration context: >>> import re, pprint >>> atre = re.compile(' at [0-9a-fA-Fx]+') >>> class Context(object): ... actions = () ... def action(self, discriminator, callable, args): ... self.actions += ((discriminator, callable, args), ) ... def __repr__(self): ... stream = StringIO() ... pprinter = pprint.PrettyPrinter(stream=stream, width=60) ... pprinter.pprint(self.actions) ... r = stream.getvalue() ... return (''.join(atre.split(r))).strip() >>> context = Context() Then we provide a test interface that we'd like to register: >>> from zope.interface import Interface >>> class I(Interface): ... pass It doesn't yet provide ``ITestType``: >>> from zope.component.tests import ITestType >>> ITestType.providedBy(I) False However, after calling the directive handler... >>> from zope.component.zcml import interface >>> interface(context, I, ITestType) >>> context ((None, , ('', , )),) ...it does provide ``ITestType``: >>> from zope.interface.interfaces import IInterface >>> ITestType.extends(IInterface) True >>> IInterface.providedBy(I) True zope2.13-2.13.21/source/zope.component/src/zope/component/security.py0000644000175000017500000001277712214017426024421 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """zope.security support for the configuration handlers """ __docformat__ = "reStructuredText" from zope.interface import providedBy from zope.proxy import ProxyBase, getProxiedObject from zope.security.adapter import LocatingTrustedAdapterFactory, \ LocatingUntrustedAdapterFactory, TrustedAdapterFactory from zope.security.checker import Checker, CheckerPublic, InterfaceChecker from zope.security.proxy import Proxy PublicPermission = 'zope.Public' class PermissionProxy(ProxyBase): __slots__ = ('__Security_checker__', ) def __providedBy__(self): return providedBy(getProxiedObject(self)) __providedBy__ = property(__providedBy__) def _checker(_context, permission, allowed_interface, allowed_attributes): if (not allowed_attributes) and (not allowed_interface): allowed_attributes = ["__call__"] if permission == PublicPermission: permission = CheckerPublic require={} if allowed_attributes: for name in allowed_attributes: require[name] = permission if allowed_interface: for i in allowed_interface: for name in i.names(all=True): require[name] = permission checker = Checker(require) return checker def proxify(ob, checker=None, provides=None, permission=None): """Try to get the object proxied with the `checker`, but not too soon We really don't want to proxy the object unless we need to. """ if checker is None: if provides is None or permission is None: raise ValueError, 'Required arguments: checker or both provides and permissions' if permission == PublicPermission: permission = CheckerPublic checker = InterfaceChecker(provides, permission) ob = PermissionProxy(ob) ob.__Security_checker__ = checker return ob def protectedFactory(original_factory, provides, permission): if permission == PublicPermission: permission = CheckerPublic checker = InterfaceChecker(provides, permission) # This has to be named 'factory', aparently, so as not to confuse apidoc :( def factory(*args): ob = original_factory(*args) try: ob.__Security_checker__ = checker except AttributeError: ob = Proxy(ob, checker) return ob factory.factory = original_factory return factory def securityAdapterFactory(factory, permission, locate, trusted): """ If a permission is provided when wrapping the adapter, it will be wrapped in a LocatingAdapterFactory. >>> class Factory: ... pass If both locate and trusted are False and a non-public permission is provided, then the factory is wrapped into a LocatingUntrustedAdapterFactory: >>> factory = securityAdapterFactory(Factory, 'zope.AnotherPermission', ... locate=False, trusted=False) >>> isinstance(factory, LocatingUntrustedAdapterFactory) True If a PublicPermission is provided, then the factory is not touched. >>> factory = securityAdapterFactory(Factory, PublicPermission, ... locate=False, trusted=False) >>> factory is Factory True Same for CheckerPublic: >>> factory = securityAdapterFactory(Factory, CheckerPublic, ... locate=False, trusted=False) >>> factory is Factory True If the permission is None, the factory isn't touched: >>> factory = securityAdapterFactory(Factory, None, ... locate=False, trusted=False) >>> factory is Factory True If the factory is trusted and a no permission is provided then the adapter is wrapped into a TrustedAdapterFactory: >>> factory = securityAdapterFactory(Factory, None, ... locate=False, trusted=True) >>> isinstance(factory, TrustedAdapterFactory) True Same for PublicPermission: >>> factory = securityAdapterFactory(Factory, PublicPermission, ... locate=False, trusted=True) >>> isinstance(factory, TrustedAdapterFactory) True Same for CheckerPublic: >>> factory = securityAdapterFactory(Factory, CheckerPublic, ... locate=False, trusted=True) >>> isinstance(factory, TrustedAdapterFactory) True If the factory is trusted and a locate is true, then the adapter is wrapped into a LocatingTrustedAdapterFactory: >>> factory = securityAdapterFactory(Factory, 'zope.AnotherPermission', ... locate=True, trusted=True) >>> isinstance(factory, LocatingTrustedAdapterFactory) True """ if permission == PublicPermission: permission = CheckerPublic if locate or (permission is not None and permission is not CheckerPublic): if trusted: return LocatingTrustedAdapterFactory(factory) else: return LocatingUntrustedAdapterFactory(factory) elif trusted: return TrustedAdapterFactory(factory) else: return factory zope2.13-2.13.21/source/zope.component/src/zope/component/hookable.py0000644000175000017500000000244412214017426024324 0ustar arnauarnau############################################################################## # # Copyright (c) 2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ This module supplies a pure-Python version of zope.hookable.hookable. """ class hookable(object): __slots__ = ('__original', '__implementation') original = property(lambda self: self.__original,) implementation = property(lambda self: self.__implementation,) def __init__(self, implementation): self.__original = self.__implementation = implementation def sethook(self, newimplementation): old, self.__implementation = self.__implementation, newimplementation return old def reset(self): self.__implementation = self.__original def __call__(self, *args, **kw): return self.__implementation(*args, **kw) zope2.13-2.13.21/source/zope.component/src/zope/component/standalonetests.py0000644000175000017500000000214112214017426025745 0ustar arnauarnau""" Standalone Tests """ import unittest import doctest import sys import pickle if __name__ == "__main__": sys.path = pickle.loads(sys.stdin.read()) from zope import interface from zope.component.testing import setUp, tearDown class I1(interface.Interface): pass class I2(interface.Interface): pass class Ob(object): interface.implements(I1) def __repr__(self): return '' ob = Ob() class Comp(object): interface.implements(I2) def __init__(self, context): self.context = context def providing_adapter_sets_adapter_hook(): """ A side effect of importing installs the adapter hook. See http://www.zope.org/Collectors/Zope3-dev/674. >>> import zope.component >>> zope.component.provideAdapter(Comp, (I1,), I2) >>> adapter = I2(ob) >>> adapter.__class__ is Comp True >>> adapter.context is ob True """ def test_suite(): return unittest.TestSuite(( doctest.DocTestSuite(setUp=setUp, tearDown=tearDown), )) if __name__ == "__main__": unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.component/src/zope/component/globalregistry.py0000644000175000017500000000630112214017426025565 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Global components support """ from zope.interface import implements from zope.interface.adapter import AdapterRegistry from zope.component.registry import Components from zope.component.interfaces import Invalid, IComponentLookup, IRegistry from zope.component.interfaces import ComponentLookupError def GAR(components, registryName): return getattr(components, registryName) class GlobalAdapterRegistry(AdapterRegistry): """A global adapter registry This adapter registry's main purpose is to be picklable in combination with a site manager.""" def __init__(self, parent, name): self.__parent__ = parent self.__name__ = name super(GlobalAdapterRegistry, self).__init__() def __reduce__(self): return GAR, (self.__parent__, self.__name__) class BaseGlobalComponents(Components): def _init_registries(self): self.adapters = GlobalAdapterRegistry(self, 'adapters') self.utilities = GlobalAdapterRegistry(self, 'utilities') def __reduce__(self): # Global site managers are pickled as global objects return self.__name__ base = BaseGlobalComponents('base') try: from zope.testing.cleanup import addCleanUp except ImportError: pass else: addCleanUp(lambda: base.__init__('base')) del addCleanUp globalSiteManager = base def getGlobalSiteManager(): return globalSiteManager # The following APIs provide global registration support for Python code. # We eventually want to deprecate these in favor of using the global # component registry directly. def provideUtility(component, provides=None, name=u''): base.registerUtility(component, provides, name, event=False) def provideAdapter(factory, adapts=None, provides=None, name=''): base.registerAdapter(factory, adapts, provides, name, event=False) def provideSubscriptionAdapter(factory, adapts=None, provides=None): base.registerSubscriptionAdapter(factory, adapts, provides, event=False) def provideHandler(factory, adapts=None): base.registerHandler(factory, adapts, event=False) import zope.component._api # see http://www.zope.org/Collectors/Zope3-dev/674 # Ideally, we will switch to an explicit adapter hook registration. For now, # if you provide an adapter, we want to make sure that the adapter hook is # registered, and that registration depends on code in _api, which itself # depends on code in this module. So, for now, we do another of these nasty # circular import workarounds. See also standalonetests.py, as run by # tests.py in StandaloneTests, for a test that fails without this hack, and # succeeds with it. zope2.13-2.13.21/source/zope.component/src/zope/component/_api.py0000644000175000017500000002075012214017426023450 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Zope 3 Component Architecture """ import sys import types from zope.interface import Interface from zope.interface import implementedBy from zope.interface import providedBy from zope.component.interfaces import IComponentArchitecture from zope.component.interfaces import IComponentRegistrationConvenience from zope.component.interfaces import IFactory from zope.component.interfaces import ComponentLookupError from zope.component.interfaces import IComponentLookup from zope.component._declaration import adaptedBy from zope.component._declaration import adapter from zope.component._declaration import adapts # Use the C implementation in zope.hookable, if available; fall back # to our Python version if not. try: from zope.hookable import hookable except ImportError: from zope.component.hookable import hookable # getSiteManager() returns a component registry. Although the term # "site manager" is deprecated in favor of "component registry", # the old term is kept around to maintain a stable API. base = None @hookable def getSiteManager(context=None): global base if context is None: if base is None: from zope.component.globalregistry import base return base else: # Use the global site manager to adapt context to `IComponentLookup` # to avoid the recursion implied by using a local `getAdapter()` call. try: return IComponentLookup(context) except TypeError, error: raise ComponentLookupError(*error.args) # Adapter API def getAdapterInContext(object, interface, context): adapter = queryAdapterInContext(object, interface, context) if adapter is None: raise ComponentLookupError(object, interface) return adapter def queryAdapterInContext(object, interface, context, default=None): conform = getattr(object, '__conform__', None) if conform is not None: try: adapter = conform(interface) except TypeError: # We got a TypeError. It might be an error raised by # the __conform__ implementation, or *we* may have # made the TypeError by calling an unbound method # (object is a class). In the later case, we behave # as though there is no __conform__ method. We can # detect this case by checking whether there is more # than one traceback object in the traceback chain: if sys.exc_info()[2].tb_next is not None: # There is more than one entry in the chain, so # reraise the error: raise # This clever trick is from Phillip Eby else: if adapter is not None: return adapter if interface.providedBy(object): return object return getSiteManager(context).queryAdapter(object, interface, '', default) def getAdapter(object, interface=Interface, name=u'', context=None): adapter = queryAdapter(object, interface, name, None, context) if adapter is None: raise ComponentLookupError(object, interface, name) return adapter def queryAdapter(object, interface=Interface, name=u'', default=None, context=None): if context is None: return adapter_hook(interface, object, name, default) return getSiteManager(context).queryAdapter(object, interface, name, default) def getMultiAdapter(objects, interface=Interface, name=u'', context=None): adapter = queryMultiAdapter(objects, interface, name, context=context) if adapter is None: raise ComponentLookupError(objects, interface, name) return adapter def queryMultiAdapter(objects, interface=Interface, name=u'', default=None, context=None): try: sitemanager = getSiteManager(context) except ComponentLookupError: # Oh blast, no site manager. This should *never* happen! return default return sitemanager.queryMultiAdapter(objects, interface, name, default) def getAdapters(objects, provided, context=None): try: sitemanager = getSiteManager(context) except ComponentLookupError: # Oh blast, no site manager. This should *never* happen! return [] return sitemanager.getAdapters(objects, provided) def subscribers(objects, interface, context=None): try: sitemanager = getSiteManager(context) except ComponentLookupError: # Oh blast, no site manager. This should *never* happen! return [] return sitemanager.subscribers(objects, interface) def handle(*objects): sitemanager = getSiteManager(None) # iterating over subscribers assures they get executed for ignored in sitemanager.subscribers(objects, None): pass ############################################################################# # Register the component architectures adapter hook, with the adapter hook # registry of the `zope.inteface` package. This way we will be able to call # interfaces to create adapters for objects. For example, `I1(ob)` is # equvalent to `getAdapterInContext(I1, ob, '')`. @hookable def adapter_hook(interface, object, name='', default=None): try: sitemanager = getSiteManager() except ComponentLookupError: # Oh blast, no site manager. This should *never* happen! return None return sitemanager.queryAdapter(object, interface, name, default) import zope.interface.interface zope.interface.interface.adapter_hooks.append(adapter_hook) ############################################################################# # Utility API def getUtility(interface, name='', context=None): utility = queryUtility(interface, name, context=context) if utility is not None: return utility raise ComponentLookupError(interface, name) def queryUtility(interface, name='', default=None, context=None): return getSiteManager(context).queryUtility(interface, name, default) def getUtilitiesFor(interface, context=None): return getSiteManager(context).getUtilitiesFor(interface) def getAllUtilitiesRegisteredFor(interface, context=None): return getSiteManager(context).getAllUtilitiesRegisteredFor(interface) _marker = object() def queryNextUtility(context, interface, name='', default=None): """Query for the next available utility. Find the next available utility providing `interface` and having the specified name. If no utility was found, return the specified `default` value. """ try: sm = getSiteManager(context) except ComponentLookupError: return default bases = sm.__bases__ for base in bases: util = base.queryUtility(interface, name, _marker) if util is not _marker: return util return default def getNextUtility(context, interface, name=''): """Get the next available utility. If no utility was found, a `ComponentLookupError` is raised. """ util = queryNextUtility(context, interface, name, _marker) if util is _marker: raise zope.component.interfaces.ComponentLookupError( "No more utilities for %s, '%s' have been found." % ( interface, name)) return util # Factories def createObject(__factory_name, *args, **kwargs): context = kwargs.pop('context', None) return getUtility(IFactory, __factory_name, context)(*args, **kwargs) def getFactoryInterfaces(name, context=None): return getUtility(IFactory, name, context).getInterfaces() def getFactoriesFor(interface, context=None): utils = getSiteManager(context) for (name, factory) in utils.getUtilitiesFor(IFactory): interfaces = factory.getInterfaces() try: if interfaces.isOrExtends(interface): yield name, factory except AttributeError: for iface in interfaces: if iface.isOrExtends(interface): yield name, factory break zope2.13-2.13.21/source/zope.component/src/zope/component/registry.py0000644000175000017500000004355212214017426024415 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Basic components support """ import types from zope.interface import Interface from zope.interface import implementedBy from zope.interface import implements from zope.interface import implementsOnly from zope.interface import providedBy from zope.interface.adapter import AdapterRegistry from zope.interface.interfaces import ISpecification from zope.component.interfaces import ComponentLookupError from zope.component.interfaces import IAdapterRegistration from zope.component.interfaces import IComponents from zope.component.interfaces import IHandlerRegistration from zope.component.interfaces import IRegistrationEvent from zope.component.interfaces import ISubscriptionAdapterRegistration from zope.component.interfaces import IUtilityRegistration from zope.component.interfaces import Registered from zope.component.interfaces import Unregistered from zope.component._api import handle from zope.component._declaration import adapter from zope.event import notify class Components(object): implements(IComponents) def __init__(self, name='', bases=()): assert isinstance(name, basestring) self.__name__ = name self._init_registries() self._init_registrations() self.__bases__ = tuple(bases) def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.__name__) def _init_registries(self): self.adapters = AdapterRegistry() self.utilities = AdapterRegistry() def _init_registrations(self): self._utility_registrations = {} self._adapter_registrations = {} self._subscription_registrations = [] self._handler_registrations = [] def _getBases(self): # Subclasses might override return self.__dict__.get('__bases__', ()) def _setBases(self, bases): # Subclasses might override self.adapters.__bases__ = tuple([ base.adapters for base in bases]) self.utilities.__bases__ = tuple([ base.utilities for base in bases]) self.__dict__['__bases__'] = tuple(bases) __bases__ = property( lambda self: self._getBases(), lambda self, bases: self._setBases(bases), ) def registerUtility(self, component=None, provided=None, name=u'', info=u'', event=True, factory=None): if factory: if component: raise TypeError("Can't specify factory and component.") component = factory() if provided is None: provided = _getUtilityProvided(component) reg = self._utility_registrations.get((provided, name)) if reg is not None: if reg[:2] == (component, info): # already registered return self.unregisterUtility(reg[0], provided, name) subscribed = False for ((p, _), data) in self._utility_registrations.iteritems(): if p == provided and data[0] == component: subscribed = True break self._utility_registrations[(provided, name)] = component, info, factory self.utilities.register((), provided, name, component) if not subscribed: self.utilities.subscribe((), provided, component) if event: notify(Registered( UtilityRegistration(self, provided, name, component, info, factory) )) def unregisterUtility(self, component=None, provided=None, name=u'', factory=None): if factory: if component: raise TypeError("Can't specify factory and component.") component = factory() if provided is None: if component is None: raise TypeError("Must specify one of component, factory and " "provided") provided = _getUtilityProvided(component) old = self._utility_registrations.get((provided, name)) if (old is None) or ((component is not None) and (component != old[0])): return False if component is None: component = old[0] # Note that component is now the old thing registered del self._utility_registrations[(provided, name)] self.utilities.unregister((), provided, name) subscribed = False for ((p, _), data) in self._utility_registrations.iteritems(): if p == provided and data[0] == component: subscribed = True break if not subscribed: self.utilities.unsubscribe((), provided, component) notify(Unregistered( UtilityRegistration(self, provided, name, component, *old[1:]) )) return True def registeredUtilities(self): for ((provided, name), data ) in self._utility_registrations.iteritems(): yield UtilityRegistration(self, provided, name, *data) def queryUtility(self, provided, name=u'', default=None): return self.utilities.lookup((), provided, name, default) def getUtility(self, provided, name=u''): utility = self.utilities.lookup((), provided, name) if utility is None: raise ComponentLookupError(provided, name) return utility def getUtilitiesFor(self, interface): for name, utility in self.utilities.lookupAll((), interface): yield name, utility def getAllUtilitiesRegisteredFor(self, interface): return self.utilities.subscriptions((), interface) def registerAdapter(self, factory, required=None, provided=None, name=u'', info=u'', event=True): if provided is None: provided = _getAdapterProvided(factory) required = _getAdapterRequired(factory, required) self._adapter_registrations[(required, provided, name) ] = factory, info self.adapters.register(required, provided, name, factory) if event: notify(Registered( AdapterRegistration(self, required, provided, name, factory, info) )) def unregisterAdapter(self, factory=None, required=None, provided=None, name=u'', ): if provided is None: if factory is None: raise TypeError("Must specify one of factory and provided") provided = _getAdapterProvided(factory) if (required is None) and (factory is None): raise TypeError("Must specify one of factory and required") required = _getAdapterRequired(factory, required) old = self._adapter_registrations.get((required, provided, name)) if (old is None) or ((factory is not None) and (factory != old[0])): return False del self._adapter_registrations[(required, provided, name)] self.adapters.unregister(required, provided, name) notify(Unregistered( AdapterRegistration(self, required, provided, name, *old) )) return True def registeredAdapters(self): for ((required, provided, name), (component, info) ) in self._adapter_registrations.iteritems(): yield AdapterRegistration(self, required, provided, name, component, info) def queryAdapter(self, object, interface, name=u'', default=None): return self.adapters.queryAdapter(object, interface, name, default) def getAdapter(self, object, interface, name=u''): adapter = self.adapters.queryAdapter(object, interface, name) if adapter is None: raise ComponentLookupError(object, interface, name) return adapter def queryMultiAdapter(self, objects, interface, name=u'', default=None): return self.adapters.queryMultiAdapter( objects, interface, name, default) def getMultiAdapter(self, objects, interface, name=u''): adapter = self.adapters.queryMultiAdapter(objects, interface, name) if adapter is None: raise ComponentLookupError(objects, interface, name) return adapter def getAdapters(self, objects, provided): for name, factory in self.adapters.lookupAll( map(providedBy, objects), provided): adapter = factory(*objects) if adapter is not None: yield name, adapter def registerSubscriptionAdapter(self, factory, required=None, provided=None, name=u'', info=u'', event=True): if name: raise TypeError("Named subscribers are not yet supported") if provided is None: provided = _getAdapterProvided(factory) required = _getAdapterRequired(factory, required) self._subscription_registrations.append( (required, provided, name, factory, info) ) self.adapters.subscribe(required, provided, factory) if event: notify(Registered( SubscriptionRegistration(self, required, provided, name, factory, info) )) def registeredSubscriptionAdapters(self): for data in self._subscription_registrations: yield SubscriptionRegistration(self, *data) def unregisterSubscriptionAdapter(self, factory=None, required=None, provided=None, name=u'', ): if name: raise TypeError("Named subscribers are not yet supported") if provided is None: if factory is None: raise TypeError("Must specify one of factory and provided") provided = _getAdapterProvided(factory) if (required is None) and (factory is None): raise TypeError("Must specify one of factory and required") required = _getAdapterRequired(factory, required) if factory is None: new = [(r, p, n, f, i) for (r, p, n, f, i) in self._subscription_registrations if not (r == required and p == provided) ] else: new = [(r, p, n, f, i) for (r, p, n, f, i) in self._subscription_registrations if not (r == required and p == provided and f == factory) ] if len(new) == len(self._subscription_registrations): return False self._subscription_registrations[:] = new self.adapters.unsubscribe(required, provided, factory) notify(Unregistered( SubscriptionRegistration(self, required, provided, name, factory, '') )) return True def subscribers(self, objects, provided): return self.adapters.subscribers(objects, provided) def registerHandler(self, factory, required=None, name=u'', info=u'', event=True): if name: raise TypeError("Named handlers are not yet supported") required = _getAdapterRequired(factory, required) self._handler_registrations.append( (required, name, factory, info) ) self.adapters.subscribe(required, None, factory) if event: notify(Registered( HandlerRegistration(self, required, name, factory, info) )) def registeredHandlers(self): for data in self._handler_registrations: yield HandlerRegistration(self, *data) def unregisterHandler(self, factory=None, required=None, name=u''): if name: raise TypeError("Named subscribers are not yet supported") if (required is None) and (factory is None): raise TypeError("Must specify one of factory and required") required = _getAdapterRequired(factory, required) if factory is None: new = [(r, n, f, i) for (r, n, f, i) in self._handler_registrations if r != required ] else: new = [(r, n, f, i) for (r, n, f, i) in self._handler_registrations if not (r == required and f == factory) ] if len(new) == len(self._handler_registrations): return False self._handler_registrations[:] = new self.adapters.unsubscribe(required, None, factory) notify(Unregistered( HandlerRegistration(self, required, name, factory, '') )) return True def handle(self, *objects): self.adapters.subscribers(objects, None) def _getUtilityProvided(component): provided = list(providedBy(component)) if len(provided) == 1: return provided[0] raise TypeError( "The utility doesn't provide a single interface " "and no provided interface was specified.") def _getAdapterProvided(factory): provided = list(implementedBy(factory)) if len(provided) == 1: return provided[0] raise TypeError( "The adapter factory doesn't implement a single interface " "and no provided interface was specified.") classTypes = type, types.ClassType def _getAdapterRequired(factory, required): if required is None: try: required = factory.__component_adapts__ except AttributeError: raise TypeError( "The adapter factory doesn't have a __component_adapts__ " "attribute and no required specifications were specified" ) elif ISpecification.providedBy(required): raise TypeError("the required argument should be a list of " "interfaces, not a single interface") result = [] for r in required: if r is None: r = Interface elif not ISpecification.providedBy(r): if isinstance(r, classTypes): r = implementedBy(r) else: raise TypeError("Required specification must be a " "specification or class." ) result.append(r) return tuple(result) class UtilityRegistration(object): implements(IUtilityRegistration) def __init__(self, registry, provided, name, component, doc, factory=None): (self.registry, self.provided, self.name, self.component, self.info, self.factory ) = registry, provided, name, component, doc, factory def __repr__(self): return '%s(%r, %s, %r, %s, %r, %r)' % ( self.__class__.__name__, self.registry, getattr(self.provided, '__name__', None), self.name, getattr(self.component, '__name__', `self.component`), self.factory, self.info, ) def __cmp__(self, other): return cmp(self.__repr__(), other.__repr__()) class AdapterRegistration(object): implements(IAdapterRegistration) def __init__(self, registry, required, provided, name, component, doc): (self.registry, self.required, self.provided, self.name, self.factory, self.info ) = registry, required, provided, name, component, doc def __repr__(self): return '%s(%r, %s, %s, %r, %s, %r)' % ( self.__class__.__name__, self.registry, '[' + ", ".join([r.__name__ for r in self.required]) + ']', getattr(self.provided, '__name__', None), self.name, getattr(self.factory, '__name__', `self.factory`), self.info, ) def __cmp__(self, other): return cmp(self.__repr__(), other.__repr__()) class SubscriptionRegistration(AdapterRegistration): implementsOnly(ISubscriptionAdapterRegistration) class HandlerRegistration(AdapterRegistration): implementsOnly(IHandlerRegistration) def __init__(self, registry, required, name, handler, doc): (self.registry, self.required, self.name, self.handler, self.info ) = registry, required, name, handler, doc @property def factory(self): return self.handler provided = None def __repr__(self): return '%s(%r, %s, %r, %s, %r)' % ( self.__class__.__name__, self.registry, '[' + ", ".join([r.__name__ for r in self.required]) + ']', self.name, getattr(self.factory, '__name__', `self.factory`), self.info, ) @adapter(IUtilityRegistration, IRegistrationEvent) def dispatchUtilityRegistrationEvent(registration, event): handle(registration.component, event) @adapter(IAdapterRegistration, IRegistrationEvent) def dispatchAdapterRegistrationEvent(registration, event): handle(registration.factory, event) @adapter(ISubscriptionAdapterRegistration, IRegistrationEvent) def dispatchSubscriptionAdapterRegistrationEvent(registration, event): handle(registration.factory, event) @adapter(IHandlerRegistration, IRegistrationEvent) def dispatchHandlerRegistrationEvent(registration, event): handle(registration.handler, event) zope2.13-2.13.21/source/zope.component/src/zope/component/zcml.txt0000644000175000017500000007446012214017426023703 0ustar arnauarnauZCML directives =============== Components may be registered using the registration API exposed by ``zope.component`` (provideAdapter, provideUtility, etc.). They may also be registered using configuration files. The common way to do that is by using ZCML (Zope Configuration Markup Language), an XML spelling of component registration. In ZCML, each XML element is a *directive*. There are different top-level directives that let us register components. We will introduce them one by one here. This helper will let us easily execute ZCML snippets: >>> from cStringIO import StringIO >>> from zope.configuration.xmlconfig import xmlconfig >>> def runSnippet(snippet): ... template = """\ ... ... %s ... """ ... xmlconfig(StringIO(template % snippet)) adapter ------- Adapters play a key role in the Component Architecture. In ZCML, they are registered with the directive. >>> from zope.component.testfiles.adapter import A1, A2, A3, Handler >>> from zope.component.testfiles.adapter import I1, I2, I3, IS >>> from zope.component.testfiles.components import IContent, Content, Comp, comp Before we register the first test adapter, we can verify that adapter lookup doesn't work yet: >>> from zope.component.tests import clearZCML >>> clearZCML() >>> from zope.component.testfiles.components import IApp >>> IApp(Content(), None) is None True Then we register the adapter and see that the lookup works: >>> runSnippet(''' ... ''') >>> IApp(Content()).__class__ It is also possible to give adapters names. Then the combination of required interface, provided interface and name makes the adapter lookup unique. The name is supplied using the ``name`` argument to the directive: >>> from zope.component.tests import clearZCML >>> clearZCML() >>> import zope.component >>> zope.component.queryAdapter(Content(), IApp, 'test') is None True >>> runSnippet(''' ... ''') >>> zope.component.getAdapter(Content(), IApp, 'test').__class__ Adapter factories ~~~~~~~~~~~~~~~~~ It is possible to supply more than one adapter factory. In this case, during adapter lookup each factory will be called and the return value will be given to the next factory. The return value of the last factory is returned as the result of the adapter lookup. For examle: >>> clearZCML() >>> runSnippet(''' ... ''') The resulting adapter is an A3, around an A2, around an A1, around the adapted object: >>> content = Content() >>> a3 = IApp(content) >>> a3.__class__ is A3 True >>> a2 = a3.context[0] >>> a2.__class__ is A2 True >>> a1 = a2.context[0] >>> a1.__class__ is A1 True >>> a1.context[0] is content True Of course, if no factory is provided at all, we will get an error: >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-8.8 ValueError: No factory specified Declaring ``for`` and ``provides`` in Python ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The directive can figure out from the in-line Python declaration (using ``zope.component.adapts()`` or ``zope.component.adapter()`` as well as ``zope.interface.implements``) what the adapter should be registered for and what it provides:: >>> clearZCML() >>> IApp(Content(), None) is None True >>> runSnippet(''' ... ''') >>> IApp(Content()).__class__ Of course, if the adapter has no ``implements()`` declaration, ZCML can't figure out what it provides: >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-7.8 TypeError: Missing 'provides' attribute On the other hand, if the factory implements more than one interface, ZCML can't figure out what it should provide either: >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-7.8 TypeError: Missing 'provides' attribute A not so common edge case is registering adapters directly for classes, not for interfaces. For example: >>> clearZCML() >>> runSnippet(''' ... ''') >>> content = Content() >>> a1 = zope.component.getAdapter(content, I1, '') >>> isinstance(a1, A1) True This time, any object providing ``IContent`` won't work if it's not an instance of the ``Content`` class: >>> import zope.interface >>> class MyContent: ... zope.interface.implements(IContent) >>> zope.component.getAdapter(MyContent(), I1, '') # doctest: +ELLIPSIS Traceback (most recent call last): ... ComponentLookupError: ... Multi-adapters ~~~~~~~~~~~~~~ Conventional adapters adapt one object to provide another interface. Multi-adapters adapt several objects at once: >>> clearZCML() >>> runSnippet(''' ... ''') >>> content = Content() >>> a1 = A1() >>> a2 = A2() >>> a3 = zope.component.queryMultiAdapter((content, a1, a2), I3) >>> a3.__class__ is A3 True >>> a3.context == (content, a1, a2) True You can even adapt an empty list of objects (we call this a null-adapter): >>> clearZCML() >>> runSnippet(''' ... ''') >>> a3 = zope.component.queryMultiAdapter((), I3) >>> a3.__class__ is A3 True >>> a3.context == () True Even with multi-adapters, ZCML can figure out the ``for`` and ``provides`` parameters from the Python declarations: >>> clearZCML() >>> runSnippet(''' ... ''') >>> a3 = zope.component.queryMultiAdapter((content, a1, a2), I3) >>> a3.__class__ is A3 True >>> a3.context == (content, a1, a2) True Chained factories are not supported for multi-adapters, though: >>> clearZCML() >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-11.8 ValueError: Can't use multiple factories and multiple for And neither for null-adapters: >>> clearZCML() >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-9.8 ValueError: Can't use multiple factories and multiple for Protected adapters ~~~~~~~~~~~~~~~~~~ Adapters can be protected with a permission. First we have to define a permission for which we'll have to register the directive: >>> clearZCML() >>> IApp(Content(), None) is None True >>> import zope.security >>> from zope.configuration.xmlconfig import XMLConfig >>> XMLConfig('meta.zcml', zope.security)() >>> runSnippet(''' ... ... ''') We see that the adapter is a location proxy now so that the appropriate permissions can be found from the context: >>> IApp(Content()).__class__ >>> type(IApp(Content())) We can also go about it a different way. Let's make a public adapter and wrap the adapter in a security proxy. That often happens when an adapter is turned over to untrusted code: >>> clearZCML() >>> IApp(Content(), None) is None True >>> runSnippet(''' ... ''') >>> from zope.security.checker import ProxyFactory >>> adapter = ProxyFactory(IApp(Content())) >>> from zope.security.proxy import getTestProxyItems >>> items = [item[0] for item in getTestProxyItems(adapter)] >>> items ['a', 'f'] >>> from zope.security.proxy import removeSecurityProxy >>> removeSecurityProxy(adapter).__class__ is Comp True Of course, this still works when we let the ZCML directive handler figure out ``for`` and ``provides`` from the Python declarations: >>> clearZCML() >>> runSnippet(''' ... ''') >>> adapter = ProxyFactory(IApp(Content())) >>> [item[0] for item in getTestProxyItems(adapter)] ['a', 'f'] >>> removeSecurityProxy(adapter).__class__ is Comp True It also works with multi adapters: >>> clearZCML() >>> runSnippet(''' ... ''') >>> content = Content() >>> a1 = A1() >>> a2 = A2() >>> a3 = ProxyFactory(zope.component.queryMultiAdapter((content, a1, a2), I3)) >>> a3.__class__ == A3 True >>> [item[0] for item in getTestProxyItems(a3)] ['f1', 'f2', 'f3'] It's probably not worth mentioning, but when we try to protect an adapter with a permission that doesn't exist, we'll obviously get an error: >>> clearZCML() >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ConfigurationExecutionError: exceptions.ValueError: ('Undefined permission id', 'zope.UndefinedPermission') in: File "", line 4.2-9.8 Could not read source. Trusted adapters ~~~~~~~~~~~~~~~~ Trusted adapters are adapters that are trusted to do anything with the objects they are given so that these objects are not security-proxied. They are registered using the ``trusted`` argument to the directive: >>> clearZCML() >>> runSnippet(''' ... ''') With an unproxied object, it's business as usual: >>> ob = Content() >>> type(I1(ob)) is A1 True With a security-proxied object, however, we get a security-proxied adapter: >>> p = ProxyFactory(ob) >>> a = I1(p) >>> type(a) While the adapter is security-proxied, the object it adapts is now proxy-free. The adapter has umlimited access to it: >>> a = removeSecurityProxy(a) >>> type(a) is A1 True >>> a.context[0] is ob True We can also protect the trusted adapter with a permission: >>> clearZCML() >>> XMLConfig('meta.zcml', zope.security)() >>> runSnippet(''' ... ... ''') Again, with an unproxied object, it's business as usual: >>> ob = Content() >>> type(I1(ob)) is A1 True With a security-proxied object, we again get a security-proxied adapter: >>> p = ProxyFactory(ob) >>> a = I1(p) >>> type(a) Since we protected the adapter with a permission, we now encounter a location proxy behind the security proxy: >>> a = removeSecurityProxy(a) >>> type(a) >>> a.context[0] is ob True There's one exception to all of this: When you use the public permission (``zope.Public``), there will be no location proxy: >>> clearZCML() >>> runSnippet(''' ... ''') >>> ob = Content() >>> p = ProxyFactory(ob) >>> a = I1(p) >>> type(a) >>> a = removeSecurityProxy(a) >>> type(a) is A1 True We can also explicitply pass the ``locate`` argument to make sure we get location proxies: >>> clearZCML() >>> runSnippet(''' ... ''') >>> ob = Content() >>> p = ProxyFactory(ob) >>> a = I1(p) >>> type(a) >>> a = removeSecurityProxy(a) >>> type(a) subscriber ---------- With the directive you can register subscription adapters or event subscribers with the adapter registry. Consider this very typical example of a directive: >>> clearZCML() >>> runSnippet(''' ... ''') >>> content = Content() >>> a1 = A1() >>> subscribers = zope.component.subscribers((content, a1), IS) >>> a3 = subscribers[0] >>> a3.__class__ is A3 True >>> a3.context == (content, a1) True Note how ZCML provides some additional information when registering components, such as the ZCML filename and line numbers: >>> sm = zope.component.getSiteManager() >>> doc = [reg.info for reg in sm.registeredSubscriptionAdapters() ... if reg.provided is IS][0] >>> print doc File "", line 4.2-9.8 Could not read source. The "fun" behind subscription adapters/subscribers is that when several ones are declared for the same for/provides, they are all found. With regular adapters, the most specific one (and in doubt the one registered last) wins. Consider these two subscribers: >>> clearZCML() >>> runSnippet(''' ... ... ''') >>> subscribers = zope.component.subscribers((content, a1), IS) >>> len(subscribers) 2 >>> sorted([a.__class__.__name__ for a in subscribers]) ['A2', 'A3'] Declaring ``for`` and ``provides`` in Python ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Like the directive, the directive can figure out from the in-line Python declaration (using ``zope.component.adapts()`` or ``zope.component.adapter()``) what the subscriber should be registered for: >>> clearZCML() >>> runSnippet(''' ... ''') >>> content = Content() >>> a2 = A2() >>> subscribers = zope.component.subscribers((content, a1, a2), IS) >>> a3 = subscribers[0] >>> a3.__class__ is A3 True >>> a3.context == (content, a1, a2) True In the same way the directive can figure out what a subscriber provides: >>> clearZCML() >>> runSnippet(''' ... ''') >>> sm = zope.component.getSiteManager() >>> a3 = sm.adapters.subscriptions((IContent, I1, I2), None)[0] >>> a3 is A3 True A not so common edge case is declaring subscribers directly for classes, not for interfaces. For example: >>> clearZCML() >>> runSnippet(''' ... ''') >>> subs = list(zope.component.subscribers((Content(),), I1)) >>> isinstance(subs[0], A1) True This time, any object providing ``IContent`` won't work if it's not an instance of the ``Content`` class: >>> list(zope.component.subscribers((MyContent(),), I1)) [] Protected subscribers ~~~~~~~~~~~~~~~~~~~~~ Subscribers can also be protected with a permission. First we have to define a permission for which we'll have to register the directive: >>> clearZCML() >>> XMLConfig('meta.zcml', zope.security)() >>> runSnippet(''' ... ... ''') >>> subscribers = zope.component.subscribers((content, a1), IS) >>> a3 = subscribers[0] >>> a3.__class__ is A3 True >>> type(a3) >>> a3.context == (content, a1) True Trusted subscribers ~~~~~~~~~~~~~~~~~~~ Like trusted adapters, trusted subscribers are subscribers that are trusted to do anything with the objects they are given so that these objects are not security-proxied. In analogy to the directive, they are registered using the ``trusted`` argument to the directive: >>> clearZCML() >>> runSnippet(''' ... ''') With an unproxied object, it's business as usual: >>> subscribers = zope.component.subscribers((content, a1), IS) >>> a3 = subscribers[0] >>> a3.__class__ is A3 True >>> a3.context == (content, a1) True >>> type(a3) is A3 True Now with a proxied object. We will see that the subscriber has unproxied access to it, but the subscriber itself is proxied: >>> p = ProxyFactory(content) >>> a3 = zope.component.subscribers((p, a1), IS)[0] >>> type(a3) There's no location proxy behind the security proxy: >>> removeSecurityProxy(a3).context[0] is content True >>> type(removeSecurityProxy(a3)) is A3 True If you want the trusted subscriber to be located, you'll also have to use the ``locate`` argument: >>> clearZCML() >>> runSnippet(''' ... ''') Again, it's business as usual with an unproxied object: >>> subscribers = zope.component.subscribers((content, a1), IS) >>> a3 = subscribers[0] >>> a3.__class__ is A3 True >>> a3.context == (content, a1) True >>> type(a3) is A3 True With a proxied object, we again get a security-proxied subscriber: >>> p = ProxyFactory(content) >>> a3 = zope.component.subscribers((p, a1), IS)[0] >>> type(a3) >>> removeSecurityProxy(a3).context[0] is content True However, thanks to the ``locate`` argument, we now have a location proxy behind the security proxy: >>> type(removeSecurityProxy(a3)) Event subscriber (handlers) ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sometimes, subscribers don't need to be adapters that actually provide anything. It's enough that a callable is called for a certain event. >>> clearZCML() >>> runSnippet(''' ... ''') In this case, simply getting the subscribers is enough to invoke them: >>> list(zope.component.subscribers((content, a1), None)) [] >>> content.args == ((a1,),) True utility ------- Apart from adapters (and subscription adapters), the Component Architecture knows a second kind of component: utilities. They are registered using the directive. Before we register the first test utility, we can verify that utility lookup doesn't work yet: >>> clearZCML() >>> zope.component.queryUtility(IApp) is None True Then we register the utility: >>> runSnippet(''' ... ''') >>> zope.component.getUtility(IApp) is comp True Like adapters, utilities can also have names. There can be more than one utility registered for a certain interface, as long as they each have a different name. First, we make sure that there's no utility yet: >>> clearZCML() >>> zope.component.queryUtility(IApp, 'test') is None True Then we register it: >>> runSnippet(''' ... ''') >>> zope.component.getUtility(IApp, 'test') is comp True Utilities can also be registered from a factory. In this case, the ZCML handler calls the factory (without any arguments) and registers the returned value as a utility. Typically, you'd pass a class for the factory: >>> clearZCML() >>> zope.component.queryUtility(IApp) is None True >>> runSnippet(''' ... ''') >>> zope.component.getUtility(IApp).__class__ is Comp True Declaring ``provides`` in Python ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Like other directives, can also figure out which interface a utility provides from the Python declaration: >>> clearZCML() >>> zope.component.queryUtility(IApp) is None True >>> runSnippet(''' ... ''') >>> zope.component.getUtility(IApp) is comp True It won't work if the component that is to be registered doesn't provide anything: >>> clearZCML() >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-4.61 TypeError: Missing 'provides' attribute Or if more than one interface is provided (then the ZCML directive handler doesn't know under which the utility should be registered): >>> clearZCML() >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-4.61 TypeError: Missing 'provides' attribute We can repeat the same drill for utility factories: >>> clearZCML() >>> runSnippet(''' ... ''') >>> zope.component.getUtility(IApp).__class__ is Comp True >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-4.59 TypeError: Missing 'provides' attribute >>> clearZCML() >>> runSnippet(''' ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 4.2-4.59 TypeError: Missing 'provides' attribute Protected utilities ~~~~~~~~~~~~~~~~~~~ TODO:: def testProtectedUtility(self): """Test that we can protect a utility. Also: Check that multiple configurations for the same utility and don't interfere. """ self.assertEqual(zope.component.queryUtility(IV), None) xmlconfig(StringIO(template % ( ''' ''' ))) utility = ProxyFactory(zope.component.getUtility(IApp)) items = getTestProxyItems(utility) self.assertEqual(items, [('a', 'tell.everyone'), ('f', 'tell.everyone') ]) self.assertEqual(removeSecurityProxy(utility), comp) def testUtilityUndefinedPermission(self): config = StringIO(template % ( ''' ''' )) self.assertRaises(ValueError, xmlconfig, config, testing=1) interface --------- The directive lets us register an interface. Interfaces are registered as named utilities. We therefore needn't go though all the lookup details again, it is sufficient to see whether the directive handler emits the right actions. First we provide a stub configuration context: >>> import re, pprint >>> atre = re.compile(' at [0-9a-fA-Fx]+') >>> class Context(object): ... actions = () ... def action(self, discriminator, callable, args): ... self.actions += ((discriminator, callable, args), ) ... def __repr__(self): ... stream = StringIO() ... pprinter = pprint.PrettyPrinter(stream=stream, width=60) ... pprinter.pprint(self.actions) ... r = stream.getvalue() ... return (''.join(atre.split(r))).strip() >>> context = Context() Then we provide a test interface that we'd like to register: >>> from zope.interface import Interface >>> class I(Interface): ... pass It doesn't yet provide ``ITestType``: >>> from zope.component.tests import ITestType >>> ITestType.providedBy(I) False However, after calling the directive handler... >>> from zope.component.zcml import interface >>> interface(context, I, ITestType) >>> context ((None, , ('', , )),) ...it does provide ``ITestType``: >>> from zope.interface.interfaces import IInterface >>> ITestType.extends(IInterface) True >>> IInterface.providedBy(I) True zope2.13-2.13.21/source/zope.component/src/zope/component/event.txt0000644000175000017500000000643712214017426024056 0ustar arnauarnauEvents ====== The Component Architecture provides a way to dispatch events to event handlers. Event handlers are registered as *subscribers* a.k.a. *handlers*. Before we can start we need to import ``zope.component.event`` to make the dispatching effective: >>> import zope.component.event Consider two event classes: >>> class Event1(object): ... pass >>> class Event2(Event1): ... pass Now consider two handlers for these event classes: >>> called = [] >>> import zope.component >>> @zope.component.adapter(Event1) ... def handler1(event): ... called.append(1) >>> @zope.component.adapter(Event2) ... def handler2(event): ... called.append(2) We can register them with the Component Architecture: >>> zope.component.provideHandler(handler1) >>> zope.component.provideHandler(handler2) Now let's go through the events. We'll see that the handlers have been called accordingly: >>> from zope.event import notify >>> notify(Event1()) >>> called [1] >>> del called[:] >>> notify(Event2()) >>> called.sort() >>> called [1, 2] Object events ------------- The ``objectEventNotify`` function is a subscriber to dispatch ObjectEvents to interested adapters. First create an object class: >>> class IUseless(zope.interface.Interface): ... """Useless object""" >>> class UselessObject(object): ... """Useless object""" ... zope.interface.implements(IUseless) Then create an event class: >>> class IObjectThrownEvent(zope.component.interfaces.IObjectEvent): ... """An object has been thrown away""" >>> class ObjectThrownEvent(zope.component.interfaces.ObjectEvent): ... """An object has been thrown away""" ... zope.interface.implements(IObjectThrownEvent) Create an object and an event: >>> hammer = UselessObject() >>> event = ObjectThrownEvent(hammer) Then notify the event to the subscribers. Since the subscribers list is empty, nothing happens. >>> zope.component.event.objectEventNotify(event) Now create an handler for the event: >>> events = [] >>> def record(*args): ... events.append(args) >>> zope.component.provideHandler(record, [IUseless, IObjectThrownEvent]) The event is notified to the subscriber: >>> zope.component.event.objectEventNotify(event) >>> events == [(hammer, event)] True Following test demonstrates how a subscriber can raise an exception to prevent an action. >>> zope.component.provideHandler(zope.component.event.objectEventNotify) Let's create a container: >>> class ToolBox(dict): ... def __delitem__(self, key): ... notify(ObjectThrownEvent(self[key])) ... return super(ToolBox,self).__delitem__(key) >>> container = ToolBox() And put the object into the container: >>> container['Red Hammer'] = hammer Create an handler function that will raise an error when called: >>> class Veto(Exception): ... pass >>> def callback(item, event): ... assert(item == event.object) ... raise Veto Register the handler: >>> zope.component.provideHandler(callback, [IUseless, IObjectThrownEvent]) Then if we try to remove the object, an ObjectThrownEvent is fired: >>> del container['Red Hammer'] ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... raise Veto Veto zope2.13-2.13.21/source/zope.component/src/zope/component/hooks.py0000644000175000017500000000721212214017426023661 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Hooks for getting and setting a site in the thread global namespace. """ __docformat__ = 'restructuredtext' import threading import zope.component try: import zope.security.proxy except ImportError: SECURITY_SUPPORT = False else: SECURITY_SUPPORT = True class read_property(object): def __init__(self, func): self.func = func def __get__(self, inst, cls): if inst is None: return self return self.func(inst) class SiteInfo(threading.local): site = None sm = zope.component.getGlobalSiteManager() def adapter_hook(self): adapter_hook = self.sm.adapters.adapter_hook self.adapter_hook = adapter_hook return adapter_hook adapter_hook = read_property(adapter_hook) siteinfo = SiteInfo() def setSite(site=None): if site is None: sm = zope.component.getGlobalSiteManager() else: # We remove the security proxy because there's no way for # untrusted code to get at it without it being proxied again. # We should really look look at this again though, especially # once site managers do less. There's probably no good reason why # they can't be proxied. Well, except maybe for performance. if SECURITY_SUPPORT: site = zope.security.proxy.removeSecurityProxy(site) # The getSiteManager method is defined by IPossibleSite. sm = site.getSiteManager() siteinfo.site = site siteinfo.sm = sm try: del siteinfo.adapter_hook except AttributeError: pass def getSite(): return siteinfo.site def getSiteManager(context=None): """A special hook for getting the site manager. Here we take the currently set site into account to find the appropriate site manager. """ if context is None: return siteinfo.sm # We remove the security proxy because there's no way for # untrusted code to get at it without it being proxied again. # We should really look look at this again though, especially # once site managers do less. There's probably no good reason why # they can't be proxied. Well, except maybe for performance. sm = zope.component.interfaces.IComponentLookup( context, zope.component.getGlobalSiteManager()) if SECURITY_SUPPORT: sm = zope.security.proxy.removeSecurityProxy(sm) return sm def adapter_hook(interface, object, name='', default=None): try: return siteinfo.adapter_hook(interface, object, name, default) except zope.component.interfaces.ComponentLookupError: return default def setHooks(): zope.component.adapter_hook.sethook(adapter_hook) zope.component.getSiteManager.sethook(getSiteManager) def resetHooks(): # Reset hookable functions to original implementation. zope.component.adapter_hook.reset() zope.component.getSiteManager.reset() # Clear the site thread global clearSite = setSite try: from zope.testing.cleanup import addCleanUp except ImportError: pass else: addCleanUp(resetHooks) zope2.13-2.13.21/source/zope.component/src/zope/component/persistentregistry.txt0000644000175000017500000000036712214017426026722 0ustar arnauarnauPersistent Component Management =============================== Persistent component management allows persistent management of components. From a usage point of view, there shouldn't be any new behavior beyond what's described in registry.txt. zope2.13-2.13.21/source/zope.component/src/zope/component/hooks.txt0000644000175000017500000000355412214017426024055 0ustar arnauarnau============================== The current component registry ============================== There can be any number of component registries in an application. One of them is the global component registry, and there is also the concept of a currently used component registry. Component registries other than the global one are associated with objects called sites. The ``zope.component.hooks`` module provides an API to set and access the current site as well as manipulate the adapter hook associated with it. As long as we haven't set a site, none is being considered current: >>> from zope.component.hooks import getSite >>> print getSite() None We can also ask for the current component registry (aka site manager historically); it will return the global one if no current site is set: >>> from zope.component.hooks import getSiteManager >>> getSiteManager() Let's set a site now. A site has to be an object that provides the ``getSiteManager`` method, which is specified by ``zope.component.interfaces.IPossibleSite``: >>> from zope.component.registry import Components >>> class Site(object): ... def __init__(self): ... self.registry = Components('components') ... def getSiteManager(self): ... return self.registry >>> from zope.component.hooks import setSite >>> site1 = Site() >>> setSite(site1) After this, the newly set site is considered the currently active one: >>> getSite() is site1 True >>> getSiteManager() is site1.registry True If we set another site, that one will be considered current: >>> site2 = Site() >>> site2.registry is not site1.registry True >>> setSite(site2) >>> getSite() is site2 True >>> getSiteManager() is site2.registry True Finally we can unset the site and the global component registry is used again: >>> setSite() >>> print getSite() None >>> getSiteManager() zope2.13-2.13.21/source/zope.component/src/zope/component/eventtesting.py0000644000175000017500000000353612214017426025262 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Placeless Test Setup """ from zope.component import provideHandler from zope.component.event import objectEventNotify from zope.component.registry import dispatchUtilityRegistrationEvent from zope.component.registry import dispatchAdapterRegistrationEvent from zope.component.registry import ( dispatchSubscriptionAdapterRegistrationEvent) from zope.component.registry import dispatchHandlerRegistrationEvent from zope.testing import cleanup events = [] def getEvents(event_type=None, filter=None): r = [] for event in events: if event_type is not None and not event_type.providedBy(event): continue if filter is not None and not filter(event): continue r.append(event) return r def clearEvents(): del events[:] cleanup.addCleanUp(clearEvents) class PlacelessSetup: def setUp(self): provideHandler(objectEventNotify) provideHandler(dispatchUtilityRegistrationEvent) provideHandler(dispatchAdapterRegistrationEvent) provideHandler(dispatchSubscriptionAdapterRegistrationEvent) provideHandler(dispatchHandlerRegistrationEvent) provideHandler(events.append, (None,)) def setUp(test=None): PlacelessSetup().setUp() zope2.13-2.13.21/source/zope.component/src/zope/component/interfaces.py0000644000175000017500000010157312214017426024666 0ustar arnauarnau############################################################################ # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################ """Component and Component Architecture Interfaces """ __docformat__ = "reStructuredText" from zope.interface import Attribute from zope.interface import Interface from zope.interface import implements class ComponentLookupError(LookupError): """A component could not be found.""" class Invalid(Exception): """A component doesn't satisfy a promise.""" class Misused(Exception): """A component is being used (registered) for the wrong interface.""" class IObjectEvent(Interface): """An event related to an object. The object that generated this event is not necessarily the object refered to by location. """ object = Attribute("The subject of the event.") class ObjectEvent(object): implements(IObjectEvent) def __init__(self, object): self.object = object class IComponentArchitecture(Interface): """The Component Architecture is defined by two key components: Adapters and Utiltities. Both are managed by site managers. All other components build on top of them. """ # Site Manager API def getGlobalSiteManager(): """Return the global site manager. This function should never fail and always return an object that provides `IGlobalSiteManager`. """ def getSiteManager(context=None): """Get the nearest site manager in the given context. If `context` is `None`, return the global site manager. If the `context` is not `None`, it is expected that an adapter from the `context` to `IComponentLookup` can be found. If no adapter is found, a `ComponentLookupError` is raised. """ # Utility API def getUtility(interface, name='', context=None): """Get the utility that provides interface Returns the nearest utility to the context that implements the specified interface. If one is not found, raises ComponentLookupError. """ def queryUtility(interface, name='', default=None, context=None): """Look for the utility that provides interface Returns the nearest utility to the context that implements the specified interface. If one is not found, returns default. """ def queryNextUtility(context, interface, name='', default=None): """Query for the next available utility. Find the next available utility providing `interface` and having the specified name. If no utility was found, return the specified `default` value. """ def getNextUtility(context, interface, name=''): """Get the next available utility. If no utility was found, a `ComponentLookupError` is raised. """ def getUtilitiesFor(interface, context=None): """Return the utilities that provide an interface An iterable of utility name-value pairs is returned. """ def getAllUtilitiesRegisteredFor(interface, context=None): """Return all registered utilities for an interface This includes overridden utilities. An iterable of utility instances is returned. No names are returned. """ # Adapter API def getAdapter(object, interface=Interface, name=u'', context=None): """Get a named adapter to an interface for an object Returns an adapter that can adapt object to interface. If a matching adapter cannot be found, raises ComponentLookupError. If context is None, an application-defined policy is used to choose an appropriate service manager from which to get an 'Adapters' service. If 'context' is not None, context is adapted to IServiceService, and this adapter's 'Adapters' service is used. """ def getAdapterInContext(object, interface, context): """Get a special adapter to an interface for an object NOTE: This method should only be used if a custom context needs to be provided to provide custom component lookup. Otherwise, call the interface, as in:: interface(object) Returns an adapter that can adapt object to interface. If a matching adapter cannot be found, raises ComponentLookupError. Context is adapted to IServiceService, and this adapter's 'Adapters' service is used. If the object has a __conform__ method, this method will be called with the requested interface. If the method returns a non-None value, that value will be returned. Otherwise, if the object already implements the interface, the object will be returned. """ def getMultiAdapter(objects, interface=Interface, name='', context=None): """Look for a multi-adapter to an interface for an objects Returns a multi-adapter that can adapt objects to interface. If a matching adapter cannot be found, raises ComponentLookupError. If context is None, an application-defined policy is used to choose an appropriate service manager from which to get an 'Adapters' service. If 'context' is not None, context is adapted to IServiceService, and this adapter's 'Adapters' service is used. The name consisting of an empty string is reserved for unnamed adapters. The unnamed adapter methods will often call the named adapter methods with an empty string for a name. """ def queryAdapter(object, interface=Interface, name=u'', default=None, context=None): """Look for a named adapter to an interface for an object Returns an adapter that can adapt object to interface. If a matching adapter cannot be found, returns the default. If context is None, an application-defined policy is used to choose an appropriate service manager from which to get an 'Adapters' service. If 'context' is not None, context is adapted to IServiceService, and this adapter's 'Adapters' service is used. """ def queryAdapterInContext(object, interface, context, default=None): """Look for a special adapter to an interface for an object NOTE: This method should only be used if a custom context needs to be provided to provide custom component lookup. Otherwise, call the interface, as in:: interface(object, default) Returns an adapter that can adapt object to interface. If a matching adapter cannot be found, returns the default. Context is adapted to IServiceService, and this adapter's 'Adapters' service is used. If the object has a __conform__ method, this method will be called with the requested interface. If the method returns a non-None value, that value will be returned. Otherwise, if the object already implements the interface, the object will be returned. """ def queryMultiAdapter(objects, interface=Interface, name=u'', default=None, context=None): """Look for a multi-adapter to an interface for objects Returns a multi-adapter that can adapt objects to interface. If a matching adapter cannot be found, returns the default. If context is None, an application-defined policy is used to choose an appropriate service manager from which to get an 'Adapters' service. If 'context' is not None, context is adapted to IServiceService, and this adapter's 'Adapters' service is used. The name consisting of an empty string is reserved for unnamed adapters. The unnamed adapter methods will often call the named adapter methods with an empty string for a name. """ def getAdapters(objects, provided, context=None): """Look for all matching adapters to a provided interface for objects Return a list of adapters that match. If an adapter is named, only the most specific adapter of a given name is returned. If context is None, an application-defined policy is used to choose an appropriate service manager from which to get an 'Adapters' service. If 'context' is not None, context is adapted to IServiceService, and this adapter's 'Adapters' service is used. """ def subscribers(required, provided, context=None): """Get subscribers Subscribers are returned that provide the provided interface and that depend on and are computed from the sequence of required objects. If context is None, an application-defined policy is used to choose an appropriate service manager from which to get an 'Adapters' service. If 'context' is not None, context is adapted to IServiceService, and this adapter's 'Adapters' service is used. """ def handle(*objects): """Call all of the handlers for the given objects Handlers are subscription adapter factories that don't produce anything. They do all of their work when called. Handlers are typically used to handle events. """ def adapts(*interfaces): """Declare that a class adapts the given interfaces. This function can only be used in a class definition. (TODO, allow classes to be passed as well as interfaces.) """ # Factory service def createObject(factory_name, *args, **kwargs): """Create an object using a factory Finds the named factory in the current site and calls it with the given arguments. If a matching factory cannot be found raises ComponentLookupError. Returns the created object. A context keyword argument can be provided to cause the factory to be looked up in a location other than the current site. (Of course, this means that it is impossible to pass a keyword argument named "context" to the factory. """ def getFactoryInterfaces(name, context=None): """Get interfaces implemented by a factory Finds the factory of the given name that is nearest to the context, and returns the interface or interface tuple that object instances created by the named factory will implement. """ def getFactoriesFor(interface, context=None): """Return a tuple (name, factory) of registered factories that create objects which implement the given interface. """ class IComponentLookup(Interface): """Component Manager for a Site This object manages the components registered at a particular site. The definition of a site is intentionally vague. """ adapters = Attribute( "Adapter Registry to manage all registered adapters.") utilities = Attribute( "Adapter Registry to manage all registered utilities.") def queryAdapter(object, interface, name=u'', default=None): """Look for a named adapter to an interface for an object If a matching adapter cannot be found, returns the default. """ def getAdapter(object, interface, name=u''): """Look for a named adapter to an interface for an object If a matching adapter cannot be found, a ComponentLookupError is raised. """ def queryMultiAdapter(objects, interface, name=u'', default=None): """Look for a multi-adapter to an interface for multiple objects If a matching adapter cannot be found, returns the default. """ def getMultiAdapter(objects, interface, name=u''): """Look for a multi-adapter to an interface for multiple objects If a matching adapter cannot be found, a ComponentLookupError is raised. """ def getAdapters(objects, provided): """Look for all matching adapters to a provided interface for objects Return an iterable of name-adapter pairs for adapters that provide the given interface. """ def subscribers(objects, provided): """Get subscribers Subscribers are returned that provide the provided interface and that depend on and are comuted from the sequence of required objects. """ def handle(*objects): """Call handlers for the given objects Handlers registered for the given objects are called. """ def queryUtility(interface, name='', default=None): """Look up a utility that provides an interface. If one is not found, returns default. """ def getUtilitiesFor(interface): """Look up the registered utilities that provide an interface. Returns an iterable of name-utility pairs. """ def getAllUtilitiesRegisteredFor(interface): """Return all registered utilities for an interface This includes overridden utilities. An iterable of utility instances is returned. No names are returned. """ class IComponentRegistrationConvenience(Interface): """API for registering components. CAUTION: This API should only be used from test or application-setup code. This api shouldn't be used by regular library modules, as component registration is a configuration activity. """ def provideUtility(component, provides=None, name=u''): """Register a utility globally A utility is registered to provide an interface with a name. If a component provides only one interface, then the provides argument can be omitted and the provided interface will be used. (In this case, provides argument can still be provided to provide a less specific interface.) CAUTION: This API should only be used from test or application-setup code. This API shouldn't be used by regular library modules, as component registration is a configuration activity. """ def provideAdapter(factory, adapts=None, provides=None, name=u''): """Register an adapter globally An adapter is registered to provide an interface with a name for some number of object types. If a factory implements only one interface, then the provides argument can be omitted and the provided interface will be used. (In this case, a provides argument can still be provided to provide a less specific interface.) If the factory has an adapts declaration, then the adapts argument can be omitted and the declaration will be used. (An adapts argument can be provided to override the declaration.) CAUTION: This API should only be used from test or application-setup code. This API shouldn't be used by regular library modules, as component registration is a configuration activity. """ def provideSubscriptionAdapter(factory, adapts=None, provides=None): """Register a subscription adapter A subscription adapter is registered to provide an interface for some number of object types. If a factory implements only one interface, then the provides argument can be omitted and the provided interface will be used. (In this case, a provides argument can still be provided to provide a less specific interface.) If the factory has an adapts declaration, then the adapts argument can be omitted and the declaration will be used. (An adapts argument can be provided to override the declaration.) CAUTION: This API should only be used from test or application-setup code. This API shouldn't be used by regular library modules, as component registration is a configuration activity. """ def provideHandler(handler, adapts=None): """Register a handler Handlers are subscription adapter factories that don't produce anything. They do all of their work when called. Handlers are typically used to handle events. If the handler has an adapts declaration, then the adapts argument can be omitted and the declaration will be used. (An adapts argument can be provided to override the declaration.) CAUTION: This API should only be used from test or application-setup code. This API shouldn't be used by regular library modules, as component registration is a configuration activity. """ class IRegistry(Interface): """Object that supports component registry """ def registrations(): """Return an iterable of component registrations """ class IFactory(Interface): """A factory is responsible for creating other components.""" title = Attribute("The factory title.") description = Attribute("A brief description of the factory.") def __call__(*args, **kw): """Return an instance of the objects we're a factory for.""" def getInterfaces(): """Get the interfaces implemented by the factory Return the interface(s), as an instance of Implements, that objects created by this factory will implement. If the callable's Implements instance cannot be created, an empty Implements instance is returned. """ class IRegistration(Interface): """A registration-information object """ registry = Attribute("The registry having the registration") name = Attribute("The registration name") info = Attribute("""Information about the registration This is information deemed useful to people browsing the configuration of a system. It could, for example, include commentary or information about the source of the configuration. """) class IUtilityRegistration(IRegistration): """Information about the registration of a utility """ factory = Attribute("The factory used to create the utility. Optional.") component = Attribute("The object registered") provided = Attribute("The interface provided by the component") class _IBaseAdapterRegistration(IRegistration): """Information about the registration of an adapter """ factory = Attribute("The factory used to create adapters") required = Attribute("""The adapted interfaces This is a sequence of interfaces adapters by the registered factory. The factory will be caled with a sequence of objects, as positional arguments, that provide these interfaces. """) provided = Attribute("""The interface provided by the adapters. This interface is implemented by the factory """) class IAdapterRegistration(_IBaseAdapterRegistration): """Information about the registration of an adapter """ class ISubscriptionAdapterRegistration(_IBaseAdapterRegistration): """Information about the registration of a subscription adapter """ class IHandlerRegistration(IRegistration): handler = Attribute("An object called used to handle an event") required = Attribute("""The handled interfaces This is a sequence of interfaces handled by the registered handler. The handler will be caled with a sequence of objects, as positional arguments, that provide these interfaces. """) class IRegistrationEvent(IObjectEvent): """An event that involves a registration""" class RegistrationEvent(ObjectEvent): """There has been a change in a registration """ implements(IRegistrationEvent) def __repr__(self): return "%s event:\n%r" % (self.__class__.__name__, self.object) class IRegistered(IRegistrationEvent): """A component or factory was registered """ class Registered(RegistrationEvent): implements(IRegistered) class IUnregistered(IRegistrationEvent): """A component or factory was unregistered """ class Unregistered(RegistrationEvent): """A component or factory was unregistered """ implements(IUnregistered) class IComponentRegistry(Interface): """Register components """ def registerUtility(component=None, provided=None, name=u'', info=u'', factory=None): """Register a utility factory Factory for the component to be registerd. component The registered component provided This is the interface provided by the utility. If the component provides a single interface, then this argument is optional and the component-implemented interface will be used. name The utility name. info An object that can be converted to a string to provide information about the registration. Only one of component and factory can be used. A Registered event is generated with an IUtilityRegistration. """ def unregisterUtility(component=None, provided=None, name=u'', factory=None): """Unregister a utility A boolean is returned indicating whether the registry was changed. If the given component is None and there is no component registered, or if the given component is not None and is not registered, then the function returns False, otherwise it returns True. factory Factory for the component to be unregisterd. component The registered component The given component can be None, in which case any component registered to provide the given provided interface with the given name is unregistered. provided This is the interface provided by the utility. If the component is not None and provides a single interface, then this argument is optional and the component-implemented interface will be used. name The utility name. Only one of component and factory can be used. An UnRegistered event is generated with an IUtilityRegistration. """ def registeredUtilities(): """Return an iterable of IUtilityRegistration instances. These registrations describe the current utility registrations in the object. """ def registerAdapter(factory, required=None, provided=None, name=u'', info=u''): """Register an adapter factory Parameters: factory The object used to compute the adapter required This is a sequence of specifications for objects to be adapted. If omitted, then the value of the factory's __component_adapts__ attribute will be used. The __component_adapts__ attribute is usually attribute is normally set in class definitions using adapts function, or for callables using the adapter decorator. If the factory doesn't have a __component_adapts__ adapts attribute, then this argument is required. provided This is the interface provided by the adapter and implemented by the factory. If the factory implements a single interface, then this argument is optional and the factory-implemented interface will be used. name The adapter name. info An object that can be converted to a string to provide information about the registration. A Registered event is generated with an IAdapterRegistration. """ def unregisterAdapter(factory=None, required=None, provided=None, name=u''): """Register an adapter factory A boolean is returned indicating whether the registry was changed. If the given component is None and there is no component registered, or if the given component is not None and is not registered, then the function returns False, otherwise it returns True. Parameters: factory This is the object used to compute the adapter. The factory can be None, in which case any factory registered to implement the given provided interface for the given required specifications with the given name is unregistered. required This is a sequence of specifications for objects to be adapted. If the factory is not None and the required arguments is omitted, then the value of the factory's __component_adapts__ attribute will be used. The __component_adapts__ attribute attribute is normally set in class definitions using adapts function, or for callables using the adapter decorator. If the factory is None or doesn't have a __component_adapts__ adapts attribute, then this argument is required. provided This is the interface provided by the adapter and implemented by the factory. If the factory is not None and implements a single interface, then this argument is optional and the factory-implemented interface will be used. name The adapter name. An Unregistered event is generated with an IAdapterRegistration. """ def registeredAdapters(): """Return an iterable of IAdapterRegistration instances. These registrations describe the current adapter registrations in the object. """ def registerSubscriptionAdapter(factory, required=None, provides=None, name=u'', info=''): """Register a subscriber factory Parameters: factory The object used to compute the adapter required This is a sequence of specifications for objects to be adapted. If omitted, then the value of the factory's __component_adapts__ attribute will be used. The __component_adapts__ attribute is usually attribute is normally set in class definitions using adapts function, or for callables using the adapter decorator. If the factory doesn't have a __component_adapts__ adapts attribute, then this argument is required. provided This is the interface provided by the adapter and implemented by the factory. If the factory implements a single interface, then this argument is optional and the factory-implemented interface will be used. name The adapter name. Currently, only the empty string is accepted. Other strings will be accepted in the future when support for named subscribers is added. info An object that can be converted to a string to provide information about the registration. A Registered event is generated with an ISubscriptionAdapterRegistration. """ def unregisterSubscriptionAdapter(factory=None, required=None, provides=None, name=u''): """Unregister a subscriber factory. A boolean is returned indicating whether the registry was changed. If the given component is None and there is no component registered, or if the given component is not None and is not registered, then the function returns False, otherwise it returns True. Parameters: factory This is the object used to compute the adapter. The factory can be None, in which case any factories registered to implement the given provided interface for the given required specifications with the given name are unregistered. required This is a sequence of specifications for objects to be adapted. If the factory is not None and the required arguments is omitted, then the value of the factory's __component_adapts__ attribute will be used. The __component_adapts__ attribute attribute is normally set in class definitions using adapts function, or for callables using the adapter decorator. If the factory is None or doesn't have a __component_adapts__ adapts attribute, then this argument is required. provided This is the interface provided by the adapter and implemented by the factory. If the factory is not None implements a single interface, then this argument is optional and the factory-implemented interface will be used. name The adapter name. Currently, only the empty string is accepted. Other strings will be accepted in the future when support for named subscribers is added. An Unregistered event is generated with an ISubscriptionAdapterRegistration. """ def registeredSubscriptionAdapters(): """Return an iterable of ISubscriptionAdapterRegistration instances. These registrations describe the current subscription adapter registrations in the object. """ def registerHandler(handler, required=None, name=u'', info=''): """Register a handler. A handler is a subscriber that doesn't compute an adapter but performs some function when called. Parameters: handler The object used to handle some event represented by the objects passed to it. required This is a sequence of specifications for objects to be adapted. If omitted, then the value of the factory's __component_adapts__ attribute will be used. The __component_adapts__ attribute is usually attribute is normally set in class definitions using adapts function, or for callables using the adapter decorator. If the factory doesn't have a __component_adapts__ adapts attribute, then this argument is required. name The handler name. Currently, only the empty string is accepted. Other strings will be accepted in the future when support for named handlers is added. info An object that can be converted to a string to provide information about the registration. A Registered event is generated with an IHandlerRegistration. """ def unregisterHandler(handler=None, required=None, name=u''): """Unregister a handler. A handler is a subscriber that doesn't compute an adapter but performs some function when called. A boolean is returned indicating whether the registry was changed. Parameters: handler This is the object used to handle some event represented by the objects passed to it. The handler can be None, in which case any handlers registered for the given required specifications with the given are unregistered. required This is a sequence of specifications for objects to be adapted. If omitted, then the value of the factory's __component_adapts__ attribute will be used. The __component_adapts__ attribute is usually attribute is normally set in class definitions using adapts function, or for callables using the adapter decorator. If the factory doesn't have a __component_adapts__ adapts attribute, then this argument is required. name The handler name. Currently, only the empty string is accepted. Other strings will be accepted in the future when support for named handlers is added. An Unregistered event is generated with an IHandlerRegistration. """ def registeredHandlers(): """Return an iterable of IHandlerRegistration instances. These registrations describe the current handler registrations in the object. """ class IComponents(IComponentLookup, IComponentRegistry): """Component registration and access """ class IPossibleSite(Interface): """An object that could be a site. """ def setSiteManager(sitemanager): """Sets the site manager for this object. """ def getSiteManager(): """Returns the site manager contained in this object. If there isn't a site manager, raise a component lookup. """ class ISite(IPossibleSite): """Marker interface to indicate that we have a site""" zope2.13-2.13.21/source/zope.component/src/zope/component/nexttesting.py0000644000175000017500000000651012214017426025112 0ustar arnauarnau############################################################################## # # Copyright (c) 2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Helper functions for testing utilities that use get/queryNextUtility. """ import zope.interface from zope.component.interfaces import IComponentLookup, IComponents class SiteManagerStub(object): zope.interface.implements(IComponents) __bases__ = () def __init__(self): self._utils = {} def setNext(self, next): self.__bases__ = (next, ) def provideUtility(self, iface, util, name=''): self._utils[(iface, name)] = util def queryUtility(self, iface, name='', default=None): return self._utils.get((iface, name), default) def testingNextUtility(utility, nextutility, interface, name='', sitemanager=None, nextsitemanager=None): """Provide a next utility for testing. This function sets up two utilities, so the get/queryNextUtility functions will see the second one as the "next" to the first one. To test it, we need to create a utility interface and implementation: >>> from zope.interface import Interface, implements >>> class IAnyUtility(Interface): ... pass >>> class AnyUtility(object): ... implements(IAnyUtility) ... def __init__(self, id): ... self.id = id >>> any1 = AnyUtility(1) >>> any1next = AnyUtility(2) Now, we can make the "any1next" be next to "any1". >>> testingNextUtility(any1, any1next, IAnyUtility) >>> from zope.component import getNextUtility >>> getNextUtility(any1, IAnyUtility) is any1next True It will work for named utilities as well. >>> testingNextUtility(any1, any1next, IAnyUtility, 'any') >>> getNextUtility(any1, IAnyUtility, 'any') is any1next True We can also provide our custom component registries: >>> sm = SiteManagerStub() >>> nextsm = SiteManagerStub() >>> testingNextUtility(any1, any1next, IAnyUtility, ... sitemanager=sm, nextsitemanager=nextsm) >>> IComponentLookup(any1) is sm True >>> IComponentLookup(any1next) is nextsm True >>> getNextUtility(any1, IAnyUtility) is any1next True """ if sitemanager is None: sitemanager = SiteManagerStub() if nextsitemanager is None: nextsitemanager = SiteManagerStub() sitemanager.setNext(nextsitemanager) sitemanager.provideUtility(interface, utility, name) utility.__conform__ = ( lambda iface: iface.isOrExtends(IComponentLookup) and sitemanager or None ) nextsitemanager.provideUtility(interface, nextutility, name) nextutility.__conform__ = ( lambda iface: iface.isOrExtends(IComponentLookup) and nextsitemanager or None ) zope2.13-2.13.21/source/zope.component/src/zope/component/testlayer.py0000644000175000017500000001003212214017426024544 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import os from zope.configuration import xmlconfig, config from zope.testing.cleanup import cleanUp from zope.component import provideHandler from zope.component.hooks import setHooks from zope.component.eventtesting import events, clearEvents class LayerBase(object): """Sane layer base class. zope.testing implements an advanced mechanism so that layer setUp, tearDown, testSetUp and testTearDown code gets called in the right order. These methods are supposed to be @classmethods and should not use super() as the test runner is supposed to take care of that. In practice, this mechanism turns out not to be useful and overcomplicated. It becomes difficult to pass information into layers (such as a ZCML file to load), because the only way to pass in information is to subclass, and subclassing these layers leads to a range of interactions that is hard to reason about. We'd rather just use Python and the super mechanism, as we know how to reason about that. This base class is a hack to make this possible. The hack requires us to set __bases__, __module__ and __name__. This fools zope.testing into thinking that this layer instance is a class it can work with. It'd be better if zope.testing just called a minimal API and didn't try to be fancy. Fancy layer inheritance mechanisms can then be implemented elsewhere if people want to. But unfortunately it does implement a fancy mechanism and we need to fool it. """ __bases__ = () def __init__(self, package, name=None): if name is None: name = self.__class__.__name__ self.__name__ = name self.__module__ = package.__name__ self.package = package def setUp(self): pass def tearDown(self): pass def testSetUp(self): pass def testTearDown(self): pass class ZCMLLayerBase(LayerBase): """Base class to load up some ZCML. """ def __init__(self, package, name=None, features=None): super(ZCMLLayerBase, self).__init__(package, name) self.features = features or [] def setUp(self): setHooks() context = config.ConfigurationMachine() xmlconfig.registerCommonDirectives(context) for feature in self.features: context.provideFeature(feature) self.context = self._load_zcml(context) provideHandler(events.append, (None,)) def testTearDown(self): clearEvents() def tearDown(self): cleanUp() def _load_zcml(self, context): raise NotImplementedError class ZCMLFileLayer(ZCMLLayerBase): """This layer can be used to run tests with a ZCML file loaded. The ZCML file is assumed to include sufficient (meta)configuration so that it can be interpreted itself. I.e. to create a ZCMLLayer based on another ZCMLLayer's ZCML, just use a ZCML include statement in your own ZCML to load it. """ def __init__(self, package, zcml_file='ftesting.zcml', name=None, features=None): super(ZCMLFileLayer, self).__init__(package, name, features) self.zcml_file = os.path.join(os.path.dirname(package.__file__), zcml_file) def _load_zcml(self, context): return xmlconfig.file(self.zcml_file, package=self.package, context=context, execute=True) zope2.13-2.13.21/source/zope.component/src/zope/component/__init__.py0000644000175000017500000000522412214017426024276 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Zope 3 Component Architecture """ from zope.interface import Interface from zope.interface import implementedBy from zope.interface import moduleProvides from zope.interface import providedBy from zope.component.interfaces import ComponentLookupError from zope.component.interfaces import IComponentArchitecture from zope.component.interfaces import IComponentLookup from zope.component.interfaces import IComponentRegistrationConvenience from zope.component.interfaces import IFactory from zope.component.globalregistry import getGlobalSiteManager from zope.component.globalregistry import globalSiteManager from zope.component.globalregistry import provideAdapter from zope.component.globalregistry import provideHandler from zope.component.globalregistry import provideSubscriptionAdapter from zope.component.globalregistry import provideUtility from zope.component._api import adapter_hook from zope.component._api import createObject from zope.component._api import getAdapter from zope.component._api import getAdapterInContext from zope.component._api import getAdapters from zope.component._api import getAllUtilitiesRegisteredFor from zope.component._api import getFactoriesFor from zope.component._api import getFactoryInterfaces from zope.component._api import getMultiAdapter from zope.component._api import getSiteManager from zope.component._api import getUtilitiesFor from zope.component._api import getUtility from zope.component._api import getNextUtility from zope.component._api import handle from zope.component._api import queryAdapter from zope.component._api import queryAdapterInContext from zope.component._api import queryMultiAdapter from zope.component._api import queryUtility from zope.component._api import queryNextUtility from zope.component._api import subscribers from zope.component._declaration import adaptedBy from zope.component._declaration import adapter from zope.component._declaration import adapts moduleProvides(IComponentArchitecture, IComponentRegistrationConvenience) __all__ = tuple(IComponentArchitecture) zope2.13-2.13.21/source/zope.component/src/zope/component/README.txt0000644000175000017500000003050712214017426023665 0ustar arnauarnauZope Component Architecture =========================== This package, together with `zope.interface`, provides facilities for defining, registering and looking up components. There are two basic kinds of components: adapters and utilities. Utilities --------- Utilities are just components that provide an interface and that are looked up by an interface and a name. Let's look at a trivial utility definition: >>> from zope import interface >>> class IGreeter(interface.Interface): ... def greet(): ... "say hello" >>> class Greeter: ... interface.implements(IGreeter) ... ... def __init__(self, other="world"): ... self.other = other ... ... def greet(self): ... print "Hello", self.other We can register an instance this class using `provideUtility` [1]_: >>> from zope import component >>> greet = Greeter('bob') >>> component.provideUtility(greet, IGreeter, 'robert') In this example we registered the utility as providing the `IGreeter` interface with a name of 'bob'. We can look the interface up with either `queryUtility` or `getUtility`: >>> component.queryUtility(IGreeter, 'robert').greet() Hello bob >>> component.getUtility(IGreeter, 'robert').greet() Hello bob `queryUtility` and `getUtility` differ in how failed lookups are handled: >>> component.queryUtility(IGreeter, 'ted') >>> component.queryUtility(IGreeter, 'ted', 42) 42 >>> component.getUtility(IGreeter, 'ted') ... # doctest: +ELLIPSIS Traceback (most recent call last): ... ComponentLookupError: (, 'ted') If a component provides only one interface, as in the example above, then we can omit the provided interface from the call to `provideUtility`: >>> ted = Greeter('ted') >>> component.provideUtility(ted, name='ted') >>> component.queryUtility(IGreeter, 'ted').greet() Hello ted The name defaults to an empty string: >>> world = Greeter() >>> component.provideUtility(world) >>> component.queryUtility(IGreeter).greet() Hello world Adapters -------- Adapters are components that are computed from other components to adapt them to some interface. Because they are computed from other objects, they are provided as factories, usually classes. Here, we'll create a greeter for persons, so we can provide personalized greetings for different people: >>> class IPerson(interface.Interface): ... name = interface.Attribute("Name") >>> class PersonGreeter: ... ... component.adapts(IPerson) ... interface.implements(IGreeter) ... ... def __init__(self, person): ... self.person = person ... ... def greet(self): ... print "Hello", self.person.name The class defines a constructor that takes an argument for every object adapted. We used `component.adapts` to declare what we adapt. We can find out if an object declares that it adapts anything using adaptedBy: >>> list(component.adaptedBy(PersonGreeter)) == [IPerson] True If an object makes no declaration, then None is returned: >>> component.adaptedBy(Greeter()) is None True If we declare the interfaces adapted and if we provide only one interface, as in the example above, then we can provide the adapter very simply [1]_: >>> component.provideAdapter(PersonGreeter) For adapters that adapt a single interface to a single interface without a name, we can get the adapter by simply calling the interface: >>> class Person: ... interface.implements(IPerson) ... ... def __init__(self, name): ... self.name = name >>> IGreeter(Person("Sally")).greet() Hello Sally We can also provide arguments to be very specific about what how to register the adapter. >>> class BobPersonGreeter(PersonGreeter): ... name = 'Bob' ... def greet(self): ... print "Hello", self.person.name, "my name is", self.name >>> component.provideAdapter( ... BobPersonGreeter, [IPerson], IGreeter, 'bob') The arguments can also be provided as keyword arguments: >>> class TedPersonGreeter(BobPersonGreeter): ... name = "Ted" >>> component.provideAdapter( ... factory=TedPersonGreeter, adapts=[IPerson], ... provides=IGreeter, name='ted') For named adapters, use `queryAdapter`, or `getAdapter`: >>> component.queryAdapter(Person("Sally"), IGreeter, 'bob').greet() Hello Sally my name is Bob >>> component.getAdapter(Person("Sally"), IGreeter, 'ted').greet() Hello Sally my name is Ted If an adapter can't be found, `queryAdapter` returns a default value and `getAdapter` raises an error: >>> component.queryAdapter(Person("Sally"), IGreeter, 'frank') >>> component.queryAdapter(Person("Sally"), IGreeter, 'frank', 42) 42 >>> component.getAdapter(Person("Sally"), IGreeter, 'frank') ... # doctest: +ELLIPSIS Traceback (most recent call last): ... ComponentLookupError: (...Person...>, <...IGreeter>, 'frank') Adapters can adapt multiple objects: >>> class TwoPersonGreeter: ... ... component.adapts(IPerson, IPerson) ... interface.implements(IGreeter) ... ... def __init__(self, person, greeter): ... self.person = person ... self.greeter = greeter ... ... def greet(self): ... print "Hello", self.person.name ... print "my name is", self.greeter.name >>> component.provideAdapter(TwoPersonGreeter) To look up a multi-adapter, use either `queryMultiAdapter` or `getMultiAdapter`: >>> component.queryMultiAdapter((Person("Sally"), Person("Bob")), ... IGreeter).greet() Hello Sally my name is Bob Adapters need not be classes. Any callable will do. We use the adapter decorator (in the Python 2.4 decorator sense) to declare that a callable object adapts some interfaces (or classes): >>> class IJob(interface.Interface): ... "A job" >>> class Job: ... interface.implements(IJob) >>> def personJob(person): ... return getattr(person, 'job', None) >>> personJob = interface.implementer(IJob)(personJob) >>> personJob = component.adapter(IPerson)(personJob) In Python 2.4, the example can be written: >>> @interface.implementer(IJob) ... @component.adapter(IPerson) ... def personJob(person): ... return getattr(person, 'job', None) which looks a bit nicer. In this example, the personJob function simply returns the person's `job` attribute if present, or None if it's not present. An adapter factory can return None to indicate that adaptation wasn't possible. Let's register this adapter and try it out: >>> component.provideAdapter(personJob) >>> sally = Person("Sally") >>> IJob(sally) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: ('Could not adapt', ... The adaptation failed because sally didn't have a job. Let's give her one: >>> job = Job() >>> sally.job = job >>> IJob(sally) is job True Subscription Adapters --------------------- Unlike regular adapters, subscription adapters are used when we want all of the adapters that adapt an object to a particular adapter. Consider a validation problem. We have objects and we want to assess whether they meet some sort of standards. We define a validation interface: >>> class IValidate(interface.Interface): ... def validate(ob): ... """Determine whether the object is valid ... ... Return a string describing a validation problem. ... An empty string is returned to indicate that the ... object is valid. ... """ Perhaps we have documents: >>> class IDocument(interface.Interface): ... summary = interface.Attribute("Document summary") ... body = interface.Attribute("Document text") >>> class Document: ... interface.implements(IDocument) ... def __init__(self, summary, body): ... self.summary, self.body = summary, body Now, we may want to specify various validation rules for documents. For example, we might require that the summary be a single line: >>> class SingleLineSummary: ... component.adapts(IDocument) ... interface.implements(IValidate) ... ... def __init__(self, doc): ... self.doc = doc ... ... def validate(self): ... if '\n' in self.doc.summary: ... return 'Summary should only have one line' ... else: ... return '' Or we might require the body to be at least 1000 characters in length: >>> class AdequateLength: ... component.adapts(IDocument) ... interface.implements(IValidate) ... ... def __init__(self, doc): ... self.doc = doc ... ... def validate(self): ... if len(self.doc.body) < 1000: ... return 'too short' ... else: ... return '' We can register these as subscription adapters [1]_: >>> component.provideSubscriptionAdapter(SingleLineSummary) >>> component.provideSubscriptionAdapter(AdequateLength) We can then use the subscribers to validate objects: >>> doc = Document("A\nDocument", "blah") >>> [adapter.validate() ... for adapter in component.subscribers([doc], IValidate) ... if adapter.validate()] ['Summary should only have one line', 'too short'] >>> doc = Document("A\nDocument", "blah" * 1000) >>> [adapter.validate() ... for adapter in component.subscribers([doc], IValidate) ... if adapter.validate()] ['Summary should only have one line'] >>> doc = Document("A Document", "blah") >>> [adapter.validate() ... for adapter in component.subscribers([doc], IValidate) ... if adapter.validate()] ['too short'] Handlers -------- Handlers are subscription adapter factories that don't produce anything. They do all of their work when called. Handlers are typically used to handle events. Event subscribers are different from other subscription adapters in that the caller of event subscribers doesn't expect to interact with them in any direct way. For example, an event publisher doesn't expect to get any return value. Because subscribers don't need to provide an API to their callers, it is more natural to define them with functions, rather than classes. For example, in a document-management system, we might want to record creation times for documents: >>> import datetime >>> def documentCreated(event): ... event.doc.created = datetime.datetime.utcnow() In this example, we have a function that takes an event and performs some processing. It doesn't actually return anything. This is a special case of a subscription adapter that adapts an event to nothing. All of the work is done when the adapter "factory" is called. We call subscribers that don't actually create anything "handlers". There are special APIs for registering and calling them. To register the subscriber above, we define a document-created event: >>> class IDocumentCreated(interface.Interface): ... doc = interface.Attribute("The document that was created") >>> class DocumentCreated: ... interface.implements(IDocumentCreated) ... ... def __init__(self, doc): ... self.doc = doc We'll also change our handler definition to: >>> def documentCreated(event): ... event.doc.created = datetime.datetime.utcnow() >>> documentCreated = component.adapter(IDocumentCreated)(documentCreated) Note that in Python 2.4, this can be written: >>> @component.adapter(IDocumentCreated) ... def documentCreated(event): ... event.doc.created = datetime.datetime.utcnow() This marks the handler as an adapter of `IDocumentCreated` events. Now we'll register the handler [1]_: >>> component.provideHandler(documentCreated) Now, if we can create an event and use the `handle` function to call handlers registered for the event: >>> component.handle(DocumentCreated(doc)) >>> doc.created.__class__.__name__ 'datetime' .. [1] CAUTION: This API should only be used from test or application-setup code. This API shouldn't be used by regular library modules, as component registration is a configuration activity. zope2.13-2.13.21/source/zope.component/src/zope/component/event.py0000644000175000017500000000222712214017426023660 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Implement Component Architecture-specific event dispatching, based on subscription adapters / handlers. """ __docformat__ = 'restructuredtext' import zope.component.interfaces import zope.event def dispatch(*event): zope.component.subscribers(event, None) zope.event.subscribers.append(dispatch) @zope.component.adapter(zope.component.interfaces.IObjectEvent) def objectEventNotify(event): """Event subscriber to dispatch ObjectEvents to interested adapters.""" zope.component.subscribers((event.object, event), None) zope2.13-2.13.21/source/zope.component/src/zope/component/tests.py0000644000175000017500000014777312214017426023721 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002, 2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Component Architecture Tests """ import doctest import persistent import re import sys import unittest import transaction from cStringIO import StringIO from zope import interface, component from zope.interface.verify import verifyObject from zope.interface.interfaces import IInterface from zope.testing import renormalizing from zope.testing.testrunner.layer import UnitTests from zope.component.interfaces import ComponentLookupError from zope.component.interfaces import IComponentArchitecture from zope.component.interfaces import IComponentLookup from zope.component.testing import setUp, tearDown, PlacelessSetup import zope.component.persistentregistry import zope.component.globalregistry from zope.configuration.xmlconfig import XMLConfig, xmlconfig from zope.configuration.exceptions import ConfigurationError from zope.security.checker import ProxyFactory from zope.component.testfiles.adapter import A1, A2, A3 from zope.component.testfiles.components import IContent, Content from zope.component.testfiles.components import IApp from zope.component.testfiles.views import Request, IC, IV, V1, R1, IR # side effect gets component-based event dispatcher installed. # we should obviously make this more explicit import zope.component.event class I1(interface.Interface): pass class I2(interface.Interface): pass class I2e(I2): pass class I3(interface.Interface): pass class ITestType(IInterface): pass class U: def __init__(self, name): self.__name__ = name def __repr__(self): return "%s(%s)" % (self.__class__.__name__, self.__name__) class U1(U): interface.implements(I1) class U12(U): interface.implements(I1, I2) class IA1(interface.Interface): pass class IA2(interface.Interface): pass class IA3(interface.Interface): pass class A: def __init__(self, *context): self.context = context def __repr__(self): return "%s%r" % (self.__class__.__name__, self.context) class A12_1(A): component.adapts(I1, I2) interface.implements(IA1) class A12_(A): component.adapts(I1, I2) class A_2(A): interface.implements(IA2) class A_3(A): interface.implements(IA3) class A1_12(U): component.adapts(I1) interface.implements(IA1, IA2) class A1_2(U): component.adapts(I1) interface.implements(IA2) class A1_23(U): component.adapts(I1) interface.implements(IA1, IA3) def noop(*args): pass @component.adapter(I1) def handle1(x): print 'handle1', x def handle(*objects): print 'handle', objects @component.adapter(I1) def handle3(x): print 'handle3', x @component.adapter(I1) def handle4(x): print 'handle4', x class Ob(object): interface.implements(I1) def __repr__(self): return '' ob = Ob() class Ob2(object): interface.implements(I2) def __repr__(self): return '' class Comp(object): interface.implements(I2) def __init__(self, context): self.context = context comp = Comp(1) class Comp2(object): interface.implements(I3) def __init__(self, context): self.context = context class ConformsToIComponentLookup(object): """This object allows the sitemanager to conform/adapt to `IComponentLookup` and thus to itself.""" def __init__(self, sitemanager): self.sitemanager = sitemanager def __conform__(self, interface): """This method is specified by the adapter PEP to do the adaptation.""" if interface is IComponentLookup: return self.sitemanager def testInterfaces(): """Ensure that the component architecture API is provided by `zope.component`. >>> verifyObject(IComponentArchitecture, component) True """ def test_getGlobalSiteManager(): """One of the most important functions is to get the global site manager. >>> from zope.component.interfaces import IComponentLookup >>> from zope.component.globalregistry import base Get the global site manager via the CA API function: >>> gsm = component.getGlobalSiteManager() Make sure that the global site manager implements the correct interface and is the global site manager instance we expect to get. >>> IComponentLookup.providedBy(gsm) True >>> base is gsm True Finally, ensure that we always get the same global site manager, otherwise our component registry will always be reset. >>> component.getGlobalSiteManager() is gsm True """ def test_getSiteManager(): """Make sure that `getSiteManager()` always returns the correct site manager instance. We don't know anything about the default service manager, except that it is an `IComponentLookup`. >>> IComponentLookup.providedBy(component.getSiteManager()) True Calling `getSiteManager()` with no args is equivalent to calling it with a context of `None`. >>> component.getSiteManager() is component.getSiteManager(None) True If the context passed to `getSiteManager()` is not `None`, it is adapted to `IComponentLookup` and this adapter returned. So, we create a context that can be adapted to `IComponentLookup` using the `__conform__` API. Let's create the simplest stub-implementation of a site manager possible: >>> sitemanager = object() Now create a context that knows how to adapt to our newly created site manager. >>> context = ConformsToIComponentLookup(sitemanager) Now make sure that the `getSiteManager()` API call returns the correct site manager. >>> component.getSiteManager(context) is sitemanager True Using a context that is not adaptable to `IComponentLookup` should fail. >>> component.getSiteManager(ob) #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: ('Could not adapt', , ) """ def testAdapterInContext(self): """The `getAdapterInContext()` and `queryAdapterInContext()` API functions do not only use the site manager to look up the adapter, but first tries to use the `__conform__()` method of the object to find an adapter as specified by PEP 246. Let's start by creating a component that support's the PEP 246's `__conform__()` method: >>> class Component(object): ... interface.implements(I1) ... def __conform__(self, iface, default=None): ... if iface == I2: ... return 42 ... def __repr__(self): ... return '''''' >>> ob = Component() We also gave the component a custom representation, so it will be easier to use in these tests. We now have to create a site manager (other than the default global one) with which we can register adapters for `I1`. >>> from zope.component.globalregistry import BaseGlobalComponents >>> sitemanager = BaseGlobalComponents() Now we create a new `context` that knows how to get to our custom site manager. >>> context = ConformsToIComponentLookup(sitemanager) We now register an adapter from `I1` to `I3`: >>> sitemanager.registerAdapter(lambda x: 43, (I1,), I3, '') If an object implements the interface you want to adapt to, `getAdapterInContext()` should simply return the object. >>> component.getAdapterInContext(ob, I1, context) >>> component.queryAdapterInContext(ob, I1, context) If an object conforms to the interface you want to adapt to, `getAdapterInContext()` should simply return the conformed object. >>> component.getAdapterInContext(ob, I2, context) 42 >>> component.queryAdapterInContext(ob, I2, context) 42 If an adapter isn't registered for the given object and interface, and you provide no default, raise ComponentLookupError... >>> class I4(interface.Interface): ... pass >>> component.getAdapterInContext(ob, I4, context) \\ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, ) ...otherwise, you get the default: >>> component.queryAdapterInContext(ob, I4, context, 44) 44 If you ask for an adapter for which something's registered you get the registered adapter >>> component.getAdapterInContext(ob, I3, context) 43 >>> component.queryAdapterInContext(ob, I3, context) 43 """ def testAdapter(): """The `getAdapter()` and `queryAdapter()` API functions are similar to `{get|query}AdapterInContext()` functions, except that they do not care about the `__conform__()` but also handle named adapters. (Actually, the name is a required argument.) If an adapter isn't registered for the given object and interface, and you provide no default, raise `ComponentLookupError`... >>> component.getAdapter(ob, I2, '') #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, , '') ...otherwise, you get the default >>> component.queryAdapter(ob, I2, '', '') '' Now get the global site manager and register an adapter from `I1` to `I2` without a name: >>> component.getGlobalSiteManager().registerAdapter( ... Comp, (I1,), I2, '') You should get a sensible error message if you forget that the 'requires' argument is supposed to be a sequence >>> component.getGlobalSiteManager().registerAdapter( ... Comp, I1, I2, '') Traceback (most recent call last): ... TypeError: the required argument should be a list of interfaces, not a single interface You can now simply access the adapter using the `getAdapter()` API function: >>> adapter = component.getAdapter(ob, I2, '') >>> adapter.__class__ is Comp True >>> adapter.context is ob True """ def testInterfaceCall(): """Here we test the `adapter_hook()` function that we registered with the `zope.interface` adapter hook registry, so that we can call interfaces to do adaptation. First, we need to register an adapter: >>> component.getGlobalSiteManager().registerAdapter( ... Comp, [I1], I2, '') Then we try to adapt `ob` to provide an `I2` interface by calling the `I2` interface with the obejct as first argument: >>> adapter = I2(ob) >>> adapter.__class__ is Comp True >>> adapter.context is ob True If no adapter is found, a `TypeError is raised... >>> I1(Ob2()) #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: ('Could not adapt', , ) ...unless we specify an alternative adapter: >>> marker = object() >>> I2(object(), marker) is marker True """ def testNamedAdapter(): """Make sure that adapters with names are correctly selected from the registry. First we register some named adapter: >>> component.getGlobalSiteManager().registerAdapter( ... lambda x: 0, [I1], I2, 'foo') If an adapter isn't registered for the given object and interface, and you provide no default, raise `ComponentLookupError`... >>> component.getAdapter(ob, I2, 'bar') \\ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, , 'bar') ...otherwise, you get the default >>> component.queryAdapter(ob, I2, 'bar', '') '' But now we register an adapter for the object having the correct name >>> component.getGlobalSiteManager().registerAdapter( ... Comp, [I1], I2, 'bar') so that the lookup succeeds: >>> adapter = component.getAdapter(ob, I2, 'bar') >>> adapter.__class__ is Comp True >>> adapter.context is ob True """ def testMultiAdapter(): """Adapting a combination of 2 objects to an interface Multi-adapters adapt one or more objects to another interface. To make this demonstration non-trivial, we need to create a second object to be adapted: >>> ob2 = Ob2() Like for regular adapters, if an adapter isn't registered for the given objects and interface, and you provide no default, raise `ComponentLookupError`... >>> component.getMultiAdapter((ob, ob2), I3) \\ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: ((, ), , u'') ...otherwise, you get the default >>> component.queryMultiAdapter((ob, ob2), I3, default='') '' Note that the name is not a required attribute here. To test multi-adapters, we also have to create an adapter class that handles to context objects: >>> class DoubleAdapter(object): ... interface.implements(I3) ... def __init__(self, first, second): ... self.first = first ... self.second = second Now we can register the multi-adapter using >>> component.getGlobalSiteManager().registerAdapter( ... DoubleAdapter, (I1, I2), I3, '') Notice how the required interfaces are simply provided by a tuple. Now we can get the adapter: >>> adapter = component.getMultiAdapter((ob, ob2), I3) >>> adapter.__class__ is DoubleAdapter True >>> adapter.first is ob True >>> adapter.second is ob2 True """ def testAdapterForInterfaceNone(): """Providing an adapter for None says that your adapter can adapt anything to `I2`. >>> component.getGlobalSiteManager().registerAdapter( ... Comp, (None,), I2, '') >>> adapter = I2(ob) >>> adapter.__class__ is Comp True >>> adapter.context is ob True It can really adapt any arbitrary object: >>> something = object() >>> adapter = I2(something) >>> adapter.__class__ is Comp True >>> adapter.context is something True """ def testGetAdapters(): """It is sometimes desireable to get a list of all adapters that are registered for a particular output interface, given a set of objects. Let's register some adapters first: >>> component.getGlobalSiteManager().registerAdapter( ... Comp, [I1], I2, '') >>> component.getGlobalSiteManager().registerAdapter( ... Comp, [None], I2, 'foo') Now we get all the adapters that are registered for `ob` that provide `I2`: >>> adapters = sorted(component.getAdapters((ob,), I2)) >>> [(name, adapter.__class__.__name__) for name, adapter in adapters] [(u'', 'Comp'), (u'foo', 'Comp')] Note that the output doesn't include None values. If an adapter factory returns None, it is as if it wasn't present. >>> component.getGlobalSiteManager().registerAdapter( ... lambda context: None, [I1], I2, 'nah') >>> adapters = sorted(component.getAdapters((ob,), I2)) >>> [(name, adapter.__class__.__name__) for name, adapter in adapters] [(u'', 'Comp'), (u'foo', 'Comp')] """ def testUtility(): """Utilities are components that simply provide an interface. They are instantiated at the time or before they are registered. Here we test the simple query interface. Before we register any utility, there is no utility available, of course. The pure instatiation of an object does not make it a utility. If you do not specify a default, you get a `ComponentLookupError`... >>> component.getUtility(I1) #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: \ (, '') ...otherwise, you get the default >>> component.queryUtility(I1, default='') '' >>> component.queryUtility(I2, default='') '' Now we declare `ob` to be the utility providing `I1` >>> component.getGlobalSiteManager().registerUtility(ob, I1) so that the component is now available: >>> component.getUtility(I1) is ob True """ def testNamedUtility(): """Like adapters, utilities can be named. Just because you register an utility having no name >>> component.getGlobalSiteManager().registerUtility(ob, I1) does not mean that they are available when you specify a name: >>> component.getUtility(I1, name='foo') \\ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, 'foo') ...otherwise, you get the default >>> component.queryUtility(I1, name='foo', default='') '' Registering the utility under the correct name >>> component.getGlobalSiteManager().registerUtility( ... ob, I1, name='foo') really helps: >>> component.getUtility(I1, 'foo') is ob True """ def test_getAllUtilitiesRegisteredFor(): """Again, like for adapters, it is often useful to get a list of all utilities that have been registered for a particular interface. Utilities providing a derived interface are also listed. Thus, let's create a derivative interface of `I1`: >>> class I11(I1): ... pass >>> class Ob11(Ob): ... interface.implements(I11) >>> ob11 = Ob11() >>> ob_bob = Ob() Now we register the new utilities: >>> gsm = component.getGlobalSiteManager() >>> gsm.registerUtility(ob, I1) >>> gsm.registerUtility(ob11, I11) >>> gsm.registerUtility(ob_bob, I1, name='bob') >>> gsm.registerUtility(Comp(2), I2) We can now get all the utilities that provide interface `I1`: >>> uts = list(component.getAllUtilitiesRegisteredFor(I1)) >>> uts = sorted([util.__class__.__name__ for util in uts]) >>> uts ['Ob', 'Ob', 'Ob11'] Note that `getAllUtilitiesRegisteredFor()` does not return the names of the utilities. """ def testNotBrokenWhenNoSiteManager(): """Make sure that the adapter lookup is not broken, when no site manager is available. Both of those things emit `DeprecationWarnings`. >>> I2(ob) #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: ('Could not adapt', , ) >>> I2(ob, 42) 42 """ def testNo__component_adapts__leakage(): """ We want to make sure that an `adapts()` call in a class definition doesn't affect instances. >>> class C: ... component.adapts() >>> C.__component_adapts__ () >>> C().__component_adapts__ Traceback (most recent call last): ... AttributeError: __component_adapts__ """ def test_ability_to_pickle_globalsitemanager(): """ We need to make sure that it is possible to pickle the global site manager and its two global adapter registries. >>> from zope.component import globalSiteManager >>> import cPickle >>> pickle = cPickle.dumps(globalSiteManager) >>> sm = cPickle.loads(pickle) >>> sm is globalSiteManager True Now let's ensure that the registries themselves can be pickled as well: >>> pickle = cPickle.dumps(globalSiteManager.adapters) >>> adapters = cPickle.loads(pickle) >>> adapters is globalSiteManager.adapters True """ def test_persistent_component_managers(): """ Here, we'll demonstrate that changes work even when data are stored in a database and when accessed from multiple connections. Start by setting up a database and creating two transaction managers and database connections to work with. >>> import ZODB.tests.util >>> db = ZODB.tests.util.DB() >>> import transaction >>> t1 = transaction.TransactionManager() >>> c1 = db.open(transaction_manager=t1) >>> r1 = c1.root() >>> t2 = transaction.TransactionManager() >>> c2 = db.open(transaction_manager=t2) >>> r2 = c2.root() Create a set of components registries in the database, alternating connections. >>> from zope.component.persistentregistry import PersistentComponents >>> _ = t1.begin() >>> r1[1] = PersistentComponents('1') >>> t1.commit() >>> _ = t2.begin() >>> r2[2] = PersistentComponents('2', (r2[1], )) >>> t2.commit() >>> _ = t1.begin() >>> r1[3] = PersistentComponents('3', (r1[1], )) >>> t1.commit() >>> _ = t2.begin() >>> r2[4] = PersistentComponents('4', (r2[2], r2[3])) >>> t2.commit() >>> _ = t1.begin() >>> r1[1].__bases__ () >>> r1[2].__bases__ == (r1[1], ) True >>> r1[1].registerUtility(U1(1)) >>> r1[1].queryUtility(I1) U1(1) >>> r1[2].queryUtility(I1) U1(1) >>> t1.commit() >>> _ = t2.begin() >>> r2[1].registerUtility(U1(2)) >>> r2[2].queryUtility(I1) U1(2) >>> r2[4].queryUtility(I1) U1(2) >>> t2.commit() >>> _ = t1.begin() >>> r1[1].registerUtility(U12(1), I2) >>> r1[4].queryUtility(I2) U12(1) >>> t1.commit() >>> _ = t2.begin() >>> r2[3].registerUtility(U12(3), I2) >>> r2[4].queryUtility(I2) U12(3) >>> t2.commit() >>> _ = t1.begin() >>> r1[1].registerHandler(handle1, info="First handler") >>> r1[2].registerHandler(handle, required=[U]) >>> r1[3].registerHandler(handle3) >>> r1[4].registerHandler(handle4) >>> r1[4].handle(U1(1)) handle1 U1(1) handle3 U1(1) handle (U1(1),) handle4 U1(1) >>> t1.commit() >>> _ = t2.begin() >>> r2[4].handle(U1(1)) handle1 U1(1) handle3 U1(1) handle (U1(1),) handle4 U1(1) >>> t2.abort() >>> db.close() """ def persistent_registry_doesnt_scew_up_subsribers(): """ >>> import ZODB.tests.util >>> db = ZODB.tests.util.DB() >>> import transaction >>> t1 = transaction.TransactionManager() >>> c1 = db.open(transaction_manager=t1) >>> r1 = c1.root() >>> t2 = transaction.TransactionManager() >>> c2 = db.open(transaction_manager=t2) >>> r2 = c2.root() >>> from zope.component.persistentregistry import PersistentComponents >>> _ = t1.begin() >>> r1[1] = PersistentComponents('1') >>> r1[1].registerHandler(handle1) >>> r1[1].registerSubscriptionAdapter(handle1, provided=I2) >>> _ = r1[1].unregisterHandler(handle1) >>> _ = r1[1].unregisterSubscriptionAdapter(handle1, provided=I2) >>> t1.commit() >>> _ = t1.begin() >>> r1[1].registerHandler(handle1) >>> r1[1].registerSubscriptionAdapter(handle1, provided=I2) >>> t1.commit() >>> _ = t2.begin() >>> len(list(r2[1].registeredHandlers())) 1 >>> len(list(r2[1].registeredSubscriptionAdapters())) 1 >>> t2.abort() """ class GlobalRegistry: pass base = zope.component.globalregistry.GlobalAdapterRegistry( GlobalRegistry, 'adapters') GlobalRegistry.adapters = base def clear_base(): base.__init__(GlobalRegistry, 'adapters') class IFoo(interface.Interface): pass class Foo(persistent.Persistent): interface.implements(IFoo) name = '' def __init__(self, name=''): self.name = name def __repr__(self): return 'Foo(%r)' % self.name def test_deghostification_of_persistent_adapter_registries(): """ We want to make sure that we see updates corrextly. >>> len(base._v_subregistries) 0 >>> import ZODB.tests.util >>> db = ZODB.tests.util.DB() >>> tm1 = transaction.TransactionManager() >>> c1 = db.open(transaction_manager=tm1) >>> r1 = zope.component.persistentregistry.PersistentAdapterRegistry( ... (base,)) >>> r2 = zope.component.persistentregistry.PersistentAdapterRegistry((r1,)) >>> c1.root()[1] = r1 >>> c1.root()[2] = r2 >>> tm1.commit() >>> r1._p_deactivate() >>> len(base._v_subregistries) 0 >>> tm2 = transaction.TransactionManager() >>> c2 = db.open(transaction_manager=tm2) >>> r1 = c2.root()[1] >>> r2 = c2.root()[2] >>> r1.lookup((), IFoo, '') >>> base.register((), IFoo, '', Foo('')) >>> r1.lookup((), IFoo, '') Foo('') >>> r2.lookup((), IFoo, '1') >>> r1.register((), IFoo, '1', Foo('1')) >>> r2.lookup((), IFoo, '1') Foo('1') >>> r1.lookup((), IFoo, '2') >>> r2.lookup((), IFoo, '2') >>> base.register((), IFoo, '2', Foo('2')) >>> r1.lookup((), IFoo, '2') Foo('2') >>> r2.lookup((), IFoo, '2') Foo('2') Cleanup: >>> db.close() >>> clear_base() """ def test_multi_handler_unregistration(): """ There was a bug where multiple handlers for the same required specification would all be removed when one of them was unregistered: >>> class I(zope.interface.Interface): ... pass >>> def factory1(event): ... print "| Factory 1 is here" >>> def factory2(event): ... print "| Factory 2 is here" >>> class Event(object): ... zope.interface.implements(I) >>> from zope.component.registry import Components >>> registry = Components() >>> registry.registerHandler(factory1, [I,]) >>> registry.registerHandler(factory2, [I,]) >>> registry.handle(Event()) | Factory 1 is here | Factory 2 is here >>> registry.unregisterHandler(factory1, [I,]) True >>> registry.handle(Event()) | Factory 2 is here """ def test_next_utilities(): """ It is common for a utility to delegate its answer to a utility providing the same interface in one of the component registry's bases. Let's first create a global utility:: >>> import zope.interface >>> class IMyUtility(zope.interface.Interface): ... pass >>> class MyUtility(ConformsToIComponentLookup): ... zope.interface.implements(IMyUtility) ... def __init__(self, id, sm): ... self.id = id ... self.sitemanager = sm ... def __repr__(self): ... return "%s('%s')" % (self.__class__.__name__, self.id) >>> from zope.component import getGlobalSiteManager >>> gsm = getGlobalSiteManager() >>> gutil = MyUtility('global', gsm) >>> gsm.registerUtility(gutil, IMyUtility, 'myutil') Now, let's create two registries and set up the bases hierarchy:: >>> from zope.component.registry import Components >>> sm1 = Components('sm1', bases=(gsm, )) >>> sm1_1 = Components('sm1_1', bases=(sm1, )) Now we create two utilities and insert them in our folder hierarchy: >>> util1 = MyUtility('one', sm1) >>> sm1.registerUtility(util1, IMyUtility, 'myutil') >>> IComponentLookup(util1) is sm1 True >>> util1_1 = MyUtility('one-one', sm1_1) >>> sm1_1.registerUtility(util1_1, IMyUtility, 'myutil') >>> IComponentLookup(util1_1) is sm1_1 True Now, if we ask `util1_1` for its next available utility we get the ``one`` utility:: >>> from zope.component import getNextUtility >>> getNextUtility(util1_1, IMyUtility, 'myutil') MyUtility('one') Next we ask `util1` for its next utility and we should get the global version: >>> getNextUtility(util1, IMyUtility, 'myutil') MyUtility('global') However, if we ask the global utility for the next one, an error is raised >>> getNextUtility(gutil, IMyUtility, ... 'myutil') #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: No more utilities for , 'myutil' have been found. You can also use `queryNextUtility` and specify a default: >>> from zope.component import queryNextUtility >>> queryNextUtility(gutil, IMyUtility, 'myutil', 'default') 'default' Let's now ensure that the function also works with multiple registries. First we create another base registry: >>> myregistry = Components() We now set up another utility into that registry: >>> custom_util = MyUtility('my_custom_util', myregistry) >>> myregistry.registerUtility(custom_util, IMyUtility, 'my_custom_util') We add it as a base to the local site manager: >>> sm1.__bases__ = (myregistry,) + sm1.__bases__ Both the ``myregistry`` and global utilities should be available: >>> queryNextUtility(sm1, IMyUtility, 'my_custom_util') MyUtility('my_custom_util') >>> queryNextUtility(sm1, IMyUtility, 'myutil') MyUtility('global') Note, if the context cannot be converted to a site manager, the default is retruned: >>> queryNextUtility(object(), IMyUtility, 'myutil', 'default') 'default' """ def dont_leak_utility_registrations_in__subscribers(): """ We've observed utilities getting left in _subscribers when they get unregistered. >>> import zope.component.registry >>> reg = zope.component.registry.Components() >>> class C: ... def __init__(self, name): ... self.name = name ... def __repr__(self): ... return "C(%s)" % self.name >>> c1 = C(1) >>> reg.registerUtility(c1, I1) >>> reg.registerUtility(c1, I1) >>> list(reg.getAllUtilitiesRegisteredFor(I1)) [C(1)] >>> reg.unregisterUtility(provided=I1) True >>> list(reg.getAllUtilitiesRegisteredFor(I1)) [] >>> reg.registerUtility(c1, I1) >>> reg.registerUtility(C(2), I1) >>> list(reg.getAllUtilitiesRegisteredFor(I1)) [C(2)] """ def test_zcml_handler_site_manager(): """ The ZCML directives provided by zope.component use the ``getSiteManager`` method to get the registry where to register the components. This makes possible to hook ``getSiteManager`` before loading a ZCML file: >>> from zope.component.registry import Components >>> registry = Components() >>> def dummy(context=None): ... return registry >>> from zope.component import getSiteManager >>> ignore = getSiteManager.sethook(dummy) >>> from zope.component.testfiles.components import comp, IApp >>> from zope.component.zcml import handler >>> handler('registerUtility', comp, IApp, u'') >>> registry.getUtility(IApp) is comp True >>> ignore = getSiteManager.reset() """ class StandaloneTests(unittest.TestCase): def testStandalone(self): import subprocess import sys import os import tempfile import pickle executable = os.path.abspath(sys.executable) program = os.path.join(os.path.dirname(__file__), 'standalonetests.py') process = subprocess.Popen([executable, program], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE) pickle.dump(sys.path, process.stdin) process.stdin.close() try: process.wait() except OSError, e: if e.errno != 4: # MacIntel raises apparently unimportant EINTR? raise # TODO verify sanity of a pass on EINTR :-/ lines = process.stdout.readlines() process.stdout.close() success = True # Interpret the result: We scan the output from the end backwards # until we find either a line that says 'OK' (which means the tests # ran successfully) or a line that starts with quite a few dashes # (which means we didn't find a line that says 'OK' within the summary # of the test runner and the tests did not run successfully.) for l in reversed(lines): l = l.strip() if not l: continue if l.startswith('-----'): break if l.endswith('OK'): sucess = True if not success: self.fail(''.join(lines)) class HookableTests(unittest.TestCase): def test_ctor_no_func(self): from zope.component.hookable import hookable self.assertRaises(TypeError, hookable) def test_ctor_simple(self): from zope.component.hookable import hookable def foo(): pass hooked = hookable(foo) self.failUnless(hooked.original is foo) self.failUnless(hooked.implementation is foo) def test_ctor_extra_arg(self): from zope.component.hookable import hookable def foo(): pass self.assertRaises(TypeError, hookable, foo, foo) def test_ctor_extra_arg(self): from zope.component.hookable import hookable def foo(): pass self.assertRaises(TypeError, hookable, foo, nonesuch=foo) def test_sethook(self): from zope.component.hookable import hookable def foo(): pass def bar(): pass hooked = hookable(foo) hooked.sethook(bar) self.failUnless(hooked.original is foo) self.failUnless(hooked.implementation is bar) def test_reset(self): from zope.component.hookable import hookable def foo(): pass def bar(): pass hooked = hookable(foo) hooked.sethook(bar) hooked.reset() self.failUnless(hooked.original is foo) self.failUnless(hooked.implementation is foo) def test_cant_assign_original(self): from zope.component.hookable import hookable def foo(): pass def bar(): pass hooked = hookable(foo) try: hooked.original = bar except TypeError: pass except AttributeError: pass else: self.fail('Assigned original') def test_cant_delete_original(self): from zope.component.hookable import hookable def foo(): pass hooked = hookable(foo) try: del hooked.original except TypeError: pass except AttributeError: pass else: self.fail('Deleted original') def test_cant_assign_original(self): from zope.component.hookable import hookable def foo(): pass def bar(): pass hooked = hookable(foo) try: hooked.implementation = bar except TypeError: pass except AttributeError: pass else: self.fail('Assigned implementation') def test_readonly_original(self): from zope.component.hookable import hookable def foo(): pass hooked = hookable(foo) try: del hooked.implementation except TypeError: pass except AttributeError: pass else: self.fail('Deleted implementation') class Ob3(object): interface.implements(IC) template = """ %s """ class ResourceViewTests(PlacelessSetup, unittest.TestCase): def setUp(self): super(ResourceViewTests, self).setUp() XMLConfig('meta.zcml', zope.component)() XMLConfig('meta.zcml', zope.security)() def testView(self): ob = Ob3() request = Request(IV) self.assertEqual( zope.component.queryMultiAdapter((ob, request), name=u'test'), None) xmlconfig(StringIO(template % ''' ''' )) self.assertEqual( zope.component.queryMultiAdapter((ob, request), name=u'test').__class__, V1) def testMultiView(self): xmlconfig(StringIO(template % ''' ''' )) ob = Ob3() a1 = A1() a2 = A2() request = Request(IV) view = zope.component.queryMultiAdapter((ob, a1, a2, request), name=u'test') self.assertEqual(view.__class__, A3) self.assertEqual(view.context, (ob, a1, a2, request)) def testMultiView_fails_w_multiple_factories(self): self.assertRaises( ConfigurationError, xmlconfig, StringIO(template % ''' ''' ) ) def testView_w_multiple_factories(self): xmlconfig(StringIO(template % ''' ''' )) ob = Ob3() # The view should be a V1 around an A3, around an A2, around # an A1, anround ob: view = zope.component.queryMultiAdapter((ob, Request(IV)), name=u'test') self.assertEqual(view.__class__, V1) a3 = view.context self.assertEqual(a3.__class__, A3) a2 = a3.context[0] self.assertEqual(a2.__class__, A2) a1 = a2.context[0] self.assertEqual(a1.__class__, A1) self.assertEqual(a1.context[0], ob) def testView_fails_w_no_factories(self): self.assertRaises(ConfigurationError, xmlconfig, StringIO(template % ''' ''' ), ) def testViewThatProvidesAnInterface(self): ob = Ob3() self.assertEqual( zope.component.queryMultiAdapter((ob, Request(IR)), IV, u'test'), None) xmlconfig(StringIO(template % ''' ''' )) self.assertEqual( zope.component.queryMultiAdapter((ob, Request(IR)), IV, u'test'), None) xmlconfig(StringIO(template % ''' ''' )) v = zope.component.queryMultiAdapter((ob, Request(IR)), IV, u'test') self.assertEqual(v.__class__, V1) def testUnnamedViewThatProvidesAnInterface(self): ob = Ob3() self.assertEqual( zope.component.queryMultiAdapter((ob, Request(IR)), IV), None) xmlconfig(StringIO(template % ''' ''' )) v = zope.component.queryMultiAdapter((ob, Request(IR)), IV) self.assertEqual(v, None) xmlconfig(StringIO(template % ''' ''' )) v = zope.component.queryMultiAdapter((ob, Request(IR)), IV) self.assertEqual(v.__class__, V1) def testViewHavingARequiredClass(self): xmlconfig(StringIO(template % ( ''' ''' ))) content = Content() a1 = zope.component.getMultiAdapter((content, Request(IR))) self.assert_(isinstance(a1, A1)) class MyContent: interface.implements(IContent) self.assertRaises(ComponentLookupError, zope.component.getMultiAdapter, (MyContent(), Request(IR))) def testInterfaceProtectedView(self): xmlconfig(StringIO(template % ''' ''' )) v = ProxyFactory(zope.component.getMultiAdapter((Ob3(), Request(IV)), name='test')) self.assertEqual(v.index(), 'V1 here') self.assertRaises(Exception, getattr, v, 'action') def testAttributeProtectedView(self): xmlconfig(StringIO(template % ''' ''' )) v = ProxyFactory(zope.component.getMultiAdapter((Ob3(), Request(IV)), name='test')) self.assertEqual(v.action(), 'done') self.assertRaises(Exception, getattr, v, 'index') def testInterfaceAndAttributeProtectedView(self): xmlconfig(StringIO(template % ''' ''' )) v = zope.component.getMultiAdapter((Ob3(), Request(IV)), name='test') self.assertEqual(v.index(), 'V1 here') self.assertEqual(v.action(), 'done') def testDuplicatedInterfaceAndAttributeProtectedView(self): xmlconfig(StringIO(template % ''' ''' )) v = zope.component.getMultiAdapter((Ob3(), Request(IV)), name='test') self.assertEqual(v.index(), 'V1 here') self.assertEqual(v.action(), 'done') def testIncompleteProtectedViewNoPermission(self): self.assertRaises( ConfigurationError, xmlconfig, StringIO(template % ''' ''' )) def testViewUndefinedPermission(self): config = StringIO(template % ( ''' ''' )) self.assertRaises(ValueError, xmlconfig, config, testing=1) def testResource(self): ob = Ob3() self.assertEqual( zope.component.queryAdapter(Request(IV), name=u'test'), None) xmlconfig(StringIO(template % ( ''' ''' ))) self.assertEqual( zope.component.queryAdapter(Request(IV), name=u'test').__class__, R1) def testResourceThatProvidesAnInterface(self): ob = Ob3() self.assertEqual(zope.component.queryAdapter(Request(IR), IV, u'test'), None) xmlconfig(StringIO(template % ''' ''' )) v = zope.component.queryAdapter(Request(IR), IV, name=u'test') self.assertEqual(v, None) xmlconfig(StringIO(template % ''' ''' )) v = zope.component.queryAdapter(Request(IR), IV, name=u'test') self.assertEqual(v.__class__, R1) def testUnnamedResourceThatProvidesAnInterface(self): ob = Ob3() self.assertEqual(zope.component.queryAdapter(Request(IR), IV), None) xmlconfig(StringIO(template % ''' ''' )) v = zope.component.queryAdapter(Request(IR), IV) self.assertEqual(v, None) xmlconfig(StringIO(template % ''' ''' )) v = zope.component.queryAdapter(Request(IR), IV) self.assertEqual(v.__class__, R1) def testResourceUndefinedPermission(self): config = StringIO(template % ( ''' ''' )) self.assertRaises(ValueError, xmlconfig, config, testing=1) class ConditionalSecurityLayer(UnitTests): __name__ = 'ConditionalSecurity' __bases__ = () def setUp(self): setUp() self.modules = {} for m in ('zope.security', 'zope.proxy'): self.modules[m] = sys.modules[m] sys.modules[m] = None import zope.component.zcml reload(zope.component.zcml) def tearDown(self): tearDown() for m in ('zope.security', 'zope.proxy'): sys.modules[m] = self.modules[m] import zope.component.zcml reload(zope.component.zcml) def setUpRegistryTests(tests): setUp() def tearDownRegistryTests(tests): tearDown() import zope.event zope.event.subscribers.pop() def clearZCML(test=None): tearDown() setUp() XMLConfig('meta.zcml', component)() def test_suite(): checker = renormalizing.RENormalizing([ (re.compile('at 0x[0-9a-fA-F]+'), 'at '), (re.compile(r":"), r'exceptions.\1Error:'), ]) zcml_conditional = doctest.DocFileSuite('zcml_conditional.txt', checker=checker) zcml_conditional.layer = ConditionalSecurityLayer() hooks_conditional = doctest.DocFileSuite('hooks.txt', checker=checker) hooks_conditional.layer = ConditionalSecurityLayer() return unittest.TestSuite(( doctest.DocTestSuite(setUp=setUp, tearDown=tearDown), unittest.makeSuite(HookableTests), doctest.DocTestSuite('zope.component.interface', setUp=setUp, tearDown=tearDown), doctest.DocTestSuite('zope.component.nexttesting'), doctest.DocFileSuite('README.txt', setUp=setUp, tearDown=tearDown), doctest.DocFileSuite('socketexample.txt', setUp=setUp, tearDown=tearDown), doctest.DocFileSuite('factory.txt', setUp=setUp, tearDown=tearDown), doctest.DocFileSuite('registry.txt', checker=checker, setUp=setUpRegistryTests, tearDown=tearDownRegistryTests), doctest.DocFileSuite('hooks.txt',checker=checker, setUp=setUp, tearDown=tearDown), doctest.DocFileSuite('event.txt', setUp=setUp, tearDown=tearDown), doctest.DocTestSuite('zope.component.security'), doctest.DocFileSuite('zcml.txt', checker=checker, setUp=setUp, tearDown=tearDown), doctest.DocFileSuite('testlayer.txt', optionflags=(doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE + doctest.REPORT_NDIFF)), zcml_conditional, hooks_conditional, unittest.makeSuite(StandaloneTests), unittest.makeSuite(ResourceViewTests), )) if __name__ == "__main__": unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.component/src/zope/component/factory.txt0000644000175000017500000000553212214017426024377 0ustar arnauarnauFactories ========= The Factory Class ----------------- >>> from zope.interface import Interface >>> class IFunction(Interface): ... pass >>> class IKlass(Interface): ... pass >>> from zope.interface import implements >>> class Klass(object): ... implements(IKlass) ... ... def __init__(self, *args, **kw): ... self.args = args ... self.kw = kw >>> from zope.component.factory import Factory >>> factory = Factory(Klass, 'Klass', 'Klassier') >>> factory2 = Factory(lambda x: x, 'Func', 'Function') >>> factory3 = Factory(lambda x: x, 'Func', 'Function', (IFunction,)) Calling a Factory ~~~~~~~~~~~~~~~~~ Here we test whether the factory correctly creates the objects and including the correct handling of constructor elements. First we create a factory that creates instanace of the `Klass` class: >>> factory = Factory(Klass, 'Klass', 'Klassier') Now we use the factory to create the instance >>> kl = factory(1, 2, foo=3, bar=4) and make sure that the correct class was used to create the object: >>> kl.__class__ Since we passed in a couple positional and keyword arguments >>> kl.args (1, 2) >>> kl.kw {'foo': 3, 'bar': 4} >>> factory2(3) 3 >>> factory3(3) 3 Title and Description ~~~~~~~~~~~~~~~~~~~~~ >>> factory.title 'Klass' >>> factory.description 'Klassier' >>> factory2.title 'Func' >>> factory2.description 'Function' >>> factory3.title 'Func' >>> factory3.description 'Function' Provided Interfaces ~~~~~~~~~~~~~~~~~~~ >>> implemented = factory.getInterfaces() >>> implemented.isOrExtends(IKlass) True >>> list(implemented) [] >>> implemented2 = factory2.getInterfaces() >>> list(implemented2) [] >>> implemented3 = factory3.getInterfaces() >>> list(implemented3) [] The Component Architecture Factory API -------------------------------------- >>> import zope.component >>> factory = Factory(Klass, 'Klass', 'Klassier') >>> gsm = zope.component.getGlobalSiteManager() >>> from zope.component.interfaces import IFactory >>> gsm.registerUtility(factory, IFactory, 'klass') Creating an Object ~~~~~~~~~~~~~~~~~~ >>> kl = zope.component.createObject('klass', 1, 2, foo=3, bar=4) >>> isinstance(kl, Klass) True >>> kl.args (1, 2) >>> kl.kw {'foo': 3, 'bar': 4} Accessing Provided Interfaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >>> implemented = zope.component.getFactoryInterfaces('klass') >>> implemented.isOrExtends(IKlass) True >>> [iface for iface in implemented] [] List of All Factories ~~~~~~~~~~~~~~~~~~~~~ >>> [(name, fac.__class__) for name, fac in ... zope.component.getFactoriesFor(IKlass)] [(u'klass', )] zope2.13-2.13.21/source/zope.component/src/zope/component/zcml.py0000644000175000017500000005405212214017426023507 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Component Architecture configuration handlers """ __docformat__ = "reStructuredText" import warnings import zope.component import zope.configuration.fields import zope.interface import zope.schema from zope.component.interface import provideInterface from zope.configuration.exceptions import ConfigurationError from zope.i18nmessageid import MessageFactory try: from zope.component.security import _checker, proxify, protectedFactory, \ securityAdapterFactory from zope.security.zcml import Permission except ImportError: SECURITY_SUPPORT = False from zope.schema import TextLine as Permission else: SECURITY_SUPPORT = True _ = MessageFactory('zope') def check_security_support(): if not SECURITY_SUPPORT: raise ConfigurationError("security proxied components are not " "supported because zope.security is not available") def handler(methodName, *args, **kwargs): method = getattr(zope.component.getSiteManager(), methodName) method(*args, **kwargs) class IBasicComponentInformation(zope.interface.Interface): component = zope.configuration.fields.GlobalObject( title=_("Component to use"), description=_("Python name of the implementation object. This" " must identify an object in a module using the" " full dotted name. If specified, the" " ``factory`` field must be left blank."), required=False, ) permission = Permission( title=_("Permission"), description=_("Permission required to use this component."), required=False, ) factory = zope.configuration.fields.GlobalObject( title=_("Factory"), description=_("Python name of a factory which can create the" " implementation object. This must identify an" " object in a module using the full dotted name." " If specified, the ``component`` field must" " be left blank."), required=False, ) class IAdapterDirective(zope.interface.Interface): """ Register an adapter """ factory = zope.configuration.fields.Tokens( title=_("Adapter factory/factories"), description=_("A list of factories (usually just one) that create" " the adapter instance."), required=True, value_type=zope.configuration.fields.GlobalObject() ) provides = zope.configuration.fields.GlobalInterface( title=_("Interface the component provides"), description=_("This attribute specifies the interface the adapter" " instance must provide."), required=False, ) for_ = zope.configuration.fields.Tokens( title=_("Specifications to be adapted"), description=_("This should be a list of interfaces or classes"), required=False, value_type=zope.configuration.fields.GlobalObject( missing_value=object(), ), ) permission = Permission( title=_("Permission"), description=_("This adapter is only available, if the principal" " has this permission."), required=False, ) name = zope.schema.TextLine( title=_("Name"), description=_("Adapters can have names.\n\n" "This attribute allows you to specify the name for" " this adapter."), required=False, ) trusted = zope.configuration.fields.Bool( title=_("Trusted"), description=_("""Make the adapter a trusted adapter Trusted adapters have unfettered access to the objects they adapt. If asked to adapt security-proxied objects, then, rather than getting an unproxied adapter of security-proxied objects, you get a security-proxied adapter of unproxied objects. """), required=False, default=False, ) locate = zope.configuration.fields.Bool( title=_("Locate"), description=_("""Make the adapter a locatable adapter Located adapter should be used if a non-public permission is used. """), required=False, default=False, ) def _rolledUpFactory(factories): # This has to be named 'factory', aparently, so as not to confuse # apidoc :( def factory(ob): for f in factories: ob = f(ob) return ob # Store the original factory for documentation factory.factory = factories[0] return factory def adapter(_context, factory, provides=None, for_=None, permission=None, name='', trusted=False, locate=False): if for_ is None: if len(factory) == 1: for_ = zope.component.adaptedBy(factory[0]) if for_ is None: raise TypeError("No for attribute was provided and can't " "determine what the factory adapts.") for_ = tuple(for_) if provides is None: if len(factory) == 1: p = list(zope.interface.implementedBy(factory[0])) if len(p) == 1: provides = p[0] if provides is None: raise TypeError("Missing 'provides' attribute") # Generate a single factory from multiple factories: factories = factory if len(factories) == 1: factory = factories[0] elif len(factories) < 1: raise ValueError("No factory specified") elif len(factories) > 1 and len(for_) != 1: raise ValueError("Can't use multiple factories and multiple for") else: factory = _rolledUpFactory(factories) if permission is not None: check_security_support() factory = protectedFactory(factory, provides, permission) # invoke custom adapter factories if locate or permission is not None or trusted: check_security_support() factory = securityAdapterFactory(factory, permission, locate, trusted) _context.action( discriminator = ('adapter', for_, provides, name), callable = handler, args = ('registerAdapter', factory, for_, provides, name, _context.info), ) _context.action( discriminator = None, callable = provideInterface, args = ('', provides) ) if for_: for iface in for_: if iface is not None: _context.action( discriminator = None, callable = provideInterface, args = ('', iface) ) class ISubscriberDirective(zope.interface.Interface): """ Register a subscriber """ factory = zope.configuration.fields.GlobalObject( title=_("Subscriber factory"), description=_("A factory used to create the subscriber instance."), required=False, ) handler = zope.configuration.fields.GlobalObject( title=_("Handler"), description=_("A callable object that handles events."), required=False, ) provides = zope.configuration.fields.GlobalInterface( title=_("Interface the component provides"), description=_("This attribute specifies the interface the adapter" " instance must provide."), required=False, ) for_ = zope.configuration.fields.Tokens( title=_("Interfaces or classes that this subscriber depends on"), description=_("This should be a list of interfaces or classes"), required=False, value_type=zope.configuration.fields.GlobalObject( missing_value = object(), ), ) permission = Permission( title=_("Permission"), description=_("This subscriber is only available, if the" " principal has this permission."), required=False, ) trusted = zope.configuration.fields.Bool( title=_("Trusted"), description=_("""Make the subscriber a trusted subscriber Trusted subscribers have unfettered access to the objects they adapt. If asked to adapt security-proxied objects, then, rather than getting an unproxied subscriber of security-proxied objects, you get a security-proxied subscriber of unproxied objects. """), required=False, default=False, ) locate = zope.configuration.fields.Bool( title=_("Locate"), description=_("""Make the subscriber a locatable subscriber Located subscribers should be used if a non-public permission is used. """), required=False, default=False, ) _handler = handler def subscriber(_context, for_=None, factory=None, handler=None, provides=None, permission=None, trusted=False, locate=False): if factory is None: if handler is None: raise TypeError("No factory or handler provided") if provides is not None: raise TypeError("Cannot use handler with provides") factory = handler else: if handler is not None: raise TypeError("Cannot use handler with factory") if provides is None: raise TypeError( "You must specify a provided interface when registering " "a factory") if for_ is None: for_ = zope.component.adaptedBy(factory) if for_ is None: raise TypeError("No for attribute was provided and can't " "determine what the factory (or handler) adapts.") if permission is not None: check_security_support() factory = protectedFactory(factory, provides, permission) for_ = tuple(for_) # invoke custom adapter factories if locate or permission is not None or trusted: check_security_support() factory = securityAdapterFactory(factory, permission, locate, trusted) if handler is not None: _context.action( discriminator = None, callable = _handler, args = ('registerHandler', handler, for_, u'', _context.info), ) else: _context.action( discriminator = None, callable = _handler, args = ('registerSubscriptionAdapter', factory, for_, provides, u'', _context.info), ) if provides is not None: _context.action( discriminator = None, callable = provideInterface, args = ('', provides) ) # For each interface, state that the adapter provides that interface. for iface in for_: if iface is not None: _context.action( discriminator = None, callable = provideInterface, args = ('', iface) ) class IUtilityDirective(IBasicComponentInformation): """Register a utility.""" provides = zope.configuration.fields.GlobalInterface( title=_("Provided interface"), description=_("Interface provided by the utility."), required=False, ) name = zope.schema.TextLine( title=_("Name"), description=_("Name of the registration. This is used by" " application code when locating a utility."), required=False, ) def utility(_context, provides=None, component=None, factory=None, permission=None, name=''): if factory and component: raise TypeError("Can't specify factory and component.") if provides is None: if factory: provides = list(zope.interface.implementedBy(factory)) else: provides = list(zope.interface.providedBy(component)) if len(provides) == 1: provides = provides[0] else: raise TypeError("Missing 'provides' attribute") if permission is not None: check_security_support() component = proxify(component, provides=provides, permission=permission) _context.action( discriminator = ('utility', provides, name), callable = handler, args = ('registerUtility', component, provides, name), kw = dict(factory=factory), ) _context.action( discriminator = None, callable = provideInterface, args = (provides.__module__ + '.' + provides.getName(), provides) ) class IInterfaceDirective(zope.interface.Interface): """ Define an interface """ interface = zope.configuration.fields.GlobalInterface( title=_("Interface"), required=True, ) type = zope.configuration.fields.GlobalInterface( title=_("Interface type"), required=False, ) name = zope.schema.TextLine( title=_("Name"), required=False, ) def interface(_context, interface, type=None, name=''): _context.action( discriminator = None, callable = provideInterface, args = (name, interface, type) ) class IBasicViewInformation(zope.interface.Interface): """This is the basic information for all views.""" for_ = zope.configuration.fields.Tokens( title=_("Specifications of the objects to be viewed"), description=_("""This should be a list of interfaces or classes """), required=True, value_type=zope.configuration.fields.GlobalObject( missing_value=object(), ), ) permission = Permission( title=_("Permission"), description=_("The permission needed to use the view."), required=False, ) class_ = zope.configuration.fields.GlobalObject( title=_("Class"), description=_("A class that provides attributes used by the view."), required=False, ) layer = zope.configuration.fields.GlobalInterface( title=_("The layer the view is in."), description=_(""" A skin is composed of layers. It is common to put skin specific views in a layer named after the skin. If the 'layer' attribute is not supplied, it defaults to 'default'."""), required=False, ) allowed_interface = zope.configuration.fields.Tokens( title=_("Interface that is also allowed if user has permission."), description=_(""" By default, 'permission' only applies to viewing the view and any possible sub views. By specifying this attribute, you can make the permission also apply to everything described in the supplied interface. Multiple interfaces can be provided, separated by whitespace."""), required=False, value_type=zope.configuration.fields.GlobalInterface(), ) allowed_attributes = zope.configuration.fields.Tokens( title=_("View attributes that are also allowed if the user" " has permission."), description=_(""" By default, 'permission' only applies to viewing the view and any possible sub views. By specifying 'allowed_attributes', you can make the permission also apply to the extra attributes on the view object."""), required=False, value_type=zope.configuration.fields.PythonIdentifier(), ) class IBasicResourceInformation(zope.interface.Interface): """ Basic information for resources """ name = zope.schema.TextLine( title=_("The name of the resource."), description=_("The name shows up in URLs/paths. For example 'foo'."), required=True, default=u'', ) provides = zope.configuration.fields.GlobalInterface( title=_("The interface this component provides."), description=_(""" A view can provide an interface. This would be used for views that support other views."""), required=False, default=zope.interface.Interface, ) type = zope.configuration.fields.GlobalInterface( title=_("Request type"), required=True ) class IViewDirective(IBasicViewInformation, IBasicResourceInformation): """Register a view for a component""" factory = zope.configuration.fields.Tokens( title=_("Factory"), required=False, value_type=zope.configuration.fields.GlobalObject(), ) def view(_context, factory, type, name, for_, layer=None, permission=None, allowed_interface=None, allowed_attributes=None, provides=zope.interface.Interface): if ((allowed_attributes or allowed_interface) and (not permission)): raise ConfigurationError( "Must use name attribute with allowed_interface or " "allowed_attributes" ) if not factory: raise ConfigurationError("No view factory specified.") if permission is not None: check_security_support() checker = _checker(_context, permission, allowed_interface, allowed_attributes) class ProxyView(object): """Class to create simple proxy views.""" def __init__(self, factory, checker): self.factory = factory self.checker = checker def __call__(self, *objects): return proxify(self.factory(*objects), self.checker) factory[-1] = ProxyView(factory[-1], checker) if not for_: raise ValueError("No for interfaces specified"); for_ = tuple(for_) # Generate a single factory from multiple factories: factories = factory if len(factories) == 1: factory = factories[0] elif len(factories) < 1: raise ValueError("No factory specified") elif len(factories) > 1 and len(for_) > 1: raise ValueError("Can't use multiple factories and multiple for") else: def factory(ob, request): for f in factories[:-1]: ob = f(ob) return factories[-1](ob, request) # BBB 2006/02/18, to be removed after 12 months if layer is not None: for_ = for_ + (layer,) warnings.warn_explicit( "The 'layer' argument of the 'view' directive has been " "deprecated. Use the 'type' argument instead. If you have " "an existing 'type' argument IBrowserRequest, replace it with the " "'layer' argument (the layer subclasses IBrowserRequest). " "which subclasses BrowserRequest.", DeprecationWarning, _context.info.file, _context.info.line) else: for_ = for_ + (type,) _context.action( discriminator = ('view', for_, name, provides), callable = handler, args = ('registerAdapter', factory, for_, provides, name, _context.info), ) if type is not None: _context.action( discriminator = None, callable = provideInterface, args = ('', type) ) _context.action( discriminator = None, callable = provideInterface, args = ('', provides) ) if for_ is not None: for iface in for_: if iface is not None: _context.action( discriminator = None, callable = provideInterface, args = ('', iface) ) class IResourceDirective(IBasicComponentInformation, IBasicResourceInformation): """Register a resource""" layer = zope.configuration.fields.GlobalInterface( title=_("The layer the resource is in."), required=False, ) allowed_interface = zope.configuration.fields.Tokens( title=_("Interface that is also allowed if user has permission."), required=False, value_type=zope.configuration.fields.GlobalInterface(), ) allowed_attributes = zope.configuration.fields.Tokens( title=_("View attributes that are also allowed if user" " has permission."), required=False, value_type=zope.configuration.fields.PythonIdentifier(), ) def resource(_context, factory, type, name, layer=None, permission=None, allowed_interface=None, allowed_attributes=None, provides=zope.interface.Interface): if ((allowed_attributes or allowed_interface) and (not permission)): raise ConfigurationError( "Must use name attribute with allowed_interface or " "allowed_attributes" ) if permission is not None: check_security_support() checker = _checker(_context, permission, allowed_interface, allowed_attributes) def proxyResource(request, factory=factory, checker=checker): return proxify(factory(request), checker) factory = proxyResource if layer is not None: warnings.warn_explicit( "The 'layer' argument of the 'resource' directive has been " "deprecated. Use the 'type' argument instead.", DeprecationWarning, _context.info.file, _context.info.line) type = layer _context.action( discriminator = ('resource', name, type, provides), callable = handler, args = ('registerAdapter', factory, (type,), provides, name, _context.info), ) _context.action( discriminator = None, callable = provideInterface, args = (type.__module__ + '.' + type.__name__, type) ) _context.action( discriminator = None, callable = provideInterface, args = (provides.__module__ + '.' + provides.__name__, type) ) zope2.13-2.13.21/source/zope.component/src/zope/component/meta.zcml0000644000175000017500000000217712214017426024006 0ustar arnauarnau zope2.13-2.13.21/source/zope.component/src/zope/component/_declaration.py0000644000175000017500000000372512214017426025167 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Adapter declarations """ import types import sys class adapter: def __init__(self, *interfaces): self.interfaces = interfaces def __call__(self, ob): if isinstance(ob, _class_types): ob.__component_adapts__ = _adapts_descr(self.interfaces) else: ob.__component_adapts__ = self.interfaces return ob def adapts(*interfaces): frame = sys._getframe(1) locals = frame.f_locals # Try to make sure we were called from a class def. In 2.2.0 we can't # check for __module__ since it doesn't seem to be added to the locals # until later on. if (locals is frame.f_globals) or ( ('__module__' not in locals) and sys.version_info[:3] > (2, 2, 0)): raise TypeError("adapts can be used only from a class definition.") if '__component_adapts__' in locals: raise TypeError("adapts can be used only once in a class definition.") locals['__component_adapts__'] = _adapts_descr(interfaces) def adaptedBy(ob): return getattr(ob, '__component_adapts__', None) _class_types = type, types.ClassType class _adapts_descr(object): def __init__(self, interfaces): self.interfaces = interfaces def __get__(self, inst, cls): if inst is None: return self.interfaces raise AttributeError('__component_adapts__') zope2.13-2.13.21/source/zope.component/src/zope/component/index.txt0000644000175000017500000000046512214017426024037 0ustar arnauarnauWelcome to zope.component's documentation! ========================================== Contents: .. toctree:: :maxdepth: 2 README socketexample event factory registry persistentregistry zcml Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` zope2.13-2.13.21/source/zope.component/src/zope/component/testlayer.txt0000644000175000017500000000631112214017426024740 0ustar arnauarnauLayers ====== zope.component.testlayer defines two things: * a LayerBase that makes it easier and saner to use zope.testing's test layers. * a ZCMLLayer which lets you implement a layer that loads up some ZCML. LayerBase --------- We check whether our LayerBase can be used to create layers of our own. We do this simply by subclassing: >>> from zope.component.testlayer import LayerBase >>> class OurLayer(LayerBase): ... def setUp(self): ... super(OurLayer, self).setUp() ... print "setUp called" ... def tearDown(self): ... super(OurLayer, self).tearDown() ... print "tearDown called" ... def testSetUp(self): ... super(OurLayer, self).testSetUp() ... print "testSetUp called" ... def testTearDown(self): ... super(OurLayer, self).testTearDown() ... print "testTearDown called" Note that if we wanted to ensure that the methods of the superclass were called we have to use super(). In this case we actually wouldn't need to, as these methods do nothing at all, but we just ensure that they are there in the first place. Let's instantiate our layer. We need to supply it with the package the layer is defined in:: >>> import zope.component >>> layer = OurLayer(zope.component) Now we run some tests with this layer: >>> import unittest >>> class TestCase(unittest.TestCase): ... layer = layer ... ... def testFoo(self): ... print "testFoo" >>> suite = unittest.TestSuite() >>> suite.addTest(unittest.makeSuite(TestCase)) >>> from zope.testing.testrunner.runner import Runner >>> runner = Runner(args=[], found_suites=[suite]) >>> succeeded = runner.run() Running zope.component.OurLayer tests: Set up zope.component.OurLayer setUp called in ... seconds. testSetUp called testFoo testTearDown called Ran 1 tests with 0 failures and 0 errors in ... seconds. Tearing down left over layers: Tear down zope.component.OurLayer tearDown called in ... seconds. ZCMLLayer --------- We now want a layer that loads up some ZCML from a file. The default is ``ftesting.zcml``, but here we'll load a test ``testlayer.zcml``. >>> from zope.component.testlayer import ZCMLFileLayer >>> zcml_file_layer = ZCMLFileLayer( ... zope.component.testfiles, ... 'testlayer.zcml') >>> class TestCase(unittest.TestCase): ... layer = zcml_file_layer ... ... def testFoo(self): ... # we should now have the adapter registered ... from zope import component ... from zope.component.testfiles import components ... self.assert_(isinstance( ... components.IApp2(components.content), components.Comp2)) Since the ZCML sets up an adapter, we expect the tests to pass:: >>> suite = unittest.TestSuite() >>> suite.addTest(unittest.makeSuite(TestCase)) >>> runner = Runner(args=[], found_suites=[suite]) >>> succeeded = runner.run() Running zope.component.testfiles.ZCMLFileLayer tests: Set up zope.component.testfiles.ZCMLFileLayer in ... seconds. Ran 1 tests with 0 failures and 0 errors in ... seconds. Tearing down left over layers: Tear down zope.component.testfiles.ZCMLFileLayer in ... seconds. zope2.13-2.13.21/source/zope.component/src/zope/component/socketexample.txt0000644000175000017500000005043612214017426025577 0ustar arnauarnauThe Zope 3 Component Architecture (Socket Example) ================================================== The component architecture provides an application framework that provides its functionality through loosely-connected components. A *component* can be any Python object and has a particular purpose associated with it. Thus, in a component-based applications you have many small component in contrast to classical object-oriented development, where you have a few big objects. Components communicate via specific APIs, which are formally defined by interfaces, which are provided by the `zope.interface` package. *Interfaces* describe the methods and properties that a component is expected to provide. They are also used as a primary mean to provide developer-level documentation for the components. For more details about interfaces see `zope/interface/README.txt`. The two main types of components are *adapters* and *utilities*. They will be discussed in detail later in this document. Both component types are managed by the *site manager*, with which you can register and access these components. However, most of the site manager's functionality is hidden behind the component architecture's public API, which is documented in `IComponentArchitecture`. Adapters -------- Adapters are a well-established pattern. An *adapter* uses an object providing one interface to produce an object that provides another interface. Here an example: Imagine that you purchased an electric shaver in the US, and thus you require the US socket type. You are now traveling in Germany, where another socket style is used. You will need a device, an adapter, that converts from the German to the US socket style. The functionality of adapters is actually natively provided by the `zope.interface` package and is thus well documented there. The `human.txt` file provides a gentle introduction to adapters, whereby `adapter.txt` is aimed at providing a comprehensive insight into adapters, but is too abstract for many as an initial read. Thus, we will only explain adapters in the context of the component architecture's API. So let's say that we have a German socket >>> from zope.interface import Interface, implements >>> class IGermanSocket(Interface): ... pass >>> class Socket(object): ... def __repr__(self): ... return '' %self.__class__.__name__ >>> class GermanSocket(Socket): ... """German wall socket.""" ... implements(IGermanSocket) and we want to convert it to an US socket >>> class IUSSocket(Interface): ... pass so that our shaver can be used in Germany. So we go to a German electronics store to look for an adapter that we can plug in the wall: >>> class GermanToUSSocketAdapter(Socket): ... implements(IUSSocket) ... __used_for__ = IGermanSocket ... ... def __init__(self, socket): ... self.context = socket Note that I could have called the passed in socket any way I like, but `context` is the standard name accepted. Single Adapters ~~~~~~~~~~~~~~~ Before we can use the adapter, we have to buy it and make it part of our inventory. In the component architecture we do this by registering the adapter with the framework, more specifically with the global site manager: >>> import zope.component >>> gsm = zope.component.getGlobalSiteManager() >>> gsm.registerAdapter(GermanToUSSocketAdapter, (IGermanSocket,), IUSSocket) `zope.component` is the component architecture API that is being presented by this file. You registered an adapter from `IGermanSocket` to `IUSSocket` having no name (thus the empty string). Anyways, you finally get back to your hotel room and shave, since you have not been able to shave in the plane. In the bathroom you discover a socket: >>> bathroomDE = GermanSocket() >>> IGermanSocket.providedBy(bathroomDE) True You now insert the adapter in the German socket >>> bathroomUS = zope.component.getAdapter(bathroomDE, IUSSocket, '') so that the socket now provides the US version: >>> IUSSocket.providedBy(bathroomUS) True Now you can insert your shaver and get on with your day. After a week you travel for a couple of days to the Prague and you notice that the Czech have yet another socket type: >>> class ICzechSocket(Interface): ... pass >>> class CzechSocket(Socket): ... implements(ICzechSocket) >>> czech = CzechSocket() You try to find an adapter for your shaver in your bag, but you fail, since you do not have one: >>> zope.component.getAdapter(czech, IUSSocket, '') \ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, , '') or the more graceful way: >>> marker = object() >>> socket = zope.component.queryAdapter(czech, IUSSocket, '', marker) >>> socket is marker True In the component architecture API any `get*` method will fail with a specific exception, if a query failed, whereby methods starting with `query*` will always return a `default` value after a failure. Named Adapters ~~~~~~~~~~~~~~ You are finally back in Germany. You also brought your DVD player and a couple DVDs with you, which you would like to watch. Your shaver was able to convert automatically from 110 volts to 240 volts, but your DVD player cannot. So you have to buy another adapter that also handles converting the voltage and the frequency of the AC current: >>> class GermanToUSSocketAdapterAndTransformer(object): ... implements(IUSSocket) ... __used_for__ = IGermanSocket ... ... def __init__(self, socket): ... self.context = socket Now, we need a way to keep the two adapters apart. Thus we register them with a name: >>> gsm.registerAdapter(GermanToUSSocketAdapter, ... (IGermanSocket,), IUSSocket, 'shaver',) >>> gsm.registerAdapter(GermanToUSSocketAdapterAndTransformer, ... (IGermanSocket,), IUSSocket, 'dvd') Now we simply look up the adapters using their labels (called *name*): >>> socket = zope.component.getAdapter(bathroomDE, IUSSocket, 'shaver') >>> socket.__class__ is GermanToUSSocketAdapter True >>> socket = zope.component.getAdapter(bathroomDE, IUSSocket, 'dvd') >>> socket.__class__ is GermanToUSSocketAdapterAndTransformer True Clearly, we do not have an adapter for the MP3 player >>> zope.component.getAdapter(bathroomDE, IUSSocket, 'mp3') \ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, , 'mp3') but you could use the 'dvd' adapter in this case of course. ;) Sometimes you want to know all adapters that are available. Let's say you want to know about all the adapters that convert a German to a US socket type: >>> sockets = list(zope.component.getAdapters((bathroomDE,), IUSSocket)) >>> len(sockets) 3 >>> names = [name for name, socket in sockets] >>> names.sort() >>> names [u'', u'dvd', u'shaver'] `zope.component.getAdapters()` returns a list of tuples. The first entry of the tuple is the name of the adapter and the second is the adapter itself. Multi-Adapters ~~~~~~~~~~~~~~ After watching all the DVDs you brought at least twice, you get tired of them and you want to listen to some music using your MP3 player. But darn, the MP3 player plug has a ground pin and all the adapters you have do not support that: >>> class IUSGroundedSocket(IUSSocket): ... pass So you go out another time to buy an adapter. This time, however, you do not buy yet another adapter, but a piece that provides the grounding plug: >>> class IGrounder(Interface): ... pass >>> class Grounder(object): ... implements(IGrounder) ... def __repr__(self): ... return '' Then together they will provided a grounded us socket: >>> class GroundedGermanToUSSocketAdapter(object): ... implements(IUSGroundedSocket) ... __used_for__ = (IGermanSocket, IGrounder) ... def __init__(self, socket, grounder): ... self.socket, self.grounder = socket, grounder You now register the combination, so that you know you can create a `IUSGroundedSocket`: >>> gsm.registerAdapter(GroundedGermanToUSSocketAdapter, ... (IGermanSocket, IGrounder), IUSGroundedSocket, 'mp3') Given the grounder >>> grounder = Grounder() and a German socket >>> livingroom = GermanSocket() we can now get a grounded US socket: >>> socket = zope.component.getMultiAdapter((livingroom, grounder), ... IUSGroundedSocket, 'mp3') >>> socket.__class__ is GroundedGermanToUSSocketAdapter True >>> socket.socket is livingroom True >>> socket.grounder is grounder True Of course, you do not have a 'dvd' grounded US socket available: >>> zope.component.getMultiAdapter((livingroom, grounder), ... IUSGroundedSocket, 'dvd') \ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: ((, ), , 'dvd') >>> socket = zope.component.queryMultiAdapter( ... (livingroom, grounder), IUSGroundedSocket, 'dvd', marker) >>> socket is marker True Again, you might want to read `adapter.txt` in `zope.interface` for a more comprehensive coverage of multi-adapters. Subscribers ----------- While subscribers are directly supported by the adapter registry and are adapters for all theoretical purposes, practically it might be better to think of them as separate components. Subscribers are particularly useful for events. Let's say one of our adapters overheated and caused a small fire: >>> class IFire(Interface): ... pass >>> class Fire(object): ... implements(IFire) >>> fire = Fire() We want to use all available objects to put out the fire: >>> class IFireExtinguisher(Interface): ... def extinguish(): ... pass >>> class FireExtinguisher(object): ... def __init__(self, fire): ... pass ... def extinguish(self): ... "Place extinguish code here." ... print 'Used ' + self.__class__.__name__ + '.' Here some specific methods to put out the fire: >>> class PowderExtinguisher(FireExtinguisher): ... pass >>> gsm.registerSubscriptionAdapter(PowderExtinguisher, ... (IFire,), IFireExtinguisher) >>> class Blanket(FireExtinguisher): ... pass >>> gsm.registerSubscriptionAdapter(Blanket, (IFire,), IFireExtinguisher) >>> class SprinklerSystem(FireExtinguisher): ... pass >>> gsm.registerSubscriptionAdapter(SprinklerSystem, ... (IFire,), IFireExtinguisher) Now let use all these things to put out the fire: >>> extinguishers = zope.component.subscribers((fire,), IFireExtinguisher) >>> extinguishers.sort() >>> for extinguisher in extinguishers: ... extinguisher.extinguish() Used Blanket. Used PowderExtinguisher. Used SprinklerSystem. If no subscribers are found for a particular object, then an empty list is returned: >>> zope.component.subscribers((object(),), IFireExtinguisher) [] Utilities --------- Utilities are the second type of component, the component architecture implements. *Utilities* are simply components that provide an interface. When you register an utility, you always register an instance (in contrast to a factory for adapters) since the initialization and setup process of a utility might be complex and is not well defined. In some ways a utility is much more fundamental than an adapter, because an adapter cannot be used without another component, but a utility is always self-contained. I like to think of utilities as the foundation of your application and adapters as components extending beyond this foundation. Back to our story... After your vacation is over you fly back home to Tampa, Florida. But it is August now, the middle of the Hurricane season. And, believe it or not, you are worried that you will not be able to shave when the power goes out for several days. (You just hate wet shavers.) So you decide to go to your favorite hardware store and by a Diesel-powered electric generator. The generator provides of course a US-style socket: >>> class Generator(object): ... implements(IUSSocket) ... def __repr__(self): ... return '' >>> generator = Generator() Like for adapters, we now have to add the newly-acquired generator to our inventory by registering it as a utility: >>> gsm.registerUtility(generator, IUSSocket) We can now get the utility using >>> utility = zope.component.getUtility(IUSSocket) >>> utility is generator True As you can see, it is very simple to register and retrieve utilities. If a utility does not exist for a particular interface, such as the German socket, then the lookup fails >>> zope.component.getUtility(IGermanSocket) Traceback (most recent call last): ... ComponentLookupError: (, '') or more gracefully when specifying a default value: >>> default = object() >>> utility = zope.component.queryUtility(IGermanSocket, default=default) >>> utility is default True Note: The only difference between `getUtility()` and `queryUtility()` is the fact that you can specify a default value for the latter function, so that it will never cause a `ComponentLookupError`. Named Utilities ~~~~~~~~~~~~~~~ It is often desirable to have several utilities providing the same interface per site. This way you can implement any sort of registry using utilities. For this reason, utilities -- like adapters -- can be named. In the context of our story, we might want to do the following: You really do not trust gas stations either. What if the roads are blocked after a hurricane and the gas stations run out of oil. So you look for another renewable power source. Then you think about solar panels! After a storm there is usually very nice weather, so why not? Via the Web you order a set of 110V/120W solar panels that provide a regular US-style socket as output: >>> class SolarPanel(object): ... implements(IUSSocket) ... def __repr__(self): ... return '' >>> panel = SolarPanel() Once it arrives, we add it to our inventory: >>> gsm.registerUtility(panel, IUSSocket, 'Solar Panel') You can now access the solar panel using >>> utility = zope.component.getUtility(IUSSocket, 'Solar Panel') >>> utility is panel True Of course, if a utility is not available, then the lookup will simply fail >>> zope.component.getUtility(IUSSocket, 'Wind Mill') Traceback (most recent call last): ... ComponentLookupError: (, 'Wind Mill') or more gracefully when specifying a default value: >>> default = object() >>> utility = zope.component.queryUtility(IUSSocket, 'Wind Mill', ... default=default) >>> utility is default True Now you want to look at all the utilities you have for a particular kind. The following API function will return a list of name/utility pairs: >>> utils = list(zope.component.getUtilitiesFor(IUSSocket)) >>> utils.sort() >>> utils #doctest: +NORMALIZE_WHITESPACE [(u'', ), (u'Solar Panel', )] Another method of looking up all utilities is by using `getAllUtilitiesRegisteredFor(iface)`. This function will return an iterable of utilities (without names); however, it will also return overridden utilities. If you are not using multiple site managers, you will not actually need this method. >>> utils = list(zope.component.getAllUtilitiesRegisteredFor(IUSSocket)) >>> utils.sort() >>> utils [, ] Factories ~~~~~~~~~ A *factory* is a special kind of utility that exists to create other components. A factory is always identified by a name. It also provides a title and description and is able to tell the developer what interfaces the created object will provide. The advantage of using a factory to create an object instead of directly instantiating a class or executing any other callable is that we can refer to the factory by name. As long as the name stays fixed, the implementation of the callable can be renamed or moved without a breakage in code. Let's say that our solar panel comes in parts and they have to be assembled. This assembly would be done by a factory, so let's create one for the solar panel. To do this, we can use a standard implementation of the `IFactory` interface: >>> from zope.component.factory import Factory >>> factory = Factory(SolarPanel, ... 'Solar Panel', ... 'This factory creates a solar panel.') Optionally, I could have also specified the interfaces that the created object will provide, but the factory class is smart enough to determine the implemented interface from the class. We now register the factory: >>> from zope.component.interfaces import IFactory >>> gsm.registerUtility(factory, IFactory, 'SolarPanel') We can now get a list of interfaces the produced object will provide: >>> ifaces = zope.component.getFactoryInterfaces('SolarPanel') >>> IUSSocket in ifaces True By the way, this is equivalent to >>> ifaces2 = factory.getInterfaces() >>> ifaces is ifaces2 True Of course you can also just create an object: >>> panel = zope.component.createObject('SolarPanel') >>> panel.__class__ is SolarPanel True Note: Ignore the first argument (`None`) for now; it is the context of the utility lookup, which is usually an optional argument, but cannot be in this case, since all other arguments beside the `name` are passed in as arguments to the specified callable. Once you register several factories >>> gsm.registerUtility(Factory(Generator), IFactory, 'Generator') you can also determine, which available factories will create objects providing a certain interface: >>> factories = zope.component.getFactoriesFor(IUSSocket) >>> factories = [(name, factory.__class__) for name, factory in factories] >>> factories.sort() >>> factories #doctest: +NORMALIZE_WHITESPACE [(u'Generator', ), (u'SolarPanel', )] Site Managers ------------- Why do we need site managers? Why is the component architecture API not sufficient? Some applications, including Zope 3, have a concept of locations. It is often desirable to have different configurations for these location; this can be done by overwriting existing or adding new component registrations. Site managers in locations below the root location, should be able to delegate requests to their parent locations. The root site manager is commonly known as *global site manager*, since it is always available. You can always get the global site manager using the API: >>> gsm = zope.component.getGlobalSiteManager() >>> from zope.component import globalSiteManager >>> gsm is globalSiteManager True >>> from zope.component.interfaces import IComponentLookup >>> IComponentLookup.providedBy(gsm) True >>> from zope.component.interfaces import IComponents >>> IComponents.providedBy(gsm) True You can also lookup at site manager in a given context. The only requirement is that the context can be adapted to a site manager. So let's create a special site manager: >>> from zope.component.globalregistry import BaseGlobalComponents >>> sm = BaseGlobalComponents() Now we create a context that adapts to the site manager via the `__conform__` method as specified in PEP 246. >>> class Context(object): ... def __init__(self, sm): ... self.sm = sm ... def __conform__(self, interface): ... if interface.isOrExtends(IComponentLookup): ... return self.sm We now instantiate the `Context` with our special site manager: >>> context = Context(sm) >>> context.sm is sm True We can now ask for the site manager of this context: >>> lsm = zope.component.getSiteManager(context) >>> lsm is sm True The site manager instance `lsm` is formally known as a *local site manager* of `context`. zope2.13-2.13.21/source/zope.component/src/zope/component/testfiles/0000755000175000017500000000000012214017426024164 5ustar arnauarnauzope2.13-2.13.21/source/zope.component/src/zope/component/testfiles/components.py0000644000175000017500000000260312214017426026724 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Components for testing """ from zope.interface import Interface, Attribute, implements from zope.component import adapts class IAppb(Interface): a = Attribute('test attribute') def f(): "test func" class IApp(IAppb): pass class IApp2(IAppb): pass class IApp3(IAppb): pass class IContent(Interface): pass class Content(object): implements(IContent) class Comp(object): adapts(IContent) implements(IApp) def __init__(self, *args): # Ignore arguments passed to constructor pass a = 1 def f(): pass class Comp2(object): def __init__(self, context): self.context = context class Comp3(object): def __init__(self, context): self.context = context comp = Comp() content = Content() zope2.13-2.13.21/source/zope.component/src/zope/component/testfiles/__init__.py0000644000175000017500000000002012214017426026265 0ustar arnauarnau#Python package zope2.13-2.13.21/source/zope.component/src/zope/component/testfiles/views.py0000644000175000017500000000263312214017426025677 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Views test. """ from zope.interface import Interface, implements, directlyProvides class Request(object): def __init__(self, type): directlyProvides(self, type) class IR(Interface): pass class IV(Interface): def index(): pass class IC(Interface): pass class V1(object): implements(IV) def __init__(self, context, request): self.context = context self.request = request def index(self): return 'V1 here' def action(self): return 'done' class VZMI(V1): def index(self): return 'ZMI here' class R1(object): def index(self): return 'R1 here' def action(self): return 'R done' def __init__(self, request): pass implements(IV) class RZMI(R1): pass zope2.13-2.13.21/source/zope.component/src/zope/component/testfiles/testlayer.zcml0000644000175000017500000000046212214017426027071 0ustar arnauarnau zope2.13-2.13.21/source/zope.component/src/zope/component/testfiles/adapter.py0000644000175000017500000000270712214017426026164 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample adapter class for testing """ import zope.interface import zope.component import components class I1(zope.interface.Interface): pass class I2(zope.interface.Interface): pass class I3(zope.interface.Interface): def f1(): pass def f2(): pass def f3(): pass class IS(zope.interface.Interface): pass class Adapter(object): def __init__(self, *args): self.context = args class A1(Adapter): zope.interface.implements(I1) class A2(Adapter): zope.interface.implements(I2) class A3(Adapter): zope.component.adapts(components.IContent, I1, I2) zope.interface.implements(I3) class A4: pass a4 = A4() class A5: zope.interface.implements(I1, I2) a5 = A5() def Handler(content, *args): # uninteresting handler content.args = getattr(content, 'args', ()) + (args, ) zope2.13-2.13.21/source/zope.component/src/zope/component/testing.py0000644000175000017500000000171312214017426024213 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Placeless Test Setup """ # HACK to make sure basicmost event subscriber is installed import zope.component.event # we really don't need special setup now: from zope.testing.cleanup import CleanUp as PlacelessSetup def setUp(test=None): PlacelessSetup().setUp() def tearDown(test=None): PlacelessSetup().tearDown() zope2.13-2.13.21/source/zope.component/src/zope/__init__.py0000644000175000017500000000007012214017426022266 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.component/src/zope.component.egg-info/0000755000175000017500000000000012214017426023653 5ustar arnauarnauzope2.13-2.13.21/source/zope.component/src/zope.component.egg-info/PKG-INFO0000644000175000017500000032223112214017426024753 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.component Version: 3.9.5 Summary: Zope Component Architecture Home-page: http://pypi.python.org/pypi/zope.component Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ***************************** zope.component Package Readme ***************************** *This package is intended to be independently reusable in any Python project. It is maintained by the* `Zope Toolkit project `_. This package represents the core of the Zope Component Architecture. Together with the 'zope.interface' package, it provides facilities for defining, registering and looking up components. .. contents:: Detailed Documentation ********************** Zope Component Architecture =========================== This package, together with `zope.interface`, provides facilities for defining, registering and looking up components. There are two basic kinds of components: adapters and utilities. Utilities --------- Utilities are just components that provide an interface and that are looked up by an interface and a name. Let's look at a trivial utility definition: >>> from zope import interface >>> class IGreeter(interface.Interface): ... def greet(): ... "say hello" >>> class Greeter: ... interface.implements(IGreeter) ... ... def __init__(self, other="world"): ... self.other = other ... ... def greet(self): ... print "Hello", self.other We can register an instance this class using `provideUtility` [1]_: >>> from zope import component >>> greet = Greeter('bob') >>> component.provideUtility(greet, IGreeter, 'robert') In this example we registered the utility as providing the `IGreeter` interface with a name of 'bob'. We can look the interface up with either `queryUtility` or `getUtility`: >>> component.queryUtility(IGreeter, 'robert').greet() Hello bob >>> component.getUtility(IGreeter, 'robert').greet() Hello bob `queryUtility` and `getUtility` differ in how failed lookups are handled: >>> component.queryUtility(IGreeter, 'ted') >>> component.queryUtility(IGreeter, 'ted', 42) 42 >>> component.getUtility(IGreeter, 'ted') ... # doctest: +ELLIPSIS Traceback (most recent call last): ... ComponentLookupError: (, 'ted') If a component provides only one interface, as in the example above, then we can omit the provided interface from the call to `provideUtility`: >>> ted = Greeter('ted') >>> component.provideUtility(ted, name='ted') >>> component.queryUtility(IGreeter, 'ted').greet() Hello ted The name defaults to an empty string: >>> world = Greeter() >>> component.provideUtility(world) >>> component.queryUtility(IGreeter).greet() Hello world Adapters -------- Adapters are components that are computed from other components to adapt them to some interface. Because they are computed from other objects, they are provided as factories, usually classes. Here, we'll create a greeter for persons, so we can provide personalized greetings for different people: >>> class IPerson(interface.Interface): ... name = interface.Attribute("Name") >>> class PersonGreeter: ... ... component.adapts(IPerson) ... interface.implements(IGreeter) ... ... def __init__(self, person): ... self.person = person ... ... def greet(self): ... print "Hello", self.person.name The class defines a constructor that takes an argument for every object adapted. We used `component.adapts` to declare what we adapt. We can find out if an object declares that it adapts anything using adaptedBy: >>> list(component.adaptedBy(PersonGreeter)) == [IPerson] True If an object makes no declaration, then None is returned: >>> component.adaptedBy(Greeter()) is None True If we declare the interfaces adapted and if we provide only one interface, as in the example above, then we can provide the adapter very simply [1]_: >>> component.provideAdapter(PersonGreeter) For adapters that adapt a single interface to a single interface without a name, we can get the adapter by simply calling the interface: >>> class Person: ... interface.implements(IPerson) ... ... def __init__(self, name): ... self.name = name >>> IGreeter(Person("Sally")).greet() Hello Sally We can also provide arguments to be very specific about what how to register the adapter. >>> class BobPersonGreeter(PersonGreeter): ... name = 'Bob' ... def greet(self): ... print "Hello", self.person.name, "my name is", self.name >>> component.provideAdapter( ... BobPersonGreeter, [IPerson], IGreeter, 'bob') The arguments can also be provided as keyword arguments: >>> class TedPersonGreeter(BobPersonGreeter): ... name = "Ted" >>> component.provideAdapter( ... factory=TedPersonGreeter, adapts=[IPerson], ... provides=IGreeter, name='ted') For named adapters, use `queryAdapter`, or `getAdapter`: >>> component.queryAdapter(Person("Sally"), IGreeter, 'bob').greet() Hello Sally my name is Bob >>> component.getAdapter(Person("Sally"), IGreeter, 'ted').greet() Hello Sally my name is Ted If an adapter can't be found, `queryAdapter` returns a default value and `getAdapter` raises an error: >>> component.queryAdapter(Person("Sally"), IGreeter, 'frank') >>> component.queryAdapter(Person("Sally"), IGreeter, 'frank', 42) 42 >>> component.getAdapter(Person("Sally"), IGreeter, 'frank') ... # doctest: +ELLIPSIS Traceback (most recent call last): ... ComponentLookupError: (...Person...>, <...IGreeter>, 'frank') Adapters can adapt multiple objects: >>> class TwoPersonGreeter: ... ... component.adapts(IPerson, IPerson) ... interface.implements(IGreeter) ... ... def __init__(self, person, greeter): ... self.person = person ... self.greeter = greeter ... ... def greet(self): ... print "Hello", self.person.name ... print "my name is", self.greeter.name >>> component.provideAdapter(TwoPersonGreeter) To look up a multi-adapter, use either `queryMultiAdapter` or `getMultiAdapter`: >>> component.queryMultiAdapter((Person("Sally"), Person("Bob")), ... IGreeter).greet() Hello Sally my name is Bob Adapters need not be classes. Any callable will do. We use the adapter decorator (in the Python 2.4 decorator sense) to declare that a callable object adapts some interfaces (or classes): >>> class IJob(interface.Interface): ... "A job" >>> class Job: ... interface.implements(IJob) >>> def personJob(person): ... return getattr(person, 'job', None) >>> personJob = interface.implementer(IJob)(personJob) >>> personJob = component.adapter(IPerson)(personJob) In Python 2.4, the example can be written: >>> @interface.implementer(IJob) ... @component.adapter(IPerson) ... def personJob(person): ... return getattr(person, 'job', None) which looks a bit nicer. In this example, the personJob function simply returns the person's `job` attribute if present, or None if it's not present. An adapter factory can return None to indicate that adaptation wasn't possible. Let's register this adapter and try it out: >>> component.provideAdapter(personJob) >>> sally = Person("Sally") >>> IJob(sally) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: ('Could not adapt', ... The adaptation failed because sally didn't have a job. Let's give her one: >>> job = Job() >>> sally.job = job >>> IJob(sally) is job True Subscription Adapters --------------------- Unlike regular adapters, subscription adapters are used when we want all of the adapters that adapt an object to a particular adapter. Consider a validation problem. We have objects and we want to assess whether they meet some sort of standards. We define a validation interface: >>> class IValidate(interface.Interface): ... def validate(ob): ... """Determine whether the object is valid ... ... Return a string describing a validation problem. ... An empty string is returned to indicate that the ... object is valid. ... """ Perhaps we have documents: >>> class IDocument(interface.Interface): ... summary = interface.Attribute("Document summary") ... body = interface.Attribute("Document text") >>> class Document: ... interface.implements(IDocument) ... def __init__(self, summary, body): ... self.summary, self.body = summary, body Now, we may want to specify various validation rules for documents. For example, we might require that the summary be a single line: >>> class SingleLineSummary: ... component.adapts(IDocument) ... interface.implements(IValidate) ... ... def __init__(self, doc): ... self.doc = doc ... ... def validate(self): ... if '\n' in self.doc.summary: ... return 'Summary should only have one line' ... else: ... return '' Or we might require the body to be at least 1000 characters in length: >>> class AdequateLength: ... component.adapts(IDocument) ... interface.implements(IValidate) ... ... def __init__(self, doc): ... self.doc = doc ... ... def validate(self): ... if len(self.doc.body) < 1000: ... return 'too short' ... else: ... return '' We can register these as subscription adapters [1]_: >>> component.provideSubscriptionAdapter(SingleLineSummary) >>> component.provideSubscriptionAdapter(AdequateLength) We can then use the subscribers to validate objects: >>> doc = Document("A\nDocument", "blah") >>> [adapter.validate() ... for adapter in component.subscribers([doc], IValidate) ... if adapter.validate()] ['Summary should only have one line', 'too short'] >>> doc = Document("A\nDocument", "blah" * 1000) >>> [adapter.validate() ... for adapter in component.subscribers([doc], IValidate) ... if adapter.validate()] ['Summary should only have one line'] >>> doc = Document("A Document", "blah") >>> [adapter.validate() ... for adapter in component.subscribers([doc], IValidate) ... if adapter.validate()] ['too short'] Handlers -------- Handlers are subscription adapter factories that don't produce anything. They do all of their work when called. Handlers are typically used to handle events. Event subscribers are different from other subscription adapters in that the caller of event subscribers doesn't expect to interact with them in any direct way. For example, an event publisher doesn't expect to get any return value. Because subscribers don't need to provide an API to their callers, it is more natural to define them with functions, rather than classes. For example, in a document-management system, we might want to record creation times for documents: >>> import datetime >>> def documentCreated(event): ... event.doc.created = datetime.datetime.utcnow() In this example, we have a function that takes an event and performs some processing. It doesn't actually return anything. This is a special case of a subscription adapter that adapts an event to nothing. All of the work is done when the adapter "factory" is called. We call subscribers that don't actually create anything "handlers". There are special APIs for registering and calling them. To register the subscriber above, we define a document-created event: >>> class IDocumentCreated(interface.Interface): ... doc = interface.Attribute("The document that was created") >>> class DocumentCreated: ... interface.implements(IDocumentCreated) ... ... def __init__(self, doc): ... self.doc = doc We'll also change our handler definition to: >>> def documentCreated(event): ... event.doc.created = datetime.datetime.utcnow() >>> documentCreated = component.adapter(IDocumentCreated)(documentCreated) Note that in Python 2.4, this can be written: >>> @component.adapter(IDocumentCreated) ... def documentCreated(event): ... event.doc.created = datetime.datetime.utcnow() This marks the handler as an adapter of `IDocumentCreated` events. Now we'll register the handler [1]_: >>> component.provideHandler(documentCreated) Now, if we can create an event and use the `handle` function to call handlers registered for the event: >>> component.handle(DocumentCreated(doc)) >>> doc.created.__class__.__name__ 'datetime' .. [1] CAUTION: This API should only be used from test or application-setup code. This API shouldn't be used by regular library modules, as component registration is a configuration activity. Events ====== The Component Architecture provides a way to dispatch events to event handlers. Event handlers are registered as *subscribers* a.k.a. *handlers*. Before we can start we need to import ``zope.component.event`` to make the dispatching effective: >>> import zope.component.event Consider two event classes: >>> class Event1(object): ... pass >>> class Event2(Event1): ... pass Now consider two handlers for these event classes: >>> called = [] >>> import zope.component >>> @zope.component.adapter(Event1) ... def handler1(event): ... called.append(1) >>> @zope.component.adapter(Event2) ... def handler2(event): ... called.append(2) We can register them with the Component Architecture: >>> zope.component.provideHandler(handler1) >>> zope.component.provideHandler(handler2) Now let's go through the events. We'll see that the handlers have been called accordingly: >>> from zope.event import notify >>> notify(Event1()) >>> called [1] >>> del called[:] >>> notify(Event2()) >>> called.sort() >>> called [1, 2] Object events ------------- The ``objectEventNotify`` function is a subscriber to dispatch ObjectEvents to interested adapters. First create an object class: >>> class IUseless(zope.interface.Interface): ... """Useless object""" >>> class UselessObject(object): ... """Useless object""" ... zope.interface.implements(IUseless) Then create an event class: >>> class IObjectThrownEvent(zope.component.interfaces.IObjectEvent): ... """An object has been thrown away""" >>> class ObjectThrownEvent(zope.component.interfaces.ObjectEvent): ... """An object has been thrown away""" ... zope.interface.implements(IObjectThrownEvent) Create an object and an event: >>> hammer = UselessObject() >>> event = ObjectThrownEvent(hammer) Then notify the event to the subscribers. Since the subscribers list is empty, nothing happens. >>> zope.component.event.objectEventNotify(event) Now create an handler for the event: >>> events = [] >>> def record(*args): ... events.append(args) >>> zope.component.provideHandler(record, [IUseless, IObjectThrownEvent]) The event is notified to the subscriber: >>> zope.component.event.objectEventNotify(event) >>> events == [(hammer, event)] True Following test demonstrates how a subscriber can raise an exception to prevent an action. >>> zope.component.provideHandler(zope.component.event.objectEventNotify) Let's create a container: >>> class ToolBox(dict): ... def __delitem__(self, key): ... notify(ObjectThrownEvent(self[key])) ... return super(ToolBox,self).__delitem__(key) >>> container = ToolBox() And put the object into the container: >>> container['Red Hammer'] = hammer Create an handler function that will raise an error when called: >>> class Veto(Exception): ... pass >>> def callback(item, event): ... assert(item == event.object) ... raise Veto Register the handler: >>> zope.component.provideHandler(callback, [IUseless, IObjectThrownEvent]) Then if we try to remove the object, an ObjectThrownEvent is fired: >>> del container['Red Hammer'] ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... raise Veto Veto Factories ========= The Factory Class ----------------- >>> from zope.interface import Interface >>> class IFunction(Interface): ... pass >>> class IKlass(Interface): ... pass >>> from zope.interface import implements >>> class Klass(object): ... implements(IKlass) ... ... def __init__(self, *args, **kw): ... self.args = args ... self.kw = kw >>> from zope.component.factory import Factory >>> factory = Factory(Klass, 'Klass', 'Klassier') >>> factory2 = Factory(lambda x: x, 'Func', 'Function') >>> factory3 = Factory(lambda x: x, 'Func', 'Function', (IFunction,)) Calling a Factory ~~~~~~~~~~~~~~~~~ Here we test whether the factory correctly creates the objects and including the correct handling of constructor elements. First we create a factory that creates instanace of the `Klass` class: >>> factory = Factory(Klass, 'Klass', 'Klassier') Now we use the factory to create the instance >>> kl = factory(1, 2, foo=3, bar=4) and make sure that the correct class was used to create the object: >>> kl.__class__ Since we passed in a couple positional and keyword arguments >>> kl.args (1, 2) >>> kl.kw {'foo': 3, 'bar': 4} >>> factory2(3) 3 >>> factory3(3) 3 Title and Description ~~~~~~~~~~~~~~~~~~~~~ >>> factory.title 'Klass' >>> factory.description 'Klassier' >>> factory2.title 'Func' >>> factory2.description 'Function' >>> factory3.title 'Func' >>> factory3.description 'Function' Provided Interfaces ~~~~~~~~~~~~~~~~~~~ >>> implemented = factory.getInterfaces() >>> implemented.isOrExtends(IKlass) True >>> list(implemented) [] >>> implemented2 = factory2.getInterfaces() >>> list(implemented2) [] >>> implemented3 = factory3.getInterfaces() >>> list(implemented3) [] The Component Architecture Factory API -------------------------------------- >>> import zope.component >>> factory = Factory(Klass, 'Klass', 'Klassier') >>> gsm = zope.component.getGlobalSiteManager() >>> from zope.component.interfaces import IFactory >>> gsm.registerUtility(factory, IFactory, 'klass') Creating an Object ~~~~~~~~~~~~~~~~~~ >>> kl = zope.component.createObject('klass', 1, 2, foo=3, bar=4) >>> isinstance(kl, Klass) True >>> kl.args (1, 2) >>> kl.kw {'foo': 3, 'bar': 4} Accessing Provided Interfaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >>> implemented = zope.component.getFactoryInterfaces('klass') >>> implemented.isOrExtends(IKlass) True >>> [iface for iface in implemented] [] List of All Factories ~~~~~~~~~~~~~~~~~~~~~ >>> [(name, fac.__class__) for name, fac in ... zope.component.getFactoriesFor(IKlass)] [(u'klass', )] Component-Management objects ============================ Component-management objects provide a higher-level component-management API over the basic adapter-registration API provided by the zope.interface package. In particular, it provides: - utilities - support for computing adapters, rather than just looking up adapter factories. - management of registration comments The zope.component.registry.Components class provides an implementation of zope.component.interfaces.IComponents that provides these features. >>> from zope.component import registry >>> from zope.component import tests >>> components = registry.Components('comps') As components are registered, events are generated. Let's register an event subscriber, so we can see the events generated: >>> import zope.event >>> def logevent(event): ... print event >>> zope.event.subscribers.append(logevent) Utilities --------- You can register Utilities using registerUtility: >>> components.registerUtility(tests.U1(1)) Registered event: UtilityRegistration(, I1, u'', 1, None, u'') Here we didn't specify an interface or name. An unnamed utility was registered for interface I1, since that is only interface implemented by the U1 class: >>> components.getUtility(tests.I1) U1(1) You can also register a utility using a factory instead of a utility instance: >>> def factory(): ... return tests.U1(1) >>> components.registerUtility(factory=factory) Unregistered event: UtilityRegistration(, I1, u'', 1, None, u'') Registered event: UtilityRegistration(, I1, u'', 1, >, u'') If a component implements other than one interface or no interface, then an error will be raised: >>> components.registerUtility(tests.U12(2)) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The utility doesn't provide a single interface and no provided interface was specified. >>> components.registerUtility(tests.A) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The utility doesn't provide a single interface and no provided interface was specified. We can provide an interface if desired: >>> components.registerUtility(tests.U12(2), tests.I2) Registered event: UtilityRegistration(, I2, u'', 2, None, u'') and we can specify a name: >>> components.registerUtility(tests.U12(3), tests.I2, u'three') Registered event: UtilityRegistration(, I2, u'three', 3, None, u'') >>> components.getUtility(tests.I2) U12(2) >>> components.getUtility(tests.I2, 'three') U12(3) If you try to get a utility that doesn't exist, you'll get a component lookup error: >>> components.getUtility(tests.I3) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, u'') Unless you use queryUtility: >>> components.queryUtility(tests.I3) >>> components.queryUtility(tests.I3, default=42) 42 You can get information about registered utilities with the registeredUtilities method: >>> for registration in sorted(components.registeredUtilities()): ... print registration.provided, registration.name ... print registration.component, registration.info U1(1) U12(2) three U12(3) Duplicate registrations replace existing ones: >>> components.registerUtility(tests.U1(4), info=u'use 4 now') Unregistered event: UtilityRegistration(, I1, u'', 1, >, u'') Registered event: UtilityRegistration(, I1, u'', 4, None, u'use 4 now') >>> components.getUtility(tests.I1) U1(4) >>> for registration in sorted(components.registeredUtilities()): ... print registration.provided, registration.name ... print registration.component, registration.info U1(4) use 4 now U12(2) three U12(3) As shown in the this example, you can provide an "info" argumemnt when registering utilities. This provides extra documentation about the registration itself that is shown when listing registrations. You can also unregister utilities: >>> components.unregisterUtility(provided=tests.I1) Unregistered event: UtilityRegistration(, I1, u'', 4, None, u'use 4 now') True A boolean is returned indicating whether anything changed: >>> components.queryUtility(tests.I1) >>> for registration in sorted(components.registeredUtilities()): ... print registration.provided, registration.name ... print registration.component, registration.info U12(2) three U12(3) When you unregister, you can specify a component. If the component doesn't match the one registered, then nothing happens: >>> u5 = tests.U1(5) >>> components.registerUtility(u5) Registered event: UtilityRegistration(, I1, u'', 5, None, u'') >>> components.unregisterUtility(tests.U1(6)) False >>> components.queryUtility(tests.I1) U1(5) >>> components.unregisterUtility(u5) Unregistered event: UtilityRegistration(, I1, u'', 5, None, u'') True >>> components.queryUtility(tests.I1) You can get the name and utility for all of the utilities that provide an interface using getUtilitiesFor: >>> sorted(components.getUtilitiesFor(tests.I2)) [(u'', U12(2)), (u'three', U12(3))] getAllUtilitiesRegisteredFor is similar to getUtilitiesFor except that it includes utilities that are overridden. For example, we'll register a utility that for an extending interface of I2: >>> util = tests.U('ext') >>> components.registerUtility(util, tests.I2e) Registered event: UtilityRegistration(, I2e, u'', ext, None, u'') We don't get the new utility for getUtilitiesFor: >>> sorted(components.getUtilitiesFor(tests.I2)) [(u'', U12(2)), (u'three', U12(3))] but we do get it from getAllUtilitiesRegisteredFor: >>> sorted(map(str, components.getAllUtilitiesRegisteredFor(tests.I2))) ['U(ext)', 'U12(2)', 'U12(3)'] Removing a utility also makes it disappear from getUtilitiesFor: >>> components.unregisterUtility(util, tests.I2e) Unregistered event: UtilityRegistration(, I2e, u'', ext, None, u'') True >>> list(components.getAllUtilitiesRegisteredFor(tests.I2e)) [] Adapters -------- You can register adapters with registerAdapter: >>> components.registerAdapter(tests.A12_1) Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Here, we didn't specify required interfaces, a provided interface, or a name. The required interfaces were determined from the factory s __component_adapts__ attribute and the provided interface was determined by introspecting what the factory implements. >>> components.getMultiAdapter((tests.U1(6), tests.U12(7)), tests.IA1) A12_1(U1(6), U12(7)) If a factory implements more than one interface, an exception will be raised: >>> components.registerAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.registerAdapter(tests.A1_12, provided=tests.IA2) Registered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') If a factory doesn't declare an implemented interface, an exception will be raised: >>> components.registerAdapter(tests.A12_) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.registerAdapter(tests.A12_, provided=tests.IA2) Registered event: AdapterRegistration(, [I1, I2], IA2, u'', A12_, u'') The required interface needs to be specified in the registration if the factory doesn't have a __component_adapts__ attribute: >>> components.registerAdapter(tests.A_2) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified Unless the required specifications specified: >>> components.registerAdapter(tests.A_2, required=[tests.I3]) Registered event: AdapterRegistration(, [I3], IA2, u'', A_2, u'') Classes can be specified in place of specifications, in which case the implementedBy specification for the class is used: >>> components.registerAdapter(tests.A_3, required=[tests.U], ... info="Really class specific") ... # doctest: +NORMALIZE_WHITESPACE Registered event: AdapterRegistration(, [zope.component.tests.U], IA3, u'', A_3, 'Really class specific') We can see the adapters that have been registered using the registeredAdapters method: >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (, ) zope.component.tests.A12_1 (, ) zope.component.tests.A12_ (,) zope.component.tests.A1_12 (,) zope.component.tests.A_2 (,) zope.component.tests.A_3 Really class specific As with utilities, we can provide registration information when registering adapters. If you try to fetch an adapter that isn't registered, you'll get a component-lookup error: >>> components.getMultiAdapter((tests.U(8), ), tests.IA1) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: ((U(8),), , u'') unless you use queryAdapter: >>> components.queryMultiAdapter((tests.U(8), ), tests.IA1) >>> components.queryMultiAdapter((tests.U(8), ), tests.IA1, default=42) 42 When looking up an adapter for a single object, you can use the slightly simpler getAdapter and queryAdapter calls: >>> components.getAdapter(tests.U1(9), tests.IA2) A1_12(U1(9)) >>> components.queryAdapter(tests.U1(9), tests.IA2) A1_12(U1(9)) >>> components.getAdapter(tests.U(8), tests.IA1) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (U(8), , u'') >>> components.queryAdapter(tests.U(8), tests.IA2) >>> components.queryAdapter(tests.U(8), tests.IA2, default=42) 42 You can unregister an adapter. If a factory is provided and if the rewuired and provided interfaces, can be infered, then they need not be provided: >>> components.unregisterAdapter(tests.A12_1) Unregistered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') True >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (, ) zope.component.tests.A12_ (,) zope.component.tests.A1_12 (,) zope.component.tests.A_2 (,) zope.component.tests.A_3 Really class specific A boolean is returned indicating whether a change was made. If a factory implements more than one interface, an exception will be raised: >>> components.unregisterAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.unregisterAdapter(tests.A1_12, provided=tests.IA2) Unregistered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') True If a factory doesn't declare an implemented interface, an exception will be raised: >>> components.unregisterAdapter(tests.A12_) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Unless the provided interface is specified: >>> components.unregisterAdapter(tests.A12_, provided=tests.IA2) Unregistered event: AdapterRegistration(, [I1, I2], IA2, u'', A12_, u'') True The required interface needs to be specified if the factory doesn't have a __component_adapts__ attribute: >>> components.unregisterAdapter(tests.A_2) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified >>> components.unregisterAdapter(tests.A_2, required=[tests.I3]) Unregistered event: AdapterRegistration(, [I3], IA2, u'', A_2, u'') True >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) zope.component.tests.A_3 Really class specific If a factory is unregistered that is not registered, False is returned: >>> components.unregisterAdapter(tests.A_2, required=[tests.I3]) False >>> components.unregisterAdapter(tests.A12_1, required=[tests.U]) False The factory can be omitted, to unregister *any* factory that matches specified required and provided interfaces: >>> components.unregisterAdapter(required=[tests.U], provided=tests.IA3) ... # doctest: +NORMALIZE_WHITESPACE Unregistered event: AdapterRegistration(, [zope.component.tests.U], IA3, u'', A_3, 'Really class specific') True >>> for registration in sorted(components.registeredAdapters()): ... print registration Adapters can be named: >>> components.registerAdapter(tests.A1_12, provided=tests.IA2, ... name=u'test') Registered event: AdapterRegistration(, [I1], IA2, u'test', A1_12, u'') >>> components.queryMultiAdapter((tests.U1(9), ), tests.IA2) >>> components.queryMultiAdapter((tests.U1(9), ), tests.IA2, name=u'test') A1_12(U1(9)) >>> components.queryAdapter(tests.U1(9), tests.IA2) >>> components.queryAdapter(tests.U1(9), tests.IA2, name=u'test') A1_12(U1(9)) >>> components.getAdapter(tests.U1(9), tests.IA2, name=u'test') A1_12(U1(9)) It is possible to look up all of the adapters that provide an interface: >>> components.registerAdapter(tests.A1_23, provided=tests.IA2, ... name=u'test 2') Registered event: AdapterRegistration(, [I1], IA2, u'test 2', A1_23, u'') >>> components.registerAdapter(tests.A1_12, provided=tests.IA2) Registered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') >>> for name, adapter in sorted(components.getAdapters((tests.U1(9), ), ... tests.IA2)): ... print name, adapter A1_12(U1(9)) test A1_12(U1(9)) test 2 A1_23(U1(9)) getAdapters is most commonly used as the basis of menu systems. If an adapter factory returns None, it is equivalent to there being no factory: >>> components.registerAdapter(tests.noop, ... required=[tests.IA1], provided=tests.IA2, ... name=u'test noop') ... # doctest: +NORMALIZE_WHITESPACE Registered event: AdapterRegistration(, [IA1], IA2, u'test noop', noop, u'') >>> components.queryAdapter(tests.U1(9), tests.IA2, name=u'test noop') >>> components.registerAdapter(tests.A1_12, provided=tests.IA2) Registered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') >>> for name, adapter in sorted(components.getAdapters((tests.U1(9), ), ... tests.IA2)): ... print name, adapter A1_12(U1(9)) test A1_12(U1(9)) test 2 A1_23(U1(9)) >>> components.unregisterAdapter(tests.A1_12, provided=tests.IA2, ... name=u'test') Unregistered event: AdapterRegistration(, [I1], IA2, u'test', A1_12, u'') True >>> components.unregisterAdapter(tests.A1_12, provided=tests.IA2) Unregistered event: AdapterRegistration(, [I1], IA2, u'', A1_12, u'') True >>> for registration in sorted(components.registeredAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) test 2 zope.component.tests.A1_23 (,) test noop Subscribers ----------- Subscribers provide a way to get multiple adapters of a given type. In this regard, subscribers are like named adapters, except that there isn't any concept of the most specific adapter for a given name. Subscribers are registered by calling registerSubscriptionAdapter: >>> components.registerSubscriptionAdapter(tests.A1_2) ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') >>> components.registerSubscriptionAdapter( ... tests.A1_12, provided=tests.IA2) ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_12, u'') >>> components.registerSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2, ... info='a sample comment') ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A, 'a sample comment') The same rules, with regard to when required and provided interfaces have to be specified apply as with adapters: >>> components.registerSubscriptionAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.registerSubscriptionAdapter(tests.A) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.registerSubscriptionAdapter(tests.A, required=[tests.IA1]) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. Note that we provided the info argument as a keyword argument above. That's because there is a name argument that's reserved for future use. We can give a name, as long as it is an empty string: >>> components.registerSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2, u'', 'a sample comment') ... # doctest: +NORMALIZE_WHITESPACE Registered event: SubscriptionRegistration(, [I1], IA2, u'', A, 'a sample comment') >>> components.registerSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2, u'oops', 'a sample comment') Traceback (most recent call last): ... TypeError: Named subscribers are not yet supported Subscribers are looked up using the subscribers method: >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_2(U1(1)) A1_12(U1(1)) A(U1(1),) A(U1(1),) Note that, because we created multiple subscriptions for A, we got multiple subscriber instances. As with normal adapters, if a factory returns None, the result is skipped: >>> components.registerSubscriptionAdapter( ... tests.noop, [tests.I1], tests.IA2) Registered event: SubscriptionRegistration(, [I1], IA2, u'', noop, u'') >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_2(U1(1)) A1_12(U1(1)) A(U1(1),) A(U1(1),) We can get registration information for subscriptions: >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info (,) zope.component.tests.A a sample comment (,) zope.component.tests.A a sample comment (,) zope.component.tests.A1_12 (,) zope.component.tests.A1_2 (,) We can also unregister subscriptions in much the same way we can for adapters: >>> components.unregisterSubscriptionAdapter(tests.A1_2) ... # doctest: +NORMALIZE_WHITESPACE Unregistered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, '') True >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_12(U1(1)) A(U1(1),) A(U1(1),) >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info (,) zope.component.tests.A a sample comment (,) zope.component.tests.A a sample comment (,) zope.component.tests.A1_12 (,) >>> components.unregisterSubscriptionAdapter( ... tests.A, [tests.I1], tests.IA2) Unregistered event: SubscriptionRegistration(, [I1], IA2, u'', A, '') True >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s A1_12(U1(1)) >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.required ... print registration.provided, registration.name ... print registration.factory, registration.info (,) zope.component.tests.A1_12 (,) Note here that both registrations for A were removed. If we omit the factory, we must specify the required and provided interfaces: >>> components.unregisterSubscriptionAdapter(required=[tests.I1]) Traceback (most recent call last): ... TypeError: Must specify one of factory and provided >>> components.unregisterSubscriptionAdapter(provided=tests.IA2) Traceback (most recent call last): ... TypeError: Must specify one of factory and required >>> components.unregisterSubscriptionAdapter( ... required=[tests.I1], provided=tests.IA2) Unregistered event: SubscriptionRegistration(, [I1], IA2, u'', None, '') True >>> for s in components.subscribers((tests.U1(1), ), tests.IA2): ... print s >>> for registration in sorted( ... components.registeredSubscriptionAdapters()): ... print registration.factory As when registering, an error is raised if the registration information can't be determined from the factory and isn't specified: >>> components.unregisterSubscriptionAdapter(tests.A1_12) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.unregisterSubscriptionAdapter(tests.A) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. >>> components.unregisterSubscriptionAdapter(tests.A, required=[tests.IA1]) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't implement a single interface and no provided interface was specified. If you unregister something that's not registered, nothing will be changed and False will be returned: >>> components.unregisterSubscriptionAdapter( ... required=[tests.I1], provided=tests.IA2) False Handlers -------- Handlers are used when you want to perform some function in response to an event. Handlers aren't expected to return anything when called and are not registered to provide any interface. >>> from zope import component >>> @component.adapter(tests.I1) ... def handle1(x): ... print 'handle1', x >>> components.registerHandler(handle1, info="First handler") ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [I1], u'', handle1, 'First handler') >>> components.handle(tests.U1(1)) handle1 U1(1) >>> @component.adapter(tests.I1, tests.I2) ... def handle12(x, y): ... print 'handle12', x, y >>> components.registerHandler(handle12) Registered event: HandlerRegistration(, [I1, I2], u'', handle12, u'') >>> components.handle(tests.U1(1), tests.U12(2)) handle12 U1(1) U12(2) If a handler doesn't document interfaces it handles, then the required interfaces must be specified: >>> def handle(*objects): ... print 'handle', objects >>> components.registerHandler(handle) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified >>> components.registerHandler(handle, required=[tests.I1], ... info="a comment") Registered event: HandlerRegistration(, [I1], u'', handle, 'a comment') Handlers can also be registered for classes: >>> components.registerHandler(handle, required=[tests.U], ... info="handle a class") ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [zope.component.tests.U], u'', handle, 'handle a class') >>> components.handle(tests.U1(1)) handle (U1(1),) handle1 U1(1) handle (U1(1),) We can list the handler registrations: >>> for registration in components.registeredHandlers(): ... print registration.required ... print registration.handler, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) First handler (, ) (,) a comment (,) handle a class and we can unregister handlers: >>> components.unregisterHandler(required=[tests.U]) ... # doctest: +NORMALIZE_WHITESPACE Unregistered event: HandlerRegistration(, [zope.component.tests.U], u'', None, '') True >>> for registration in components.registeredHandlers(): ... print registration.required ... print registration.handler, registration.info ... # doctest: +NORMALIZE_WHITESPACE (,) First handler (, ) (,) a comment >>> components.unregisterHandler(handle12) Unregistered event: HandlerRegistration(, [I1, I2], u'', handle12, '') True >>> for registration in components.registeredHandlers(): ... print registration.required ... print registration.handler, registration.info (,) First handler (,) a comment >>> components.unregisterHandler(handle12) False >>> components.unregisterHandler() Traceback (most recent call last): ... TypeError: Must specify one of factory and required >>> components.registerHandler(handle) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: The adapter factory doesn't have a __component_adapts__ attribute and no required specifications were specified Extending --------- Component-management objects can extend other component-management objects. >>> c1 = registry.Components('1') >>> c1.__bases__ () >>> c2 = registry.Components('2', (c1, )) >>> c2.__bases__ == (c1, ) True >>> c1.registerUtility(tests.U1(1)) Registered event: UtilityRegistration(, I1, u'', 1, None, u'') >>> c1.queryUtility(tests.I1) U1(1) >>> c2.queryUtility(tests.I1) U1(1) >>> c1.registerUtility(tests.U1(2)) Unregistered event: UtilityRegistration(, I1, u'', 1, None, u'') Registered event: UtilityRegistration(, I1, u'', 2, None, u'') >>> c2.queryUtility(tests.I1) U1(2) We can use multiple inheritence: >>> c3 = registry.Components('3', (c1, )) >>> c4 = registry.Components('4', (c2, c3)) >>> c4.queryUtility(tests.I1) U1(2) >>> c1.registerUtility(tests.U12(1), tests.I2) Registered event: UtilityRegistration(, I2, u'', 1, None, u'') >>> c4.queryUtility(tests.I2) U12(1) >>> c3.registerUtility(tests.U12(3), tests.I2) Registered event: UtilityRegistration(, I2, u'', 3, None, u'') >>> c4.queryUtility(tests.I2) U12(3) >>> c1.registerHandler(handle1, info="First handler") Registered event: HandlerRegistration(, [I1], u'', handle1, 'First handler') >>> c2.registerHandler(handle, required=[tests.U]) ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [zope.component.tests.U], u'', handle, u'') >>> @component.adapter(tests.I1) ... def handle3(x): ... print 'handle3', x >>> c3.registerHandler(handle3) Registered event: HandlerRegistration(, [I1], u'', handle3, u'') >>> @component.adapter(tests.I1) ... def handle4(x): ... print 'handle4', x >>> c4.registerHandler(handle4) Registered event: HandlerRegistration(, [I1], u'', handle4, u'') >>> c4.handle(tests.U1(1)) handle1 U1(1) handle3 U1(1) handle (U1(1),) handle4 U1(1) Redispatch of registration events --------------------------------- Some handlers are available that, if registered, redispatch registration events to the objects being registered. They depend on being dispatched to by the object-event dispatcher: >>> from zope import component >>> import zope.component.event >>> zope.component.getGlobalSiteManager().registerHandler( ... zope.component.event.objectEventNotify) ... # doctest: +NORMALIZE_WHITESPACE Registered event: HandlerRegistration(, [IObjectEvent], u'', objectEventNotify, u'') To see this, we'll first register a multi-handler to show is when handlers are called on 2 objects: >>> @zope.component.adapter(None, None) ... def double_handler(o1, o2): ... print 'Double dispatch:' ... print ' ', o1 ... print ' ', o2 >>> zope.component.getGlobalSiteManager().registerHandler(double_handler) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: HandlerRegistration(, [Interface, Interface], u'', double_handler, u'') Registered event: HandlerRegistration(, [Interface, Interface], u'', double_handler, u'') Registered event: HandlerRegistration(, [Interface, Interface], u'', double_handler, u'') In the example above, the double_handler reported it's own registration. :) Now we'll register our handlers: >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchUtilityRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Double dispatch: ... >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchAdapterRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Double dispatch: ... >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchSubscriptionAdapterRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Double dispatch: ... >>> zope.component.getGlobalSiteManager().registerHandler( ... registry.dispatchHandlerRegistrationEvent) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') Registered event: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') Double dispatch: Registered event: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') Registered event: HandlerRegistration(, [IHandlerRegistration, IRegistrationEvent], u'', dispatchHandlerRegistrationEvent, u'') In the last example above, we can see that the registration of dispatchHandlerRegistrationEvent was handled by dispatchHandlerRegistrationEvent and redispatched. This can be seen in the second double-dispatch output, where the first argument is the object being registered, which is dispatchHandlerRegistrationEvent. If we change some other registrations, we can the double dispatch taking place: >>> components.registerUtility(u5) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: UtilityRegistration(, I1, u'', 5, None, u'') Registered event: UtilityRegistration(, I1, u'', 5, None, u'') Double dispatch: U1(5) Registered event: UtilityRegistration(, I1, u'', 5, None, u'') Registered event: UtilityRegistration(, I1, u'', 5, None, u'') >>> components.registerAdapter(tests.A12_1) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Double dispatch: zope.component.tests.A12_1 Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') Registered event: AdapterRegistration(, [I1, I2], IA1, u'', A12_1, u'') >>> components.registerSubscriptionAdapter(tests.A1_2) ... # doctest: +NORMALIZE_WHITESPACE Double dispatch: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Double dispatch: zope.component.tests.A1_2 Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Registered event: SubscriptionRegistration(, [I1], IA2, u'', A1_2, u'') Persistent Component Management =============================== Persistent component management allows persistent management of components. From a usage point of view, there shouldn't be any new behavior beyond what's described in registry.txt. The Zope 3 Component Architecture (Socket Example) ================================================== The component architecture provides an application framework that provides its functionality through loosely-connected components. A *component* can be any Python object and has a particular purpose associated with it. Thus, in a component-based applications you have many small component in contrast to classical object-oriented development, where you have a few big objects. Components communicate via specific APIs, which are formally defined by interfaces, which are provided by the `zope.interface` package. *Interfaces* describe the methods and properties that a component is expected to provide. They are also used as a primary mean to provide developer-level documentation for the components. For more details about interfaces see `zope/interface/README.txt`. The two main types of components are *adapters* and *utilities*. They will be discussed in detail later in this document. Both component types are managed by the *site manager*, with which you can register and access these components. However, most of the site manager's functionality is hidden behind the component architecture's public API, which is documented in `IComponentArchitecture`. Adapters -------- Adapters are a well-established pattern. An *adapter* uses an object providing one interface to produce an object that provides another interface. Here an example: Imagine that you purchased an electric shaver in the US, and thus you require the US socket type. You are now traveling in Germany, where another socket style is used. You will need a device, an adapter, that converts from the German to the US socket style. The functionality of adapters is actually natively provided by the `zope.interface` package and is thus well documented there. The `human.txt` file provides a gentle introduction to adapters, whereby `adapter.txt` is aimed at providing a comprehensive insight into adapters, but is too abstract for many as an initial read. Thus, we will only explain adapters in the context of the component architecture's API. So let's say that we have a German socket >>> from zope.interface import Interface, implements >>> class IGermanSocket(Interface): ... pass >>> class Socket(object): ... def __repr__(self): ... return '' %self.__class__.__name__ >>> class GermanSocket(Socket): ... """German wall socket.""" ... implements(IGermanSocket) and we want to convert it to an US socket >>> class IUSSocket(Interface): ... pass so that our shaver can be used in Germany. So we go to a German electronics store to look for an adapter that we can plug in the wall: >>> class GermanToUSSocketAdapter(Socket): ... implements(IUSSocket) ... __used_for__ = IGermanSocket ... ... def __init__(self, socket): ... self.context = socket Note that I could have called the passed in socket any way I like, but `context` is the standard name accepted. Single Adapters ~~~~~~~~~~~~~~~ Before we can use the adapter, we have to buy it and make it part of our inventory. In the component architecture we do this by registering the adapter with the framework, more specifically with the global site manager: >>> import zope.component >>> gsm = zope.component.getGlobalSiteManager() >>> gsm.registerAdapter(GermanToUSSocketAdapter, (IGermanSocket,), IUSSocket) `zope.component` is the component architecture API that is being presented by this file. You registered an adapter from `IGermanSocket` to `IUSSocket` having no name (thus the empty string). Anyways, you finally get back to your hotel room and shave, since you have not been able to shave in the plane. In the bathroom you discover a socket: >>> bathroomDE = GermanSocket() >>> IGermanSocket.providedBy(bathroomDE) True You now insert the adapter in the German socket >>> bathroomUS = zope.component.getAdapter(bathroomDE, IUSSocket, '') so that the socket now provides the US version: >>> IUSSocket.providedBy(bathroomUS) True Now you can insert your shaver and get on with your day. After a week you travel for a couple of days to the Prague and you notice that the Czech have yet another socket type: >>> class ICzechSocket(Interface): ... pass >>> class CzechSocket(Socket): ... implements(ICzechSocket) >>> czech = CzechSocket() You try to find an adapter for your shaver in your bag, but you fail, since you do not have one: >>> zope.component.getAdapter(czech, IUSSocket, '') \ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, , '') or the more graceful way: >>> marker = object() >>> socket = zope.component.queryAdapter(czech, IUSSocket, '', marker) >>> socket is marker True In the component architecture API any `get*` method will fail with a specific exception, if a query failed, whereby methods starting with `query*` will always return a `default` value after a failure. Named Adapters ~~~~~~~~~~~~~~ You are finally back in Germany. You also brought your DVD player and a couple DVDs with you, which you would like to watch. Your shaver was able to convert automatically from 110 volts to 240 volts, but your DVD player cannot. So you have to buy another adapter that also handles converting the voltage and the frequency of the AC current: >>> class GermanToUSSocketAdapterAndTransformer(object): ... implements(IUSSocket) ... __used_for__ = IGermanSocket ... ... def __init__(self, socket): ... self.context = socket Now, we need a way to keep the two adapters apart. Thus we register them with a name: >>> gsm.registerAdapter(GermanToUSSocketAdapter, ... (IGermanSocket,), IUSSocket, 'shaver',) >>> gsm.registerAdapter(GermanToUSSocketAdapterAndTransformer, ... (IGermanSocket,), IUSSocket, 'dvd') Now we simply look up the adapters using their labels (called *name*): >>> socket = zope.component.getAdapter(bathroomDE, IUSSocket, 'shaver') >>> socket.__class__ is GermanToUSSocketAdapter True >>> socket = zope.component.getAdapter(bathroomDE, IUSSocket, 'dvd') >>> socket.__class__ is GermanToUSSocketAdapterAndTransformer True Clearly, we do not have an adapter for the MP3 player >>> zope.component.getAdapter(bathroomDE, IUSSocket, 'mp3') \ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: (, , 'mp3') but you could use the 'dvd' adapter in this case of course. ;) Sometimes you want to know all adapters that are available. Let's say you want to know about all the adapters that convert a German to a US socket type: >>> sockets = list(zope.component.getAdapters((bathroomDE,), IUSSocket)) >>> len(sockets) 3 >>> names = [name for name, socket in sockets] >>> names.sort() >>> names [u'', u'dvd', u'shaver'] `zope.component.getAdapters()` returns a list of tuples. The first entry of the tuple is the name of the adapter and the second is the adapter itself. Multi-Adapters ~~~~~~~~~~~~~~ After watching all the DVDs you brought at least twice, you get tired of them and you want to listen to some music using your MP3 player. But darn, the MP3 player plug has a ground pin and all the adapters you have do not support that: >>> class IUSGroundedSocket(IUSSocket): ... pass So you go out another time to buy an adapter. This time, however, you do not buy yet another adapter, but a piece that provides the grounding plug: >>> class IGrounder(Interface): ... pass >>> class Grounder(object): ... implements(IGrounder) ... def __repr__(self): ... return '' Then together they will provided a grounded us socket: >>> class GroundedGermanToUSSocketAdapter(object): ... implements(IUSGroundedSocket) ... __used_for__ = (IGermanSocket, IGrounder) ... def __init__(self, socket, grounder): ... self.socket, self.grounder = socket, grounder You now register the combination, so that you know you can create a `IUSGroundedSocket`: >>> gsm.registerAdapter(GroundedGermanToUSSocketAdapter, ... (IGermanSocket, IGrounder), IUSGroundedSocket, 'mp3') Given the grounder >>> grounder = Grounder() and a German socket >>> livingroom = GermanSocket() we can now get a grounded US socket: >>> socket = zope.component.getMultiAdapter((livingroom, grounder), ... IUSGroundedSocket, 'mp3') >>> socket.__class__ is GroundedGermanToUSSocketAdapter True >>> socket.socket is livingroom True >>> socket.grounder is grounder True Of course, you do not have a 'dvd' grounded US socket available: >>> zope.component.getMultiAdapter((livingroom, grounder), ... IUSGroundedSocket, 'dvd') \ ... #doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... ComponentLookupError: ((, ), , 'dvd') >>> socket = zope.component.queryMultiAdapter( ... (livingroom, grounder), IUSGroundedSocket, 'dvd', marker) >>> socket is marker True Again, you might want to read `adapter.txt` in `zope.interface` for a more comprehensive coverage of multi-adapters. Subscribers ----------- While subscribers are directly supported by the adapter registry and are adapters for all theoretical purposes, practically it might be better to think of them as separate components. Subscribers are particularly useful for events. Let's say one of our adapters overheated and caused a small fire: >>> class IFire(Interface): ... pass >>> class Fire(object): ... implements(IFire) >>> fire = Fire() We want to use all available objects to put out the fire: >>> class IFireExtinguisher(Interface): ... def extinguish(): ... pass >>> class FireExtinguisher(object): ... def __init__(self, fire): ... pass ... def extinguish(self): ... "Place extinguish code here." ... print 'Used ' + self.__class__.__name__ + '.' Here some specific methods to put out the fire: >>> class PowderExtinguisher(FireExtinguisher): ... pass >>> gsm.registerSubscriptionAdapter(PowderExtinguisher, ... (IFire,), IFireExtinguisher) >>> class Blanket(FireExtinguisher): ... pass >>> gsm.registerSubscriptionAdapter(Blanket, (IFire,), IFireExtinguisher) >>> class SprinklerSystem(FireExtinguisher): ... pass >>> gsm.registerSubscriptionAdapter(SprinklerSystem, ... (IFire,), IFireExtinguisher) Now let use all these things to put out the fire: >>> extinguishers = zope.component.subscribers((fire,), IFireExtinguisher) >>> extinguishers.sort() >>> for extinguisher in extinguishers: ... extinguisher.extinguish() Used Blanket. Used PowderExtinguisher. Used SprinklerSystem. If no subscribers are found for a particular object, then an empty list is returned: >>> zope.component.subscribers((object(),), IFireExtinguisher) [] Utilities --------- Utilities are the second type of component, the component architecture implements. *Utilities* are simply components that provide an interface. When you register an utility, you always register an instance (in contrast to a factory for adapters) since the initialization and setup process of a utility might be complex and is not well defined. In some ways a utility is much more fundamental than an adapter, because an adapter cannot be used without another component, but a utility is always self-contained. I like to think of utilities as the foundation of your application and adapters as components extending beyond this foundation. Back to our story... After your vacation is over you fly back home to Tampa, Florida. But it is August now, the middle of the Hurricane season. And, believe it or not, you are worried that you will not be able to shave when the power goes out for several days. (You just hate wet shavers.) So you decide to go to your favorite hardware store and by a Diesel-powered electric generator. The generator provides of course a US-style socket: >>> class Generator(object): ... implements(IUSSocket) ... def __repr__(self): ... return '' >>> generator = Generator() Like for adapters, we now have to add the newly-acquired generator to our inventory by registering it as a utility: >>> gsm.registerUtility(generator, IUSSocket) We can now get the utility using >>> utility = zope.component.getUtility(IUSSocket) >>> utility is generator True As you can see, it is very simple to register and retrieve utilities. If a utility does not exist for a particular interface, such as the German socket, then the lookup fails >>> zope.component.getUtility(IGermanSocket) Traceback (most recent call last): ... ComponentLookupError: (, '') or more gracefully when specifying a default value: >>> default = object() >>> utility = zope.component.queryUtility(IGermanSocket, default=default) >>> utility is default True Note: The only difference between `getUtility()` and `queryUtility()` is the fact that you can specify a default value for the latter function, so that it will never cause a `ComponentLookupError`. Named Utilities ~~~~~~~~~~~~~~~ It is often desirable to have several utilities providing the same interface per site. This way you can implement any sort of registry using utilities. For this reason, utilities -- like adapters -- can be named. In the context of our story, we might want to do the following: You really do not trust gas stations either. What if the roads are blocked after a hurricane and the gas stations run out of oil. So you look for another renewable power source. Then you think about solar panels! After a storm there is usually very nice weather, so why not? Via the Web you order a set of 110V/120W solar panels that provide a regular US-style socket as output: >>> class SolarPanel(object): ... implements(IUSSocket) ... def __repr__(self): ... return '' >>> panel = SolarPanel() Once it arrives, we add it to our inventory: >>> gsm.registerUtility(panel, IUSSocket, 'Solar Panel') You can now access the solar panel using >>> utility = zope.component.getUtility(IUSSocket, 'Solar Panel') >>> utility is panel True Of course, if a utility is not available, then the lookup will simply fail >>> zope.component.getUtility(IUSSocket, 'Wind Mill') Traceback (most recent call last): ... ComponentLookupError: (, 'Wind Mill') or more gracefully when specifying a default value: >>> default = object() >>> utility = zope.component.queryUtility(IUSSocket, 'Wind Mill', ... default=default) >>> utility is default True Now you want to look at all the utilities you have for a particular kind. The following API function will return a list of name/utility pairs: >>> utils = list(zope.component.getUtilitiesFor(IUSSocket)) >>> utils.sort() >>> utils #doctest: +NORMALIZE_WHITESPACE [(u'', ), (u'Solar Panel', )] Another method of looking up all utilities is by using `getAllUtilitiesRegisteredFor(iface)`. This function will return an iterable of utilities (without names); however, it will also return overridden utilities. If you are not using multiple site managers, you will not actually need this method. >>> utils = list(zope.component.getAllUtilitiesRegisteredFor(IUSSocket)) >>> utils.sort() >>> utils [, ] Factories ~~~~~~~~~ A *factory* is a special kind of utility that exists to create other components. A factory is always identified by a name. It also provides a title and description and is able to tell the developer what interfaces the created object will provide. The advantage of using a factory to create an object instead of directly instantiating a class or executing any other callable is that we can refer to the factory by name. As long as the name stays fixed, the implementation of the callable can be renamed or moved without a breakage in code. Let's say that our solar panel comes in parts and they have to be assembled. This assembly would be done by a factory, so let's create one for the solar panel. To do this, we can use a standard implementation of the `IFactory` interface: >>> from zope.component.factory import Factory >>> factory = Factory(SolarPanel, ... 'Solar Panel', ... 'This factory creates a solar panel.') Optionally, I could have also specified the interfaces that the created object will provide, but the factory class is smart enough to determine the implemented interface from the class. We now register the factory: >>> from zope.component.interfaces import IFactory >>> gsm.registerUtility(factory, IFactory, 'SolarPanel') We can now get a list of interfaces the produced object will provide: >>> ifaces = zope.component.getFactoryInterfaces('SolarPanel') >>> IUSSocket in ifaces True By the way, this is equivalent to >>> ifaces2 = factory.getInterfaces() >>> ifaces is ifaces2 True Of course you can also just create an object: >>> panel = zope.component.createObject('SolarPanel') >>> panel.__class__ is SolarPanel True Note: Ignore the first argument (`None`) for now; it is the context of the utility lookup, which is usually an optional argument, but cannot be in this case, since all other arguments beside the `name` are passed in as arguments to the specified callable. Once you register several factories >>> gsm.registerUtility(Factory(Generator), IFactory, 'Generator') you can also determine, which available factories will create objects providing a certain interface: >>> factories = zope.component.getFactoriesFor(IUSSocket) >>> factories = [(name, factory.__class__) for name, factory in factories] >>> factories.sort() >>> factories #doctest: +NORMALIZE_WHITESPACE [(u'Generator', ), (u'SolarPanel', )] Site Managers ------------- Why do we need site managers? Why is the component architecture API not sufficient? Some applications, including Zope 3, have a concept of locations. It is often desirable to have different configurations for these location; this can be done by overwriting existing or adding new component registrations. Site managers in locations below the root location, should be able to delegate requests to their parent locations. The root site manager is commonly known as *global site manager*, since it is always available. You can always get the global site manager using the API: >>> gsm = zope.component.getGlobalSiteManager() >>> from zope.component import globalSiteManager >>> gsm is globalSiteManager True >>> from zope.component.interfaces import IComponentLookup >>> IComponentLookup.providedBy(gsm) True >>> from zope.component.interfaces import IComponents >>> IComponents.providedBy(gsm) True You can also lookup at site manager in a given context. The only requirement is that the context can be adapted to a site manager. So let's create a special site manager: >>> from zope.component.globalregistry import BaseGlobalComponents >>> sm = BaseGlobalComponents() Now we create a context that adapts to the site manager via the `__conform__` method as specified in PEP 246. >>> class Context(object): ... def __init__(self, sm): ... self.sm = sm ... def __conform__(self, interface): ... if interface.isOrExtends(IComponentLookup): ... return self.sm We now instantiate the `Context` with our special site manager: >>> context = Context(sm) >>> context.sm is sm True We can now ask for the site manager of this context: >>> lsm = zope.component.getSiteManager(context) >>> lsm is sm True The site manager instance `lsm` is formally known as a *local site manager* of `context`. CHANGES ******* 3.9.5 (2010-07-09) ================== - Fix test requirements specification. 3.9.4 (2010-04-30) ================== - Prefer the standard libraries doctest module to the one from zope.testing. 3.9.3 (2010-03-08) ================== - The ZCML directives provided by zope.component now register the components in the registry returned by getSiteManager instead of the global registry. This allows the hooking of the getSiteManager method before the load of a ZCML file to register the components in a custom registry. 3.9.2 (2010-01-22) ================== - Fixed a bug introduced by recent refactoring, where passing CheckerPublic to securityAdapterFactory wrongly wrapped the factory into a LocatingUntrustedAdapterFactory. 3.9.1 (2010-01-21) ================== - The tested testrunner somehow gets influenced by options of the outer testrunner, such a the -v option. We modified the tests so that it avoids this. 3.9.0 (2010-01-21) ================== - Add testlayer support. It is now possible to load a ZCML file within tests more easily. See zope.component.testlayer.py and zope.component.testlayer.txt. 3.8.0 (2009-11-16) ================== - Removed the dependencies on zope.proxy and zope.security from the zcml extra: zope.component does not hard depend on them anymore; the support for security proxied components ZCML registrations is enabled only if zope.security and zope.proxy are available. - Moved the IPossibleSite and ISite interfaces here from zope.location as they are dealing with zope.component's concept of a site, but not with location. - Moved the zope.site.hooks functionality to zope.component.hooks as it isn't actually dealing with zope.site's concept of a site. 3.7.1 (2009-07-24) ================== - Fixed a problem, where ``queryNextUtility`` could fail if the context could not be adapted to a ``IComponentLookup``. - Fixed 2 related bugs: When a utility is registered and there was previously a utility registered for the same interface and name, then the old utility is unregistered. The 2 bugs related to this: - There was no ``Unregistered`` for the implicit unregistration. Now there is. - The old utility was still held and returned by getAllUtilitiesRegisteredFor. In other words, it was still considered registered, eeven though it wasn't. A particularly negative consequence of this is that the utility is held in memory or in the database even though it isn't used. 3.7.0 (2009-05-21) ================== - The HookableTests were not run by the testrunner. - Add in zope:view and zope:resource implementations into zope.component.zcml (dependency loaded with zope.component [zcml]). 3.6.0 (2009-03-12) ================== - IMPORTANT: the interfaces that were defined in the zope.component.bbb.interfaces and deprecated for years are now (re)moved. However, some packages, including part of zope framework were still using those interfaces. They will be adapted for this change. If you were using some of those interfaces, you need to adapt your code as well: - The IView and IDefaultViewName were moved to zope.publisher.interfaces. - The IResource was moved to zope.app.publisher.interfaces. - IContextDependent, IPresentation, IPresentationRequest, IResourceFactory, IViewFactory were removed completely. If you used IViewFactory in context of zope.app.form, there's now IWidgetFactory in the zope.app.form.interfaces instead. - Add getNextUtility/queryNextUtility functions that used to be in zope.site earlier (and in zope.app.component even more earlier). - Added a pure-Python 'hookable' implementation, for use when 'zope.hookable' is not present. - Removed use of 'zope.deferredimport' by breaking import cycles. - Cleanup package documentation and changelog a bit. Add sphinx-based documentation building command to the buildout. - Remove deprecated code. - Change package's mailing list address to zope-dev at zope.org, because zope3-dev at zope.org is now retired. 3.5.1 (2008-07-25) ================== - Fix bug introduced in 3.5.0: no longer supported interfaces declared in Python and always wanted an explicit provides="..." attribute. https://bugs.launchpad.net/zope3/+bug/251865 3.5.0 (2008-07-25) ================== - Support registration of utilities via factories through the component registry and return factory information in the registration information. This fixes https://bugs.launchpad.net/zope3/+bug/240631 - Optimized un/registerUtility via storing an optimized data structure for efficient retrieval of already registered utilities. This avoids looping over all utilities when registering a new one. 3.4.0 (2007-09-29) ================== No further changes since 3.4.0a1. 3.4.0a1 (2007-04-22) ==================== Corresponds to zope.component from Zope 3.4.0a1. - In the Zope 3.3.x series, ``zope.component`` was simplified yet once more. See http://wiki.zope.org/zope3/LocalComponentManagementSimplification for the proposal describing the changes. 3.2.0.2 (2006-04-15) ==================== - Fix packaging bug: 'package_dir' must be a *relative* path. 3.2.0.1 (2006-04-14) ==================== - Packaging change: suppress inclusion of 'setup.cfg' in 'sdist' builds. 3.2.0 (2006-01-05) ================== Corresponds to the verison of the zope.component package shipped as part of the Zope 3.2.0 release. - Deprecated services and related APIs. The adapter and utility registries are now available directly via the site manager's 'adapters' and 'utilities' attributes, respectively. Services are accessible, but deprecated, and will be removed in Zope 3.3. - Deprectaed all presentation-related APIs, including all view-related API functions. Use the adapter API functions instead. See http://dev.zope.org/Zope3/ImplementViewsAsAdapters` - Deprecated 'contextdependent' package: site managers are now looked up via a thread global, set during URL traversal. The 'context' argument is now always optional, and should no longer be passed. 3.0.0 (2004-11-07) ================== Corresponds to the verison of the zope.component package shipped as part of the Zope X3.0.0 release. Download ******** Platform: UNKNOWN zope2.13-2.13.21/source/zope.component/src/zope.component.egg-info/dependency_links.txt0000644000175000017500000000000112214017426027721 0ustar arnauarnau zope2.13-2.13.21/source/zope.component/src/zope.component.egg-info/requires.txt0000644000175000017500000000036112214017426026253 0ustar arnauarnausetuptools zope.interface zope.event [test] ZODB3 zope.hookable zope.location zope.proxy zope.security zope.testing [docs] z3c.recipe.sphinxdoc [zcml] zope.configuration zope.i18nmessageid [persistentregistry] ZODB3 [hook] zope.hookablezope2.13-2.13.21/source/zope.component/src/zope.component.egg-info/namespace_packages.txt0000644000175000017500000000000512214017426030201 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.component/src/zope.component.egg-info/top_level.txt0000644000175000017500000000000512214017426026400 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.component/src/zope.component.egg-info/SOURCES.txt0000644000175000017500000000322412214017426025540 0ustar arnauarnau.bzrignore CHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.component.egg-info/PKG-INFO src/zope.component.egg-info/SOURCES.txt src/zope.component.egg-info/dependency_links.txt src/zope.component.egg-info/namespace_packages.txt src/zope.component.egg-info/not-zip-safe src/zope.component.egg-info/requires.txt src/zope.component.egg-info/top_level.txt src/zope/component/README.txt src/zope/component/__init__.py src/zope/component/_api.py src/zope/component/_declaration.py src/zope/component/configure.zcml src/zope/component/event.py src/zope/component/event.txt src/zope/component/eventtesting.py src/zope/component/factory.py src/zope/component/factory.txt src/zope/component/globalregistry.py src/zope/component/hookable.py src/zope/component/hooks.py src/zope/component/hooks.txt src/zope/component/index.txt src/zope/component/interface.py src/zope/component/interfaces.py src/zope/component/meta.zcml src/zope/component/nexttesting.py src/zope/component/persistentregistry.py src/zope/component/persistentregistry.txt src/zope/component/registry.py src/zope/component/registry.txt src/zope/component/security.py src/zope/component/socketexample.txt src/zope/component/standalonetests.py src/zope/component/testing.py src/zope/component/testlayer.py src/zope/component/testlayer.txt src/zope/component/tests.py src/zope/component/zcml.py src/zope/component/zcml.txt src/zope/component/zcml_conditional.txt src/zope/component/testfiles/__init__.py src/zope/component/testfiles/adapter.py src/zope/component/testfiles/components.py src/zope/component/testfiles/testlayer.zcml src/zope/component/testfiles/views.pyzope2.13-2.13.21/source/zope.component/src/zope.component.egg-info/not-zip-safe0000644000175000017500000000000112214017426026101 0ustar arnauarnau zope2.13-2.13.21/source/zope.component/.bzrignore0000644000175000017500000000010712214017426020414 0ustar arnauarnau./.installed.cfg ./bin ./eggs ./develop-eggs ./docs ./parts *.egg-info zope2.13-2.13.21/source/zope.browserpage/0000755000175000017500000000000012214017535016733 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserpage/setup.py0000644000175000017500000000441512214017535020451 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """zope.browserpage setup """ from setuptools import setup, find_packages long_description = (open('README.txt').read() + '\n\n' + open('CHANGES.txt').read()) setup(name='zope.browserpage', version = '3.12.2', url='http://pypi.python.org/pypi/zope.browserpage/', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', classifiers = ['Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3', ], description='ZCML directives for configuring browser views for Zope.', long_description=long_description, license='ZPL 2.1', packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope'], include_package_data=True, install_requires=['setuptools', 'zope.pagetemplate', 'zope.component>=3.7', 'zope.configuration', 'zope.interface', 'zope.publisher>=3.8', 'zope.schema', 'zope.security [untrustedpython]', 'zope.traversing', ], extras_require={ 'menu': ['zope.browsermenu'], 'test': ['zope.testing', 'zope.browsermenu'], }, zip_safe = False, ) zope2.13-2.13.21/source/zope.browserpage/PKG-INFO0000644000175000017500000000527312214017535020037 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.browserpage Version: 3.12.2 Summary: ZCML directives for configuring browser views for Zope. Home-page: http://pypi.python.org/pypi/zope.browserpage/ Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides ZCML directives for configuring browser views. More specifically it defines the following ZCML directives: * browser:page * browser:pages * browser:view These directives also support menu item registration for pages, when ``zope.browsermenu`` package is installed. Otherwise, they simply ignore the menu attribute. ======= CHANGES ======= 3.12.2 (2010-05-24) =================== - Fixed unit tests broken under Python 2.4 by the switch to the standard library ``doctest`` module. 3.12.1 (2010-04-30) =================== - Prefer the standard library's ``doctest`` module to the one from ``zope.testing``. 3.12.0 (2010-04-26) =================== - Move the implementation of ``tales:expressiontype`` here from ``zope.app.pagetemplate``. 3.11.0 (2009-12-22) =================== - Move the named template implementation here from ``zope.app.pagetemplate``. 3.10.1 (2009-12-22) =================== - Depend on the ``untrustedpython`` extra of ``zope.security``, since we import from ``zope.pagetemplate.engine``. 3.10.0 (2009-12-22) =================== - Remove the dependency on ``zope.app.pagetemplate`` by moving ``viewpagetemplatefile``, ``simpleviewclass`` and ``metaconfigure.registerType`` into this package. 3.9.0 (2009-08-27) ================== - Initial release. This package was split off from ``zope.app.publisher``. Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.browserpage/pip-egg-info/0000755000175000017500000000000012214017536021215 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserpage/pip-egg-info/zope.browserpage.egg-info/0000755000175000017500000000000012214017536026203 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserpage/pip-egg-info/zope.browserpage.egg-info/PKG-INFO0000644000175000017500000000527312214017536027307 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.browserpage Version: 3.12.2 Summary: ZCML directives for configuring browser views for Zope. Home-page: http://pypi.python.org/pypi/zope.browserpage/ Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides ZCML directives for configuring browser views. More specifically it defines the following ZCML directives: * browser:page * browser:pages * browser:view These directives also support menu item registration for pages, when ``zope.browsermenu`` package is installed. Otherwise, they simply ignore the menu attribute. ======= CHANGES ======= 3.12.2 (2010-05-24) =================== - Fixed unit tests broken under Python 2.4 by the switch to the standard library ``doctest`` module. 3.12.1 (2010-04-30) =================== - Prefer the standard library's ``doctest`` module to the one from ``zope.testing``. 3.12.0 (2010-04-26) =================== - Move the implementation of ``tales:expressiontype`` here from ``zope.app.pagetemplate``. 3.11.0 (2009-12-22) =================== - Move the named template implementation here from ``zope.app.pagetemplate``. 3.10.1 (2009-12-22) =================== - Depend on the ``untrustedpython`` extra of ``zope.security``, since we import from ``zope.pagetemplate.engine``. 3.10.0 (2009-12-22) =================== - Remove the dependency on ``zope.app.pagetemplate`` by moving ``viewpagetemplatefile``, ``simpleviewclass`` and ``metaconfigure.registerType`` into this package. 3.9.0 (2009-08-27) ================== - Initial release. This package was split off from ``zope.app.publisher``. Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.browserpage/pip-egg-info/zope.browserpage.egg-info/dependency_links.txt0000644000175000017500000000000112214017536032251 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserpage/pip-egg-info/zope.browserpage.egg-info/requires.txt0000644000175000017500000000034112214017536030601 0ustar arnauarnausetuptools zope.pagetemplate zope.component>=3.7 zope.configuration zope.interface zope.publisher>=3.8 zope.schema zope.security [untrustedpython] zope.traversing [test] zope.testing zope.browsermenu [menu] zope.browsermenu././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.browserpage/pip-egg-info/zope.browserpage.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.browserpage/pip-egg-info/zope.browserpage.egg-info/namespace_packages.t0000644000175000017500000000000512214017536032155 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.browserpage/pip-egg-info/zope.browserpage.egg-info/top_level.txt0000644000175000017500000000000512214017536030730 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.browserpage/pip-egg-info/zope.browserpage.egg-info/SOURCES.txt0000644000175000017500000000204312214017536030066 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.browserpage.egg-info/PKG-INFO pip-egg-info/zope.browserpage.egg-info/SOURCES.txt pip-egg-info/zope.browserpage.egg-info/dependency_links.txt pip-egg-info/zope.browserpage.egg-info/namespace_packages.txt pip-egg-info/zope.browserpage.egg-info/not-zip-safe pip-egg-info/zope.browserpage.egg-info/requires.txt pip-egg-info/zope.browserpage.egg-info/top_level.txt src/zope/__init__.py src/zope/browserpage/__init__.py src/zope/browserpage/metaconfigure.py src/zope/browserpage/metadirectives.py src/zope/browserpage/namedtemplate.py src/zope/browserpage/simpleviewclass.py src/zope/browserpage/viewpagetemplatefile.py src/zope/browserpage/tests/__init__.py src/zope/browserpage/tests/sample.py src/zope/browserpage/tests/simpletestview.py src/zope/browserpage/tests/test_boundpagetemplate.py src/zope/browserpage/tests/test_expressiontype.py src/zope/browserpage/tests/test_namedtemplate.py src/zope/browserpage/tests/test_page.py src/zope/browserpage/tests/test_simpleviewclass.py src/zope/browserpage/tests/test_viewzpt.pyzope2.13-2.13.21/source/zope.browserpage/pip-egg-info/zope.browserpage.egg-info/not-zip-safe0000644000175000017500000000000112214017536030431 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserpage/LICENSE.txt0000644000175000017500000000402612214017535020560 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.browserpage/README.txt0000644000175000017500000000107612214017535020435 0ustar arnauarnau======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides ZCML directives for configuring browser views. More specifically it defines the following ZCML directives: * browser:page * browser:pages * browser:view These directives also support menu item registration for pages, when ``zope.browsermenu`` package is installed. Otherwise, they simply ignore the menu attribute. zope2.13-2.13.21/source/zope.browserpage/setup.cfg0000644000175000017500000000007312214017535020554 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.browserpage/COPYRIGHT.txt0000644000175000017500000000004012214017535021036 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.browserpage/buildout.cfg0000644000175000017500000000061612214017535021246 0ustar arnauarnau[buildout] develop = . parts = test coverage-test coverage-report [test] recipe = zc.recipe.testrunner eggs = zope.browserpage [test] [coverage-test] recipe = zc.recipe.testrunner eggs = zope.browserpage [test] defaults = ['--coverage', '../../coverage'] [coverage-report] recipe = zc.recipe.egg eggs = z3c.coverage scripts = coverage=coverage-report arguments = ('coverage', 'coverage/report') zope2.13-2.13.21/source/zope.browserpage/bootstrap.py0000644000175000017500000000742212214017535021327 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 111688 2010-04-30 19:53:41Z hannosch $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.browserpage/CHANGES.txt0000644000175000017500000000201512214017535020542 0ustar arnauarnau======= CHANGES ======= 3.12.2 (2010-05-24) =================== - Fixed unit tests broken under Python 2.4 by the switch to the standard library ``doctest`` module. 3.12.1 (2010-04-30) =================== - Prefer the standard library's ``doctest`` module to the one from ``zope.testing``. 3.12.0 (2010-04-26) =================== - Move the implementation of ``tales:expressiontype`` here from ``zope.app.pagetemplate``. 3.11.0 (2009-12-22) =================== - Move the named template implementation here from ``zope.app.pagetemplate``. 3.10.1 (2009-12-22) =================== - Depend on the ``untrustedpython`` extra of ``zope.security``, since we import from ``zope.pagetemplate.engine``. 3.10.0 (2009-12-22) =================== - Remove the dependency on ``zope.app.pagetemplate`` by moving ``viewpagetemplatefile``, ``simpleviewclass`` and ``metaconfigure.registerType`` into this package. 3.9.0 (2009-08-27) ================== - Initial release. This package was split off from ``zope.app.publisher``. zope2.13-2.13.21/source/zope.browserpage/src/0000755000175000017500000000000012214017535017522 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserpage/src/zope/0000755000175000017500000000000012214017535020477 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserpage/src/zope/__init__.py0000644000175000017500000000007012214017535022605 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/0000755000175000017500000000000012214017535023017 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/configure.zcml0000644000175000017500000000120112214017535025661 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/viewpagetemplatefile.py0000644000175000017500000000653612214017535027606 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """File-based page templates that can be used as methods on views. $Id: viewpagetemplatefile.py 111996 2010-05-05 17:34:04Z tseaver $ """ __docformat__ = 'restructuredtext' from zope.component import getMultiAdapter from zope.pagetemplate.pagetemplatefile import PageTemplateFile from zope.pagetemplate.engine import TrustedAppPT class ViewPageTemplateFile(TrustedAppPT, PageTemplateFile): """Page Templates used as methods of views defined as Python classes. """ def __init__(self, filename, _prefix=None, content_type=None): _prefix = self.get_path_from_prefix(_prefix) super(ViewPageTemplateFile, self).__init__(filename, _prefix) if content_type is not None: self.content_type = content_type def pt_getContext(self, instance, request, **_kw): # instance is a View component namespace = super(ViewPageTemplateFile, self).pt_getContext(**_kw) namespace['request'] = request namespace['view'] = instance namespace['context'] = context = instance.context namespace['views'] = ViewMapper(context, request) return namespace def __call__(self, instance, *args, **keywords): namespace = self.pt_getContext( request=instance.request, instance=instance, args=args, options=keywords) debug_flags = instance.request.debug s = self.pt_render( namespace, showtal=getattr(debug_flags, 'showTAL', 0), sourceAnnotations=getattr(debug_flags, 'sourceAnnotations', 0), ) response = instance.request.response if not response.getHeader("Content-Type"): response.setHeader("Content-Type", self.content_type) return s def __get__(self, instance, type): return BoundPageTemplate(self, instance) class ViewMapper(object): def __init__(self, ob, request): self.ob = ob self.request = request def __getitem__(self, name): return getMultiAdapter((self.ob, self.request), name=name) class BoundPageTemplate(object): def __init__(self, pt, ob): object.__setattr__(self, 'im_func', pt) object.__setattr__(self, 'im_self', ob) macros = property(lambda self: self.im_func.macros) filename = property(lambda self: self.im_func.filename) def __call__(self, *args, **kw): if self.im_self is None: im_self, args = args[0], args[1:] else: im_self = self.im_self return self.im_func(im_self, *args, **kw) def __setattr__(self, name, v): raise AttributeError("Can't set attribute", name) def __repr__(self): return "" % self.im_self def NoTraverser(ob, request): return None zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/namedtemplate.txt0000644000175000017500000000467712214017535026416 0ustar arnauarnau=============== Named Templates =============== We often want to be able to define view logic and view templates independently. We'd like to be able to change the template used by a form without being forced to modify the form. Named templates provide templates that are registered as named view adapters. To define a named template, use the `NamedTemplateImplementation` constructor: >>> from zope.browserpage import ViewPageTemplateFile >>> from zope.browserpage.namedtemplate import ( ... NamedTemplateImplementation) >>> sample = ViewPageTemplateFile('tests/namedtemplate.pt') >>> sample = NamedTemplateImplementation(sample) Let's define a view that uses the named template. To use a named template, use the NamedTemplate constructor, and give a template name: >>> from zope.browserpage.namedtemplate import NamedTemplate >>> class MyView: ... def __init__(self, context, request): ... self.context = context ... self.request = request ... ... __call__ = NamedTemplate('sample') Normally, we'd register a named template for a view interface, to allow it to be registered for multiple views. We'll just register it for our view class. >>> from zope import component >>> component.provideAdapter(sample, [MyView], name='sample') Now, with this in place, we should be able to use our view: >>> class MyContent: ... def __init__(self, name): ... self.name = name >>> from zope.publisher.browser import TestRequest >>> print MyView(MyContent('bob'), TestRequest())(x=42) Hello bob The URL is http://127.0.0.1 The positional arguments were () The keyword argument x is 42 The view type that a named template is to be used for can be supplied when the named template is created: >>> class MyView: ... def __init__(self, context, request): ... self.context = context ... self.request = request ... ... __call__ = NamedTemplate('sample2') >>> sample = ViewPageTemplateFile('tests/namedtemplate.pt') >>> sample = NamedTemplateImplementation(sample, MyView) >>> component.provideAdapter(sample, name='sample2') >>> print MyView(MyContent('bob'), TestRequest())(x=42) Hello bob The URL is http://127.0.0.1 The positional arguments were () The keyword argument x is 42 zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/metaconfigure.py0000644000175000017500000003573112214017535026232 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Browser page ZCML configuration code $Id: metaconfigure.py 111996 2010-05-05 17:34:04Z tseaver $ """ import os from zope.component import queryMultiAdapter from zope.component.interface import provideInterface from zope.component.zcml import handler from zope.interface import implements, classImplements, Interface from zope.publisher.interfaces import NotFound from zope.security.checker import CheckerPublic, Checker, defineChecker from zope.configuration.exceptions import ConfigurationError from zope.pagetemplate.engine import Engine from zope.pagetemplate.engine import _Engine from zope.pagetemplate.engine import TrustedEngine from zope.pagetemplate.engine import _TrustedEngine from zope.publisher.interfaces.browser import IBrowserRequest from zope.publisher.interfaces.browser import IDefaultBrowserLayer from zope.publisher.interfaces.browser import IBrowserPublisher from zope.publisher.browser import BrowserView from zope.browserpage.simpleviewclass import SimpleViewClass from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile try: from zope.browsermenu.metaconfigure import menuItemDirective except ImportError: menuItemDirective = None # There are three cases we want to suport: # # Named view without pages (single-page view) # # # # Unnamed view with pages (multi-page view) # # # # # # # # Named view with pages (add view is a special case of this) # # # # # # # We'll also provide a convenience directive for add views: # # # # # # # page def page(_context, name, permission, for_=Interface, layer=IDefaultBrowserLayer, template=None, class_=None, allowed_interface=None, allowed_attributes=None, attribute='__call__', menu=None, title=None, ): _handle_menu(_context, menu, title, [for_], name, permission, layer) required = {} permission = _handle_permission(_context, permission) if not (class_ or template): raise ConfigurationError("Must specify a class or template") if attribute != '__call__': if template: raise ConfigurationError( "Attribute and template cannot be used together.") if not class_: raise ConfigurationError( "A class must be provided if attribute is used") if template: template = os.path.abspath(str(_context.path(template))) if not os.path.isfile(template): raise ConfigurationError("No such file", template) required['__getitem__'] = permission # TODO: new __name__ attribute must be tested if class_: if attribute != '__call__': if not hasattr(class_, attribute): raise ConfigurationError( "The provided class doesn't have the specified attribute " ) if template: # class and template new_class = SimpleViewClass(template, bases=(class_, ), name=name) else: if not hasattr(class_, 'browserDefault'): cdict = { 'browserDefault': lambda self, request: (getattr(self, attribute), ()) } else: cdict = {} cdict['__name__'] = name cdict['__page_attribute__'] = attribute new_class = type(class_.__name__, (class_, simple,), cdict) if hasattr(class_, '__implements__'): classImplements(new_class, IBrowserPublisher) else: # template new_class = SimpleViewClass(template, name=name) for n in (attribute, 'browserDefault', '__call__', 'publishTraverse'): required[n] = permission _handle_allowed_interface(_context, allowed_interface, permission, required) _handle_allowed_attributes(_context, allowed_attributes, permission, required) _handle_for(_context, for_) defineChecker(new_class, Checker(required)) _context.action( discriminator = ('view', for_, name, IBrowserRequest, layer), callable = handler, args = ('registerAdapter', new_class, (for_, layer), Interface, name, _context.info), ) # pages, which are just a short-hand for multiple page directives. # Note that a class might want to access one of the defined # templates. If it does though, it should use getMultiAdapter. class pages(object): def __init__(self, _context, permission, for_=Interface, layer=IDefaultBrowserLayer, class_=None, allowed_interface=None, allowed_attributes=None, ): self.opts = dict(for_=for_, permission=permission, layer=layer, class_=class_, allowed_interface=allowed_interface, allowed_attributes=allowed_attributes, ) def page(self, _context, name, attribute='__call__', template=None, menu=None, title=None): return page(_context, name=name, attribute=attribute, template=template, menu=menu, title=title, **(self.opts)) def __call__(self): return () # view (named view with pages) # This is a different case. We actually build a class with attributes # for all of the given pages. class view(object): default = None def __init__(self, _context, permission, for_=Interface, name='', layer=IDefaultBrowserLayer, class_=None, allowed_interface=None, allowed_attributes=None, menu=None, title=None, provides=Interface, ): _handle_menu(_context, menu, title, [for_], name, permission, layer) permission = _handle_permission(_context, permission) self.args = (_context, name, for_, permission, layer, class_, allowed_interface, allowed_attributes) self.pages = [] self.menu = menu self.provides = provides def page(self, _context, name, attribute=None, template=None): if template: template = os.path.abspath(_context.path(template)) if not os.path.isfile(template): raise ConfigurationError("No such file", template) else: if not attribute: raise ConfigurationError( "Must specify either a template or an attribute name") self.pages.append((name, attribute, template)) return () def defaultPage(self, _context, name): self.default = name return () def __call__(self): (_context, name, for_, permission, layer, class_, allowed_interface, allowed_attributes) = self.args required = {} cdict = {} pages = {} for pname, attribute, template in self.pages: if template: cdict[pname] = ViewPageTemplateFile(template) if attribute and attribute != name: cdict[attribute] = cdict[pname] else: if not hasattr(class_, attribute): raise ConfigurationError("Undefined attribute", attribute) attribute = attribute or pname required[pname] = permission pages[pname] = attribute # This should go away, but noone seems to remember what to do. :-( if hasattr(class_, 'publishTraverse'): def publishTraverse(self, request, name, pages=pages, getattr=getattr): if name in pages: return getattr(self, pages[name]) view = queryMultiAdapter((self, request), name=name) if view is not None: return view m = class_.publishTraverse.__get__(self) return m(request, name) else: def publishTraverse(self, request, name, pages=pages, getattr=getattr): if name in pages: return getattr(self, pages[name]) view = queryMultiAdapter((self, request), name=name) if view is not None: return view raise NotFound(self, name, request) cdict['publishTraverse'] = publishTraverse if not hasattr(class_, 'browserDefault'): if self.default or self.pages: default = self.default or self.pages[0][0] cdict['browserDefault'] = ( lambda self, request, default=default: (self, (default, )) ) elif providesCallable(class_): cdict['browserDefault'] = ( lambda self, request: (self, ()) ) if class_ is not None: bases = (class_, simple) else: bases = (simple,) try: cname = str(name) except: cname = "GeneratedClass" cdict['__name__'] = name newclass = type(cname, bases, cdict) for n in ('publishTraverse', 'browserDefault', '__call__'): required[n] = permission _handle_allowed_interface(_context, allowed_interface, permission, required) _handle_allowed_attributes(_context, allowed_attributes, permission, required) _handle_for(_context, for_) defineChecker(newclass, Checker(required)) if self.provides is not None: _context.action( discriminator = None, callable = provideInterface, args = ('', self.provides) ) _context.action( discriminator = ('view', (for_, layer), name, self.provides), callable = handler, args = ('registerAdapter', newclass, (for_, layer), self.provides, name, _context.info), ) def _handle_menu(_context, menu, title, for_, name, permission, layer=IDefaultBrowserLayer): if menu or title: if not (menu and title): raise ConfigurationError( "If either menu or title are specified, they must " "both be specified.") if len(for_) != 1: raise ConfigurationError( "Menus can be specified only for single-view, not for " "multi-views.") if menuItemDirective is None: import warnings warnings.warn_explicit( 'Page directive used with "menu" argument, while "zope.browsermenu" ' 'package is not installed. Doing nothing.', UserWarning, _context.info.file, _context.info.line) return [] return menuItemDirective( _context, menu, for_[0], '@@' + name, title, permission=permission, layer=layer) return [] def _handle_permission(_context, permission): if permission == 'zope.Public': permission = CheckerPublic return permission def _handle_allowed_interface(_context, allowed_interface, permission, required): # Allow access for all names defined by named interfaces if allowed_interface: for i in allowed_interface: _context.action( discriminator = None, callable = provideInterface, args = (None, i) ) for name in i: required[name] = permission def _handle_allowed_attributes(_context, allowed_attributes, permission, required): # Allow access for all named attributes if allowed_attributes: for name in allowed_attributes: required[name] = permission def _handle_for(_context, for_): if for_ is not None: _context.action( discriminator = None, callable = provideInterface, args = ('', for_) ) class simple(BrowserView): implements(IBrowserPublisher) def publishTraverse(self, request, name): raise NotFound(self, name, request) def __call__(self, *a, **k): # If a class doesn't provide it's own call, then get the attribute # given by the browser default. attr = self.__page_attribute__ if attr == '__call__': raise AttributeError("__call__") meth = getattr(self, attr) return meth(*a, **k) def providesCallable(class_): if hasattr(class_, '__call__'): for c in class_.__mro__: if '__call__' in c.__dict__: return True return False def expressiontype(_context, name, handler): _context.action( discriminator = ("tales:expressiontype", name), callable = registerType, args = (name, handler) ) def registerType(name, handler): Engine.registerType(name, handler) TrustedEngine.registerType(name, handler) def clear(): Engine.__init__() _Engine(Engine) TrustedEngine.__init__() _TrustedEngine(TrustedEngine) try: from zope.testing.cleanup import addCleanUp except ImportError: pass else: addCleanUp(clear) zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/namedtemplate.py0000644000175000017500000000430212214017535026210 0ustar arnauarnau############################################################################## # # Copyright (c) 2005-2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ """ from zope import component, interface import zope.traversing.interfaces class INamedTemplate(interface.Interface): """A template that is looked up by name """ class NamedTemplateImplementation: def __init__(self, descriptor, view_type=None): try: descriptor.__get__ except AttributeError: raise TypeError( "NamedTemplateImplementation must be passed a descriptor." ) self.descriptor = descriptor interface.implementer(INamedTemplate)(self) if view_type is not None: component.adapter(view_type)(self) def __call__(self, instance): return self.descriptor.__get__(instance, instance.__class__) class implementation: def __init__(self, view_type=None): self.view_type = view_type def __call__(self, descriptor): return NamedTemplateImplementation(descriptor, self.view_type) class NamedTemplate(object): def __init__(self, name): self.__name__ = name def __get__(self, instance, type=None): if instance is None: return self return component.getAdapter(instance, INamedTemplate, self.__name__) def __call__(self, instance, *args, **kw): self.__get__(instance)(*args, **kw) # TODO need test class NamedTemplatePathAdapter(object): interface.implements(zope.traversing.interfaces.IPathAdapter) def __init__(self, context): self.context = context def __getitem__(self, name): return component.getAdapter(self.context, INamedTemplate, name) zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/__init__.py0000644000175000017500000000010612214017535025125 0ustar arnauarnaufrom zope.browserpage.viewpagetemplatefile import ViewPageTemplateFilezope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/simpleviewclass.py0000644000175000017500000000355412214017535026612 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Simple View Class $Id: simpleviewclass.py 111996 2010-05-05 17:34:04Z tseaver $ """ __docformat__ = 'restructuredtext' import sys from zope.interface import implements from zope.publisher.browser import BrowserView from zope.publisher.interfaces.browser import IBrowserPublisher from zope.publisher.interfaces import NotFound from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile class simple(BrowserView): implements(IBrowserPublisher) def browserDefault(self, request): return self, () def publishTraverse(self, request, name): if name == 'index.html': return self.index raise NotFound(self, name, request) def __getitem__(self, name): return self.index.macros[name] def __call__(self, *args, **kw): return self.index(*args, **kw) def SimpleViewClass(src, offering=None, used_for=None, bases=(), name=u''): if offering is None: offering = sys._getframe(1).f_globals bases += (simple, ) class_ = type("SimpleViewClass from %s" % src, bases, {'index': ViewPageTemplateFile(src, offering), '__name__': name}) if used_for is not None: class_.__used_for__ = used_for return class_ zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/meta.zcml0000644000175000017500000000236312214017535024640 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/0000755000175000017500000000000012214017535024161 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/test_namedtemplate.py0000644000175000017500000000242512214017535030415 0ustar arnauarnau############################################################################## # # Copyright (c) 2005-2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ $Id: test_namedtemplate.py 111996 2010-05-05 17:34:04Z tseaver $ """ import os import os.path import zope.component.testing import zope.traversing.adapters def pageSetUp(test): zope.component.testing.setUp(test) zope.component.provideAdapter( zope.traversing.adapters.DefaultTraversable, [None], ) def test_suite(): import doctest filename = os.path.join(os.pardir, 'namedtemplate.txt') return doctest.DocFileSuite( filename, setUp=pageSetUp, tearDown=zope.component.testing.tearDown, globs={'__file__': os.path.abspath(os.path.join(os.path.dirname(__file__), filename))} ) zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/test_page.py0000644000175000017500000007733512214017535026525 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for browser:page directive and friends $Id: test_page.py 111996 2010-05-05 17:34:04Z tseaver $ """ import sys import os import unittest from doctest import DocTestSuite from cStringIO import StringIO from zope import component from zope.interface import Interface, implements, directlyProvides, providedBy import zope.security.management from zope.configuration.xmlconfig import xmlconfig, XMLConfig from zope.configuration.exceptions import ConfigurationError from zope.publisher.browser import TestRequest from zope.publisher.interfaces import IDefaultViewName from zope.publisher.interfaces.browser import IBrowserPublisher from zope.publisher.interfaces.browser import IBrowserRequest from zope.publisher.interfaces.browser import IBrowserSkinType, IDefaultSkin from zope.security.proxy import removeSecurityProxy, ProxyFactory from zope.security.permission import Permission from zope.security.interfaces import IPermission from zope.testing import cleanup from zope.traversing.adapters import DefaultTraversable from zope.traversing.interfaces import ITraversable import zope.publisher.defaultview import zope.browserpage import zope.browsermenu from zope.browsermenu.menu import getFirstMenuItem from zope.browsermenu.interfaces import IMenuItemType from zope.component.testfiles.views import IC, V1, VZMI, R1, IV tests_path = os.path.dirname(__file__) template = """ %s """ class templateclass(object): def data(self): return 42 request = TestRequest() class V2(V1, object): def action(self): return self.action2() def action2(self): return "done" class VT(V1, object): def publishTraverse(self, request, name): try: return int(name) except: return super(VT, self).publishTraverse(request, name) class Ob(object): implements(IC) ob = Ob() class NCV(object): "non callable view" def __init__(self, context, request): pass class CV(NCV): "callable view" def __call__(self): pass class C_w_implements(NCV): implements(Interface) def index(self): return self class ITestLayer(IBrowserRequest): """Test Layer.""" class ITestSkin(ITestLayer): """Test Skin.""" class ITestMenu(Interface): """Test menu.""" directlyProvides(ITestMenu, IMenuItemType) class Test(cleanup.CleanUp, unittest.TestCase): def setUp(self): super(Test, self).setUp() XMLConfig('meta.zcml', zope.browserpage)() XMLConfig('meta.zcml', zope.browsermenu)() component.provideAdapter(DefaultTraversable, (None,), ITraversable, ) zope.security.management.newInteraction() def testPage(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) xmlconfig(StringIO(template % ( ''' ''' ))) v = component.queryMultiAdapter((ob, request), name='test') self.assert_(issubclass(v.__class__, V1)) def testPageWithClassWithMenu(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) testtemplate = os.path.join(tests_path, 'testfiles', 'test.pt') xmlconfig(StringIO(template % ( ''' ''' % testtemplate ))) menuItem = getFirstMenuItem('test_menu', ob, TestRequest()) self.assertEqual(menuItem["title"], "Test View") self.assertEqual(menuItem["action"], "@@test") v = component.queryMultiAdapter((ob, request), name='test') self.assertEqual(v(), "

test

\n") def testPageWithTemplateWithMenu(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) testtemplate = os.path.join(tests_path, 'testfiles', 'test.pt') xmlconfig(StringIO(template % ( ''' ''' % testtemplate ))) menuItem = getFirstMenuItem('test_menu', ob, TestRequest()) self.assertEqual(menuItem["title"], "Test View") self.assertEqual(menuItem["action"], "@@test") v = component.queryMultiAdapter((ob, request), name='test') self.assertEqual(v(), "

test

\n") def testPageInPagesWithTemplateWithMenu(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) testtemplate = os.path.join(tests_path, 'testfiles', 'test.pt') xmlconfig(StringIO(template % ( ''' ''' % testtemplate ))) menuItem = getFirstMenuItem('test_menu', ob, TestRequest()) self.assertEqual(menuItem["title"], "Test View") self.assertEqual(menuItem["action"], "@@test") v = component.queryMultiAdapter((ob, request), name='test') self.assertEqual(v(), "

test

\n") def testPageInPagesWithClassWithMenu(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) testtemplate = os.path.join(tests_path, 'testfiles', 'test.pt') xmlconfig(StringIO(template % ( ''' ''' % testtemplate ))) menuItem = getFirstMenuItem('test_menu', ob, TestRequest()) self.assertEqual(menuItem["title"], "Test View") self.assertEqual(menuItem["action"], "@@test") v = component.queryMultiAdapter((ob, request), name='test') self.assertEqual(v(), "

test

\n") def testSkinPage(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) xmlconfig(StringIO(template % ( ''' ''' ))) v = component.queryMultiAdapter((ob, request), name='test') self.assert_(issubclass(v.__class__, V1)) v = component.queryMultiAdapter( (ob, TestRequest(skin=ITestSkin)), name='test') self.assert_(issubclass(v.__class__, VZMI)) def testInterfaceProtectedPage(self): xmlconfig(StringIO(template % ''' ''' )) v = component.getMultiAdapter((ob, request), name='test') v = ProxyFactory(v) self.assertEqual(v.index(), 'V1 here') self.assertRaises(Exception, getattr, v, 'action') def testAttributeProtectedPage(self): xmlconfig(StringIO(template % ''' ''' )) v = component.getMultiAdapter((ob, request), name='test') v = ProxyFactory(v) self.assertEqual(v.action(), 'done') self.assertEqual(v.action2(), 'done') self.assertRaises(Exception, getattr, v, 'index') def testAttributeProtectedView(self): xmlconfig(StringIO(template % ''' ''' )) v = component.getMultiAdapter((ob, request), name='test') v = ProxyFactory(v) page = v.publishTraverse(request, 'index.html') self.assertEqual(page(), 'done') self.assertEqual(v.action2(), 'done') self.assertRaises(Exception, getattr, page, 'index') def testInterfaceAndAttributeProtectedPage(self): xmlconfig(StringIO(template % ''' ''' )) v = component.getMultiAdapter((ob, request), name='test') self.assertEqual(v.index(), 'V1 here') self.assertEqual(v.action(), 'done') def testDuplicatedInterfaceAndAttributeProtectedPage(self): xmlconfig(StringIO(template % ''' ''' )) v = component.getMultiAdapter((ob, request), name='test') self.assertEqual(v.index(), 'V1 here') self.assertEqual(v.action(), 'done') def test_class_w_implements(self): xmlconfig(StringIO(template % ''' ''' )) v = component.getMultiAdapter((ob, request), name='test') self.assertEqual(v.index(), v) self.assert_(IBrowserPublisher.providedBy(v)) def testIncompleteProtectedPageNoPermission(self): self.assertRaises( ConfigurationError, xmlconfig, StringIO(template % ''' ''' )) def testPageViews(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) test3 = os.path.join(tests_path, 'testfiles', 'test3.pt') xmlconfig(StringIO(template % ''' ''' % test3 )) v = component.getMultiAdapter((ob, request), name='index.html') self.assertEqual(v(), 'V1 here') v = component.getMultiAdapter((ob, request), name='action.html') self.assertEqual(v(), 'done') v = component.getMultiAdapter((ob, request), name='test.html') self.assertEqual(str(v()), '

done

\n') def testNamedViewPageViewsCustomTraversr(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) xmlconfig(StringIO(template % ''' ''' )) view = component.getMultiAdapter((ob, request), name='test') view = removeSecurityProxy(view) self.assertEqual(view.browserDefault(request)[1], (u'index.html', )) v = view.publishTraverse(request, 'index.html') v = removeSecurityProxy(v) self.assertEqual(v(), 'V1 here') v = view.publishTraverse(request, 'action.html') v = removeSecurityProxy(v) self.assertEqual(v(), 'done') def testNamedViewNoPagesForCallable(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) xmlconfig(StringIO(template % ''' ''' )) view = component.getMultiAdapter((ob, request), name='test') view = removeSecurityProxy(view) self.assertEqual(view.browserDefault(request), (view, ())) def testNamedViewNoPagesForNonCallable(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) xmlconfig(StringIO(template % ''' ''' )) view = component.getMultiAdapter((ob, request), name='test') view = removeSecurityProxy(view) self.assertEqual(getattr(view, 'browserDefault', None), None) def testNamedViewPageViewsNoDefault(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) test3 = os.path.join(tests_path, 'testfiles', 'test3.pt') xmlconfig(StringIO(template % ''' ''' % test3 )) view = component.getMultiAdapter((ob, request), name='test') view = removeSecurityProxy(view) self.assertEqual(view.browserDefault(request)[1], (u'index.html', )) v = view.publishTraverse(request, 'index.html') v = removeSecurityProxy(v) self.assertEqual(v(), 'V1 here') v = view.publishTraverse(request, 'action.html') v = removeSecurityProxy(v) self.assertEqual(v(), 'done') v = view.publishTraverse(request, 'test.html') v = removeSecurityProxy(v) self.assertEqual(str(v()), '

done

\n') def testNamedViewPageViewsWithDefault(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) test3 = os.path.join(tests_path, 'testfiles', 'test3.pt') xmlconfig(StringIO(template % ''' ''' % test3 )) view = component.getMultiAdapter((ob, request), name='test') view = removeSecurityProxy(view) self.assertEqual(view.browserDefault(request)[1], (u'test.html', )) v = view.publishTraverse(request, 'index.html') v = removeSecurityProxy(v) self.assertEqual(v(), 'V1 here') v = view.publishTraverse(request, 'action.html') v = removeSecurityProxy(v) self.assertEqual(v(), 'done') v = view.publishTraverse(request, 'test.html') v = removeSecurityProxy(v) self.assertEqual(str(v()), '

done

\n') def testTraversalOfPageForView(self): """Tests proper traversal of a page defined for a view.""" xmlconfig(StringIO(template % ''' ''' )) view = component.getMultiAdapter((ob, request), name='test') view = removeSecurityProxy(view) view.publishTraverse(request, 'index.html') def testTraversalOfPageForViewWithPublishTraverse(self): """Tests proper traversal of a page defined for a view. This test is different from testTraversalOfPageForView in that it tests the behavior on a view that has a publishTraverse method -- the implementation of the lookup is slightly different in such a case. """ xmlconfig(StringIO(template % ''' ''' )) view = component.getMultiAdapter((ob, request), name='test') view = removeSecurityProxy(view) view.publishTraverse(request, 'index.html') def testProtectedPageViews(self): component.provideUtility(Permission('p', 'P'), IPermission, 'p') request = TestRequest() self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) xmlconfig(StringIO(template % ''' ''' )) v = component.getMultiAdapter((ob, request), name='index.html') v = ProxyFactory(v) zope.security.management.getInteraction().add(request) self.assertRaises(Exception, v) v = component.getMultiAdapter((ob, request), name='action.html') v = ProxyFactory(v) self.assertRaises(Exception, v) def testProtectedNamedViewPageViews(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) xmlconfig(StringIO(template % ''' ''' )) view = component.getMultiAdapter((ob, request), name='test') self.assertEqual(view.browserDefault(request)[1], (u'index.html', )) v = view.publishTraverse(request, 'index.html') self.assertEqual(v(), 'V1 here') def testSkinnedPageView(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) xmlconfig(StringIO(template % ''' ''' )) v = component.getMultiAdapter((ob, request), name='index.html') self.assertEqual(v(), 'V1 here') v = component.getMultiAdapter((ob, TestRequest(skin=ITestSkin)), name='index.html') self.assertEqual(v(), 'done') def test_template_page(self): path = os.path.join(tests_path, 'testfiles', 'test.pt') self.assertEqual( component.queryMultiAdapter((ob, request), name='index.html'), None) xmlconfig(StringIO(template % ''' ''' % path )) v = component.getMultiAdapter((ob, request), name='index.html') self.assertEqual(v().strip(), '

test

') def test_page_menu_within_different_layers(self): path = os.path.join(tests_path, 'testfiles', 'test.pt') self.assertEqual( component.queryMultiAdapter((ob, request), name='index.html'), None) xmlconfig(StringIO(template % ''' ''' % (path, path) )) v = component.getMultiAdapter((ob, request), name='index.html') self.assertEqual(v().strip(), '

test

') def testtemplateWClass(self): path = os.path.join(tests_path, 'testfiles', 'test2.pt') self.assertEqual( component.queryMultiAdapter((ob, request), name='index.html'), None) xmlconfig(StringIO(template % ''' ''' % path )) v = component.getMultiAdapter((ob, request), name='index.html') self.assertEqual(v().strip(), '

42

') def testProtectedtemplate(self): path = os.path.join(tests_path, 'testfiles', 'test.pt') request = TestRequest() self.assertEqual( component.queryMultiAdapter((ob, request), name='test'), None) xmlconfig(StringIO(template % ''' ''' % path )) xmlconfig(StringIO(template % ''' ''' % path )) v = component.getMultiAdapter((ob, request), name='xxx.html') v = ProxyFactory(v) zope.security.management.getInteraction().add(request) self.assertRaises(Exception, v) v = component.getMultiAdapter((ob, request), name='index.html') v = ProxyFactory(v) self.assertEqual(v().strip(), '

test

') def testtemplateNoName(self): path = os.path.join(tests_path, 'testfiles', 'test.pt') self.assertRaises( ConfigurationError, xmlconfig, StringIO(template % ''' ''' % path )) def testtemplateAndPage(self): path = os.path.join(tests_path, 'testfiles', 'test.pt') self.assertRaises( ConfigurationError, xmlconfig, StringIO(template % ''' ''' % path )) def testViewThatProvidesAnInterface(self): request = TestRequest() self.assertEqual( component.queryMultiAdapter((ob, request), IV, name='test'), None) xmlconfig(StringIO(template % ''' ''' )) v = component.queryMultiAdapter((ob, request), IV, name='test') self.assertEqual(v, None) xmlconfig(StringIO(template % ''' ''' )) v = component.queryMultiAdapter((ob, request), IV, name='test') self.assert_(isinstance(v, V1)) def testUnnamedViewThatProvidesAnInterface(self): request = TestRequest() self.assertEqual(component.queryMultiAdapter((ob, request), IV), None) xmlconfig(StringIO(template % ''' ''' )) v = component.queryMultiAdapter((ob, request), IV) self.assertEqual(v, None) xmlconfig(StringIO(template % ''' ''' )) v = component.queryMultiAdapter((ob, request), IV) self.assert_(isinstance(v, V1)) def test_suite(): return unittest.makeSuite(Test) zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/sample.py0000644000175000017500000000150012214017535026010 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample Component $Id: sample.py 111996 2010-05-05 17:34:04Z tseaver $ """ from zope.browserpage import ViewPageTemplateFile class C(object): index = ViewPageTemplateFile('test.pt') zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/test.pt0000644000175000017500000000003312214017535025501 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/namedtemplate.pt0000644000175000017500000000035112214017535027345 0ustar arnauarnau Hello The URL is The positional arguments were The keyword argument x is zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/test_expressiontype.py0000644000175000017500000000353112214017535030675 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests to check talesapi zcml configuration $Id: test_expressiontype.py 111996 2010-05-05 17:34:04Z tseaver $ """ import unittest from cStringIO import StringIO from zope.configuration.xmlconfig import xmlconfig, XMLConfig from zope.pagetemplate.engine import Engine import zope.browserpage from zope.component.testing import PlacelessSetup template = """ %s """ class Handler(object): pass class Test(PlacelessSetup, unittest.TestCase): def setUp(self): super(Test, self).setUp() XMLConfig('meta.zcml', zope.browserpage)() def testExpressionType(self): xmlconfig(StringIO(template % ( """ """ ))) self.assert_("test" in Engine.getTypes()) self.assert_(Handler is Engine.getTypes()['test']) def test_suite(): loader=unittest.TestLoader() return loader.loadTestsFromTestCase(Test) if __name__=='__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/__init__.py0000644000175000017500000000000112214017535026261 0ustar arnauarnau#zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/simpletestview.py0000644000175000017500000000152112214017535027616 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Simple Test View $Id: simpletestview.py 111996 2010-05-05 17:34:04Z tseaver $ """ from zope.browserpage.simpleviewclass import SimpleViewClass SimpleTestView = SimpleViewClass('testsimpleviewclass.pt') zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/testsimpleviewclass.pt0000644000175000017500000000012312214017535030634 0ustar arnauarnau

hello world

zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/test_boundpagetemplate.py0000644000175000017500000000230212214017535031267 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bound Page Template Tests $Id: test_boundpagetemplate.py 111996 2010-05-05 17:34:04Z tseaver $ """ import unittest class Test(unittest.TestCase): def testAttributes(self): from zope.browserpage.tests.sample import C C.index.im_func.foo = 1 self.assertEqual(C.index.macros, C.index.im_func.macros) self.assertEqual(C.index.filename, C.index.im_func.filename) def test_suite(): loader=unittest.TestLoader() return loader.loadTestsFromTestCase(Test) if __name__=='__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/test_simpleviewclass.py0000644000175000017500000001341212214017535031005 0ustar arnauarnau############################################################################## # # Copyright (c) 2001-2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Simple View Class Tests $Id: test_simpleviewclass.py 111996 2010-05-05 17:34:04Z tseaver $ """ import unittest class Test_SimpleTestView(unittest.TestCase): def _getTargetClass(self): from zope.browserpage.tests.simpletestview import SimpleTestView return SimpleTestView def _makeOne(self, context, request): return self._getTargetClass()(context, request) def test_simple(self): from zope.publisher.browser import TestRequest context = DummyContext() request = TestRequest() view = self._makeOne(context, request) macro = view['test'] out = view() self.assertEqual(out, '\n' ' \n' '

hello world

\n' ' \n\n') class Test_SimpleViewClass(unittest.TestCase): def _getTargetClass(self): from zope.browserpage.simpleviewclass import SimpleViewClass return SimpleViewClass def _makeKlass(self, *args, **kw): return self._getTargetClass()(*args, **kw) def test___name__(self): klass = self._makeKlass('testsimpleviewclass.pt', name='test.html') view = klass(None, None) self.assertEqual(view.__name__, 'test.html') def test___getitem___(self): klass = self._makeKlass('testsimpleviewclass.pt', name='test.html') view = klass(None, None) self.assert_(view['test'] is not None) self.assertRaises(KeyError, view.__getitem__, 'foo') def test_w_base_classes(self): from zope.publisher.browser import TestRequest class BaseClass(object): pass klass = self._makeKlass('testsimpleviewclass.pt', bases=(BaseClass, )) self.failUnless(issubclass(klass, BaseClass)) ob = DummyContext() request = TestRequest() view = klass(ob, request) macro = view['test'] out = view() self.assertEqual(out, '\n' ' \n' '

hello world

\n' ' \n\n') class Test_simple(unittest.TestCase): def _getTargetClass(self): from zope.browserpage.simpleviewclass import simple return simple def _makeOne(self, context=None, request=None): if context is None: context = DummyContext() if request is None: request = DummyRequest() return self._getTargetClass()(context, request) def test_class_conforms_to_IBrowserPublisher(self): from zope.interface.verify import verifyClass from zope.publisher.interfaces.browser import IBrowserPublisher verifyClass(IBrowserPublisher, self._getTargetClass()) def test_browserDefault(self): request = DummyRequest() view = self._makeOne(request=request) self.assertEqual(view.browserDefault(request), (view, ())) def test_publishTraverse_not_index_raises_NotFound(self): from zope.publisher.interfaces import NotFound request = DummyRequest() view = self._makeOne(request=request) self.assertRaises(NotFound, view.publishTraverse, request, 'nonesuch') def test_publishTraverse_w_index_returns_index(self): request = DummyRequest() view = self._makeOne(request=request) index = view.index = DummyTemplate() self.failUnless(view.publishTraverse(request, 'index.html') is index) def test___getitem___uses_index_macros(self): view = self._makeOne() view.index = index = DummyTemplate() index.macros = {} index.macros['aaa'] = aaa = object() self.failUnless(view['aaa'] is aaa) def test___call___no_args_no_kw(self): view = self._makeOne() view.index = index = DummyTemplate() result = view() self.failUnless(result is index) self.assertEqual(index._called_with, ((), {})) def test___call___w_args_no_kw(self): view = self._makeOne() view.index = index = DummyTemplate() result = view('abc') self.failUnless(result is index) self.assertEqual(index._called_with, (('abc',), {})) def test___call___no_args_w_kw(self): view = self._makeOne() view.index = index = DummyTemplate() result = view(foo='bar') self.failUnless(result is index) self.assertEqual(index._called_with, ((), {'foo': 'bar'})) def test___call___no_args_no_kw(self): view = self._makeOne() view.index = index = DummyTemplate() result = view('abc', foo='bar') self.failUnless(result is index) self.assertEqual(index._called_with, (('abc',), {'foo': 'bar'})) class DummyContext: pass class DummyResponse: pass class DummyRequest: debug = False response = DummyResponse() class DummyTemplate: def __call__(self, *args, **kw): self._called_with = (args, kw) return self def test_suite(): return unittest.TestSuite(( unittest.makeSuite(Test_SimpleTestView), unittest.makeSuite(Test_SimpleViewClass), unittest.makeSuite(Test_simple), )) zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/test_viewzpt.py0000644000175000017500000001115412214017535027304 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """View ZPT Tests $Id: test_viewzpt.py 111996 2010-05-05 17:34:04Z tseaver $ """ import unittest from zope.component import getGlobalSiteManager from zope.component.testing import PlacelessSetup from zope.interface import Interface, implements from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile class I1(Interface): pass class C1(object): implements(I1) class InstanceWithContext(object): def __init__(self, context): self.context = context class InstanceWithoutContext(object): pass class TestViewZPT(PlacelessSetup, unittest.TestCase): def setUp(self): super(TestViewZPT, self).setUp() self.t = ViewPageTemplateFile('test.pt') self.context = C1() def testNamespaceContextAvailable(self): context = self.context request = None namespace = self.t.pt_getContext(InstanceWithContext(context), request) self.failUnless(namespace['context'] is context) self.failUnless('views' in namespace) def testNamespaceHereNotAvailable(self): request = None self.assertRaises(AttributeError, self.t.pt_getContext, InstanceWithoutContext(), request) def testViewMapper(self): the_view = "This is the view" the_view_name = "some view name" def ViewMaker(*args, **kw): return the_view from zope.publisher.interfaces import IRequest gsm = getGlobalSiteManager() gsm.registerAdapter( ViewMaker, (I1, IRequest), Interface, the_view_name, event=False) class MyRequest(object): implements(IRequest) request = MyRequest() namespace = self.t.pt_getContext(InstanceWithContext(self.context), request) views = namespace['views'] self.failUnless(the_view is views[the_view_name]) def test_debug_flags(self): from zope.publisher.browser import TestRequest self.request = TestRequest() self.request.debug.sourceAnnotations = False self.assert_('test.pt' not in self.t(self)) self.request.debug.sourceAnnotations = True self.assert_('test.pt' in self.t(self)) t = ViewPageTemplateFile('testsimpleviewclass.pt') self.request.debug.showTAL = False self.assert_('metal:' not in t(self)) self.request.debug.showTAL = True self.assert_('metal:' in t(self)) def test_render_sets_content_type_unless_set(self): from zope.publisher.browser import TestRequest t = ViewPageTemplateFile('test.pt') self.request = TestRequest() response = self.request.response self.assert_(not response.getHeader('Content-Type')) t(self) self.assertEquals(response.getHeader('Content-Type'), 'text/html') self.request = TestRequest() response = self.request.response response.setHeader('Content-Type', 'application/x-test-junk') t(self) self.assertEquals(response.getHeader('Content-Type'), 'application/x-test-junk') class TestViewZPTContentType(unittest.TestCase): def testInitWithoutType(self): t = ViewPageTemplateFile('test.pt') t._cook_check() self.assertEquals(t.content_type, "text/html") t = ViewPageTemplateFile('testxml.pt') t._cook_check() self.assertEquals(t.content_type, "text/xml") def testInitWithType(self): t = ViewPageTemplateFile('test.pt', content_type="text/plain") t._cook_check() self.assertEquals(t.content_type, "text/plain") t = ViewPageTemplateFile('testxml.pt', content_type="text/plain") t._cook_check() self.assertEquals(t.content_type, "text/xml") def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestViewZPT)) suite.addTest(unittest.makeSuite(TestViewZPTContentType)) return suite if __name__ == '__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/testxml.pt0000644000175000017500000000006212214017535026224 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/testfiles/0000755000175000017500000000000012214017535026163 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/testfiles/test3.pt0000644000175000017500000000010012214017535027561 0ustar arnauarnau

test

zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/testfiles/test.pt0000644000175000017500000000004612214017535027507 0ustar arnauarnau

test

zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/tests/testfiles/test2.pt0000644000175000017500000000007612214017535027574 0ustar arnauarnau

test

zope2.13-2.13.21/source/zope.browserpage/src/zope/browserpage/metadirectives.py0000644000175000017500000001501512214017535026403 0ustar arnauarnau############################################################################# # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZCML directives for defining browser pages $Id: metadirectives.py 111996 2010-05-05 17:34:04Z tseaver $ """ from zope.interface import Interface from zope.configuration.fields import GlobalObject, GlobalInterface from zope.configuration.fields import Tokens, Path, PythonIdentifier, MessageID from zope.schema import TextLine, Id, Int, Bool from zope.security.zcml import Permission from zope.component.zcml import IBasicViewInformation try: from zope.browsermenu.field import MenuField except ImportError: # avoid hard dependency on zope.browsermenu MenuField = TextLine class IPagesDirective(IBasicViewInformation): """ Define multiple pages without repeating all of the parameters. The pages directive allows multiple page views to be defined without repeating the 'for', 'permission', 'class', 'layer', 'allowed_attributes', and 'allowed_interface' attributes. """ for_ = GlobalObject( title=u"The interface or class this view is for.", required=False ) permission = Permission( title=u"Permission", description=u"The permission needed to use the view.", required=True ) class IViewDirective(IPagesDirective): """ The view directive defines a view that has subpages. The pages provided by the defined view are accessed by first traversing to the view name and then traversing to the page name. """ for_ = GlobalInterface( title=u"The interface this view is for.", required=False ) name = TextLine( title=u"The name of the view.", description=u"The name shows up in URLs/paths. For example 'foo'.", required=False, default=u'', ) menu = MenuField( title=u"The browser menu to include the page (view) in.", description=u""" Many views are included in menus. It's convenient to name the menu in the page directive, rather than having to give a separate menuItem directive. 'zmi_views' is the menu most often used in the Zope management interface. This attribute will only work if zope.browsermenu is installed. """, required=False ) title = MessageID( title=u"The browser menu label for the page (view)", description=u""" This attribute must be supplied if a menu attribute is supplied. This attribute will only work if zope.browsermenu is installed. """, required=False ) provides = GlobalInterface( title=u"The interface this view provides.", description=u""" A view can provide an interface. This would be used for views that support other views.""", required=False, default=Interface, ) class IViewPageSubdirective(Interface): """ Subdirective to IViewDirective. """ name = TextLine( title=u"The name of the page (view)", description=u""" The name shows up in URLs/paths. For example 'foo' or 'foo.html'. This attribute is required unless you use the subdirective 'page' to create sub views. If you do not have sub pages, it is common to use an extension for the view name such as '.html'. If you do have sub pages and you want to provide a view name, you shouldn't use extensions.""", required=True ) attribute = PythonIdentifier( title=u"The name of the view attribute implementing the page.", description=u""" This refers to the attribute (method) on the view that is implementing a specific sub page.""", required=False ) template = Path( title=u"The name of a template that implements the page.", description=u""" Refers to a file containing a page template (should end in extension '.pt' or '.html').""", required=False ) class IViewDefaultPageSubdirective(Interface): """ Subdirective to IViewDirective. """ name = TextLine( title=u"The name of the page that is the default.", description=u""" The named page will be used as the default if no name is specified explicitly in the path. If no defaultPage directive is supplied, the default page will be the first page listed.""", required=True ) class IPagesPageSubdirective(IViewPageSubdirective): """ Subdirective to IPagesDirective """ menu = MenuField( title=u"The browser menu to include the page (view) in.", description=u""" Many views are included in menus. It's convenient to name the menu in the page directive, rather than having to give a separate menuItem directive. This attribute will only work if zope.browsermenu is installed. """, required=False ) title = MessageID( title=u"The browser menu label for the page (view)", description=u""" This attribute must be supplied if a menu attribute is supplied. This attribute will only work if zope.browsermenu is installed. """, required=False ) class IPageDirective(IPagesDirective, IPagesPageSubdirective): """ The page directive is used to create views that provide a single url or page. The page directive creates a new view class from a given template and/or class and registers it. """ class IExpressionTypeDirective(Interface): """Register a new TALES expression type""" name = TextLine( title=u"Name", description=u"""Name of the expression. This will also be used as the prefix in actual TALES expressions.""", required=True ) handler = GlobalObject( title=u"Handler", description=u"""Handler is class that implements zope.tales.interfaces.ITALESExpression.""", required=True ) zope2.13-2.13.21/source/zope.browserpage/src/zope.browserpage.egg-info/0000755000175000017500000000000012214017535024510 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserpage/src/zope.browserpage.egg-info/PKG-INFO0000644000175000017500000000527312214017535025614 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.browserpage Version: 3.12.2 Summary: ZCML directives for configuring browser views for Zope. Home-page: http://pypi.python.org/pypi/zope.browserpage/ Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides ZCML directives for configuring browser views. More specifically it defines the following ZCML directives: * browser:page * browser:pages * browser:view These directives also support menu item registration for pages, when ``zope.browsermenu`` package is installed. Otherwise, they simply ignore the menu attribute. ======= CHANGES ======= 3.12.2 (2010-05-24) =================== - Fixed unit tests broken under Python 2.4 by the switch to the standard library ``doctest`` module. 3.12.1 (2010-04-30) =================== - Prefer the standard library's ``doctest`` module to the one from ``zope.testing``. 3.12.0 (2010-04-26) =================== - Move the implementation of ``tales:expressiontype`` here from ``zope.app.pagetemplate``. 3.11.0 (2009-12-22) =================== - Move the named template implementation here from ``zope.app.pagetemplate``. 3.10.1 (2009-12-22) =================== - Depend on the ``untrustedpython`` extra of ``zope.security``, since we import from ``zope.pagetemplate.engine``. 3.10.0 (2009-12-22) =================== - Remove the dependency on ``zope.app.pagetemplate`` by moving ``viewpagetemplatefile``, ``simpleviewclass`` and ``metaconfigure.registerType`` into this package. 3.9.0 (2009-08-27) ================== - Initial release. This package was split off from ``zope.app.publisher``. Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.browserpage/src/zope.browserpage.egg-info/dependency_links.txt0000644000175000017500000000000112214017535030556 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserpage/src/zope.browserpage.egg-info/requires.txt0000644000175000017500000000034112214017535027106 0ustar arnauarnausetuptools zope.pagetemplate zope.component>=3.7 zope.configuration zope.interface zope.publisher>=3.8 zope.schema zope.security [untrustedpython] zope.traversing [test] zope.testing zope.browsermenu [menu] zope.browsermenuzope2.13-2.13.21/source/zope.browserpage/src/zope.browserpage.egg-info/namespace_packages.txt0000644000175000017500000000000512214017535031036 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.browserpage/src/zope.browserpage.egg-info/top_level.txt0000644000175000017500000000000512214017535027235 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.browserpage/src/zope.browserpage.egg-info/SOURCES.txt0000644000175000017500000000267512214017535026406 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.browserpage.egg-info/PKG-INFO src/zope.browserpage.egg-info/SOURCES.txt src/zope.browserpage.egg-info/dependency_links.txt src/zope.browserpage.egg-info/namespace_packages.txt src/zope.browserpage.egg-info/not-zip-safe src/zope.browserpage.egg-info/requires.txt src/zope.browserpage.egg-info/top_level.txt src/zope/browserpage/__init__.py src/zope/browserpage/configure.zcml src/zope/browserpage/meta.zcml src/zope/browserpage/metaconfigure.py src/zope/browserpage/metadirectives.py src/zope/browserpage/namedtemplate.py src/zope/browserpage/namedtemplate.txt src/zope/browserpage/simpleviewclass.py src/zope/browserpage/viewpagetemplatefile.py src/zope/browserpage/tests/__init__.py src/zope/browserpage/tests/namedtemplate.pt src/zope/browserpage/tests/sample.py src/zope/browserpage/tests/simpletestview.py src/zope/browserpage/tests/test.pt src/zope/browserpage/tests/test_boundpagetemplate.py src/zope/browserpage/tests/test_expressiontype.py src/zope/browserpage/tests/test_namedtemplate.py src/zope/browserpage/tests/test_page.py src/zope/browserpage/tests/test_simpleviewclass.py src/zope/browserpage/tests/test_viewzpt.py src/zope/browserpage/tests/testsimpleviewclass.pt src/zope/browserpage/tests/testxml.pt src/zope/browserpage/tests/testfiles/test.pt src/zope/browserpage/tests/testfiles/test2.pt src/zope/browserpage/tests/testfiles/test3.ptzope2.13-2.13.21/source/zope.browserpage/src/zope.browserpage.egg-info/not-zip-safe0000644000175000017500000000000112214017535026736 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/0000755000175000017500000000000012214017462014775 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/setup.py0000644000175000017500000000427112214017462016513 0ustar arnauarnauREADME = open("README.txt").read() NEWS = open("NEWS.txt").read() def alltests(): import os import sys import unittest # use the zope.testrunner machinery to find all the # test suites we've put under ourselves import zope.testrunner.find import zope.testrunner.options here = os.path.abspath(os.path.dirname(sys.argv[0])) args = sys.argv[:] defaults = ["--test-path", here] options = zope.testrunner.options.get_options(args, defaults) suites = list(zope.testrunner.find.find_suites(options)) return unittest.TestSuite(suites) options = dict( name="ZConfig", version="2.9.1", author="Fred L. Drake, Jr.", author_email="fred@zope.com", maintainer="Zope Foundation and Contributors", description="Structured Configuration Library", long_description=README + "\n\n" + NEWS, license="ZPL 2.1", url="http://www.zope.org/Members/fdrake/zconfig/", # List packages explicitly so we don't have to assume setuptools: packages=[ "ZConfig", "ZConfig.components", "ZConfig.components.basic", "ZConfig.components.basic.tests", "ZConfig.components.logger", "ZConfig.components.logger.tests", "ZConfig.tests", "ZConfig.tests.library", "ZConfig.tests.library.thing", "ZConfig.tests.library.widget", ], scripts=["scripts/zconfig", "scripts/zconfig_schema2html"], include_package_data=True, zip_safe=False, classifiers=[ "Intended Audience :: Developers", "Intended Audience :: System Administrators", "License :: OSI Approved :: Zope Public License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2.4", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", ], # Support for 'setup.py test' when setuptools is available: test_suite="__main__.alltests", tests_require=[ "zope.testrunner", ], ) try: from setuptools import setup except ImportError: from distutils.core import setup setup(**options) zope2.13-2.13.21/source/ZConfig/NEWS.txt0000644000175000017500000001521512214017462016316 0ustar arnauarnau========================== Change History for ZConfig ========================== ZConfig 2.9.1 (2012-02-11) -------------------------- - Make FileHandler.reopen thread safe. ZConfig 2.9.0 (2011-03-22) -------------------------- - Allow identical redefinition of ``%define`` names. - Added support for IPv6 addresses. ZConfig 2.8.0 (2010-04-13) -------------------------- - Fix relative path recognition. https://bugs.launchpad.net/zconfig/+bug/405687 - Added SMTP authentication support for email logger on Python 2.6. ZConfig 2.7.1 (2009-06-13) -------------------------- - Improved documentation - Fixed tests failures on windows. ZConfig 2.7.0 (2009-06-11) -------------------------- - Added a convenience function, ``ZConfig.configureLoggers(text)`` for configuring loggers. - Relaxed the requirement for a logger name in logger sections, allowing the logger section to be used for both root and non-root loggers. ZConfig 2.6.1 (2008-12-05) -------------------------- - Fixed support for schema descriptions that override descriptions from a base schema. If multiple base schema provide descriptions but the derived schema does not, the first base mentioned that provides a description wins. https://bugs.launchpad.net/zconfig/+bug/259475 - Fixed compatibility bug with Python 2.5.0. - No longer trigger deprecation warnings under Python 2.6. ZConfig 2.6.0 (2008-09-03) -------------------------- - Added support for file rotation by time by specifying when and interval, rather than max-size, for log files. - Removed dependency on setuptools from the setup.py. ZConfig 2.5.1 (2007-12-24) -------------------------- - Made it possible to run unit tests via 'python setup.py test' (requires setuptools on sys.path). - Added better error messages to test failure assertions. ZConfig 2.5 (2007-08-31) ------------------------ *A note on the version number:* Information discovered in the revision control system suggests that some past revision has been called "2.4", though it is not clear that any actual release was made with that version number. We're going to skip revision 2.4 entirely to avoid potential issues with anyone using something claiming to be ZConfig 2.4, and go straight to version 2.5. - Add support for importing schema components from ZIP archives (including eggs). - Added a 'formatter' configuration option in the logging handler sections to allow specifying a constructor for the formatter. - Documented the package: URL scheme that can be used in extending schema. - Added support for reopening all log files opened via configurations using the ZConfig.components.logger package. For Zope, this is usable via the ``zc.signalhandler`` package. ``zc.signalhandler`` is not required for ZConfig. - Added support for rotating log files internally by size. - Added a minimal implementation of schema-less parsing; this is mostly intended for applications that want to read several fragments of ZConfig configuration files and assemble a combined configuration. Used in some ``zc.buildout`` recipes. - Converted to using ``zc.buildout`` and the standard test runner from ``zope.testing``. - Added more tests. ZConfig 2.3.1 (2005-08-21) -------------------------- - Isolated some of the case-normalization code so it will at least be easier to override. This remains non-trivial. ZConfig 2.3 (2005-05-18) ------------------------ - Added "inet-binding-address" and "inet-connection-address" to the set of standard datatypes. These are similar to the "inet-address" type, but the default hostname is more sensible. The datatype used should reflect how the value will be used. - Alternate rotating logfile handler for Windows, to avoid platform limitations on renaming open files. Contributed by Sidnei da Silva. - For
and , if the name attribute is omitted, assume name="*", since this is what is used most often. ZConfig 2.2 (2004-04-21) ------------------------ - More documentation has been written. - Added a timedelta datatype function; the input is the same as for the time-interval datatype, but the resulting value is a datetime.timedelta object. - Make sure keys specified as attributes of the element are converted by the appropriate key type, and are re-checked for derived sections. - Refactored the ZConfig.components.logger schema components so that a schema can import just one of the "eventlog" or "logger" sections if desired. This can be helpful to avoid naming conflicts. - Added a reopen() method to the logger factories. - Always use an absolute pathname when opening a FileHandler. - A fix to the logger 'format' key to allow the %(process)d expansion variable that the logging package supports. - A new timedelta built-in datatype was added. Similar to time-interval except that it returns a datetime.timedelta object instead. ZConfig 2.1 (2004-04-12) ------------------------ - Removed compatibility with Python 2.1 and 2.2. - Schema components must really be in Python packages; the directory search has been modified to perform an import to locate the package rather than incorrectly implementing the search algorithm. - The default objects use for section values now provide a method getSectionAttributes(); this returns a list of all the attributes of the section object which store configuration-defined data (including information derived from the schema). - Default information can now be included in a schema for and by using . - More documentation has been added to discuss schema extension. - Support for a Unicode-free Python has been fixed. - Derived section types now inherit the datatype of the base type if no datatype is identified explicitly. - Derived section types can now override the keytype instead of always inheriting from their base type. - makes use of the current prefix if the package name begins witha dot. - Added two standard datatypes: dotted-name and dotted-suffix. - Added two standard schema components: ZConfig.components.basic and ZConfig.components.logger. ZConfig 2.0 (2003-10-27) ------------------------ - Configurations can import additional schema components using a new "%import" directive; this can be used to integrate 3rd-party components into an application. - Schemas may be extended using a new "extends" attribute on the element. - Better error messages when elements in a schema definition are improperly nested. - The "zconfig" script can now simply verify that a schema definition is valid, if that's all that's needed. ZConfig 1.0 (2003-03-25) ------------------------ - Initial release. zope2.13-2.13.21/source/ZConfig/PKG-INFO0000644000175000017500000003156212214017462016101 0ustar arnauarnauMetadata-Version: 1.0 Name: ZConfig Version: 2.9.1 Summary: Structured Configuration Library Home-page: http://www.zope.org/Members/fdrake/zconfig/ Author: Zope Foundation and Contributors Author-email: fred@zope.com License: ZPL 2.1 Description: ZConfig: Schema-driven configuration ==================================== ZConfig is a configuration library intended for general use. It supports a hierarchical schema-driven configuration model that allows a schema to specify data conversion routines written in Python. ZConfig's model is very different from the model supported by the ConfigParser module found in Python's standard library, and is more suitable to configuration-intensive applications. ZConfig schema are written in an XML-based language and are able to "import" schema components provided by Python packages. Since components are able to bind to conversion functions provided by Python code in the package (or elsewhere), configuration objects can be arbitrarily complex, with values that have been verified against arbitrary constraints. This makes it easy for applications to separate configuration support from configuration loading even with configuration data being defined and consumed by a wide range of separate packages. ZConfig is licensed under the Zope Public License, version 2.1. See the file LICENSE.txt in the distribution for the full license text. Reference documentation is available in the doc/ directory. One common use of ZConfig is to configure the Python logging framework. This is extremely simple to do as the following example demonstrates: >>> from ZConfig import configureLoggers >>> configureLoggers(''' ... ... level INFO ... ... PATH STDOUT ... format %(levelname)s %(name)s %(message)s ... ... ... ''') The above configures the root logger to output messages logged at INFO or above to the console, as we can see in the following example: >>> from logging import getLogger >>> logger = getLogger() >>> logger.info('An info message') INFO root An info message >>> logger.debug('A debug message') A more common configuration would see STDOUT replaced with a path to the file into which log entries would be written. For more information, see section 5.2 on the ZConfig documentation and the examples in ZConfig/components/logger/tests. Information on the latest released version of the ZConfig package is available at http://www.zope.org/Members/fdrake/zconfig/ You may either create an RPM and install this, or install directly from the source distribution. There is a mailing list for discussions and questions about ZConfig; more information on the list is available at http://mail.zope.org/mailman/listinfo/zconfig/ Installing from the source distribution --------------------------------------- For a simple installation:: python setup.py install To install to a user's home-dir:: python setup.py install --home= To install to another prefix (for example, /usr/local):: python setup.py install --prefix=/usr/local If you need to force the python interpreter to (for example) python2:: python2 setup.py install For more information on installing packages, please refer to `Installing Python Modules `__. ========================== Change History for ZConfig ========================== ZConfig 2.9.1 (2012-02-11) -------------------------- - Make FileHandler.reopen thread safe. ZConfig 2.9.0 (2011-03-22) -------------------------- - Allow identical redefinition of ``%define`` names. - Added support for IPv6 addresses. ZConfig 2.8.0 (2010-04-13) -------------------------- - Fix relative path recognition. https://bugs.launchpad.net/zconfig/+bug/405687 - Added SMTP authentication support for email logger on Python 2.6. ZConfig 2.7.1 (2009-06-13) -------------------------- - Improved documentation - Fixed tests failures on windows. ZConfig 2.7.0 (2009-06-11) -------------------------- - Added a convenience function, ``ZConfig.configureLoggers(text)`` for configuring loggers. - Relaxed the requirement for a logger name in logger sections, allowing the logger section to be used for both root and non-root loggers. ZConfig 2.6.1 (2008-12-05) -------------------------- - Fixed support for schema descriptions that override descriptions from a base schema. If multiple base schema provide descriptions but the derived schema does not, the first base mentioned that provides a description wins. https://bugs.launchpad.net/zconfig/+bug/259475 - Fixed compatibility bug with Python 2.5.0. - No longer trigger deprecation warnings under Python 2.6. ZConfig 2.6.0 (2008-09-03) -------------------------- - Added support for file rotation by time by specifying when and interval, rather than max-size, for log files. - Removed dependency on setuptools from the setup.py. ZConfig 2.5.1 (2007-12-24) -------------------------- - Made it possible to run unit tests via 'python setup.py test' (requires setuptools on sys.path). - Added better error messages to test failure assertions. ZConfig 2.5 (2007-08-31) ------------------------ *A note on the version number:* Information discovered in the revision control system suggests that some past revision has been called "2.4", though it is not clear that any actual release was made with that version number. We're going to skip revision 2.4 entirely to avoid potential issues with anyone using something claiming to be ZConfig 2.4, and go straight to version 2.5. - Add support for importing schema components from ZIP archives (including eggs). - Added a 'formatter' configuration option in the logging handler sections to allow specifying a constructor for the formatter. - Documented the package: URL scheme that can be used in extending schema. - Added support for reopening all log files opened via configurations using the ZConfig.components.logger package. For Zope, this is usable via the ``zc.signalhandler`` package. ``zc.signalhandler`` is not required for ZConfig. - Added support for rotating log files internally by size. - Added a minimal implementation of schema-less parsing; this is mostly intended for applications that want to read several fragments of ZConfig configuration files and assemble a combined configuration. Used in some ``zc.buildout`` recipes. - Converted to using ``zc.buildout`` and the standard test runner from ``zope.testing``. - Added more tests. ZConfig 2.3.1 (2005-08-21) -------------------------- - Isolated some of the case-normalization code so it will at least be easier to override. This remains non-trivial. ZConfig 2.3 (2005-05-18) ------------------------ - Added "inet-binding-address" and "inet-connection-address" to the set of standard datatypes. These are similar to the "inet-address" type, but the default hostname is more sensible. The datatype used should reflect how the value will be used. - Alternate rotating logfile handler for Windows, to avoid platform limitations on renaming open files. Contributed by Sidnei da Silva. - For
and , if the name attribute is omitted, assume name="*", since this is what is used most often. ZConfig 2.2 (2004-04-21) ------------------------ - More documentation has been written. - Added a timedelta datatype function; the input is the same as for the time-interval datatype, but the resulting value is a datetime.timedelta object. - Make sure keys specified as attributes of the element are converted by the appropriate key type, and are re-checked for derived sections. - Refactored the ZConfig.components.logger schema components so that a schema can import just one of the "eventlog" or "logger" sections if desired. This can be helpful to avoid naming conflicts. - Added a reopen() method to the logger factories. - Always use an absolute pathname when opening a FileHandler. - A fix to the logger 'format' key to allow the %(process)d expansion variable that the logging package supports. - A new timedelta built-in datatype was added. Similar to time-interval except that it returns a datetime.timedelta object instead. ZConfig 2.1 (2004-04-12) ------------------------ - Removed compatibility with Python 2.1 and 2.2. - Schema components must really be in Python packages; the directory search has been modified to perform an import to locate the package rather than incorrectly implementing the search algorithm. - The default objects use for section values now provide a method getSectionAttributes(); this returns a list of all the attributes of the section object which store configuration-defined data (including information derived from the schema). - Default information can now be included in a schema for and by using . - More documentation has been added to discuss schema extension. - Support for a Unicode-free Python has been fixed. - Derived section types now inherit the datatype of the base type if no datatype is identified explicitly. - Derived section types can now override the keytype instead of always inheriting from their base type. - makes use of the current prefix if the package name begins witha dot. - Added two standard datatypes: dotted-name and dotted-suffix. - Added two standard schema components: ZConfig.components.basic and ZConfig.components.logger. ZConfig 2.0 (2003-10-27) ------------------------ - Configurations can import additional schema components using a new "%import" directive; this can be used to integrate 3rd-party components into an application. - Schemas may be extended using a new "extends" attribute on the element. - Better error messages when elements in a schema definition are improperly nested. - The "zconfig" script can now simply verify that a schema definition is valid, if that's all that's needed. ZConfig 1.0 (2003-03-25) ------------------------ - Initial release. Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 zope2.13-2.13.21/source/ZConfig/scripts/0000755000175000017500000000000012214017462016464 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/scripts/zconfig_schema2html0000644000175000017500000000675512214017462022352 0ustar arnauarnau#!/usr/bin/env python ############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## __version__ = '$Revision: 1.3 $'[11:-2] import sys import os import cgi if __name__ == "__main__": here = os.path.dirname(os.path.realpath(__file__)) swhome = os.path.dirname(here) for parts in [("src",), ("lib", "python"), ("Lib", "site-packages")]: d = os.path.join(swhome, *(parts + ("ZConfig",))) if os.path.isdir(d): d = os.path.join(swhome, *parts) sys.path.insert(0, d) break import ZConfig.loader from ZConfig.info import * def esc(x): return cgi.escape(str(x)) def dt(x): tn = type(x).__name__ if tn == 'instance': return '%s %s'%(tn, x.__class__.__module__ + '.' + x.__class__.__name__) elif tn == 'class': return '%s %s'%(tn, x.__module__ + '.' + x.__name__) else: return '%s %s'%(tn, x.__name__) class explain: done = [] def __call__(self, st): if st.name in self.done: return self.done.append(st.name) if st.description: print st.description for sub in st.getsubtypenames(): print '
' printContents(None, st.getsubtype(sub)) print '
' explain = explain() def printSchema(schema): print '
' for child in schema: printContents(*child) print '
' def printContents(name, info): if isinstance(info, SectionType): print '
', info.name, ' (%s)
'%dt(info.datatype) print '
' if info.description: print info.description print '
' for sub in info: printContents(*sub) print '
' elif isinstance(info, SectionInfo): st = info.sectiontype if st.isabstract(): print '
', st.name, '', info.name, '
' print '
' if info.description: print info.description explain(st) print '
' else: print '
', info.attribute, info.name, '' print '(%s)
'%dt(info.datatype) print '
' for sub in info.sectiontype: printContents(*sub) print '
' else: print '
',info.name, '', '(%s)'%dt(info.datatype) default = info.getdefault() if isinstance(default, ValueInfo): print '(default: %r)'%esc(default.value) elif default is not None: print '(default: %r)'%esc(default) if info.metadefault: print '(metadefault: %s)' % info.metadefault print '
' if info.description: print '
',info.description,'
' schema = ZConfig.loader.loadSchemaFile(sys.argv[1]) print ''' ''' printSchema(schema) print '' # vim: set filetype=python ts=4 sw=4 et si zope2.13-2.13.21/source/ZConfig/scripts/zconfig0000644000175000017500000000540512214017462020052 0ustar arnauarnau#!/usr/bin/env python ############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """zconfig: Script to check validity of a configuration file. Usage: zconfig [options] [file...] Options: -h --help Print this help text. -s file --schema file Use the schema in 'file' to validate the configuration; this must be specified. Each file named on the command line is checked for syntactical errors and schema conformance. The schema must be specified. If no files are specified and standard input is not a TTY, standard in is treated as a configuration file. Specifying a schema and no configuration files causes the schema to be checked. """ import optparse import sys import os if __name__ == "__main__": here = os.path.dirname(os.path.realpath(__file__)) swhome = os.path.dirname(here) for parts in [("src",), ("lib", "python"), ("Lib", "site-packages")]: d = os.path.join(swhome, *(parts + ("ZConfig",))) if os.path.isdir(d): d = os.path.join(swhome, *parts) sys.path.insert(0, d) break import ZConfig def main(): optparser = optparse.OptionParser( usage="usage: %prog [-s FILE] [file ...]") optparser.add_option( "-s", "--schema", dest="schema", help="use the schema in FILE (can be a URL)", metavar="FILE") options, args = optparser.parse_args() if not options.schema: print >>sys.stderr, "No schema specified, but is required." usage(sys.stderr) return 2 schema = ZConfig.loadSchema(options.schema) if not args: if sys.stdin.isatty(): # just checking the schema return 0 else: # stdin is a pipe args = ["-"] errors = 0 for fn in args: try: if fn == "-": ZConfig.loadConfigFile(schema, sys.stdin) else: ZConfig.loadConfig(schema, fn) except ZConfig.ConfigurationError, e: print >>sys.stderr, str(e) errors += 1 if errors: return 1 else: return 0 def usage(fp): print >>fp, __doc__ if __name__ == "__main__": sys.exit(main()) zope2.13-2.13.21/source/ZConfig/ZConfig.egg-info/0000755000175000017500000000000012214017462020026 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig.egg-info/PKG-INFO0000644000175000017500000003156212214017462021132 0ustar arnauarnauMetadata-Version: 1.0 Name: ZConfig Version: 2.9.1 Summary: Structured Configuration Library Home-page: http://www.zope.org/Members/fdrake/zconfig/ Author: Zope Foundation and Contributors Author-email: fred@zope.com License: ZPL 2.1 Description: ZConfig: Schema-driven configuration ==================================== ZConfig is a configuration library intended for general use. It supports a hierarchical schema-driven configuration model that allows a schema to specify data conversion routines written in Python. ZConfig's model is very different from the model supported by the ConfigParser module found in Python's standard library, and is more suitable to configuration-intensive applications. ZConfig schema are written in an XML-based language and are able to "import" schema components provided by Python packages. Since components are able to bind to conversion functions provided by Python code in the package (or elsewhere), configuration objects can be arbitrarily complex, with values that have been verified against arbitrary constraints. This makes it easy for applications to separate configuration support from configuration loading even with configuration data being defined and consumed by a wide range of separate packages. ZConfig is licensed under the Zope Public License, version 2.1. See the file LICENSE.txt in the distribution for the full license text. Reference documentation is available in the doc/ directory. One common use of ZConfig is to configure the Python logging framework. This is extremely simple to do as the following example demonstrates: >>> from ZConfig import configureLoggers >>> configureLoggers(''' ... ... level INFO ... ... PATH STDOUT ... format %(levelname)s %(name)s %(message)s ... ... ... ''') The above configures the root logger to output messages logged at INFO or above to the console, as we can see in the following example: >>> from logging import getLogger >>> logger = getLogger() >>> logger.info('An info message') INFO root An info message >>> logger.debug('A debug message') A more common configuration would see STDOUT replaced with a path to the file into which log entries would be written. For more information, see section 5.2 on the ZConfig documentation and the examples in ZConfig/components/logger/tests. Information on the latest released version of the ZConfig package is available at http://www.zope.org/Members/fdrake/zconfig/ You may either create an RPM and install this, or install directly from the source distribution. There is a mailing list for discussions and questions about ZConfig; more information on the list is available at http://mail.zope.org/mailman/listinfo/zconfig/ Installing from the source distribution --------------------------------------- For a simple installation:: python setup.py install To install to a user's home-dir:: python setup.py install --home= To install to another prefix (for example, /usr/local):: python setup.py install --prefix=/usr/local If you need to force the python interpreter to (for example) python2:: python2 setup.py install For more information on installing packages, please refer to `Installing Python Modules `__. ========================== Change History for ZConfig ========================== ZConfig 2.9.1 (2012-02-11) -------------------------- - Make FileHandler.reopen thread safe. ZConfig 2.9.0 (2011-03-22) -------------------------- - Allow identical redefinition of ``%define`` names. - Added support for IPv6 addresses. ZConfig 2.8.0 (2010-04-13) -------------------------- - Fix relative path recognition. https://bugs.launchpad.net/zconfig/+bug/405687 - Added SMTP authentication support for email logger on Python 2.6. ZConfig 2.7.1 (2009-06-13) -------------------------- - Improved documentation - Fixed tests failures on windows. ZConfig 2.7.0 (2009-06-11) -------------------------- - Added a convenience function, ``ZConfig.configureLoggers(text)`` for configuring loggers. - Relaxed the requirement for a logger name in logger sections, allowing the logger section to be used for both root and non-root loggers. ZConfig 2.6.1 (2008-12-05) -------------------------- - Fixed support for schema descriptions that override descriptions from a base schema. If multiple base schema provide descriptions but the derived schema does not, the first base mentioned that provides a description wins. https://bugs.launchpad.net/zconfig/+bug/259475 - Fixed compatibility bug with Python 2.5.0. - No longer trigger deprecation warnings under Python 2.6. ZConfig 2.6.0 (2008-09-03) -------------------------- - Added support for file rotation by time by specifying when and interval, rather than max-size, for log files. - Removed dependency on setuptools from the setup.py. ZConfig 2.5.1 (2007-12-24) -------------------------- - Made it possible to run unit tests via 'python setup.py test' (requires setuptools on sys.path). - Added better error messages to test failure assertions. ZConfig 2.5 (2007-08-31) ------------------------ *A note on the version number:* Information discovered in the revision control system suggests that some past revision has been called "2.4", though it is not clear that any actual release was made with that version number. We're going to skip revision 2.4 entirely to avoid potential issues with anyone using something claiming to be ZConfig 2.4, and go straight to version 2.5. - Add support for importing schema components from ZIP archives (including eggs). - Added a 'formatter' configuration option in the logging handler sections to allow specifying a constructor for the formatter. - Documented the package: URL scheme that can be used in extending schema. - Added support for reopening all log files opened via configurations using the ZConfig.components.logger package. For Zope, this is usable via the ``zc.signalhandler`` package. ``zc.signalhandler`` is not required for ZConfig. - Added support for rotating log files internally by size. - Added a minimal implementation of schema-less parsing; this is mostly intended for applications that want to read several fragments of ZConfig configuration files and assemble a combined configuration. Used in some ``zc.buildout`` recipes. - Converted to using ``zc.buildout`` and the standard test runner from ``zope.testing``. - Added more tests. ZConfig 2.3.1 (2005-08-21) -------------------------- - Isolated some of the case-normalization code so it will at least be easier to override. This remains non-trivial. ZConfig 2.3 (2005-05-18) ------------------------ - Added "inet-binding-address" and "inet-connection-address" to the set of standard datatypes. These are similar to the "inet-address" type, but the default hostname is more sensible. The datatype used should reflect how the value will be used. - Alternate rotating logfile handler for Windows, to avoid platform limitations on renaming open files. Contributed by Sidnei da Silva. - For
and , if the name attribute is omitted, assume name="*", since this is what is used most often. ZConfig 2.2 (2004-04-21) ------------------------ - More documentation has been written. - Added a timedelta datatype function; the input is the same as for the time-interval datatype, but the resulting value is a datetime.timedelta object. - Make sure keys specified as attributes of the element are converted by the appropriate key type, and are re-checked for derived sections. - Refactored the ZConfig.components.logger schema components so that a schema can import just one of the "eventlog" or "logger" sections if desired. This can be helpful to avoid naming conflicts. - Added a reopen() method to the logger factories. - Always use an absolute pathname when opening a FileHandler. - A fix to the logger 'format' key to allow the %(process)d expansion variable that the logging package supports. - A new timedelta built-in datatype was added. Similar to time-interval except that it returns a datetime.timedelta object instead. ZConfig 2.1 (2004-04-12) ------------------------ - Removed compatibility with Python 2.1 and 2.2. - Schema components must really be in Python packages; the directory search has been modified to perform an import to locate the package rather than incorrectly implementing the search algorithm. - The default objects use for section values now provide a method getSectionAttributes(); this returns a list of all the attributes of the section object which store configuration-defined data (including information derived from the schema). - Default information can now be included in a schema for and by using . - More documentation has been added to discuss schema extension. - Support for a Unicode-free Python has been fixed. - Derived section types now inherit the datatype of the base type if no datatype is identified explicitly. - Derived section types can now override the keytype instead of always inheriting from their base type. - makes use of the current prefix if the package name begins witha dot. - Added two standard datatypes: dotted-name and dotted-suffix. - Added two standard schema components: ZConfig.components.basic and ZConfig.components.logger. ZConfig 2.0 (2003-10-27) ------------------------ - Configurations can import additional schema components using a new "%import" directive; this can be used to integrate 3rd-party components into an application. - Schemas may be extended using a new "extends" attribute on the element. - Better error messages when elements in a schema definition are improperly nested. - The "zconfig" script can now simply verify that a schema definition is valid, if that's all that's needed. ZConfig 1.0 (2003-03-25) ------------------------ - Initial release. Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 zope2.13-2.13.21/source/ZConfig/ZConfig.egg-info/dependency_links.txt0000644000175000017500000000000112214017462024074 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig.egg-info/top_level.txt0000644000175000017500000000001012214017462022547 0ustar arnauarnauZConfig zope2.13-2.13.21/source/ZConfig/ZConfig.egg-info/SOURCES.txt0000644000175000017500000000545712214017462021725 0ustar arnauarnauCOPYRIGHT.txt LICENSE.txt NEWS.txt README.txt bootstrap.py buildout.cfg setup.cfg setup.py ZConfig/__init__.py ZConfig/cfgparser.py ZConfig/cmdline.py ZConfig/datatypes.py ZConfig/info.py ZConfig/loader.py ZConfig/matcher.py ZConfig/schema.py ZConfig/schemaless.py ZConfig/schemaless.txt ZConfig/substitution.py ZConfig/url.py ZConfig.egg-info/PKG-INFO ZConfig.egg-info/SOURCES.txt ZConfig.egg-info/dependency_links.txt ZConfig.egg-info/not-zip-safe ZConfig.egg-info/top_level.txt ZConfig/components/__init__.py ZConfig/components/basic/__init__.py ZConfig/components/basic/component.xml ZConfig/components/basic/mapping.py ZConfig/components/basic/mapping.xml ZConfig/components/basic/tests/__init__.py ZConfig/components/basic/tests/test_mapping.py ZConfig/components/logger/__init__.py ZConfig/components/logger/abstract.xml ZConfig/components/logger/base-logger.xml ZConfig/components/logger/component.xml ZConfig/components/logger/datatypes.py ZConfig/components/logger/eventlog.xml ZConfig/components/logger/factory.py ZConfig/components/logger/handlers.py ZConfig/components/logger/handlers.xml ZConfig/components/logger/logger.py ZConfig/components/logger/logger.xml ZConfig/components/logger/loghandler.py ZConfig/components/logger/tests/__init__.py ZConfig/components/logger/tests/test_logger.py ZConfig/tests/__init__.py ZConfig/tests/foosample.zip ZConfig/tests/support.py ZConfig/tests/test_cfgimports.py ZConfig/tests/test_cmdline.py ZConfig/tests/test_config.py ZConfig/tests/test_cookbook.py ZConfig/tests/test_datatypes.py ZConfig/tests/test_loader.py ZConfig/tests/test_readme.py ZConfig/tests/test_schema.py ZConfig/tests/test_schemaless.py ZConfig/tests/test_subst.py ZConfig/tests/input/base-datatype1.xml ZConfig/tests/input/base-datatype2.xml ZConfig/tests/input/base-keytype1.xml ZConfig/tests/input/base-keytype2.xml ZConfig/tests/input/base.xml ZConfig/tests/input/include.conf ZConfig/tests/input/inner.conf ZConfig/tests/input/library.xml ZConfig/tests/input/logger.xml ZConfig/tests/input/outer.conf ZConfig/tests/input/simple.conf ZConfig/tests/input/simple.xml ZConfig/tests/input/simplesections.conf ZConfig/tests/input/simplesections.xml ZConfig/tests/library/README.txt ZConfig/tests/library/__init__.py ZConfig/tests/library/thing/__init__.py ZConfig/tests/library/thing/component.xml ZConfig/tests/library/thing/extras/extras.xml ZConfig/tests/library/widget/__init__.py ZConfig/tests/library/widget/component.xml ZConfig/tests/library/widget/extra.xml ZConfig/tests/zipsource/README.txt ZConfig/tests/zipsource/foo/__init__.py ZConfig/tests/zipsource/foo/sample/__init__.py ZConfig/tests/zipsource/foo/sample/component.xml ZConfig/tests/zipsource/foo/sample/datatypes.py doc/Makefile doc/README.txt doc/schema.dtd doc/xmlmarkup.perl doc/xmlmarkup.sty doc/zconfig.pdf doc/zconfig.tex scripts/zconfig scripts/zconfig_schema2htmlzope2.13-2.13.21/source/ZConfig/ZConfig.egg-info/not-zip-safe0000644000175000017500000000000112214017462022254 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/pip-egg-info/0000755000175000017500000000000012214017462017256 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/pip-egg-info/ZConfig.egg-info/0000755000175000017500000000000012214017462022307 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/pip-egg-info/ZConfig.egg-info/PKG-INFO0000644000175000017500000003156212214017462023413 0ustar arnauarnauMetadata-Version: 1.1 Name: ZConfig Version: 2.9.1 Summary: Structured Configuration Library Home-page: http://www.zope.org/Members/fdrake/zconfig/ Author: Zope Foundation and Contributors Author-email: fred@zope.com License: ZPL 2.1 Description: ZConfig: Schema-driven configuration ==================================== ZConfig is a configuration library intended for general use. It supports a hierarchical schema-driven configuration model that allows a schema to specify data conversion routines written in Python. ZConfig's model is very different from the model supported by the ConfigParser module found in Python's standard library, and is more suitable to configuration-intensive applications. ZConfig schema are written in an XML-based language and are able to "import" schema components provided by Python packages. Since components are able to bind to conversion functions provided by Python code in the package (or elsewhere), configuration objects can be arbitrarily complex, with values that have been verified against arbitrary constraints. This makes it easy for applications to separate configuration support from configuration loading even with configuration data being defined and consumed by a wide range of separate packages. ZConfig is licensed under the Zope Public License, version 2.1. See the file LICENSE.txt in the distribution for the full license text. Reference documentation is available in the doc/ directory. One common use of ZConfig is to configure the Python logging framework. This is extremely simple to do as the following example demonstrates: >>> from ZConfig import configureLoggers >>> configureLoggers(''' ... ... level INFO ... ... PATH STDOUT ... format %(levelname)s %(name)s %(message)s ... ... ... ''') The above configures the root logger to output messages logged at INFO or above to the console, as we can see in the following example: >>> from logging import getLogger >>> logger = getLogger() >>> logger.info('An info message') INFO root An info message >>> logger.debug('A debug message') A more common configuration would see STDOUT replaced with a path to the file into which log entries would be written. For more information, see section 5.2 on the ZConfig documentation and the examples in ZConfig/components/logger/tests. Information on the latest released version of the ZConfig package is available at http://www.zope.org/Members/fdrake/zconfig/ You may either create an RPM and install this, or install directly from the source distribution. There is a mailing list for discussions and questions about ZConfig; more information on the list is available at http://mail.zope.org/mailman/listinfo/zconfig/ Installing from the source distribution --------------------------------------- For a simple installation:: python setup.py install To install to a user's home-dir:: python setup.py install --home= To install to another prefix (for example, /usr/local):: python setup.py install --prefix=/usr/local If you need to force the python interpreter to (for example) python2:: python2 setup.py install For more information on installing packages, please refer to `Installing Python Modules `__. ========================== Change History for ZConfig ========================== ZConfig 2.9.1 (2012-02-11) -------------------------- - Make FileHandler.reopen thread safe. ZConfig 2.9.0 (2011-03-22) -------------------------- - Allow identical redefinition of ``%define`` names. - Added support for IPv6 addresses. ZConfig 2.8.0 (2010-04-13) -------------------------- - Fix relative path recognition. https://bugs.launchpad.net/zconfig/+bug/405687 - Added SMTP authentication support for email logger on Python 2.6. ZConfig 2.7.1 (2009-06-13) -------------------------- - Improved documentation - Fixed tests failures on windows. ZConfig 2.7.0 (2009-06-11) -------------------------- - Added a convenience function, ``ZConfig.configureLoggers(text)`` for configuring loggers. - Relaxed the requirement for a logger name in logger sections, allowing the logger section to be used for both root and non-root loggers. ZConfig 2.6.1 (2008-12-05) -------------------------- - Fixed support for schema descriptions that override descriptions from a base schema. If multiple base schema provide descriptions but the derived schema does not, the first base mentioned that provides a description wins. https://bugs.launchpad.net/zconfig/+bug/259475 - Fixed compatibility bug with Python 2.5.0. - No longer trigger deprecation warnings under Python 2.6. ZConfig 2.6.0 (2008-09-03) -------------------------- - Added support for file rotation by time by specifying when and interval, rather than max-size, for log files. - Removed dependency on setuptools from the setup.py. ZConfig 2.5.1 (2007-12-24) -------------------------- - Made it possible to run unit tests via 'python setup.py test' (requires setuptools on sys.path). - Added better error messages to test failure assertions. ZConfig 2.5 (2007-08-31) ------------------------ *A note on the version number:* Information discovered in the revision control system suggests that some past revision has been called "2.4", though it is not clear that any actual release was made with that version number. We're going to skip revision 2.4 entirely to avoid potential issues with anyone using something claiming to be ZConfig 2.4, and go straight to version 2.5. - Add support for importing schema components from ZIP archives (including eggs). - Added a 'formatter' configuration option in the logging handler sections to allow specifying a constructor for the formatter. - Documented the package: URL scheme that can be used in extending schema. - Added support for reopening all log files opened via configurations using the ZConfig.components.logger package. For Zope, this is usable via the ``zc.signalhandler`` package. ``zc.signalhandler`` is not required for ZConfig. - Added support for rotating log files internally by size. - Added a minimal implementation of schema-less parsing; this is mostly intended for applications that want to read several fragments of ZConfig configuration files and assemble a combined configuration. Used in some ``zc.buildout`` recipes. - Converted to using ``zc.buildout`` and the standard test runner from ``zope.testing``. - Added more tests. ZConfig 2.3.1 (2005-08-21) -------------------------- - Isolated some of the case-normalization code so it will at least be easier to override. This remains non-trivial. ZConfig 2.3 (2005-05-18) ------------------------ - Added "inet-binding-address" and "inet-connection-address" to the set of standard datatypes. These are similar to the "inet-address" type, but the default hostname is more sensible. The datatype used should reflect how the value will be used. - Alternate rotating logfile handler for Windows, to avoid platform limitations on renaming open files. Contributed by Sidnei da Silva. - For
and , if the name attribute is omitted, assume name="*", since this is what is used most often. ZConfig 2.2 (2004-04-21) ------------------------ - More documentation has been written. - Added a timedelta datatype function; the input is the same as for the time-interval datatype, but the resulting value is a datetime.timedelta object. - Make sure keys specified as attributes of the element are converted by the appropriate key type, and are re-checked for derived sections. - Refactored the ZConfig.components.logger schema components so that a schema can import just one of the "eventlog" or "logger" sections if desired. This can be helpful to avoid naming conflicts. - Added a reopen() method to the logger factories. - Always use an absolute pathname when opening a FileHandler. - A fix to the logger 'format' key to allow the %(process)d expansion variable that the logging package supports. - A new timedelta built-in datatype was added. Similar to time-interval except that it returns a datetime.timedelta object instead. ZConfig 2.1 (2004-04-12) ------------------------ - Removed compatibility with Python 2.1 and 2.2. - Schema components must really be in Python packages; the directory search has been modified to perform an import to locate the package rather than incorrectly implementing the search algorithm. - The default objects use for section values now provide a method getSectionAttributes(); this returns a list of all the attributes of the section object which store configuration-defined data (including information derived from the schema). - Default information can now be included in a schema for and by using . - More documentation has been added to discuss schema extension. - Support for a Unicode-free Python has been fixed. - Derived section types now inherit the datatype of the base type if no datatype is identified explicitly. - Derived section types can now override the keytype instead of always inheriting from their base type. - makes use of the current prefix if the package name begins witha dot. - Added two standard datatypes: dotted-name and dotted-suffix. - Added two standard schema components: ZConfig.components.basic and ZConfig.components.logger. ZConfig 2.0 (2003-10-27) ------------------------ - Configurations can import additional schema components using a new "%import" directive; this can be used to integrate 3rd-party components into an application. - Schemas may be extended using a new "extends" attribute on the element. - Better error messages when elements in a schema definition are improperly nested. - The "zconfig" script can now simply verify that a schema definition is valid, if that's all that's needed. ZConfig 1.0 (2003-03-25) ------------------------ - Initial release. Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Zope Public License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 zope2.13-2.13.21/source/ZConfig/pip-egg-info/ZConfig.egg-info/dependency_links.txt0000644000175000017500000000000112214017462026355 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/pip-egg-info/ZConfig.egg-info/top_level.txt0000644000175000017500000000001012214017462025030 0ustar arnauarnauZConfig zope2.13-2.13.21/source/ZConfig/pip-egg-info/ZConfig.egg-info/SOURCES.txt0000644000175000017500000000270612214017462024200 0ustar arnauarnauREADME.txt setup.cfg ZConfig/__init__.py ZConfig/cfgparser.py ZConfig/cmdline.py ZConfig/datatypes.py ZConfig/info.py ZConfig/loader.py ZConfig/matcher.py ZConfig/schema.py ZConfig/schemaless.py ZConfig/substitution.py ZConfig/url.py ZConfig/components/__init__.py ZConfig/components/basic/__init__.py ZConfig/components/basic/mapping.py ZConfig/components/basic/tests/__init__.py ZConfig/components/basic/tests/test_mapping.py ZConfig/components/logger/__init__.py ZConfig/components/logger/datatypes.py ZConfig/components/logger/factory.py ZConfig/components/logger/handlers.py ZConfig/components/logger/logger.py ZConfig/components/logger/loghandler.py ZConfig/components/logger/tests/__init__.py ZConfig/components/logger/tests/test_logger.py ZConfig/tests/__init__.py ZConfig/tests/support.py ZConfig/tests/test_cfgimports.py ZConfig/tests/test_cmdline.py ZConfig/tests/test_config.py ZConfig/tests/test_cookbook.py ZConfig/tests/test_datatypes.py ZConfig/tests/test_loader.py ZConfig/tests/test_readme.py ZConfig/tests/test_schema.py ZConfig/tests/test_schemaless.py ZConfig/tests/test_subst.py ZConfig/tests/library/__init__.py ZConfig/tests/library/thing/__init__.py ZConfig/tests/library/widget/__init__.py pip-egg-info/ZConfig.egg-info/PKG-INFO pip-egg-info/ZConfig.egg-info/SOURCES.txt pip-egg-info/ZConfig.egg-info/dependency_links.txt pip-egg-info/ZConfig.egg-info/not-zip-safe pip-egg-info/ZConfig.egg-info/top_level.txt scripts/zconfig scripts/zconfig_schema2htmlzope2.13-2.13.21/source/ZConfig/pip-egg-info/ZConfig.egg-info/not-zip-safe0000644000175000017500000000000112214017462024535 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/0000755000175000017500000000000012214017462016334 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/schema.py0000644000175000017500000005201512214017462020151 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Parser for ZConfig schemas.""" import os import xml.sax import ZConfig from ZConfig import info from ZConfig import url def parseResource(resource, loader): parser = SchemaParser(loader, resource.url) xml.sax.parse(resource.file, parser) return parser._schema def parseComponent(resource, loader, schema): parser = ComponentParser(loader, resource.url, schema) xml.sax.parse(resource.file, parser) def _srepr(ob): if isinstance(ob, type(u'')): # drop the leading "u" from a unicode repr return `ob`[1:] else: return `ob` class BaseParser(xml.sax.ContentHandler): _cdata_tags = "description", "metadefault", "example", "default" _handled_tags = ("import", "abstracttype", "sectiontype", "key", "multikey", "section", "multisection") _allowed_parents = { "description": ["key", "section", "multikey", "multisection", "sectiontype", "abstracttype", "schema", "component"], "example": ["key", "section", "multikey", "multisection"], "metadefault": ["key", "section", "multikey", "multisection"], "default": ["key", "multikey"], "import": ["schema", "component"], "abstracttype": ["schema", "component"], "sectiontype": ["schema", "component"], "key": ["schema", "sectiontype"], "multikey": ["schema", "sectiontype"], "section": ["schema", "sectiontype"], "multisection": ["schema", "sectiontype"], } def __init__(self, loader, url): self._registry = loader.registry self._loader = loader self._basic_key = self._registry.get("basic-key") self._identifier = self._registry.get("identifier") self._cdata = None self._locator = None self._prefixes = [] self._schema = None self._stack = [] self._url = url self._elem_stack = [] # SAX 2 ContentHandler methods def setDocumentLocator(self, locator): self._locator = locator def startElement(self, name, attrs): attrs = dict(attrs) if self._elem_stack: parent = self._elem_stack[-1] if not self._allowed_parents.has_key(name): self.error("Unknown tag " + name) if parent not in self._allowed_parents[name]: self.error("%s elements may not be nested in %s elements" % (_srepr(name), _srepr(parent))) elif name != self._top_level: self.error("Unknown document type " + name) self._elem_stack.append(name) if name == self._top_level: if self._schema is not None: self.error("schema element improperly nested") getattr(self, "start_" + name)(attrs) elif name in self._handled_tags: if self._schema is None: self.error(name + " element outside of schema") getattr(self, "start_" + name)(attrs) elif name in self._cdata_tags: if self._schema is None: self.error(name + " element outside of schema") if self._cdata is not None: self.error(name + " element improperly nested") self._cdata = [] self._position = None self._attrs = attrs def characters(self, data): if self._cdata is not None: if self._position is None: self._position = self.get_position() self._cdata.append(data) elif data.strip(): self.error("unexpected non-blank character data: " + `data.strip()`) def endElement(self, name): del self._elem_stack[-1] if name in self._handled_tags: getattr(self, "end_" + name)() else: data = ''.join(self._cdata).strip() self._cdata = None getattr(self, "characters_" + name)(data) def endDocument(self): if self._schema is None: self.error("no %s found" % self._top_level) # helper methods def get_position(self): if self._locator: return (self._locator.getLineNumber(), self._locator.getColumnNumber(), (self._locator.getSystemId() or self._url)) else: return None, None, self._url def get_handler(self, attrs): v = attrs.get("handler") if v is None: return v else: return self.basic_key(v) def push_prefix(self, attrs): name = attrs.get("prefix") if name: if self._prefixes: convert = self._registry.get("dotted-suffix") else: convert = self._registry.get("dotted-name") try: name = convert(name) except ValueError, err: self.error("not a valid prefix: %s (%s)" % (_srepr(name), str(err))) if name[0] == ".": prefix = self._prefixes[-1] + name else: prefix = name elif self._prefixes: prefix = self._prefixes[-1] else: prefix = '' self._prefixes.append(prefix) def pop_prefix(self): del self._prefixes[-1] def get_classname(self, name): name = str(name) if name.startswith("."): return self._prefixes[-1] + name else: return name def get_datatype(self, attrs, attrkey, default, base=None): if attrs.has_key(attrkey): dtname = self.get_classname(attrs[attrkey]) else: convert = getattr(base, attrkey, None) if convert is not None: return convert dtname = default try: return self._registry.get(dtname) except ValueError, e: self.error(e[0]) def get_sect_typeinfo(self, attrs, base=None): keytype = self.get_datatype(attrs, "keytype", "basic-key", base) valuetype = self.get_datatype(attrs, "valuetype", "string") datatype = self.get_datatype(attrs, "datatype", "null", base) return keytype, valuetype, datatype def get_required(self, attrs): if attrs.has_key("required"): v = attrs["required"] if v == "yes": return True elif v == "no": return False self.error("value for 'required' must be 'yes' or 'no'") else: return False def get_ordinality(self, attrs): # used by start_multi*() min, max = 0, info.Unbounded if self.get_required(attrs): min = 1 return min, max def get_sectiontype(self, attrs): type = attrs.get("type") if not type: self.error("section must specify type") return self._schema.gettype(type) def get_key_info(self, attrs, element): any, name, attribute = self.get_name_info(attrs, element) if any == '*': self.error(element + " may not specify '*' for name") if not name and any != '+': self.error(element + " name may not be omitted or empty") datatype = self.get_datatype(attrs, "datatype", "string") handler = self.get_handler(attrs) return name or any, datatype, handler, attribute def get_name_info(self, attrs, element, default=None): name = attrs.get("name", default) if not name: self.error(element + " name must be specified and non-empty") aname = attrs.get("attribute") if aname: aname = self.identifier(aname) if aname.startswith("getSection"): # reserved; used for SectionValue methods to get meta-info self.error("attribute names may not start with 'getSection'") if name in ("*", "+"): if not aname: self.error( "container attribute must be specified and non-empty" " when using '*' or '+' for a section name") return name, None, aname else: # run the keytype converter to make sure this is a valid key try: name = self._stack[-1].keytype(name) except ValueError, e: self.error("could not convert key name to keytype: " + str(e)) if not aname: aname = self.basic_key(name) aname = self.identifier(aname.replace('-', '_')) return None, name, aname # schema loading logic def characters_default(self, data): key = self._attrs.get("key") self._stack[-1].adddefault(data, self._position, key) def characters_description(self, data): if self._stack[-1].description is not None: self.error( "at most one may be used for each element") self._stack[-1].description = data def characters_example(self, data): self._stack[-1].example = data def characters_metadefault(self, data): self._stack[-1].metadefault = data def start_import(self, attrs): src = attrs.get("src", "").strip() pkg = attrs.get("package", "").strip() file = attrs.get("file", "").strip() if not (src or pkg): self.error("import must specify either src or package") if src and pkg: self.error("import may only specify one of src or package") if src: if file: self.error("import may not specify file and src") src = url.urljoin(self._url, src) src, fragment = url.urldefrag(src) if fragment: self.error("import src many not include" " a fragment identifier") schema = self._loader.loadURL(src) for n in schema.gettypenames(): self._schema.addtype(schema.gettype(n)) else: if os.path.dirname(file): self.error("file may not include a directory part") pkg = self.get_classname(pkg) src = self._loader.schemaComponentSource(pkg, file) if not self._schema.hasComponent(src): self._schema.addComponent(src) self.loadComponent(src) def loadComponent(self, src): r = self._loader.openResource(src) parser = ComponentParser(self._loader, src, self._schema) try: xml.sax.parse(r.file, parser) finally: r.close() def end_import(self): pass def start_sectiontype(self, attrs): name = attrs.get("name") if not name: self.error("sectiontype name must not be omitted or empty") name = self.basic_key(name) self.push_prefix(attrs) if attrs.has_key("extends"): basename = self.basic_key(attrs["extends"]) base = self._schema.gettype(basename) if base.isabstract(): self.error("sectiontype cannot extend an abstract type") keytype, valuetype, datatype = self.get_sect_typeinfo(attrs, base) sectinfo = self._schema.deriveSectionType( base, name, keytype, valuetype, datatype) else: keytype, valuetype, datatype = self.get_sect_typeinfo(attrs) sectinfo = self._schema.createSectionType( name, keytype, valuetype, datatype) if attrs.has_key("implements"): ifname = self.basic_key(attrs["implements"]) interface = self._schema.gettype(ifname) if not interface.isabstract(): self.error( "type specified by implements is not an abstracttype") interface.addsubtype(sectinfo) self._stack.append(sectinfo) def end_sectiontype(self): self.pop_prefix() self._stack.pop() def start_section(self, attrs): sectiontype = self.get_sectiontype(attrs) handler = self.get_handler(attrs) min = self.get_required(attrs) and 1 or 0 any, name, attribute = self.get_name_info(attrs, "section", "*") if any and not attribute: self.error( "attribute must be specified if section name is '*' or '+'") section = info.SectionInfo(any or name, sectiontype, min, 1, handler, attribute) self._stack[-1].addsection(name, section) self._stack.append(section) def end_section(self): self._stack.pop() def start_multisection(self, attrs): sectiontype = self.get_sectiontype(attrs) min, max = self.get_ordinality(attrs) any, name, attribute = self.get_name_info(attrs, "multisection", "*") if any not in ("*", "+"): self.error("multisection must specify '*' or '+' for the name") handler = self.get_handler(attrs) section = info.SectionInfo(any or name, sectiontype, min, max, handler, attribute) self._stack[-1].addsection(name, section) self._stack.append(section) def end_multisection(self): self._stack.pop() def start_abstracttype(self, attrs): name = attrs.get("name") if not name: self.error("abstracttype name must not be omitted or empty") name = self.basic_key(name) abstype = info.AbstractType(name) self._schema.addtype(abstype) self._stack.append(abstype) def end_abstracttype(self): self._stack.pop() def start_key(self, attrs): name, datatype, handler, attribute = self.get_key_info(attrs, "key") min = self.get_required(attrs) and 1 or 0 key = info.KeyInfo(name, datatype, min, handler, attribute) if attrs.has_key("default"): if min: self.error("required key cannot have a default value") key.adddefault(str(attrs["default"]).strip(), self.get_position()) if name != "+": key.finish() self._stack[-1].addkey(key) self._stack.append(key) def end_key(self): key = self._stack.pop() if key.name == "+": key.computedefault(self._stack[-1].keytype) key.finish() def start_multikey(self, attrs): if attrs.has_key("default"): self.error("default values for multikey must be given using" " 'default' elements") name, datatype, handler, attribute = self.get_key_info(attrs, "multikey") min, max = self.get_ordinality(attrs) key = info.MultiKeyInfo(name, datatype, min, max, handler, attribute) self._stack[-1].addkey(key) self._stack.append(key) def end_multikey(self): multikey = self._stack.pop() if multikey.name == "+": multikey.computedefault(self._stack[-1].keytype) multikey.finish() # datatype conversion wrappers def basic_key(self, s): try: return self._basic_key(s) except ValueError, e: self.error(e[0]) def identifier(self, s): try: return self._identifier(s) except ValueError, e: self.error(e[0]) # exception setup helpers def initerror(self, e): if self._locator is not None: e.colno = self._locator.getColumnNumber() e.lineno = self._locator.getLineNumber() e.url = self._locator.getSystemId() return e def error(self, message): raise self.initerror(ZConfig.SchemaError(message)) class SchemaParser(BaseParser): # needed by startElement() and endElement() _handled_tags = BaseParser._handled_tags + ("schema",) _top_level = "schema" def __init__(self, loader, url, extending_parser=None): BaseParser.__init__(self, loader, url) self._extending_parser = extending_parser self._base_keytypes = [] self._base_datatypes = [] self._descriptions = [] def start_schema(self, attrs): self.push_prefix(attrs) handler = self.get_handler(attrs) keytype, valuetype, datatype = self.get_sect_typeinfo(attrs) if self._extending_parser is None: # We're not being inherited, so we need to create the schema self._schema = info.SchemaType(keytype, valuetype, datatype, handler, self._url, self._registry) else: # Parse into the extending ("subclass") parser's schema self._schema = self._extending_parser._schema self._stack = [self._schema] if attrs.has_key("extends"): sources = attrs["extends"].split() sources.reverse() for src in sources: src = url.urljoin(self._url, src) src, fragment = url.urldefrag(src) if fragment: self.error("schema extends many not include" " a fragment identifier") self.extendSchema(src) # Inherit keytype from bases, if unspecified and not conflicting if self._base_keytypes and not attrs.has_key("keytype"): keytype = self._base_keytypes[0] for kt in self._base_keytypes[1:]: if kt is not keytype: self.error("base schemas have conflicting keytypes," " but no keytype was specified in the" " extending schema") # Inherit datatype from bases, if unspecified and not conflicting if self._base_datatypes and not attrs.has_key("datatype"): datatype = self._base_datatypes[0] for dt in self._base_datatypes[1:]: if dt is not datatype: self.error("base schemas have conflicting datatypes," " but no datatype was specified in the" " extending schema") # Reset the schema types to our own, while we parse the schema body self._schema.keytype = keytype self._schema.valuetype = valuetype self._schema.datatype = datatype # Update base key/datatypes for the "extending" parser if self._extending_parser is not None: self._extending_parser._base_keytypes.append(keytype) self._extending_parser._base_datatypes.append(datatype) def extendSchema(self, src): parser = SchemaParser(self._loader, src, self) r = self._loader.openResource(src) try: xml.sax.parse(r.file, parser) finally: r.close() def end_schema(self): del self._stack[-1] assert not self._stack self.pop_prefix() assert not self._prefixes schema = self._schema if self._extending_parser is None: # Top-level schema: if self._descriptions and not schema.description: # Use the last one, since the base schemas are processed in # reverse order. schema.description = self._descriptions[-1] elif schema.description: self._extending_parser._descriptions.append(schema.description) schema.description = None class ComponentParser(BaseParser): _handled_tags = BaseParser._handled_tags + ("component",) _top_level = "component" def __init__(self, loader, url, schema): BaseParser.__init__(self, loader, url) self._parent = schema def characters_description(self, data): if self._stack: self._stack[-1].description = data def start_key(self, attrs): self._check_not_toplevel("key") BaseParser.start_key(self, attrs) def start_multikey(self, attrs): self._check_not_toplevel("multikey") BaseParser.start_multikey(self, attrs) def start_section(self, attrs): self._check_not_toplevel("section") BaseParser.start_section(self, attrs) def start_multisection(self, attrs): self._check_not_toplevel("multisection") BaseParser.start_multisection(self, attrs) def start_component(self, attrs): self._schema = self._parent self.push_prefix(attrs) def end_component(self): self.pop_prefix() def _check_not_toplevel(self, what): if not self._stack: self.error("cannot define top-level %s in a schema %s" % (what, self._top_level)) zope2.13-2.13.21/source/ZConfig/ZConfig/schemaless.py0000644000175000017500000000614612214017462021044 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """\ Support for working with ZConfig data without a schema. """ __docformat__ = "reStructuredText" import ZConfig.cfgparser def loadConfigFile(file, url=None): c = Context() Parser(Resource(file, url), c).parse(c.top) return c.top class Resource: def __init__(self, file, url=''): self.file, self.url = file, url class Section(dict): imports = () def __init__(self, type='', name='', data=None, sections=None): dict.__init__(self) if data: self.update(data) self.sections = sections or [] self.type, self.name = type, name def addValue(self, key, value, *args): if key in self: self[key].append(value) else: self[key] = [value] def __str__(self, pre=''): result = [] if self.imports: for pkgname in self.imports: result.append('%import ' + pkgname) result.append('') if self.type: if self.name: start = '%s<%s %s>' % (pre, self.type, self.name) else: start = '%s<%s>' % (pre, self.type) result.append(start) pre += ' ' lst = list(self.items()) lst.sort() for name, values in lst: for value in values: result.append('%s%s %s' % (pre, name, value)) if self.sections and self: result.append('') for section in self.sections: result.append(section.__str__(pre)) if self.type: pre = pre[:-2] result.append('%s' % (pre, self.type)) result.append('') result = '\n'.join(result).rstrip() if not pre: result += '\n' return result class Context: def __init__(self): self.top = Section() self.sections = [] def startSection(self, container, type, name): newsec = Section(type, name) container.sections.append(newsec) return newsec def endSection(self, container, type, name, newsect): pass def importSchemaComponent(self, pkgname): if pkgname not in self.top.imports: self.top.imports += (pkgname, ) def includeConfiguration(self, section, newurl, defines): raise NotImplementedError('includes are not supported') class Parser(ZConfig.cfgparser.ZConfigParser): def handle_define(self, section, rest): raise NotImplementedError('defines are not supported') zope2.13-2.13.21/source/ZConfig/ZConfig/cmdline.py0000644000175000017500000001366112214017462020330 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Support for command-line provision of settings. This module provides an extension of the ConfigLoader class which adds a way to add configuration settings from an alternate source. Each setting is described by a string of the form:: some/path/to/key=value """ import ZConfig import ZConfig.loader import ZConfig.matcher class ExtendedConfigLoader(ZConfig.loader.ConfigLoader): def __init__(self, schema): ZConfig.loader.ConfigLoader.__init__(self, schema) self.clopts = [] # [(optpath, value, source-position), ...] def addOption(self, spec, pos=None): if pos is None: pos = "", -1, -1 if "=" not in spec: e = ZConfig.ConfigurationSyntaxError( "invalid configuration specifier", *pos) e.specifier = spec raise e # For now, just add it to the list; not clear that checking # against the schema at this point buys anything. opt, val = spec.split("=", 1) optpath = opt.split("/") if "" in optpath: # // is not allowed in option path e = ZConfig.ConfigurationSyntaxError( "'//' is not allowed in an option path", *pos) e.specifier = spec raise e self.clopts.append((optpath, val, pos)) def createSchemaMatcher(self): if self.clopts: sm = ExtendedSchemaMatcher(self.schema) sm.set_optionbag(self.cook()) else: sm = ZConfig.loader.ConfigLoader.createSchemaMatcher(self) return sm def cook(self): if self.clopts: return OptionBag(self.schema, self.schema, self.clopts) else: return None class OptionBag: def __init__(self, schema, sectiontype, options): self.sectiontype = sectiontype self.schema = schema self.keypairs = {} self.sectitems = [] self._basic_key = schema.registry.get("basic-key") for item in options: optpath, val, pos = item name = sectiontype.keytype(optpath[0]) if len(optpath) == 1: self.add_value(name, val, pos) else: self.sectitems.append(item) def basic_key(self, s, pos): try: return self._basic_key(s) except ValueError: raise ZConfig.ConfigurationSyntaxError( "could not convert basic-key value", *pos) def add_value(self, name, val, pos): if self.keypairs.has_key(name): L = self.keypairs[name] else: L = [] self.keypairs[name] = L L.append((val, pos)) def has_key(self, name): return self.keypairs.has_key(name) def get_key(self, name): """Return a list of (value, pos) items for the key 'name'. The returned list may be empty. """ L = self.keypairs.get(name) if L: del self.keypairs[name] return L else: return [] def keys(self): return self.keypairs.keys() def get_section_info(self, type, name): L = [] # what pertains to the child section R = [] # what we keep for item in self.sectitems: optpath, val, pos = item s = optpath[0] bk = self.basic_key(s, pos) if name and self._normalize_case(s) == name: L.append((optpath[1:], val, pos)) elif bk == type: L.append((optpath[1:], val, pos)) else: R.append(item) if L: self.sectitems[:] = R return OptionBag(self.schema, self.schema.gettype(type), L) else: return None def finish(self): if self.sectitems or self.keypairs: raise ZConfig.ConfigurationError( "not all command line options were consumed") def _normalize_case(self, string): return string.lower() class MatcherMixin: def set_optionbag(self, bag): self.optionbag = bag def addValue(self, key, value, position): try: realkey = self.type.keytype(key) except ValueError, e: raise ZConfig.DataConversionError(e, key, position) if self.optionbag.has_key(realkey): return ZConfig.matcher.BaseMatcher.addValue(self, key, value, position) def createChildMatcher(self, type, name): sm = ZConfig.matcher.BaseMatcher.createChildMatcher(self, type, name) bag = self.optionbag.get_section_info(type.name, name) if bag is not None: sm = ExtendedSectionMatcher( sm.info, sm.type, sm.name, sm.handlers) sm.set_optionbag(bag) return sm def finish_optionbag(self): for key in self.optionbag.keys(): for val, pos in self.optionbag.get_key(key): ZConfig.matcher.BaseMatcher.addValue(self, key, val, pos) self.optionbag.finish() class ExtendedSectionMatcher(MatcherMixin, ZConfig.matcher.SectionMatcher): def finish(self): self.finish_optionbag() return ZConfig.matcher.SectionMatcher.finish(self) class ExtendedSchemaMatcher(MatcherMixin, ZConfig.matcher.SchemaMatcher): def finish(self): self.finish_optionbag() return ZConfig.matcher.SchemaMatcher.finish(self) zope2.13-2.13.21/source/ZConfig/ZConfig/loader.py0000644000175000017500000002547412214017462020170 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Schema loader utility.""" import cStringIO import os.path import re import sys import urllib import urllib2 import ZConfig import ZConfig.cfgparser import ZConfig.datatypes import ZConfig.info import ZConfig.matcher import ZConfig.schema import ZConfig.url def loadSchema(url): return SchemaLoader().loadURL(url) def loadSchemaFile(file, url=None): return SchemaLoader().loadFile(file, url) def loadConfig(schema, url, overrides=()): return _get_config_loader(schema, overrides).loadURL(url) def loadConfigFile(schema, file, url=None, overrides=()): return _get_config_loader(schema, overrides).loadFile(file, url) def _get_config_loader(schema, overrides): if overrides: from ZConfig import cmdline loader = cmdline.ExtendedConfigLoader(schema) for opt in overrides: loader.addOption(opt) else: loader = ConfigLoader(schema) return loader class BaseLoader: def __init__(self): pass def createResource(self, file, url): return Resource(file, url) def loadURL(self, url): url = self.normalizeURL(url) r = self.openResource(url) try: return self.loadResource(r) finally: r.close() def loadFile(self, file, url=None): if not url: url = _url_from_file(file) r = self.createResource(file, url) try: return self.loadResource(r) finally: r.close() # utilities def loadResource(self, resource): raise NotImplementedError( "BaseLoader.loadResource() must be overridden by a subclass") def openResource(self, url): # ConfigurationError exceptions raised here should be # str()able to generate a message for an end user. # # XXX This should be replaced to use a local cache for remote # resources. The policy needs to support both re-retrieve on # change and provide the cached resource when the remote # resource is not accessible. url = str(url) if url.startswith("package:"): _, package, filename = url.split(":", 2) file = openPackageResource(package, filename) else: try: file = urllib2.urlopen(url) except urllib2.URLError, e: # urllib2.URLError has a particularly hostile str(), so we # generally don't want to pass it along to the user. self._raise_open_error(url, e.reason) except (IOError, OSError), e: # Python 2.1 raises a different error from Python 2.2+, # so we catch both to make sure we detect the situation. self._raise_open_error(url, str(e)) return self.createResource(file, url) def _raise_open_error(self, url, message): if url[:7].lower() == "file://": what = "file" ident = urllib.url2pathname(url[7:]) else: what = "URL" ident = url raise ZConfig.ConfigurationError( "error opening %s %s: %s" % (what, ident, message), url) def normalizeURL(self, url): if self.isPath(url): url = "file://" + urllib.pathname2url(os.path.abspath(url)) newurl, fragment = ZConfig.url.urldefrag(url) if fragment: raise ZConfig.ConfigurationError( "fragment identifiers are not supported", url) return newurl # from RFC 3986: # schema = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) _pathsep_rx = re.compile(r"[a-zA-Z][-+.a-zA-Z0-9]*:") def isPath(self, s): """Return True iff 's' should be handled as a filesystem path.""" if ":" in s: # XXX This assumes that one-character scheme identifiers # are always Windows drive letters; I don't know of any # one-character scheme identifiers. m = self._pathsep_rx.match(s) if m is None: return True # Does it look like a drive letter? return len(m.group(0)) == 2 else: return True def openPackageResource(package, path): __import__(package) pkg = sys.modules[package] try: loader = pkg.__loader__ except AttributeError: relpath = os.path.join(*path.split("/")) for dir in pkg.__path__: filename = os.path.join(dir, relpath) if os.path.exists(filename): break else: raise ZConfig.SchemaResourceError("schema component not found", filename=path, package=package, path=pkg.__path__) url = "file:" + urllib.pathname2url(filename) url = ZConfig.url.urlnormalize(url) return urllib2.urlopen(url) else: loadpath = os.path.join(os.path.dirname(pkg.__file__), path) return cStringIO.StringIO(loader.get_data(loadpath)) def _url_from_file(file): name = getattr(file, "name", None) if name and name[0] != "<" and name[-1] != ">": return "file://" + urllib.pathname2url(os.path.abspath(name)) else: return None class SchemaLoader(BaseLoader): def __init__(self, registry=None): if registry is None: registry = ZConfig.datatypes.Registry() BaseLoader.__init__(self) self.registry = registry self._cache = {} def loadResource(self, resource): if resource.url and self._cache.has_key(resource.url): schema = self._cache[resource.url] else: schema = ZConfig.schema.parseResource(resource, self) self._cache[resource.url] = schema return schema # schema parser support API def schemaComponentSource(self, package, file): parts = package.split(".") if not parts: raise ZConfig.SchemaError( "illegal schema component name: " + `package`) if "" in parts: # '' somewhere in the package spec; still illegal raise ZConfig.SchemaError( "illegal schema component name: " + `package`) file = file or "component.xml" try: __import__(package) except ImportError, e: raise ZConfig.SchemaResourceError( "could not load package %s: %s" % (package, str(e)), filename=file, package=package) pkg = sys.modules[package] if not hasattr(pkg, "__path__"): raise ZConfig.SchemaResourceError( "import name does not refer to a package", filename=file, package=package) return "package:%s:%s" % (package, file) class ConfigLoader(BaseLoader): def __init__(self, schema): if schema.isabstract(): raise ZConfig.SchemaError( "cannot check a configuration an abstract type") BaseLoader.__init__(self) self.schema = schema self._private_schema = False def loadResource(self, resource): sm = self.createSchemaMatcher() self._parse_resource(sm, resource) result = sm.finish(), CompositeHandler(sm.handlers, self.schema) return result def createSchemaMatcher(self): return ZConfig.matcher.SchemaMatcher(self.schema) # config parser support API def startSection(self, parent, type, name): t = self.schema.gettype(type) if t.isabstract(): raise ZConfig.ConfigurationError( "concrete sections cannot match abstract section types;" " found abstract type " + `type`) return parent.createChildMatcher(t, name) def endSection(self, parent, type, name, matcher): sectvalue = matcher.finish() parent.addSection(type, name, sectvalue) def importSchemaComponent(self, pkgname): schema = self.schema if not self._private_schema: # replace the schema with an extended schema on the first %import self._loader = SchemaLoader(self.schema.registry) schema = ZConfig.info.createDerivedSchema(self.schema) self._private_schema = True self.schema = schema url = self._loader.schemaComponentSource(pkgname, '') if schema.hasComponent(url): return resource = self.openResource(url) schema.addComponent(url) try: ZConfig.schema.parseComponent(resource, self._loader, schema) finally: resource.close() def includeConfiguration(self, section, url, defines): url = self.normalizeURL(url) r = self.openResource(url) try: self._parse_resource(section, r, defines) finally: r.close() # internal helper def _parse_resource(self, matcher, resource, defines=None): parser = ZConfig.cfgparser.ZConfigParser(resource, self, defines) parser.parse(matcher) class CompositeHandler: def __init__(self, handlers, schema): self._handlers = handlers self._convert = schema.registry.get("basic-key") def __call__(self, handlermap): d = {} for name, callback in handlermap.items(): n = self._convert(name) if d.has_key(n): raise ZConfig.ConfigurationError( "handler name not unique when converted to a basic-key: " + `name`) d[n] = callback L = [] for handler, value in self._handlers: if not d.has_key(handler): L.append(handler) if L: raise ZConfig.ConfigurationError( "undefined handlers: " + ", ".join(L)) for handler, value in self._handlers: f = d[handler] if f is not None: f(value) def __len__(self): return len(self._handlers) class Resource: def __init__(self, file, url): self.file = file self.url = url def close(self): if self.file is not None: self.file.close() self.file = None self.closed = True def __getattr__(self, name): return getattr(self.file, name) zope2.13-2.13.21/source/ZConfig/ZConfig/datatypes.py0000644000175000017500000003467312214017462020721 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Selection of standard datatypes for ZConfig.""" import os import re import sys import datetime try: unicode except NameError: have_unicode = False else: have_unicode = True class MemoizedConversion: """Conversion helper that caches the results of expensive conversions.""" def __init__(self, conversion): self._memo = {} self._conversion = conversion def __call__(self, value): try: return self._memo[value] except KeyError: v = self._conversion(value) self._memo[value] = v return v class RangeCheckedConversion: """Conversion helper that range checks another conversion.""" def __init__(self, conversion, min=None, max=None): self._min = min self._max = max self._conversion = conversion def __call__(self, value): v = self._conversion(value) if self._min is not None and v < self._min: raise ValueError("%s is below lower bound (%s)" % (`v`, `self._min`)) if self._max is not None and v > self._max: raise ValueError("%s is above upper bound (%s)" % (`v`, `self._max`)) return v class RegularExpressionConversion: reason = "value did not match regular expression" def __init__(self, regex): self._rx = re.compile(regex) def __call__(self, value): m = self._rx.match(value) if m and m.group() == value: return value else: raise ValueError("%s: %s" % (self.reason, repr(value))) def check_locale(value): import locale prev = locale.setlocale(locale.LC_ALL) try: try: locale.setlocale(locale.LC_ALL, value) finally: locale.setlocale(locale.LC_ALL, prev) except locale.Error: raise ValueError( 'The specified locale "%s" is not supported by your system.\n' 'See your operating system documentation for more\n' 'information on locale support.' % value) else: return value class BasicKeyConversion(RegularExpressionConversion): def __init__(self): RegularExpressionConversion.__init__(self, "[a-zA-Z][-._a-zA-Z0-9]*") def __call__(self, value): value = str(value) return RegularExpressionConversion.__call__(self, value).lower() class ASCIIConversion(RegularExpressionConversion): def __call__(self, value): value = RegularExpressionConversion.__call__(self, value) if have_unicode and isinstance(value, unicode): value = value.encode("ascii") return value _ident_re = "[_a-zA-Z][_a-zA-Z0-9]*" class IdentifierConversion(ASCIIConversion): reason = "not a valid Python identifier" def __init__(self): ASCIIConversion.__init__(self, _ident_re) class DottedNameConversion(ASCIIConversion): reason = "not a valid dotted name" def __init__(self): ASCIIConversion.__init__(self, r"%s(?:\.%s)*" % (_ident_re, _ident_re)) class DottedNameSuffixConversion(ASCIIConversion): reason = "not a valid dotted name or suffix" def __init__(self): ASCIIConversion.__init__(self, r"(?:%s)(?:\.%s)*|(?:\.%s)+" % (_ident_re, _ident_re, _ident_re)) def integer(value): try: return int(value) except ValueError: return long(value) except OverflowError: return long(value) def null_conversion(value): return value def asBoolean(s): """Convert a string value to a boolean value.""" ss = str(s).lower() if ss in ('yes', 'true', 'on'): return True elif ss in ('no', 'false', 'off'): return False else: raise ValueError("not a valid boolean value: " + repr(s)) def string_list(s): """Convert a string to a list of strings using .split().""" return s.split() port_number = RangeCheckedConversion(integer, min=1, max=0xffff).__call__ class InetAddress: def __init__(self, default_host): self.DEFAULT_HOST = default_host def __call__(self, s): # returns (host, port) tuple host = '' port = None if ":" in s: host, p = s.rsplit(":", 1) if host.startswith('[') and host.endswith(']'): # [IPv6]:port host = host[1:-1] elif ':' in host: # Unbracketed IPv6 address; # last part is not the port number host = s p = None if p: # else leave port at None port = port_number(p) host = host.lower() else: try: port = port_number(s) except ValueError: if len(s.split()) != 1: raise ValueError("not a valid host name: " + repr(s)) host = s.lower() if not host: host = self.DEFAULT_HOST return host, port if sys.platform[:3] == "win": DEFAULT_HOST = "localhost" else: DEFAULT_HOST = "" inet_address = InetAddress(DEFAULT_HOST) inet_connection_address = InetAddress("127.0.0.1") inet_binding_address = InetAddress("") class SocketAddress: # Parsing results in family and address # Family can be AF_UNIX (for addresses that are path names) # or AF_INET6 (for inet addresses with colons in them) # or AF_INET (for all other inet addresses); # An inet address is a (host, port) pair # Notice that no DNS lookup is performed, so if the host # is a DNS name, DNS lookup may end up with either IPv4 or # IPv6 addresses, or both def __init__(self, s): import socket if "/" in s or s.find(os.sep) >= 0: self.family = getattr(socket, "AF_UNIX", None) self.address = s else: self.family = socket.AF_INET self.address = self._parse_address(s) if ':' in self.address[0]: self.family = socket.AF_INET6 def _parse_address(self, s): return inet_address(s) class SocketBindingAddress(SocketAddress): def _parse_address(self, s): return inet_binding_address(s) class SocketConnectionAddress(SocketAddress): def _parse_address(self, s): return inet_connection_address(s) def float_conversion(v): if isinstance(v, basestring): if v.lower() in ["inf", "-inf", "nan"]: raise ValueError(`v` + " is not a portable float representation") return float(v) class IpaddrOrHostname(RegularExpressionConversion): def __init__(self): # IP address regex from the Perl Cookbook, Recipe 6.23 (revised ed.) # We allow underscores in hostnames although this is considered # illegal according to RFC1034. # Addition: IPv6 addresses are now also accepted expr = (r"(^(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." #ipaddr r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." #ipaddr cont'd r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." #ipaddr cont'd r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])$)" #ipaddr cont'd r"|([A-Za-z_][-A-Za-z0-9_.]*[-A-Za-z0-9_])" # or hostname r"|([0-9A-Fa-f:.]+:[0-9A-Fa-f:.]*)" # or superset of IPv6 addresses # (requiring at least one colon) ) RegularExpressionConversion.__init__(self, expr) def __call__(self, value): result = RegularExpressionConversion.__call__(self, value).lower() # Use C library to validate IPv6 addresses, in particular wrt. # number of colons and number of digits per group if ':' in result: import socket try: socket.inet_pton(socket.AF_INET6, result) except socket.error: raise ValueError('%r is not a valid IPv6 address' % value) return result def existing_directory(v): nv = os.path.expanduser(v) if os.path.isdir(nv): return nv raise ValueError('%s is not an existing directory' % v) def existing_path(v): nv = os.path.expanduser(v) if os.path.exists(nv): return nv raise ValueError('%s is not an existing path' % v) def existing_file(v): nv = os.path.expanduser(v) if os.path.exists(nv): return nv raise ValueError('%s is not an existing file' % v) def existing_dirpath(v): nv = os.path.expanduser(v) dir = os.path.dirname(nv) if not dir: # relative pathname with no directory component return nv if os.path.isdir(dir): return nv raise ValueError('The directory named as part of the path %s ' 'does not exist.' % v) class SuffixMultiplier: # d is a dictionary of suffixes to integer multipliers. If no suffixes # match, default is the multiplier. Matches are case insensitive. Return # values are in the fundamental unit. def __init__(self, d, default=1): self._d = d self._default = default # all keys must be the same size self._keysz = None for k in d.keys(): if self._keysz is None: self._keysz = len(k) else: assert self._keysz == len(k) def __call__(self, v): v = v.lower() for s, m in self._d.items(): if v[-self._keysz:] == s: return int(v[:-self._keysz]) * m return int(v) * self._default def timedelta(s): # Unlike the standard time-interval data type, which returns a float # number of seconds, this datatype takes a wider range of syntax and # returns a datetime.timedelta # # Accepts suffixes: # w - weeks # d - days # h - hours # m - minutes # s - seconds # # and all arguments may be integers or floats, positive or negative. # More than one time interval suffix value may appear on the line, but # they should all be separated by spaces, e.g.: # # sleep_time 4w 2d 7h 12m 0.00001s weeks = days = hours = minutes = seconds = 0 for part in s.split(): val = float(part[:-1]) suffix = part[-1] if suffix == 'w': weeks = val elif suffix == 'd': days = val elif suffix == 'h': hours = val elif suffix == 'm': minutes = val elif suffix == 's': seconds = val else: raise TypeError('bad part %s in %s' % (part, s)) return datetime.timedelta(weeks=weeks, days=days, hours=hours, minutes=minutes, seconds=seconds) stock_datatypes = { "boolean": asBoolean, "dotted-name": DottedNameConversion(), "dotted-suffix": DottedNameSuffixConversion(), "identifier": IdentifierConversion(), "integer": integer, "float": float_conversion, "string": str, "string-list": string_list, "null": null_conversion, "locale": MemoizedConversion(check_locale), "port-number": port_number, "basic-key": BasicKeyConversion(), "inet-address": inet_address, "inet-binding-address": inet_binding_address, "inet-connection-address": inet_connection_address, "socket-address": SocketAddress, "socket-binding-address": SocketBindingAddress, "socket-connection-address": SocketConnectionAddress, "ipaddr-or-hostname":IpaddrOrHostname(), "existing-directory":existing_directory, "existing-path": existing_path, "existing-file": existing_file, "existing-dirpath": existing_dirpath, "byte-size": SuffixMultiplier({'kb': 1024, 'mb': 1024*1024, 'gb': 1024*1024*1024L, }), "time-interval": SuffixMultiplier({'s': 1, 'm': 60, 'h': 60*60, 'd': 60*60*24, }), "timedelta": timedelta, } class Registry: def __init__(self, stock=None): if stock is None: stock = stock_datatypes.copy() self._stock = stock self._other = {} self._basic_key = None def get(self, name): if '.' not in name: if self._basic_key is None: self._basic_key = self._other.get("basic-key") if self._basic_key is None: self._basic_key = self._stock.get("basic-key") if self._basic_key is None: self._basic_key = stock_datatypes["basic-key"] name = self._basic_key(name) t = self._stock.get(name) if t is None: t = self._other.get(name) if t is None: t = self.search(name) return t def register(self, name, conversion): if self._stock.has_key(name): raise ValueError("datatype name conflicts with built-in type: " + `name`) if self._other.has_key(name): raise ValueError("datatype name already registered: " + `name`) self._other[name] = conversion def search(self, name): if not "." in name: raise ValueError("unloadable datatype name: " + `name`) components = name.split('.') start = components[0] g = {} package = __import__(start, g, g) modulenames = [start] for component in components[1:]: modulenames.append(component) try: package = getattr(package, component) except AttributeError: n = '.'.join(modulenames) package = __import__(n, g, g, component) self._other[name] = package return package zope2.13-2.13.21/source/ZConfig/ZConfig/__init__.py0000644000175000017500000001355312214017462020454 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Structured, schema-driven configuration library. ZConfig is a configuration library intended for general use. It supports a hierarchical schema-driven configuration model that allows a schema to specify data conversion routines written in Python. ZConfig's model is very different from the model supported by the ConfigParser module found in Python's standard library, and is more suitable to configuration-intensive applications. ZConfig schema are written in an XML-based language and are able to ``import`` schema components provided by Python packages. Since components are able to bind to conversion functions provided by Python code in the package (or elsewhere), configuration objects can be arbitrarily complex, with values that have been verified against arbitrary constraints. This makes it easy for applications to separate configuration support from configuration loading even with configuration data being defined and consumed by a wide range of separate packages. $Id: __init__.py,v 1.18 2004/04/15 20:33:32 fdrake Exp $ """ __docformat__ = "reStructuredText" version_info = (2, 3) __version__ = ".".join([str(n) for n in version_info]) from ZConfig.loader import loadConfig, loadConfigFile from ZConfig.loader import loadSchema, loadSchemaFile class ConfigurationError(Exception): """Base class for ZConfig exceptions.""" # The 'message' attribute was deprecated for BaseException with # Python 2.6; here we create descriptor properties to continue using it def __set_message(self, v): self.__dict__['message'] = v def __get_message(self): return self.__dict__['message'] def __del_message(self): del self.__dict__['message'] message = property(__get_message, __set_message, __del_message) def __init__(self, msg, url=None): self.message = msg self.url = url Exception.__init__(self, msg) def __str__(self): return self.message class _ParseError(ConfigurationError): def __init__(self, msg, url, lineno, colno=None): self.lineno = lineno self.colno = colno ConfigurationError.__init__(self, msg, url) def __str__(self): s = self.message if self.url: s += "\n(" elif (self.lineno, self.colno) != (None, None): s += " (" if self.lineno: s += "line %d" % self.lineno if self.colno is not None: s += ", column %d" % self.colno if self.url: s += " in %s)" % self.url else: s += ")" elif self.url: s += self.url + ")" return s class SchemaError(_ParseError): """Raised when there's an error in the schema itself.""" def __init__(self, msg, url=None, lineno=None, colno=None): _ParseError.__init__(self, msg, url, lineno, colno) class SchemaResourceError(SchemaError): """Raised when there's an error locating a resource required by the schema. """ def __init__(self, msg, url=None, lineno=None, colno=None, path=None, package=None, filename=None): self.filename = filename self.package = package if path is not None: path = path[:] self.path = path SchemaError.__init__(self, msg, url, lineno, colno) def __str__(self): s = SchemaError.__str__(self) if self.package is not None: s += "\n Package name: " + repr(self.package) if self.filename is not None: s += "\n File name: " + repr(self.filename) if self.package is not None: s += "\n Package path: " + repr(self.path) return s class ConfigurationSyntaxError(_ParseError): """Raised when there's a syntax error in a configuration file.""" class DataConversionError(ConfigurationError, ValueError): """Raised when a data type conversion function raises ValueError.""" def __init__(self, exception, value, position): ConfigurationError.__init__(self, str(exception)) self.exception = exception self.value = value self.lineno, self.colno, self.url = position def __str__(self): s = "%s (line %s" % (self.message, self.lineno) if self.colno is not None: s += ", %s" % self.colno if self.url: s += ", in %s)" % self.url else: s += ")" return s class SubstitutionSyntaxError(ConfigurationError): """Raised when interpolation source text contains syntactical errors.""" class SubstitutionReplacementError(ConfigurationSyntaxError, LookupError): """Raised when no replacement is available for a reference.""" def __init__(self, source, name, url=None, lineno=None): self.source = source self.name = name ConfigurationSyntaxError.__init__( self, "no replacement for " + `name`, url, lineno) def configureLoggers(text): """Configure one or more loggers from configuration text.""" import StringIO schema = loadSchemaFile(StringIO.StringIO(""" """)) for factory in loadConfigFile(schema, StringIO.StringIO(text))[0].loggers: factory() zope2.13-2.13.21/source/ZConfig/ZConfig/matcher.py0000644000175000017500000002475412214017462020345 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Utility that manages the binding of configuration data to a section.""" import ZConfig from ZConfig.info import ValueInfo class BaseMatcher: def __init__(self, info, type, handlers): self.info = info self.type = type self._values = {} for key, info in type: if info.name == "+" and not info.issection(): v = {} elif info.ismulti(): v = [] else: v = None assert info.attribute is not None self._values[info.attribute] = v self._sectionnames = {} if handlers is None: handlers = [] self.handlers = handlers def __repr__(self): clsname = self.__class__.__name__ extra = "type " + `self.type.name` return "<%s for %s>" % (clsname, extra) def addSection(self, type, name, sectvalue): if name: if self._sectionnames.has_key(name): raise ZConfig.ConfigurationError( "section names must not be re-used within the" " same container:" + `name`) self._sectionnames[name] = name ci = self.type.getsectioninfo(type, name) attr = ci.attribute v = self._values[attr] if ci.ismulti(): v.append(sectvalue) elif v is None: self._values[attr] = sectvalue else: raise ZConfig.ConfigurationError( "too many instances of %s section" % `ci.sectiontype.name`) def addValue(self, key, value, position): try: realkey = self.type.keytype(key) except ValueError, e: raise ZConfig.DataConversionError(e, key, position) arbkey_info = None for i in range(len(self.type)): k, ci = self.type[i] if k == realkey: break if ci.name == "+" and not ci.issection(): arbkey_info = k, ci else: if arbkey_info is None: raise ZConfig.ConfigurationError( `key` + " is not a known key name") k, ci = arbkey_info if ci.issection(): if ci.name: extra = " in %s sections" % `self.type.name` else: extra = "" raise ZConfig.ConfigurationError( "%s is not a valid key name%s" % (`key`, extra)) ismulti = ci.ismulti() attr = ci.attribute assert attr is not None v = self._values[attr] if v is None: if k == '+': v = {} elif ismulti: v = [] self._values[attr] = v elif not ismulti: if k != '+': raise ZConfig.ConfigurationError( `key` + " does not support multiple values") elif len(v) == ci.maxOccurs: raise ZConfig.ConfigurationError( "too many values for " + `name`) value = ValueInfo(value, position) if k == '+': if ismulti: if v.has_key(realkey): v[realkey].append(value) else: v[realkey] = [value] else: if v.has_key(realkey): raise ZConfig.ConfigurationError( "too many values for " + `key`) v[realkey] = value elif ismulti: v.append(value) else: self._values[attr] = value def createChildMatcher(self, type, name): ci = self.type.getsectioninfo(type.name, name) assert not ci.isabstract() if not ci.isAllowedName(name): raise ZConfig.ConfigurationError( "%s is not an allowed name for %s sections" % (`name`, `ci.sectiontype.name`)) return SectionMatcher(ci, type, name, self.handlers) def finish(self): """Check the constraints of the section and convert to an application object.""" values = self._values for key, ci in self.type: if key: key = repr(key) else: key = "section type " + `ci.sectiontype.name` assert ci.attribute is not None attr = ci.attribute v = values[attr] if ci.name == '+' and not ci.issection(): # v is a dict if ci.minOccurs > len(v): raise ZConfig.ConfigurationError( "no keys defined for the %s key/value map; at least %d" " must be specified" % (attr, ci.minOccurs)) if v is None and ci.minOccurs: default = ci.getdefault() if default is None: raise ZConfig.ConfigurationError( "no values for %s; %s required" % (key, ci.minOccurs)) else: v = values[attr] = default[:] if ci.ismulti(): if not v: default = ci.getdefault() if isinstance(default, dict): v.update(default) else: v[:] = default if len(v) < ci.minOccurs: raise ZConfig.ConfigurationError( "not enough values for %s; %d found, %d required" % (key, len(v), ci.minOccurs)) if v is None and not ci.issection(): if ci.ismulti(): v = ci.getdefault()[:] else: v = ci.getdefault() values[attr] = v return self.constuct() def constuct(self): values = self._values for name, ci in self.type: assert ci.attribute is not None attr = ci.attribute if ci.ismulti(): if ci.issection(): v = [] for s in values[attr]: if s is not None: st = s.getSectionDefinition() try: s = st.datatype(s) except ValueError, e: raise ZConfig.DataConversionError( e, s, (-1, -1, None)) v.append(s) elif ci.name == '+': v = values[attr] for key, val in v.items(): v[key] = [vi.convert(ci.datatype) for vi in val] else: v = [vi.convert(ci.datatype) for vi in values[attr]] elif ci.issection(): if values[attr] is not None: st = values[attr].getSectionDefinition() try: v = st.datatype(values[attr]) except ValueError, e: raise ZConfig.DataConversionError( e, values[attr], (-1, -1, None)) else: v = None elif name == '+': v = values[attr] if not v: for key, val in ci.getdefault().items(): v[key] = val.convert(ci.datatype) else: for key, val in v.items(): v[key] = val.convert(ci.datatype) else: v = values[attr] if v is not None: v = v.convert(ci.datatype) values[attr] = v if ci.handler is not None: self.handlers.append((ci.handler, v)) return self.createValue() def createValue(self): return SectionValue(self._values, None, self) class SectionMatcher(BaseMatcher): def __init__(self, info, type, name, handlers): if name or info.allowUnnamed(): self.name = name else: raise ZConfig.ConfigurationError( `type.name` + " sections may not be unnamed") BaseMatcher.__init__(self, info, type, handlers) def createValue(self): return SectionValue(self._values, self.name, self) class SchemaMatcher(BaseMatcher): def __init__(self, schema): BaseMatcher.__init__(self, schema, schema, []) def finish(self): # Since there's no outer container to call datatype() # for the schema, we convert on the way out. v = BaseMatcher.finish(self) v = self.type.datatype(v) if self.type.handler is not None: self.handlers.append((self.type.handler, v)) return v class SectionValue: """Generic 'bag-of-values' object for a section. Derived classes should always call the SectionValue constructor before attempting to modify self. """ def __init__(self, values, name, matcher): self.__dict__.update(values) self._name = name self._matcher = matcher self._attributes = tuple(values.keys()) def __repr__(self): if self._name: # probably unique for a given config file; more readable than id() name = `self._name` else: # identify uniquely name = "at %#x" % id(self) clsname = self.__class__.__name__ return "<%s for %s %s>" % (clsname, self._matcher.type.name, name) def __str__(self): l = [] attrnames = [s for s in self.__dict__.keys() if s[0] != "_"] attrnames.sort() for k in attrnames: v = getattr(self, k) l.append('%-40s: %s' % (k, v)) return '\n'.join(l) def getSectionName(self): return self._name def getSectionType(self): return self._matcher.type.name def getSectionDefinition(self): return self._matcher.type def getSectionMatcher(self): return self._matcher def getSectionAttributes(self): return self._attributes zope2.13-2.13.21/source/ZConfig/ZConfig/cfgparser.py0000644000175000017500000001514312214017462020666 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Configuration parser.""" import ZConfig import ZConfig.url from ZConfig.substitution import isname, substitute class ZConfigParser: __metaclass__ = type __slots__ = ('resource', 'context', 'lineno', 'stack', 'defines', 'file', 'url') def __init__(self, resource, context, defines=None): self.resource = resource self.context = context self.file = resource.file self.url = resource.url self.lineno = 0 self.stack = [] # [(type, name, prevmatcher), ...] if defines is None: defines = {} self.defines = defines def nextline(self): line = self.file.readline() if line: self.lineno += 1 return False, line.strip() else: return True, None def parse(self, section): done, line = self.nextline() while not done: if line[:1] in ("", "#"): # blank line or comment pass elif line[:2] == "": self.error("malformed section end") section = self.end_section(section, line[2:-1]) elif line[0] == "<": # section start if line[-1] != ">": self.error("malformed section start") section = self.start_section(section, line[1:-1]) elif line[0] == "%": self.handle_directive(section, line[1:]) else: self.handle_key_value(section, line) done, line = self.nextline() if self.stack: self.error("unclosed sections not allowed") def start_section(self, section, rest): isempty = rest[-1:] == "/" if isempty: rest = rest[:-1] text = rest.rstrip() # parse section start stuff here m = _section_start_rx.match(text) if not m: self.error("malformed section header") type, name = m.group('type', 'name') type = self._normalize_case(type) if name: name = self._normalize_case(name) try: newsect = self.context.startSection(section, type, name) except ZConfig.ConfigurationError, e: self.error(e[0]) if isempty: self.context.endSection(section, type, name, newsect) return section else: self.stack.append((type, name, section)) return newsect def end_section(self, section, rest): if not self.stack: self.error("unexpected section end") type = self._normalize_case(rest.rstrip()) opentype, name, prevsection = self.stack.pop() if type != opentype: self.error("unbalanced section end") try: self.context.endSection( prevsection, type, name, section) except ZConfig.ConfigurationError, e: self.error(e[0]) return prevsection def handle_key_value(self, section, rest): m = _keyvalue_rx.match(rest) if not m: self.error("malformed configuration data") key, value = m.group('key', 'value') if not value: value = '' else: value = self.replace(value) try: section.addValue(key, value, (self.lineno, None, self.url)) except ZConfig.ConfigurationError, e: self.error(e[0]) def handle_directive(self, section, rest): m = _keyvalue_rx.match(rest) if not m: self.error("missing or unrecognized directive") name, arg = m.group('key', 'value') if name not in ("define", "import", "include"): self.error("unknown directive: " + `name`) if not arg: self.error("missing argument to %%%s directive" % name) if name == "include": self.handle_include(section, arg) elif name == "define": self.handle_define(section, arg) elif name == "import": self.handle_import(section, arg) else: assert 0, "unexpected directive for " + `"%" + rest` def handle_import(self, section, rest): pkgname = self.replace(rest.strip()) self.context.importSchemaComponent(pkgname) def handle_include(self, section, rest): rest = self.replace(rest.strip()) newurl = ZConfig.url.urljoin(self.url, rest) self.context.includeConfiguration(section, newurl, self.defines) def handle_define(self, section, rest): parts = rest.split(None, 1) defname = self._normalize_case(parts[0]) defvalue = '' if len(parts) == 2: defvalue = parts[1] if self.defines.has_key(defname): if self.defines[defname] != defvalue: self.error("cannot redefine " + `defname`) if not isname(defname): self.error("not a substitution legal name: " + `defname`) self.defines[defname] = self.replace(defvalue) def replace(self, text): try: return substitute(text, self.defines) except ZConfig.SubstitutionReplacementError, e: e.lineno = self.lineno e.url = self.url raise def error(self, message): raise ZConfig.ConfigurationSyntaxError(message, self.url, self.lineno) def _normalize_case(self, string): # This method is factored out solely to allow subclasses to modify # the behavior of the parser. return string.lower() import re # _name_re does not allow "(" or ")" for historical reasons. Though # the restriction could be lifted, there seems no need to do so. _name_re = r"[^\s()]+" _keyvalue_rx = re.compile(r"(?P%s)\s*(?P[^\s].*)?$" % _name_re) _section_start_rx = re.compile(r"(?P%s)" r"(?:\s+(?P%s))?" r"$" % (_name_re, _name_re)) del re zope2.13-2.13.21/source/ZConfig/ZConfig/info.py0000644000175000017500000004146312214017462017651 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Objects that can describe a ZConfig schema.""" import copy import ZConfig class UnboundedThing: __metaclass__ = type __slots__ = () def __lt__(self, other): return False def __le__(self, other): return isinstance(other, self.__class__) def __gt__(self, other): return True def __ge__(self, other): return True def __eq__(self, other): return isinstance(other, self.__class__) def __ne__(self, other): return not isinstance(other, self.__class__) def __repr__(self): return "" Unbounded = UnboundedThing() class ValueInfo: __metaclass__ = type __slots__ = 'value', 'position' def __init__(self, value, position): self.value = value # position is (lineno, colno, url) self.position = position def convert(self, datatype): try: return datatype(self.value) except ValueError, e: raise ZConfig.DataConversionError(e, self.value, self.position) class BaseInfo: """Information about a single configuration key.""" description = None example = None metadefault = None def __init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute): if maxOccurs is not None and maxOccurs < 1: if maxOccurs < 1: raise ZConfig.SchemaError( "maxOccurs must be at least 1") if minOccurs is not None and minOccurs < maxOccurs: raise ZConfig.SchemaError( "minOccurs must be at least maxOccurs") self.name = name self.datatype = datatype self.minOccurs = minOccurs self.maxOccurs = maxOccurs self.handler = handler self.attribute = attribute def __repr__(self): clsname = self.__class__.__name__ return "<%s for %s>" % (clsname, `self.name`) def isabstract(self): return False def ismulti(self): return self.maxOccurs > 1 def issection(self): return False class BaseKeyInfo(BaseInfo): _rawdefaults = None def __init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute): assert minOccurs is not None BaseInfo.__init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute) self._finished = False def finish(self): if self._finished: raise ZConfig.SchemaError( "cannot finish KeyInfo more than once") self._finished = True def adddefault(self, value, position, key=None): if self._finished: raise ZConfig.SchemaError( "cannot add default values to finished KeyInfo") # Check that the name/keyed relationship is right: if self.name == "+" and key is None: raise ZConfig.SchemaError( "default values must be keyed for name='+'") elif self.name != "+" and key is not None: raise ZConfig.SchemaError( "unexpected key for default value") self.add_valueinfo(ValueInfo(value, position), key) def add_valueinfo(self, vi, key): """Actually add a ValueInfo to this key-info object. The appropriate value of None-ness of key has already been checked with regard to the name of the key, and has been found permissible to add. This method is a requirement for subclasses, and should not be called by client code. """ raise NotImplementedError( "add_valueinfo() must be implemented by subclasses of BaseKeyInfo") def prepare_raw_defaults(self): assert self.name == "+" if self._rawdefaults is None: self._rawdefaults = self._default self._default = {} class KeyInfo(BaseKeyInfo): _default = None def __init__(self, name, datatype, minOccurs, handler, attribute): BaseKeyInfo.__init__(self, name, datatype, minOccurs, 1, handler, attribute) if self.name == "+": self._default = {} def add_valueinfo(self, vi, key): if self.name == "+": if self._default.has_key(key): # not ideal: we're presenting the unconverted # version of the key raise ZConfig.SchemaError( "duplicate default value for key %s" % `key`) self._default[key] = vi elif self._default is not None: raise ZConfig.SchemaError( "cannot set more than one default to key with maxOccurs == 1") else: self._default = vi def computedefault(self, keytype): self.prepare_raw_defaults() for k, vi in self._rawdefaults.iteritems(): key = ValueInfo(k, vi.position).convert(keytype) self.add_valueinfo(vi, key) def getdefault(self): # Use copy.copy() to make sure we don't allow polution of # our internal data without having to worry about both the # list and dictionary cases: return copy.copy(self._default) class MultiKeyInfo(BaseKeyInfo): def __init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute): BaseKeyInfo.__init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute) if self.name == "+": self._default = {} else: self._default = [] def add_valueinfo(self, vi, key): if self.name == "+": # This is a keyed value, not a simple value: if key in self._default: self._default[key].append(vi) else: self._default[key] = [vi] else: self._default.append(vi) def computedefault(self, keytype): self.prepare_raw_defaults() for k, vlist in self._rawdefaults.iteritems(): key = ValueInfo(k, vlist[0].position).convert(keytype) for vi in vlist: self.add_valueinfo(vi, key) def getdefault(self): return copy.copy(self._default) class SectionInfo(BaseInfo): def __init__(self, name, sectiontype, minOccurs, maxOccurs, handler, attribute): # name - name of the section; one of '*', '+', or name1 # sectiontype - SectionType instance # minOccurs - minimum number of occurances of the section # maxOccurs - maximum number of occurances; if > 1, name # must be '*' or '+' # handler - handler name called when value(s) must take effect, # or None # attribute - name of the attribute on the SectionValue object if maxOccurs > 1: if name not in ('*', '+'): raise ZConfig.SchemaError( "sections which can occur more than once must" " use a name of '*' or '+'") if not attribute: raise ZConfig.SchemaError( "sections which can occur more than once must" " specify a target attribute name") if sectiontype.isabstract(): datatype = None else: datatype = sectiontype.datatype BaseInfo.__init__(self, name, datatype, minOccurs, maxOccurs, handler, attribute) self.sectiontype = sectiontype def __repr__(self): clsname = self.__class__.__name__ return "<%s for %s (%s)>" % ( clsname, self.sectiontype.name, `self.name`) def issection(self): return True def allowUnnamed(self): return self.name == "*" def isAllowedName(self, name): if name == "*" or name == "+": return False elif self.name == "+": return name and True or False elif self.name == "*": return True else: return name == self.name def getdefault(self): # sections cannot have defaults if self.maxOccurs > 1: return [] else: return None class AbstractType: __metaclass__ = type __slots__ = '_subtypes', 'name', 'description' def __init__(self, name): self._subtypes = {} self.name = name self.description = None def addsubtype(self, type): self._subtypes[type.name] = type def getsubtype(self, name): try: return self._subtypes[name] except KeyError: raise ZConfig.SchemaError("no sectiontype %s in abstracttype %s" % (`name`, `self.name`)) def hassubtype(self, name): """Return true iff this type has 'name' as a concrete manifestation.""" return name in self._subtypes.keys() def getsubtypenames(self): """Return the names of all concrete types as a sorted list.""" L = self._subtypes.keys() L.sort() return L def isabstract(self): return True class SectionType: def __init__(self, name, keytype, valuetype, datatype, registry, types): # name - name of the section, or '*' or '+' # datatype - type for the section itself # keytype - type for the keys themselves # valuetype - default type for key values self.name = name self.datatype = datatype self.keytype = keytype self.valuetype = valuetype self.handler = None self.description = None self.registry = registry self._children = [] # [(key, info), ...] self._attrmap = {} # {attribute: info, ...} self._keymap = {} # {key: info, ...} self._types = types def gettype(self, name): n = name.lower() try: return self._types[n] except KeyError: raise ZConfig.SchemaError("unknown type name: " + `name`) def gettypenames(self): return self._types.keys() def __len__(self): return len(self._children) def __getitem__(self, index): return self._children[index] def _add_child(self, key, info): # check naming constraints assert key or info.attribute if key and self._keymap.has_key(key): raise ZConfig.SchemaError( "child name %s already used" % key) if info.attribute and self._attrmap.has_key(info.attribute): raise ZConfig.SchemaError( "child attribute name %s already used" % info.attribute) # a-ok, add the item to the appropriate maps if info.attribute: self._attrmap[info.attribute] = info if key: self._keymap[key] = info self._children.append((key, info)) def addkey(self, keyinfo): self._add_child(keyinfo.name, keyinfo) def addsection(self, name, sectinfo): assert name not in ("*", "+") self._add_child(name, sectinfo) def getinfo(self, key): if not key: raise ZConfig.ConfigurationError( "cannot match a key without a name") try: return self._keymap[key] except KeyError: raise ZConfig.ConfigurationError("no key matching " + `key`) def getrequiredtypes(self): d = {} if self.name: d[self.name] = 1 stack = [self] while stack: info = stack.pop() for key, ci in info._children: if ci.issection(): t = ci.sectiontype if not d.has_key(t.name): d[t.name] = 1 stack.append(t) return d.keys() def getsectioninfo(self, type, name): for key, info in self._children: if key: if key == name: if not info.issection(): raise ZConfig.ConfigurationError( "section name %s already in use for key" % key) st = info.sectiontype if st.isabstract(): try: st = st.getsubtype(type) except ZConfig.ConfigurationError: raise ZConfig.ConfigurationError( "section type %s not allowed for name %s" % (`type`, `key`)) if not st.name == type: raise ZConfig.ConfigurationError( "name %s must be used for a %s section" % (`name`, `st.name`)) return info # else must be a sectiontype or an abstracttype: elif info.sectiontype.name == type: if not (name or info.allowUnnamed()): raise ZConfig.ConfigurationError( `type` + " sections must be named") return info elif info.sectiontype.isabstract(): st = info.sectiontype if st.name == type: raise ZConfig.ConfigurationError( "cannot define section with an abstract type") try: st = st.getsubtype(type) except ZConfig.ConfigurationError: # not this one; maybe a different one pass else: return info raise ZConfig.ConfigurationError( "no matching section defined for type='%s', name='%s'" % ( type, name)) def isabstract(self): return False class SchemaType(SectionType): def __init__(self, keytype, valuetype, datatype, handler, url, registry): SectionType.__init__(self, None, keytype, valuetype, datatype, registry, {}) self._components = {} self.handler = handler self.url = url def addtype(self, typeinfo): n = typeinfo.name if self._types.has_key(n): raise ZConfig.SchemaError("type name cannot be redefined: " + `typeinfo.name`) self._types[n] = typeinfo def allowUnnamed(self): return True def isAllowedName(self, name): return False def issection(self): return True def getunusedtypes(self): alltypes = self.gettypenames() reqtypes = self.getrequiredtypes() for n in reqtypes: alltypes.remove(n) if self.name and self.name in alltypes: alltypes.remove(self.name) return alltypes def createSectionType(self, name, keytype, valuetype, datatype): t = SectionType(name, keytype, valuetype, datatype, self.registry, self._types) self.addtype(t) return t def deriveSectionType(self, base, name, keytype, valuetype, datatype): if isinstance(base, SchemaType): raise ZConfig.SchemaError( "cannot derive sectiontype from top-level schema") t = self.createSectionType(name, keytype, valuetype, datatype) t._attrmap.update(base._attrmap) t._keymap.update(base._keymap) t._children.extend(base._children) for i in range(len(t._children)): key, info = t._children[i] if isinstance(info, BaseKeyInfo) and info.name == "+": # need to create a new info object and recompute the # default mapping based on the new keytype info = copy.copy(info) info.computedefault(t.keytype) t._children[i] = (key, info) return t def addComponent(self, name): if self._components.has_key(name): raise ZConfig.SchemaError("already have component %s" % name) self._components[name] = name def hasComponent(self, name): return self._components.has_key(name) def createDerivedSchema(base): new = SchemaType(base.keytype, base.valuetype, base.datatype, base.handler, base.url, base.registry) new._components.update(base._components) new.description = base.description new._children[:] = base._children new._attrmap.update(base._attrmap) new._keymap.update(base._keymap) new._types.update(base._types) return new zope2.13-2.13.21/source/ZConfig/ZConfig/schemaless.txt0000644000175000017500000001541112214017462021226 0ustar arnauarnau================================= Using ZConfig data without schema ================================= Sometimes it's useful to use ZConfig configuration data without a schema. This is most interesting when assembling a configuration from fragments, as some buildout recipes do. This is not recommended for general application use. The ``ZConfig.schemaless`` module provides some support for working without schema. Something things are not (currently) supported, including the %define and %include directives. The %import directive is supported. This module provides basic support for loading configuration, inspecting and modifying it, and re-serializing the result. >>> from ZConfig import schemaless There is a single function which loads configuration data from a file open for reading. Let's take a look at this, and what it returns:: >>> config_text = ''' ... ... some-key some-value ... ... some-key another-value ... ...
... key1 value1.1 ... key1 value1.2 ... key2 value2 ... ... ... another key ... another value ... ...
... ... another-key whee! ... ... ... ... nothing here ... ... ... ''' >>> import StringIO >>> config = schemaless.loadConfigFile(StringIO.StringIO(config_text)) The `config` object is a mapping from top-level keys to lists of values:: >>> config["some-key"] ['some-value', 'another-value'] >>> config["another-key"] ['whee!'] >>> config["no-such-key-in-the-config"] Traceback (most recent call last): KeyError: 'no-such-key-in-the-config' >>> lst = list(config) >>> lst.sort() >>> lst ['another-key', 'some-key'] There is also a ``sections`` attribute that lists child sections:: >>> len(config.sections) 2 Let's take a look at one of the sections. Like the top-level configuration, the section maps keys >>> section = config.sections[0] >>> section["key1"] ['value1.1', 'value1.2'] >>> section["key2"] ['value2'] >>> section["no-such-key-in-the-config"] Traceback (most recent call last): KeyError: 'no-such-key-in-the-config' >>> lst = list(section) >>> lst.sort() >>> lst ['key1', 'key2'] Child sections are again available via the ``sections`` attribute:: >>> len(section.sections) 1 In addition, the section has ``type`` and ``name`` attributes that record the type and name of the section as ZConfig understands them:: >>> section.type 'section' >>> print section.name None Let's look at the named section from our example, so we can see the name:: >>> section = config.sections[1] >>> section.type 'another' >>> section.name 'named' We can also mutate the configuration, adding new keys and values as desired:: >>> config["new-key"] = ["new-value-1", "new-value-2"] >>> config["some-key"].append("third-value") New sections can also be added:: >>> section = schemaless.Section("sectiontype", "my-name") >>> section["key"] = ["value"] >>> config.sections.insert(1, section) The configuration can be re-serialized using ``str()``:: >>> print str(config) another-key whee! new-key new-value-1 new-key new-value-2 some-key some-value some-key another-value some-key third-value
key1 value1.1 key1 value1.2 key2 value2 another key another value
key value nothing here Note that some adjustments have been made: - key/value pairs come before child sections - keys are sorted at each level - blank lines are removed, with new blank lines inserted to preserve some semblance of readability These are all presentation changes, but not essential changes to the configuration data. The ordering of sections is not modified in rendering, nor are the values for a single key re-ordered within a section or top-level configuration. Support for %import ------------------- Imports are supported, and are re-ordered in much the same way that other elements of a configuration are:: >>> config_text = ''' ... ... %import some.package ... ...
... ... %import another.package ... ... ... some value ... ... ...
... ... some-key some-value ... ... ''' >>> config = schemaless.loadConfigFile(StringIO.StringIO(config_text)) >>> print config %import some.package %import another.package some-key some-value
some value
The imports are also available as the ``imports`` attribute of the configuration object:: >>> config.imports ('some.package', 'another.package') Multiple imports of the same name are removed:: >>> config_text = ''' ... ... %import some.package ... %import another.package ... %import some.package ... ... ''' >>> config = schemaless.loadConfigFile(StringIO.StringIO(config_text)) >>> print config %import some.package %import another.package >>> config.imports ('some.package', 'another.package') Limitations ----------- There are some limitations of handling ZConfig-based configurations using the ``ZConfig.schemaless`` module. Some of these are implementation issues, and may be corrected in the future: - %define is not supported. - %include is not supported. Others are a function of not processing the schema, and can't easily be avoided: - normalization of keys based on keytypes specified in the or elements of the schema if not performed. If the transformation of a key might affect the behavior controlled by the resulting configuration, the generated configuration may not be equivalent. Examples of this are unusual, but exist. Limitations related to the non-processing of the schema cannot be detected by the ``ZConfig.schemaless``, so no errors are reported in these situations. For the strictly syntactic limitations, we do get errors when the input data requires they be supported. Let's look at both the %define and %include handling. When %define is used in the input configuration, an exception is raised when loading the configuration:: >>> config_text = ''' ... ... %define somename somevalue ... ... ''' >>> schemaless.loadConfigFile(StringIO.StringIO(config_text)) Traceback (most recent call last): NotImplementedError: defines are not supported A similar exception is raised for %include:: >>> config_text = ''' ... ... %include some/other/file.conf ... ... ''' >>> schemaless.loadConfigFile(StringIO.StringIO(config_text)) Traceback (most recent call last): NotImplementedError: includes are not supported zope2.13-2.13.21/source/ZConfig/ZConfig/components/0000755000175000017500000000000012214017462020521 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/components/basic/0000755000175000017500000000000012214017462021602 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/components/basic/mapping.py0000644000175000017500000000137412214017462023614 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Python datatype for the ZConfig.components.basic.mapping section type.""" def mapping(section): return section.mapping zope2.13-2.13.21/source/ZConfig/ZConfig/components/basic/component.xml0000644000175000017500000000031612214017462024326 0ustar arnauarnau Convenient loader which causes all the "basic" components to be loaded. zope2.13-2.13.21/source/ZConfig/ZConfig/components/basic/__init__.py0000644000175000017500000000003412214017462023710 0ustar arnauarnau# This is a Python package. zope2.13-2.13.21/source/ZConfig/ZConfig/components/basic/mapping.xml0000644000175000017500000000166112214017462023763 0ustar arnauarnau Section that provides a simple mapping implementation. An application should derive a more specific section type for use in configuration files: <import package="ZConfig.components.basic" file="mapping.xml" /> <sectiontype name="mapping" extends="ZConfig.basic.mapping" /> If a non-standard keytype is needed, it can be overridden as well: <sectiontype name="system-map" extends="ZConfig.basic.mapping" keytype="mypkg.datatypes.system_name" /> zope2.13-2.13.21/source/ZConfig/ZConfig/components/basic/tests/0000755000175000017500000000000012214017462022744 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/components/basic/tests/__init__.py0000644000175000017500000000003412214017462025052 0ustar arnauarnau# This is a Python package. zope2.13-2.13.21/source/ZConfig/ZConfig/components/basic/tests/test_mapping.py0000644000175000017500000000477212214017462026022 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the 'basic' section types provided as part of ZConfig.components.basic.""" import unittest from ZConfig.tests import support SIMPLE_SCHEMA = '''\
''' class BasicSectionTypeTestCase(support.TestBase): schema = None def setUp(self): if self.schema is None: self.__class__.schema = self.load_schema_text(SIMPLE_SCHEMA) def test_simple_empty_dict(self): conf = self.load_config_text(self.schema, "") self.assertEqual(conf.simple_dict, {}) conf = self.load_config_text(self.schema, """\ # comment """) self.assertEqual(conf.simple_dict, {}) def test_simple_dict(self): conf = self.load_config_text(self.schema, """\ key-one value-one key-two value-two """) L = conf.simple_dict.items() L.sort() self.assertEqual(L, [("key-one", "value-one"), ("key-two", "value-two")]) def test_derived_dict(self): conf = self.load_config_text(self.schema, """\ 1 foo 2 bar 42 question? """) L = conf.int_dict.items() L.sort() self.assertEqual(L, [(1, "foo"), (2, "bar"), (42, "question?")]) def test_suite(): return unittest.makeSuite(BasicSectionTypeTestCase) zope2.13-2.13.21/source/ZConfig/ZConfig/components/__init__.py0000644000175000017500000000003412214017462022627 0ustar arnauarnau# This is a Python package. zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/0000755000175000017500000000000012214017462022000 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/eventlog.xml0000644000175000017500000000073612214017462024353 0ustar arnauarnau Configuration for the root logger. zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/handlers.py0000644000175000017500000001713012214017462024154 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZConfig factory datatypes for log handlers.""" import sys from ZConfig.components.logger.factory import Factory _log_format_variables = { 'name': '', 'levelno': '3', 'levelname': 'DEBUG', 'pathname': 'apath', 'filename': 'afile', 'module': 'amodule', 'lineno': 1, 'created': 1.1, 'asctime': 'atime', 'msecs': 1, 'relativeCreated': 1, 'thread': 1, 'message': 'amessage', 'process': 1, } def log_format(value): value = ctrl_char_insert(value) try: # Make sure the format string uses only names that will be # provided, and has reasonable type flags for each, and does # not expect positional args. value % _log_format_variables except (ValueError, KeyError): raise ValueError('Invalid log format string %s' % value) return value _control_char_rewrites = {r'\n': '\n', r'\t': '\t', r'\b': '\b', r'\f': '\f', r'\r': '\r'}.items() def ctrl_char_insert(value): for pattern, replacement in _control_char_rewrites: value = value.replace(pattern, replacement) return value def resolve(name): """Given a dotted name, returns an object imported from a Python module.""" name = name.split('.') used = name.pop(0) found = __import__(used) for n in name: used += '.' + n try: found = getattr(found, n) except AttributeError: __import__(used) found = getattr(found, n) return found class HandlerFactory(Factory): def __init__(self, section): Factory.__init__(self) self.section = section def create_loghandler(self): raise NotImplementedError( "subclasses must override create_loghandler()") def create(self): import logging logger = self.create_loghandler() if self.section.formatter: f = resolve(self.section.formatter) else: f = logging.Formatter logger.setFormatter(f(self.section.format, self.section.dateformat)) logger.setLevel(self.section.level) return logger def getLevel(self): return self.section.level class FileHandlerFactory(HandlerFactory): def create_loghandler(self): from ZConfig.components.logger import loghandler path = self.section.path max_bytes = self.section.max_size old_files = self.section.old_files when = self.section.when interval = self.section.interval if path == "STDERR": if max_bytes or old_files: raise ValueError("cannot rotate STDERR") handler = loghandler.StreamHandler(sys.stderr) elif path == "STDOUT": if max_bytes or old_files: raise ValueError("cannot rotate STDOUT") handler = loghandler.StreamHandler(sys.stdout) elif when or max_bytes or old_files or interval: if not old_files: raise ValueError("old-files must be set for log rotation") if when: if max_bytes: raise ValueError("can't set *both* max_bytes and when") if not interval: interval = 1 handler = loghandler.TimedRotatingFileHandler( path, when=when, interval=interval, backupCount=old_files) elif max_bytes: handler = loghandler.RotatingFileHandler( path, maxBytes=max_bytes, backupCount=old_files) else: raise ValueError( "max-bytes or when must be set for log rotation") else: handler = loghandler.FileHandler(path) return handler _syslog_facilities = { "auth": 1, "authpriv": 1, "cron": 1, "daemon": 1, "kern": 1, "lpr": 1, "mail": 1, "news": 1, "security": 1, "syslog": 1, "user": 1, "uucp": 1, "local0": 1, "local1": 1, "local2": 1, "local3": 1, "local4": 1, "local5": 1, "local6": 1, "local7": 1, } def syslog_facility(value): value = value.lower() if not _syslog_facilities.has_key(value): L = _syslog_facilities.keys() L.sort() raise ValueError("Syslog facility must be one of " + ", ".join(L)) return value class SyslogHandlerFactory(HandlerFactory): def create_loghandler(self): from ZConfig.components.logger import loghandler return loghandler.SysLogHandler(self.section.address.address, self.section.facility) class Win32EventLogFactory(HandlerFactory): def create_loghandler(self): from ZConfig.components.logger import loghandler return loghandler.Win32EventLogHandler(self.section.appname) def http_handler_url(value): import urlparse scheme, netloc, path, param, query, fragment = urlparse.urlparse(value) if scheme != 'http': raise ValueError('url must be an http url') if not netloc: raise ValueError('url must specify a location') if not path: raise ValueError('url must specify a path') q = [] if param: q.append(';') q.append(param) if query: q.append('?') q.append(query) if fragment: q.append('#') q.append(fragment) return (netloc, path + ''.join(q)) def get_or_post(value): value = value.upper() if value not in ('GET', 'POST'): raise ValueError('method must be "GET" or "POST", instead received: ' + repr(value)) return value class HTTPHandlerFactory(HandlerFactory): def create_loghandler(self): from ZConfig.components.logger import loghandler host, selector = self.section.url return loghandler.HTTPHandler(host, selector, self.section.method) class SMTPHandlerFactory(HandlerFactory): def create_loghandler(self): from ZConfig.components.logger import loghandler host, port = self.section.smtp_server if not port: mailhost = host else: mailhost = host, port kwargs = {} if self.section.smtp_username and self.section.smtp_password: # Since credentials were only added in py2.6 we use a kwarg to not # break compatibility with older py if sys.version_info < (2, 6): raise ValueError('SMTP auth requires at least Python 2.6.') kwargs['credentials'] = (self.section.smtp_username, self.section.smtp_password) elif (self.section.smtp_username or self.section.smtp_password): raise ValueError( 'Either both smtp-username and smtp-password or none must be ' 'given') return loghandler.SMTPHandler(mailhost, self.section.fromaddr, self.section.toaddrs, self.section.subject, **kwargs) zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/factory.py0000644000175000017500000000244012214017462024021 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## _marker = object() class Factory: """Generic wrapper for instance construction. Calling the factory causes the instance to be created if it hasn't already been created, and returns the object. Calling the factory multiple times returns the same object. The instance is created using the factory's create() method, which must be overriden by subclasses. """ def __init__(self): self.instance = _marker def __call__(self): if self.instance is _marker: self.instance = self.create() return self.instance def create(self): raise NotImplementedError("subclasses need to override create()") zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/base-logger.xml0000644000175000017500000000301712214017462024712 0ustar arnauarnau Base definition for the logger types defined by ZConfig.components.logger. This exists entirely to provide shared key definitions and documentation. Verbosity setting for the logger. Values must be a name of a level, or an integer in the range [0..50]. The names of the levels, in order of increasing verbosity (names on the same line are equivalent): critical, fatal error warn, warning info blather debug trace all The special name "notset", or the numeric value 0, indicates that the setting for the parent logger should be used. It is strongly recommended that names be used rather than numeric values to ensure that configuration files can be deciphered more easily. Handlers to install on this logger. Each handler describes how logging events should be presented. zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/component.xml0000644000175000017500000000056712214017462024534 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/datatypes.py0000644000175000017500000000215712214017462024355 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZConfig datatypes for logging support.""" _logging_levels = { "critical": 50, "fatal": 50, "error": 40, "warn": 30, "warning": 30, "info": 20, "blather": 15, "debug": 10, "trace": 5, "all": 1, "notset": 0, } def logging_level(value): s = str(value).lower() if _logging_levels.has_key(s): return _logging_levels[s] else: v = int(s) if v < 0 or v > 50: raise ValueError("log level not in range: " + `v`) return v zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/handlers.xml0000644000175000017500000000710612214017462024326 0ustar arnauarnau Base type for most log handlers. This is cannot be used as a loghandler directly since it doesn't implement the loghandler abstract section type. Logging formatter class. The default is 'logging.Formatter'. An alternative is 'zope.exceptions.log.Formatter', which enhances exception tracebacks with information from __traceback_info__ and __traceback_supplement__ variables. zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/__init__.py0000644000175000017500000000161612214017462024115 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZConfig schema component package for logging configuration.""" # Make sure we can't import this if "logging" isn't available; we # don't want partial imports to appear to succeed. try: import logging except ImportError: import sys del sys.modules[__name__] zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/abstract.xml0000644000175000017500000000022712214017462024326 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/logger.xml0000644000175000017500000000266412214017462024011 0ustar arnauarnau Indicates whether events that reach this logger should be propogated toward the root of the logger hierarchy. If true (the default), events will be passed to the logger's parent after being handled. If false, events will be handled and the parent will not be informed. There is not a way to control propogation by the severity of the event. The dotted name of the logger. This give it a location in the logging hierarchy. Most applications provide a specific set of subsystem names for which logging is meaning; consult the application documentation for the set of names that are actually interesting for the application. zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/logger.py0000644000175000017500000000666612214017462023647 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZConfig factory datatypes for loggers.""" from ZConfig.components.logger.factory import Factory class LoggerFactoryBase(Factory): """Base class for logger factories. Factory used to create loggers while delaying actual logger instance construction. We need to do this because we may want to reference a logger before actually instantiating it (for example, to allow the app time to set an effective user). An instance of this wrapper is a callable which, when called, returns a logger object. """ def __init__(self, section): Factory.__init__(self) self.level = section.level self.handler_factories = section.handlers def create(self): # set the logger up import logging logger = logging.getLogger(self.name) logger.setLevel(self.level) if self.handler_factories: for handler_factory in self.handler_factories: handler = handler_factory() logger.addHandler(handler) else: from ZConfig.components.logger import loghandler logger.addHandler(loghandler.NullHandler()) return logger def startup(self): # make sure we've instantiated the logger self() def getLowestHandlerLevel(self): """Return the lowest log level provided by any configured handler. If all handlers and the logger itself have level==NOTSET, this returns NOTSET. """ import logging lowest = self.level for factory in self.handler_factories: level = factory.getLevel() if level != logging.NOTSET: if lowest == logging.NOTSET: lowest = level else: lowest = min(lowest, level) return lowest def reopen(self): """Re-open any handlers for which this is a meaningful operation. This only works on handlers on the logger provided by this factory directly; handlers for child loggers are not affected. (This can be considered a bug, but is sufficient at the moment.) """ logger = self() for handler in logger.handlers: reopen = getattr(handler, "reopen", None) if reopen is not None and callable(reopen): reopen() class EventLogFactory(LoggerFactoryBase): """Logger factory that returns the root logger.""" name = None class LoggerFactory(LoggerFactoryBase): """Logger factory that returns the named logger.""" def __init__(self, section): LoggerFactoryBase.__init__(self, section) self.name = section.name self.propagate = section.propagate def create(self): logger = LoggerFactoryBase.create(self) logger.propagate = self.propagate return logger zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/loghandler.py0000644000175000017500000001200312214017462024465 0ustar arnauarnau############################################################################## # # Copyright (c) 2001 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Handlers which can plug into a PEP 282 logger.""" import os import sys import weakref from logging import Handler, StreamHandler from logging.handlers import RotatingFileHandler as _RotatingFileHandler from logging.handlers import TimedRotatingFileHandler \ as _TimedRotatingFileHandler from logging.handlers import SysLogHandler, BufferingHandler from logging.handlers import HTTPHandler, SMTPHandler from logging.handlers import NTEventLogHandler as Win32EventLogHandler _reopenable_handlers = [] def closeFiles(): """Reopen all logfiles managed by ZConfig configuration.""" while _reopenable_handlers: wr = _reopenable_handlers.pop() h = wr() if h is not None: h.close() def reopenFiles(): """Reopen all logfiles managed by ZConfig configuration.""" for wr in _reopenable_handlers[:]: h = wr() if h is None: try: _reopenable_handlers.remove(wr) except ValueError: continue else: h.reopen() def _remove_from_reopenable(wr): try: _reopenable_handlers.remove(wr) except ValueError: pass class FileHandler(StreamHandler): """File handler which supports reopening of logs. Re-opening should be used instead of the 'rollover' feature of the FileHandler from the standard library's logging package. """ def __init__(self, filename, mode="a"): filename = os.path.abspath(filename) StreamHandler.__init__(self, open(filename, mode)) self.baseFilename = filename self.mode = mode self._wr = weakref.ref(self, _remove_from_reopenable) _reopenable_handlers.append(self._wr) def close(self): self.stream.close() # This can raise a KeyError if the handler has already been # removed, but a later error can be raised if # StreamHandler.close() isn't called. This seems the best # compromise. :-( try: StreamHandler.close(self) except KeyError: pass _remove_from_reopenable(self._wr) def reopen(self): self.acquire() try: self.stream.close() self.stream = open(self.baseFilename, self.mode) finally: self.release() class Win32FileHandler(FileHandler): """File-based log handler for Windows that supports an additional 'rotate' method. reopen() is generally useless since Windows cannot do a move on an open file. """ def rotate(self, rotateFilename=None): if not rotateFilename: rotateFilename = self.baseFilename + ".last" error = None self.close() try: os.rename(self.baseFilename, rotateFilename) except OSError: pass self.stream = open(self.baseFilename, self.mode) if os.name == "nt": # Make it the default for Windows - we install a 'reopen' handler that # tries to rotate the logfile. FileHandler = Win32FileHandler class RotatingFileHandler(_RotatingFileHandler): def __init__(self, *args, **kw): _RotatingFileHandler.__init__(self, *args, **kw) self._wr = weakref.ref(self, _remove_from_reopenable) _reopenable_handlers.append(self._wr) def close(self): _RotatingFileHandler.close(self) _remove_from_reopenable(self._wr) def reopen(self): self.doRollover() class TimedRotatingFileHandler(_TimedRotatingFileHandler): def __init__(self, *args, **kw): _TimedRotatingFileHandler.__init__(self, *args, **kw) self._wr = weakref.ref(self, _remove_from_reopenable) _reopenable_handlers.append(self._wr) def close(self): _TimedRotatingFileHandler.close(self) _remove_from_reopenable(self._wr) def reopen(self): self.doRollover() class NullHandler(Handler): """Handler that does nothing.""" def emit(self, record): pass def handle(self, record): pass class StartupHandler(BufferingHandler): """Handler which stores messages in a buffer until later. This is useful at startup before we can know that we can safely write to a configuration-specified handler. """ def __init__(self): BufferingHandler.__init__(self, sys.maxint) def shouldFlush(self, record): return False def flushBufferTo(self, target): while self.buffer: target.handle(self.buffer.pop(0)) zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/tests/0000755000175000017500000000000012214017462023142 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/tests/__init__.py0000644000175000017500000000003412214017462025250 0ustar arnauarnau# This is a Python package. zope2.13-2.13.21/source/ZConfig/ZConfig/components/logger/tests/test_logger.py0000644000175000017500000006434412214017462026045 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for logging configuration via ZConfig.""" import cStringIO as StringIO import doctest import logging import os import sys import tempfile import unittest import ZConfig from ZConfig.components.logger import datatypes from ZConfig.components.logger import handlers from ZConfig.components.logger import loghandler class CustomFormatter(logging.Formatter): def formatException(self, ei): """Format and return the exception information as a string. This adds helpful advice to the end of the traceback. """ import traceback sio = StringIO.StringIO() traceback.print_exception(ei[0], ei[1], ei[2], file=sio) return sio.getvalue() + "... Don't panic!" class LoggingTestBase(unittest.TestCase): # XXX This tries to save and restore the state of logging around # the test. Somewhat surgical; there may be a better way. def setUp(self): self._created = [] self._old_logger = logging.getLogger() self._old_level = self._old_logger.level self._old_handlers = self._old_logger.handlers[:] self._old_logger.handlers[:] = [] self._old_logger.setLevel(logging.WARN) self._old_logger_dict = logging.root.manager.loggerDict.copy() logging.root.manager.loggerDict.clear() def tearDown(self): logging.root.manager.loggerDict.clear() logging.root.manager.loggerDict.update(self._old_logger_dict) for h in self._old_logger.handlers: self._old_logger.removeHandler(h) for h in self._old_handlers: self._old_logger.addHandler(h) self._old_logger.setLevel(self._old_level) while self._created: os.unlink(self._created.pop()) self.assertEqual(loghandler._reopenable_handlers, []) loghandler.closeFiles() loghandler._reopenable_handlers == [] def mktemp(self): fd, fn = tempfile.mkstemp() os.close(fd) self._created.append(fn) return fn def move(self, fn): nfn = self.mktemp() os.rename(fn, nfn) return nfn _schema = None def get_schema(self): if self._schema is None: sio = StringIO.StringIO(self._schematext) self.__class__._schema = ZConfig.loadSchemaFile(sio) return self._schema def get_config(self, text): conf, handler = ZConfig.loadConfigFile(self.get_schema(), StringIO.StringIO(text)) self.assert_(not handler) return conf class TestConfig(LoggingTestBase): _schematext = """
""" def test_logging_level(self): # Make sure the expected names are supported; it's not clear # how to check the values in a meaningful way. # Just make sure they're case-insensitive. convert = datatypes.logging_level for name in ["notset", "all", "trace", "debug", "blather", "info", "warn", "warning", "error", "fatal", "critical"]: self.assertEqual(convert(name), convert(name.upper())) self.assertRaises(ValueError, convert, "hopefully-not-a-valid-value") def test_http_method(self): convert = handlers.get_or_post self.assertEqual(convert("get"), "GET") self.assertEqual(convert("GET"), "GET") self.assertEqual(convert("post"), "POST") self.assertEqual(convert("POST"), "POST") self.assertRaises(ValueError, convert, "") self.assertRaises(ValueError, convert, "foo") def test_syslog_facility(self): convert = handlers.syslog_facility for name in ["auth", "authpriv", "cron", "daemon", "kern", "lpr", "mail", "news", "security", "syslog", "user", "uucp", "local0", "local1", "local2", "local3", "local4", "local5", "local6", "local7"]: self.assertEqual(convert(name), name) self.assertEqual(convert(name.upper()), name) self.assertRaises(ValueError, convert, "hopefully-never-a-valid-value") def test_config_without_logger(self): conf = self.get_config("") self.assert_(conf.eventlog is None) def test_config_without_handlers(self): logger = self.check_simple_logger("") # Make sure there's a NullHandler, since a warning gets # printed if there are no handlers: self.assertEqual(len(logger.handlers), 1) self.assert_(isinstance(logger.handlers[0], loghandler.NullHandler)) def test_with_logfile(self): fn = self.mktemp() logger = self.check_simple_logger("\n" " \n" " path %s\n" " level debug\n" " \n" "" % fn) logfile = logger.handlers[0] self.assertEqual(logfile.level, logging.DEBUG) self.assert_(isinstance(logfile, loghandler.FileHandler)) logger.removeHandler(logfile) logfile.close() def test_with_stderr(self): self.check_standard_stream("stderr") def test_with_stdout(self): self.check_standard_stream("stdout") def test_with_rotating_logfile(self): fn = self.mktemp() logger = self.check_simple_logger("\n" " \n" " path %s\n" " level debug\n" " max-size 5mb\n" " old-files 10\n" " \n" "" % fn) logfile = logger.handlers[0] self.assertEqual(logfile.level, logging.DEBUG) self.assertEqual(logfile.backupCount, 10) self.assertEqual(logfile.maxBytes, 5*1024*1024) self.assert_(isinstance(logfile, loghandler.RotatingFileHandler)) logger.removeHandler(logfile) logfile.close() def test_with_timed_rotating_logfile(self): fn = self.mktemp() logger = self.check_simple_logger("\n" " \n" " path %s\n" " level debug\n" " when D\n" " old-files 11\n" " \n" "" % fn) logfile = logger.handlers[0] self.assertEqual(logfile.level, logging.DEBUG) self.assertEqual(logfile.backupCount, 11) self.assertEqual(logfile.interval, 86400) self.assert_(isinstance(logfile, loghandler.TimedRotatingFileHandler)) logger.removeHandler(logfile) logfile.close() def test_with_timed_rotating_logfile(self): fn = self.mktemp() logger = self.check_simple_logger("\n" " \n" " path %s\n" " level debug\n" " when D\n" " interval 3\n" " old-files 11\n" " \n" "" % fn) logfile = logger.handlers[0] self.assertEqual(logfile.level, logging.DEBUG) self.assertEqual(logfile.backupCount, 11) self.assertEqual(logfile.interval, 86400*3) self.assert_(isinstance(logfile, loghandler.TimedRotatingFileHandler)) logger.removeHandler(logfile) logfile.close() def test_with_timed_rotating_logfile_and_size_should_fail(self): fn = self.mktemp() self.assertRaises( ValueError, self.check_simple_logger, "\n" " \n" " path %s\n" " level debug\n" " max-size 5mb\n" " when D\n" " old-files 10\n" " \n" "" % fn) def check_standard_stream(self, name): old_stream = getattr(sys, name) conf = self.get_config(""" level info path %s """ % name.upper()) self.assert_(conf.eventlog is not None) # The factory has already been created; make sure it picks up # the stderr we set here when we create the logger and # handlers: sio = StringIO.StringIO() setattr(sys, name, sio) try: logger = conf.eventlog() finally: setattr(sys, name, old_stream) logger.warn("woohoo!") self.assert_(sio.getvalue().find("woohoo!") >= 0) def test_custom_formatter(self): old_stream = sys.stdout conf = self.get_config(""" formatter ZConfig.components.logger.tests.test_logger.CustomFormatter level info path STDOUT """) sio = StringIO.StringIO() sys.stdout = sio try: logger = conf.eventlog() finally: sys.stdout = old_stream try: raise KeyError except KeyError: logger.exception("testing a KeyError") self.assert_(sio.getvalue().find("KeyError") >= 0) self.assert_(sio.getvalue().find("Don't panic") >= 0) def test_with_syslog(self): logger = self.check_simple_logger("\n" " \n" " level error\n" " facility local3\n" " \n" "") syslog = logger.handlers[0] self.assertEqual(syslog.level, logging.ERROR) self.assert_(isinstance(syslog, loghandler.SysLogHandler)) def test_with_http_logger_localhost(self): logger = self.check_simple_logger("\n" " \n" " level error\n" " method post\n" " \n" "") handler = logger.handlers[0] self.assertEqual(handler.host, "localhost") # XXX The "url" attribute of the handler is misnamed; it # really means just the selector portion of the URL. self.assertEqual(handler.url, "/") self.assertEqual(handler.level, logging.ERROR) self.assertEqual(handler.method, "POST") self.assert_(isinstance(handler, loghandler.HTTPHandler)) def test_with_http_logger_remote_host(self): logger = self.check_simple_logger("\n" " \n" " method get\n" " url http://example.com/log/\n" " \n" "") handler = logger.handlers[0] self.assertEqual(handler.host, "example.com") # XXX The "url" attribute of the handler is misnamed; it # really means just the selector portion of the URL. self.assertEqual(handler.url, "/log/") self.assertEqual(handler.level, logging.NOTSET) self.assertEqual(handler.method, "GET") self.assert_(isinstance(handler, loghandler.HTTPHandler)) def test_with_email_notifier(self): logger = self.check_simple_logger("\n" " \n" " to sysadmin@example.com\n" " to sa-pager@example.com\n" " from zlog-user@example.com\n" " level fatal\n" " \n" "") handler = logger.handlers[0] self.assertEqual(handler.toaddrs, ["sysadmin@example.com", "sa-pager@example.com"]) self.assertEqual(handler.fromaddr, "zlog-user@example.com") self.assertEqual(handler.level, logging.FATAL) def test_with_email_notifier_with_credentials(self): try: logger = self.check_simple_logger("\n" " \n" " to sysadmin@example.com\n" " from zlog-user@example.com\n" " level fatal\n" " smtp-username john\n" " smtp-password johnpw\n" " \n" "") except ValueError: if sys.version_info >= (2, 6): # For python 2.6 no ValueError must be raised. raise else: # This path must only be reached with python >=2.6 self.assert_(sys.version_info >= (2, 6)) handler = logger.handlers[0] self.assertEqual(handler.toaddrs, ["sysadmin@example.com"]) self.assertEqual(handler.fromaddr, "zlog-user@example.com") self.assertEqual(handler.fromaddr, "zlog-user@example.com") self.assertEqual(handler.level, logging.FATAL) self.assertEqual(handler.username, 'john') self.assertEqual(handler.password, 'johnpw') def test_with_email_notifier_with_invalid_credentials(self): self.assertRaises(ValueError, self.check_simple_logger, "\n" " \n" " to sysadmin@example.com\n" " from zlog-user@example.com\n" " level fatal\n" " smtp-username john\n" " \n" "") self.assertRaises(ValueError, self.check_simple_logger, "\n" " \n" " to sysadmin@example.com\n" " from zlog-user@example.com\n" " level fatal\n" " smtp-password john\n" " \n" "") def check_simple_logger(self, text, level=logging.INFO): conf = self.get_config(text) self.assert_(conf.eventlog is not None) self.assertEqual(conf.eventlog.level, level) logger = conf.eventlog() self.assert_(isinstance(logger, logging.Logger)) self.assertEqual(len(logger.handlers), 1) return logger class TestReopeningLogfilesBase(LoggingTestBase): # These tests should not be run on Windows. _schematext = """ """ def test_filehandler_reopen(self): def mkrecord(msg): # # Python 2.5.0 added an additional required argument to the # LogRecord constructor, making it incompatible with prior # versions. Python 2.5.1 corrected the bug by making the # additional argument optional. We deal with 2.5.0 by adding # the extra arg in only that case, using the default value # from Python 2.5.1. # args = ["foo.bar", logging.ERROR, __file__, 42, msg, (), ()] if sys.version_info[:3] == (2, 5, 0): args.append(None) return logging.LogRecord(*args) # This goes through the reopening operation *twice* to make # sure that we don't lose our handle on the handler the first # time around. fn = self.mktemp() h = self.handler_factory(fn) h.handle(mkrecord("message 1")) nfn1 = self.move(fn) h.handle(mkrecord("message 2")) h.reopen() h.handle(mkrecord("message 3")) nfn2 = self.move(fn) h.handle(mkrecord("message 4")) h.reopen() h.handle(mkrecord("message 5")) h.close() # Check that the messages are in the right files:: text1 = open(nfn1).read() text2 = open(nfn2).read() text3 = open(fn).read() self.assert_("message 1" in text1) self.assert_("message 2" in text1) self.assert_("message 3" in text2) self.assert_("message 4" in text2) self.assert_("message 5" in text3) class TestReopeningLogfiles(TestReopeningLogfilesBase): handler_factory = loghandler.FileHandler _sampleconfig_template = """ name foo.bar path %(path0)s level debug path %(path1)s level info name bar.foo path %(path2)s level info """ def test_logfile_reopening(self): # # This test only applies to the simple logfile reopening; it # doesn't work the same way as the rotating logfile handler. # paths = self.mktemp(), self.mktemp(), self.mktemp() d = { "path0": paths[0], "path1": paths[1], "path2": paths[2], } text = self._sampleconfig_template % d conf = self.get_config(text) self.assertEqual(len(conf.loggers), 2) # Build the loggers from the configuration, and write to them: conf.loggers[0]().info("message 1") conf.loggers[1]().info("message 2") npaths1 = [self.move(fn) for fn in paths] # # We expect this to re-open the original filenames, so we'll # have six files instead of three. # loghandler.reopenFiles() # # Write to them again: conf.loggers[0]().info("message 3") conf.loggers[1]().info("message 4") npaths2 = [self.move(fn) for fn in paths] # # We expect this to re-open the original filenames, so we'll # have nine files instead of six. # loghandler.reopenFiles() # # Write to them again: conf.loggers[0]().info("message 5") conf.loggers[1]().info("message 6") # # We should now have all nine files: for fn in paths: self.assert_(os.path.isfile(fn), "%r must exist" % fn) for fn in npaths1: self.assert_(os.path.isfile(fn), "%r must exist" % fn) for fn in npaths2: self.assert_(os.path.isfile(fn), "%r must exist" % fn) # # Clean up: for logger in conf.loggers: logger = logger() for handler in logger.handlers[:]: logger.removeHandler(handler) handler.close() def test_filehandler_reopen_thread_safety(self): # The reopen method needs to do locking to avoid a race condition # with emit calls. For simplicity we replace the "acquire" and # "release" methods with dummies that record calls to them. fn = self.mktemp() h = self.handler_factory(fn) calls = [] h.acquire = lambda: calls.append("acquire") h.release = lambda: calls.append("release") h.reopen() h.close() self.assertEqual(calls, ["acquire", "release"]) class TestReopeningRotatingLogfiles(TestReopeningLogfilesBase): _sampleconfig_template = """ name foo.bar path %(path0)s level debug max-size 1mb old-files 10 path %(path1)s level info max-size 1mb old-files 3 path %(path1)s level info when D old-files 3 name bar.foo path %(path2)s level info max-size 10mb old-files 10 """ handler_factory = loghandler.RotatingFileHandler def test_logfile_reopening(self): # # This test only applies to the simple logfile reopening; it # doesn't work the same way as the rotating logfile handler. # paths = self.mktemp(), self.mktemp(), self.mktemp() d = { "path0": paths[0], "path1": paths[1], "path2": paths[2], } text = self._sampleconfig_template % d conf = self.get_config(text) self.assertEqual(len(conf.loggers), 2) # Build the loggers from the configuration, and write to them: conf.loggers[0]().info("message 1") conf.loggers[1]().info("message 2") # # We expect this to re-open the original filenames, so we'll # have six files instead of three. # loghandler.reopenFiles() # # Write to them again: conf.loggers[0]().info("message 3") conf.loggers[1]().info("message 4") # # We expect this to re-open the original filenames, so we'll # have nine files instead of six. # loghandler.reopenFiles() # # Write to them again: conf.loggers[0]().info("message 5") conf.loggers[1]().info("message 6") # # We should now have all nine files: for fn in paths: fn1 = fn + ".1" fn2 = fn + ".2" self.assert_(os.path.isfile(fn), "%r must exist" % fn) self.assert_(os.path.isfile(fn1), "%r must exist" % fn1) self.assert_(os.path.isfile(fn2), "%r must exist" % fn2) # # Clean up: for logger in conf.loggers: logger = logger() for handler in logger.handlers[:]: logger.removeHandler(handler) handler.close() def test_logger_convenience_function_and_ommiting_name_to_get_root_logger(): """ The ZConfig.loggers function can be used to configure one or more loggers. We'll configure the rot logger and a non-root logger. >>> old_level = logging.getLogger().getEffectiveLevel() >>> old_handler_count = len(logging.getLogger().handlers) >>> ZConfig.configureLoggers(''' ... ... level INFO ... ... PATH STDOUT ... format root %(levelname)s %(name)s %(message)s ... ... ... ... ... name ZConfig.TEST ... level DEBUG ... ... PATH STDOUT ... format test %(levelname)s %(name)s %(message)s ... ... ... ''') >>> logging.getLogger('ZConfig.TEST').debug('test message') test DEBUG ZConfig.TEST test message root DEBUG ZConfig.TEST test message >>> logging.getLogger().getEffectiveLevel() == logging.INFO True >>> len(logging.getLogger().handlers) == old_handler_count + 1 True >>> logging.getLogger('ZConfig.TEST').getEffectiveLevel() == logging.DEBUG True >>> len(logging.getLogger('ZConfig.TEST').handlers) == 1 True .. cleanup >>> logging.getLogger('ZConfig.TEST').setLevel(logging.NOTSET) >>> logging.getLogger('ZConfig.TEST').removeHandler( ... logging.getLogger('ZConfig.TEST').handlers[-1]) >>> logging.getLogger().setLevel(old_level) >>> logging.getLogger().removeHandler(logging.getLogger().handlers[-1]) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite()) suite.addTest(unittest.makeSuite(TestConfig)) if os.name != "nt": # Though log files can be closed and re-opened on Windows, these # tests expect to be able to move the underlying files out from # underneath the logger while open. That's not possible on # Windows. # # Different tests are needed that only test that close/re-open # operations are performed by the handler; those can be run on # any platform. suite.addTest(unittest.makeSuite(TestReopeningLogfiles)) suite.addTest(unittest.makeSuite(TestReopeningRotatingLogfiles)) return suite if __name__ == '__main__': unittest.main(defaultTest="test_suite") zope2.13-2.13.21/source/ZConfig/ZConfig/tests/0000755000175000017500000000000012214017462017476 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/tests/zipsource/0000755000175000017500000000000012214017462021521 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/tests/zipsource/README.txt0000644000175000017500000000015412214017462023217 0ustar arnauarnauThis directory contains a sample package that is used to create the 'foosample.zip' file used in the tests. zope2.13-2.13.21/source/ZConfig/ZConfig/tests/zipsource/foo/0000755000175000017500000000000012214017462022304 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/tests/zipsource/foo/sample/0000755000175000017500000000000012214017462023565 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/tests/zipsource/foo/sample/component.xml0000644000175000017500000000035212214017462026311 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/tests/zipsource/foo/sample/datatypes.py0000644000175000017500000000017412214017462026137 0ustar arnauarnau"""Sample datatypes used for testing. """ __docformat__ = "reStructuredText" def data(value): return "| %s |" % value zope2.13-2.13.21/source/ZConfig/ZConfig/tests/zipsource/foo/sample/__init__.py0000644000175000017500000000004612214017462025676 0ustar arnauarnau# This directory is a Python package. zope2.13-2.13.21/source/ZConfig/ZConfig/tests/zipsource/foo/__init__.py0000644000175000017500000000004612214017462024415 0ustar arnauarnau# This directory is a Python package. zope2.13-2.13.21/source/ZConfig/ZConfig/tests/test_subst.py0000644000175000017500000000647112214017462022257 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the string interpolation module.""" # This is needed to support Python 2.1. from __future__ import nested_scopes import unittest from ZConfig import SubstitutionReplacementError, SubstitutionSyntaxError from ZConfig.substitution import isname, substitute class SubstitutionTestCase(unittest.TestCase): def test_simple_names(self): d = {"name": "value", "name1": "abc", "name_": "def", "_123": "ghi"} def check(s, v): self.assertEqual(substitute(s, d), v) check("$name", "value") check(" $name ", " value ") check("${name}", "value") check(" ${name} ", " value ") check("$name$name", "valuevalue") check("$name1$name", "abcvalue") check("$name_$name", "defvalue") check("$_123$name", "ghivalue") check("$name $name", "value value") check("$name1 $name", "abc value") check("$name_ $name", "def value") check("$_123 $name", "ghi value") check("splat", "splat") check("$$", "$") check("$$$name$$", "$value$") def test_undefined_names(self): d = {"name": "value"} self.assertRaises(SubstitutionReplacementError, substitute, "$splat", d) self.assertRaises(SubstitutionReplacementError, substitute, "$splat1", d) self.assertRaises(SubstitutionReplacementError, substitute, "$splat_", d) def test_syntax_errors(self): d = {"name": "${next"} def check(s): self.assertRaises(SubstitutionSyntaxError, substitute, s, d) check("${") check("${name") check("${1name}") check("${ name}") def test_edge_cases(self): # It's debatable what should happen for these cases, so we'll # follow the lead of the Bourne shell here. def check(s): self.assertRaises(SubstitutionSyntaxError, substitute, s, {}) check("$1") check("$") check("$ stuff") def test_non_nesting(self): d = {"name": "$value"} self.assertEqual(substitute("$name", d), "$value") def test_isname(self): self.assert_(isname("abc")) self.assert_(isname("abc_def")) self.assert_(isname("_abc")) self.assert_(isname("abc_")) self.assert_(not isname("abc-def")) self.assert_(not isname("-def")) self.assert_(not isname("abc-")) self.assert_(not isname("")) def test_suite(): return unittest.makeSuite(SubstitutionTestCase) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZConfig/ZConfig/tests/test_config.py0000644000175000017500000001471412214017462022363 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the configuration data structures and loader.""" import os import StringIO import tempfile import unittest import ZConfig from ZConfig.tests.support import CONFIG_BASE class ConfigurationTestCase(unittest.TestCase): schema = None def get_schema(self): if self.schema is None: ConfigurationTestCase.schema = ZConfig.loadSchema( CONFIG_BASE + "simple.xml") return self.schema def load(self, relurl, context=None): url = CONFIG_BASE + relurl self.conf, self.handlers = ZConfig.loadConfig(self.get_schema(), url) conf = self.conf #self.assertEqual(conf.url, url) self.assert_(conf.getSectionName() is None) self.assert_(conf.getSectionType() is None) #self.assert_(conf.delegate is None) return conf def loadtext(self, text): sio = StringIO.StringIO(text) return self.loadfile(sio) def loadfile(self, file): schema = self.get_schema() self.conf, self.handlers = ZConfig.loadConfigFile(schema, file) return self.conf def check_simple_gets(self, conf): self.assertEqual(conf.empty, '') self.assertEqual(conf.int_var, 12) self.assertEqual(conf.neg_int, -2) self.assertEqual(conf.float_var, 12.02) self.assertEqual(conf.var1, 'abc') self.assert_(conf.true_var_1) self.assert_(conf.true_var_2) self.assert_(conf.true_var_3) self.assert_(not conf.false_var_1) self.assert_(not conf.false_var_2) self.assert_(not conf.false_var_3) self.assertEqual(conf.list_1, []) self.assertEqual(conf.list_2, ['abc']) self.assertEqual(conf.list_3, ['abc', 'def', 'ghi']) self.assertEqual(conf.list_4, ['[', 'what', 'now?', ']']) def test_simple_gets(self): conf = self.load("simple.conf") self.check_simple_gets(conf) def test_type_errors(self): Error = ZConfig.DataConversionError raises = self.assertRaises raises(Error, self.loadtext, "int-var true") raises(Error, self.loadtext, "float-var true") raises(Error, self.loadtext, "neg-int false") raises(Error, self.loadtext, "true-var-1 0") raises(Error, self.loadtext, "true-var-1 1") raises(Error, self.loadtext, "true-var-1 -1") def test_simple_sections(self): self.schema = ZConfig.loadSchema(CONFIG_BASE + "simplesections.xml") conf = self.load("simplesections.conf") self.assertEqual(conf.var, "foo") # check each interleaved position between sections for c in "0123456": self.assertEqual(getattr(conf, "var_" +c), "foo-" + c) sect = [sect for sect in conf.sections if sect.getSectionName() == "name"][0] self.assertEqual(sect.var, "bar") self.assertEqual(sect.var_one, "splat") self.assert_(sect.var_three is None) sect = [sect for sect in conf.sections if sect.getSectionName() == "delegate"][0] self.assertEqual(sect.var, "spam") self.assertEqual(sect.var_two, "stuff") self.assert_(sect.var_three is None) def test_include(self): conf = self.load("include.conf") self.assertEqual(conf.var1, "abc") self.assertEqual(conf.var2, "value2") self.assertEqual(conf.var3, "value3") self.assertEqual(conf.var4, "value") def test_includes_with_defines(self): self.schema = ZConfig.loadSchemaFile(StringIO.StringIO("""\ """)) conf = self.load("outer.conf") self.assertEqual(conf.refinner, "inner") self.assertEqual(conf.refouter, "outer") def test_define(self): conf = self.load("simple.conf") self.assertEqual(conf.getname, "value") self.assertEqual(conf.getnametwice, "valuevalue") self.assertEqual(conf.getdollars, "$$") self.assertEqual(conf.getempty, "xy") self.assertEqual(conf.getwords, "abc two words def") def test_define_errors(self): self.assertRaises(ZConfig.ConfigurationSyntaxError, self.loadtext, "%define\n") self.assertRaises(ZConfig.ConfigurationSyntaxError, self.loadtext, "%define abc-def\n") self.assertRaises(ZConfig.ConfigurationSyntaxError, self.loadtext, "%define a value\n%define a other\n") # doesn't raise if value is equal self.loadtext("%define a value\n%define a value\n") def test_fragment_ident_disallowed(self): self.assertRaises(ZConfig.ConfigurationError, self.load, "simplesections.conf#another") def test_load_from_fileobj(self): sio = StringIO.StringIO("%define name value\n" "getname x $name y \n") cf = self.loadfile(sio) self.assertEqual(cf.getname, "x value y") def test_load_from_abspath(self): fn = self.write_tempfile() try: self.check_load_from_path(fn) finally: os.unlink(fn) def test_load_from_relpath(self): fn = self.write_tempfile() dir, name = os.path.split(fn) pwd = os.getcwd() try: os.chdir(dir) self.check_load_from_path(name) finally: os.chdir(pwd) os.unlink(fn) def write_tempfile(self): fn = tempfile.mktemp() fp = open(fn, "w") fp.write("var1 value\n") fp.close() return fn def check_load_from_path(self, path): schema = self.get_schema() ZConfig.loadConfig(schema, path) def test_suite(): return unittest.makeSuite(ConfigurationTestCase) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZConfig/ZConfig/tests/test_cmdline.py0000644000175000017500000001477212214017462022535 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the command-line integration.""" import unittest import ZConfig from ZConfig.cmdline import ExtendedConfigLoader from ZConfig.tests.support import TestBase class CommandLineTest(TestBase): def create_config_loader(self, schema): loader = ExtendedConfigLoader(schema) for item in self.clopts: loader.addOption(*item) return loader def test_loading(self): schema = self.load_schema_text("""\
""") self.clopts = [("mykey=splat!", None), ("section/innerkey=spoogey", None)] bag = self.create_config_loader(schema).cook() # Test a variety of queries on the OptionBag: self.assert_(bag.has_key("mykey")) self.assert_(not bag.has_key("another")) self.assertEqual(bag.get_section_info("st", None), None) self.assertEqual(bag.get_section_info("st", "missing-sect"), None) # Consume everything in the OptionBag: L = bag.get_key("mykey") s, pos = L[0] self.assertEqual(len(L), 1) self.assertEqual(s, "splat!") bag2 = bag.get_section_info("st", "section") self.assert_(bag2.has_key("innerkey")) self.assert_(not bag2.has_key("another")) L = bag2.get_key("innerkey") s, pos = L[0] self.assertEqual(len(L), 1) self.assertEqual(s, "spoogey") # "Finish" to make sure everything has been consumed: bag2.finish() bag.finish() def test_named_sections(self): schema = self.load_schema_text("""\
""") self.clopts = [("foo/k1=v1", None), ("bar/k2=v2", ("someurl", 2, 3))] bag = self.create_config_loader(schema).cook() foo = bag.get_section_info("st2", "foo") bar = bag.get_section_info("st2", "bar") bag.finish() self.assertEqual(bar.get_key("k2"), [("v2", ("someurl", 2, 3))]) bar.finish() # Ignore foo for now; it's not really important *when* it fails. simple_schema = None def get_simple_schema(self): if self.simple_schema is None: self.__class__.simple_schema = self.load_schema_text("""\ """) return self.simple_schema def test_reading_config(self): self.clopts = [("k1=stringvalue", None), ("k2=12", None)] schema = self.get_simple_schema() conf = self.load_config_text(schema, """\ k0 stuff k1 replaced-stuff k2 42 """) self.assertEqual(conf.k0, "stuff") self.assertEqual(conf.k1, "stringvalue") self.assertEqual(conf.k2, 12) self.assertEqual(conf.k3, 19) def test_unknown_key(self): self.clopts = [("foo=bar", None)] schema = self.get_simple_schema() self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_too_many_keys(self): self.clopts = [("k1=v1", None), ("k1=v2", None)] schema = self.get_simple_schema() self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_bad_datatype(self): self.clopts = [("k2=42.0", None)] schema = self.get_simple_schema() self.assertRaises(ZConfig.DataConversionError, self.load_config_text, schema, "") def test_without_clopts(self): self.clopts = [] schema = self.get_simple_schema() conf = self.load_config_text(schema, "k3 42") self.assertEqual(conf.k0, None) self.assertEqual(conf.k1, None) self.assertEqual(conf.k2, None) self.assertEqual(conf.k3, 42) def test_section_contents(self): schema = self.load_schema_text("""\ k3-v1 k3-v2 k3-v3
""") self.clopts = [("s1/k1=foo", None), ("s2/k3=value1", None), ("s2/k3=value2", None), ("s1/k2=99", None), ("s2/k3=value3", None), ("s2/k3=value4", None), ] conf = self.load_config_text(schema, "\n") self.assertEqual(conf.s1.k1, "foo") self.assertEqual(conf.s1.k2, 99) self.assertEqual(conf.s1.k3, ["k3-v1", "k3-v2", "k3-v3"]) self.assertEqual(conf.s2.k1, None) self.assertEqual(conf.s2.k2, 3) self.assertEqual(conf.s2.k3, ["value1", "value2", "value3", "value4"]) def test_suite(): return unittest.makeSuite(CommandLineTest) if __name__ == "__main__": unittest.main(defaultTest="test_suite") zope2.13-2.13.21/source/ZConfig/ZConfig/tests/support.py0000644000175000017500000000451312214017462021567 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Support code shared among the tests.""" import os import StringIO import unittest import urllib import ZConfig from ZConfig.loader import ConfigLoader from ZConfig.url import urljoin try: __file__ except NameError: import sys __file__ = sys.argv[0] d = os.path.abspath(os.path.join(os.path.dirname(__file__), "input")) CONFIG_BASE = "file://%s/" % urllib.pathname2url(d) class TestBase(unittest.TestCase): """Utility methods which can be used with the schema support.""" def load_both(self, schema_url, conf_url): schema = self.load_schema(schema_url) conf = self.load_config(schema, conf_url) return schema, conf def load_schema(self, relurl): self.url = urljoin(CONFIG_BASE, relurl) self.schema = ZConfig.loadSchema(self.url) self.assert_(self.schema.issection()) return self.schema def load_schema_text(self, text, url=None): sio = StringIO.StringIO(text) self.schema = ZConfig.loadSchemaFile(sio, url) return self.schema def load_config(self, schema, conf_url, num_handlers=0): conf_url = urljoin(CONFIG_BASE, conf_url) loader = self.create_config_loader(schema) self.conf, self.handlers = loader.loadURL(conf_url) self.assertEqual(len(self.handlers), num_handlers) return self.conf def load_config_text(self, schema, text, num_handlers=0, url=None): sio = StringIO.StringIO(text) loader = self.create_config_loader(schema) self.conf, self.handlers = loader.loadFile(sio, url) self.assertEqual(len(self.handlers), num_handlers) return self.conf def create_config_loader(self, schema): return ConfigLoader(schema) zope2.13-2.13.21/source/ZConfig/ZConfig/tests/test_loader.py0000644000175000017500000003245712214017462022370 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of ZConfig.loader classes and helper functions.""" import os.path import sys import tempfile import unittest import urllib2 from StringIO import StringIO import ZConfig import ZConfig.loader import ZConfig.url from ZConfig.tests.support import CONFIG_BASE, TestBase try: myfile = __file__ except NameError: myfile = sys.argv[0] myfile = os.path.abspath(myfile) LIBRARY_DIR = os.path.join(os.path.dirname(myfile), "library") class LoaderTestCase(TestBase): def test_schema_caching(self): loader = ZConfig.loader.SchemaLoader() url = ZConfig.url.urljoin(CONFIG_BASE, "simple.xml") schema1 = loader.loadURL(url) schema2 = loader.loadURL(url) self.assert_(schema1 is schema2) def test_simple_import_with_cache(self): loader = ZConfig.loader.SchemaLoader() url1 = ZConfig.url.urljoin(CONFIG_BASE, "library.xml") schema1 = loader.loadURL(url1) sio = StringIO("" " " "
" "") url2 = ZConfig.url.urljoin(CONFIG_BASE, "stringio") schema2 = loader.loadFile(sio, url2) self.assert_(schema1.gettype("type-a") is schema2.gettype("type-a")) def test_simple_import_using_prefix(self): self.load_schema_text("""\ """) def test_import_errors(self): # must specify exactly one of package or src self.assertRaises(ZConfig.SchemaError, ZConfig.loadSchemaFile, StringIO("")) self.assertRaises(ZConfig.SchemaError, ZConfig.loadSchemaFile, StringIO("" " " "")) # cannot specify src and file self.assertRaises(ZConfig.SchemaError, ZConfig.loadSchemaFile, StringIO("" " " "")) # cannot specify module as package sio = StringIO("" " " "") try: ZConfig.loadSchemaFile(sio) except ZConfig.SchemaResourceError, e: self.assertEqual(e.filename, "component.xml") self.assertEqual(e.package, "ZConfig.tests.test_loader") self.assert_(e.path is None) # make sure the str() doesn't raise an unexpected exception str(e) else: self.fail("expected SchemaResourceError") def test_import_from_package(self): loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " "") schema = loader.loadFile(sio) self.assert_(schema.gettype("widget-a") is not None) def test_import_from_package_with_file(self): loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " "") schema = loader.loadFile(sio) self.assert_(schema.gettype("extra-type") is not None) def test_import_from_package_extra_directory(self): loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " "") schema = loader.loadFile(sio) self.assert_(schema.gettype("extra-thing") is not None) def test_import_from_package_with_missing_file(self): loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " "") try: loader.loadFile(sio) except ZConfig.SchemaResourceError, e: self.assertEqual(e.filename, "notthere.xml") self.assertEqual(e.package, "ZConfig.tests.library.widget") self.assert_(e.path) # make sure the str() doesn't raise an unexpected exception str(e) else: self.fail("expected SchemaResourceError") def test_import_from_package_with_directory_file(self): loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " "") self.assertRaises(ZConfig.SchemaError, loader.loadFile, sio) def test_import_two_components_one_package(self): loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " " " "") schema = loader.loadFile(sio) schema.gettype("widget-a") schema.gettype("extra-type") def test_import_component_twice_1(self): # Make sure we can import a component twice from a schema. # This is most likely to occur when the component is imported # from each of two other components, or from the top-level # schema and a component. loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " " " "") schema = loader.loadFile(sio) schema.gettype("widget-a") def test_import_component_twice_2(self): # Make sure we can import a component from a config file even # if it has already been imported from the schema. loader = ZConfig.loader.SchemaLoader() sio = StringIO("" " " "") schema = loader.loadFile(sio) loader = ZConfig.loader.ConfigLoader(schema) sio = StringIO("%import ZConfig.tests.library.widget") loader.loadFile(sio) def test_urlsplit_urlunsplit(self): # Extracted from Python's test.test_urlparse module: for url, parsed, split in [ ('http://www.python.org', ('http', 'www.python.org', '', '', '', ''), ('http', 'www.python.org', '', '', '')), ('http://www.python.org#abc', ('http', 'www.python.org', '', '', '', 'abc'), ('http', 'www.python.org', '', '', 'abc')), ('http://www.python.org/#abc', ('http', 'www.python.org', '/', '', '', 'abc'), ('http', 'www.python.org', '/', '', 'abc')), ("http://a/b/c/d;p?q#f", ('http', 'a', '/b/c/d', 'p', 'q', 'f'), ('http', 'a', '/b/c/d;p', 'q', 'f')), ('file:///tmp/junk.txt', ('file', '', '/tmp/junk.txt', '', '', ''), ('file', '', '/tmp/junk.txt', '', '')), ]: result = ZConfig.url.urlsplit(url) self.assertEqual(result, split) result2 = ZConfig.url.urlunsplit(result) self.assertEqual(result2, url) def test_file_url_normalization(self): self.assertEqual( ZConfig.url.urlnormalize("file:/abc/def"), "file:///abc/def") self.assertEqual( ZConfig.url.urlunsplit(("file", "", "/abc/def", "", "")), "file:///abc/def") self.assertEqual( ZConfig.url.urljoin("file:/abc/", "def"), "file:///abc/def") self.assertEqual( ZConfig.url.urldefrag("file:/abc/def#frag"), ("file:///abc/def", "frag")) def test_isPath(self): assert_ = self.assert_ isPath = ZConfig.loader.BaseLoader().isPath assert_(isPath("abc")) assert_(isPath("abc/def")) assert_(isPath("/abc")) assert_(isPath("/abc/def")) assert_(isPath(r"\abc")) assert_(isPath(r"\abc\def")) assert_(isPath(r"c:\abc\def")) assert_(isPath("/ab:cd")) assert_(isPath(r"\ab:cd")) assert_(isPath("long name with spaces")) assert_(isPath("long name:with spaces")) assert_(not isPath("ab:cd")) assert_(not isPath("http://www.example.com/")) assert_(not isPath("http://www.example.com/sample.conf")) assert_(not isPath("file:///etc/zope/zope.conf")) assert_(not isPath("file:///c|/foo/bar.conf")) class TestNonExistentResources(unittest.TestCase): # XXX Not sure if this is the best approach for these. These # tests make sure that the error reported by ZConfig for missing # resources is handled in a consistent way. Since ZConfig uses # urllib2.urlopen() for opening all resources, what we do is # replace that function with one that always raises an exception. # Since urllib2.urlopen() can raise either IOError or OSError # (depending on the version of Python), we run test for each # exception. urllib2.urlopen() is restored after running the # test. def setUp(self): self.urllib2_urlopen = urllib2.urlopen urllib2.urlopen = self.fake_urlopen def tearDown(self): urllib2.urlopen = self.urllib2_urlopen def fake_urlopen(self, url): raise self.error() def test_nonexistent_file_ioerror(self): self.error = IOError self.check_nonexistent_file() def test_nonexistent_file_oserror(self): self.error = OSError self.check_nonexistent_file() def check_nonexistent_file(self): fn = tempfile.mktemp() schema = ZConfig.loadSchemaFile(StringIO("")) self.assertRaises(ZConfig.ConfigurationError, ZConfig.loadSchema, fn) self.assertRaises(ZConfig.ConfigurationError, ZConfig.loadConfig, schema, fn) self.assertRaises(ZConfig.ConfigurationError, ZConfig.loadConfigFile, schema, StringIO("%include " + fn)) self.assertRaises(ZConfig.ConfigurationError, ZConfig.loadSchema, "http://www.zope.org/no-such-document/") self.assertRaises(ZConfig.ConfigurationError, ZConfig.loadConfig, schema, "http://www.zope.org/no-such-document/") class TestResourcesInZip(unittest.TestCase): def setUp(self): self.old_path = sys.path[:] # now add our sample EGG to sys.path: zipfile = os.path.join(os.path.dirname(myfile), "foosample.zip") sys.path.append(zipfile) def tearDown(self): sys.path[:] = self.old_path def test_zip_import_component_from_schema(self): sio = StringIO('''
''') schema = ZConfig.loadSchemaFile(sio) t = schema.gettype("sample") self.failIf(t.isabstract()) def test_zip_import_component_from_config(self): sio = StringIO('''
''') schema = ZConfig.loadSchemaFile(sio) sio = StringIO(''' %import foo.sample data value ''') config, _ = ZConfig.loadConfigFile(schema, sio) self.assertEqual(config.something.data, "| value |") def test_suite(): suite = unittest.makeSuite(LoaderTestCase) suite.addTest(unittest.makeSuite(TestNonExistentResources)) suite.addTest(unittest.makeSuite(TestResourcesInZip)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZConfig/ZConfig/tests/test_cfgimports.py0000644000175000017500000000415312214017462023267 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of the %import mechanism. $Id: test_cfgimports.py,v 1.1 2003/10/03 20:01:57 fdrake Exp $ """ import unittest from StringIO import StringIO import ZConfig import ZConfig.tests.support class TestImportFromConfiguration(ZConfig.tests.support.TestBase): def test_simple_import(self): schema = self.load_schema_text("") loader = self.create_config_loader(schema) config, _ = loader.loadFile( StringIO("%import ZConfig.tests.library.widget\n")) # make sure we now have a "private" schema object; the only # way to get it is from the loader itself self.assert_(schema is not loader.schema) # make sure component types are only found on the private schema: loader.schema.gettype("widget-b") self.assertRaises(ZConfig.SchemaError, schema.gettype, "widget-b") def test_repeated_import(self): schema = self.load_schema_text("") loader = self.create_config_loader(schema) config, _ = loader.loadFile( StringIO("%import ZConfig.tests.library.widget\n" "%import ZConfig.tests.library.widget\n")) def test_missing_import(self): schema = self.load_schema_text("") loader = self.create_config_loader(schema) self.assertRaises(ZConfig.SchemaError, loader.loadFile, StringIO("%import ZConfig.tests.missing\n")) def test_suite(): return unittest.makeSuite(TestImportFromConfiguration) zope2.13-2.13.21/source/ZConfig/ZConfig/tests/test_cookbook.py0000644000175000017500000000451312214017462022720 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of examples from the online cookbook, so we don't break them down the road. Unless we really mean to. The ZConfig Cookbook is available online at: http://dev.zope.org/Zope3/ZConfig """ import unittest from ZConfig.tests.support import TestBase def basic_key_mapping_password_to_passwd(key): # Lower-case the key since that's what basic-key does: key = key.lower() # Now map password to passwd: if key == "password": key = "passwd" return key def user_info_conversion(section): return section class CookbookTestCase(TestBase): def test_rewriting_key_names(self): schema = self.load_schema_text("""
""" % __name__) config = self.load_config_text(schema, """\ USERID 42 USERNAME foouser PASSWORD yeah-right """) self.assertEqual(config.userinfo.userid, 42) self.assertEqual(config.userinfo.username, "foouser") self.assertEqual(config.userinfo.passwd, "yeah-right") self.assert_(not hasattr(config.userinfo, "password")) def test_suite(): return unittest.makeSuite(CookbookTestCase) if __name__ == "__main__": unittest.main(defaultTest="test_suite") zope2.13-2.13.21/source/ZConfig/ZConfig/tests/test_datatypes.py0000644000175000017500000003612612214017462023115 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of standard ZConfig datatypes.""" import os import sys import shutil import socket import datetime import tempfile import unittest import ZConfig.datatypes try: here = __file__ except NameError: here = sys.argv[0] here = os.path.abspath(here) try: unicode except NameError: have_unicode = False else: have_unicode = True class DatatypeTestCase(unittest.TestCase): types = ZConfig.datatypes.Registry() def test_datatype_basickey(self): convert = self.types.get("basic-key") eq = self.assertEqual raises = self.assertRaises eq(convert("abc"), "abc") eq(convert("ABC_DEF.123"), "abc_def.123") eq(convert("Abc-Def-456"), "abc-def-456") eq(convert("Abc.Def"), "abc.def") raises(ValueError, convert, "_abc") raises(ValueError, convert, "-abc") raises(ValueError, convert, "123") raises(ValueError, convert, "") def test_datatype_boolean(self): convert = self.types.get("boolean") check = self.assert_ raises = self.assertRaises check(convert("on")) check(convert("true")) check(convert("yes")) check(not convert("off")) check(not convert("false")) check(not convert("no")) raises(ValueError, convert, '0') raises(ValueError, convert, '1') raises(ValueError, convert, '') raises(ValueError, convert, 'junk') def test_datatype_float(self): convert = self.types.get("float") eq = self.assertEqual raises = self.assertRaises eq(convert("1"), 1.0) self.assert_(type(convert(1)) is type(1.0)) eq(convert("1.1"), 1.1) eq(convert("50.50"), 50.50) eq(convert("-50.50"), -50.50) eq(convert(0), 0.0) eq(convert("0"), 0.0) eq(convert("-0"), 0.0) eq(convert("0.0"), 0.0) raises(ValueError, convert, "junk") raises(ValueError, convert, "0x234.1.9") raises(ValueError, convert, "0.9-") # These are not portable representations; make sure they are # disallowed everywhere for consistency. raises(ValueError, convert, "inf") raises(ValueError, convert, "-inf") raises(ValueError, convert, "nan") if have_unicode: raises(ValueError, convert, unicode("inf")) raises(ValueError, convert, unicode("-inf")) raises(ValueError, convert, unicode("nan")) def test_datatype_identifier(self): convert = self.types.get("identifier") raises = self.assertRaises self.check_names(convert) self.check_never_namelike(convert) raises(ValueError, convert, ".abc") def check_names(self, convert): eq = self.assert_ascii_equal eq(convert, "AbcDef") eq(convert, "a________") eq(convert, "abc_def") eq(convert, "int123") eq(convert, "_abc") eq(convert, "_123") eq(convert, "__dict__") def assert_ascii_equal(self, convert, value): v = convert(value) self.assertEqual(v, value) self.assert_(isinstance(v, str)) if have_unicode: unicode_value = unicode(value) v = convert(unicode_value) self.assertEqual(v, value) self.assert_(isinstance(v, str)) def check_never_namelike(self, convert): raises = self.assertRaises raises(ValueError, convert, "2345") raises(ValueError, convert, "23.45") raises(ValueError, convert, ".45") raises(ValueError, convert, "23.") raises(ValueError, convert, "abc.") raises(ValueError, convert, "-abc") raises(ValueError, convert, "-123") raises(ValueError, convert, "abc-") raises(ValueError, convert, "123-") raises(ValueError, convert, "-") raises(ValueError, convert, ".") raises(ValueError, convert, "&%$*()") raises(ValueError, convert, "") def test_datatype_dotted_name(self): convert = self.types.get("dotted-name") raises = self.assertRaises self.check_names(convert) self.check_dotted_names(convert) self.check_never_namelike(convert) raises(ValueError, convert, "abc.") raises(ValueError, convert, ".abc.") raises(ValueError, convert, "abc.def.") raises(ValueError, convert, ".abc.def.") raises(ValueError, convert, ".abc.def") def test_datatype_dotted_suffix(self): convert = self.types.get("dotted-suffix") eq = self.assert_ascii_equal raises = self.assertRaises self.check_names(convert) self.check_dotted_names(convert) self.check_never_namelike(convert) eq(convert, ".a") eq(convert, ".a.b") eq(convert, ".a.b.c.d.e.f.g.h.i.j.k.l.m.n.o") raises(ValueError, convert, "abc.") raises(ValueError, convert, ".abc.") raises(ValueError, convert, "abc.def.") raises(ValueError, convert, ".abc.def.") def check_dotted_names(self, convert): eq = self.assert_ascii_equal eq(convert, "abc.def") eq(convert, "abc.def.ghi") eq(convert, "a.d.g.g.g.g.g.g.g") def test_datatype_inet_address(self): convert = self.types.get("inet-address") eq = self.assertEqual defhost = ZConfig.datatypes.DEFAULT_HOST eq(convert("Host.Example.Com:80"), ("host.example.com", 80)) eq(convert(":80"), (defhost, 80)) eq(convert("80"), (defhost, 80)) eq(convert("[::1]:80"), ("::1", 80)) eq(convert("host.EXAMPLE.com"), ("host.example.com", None)) eq(convert("2001::ABCD"), ("2001::abcd", None)) self.assertRaises(ValueError, convert, "40 # foo") def test_datatype_inet_binding_address(self): convert = self.types.get("inet-binding-address") eq = self.assertEqual defhost = "" eq(convert("Host.Example.Com:80"), ("host.example.com", 80)) eq(convert(":80"), (defhost, 80)) eq(convert("80"), (defhost, 80)) eq(convert("host.EXAMPLE.com"), ("host.example.com", None)) self.assertRaises(ValueError, convert, "40 # foo") def test_datatype_inet_connection_address(self): convert = self.types.get("inet-connection-address") eq = self.assertEqual defhost = "127.0.0.1" eq(convert("Host.Example.Com:80"), ("host.example.com", 80)) eq(convert(":80"), (defhost, 80)) eq(convert("80"), (defhost, 80)) eq(convert("host.EXAMPLE.com"), ("host.example.com", None)) self.assertRaises(ValueError, convert, "40 # foo") def test_datatype_integer(self): convert = self.types.get("integer") eq = self.assertEqual raises = self.assertRaises eq(convert('-100'), -100) eq(convert('-1'), -1) eq(convert('-0'), 0) eq(convert('0'), 0) eq(convert('1'), 1) eq(convert('100'), 100) eq(convert('65535'), 65535) eq(convert('65536'), 65536) big = sys.maxint + 1L # Python 2.1 needs the L suffix here s = str(big) # s won't have the suffix eq(convert(s), big) eq(convert("-" + s), -big) raises(ValueError, convert, 'abc') raises(ValueError, convert, '-0xabc') raises(ValueError, convert, '') raises(ValueError, convert, '123 456') raises(ValueError, convert, '123-') def test_datatype_locale(self): convert = self.types.get("locale") # Python supports "C" even when the _locale module is not available self.assertEqual(convert("C"), "C") self.assertRaises(ValueError, convert, "locale-does-not-exist") def test_datatype_port(self): convert = self.types.get("port-number") eq = self.assertEqual raises = self.assertRaises raises(ValueError, convert, '-1') raises(ValueError, convert, '0') eq(convert('1'), 1) eq(convert('80'), 80) eq(convert('1023'), 1023) eq(convert('1024'), 1024) eq(convert('60000'), 60000) eq(convert('65535'), 0xffff) raises(ValueError, convert, '65536') def test_datatype_socket_address(self): convert = self.types.get("socket-address") eq = self.assertEqual AF_INET = socket.AF_INET AF_INET6 = socket.AF_INET6 defhost = ZConfig.datatypes.DEFAULT_HOST def check(value, family, address, self=self, convert=convert): a = convert(value) self.assertEqual(a.family, family) self.assertEqual(a.address, address) check("Host.Example.Com:80", AF_INET, ("host.example.com", 80)) check(":80", AF_INET, (defhost, 80)) check("80", AF_INET, (defhost, 80)) check("host.EXAMPLE.com", AF_INET, ("host.example.com",None)) check("::1", AF_INET6,("::1", None)) check("[::]:80", AF_INET6,("::", 80)) a1 = convert("/tmp/var/@345.4") a2 = convert("/tmp/var/@345.4:80") self.assertEqual(a1.address, "/tmp/var/@345.4") self.assertEqual(a2.address, "/tmp/var/@345.4:80") if hasattr(socket, "AF_UNIX"): self.assertEqual(a1.family, socket.AF_UNIX) self.assertEqual(a2.family, socket.AF_UNIX) else: self.assert_(a1.family is None) self.assert_(a2.family is None) def test_ipaddr_or_hostname(self): convert = self.types.get('ipaddr-or-hostname') eq = self.assertEqual raises = self.assertRaises eq(convert('hostname'), 'hostname') eq(convert('hostname.com'), 'hostname.com') eq(convert('www.hostname.com'), 'www.hostname.com') eq(convert('HOSTNAME'), 'hostname') eq(convert('HOSTNAME.COM'), 'hostname.com') eq(convert('WWW.HOSTNAME.COM'), 'www.hostname.com') eq(convert('127.0.0.1'), '127.0.0.1') eq(convert('::1'), '::1') eq(convert('2001:DB8:1234:4567:89AB:cdef:0:1'), '2001:db8:1234:4567:89ab:cdef:0:1') eq(convert('2001:DB8:1234:4567::10.11.12.13'), '2001:db8:1234:4567::10.11.12.13') raises(ValueError, convert, '1hostnamewithleadingnumeric') raises(ValueError, convert, '255.255') raises(ValueError, convert, '12345678') raises(ValueError, convert, '999.999.999.999') raises(ValueError, convert, 'a!badhostname') raises(ValueError, convert, '2001:DB8:0123:4567:89AB:cdef:0:1:2') raises(ValueError, convert, '2001:DB8:0123:4567::10.11.12.13.14') def test_existing_directory(self): convert = self.types.get('existing-directory') eq = self.assertEqual raises = self.assertRaises eq(convert('.'), '.') eq(convert(os.path.dirname(here)), os.path.dirname(here)) raises(ValueError, convert, tempfile.mktemp()) def test_existing_file(self): convert = self.types.get('existing-file') eq = self.assertEqual raises = self.assertRaises eq(convert('.'), '.') eq(convert(here), here) raises(ValueError, convert, tempfile.mktemp()) def test_existing_path(self): convert = self.types.get('existing-path') eq = self.assertEqual raises = self.assertRaises eq(convert('.'), '.') eq(convert(here), here) eq(convert(os.path.dirname(here)), os.path.dirname(here)) raises(ValueError, convert, tempfile.mktemp()) def test_existing_dirpath(self): convert = self.types.get('existing-dirpath') eq = self.assertEqual raises = self.assertRaises eq(convert('.'), '.') eq(convert(here), here) raises(ValueError, convert, '/a/hopefully/nonexistent/path') raises(ValueError, convert, here + '/bogus') def test_byte_size(self): eq = self.assertEqual raises = self.assertRaises convert = self.types.get('byte-size') eq(convert('128'), 128) eq(convert('128KB'), 128*1024) eq(convert('128MB'), 128*1024*1024) eq(convert('128GB'), 128*1024*1024*1024L) raises(ValueError, convert, '128TB') eq(convert('128'), 128) eq(convert('128kb'), 128*1024) eq(convert('128mb'), 128*1024*1024) eq(convert('128gb'), 128*1024*1024*1024L) raises(ValueError, convert, '128tb') def test_time_interval(self): eq = self.assertEqual raises = self.assertRaises convert = self.types.get('time-interval') eq(convert('120'), 120) eq(convert('120S'), 120) eq(convert('120M'), 120*60) eq(convert('120H'), 120*60*60) eq(convert('120D'), 120*60*60*24) raises(ValueError, convert, '120W') eq(convert('120'), 120) eq(convert('120s'), 120) eq(convert('120m'), 120*60) eq(convert('120h'), 120*60*60) eq(convert('120d'), 120*60*60*24) raises(ValueError, convert, '120w') def test_timedelta(self): eq = self.assertEqual raises = self.assertRaises convert = self.types.get('timedelta') eq(convert('4w'), datetime.timedelta(weeks=4)) eq(convert('2d'), datetime.timedelta(days=2)) eq(convert('7h'), datetime.timedelta(hours=7)) eq(convert('12m'), datetime.timedelta(minutes=12)) eq(convert('14s'), datetime.timedelta(seconds=14)) eq(convert('4w 2d 7h 12m 14s'), datetime.timedelta(2, 14, minutes=12, hours=7, weeks=4)) class RegistryTestCase(unittest.TestCase): def test_registry_does_not_mask_toplevel_imports(self): old_sys_path = sys.path[:] tmpdir = tempfile.mkdtemp(prefix="test_datatypes_") fn = os.path.join(tmpdir, "datatypes.py") f = open(fn, "w") f.write(TEST_DATATYPE_SOURCE) f.close() registry = ZConfig.datatypes.Registry() # we really want the temp area to override everything else: sys.path.insert(0, tmpdir) try: datatype = registry.get("datatypes.my_sample_datatype") finally: shutil.rmtree(tmpdir) sys.path[:] = old_sys_path self.assertEqual(datatype, 42) TEST_DATATYPE_SOURCE = """ # sample datatypes file my_sample_datatype = 42 """ def test_suite(): suite = unittest.makeSuite(DatatypeTestCase) suite.addTest(unittest.makeSuite(RegistryTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZConfig/ZConfig/tests/test_schemaless.py0000644000175000017500000000146612214017462023245 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """\ Test driver for ZConfig.schemaless. """ __docformat__ = "reStructuredText" import doctest def test_suite(): return doctest.DocFileSuite("schemaless.txt", package="ZConfig") zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/0000755000175000017500000000000012214017462020635 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/base.xml0000644000175000017500000000024112214017462022266 0ustar arnauarnau base description zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/base-keytype2.xml0000644000175000017500000000014012214017462024036 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/simplesections.conf0000644000175000017500000000063112214017462024545 0ustar arnauarnauvar foo var-0 foo-0
var bar var-one splat
var-1 foo-1
var spam var-two stuff
var-2 foo-2
var quack! var-three yet
var-3 foo-3 # An anonymous empty section:
var-4 foo-4 # A fairly trivial section: var triv var-5 foo-5 # A minimal section: var-6 foo-6 zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/outer.conf0000644000175000017500000000007612214017462022645 0ustar arnauarnau%define outervar outer %include inner.conf refinner $innervar zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/include.conf0000644000175000017500000000007012214017462023124 0ustar arnauarnauvar2 value2 %include simple.conf var3 value3 var4 $name zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/base-datatype1.xml0000644000175000017500000000014212214017462024160 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/base-keytype1.xml0000644000175000017500000000010612214017462024037 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/simplesections.xml0000644000175000017500000000120112214017462024412 0ustar arnauarnau
zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/inner.conf0000644000175000017500000000005212214017462022614 0ustar arnauarnaurefouter $outervar %define innervar inner zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/logger.xml0000644000175000017500000000062412214017462022640 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/simple.conf0000644000175000017500000000067012214017462023000 0ustar arnauarnauempty var1 abc int-var 12 float-var 12.02 neg-int -2 true-var-1 true true-var-2 on true-var-3 yes false-var-1 false false-var-2 off false-var-3 no list-1 list-2 abc list-3 abc def ghi list-4 [ what now? ] # These test the %define mechanism: %define dollars $$$$ %define empty %define name value %define twowords two words getname $name getnametwice $name${name} getdollars $dollars getempty x${empty}y getwords abc $twowords def zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/simple.xml0000644000175000017500000000166712214017462022662 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/library.xml0000644000175000017500000000023512214017462023023 0ustar arnauarnau Sample library of reusable data types. zope2.13-2.13.21/source/ZConfig/ZConfig/tests/input/base-datatype2.xml0000644000175000017500000000014212214017462024161 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/tests/library/0000755000175000017500000000000012214017462021142 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/tests/library/__init__.py0000644000175000017500000000002712214017462023252 0ustar arnauarnau# Make this a package. zope2.13-2.13.21/source/ZConfig/ZConfig/tests/library/README.txt0000644000175000017500000000013012214017462022632 0ustar arnauarnauThis is a sample library of configuration schema components. This is used for testing. zope2.13-2.13.21/source/ZConfig/ZConfig/tests/library/widget/0000755000175000017500000000000012214017462022425 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/tests/library/widget/component.xml0000644000175000017500000000040012214017462025143 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/tests/library/widget/__init__.py0000644000175000017500000000002712214017462024535 0ustar arnauarnau# Make this a package. zope2.13-2.13.21/source/ZConfig/ZConfig/tests/library/widget/extra.xml0000644000175000017500000000014712214017462024274 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/tests/library/thing/0000755000175000017500000000000012214017462022253 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/tests/library/thing/component.xml0000644000175000017500000000053012214017462024775 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/tests/library/thing/extras/0000755000175000017500000000000012214017462023561 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/ZConfig/tests/library/thing/extras/extras.xml0000644000175000017500000000015012214017462025605 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/ZConfig/tests/library/thing/__init__.py0000644000175000017500000000150712214017462024367 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Example of a package that extends its __path__. $Id: __init__.py,v 1.2 2003/10/03 17:11:33 fdrake Exp $ """ import os here = os.path.dirname(__file__) __path__.append(os.path.join(here, "extras")) zope2.13-2.13.21/source/ZConfig/ZConfig/tests/__init__.py0000644000175000017500000000137212214017462021612 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for the configuration data structures and loader. $Id: __init__.py,v 1.2 2003/01/03 21:05:56 fdrake Exp $ """ zope2.13-2.13.21/source/ZConfig/ZConfig/tests/foosample.zip0000644000175000017500000000305512214017462022212 0ustar arnauarnauPKšfg3ÚÎÖI&&foo/__init__.pySVÉÈ,VHÉ,JM.É/ªTr*K2òó “³ÓSõ¸PK­yg3H1ÿ:Yzfoo/__init__.pycËýÄ˵t_¾s20±³‰†` ˆŸH(ÑI¥™9)úI)™Å%z9™y¥º™ffú©ééúiùùúññ™y™%ññz•% ƒìA‹APK²sg3ÚÎÖI&&foo/sample/__init__.pySVÉÈ,VHÉ,JM.É/ªTr*K2òó “³ÓSõ¸PK¨sg3Â÷Öj|foo/sample/datatypes.pyŒK Ã0 D÷:Å ´› S${#âI)ä‡,—røšÌò½Ç¨ê`ë±ÙÂâw° fÌ»#Xâ½½¢ª’RÞ§FW‹”ÐCCx¢:óÈo¨ˆdÎ×ÑícKåý)hs¶fƒžè NE‡ËÊPK‚sg3¶Ù‰(p§foo/sample/component.xmlMŽA1E÷=áÓ Ð¹K32Ú(ÐXÎí¥f4²€àñ¡Í¤›²:ô'ïíUp7[F•þ`\S Á›7S?:ƒVá‚çÚ,ðˆž û­é5(ˆ ;çú¥zE˜yÞ(¸|åø(Ì“ üçÆ”¯­é PK­yg3Br¾`foo/sample/__init__.pycËýÄËp5ß9™ ؈‹Y€D C°HÄO$‡F”‰¤ÒÌœý¤”Ìâ½œÌ¼Ò ÝL3 3ýÔôtý´ü|ýâÄÜ‚œTýøøÌ¼Ì’øx½‚Êyö ýÅ PK­yg3¥CwÍþpfoo/sample/datatypes.pycUÁjÃ0 †ådÍJ¡°Ó`7Ãä²z(»Œ zï!Ù©“Æn0$MˆåÑBŽ{ŽȞ`’i3èC²¤_î~–‹×ï~SÃåŠw ÷@Ð;¢€Á•èÊô†%„²ê†ÖH]a…çÁ8éÑòÐC{l²Þ‘n4%޾F?ýaNX‹Ë°ˆbÃÝæFM÷eÊ·[—'ùä䔲g„Ϫõ&”#…+pEØ{Ûê|¯­Ã¬µGz¶ë—unš&?ô}î‚ÛüÏm6œ‘Wá æ¢ðÛ4憷¥t_+…ËkN«u*U°$ø Y1ûg¦àdƒoœ9’ˆ$þPKšfg3ÚÎÖI&&¤foo/__init__.pyPK­yg3H1ÿ:Yz¤Sfoo/__init__.pycPK²sg3ÚÎÖI&&¤Úfoo/sample/__init__.pyPK¨sg3Â÷Öj|¤4foo/sample/datatypes.pyPK‚sg3¶Ù‰(p§¤Ófoo/sample/component.xmlPK­yg3Br¾`¤yfoo/sample/__init__.pycPK­yg3¥CwÍþp¤foo/sample/datatypes.pycPKÕBzope2.13-2.13.21/source/ZConfig/ZConfig/tests/test_schema.py0000644000175000017500000012304412214017462022353 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of ZConfig schemas.""" import unittest import ZConfig from ZConfig.tests.support import TestBase, CONFIG_BASE def uppercase(value): return str(value).upper() def appsection(value): return MySection(value) def get_foo(section): return section.foo class MySection: def __init__(self, value): self.conf = value def get_section_attributes(section): L = list(section.getSectionAttributes()) L.sort() return L class SchemaTestCase(TestBase): """Tests of the basic schema support itself.""" def test_minimal_schema(self): schema = self.load_schema_text("") self.assertEqual(len(schema), 0) self.assertRaises(IndexError, lambda schema=schema: schema[0]) self.assertRaises(ZConfig.ConfigurationError, schema.getinfo, "foo") def test_simple(self): schema, conf = self.load_both("simple.xml", "simple.conf") self._verifySimpleConf(conf) def _verifySimpleConf(self,conf): eq = self.assertEqual eq(conf.var1, 'abc') eq(conf.int_var, 12) eq(conf.float_var, 12.02) eq(conf.neg_int, -2) check = self.assert_ check(conf.true_var_1) check(conf.true_var_2) check(conf.true_var_3) check(not conf.false_var_1) check(not conf.false_var_2) check(not conf.false_var_3) def test_app_datatype(self): dtname = __name__ + ".uppercase" schema = self.load_schema_text("""\ abc abc not lower case """ % (dtname, dtname, dtname, dtname)) conf = self.load_config_text(schema, """\ a qwerty c upp c er c case """) eq = self.assertEqual eq(conf.a, 'QWERTY') eq(conf.b, 'ABC') eq(conf.c, ['UPP', 'ER', 'CASE']) eq(conf.d, ['NOT', 'LOWER', 'CASE']) eq(get_section_attributes(conf), ["a", "b", "c", "d"]) def test_app_sectiontype(self): schema = self.load_schema_text("""\
""" % __name__) conf = self.load_config_text(schema, """\ sample 42 """) self.assert_(isinstance(conf, MySection)) o1 = conf.conf.sect self.assert_(isinstance(o1, MySection)) self.assertEqual(o1.conf.sample, 42) def test_empty_sections(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\
""") self.assert_(conf.s1 is not None) self.assert_(conf.s2 is not None) self.assertEqual(get_section_attributes(conf), ["s1", "s2"]) def test_deeply_nested_sections(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ key sect3-value key sect2-value """) eq = self.assertEqual eq(conf.sect.sect.sect.key, "type1-value") eq(conf.sect.sect.key, "sect2-value") eq(conf.sect.key, "sect3-value") eq(get_section_attributes(conf), ["sect"]) eq(get_section_attributes(conf.sect), ["key", "sect"]) eq(get_section_attributes(conf.sect.sect), ["key", "sect"]) eq(get_section_attributes(conf.sect.sect.sect), ["key"]) def test_multivalued_keys(self): schema = self.load_schema_text("""\ 1 2 3 4 5 """) conf = self.load_config_text(schema, """\ a foo a bar c 41 c 42 c 43 """, num_handlers=2) L = [] self.handlers({'abc': L.append, 'DEF': L.append}) self.assertEqual(L, [['foo', 'bar'], conf]) L = [] self.handlers({'abc': None, 'DEF': L.append}) self.assertEqual(L, [conf]) self.assertEqual(conf.a, ['foo', 'bar']) self.assertEqual(conf.b, [1, 2]) self.assertEqual(conf.c, [41, 42, 43]) self.assertEqual(conf.d, []) self.assertEqual(get_section_attributes(conf), ["a", "b", "c", "d"]) def test_multikey_required(self): schema = self.load_schema_text("""\ """) self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_multisection_required(self): schema = self.load_schema_text("""\ """) self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_key_required_but_missing(self): schema = self.load_schema_text("""\ """) self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_section_required_but_missing(self): schema = self.load_schema_text("""\
""") self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_key_default_element(self): self.assertRaises( ZConfig.SchemaError, self.load_schema_text, """\ text """) def test_bad_handler_maps(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, """\ a foo b bar """, num_handlers=2) self.assertEqual(get_section_attributes(conf), ["a", "b"]) self.assertRaises(ZConfig.ConfigurationError, self.handlers, {'abc': id, 'ABC': id, 'def': id}) self.assertRaises(ZConfig.ConfigurationError, self.handlers, {}) def test_handler_ordering(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ """, num_handlers=3) L = [] self.handlers({'a': L.append, 'b': L.append, 'c': L.append}) outer = conf.sect_outer inner = outer.sect_inner self.assertEqual(L, [inner, outer, conf]) def test_duplicate_section_names(self): schema = self.load_schema_text("""\
""") self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, """\ """) conf = self.load_config_text(schema, """\ """) def test_disallowed_duplicate_attribute(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) def test_unknown_datatype_name(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, "") def test_load_abstracttype(self): schema = self.load_schema_text("""\ This is an abstract section type. """) # check the types that get defined t = schema.gettype("group") self.assert_(t.isabstract()) t1 = schema.gettype("t1") self.assert_(not t1.isabstract()) self.assert_(t.getsubtype("t1") is t1) t2 = schema.gettype("t2") self.assert_(not t2.isabstract()) self.assert_(t.getsubtype("t2") is t2) self.assertRaises(ZConfig.ConfigurationError, t.getsubtype, "group") self.assert_(t1 is not t2) # try loading a config that relies on this schema conf = self.load_config_text(schema, """\ k1 value1 k2 value2 """) eq = self.assertEqual eq(get_section_attributes(conf), ["g"]) eq(len(conf.g), 4) eq(conf.g[0].k1, "default1") eq(conf.g[1].k1, "value1") eq(conf.g[2].k2, "default2") eq(conf.g[3].k2, "value2") # white box: self.assert_(conf.g[0].getSectionDefinition() is t1) self.assert_(conf.g[1].getSectionDefinition() is t1) self.assert_(conf.g[2].getSectionDefinition() is t2) self.assert_(conf.g[3].getSectionDefinition() is t2) def test_abstracttype_extension(self): schema = self.load_schema_text("""\
""") abstype = schema.gettype("group") self.assert_(schema.gettype("extra") is abstype.getsubtype("extra")) # make sure we can use the extension in a config: conf = self.load_config_text(schema, "") self.assertEqual(conf.thing.getSectionType(), "extra") self.assertEqual(get_section_attributes(conf), ["thing"]) self.assertEqual(get_section_attributes(conf.thing), []) def test_abstracttype_extension_errors(self): # specifying a non-existant abstracttype self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) # specifying something that isn't an abstracttype self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) def test_arbitrary_key(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, "some-key 42") self.assertEqual(conf.keymap, {'some-key': 42}) self.assertEqual(get_section_attributes(conf), ["keymap"]) def test_arbitrary_multikey_required(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, """\ some-key 42 some-key 43 """) self.assertEqual(conf.keymap, {'some-key': [42, 43]}) def test_arbitrary_multikey_optional(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ some-key 42 some-key 43 """) self.assertEqual(conf.stuff.keymap, {'some-key': ['42', '43']}) self.assertEqual(get_section_attributes(conf), ["stuff"]) def test_arbitrary_multikey_optional_empty(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, "") self.assertEqual(conf.stuff.keymap, {}) def test_arbitrary_multikey_with_defaults(self): schema = self.load_schema_text("""\ value-a1 value-a2 value-b """) conf = self.load_config_text(schema, "") self.assertEqual(conf.keymap, {'a': ['value-a1', 'value-a2'], 'b': ['value-b']}) def test_arbitrary_multikey_with_unkeyed_default(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ value-a1 """) def test_arbitrary_key_with_defaults(self): schema = self.load_schema_text("""\ value-a value-b """) conf = self.load_config_text(schema, "") self.assertEqual(conf.keymap, {'a': 'value-a', 'b': 'value-b'}) def test_arbitrary_key_with_unkeyed_default(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ value-a1 """) def test_arbitrary_keys_with_others(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, """\ some-key 42 k2 3 """) self.assertEqual(conf.k1, 'v1') self.assertEqual(conf.k2, 3) self.assertEqual(conf.keymap, {'some-key': 42}) self.assertEqual(get_section_attributes(conf), ["k1", "k2", "keymap"]) def test_arbitrary_key_missing(self): schema = self.load_schema_text("""\ """) self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "# empty config file") def test_arbitrary_key_bad_schema(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) def test_getrequiredtypes(self): schema = self.load_schema("library.xml") self.assertEqual(schema.getrequiredtypes(), []) schema = self.load_schema_text("""\
""") L = schema.getrequiredtypes() L.sort() self.assertEqual(L, ["used"]) def test_getunusedtypes(self): schema = self.load_schema("library.xml") L = schema.getunusedtypes() L.sort() self.assertEqual(L, ["type-a", "type-b"]) schema = self.load_schema_text("""\
""") self.assertEqual(schema.getunusedtypes(), ["unused"]) def test_section_value_mutation(self): schema, conf = self.load_both("simple.xml", "simple.conf") orig = conf.empty new = [] conf.empty = new self.assert_(conf.empty is new) def test_simple_anonymous_section(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, "") self.assertEqual(conf.attr.key, "value") def test_simple_anonymous_section_without_name(self): # make sure we get the same behavior without name='*' schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, "") self.assertEqual(conf.attr.key, "value") def test_simple_anynamed_section(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, "") self.assertEqual(conf.attr.key, "value") self.assertEqual(conf.attr.getSectionName(), "name") # if we omit the name, it's an error self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "") def test_nested_abstract_sectiontype(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ """) def test_nested_abstract_sectiontype_without_name(self): # make sure we get the same behavior without name='*' schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ """) def test_reserved_attribute_prefix(self): template = """\ %s """ def check(thing, self=self, template=template): text = template % thing self.assertRaises(ZConfig.SchemaError, self.load_schema_text, text) check("") check("") check("") check("") check("
") check("
") check("") check("") def test_sectiontype_as_schema(self): schema = self.load_schema_text("""\
""") t = schema.gettype("t") conf = self.load_config_text(t, "") self.assertEqual(conf.tkey, "tkey-default") self.assertEqual(conf.section.skey, "skey-default") self.assertEqual(get_section_attributes(conf), ["section", "tkey"]) self.assertEqual(get_section_attributes(conf.section), ["skey"]) def test_datatype_conversion_error(self): schema_url = "file:///tmp/fake-url-1.xml" config_url = "file:///tmp/fake-url-2.xml" schema = self.load_schema_text("""\ """, url=schema_url) e = self.get_data_conversion_error( schema, "", config_url) self.assertEqual(e.url, schema_url) self.assertEqual(e.lineno, 2) e = self.get_data_conversion_error(schema, """\ # comment key splat """, config_url) self.assertEqual(e.url, config_url) self.assertEqual(e.lineno, 3) def get_data_conversion_error(self, schema, src, url): try: self.load_config_text(schema, src, url=url) except ZConfig.DataConversionError, e: return e else: self.fail("expected ZConfig.DataConversionError") def test_numeric_section_name(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, "") self.assertEqual(len(conf.things), 1) def test_sectiontype_extension(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ k1 k1-value k2 k2-value """) eq = self.assertEqual eq(conf.s.k1, "k1-value") eq(conf.s.k2, "k2-value") eq(get_section_attributes(conf), ["s"]) eq(get_section_attributes(conf.s), ["k1", "k2"]) def test_sectiontype_extension_errors(self): # cannot override key from base self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) # cannot extend non-existing section self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) # cannot extend abstract type self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) def test_sectiontype_derived_keytype(self): # make sure that a derived section type inherits the keytype # of its base schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ foo bar Foo BAR """) self.assertEqual(conf.foo.foo, "bar") self.assertEqual(conf.foo.Foo, "BAR") self.assertEqual(get_section_attributes(conf.foo), ["Foo", "foo"]) def test_sectiontype_override_keytype(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ ident1 foo Ident2 bar EXAMPLE.COM foo """) L = conf.base.map.items() L.sort() self.assertEqual(L, [("Ident2", "bar"), ("ident1", "foo")]) L = conf.derived.map.items() L.sort() self.assertEqual(L, [("example.com", "foo")]) self.assertEqual(get_section_attributes(conf), ["base", "derived"]) def test_keytype_applies_to_default_key(self): schema = self.load_schema_text("""\ 42 24
""") conf = self.load_config_text(schema, "") items = conf.sect.mapping.items() items.sort() self.assertEqual(items, [("bar", "24"), ("foo", "42")]) def test_duplicate_default_key_checked_in_schema(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ 42 24
""") def test_default_keys_rechecked_clash_in_derived_sectiontype(self): # If the default values associated with a can't # be supported by a new keytype for a derived sectiontype, an # error should be indicated. self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ 42 42
""") def test_default_keys_rechecked_dont_clash_in_derived_sectiontype(self): # If the default values associated with a can't # be supported by a new keytype for a derived sectiontype, an # error should be indicated. schema = self.load_schema_text("""\ 42 42
""") conf = self.load_config_text(schema, """\ """) base = conf.base.mapping.items() base.sort() self.assertEqual(base, [("Foo", ["42"]), ("foo", ["42"])]) sect = conf.sect.mapping.items() sect.sort() self.assertEqual(sect, [("foo", ["42", "42"])]) def test_sectiontype_inherited_datatype(self): schema = self.load_schema_text("""\
""") conf = self.load_config_text(schema, """\ foo bar """) self.assertEqual(conf.splat, "bar") def test_schema_keytype(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, "host.example.com 127.0.0.1\n" "www.example.org 127.0.0.2\n") table = conf.table self.assertEqual(len(table), 2) L = table.items() L.sort() self.assertEqual(L, [("host.example.com", "127.0.0.1"), ("www.example.org", "127.0.0.2")]) self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "abc. 127.0.0.1") def test_keytype_identifier(self): schema = self.load_schema_text("""\ """) conf = self.load_config_text(schema, "Foo Foo-value\n" "foo foo-value\n") self.assertEqual(conf.foo, "foo-value") self.assertEqual(conf.Foo, "Foo-value") self.assertEqual(get_section_attributes(conf), ["Foo", "foo"]) # key mis-match based on case: self.assertRaises(ZConfig.ConfigurationError, self.load_config_text, schema, "FOO frob\n") # attribute names conflict, since the keytype isn't used to # generate attribute names self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """) def test_datatype_casesensitivity(self): self.load_schema_text("") def test_simple_extends(self): schema = self.load_schema_text("""\
""" % (CONFIG_BASE, CONFIG_BASE)) self._verifySimpleConf(self.load_config(schema, "simple.conf")) def test_extends_fragment_failure(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, "" % CONFIG_BASE) def test_extends_description_override(self): schema = self.load_schema_text("""\ overriding description
""" % (CONFIG_BASE, CONFIG_BASE)) description = schema.description.strip() self.assertEqual(description, "overriding description") def test_extends_description_first_extended_wins(self): schema = self.load_schema_text("""\
""" % (CONFIG_BASE, CONFIG_BASE)) description = schema.description.strip() self.assertEqual(description, "base description") def test_multi_extends_implicit_OK(self): self.load_schema_text("""\
""" % (CONFIG_BASE, CONFIG_BASE)) def test_multi_extends_explicit_datatype_OK(self): self.load_schema_text("""\
""" % (CONFIG_BASE, CONFIG_BASE)) def test_multi_extends_explicit_keytype_OK(self): self.load_schema_text("""\
""" % (CONFIG_BASE, CONFIG_BASE, __name__)) def test_multi_extends_datatype_conflict(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """ % (CONFIG_BASE, CONFIG_BASE)) def test_multi_extends_keytype_conflict(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ """ % (CONFIG_BASE, CONFIG_BASE)) def test_multiple_descriptions_is_error(self): self.assertRaises(ZConfig.SchemaError, self.load_schema_text, """\ foo bar """) def test_suite(): return unittest.makeSuite(SchemaTestCase) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/ZConfig/ZConfig/tests/test_readme.py0000644000175000017500000000217512214017462022351 0ustar arnauarnau############################################################################## # # Copyright (c) 2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import doctest import logging options = doctest.REPORT_NDIFF | doctest.ELLIPSIS old = {} def setUp(test): global old logger = logging.getLogger() old['level'] = logger.level old['handlers'] = logger.handlers[:] def tearDown(test): logger = logging.getLogger() logger.level = old['level'] logger.handlers = old['handlers'] def test_suite(): return doctest.DocFileSuite( '../../README.txt', optionflags=options, setUp=setUp, tearDown=tearDown, ) zope2.13-2.13.21/source/ZConfig/ZConfig/substitution.py0000644000175000017500000000533712214017462021472 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Substitution support for ZConfig values.""" import ZConfig def substitute(s, mapping): """Interpolate variables from `mapping` into `s`.""" if "$" in s: result = '' rest = s while rest: p, name, namecase, rest = _split(rest) result += p if name: v = mapping.get(name) if v is None: raise ZConfig.SubstitutionReplacementError(s, namecase) result += v return result else: return s def isname(s): """Return True iff s is a valid substitution name.""" m = _name_match(s) if m: return m.group() == s else: return False def _split(s): # Return a four tuple: prefix, name, namecase, suffix # - prefix is text that can be used literally in the result (may be '') # - name is a referenced name, or None # - namecase is the name with case preserved # - suffix is trailling text that may contain additional references # (may be '' or None) if "$" in s: i = s.find("$") c = s[i+1:i+2] if c == "": raise ZConfig.SubstitutionSyntaxError( "illegal lone '$' at end of source") if c == "$": return s[:i+1], None, None, s[i+2:] prefix = s[:i] if c == "{": m = _name_match(s, i + 2) if not m: raise ZConfig.SubstitutionSyntaxError( "'${' not followed by name") name = m.group(0) i = m.end() + 1 if not s.startswith("}", i - 1): raise ZConfig.SubstitutionSyntaxError( "'${%s' not followed by '}'" % name) else: m = _name_match(s, i+1) if not m: raise ZConfig.SubstitutionSyntaxError( "'$' not followed by '$' or name") name = m.group(0) i = m.end() return prefix, name.lower(), name, s[i:] else: return s, None, None, None import re _name_match = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*").match del re zope2.13-2.13.21/source/ZConfig/ZConfig/url.py0000644000175000017500000000406712214017462017517 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """urlparse-like helpers that normalize file: URLs. ZConfig and urllib2 expect file: URLs to consistently use the '//' hostpart seperator; the functions here enforce this constraint. """ import urlparse as _urlparse try: from urlparse import urlsplit except ImportError: def urlsplit(url): # Check for the fragment here, since Python 2.1.3 didn't get # it right for things like "http://www.python.org#frag". if '#' in url: url, fragment = url.split('#', 1) else: fragment = '' parts = list(_urlparse.urlparse(url)) parts[-1] = fragment param = parts.pop(3) if param: parts[2] += ";" + param return tuple(parts) def urlnormalize(url): lc = url.lower() if lc.startswith("file:/") and not lc.startswith("file:///"): url = "file://" + url[5:] return url def urlunsplit(parts): parts = list(parts) parts.insert(3, '') url = _urlparse.urlunparse(tuple(parts)) if (parts[0] == "file" and url.startswith("file:/") and not url.startswith("file:///")): url = "file://" + url[5:] return url def urldefrag(url): url, fragment = _urlparse.urldefrag(url) return urlnormalize(url), fragment def urljoin(base, relurl): url = _urlparse.urljoin(base, relurl) if url.startswith("file:/") and not url.startswith("file:///"): url = "file://" + url[5:] return url zope2.13-2.13.21/source/ZConfig/LICENSE.txt0000644000175000017500000000402612214017462016622 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/ZConfig/README.txt0000644000175000017500000000606112214017462016476 0ustar arnauarnauZConfig: Schema-driven configuration ==================================== ZConfig is a configuration library intended for general use. It supports a hierarchical schema-driven configuration model that allows a schema to specify data conversion routines written in Python. ZConfig's model is very different from the model supported by the ConfigParser module found in Python's standard library, and is more suitable to configuration-intensive applications. ZConfig schema are written in an XML-based language and are able to "import" schema components provided by Python packages. Since components are able to bind to conversion functions provided by Python code in the package (or elsewhere), configuration objects can be arbitrarily complex, with values that have been verified against arbitrary constraints. This makes it easy for applications to separate configuration support from configuration loading even with configuration data being defined and consumed by a wide range of separate packages. ZConfig is licensed under the Zope Public License, version 2.1. See the file LICENSE.txt in the distribution for the full license text. Reference documentation is available in the doc/ directory. One common use of ZConfig is to configure the Python logging framework. This is extremely simple to do as the following example demonstrates: >>> from ZConfig import configureLoggers >>> configureLoggers(''' ... ... level INFO ... ... PATH STDOUT ... format %(levelname)s %(name)s %(message)s ... ... ... ''') The above configures the root logger to output messages logged at INFO or above to the console, as we can see in the following example: >>> from logging import getLogger >>> logger = getLogger() >>> logger.info('An info message') INFO root An info message >>> logger.debug('A debug message') A more common configuration would see STDOUT replaced with a path to the file into which log entries would be written. For more information, see section 5.2 on the ZConfig documentation and the examples in ZConfig/components/logger/tests. Information on the latest released version of the ZConfig package is available at http://www.zope.org/Members/fdrake/zconfig/ You may either create an RPM and install this, or install directly from the source distribution. There is a mailing list for discussions and questions about ZConfig; more information on the list is available at http://mail.zope.org/mailman/listinfo/zconfig/ Installing from the source distribution --------------------------------------- For a simple installation:: python setup.py install To install to a user's home-dir:: python setup.py install --home= To install to another prefix (for example, /usr/local):: python setup.py install --prefix=/usr/local If you need to force the python interpreter to (for example) python2:: python2 setup.py install For more information on installing packages, please refer to `Installing Python Modules `__. zope2.13-2.13.21/source/ZConfig/setup.cfg0000644000175000017500000000023112214017462016612 0ustar arnauarnau[bdist_rpm] doc_files = LICENSE.txt NEWS.txt README.txt doc/schema.dtd doc/zconfig.pdf [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/ZConfig/doc/0000755000175000017500000000000012214017462015542 5ustar arnauarnauzope2.13-2.13.21/source/ZConfig/doc/xmlmarkup.sty0000644000175000017500000000256412214017462020332 0ustar arnauarnau%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Copyright (c) 2003 Zope Foundation and Contributors. % All Rights Reserved. % % This software is subject to the provisions of the Zope Public License, % Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. % THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED % WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED % WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS % FOR A PARTICULAR PURPOSE. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Define some simple markup for the LaTeX command documentation: \ProvidesPackage{xmlmarkup} \RequirePackage{python} % fulllineitems environment \newcommand{\element}[1]{\code{#1}} \newcommand{\attribute}[1]{\code{#1}} % \begin{elementdesc}{type}{content-model} \newenvironment{elementdesc}[2]{ \begin{fulllineitems} \item[\code{\textless{\bfseries #1}\textgreater}] \code{#2} \item[\code{\textless/{\bfseries #1}\textgreater}] \index{#1 element@\py@idxcode{#1} element} \index{elements!#1@\py@idxcode{#1}} }{\end{fulllineitems}} % \begin{attributedesc}{name}{content-type} \newenvironment{attributedesc}[2]{ \begin{fulllineitems} \item[\code{\bfseries#1}{\quad(#2)}] \index{#1@\py@idxcode{#1}} }{\end{fulllineitems}} zope2.13-2.13.21/source/ZConfig/doc/zconfig.tex0000644000175000017500000022327212214017462017733 0ustar arnauarnau%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Copyright (c) 2002-2007 Zope Foundation and Contributors. % All Rights Reserved. % % This software is subject to the provisions of the Zope Public License, % Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. % THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED % WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED % WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS % FOR A PARTICULAR PURPOSE. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \documentclass{howto} \usepackage{xmlmarkup} \newcommand{\datatype}[1]{\strong{#1}} \title{ZConfig Package Reference} \date{13 April 2010} \release{2.8.0} \setshortversion{2.8} \author{Zope Corporation} \authoraddress{\url{http://www.zope.com/}} \begin{document} \maketitle \begin{abstract} \noindent This document describes the syntax and API used in configuration files for components of a Zope installation written by Zope Corporation. This configuration mechanism is itself configured using a schema specification written in XML. \end{abstract} \tableofcontents \section{Introduction \label{intro}} Zope uses a common syntax and API for configuration files designed for software components written by Zope Corporation. Third-party software which is also part of a Zope installation may use a different syntax, though any software is welcome to use the syntax used by Zope Corporation. Any software written in Python is free to use the \module{ZConfig} software to load such configuration files in order to ensure compatibility. This software is covered by the Zope Public License, version 2.1. The \module{ZConfig} package has been tested with Python 2.3. Older versions of Python are not supported. \module{ZConfig} only relies on the Python standard library. Configurations which use \module{ZConfig} are described using \dfn{schema}. A schema is a specification for the allowed structure and content of the configuration. \module{ZConfig} schema are written using a small XML-based language. The schema language allows the schema author to specify the names of the keys allowed at the top level and within sections, to define the types of sections which may be used (and where), the types of each values, whether a key or section must be specified or is optional, default values for keys, and whether a value can be given only once or repeatedly. \section{Configuration Syntax \label{syntax}} Like the \ulink{\module{ConfigParser}} {http://docs.python.org/library/configparser.html} format, this format supports key-value pairs arranged in sections. Unlike the \module{ConfigParser} format, sections are typed and can be organized hierarchically. Additional files may be included if needed. Schema components not specified in the application schema can be imported from the configuration file. Though both formats are substantially line-oriented, this format is more flexible. The intent of supporting nested section is to allow setting up the configurations for loosely-associated components in a container. For example, each process running on a host might get its configuration section from that host's section of a shared configuration file. The top level of a configuration file consists of a series of inclusions, key-value pairs, and sections. Comments can be added on lines by themselves. A comment has a \character{\#} as the first non-space character and extends to the end of the line: \begin{verbatim} # This is a comment \end{verbatim} An inclusion is expressed like this: \begin{verbatim} %include defaults.conf \end{verbatim} The resource to be included can be specified by a relative or absolute URL, resolved relative to the URL of the resource the \keyword{\%include} directive is located in. A key-value pair is expressed like this: \begin{verbatim} key value \end{verbatim} The key may include any non-white characters except for parentheses. The value contains all the characters between the key and the end of the line, with surrounding whitespace removed. Since comments must be on lines by themselves, the \character{\#} character can be part of a value: \begin{verbatim} key value # still part of the value \end{verbatim} Sections may be either empty or non-empty. An empty section may be used to provide an alias for another section. A non-empty section starts with a header, contains configuration data on subsequent lines, and ends with a terminator. The header for a non-empty section has this form (square brackets denote optional parts): \begin{alltt} <\var{section-type} \optional{\var{name}} > \end{alltt} \var{section-type} and \var{name} all have the same syntactic constraints as key names. The terminator looks like this: \begin{alltt} \end{alltt} The configuration data in a non-empty section consists of a sequence of one or more key-value pairs and sections. For example: \begin{verbatim} key-1 value-1 key-2 value-2 key-3 value-3 \end{verbatim} (The indentation is used here for clarity, but is not required for syntactic correctness.) The header for empty sections is similar to that of non-empty sections, but there is no terminator: \begin{alltt} <\var{section-type} \optional{\var{name}} /> \end{alltt} \subsection{Extending the Configuration Schema} As we'll see in section~\ref{writing-schema}, ``Writing Configuration Schema,'' what can be written in a configuration is controlled by schemas which can be built from \emph{components}. These components can also be used to extend the set of implementations of objects the application can handle. What this means when writing a configuration is that third-party implementations of application object types can be used wherever those application types are used in the configuration, if there's a \module{ZConfig} component available for that implementation. The configuration file can use an \keyword{\%import} directive to load a named component: \begin{verbatim} %import Products.Ape \end{verbatim} The text to the right of the \keyword{\%import} keyword must be the name of a Python package; the \module{ZConfig} component provided by that package will be loaded and incorporated into the schema being used to load the configuration file. After the import, section types defined in the component may be used in the configuration. More detail is needed for this to really make sense. A schema may define section types which are \emph{abstract}; these cannot be used directly in a configuration, but multiple concrete section types can be defined which \emph{implement} the abstract types. Wherever the application allows an abstract type to be used, any concrete type which implements that abstract type can be used in an actual configuration. The \keyword{\%import} directive allows loading schema components which provide alternate concrete section types which implement the abstract types defined by the application. This allows third-party implementations of abstract types to be used in place of or in addition to implementations provided with the application. Consider an example application application which supports logging in the same way Zope 2 does. There are some parameters which configure the general behavior of the logging mechanism, and an arbitrary number of \emph{log handlers} may be specified to control how the log messages are handled. Several log handlers are provided by the application. Here is an example logging configuration: \begin{verbatim} level verbose path /var/log/myapp/events.log \end{verbatim} A third-party component may provide a log handler to send high-priority alerts the system administrator's text pager or SMS-capable phone. All that's needed is to install the implementation so it can be imported by Python, and modify the configuration: \begin{verbatim} %import my.pager.loghandler level verbose path /var/log/myapp/events.log number 1-800-555-1234 message Something broke! \end{verbatim} \subsection{Textual Substitution in Values} \module{ZConfig} provides a limited way to re-use portions of a value using simple string substitution. To use this facility, define named bits of replacement text using the \keyword{\%define} directive, and reference these texts from values. The syntax for \keyword{\%define} is: \begin{alltt} %define \var{name} \optional{\var{value}} \end{alltt} The value of \var{name} must be a sequence of letters, digits, and underscores, and may not start with a digit; the namespace for these names is separate from the other namespaces used with \module{ZConfig}, and is case-insensitive. If \var{value} is omitted, it will be the empty string. If given, there must be whitespace between \var{name} and \var{value}; \var{value} will not include any whitespace on either side, just like values from key-value pairs. Names must be defined before they are used, and may not be re-defined with a different value. All resources being parsed as part of a configuration share a single namespace for defined names. References to defined names from configuration values use the syntax described for the \refmodule{ZConfig.substitution} module. Configuration values which include a \character{\$} as part of the actual value will need to use \code{\$\$} to get a single \character{\$} in the result. The values of defined names are processed in the same way as configuration values, and may contain references to named definitions. For example, the value for \code{key} will evaluate to \code{value}: \begin{verbatim} %define name value key $name \end{verbatim} %$ <-- bow to font-lock \section{Writing Configuration Schema \label{writing-schema}} \module{ZConfig} schema are written as XML documents. Data types are searched in a special namespace defined by the data type registry. The default registry has slightly magical semantics: If the value can be matched to a standard data type when interpreted as a \datatype{basic-key}, the standard data type will be used. If that fails, the value must be a \datatype{dotted-name} containing at least one dot, and a conversion function will be sought using the \method{search()} method of the data type registry used to load the schema. \subsection{Schema Elements \label{elements}} For each element, the content model is shown, followed by a description of how the element is used, and then a list of the available attributes. For each attribute, the type of the value is given as either the name of a \module{ZConfig} datatype or an XML attribute value type. Familiarity with XML's Document Type Definition language is helpful. The following elements are used to describe a schema: \begin{elementdesc}{schema}{description?, metadefault?, example?, import*, (sectiontype | abstracttype)*, (section | key | multisection | multikey)*} Document element for a \module{ZConfig} schema. \begin{attributedesc}{extends}{\datatype{space-separated-url-references}} A list of URLs of base schemas from which this section type will inherit key, section, and section type declarations. If omitted, this schema is defined using only the keys, sections, and section types contained within the \element{schema} element. \end{attributedesc} \begin{attributedesc}{datatype}{\datatype{basic-key} or \datatype{dotted-name}} The data type converter which will be applied to the value of this section. If the value is a \datatype{dotted-name} that begins with a period, the value of \attribute{prefix} will be pre-pended, if set. If any base schemas are listed in the \attribute{extends} attribute, the default value for this attribute comes from the base schemas. If the base schemas all use the same \attribute{datatype}, then that data type will be the default value for the extending schema. If there are no base schemas, the default value is \datatype{null}, which means that the \module{ZConfig} section object will be used unconverted. If the base schemas have different \attribute{datatype} definitions, you must explicitly define the \attribute{datatype} in the extending schema. \end{attributedesc} \begin{attributedesc}{handler}{\datatype{basic-key}} \end{attributedesc} \begin{attributedesc}{keytype}{\datatype{basic-key} or \datatype{dotted-name}} The data type converter which will be applied to keys found in this section. This can be used to constrain key values in different ways; two data types which may be especially useful are the \datatype{identifier} and \datatype{ipaddr-or-hostname} types. If the value is a \datatype{dotted-name} that begins with a period, the value of \attribute{prefix} will be pre-pended, if set. If any base schemas are listed in the \attribute{extends} attribute, the default value for this attribute comes from the base schemas. If the base schemas all use the same \attribute{keytype}, then that key type will be the default value for the extending schema. If there are no base schemas, the default value is \datatype{basic-key}. If the base schemas have different \attribute{keytype} definitions, you must explicitly define the \attribute{keytype} in the extending schema. \end{attributedesc} \begin{attributedesc}{prefix}{\datatype{dotted-name}} Prefix to be pre-pended in front of partial dotted-names that start with a period. The value of this attribute is used in all contexts with the \element{schema} element if it hasn't been overridden by an inner element with a \attribute{prefix} attribute. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{description}{PCDATA} Descriptive text explaining the purpose the container of the \element{description} element. Most other elements can contain a \element{description} element as their first child. At most one \element{description} element may appear in a given context. \begin{attributedesc}{format}{NMTOKEN} Optional attribute that can be added to indicate what conventions are used to mark up the contained text. This is intended to serve as a hint for documentation extraction tools. Suggested values are: \begin{tableii}{l|l}{code}{Value}{Content Format} \lineii{plain}{\mimetype{text/plain}; blank lines separate paragraphs} \lineii{rest}{reStructuredText} \lineii{stx}{Classic Structured Text} \end{tableii} \end{attributedesc} \end{elementdesc} \begin{elementdesc}{example}{PCDATA} An example value. This serves only as documentation. \end{elementdesc} \begin{elementdesc}{metadefault}{PCDATA} A description of the default value, for human readers. This may include information about how a computed value is determined when the schema does not specify a default value. \end{elementdesc} \begin{elementdesc}{abstracttype}{description?} Define an abstract section type. \begin{attributedesc}{name}{\datatype{basic-key}} The name of the abstract section type; required. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{sectiontype}{description?, (section | key | multisection | multikey)*} Define a concrete section type. \begin{attributedesc}{datatype}{\datatype{basic-key} or \datatype{dotted-name}} The data type converter which will be applied to the value of this section. If the value is a \datatype{dotted-name} that begins with a period, the value of \attribute{prefix} will be pre-pended, if set. If \attribute{datatype} is omitted and \attribute{extends} is used, the \attribute{datatype} from the section type identified by the \attribute{extends} attribute is used. \end{attributedesc} \begin{attributedesc}{extends}{\datatype{basic-key}} The name of a concrete section type from which this section type acquires all key and section declarations. This type does \emph{not} automatically implement any abstract section type implemented by the named section type. If omitted, this section is defined with only the keys and sections contained within the \element{sectiontype} element. The new section type is called a \emph{derived} section type, and the type named by this attribute is called the \emph{base} type. Values for the \attribute{datatype} and \attribute{keytype} attributes are acquired from the base type if not specified. \end{attributedesc} \begin{attributedesc}{implements}{\datatype{basic-key}} The name of an abstract section type which this concrete section type implements. If omitted, this section type does not implement any abstract type, and can only be used if it is specified directly in a schema or other section type. \end{attributedesc} \begin{attributedesc}{keytype}{\datatype{basic-key}} The data type converter which will be applied to keys found in this section. This can be used to constrain key values in different ways; two data types which may be especially useful are the \datatype{identifier} and \datatype{ipaddr-or-hostname} types. If the value is a \datatype{dotted-name} that begins with a period, the value of \attribute{prefix} will be pre-pended, if set. The default value is \datatype{basic-key}. If \attribute{keytype} is omitted and \attribute{extends} is used, the \attribute{keytype} from the section type identified by the \attribute{extends} attribute is used. \end{attributedesc} \begin{attributedesc}{name}{\datatype{basic-key}} The name of the section type; required. \end{attributedesc} \begin{attributedesc}{prefix}{\datatype{dotted-name}} Prefix to be pre-pended in front of partial dotted-names that start with a period. The value of this attribute is used in all contexts in the \element{sectiontype} element. If omitted, the prefix specified by a containing context is used if specified. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{import}{EMPTY} Import a schema component. Exactly one of the attributes \attribute{package} and \attribute{src} must be specified. \begin{attributedesc}{file}{file name without directory information} Name of the component file within a package; if not specified, \file{component.xml} is used. This may only be given when \attribute{package} is used. (The \file{component.xml} file is always used when importing via \keyword{\%import} from a configuration file.) \end{attributedesc} \begin{attributedesc}{package}{\datatype{dotted-suffix}} Name of a Python package that contains the schema component being imported. The component will be loaded from the file identified by the \attribute{file} attribute, or \file{component.xml} if \attribute{file} is not specified. If the package name given starts with a dot (\character{.}), the name used will be the current prefix and the value of this attribute concatenated. \end{attributedesc} \begin{attributedesc}{src}{\datatype{url-reference}} URL to a separate schema which can provide useful types. The referenced resource must contain a schema, not a schema component. Section types defined or imported by the referenced schema are added to the schema containing the \element{import}; top-level keys and sections are ignored. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{key}{description?, example?, metadefault?, default*} A \element{key} element is used to describe a key-value pair which may occur at most once in the section type or top-level schema in which it is listed. \begin{attributedesc}{attribute}{\datatype{identifier}} The name of the Python attribute which this key should be the value of on a \class{SectionValue} instance. This must be unique within the immediate contents of a section type or schema. If this attribute is not specified, an attribute name will be computed by converting hyphens in the key name to underscores. \end{attributedesc} \begin{attributedesc}{datatype}{\datatype{basic-key} or \datatype{dotted-name}} The data type converter which will be applied to the value of this key. If the value is a \datatype{dotted-name} that begins with a period, the value of \attribute{prefix} will be pre-pended, if set. \end{attributedesc} \begin{attributedesc}{default}{\datatype{string}} If the key-value pair is optional and this attribute is specified, the value of this attribute will be converted using the appropriate data type converter and returned to the application as the configured value. This attribute may not be specified if the \attribute{required} attribute is \code{yes}. \end{attributedesc} \begin{attributedesc}{handler}{\datatype{basic-key}} \end{attributedesc} \begin{attributedesc}{name}{\datatype{basic-key}} The name of the key, as it must be given in a configuration instance, or `\code{*}'. If the value is `\code{*}', any name not already specified as a key may be used, and the configuration value for the key will be a dictionary mapping from the key name to the value. In this case, the \attribute{attribute} attribute must be specified, and the data type for the key will be applied to each key which is found. \end{attributedesc} \begin{attributedesc}{required}{\code{yes|no}} Specifies whether the configuration instance is required to provide the key. If the value is \code{yes}, the \attribute{default} attribute may not be specified and an error will be reported if the configuration instance does not specify a value for the key. If the value is \code{no} (the default) and the configuration instance does not specify a value, the value reported to the application will be that specified by the \attribute{default} attribute, if given, or \code{None}. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{multikey}{description?, example?, metadefault?, default*} A \element{multikey} element is used to describe a key-value pair which may occur any number of times in the section type or top-level schema in which it is listed. \begin{attributedesc}{attribute}{\datatype{identifier}} The name of the Python attribute which this key should be the value of on a \class{SectionValue} instance. This must be unique within the immediate contents of a section type or schema. If this attribute is not specified, an attribute name will be computed by converting hyphens in the key name to underscores. \end{attributedesc} \begin{attributedesc}{datatype}{\datatype{basic-key} or \datatype{dotted-name}} The data type converter which will be applied to the value of this key. If the value is a \datatype{dotted-name} that begins with a period, the value of \attribute{prefix} will be pre-pended, if set. \end{attributedesc} \begin{attributedesc}{handler}{\datatype{basic-key}} \end{attributedesc} \begin{attributedesc}{name}{\datatype{basic-key}} The name of the key, as it must be given in a configuration instance, or `\code{+}'. If the value is `\code{+}', any name not already specified as a key may be used, and the configuration value for the key will be a dictionary mapping from the key name to the value. In this case, the \attribute{attribute} attribute must be specified, and the data type for the key will be applied to each key which is found. \end{attributedesc} \begin{attributedesc}{required}{\code{yes|no}} Specifies whether the configuration instance is required to provide the key. If the value is \code{yes}, no \element{default} elements may be specified and an error will be reported if the configuration instance does not specify at least one value for the key. If the value is \code{no} (the default) and the configuration instance does not specify a value, the value reported to the application will be a list containing one element for each \element{default} element specified as a child of the \element{multikey}. Each value will be individually converted according to the \attribute{datatype} attribute. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{default}{PCDATA} Each \element{default} element specifies a single default value for a \element{multikey}. This element can be repeated to produce a list of individual default values. The text contained in the element will be passed to the datatype conversion for the \element{multikey}. \begin{attributedesc}{key}{key type of the containing sectiontype} Key to associate with the default value. This is only used for defaults of a \element{key} or \element{multikey} with a \attribute{name} of \code{+}; in that case this attribute is required. It is an error to use the \attribute{key} attribute with a \element{default} element for a \element{multikey} with a name other than \code{+}. \begin{notice}[warning] The datatype of this attribute is that of the section type \emph{containing} the actual keys, not necessarily that of the section type which defines the key. If a derived section overrides the key type of the base section type, the actual key type used is that of the derived section. This can lead to confusing errors in schemas, though the \refmodule{ZConfig} package checks for this when the schema is loaded. This situation is particularly likely when a derived section type uses a key type which collapses multiple default keys which were not collapsed by the base section type. Consider this example schema: \begin{verbatim} some value some value
\end{verbatim} When this schema is loaded, a set of defaults for the \datatype{derived} section type is computed. Since \datatype{basic-key} is case-insensitive (everything is converted to lower case), \samp{foo} and \samp{Foo} are both converted to \samp{foo}, which clashes since \element{key} only allows one value for each key. \end{notice} \end{attributedesc} \end{elementdesc} \begin{elementdesc}{section}{description?} A \element{section} element is used to describe a section which may occur at most once in the section type or top-level schema in which it is listed. \begin{attributedesc}{attribute}{\datatype{identifier}} The name of the Python attribute which this section should be the value of on a \class{SectionValue} instance. This must be unique within the immediate contents of a section type or schema. If this attribute is not specified, an attribute name will be computed by converting hyphens in the section name to underscores, in which case the \attribute{name} attribute may not be \code{*} or \code{+}. \end{attributedesc} \begin{attributedesc}{handler}{\datatype{basic-key}} \end{attributedesc} \begin{attributedesc}{name}{\datatype{basic-key}} The name of the section, as it must be given in a configuration instance, \code{*}, or \code{+}. If the value is \code{*} or this attribute is omitted, any name not already specified as a key may be used. If the value is \code{*} or \code{+}, the \attribute{attribute} attribute must be specified. If the value is \code{*}, any name is allowed, or the name may be omitted. If the value is \code{+}, any name is allowed, but some name must be provided. \end{attributedesc} \begin{attributedesc}{required}{\code{yes|no}} Specifies whether the configuration instance is required to provide the section. If the value is \code{yes}, an error will be reported if the configuration instance does not include the section. If the value is \code{no} (the default) and the configuration instance does not include the section, the value reported to the application will be \code{None}. \end{attributedesc} \begin{attributedesc}{type}{\datatype{basic-key}} The section type which matching sections must implement. If the value names an abstract section type, matching sections in the configuration file must be of a type which specifies that it implements the named abstract type. If the name identifies a concrete type, the section type must match exactly. \end{attributedesc} \end{elementdesc} \begin{elementdesc}{multisection}{description?} A \element{multisection} element is used to describe a section which may occur any number of times in the section type or top-level schema in which it is listed. \begin{attributedesc}{attribute}{\datatype{identifier}} The name of the Python attribute which matching sections should be the value of on a \class{SectionValue} instance. This is required and must be unique within the immediate contents of a section type or schema. The \class{SectionValue} instance will contain a list of matching sections. \end{attributedesc} \begin{attributedesc}{handler}{\datatype{basic-key}} \end{attributedesc} \begin{attributedesc}{name}{\datatype{basic-key}} For a \element{multisection}, any name not already specified as a key may be used. If the value is \code{*} or \code{+}, the \attribute{attribute} attribute must be specified. If the value is \code{*} or this attribute is omitted, any name is allowed, or the name may be omitted. If the value is \code{+}, any name is allowed, but some name must be provided. No other value for the \attribute{name} attribute is allowed for a \element{multisection}. \end{attributedesc} \begin{attributedesc}{required}{\code{yes|no}} Specifies whether the configuration instance is required to provide at least one matching section. If the value is \code{yes}, an error will be reported if the configuration instance does not include the section. If the value is \code{no} (the default) and the configuration instance does not include the section, the value reported to the application will be \code{None}. \end{attributedesc} \begin{attributedesc}{type}{\datatype{basic-key}} The section type which matching sections must implement. If the value names an abstract section type, matching sections in the configuration file must be of types which specify that they implement the named abstract type. If the name identifies a concrete type, the section type must match exactly. \end{attributedesc} \end{elementdesc} \subsection{Schema Components \label{schema-components}} XXX need more explanation \module{ZConfig} supports schema components that can be provided by disparate components, and allows them to be knit together into concrete schema for applications. Components cannot add additional keys or sections in the application schema. A schema \dfn{component} is allowed to define new abstract and section types. Components are identified using a dotted-name, similar to a Python module name. For example, one component may be \code{zodb.storage}. Schema components are stored alongside application code since they directly reference datatype code. Schema components are provided by Python packages. The component definition is normally stored in the file \file{component.xml}; an alternate filename may be specified using the \attribute{file} attribute of the \element{import} element. Components imported using the \keyword{\%import} keyword from a configuration file must be named \file{component.xml}. The component defines the types provided by that component; it must have a \element{component} element as the document element. The following element is used as the document element for schema components. Note that schema components do not allow keys and sections to be added to the top-level of a schema; they serve only to provide type definitions. \begin{elementdesc}{component}{description?, (abstracttype | sectiontype)*} The top-level element for schema components. \begin{attributedesc}{prefix}{\datatype{dotted-name}} Prefix to be pre-pended in front of partial dotted-names that start with a period. The value of this attribute is used in all contexts within the \element{component} element if it hasn't been overridden by an inner element with a \attribute{prefix} attribute. \end{attributedesc} \end{elementdesc} \subsection{Referring to Files in Packages} The \attribute{extends} attribute of the \element{schema} element is used to refer to files containing base schema; sometimes it makes sense to refer to a base schema relative to the Python package that provides it. For this purpose, ZConfig supports the special \code{package:} URL scheme. The \code{package:} URL scheme is straightforward, and contains three parts: the scheme name, the package name, and a relative path. The relative path is searched for using the named package's \code{__path__} if it's a conventional filesystem package, or using the package's loader if that supports resource access (such as the loader for eggs and other ZIP-file based packages). The basic form of the \code{package:} URL is \begin{alltt} package:\var{package.name}:\var{relative-path} \end{alltt} The package name must be fully specified; the current prefix, if any, is not used. If the named package is contained in an egg or ZIP file, the resource identified by the relative path must reside in the same egg or ZIP file. The \code{package:} URL scheme is generally available everywhere ZConfig supports loading text from URLs directly, but applications using ZConfig do not automatically acquire general support for this. \section{Standard \module{ZConfig} Datatypes\label{standard-datatypes}} There are a number of data types which can be identified using the \attribute{datatype} attribute on \element{key}, \element{sectiontype}, and \element{schema} elements. Applications may extend the set of datatypes by calling the \method{register()} method of the data type registry being used or by using Python dotted-names to refer to conversion routines defined in code. The following data types are provided by the default type registry. \begin{definitions} \term{\datatype{basic-key}} The default data type for a key in a ZConfig configuration file. The result of conversion is always lower-case, and matches the regular expression \regexp{[a-z][-._a-z0-9]*}. \term{\datatype{boolean}} Convert a human-friendly string to a boolean value. The names \code{yes}, \code{on}, and \code{true} convert to \constant{True}, while \code{no}, \code{off}, and \code{false} convert to \constant{False}. Comparisons are case-insensitive. All other input strings are disallowed. \term{\datatype{byte-size}} A specification of a size, with byte multiplier suffixes (for example, \samp{128MB}). Suffixes are case insensitive and may be \samp{KB}, \samp{MB}, or \samp{GB} \term{\datatype{dotted-name}} A string consisting of one or more \datatype{identifier} values separated by periods (\character{.}). \term{\datatype{dotted-suffix}} A string consisting of one or more \datatype{identifier} values separated by periods (\character{.}), possibly prefixed by a period. This can be used to indicate a dotted name that may be specified relative to some base dotted name. \term{\datatype{existing-dirpath}} Validates that the directory portion of a pathname exists. For example, if the value provided is \file{/foo/bar}, \file{/foo} must be an existing directory. No conversion is performed. \term{\datatype{existing-directory}} Validates that a directory by the given name exists on the local filesystem. No conversion is performed. \term{\datatype{existing-file}} Validates that a file by the given name exists. No conversion is performed. \term{\datatype{existing-path}} Validates that a path (file, directory, or symlink) by the given name exists on the local filesystem. No conversion is performed. \term{\datatype{float}} A Python float. \code{Inf}, \code{-Inf}, and \code{NaN} are not allowed. \term{\datatype{identifier}} Any valid Python identifier. \term{\datatype{inet-address}} An Internet address expressed as a \code{(\var{hostname}, \var{port})} pair. If only the port is specified, the default host will be returned for \var{hostname}. The default host is \code{localhost} on Windows and the empty string on all other platforms. If the port is omitted, \code{None} will be returned for \var{port}. IPv6 addresses can be specified in colon-separated notation; if both host and port need to be specified, the bracketed form (\code{[addr]:port}) must be used. \term{\datatype{inet-binding-address}} An Internet address expressed as a \code{(\var{hostname}, \var{port})} pair. The address is suitable for binding a socket. If only the port is specified, the default host will be returned for \var{hostname}. The default host is the empty string on all platforms. If the port is omitted, \code{None} will be returned for \var{port}. \term{\datatype{inet-connection-address}} An Internet address expressed as a \code{(\var{hostname}, \var{port})} pair. The address is suitable for connecting a socket to a server. If only the port is specified, \code{'127.0.0.1'} will be returned for \var{hostname}. If the port is omitted, \code{None} will be returned for \var{port}. \term{\datatype{integer}} Convert a value to an integer. This will be a Python \class{int} if the value is in the range allowed by \class{int}, otherwise a Python \class{long} is returned. \term{\datatype{ipaddr-or-hostname}} Validates a valid IP address or hostname. If the first character is a digit, the value is assumed to be an IP address. If the first character is not a digit, the value is assumed to be a hostname. Strings containing colons are considered IPv6 address. Hostnames are converted to lower case. \term{\datatype{locale}} Any valid locale specifier accepted by the available \function{locale.setlocale()} function. Be aware that only the \code{'C'} locale is supported on some platforms. \term{\datatype{null}} No conversion is performed; the value passed in is the value returned. This is the default data type for section values. \term{\datatype{port-number}} Returns a valid port number as an integer. Validity does not imply that any particular use may be made of the port, however. For example, port number lower than 1024 generally cannot be bound by non-root users. \term{\datatype{socket-address}} An address for a socket. The converted value is an object providing two attributes. \member{family} specifies the address family (\constant{AF_INET} or \constant{AF_UNIX}), with \code{None} instead of \constant{AF_UNIX} on platforms that don't support it. The \member{address} attribute will be the address that should be passed to the socket's \method{bind()} method. If the family is \constant{AF_UNIX}, the specific address will be a pathname; if the family is \constant{AF_INET}, the second part will be the result of the \datatype{inet-address} conversion. \term{\datatype{string}} Returns the input value as a string. If the source is a Unicode string, this implies that it will be checked to be simple 7-bit \ASCII. This is the default data type for values in configuration files. \term{\datatype{time-interval}} A specification of a time interval in seconds, with multiplier suffixes (for example, \code{12h}). Suffixes are case insensitive and may be \samp{s} (seconds), \samp{m} (minutes), \samp{h} (hours), or \samp{d} (days). \term{\datatype{timedelta}} Similar to the \datatype{time-interval}, this data type returns a Python datetime.timedelta object instead of a float. The set of suffixes recognized by \datatype{timedelta} are: \samp{w} (weeks), \samp{d} (days), \samp{h} (hours), \samp{m} (minutes), \samp{s} (seconds). Values may be floats, for example: \code{4w 2.5d 7h 12m 0.001s}. \end{definitions} \section{Standard \module{ZConfig} Schema Components \label{standard-components}} \module{ZConfig} provides a few convenient schema components as part of the package. These may be used directly or can server as examples for creating new components. \subsection{\module{ZConfig.components.basic}} The \module{ZConfig.components.basic} package provides small components that can be helpful in composing application-specific components and schema. There is no large functionality represented by this package. The default component provided by this package simply imports all of the smaller components. This can be imported using \begin{verbatim} \end{verbatim} Each of the smaller components is documented directly; importing these selectively can reduce the time it takes to load a schema slightly, and allows replacing the other basic components with alternate components (by using different imports that define the same type names) if desired. \subsubsection{The Mapping Section Type \label{basic-mapping}} There is a basic section type that behaves like a simple Python mapping; this can be imported directly using \begin{verbatim} \end{verbatim} This defines a single section type, \datatype{ZConfig.basic.mapping}. When this is used, the section value is a Python dictionary mapping keys to string values. This type is intended to be used by extending it in simple ways. The simplest is to create a new section type name that makes more sense for the application: \begin{verbatim}
\end{verbatim} This allows a configuration to contain a mapping from \datatype{basic-key} names to string values like this: \begin{verbatim} This that and the other \end{verbatim} The value of the configuration object's \member{map} attribute would then be the dictionary \begin{verbatim} {'this': 'that', 'and': 'the other', } \end{verbatim} (Recall that the \datatype{basic-key} data type converts everything to lower case.) Perhaps a more interesting application of \datatype{ZConfig.basic.mapping} is using the derived type to override the \attribute{keytype}. If we have the conversion function: \begin{verbatim} def email_address(value): userid, hostname = value.split("@", 1) hostname = hostname.lower() # normalize what we know we can return "%s@%s" % (userid, hostname) \end{verbatim} then we can use this as the key type for a derived mapping type: \begin{verbatim}
\end{verbatim} \subsection{\module{ZConfig.components.logger}} The \module{ZConfig.components.logger} package provides configuration support for the \ulink{\module{logging} package} {http://docs.python.org/library/logging.html} in Python's standard library. This component can be imported using \begin{verbatim} \end{verbatim} This component defines two abstract types and several concrete section types. These can be imported as a unit, as above, or as four smaller components usable in creating alternate logging packages. The first of the four smaller components contains the abstract types, and can be imported using \begin{verbatim} \end{verbatim} The two abstract types imported by this are: \begin{definitions} \term{\datatype{ZConfig.logger.log}} Logger objects are represented by this abstract type. \term{\datatype{ZConfig.logger.handler}} Each logger object can have one or more ``handlers'' associated with them. These handlers are responsible for writing logging events to some form of output stream using appropriate formatting. The output stream may be a file on a disk, a socket communicating with a server on another system, or a series of \code{syslog} messages. Section types which implement this type represent these handlers. \end{definitions} The second and third of the smaller components provides section types that act as factories for \class{logging.Logger} objects. These can be imported using \begin{verbatim} \end{verbatim} The types defined in these components implement the \datatype{ZConfig.logger.log} abstract type. The \file{eventlog.xml} component defines an \datatype{eventlog} type which represents the root logger from the the \module{logging} package (the return value of \function{logging.getLogger()}), while the \file{logger.xml} component defines a \datatype{logger} section type which represents a named logger (as returned by \function{logging.getLogger(\var{name})}). The third of the smaller components provides section types that are factories for \class{logging.Handler} objects. This can be imported using \begin{verbatim} \end{verbatim} The types defined in this component implement the \datatype{ZConfig.logger.handler} abstract type. The configuration objects provided by both the logger and handler types are factories for the finished loggers and handlers. These factories should be called with no arguments to retrieve the logger or log handler objects. Calling the factories repeatedly will cause the same objects to be returned each time, so it's safe to simply call them to retrieve the objects. The factories for the logger objects, whether the \datatype{eventlog} or \datatype{logger} section type is used, provide a \method{reopen()} method which may be called to close any log files and re-open them. This is useful when using a \UNIX{} signal to effect log file rotation: the signal handler can call this method, and not have to worry about what handlers have been registered for the logger. There is also a function in the \module{ZConfig.components.logger.loghandler} module that re-opens all open log files created using ZConfig configuraiton: \begin{funcdesc}{reopenFiles}{} Closes and re-opens all the log files held open by handlers created by the factories for \code{logfile} sections. This is intended to help support log rotation for applications. \end{funcdesc} Building an application that uses the logging components is fairly straightforward. The schema needs to import the relevant components and declare their use: \begin{verbatim}
\end{verbatim} In the application, the schema and configuration file should be loaded normally. Once the configuration object is available, the logger factory should be called to configure Python's \module{logging} package: \begin{verbatim} import os import ZConfig def run(configfile): schemafile = os.path.join(os.path.dirname(__file__), "schema.xml") schema = ZConfig.loadSchema(schemafile) config, handlers = ZConfig.loadConfig(schema, configfile) # configure the logging package: config.eventlog() # now do interesting things \end{verbatim} An example configuration file for this application may look like this: \begin{verbatim} level info path /var/log/myapp format %(asctime)s %(levelname)s %(name)s %(message)s # locale-specific date/time representation dateformat %c level error address syslog.example.net:514 format %(levelname)s %(name)s %(message)s \end{verbatim} Refer to the \module{logging} package documentation for the names available in the message format strings (the \code{format} key in the log handlers). The date format strings (the \code{dateformat} key in the log handlers) are the same as those accepted by the \function{time.strftime()} function. \subsubsection{Configuring the email logger} ZConfig has support for Python's STMTPHandler via the handler. \begin{verbatim} to sysadmin@example.com to john@example.com from zlog-user@example.com level fatal smtp-username john smtp-password johnpw \end{verbatim} For details about the SMTPHandler see the Python \module{logging} module. \begin{seealso} \seepep{282}{A Logging System} {The proposal which described the logging feature for inclusion in the Python standard library.} \seelink{http://docs.python.org/library/logging.html} {\module{logging} --- Logging facility for Python} {Python's \module{logging} package documentation, from the \citetitle[http://docs.python.org/library/] {Python Library Reference}.} \seelink{http://www.red-dove.com/python_logging.html} {Original Python \module{logging} package} {This is the original source for the \module{logging} package. This is mostly of historical interest.} \end{seealso} \section{Using Components to Extend Schema} % XXX This section needs a lot of work, but should get people started % who really want to add new pieces to ZConfig-configured applications. It is possible to use schema components and the \keyword{\%import} construct to extend the set of section types available for a specific configuration file, and allow the new components to be used in place of standard components. The key to making this work is the use of abstract section types. Wherever the original schema accepts an abstract type, it is possible to load new implementations of the abstract type and use those instead of, or in addition to, the implementations loaded by the original schema. Abstract types are generally used to represent interfaces. Sometimes these are interfaces for factory objects, and sometimes not, but there's an interface that the new component needs to implement. What interface is required should be documented in the \element{description} element in the \element{abstracttype} element; this may be by reference to an interface specified in a Python module or described in some other bit of documentation. The following things need to be created to make the new component usable from the configuration file: \begin{enumerate} \item An implementation of the required interface. \item A schema component that defines a section type that contains the information needed to construct the component. \item A ``datatype'' function that converts configuration data to an instance of the component. \end{enumerate} For simplicity, let's assume that the implementation is defined by a Python class. The example component we build here will be in the \module{noise} package, but any package will do. Components loadable using \keyword{\%import} must be contained in the \file{component.xml} file; alternate filenames may not be selected by the \keyword{\%import} construct. Create a ZConfig component that provides a section type to support your component. The new section type must declare that it implements the appropriate abstract type; it should probably look something like this: \begin{verbatim} Port number to listen on. Silly way to specify a noise generation algorithm. \end{verbatim} This example uses one of the standard ZConfig datatypes, \datatype{port-number}, and requires two additional types to be provided by the \module{noise.server} module: \class{NoiseServerFactory} and \function{noise_color()}. The \function{noise_color()} function is a datatype conversion for a key, so it accepts a string and returns the value that should be used: \begin{verbatim} _noise_colors = { # color -> r,g,b 'white': (255, 255, 255), 'pink': (255, 182, 193), } def noise_color(string): if string in _noise_colors: return _noise_colors[string] else: raise ValueError('unknown noise color: %r' % string) \end{verbatim} \class{NoiseServerFactory} is a little different, as it's the datatype function for a section rather than a key. The parameter isn't a string, but a section value object with two attributes, \member{port} and \member{color}. Since the \datatype{ZServer.server} abstract type requires that the component returned is a factory object, the datatype function can be implemented at the constructor for the class of the factory object. (If the datatype function could select different implementation classes based on the configuration values, it makes more sense to use a simple function that returns the appropriate implementation.) A class that implements this datatype might look like this: \begin{verbatim} from ZServer.datatypes import ServerFactory from noise.generator import WhiteNoiseGenerator, PinkNoiseGenerator class NoiseServerFactory(ServerFactory): def __init__(self, section): # host and ip will be initialized by ServerFactory.prepare() self.host = None self.ip = None self.port = section.port self.color = section.color def create(self): if self.color == 'white': generator = WhiteNoiseGenerator() else: generator = PinkNoiseGenerator() return NoiseServer(self.ip, self.port, generator) \end{verbatim} You'll need to arrange for the package containing this component to be available on Python's \code{sys.path} before the configuration file is loaded; this is mostly easily done by manipulating the \envvar{PYTHONPATH} environment variable. Your configuration file can now include the following to load and use your new component: \begin{verbatim} %import noise port 1234 color white \end{verbatim} \section{\module{ZConfig} --- Basic configuration support} \declaremodule{}{ZConfig} \modulesynopsis{Configuration package.} The main \module{ZConfig} package exports these convenience functions: \begin{funcdesc}{loadConfig}{schema, url\optional{, overrides}} Load and return a configuration from a URL or pathname given by \var{url}. \var{url} may be a URL, absolute pathname, or relative pathname. Fragment identifiers are not supported. \var{schema} is a reference to a schema loaded by \function{loadSchema()} or \function{loadSchemaFile()}. The return value is a tuple containing the configuration object and a composite handler that, when called with a name-to-handler mapping, calls all the handlers for the configuration. The optional \var{overrides} argument represents information derived from command-line arguments. If given, it must be either a sequence of value specifiers, or \code{None}. A \dfn{value specifier} is a string of the form \code{\var{optionpath}=\var{value}}. The \var{optionpath} specifies the ``full path'' to the configuration setting: it can contain a sequence of names, separated by \character{/} characters. Each name before the last names a section from the configuration file, and the last name corresponds to a key within the section identified by the leading section names. If \var{optionpath} contains only one name, it identifies a key in the top-level schema. \var{value} is a string that will be treated just like a value in the configuration file. \end{funcdesc} \begin{funcdesc}{loadConfigFile}{schema, file\optional{, url\optional{, overrides}}} Load and return a configuration from an opened file object. If \var{url} is omitted, one will be computed based on the \member{name} attribute of \var{file}, if it exists. If no URL can be determined, all \keyword{\%include} statements in the configuration must use absolute URLs. \var{schema} is a reference to a schema loaded by \function{loadSchema()} or \function{loadSchemaFile()}. The return value is a tuple containing the configuration object and a composite handler that, when called with a name-to-handler mapping, calls all the handlers for the configuration. The \var{overrides} argument is the same as for the \function{loadConfig()} function. \end{funcdesc} \begin{funcdesc}{loadSchema}{url} Load a schema definition from the URL \var{url}. \var{url} may be a URL, absolute pathname, or relative pathname. Fragment identifiers are not supported. The resulting schema object can be passed to \function{loadConfig()} or \function{loadConfigFile()}. The schema object may be used as many times as needed. \end{funcdesc} \begin{funcdesc}{loadSchemaFile}{file\optional{, url}} Load a schema definition from the open file object \var{file}. If \var{url} is given and not \code{None}, it should be the URL of resource represented by \var{file}. If \var{url} is omitted or \code{None}, a URL may be computed from the \member{name} attribute of \var{file}, if present. The resulting schema object can be passed to \function{loadConfig()} or \function{loadConfigFile()}. The schema object may be used as many times as needed. \end{funcdesc} The following exceptions are defined by this package: \begin{excdesc}{ConfigurationError} Base class for exceptions specific to the \module{ZConfig} package. All instances provide a \member{message} attribute that describes the specific error, and a \member{url} attribute that gives the URL of the resource the error was located in, or \constant{None}. \end{excdesc} \begin{excdesc}{ConfigurationSyntaxError} Exception raised when a configuration source does not conform to the allowed syntax. In addition to the \member{message} and \member{url} attributes, exceptions of this type offer the \member{lineno} attribute, which provides the line number at which the error was detected. \end{excdesc} \begin{excdesc}{DataConversionError} Raised when a data type conversion fails with \exception{ValueError}. This exception is a subclass of both \exception{ConfigurationError} and \exception{ValueError}. The \function{str()} of the exception provides the explanation from the original \exception{ValueError}, and the line number and URL of the value which provoked the error. The following additional attributes are provided: \begin{tableii}{l|l}{member}{Attribute}{Value} \lineii{colno} {column number at which the value starts, or \code{None}} \lineii{exception} {the original \exception{ValueError} instance} \lineii{lineno} {line number on which the value starts} \lineii{message} {\function{str()} returned by the original \exception{ValueError}} \lineii{value} {original value passed to the conversion function} \lineii{url} {URL of the resource providing the value text} \end{tableii} \end{excdesc} \begin{excdesc}{SchemaError} Raised when a schema contains an error. This exception type provides the attributes \member{url}, \member{lineno}, and \member{colno}, which provide the source URL, the line number, and the column number at which the error was detected. These attributes may be \code{None} in some cases. \end{excdesc} \begin{excdesc}{SchemaResourceError} Raised when there's an error locating a resource required by the schema. This is derived from \exception{SchemaError}. Instances of this exception class add the attributes \member{filename}, \member{package}, and \member{path}, which hold the filename searched for within the package being loaded, the name of the package, and the \code{__path__} attribute of the package itself (or \constant{None} if it isn't a package or could not be imported). \end{excdesc} \begin{excdesc}{SubstitutionReplacementError} Raised when the source text contains references to names which are not defined in \var{mapping}. The attributes \member{source} and \member{name} provide the complete source text and the name (converted to lower case) for which no replacement is defined. \end{excdesc} \begin{excdesc}{SubstitutionSyntaxError} Raised when the source text contains syntactical errors. \end{excdesc} \subsection{Basic Usage} The simplest use of \refmodule{ZConfig} is to load a configuration based on a schema stored in a file. This example loads a configuration file specified on the command line using a schema in the same directory as the script: \begin{verbatim} import os import sys import ZConfig try: myfile = __file__ except NameError: myfile = os.path.realpath(sys.argv[0]) mydir = os.path.dirname(myfile) schema = ZConfig.loadSchema(os.path.join(mydir, 'schema.xml')) conf, handler = ZConfig.loadConfig(schema, sys.argv[1]) \end{verbatim} If the schema file contained this schema: \begin{verbatim} \end{verbatim} and the file specified on the command line contained this text: \begin{verbatim} # sample configuration server www.example.com \end{verbatim} then the configuration object \code{conf} loaded above would have two attributes: \begin{tableii}{l|l}{member}{Attribute}{Value} \lineii{server}{\code{'www.example.com'}} \lineii{attempts}{\code{5}} \end{tableii} \section{\module{ZConfig.datatypes} --- Default data type registry} \declaremodule{}{ZConfig.datatypes} \modulesynopsis{Default implementation of a data type registry} The \module{ZConfig.datatypes} module provides the implementation of the default data type registry and all the standard data types supported by \module{ZConfig}. A number of convenience classes are also provided to assist in the creation of additional data types. A \dfn{datatype registry} is an object that provides conversion functions for data types. The interface for a registry is fairly simple. A \dfn{conversion function} is any callable object that accepts a single argument and returns a suitable value, or raises an exception if the input value is not acceptable. \exception{ValueError} is the preferred exception for disallowed inputs, but any other exception will be properly propagated. \begin{classdesc}{Registry}{\optional{stock}} Implementation of a simple type registry. If given, \var{stock} should be a mapping which defines the ``built-in'' data types for the registry; if omitted or \code{None}, the standard set of data types is used (see section~\ref{standard-datatypes}, ``Standard \module{ZConfig} Datatypes''). \end{classdesc} \class{Registry} objects have the following methods: \begin{methoddesc}{get}{name} Return the type conversion routine for \var{name}. If the conversion function cannot be found, an (unspecified) exception is raised. If the name is not provided in the stock set of data types by this registry and has not otherwise been registered, this method uses the \method{search()} method to load the conversion function. This is the only method the rest of \module{ZConfig} requires. \end{methoddesc} \begin{methoddesc}{register}{name, conversion} Register the data type name \var{name} to use the conversion function \var{conversion}. If \var{name} is already registered or provided as a stock data type, \exception{ValueError} is raised (this includes the case when \var{name} was found using the \method{search()} method). \end{methoddesc} \begin{methoddesc}{search}{name} This is a helper method for the default implementation of the \method{get()} method. If \var{name} is a Python dotted-name, this method loads the value for the name by dynamically importing the containing module and extracting the value of the name. The name must refer to a usable conversion function. \end{methoddesc} The following classes are provided to define conversion functions: \begin{classdesc}{MemoizedConversion}{conversion} Simple memoization for potentially expensive conversions. This conversion helper caches each successful conversion for re-use at a later time; failed conversions are not cached in any way, since it is difficult to raise a meaningful exception providing information about the specific failure. \end{classdesc} \begin{classdesc}{RangeCheckedConversion}{conversion\optional{, min\optional{, max}}} Helper that performs range checks on the result of another conversion. Values passed to instances of this conversion are converted using \var{conversion} and then range checked. \var{min} and \var{max}, if given and not \code{None}, are the inclusive endpoints of the allowed range. Values returned by \var{conversion} which lay outside the range described by \var{min} and \var{max} cause \exception{ValueError} to be raised. \end{classdesc} \begin{classdesc}{RegularExpressionConversion}{regex} Conversion that checks that the input matches the regular expression \var{regex}. If it matches, returns the input, otherwise raises \exception{ValueError}. \end{classdesc} \section{\module{ZConfig.loader} --- Resource loading support} \declaremodule{}{ZConfig.loader} \modulesynopsis{Support classes for resource loading} This module provides some helper classes used by the primary APIs exported by the \module{ZConfig} package. These classes may be useful for some applications, especially applications that want to use a non-default data type registry. \begin{classdesc}{Resource}{file, url\optional{, fragment}} Object that allows an open file object and a URL to be bound together to ease handling. Instances have the attributes \member{file}, \member{url}, and \member{fragment} which store the constructor arguments. These objects also have a \method{close()} method which will call \method{close()} on \var{file}, then set the \member{file} attribute to \code{None} and the \member{closed} to \constant{True}. \end{classdesc} \begin{classdesc}{BaseLoader}{} Base class for loader objects. This should not be instantiated directly, as the \method{loadResource()} method must be overridden for the instance to be used via the public API. \end{classdesc} \begin{classdesc}{ConfigLoader}{schema} Loader for configuration files. Each configuration file must conform to the schema \var{schema}. The \method{load*()} methods return a tuple consisting of the configuration object and a composite handler. \end{classdesc} \begin{classdesc}{SchemaLoader}{\optional{registry}} Loader that loads schema instances. All schema loaded by a \class{SchemaLoader} will use the same data type registry. If \var{registry} is provided and not \code{None}, it will be used, otherwise an instance of \class{ZConfig.datatypes.Registry} will be used. \end{classdesc} \subsection{Loader Objects} Loader objects provide a general public interface, an interface which subclasses must implement, and some utility methods. The following methods provide the public interface: \begin{methoddesc}[loader]{loadURL}{url} Open and load a resource specified by the URL \var{url}. This method uses the \method{loadResource()} method to perform the actual load, and returns whatever that method returns. \end{methoddesc} \begin{methoddesc}[loader]{loadFile}{file\optional{, url}} Load from an open file object, \var{file}. If given and not \code{None}, \var{url} should be the URL of the resource represented by \var{file}. If omitted or \code{None}, the \member{name} attribute of \var{file} is used to compute a \code{file:} URL, if present. This method uses the \method{loadResource()} method to perform the actual load, and returns whatever that method returns. \end{methoddesc} The following method must be overridden by subclasses: \begin{methoddesc}[loader]{loadResource}{resource} Subclasses of \class{BaseLoader} must implement this method to actually load the resource and return the appropriate application-level object. \end{methoddesc} The following methods can be used as utilities: \begin{methoddesc}[loader]{isPath}{s} Return true if \var{s} should be considered a filesystem path rather than a URL. \end{methoddesc} \begin{methoddesc}[loader]{normalizeURL}{url-or-path} Return a URL for \var{url-or-path}. If \var{url-or-path} refers to an existing file, the corresponding \code{file:} URL is returned. Otherwise \var{url-or-path} is checked for sanity: if it does not have a schema, \exception{ValueError} is raised, and if it does have a fragment identifier, \exception{ConfigurationError} is raised. This uses \method{isPath()} to determine whether \var{url-or-path} is a URL of a filesystem path. \end{methoddesc} \begin{methoddesc}[loader]{openResource}{url} Returns a resource object that represents the URL \var{url}. The URL is opened using the \function{urllib2.urlopen()} function, and the returned resource object is created using \method{createResource()}. If the URL cannot be opened, \exception{ConfigurationError} is raised. \end{methoddesc} \begin{methoddesc}[loader]{createResource}{file, url} Returns a resource object for an open file and URL, given as \var{file} and \var{url}, respectively. This may be overridden by a subclass if an alternate resource implementation is desired. \end{methoddesc} \section{\module{ZConfig.cmdline} --- Command-line override support} \declaremodule{}{ZConfig.cmdline} \modulesynopsis{Support for command-line overrides for configuration settings.} This module exports an extended version of the \class{ConfigLoader} class from the \refmodule{ZConfig.loader} module. This provides support for overriding specific settings from the configuration file from the command line, without requiring the application to provide specific options for everything the configuration file can include. \begin{classdesc}{ExtendedConfigLoader}{schema} Construct a \class{ConfigLoader} subclass that adds support for command-line overrides. \end{classdesc} The following additional method is provided, and is the only way to provide position information to associate with command-line parameters: \begin{methoddesc}{addOption}{spec\optional{, pos}} Add a single value to the list of overridden values. The \var{spec} argument is a value specified, as described for the \function{\refmodule{ZConfig}.loadConfig()} function. A source position for the specifier may be given as \var{pos}. If \var{pos} is specified and not \code{None}, it must be a sequence of three values. The first is the URL of the source (or some other identifying string). The second and third are the line number and column of the setting. These position information is only used to construct a \exception{DataConversionError} when data conversion fails. \end{methoddesc} \section{\module{ZConfig.substitution} --- String substitution} \declaremodule{}{ZConfig.substitution} \modulesynopsis{Shell-style string substitution helper.} This module provides a basic substitution facility similar to that found in the Bourne shell (\program{sh} on most \UNIX{} platforms). The replacements supported by this module include: \begin{tableiii}{l|l|c}{code}{Source}{Replacement}{Notes} \lineiii{\$\$}{\code{\$}}{(1)} \lineiii{\$\var{name}}{The result of looking up \var{name}}{(2)} \lineiii{\$\{\var{name}\}}{The result of looking up \var{name}}{} \end{tableiii} \noindent Notes: \begin{description} \item[(1)] This is different from the Bourne shell, which uses \code{\textbackslash\$} to generate a \character{\$} in the result text. This difference avoids having as many special characters in the syntax. \item[(2)] Any character which immediately follows \var{name} may not be a valid character in a name. \end{description} In each case, \var{name} is a non-empty sequence of alphanumeric and underscore characters not starting with a digit. If there is not a replacement for \var{name}, the exception \exception{SubstitutionReplacementError} is raised. Note that the lookup is expected to be case-insensitive; this module will always use a lower-case version of the name to perform the query. This module provides these functions: \begin{funcdesc}{substitute}{s, mapping} Substitute values from \var{mapping} into \var{s}. \var{mapping} can be a \class{dict} or any type that supports the \method{get()} method of the mapping protocol. Replacement values are copied into the result without further interpretation. Raises \exception{SubstitutionSyntaxError} if there are malformed constructs in \var{s}. \end{funcdesc} \begin{funcdesc}{isname}{s} Returns \constant{True} if \var{s} is a valid name for a substitution text, otherwise returns \constant{False}. \end{funcdesc} \subsection{Examples} \begin{verbatim} >>> from ZConfig.substitution import substitute >>> d = {'name': 'value', ... 'top': '$middle', ... 'middle' : 'bottom'} >>> >>> substitute('$name', d) 'value' >>> substitute('$top', d) '$middle' \end{verbatim} \appendix \section{Schema Document Type Definition \label{schema-dtd}} The following is the XML Document Type Definition for \module{ZConfig} schema: \verbatiminput{schema.dtd} \end{document} zope2.13-2.13.21/source/ZConfig/doc/zconfig.pdf0000644000175000017500000057075612214017462017720 0ustar arnauarnau%PDF-1.4 %ÐÔÅØ 28 0 obj << /Length 2046 /Filter /FlateDecode >> stream xÚíZMsÛ6½ûWð(ã“ zKœd&™dšÆNÛi’MÑ6}U¤ê¸¿¾KbI‚"H1I{Š'žÐÁ·ËÝ· H, ðšÃ ’õÅ‘Ïg1ü Cd(ËùŸh°„‘W%Æ„Á}…Y2Œàº .Ï~iî[À² \÷éÕÙù n.‰Ž4 ®n.(‰"„Z&dpµ >Ìþ¸Øn>RÊoç ®£ÙÛ¹¤³8™s:ûߦvð]z3t–îÓM’Î?]½:!àyŒ¥¸]Y¡T°àðٕߥ«4Îq N"Bí­@ŠI"dXݺa ¸KN”`Hj»Ãû.¶ûùž½ƒ+fE¶Ý bB–«¨P&pna–Ptöd·ÏVöWNYEâìùUã4©¡& ”$nå;!F‚»•¬ Ÿ[ÿÖØ…®xe@æxÅŠÍ]Qì~:?¿¿¿Ÿ‡tFþ™35+­„ I¶ëócZJÒ¥&L2%¢G^kK„G†xŠkB¾–'×y±“¢ñb‹_0Éd°6ƒ—ÂÑWwYXn“Ã:Ýå_l¶LódŸ]§8YÜ¥v<Øñ;o–vðÉÛ—vä§8”mìH‚xØÛ·[M–#«zé›2ÊQpÔn»8±½±ã±ýæZ;/âÕªYÎî÷YQ¤Uì”ùÅà+|5ס¢¾Ê2ê ઻ ŒY;æútáÖušÜÅ›,_[ŒÅŠYVäéêæø¾Ê0{ȳͭŒíHžÜ¥ëØå»4ÉÊ;ç9µö)8øû›×Ä›Zû†jH±2F0ß!Ý‹ÊGñÒ!!{hŽf@©0®Ž¹6ulÎÁ×”Î^n Èàh¶]’:‰Ýç3)ˆ¢¬C¢ó5jœƒ9¢Ìt‰pŸÅ WLšÓ·8¿Å8²_ôèÒ&DZñ#Tuèxl· q2é]FGÏ‹B°V’Bš1j>BÖ>ՕߣõìCIõêÁæç_ â–6ÖU-jÈA6Žs¥¡Àt÷=T£ÆÉòé.[Hsïìǹ0ÍÙ aB¡`L ;5ƒc`\Í5te üRb¬Ñ—(PYqhC"Ãë¯sÆ@ëW‡´§^œC¥ƒ’ë÷Ä¢Æ9Ã);¦é.>†ƒFz„Th(;ü¤Ž¶0¯Œâtåp*úT¼F:¾F18#,4.3_@XÐ(±ZR]vj(qBND¨¦%N‹NÄX—4ŠzY÷ ¥º®Òµ¯ ³HÀ‚u(ùŠŠE³a@GÁ†£Cç1%¾=‡ãGQhÀ§…OŽ Áàážà¹hêãðõÓ°/sy¢A£d`ÓEã6¡05Z8DKÓZ¿`(U°Ûœ/-v8`ƒ#0b`ÿŸî÷m·µ×™Ý¡9%úmYÌã¤<:ðÖhmt‡´¿F—¨q¾Mv ?FL1ÌW¤¨¸Ð§«t‹ó—iœ¯œ.±N_°×÷K6´Áà'¡=à<)\vP¸jŒõ*aè È• N “GŽ$퉹Žó,éo ±¨qv\0"»Ü%£&Ú×ÈqI¢ˆnäZœ¿‘ÃùÊ÷!6rïsç¬Êù¬ÊÙ5ÙÓðÑ£+x«¦l˦ž8AÔ8Ѧ-sÙúýBAÛBqÚ/-Îž¤=-“•Ú~eGû‘smö´êÓª_ßy`~Øí¶û¢ßì2‚˜»vúš]‹7³ñªk+3uŠF(všî`ëT±n¿¼ÏËÏî%Ê‹ˆx‡ŽçHQãLX( S¢KåQy¾ç"éê—žqßF”…†ÐÓûÐæÍRœ®Þ[45IÉÒÝ“-Ê(„ë§ë³ô&>¬ ûG¹ÊáÃëfõ±pz›åÅþáØ^Á`³Ä´kt?dkШÍuʺ†ûý+!¥ôéÏ2œßÃ8_=ÉLvñj/mëp´$$?Óºïßwi¾=T>LÒºûˆÛO[‡Ô¶éR¨Žµ5DÔ¸±k]‹ùЇÎL@Ãô45l±ÃjˆëçF _[?V>øùúOØâõ¸uù•ª¨ÃÈ#ˆˆ'ÃtVºËæQؾ_…/E™€Ò lqþÅy[¾éäMÖËU¶I=Ij8”EOÏÍà:Þ,ÕmÕȶü:ÛßååÑú2ÍVÁ!°CÙ1Ü£ƒˆ·»ÉV×x¯›¡¶‹ r ó:§“ÕI>Îݯxª ìÄ©ä}O_{Gûºkt\JiÕ;Fz<Š Qk‡º†r9 ~ ’‡“į…jBз¥ö©j‡¯w«þÁ9“Ê¥àû‚U…}8S! ÁR÷éöŸË Õ¿æ¤ã_X¦:j endstream endobj 27 0 obj << /Type /Page /Contents 28 0 R /Resources 26 0 R /MediaBox [0 0 612 792] /Parent 35 0 R /Annots [ 1 0 R 2 0 R 3 0 R 4 0 R 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R 24 0 R ] >> endobj 1 0 obj << /Type /Annot /Border [0 0 0] /Rect [455.098 551.76 540 560.237] /Subtype/Link/A<> >> endobj 2 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 441.927 143.501 448.931] /Subtype /Link /A << /S /GoTo /D (page002) >> >> endobj 3 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 418.121 181.05 427.013] /Subtype /Link /A << /S /GoTo /D (page002) >> >> endobj 4 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 406.037 257.354 414.988] /Subtype /Link /A << /S /GoTo /D (page003) >> >> endobj 5 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 396.099 229.398 403.033] /Subtype /Link /A << /S /GoTo /D (page004) >> >> endobj 6 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 372.293 221.169 381.185] /Subtype /Link /A << /S /GoTo /D (page005) >> >> endobj 7 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 362.226 183.461 369.16] /Subtype /Link /A << /S /GoTo /D (page005) >> >> endobj 8 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 348.253 196.751 357.205] /Subtype /Link /A << /S /GoTo /D (page010) >> >> endobj 9 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 336.298 229.797 345.249] /Subtype /Link /A << /S /GoTo /D (page011) >> >> endobj 10 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 314.51 216.238 323.401] /Subtype /Link /A << /S /GoTo /D (page011) >> >> endobj 11 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 292.592 265.503 301.484] /Subtype /Link /A << /S /GoTo /D (page013) >> >> endobj 12 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 281.1 253.32 289.459] /Subtype /Link /A << /S /GoTo /D (page013) >> >> endobj 13 0 obj << /Type /Annot /Border [0 0 0] /Rect [109.858 268.552 220.851 277.503] /Subtype /Link /A << /S /GoTo /D (page013) >> >> endobj 14 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 257.19 259.298 265.548] /Subtype /Link /A << /S /GoTo /D (page015) >> >> endobj 15 0 obj << /Type /Annot /Border [0 0 0] /Rect [109.858 244.642 225.254 253.593] /Subtype /Link /A << /S /GoTo /D (page017) >> >> endobj 16 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 222.854 246.903 231.745] /Subtype /Link /A << /S /GoTo /D (page017) >> >> endobj 17 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 200.936 261.638 209.827] /Subtype /Link /A << /S /GoTo /D (page019) >> >> endobj 18 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 188.851 159.382 197.802] /Subtype /Link /A << /S /GoTo /D (page021) >> >> endobj 19 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 167.063 312.617 175.954] /Subtype /Link /A << /S /GoTo /D (page021) >> >> endobj 20 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 145.145 294.435 154.036] /Subtype /Link /A << /S /GoTo /D (page022) >> >> endobj 21 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 133.06 170.998 142.011] /Subtype /Link /A << /S /GoTo /D (page023) >> >> endobj 22 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 111.272 329.164 120.163] /Subtype /Link /A << /S /GoTo /D (page023) >> >> endobj 23 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 89.354 300.881 98.246] /Subtype /Link /A << /S /GoTo /D (page024) >> >> endobj 24 0 obj << /Type /Annot /Border [0 0 0] /Rect [86.944 77.269 149.15 86.221] /Subtype /Link /A << /S /GoTo /D (page025) >> >> endobj 26 0 obj << /Font << /F29 29 0 R /F30 30 0 R /F32 31 0 R /F34 32 0 R /F37 33 0 R /F38 34 0 R >> /ProcSet [ /PDF /Text ] >> endobj 54 0 obj << /Length 2302 /Filter /FlateDecode >> stream xÚµYmä¶ þ~¿b€"ˆ8»–_d»ß¶×HpA¯É(š¨ÇÖÌç±§–½í¯/)R~[w»ýPp#QERÔCÒ+!ü‡,:d" Â8;T·waP$‡0H»úù`.Øüßßýþ›89A!#yx­‹ ã -Þv“ád%ù„¶W½>ÙKLnys"–Ù<æh1Þ—Íã/‰Š ‹¦sì]G˜üQºqÉ?bœŽSajîªÒè‚ʽUDX›j`•<ŒÜMÓ!ö>‘°oè!mŽŒ§àk„¹\ß™M~q’" ²q‚!†¿V6¯.ï`/¦D\I–¿ÁÙieÅì0²O±žÇÁ²8!¿þ$ž¹­DùÛ÷ýSi§MÙ^Fx…àÛ´Híû¶t>ÅúAä ?›ØuñNòÙ•†Þ#ù&m‘i„ˆî™¡ãu¼ªó3M¦]myS,Ézv¹ö‘QÙ”Ã,«{´ «=xÚ(BF‚ˆ†™í…†„1ˆáV-eË'øG1j»ƒ¡5••M¹ƒß<ò¨; Ñ,òɉ#ª+0`HepœX §^ý¦!UZ°üt¼Š@ÀÎ w˜˜‹ø0H6%IÀNå£2¼ ÄÀΞøJbZø“•æuVš¸n£V>à$zb´šžœËJ]¿L]0¿£°²áŒS«³Ípc3¸ät¥ùÙ XÞ7o&·À`¶©AœnKÆP§éEÏ!à26Â4*µÖ¿WwUBVhf¼ÝâàxÊQsÁÏuø ÃÖRö%¶-TjýçÒ|ÊÑl»/Eèj eï"@‰$O À¶€¨|­Çr¼þ‚™:˜&m$º|Àô©ì ¸z«B.ƒHÈ• /Û'Çõòð¥ùZ"ðìêlƒ[9À½g!º£I†“É1œ¸ Íáâ/B—î¥îyÙ÷€_«¸€¯ÞJâg `(bx.?µÍÞl|æ‹(D½Wïb볩Y¬ôOFÆÉ+â(a„‡>ôši-llãàÄL´Ð]Ž`u«ÿåø¯ZA½PêRº’3…÷Pך^%1Nå,N,4-…ë¶jƄڤÀ¬[‹Õ‰×*Ucå'Y<5éq˜®z:ËØ 4XÁr'ÂÁèûJY~EvËRnÉÄsëÕr´|î»ÛFÜ‹rùÈþ`ë]†HÒ × ¤àⱦjÝŒ',ØmÝÍ%[«ü®×`ºªß»JO›¥˜5^Þ:'´Šm@ѧ¥v›²ÜvLšklš¨;ÍÝû°5Ò[WWË|ñ¸Ù¬Û¯)¯rk§'Z³û¦¬^ØÓY…AÓuF5Ï~iLWi¨CËI‡U£omØK46[I[u•àÌþ˜Ú0N Þ¿AçPÖ’ôô¿”·{c›¨i\>„æ½ï*e Mú±mÉœ<¥ê;—î”kgÝôåÊËâ̤̦'â"mJŽ< ×r˜…ê›õ†¨³W8§6s-§®ï•~ô•±=écr] Ù¤·>ï•3&£{[mU?w7¼d1ÄpeµÉîþ&s[¸Þfü¢wŸÛÍÅ‘ËüÒe~ýº¦˜ãnKÒ»dvúÔaAþ¡J~ÁTÇ…í9¤íÝù#L¨eE¹4ÿÇNnˆ²(ÈæNéw;© й øšD•fÒ…Ž@§÷†n»Ö7Ð=+V ¢¢¬[Iç*É^ýxP³ž\Åb Í즞ŸúNå\íîlZAgýa22 dḇ¹yæ±³òlÈ5pä~KúqžÜùXãÄÅPaåðm7ñ³K2õÞÃãVS“ì2·ÃÚ·ªþ':R U,RM±~þ¯êŠåcƒ:4êÆ¾â™Ö° €„ß)£ÚÅëby]S‡üÕ3¤ï³V8~aœ1Q-b‰s2]3Lýé‡ïg•8ºë×ä8e‡ë,‚O8oV&+wp{¿f‚2SÊÔEýäñ×±ޱÖ=¾ÿ•ª‹0hºŠÓŠ Â_B¥’É5>¼ }þAöY=S|ýfÜû AšX· ˆ‹üÕ?¬þh’D‘kKÐqš1ÜMø?÷*¨ß¿—ÙÍ` endstream endobj 53 0 obj << /Type /Page /Contents 54 0 R /Resources 52 0 R /MediaBox [0 0 612 792] /Parent 35 0 R /Annots [ 25 0 R 51 0 R ] >> endobj 25 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 708.01 236.751 716.902] /Subtype /Link /A << /S /GoTo /D (page025) >> >> endobj 51 0 obj << /Type /Annot /Border [0 0 0] /Rect [114.485 400.998 186.216 409.949] /Subtype/Link/A<> >> endobj 36 0 obj << /D [53 0 R /XYZ 72 744.907 null] >> endobj 55 0 obj << /D [53 0 R /XYZ 173.268 641.165 null] >> endobj 57 0 obj << /D [53 0 R /XYZ 231.327 431.09 null] >> endobj 52 0 obj << /Font << /F34 32 0 R /F29 29 0 R /F32 31 0 R /F38 34 0 R /F41 56 0 R >> /ProcSet [ /PDF /Text ] >> endobj 60 0 obj << /Length 1692 /Filter /FlateDecode >> stream xÚÝXKÛ6¾ï¯P‘H+RïbQ`$‡žZdM”¶¸1=¼¢Çÿ¾3R–de›CO½X| çÁùæA¿z¸¹}s¯ ËŒgÞã—s/gQŹ÷Pyü‡½Ü£‡•)@R– ¤€•<̤/´°WóE‚uYg 8ü‰>zP ’D¡Dü»/ç® C)q’yAœ„qÁ¬#änP]ën_œç†Ip»³Z6‡Ánwv c‚–³ÔGtØîÛ+rMB¾#㨥ƒdg¯³'©J:ÐÚo­„UõÑé Ú•4Çœ‰{ IØDg˜r_o˜?ª‡ónV©=Œ}öRT *‰|[OCÎÂìcñÏÇ^8†Ì¯Ä`O»}ÜjùtÐÓ1ƒlˈ *×µà>à°Q­àR¼ö•øzpA`5^\ÙÒ{×^Ú»{öêrã >Fi¤ŸŽØ¬'!8(Ѹ0®$xÅðÙŠú*°µˆpšrGt´Ó…c6D­ÚÁp>\PÏdÄ m26ô‡n áHЊf• çaYfŽêÓJnJ“Cþµ¿® ÏÂ8g‚`A^!DÐlÐW¬d¦i<'­´KL¦Ê¸ƒH‰ÙÄÔÉf×Z-ÌÂFÓõveâ§`Ú„$Ÿª·Rl.51ýK”ûoñ 0]é;¨ Í¡–Ï9ÉÔ»Ø:©9V€¹`¨Ña 5üT¦åX6©n0qÂT|JŰ,™uÎ-×BYyÍ/žò‹ Èk´w·«lƒcînÿHÆ”;‹j!¿â’­]о”RÐD.²þ®½¢Rm[¥-†þq˜ó3\÷tT½c8²¹Ê}¶@Ó!¦õµÊû|5º.Az®‘VÝç=°‹–k½œé5[‡Ëõ\Ì^&¢ÿyºµØƒÚBÌç%*…ÅÌ:‡},‚@xóm®÷Ï ÷6)ü×c¦ÃÔâ  ïvФ‹˜cX¢ö6*îÁ1d¢“|°<Å<ƒË“´Ž¹’lVÄÅœ'‰ÿ‘óø=€Ûè…T¯—‰©I•—›< =áÓÕ#ÝÖŠ<G°–v•Ý–xM€²6`WØwu±ƒÄð^Yɕڨƒð‹P µÃ./Jíë"J¨AƯ®ªš>ö]³V×bÈ1ÅxV `‰¾¾û$Ór|AZçeŠQª­ÄÉaš“JX't7WΦØìÕ€ž•0'ŒÀ@Ë(MÄ‚Â:ѸT¦)c²ŠÀ}vWx‚•™ßmÿÿkšÆ8‡C­vÖ¸`ôÅÁjX-Üo\mNBÓoúZœ4R´vxÚK{ðäÐdø[®KŸã¢•™ïU_Øçž-ÉŠ™Ñµ}q’Ï-Á2Çl@4-Va`° _rRLnãîýÝÓþ°ï´üŽŒ‘oá›–~Ê/'à’h×€!˜3_=Žd=„0> endobj 37 0 obj << /D [59 0 R /XYZ 72 744.907 null] >> endobj 58 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F41 56 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 63 0 obj << /Length 1897 /Filter /FlateDecode >> stream xÚ½XY7 ~ß_á>ëÙ¹6(-ŠZ¬ÑIó Ïȶ9\Íx7þ÷%Ej®õ& è‹-QER)rÞnoî~ ƒUîæI¬¶ûU¬Rßs½0]mËÕGg{”ëMùN/×~ì|éy¶ö–‡–C«Ã‘—ÛýÈËŸ¶¿Á1ÙäßÝ IWòJÕ§V÷Ì8Õ'ŠÝv2ßçµï9F‘Ë#[]ÒAõ¹ã£wFoT«µœk%ˆã÷Kl"DñYä0˳÷©Îã,´º|x×6{uø¦ÎE ¶5²1Ö­6Qº)ðm|ßÍã˜xNºE£T)Ñ °lw¡ÿþ(z±Š4yTUÅŒL©Z1l TS´<+z9PúÖJæ]q”µ°ÒTsÀ¡ïœ;»ÇîÀ#{‹¶ùÛó‚ÃŽPÆ™@DJ%]c¯77ô;—z½ b–ºû[št² A†år’ K‰RTÉìiBF/›i-.4Øñ:YóÌÖ™ £Þ©›éý¾ÕÒ*Ò U±(V®‘²´â÷í`Ÿ]Fÿá¿–¢ª.VAÆ2[Ýtr~°u0¤ÉpG8&Û``}Â,ƒç`b=ÃÇ£*Ž4šqùÓXÌ7Œ‹W±ëzhŵ€ 7ö†À…€ ²}Ù±…hš¶§ñŽiì|TYiP³bÌU¤©Ã†-.‘ÎnÀõ±Äú\õêTIŠ¥ÀÏÜ,ç)…–=¦¯ÀÄz%4jmÇŒS€Á”¼vÅYiâFp9l?`·’5÷2 „n– é‚à’Ñ·£J.Ä;%\PyÝoâ9†§S¥ 1šhj‘ý‘m²& '“Ï ²lî$\WÞÜõ¢üEÏNäù–P«Æ«ûŠçþÇ9æGJŸ0²*Œ‡äÄŒì3N2¿ ²½´[­ÛP‹fœ0šÃ8ѺÚ$‘ÅóËAJ¹áÈa9¹•iʘdžñe¹qD"BÀ–’®MAi6)]nNB÷6è­6f?sáÓ¼T)f1€é•çäyØœ*Qð¢‘ÿz¬²T“·¦]€xP¥ÍÞg{ÕïÈÌ+WÞ¨: 1Ø ×øOe•ÀSyaæy>' ‡… »ó ÜѬj Gfâ?½ÕÈK… À§_œ|hM4ÂAÊÖd¤(ñ0ˆ´U˺¶–³»H¸†ðšCêOâUÒ#És²‘Ú„uŠ¥ÛQ`²PæªR¾º‘ß-Dj-‹£hTWß2xš’LÎÀ½S'}¡Í͹ÞÉQòÓäÁuåÞÜÈà'> N*+ØÓ•„±±{gø¤§:‹¨Yèt'Y(ô…Á,ØÁ¸¨×mEÄ#E­í@Û‰RË®ƒJ°#²¹$“†%^”o÷Ó—D°à ËöB-¥, Žº_Š\úÄê_¥-–ld“ø ìHïóiZÿaÈ߸9‰L ž™RÔpc(ÅÍq¯å„+HúÉ(ÝKŠåµ å|‘ÀH½RïÚnpjûË{UÉgÅœÆñ¾iÎÊ<§µøXêË‘´7ªÂd–ÄÎ-¡!cgIhÒjc¿…×P`tó2SKS{Ö÷ˆIž3¥êðÞõÏ´(€™tè_•r¯yýRÂ0|¶Gš¸Ò굇w¹±U±mÙ' ²!{ÝÖ4.¡s¹— c×÷’§}Ÿ@Æü2~yj[â¹q¼Ð´hh²;“ýn~ÞÞüsƒM™·òñCe™oˆE}óñ“·* ‘ì†y¶z4Lõ*Jð3\µº¿ùãæ-~ë„X›}ëŒ×÷:†RtVnX·q÷nHÇ4c Ý“Ѭ߿vüÅ÷ endstream endobj 62 0 obj << /Type /Page /Contents 63 0 R /Resources 61 0 R /MediaBox [0 0 612 792] /Parent 35 0 R >> endobj 38 0 obj << /D [62 0 R /XYZ 72 744.907 null] >> endobj 61 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F41 56 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 67 0 obj << /Length 2890 /Filter /FlateDecode >> stream xÚY[ë¶~?¿Â)ê-VŒ$’º$E‹Ü"-Ðd‹M”¶èµYr$9{èï gHK^žœm ‘ÔhHg¾ù†þüáÍÇoeµ©D]jópØdi*¤*6e–ŠT–›‡fóýö7=´½½ûñáO¿UÙ¦ñ¼@q%AªÞ¤$×› É|“)T”£T^ ­K/õ}L(Ë ð‹é.q=¹¨ë‹ýD®z’,/D™éM"µ%¯ÿáhï’<—Û_îr½uÚ]w8D–¢3XK|KW¡\вB§Ë4“Æk6øÈ·“ýùbûýu:÷ìì<ÛqºÇ^µmÚÇvöÓ7$séÙ£}ñêdž©Ñ<ë4›q¦ŸÚù¸X‚$õŸB[æÛÙÛw5ÍÞmm“”Zè²ãe¢Öíé0Œ ›º&KM÷6å¶hh²g3š™ãp _Qc€Ö¸øge—É6ôÆ­š¬\-­¬8ŽòfþçCh#Ç¡2Q)éåÐ`YÎí~­{3Ù¤í'ÛOíÜ¢+ür—é­ðºVÛ¯cÞ¥…P™Š»æÊt¤ü„é…“nîÉÊ \S¥šÍ¬YÎMfh®Ã¹Ž8 â€=çg›æ±ía½ª®p½nðq±•ö.ó?Y9çRóÓ±ùø]w—d[{—mç'kûˆò:p`Š %Š:÷Bhýˆ9 >hËÌy#K}ÑRˆ¬þ°šÕr¼}sŠ4GÛï»KÃ60=Úï™NJV…¨ŠõA-–ët;ôô´-;8´§¶±÷Ôü7A´ºö§» ä¨h¢>G ´X W‘,q ^M;N­-…U•¢È­ê/‘²ç -w΄¾ý!M󃌆!¦-µÑ¹Ü~ ñÑèAuJÑãô–"Ãýùv´I˜|²Påö3ge÷n.ãÞ/ngÁoI àÅä—døõÙá¶#Ý’Ân˜´zœîñˆÓ⸘8ïê†v‚¹:º"ë:TÃÆÒ6^ÌÅU Ùã×…Ž5ýä£Ñ°Û,wFÁ´?²¸á0Ýqä¶S=×èk Ÿð–ûcƒIzï]ÞϦãpéjàCïžzÞwI†vñ•oí@¡'ø•Åvð©—¦ÀaòÐdç™–Ç×+_†þÅå &ð‡‘鹟Í;j7vÚ펦ќ_pGg‹7_=¼É`Éé&Û¨<JU]Ö¢z³?½I«pÉ ÝüD/›,„?gZ³H7—ùDL—Ý4·óÅíìf)Z¥«eððb(ñrò%•ùFp³…¡¹t˜t$˜ï‹†Õú$ ïcø*€v =þIY <. é£@jQ_aö·¬q¢'$¶iÑþLa1f?_Lw³PêÈb«·îœñ»žè‘e–…HÓ,,3¶N €–^Äë{´óÊ>èßg"ƒÊÿW{´=çn`"Àso¢•ãáÒÍë˜Ó æ)³ju¤Øw8ÏU>ø°É¨VmÏã1;y‘¶§çìUO˜Ž]ë SPEØ ÏhÖ‹ƒË¶H0%"!>|qò*àÓÙøùÆ|¸ù‡„ášHµ8ëµÎ[Ä­“§KHïÌéÜù |“:"®”–B^«ŒŸìsŒ ärM hòüêÍDgqCÌ‘$¨È>È>jQUÁ›> z®•V’«*µ&y%t%×¥VR¤„åÔ Ó8êúºM'ñÑ••åõ²hBõXÑ%ZŠJgô™„¬”ÂWãƒÙáŒ0=çeµ$çL Jðâ»ýÑžLÄ °"(ðjY|¥Ã¢d€¤‰ôQÙâO#2fN‚†9Ò?þü {Ô°¿œl?³7%¬p•å¾43Ò)ûùì|šKИÀß­aꆜw+¢øþl÷-¢v–ÜA­)tw$*ã8Ä÷«™©5:¯~l¸?ßz ¨¯+Ͱàtœ™?"¸‘£áML]ûxœ»çXØÌc»w¨\e»ös»Ÿ>ÁlX(à8ÅS¹Ž'èîë€ÆŽNf& ¹¯zV>N˜‘_Òž˜Û3¶žŽîË Œ uîy´³×åNÔé"ïP ï%àü5ìÌÔî"űH–µ(õ²â+õuƒa‘ä& xþŠKòieÊŸV¦C‘ ˜Á)GˆñÌRÅå– LPméèÚŽÔ{¼XØÕú ìª6¶ÿ\¦ 65–ŽÉ{j- J0Ã41lÔ>Ó¼5~â¡ç™A-¯–á^kvïN¥\GŽs!„œK®ýípé÷ž¯¤ ÝyÖ:\ÀW©}™\Ñ#È-¥W°œ\€þê~Yló@m²ÊOv>œrˆ¬,2û§þéÒõ2Äh…>g»€¢m0Í*Â+q…Xg‚ج’¢„ºü¬â;')2ÆØï<Ј~ÕY`‘]!Œ"j×·¹Ê*kÿÃ)¸§-è0L ˜¶£¦»‰€'Tè“O=xºŽFÜÎaá Ÿ†ÄÖÏ|Æ0àL«Š-+º™Ÿ—µž”«I§µoÂýj¢®æ0C´&rSÔ Ÿ{äj;³s\OÍ™g¨*ðÝev%œ„ \¯ ƫײ÷4ô³ƒ¨Š·ºxµÄKxOû«×/<ÃÄsú[«šŠÓ9ùbéTB)‘éWÜy³\y:ºßA½%‚„Å•òù|ÒêÊ,QÆ…š—¦5§¶k äêgîÖ‘•Ö9Ðg¤ä4ô%gmê=ÜUé6Äâ— ŽÈ‘gúÇ‹yä÷-ë8Úî|¸tѺöáx¡wê8ÖÇÛšf¼ x_²à2䙼eÙ:X—Dö÷|>å²êrWߗФUNûpeZ®£bž¼'8h€N •ðC–è´ï¶§óeš0›:PwÔéü{CUÐb˜?F<ž¬}ò3ü§ˆhm­—h ¨¡XDi䛬’Vÿß´~J÷¼—û'Øvt/@:,!×¥Heñr 7'“ÈBà ׎oçoV']½ï¤?ޏn®|+·®°úwï4«›äux]˜ %öç—«®k‘ªòu8sËÚEd“I¦0£h¼¬qâö¤¥&–ît&ª48>sŒB¢¿ò¤â‰ÿáI.c—@‘` …©{ºâ4r̪,ªÅ\ä IQP—j ŸáŌ⼄-WªÃóoß~3­G€£Zj‘Yø5]›aË_æH[~¼Ô ùDY"ŸÐj{ÈîÀ²Åµ0÷\ דL墪#µ±ûÎÐe€Ë—@œ©B ?,ðÊÛeÅ«F.ʵ’ÿP±åÍa™¢‡Šúî™d˜“ç˸¿´ÊÒ/×w‰$”¼;X•ž¡’Ã&s]ÛÄv†i¨íjªB:‰ëÚ*f"‘C*îói.ªZ®|>¤âˆÓTTòN_¾¶ ‚ å*ì(\Œ¨ÕÅLZ¿²®¢(c‘¢œê|mnJ¼©ö,ZLAÒ⦜˜é?@®2Óp[­ë1çs×Ò‚|=ú¢.]_6¦|1éÞÓßm…w'÷wûô¯|O_éx- %†Î^i2-Ê<œ€«‰{¥…Èe¾—×ýDlš‰“§ÃÅö ‘?4kz_¬ hþ·¾©¢”¨ópvPvÚwïq¹¥;V&gíÀÝÛ×vF¸HéŸGbõþüÞ!¶b‡8}*6 wë‚ÏÅÝÿÏáF¯Ë•È‹oÓ¿ÿ1Ý40 ±.d]mžœÐi£žw›ïÞü•nÜ!Q/ÝX Uà "ó™[û™þ "†i endstream endobj 66 0 obj << /Type /Page /Contents 67 0 R /Resources 65 0 R /MediaBox [0 0 612 792] /Parent 35 0 R /Annots [ 64 0 R ] >> endobj 64 0 obj << /Type /Annot /Border [0 0 0] /Rect [420.448 577.478 540 586.429] /Subtype /Link /A << /S /GoTo /D (label-module-ZConfig.substitution) >> >> endobj 39 0 obj << /D [66 0 R /XYZ 72 744.907 null] >> endobj 68 0 obj << /D [66 0 R /XYZ 288.926 415.127 null] >> endobj 69 0 obj << /D [66 0 R /XYZ 200.899 299.514 null] >> endobj 65 0 obj << /Font << /F38 34 0 R /F41 56 0 R /F32 31 0 R /F29 29 0 R /F34 32 0 R /F11 70 0 R /F37 33 0 R >> /ProcSet [ /PDF /Text ] >> endobj 74 0 obj << /Length 2695 /Filter /FlateDecode >> stream xÚÕZ[ëÆ ~ß_á·ÚE¬hnÒ¨)Zœœ$@Z$iq-Ð$Z{¼V-9’Ü= ôÇ—R—‘å=Þ$hÑ—µæ"ÉùH~3ÚÏïï>ýJÙEe‰L÷»…6²R/RG±J÷ÛÅ÷K÷¡uå¶Yýxÿ'˜.Gӵ޴Î1ÍËÛ¶.V2^ž[÷Éj-½l÷ärëv+/óó¡Å±ü×Jše~8óø®ª©¿Ý uòhpSîêêØ½Á2ò†ç5›½;æM´Z+m–_ï>6m[¬ÙœµQf u8€i—g|¼%³e“;eìÃÄFYÚùd›·yû|rsÎKÁÕݼOPS/»ìVÉ[zB܇‚üÒOE§ÖC¨• #Gã«ÞÑÁn%ÌÒïqQ>R¹ÜgÈ}Þ=B$Q:q©Ñ• – ½,+ê`/C{1¡xaì1oŒ1ãE‡9=†¨ÕQš˜ÎqåU è^—C^ TQ’fS›Á:ðE&Lˆ•{Jõš³§L gOì›TMºô38JáéÁÑ`~¼ú˜ÄªVÑ/ÙŽbžš¶«Î%Oòó%°àAN X•Œ©ôÉKšðÀ rô÷+T¹iëd¢ÍqhìH L-éG}ŠKHèãÞ=ù üÜ|M %Ò·+žN¾ƒY軆:û$š,9¯çõ†_לܦÀ’):UÀ”Ýù@Ï\!’Q$àR"2Ù¡[P²À¬0SÒrÜ£ Bo^P°ô ð”o·õ YsæµY¤Õ9ÍØWM{ ˆ Є> ¼k°RÆD4”0\ZEPа‰Ž¿ùŒ®™‰´¹1 ùˆ¤W€˜‚ðð¿EÉË<í¾[Ðÿœ\]T[LÌJ\U²ÚÍeY“‚‹zZsªÝ®ø0Ÿ—âXwÓ|µŠ DïÄÕ6™Fië$^çU?;h\‹µpù5÷@¤0Ì¡ÑÕ;=®wša‡¢i©Œr4f×j¼D½ÓÞ¼ošŠkŒuçj40†ô›”Z1›„5™MjyÉ&Q£ŽMöÍh•¤‘RæµZq ^k¡€¢šKŠú3ؤ" ˆÚÕ ð·ÒÞÀÓÒ_F«leq¦¢¿ÔޯРæ&@d|È™q§6Ql…)ʳ èªv<5¯[à 4>Ò¯aÉ”`¬ia&uR5çœÆ¨š!z¥`ÂgÂ, “º™…áÛA>¡S¾JÇ’Yä6o öÐe<ky‹šÂµ6–×Ð&á”§û¢M`˜+ 8ûPˆîè© ƪèµáö>oÊ߬$Gj?8ÇzV+_Ö=½­‹í–úa§žÙžW”%’_ VŒÉgLY‹ÄDFNRðuJ`#50‡Àû BG+çhÂñ ÊÑðû™¨IÛNžÍ¦.NþÌ})ôMM_wþ0g“¶À=Íîo¿xsÿfNG©GÁaBí5?1>M_a„ž1öôÚèȤ“}ù¢ŠqÑgípÊÆ–ê³(1(c%r¸/;ëSEWfjèÅ €ét|’i<>ϰD¦¯š§•LƒÀ_æ¥Ëo*ŸøqA,À¬=Má«Aã¾à•@5Ô¯õ€Ì–MtàѲ8ÃFÚ¯?­Ú ªè3Íg䩇C^¾§Ç€·éptŽBZøüXç§}3WfÓ4ÒfR8kÔ €Z+Áž×b@Îa@¦ûeßµõyÓžk·½_¥=ögÙ`üŽÔnÚ/h-$œÇô¯£5$PËÙîí!oàÔG®ô§ö¥!k™ñÀ’êFjå>äÇÓÁÍ ó³´ 6/±ÿcZõZJ¥bÙɇˆ7A²$áa:äl9dî>7s»*ýQu”¦ƒd½è×ã£kó­Ûù{†ÿ[b|ƒ¯'ÆoðT$Êw UÝUš~¦ÄŽ~›ñÊÏ$üIöçcÎRj×þb8éîúQ*Q/ü¸Ynç­ëÄŒX þPyµ}…Ë=Q#§áMu<± ÏÐ…à> endobj 75 0 obj << /D [73 0 R /XYZ 72 744.907 null] >> endobj 72 0 obj << /Font << /F38 34 0 R /F32 31 0 R /F34 32 0 R /F37 33 0 R /F11 70 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 78 0 obj << /Length 3071 /Filter /FlateDecode >> stream xÚÝZëã¶ÿ¾…¿±‹3ǨR´hÐ ¢-®½m" ­-ß gKŽ$ßíþ÷á õ2µñ¶A ô“Èш‡¿yP_ßß}ùR«Ld±ŽW÷‡U¢W‰’Bšdu¿_}·þÍæ‡û?~ùIÇ<"‰²•$†/™!1X‘%‰gh‹]WÖU÷|.ˆu2_lEb#Ïû[L8¶661«­R"³–ÿP|/¥®ŠÍV[¹Î鱫«]StLäy©ƒ³‹€¤†µB%ІÝç]>:êSãý^ZÉLшÉ£ú…?äm¹Û~Ü(¹.ž#FZèH{îº Œ;‘¤½ª÷u×ûm•Ÿ‚'éH@…L°¾ÈŠ4ÓSýÝ?¢–2íVL-·n×=n"¹þ´Qv]4]Ñùóc¹{äfy–Åž‡ªùégù´Ñv/Ü­þ}ÙR‹7Kl¶‘‰×ß&ßÿýF­/Lñæ¡]ˆeémjÛzî‰~ºÇ¼Ã b\ ¨áCYµÔÿ\vÔÊéq.š²Þ¿ÙlL½ÄñtÅ1®øú)‹H*/ç¹)åS@D“ŠTYÏæu³îcüp{.ª}á¥(ô¢-:Ôh”¢Fó«d<ðKÆvØÛuÉš¨O%*–•Qí3€rHa‰åOHÛ†ŽD$Œ‰&Áá½´neî?i¢DÈÈܶ£ú#whêÓ0l4¨U+rÜzòðü’@ 0Kõݺ޼ëšòa£aÅÄÆ*á(X ™LQly>«`7ì –Þ b&ü¾DÃvÇÌ1Æ®ÝI„–;Ž’Ócpìõ{œ´H¦]Ã’{ídö•}•ï~º”MÑ{ŽGÉ´0<áÏÌv|=û¾Øó&ߺÅÉTW‹Ç,³°D'†R~fhíë‚·";¾4Úôš«ê. \ØÔtàÉ/]}Ê»rÒ?ÓØåé|,N`›Ø0¬üJà]þÐvM¾ãWÃrFÂéa´k|õðL/܉@nÖž=‰´dk£Õãf{ÙmáHbxƒÀ.ý6¦ZÀòáS»&zš&Ǿ§.¡­¨r‹V H£Íã¯i÷ Áb1ìªËKÔºAK/rðüú-ŽB˜É.ÅÏQâé "ÍØæALUÀ!|¦Îh3d¿tØÌÖ¸Á´Òû·‰é(8]6ðЧb:œp ãhˆ ³¢·ÐÞäaÞÎ ;ˆD»ïšÏd:кö—©´¾1 Ñ+Õ¯ i½Þ§+“JÈXÀ'¤p-E”ôLltFªõ?6 ¼.Ïx¨›ÙtÓ@6±V·ù izư¯S1lJìy>Ï cæKk‚˜ïUذâ´ö¼>·p.Ì| Øg8­ÍØžˆ3äèÎÅŽZЯhYÍüJ!׃#0éÁµdR¤ÂYkص`ËŘðÌ+~öpˆ½‘Ÿ7½šLâ"‘,Zã´bñËA7h‹*£åèPÛh4ª'äQhÔ4%ßáZ´qÐA>v{ÈwaÐã#®³„Ž8ÒvNÐ 8ÅÖÏ…!Oqà§Ÿ’eÙ ‹ƹëü@%3÷î±8q›’›ù:k0ÙæÉZÐ(—Úÿ>ØÑq䬨#H %XqÏ ùÐrÛ/‡ ˆ.ÁŠçÙõ¥b&·qos±eY,ðžé[0<°€l ýL.4CÛ*ƒ;‰¢´I†ì'€øÉ‰ô=˜¼9 0¼úŒßæÏíWÐ 4qýšÙIwþµDôZê)çùœÜð,œ‰ºDñȯ`)‡Ë‘Ú¬.\,B¶`@xŸŒ‚þ&„ãJ(ð ÑÔ'$CŠ_žóý¾Ù€ [Æq#ð&ÁFMuÛ-eú6FéØÿ!èXi)êR>D‚Æ( Å.E§6˜5gF½µÖCâØ'„”,ã¸ãdû¾)Šðá1$ËF-J¹,' £äç“åLHi&ɲÓuÁkœDSÃ%ìSÓ ZCÜF¨|- h 틃3Ù˱#Â8Õ‡nÙ† !À&‰_4½m±áÞŠl>_Œ:â‘%¶$ëÂC'ÖØL¤ƒà/¦² |êžf !ØÖ$°= '_\Ç(cþÈàeE¢ìÔ«-œ;c`3~Ñ"ä‚K³éi ²ddI²^ÒþWÔl Šdovë‹ç«™:} þ½²i*E&gNý]ã²Ó'ŽåjŠäˆ@ê—$‚‘:g$Ò°æMWæè¥¨?±õbîCÕ˜‰ÈµGhåôŽàý9ä¼?ÑúHxˆ=£™™ºóVs`¬r‹A UN¬+wퟺvʲp  kSzùV`žLè뇔:Vìã`š!°6I¾Ä:(ç=B¦IÈŠWßÀu7WÊê—8À#›tæ%FK½>ÓÓüê:­šÜvl#pœ‡O ó´Ó+–déŠBþºéÃ8 ±_žLàU¥"™×lÞþùÝý?CÒél~ÆüŸ_½,~üâÝH§v*Ñ·4Þøæ§Ï5Ü-¼¯È^°nñö)ç«ZB®Y"~½h àk’ÞÃóÝÇüÃÏ»¨ÅbÁÈ«¶Ín¡Ì¡ûqN—–×üPÜ–ÂC(¡P<ÅÕCy¼Í±àÀÇ+ÀGª/,¥uú-«CÝ`²®F·Oê:7ú gRÚã¯M Ñov³u/¼òU;lçôà=p©ƒ¦3h|ªl&ùêÊ.~¤¥ël•ÂÒãÈ©Ùj‘ å¤Á|žNÇ4I‘d=÷4““²“ßûì ˆœ¥d>ç6.çæBŽ6žÝÍ}(¿)9¬è¶ëócQ…î=2¡bs“E&ÃA£ûã… Å ç+Á(¨$‹a‚y•ŽôXG0]¿—0…35Ù§€ƒXÄåVM·}îÈ#JÓu`™ÏT$RÛãүƸ3»òKåçõ³!–®“Qæ—&bZ…ð~S(³¸/¯¯Pp Ð^ääÂñLŠgq²(xSºˆÁgNwÏpÌ+j{ñ]‡Ãh±çl™±Ì#ó …ŸxržS¼±¤=„&íY J}zuõBÜ·¡HåXçp¥Æï`1ž¤#£â,€·.HÍb ð{£_@N )þBð†o»š%x®´H‡¬éõç †-ÒB9$7‰N)›ñ•Æhê_`“$Õ4}ÁA2ZXO #ƒé ²ÐšÃZÊ¡-O˱mªÉ šºßC+ ®1zóEè0@ÒŸ…/ð0¸=Qƒ òùú§émŽCûTì»KÃ¥+è ¡&ö¸ÆjæIa€ãð”+Ëä±T6 o9$V sÈ@óîÔBP‘ ¼ r.ÍqÛlàˆ‡‚žÕ.fVX¥®QyTþýo‚•IΡ¤+a·- 3Êݲ±çDF}Õx©Z)1áªÑš>ÁÁ&B_ë“Q_ÓîZ–šÂ•wn§a¨¦hëK³ã·bÉþÂ:9ñ’4oB 䎌ђª\Y/8’Æá¨…wï‡[¸Ì—5‘o|ñ o4ìa’zt;ëoê´œ,h>³«vºÆ~ïߣ¾‘äOðl!Ó8ºO^¨“dJØ4…úÓør’©pQ «ÏÛc1 Â‘ȳ¢6:äj?)/xrãk,ªºYȸT ØW¦\}±DM/^Efô‹ùþ(3ó²û¢Ý5åÿÀO,auO9^ãøî©èÀ‹°¤èIÜu›¤cpþD"ô×Áü-ƒ0Þ]ÍJ@FýK%p/(ü˜½±³àö÷!:»Öü<’Ѭ{¯¹-åKHq˜Ò»ƒp¼&âȸ¶c|Žs^6ÄäÁˆ¼­ÞöóP‘NuËBÔÕŽq áÿŸ@ÂPSý?vøOu}è’Ôlùp–Žö–’nëFʼncÙzwq÷öþî§;Äf¹Rø‹§…3'«Ýéî»äjd€w«ÿÙ1VQŒà¸z÷×»¯ñ/Q[&‰FZ(obF¨ ؘí{/c’®ßŽ.‹a<-¢áf?ñbý à%ò  endstream endobj 77 0 obj << /Type /Page /Contents 78 0 R /Resources 76 0 R /MediaBox [0 0 612 792] /Parent 80 0 R >> endobj 79 0 obj << /D [77 0 R /XYZ 72 744.907 null] >> endobj 76 0 obj << /Font << /F11 70 0 R /F38 34 0 R /F37 33 0 R /F32 31 0 R /F34 32 0 R /F41 56 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 83 0 obj << /Length 2578 /Filter /FlateDecode >> stream xÚíZ[ë¶~ß_á·ÚmÌð&ŠŠIšÍC‘â,Ú‡$@µ6},Ä–|$9' ôÇwÈ!u¥÷Ò.¤=O"‡/Ùo†ŸôåýÝçùÊ£¸ZÝVFCóUÎ(¡Ðp¿_}·.º®)®Ýüpÿ ôç£þJ\±ÅžßÓŒ†NrÔIÁòاÜÛª+¿§”Û&1 Ð$—r4 sV[-`aÙjË1Y†­÷G»Ùr£×Uq¥ú€Ï.6}ûØë ˸‘ §k·/úx,wÇøJÙbéÇ £k»aÙúí±¾žöX~°³~Úðl]œ®¾júô“†Mêñ&a$a—ïì®+ëêï~¥JrIíUmWT;KP+B+8'=W‹Ûˆj}¾¶+e~ÕNr­ÊW‹²%l¹B¹ß–ç³Ý—Eúïꪃój±ÑíÍI ¬¶¸î0Âã% Q7(iwG{.Èf›©|ý—6výâf§á'MUÝùýÑéÆÚ‹Ý¡åì?åft]Tá9;Y›€ÒÇòtÂÒCìêó:îƒô1J«¤pžpð¶éÊê=Ê0„ËÑV-ÖË0)Z¦ö2™»«ñy­ö¶iwuc[Žxìv[ÛÙ²Œ‰»Ý]ᕺ´ˆ,'¹¯ñº‡¢-w[\ècbDÉ —<ö®›Äx ¹6±Ë¾î@[¿ÓÄQ¹^z1“ц‡cUc7εß1–И\iv(€ Î{¿…áxóàšð,.—SéO× U‡gœeì¯0’÷×¼wÿ|êþ*[ƒ #½ÆÉ§ãèu|»H…4 ;õBÝ)"eß·;è [Iá‚O]âÁ¯ó}mÓùtp |\lSÖÑ]z‹¯ª ƒ%H1‘¦{›¸4öPþœFl‘õë]:¼¸½X°ÿ¸Šò€ÏÖvI_`˦Î`ÅõÔ¥Ôň†Àð¼/ôæØV€o/‡âbŒZZm¦ c3Å£9¨ÁFV³˜†Z_вÁ‰ZלŠkEµÇ1¢ .Àqüî µˆkPiÓVOÝG÷É.·Oañµõø(¢U‰;¿kêK¢´ C»¦–(ëÚ¿ëtàÚÛ]›Ê†šó^ÿþ1¼î}{W„¸ã^lg«€ œvÞ_›8H¯peE ÈÄžQä\DH‡41ëÑ1LÍÚ-$áMRÉúL©±®eƒª4cí;&Ö„‹Ÿ/%Q¢wÖGÛ&Ff91¢é8”k© ¾Ç°çç”ÌÖ^ï|Ï"H™Í]p¾J#‰ÎÅ n€©¾çFÊä#2B¹Hå§àY!pŽï]2\vl¼GsoÔÞç;|b"78©ƒ|gÔèEUèž>?Þâ^‹ùc@ŽºAñ?Å #Ü¥–>!÷úmʈH?êô0u>GÈ:EÕÁµõ y~7!Hqº<×:Rësð‰ôø­Å¤Þ®ô`'ËÛ—>5/šÇÐE¾)[šúìJ<ÌC盃4`×䙯5AÓ\¥‚<©Š|6¥]Ñ:{,»R6¤OÝ7!9Òô8ºÉ•ö$Óx)˜ôÇáí˜,Ÿ¦£}’ÒQ阎fáLžNþ—ÐŽn¶Øo’[Äú0/Ü nßô Ÿ .îæ x¨oá!„’Uu !•W‰l)$”fjpïú3€ý@„-ZÐ`ƒ¯KWXx…dÜAÖú8Ô ‚0F¿M¤Æ'¤ N•?•{;}™×ç!¯Ÿty¡«&£­Ð’PÍ^nÁödÆnx$÷œP&§Z»ôB´×R?åEôÜú¶;`‚´ lÓxä¡=¾Œ^oì¥ö9¡—ºœg‚K\£¼:Øw_£¶`Ó¸‹;rÁ­óðˆ•@8Œª"Ø‹ågð¸›'yÄÌp¢Yé“.>Ç {S?œ[‹;?ï¸ ¯êñJ–úÙX_n$§/_Š´È–SL:íAaT{…ÑÑ]×!ï~r vÕñ©ê€ñ:öÎgI7Õ½]è`奄430íù•8\•#2ï]éI›7iä›+1Ê+"À9'v5É—ÂE´§9&ëÉIF{šã¯u• Còˆ!G¸ ‚ñ-ΈKçÑ9Q< óïˆ,ûùΰ÷òǘxNF”9nãHˆ D)1Çv×”þºùÇÏÜõüãçâ|9ÙX=Û®Z¢Ñ!¬¸Ò3ñT.6Ý07kçΔpƦÖ·6üyB#˜Ö½P#l®>Q‰"J²©£|‘X“!™Qé)ç—3Î{L·'{¶•ËÌ%ÃP-1ïÃ’õ’‡3ðÙH |Üâ  )ð’õ™€ ˆî õnwmpà!Íyu=?Øðž¿f¸”g–åo2@µ+Dö¯/Àn´‡¬§OzêËödG y½SrtœÃtÓ\æT¶5©d†NÒ˜7ùî ?}wøôÝá?ýî þ?¾;èW}woþÝ=ûÝáM?;d¿¾Ïùq>}vxëÏjâ Ï0Ÿê—a>gôìK}êOÔgšú ;ýÝMÖ“¾1㙿|Ê_èÜ@ŸÇ1ë˜â:ãÎ~…\'ì0ðÿÃtgö‰î|sºS¥èN.Ft'—ié’îá@þøZ‹ïŒ¸Nz®žS®s<ô2™Ð‚"×ÉÅÐmÌuÂë7¸NE˜|5× ¹w¯ÇÕ) Í_Nuf$jvãv®f"ËiÂ73'7 hd¹ =#¹iz@1P Ó` õ䦉¾oR@f蘬3r×@`pÉ«sþ„'[xgfœ*‹²1LBa’…ƒv…ã ào=ãn³q’Í…£xòçOªŒ'Œ¹d<öøõÝ–Jtë‰JDœ’{|ÆFT†Oñ®à¾ ôŠ”$’ž*B½œ¤Ÿ®::maŸtè=ûÓ "ëû`':BKîÚZ”°¬ Cˆð(¡Ð“E®rÀ«Î> endobj 84 0 obj << /D [82 0 R /XYZ 72 744.907 null] >> endobj 81 0 obj << /Font << /F37 33 0 R /F32 31 0 R /F34 32 0 R /F38 34 0 R /F11 70 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 88 0 obj << /Length 3098 /Filter /FlateDecode >> stream xÚ¥]ã¶ñý~…±/µ›µVü¥ôÈIÒâ-Ð$@µ¶ö,œ-9’|›úã;Ã!%R¢o÷Ð'Säpf8œoú»¯îÞ1¶*’"ãÙêÃÃJó•fi’ ½ú°_ý¼~½ùõÃwïDîÃ$Z«”î,€öTRhíöÕCy9Ð’,É¥ppo-"îAlEšd_mK ¥ðûrwˆpÅE"r¥à”*À†…«ŽÕ©j†ÍVÈ|ÝŸ«]ýKšòª§‰ÒÎ×ÍÇcEc@¼aéÚ 7Ÿ7\­ËãÅ®?´Û»ä‘åY¢ÕHüHêOÕSŒKä|L6Û,•ë‡Ú2°½+Ü[ºê\•Cµ§¯¡Eü(K‘ðL…ÂÓ&ƒÔü•!; Ž® $¾¤YˆDúÅÊaœÔ«ï?¼úí2š®ØŠ±ܘ\eà¢ÐíN¯pAÂi˜ÎVpøB¬ºjõðê‘]â0з›˜é•Ûó&`ªžBƒ¯Ó[e)Àç‚8û×&KïСûöšŽÖJá†ë)˜/Ãà—tG3åÁ)·DÆ1msq&¬µp6Àܽ“~FÃyžp1޸焣yH>ÝÃH¦Ü baì9üþ£[¶nZ§\&©V¡÷nª]Õ÷eW·ª gì02¾SÛ0ËsˆXÝ‘ðÛy<Ô» ÷¦C æCÓfø¡® ]B‘¯°4J·¹£¬Á„Û=MŽb4Lm˜[îºz’žÂˆ‡+Wû"þÝ[ß–zmļ:‰ãø !ÖF(‘ÇKGO ¿Ë£Oœ$±ØØˆé‘q¤Ñ«rOSÆb¢×6—žb:LOÛÓ’ñî0×ïÕ©Dm1íåãÁ+gëÎf…’`³+•e cd³˜”¤‰’âÓ¹µ [–ìØ÷=3|æ`ÿþ+0]\.2ÈìE@Û.ùT-Ô’¬oI¢(&C²çr÷©üXY‘ªÝ'+(rØFµzÌ <<ßX$ª L½¹9•ç3˜¾‡C3$.­qôßÜ<´íÍÛ¾=Y&?c6þúνúe$ï~úé9$Û(?w°Ý[ŸŸùÎâÈ dË¿,e´‰ÏÕ~.h~½ÛÂçA–‡yH³ú}¨š}o/êÎ2ç mÒ&D ¤K¼Ðmdà. ƉÊ,©?šÈlu±ò¦—k Ü0­ùÑ\X“FÎK§Á&³Ùù4­œMÀÈù/Óœ´þ ÃEÎ)nK°¼€(gXTb0C^T+/£—AâËýòÈø0fêî}ØòBBj=Â{9‚ódzr:ƒäL!’ËõûºÙŘ`tn,ÛI9lî¤A`bVj‘'‹eçL',KKÃ…P¦zÚÖM_<M«X¯WÓd÷×`â¶P† aú58m¢½À«ÁýÆ·YzXºã]‰|ýŸh LA‘4¶æÀøã§)Ä(–?ò²±Ä£hU¢Ó±:yw+Ÿcí¬4î[SÑ|ñÌt3L&³v_Œ#å’þªcÞ.‚ÓîXöåzO‡|BPò‰ìù®‚ä#%ÛÒÀ¼òhoÐÒhмŽaÐ|ªJÇÛ"½^iJäIšê”[±™ðK…Ýw±Î°HóDËYgX_ë ;+~iã7\8„|VkC}°ëê3"ýs+î)@$Ážÿ¿ýµÇ¸Þ¿¶EÚ_bU{¢¦ú? 8ïN°Œ/z×\ëT„+S·~DX±ÝWôUÒÏäbÒYÌŸÊ'´»Ý¥³[,‰SÛ´¡Ek!² ýR"Îwã‹khÏÛ£ç¶)’LËxºmJ®fa¿õVcصƘ°¼Î4è¶Ž‘7"ßL@–Âüžk$®·’;ófÐÅâŒôÚÎkV›ªy XQ{Ó¶{˜žƒ[ú;Ä#\&=0Xvâac؇Ñt#øáqOãû*À.ÇäQw{c-.‘že£8Þ­$Kqh‘äz”/Ï¡MJ\-KR‘ÇêbÖPˆ˜W A|ãÌ¥©3Ï.0‡=3ó’óÔKÁ@{:U{êYã<¶cà¶zZ4 ‘™RIxR2¨¤B¨ŽfH±ÎË´ésàâ027o)Ù(K®c3+\¦·&“}Œ¨ù² SV#`ôXãc&k÷vÆeCôe *3ÄYÊ=`þ°˜r†ä°§ocX9w-ß~ºÆ™Àï¥d®ßµ]ÕßÇÊÜ.íXÒ]y]aþÃÝ•ö8öÐSvµý›ZÇå—š÷рΞáó Ø¥ðõ¹<‘¾z@×^ t^|]¯uþ Ä9d—E¸¨¤cǨ?QrRõ•‡yïõäVP¸ÍÔœÉB‚ÝŠ€Ç+÷$„ü ú"ÙÒ…*•¤\F\¨PÚ*.ŽŒ™+×ÇUc÷–Ї²§YŒ&øk½ Œî-üG/·o—q4°TÒØòÇKçZ>ˆÐ:·Û˜ÓLE"'-”×µòÈ2WƲ8ùœ2jÿIâ²qeJOâñŸ³•iŒGS ÐBö,ûÔˆÌgŸø^máì %³LÍ:S¥ÎTYg ¿í©ã1En<¦mm)5^¹"`»ªÜÛuÏáÚUGä¬WóÆ£ààÞâÅÔ Å"±)'6ŠMÅÅ& /¦Iò¬Ü˜\Êm~ßà „zñ}ß:Ñç^bM``L„.7ž6© ªw}Í9çÊZŽîíŒw R ɇy<È]6’"ÅϨH™†â+ÞŸ“ö€LÌ•“!í$唹šJ8¤%å±>—n“Õ{Fj"K0ÉtÖD¶lQS:´ßÿœÕ†ÑC<Ñúåï¿¶ô޼(ºôyvTŒŸt…¶@n݆i«½U µæ÷ÜÑ‹N½gâ[Ʊ £{WŽJµ<M®6žªþ¿M¬ T’Oo…SÑ åóFÿ{ÿ@œcTÙ‡WîR%,ü>N:¿o¿ 6‘0´ôë‰k†z|*‚ ’‘–LË,ТK¦ÁO0ÏvIðâyN‰(P¡—|››+óLjðÑ€rQM±’ë ÿxÔÚ¦|Õ´j_ ³ˆÀ´ö û–µÍãM³;^Œ„|\ž„ kG:sËA38¢õÂ6 Ë<£m$™è  ðš¢N›Âæ+%!Û4Ý™ŸMW{˜†„9E¾z4@§•4]Òãê½ë¹ð"ì¹H¨¯˜mꊄm¶,MÓõû±•›¯¿§6€9!à㺎Ž­ÿöã#7 endstream endobj 87 0 obj << /Type /Page /Contents 88 0 R /Resources 86 0 R /MediaBox [0 0 612 792] /Parent 80 0 R /Annots [ 85 0 R ] >> endobj 85 0 obj << /Type /Annot /Border [0 0 0] /Rect [354.82 563.96 396.663 572.912] /Subtype /Link /A << /S /GoTo /D (label-module-ZConfig) >> >> endobj 89 0 obj << /D [87 0 R /XYZ 72 744.907 null] >> endobj 86 0 obj << /Font << /F11 70 0 R /F38 34 0 R /F37 33 0 R /F32 31 0 R /F34 32 0 R /F41 56 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 93 0 obj << /Length 2822 /Filter /FlateDecode >> stream xÚÕZ[¯ã¶~?¿Â/E}š5ˮؠE4@û´ØƒvÑ$@u,ùXX]¼’¼'.òã3C)J¦Ov HŸD“Ôp8œùæãÈ_>Ü}þµ’›œå‰L6‡Ëd´Ig\¥›‡róí¶èÊûr;+lˆí¾ï¾ã\>‡bªûÎŒÖÝ8Ýž¦”}5šþ®Ÿì„}s.iœdÉíXíQÆ+ø%Óy‰÷2ÞÍ™f Õ©¦ª¤—û…Br[œNM½wʈísÝ4f豺ÿþáo°ÏÌÛ'ßì„`y›~Ówv–o ©X.LÖ“ÍH½»({e -fydæM—SH˜R,K•ö9MŠüIL‰ÔÎy,Æz¿{w/ø¶º„$æ,…'Qà¤Í.އ3\ìðaen²ªª[ÏÇz4Ͷ˜öÇú^l»§Å+tœíy´çÙžšª­º‰Ýï¢<Ùþõ°r“ÕvEk}¢ ŠÇqŠýtC·WzC«Ã2ú¡r2Ž=åðWÝ™§VWžŠØÓгGè(º§?˜gA¢´°E6Ò«žª}bªÑ.XL¤‚}ZëŒ+•Ð åbc Å™3†Œ¹^—¡)¸6¬î2"¸aZu +xšp£6Çï‡jªfa¯VBœµíÓ²áÆÐ¦YÝ‹xûèÖ\î“Ø†Ć ‰†ŽHY")¾ÄLÊÒ(·^Ûž›©¶š\ MK•sñ?"yG,†¸Z¸HYû¡>¡Ð?…T•Ëyº|ç‹€ð…ªŸöQŸ¦¿j/~@ïÎ@…Sü9 Vβè…U2Óˆ©,±“+ã’p¨Iº­Ñc’d{W±…¸Š#Æ~:  ·0³¿@Ÿèo‹‹éë÷ûó`ú ¾Áa¨¿;· èà•jZ iÓ[HƒY1©VÇ;;k?ûö§D4ÀÍ\¸jÈÅ÷Ǫ¥€¨ÉÓ]s Un Ϧ!Å„pž/¾˜&°Îy Á|¢XšˆOy/„‡Êgà„Ñ5Êgœx@y™I ÐÒÏ”±ñ÷ËtÔ§˜)ÚȽäÛóDÃÖ<™ô6ó³€þuìÏMiÚÕj ÷}ܪ—VQ΢(¶Û|cû§;}±{K@5ÁŽÆÁó­}Õûs=gÏ ­Á!´ŒÇgÛsW¿×ÚCßs=‹æÖEAb ^ý°éá—ññÜÄMîÇMn]g 4¬}”…2絩_óL}¤¥ ü¥”ÎÕEÓ‘ 9%‹©°RÌÁ@Avðò%^—>Æ -"bÖ5Q#ÌêG°xôñX°ŒÇ4Ù'q£uЬµÌ#MÉüÐÖó[#o_#¦iIó`ôQMRñ±a‘‡d‰ذ¡¶»ëU›¡*JŸÙOI££ÕM?Ì®=&E¤`ðÄ”ƒaš§†Ü¤6¶ á£Gªƒ7°áLRükpÔ;ùC`¯€qºñ&õC(¢ã©s­ÏB y>[™l Fç.âpÉò_JqÂÄ|vkDV™…'h=Rg4"7ÔGŒ¡á~(X<áç¨â• i1 ¬Ð hm‡ú¶ž ©¢Í"á¹ ‘›y“‹¦éQñg|A‡ƒTœ%±\šÓd}e©¬r|X‘§Aã‘:h}ͤsbÒÞ›¾à¶‚(—?ç©ï kÞëBCC6Ôr¯æb h†Ç¾½ÞQóyw§¡ÇE>‡°[ü¦7–ÇÊ—l²‡eZìœoŽJÝ:™:y%eÂ"~Ó•=¢åí}µzÝrÈõ*ùEèLN2ŒÄ÷5œ-ܲög·°ÿR?v}ôc–%É5觨P¾tò7þSI¤Ü•9=üaîØÐ¸.Å@§WŠÁ_$`¦?ZBožž ™¼Èâ³©Š‘šXÑå ÓbkYvÐA€ïÅ¿º 4®ùŒ_2l ï+³dÀŒÈ€”òÂM©Ü”@j5 ½6’+ICQ±Ç+#¡ÓKÆ”!cŠ…1¥­kA¿©kI¿®åËò,##²Œt–Nð˜è¦}$ܲÃöQL:bt2 ^q.}%Úý¹€ÒΑåÁ¶B‘„Š&¨ÍÌá%cì7AO 3ณ5—÷X»îÕVçsÑŠ„E)\tWØïÊ‹’ß’ÎK~¹ðCô¥Šß‹à!TÊD-™íÿ¶äwƒ4ª_Sñƒ°¶Qý›®ø%¿ˆ&’ªÂz– 9¯˜+~‰t×=A×=A6£Àhº\}C¶u¸ØeŠi^ÐÖ8<ˬ´¹.öÅÿ§Å>™oŒþrQí¼}(hºß Îùö-¼¤Ùö«¾=A´áõøÚ¡¹.Æ)2ÊÛ·oiÏ•em?T¾Z§¦è /÷¯«ú^ññß_õÝ¡~ ¬A¼ÄŽÏ碆uåjF&8Õõ˜9}líMºQ”Y䂉ÑÈÅ<Ëz<ƒ®,ebâÒœ˜^p¬È­VµÔêý4¦¶ïºz"ˆ4;YBdÿDLA&À7;SÕS¾—B»ÚŒú TGôG0»w|FJÑäÃô²t_*#Ü»Òkv?zE/¤ ÀŸ#)æ{ pÖõ<ðØÖJábæv¦=ò+°‘dé\ qr„Å‘Kš¶‚à%mÍ̹à@YaPv•éít!ò™Þ™ã<³¹6S^ŽêeøEnét©Îwçœì‹u8XÇ•›¾{ë’†–TC ±Cpûj.Ú„CÔ•FPª<ª¡rSËb*æJ J£šåJÓ](<÷°êCc‰?؃ø#sï0qV±ÆÔÎ&S[/Χ¯?×ÖE©„šaAhh! .æ—³Π™&倗s¡ó?.‡dp|I¤cŒ ]#\‡#û¡mB…uÎÒ™›ýþ5á_–+/GlÖÕ†fª†ÎTi#«¥S6¾€ ˺ Ø‹Äk±È%\E\&9ÔÍ{±âÙ­{qD$ºu÷†'é\Ãm1Y…?`¼/¿ôèúPºDX°npåv;5$2Héâšv¶ù¥èßÝT-Št‡æyÉá›ý@CßšVa×\:g¿r\Î}„D¸tWÈé$™ÌÕ/t: žÌŸYü°Ÿ6 Œ«ï!†Wãʘ¥buÕ_Å.¢ãÅò·bZáèëå·«™Á ô)óýëv1‹Yy¤>& Æ “Yvýé%+~Yöûó<ìœjx‚I]gô0ÙØP&À=lV5±°4/EÕ |ÒWJ.¨g²"¼Å×ofƒ… }Ã1à ?ç4 #DÂ…pª¬Ø‚$9Фë,^é5ÍäB!zo7e ²âChÁ®\~GY&âG{„¥ó ;äl~㣧ý\Søß@_ÏïÚ¢$.=øîÒwÍe¹Ô¢6´¼9ø)b ý€â?Vž¸+ÃìÅ¿¨„%Ñí-I8×wpËÈÌ^°÷Gó “À~wµ–± ‚½ª‘ßýåáîýB߈ Àsü¦›}{÷í÷|SB78S ö³žÔ©Ä-4›7wÿ¸ûÿzÈäC"?BšE×È©­¢;Õ¿À“þ¿Äĉ¾YYxÔU‚Hxã0ªù@\÷t endstream endobj 92 0 obj << /Type /Page /Contents 93 0 R /Resources 91 0 R /MediaBox [0 0 612 792] /Parent 80 0 R >> endobj 40 0 obj << /D [92 0 R /XYZ 72 744.907 null] >> endobj 94 0 obj << /D [92 0 R /XYZ 218.844 299.571 null] >> endobj 91 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F37 33 0 R /F34 32 0 R /F11 70 0 R /F29 29 0 R /F41 56 0 R >> /ProcSet [ /PDF /Text ] >> endobj 97 0 obj << /Length 2854 /Filter /FlateDecode >> stream xÚµZݓ۶¿¿Bo–:'Á¯¤Ó;­;i›'¾¼Äñ¸8:qL‰ Iå¬üõÝÅ ©ƒìkgúDX.ûñÛ]H¯ïn^¾bQ²2“Ùân»Èå"œñ$_ÜU‹÷Ë?¯>Üýã囤˜Ò°\• N/A>!HY™çž`ÓîíÁ"œí–&,/«¿8VrB±V’e¢X¬…`ešáÝάÖ2åË¡=®³’éò÷•H—¦¡iÓ˜=nh_¶mGƒ~³3{Mã TÏ"òsØΠh·cg¶õçˆl*e…,¼ô¿ò”;"5!JX"‚.ªvLµ>轉ðmdy9á'h±.8+y>WÁÛÎüʹüŒÇQ |&Ë{Cï òúh•©è½>Ðú¶kI-jÙn©î†Z7´>‘¯wœwz µ~Jš|¬‡4­MW·[­)¼uØ £›“!"Q,·4vµÛ@CW߯$_ž««ýÉI‘T¸<õxái𩛆8•Aø<8b”Г ;qb‘¤L‰ìŠ—Îí’428—eëžî}§ûË•(–îýÞ'E‹ò‘—v]]U~þþŒÏl©þpÓÑÐïd@”’%¤›Œdñ†à`ˆ§G”9"øçU7N –¤ÁEgöpÁ!ËYDÚB2 a»N… ðs°/ç|ù“Ù®ŽÇ„‰ô"æ9)<ßÔõ.Z¿„çÛ•‚#lp×OúVŸJ ^!KxDŒZ01±Ôçb ÆL)–òø‘A ,§èÈŠ+¾“ ¦rá?w¸UlšEJ³È·X“E¨(ëÍšZèXÐIõôºŽŽW&¢”‰?\)½P0GX MO¡t;Ó„Uç¸>KY-dßN ¢ñ‰y­’rr¾“D’7Â\ÔÀ¸iï6mPêÏnΙ |pk½ÑØzG+g"ÈQÿý“ZÅãÔènRk|†R®·bв\€‚ ‘XLQÐ"…U2m zX±4¡z¯×|x¿fáÉ×å+m™B׉Å6Ë•ËrŠlªÓÅ„hóúv1¯ŸLex³ÇJ_#Ý·mcôá™^õÝÌRhF)­OÀcwÚëÃzÛÕˆ¶Û} *X·á<’ûíK¸¸µ¨Ë»À!DS8®ÕÙ9š¹¿žŒP±¡ZhcZp§<@ªüJº‘‚%cººS,§ÉŒ 9ÞGÔ zŠ* é…´yw…wÂJ!.d…"£‰¦•’ ¤=´Ï8ûS&k¥8ËøÅ%q»Ý^³†œJg»£+š,ÆÖv«›>vÜï§Ë뺤Ñ%¸ÎX&½¹ÂôSŒUmÏ8øÿþ¨»º•rH ˆ@ëú€·˜õ´ûq¾j\ ì¯lŠ:úœâä’gU÷Úç&ì#!-² æ2¦ÏƒY÷õæ™Qý ïuÄØèn<Š+‡ÑŠ¢i€+šN%þ§XÃýhyØ^CÁd+ýi‹ÒÓG¨Žžè°xq·_Òa®ÞÏúßÑ2(L f²øáõWÍö‚n^ì6ï®ÈBê‚ ]*ÃTÄŽN ä®P-ñz‚¹©0œ\"ÄŽ‘(píà´ÿ|ýÕ°{q{Pfã íÏgæË´¨¢%S<0ýû3˜F ‘¥ÉÜ1¿ükäÌ5³Ñ5C|„r°‡ê)¼û[²ö`æ‡Û·‰ˆ& §Åx :6Z]ì‡ÁdŽò” ÕÞèᲤ_'{A‘ò¸ñ ­S‹õiÓŸµ_„[ÄK]' ‡dXFu Hÿ|((ËQße1×7¬QÕV8}ãDGÏkú¼MÇþåëúÎEÕ7Ê5Ñ7ˆ`õ Ó£¾aòª¾%¤ªTþwú¾¥k¹c õà=Ýïá¦ÏCE"xQ´#q¿OXè·evY û{À¿žó¸1¶S¾ª6¡Æÿô:i¢æw™<üˆÅŸâÑä&óKWu~Sü=/ÜN›øžö„7»»ùí ¾ø‡ŽT1™å‹Íþæý¾¨` s–@Oúh‰ö •¡C4‹w7?Þ¼Æÿ„ÈrþŸ,³¡Pú?üÒ‹¿I…ð¢ÿÜË⣠endstream endobj 96 0 obj << /Type /Page /Contents 97 0 R /Resources 95 0 R /MediaBox [0 0 612 792] /Parent 80 0 R >> endobj 41 0 obj << /D [96 0 R /XYZ 72 744.907 null] >> endobj 98 0 obj << /D [96 0 R /XYZ 285.355 358.885 null] >> endobj 95 0 obj << /Font << /F11 70 0 R /F38 34 0 R /F37 33 0 R /F32 31 0 R /F34 32 0 R /F29 29 0 R /F41 56 0 R /F12 99 0 R >> /ProcSet [ /PDF /Text ] >> endobj 102 0 obj << /Length 2422 /Filter /FlateDecode >> stream xÚíZK“㶾ϯÐm9U#.Y9yoesزãÉ£b»J¤fK¤BRžÌ¿OÝ ³«]'›Kjñh4úñu£1oîo^¿ùªLKÅÕê~·Ò|¥Y–fB¯îëÕIó¯vœÚîa]·Ã-Ë’c5=Þþtÿ{XÆËxž–™^­KK)qéŸncIµoëjjÆÛ5W:™«É´ h5Ø„›íÔÏØ쇩í;üèw8§¢1ؼ«´°¹e21ìéíZ(‘¼5ý°­Ç}s=š'íîlç_n¹þN Qz³æ—¶njœÐŽ8òw<1/WœXåæÄ¢)cå*ÃþÞÝŠ,éû×›jˆÈGd)ÒM~EÅè‚‹y¦#!ÉòTÊ“43Vë\”©Ö2TÄá4¡Ë,Ù4ø[uøëEÆžYJ&F® ó÷=ŽmA-ybƒEÍ0¢– ßŠ ~Ͱë‡CS§ÄîÒ°ÖÞD Ë˜u mý™ö,}™#žŸðìæ‘$Zcx$:YØRF.Ksr…®wNµß÷FñO/ˆ™ij&B9Cê¦Ö(º®vg”NîJq­­/5–á¯Dœ¶k¦uU×Óãx=_`¢T:5À/CÇаhüGÓaâ®§¡*¢–ó´Ì¹¹qSœ•³Å,™–Ú«î±'ël—ÔD‘²9ˆÞE(Ñih†ÉNbL©TzÁ‹Èbü¼Õ±jIë\©äÝOÜwûglYß7 »¥mµ$–ñØlQ‡õAZ9O®›ÉÒªÓžÖ˜³cë©Ýï1Ià ©Thʤr’¡™N ©¿!"2Q2°>.^þ¢xá¬J«äþ‘¶ ™ÕžYmz)å2K…'f!Ñ.¸ÜVêTfž?cû†æŸ 8¶]9b§q}Û˜WÍá8=csœ›™¶£M°b J–ŠâL Ç}5€uÑШ8¦¨Øe ìí4¥F, 2Hµ@£¾‹F}•æ™G7«ñ k¾âÊÕ%ä•{ç/Ú{Dt‘æBDPd:0±ûóЄór&œ3š˜¡34±ã4E°P9»÷—Au š¨¢ —14™@»MK¿ã©ªÍž¦ìlݤZHSûíÏÆE›‰([#†q‹Shû _ÍÎ…6^8ð2-»{!ÎÀ‹—Ù<9À3ñÀ ‘)CkC“¦ 3㦠G€.¯ª…A¯óL“/ãž1AÇâó!>LÓÁ‡™gpðrSEšAû?f¼„rÚv}CJû)°¡°‘/“ž³…oäIˆéªh(e:À †¾5ô—ÊAD!£¨áÌ=ç¡d¬¸¨‘sB h8ÅX;·ÒÂÙ8°{êÂ4X¦`¡ñÍ@WÏT‰KJŒx®È A¾-äQ†ØKYšq_²yŸN3øc¯"’÷än{mh÷…[ÀÎ/º…œ/Ÿ?Ë£¿x`EÎË’èyK1B~Äý×98+D¨‹ÿ) 0¸y ŸÃÀÔ<\}ú:¸ [ÈÄ &òôZÓ4£þšÌ²‡Ù… ©¸…&ÑÝÂݳ"É Ô[z¸€ â7I9Û›­UJW«¼dÚ1Óvgó†ª{plÍwOìØ<ÇœBrâÃÌß½!ze˜?µcƒ>-Ê2åÅ™OWÁ4‚}p ˜sz€‘E(øœAFM¨€ËV®Î’Ï£ B'ë>à‘WUxމ’®ÜÐûî[ó¯‹EðëvµnÎ(¥ÊI…@ËØ`ó èÝ>VCµ…¸…Ÿm¸mÝ>´Óæj´>Le¹dO‡†´F64Éÿ%ãiP@"1Ø»*£ Dbϰé]0l>íæØŠˆé¨ðÇq.„š -8Ÿ×–3çv×çnš€ÞB¨’©äwôé( ´CX"›B¢eâfˆ{[ÍÕõ{=m>¹zÒXš|!hëE|p°Ún›£=…ùÚ*šgîªAíÞï8I¸ÁµºIÇf–A£¹Ë íÃËîÔÙüÌZµLÞxàdO6{¶Å1Ë]• êÄò%o¦¶¨X„Òzõõ«Vâ¼Ô™òx:š˜äB˜+‰½+`/²ïˆ–sSôcRwÂlþ Ûm!ãµð‚":ŒûªìoðÂE÷ xµ™WQ¦ íöŒ­‘áµ@LúÊ…4C`Ä ~áÙ…G%€x>Ò$ÌapÄÌ‹~ð}úsÁ°ßÚ›,¨û†¶ÃJ/tµ‡£5æÒ¿*˜ ¼c—æét˜Úíi_Ñî§±ÁCE 7¾£n°Ç>€–ÌùÇ9$™cQ û‘ k~fðé hr~õ5þi4¸VzñØ×-‡†î©ˆ\†e<§¡¦kH4¨ò¼­:_úv‰ã¦?uuøžÔÁ5nèÝDÃp½yÌ7‹Ï¼ -h­ÐŽm/~EUø‚¥ŒF¸¦™ÕÝŠ6ë7ÿÁ¾ù™ ‹àP{Z3A2? íÆ ÷išÝ&Hš¤H¹ò%ð]uh÷±GS¸¤ªÌ׈æà1ÒÆ>sãi¡ÎPvN]J Ømà[-n¼á#Ž„û•Ûï«·{÷þ›û_yžêÒGw“³R¶¼º©?¾÷—)–¹X^zïã§Ö? Ñ®LóùâþrE¤PzΈǩ©j$ÛïbïNˆ^ì9w¹,K’>ê hxh™î•1oú¤ð…­±k7ßÜßüóÆdÇÙŠ™ß9‡^m7?ü”­jèØHüÉN:¬r{IÛ¯¾¿ùîæù¸ÿbŒ#›ŒÛÛhÉpy–eYòýTuu5ÔÑt¾Hå\£ÿ+\Ãvíƒÿ/‡@Šy½ý¢‹‰+£;пKHÖù endstream endobj 101 0 obj << /Type /Page /Contents 102 0 R /Resources 100 0 R /MediaBox [0 0 612 792] /Parent 80 0 R >> endobj 103 0 obj << /D [101 0 R /XYZ 72 744.907 null] >> endobj 100 0 obj << /Font << /F34 32 0 R /F32 31 0 R /F29 29 0 R /F38 34 0 R /F41 56 0 R >> /ProcSet [ /PDF /Text ] >> endobj 106 0 obj << /Length 2306 /Filter /FlateDecode >> stream xÚµYmã¶þ¾¿ÂÈ—ÈÀ™I½&mäp®@ƒ6ë´E“ ¥%z­ž^\I¾=÷×g†CR’W›ìè‡;ÑÃáp†|æûÍþî‹o¥Øä,OD²Ù7yÂò0ݤNá„E¢Óå ƒÑ—‡Aw¤ïè‘Í´‰û™¿ÜªÕãm˳`âqĤLÜPv…ÁÇ-ÝUײ•Mv\äLŠ@Vɼ£}XÙBDÆ/}¯ÇKߢw䉅 ªö|A3óTq ê‹Q–UÑì°…#Ãí¸Q”pî% Ý¥/œÔåÚÚªèJÇg¤à­…軎µjÎu¥½zÆy‘l•³n”ƒ•Sœ´uÓÒ.êˆõ@÷Zó{V¸¦‹LwëŽ-eyr{• Ï=·»D÷ð?Þšñ{ûÿÊK IômPlOÆÁ†îK‡ƒR“KxŒ•jT–çz¶LÇ®§¿'®¥/`]õáÒ«€CD¤ÔzXEQž±8Ê— «FïªvÔ=jôѨU¿S_£¿‹YЀkïFUŽŽƒ_EÜ GÀÛY«h®ji†¼w@x¤ìÇÑ8© ÒÛ-/G\Ž›~2®3£°97\¤qæ“‹³p$$›$ò÷ÍÅiÅlH\ù”0À3DDÜ?£ê­‘…\„I‹3qÃÛA·C5V(…ÜŸ.PaôÂA£®48Ø™­Eý„E‘OD«ñðíóÂç$qhMzó¼üX0‘yû›WÈoª²öKäç^þéùéŠ|Hëý\ºó–Õ]$KÓå+v)Õu [_ñ'J‡Î¡’É¡J]ƒ?¿Ì‰î+HŒ !Ë©1ßõ\“±tÒò¥žÇŒa–/el#¯ä¹ =fSzpÔ»d?ñýù uKK$X£q{6kèÝáߺi µ*i± NV†^#;5‚+Åü½KÅœK8Ÿ|y>ƒ69Àæaø®ù>Ò{ôC[ý—¸Íuå9ÏX˜Êù>wY‘`©ð ¿þ”Ï×&œqìx_â&9ìQëÆ2\—%,ž‚Pù 龿"\Bá˜ç¯òÀü©>«ºÈF¯ŠùZüxN>x6ϲWÅ¿üIüc6NC07aú¯[Îù<ù>‰ÊËÛ›¬=Ë>_®uDQÈRé ÇèÒ Ø™¢¤Qz¢/  Ð ùš…\Ä,žjC8”šQ"L¬9gË\¸oŒ¸†5Þîx¢ïGÈ:ª/½ž³•YÂýõÿãm׫‡µ-èÝ"gî¡@k°¦<þ¶kÎ]«Ûq­îßñÂDg`…\›¬ìsÛ"Ϊ´sßá9¬JsAiØ=ƒÄ#ÑUv[&Ä4x%‘Ç+i攕GMŽLJ]Çi¦ŠêAc½(„±A$µ '.Ôn¥¬ Nõ•è,¸¹j ƒÖº›ØÛþÀVÄ 7à+z 5!4æWëÏÂTÎÎPÌnæ¡n–ø”mç!wL ˜¸ gЧ(j/‹ÍDÔP+··³+w eükO"dQœÿ[ ž"á]Þ^´-y~—(4 [ œœ#§¨ 13p=†t°rNº>/5«v¶~°-\a’±,»Éöê UlaêõÝ¢Ù–r±?þ6• ¦˜-ãOË”˜@¯,cÛÑËÈ®vúxi ÜHÕÕx%R¯ÏÐÚÂÚŠ>Ø [Àhuœ„æn ¹Ô»E_eí¹éc¼ ôsvܶ¨=\]+äš"?øÃ´i–†]?Z.sI·Ý8-Á t®1÷Mfîp¸I¾Sì2L½t¶Á'$²H—P`Üf6lþŽ–RX¶úÿþ³ç úÙxÆŒ`ð6 ÍüNØðˆ„ &‘ºwd2© ¤j¸‘^vÅ¥q· tlða&‰­Å&,àìH ºÆY/BO5 rôº¼Ú/´jé`º-EQSn‚zv4u§Ìã›4ÁZFSF°×Õà $(Œ9U¦¶%BöºîL(è7À¹VÅ9c_ÑdÐÁ¨§!E 3½ˆð0Eí%É…*ºUôÈø”Ë\N [Y™BôþØŽkv.l5L÷Nßê[ «Fß¾૚)Q,dNÂ7[N|ÂÆo(FóYl5™æOyHsøq¯ ûb?ö[.·|ŠÒ5tÉNZ¯—oöÙÂ3Úá…/L™ì?è“ÂGݾzb]pRç¯6®ÿ "ÌóÕm,ù ŸåÞÿ¯Çß±ªÉêÊ>5õoG‚(î¤M<ñh±?}Pws2p„ÓÃø›µÆGÆP?Šy>5Gx¤5³š®¨˜ÃéLË ‹è<øÛI·¤Àè•u_¬wÐy…´øF•çjÎ_ABµ´Îß5Ú_Qëí‹88!xØÒÑn¸W‹#zu®×Y8уéÍ£ó®›²DD‹óǧyB°ÌGÁž»¥Œ†óÝòo ¶ÔCÊ•¾T§á:2z×4¢èëŽâ0)õj2X˜ZÆ¿KžÁ­æÛgTñi¢)úLeîàO_fަܘÙ_LV‚X3Etãz‹ø~uûTéƒÙ¬Î1ÝÏÝ»ýÝî°Ý 7|“ |ÐŽÍÝ?‡›Ȱ3“y¶y4L t½ˆ¸zs÷—»oðoO‹8ã³=tkÐ¥sn†K·Õ/íó3 endstream endobj 105 0 obj << /Type /Page /Contents 106 0 R /Resources 104 0 R /MediaBox [0 0 612 792] /Parent 109 0 R >> endobj 42 0 obj << /D [105 0 R /XYZ 72 744.907 null] >> endobj 107 0 obj << /D [105 0 R /XYZ 362.694 503.372 null] >> endobj 108 0 obj << /D [105 0 R /XYZ 192.626 261.964 null] >> endobj 104 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F34 32 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 113 0 obj << /Length 1134 /Filter /FlateDecode >> stream xÚÍW]¯Ú6¾çWDLGÀÔøØŽã8U;U­Vi»êv¸Z[M9Ä@FHXJÙÔÿ¾×_„ôL›4iW1öûý<ïkóz9¹‰@ ”s,×ÁEŒûº‘vUT­l¤j­'-q8”ÅÊó 6€•·Õ œ ƒô¼6LÕ‘÷§öHq&ˆâ®–…‹í¨º ,»`‘˦¸”$w§¶ˆzU»õ±+[Säòbe¬A$Dç}'ÏÆÞ† y9dKÌxŒb¬ï¸hbnÏ~p{rØn3ÝL6žÛ–¾‚]u]½>V¦ÆžŸ!רærm{Aî³¢ü5Ës@QiRÒEs˜‚ùbÚï‘£‚ªæÏ¬þ¶V­Óö×Kû16&´ÚäôÕÔI“Žlƒ¾5ã7QYeµ%£ ‡ß¸k¹nöYYüá4O¦ ÍÊíìªúÔßYeÕXl»ì§wêÕrO—;ûÑþGS÷9 ÇK‘à~v»±q™„lY€íËÅåîY5€ßÝoþy8ÖuÓ»œo©?¼±µîßò%þ/ß|âé7Ÿáf¨k®F}â_?ú\Ïêç×a·Az¼êß õšáÿø^¼©È׌6—¯‹ÚL&ß/'¿Oô4Ã Ä Qž«ýäýGä° š(‚ðOFh0.à[“Ÿ&¯õ?š^±LÀåEˆ{«¦¹Æ1™ÇзêòМ5ùÈh(C‚Šë[µ‚='Œ!NºÑú°ÚB²à'ó7}vžïb¦ endstream endobj 112 0 obj << /Type /Page /Contents 113 0 R /Resources 111 0 R /MediaBox [0 0 612 792] /Parent 109 0 R >> endobj 114 0 obj << /D [112 0 R /XYZ 72 744.907 null] >> endobj 111 0 obj << /Font << /F38 34 0 R /F32 31 0 R /F34 32 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 117 0 obj << /Length 2313 /Filter /FlateDecode >> stream xÚÍÛ®ã¶ñý|…—Ø@ÌIQ— íC‚m-r Í(mÓ¶ºº¸’œÓó÷r†ÔÅòîé¶@û°ë9ÎýÂóÍóÓ»ïEqÎ ¥Dô|Š2eæðzõX )z³à¹Ö}M§+CsèÌ`ðXoèÃŽ¼¥ U‰²!Ö:¡DÞÓ¨Bw]»?oM9|`âv¬ã¹qõʱõšÁÆ‹ýÕdÄS{£•¾ÖUeº…m{o=½¯¼]ÉÎ „% Vƒé=’ ÷A&‰=—KE2¦‡e ÐõäMí ¿ûFFAéƒ.›~qz4¡[¶ê'…8þ·ü7ù<ÿEÄSY–ç”ý³®ÞâØ|¡Åà¹Ä^“mÿêÕåc[wækº:™\ ¦³‰(ÏÆÌï´õ0Ÿô9ˆbáΡ²qFNš"ð}ÿwˆ’‘:sí Dš±{''[á~î‡5¡>%Ã\£Z­b39ˆÎwúpë t}'Q^D¸Ž‹½1…%n;@çê¶£•÷BHâ¤8Á}Ý÷í¡Ô¨Àz)º\¾Þr›­H”\ì†'B<}P-xc_bx±+©+Éä¥+)Ø¡ÚŽÑ “܇ahËq‹¿}[„€l mû{®·ðH&´‘æ@}…¦àÚ•˜VˆŠ,#¶å‚üþ#ÔjýŠÀÞ¬æH # —{¬, ¬óMƒ0mËþƒMµ©ð+ þ6ÐÌ€ßÔ5$äeD»„6‘IΘŽJ]ä/iZ°Z‡¸ýk?˜Ú]Æ;Ì ”®öØÃ§•ÆL¦’e"÷-ÐZDY@+:“Úôý˜žwI,X‘%ó0ýÑ—¯E"y¹”Îï1§T¦ýÀ}Ê!=÷´ã½“xð6Í6hêÜV[ÈòG„µຎ@ç`nÉ#Ïç¾çm'Ñ'Yó<Èêèén=x€¶NÖ`µE¹¥¶[³PI›'^õH쇇]¹?0óZJ”¡% jü6aÓ"f~XŸ1xЫâ?'Ko«‰šVHÐÀ¥Ièúޏc;‡A-YšÌ¼Å¡ŒNN˜+…F”N¤§LæœY(ö]\è>‰y0{ªb/P²ùÒ€yq´h.Wa4[i Žp)šì²6°,—a®ûo ì(1$0:-ˆÐCólÖCçnVT“% ŠØÈö§1ñµ–ªìÈ2¦KCV¡‹]Ü»¶%Ö|M–³këâð`ÌÍXÆ‹E¬®éM1ün¨µ”ßÇ*ù1íkvÖÐÕ «hz;}O“îjŽY,™bg3`±Â?¾6|s ~P¾Å²uF¤V™®úq± y¸¼æXJ°<ϧ׃^Á¹„|œL]ŠóÙìŸ3¿áPìîݦ,•Ùâ֕Ƭ`ãÀ=N\@Ô;‡/‡n¶Yƒ³8sGotí³h5iY­ºý,…æt©kƳ•W|Ôzx,áÓcà‰ª’Y^Öü•ƒy‹©yWTd3S>ÅñWA)ÔÚl–Å¡ºf4™¥U×l:e‹y °ÕÕ¢‡êš„»ÂU×Ô÷¦°´¬ªYü¨ª ðì{µþöaþ$÷ø“ršøF€ +–rîÿ'sbh˜>£ .z¸IœÎ÷ã—ž»FoµÆ)+ ñéøxÌ‚mVä÷up6ãxE&ÈsÕ´Ú+yÿ†(U2šcâ¢Gܶ¦ÝÙ·®ƒW U X …ñù½ˆJí¬;G¶À‘-š{¹´{D:q3HSösœ^Õw­Œ/cÛ ,;Aq;°Ô_Ú[uDxOh»´FC¤‚é§Å 6Ê7ç[íXNvð É(£í^s¤ 7'·†@ЗÛô²éæ[à‡Þ„÷¬¥ÐKѤ´™ÜØi¸zÅï—²ª:è›S‰$M[ ×5A㋃Ýoñwo<ÑÏáËà€oñÊÚ=§ÉÆ@\*‡ð†êèŸÌœdocæÕsäys³ú oE«R>ÒCÐÜôUmêÿºT–]r`ðHÁZU+ Ÿ¸œM=þ}¤ó‚3>§·ôtŒ4¡P…Œ>#»œõÉ‚ŸC‡Æï*¾c§N€J’áèÄšÅ<îéµb-€‰44Ñi¯¦ùHí³ÍCaª.í‘ t±œ/: ?:Ëœ)@×Ëǰ}Öª¶§m(AÀ=Á°1zB>®! ÙYÖ‰8 ¶8õoù°èAE§[…0˜¾ñ«’Ž,”h¹ùsßæÞ˜ã㟎¶€j%6¿sÿÿe-»Cs=þ%¨/Ϯ曓 „> Ý JÙ?+X ³ÖLµ];¸lÿ5>£P«’Ž·<&¡LQQ‡U Î,õå6Ñ’ÖsrA°ß´ƒ'4{΃øúmµ{ŠíºWüÔû_¨èV36úG´öÆk‹¬sÙ¦3ĵCöJÿ-„fN¤™ý+ÑO?ÇÑ–!C@—G/©Ž’ÔVÖ*úñéOø—$èò'cšˆá\öî“à“,çoü+àŒz"¥£MG¹ò2ü ¼®Ät endstream endobj 116 0 obj << /Type /Page /Contents 117 0 R /Resources 115 0 R /MediaBox [0 0 612 792] /Parent 109 0 R /Annots [ 110 0 R ] >> endobj 110 0 obj << /Type /Annot /Border [0 0 0] /Rect [448.323 683.531 527.526 692.483] /Subtype/Link/A<> >> endobj 43 0 obj << /D [116 0 R /XYZ 72 744.907 null] >> endobj 115 0 obj << /Font << /F29 29 0 R /F38 34 0 R /F32 31 0 R /F34 32 0 R /F41 56 0 R >> /ProcSet [ /PDF /Text ] >> endobj 120 0 obj << /Length 1486 /Filter /FlateDecode >> stream xÚ­WYÛ6~÷¯6*1MêÖ"   }é±ûÔ$Xh%z­DINâß!g¨ÃÖÚ[#r8ç73Ôë»Õö­ï9)K#/rîvNì9±àŒû±sW8ïÜ~/×/n¥e»C—­7¾ðÝ»½li¯ìð™U" »c“÷¥jˆžZ懻ßAs2Ñ,¢ˆ‰Øw8*þçjvå#ËU}PlúŽ¡ ú±Ïš¢kPÊÔ~OÄ,J+¥Vű’VmÖ#ÕÊ:ȦÓçŸø,‰bg#KÃfU¥y¹«‘½H¼çÜ«d‡/y+³^ørìʆ˜´ýš‘^sz;¶YÙ«æšL'¦s°Œ÷´ •ZõÛRkºŒV²8‚õž‡~b! ›€³$ˆæÞ½©TgRB‰Ä\6úÄè?¼ìeEç)R@=œhÓDœc¤&,ƒôÝZp7Ë{Õ–VôNµ žûÏüDX×ÁªÄhÁó `¾7 ¡“‰A/ô–Cúcó!wIÙ-›^6…Mo¯ð >ꎇƒjû3€´ªÏ,ì¹qÃÙáP•yFfå^ÀRÏÿúXV…Á‘ÏH=íáµp­!¢5u4éÄ5‰„®’â»cáfIOŒzÙV'|ïzÀçã¾›¿š­¶€€±ÐåN,ù^ÖŠm¤,¬^E’k ˆµE»Éçèk%äË Ý/ú/kz[!£‰&Z E½y•a«áZdÙÚb“×>H{˜0_ãžaB!}‰F¿2Ö@{‹u=p°(¡}kuĹ{ÈòOÙ£¼¹z²]!£ÆÝÍ•ü;°Î¾ÕÕÕöÕà°Ÿþwñ¶†ÎÄ{Oa­'lã±þt˜XE²š¬†Etúl`! #2ðg³B]9“U:›õ}[>û™Tƒ^áù,ö£¹³­ü|,[YÜ\dg-Þ„)4÷ä,,ÛIZÎÛ–M£˜j7~Ó¸<Â8“Šz+)·[b„©fÓPÒĤ cÀô"¶3¤»½:VÄü@k•ÊLÐt£Ú:ãi™9¡ûG“Ë3³Õ¨‡&¤©÷ðyÓÉÖ§¢(«ì¡’/Ì FÏ`ì*C¿ªÒH³ïEÙêšÒK÷÷úÔý½ÿO]¡4S³v.Ÿ»c:Qf;‚FÜ­ÙÖòGË.D‘Ëè"é‡î’‘%Ó¹yœ–"ûlÊhàcúÍžˆaàLºÜõ’ç(€Ùf2½¼<¡²Q_‘(>õ\ne×úú=Ýâý'…ÛÝY#ù…F³\‹Ðý–Õ‡J^\ÓÆnÄl˜÷{{G˜4!\¨³“­Wõ‰¨ò“®W9žýáÑeÃt9¼(œpÀ]Mx&.;µ8"èšôäÔ †AŸ€Œí—¬Ýÿ¶>kK Üé.hZ°?×ùË:˜@¦Ï,Ó¨NfKkµì:€ ./h}fa]MnºƒÌË]™.àf¹ÕPéË â†YY¦X7Läžç4Ó.ür;‹ÞÅô=u?/%²mñN{nNV`pG¶ D&™¬‘ýu(‚ïeB$ÿOÈ7K—£íÔÃåÍ@úÝQîá‘¿åNO2Ÿ§xsü‰ÏÂP0ÿ‡†O Ô|ø øBåÇz€‚ÑdŠØ¨6[àý¨Ãµ‹)ŒËe3Zi(vƒD¬Ø€;´éEfCGyÙ1ßáî? V’|«_ïVŸWú‡;B‡óàû4¯Wï>p§€eÀóÓÄùj˜j'0ºrnW­^ëoz/Ó‚B/"Ý…".܀à ·=Ì ¸ê/Y _(‰—œ}•#ãL d C¦ni¸yqâ¾/öäÝ¿n}» endstream endobj 119 0 obj << /Type /Page /Contents 120 0 R /Resources 118 0 R /MediaBox [0 0 612 792] /Parent 109 0 R >> endobj 121 0 obj << /D [119 0 R /XYZ 72 744.907 null] >> endobj 118 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F37 33 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 127 0 obj << /Length 2128 /Filter /FlateDecode >> stream xÚ]ã¶ñ}…^‚Ø@¬ˆ¤>Ó è]pAS4ȵë @syàJôZYIô‰òmœ_Ÿ)K²6wé‹M ‡œï/¾Þß}ùàA)Oƒý!Èx±(ŒDì«àçÍÓ–EµeÉæ²Ý‰(ßÔýGE‹F?â¢ØeW5ªß²y%,Üîâ<Ùì=^%·:辕­ÍÐ×Ý£¡8὿ìÿ lå¶8+„1…W¹Ku*AÊÃ~÷ÐÜbÿ¬îÎFõ;Ò€` 9ÈA6k8¦Nö²Îº¸çf†šOPOÒ˜gÝWWÔÓ3…ÁšÞ¾\SÜ ˜S#,¼lçÍ&À×rNG¾Ãèö®T©9/’ú<,¼æ~éXF© ¹ãZŠ’e…5 Œ–[>c`O×êê ÆqxñLžò$Ë”DŽï9zÕýÕz°Y|Àoß¼%|H@_øØà‚ÆlÎW%ž¯W„û/9òÑþPð£š.fPí 팅Ùx‰É”EY7Ÿ§¾½Wè©×'mdC_ÏǺ§ †Å³ðlþ™õ<êîŠk¥ž…Ê⾉ØÎ>àE˜xГ,à?ê)EdÕ•7Üů1çSînÍá‘–|Íu6¿ÉòµæÆ"OÂD|Ü'x¯×ºA¿o ýØ×`JÈ/æÉ4ÿ_ó/=\Sl&ÏŠ';¬?Çy²ÈDèkÙþX»báÿÇ ¢gÂ}îËeZoãT§,µL8@ß, ÚokµçlúàºœÚ Àg鹬»AõÊ ×îaW–rª6à3E!@KEÈ ×õ¤ÐõEPÂ2cç÷­nOºƒè3®Ôôÿæ·Au­ïË#Ôô:AH+Âùæ÷X€SNR¤l…ÀÔ"¨½ ÐxÀеXNÙ€=iiÃÆ j/fíóguk[ÍUµÇñ˜žJÝA“}.w·&bvfðò"§Ö-Á('‘µ؆ۡ]NÊI*·ÚÖ-‡Q7r™œÇîÓÔ r땳ÒiNª´-mIv-Ç]cÍCH£\r$åà¢i4}^¸s§&ЙjK»ùÄ!ƒ=ªya=5Ò»¿w¾yY½^ŽM㤩¡9Å‹‰Œ K7Zùd=Ь÷#ôÑuÿDp¥ Öq‚L!@>€5%Z¡£mì¥h¯$b›ÿ!NP!쉬¿mŒ}{§ÄšçŒ#Ö9 'êGSº@ª`#÷ÈI ø¿Æ‚N˜œ5--+B­fÏa“?VEw‰[Þy<Õ|!yï*Â$áž]éjå‰êƒc]•v餬ªÚék‡æâq”Ü9ñ »ÔÚÉjm^K¹Ví«Þôê*#æ' :\ÒÈ‹GÕ©báBŸäÐÂ%3üïÕ 2%pGŸ6sº>ËzIZÄ›{Ý*œ¾ÝåÀ§Q3:Åü¾}ØF9"º-ÈÒŽýð+8¥ÕÅŒÂvEuæJš´ ÌMæ1¨ “Šj< c3Ð!Ÿ,˜r¸òzÊÝ:f8‚£#fçÎj!R•»×¦ø-ŠE*J!Šü¥+mƨh¸øýÊ̦†Uo­psÔç¦Z ÌB0ÞM 5|[7ÌÅ ù¿!OÆ´N£ÅÉzëÊ#Œ])󸊄šá/=Ñð0/ƃ>Ôl­ÌJØ‹ *ÃǹÏp@¨•Ë‹ýp¡ÿ;JÛMCšÐ$ å`/BnaŒ"_J–]ÞJ4©úX\ÑüiôP‡ˆnèºæ%bÖÆ¿\¨©ò…k{@GömÊ×8%.jUÙÀ¸Ü„bBuæSŠ ¯y®Hߌ+Á_Ñ;‡öCØî'ÉÝó)³å&y?¦ÎRâ\W#¥^½?×ýUá“^U^Æ]¸pOìÚBs æ“@VEÖ;›` ¶qè×V‘må@°?†-°^Û\^xŽ\wô;žEkY g:;6ZÓÖÇÝU…ñÜ¥;›nè½Èo£ðn[L„w•õóöu‚ÌéE½¢å0ÚtG¾ðÍЪ¥ñÒ¹‹Éñé¡es4ñg‚© 7™=at’ºÄQ?M6®½kÔ0}•Æœ[µg¤¹æf~Œð–_Ôà•lP6@fœ-ßO§Ë$yšá”õó/QP¼&°> endobj 122 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 412.958 237.997 421.909] /Subtype/Link/A<> >> endobj 123 0 obj << /Type /Annot /Border [0 0 0] /Rect [314.459 401.003 417.422 409.979] /Subtype/Link/A<> >> endobj 124 0 obj << /Type /Annot /Border [0 0 0] /Rect [72 383.378 215.391 392.33] /Subtype/Link/A<> >> endobj 44 0 obj << /D [126 0 R /XYZ 72 744.907 null] >> endobj 125 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F29 29 0 R /F34 32 0 R /F41 56 0 R >> /ProcSet [ /PDF /Text ] >> endobj 130 0 obj << /Length 1661 /Filter /FlateDecode >> stream xÚ­XYoÛF~÷¯P]–‘æò¦û!Aò‡¢…Ý>ä@º"Wk ËBÑÿÞ™!EJt }öàìßεz}wvùαg‘ù¶?»[Ï{Ë´œ`v—Ì>Îï6ja8š«…ðæO2ßf¼—ù¶,TÑÐrÇÛ«…mÍÛ4Kh¹QØ¥YÆ$¼“46 ãóÝ{Ð$hb‹Ðv4³H‘¢LëŽl¨°c›QtT[?È{µƶǺ°‚²@ ö´`ºcÍ’Ò\ndÍßtÆÕô!+eb ô™a{–?3„0#Ï#¹r¥a±œy[§Åý„9®kÚ¶×éù"þU3aÐ…¾ÓÑåmÝßóË¢‘i¡Zj al6üýOâ À…ÀÓw‘§°<3ôüŽisæSžM¨à[fÙõñýdYv¦^ÂÂ,³FU…lÔð[!sUÓF.÷4)Ê#õk•©¸é´_íÚŸBP{fàAýMèÇ=XYÔMÕÆI÷Æ_ ˜¾íÍ›J‘ØDÇ7eöÜãRŒ¼ÜCW•<ÛV%úÓcšh£;,Œ›´,˜~¿eöMIcÝnµúšv_¶mîdaøBpÜÁ‡B-À“w|xŠ· /A#-†Ê'ógãÏîUÇYÚÌ999Q0©Ü‚uÛ*ÕØè@)c>‡‚_ŽYÕ›²Å· ™ÄÞVYY>0M™«fƒÑAÒ‡…Î+,;­¯z8¸®šŽ £:dÖ«Á¥ø Tëôéú\§³VÕ£ªÎo4 Êô @‰ø4û¥ßmÒFýW¼n¡0²¢;¹£EþÅ›ìHT£õ”c¢÷6™Ý—UÚlòÿ׎ÍÈ/Ázs*­'ë³ÈÍD-1ºÄã@Ùí®J1×ÞQ7m­ø°äÉšF*Í0©Y$²Jh5,4°ì.¹^²6î°Ââ剾Ë9Í„êP4·/ßÐ ¹–Ý‹å€be›æà’2IRNfüÕá)“¬7AQdú‘3®Ìƒ’È¥`µ?T–ÓúîC«ø£“â„UöO½Uy™´™ºš`ê@ËàöLO³çk oØ÷Õ)_ˆ¡ce)?Yž?1¥s`VßÁ²ç;AdzÂçFÄ;´Ù2CÓ÷Ü“Z gï)ë¶è:̵÷ŠPw*°ì\Ž6¡D,\kþˆW¨ªº?´.+šH¢ãª­{ißCÿ‚¶ Š;}M&Žcµ¥î¹? =„®þzKû#lVªi«‚ )T`òˆMÌZÖM÷.=ð;@ùݰùXq?ñ˜|o?ñe€lMéꚆ¿)' 9ÎA?we5+¹Š@YÔcµ¼_®¦Ró…NÉWD†7íí’V£^ìr’Ã6-°O8ˆÐîf‘ó,‡8zÉ aÖ„Â1Èá/£;C†W'XðÑ”Ov׋ó”ÓþÚWyt‚‰#‰éçƒÞÇGUV«o0–}EúÝémU‘]mñP”»â¤pi¹|I/ª ž -Þ赨޸b|wþvÿæKu;î`ÔØž mIÓdŠæ ` ±†n¬h–´'™~¶f lC„›NÎvš(Ÿ¹>²Èf·g¿½Æ?wìhüçŽk›B°sŠÀÏŸûS,pðßkzžáè\c€ãÛ'h’šßÆ•ËNÕåÏÝ¡ endstream endobj 129 0 obj << /Type /Page /Contents 130 0 R /Resources 128 0 R /MediaBox [0 0 612 792] /Parent 109 0 R >> endobj 131 0 obj << /D [129 0 R /XYZ 72 744.907 null] >> endobj 128 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F29 29 0 R /F34 32 0 R >> /ProcSet [ /PDF /Text ] >> endobj 134 0 obj << /Length 2430 /Filter /FlateDecode >> stream xÚ•iÛ¸õûü E120fDêÞ6šbg·Åb›6S»IÒ6íQ¢Ã‘äLÝ_ßwº¬IÚ/#úñññÝçÕÃÍ‹û@­2‘Å*^=V‰Z%Ò~¬ö«·Þ›¼Ú™õFE©×=šõû‡¿Àpt ÈD¯|ÆþõiÖÒ÷¾àZúžh§0&1¾3Ž„Ÿ:zÛvÞuöÎˉnϼÆ|>çi/ºc8rE ]]žêÊTödcºsS™=cåöœæŸä.©› ÃëíG³ëîàG,’{ÝiÇAêÎÕ®Ëë %Xm¤Š(„¯Y1ï;]ªL½-‘‰——§Â”ÀñĵL퀱«+÷Œ¬ðΉCÁ ݶ¼¬³½© Ò "àG–yïüÈÿóavãH(9Êrs.,§­)ÌÎr»ÏkyÓ zQ|*x/§fZ¯˜m´WàÞV·¨„ó~À b]½ó}u<7ýé¼.ÔÅÙ´`”@F^ÞñF©?‘#µ|¸¬Ã­©ZK±«ù{nížæß-ñÉ Ap:Aþ„pö›vIJë ðÓ©©OM®; ˜Ê/@ï²'ˆXYdlgPX:?‘èwr»{¡ï•ùñÑ)êú“]嬔áèw6ÎÒU q‡g*Ä*³2K‡¦.×›âc÷‹i„»®e0ðU7¯ãÞú›Ð esjU·FMeÀªèÒsZÿ|Ì;ó3býàîxçu^}šn¸«C¦6ÖX‰x€'¼¡ãÏ’T²‘š’Ú›úð!¯òîÃ<þ° µ†!Ž.–²7d¸·ùr‚>¤¥=¶†RšzÇ3‡ ^C`UŠdCÿr”ÖTˆ°7Í(¨÷ äôˆ+°´ˆûM‘W†)ŒïhéÉ(ðð¹÷&ió‡ŸÒ î•ç–‡¾M3I4ë|©C÷áM¥ü˜‚@­ù|¶‘ @zÌò'ž‹àödv.ÓàÅ2|&¢âD iÓ¯3¥*(Zr>*‹ ¾6Ô¹xˆà/‹Ü,M`3•L•±´p´k¸‡ËÛ>×§¹¨\*‘HӾȲo&“Iµ…<5”Û—ÏtéT¦ “l¦¢÷Ô;¡uAÁÓzÅ㤋…?øô KTT¦¬6`]ep8ÓH ¿@BöHJðcäëy€­épðù;ãÇBBÕU“ÿÐîHïp Y ˜jèÍß ñÝEw®†Xi¦ÞŽÕ:Œ½-z¨H†ûÅ’ÿŒ-tË”vß¿Áû1ÝCà~¯w˜ Ãȳ] ¹A×Ýœ]Ÿ€pErð1ÍûøÂ?lÛºüÆ ù€<ÇQç‘Ù$ÆîL4»Ï>T(‘øržRH^0‹íÑôaÂV 93ÀOû| ÙæÂp,d¯Ð½vâ—1F-Éž·iF|T¤*êlP)Fï9ç4HIaæ»vy¨,0ý<_‡R=ÌÖ᬴uUX¹Ü+Û&H4 ËÍê“Þ»ÕDÌ–!š ÞDe„YñGáƒÌiS˜!¼Ï-Òbæ"Ê¢o¦ hw9Îz#¶\Ö#øU üö‰0jk‘KzÚÛ3ðã¹µˆÃKvO3žô6€Ã‚Æ®|ß|ÿpóùùñWÿÁÄ'«]yóö½¿ÚÊ7 O„TB^¨+Vonþvó ÿ#ï¸nC×®Ô*„l'íÛ—ÌÜUÿY¢@ý endstream endobj 133 0 obj << /Type /Page /Contents 134 0 R /Resources 132 0 R /MediaBox [0 0 612 792] /Parent 135 0 R >> endobj 45 0 obj << /D [133 0 R /XYZ 72 744.907 null] >> endobj 90 0 obj << /D [133 0 R /XYZ 72 238.745 null] >> endobj 132 0 obj << /Font << /F32 31 0 R /F34 32 0 R /F38 34 0 R /F29 29 0 R /F37 33 0 R /F41 56 0 R >> /ProcSet [ /PDF /Text ] >> endobj 138 0 obj << /Length 3422 /Filter /FlateDecode >> stream xÚ½ZYãÆ~Ÿ_¡—À`uún2oÞÄl²;ÉCìÅ‚’¨cŠ”IjÇûïSÕ/µFšÉ"/b«ÏêꪯŽîwþAðEJRÍõâq¿H5I©YF fñ¸[ü²ÜÖÕ¯”ò§s“uE]­Ö\Ñ%Ö”9Y}|ü ¦0£)Ö\Ú9ÖÌÍ¥›£¬³Ý_ëj_<ýP”¹•ŒF%‚èD/¨ëþ+UÔu’lÔI‘Ô$¡O»]1µ<äÇìÛ1I~n¾`’©9Ž“š$‰ ‰Ì,‰1&tðó›26ÓD3óÚÉê£ËÏyÓ»¼Ò(HÊûÍ}\­Ó~/'“6bëgK91ŒA‘T)×ùg8„ÕZµÌ*_hòîÜT¾Ò}.Ž+÷M} CÝ·>åUîgñœwõ›ÿäÛŽ¬Ö:QË÷öp- “ýÆlmSaDèS´~òcÑuùx*©^Ö•_ó¹(KWÚäaÇÓ¹ ôm²а§I‰brÊ»Ë$Yv‡˜hò”$ÃyVÙ1ÐË D3뺦ج8ÈP—»©ë'dJ¨TÃYNäwÄ N˜‘#’\,‹=Nœ.‹Î-£6üQ´] ¼7ÜòÞv¨j×áŸïvÛÌo×2 *vy—7Ç¢rŒ…¹³²Œñ‚L¥i ãOEµ-Ï»ÁŠöÜvY—óªkݲEå–õÜ^À "´ög¢¯ =ž[¿Ýsë›mÚºôlNq“-‰pšijs â… *UÉÃeܧÉ÷y“W[¿xW»e}k»µsÚ2"ž•?dô—˜"+ÀGÑs û°ãü®èµQ@[/fu™v-àO:“ðavÄßVHуn‚( Ê–pRkÎeXþ¼!ËʳoBFá7sŸî|*} œd—UQ=áaÏ=4Ì T:üðs!L&E­®Û¢óãÐ^æ"Oød×Ý!ë@¤ú|ÈQ†(ŠYÚƒ@~.ºƒ+e®µ{ÝÕë0µm;f§PïgÂñ­ëYØvKXðã|û¾nfí³-%ˆÏcÀ):P8mÝë}Ü‚ŒÞ÷Ÿ° kV,Y>Qÿœ%²¥†…#0NÑ÷ëŠcÞN;Ty¾CöG”šqJ”L®)õW‹ ®S/ê ˆÔà­_’>™ìª›N%QQ(á©…žòáÄÒ9”`ƒìçªÐ5w¥à˜c³?ìˆ?N ¡‰x¥ЦKȸ‡!’PâÞÀÎE:v³à§XTŽnç@*ú%ŸÁ±c¬Ç¶¿cLñÎ!¸blt´‚rë9ã¼í¡>—~M>ã¥õš-S÷ÞY• Ø0;3@˜úÜX¯aPu‚ @,çfèÞÿ›b28ÿŠßÇu5⺂È'ÎuC`õ›\7fÂu$؇UŽØ($)I–ÞÃå1“·vVpÁÌî ó‘TŒ"5ÖÇ™PÅõ€II~;£ƒüÍ£1ÜpŒk‘&€œ3CtTÆ¥²Q—réÀúω_)ƒ%Z0?¹š^¹åÈõ…²5FX°)EoŒ°.jŒ8Ä©B¾Ö)cúESÄ(1Ü\Nz â“(" ^.˜7Î!îS>ÿŸìÒ,=åï­ã¾.Ë!èÙ…+Påëm~BÌ 7¾¿Çã@Ñ&Ðp^ö)Ûþ–=åñL’#&ÑiŠ,kḐä$IzõsGàCˆï›¦?¹©mˆwËÚ v%ÈMˆRôÅN±®=å[ëxm]gñ^WO­ýÁÿÛQ¡NÂ! A¶g’·tù‹ªô²¨Ú.ƒXÛÓyjj$ð38ƒ®= 0¡É`wAZ˜ø&¡mìè?¦|šž€l C¬¤%Þçè\Žø… ¹=I-@`ÁZ³„]'>Õ/C8G•L¯PîhÉ:GÌØšN †-±†Ívî[c6Ù™Û‰­{ÆP4ó3–õ6³øŠÊï3îØbÎCÍ]ý{¬ ‰è„Î`”ÌWÑ¢_ª.ûãUºô}¿’MF6Ya Ë.Ù€¥Ì}.2X¸Œå]„%Îø1 ™G÷ÕÎ~~@p*¬ÙÚ`èc’åaùÝ®Ory8tº½TG¹Îj“ä£)^Îàøâ9ÓÖ&"Õ%"¥ÔÊ+~ÆÚÒ—SÞ·Á¨½MܤWÓ „"ƒ7XU^Õ±D`JŒbQúCÐýZKóÞ«ƒRÒ«–2÷ÙÁ¼®äŽK ìz®~o÷Z”à3µ~:LÚÅÒÓ ,ßÓþ/LM^#yš0¥aœsð ¿ÄDî\Uh²„‹e{ÞxŠ•(“øÝÔQÒÖ\'ĈîÝeÆŒ`)¿¡mxâm;×à[>Æï=pV9dñ›>!ˆúZÕ!¹…?±j¦0—½OeVeC8NQ"ÙL‹œš _Š'ë`g± Ÿ4$Ñì~N©±/‚ùzXÏj7”Ô ÈPëûùÀÇÒµwýÀqþ\ôX‚-56õo(ÿùn6ØRηVÞ…†©kùŨ±ùÁi^Ø[à’Gž1üμÜÑYíbžl’¦0·£ˆ¢þÖø»‰cc¾|øýyOla X›h*ÛãÃ/ébm?-(Hlñl{±·£»\|xøÇÃ;{g=^å O5¨ óG»ÂÔr÷bif òNŒ][RõÒÚÜ Þ¢fŸE¨†$‚$Ãyé,J|Ï`¸zõ–yl˳UÏG=¯Q !&»¬éÚpe  æ[ä<€–L‡N¦R615qf2òô+qÀG†hvWÐäVÞg14'\÷¸"“¨k@ u:KÞöNFœ JcìþU˜¸•r3,{)á ÇÝÛ&DzFÉ)|Æø6á#’×vÛ§4ö!Ä=Ɖ1dˆÝæeÄ~[\_ ´‰‹ >¹Dμ Ïq ì¤ø:§‘#e:drüb'Úç‘”½Ô˜n>æ„Ù´ˆ¿¯»)jâðÇw ¬4_IãÁ¿¥Ú 3µÊE“m"Ú‰5ës<×Ä¿s®Hs­™I, èþ²Àúû}kwïñ&Ÿš÷!&§ÆÞ`üògPåoÜ[×’ùŽcŸ ×»»¶eêžá ç˜c©÷FÖÞ¯ð ÂrLæ¾CäžL0Âé­Ô4ìxH}½RÑÃËõ°MšømL]ñ›!¥C²¨·ÚsaKáÜ’ a‹µ”—YR¨÷á’i² +¼ë–ôa?–Ýuê¤9ø“I€lŸ;’,8•£Î½Éº;7µsßAý˜GŠtGBýGbœŠeëLû,1óÇIÚK6JR&n]hpÆØ``½ý©ÃÛ„mÖæm4Æ…ØÍ¾-xcëñ½‡ˆ·)¤ L°œnòoPÑZ—®Ï|Cà<mÒÌÁ´/ïð3‚)ûï÷sÑ„”¿µS2DPpúÞ§Ö­*ã„ÿîòf”üó4†j®žùNd²p/&y¹+Úðã¾å>FÆoÈ‹ ØÁ,€‹©±J»þ7q†SþEï¥í‹2¿rC„q g/ ¸õC’Ë'¨cË)ÅäÒø´I¥¦ìS/Ý¡át½Ÿ&—‡ÚÞTªðŠ ªÜUا» D Þ®äYÇŠ·l"¼K’îý“}Ž'C¾ ZÃfmí&wï& Þ½hÃ{=í/H±ƒ]ضۨ?2‘áÞœÊkY9´Zög÷é2èÓ§øëBN¯ÞîIŸ!ㆠ·>aI¿Mû(ªkórÞ+} ¼ Ë}0¶Ó‡·WÕ7øËÿÍ&wD}Ôå]1w)­ü —ñXqt]Ðß½ ‰©JÞ ‰çMÛÝ;¿ÏOe¶µï7ß„B&áñL‚Ø VÐV/Ëþœü×?¸ôÿíý|QìÚ0»ËÂBÑ= ÂæÚÏ6º Ã¿E¹ø¢èá*×?÷‹¹€“:'ϨôÏŒpñ xšú¿†ã5:׿%×WêdšmàéX9ž¾w°p òJÖ&ún™6¼\ÆMf¿\öùWÎ Lk¼-,¶®8\`X—Á64¸GWx¯°ì–ÿ øéN» endstream endobj 137 0 obj << /Type /Page /Contents 138 0 R /Resources 136 0 R /MediaBox [0 0 612 792] /Parent 135 0 R >> endobj 139 0 obj << /D [137 0 R /XYZ 72 744.907 null] >> endobj 136 0 obj << /Font << /F32 31 0 R /F37 33 0 R /F38 34 0 R /F41 56 0 R /F34 32 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 143 0 obj << /Length 1568 /Filter /FlateDecode >> stream xÚ­WßÛ6 ~¿¿ÂÀâ‹*É¿‹¶@»uÀö0`»ÛÖ­\Ü:vj9w—ÿ~¤(åìĽ¶@Ÿ,KI}üHQo®.žýåAÁŠT¦ÁÕ:(RVð,Èg<Ê‚«*xšnß—zùáê–#á¨`I”œÄT[9™±B‘3žeZµS$3–ä^f×wK‘„wu¥—«(ÍÃacEXvÛ]£7íÜ"[’COÆ;s²k×Þó„—]»Œyx‡{t?h/ÝÑ·é–2 ïuï¬*£a— Åu×Óà~S—’h;EW^v5~s–K§­cÿýÒµëúöÌz‘1Yˆ‰u·4¶ë¤ÎíN²‰ƒÏÔ‰]äÜ’h:UÑŒ¢y»ï•%™¼Q– ¸ÁO9qSnôVÑ’º^;mõT U6š-WI" (ä…‰6 CCrèŽñ;m2 ™ÛSL¨yæ$òŠl8ÒítYã„'pwJ_("[[ ð§©[7½7u{KCåT¹Ú¬=K[Pl"×½.€ƒÛlN%Ë¾Þ Ï¥1ǘÄ6N9ð5ÞâA#¦í®ë¬)°¾3 `6Tã")Î%Ìá«"#¾Ù0…˯¡?X·˜LäTÅö°®STñ’>××8u}Má™ÙCŠ„ÿ|lÑù a;5lX¯Uƒ¬ÕpD¦úÛ»wüƒ-ÂcããÃlˆYm0å•‘Í£‡¿ç\ÀG:z éyi—Q×ü±«[R 6~¦ ÒÂ¶Í L^»- c£û/¦¡Ãêþù|BF‘9­îžaPU#H%kø÷õAy>N%WÍ}" uцoeô eA²t Ä‹Oú@Â(½\Ýßé~AS½þ¼‡üª^.Ú,ž½šCòT½Ý Æ©¨Ô †ÃêvзGÕ•^«}3¼\$^ïjλg#÷Ÿ„Wb¶–w7¡šÎõ‘"eY&|‹ƒ>Ï5’)‹ùQ“Ç#¦n¨«´=Ÿëm– î'±QKÉ'I8ÃÐ×7(²´y>ÓµoD—3t³"qp¿ïò—þçã.³‚ Ž×¿d÷,BXTäÁ½Ý¢8]„Mpyñ—»öÇæ%´Ÿ<ËAWÂÒÔÑñße!CÕìçl‹‚E2yÁò,}ʶÈ"fý±íqt¤LXÄ#èöAçÚ|ùÐðͤøîCçs‡^œtAé€9ÀxÒPøbñ„—iU!ý1^fpt…1yl„±qM]#,"+ºŠSÈ×¼åÇ„‰J á‹<×ý…áË™Ó.p\ß ï¥Ì¨«þU¯—ØzAA¤ TC#TF£^ßÖfÀçó ÉÒXÚ (¢ô±ŸIçŒÅÀ¹§Ÿh> endobj 140 0 obj << /Type /Annot /Border [0 0 0] /Rect [155.448 607.809 197.291 616.761] /Subtype /Link /A << /S /GoTo /D (label-module-ZConfig) >> >> endobj 46 0 obj << /D [142 0 R /XYZ 72 744.907 null] >> endobj 144 0 obj << /D [142 0 R /XYZ 72 111.464 null] >> endobj 141 0 obj << /Font << /F38 34 0 R /F32 31 0 R /F34 32 0 R /F37 33 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 147 0 obj << /Length 3334 /Filter /FlateDecode >> stream xÚ½ZK“㶾ϯÐQS5¢ | 9ÙŽ]Ù”“8뉱}àPЈYŠ”ùØÙɯO7 .´Ò:®œ‚x4ݾnð«Ç»/¾MøFF2ãÙæñ°Éù&gq'ùæq¿ùiûåý/ùâ[Áœ62Ê‹|Sƒ}9–ãëYÝïxoûû$ߪ{oŸëaì_©·7CÝyj»×u,[|²m÷ôoUT7KS:÷Ý=K·ï뽨]Õµ8Ë{¬VýPw-µåQÆÅeÃ0ɬ’ìbU3Ñb“˜Í‹ h¹`Q‘'®–±hÖðJ/UÙ4åS£èÍ*ˤxÝ¡ªÔy´½é1Ôí³íU‚DÅöy:©Ööh÷TèÕ8õíªëTË”ïïyº-›I=À;[T¸îYÖƒZ$Ö:•<Š3áëToȶ?M¶õŸV LE{žFª›§3_z¶ùNkEñ"£ÐÂÝ‘"‰D2kôG蛾™&Î#Oõ8‘H€-«ƒê{µ§ú!ŒåÁ·}=Àu(÷‹m¯4<àKº}ºçñ–Ö—Ì›«õÅEå .),#Y:B×h‚íC`ñœ¥çÙåå;MEÄø 9ñ›š=Íò¤]"0ì³±Tõ@ ÿgxήÓYDi&}¿uÌŒŽPrŒ¸°ÆK TPZàÅ!àR\à eéõ¡-áˆÓ`¦3óÊ0 A½êøóÃØ2Í?,6=‰8+H€Yo¡Ñó(ég­Ž¢-À¦Wåþ•Vè»45 Lßìðº5 òÁ‹Ŷ4졤W].Ù(ôf=„PHwß½dBzñ ŒL(¼°ƒFð¥n«fÚ[ŽcR'Å`xÍËQ÷-…0)“×U+ÒYðj›‰õ¹cцâ …2_E·À¡Cf†\žäÁCžj£òÐf”¤ldÿ7^’€û³Ô_½Áã$£MNrmZðzTÍYŠPž¡> O iÀš½¢äÇÔŒf¤U¤ƒušCãðÎpðÅBÌø,îû‘p ¢‰¿xºÄiØ{Üã×L xþrX”$ô÷¯0‡YȾøb‡Ã<ÏÅ æ0ÛèÕp&få%,Ñ禩&´c^J_µ~±íÜÀ°fˆ<÷¯PWcîæ•Ú‚Æ»~¤€Ìpp,ëÖ|H¶§n?5æ1(íû²òG/}Ó¦ºÇ1Š:ôN9Á'{Xëô‡éÛѳ¤Ç4PnÈ=ùêT&UÓ ³µ•°6£Ôfü}}@}£…WÀµ YYYÏ ót’Yì-[Œî‚ ÅÅ­fÿ«[PÝ©\Ú”OÝ4®dŽ+ã^FaS¯BiG>/ùX¶Ïê룪Þ]sµNÙø÷q5/çèÑ«ùKœêà` z-W+7V~fC“(æN:t'e0)ÊbX5¿š,ÊŠÂ÷«?[-r{Ù%¨B#è­Ç-¢b…eª)M–[º™c4H$?èY¶6ã]„‚T¤ýœm¼g€'x²™±ÏxFìÍø=ë~•mbg°,8¿ ;0¡G§Ž,VQÂRZ* ³ züóœ,br¦KúÔ7IÌ–d°Ú…J­]kNî¯n¤x´ ®ú¹6îÎ §€‰ÏÃ8¦çfµÀ]+E¼×É]¹JyÓ"æu™4ËÊ@spù$»%×ËVsš­’KžB˜‹9 u´:»râíÏ]Ýêä'¼j“'Y,ü«¨Ð›Ç[ž®Œ?ê$¢múôJö ˆ)ùy˜µx¹Éæã MùjäžÆˆÑJxk8P„Яêë'µ7”L²(ÏV¡FP^ÆÝÐî¢I‰…›_°©,*T»`S2b ÎV%žÚ¡X$Ê–V×¢d¾Äo–/Ú›3›³ œV,‡c*O~ã]ÙÔ”ý7΀v¸¡Ÿ>³2%Kbæ–3˹¾×H€éúá˜3–ì*C,R}4x–ðu(Á“r{ç % ó~-Å5P07ºX"A´Îk`2-¨)ª0üÒè,ä8 ¨Hå-ZീWs,£5ŠéËø@Üo¹ÿhI6¢Œ³(M/牭q ¡L`‰›Œ7•À²ErÝ‚K÷ø~Ë+ÀNqiìTγºLG _gZ#ŒwmVÔv‰[8ÏI °ï¡›zýFN‰f"Žð2Lç3’nØ”1hkpÔLüà}ŸàˆÅüò8]ÿL_‡N‡©Pšch·ÄŠðÁ\Ùň㓠PWŸÊÞÔ~ùýÓÃX]gNvhèö $„¥YzCÂ:¸›õ\VïJ}lp™bì;˜¥Xñɪr¼%+ _¾ú¨‹œùü®å W?Ÿ›ºÒä}0ÜQi®nC9¿‰eõ¥á÷›´£aOÖªM åg•ÒùïÔ^Sa}5þ»ý‘`¬ðæ?.*F1›ÑŸú&H¶‹([¨Ó­ÌýУ JþYÿ 0À9cqí¯†ëÑ>@EºÊQÿÝþ3”H‹Ô‰\xÍ`Þ[zvg$l x°Ñ Õ:cPZ*¡? àñÏ·ß™Á;z>™NO”ùűÆîYÉwªr0M0h0’æùöÍÂã±·#Mëf¿ÊqŽC¿Û±WÄX´j<@xý‚‚Civm×V.]hãµ+:Oú9€§.ó=ô¥o%ŽÍÂJc¾bYãØ!öjë€W‚ÏMÕHx,½ŸÀ0=“Ò¶ŸÐm›¡£j_õXSÿoÈr`n+Q5Ý >‘&V›0±¾ÅLD1ÿ¯"¤ùß +1›ºA,¢8^8ã-sÎð¾©ò~!3^ØaŒ´R7ɳ¡´tQ-ä…s$Í£xá°— °É<ã¦IÀ‘‚ð—Iy-’¡™s&o§ˆi—k)­â}~DœGœþù”4.æéûéz´¤é@‘âTþ–óã+ÀžïòãïŽÇ¼\sºûæñî×;*Þ0ü7aä›êt÷Ó/ñfÕ€JÀè‹Í‹nt‚ð÷¹Ùüp÷»¯ðW^àYÞ¯¼þè;z.ÎáЀà¬pHœ ˜DcWHœó)ð‰åÆó7Q8\ð°ôxW endstream endobj 146 0 obj << /Type /Page /Contents 147 0 R /Resources 145 0 R /MediaBox [0 0 612 792] /Parent 135 0 R >> endobj 47 0 obj << /D [146 0 R /XYZ 72 744.907 null] >> endobj 148 0 obj << /D [146 0 R /XYZ 72 182.117 null] >> endobj 145 0 obj << /Font << /F32 31 0 R /F41 56 0 R /F38 34 0 R /F34 32 0 R /F37 33 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 152 0 obj << /Length 2937 /Filter /FlateDecode >> stream xÚÅZK“Û6¾ûWèHmYX¼¾Å)§*©Ô:kÏîam85à E*$•ÙÙ_¿Ýh€ 43ª¤j6!ݯ¿nÌû›7ýAÉUÎr#Íêf¿Ê ˹]YÁWvu³[}IÞ}¹ÞÈÔ$Ûºè{lÚdßvÔW·Å®ì¨³½ýw¹z¶Þ(™'7÷•Üß·§zGã›v Î[?iÕôCÑ U1”;z³«:˜¦~\›4y‹]YRô4x¸/×ßn~©³™ÔЧ ®\qEúTöí©Û–_yÊáŸð_Í÷š&µÊá¾õ2Ný€¬6*LZµÚÁò4¥¡$9OÚµH“ßñ¿²ëªÝ®l¨ßë†;i]ƒö¸õ¿†–žažSO;çÉïUqöåñt[W[j÷ËÌïCÏö±‘Úm#,3R“Œd*lgƒ¥dZ˜°éïÛf_ÝýL&|ªW+˜U£†P•4´6 JYnmÓoQ÷塈L'sfe>›.f•à°m–*÷"n”V¤^llÛæ+çòîÔCÕ6Ô‰=u‰>˜æ<ùPlï_Lmgô0Ö8à팅Î"Øè·ÓöŠ€ÑàPÙEUÈsUŒCQØ L̹¥Ôsßv®)Ë(‰YíMþ׿ÈV³îgNÀF+Á¬Fº6ó“ГÏuåpê¼_ß<kï ©¾ê‡ª¹ó'bæ¾O´îF9˜ðs6»ÅäÛöplûjðßßÃû Ÿ¦ÉŸìúŸ¯v}ПÐLi#Ÿøþ—ˆOhf§ÝZÙ¤\kžÜ񼂯Œ`£Gk‹H¶˜òâ!6›Æ‘èÎÅ@-ô¨›2¸¶ë…'I¨¾Â€øTa9úÁˆ•k›2†"üˆ”FÛ¤hZÒ®t[R†?¨vU_YhĦ°·ÝGä‚S©XÊÅ2þE„¡¾QÓ=ûT^ÔH-ÚliÞóxèO;„ ²§$·Î$ËЭ•f"óü$gLÈ9\Ú,ùH\$²>w°¾ò$´xƒ?äÜfô¶ Ç]Ù”]QûA>>㋪Ên¿fÛ4­ŒqªÅq‹w4üá¾¢àn|ºu¨Uö籨:ØÊfpó¥ä6. µ?Ïi¨êjpšÆ½ÍýßC9ß!Lúw7£÷m]·k™‚%Ž/BÀBÉÉbƒï"ðË—(ÈñO?ÇN°L«W°ŽÑ…N]™a&¹„”àIéN~<"“SÖG'l¸°K]ô@IˆbRG,·ƺÒD8Ä'hŒ»]lDrXybEÓVäÅ­b˜ø5θ«29ž~Z8¢Î\Õµ\Ù2eå9Wv«´„¿öaíR‘GðG¤^Sl‡Sá<.ÿöŒ åðÎ÷qªDçô<;ÌU sO]|È"η$ é´÷ª:¦% H^Å|=•Œ„t!™˜Ü%–ÍpÜàÌæ“eé¼È²—¼Þd̤zkË,ÙwíZ_ølÝ‘ÀV Ì®×ÁåÛXÜ”pˆÍÝ̇ÍUƒy#wØÍ~WMvo‚8^Âxдµföº ³FÆÍ çæàã™Ó[ìÖ«†¼xêIYû³7]ðƒ~¡€¾ 3=Fd?6@íϲ£+ô› Ò/DÈöP ´š…`#Áô™ê+IˆÌô葜IÙm«@ÈTD0„¸¾"¬yZ™ ÿG<‡ý딩\_Kb|Ûª9ɼT¼Á°Ï‰’½’Ì ÿ‘:J¶L4pBll/ØX,BTèÁ¨¹<óžÏ3¶)U'ú©aR' k€Ó5(c&r@ª†yGî ?5¹ò Ž–ÑÞ· ¼¨~¤^â[ØOn¦ù.qxãß \ß´»jã]¢¦ÊóeÎR„ ëê›zæ›õ¼Zá0Vox5‘ÞÍ¥Â_ᇗ¯žwº4À×/Åpó HA¹ªl›…eöEö ž ¹t¯O³’ÑÐB 4ÛàdÂdçR,|‹©i+SyÒ¡+A_–Ë:’/>öCéñêˆÊ"\)0ý‘gYÝ”ŽH`ÀZ‹¥åâ®þ[Æ™ë=° Dbw›cܶZC& _2KÂ*7 —Ääž›àï}ÿ ÑlÍôyÙäEÙ ªÙÔÄË*à6ØzýÌ@Wù˜³tå¾ìz’Ü•gs"ªøÛwþj’ø†<‚’VÆ'c0nÛv(ǶÙáÀHš”)f'^s1ƒù&”ôúÌYPR,MÏ Aˆ…% »"ù8Öbžê)C-¯ÐÐ ³(>I©“í}¹ý3s¢–Ú_PH•ôES ïàGîY ¼Å >w­ 0Œîl ë¾ÀÕ ©§ ÕßFIwüÅËôÏ¢>•º®½H8OÏÅ*WRîÏnfÎêP¸%¤Û>i ØZJŽ=x„€\§¸£å>0Pßa™/¶#Þh§ú•¾|ûÒ΄àðžo —£­#èÜ„|drô.B:A=éL=ÿÏð¹”CèÅtURew¨šP6Í%„ƳLðá¾t»ã`iv…?Zà(jaÐÒ.o ž…î(( ÙÂ(cªú§r±?R]2€8‹±£ú®¦(­œ“«¦  õØ3Äž¾!¶/.–$dRÙ•…% ÏÍ|bר¼´¨[Ê•$8)±«.Uš$Xòuu+> endobj 149 0 obj << /Type /Annot /Border [0 0 0] /Rect [399.528 83.698 483.214 92.649] /Subtype /Link /A << /S /GoTo /D (label-module-ZConfig.loader) >> >> endobj 48 0 obj << /D [151 0 R /XYZ 72 744.907 null] >> endobj 153 0 obj << /D [151 0 R /XYZ 72 100.488 null] >> endobj 150 0 obj << /Font << /F32 31 0 R /F38 34 0 R /F34 32 0 R /F37 33 0 R /F41 56 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 157 0 obj << /Length 2737 /Filter /FlateDecode >> stream xÚ½YYܸ~÷¯èéÜŒxèržìÄ l°ð"žY ˆm ‰=-¬ZêÕ±ãAÿž*^"»93ë]#/-ФX¬ª¯Î~{óâÏßq¶)I™±ls³ßäl“Ó„$<ßÜ4›Ûù ¯v,M¶ÕéÔµu5·C¯'æA?OãpEÓí¯mcvN'Y·Ÿ’„Õú}8áG“~Ù£È+á—r|˜mg޵ë¡ÇCî–Ñ#Š3ÝP™É¶¯»¥‘äêóÍß᱓lv4'š›º«¦Él˽mŒA3Ø­v½û2˾‘Í_‡~ßÞý0TÍG…÷¥%ISj¿ú”¤‰Þ%¨·+%e^Ø=S 䱊œÇJ’³Â;ŽšM¾~v4e$Ï2PR¦©Þ 7æq©g£«Èé¢ T”öôk aÌq6-·ZpFA•%Ó4fnZN§aœÏT\ÇcÕ7»®íÊ4R´ÒG̤t¼ˆœ°<7Ê2LÝ 8ÍáÀ®.÷ %8¤[DEÕé÷£œC£Çí„ÏÌfó ¦¸ØÂmü=¹F†¾{Уû+ ¬=è3ä8€\M S«a©ŽëécµNØï@jCÝV³ù꾊ã$Ôߥ¤NÕXSrœ^GðzkÆÊÈ"zO9ÉËâ«@ öë0Aá"c ï9Énw}Œ#HJ4^F†)vKãnïç`ý󆂞q#ÌLï}Ó ’9_?&@ ú Î ÀÀ¨ê©—”š¸… ºvšõhØ›gÙFög'†wiN\/EÂ8¸–ÕI²õvQ’dN´ÕxE‹íÝr”½¹Œ‚«åè‚ çp5ÎÓ`§WÀÂê±½•F h™ÊÞ "¥"á¬xñîæÞ7ÙPpqŒÐ´ØdI šã›úø"!¥Ø$$jËÏnëÎîÝy›ßªø:ÍðDEù_Ú]POIP7K>]³ëºgg)º¤¨i£a<²"!yá‚Ã~ék45к(òílRn§akTGRø®V”7ÄÆîuê2+Gåm`pkvܵkp4ÇTS \)\mÅojçœÜ·¹ö÷ûÈq`àIÊž9-#%MížvÒx<#B!žÀ÷ | ‘¦ŸÛýƒy0ƈQÔaV˜;⊄\©ÑãÊ ¥íÜ(íœè8ƒ£~9ÞZ’æ[á¨[޽žTœùßNržá"ê%^b2 ÷‘H•³™ÌAÇ]-âÄÏ,ë糚¦Þ„Äߪ¹{þÂ&Pz7ŽC,»¡â$sݤ¹TŸ;ÒW"±~r×Þ«ä í&“l²Òj;Jͳt³ã%a¥ñ2”º‹{{9ÀÒåcÆHµ¦¹—5˜‡hžAçlôc9\,/¶×3(.«óh˜¸<)Œœ,A“·ÉrRò̦\ âLlC³¨  c/ù1«hW`1·ÕÔÖz& §f´¨ê¶kç³§=¶]5êzñ©ÒIõÁ°(2äÂ.š+¼sêÍx:È®ÓÃ5 ²~ùk΂§C ŒdÂm±—>ÚÖËíOîO‘SÈ¥¸ÓÙû«]ƶ߫߯(¡­î´«æñ–¢Tç‚ö1ië¶ösž #G ÕSƒ0÷¶Æsû`ÓtkbV•^©ô:"4ŠˆÔx ©î5ˆs‘Úe¿¸ÀË gÏÁB@ö(…°ûñs²i`®O8$÷jë·kN»Íõ‹˜è,‚@ÆIÙJ&¥ŒVV/©sˆÊ H÷[PçÝÊï‡YN—4)X7äsŠfΟ"ɰ4äË|šA±—0”¶à m4m$ûòåRæ±åÙWóYĤ}B¶¿“¦o¼d$)Îh¢ÙR/Û*‚·(I¡jAÏ:^>R´¸¡¯ŽOal þGqÂc)aI~a–ÓÒ™8…ŸÝ0üìÚË)ÂAY@fY>Í‚SÆgÁéÂguÁ¾Nÿ‰åŽzÃ+礀'ÇïŸÐdéÄÑo¢0H€YÊþÿ û<ä áóÓëxŸ ”š–iÜÀÎ2øtmèˆÏ “‘Á³i÷˜ ïå¨ÊO–C 3G½¨»s0°Ç* c¹…Åý¡­z:SAÐÀ×'9èÃåù}´ô/ 8_ÆJ2•e™ *]zw²—£jµà[¥¯þïXK$ äOôCþdäÓû"€£„pRb†ôe†¬˜—¥'U_šµ»ÃÔ~†¶1ƒÄŽeg9ÁAït˜¬lD¯z$÷àõ[±¦×C5V5ölÀ?k«Ný\}‰õJw äž ±ÃKãîáq$½ñï䮡_-*ðBÇ£l°#fóþµ»+qi¹RF#Ž%°u(îBU9'c™©s"ÛÃÖSíµMô®Vbf3R4]JF" ×Lí{U'‰­¬CÕÕ$_ÅâUJÀ'<ÏI–^}­Ž¬ô£ú<žT> ¯^Y oºPƒ½ÝéPAY'G•›'¶ë ȯõ ëÁÔr~E· /8¥d¦Ÿ«q6ŧÐKÿ*M{×"ÚEÎL3a³CÇGó‹¦RƒbH;˜’µÀD¥#Ò_S=5··…[ ôY0Qæ•W¾âÉÚPkùX¿4c„&Îù\{e—>ZYæCú‡U Å-J‹èÑa½bšé%·Ý7(sÀÉY+#¤3½@|jFÀöuêoº™øTp‡'q×ö“ì±0_[JeÕÿ°G¹ c‹‹+K δÃÍÎeRÛ ¥;ì—jËÛGµC‚z>,œM;•ÙN+”ò‚kŸäˆEк/ÒGÀWYº}¤JŠÕiÚzŽy’Œ…Ó—ú[ s9?œ kÊ¥­È';/£*–˜³ïäüDš3R¬ý*ó_—n%`r›¤g]Ä}Ðq.œäÔ,˜Ã<ÔC§:ÁiPe«u*ø±é–Ûz8µª• “Jå!›á$†‹a1/ûe´mMõ¡O£œ+×D϶Ð?N)%á?;¾7¾VyÌ£Ž˜ÃxÅV»w†?Ú\ÀŽU‡nÇöO\ÓqM "Ð*åâ9lgg؜ˎSl'±ÐÇ´Ó#µO_ç_b§ø÷˜Ca )( 1õAÎðG䮜(nÆEÆ-I$ÔSÇåÝ ðbùS…:h€uôñ”ÎÿÏèÊvËü>¥Â„IÝ7V-øûvruâc|3Œ»û|WuS”q( ‹"ÀAX.‚çN¤þùS…¢Èа—ž#¨d-Ô‰LÀÕávÜï<ŸÕÅë_8OvžýÔ‹Î~OãÙýaé% endstream endobj 156 0 obj << /Type /Page /Contents 157 0 R /Resources 155 0 R /MediaBox [0 0 612 792] /Parent 135 0 R /Annots [ 154 0 R ] >> endobj 154 0 obj << /Type /Annot /Border [0 0 0] /Rect [112.158 607.187 154.001 616.138] /Subtype /Link /A << /S /GoTo /D (label-module-ZConfig) >> >> endobj 49 0 obj << /D [156 0 R /XYZ 72 744.907 null] >> endobj 71 0 obj << /D [156 0 R /XYZ 72 520.681 null] >> endobj 155 0 obj << /Font << /F32 31 0 R /F34 32 0 R /F37 33 0 R /F38 34 0 R /F41 56 0 R /F29 29 0 R /F14 158 0 R >> /ProcSet [ /PDF /Text ] >> endobj 161 0 obj << /Length 1316 /Filter /FlateDecode >> stream xÚµWY“›F~ׯÀŽ«v•Z0 âr9v±²IÐiÖñ?°h´K¡²½•ä¿§‡iÐavcU%¼0G_Ÿ4—´÷tLIÓÇ0ˆD×’E$KSU·$º’>œÃ•Ö—5UUϽ/aºIXÙÿH|:ÖmÉVÓp.U’uCÑU[0½xñ¢/›À².òT¬Þólß(åöº¬âj[Åy&nât“•X··Œ+áb‹c8GbWâõƒxýy–…);{&vgŸÂdËÎ.º(ŠÒ—õšªÊ7-Ç“4^­’cû+$\ óu^Uyzö÷}€²cg¡ž=©í¸@áHëâFOÌ­ýW¹D|yZ }` °¡+¶¡ ZSbݲ4ìËIJÏGy´MYV‰íkD=¿Û0¼ *Éâ:ì"ˆä@þÅKqtSˆ§·œÍ€üÉ“$ïãüsœÝˆ£¸瑱y; Äbìhßnôó»}ý(ºhy„èªb˜:ª`Öv €oôACWÖ~xÖQ2˜Š~“ QL |þH–ë@,â@ƒ`Â}ÿ_=MEÇóÍ]ßÜbµñ܈êt¨·­ » “ƒñ £î¦MEѶpöi ¶fË"¼KÃlÛÈZ±2‚’ae{u× ¥ÆvíúM=›„ÙQ§FýÎWõgé^:´Ê² dìüu׳÷¾ ‡žã_©ûäGûFz,Û êþZïbÂgÞe^b’§¬ Wln“ª9bb|)Z¤c*6Œ‰æá0ǯu¢Ëkø â­ÈGmiïTH“uK1 çP‡VB£XÕ]Ó³ÿ ]óðE¿hgÍPS7¾]«f˜Šª÷ª=Pù;»;Ø§à›¸‹°¾ê 4,ýpqLFëÈ#Ϲ”þò0޵ ˆÃ c_*–­øÄ4à=uBg?yS¬«ïh(¯ø ̽êIl·P +NVžá=‘Æ‘P0jö½ŒèÌÆ=ÿç˪ëâäœÔu‰sbìù€©>üÞ¯âÿíqìrÕéžý5o2§ï:nŽ0î5„.€ë88 áÈ¥.¦À~Ð{íýÑÓ@*iüÿÔ(Ä´¤(í}ø¨J+80ãÚÒçš(•¦ ïDZö~î]â?întá4òà®4€ÎàÒŒøÄhÀüpÖ endstream endobj 160 0 obj << /Type /Page /Contents 161 0 R /Resources 159 0 R /MediaBox [0 0 612 792] /Parent 163 0 R >> endobj 50 0 obj << /D [160 0 R /XYZ 72 744.907 null] >> endobj 162 0 obj << /D [160 0 R /XYZ 319.828 533.207 null] >> endobj 159 0 obj << /Font << /F29 29 0 R /F38 34 0 R /F32 31 0 R >> /ProcSet [ /PDF /Text ] >> endobj 166 0 obj << /Length 757 /Filter /FlateDecode >> stream xÚÝW]o›0}ϯpÛ—0 Ï6ØØRµ©]™”­íúÁžÚ>PpVÔ@R Z+õÇÏ`’†…0h»MÚSbãë{ï¹çÛûÞàÝ'‹c6ðÆSb›#ˆ,x!¸ÎüàÖÿ. “Ø ¼¯_ÜcÃÄDvFG'‡#÷À¸ò>Le'¨ÐvY¦ÍÕª{Þ^e¿°x_š˜Ô‚ŽÀ$ \9ÜÝrÝ#÷Ø3L¦,B™i4Ë£i¢'.E;'å¦ê/.7ÂŒAÇ¡*HYÀ›ò …*Å•ÙÊ1vÄ”ÖCÞÝÚó¼ÃÑyƒçr«*ØU“ñ4ýÜ0-´œ¦Tycª±ÌýPŽýù$ÿó©Ö<Ë{?žMd•ÇëºmI8“Anþ0“O ¯Àþá­öMv«gP,­ìµí£þ¹•µq¬ÐŒš–ÔêeŠ„ H«CŠ…I•¦e ȸh%ÒjškDªIüXa`“u¹§ßFgÍM6Kå8ºo¥_“™ÊZcÞ³§C?÷µ!æ½ £‚]±LòL#ÒÝRÞç2 ³ÖP»w˜å©ä›÷TZÔZÓÚN›Ô¡gQyç¢vÏx©'%~Í¡%útöR Ú´S»Ý„LÙ¥½N‡æô–ݾ®¿JêbªÒºÅp5R‹å± 5¿A‡˜µ+@f’Ô°hë{?ÏÓèzžËŠú‰¿°…oü$œÈ´·f¤òn¥2¬uzÙc2- X¢³L·å¦ÆÝ—e¡ö¯É–¥ô÷9/(…è7lYÆú)#þ6ex/Êt/~íø®â/6éYÍlŒ ëÀ•íJd1‡e¯UC]¿¾×‹?ÜóÝë—¸çV±¶Ó\Êë îX#€C€zÛæ€ \\!ªiµ³:ø9øQ.ŠÍ¸ú€óÁé`¿x„zV<Õ¶ºÑc¢£ LÂ)C….n*çÁŒ}5ëðáÁ4˜×==ò ßP_8‹ò!’D‹¡þ"©¶ endstream endobj 165 0 obj << /Type /Page /Contents 166 0 R /Resources 164 0 R /MediaBox [0 0 612 792] /Parent 163 0 R >> endobj 167 0 obj << /D [165 0 R /XYZ 72 744.907 null] >> endobj 164 0 obj << /Font << /F38 34 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 168 0 obj << /S /GoTo /D (page002) >> endobj 170 0 obj (1 Introduction ) endobj 171 0 obj << /S /GoTo /D (page002) >> endobj 173 0 obj (2 Configuration Syntax ) endobj 174 0 obj << /S /GoTo /D (page003) >> endobj 176 0 obj (2.1 Extending the Configuration Schema) endobj 177 0 obj << /S /GoTo /D (page004) >> endobj 179 0 obj (2.2 Textual Substitution in Values) endobj 180 0 obj << /S /GoTo /D (page005) >> endobj 182 0 obj (3 Writing Configuration Schema ) endobj 183 0 obj << /S /GoTo /D (page005) >> endobj 185 0 obj (3.1 Schema Elements ) endobj 186 0 obj << /S /GoTo /D (page010) >> endobj 188 0 obj (3.2 Schema Components ) endobj 189 0 obj << /S /GoTo /D (page011) >> endobj 191 0 obj (3.3 Referring to Files in Packages) endobj 192 0 obj << /S /GoTo /D (page011) >> endobj 194 0 obj (4 Standard ZConfig Datatypes) endobj 195 0 obj << /S /GoTo /D (page013) >> endobj 197 0 obj (5 Standard ZConfig Schema Components ) endobj 198 0 obj << /S /GoTo /D (page013) >> endobj 200 0 obj (5.1 ZConfig.components.basic) endobj 201 0 obj << /S /GoTo /D (page013) >> endobj 203 0 obj (The Mapping Section Type ) endobj 204 0 obj << /S /GoTo /D (page015) >> endobj 206 0 obj (5.2 ZConfig.components.logger) endobj 207 0 obj << /S /GoTo /D (page017) >> endobj 209 0 obj (Configuring the email logger) endobj 210 0 obj << /S /GoTo /D (page017) >> endobj 212 0 obj (6 Using Components to Extend Schema) endobj 213 0 obj << /S /GoTo /D (page019) >> endobj 215 0 obj (7 ZConfig --- Basic configuration support) endobj 216 0 obj << /S /GoTo /D (page021) >> endobj 218 0 obj (7.1 Basic Usage) endobj 219 0 obj << /S /GoTo /D (page021) >> endobj 221 0 obj (8 ZConfig.datatypes --- Default data type registry) endobj 222 0 obj << /S /GoTo /D (page022) >> endobj 224 0 obj (9 ZConfig.loader --- Resource loading support) endobj 225 0 obj << /S /GoTo /D (page023) >> endobj 227 0 obj (9.1 Loader Objects) endobj 228 0 obj << /S /GoTo /D (page023) >> endobj 230 0 obj (10 ZConfig.cmdline --- Command-line override support) endobj 231 0 obj << /S /GoTo /D (page024) >> endobj 233 0 obj (11 ZConfig.substitution --- String substitution) endobj 234 0 obj << /S /GoTo /D (page025) >> endobj 236 0 obj (11.1 Examples) endobj 237 0 obj << /S /GoTo /D (page025) >> endobj 239 0 obj (A Schema Document Type Definition ) endobj 242 0 obj << /Length 205 /Filter /FlateDecode >> stream xÚ5OMKÃP¼¿_1ÔKsÈswó>/Šª¶*¾[íALD¡MiDDðÇ»±xÚÝafvfQÌùU“lÁ¡¼‚½³ì<"“¥&¢tØÌßž‡n×U-Žh¾^•»›v]Õ,z-W÷·Ëö²Ú–kjÕeŸOº±?~¾}§Ô¤Ô'òôÝü ݸªƒb³á0»˜´¦-æhX-Œ(Ð "^öf³%t «¿mrÂ×i’Î̓YLM$#k S/ÚJN,³œIüõ F\9ÿ endstream endobj 241 0 obj << /Type /Page /Contents 242 0 R /Resources 240 0 R /MediaBox [0 0 612 792] /Parent 163 0 R >> endobj 243 0 obj << /D [241 0 R /XYZ 72 744.907 null] >> endobj 240 0 obj << /Font << /F38 34 0 R /F29 29 0 R >> /ProcSet [ /PDF /Text ] >> endobj 244 0 obj [500] endobj 245 0 obj [666.7 666.7] endobj 246 0 obj [777.8 500 777.8] endobj 248 0 obj [500 500 167 333 556 278 333 333 0 333 675 0 556 389 333 278 0 0 0 0 0 0 0 0 0 0 0 0 333 214 250 333 420 500 500 833 778 333 333 333 500 675 250 333 250 278 500 500 500 500 500 500 500 500 500 500 333 333 675 675 675 500 920 611 611 667 722 611 611 722 722 333 444 667 556 833 667 722 611 722 611 500 556 722 611 833 611 556 556 389 278 389 422 500 333 500 500 444 500 444 278 500 500 278 278 444 278 722 500 500 500 500 389 389 278 500 444 667 444 444] endobj 249 0 obj [600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600] endobj 250 0 obj [600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600] endobj 251 0 obj [556 556 167 333 667 278 333 333 0 333 570 0 667 444 333 278 0 0 0 0 0 0 0 0 0 0 0 0 333 278 250 333 555 500 500 1000 833 333 333 333 500 570 250 333 250 278 500 500 500 500 500 500 500 500 500 500 333 333 570 570 570 500 930 722 667 722 722 667 611 778 778 389 500 778 667 944 722 778 611 778 722 556 667 722 722 1000 722 722 667 333 278 333 581 500 333 500 556 444 556 444 333 500 556 278 333 556 278 833 556 500 556 556 444 389 333 556 500 722 500 500 444 394 220 394 520 0 0 0 333 500 500 1000 500 500 333 1000 556 333 1000 0 0 0 0 0 0 500 500 350 500 1000] endobj 252 0 obj [556 556 167 333 611 278 333 333 0 333 564 0 611 444 333 278 0 0 0 0 0 0 0 0 0 0 0 0 333 180 250 333 408 500 500 833 778 333 333 333 500 564 250 333 250 278 500 500 500 500 500 500 500 500 500 500 278 278 564 564 564 444 921 722 667 667 722 611 556 722 722 333 389 722 611 889 722 722 556 722 667 556 611 722 722 944 722 722 611 333 278 333 469 500 333 444 500 444 500 444 333 500 500 278 278 500 278 778 500 500 500 500 333 389 278 500 500 722 500 500 444 480 200 480 541 0 0 0 333 500 444 1000 500 500 333 1000 556 333 889 0 0 0 0 0 0 444 444 350 500 1000 333 980 389 333 722 0 0 722 0 333 500 500 500 500 200 500 333 760 276 500 564 333 760 333 400 564 300 300 333 500 453 250 333 300 310 500 750 750 750 444] endobj 253 0 obj [278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 222 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500] endobj 254 0 obj [500 500 167 333 556 222 333 333 0 333 584 0 611 500 333 278 0 0 0 0 0 0 0 0 0 0 0 0 333 191 278 278 355 556 556 889 667 222 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 222 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500 278 556 500 722 500 500 500 334 260 334 584 0 0 0 222 556 333 1000 556 556 333 1000 667 333 1000 0 0 0 0 0 0 333 333 350 556 1000] endobj 255 0 obj << /Length1 771 /Length2 1151 /Length3 0 /Length 1689 /Filter /FlateDecode >> stream xÚ­RkTבª¡¬òRIÕzX%róÔ  ˆ±h €<$f&dJ2C‡ $ âƒJª²,b£Kž¢¢TXU꥖X…[ÀiáËëEªVEÀ×°®®KÿÞ5η÷>ß·gŸæ%gˆ l,ÁP‚Áar„ T&“rØ€<³Ù-‡•‚¡aJŽ@àVëµ€»°ùBÞ !O¡P,݈#©ø„Ò'E| ÒÁ8¢R¢@¦$4°Žì¡RjS!0ad‘V ÖOÞÈëá Ï„!&…â"À&8A)¬IORTþ;Ò§¿§2a<ƒ4|¦lÒiÂP­@°šÂZ‹‘Ó`ÒËÿÃÖôæ½V»V©›l?•Ôßx¥ÑÿT`ºt=ã@†A0ŽN—ÆÂïÌÉ`Ñ릳RB©ET"4U g%“½òŽdH E!„JÔJm<…Ã(4Ý ™ß”–<^¢ðýói§È(%‚ÑÆt°ÿROÕœ¿j2$1€6“ÍæBò{Jš6LŒª0AS—ç”8®4RÈ%"+0q‚B°ÀÒ1‹‰byÉä5†S&ßÕ X©“«F†Eï06`iጌ)àï¿‚LŒ\ÀàòÈQì•þ€ÏcçüP¥Çq%¦Ö‡ è}­FÈLaØ«(¶[˜*`ÛçûÏì¨ÉWuw¤g,´ÝÖ~aûiKs¸7ÀíÞøK4üÉ®Û]?GN¸”ŒD~ǂԼ>êQ¶Õ$gmïqµ:{Ð+Èg=R '&ϸRÖÖú‘îÐÎû¶Åƒé¾~õ~~/ó‚cÝš~%u¤pfÍí[¯Ÿæ¦mîè³9} y•P’ÊήÙ+þ}¯èÚÄÃp¬ ³U_™Ÿxà¹ûµõnwÚg‹À\&s¸i |5‡M7¨û;(œƒi‹mq¥‚Õráì…~7å`÷q —%.¨¹)Röûì.º`ÀËòa·4?§$"V3ºàx…›ªf60¼šj×·nhÑ¡— ë×廥Õwr‹9éûhëSÆ_ÓþX²k泤—/{_¼5S[©L^vž'gÄúú Îôñ¦·³©Ž×¿²zÅ+5ßÜ'DÁ½N«Ìmº÷g7É@œàfžÇ­c_yÛ¸”ÂqÝþòÈ6ø~ßdÝ›(6Ø â/£ŸöW>þÏåóAn²$4¡7å\ômß+µ «ÂÜk f&Y›$’ª%M³±žù^Aî 溈£f·íR·¡è¡æ|¶ob|Ñ ¡ß¡îWC¿”ÄXGŽú-ÊuÚº m7óaôr“ÿï"kSŒþµ§¬?Ï[¶ŠÎf¬ÛŸv¢&²f‡ÎøD0gßqAýcŽCyöùg¢¦ŸÔex¨ó‰Üü|9½¶ñjíÂ¥(êee >×ÒŠâèàÉqS&÷Ügy‰©oºƒŽ®?\mÚ³ùÑ“Û+íÜH:õ÷ ¨éâ/Krßní ×;¹Š›Úlèýã–»?÷!‚­ç»ê½ã¾[ÔB­»ø¯±Uë\Wu´‰­÷-´ïŸ AÍ•ºZä1x%D™êcó´ÊÜ”>óD­Íéæ‚väõ©ª+w°gÕi?/bÍX¥•/•žÑ.·›qwæØÓÏÁçø–C×#ø 廡à;QF+&ïuMèóܯŽvüvv…L-{› ß—2_e4tÎËjê*]àRneøl;û™ékî7gÌ[ìæák¥QÐöšö7ܯE(œuœ­±÷ű¸Ž|‡s WS©_ æ¥uc†u¥ƒ}Çv]Ίù¸ÀzÏGáY*.yc¯¨j·/K<º5ªëÍ{/U;ªÐ†â¥Ñùª¶;É[WËêNîqÚÉ9¬É.îš[Дz_Ü‘ý"ø§ ÃF—<=g),x9¨é=ë_^,¾Ú)ºÝº—VÛ¥mª.-Ø+›[ã¶ñƒp—#íW…×]÷´·ó¾h§.#^½I8½Üã{cJ'¬Ûü8¼eM„mÁœ×ö6ºµ3óòD¥!]7wh’:n”åý†ÆÈ¨mUæpbòXAm³ðŇY>¹ÿå)Õü±‹â–¤ë=_®ÛÅeKbÑ“ŠJbU7ožmÓ³¬¨#ùUÔÚþÿÍé'» endstream endobj 256 0 obj << /Type /FontDescriptor /FontName /SYFPBV+CMMI10 /Flags 4 /FontBBox [-32 -250 1048 750] /Ascent 694 /CapHeight 683 /Descent -194 /ItalicAngle -14 /StemV 72 /XHeight 431 /CharSet (/greater/less) /FontFile 255 0 R >> endobj 257 0 obj << /Length1 754 /Length2 601 /Length3 0 /Length 1119 /Filter /FlateDecode >> stream xÚSU ÖuLÉOJuËÏ+Ñ5Ô3´Rpö Ž44P0Ô3àRUu.JM,ÉÌÏsI,IµR0´´4Tp,MW04U00·22°25çRUpÎ/¨,ÊLÏ(QÐpÖ)2WpÌM-ÊLNÌSðM,ÉHÍš‘œ˜£œŸœ™ZR©§à˜“£ÒQ¬”ZœZT–š¢Çeh¨’™\¢”šž™Ç¥r‘g^Z¾‚9D8¥´&U–ZT t”‚Бš @'¦äçåT*¤¤¦qéûåíJº„ŽB7Ü­4'Ç/1d<8”0äs3s*¡*òs JKR‹|óSR‹òЕ†§B盚’Yš‹.ëY’˜“™ì˜—ž“ª kh¢g`l ‘È,vˬHM È,IÎPHKÌ)N‹§æ¥ ;|`‡è{8z…:ùjCã,˜™WRYª`€P æ"øÀP*ʬPˆ6Ð300*B+Í2×¼äü”̼t#S3…Ä¢¢ÄJ.` òLª 2óRR+R+€.Ö×ËË/jQM­BZ~(Z îÒOJLÎ.ÎI,ÎÉqazÆÉ)¿¢Z×ÈRA×Ò h¸¡¡™‚¹¹i-ŠÂäÒ¢¢Ô¼pz ŒŸ– ÆÔÔŠÔd®›×ò“­[²¦ok[YçºøÂ*V}Ο'Ö¾¼É~ bGÝìÌ”Ú`ÓyŠé%K^-|´µï°xöE ¯dë©ÂE[${ýzâ¾,^³µkÁ²ˆ ¦ûõn‹W6wMãý§­þÊópaØŒ9­¤­ö=žÕ©¬só‹rý žy)/œ¢ïû~ï·VÐ$-%)ó›+Øï2ЉWÞ}~YþÓŽúéÇm“ß½åã ^ìÖ¥~1ÍŽ;éKâ·'\±ie/j>s½×}´À\ð'³Ð.û\G‚qS_±ïæe/·›ñBuLE ŒjjXc÷þ½O74ÞèSÌS¡Þº•;ÄŸ2¾î=áô¸Küe¬ékÑo~†–sÚ`qšañ«¯];Dn?Ú>7jÞæG%*M¿Î9º7í? wRü\jÇìÝæÙ_9ljßä¼²^öºcº¼»¤Ðö <ÑyDz/(3¬‹Ò·”ÿmfóÂ{²{…é5›‹Ò›ù»’¦ï`+Ó<Ë×tÖ–#¾okú°•‡Ï³Pù¿Œîq«}7.NÕ™º†7u¶áÖ-Â2&+gÙWÆôn®[ß ¡2 endstream endobj 258 0 obj << /Type /FontDescriptor /FontName /HAJUBM+CMSY10 /Flags 4 /FontBBox [-29 -960 1116 775] /Ascent 750 /CapHeight 683 /Descent -194 /ItalicAngle -14 /StemV 85 /XHeight 431 /CharSet (/backslash) /FontFile 257 0 R >> endobj 259 0 obj << /Length1 775 /Length2 691 /Length3 0 /Length 1229 /Filter /FlateDecode >> stream xÚ­’kPW†«pQP{À€ %ÉBB–‘€à€Bi‚·H Ù“da³6Lª(JK¥Ž‹J¥P@PñR•‹x¼€”"•ÃÅ•:¥Tìê8Å¿Îþ9ç{ßóç¼ßº»ÆH}Ä(ÃI‚öA8H ’n„Ãc¹»‡RPNc$±NNÃ@€ˆDëUž0Ð DüYî ”Ô)L¥¦g¨—É$b ¤0…œQrZ 5L…RRAÚÈbÓ @¤’!Êa!@1 â¡ #X\P¡$p¦Œêµ¯¥dHé(àÉ@z% ÜP¨dq£Iæ.ȼ ¨ÙÍÃõ8-טڛBzK–k0ÜøŸÔhõ4¤@‰BŠ˜mÝgØ¢ Šé5³ÕZŽc 1¡Â!ðAøžŸ`FÀtᘢ1­P¥×Áé:$ÐÙ(LzÓ ÜˆMaQ1±Þ3SÖbäAǵðÞ˜§÷È›=“…Àv‡ÇC#ó½^}:ë®0BA¢¡¾ §(¹‘ÅcZù `0… 0—C4s0ɤ%I±LCEx<ÀU@ ÇLïUÒ&uZ‰W‰“$53GSùíG†„†]¾ðñùáó€P„¤üϧÐS$è鿈Iêõ^‰1éBh€ VG;©X“žwnÿÉ=aEÍåf\ˉ[•Cæõ[/ì9Š¡)RAÁ'®*ºøiaOÍ׎‰-K#kr¨³NÙÑ_íýÁ¡¢&ëDÉÖœTAç‘Ôј–õ­Í”÷ª§I›Ëèq ¬£zó¬ü°ctåÞ[Öè`ˆ¬¨vü×v«…«%‡V»9 ;ÊÌ»æ,q4v ´ýãâj™V°]òä§’ê¯Îâ#k/‚ÙûÙë¦VZ6ÝÄT›ïh°¥ÛfûôëžB·®¿©ŒþU±BŸ$6²¾ÛŠ›p.)–½ŒŽ(Ÿgð;|¯Óbð¶—ùu¢JÖ›°ëGEûÀ–•ä`×`d®ÝÔåqÏDC í#î\¨¾õÙµš^Ô:ù<÷|Ãèû—† Æ~n驸¶¿ßR”¼°îå‚Fö‹ÇIöãc‹ü¿9û^ïNQfÈFo›,ZwC¸¼“2²cN–wÜÉârÇòK¹Í[sî¿–Õ/Ê}”/m½]);¶²ÍÌVZiÛÙDßoíñÒZ\¬rZŸ¶4àÇóu^ORŒE/øeÎMC}ì³_äeÚç ‹TØz鸦ÚL§2/ÛpzíÆ S+¬ EwìçßÜ)pxxÙ.–×ç±g‡æ·¸‡<-ÇmKXœà/Ûbjì"Õ7/ÿ•¬ÿ»Ò{õûhI©¬¢dqÛ’Ýñè&÷’º?×* [æ­l¼çù³¹^Ã} U4ŒÇM•>ëIÙ$[\ðñç#»¥+&%Éʼn†ôyÂm껇µªØÇjË.nsÙÒâ±ßÙúðËÝæÍv4üî”c»_vÉ¿š*SQ§NW4¸LÀëaâîÍnµ2„³1§ûSmñ5ôdp¾ûÄÞò¹'³ÎxÞù}iSß^Áؾ<ñÝ+6ûx׎?˜{Ziû]æÍgæçImîý tÛMŒ endstream endobj 260 0 obj << /Type /FontDescriptor /FontName /IUEMPT+CMSY5 /Flags 4 /FontBBox [21 -944 1448 791] /Ascent 750 /CapHeight 683 /Descent -194 /ItalicAngle -14 /StemV 101 /XHeight 431 /CharSet (/ceilingleft/floorright) /FontFile 259 0 R >> endobj 261 0 obj << /Length1 1606 /Length2 11768 /Length3 0 /Length 12590 /Filter /FlateDecode >> stream xÚ­tct¥Ý–u*FÅVåÄVÅvŶybÕɉm§bTŶ+¶mÛ6*©Š¾¼ïíîÛã~ý«ûþxÆxö\s­¹ö¦$URe1s0~q°3²2±ð¬ìLœäìåElÍïNDJJ1Ðlå`/n ò4fq )àóg+"%@ÌÁÑdea Ш«hÒÒÓ3üÓóW ÀÄý?#ï'¬,ìTï?.@[G; =øâ}P€-s+[ @LQI[ZA@#© ÚAƶ%g[+S€œ•)ÐÞ H 0wlÿaLìͬþj͉éKÄ ` pršZ½º™ÿ 1 ;+'§÷€•Àdl~ŸØ`eojëlöw¿¹Ãß„Aïvï±w0%'°“)ÈÊ x¯ª$þå<Á–Æà¿j;Y½‡æï™f¦Îµôwìæ= 6¶²w€nà¿j™fVN޶ÆîïµßÁAVÓpv²²·ø'ha 2³:9½Ã¼cÿ5ö øoÝ;:Úºÿ}Úáï¬ÿâ`vÚš3!²~~¯i ~¯maeÈü×¢HÛ›;XYþá7svüϘ ô÷€hþÚÚwÆfö¶î3 9"³‚ø½$€æ§2Ó¿OäƒÄÿÿ-òþßÄýWþÛ%þ¿Þç…þâlk«`l÷¾ÿx`ï/Œ@ð×óÿåÛYÙºÿÙÿš¨ üÃÿ Dlü>{‹w)X˜Xþá´rúbå4S²›ZÌmßgô·_ÝÞ ²µ²¾kù÷Œ¬,,ÿS³´2µ±ÿkèÿíÍþ•ù»<óf–R‘ÒÑ ÿ××ôï,¥wÕÁjîŽïÄþ£y³ÿ2þÂupx2²³?sq8¹YÜ\¬ÞÿCµ¿aXÿi˃AVnÝ÷–YXÿnü?¾Zúÿ#aoê`ö×–¨‚íÍÞë¿…MA w=ÿ¾ëï ÿ§ý÷Šn@SÄåS¾ ëÔiàjܬ1qÝž.V¨`Ç¢:µü\¿J‡NßÔ°-ž2£çª`¦ú Þ×f÷ùÇ—=ºý¡.[êÎdàE‘79mw.ú:U+ý~³ArÚ©f”çåœÜ&´'‹Æþö˜²ŠAá3,ñD+þòÖÜ%׋â—#ŠiJí7ì6´zŒê¼“Sª„£‡_Ô}Ãý×0Ý{„ô™ß(ù\ ‘©"ý¾f 騗y†ÓÍ|³Å>e±èæ¾Ãá\ c ã[/põ¾À½9R$VEtP u>³yt'LwùôÅ·îø×›Sµ“ÞT´¾`I&\:Ëœ­°¡ ¸e§â|4_¨ˆ–v ÎèÎŽþFì=üÍ¿U´€&*5ÂÑp®ð’ž¨qì¨Nc‹%/øBàäL72.±5÷ǵË_œ%Ȳ9¸þ2W²æv‡ÿ§qø`P^ø˜NàÖ‘ks_Dùª©O2<ÿ åHw NrhH98†\PUË—­“Š…ëW/ÌŽ¬Ö<ËõJèœ8ÞÝqc>—7—Y¼™án<Ï‘™sjMÒÞ&È"U ŒÚÎ-„ÁHÑE§g³4#àtbà]}QÒABxK2L÷&¡ù Pñ¤¦r.Ú‘EÔÿV«FÎöìËßÞ¬Œûs GôáÖèGVA GËJî+ÓVï'  ‹r‘Åo¿Iq[)C¸<\ŸÇTnìÔ'±=z¯^ÒµŽˆ‘Ã=T ñ H’Þøý¨vˆô1¯ò âÍy \ÙDUlh)ò×óWˆä²¤¡—‹¡Øs{$‰¦º X˜Ú^‘J~“!mÖO;BÂd{Äò×* ªW)·ÃCðìP&²véI]/8“í8Õ‹U¦*~²ðO-ñ¼MûÔ<[ib×È2 å“åû Ý’WEhhîÃ0eA¬’§Xyð«¿¥8/V›ÛA[9ïÚU”µxÎÅžp™=>¨CxšoÇøµ³Ü6SÀ*§v¤êæâR*EÅ”zRmŠ+߯kÄãÓBצÛs¶…-“!8û@±Æ*#¯2ŽŒÔƒ±'‚â(Š"êg޼ä¥mJýy̾¯¢flÇ=‘[Ü£û(îl‰†0Ôšs~ýÑòÞ}eñŠ©0ŠóªÓ]\e¹?°ŽÈóáÏ謦{OQ„J8WÉ´¢˜‹¨ãŒM¡´ÆÂë&ÒÖÏ ñQS¶x~—)ÈÇUJ„ÆmNZJ‚Æh½’n'Žxmj_y¶¾Gܨðbn Åþ6ïfßÍPÙF2ÝE¬m-×6ÙÛ}Àäjcžƒuï¨W@ÇPâ`†…†ÆŽ¬È‹Ø&BðM3„-ë•ó þtv¬—áÿœhW¾Ês_Ÿ«,´™iS{°C¤l«â5†R•4äýœi-ìgÊ"΄—ô“×8lß Xo¾à ~þÌôbvsðË!ÍæaJ·ÝEª8Ø—íË÷ ´u²Åà ê+b®F_œ#t¶õ3Ä6ÔÜ7À‰øCb÷æB œZ&…‡^Ýï^Géãþ+¬Gt¸ ¬åÑÒ͘߼ \I]£O è5#]Y~õÑäþÅøõ—¸ùæKƒ0-W•À‰ü™é¼óF­ÃEtJuYœõ[Óg6Ó)3O½ˆÄFf;IžØ,»Í¿Ðä 0Üé\ ™íE—¾ ª°R¾]HÒ¸¹˜ÖÛ¥åðM³8â]„)mX1¢f¢,ûƒ‡ (óWkl»p~-g_ö<#½|n a‰>à}ƒ¿JSî•„\‘ˆºN%`9мM‹JËç…ÆÚE~KLŒ¶¢ýœWqT^Zl£E¯°Ü;R ?=˜dŠÔ )§ŒÄd2«½ ÙM"õá[*qCÐÈ´ÉqvåÁu,-Å\Fæ!ì˜Ú8LolÙ­g:œ°ÑféF®VÁ¨2õ¡²—ø³¢FU¬iS§4¡Œáyˆr-‚nºŸÿ Î§Ó$<ˆ´ïc’vMßêÑ­ßÉp'¯ûvÕU<LöåÈÛ°,.™»Ž#èkc´Ùv~í#sM„Óç&3ªÊÆ *¸'bûøÍƒÈ_É©¿¥.}u*9!—µ´ÉQ ½£ÂGÿDT‡ãÈX †‚£RË6º%0`Aä‘É$ž!} -+Â9ö?÷ÇÞ†\@fѶ´uÄ·¡—êÉÍÊsÖ›óÀöø\û™tñÇÆV‡NPë8Íw‹_¹¿$Ì‘T}s ; ’Ün0ñ;Ǫ äƒ}­6sG'Ú¸3Å;0¸§ ã4“…Œ¹…ăyV–Œ4+z¤–©‘ágÿè‹ki4›bÝeƯ†ð±¤¶oŸÎÁm™Qˆ¿uY])u–»§uÈPÐgÆ o©ù æÝÏðÅ µ÷$qïPŠ ûŸ¤>Ìke$!ÐH¯"¦s9l' ÝqbN¡O8™Ò@TîÚßyÜ%Vî’ Îg“¼Á0B;'oWú”Á-ë¿Æ¬†š„¢â Š #ßÕW¶¿¨Íz<¿‰¾¡‰:Gnˆ}ÕN¾=ÀáÐ<•½i}PÒÎå­œ›±‘\G×aj»(hXù½jåÜß…O_ñá&f»(=/rc´°Œƒe|žà™æq#ž«yý™¢•¬mK»Ó¦&ÚþÅà,½« ¹M¬ d!…;ó6q±_d+8ƒõGÝ®‚Öレß§Ê¿ŠƒJ[ˆF–5ËâèíÒIqq':Rö¸4öI¯n#²c<÷5Pý´q6ƒul.‡Ië†ÄÁw*-^‘ôQYžWÂ Õ ª 1ÓcÚP›{eŒ›Œ­.œ ¢…ÛœwRRÑ%Ýøï´Hp ðù®õÜ’äBƒÀĹúÝC sèÛç7t õíp• ¶9ï‹¥Õ qòëǼ23µÌG'Õ{ä[P¹œ ×ë ¯äÏ|¨P•Crš[UZm.a—È &ûy³Ýu„¸ÑïÍùp!D_W¿nøÍ¨h¼4Š”Ym¨|ø•Z`7h›–•œdYÏéTŒüQkÊ}" õùª(ñÿA7w$Káû¼•;Š£D3 •/ÊÉ#Àöç8LºÃzUr9êá¢iµ8ÇüÿØÅj¾£y ðk!oIš€/*÷#ôÏ;H:"SXìMq['&|,êgÓ¢BçJrøÏè%Ôˆ}…ã¬DjbO€IšHü€Ì=¥PN _ƒ²kŒÏÆ‚ …X{l˜#¡êhÈ¢M¡X¼vŸµ—ëÓS<^˜»7ÿìôŽ @BÚ[8Ó›¦=ý|Ëj‡.-¦šMœ˜þ¥D]dŠû¡X9ÂÇ«¨äÑØ»n¶²|ÍÕÊaW$äŠ+Òg#ž%Ó5ÉõÛ® ºè¬ ãÊÀ`­.ª<Ú<ɰ¡dî†`ÑÙîoþ[L«0,r@¹aÐ+pÃÕ Š®…îØíûiHçéú,²¾Êhèçê*ÑËñf·õÎÜ´†¯›ÕØQFÝR߇T°öß¾¢uÏo‰|_gÖëîú¾S÷;ËMR½òt}ùÒéR$$o¯ñ5Å¿ܳn`Gt¯ÿ¨nËNfùM§*î¥éæ5ï}lÀ¤LâÁüºú¸—’ ýUbf“ª'GìKr¾øä4™‘@Ôín~Õ*bdW²È¤ŸÈ–ò*Ù\H8œ‡òºÝü£CM¼ÇåÞJ‚‡°;¢wµ©ÒÎ6Äãû±¬›’-å%(e¶5& Zþq^Bm ,!u*ÙØiž”÷ˆ\€w;lgª+ÙešäÕrWBò iši˜ä‡-+@k㸵­¸¶ïÓŒ+å±ë¸9'h<)H`ãPŒ>ÅpèÁ§éì éiv8Âð5ôRêû"Tߤ‰…ÏPÿ¹Á#†ežPžÄ|Æ ·\dÈ™àrg¸,,ÁÌÁ/–ò#Ê4ÓMl²Eßüì l}V¯ÓaªƒÒ%2 6?H¯Î¶=]­ ù É^oÂÍ`æó” æ“¾üÝ©C}ómÑY-ØEro—àò>SgðJŽNÓ˜]/EÒ<…w¨3¼uÿǵßIL h‰9 ªÂ9å O˜ÃÄDNº}-óül°ÊR»ÌEròi}™ö{5EðÅ^ÕvØW‹éâÏÇøÙA¿l¡Ý“pÖ®U¬ÂO!–ú,uJ‘0}5ÎGæ_ÍÛ?MESEà)êNdSÖžÞ”*tt6Hˆ(Ñ¡°Å¢1 -ü°× È•gAiAKô&pò#|"世få ô"'¹‰X{\ôý½CÐ ueeÍèxæå&¯owÎr¢GA%×?AX&‹Ó¢U¯N#m%zy(~MÉÝ6ÃŽ0ËìH¤LX›B¾Æ‡8i™°²õ¯Ë)uÝl<,uãóü 8ÛÙŸvŽ·Þnq~£dþèwÚ…Æ GÇ£'NñÀ{m«§ 4…'Šû½ÂO*Ößg&tÐi9ÉÍü̉·èñúLã!P:D©9;UŽo뫾%ôaƒÐyçövLéË2F!þî91Hó®_7O¿Óêùä')MïàÎ@””*tC/øôÔÛ*àxݳûøšg©ÒýôKÛtvrÛS’°8´Zq«™«þH2‹´Ï¢ÌðI€ ÓƒŸ¾© S¼rw$|ïØ“Ø<¼Ôìǰ+Ý"òBÿØ8n£”‚¸! –’Á½qógÃÿSekŽë € ïÊ‘.ÎØqâKÕP:—yhQß§Šöî‡uî=áÕ9 ú.‡¸jæ4•ï—ýQùüÞ@žð¾éé™$< W4µrbó#ÁÌW™äù}–¦ ÏÌrˆ¬ö7³›„rÐ4ˆÕZÇÜÉr³ŽWf]k BýCCÆ:×·â«ÀÕ²œºŠ…µ’œ¥®éŽŒ„à€37CdX‹“’ž»~—)"=ašå‰­îpàt‘¯ÇpöÒo†ÎɦÅ*ТáÊÔ’¸ D#ÃÖ£Ñ/½sNf›Ã†: ÆOØÓcàEа/•ZÀ59Ìd˜pŒÆs­€Œº«§Ë45PÎIô¢TkèeP;ríÂÌge¡»|ætãÙBÚÚ €))ñ@kA/ÐèðuøÐÄ8ˆ Q‰˜)§-ÜæQ×~@©û5Œ¦ýºëÃRF%Æ=0%FûäVZI[#Ìz‚d®DÅÒ©ICs÷”†>]w,Ùºíq5P¿7Ÿ4õ6ëU´v›ß!¹Aimq$ὪD^¥ž®/‹M^½éŸW>2ÀʉòA@ñB)Àø®²ýW›F*Ûd…ûö{ó*BÙhR4¼ÊΨOdôù!!*ñ¶pÐÂ9G4]õµjÌ{ =ƒƒîi³7¢Öîê›>Ø<õµÔÚ€DQš˜oµ¦†H¥JÉËÉ©jß/I²]M»t¸n›Ü¥üUŽ;tâÇi§JÈõ¿ÐİÚÍi¨®‡ 2ÅÈÁÓ¨†AÂ#·ÀˆÜÈÕ׸¤&0ýb±ž o÷ÆhÔѰõNltOèÚäØéc{VgÚ58H»ç ¦ïÕž§61«jÚÂÚG½·DKáä{î–¨ºÕ«¹òIÛrÚŒv‰¿ò„ÆL°¬>Z©NGL÷h²íÕ=Ú!ÊÍ#à“$ê!›?¦*†íe˜‰Y¢-×,Båú@Ï÷‚_¸`Ô¦ðæîK8{9. ?Pž/d¡£Öÿ䢠^ˆs6}óêQ…›“õ±¡fäSÂ{êzóÏ”éåµ²­s¦uY‚Çèc õ7o}T䨓æ5Léó<†˜k4¶”o–|‡š±>\î4©‚½ …0K…®wu-¼*’ ¤­vµ-h¬” NËz?Éìâ—³`±©Ô”3g‹#K9÷}¦– HA·“©: ;(sú†ZX!PÔbU4ëÊÙóè¼ð5n¡ ½:OjdS©¬ßÇ)‚ê4£g?<œŒ¾J³‘ šZ¾•ýÈiªò¨rþÁôlºòçê­àö ª§¹æÄoÍm’Ö©i@bÿÌ{@U­Õ¼ŒCc/!Êîí«À.HŸ£Ñ±á‹u/uñ™•cîÂ莢KÊ´=sÓþpiöñ$áÌqµØEzv¤‰,&/R:¹oŠ”KkB`|˜¢†S}0¸ë*’ÿ9cúÆ<ØôG›Ã‰z¸ÀµßÁ™ õµ›?#»íi0Äh[’J5ïl¦(ªüàê°™Læ“*K`äÞæ~ z»žž“£Ú‰`vl8_F™ÿE®ÓøNKó? 39ÛkÒÈÆ< ÎÐìºz¶ê äT a<×ø_ñ~6‹C“ûîü?ýdhàþ¨Z>Ut[Îu¤Žb¬ßtâ‹ÌÂx`ÍÔ¯ÏgwOäâ rZGõ4¥D\ÝEZý,ñÊÆõgRþÖ3¡Œÿ%<c¢´p6#g#ËŠI”’޳Ê-$ëG3RØKlœ€kƒˆïET:ÆÒØ=N©þŒ­XÇ3r<+d –TP¾­òÔ‰‡Ñ ùEPkúø æ9x›°à}uu‹¸sÅ‘ÿÛž¼ˆ€Ñtn"v`0]¶ŒJåÇdž\T)^çܧâ'BÜíØòD8е$A¼£!ŽõÜŠ0¹Ee5˜ïäêh}ÉZ?áÜI0TBÄ|îBqôpá¼mBØ][ÑNÇ,‘ QŒÖM0/Öœ‡Â‘;d'ûz–Ô’†X¡áM>ΦÝsW”©GÕMÎæÔxêÞ¢n)ÀSEA¶`÷ÁfF Ù»œ‚HëîG㶇 5ÖjÖØkžmˆµÙ»8°‘XЫŃv½1hpï@)è5U Âßî‹ã¢ÌUt—Êùftëøé?ê»Â…_͈÷×Û2^j“QˆnžfHµ<"®7a`öX x¦¾çð;PÚÇ’â‡Ük§PV »™â4†¾žgQ´ ûá|í¤€ü|liÉöµœìH_ϱ¦ˆ{SœŸ ÿÒ›Ô. é‘#_¨€¾{ÉB¼˜>¥ÆÖÌm¤‚¤ŠÆX¨dáþñQ|Üô‹ëÙoXr†Vd_ÁÁÈl"Ôâí±BÖµÓÞõÀ¹¸h‡[…x›–unï„ËØCV¤¨FgºG«CdgÝÈaìkXÅupNSáâ&)H 3£,âgkZü`94*,§j+9` ×èb³ w㙉7Ô]ª(P b7D^[„3îðÿÜ»E”ç®Å<]Cfa[Ju4DwY–—”W¥^Ó–ú•{OJ¡Ú`ë?`, <¾®phùà‰d‰~b¥5–U ·áì†kŠ› úýµOI2b˜È}Û¶¼l+2…“ÐD1³ÛxþH$wå#+ÈU÷BÏ °Á,½$ü>Q0ßÅna®ÅCá–Ç„>1ròä-gLÝ—k_.ù ú5оžÔÒ×J;y¢*†¿DõíGéÇR«D)z º«5Cœ!e”¨ûý?õV\{ öݱÄ1=’ŽÄµ›Þ´/1Kh½lšò»9.¯é«ˆ´»^Ábð'ËXÚηFäÉ9½Ó±ö4ÔeÌKHšpǨ˜áw* Öî‘wÆ}RýRsf 3õËrF(Ÿ±bÛï‡Q4Ù+–íi-ÓáCSm'í±æ—ÇR­!”B¤åe8Àò/ã.Ôóþ75Y õPDóíØ¿ŠÀ…+YñóØ=oN}¥¢?d˜ã‹û˜Þe²!MeN^õ½¾•Ó'±Ké¤Í…¤šýp¾ØgT8ž± åY2§ü[ä6®WÕ˜|—ù¬nãsïÄv7£ZËR`âÌîåýÓÅxÕ³ ?ïzP;yÆŸ­¨NU#Æ`ØíDª$9Ÿ›P q>šŒÝDî¥Ä!˜|ŠŽH"´ìh·wÈ8+¸†þ)fp!…[Û]§1`pA%dÝw3OmšËt†÷¦]BͳúW›¯g$—WEöA¨våKÎ’œUž „PC¿ïž¦WrðİŸˆïgNö«]ünªÌëg¹ƒÊ•üè•ã}%‚ËwÑ[£ðV1-çèÝ¡­÷t^!óOÊ\¥G÷Y]¿tïÖÛ¯0ÿßìkd<ïŠñð0 ²q„NÍÀdz¶T¼ &YRDS’ îbSÙ!½`Í/ŽŸ±uïc‚Ø q£¬‘Hê 62ã%yɯ¥Aȹú%¥-·Ñ„ˆíÞLÚa^Ù| eêÄwËÄl‘ÁW]$Iß ¼>Ž-k…€±Lw¬püe-²]ù á”°åwíÇ‹$?ÜOVj†AQú›¸îœ ØŽè]y¼òÖÍšGë³´n¬>1LáÂT °[|ŸÕ:ÁºŠ-¤évgÝ) ·œH`:êÙð5Û:} x[¦û`â1Ìá9fÑ®œ­/jzë«ËH —bÿl^gœÍÃÈK.¦r}ŽàšRqs>\ß“»¨¸©—Éã0akZñ&ƒåSO'á¼çê^¬µÃŸ¼ ”Dua'.Ù“xkhùq]å:ŽfŸæ°k§>ó}œ@±RàÒÕ÷±qñÝ@%ž=qZLÑ™f?@JK4_YФ㤬ìºÁXoŠâú<˜¾ jWll*±ò,–x㧦¬· {ä¥VWýüËŸ‹ ·F…{šJší*ÕÚLºµÇ™è3Pb¿õêàüõb'p´Â~±C0îɽ†„Ò Q>G«"Á××…çõ¡›Dµ–;}£÷<9À¹þj¾¤ø ’Í®›§¬Sóª1z}딇û"ý¦ü{§Òeð¥ï×HvîàŽ“å‘”åiÛƒ\¤Åä]!õ±}y²YQúÖœôä§).¸ÂaÒ{y{×y9+‹Õ3üÃ*)”öéýeX%¸,;%’Ap½ÉU~YŸyÀÓÔ—&lŠnŽGÓÏq~¨ž±ô¢U”õYóa狌‹©Àým1”%K¯}å̇‚ÎînÝTÓn}ßp‡EE Í^(l¶i(8«·«CüÖÐꑌöã÷qÇ^i,ôÂ)žšÈËÙŠr&Ñ5*ÜN›sv\D8i¶`OÉ}i[e(åæ[Êb£ªkûdXzášì‹04‚Ãbx¥ÑY( ཈'›ª^ã,!óݱ{Éר ¶€¬¯ñÚKwÈûJ y4ëx Ÿ “Á¸0FÎ\Q›G^½ß„Cu¼ns2¶ôêÝbY–€£mwûžõ÷·¥ó£]gú5 BvX–t•Iàð 9öMþdrZ 3Uxe}iƒÂô„(Žr¸G¸ÉÖ3–ï&€ãûšÈ7¸gøL´~XZPl{mŽ«ÌºMÖL‡ÉL¦„ç¢ÄùíéÏœ2Y>of¢EŠOå9©Y‚¿‚èw’oïå¾ñelÃP¸¾õ½ÔKeå“MXÕaë03Ñ¢´öÖéÇïýAJZ8Ä—˜YQ8ùù@ ¯b)¡f¤ÛMœb…E #hJË×x¤è©Á¤ýÀá9›Q)KóÚt_0cv]üEŸÎEÏDŠïìfmê”8ÿÇ {}ŒGµ©0ò"|ß\)ñ1RjolÀ¤u\q/üvÝ7K2F‡H”ÁM˜/ Ì¿¯Ë >0³ÈÞsb†„æ(Þ“¡ýiÔßfØ¡°òî»è–Íyæ 3öåòìS '¾Ù\Pý,iQ #«ŠžåZHh€çAËÈÞÀ™ˆ‰fmE Su Äþ¥3XôÆÜTjϨJ…^Š4Õ€™›ÆX†K¢©£cQ¼ˆ ¤РlÎ,ЩýÙ'ÁèÞŽjbÛb‰ZAeQ~[¶Ý×M/xr$Š6<œÎY$i±ˆÜj8™ñÛÍ;§)œˆªI jg>Mýfƒ‹[ø[ÚX»øçj쪛WÓh—¶¸Ò®½éÚ‹§46xyuô!Rý˜ßäIýövAã§§úxIœ(ã’Š‰×…Sä¤”iª…PÍÜû±™Sðɺ:î"gœ‰ÕuÑæ.ài'…¯g—ëM£L-Õ’FÙr7…ü[oŸñWÛŸwjOšÕø2'‹ 6i. _]·Æ©à ¥œs²¾*-8Ý~!Þ'j‰u¦õ ¥ß”& ­Õìq—´¨U;Cogò MfËæÉîff€ò¹g:´¾jÈn©Ìîê•Ý>£C¢v°N ‘OžŸ,ÄâñrÊçf^Ê8’J½‹÷m„’½%ï‘ûš £X3þÔkwðÀáÎâ!×F,S¯Õ±‰XÀ+|/¤¸I>ÂóãŽHnáÇSÐ&¤­~Ðwö"{AA˜¢K;ž×nd|ÙdŒ¾î3µ߈L`Öׯ[ï”hßàá^ ÙxÖ5bœé¶PÌpÞ‡¨ƒ0jò­lûv„dTËGq5–Gaú Ž®T@ö˜¯È[6'‚OmÜIŠxüÞ¼n“)AýœwÙìR™ŠÄöŸ^R3è!ï(—ŒG+ªÖŒ¾C=X¿ÎÉx8ÃìUê Ò”×ГЈ±e;ÇgùÏžPèi%A½È^A-'ÿDÔ”!gÊl8P€­‘É÷BG¦‰݃¡´ùã÷'_sÙîÄ]T†ÈLJí?\’"ÚW®ò´Ÿäk<™:Ä»j^Ó!Rs‚\¹>Cbº¢…jv)â¼RqÆ@ ÷$ÝIÛüð—È&Åk÷i# ÐÅSÙ©µ&fÆ“0‡‹ÖiÔÊè;¤ûO>¡¨&U\èx¬:[½F´íc ∯~9´=ªßáK{¦tcb*ˆVÃËû£ç$íá:ŽpRn¦Ö7u^kÒÓ çŸ4¬KÃè{öÌÃÿè /æ@#IoC.ž¹î>ùyIç\ (áSp“ñâÓVÆþ)_âîêó:Glqz»õ†ÃñvݯGÔó³þpcÁV)Ä5Ñn¹mešõzþf(zr|vÄKr(ÞL")Cnƒ1jÞk öˆ¾ge%ªÙ·a‰›‘µ3ž|¹?Øéÿ°´^ÐhæänòU ˜@o_Ú¥¯Ÿ PFÐmRØCÖ©à„Aá"×óåd ^YœÑoâ+9ư"T¨?ì= æ~0F`lêïœÍt¯±`ù2œÃ„5=ëW÷©òévôVm€¹Vdó,@X±”%=óî@¬ƒü³Ç:WµïèŠaËQÈOçµÖÓÌw5˜Cš6”þá#–‰p7 ņx©×'eBA¾Ö€©zƒ¡ç+eYZUí`®á—9|Ù±úÊÆp#Æ·îsëªSXHÏ>v¹®^ÒNxãè8Z0øWŠA‚30ì}ûñÙT9‘Ø¿Âÿ`/Gð××ù®Ö±€Ã3Bp}SÄlÇB-ø ö¹¦|ÎlhœÒ!*L¡¬ k:Èq‡éˆi£ÈëT'Œ ð‘€*(üì²ÐL+Ⱥ]à/¶ðÔ:ä}táxK&Ëøû^ïrýyq‰a§pžë'ï/ÀÚ¥Waî@Oœ3oîæðìåy¬| Žƒ`SPö>qZˆ°qµ‡ )Q&:†«öE¬ 1/=Ù‘…IÀÐCq½êÛ¦È0ÐÓ}~áÙ§¹q´ £È,ŠÛÁ¿{ްêÎ ¾™µÖpEõqõø­Šo8½×U±lÎUõÁ±ÑÌ‚Å{ÅŽUfVPLŒ¡îwã&Ø žUB%’ûlËžôçÂZÿèÌë ÙÏèüÂ9ýz’H ìèýB"ïóÅŸ*’×ψaËÛý¿ŠB¡–šœ EÄ?z¯†Tïá]×UÏM}zèm_†×°x°A8Ú,Ñ>…³øÇÔ0î-î1°ú 8=óÎâÞÚ¹·™³}Øy£uº7Èqœ>å«_}Î+²Ò·MË™=„¹D®È€«ù,iƒ endstream endobj 262 0 obj << /Type /FontDescriptor /FontName /HRAHZV+NimbusMonL-Bold /Flags 4 /FontBBox [-43 -278 681 871] /Ascent 624 /CapHeight 552 /Descent -126 /ItalicAngle 0 /StemV 101 /XHeight 439 /CharSet (/B/C/D/E/F/L/M/O/P/R/S/U/Z/a/b/c/d/e/f/g/h/i/k/l/m/n/o/p/period/q/r/s/t/u/v/x/y/z) /FontFile 261 0 R >> endobj 263 0 obj << /Length1 1612 /Length2 18453 /Length3 0 /Length 19297 /Filter /FlateDecode >> stream xÚ¬¶ct¦]Ó-'Û¸£Žm[ÛÎ۶ѱmÛì cÛ¶t|úyß½÷·Ç{öù³Ï÷ããZUµfͪYkEN¬ L'dbg·³u¦c¢gäÈYع8ÉÚÙÊÐ)Í\l°ää"Ž@Cg ;[QCg 7@h˜™L\\\°ä;{G 3sg¥ª’: íYþ yüOÏßNf¶€ï\Övö6@[ç¿ÿו@€³9`ja ˆÈ+hJÉI(%äT@[ £¡5@ÁÅÈÚ ca ´uRLíÖÿ^ŒílM,þ)͉þ/–Ààd4¶ø» èn ´ÿÇE °:ÚX89ýýX8Ì mÿöÀÙ`aklíbò¿vS»²w´ûaó×÷LÁÎÉÙÉØÑÂÞð7«‚¨ø¿y:›:ÿ“ÛÉâ¯`gú7ÒÄÎØåŸ’þåû ó×ëlhaëpº;ÿ“Ë0±p²·6ôø›û/˜½£Å¿h¸8YØšýZ€#ÐÌÐÑÄèäôæ/ö?Ýù¯:ÿ[õ†ööÖÿÚm÷¯¨ÿÅÁÂÙ hmJËÄü7§±óßÜf¶° ÿ Š”­©€‰ñßvûÿés:þ«A”ÿÌ Õ_†&v¶Ö ),ƒœóß”Êÿ;•éÿûDþoø¿Eàÿyÿÿ‰ûŸýo‡øÿïyþOhqkk9C›¿ðï ð÷†±Èþ¹c¬ ÿ_á†6Öÿ‡ ÿ¨ü7Éÿ)gÿͲ5û+#=ã¿Nâî@ gcs€©¡õßNýË®jkt´¶°þUô_ÍÐ112þ‡OÅÜÂØÊöŸÖ³ýÛ´5ùOòEúu5E 1 šÿ¼Sÿ¥ðW{gû¿ÄþG)²v&ÿkñ†°°;À‹îï ¤cfá°ÿMÈÉÄäóÈö/¦ÿZË:;Z¸´ÿ–ÌÈô¯ÂÿÇ÷_+Ýÿ€³5¶3ùgV” mMþŽ×ÿ2üã6vqtü«ê¿Nüß‚ÿçú_ƒºaW—ìŒy‚-Ó2Óë°r‡&Dµû{™À‡BìKUŠ ükì~ù¥…ïpU¼×†Ð7Mq¶{,žÙü >éÅ´¦ø•¼ÊÇ÷!¥ê+@ÙüÞÉAsÈ WŠ~®íu½ ³ ¡ÅΨv¸;¡¨¤WòE0ÕÉâsýLåOêZàNödèkœÚ‹Ñ…Ü‚ZWxvþ=ñäù‰â÷èðÐà¯[Ⱦ<šœØoä<®ߣürð´T+½"¨ Ò›áñn;®?‰Ô{¼ôòÈÉpÉ"md^Ìò×l;dÞ“ Ó;xe%Ûª40o榢x…§¸fs¿Ö]ÈÅœ°x›ñ'ƒúïf¬È‚¦3¡‡Án(û½êlj¨_ûs燧»JÜZ¡Èé†#\ªEÚìÈ=u–…¥ÍïeÙᶱɿŸÔø:{g­jÏeLruë2±èÏZÂN1N›Ù ›nE&e†‚šo´äÆ%I‹Ï=:}¯¢\ûñÈâËÝaž˜¢4´áªÓšJU»ZeæUž“FzŠÛ‘ÚÍñƒ³ÅÈ|¹áÃÉâH1æ³ÆÂ´¿ÎŽ3Ü·lQÓ*OÇèR®éèµlŽ¢kyâ£AvLó¸ÕÈBˆ7“ö¯3rÚ`Ø•tû˜¿å+Á—†¯ô~W‰&«þfÁ#iƒ³o¶u3!bâ%aüäáøŒ¥ =Úê°]C£^Û5—‹´½Öœê©ù¸àA°¬Šê6/¡{lÖćú-m?ý]ŽúæsÏ›'ÞÛœcE èº6˜ª’aí/ooêWƒö õ Òœ”d»3ö„A&úgÈofÇßωŸ½õ2«7\œ=p­dhúŠœºO¸Q?&iŒ§z©__ëàÙYà*¬Ï^È_ˆ±v©­?R‡wÍ„}ÅÇ‹“ÜIÝq ~ÝÏÏgŽõWå"dÍÿj~ñÁE­Jšî ñ"G*|]J}“Òfð~élrÞöHÒ‰ø‹W°>gÿm9Lj’Ûð7ŒIìÀ‹ÓŸˆ< rÜí}¾ Ç[!Àã#î“n.g|·Ž@üDüðîy[Ô$i9Õs+X‰yÈÑD ŠhÃ…Œ£)}_#=$[ˆ% cœ™PŒ¼VŠ’k•ÏJt¼šöäº4îê’Sq¶FÜñ•?®™zKÞ©&ŠA>ôXkS‰J¸(Cíeœý¯ˆ¾+×!øJþ/üÀúBô„ÖR•ϹÉ+¢w|ÿ²(îA€#J0þöþF’«˜D3fF-‹lKˆ=׳ÛÑÀ}Ãëô(ŽžÐJ]‘ödÃÓ¶¯l¯–Kc¦„|R/ä1',Ãl=&ç^¨ß…Ûñó¤EÓu4@©2_¾üsÐy ˜Yì½fá7Ø~a]Öf§!dîÁIŒŒÉM&щþ[ÔôPa.bûŒ±óêwŽ˜Jê,¨*â#Õm빸ÞíK2ôCÓô«}eµ¿tbÄõtÏÓšÖ§i¥ÓÎZ¿Ùo˜]9Kgãßt?‹·Ôh„ØCÃrnPõ±…ñ£nÎg™5&Jàeo/Œú¾tn ¢2|«4ûëuZUÔ<{ઠEÞï‚bÀÁ7Üš8[~†ƒv5ÝÍ¡㤮êk…fˆºŽd]Î))?BÝ,‚ǃÖ§ÛÉN¢Ò:íx¾@§ÚÜ»wj×+¨YPŸ_K¬"‘š–¥»™Òý!Ò:Ë>„@:b4ù†Î®f‘âŸêtl£5lœ^oþl¤ŠØœýTÊ|³ìÕÚÓ3M•ÎY-tõ–b©Ùst‡º•Uqµ•Øëu}Lã”Âs *A]”ïç+œú I?É ”ɲôSªµ9ʳî­~EäûÖÀ5g5XZ¢¬`&wX„f©ª·D»ÕTrgt²†çåyIiˆ KI£Rüé])_œ5xü áÝQPBÑ%úƒÖEQ¯&¾É°ULJóˆì²@ÛDzýVª’éï=ꟑ&xë1jé!ê G§Æ5(8–ÔÎÅûì×)”áð°Ö5ìâ#GdOü¢µÍæ2¾ªK|¨ZèBUfÜö»^Û­uE‚r'æ¡U˜âARšˆóîL3__†‡ƒwRY1v™¿B š¸9Ê ↠6•ôCŠÔKˆõiå™tã*«tM0ãq…Ùcú›šl3¥×ôM"Ô÷D—]“Š Q®+# ÝsÒúsŠO£¶ÏЀrÂeFéÓ71í¶0Mv÷ NSä²þ±Ò[¥'¤sÖ£"F¹]dÔqƒôBÛª;/‹‰$[oK-âcváRãÚ–‹Ôýòë/8þÔõ˜à¨*—ÏT7‡÷ˆñOy➨Ì$Í%HÆõ K @ìÊrYUDÞ럤ù¹æßMkgw{¡Î_±)Ì•hoàºÄ #7›Ç’GÏ%2\5u.¬ø(Tâ<Ý–šþþT.B<2U‚N~ÀúàXl×̽…µIÀ²ÃsÍtØ‹AÈ&„Ý'@kâà f×^Û T33&þzEÔ¶2]l­÷ÊNõ Ñ;µŽüåæ*DLx­–ó`£ŸáWà y‰ƒNÌžOáKž’hˆnÿÍ…\Îm…„ˆ´u€÷Eü`0öŒ¦C´Ý·Cg5¼XA ç@" Šòs ZâÜ×`rº|^À &«V´CuåMeûÝ#Ò„?5G‚wS_Оd—¨ó,YgF1È¥|÷¾î !¾Ä SƇu˜ð…©"®ºÜ·Žf©J€‹®L`qТòvüð|–º·éôËVñ…ÁY{.*›ÈDCÔw~ô‰7¸ªÒ˜±b¶-_¾·¤/ÒH:D«>…|1 Õº"™y«:o „AÖXŒI©,PÚɯ‰ ©AŸO¦ÔÚð–ý=ïÈ™¾o $sc _§Ü84Ÿ9yÜ$R, ‹_ÃøÒL/Ë+°ÄOÖ’®|NóyT¨h–•؃³kl-¥L Øãmtz'Œ)g#‚Xž‚6ÐÚF(;pü!Ý(ášÜtíªlO–Ô9á[œ~D»ñ q¢sGÄ’}ÉÏ…*¢f€ ONÏv{ -‚:Þ7wAVs”ÈM\Å1ƒv“Fu¨o0ñîC‰U¦un<õ/?§hé*¦ü(¸)Ü{ ÐÍÔ‘Âêº1e9êè2ÇøêMåXP~¤VXlå·D¿ÈÑÇCØ·È[F‰Y§6ªšÂ˜álà˜WFb'ÏÊlÙgÔ®pÚ,õ–é2›Ùl¹æþ$‘¤p]º[Ø8X¢Ï’/žByuuMéê{7¸‘l…%NåýÕbR,më“4˜fWóÄD•ˆ糞¾_«#6œîJXßÁî8BtE° Sßqqü´ I?G¨Ð9‚dÇlzW¢ µ§ü>îÊPmŽ–½Í•Gän=¡{IuÜ®BÑô^^©ÄP:Ñʉ8KÂCe *Z£27×úý§XÀÎcUüf¢ÂÜGm>Éz@&º"ÃìsôTïD:©R†ˆ%êq)[ u€¡Æ}Ùxb®ô:à‰˜®Ä’[☳“r~1ºŽŽ…á‡Ñ—6ÌyAà¸ÑTF|4Ç5±Gy3Ê}öØæ •ó»{¨Clµ<\ÀS‘dÁnbèE{³ûàÔë”9ÎŽ ‹ºšß7F'ÏÄKEn÷Â5ã$§+ùÖјÓÏXˆµù¤qˇe6‰y[„bBbõo_ÌtÂ#‰…H´½Ø¤†eÖ°ú°Ûá#1îè\úX(2ý;…ü^c¡Søl‘õ×i¦Tö˜’Ý\»ªZdÁ&L|_ñ|1ú™Lˆ+#ކ¯é”Ù¸A·ÊÆYúÑ!|; ­‚XEøJtƒ¼È~D¾š°aªAr«UCà¼ÅÔoƒaå _æMh(4#Ø%š€Hûé@WÉ6~žÅe¥ƒ¬ªòƒ.¥n¬gâ?Þ,@kÝ4¢á‚˜’8ù3‰wµr‘Îé‹Ã۔˞œß£eݶܱèÍIƒŸEz¬p ,'­ëxoÕ¼³h»­g…ÊæiÏY“¼sƒkO(dA1LM‡Aô{ã”ê±Å–Šq ;f–Hm^Øš4[ËmtØQÒ_T.>|øžþw½DhïÇHÓ›_„YÑצ!ŽÓ‰Øã¾<õYDËò³t†ÌBæšÞ‘]>ñܶ€ƒï¬¨½Óp†ÂfDˆo^Hˆ©é@uáGۧʱwòrùVÃu¿ö…ëŽßc¶ƒ0\Î^Jò—Þ ‚y& T~ìo®Úv/¢K8 fTw}ÄÓ*š¿ Q)VV=J¯l_–dr^“YŽ ?±Px2ØK -GMDεóÿüºTŸI[U&Ø‹t¨7hƒqè]ÚŒ2à…÷v Ã'‚$о§‘gûVÀØ €›:ÜetÈœJ Ø öd’hU1•Wˆ?¬ …šáxA-ðºÛŒK%ÓIdæ »· ´H9üzô8Ñ> €4a¶jŽÔO­ú Rµ ˆò«`Ü/è[ûZÏy|A¶. ŠÞìI¬ª'ˆÕ2f÷ Qi˜"è’ó=U¤ÿ\V•ÇV äMCil¾T3¨ßîGÚÎÆojc”[sEó†^¢+sƒïGáY 5µµ¼>i$«xz;3 ,¿ Þ…ËëøØp<½Jü²ì…ƒ‚<]¿¾Õ¤O‚m×½SD)eiÛˆ4k p­—òƒ¿ÐØu )jlP‡`Íšƒ~9§Í\/PŽ?}”Rø¢«ç×_Ѻw}á½ûH ø9£|içpế¢Ú’v Ý¾©œ&ûšñYë8pE5kÈç_Á»zFd[§@ÞïH'ö‰Ö“Àâ¸`VÍò¹÷ûCRʺFé7ì ”8‡øYˆ°{ÄÊ:^× ClísŒ… ÂO‰K®‹þiAbP³»-ËTºu,¾¿ŒXæÓVûé3x Ür“xÿ’^ñ­Óa…@¸(øàžÃÄ—DÇÙ!²z±Õg,Aˆ3´e£—uI!d÷Êx’8'WzÓ©puÁØâyÇü$ž"IŽl5<ð¸[š]»òv ¦YÁXV*éõ¯zü´4×@ZõpÊ«5(/ÒíA†ægƒ8/€ Ûj¶È^FlÜøÝ‘&È\š°pGÿ. ĺP‰>P1ô']b–üüãÖ'¥šç,dÑ¥‡ÃÜ<¥~Q=ª÷åaJr!R1õ%–5‡=éG&œpOûkßò ^šDF7O²ô÷øpaŸ˜è9+_O)dG¢['s"¥t꡸åϪX ‹W®å6/^ôÇ}{{ÒEÚèRWA½ìÖvÿn†[ ê² Ö,%:îh5´#®†£ÝqIت^ñÞ¥QÈË-íÁq¥+Ë‹º©B‹(ïtÅ C´Ðª (÷“'Sµ¯]þê^9Ó¯Q ßîQáVô,§µyÅ.„_ô×O· šþÌÛȺ˜2éXÑõV1´Ê™9ó½Ù.ÏÁO „-I*.™Ñ\i‰Ãm†‰éÑO-ç…¬ô®ãÞ¾ÖØ–Ó:‘$´äËË‘°Is-üy)be)P!¹QáS¿Ô|¾ŸQ~¾:r0w¡Ä¡u)s±~«me³pê«ü Ê‚÷ñ/î‚âÝr¤½­ç@eRac޽ÌqÀA×ÔŸþ"ü¤øªœ+—E [õŽûø•cùu)vTÞT/’NÜÚÏ8d.0¯(n³ƒlVµ‹ÙÈW*¢\ÿò§Lå`°„àï8¨¯Û³´‹Ð˜ˆ=Í•· ‡›݇œ¤’Ákl¦ëñG^:[‘€è¼¥Šû$Ây± E‘B_ý^GU—bÁÜ5!¨Z0Mƒß°n •ÅM»3A/)+ý1H=—/óÞ/ÛóÍу6Ü$~H´ï7œC²##sÕ\\mz\C«èñì´¹PÕ£ÇÃ爾ŸÍ?Œ•’+ígÂÆK X°E y°„-A¹„y}ÂÞn8³§ ’»Žo[BþÔ¾Ñ;pDþ¡‰&£>ù-;‰¬>>(„¤,™+ªµºˆm=©%`'HÀ¤z¦­@je,â›Õ11.°&o½»„‚‹•0á.ÃXn˜¸ÎTj·}#³ ÎáÁBõcåณ6Lù‡qø7€‚.²-ìy[Ù`Xô™®ôÅP èÉ Œõ|?Úâ=ÿÈcØ1w²Ú&|·÷¹!p Mù’4o¦sjY®y²€Þ¿lŒaD‹/F×Öäf`”Ã7DH2‹ò]P™lª®x = Žè€[FFÍv}¸L)ÏÀy»N".NÁ­Sb­TZpèOÿ̾0Í6ÎÒs)› Ü þ|Q®v^kLÆBuÎǬ½Âþ¶ƒÐ·YpÁdg?ü»¹šY…Ï^æ¾øàbáô‡"‹¥Bs­ùw5ärkP¼=)V‚¤¸‘^5¯L§åô¼›C‘Á—Öñ¨/º8Ùöú¯'ù\¥cKn×›aæ&#¸]á×j”µ° ðâû>|'3QZ‹N:ó„:FúSº¹Úm”:)žÓæ[Ó¤$U ?ЬaüŸ$c¬ÑD±B‡Ál˜?Y¥ûÒs#H£õ‡8•=wÝÒ‚ÑȰ¨ùÞÿÞ‘÷^©{D½~óÀöKïÚÚ ¥<§²„*ü]­dÓØWÝäDÏ"lâû}ô˜n¸Y%¹*µó”áÙßZk#;p;°,J  r¾í&²[rÑPÚam~Ÿ$‘ÇbȉES|º±½†éMþ<½Íí1„X´o! _»eŽ´pˆÉôDý#j(hüÌEÕÙ7r"w’B-³¯óÎ…5I«›}qe—“;i€l5a'Ï9òD¸õçÆ·7~ Êó!Oæñ_[•wn~·)IˆÊ€ü‘¯N]è/œDí]ª~ÿ(Éœ¾µï ¥:p’®êà4Áå%4ò¼2õ}ÄZc°cÎKj5Å)/‰ÃLïÚòï¦Ìâï‹Þ«¸ûDzÏàÙ³Á x^~@È|Bq©çVDŽ?ñ†¶º(^ÝíˆCobÜ Ìü)Êe=èš)€Tïé¸w¿ÛnÓb”:H±x¨jâÁs†}Йà°“{à\Êe§ŸëvV’¤:¿JñµôVÁÕfñ­SDÿ»a@ B%02å‚cx_ös1•XQcEŽ1Â6‚)à—qö;^¯Žd=pˆˆîfÜèÝ¢„Óo¦í"2G8øç+Ç^ ­¯—pŠ!ÙvÈ¡‚䥚rÆ(é’ -üê0S‹Þ^Œp g®Úæ‡95ºi"‚BœQ7*Âh<Í&ÏÏ{: P´Ïãmg¿èìyö7¢ösWNT7Ù$Gùâ0™…´žç•ŠÎCbŸ@óQ:æ£.^«ÍÝš´Ôlç""¶ËÏ„/ ™ïýMïÖàvÎ:X¡“Ô‡¬*@BF’€Òr8©­s›ì ý°ù@]›ø…¢òç`ÈÃÍ™ÕOéEeZfR¬Òê¦êø©Wèzp³~ Ÿ%÷ñáÄèã‹›óÇ%6ðGÂ^ŒˆeqP¡w¡Ð(Êgéciж ¬·@ªÁ×N~ˆçg&PjM÷x\›:sÀd* #&“8yÚæµLíHz„-¡'\—Ê^ë72X˜Dö|ÐçÞø©šÁ/2ÃÁÑš›Œ“¹óbËÇûýø3,V›UȳGÑ×(›ÇuÖ•ØîôGhs¿õý aW°ë¯£+}\)iÚyõFÖ„Tú¾[rê}á•Îï’7(Q'ŸCºi°ƒ8[bkA¥õQI0: ݯúNIÊݬÎjŒUÔ2<–§âÌ€DqÅQö¿þ¹4ˆ¹{4™´ÔŸ‹½àÃ< ¶õ&uÈÅò¬'Kh“!?D(£,ÊãЏú‚Ó®Ið"‰ö¿ú.ü˜KJKbw|¥jîiu•êõSˆÇ Ðã+¬…[÷;Ó‚,EqnGdçýõ’ê‘?½TL¥ŸÈƒ)Z[v7ý­¶¨piš°sűÞ=ÒÌKЉæœ1q~¨ë´L7„ “§Âé®Â)(=½kî#e}Õ†í’zNÊJ×Öðz€ûN¬[ Ÿð–uÄ1Ñ«¹‡hœ!jSÔ~*­ÿ£À(Söö Å2f†ƒ¼²ïˆÌ±Q É%¨˜bÑ·Æøèžnt7€ e|óªìÙ#€¥^ñ~G¬Æòæéð ú™È”dJÿ§èEÊexý ^›™ýˆ¦u×¢{€Ñ·ùÔ^Z\÷E•[yƒ¼©Ú&- )®úybŒGClYS«àühf¥v„*ÂÊØ8Yj£ž‚)êRB  Û´½ä¤A–ÜÓOŽÏêK\¬: çAi|¹ÔëM9Ö¯ 3EwM³¦öÄ0ýỈ6[(d&L¯5ë`bŒ†ÞöªÏ!1¾5%‘×_Þ•³æÉŽÌG ø®-fúz¿ç‰5¼§e¤òƒRØ'”½2£+Gr¼}ÌÉF„ÇY›uL2-+‘pü÷…‡—¯‰G›ž‡ÝÅ™‘†’8u‡{C(?&®—hg.v0@h—lÊäê5Á?ÝMl ’4ŠVQW*6c~¨Ž!.Šô¿‹ˆ%+ÏMöB± ádžƒ©Ë{;ÆK÷äDÐ37“#~׉ gAƒ4ÍK‡Û Í&m° ÿ°ÁáÓ’"÷YPLœ½*´Šé#©©€D"¹MiHÑö¡dno€q¿âöö:a ó7ÈÝÂ|¯Ô F·®²RkɌˇV/nO‰„ê2î}ÅûP ¶bL›ÅÌ»œ'²©3áä‘ênÎø”Öe+þyÄÊј½Ó”{Ƀ]¼6˜ÕQý=1”< ‚¤m¹ì/«I£1·ã6ÑÆYþv‡vr£"¤T¶TÔy÷>î+ 1P—É)÷Å›joÞÓ…DðF·T+“VxŒ¥‚’9›ÅÁ¨¢ó³"Nm«OW¬ÝBf"3:ç%¶ùœÏª‡M aËæƒ/£vAqš•x_ r{rY9ÉZw¿ÒL„—|Zt{¨î$1÷8Òµ•ì®ôÃÚêûr"ÊZ8ã7ü—1:¥å¼ûXµ LZƒŠc”føHýŘGiê@Ñgy‚¬)æ{(üX¡  a6‚ú6[aúväè–xJ«O ‘T_‘Ã`j™7Ú MìêM^…WsÕ GŽ”vúsÅÂ1ƒÄá‡>¢Oï>*FÞVUgò|@\l…ñs9lŸU5æþôxnÚ± 5NôzG£¸y ˜˜FBýšžÔ›ƒ¤T;ÆÐØÕÙ(Iù¡o[àzû³gº=–f_Oá–HÓ÷Á]—:Mí»xBs]PÚrA¡’IÈ‹);ØÞ$µŒå¸¯~ê?L$,¤éòuýmÕé 0… ˉ2Ûì½Wýó`uÆô$E}v2––0FýÃÓ”<Ì…>rÙœóüâûSV›77¯Á=¶Ù¨Ë_£¯U ‰¥ùfT…™ßy¼ykFª$=¥w×dÝsØÝ!i]©\jzgV~¤ÐÍäÉK[ #¦¡wdIïñýp5—=-kÞmâXeÞÙO•fñȆu” 6n5Û®?¥†szÝM(…¢Î®Bº`ŠÏ;ÎgÈÞQöŸ U\Ѩ‰³E~Úª]¼þ©µn¬7i}÷º‚7xs†3%ª,Ô@©¬öùñ› ]õ¬y')Åy@ÂÜÞ.úB _ „ƒŒŸHË•VJ¼¦z²Ú}ï·…ÕÊ“$Ì[(ûFÉnõaÍ‚£Ÿ§ÊŸZUòž´»TòKëôÑnè-‹ðmä´Ð%¯u¤ÎAShÝ·¡*VöµëÚ4ü;Ð\'+¸ˆ÷.ò?ÆœÎûõ-ÕçK0‡ 0aúal_+ìV×+ñA2ðU÷ „ :ù(Ú²¦Xš“™&¤¹Ô [èIƒ?nŸ¦Pí…ÓÒõá¾$vR5$ÆiÎŽàsëŽÊý¬¤:Ä »2at,èÚ¨Ó”õø²Z•Ù»£u«"ÿ†šö£?Ûœr&ö•A—Ïy­›38yóù1òã0׊$$¼(¦(&V*öF„zô‡!t´uE×RÜÜ…òÒÊÞQv.H«Þ“¡lÂb#" Ôð R̺Ê_ÍŒéWáO=éfH„„]#¯ànx|-k¾lÀCB±…¢ÉÔ#®²ÔXPI8TxðÙ…mÑÎh¡GKh™°A!ÐÃ$&gêÐFà Wa÷©^üO‹Þ•5;Ø0{æGFÝq#ñ]#ô5ô§Žá&‹u®ŠêÙmp›\ÚT‚'k´ú­ÜCëAíË9"—£ð åS…¬ÌE}Óc¢8¹±Ú1™ÐC„ÌœbPè,,ƒ¦%¨¿)èÚO:¹ä®¸ÚSÂ|Ɖä´ôFN‰ Þ’îròü4º€=!=üf°½IÙ!9n«bª`Ÿ˜S‹6Qb7ñ¨öh7SëFp¿˜‚U‰êé0wš¹œ?CÝòDqû]0¹—‹Ñ!zb^ü™“Ná,†?%¾+4´ŸjßvO¶Ð×W>&¤îf£U%²±–˜B7ñèüV$­µ¾s5`$bǬ•%ØsW 7ª‹§uÅÈqHs.Î IØX©Ü-‚h<Ï€¿ü ü·>i1HúSüüˆ½…XÈei !…<ߊg”,GöŽp”\ ?»‡èRy~Ôˆ¿ÅWä0hÖð×Õæg¤/ÍÜ…+¾ ‚Ú)=>êD$€ÏÁ=-™áL[ÆÕ.‚¨¡Ç –î'wî˜IS( áJ&—ñþwõÁ<4£’Ãj Ò¿Q•ìÜ@l¡[Å6¹nÞ>6,aûÎÉC(a‡gU.½wâlõÄtW Ž·fzÎê×ZÔOT{ŸŽ¨¾çÐqS˜)›º :æ !Ë&ž9¿ÙŠÞ'«ëñæêÚîÀáÐ æL/¡sp¾·½ý°¼l4ªö–ÛQ³E@hÆ=¬¼‹âA¢“‡É¼j"»ÂEtBR|OûþÓK'ÌÝgœÞ7߹ˮ'Ý”ûZiæÈûCi,‹h·zt&x3g#UÚ¼n˱[ù¹ÀUÜ·h!v- bÁKŸÒÞ©:$ElVðA0lYKq•p=7g²¢'UcjžlZ^²¥Á-€'¬¹&ßÝ4v¬À=áÝ;±Ç£I0 ·nã#p‚Þ'„Ý·Q⮦w/úURö7n)Æ7•×½Þ+ðíFhãmwæím³)íWJÉÄxpÂ'C+ÙGZ!v÷B…1w© 1´nü,M:Ò'½?­£œÏÄ># éßAÃZ€L\–Õ lJÀø©=á IW¤~ ŽqH=ðh’pÌcJ£þ¾*Í{Òw+˾‘Ö¯crýþcì›~>òAs,òNž· ûkpç?œ`T=‹Ò4èHa2be‚'ûyhÖ*å}…aMB¾.¦t½Y¾ÚŠ;4•ÛiÖl#È”óÂLUi4Ÿ®„ÒNFsj8zÖÖnrfêO“]ÚêîLÒìª;þP¹:],èW—[‚úœ£o÷ii§5yŸÃýcW›EoTBrüFö,õÆú ÄååâþÅgkHž|¢ÿ²”Wm ì;¼ä†jµ&Ù=Ÿ§îüŸÔ€ä«êÈà‚º+÷%m)«¸Ýû>Ä,9h_€Ÿ‚Úú騙ðàCvVUÛšµ¢”Î ŒkÔ=‚ÀUÿ4=­'uõ¤#ð>»‰+›ÎµÄ™,ÎÙä.Ù&dY=4Æ£¯©b:¬ë]bö ì¯NF߉µô~p5OPKdÐSnZ1º™E[Ý-1 %¿ð§a˜’7Ðۂе9¡ÞݯxÄ¿U:·]Þ¿qúD¬B¤ov;b·Ú7‰’Ù†ÙèGèáÒ^Ë—d0/·;¤3I^Bd‰Ïò™vù‘æ° ]Í6G Õ×c}õV´Œ¾¥&"?~Üö–˜ ¿eäÞ£ ®j| åh ÔMèÓÓw£U¦™ó}$ÁÁ†›ó"ÃøEËòF*ðžìÀ+©ªýäôœ5l~›(QÝíïOn7ñ:Ï‹w£tσ= hhªF ­€—º«ü¸”å#š›e7—N ]cRKXꡊ –×`VA·Të°ÿy…]tM??´W@¡ÆIHer¿/ÌX®ô½!³ÏsFÚñÕ[âd»æ@ØØQtøÈ’Yê†e5Ä“0Eöô…|V³Ì½f½‚ÚÓÀKؾæÖ–ïŸ|°xGw0^×5Fd¤‡%Œµ°¹ª„—u´P5VÕ0~¯è>·X~®;B:‘Oa¡èM\ü0߉°ŒñÁ¸ýâ)ˆiUý#$ÚÞÆBËxð>-kŸ+»ö ¸%¦]Q‰ÃJ¼¹ÐÓ™ÿ#éôêâ»åà=Ö+»Ð¶jÅ ¢ªÕ^ye^Hqb…øÏÃŒïM3Á–¡Óòžbv‚ƒ¯¬Ž0«fEEQ:Á¼O°°(â\ñX«ÝÄ΂§KÒq·~¤ƒQîs]qsÆòœõ´ g«;Ûçhª°WA-¬Ýˆ ?3„Š>÷^Ž™q¿[×cµ©,–¿ñâW黣çMWõh‡…ŽxaŸÂíðAmÚ3ò˜fm. 5Žê(1!S¼4Ÿ\ùˆ×v©²~$&ÿ&À¯Ž=©3ÓŒÎq©ØµG¶+ÊQ…’M…xR<„ËÎ̶‚;ÙÜÌ"ÿ +©M¸õ-¤Öé T‰"™KÞ$àšó .”VFˆãÅ5W)¶Ôɪð;Ø"Û4ÄŠ¦Íl†­ß5¶;õòþHy&àÛ¬qÃïFb”]}4ànŽ ¡Óç‘D*ùŒ›‰59mlªÓ\¯ñ\'ÔŒÊ(n{˜Ñ$”ï(r¸îo)¯ìR´ã6‹xU¬ n\¨<=Êæ™ÎÛPSqÐÁ06ˆÈ_1WÈ;¯N¬+õ±¼Æ)žK nóµE Â#Y[nOÊf)6|uËnòbd Zö—)=v™_G]‘Aôà>êî3 R«W55«.6ñ=!Lú@© Ù_Fû,ÃêhžM)ªHß¾Nç¯iì+jIex«gúŠ»KÛ¢Sï½ý†ej[ƒy¥8L[G”MÑu¢¨†u‰Û/Òþwâ[ošë3›êJQ®BËål)FII%§ûêS¿Öú¾ôðCè¼XµV²û< ,¹d{¥þ€Šù s­@l2”sÃ…¾€ïŠg"–w"·vé#«Â5!ŒG£•Itæ’OºTùµH^êaÖ \ÝßË#*æ[XE°âµ ÷ýc=¨žw%¤pÂ*T³–H@9± o—È96Ò%êÅ€›d¯BŒù}‰OYc«q^4.Êûµ†º**ô2ëø¹ôÙÜ“M†YH¾TR'UDÐûœÔ÷ˆ;×ââ}ÑÊ”øÆ Y8’{hËŒV¬ "Æ¥PÏVùzú…Âð:[e—'=ô¬¿Imö˜Š x~NF{ÔÈÍ`5ÈV{qèSO`qU ˜ ðM;óL0H¢%Þf– zq‡R´Ðï„u©*ü”@@ÜTîÿ¥…&á’]+ x.ÊBjÈþN²Cê¶@ µ¸¡6ñ¨ã†·SÁÏsðd5{ݸ͚§Á s®L%Ã>„D´Õ"jÖE‹åþ؆ÂÝe#÷v~Já”—^ÔÕœ9hÒÃΤ—Ë”¬A æèQQkƪñ}«‹ùËv’GKfĆ— DÜv­#§ÿÉ¢¬æ0h‘©¤<ÐöÕEHò=G±¡¦rÞF!D¤º ‡¨"DëGùGp‡ëÖPÐïþæ<)û,œ‚ø>R ÄÛ|äÑÎÖÏÎCCã‹oÜì=šÏòZß (g9;wµ †ÏÒX%8Åv,!ØIOçÃa_m[üTs¼+cŠa 9D„¤*Æ ?_É‘Òé:r àQ&‰©„Ý+ëº~²™ Ñ„ð2ä¡džD%£l÷ðƒ™uÅq G úµÖŒ¿ë>“˜­bŽc<ªa£_Û;€ùÈó`æ^€ZÀŒ­ÔÉm€€ö>³Æ~2ã?\4½Or~µ»¿ê;ò‰PéÛ~t »Ì"8®’»§£„ýëŒd›Øû'÷ÞPr9ÛØYeÓ¢÷9ÊèÀ“i¢ù¶špÚ13Ú¼2b¯)ìdà n‘r2-[bñåÀ—ÍëbÒ…¤€vÞŒê= =TnÅæ‘ï.˜&Õ[V–kÇÁ«ÕÛ1߆ÒIJ'ýRf®ïíä•@›4r ßÌßF âGŽÜvËÁÂà%A kµÓKY•l1Êhüúè«‚ø<ÄŠÒ‡ªLGRѯä9Ôa,¨^®¦t}ÅñF-lŒAI’÷ª§}o'+B¿Å‡<¹ž«º=L«üòî%ó¯aŸ–’rÑœ-ë€&6í=F(ã_·õ`ðä&vþÑ_7¾íÝ@s1™¡¥¯BvAùÂà´ÒëùÇ%¾«Ú1Ïì“ÅKãÁHÏàQ¥™Mµ’h••Ý"Tìàrƒ÷YrˆL'nAOe_k(YÇ÷gQ®ðßåößªŽ‰“Šâ6Þj¹>øûŠäÝ‹0všƒ ÞïäZÅÂMG ’‰Üð‡Ô¶¼ .Y>({WˆZÆ`š.ÒszC˜§Äûf`FÁ¾øL˜•M¬:;›×h„ì¢ÚÈâOÊN·S ¾~¶Ö Ëk& É…†zðöÅû¾ç¨Ü5ØÍ\¦âÂs„IÉpÒà3ýä’ò&<~ #©M-¾cÃB}… »k—Šywq,2‹=£ c†XŸ³ÅKó@Sñäñ2\E~1æ¹™tBÈ$Ò3 ¤Œ9*ǤUïÖÏ¿j[˜6QÀi8MoFìu[Hî³Ìøm1¾ô 7ÿê%} ç(/s\ü=ÝoË(õtEW”ýyÕdúÄ•QþÕ…!Á@¹ƒ·Ñ9*#·â™ânåF àƒÜH½SÖ:¶oºþ"»%„¢ð÷}h—ëÕf´•¬î êt‰/:\r¾ÌÅt¼‰"|Üj€’”âÿ”¼…?A"‹=þaž"r˜åí,Âa\L Þtº†²AI3 ‘œ$­F,"46E……ðÒåËŠÆ©@0€|{²ø¾ë–hN¯œ:+?–ìe¯€I‡Ç³ó65DeÈ.,jÞðyyðë«"¤áŽ9{Y‡üÕZ’ðéJ³ê’Àrƒ=r«ÿ¶Ð7ùšŠY.ÞMì±T¡8$ïëÍèÂ+ Lº¥ÐÈŸÓXJÅd=±~.²LPâlÓÝËß ÿÅ€¯þuëù÷e¶Ô‘Ü 58Kã0æ—ªð¢t­V¢´=ÀY¥…ª•ÊQH¶øîÇQ&k;÷Ñ_“‘~—“gBŒp|òÁ¶zÚŠA£)•ßáe<²u¤wµ!ý/F^d½¥>jÏÑæØ3Õ»æF0ž<ÄØ$öKn†ŸìK-7űtT³(5ñ\„»)«pÑ›wá½ÐÚš½²-r¢N®KÏPjéŽA?^ä^Î 1ñ­àgÁ^Ûä’’4úúd„’#Çí*qh™s"ª[ Ÿ¤OŠð:kâÄ꣑ T"·äÈõ¾g—u€ýOÃLEÌüKí"çÉJ×Ð.ŠâUª(V{ +›Å=~G bt¤fÌ_ðᛥ°¥ãÊ7ü£S•Ð AÌÙ‘^‘ 4µ29µï¡ÀÅÇp)Ç?tq®¤SjG^X øßÒtÛa‡02b,ZÓ´L¦Þ~lôé…¶ÞN+¦Þ–ˆ¡yÿѽ¦°Ì©œT©»S¶w÷ÖÁAÞH?ÝÖ©ì÷¡M|¼T¿æçÐ.§ˆtÙ–˜x"6ýsú&8æk¨Å§b÷È…²TÓã*vÔÛê-xžo+ëÊЭ#ºM®´ y”YRà,üÉÓ27”®ríã%è½kƒç- RÊÞè«ÑÌÊÁÌУÕï¬{A g ´KÜ퇘EÒ¼ÄjeºâbF­FŸ:fEÙ/¯ÊòAbB¶ó+gÕ¤ù"‡¬™lEÞF:ÆF²f‚Ü[k‡a CöÖÀôc´^¦ŠL "žt¸lI¢ë ù᜶©¦Sû‰}/ŸŽEÿmç^øyËáùc™ß:$6¼AšºfO¹£=Oé÷7Žßƒ’saŸb8û3‹Í×O:x¡SH¹®zîàÊ冟É5ÈÙvˆéœÛ¿í8³â¬’¨)ÝãS #|ã&,Xœì=6ÜUôÁüÙÃÏǦÁÚF5—¶McÂÎVŽckDÊ‘–ã#)1üPÄý¡ÍøÕýäÜ1ФZXnÉóLoÅßÌA|6€€ÔÑB´ù£øJÿ–<¨{ÊÑ=FSÌ2##Ó¦1¼äMV…ª ”ªÇ_I×WÄ5Þ¼‹fÊÃu²R;Ñ»®U[ÜQ˜Š¥¸Q tC›áuj§S‹P`+ÇË«V&ð" ,TI ëø'$²·/ß{à¢é½U*j#°G®Í»+ª˜Î`Ò±%6EˆÉð⡼ÃâLÓ.W“.s:¨D˜t #—;½=kIó„ ÂQO_Dm\ä ¤È»à³DY´ÏïÙ£IÂyôxÆŸœá.PŠÕ*FRŸ±²p!ŠÐç¿FŽä/¯iÀèmõ/{¶¾Iåꡱ=ÅQÕ^!k¡t3Þ=*+iCxBã<‚.ì„Åp\ÕS±dcÙø¹µôл§7všTΆь~»»¸žØò¯w¾½ød'u“Úõ°Â‚™,Å™–`•ú½ \–š+µ«¢rI¯º (§†¾ÜùPkè°¨JGd(ƒ·d>³EÆŠoÓE꟢r’Ì›¾ª‡p.±rœ_RHÿüöïÌØV`©Ó‹ÐŒ7-Чïžç"s kno´2ÒžY¿Á™}NÀÇQ_ºð:xÌóÀdÄÞ"­l#Ϋ­~í»ñÜó¯½Sr¥ê†8#COénõߣ ësÈ‚"ùÒ8(’Ûc1=ä\÷Ê0¼[…ô\¸Úàv%}ÄóÇÏyÒ‰çæH19_s;DjZn­ƒAÏŸä^Aàó2‹AÌ™©eáÏD¨nÒ|^?¥€¬>vÕïðG#Çõö$è|—>U—Jgm)oVËóg'$£I-Ÿ“/¬ÁÓ]6«‰æi˜: L_xç0ˆ\…ôö¤Ów¼èr™§¢LZ89?ãí´`@ù2X2›ïR&%’ù2Ç—ìÒàCvú7i}¹àD šmêÀN‡×ìÒˆÒø"زÖò­BÀI*¡hÅ€´R7C/T‡Ô6ÕW ûŽiIèWS7•H¾¯°ífñ.(ËSúHûß:¹ÒCî÷ÚYjö_þÈvžgxèÆ¸Qü@}|&Õ’ò8#ÞÏTH×P 4KÃø!~÷!O­}à=â,µôMX$:~Uw:±ï£æ\I‰ÂÝÆeBkXa`5xõ{ÖμD"j0‚âqÕ\$ÛÂ7=.³ÇTmŒ +õñƒ¯M)FQ'ÞÊÆÅ`\ñ:÷rêüà²\ÝmŠ{ÌÉÈIŸ^O+^~1²¶­”ÆöoPý,ï4ÐÐN_X4z3$Ä–XBÎé·ÖØoPǶ–˜høÔp­ð[ óˆ®zV•|9ÖUÓ&z}î:}‚+Bg,d˜_Ö¸N¬VOÙd™ï@N E)I²k(khë`ŒÉp-hí®zªÍ•É\()ÐO§ÉøH iÒxóiÐ: ŽI ƒ³œ-V]‚¹¡Z¹ŒI5“ ¿pX@¢ÓŸ6¸‘t*_LÍ‚)šÊakì¡nTÀíÕK a3$s d{|ÄØ@8GÔïˆS"'‡¨¸ ˆâghCßÝ5íÀžä0©LoVÊ· ZÏ"`´ôv -jêSQÙü5ÐVŽ~¢Äaõ¼­²”3ä‹%À˜ïDO©2‘¹ÑËå·À¦¹ùxMt*HY.w¬Þ9I§ÔýÙ!M ¡ŽJq-惆Fæ’þ\,uú\mQ_vˆ°±¬öO¿1ÞƒÎ÷œµ¼Ä8+ºj„‚<„k-8ʃ@Ö›ª—¯ž™Ú7=ülúûìÞmM‡_ùôutßs« ¾—pܤ]~©.,ܾכBŸP¹p§×*ËR•õ?÷ÝË1œ_/òÓá9Á1I$˜Æ×u~'‡Ï¼˜öÝí·•‰”äéè6Ï“©£r;ín®ß‚wà¢ß—©ãiN³}[²é@#½18”äôujF)á ‰c¥ö9J´2ò¶ªóä±Ó É7]„ŠdÃÈݰ-2Åšjü±c;oëî/%¼ÒVêUK§I %Ý Z{Ç©¥§šd%ÍÃ\JƒÔÀD® ™é1Ë~ÐÅÂä¾töæç#+wt\ØQå‡;ÿq®#v·LEŸýjצ‚ÑYs_„-E“;³hCÀÍóêä烖àµì¼«7z±9Hð»ßezGä‘¿F ÇôiÄú½>.ù±Ûqczø85ybxŽLco¿G$¨p糆ttÝ@ÿ#^ÞÍšþbç'BáIèþÅÞDFÀ|À™G¸q8?åãvÚ~ˆeÛ=ÏdÄUôÿ] ¢õÈ]Xë;ƊӲўÛQ`W·fé[n’µƒŠ;¢­ F`¿±ܪ ØÓHoq´BÚ‹(ªkm æP m#h4žøB¨býF¼ípT>BޝþŽòFw=^Ûü6ûÆðÕkSbÏF2Ü53"ow8_¦ò°™ "¿å ¨{bãU®Ü}ÆAuW'àD¹s êC3Æ´Ðø‹†áï|Têöý±`é{ 0±AmþY"»w¨oz`€[X‹Ü]è›NvÍ)÷Fü¹'ý*©„ßöxš-sý0Ðqz !tëƒo²ñ—_ø¢+ödàTNçkTèO²²ÿyÕ¶y¯ ¾Éˆ8ÌÒOyþ%·§×¼’“úO2ÐC}š÷ÄèàW" ¯FÈJ¢tŒ¯“š!>rlï Á_­`Ððlµ†€Q ÍÛñ÷ÑLÿUQÊïÕg¨ë5Ž _#ß„YëOˆ¶RõZ¶ûxEƒô\ÚA<ÐPg ıAçsCëƒ|X›L7í.Óµ‹Và§<vnž£ð&)+°6¼²b‡ÛšpQ´þ¥£^‘Y‡™ì³IžY4P¸ô¨ ºú¶^À<À_ ²sPÿº‚­*Ú_ª•U«ðÛ‘Ÿ¯EîlI*ü¸].jn¤¤+0{Š%1„Å 8 bc¶ßcæýïíp#Í ŽqtƒÄŽqÎ5 éûc9—ãr`6Óâñö,ÕŠNzèòúžL¦¿ŠA_7žn%Å(Ȳ¼š)äïµAÏÐòËÅq£ 诽½¾âñ³¹™!“ýÂX@!#ß…ë“™ÎçCäöêk›hI¦ÄÚOæ nÕÍ¿{c|D–÷Yª»Üq£†üŒG§Ýå[x«ÌÓªº½VÇDPÈ^8M9ùWA9Z»]JÞ;UàÜíÃ7¡çðÁêÙýëîJ%Ñ ²¸M2LáXqÀ§ ÇT,>º\½’«W.ã¤Í0ÍVóç°%xtâ,Æo‚j:i$ÈÃÁø@"+³ž+”ÿêa{TJ½ÜÎj„¶²UG½(S…/–-Ó0î"IØž‘®Ç \zÊF½Öö 3Þ)ÉA2}¼ûdWÛŽ; Æ×SÔ¾WI ª\ÂþWäE‰ ²dõ\`É&©¿œ—d{ìeÛk»1Uxƒ_ë'ö¤ÅÁœËV¥ñœ*lêBÍÄ¢2ÒÚ •y°HÀ x|·(@±É.ÄÓP™WÅò«9}K_iœi§*ðão„ "H#f¶U¯ GäAn¥`þ¸vÌ¼ÍØ(ßX¡ 46‘;eCÓ“ÝM²%•C•¡6ND™{au‹otEA(ë‰K#L†NŸëšÌzT<þP´yöX+AT¡‘¹„]ý~“‘ÌêùGq‹±r¡nÏ„è/rš§ë{»:C‰: îƒ<:i÷´J¥ ý£]ù½täP|C{]œhÏüêÐëõ®ÍkÅzE-S(´~ž׆†™´«€®§\bDš¤Ô,5Û;êhäOдV‹\c¾<ÔqØŒ ä½'Ë=ñ–áé=®þþÔÞöy/õž;jäuT—‹dÁŒ/m⥠˜ª#ÁÜðçŠwTï.ø‘hÀ´i^e*r@§{ì4šì¬o $?+.LüZTˆ~QlUUÏ™ÕÕ_0§ºˆ9a ßBÕ"NPHLóp| rÈîeÉ[^tþþW,¡fZÁz·%‚`­?Ã-Äéq„ÔbhŒ©ï& ‚ªîÜS¸[;Œ.´‚}©ƒÃŽð=-±ÿ)Ø‘dÖûäÊÚ¡ßÁM¡ïæ»+«!:ÿ—ZŒkpŒªÔ¬…êûCØB™Ó6s³‡?á;œéîÿ$ÿ€'Ëtf¯zŸ‚µ°½3xh‘ݰ…Åœí7ðáœj‘3¡69¿Õ pÁw¹ÅÍ)ýaf^Sí Ó}£K‹²µžÁ~óüäYSÄ]ÚÀ  ºŠ¾>$³Û­°KE]ö0°ôZtJm6`ìúÌ}߀ùÙI›Ø£Ëü"/ ÿAp­þ(9 W BÕЯ/³G††?¸váÛèÈJ!¢sYæÎ¶‰ãsT\z>›~¾•ŒPkCjµÝ>Œ¥¹ÎnÌß~[‹ôÃw>©tyþ±î7ù“Iḩ ùãÊmëHlZiAàÓÇ®¨ÅäóíÒš†î^qw,‰!Öa1½Ž!wkx75ë5ÎÁQÞQ¹y„“µ'¥-r‚5qÉïÕýŸoð7 rÓÞM•ñoÍцA_3DˆÿÍ#åÔd³ÖAø|Ã4÷l°g,Ü ÿÝ’ôÍÚ{^õ£?RQÔsþ/ÔþÏ¢¨dõï$ßOCp _Š•¿}!§õµ_¿> endobj 265 0 obj << /Length1 1166 /Length2 8243 /Length3 0 /Length 9013 /Filter /FlateDecode >> stream xÚuweTÛºm‚wNÜw‚»»{54Á=¸k ¸[p ww šàîÂeïs÷ÙïìsߨµÖœ_}2׬£¨)TÔ™E- f )ˆƒ 3  ¶7suV7uP`VY¹^@.Sdjj °‹è¿èB 2uC$L]^x kW€¢)À°ù¼ü\l/k Ç_(?@ ¶‡xT@. ¨Øá…’€˜»Úƒ\Ô]íÀ 53ÄjræX¾tößUâGO(ØÊÚ@§©¦MÏÈÈô7ÂÆÇÇ0óü‹H€œÁVš—…ÈâøG¥—Ò ô¥i‹?bU,M%-À.Œ  ³vqqägeu´4½`,Ζ, Vú—F%,Ä!ö$pFþC3 0dþ2”'ë?u³u€¸;xÿl v°øs$ WGVM°“+HVâƒ_ ä¿1+ € Èä²@N‡¹5ë%5<A’lÀ¦>ÞŽG€¥©3Èl z¹!{;›º.PW÷ÿKüç™ `6w˜¬^Žáïì/0Èò_{ES(Ø dÙÀ?®¯ _Ôâ`çùw¸’©=Àªª¦­-.ÃøÏÙÿ%&yIÉÌÆÃ `fçåzqÊKF>.Žfü·éð'ªb þß>§”u°„øþ5΋Žä‚:¿x@÷§éÿ™_ â6èþ¶Ž øâš—Ûÿi©ÿàÿOcý³†”«ÝŸªÐýKÀ‹ÎÀŠØ™Bÿ+ÜÔlçù<ðÏ@mпÜÿÿÉ#ëbj6u°²û·L`g)°ÈBìbný/»ü¥²ÅŸï!Hâ þãM0³q±ýƒÓ°›Û:€œ_ÎâO ä`ñ’’æ °ƒ@ÝåÅ•¦P‹Ðæ®Pè‹<Ð˳í-Á/ ‚@ sä¥yˆ¹@°Í—àö»Qbwæã=Üš7Ï®äœÈÑv¯·Y`º•lK‰9s¯y“?I¨ŒX WéÃ*¾#–Œæq·åë`yƒ‚ #AtÄR'¨Äz†H7yì!Š"HÍÙ÷ì—9¥(¿Ñ¥ÃV…àù‘Þéíî9)#“•çW")ïÍÓóá?ž(ËØùªò#Ì}•Ú†¥¢Èu6NÝÓÊé¡4ÝâTv`›ÌÍ÷Š¡qù͆J#»|93#Ö-åáÿ:ÖÉþ`]ÖfùP3ä˜Ï%Çaè¸ÂÔ—ˆl\°ÞmˆZjg´õÅBHì+‰¶FÆ­`¦l7 V RâBýï:ûQD5Õ1ÎvÞi×[UÎÍ©Üõȹár$nÔ)jvEZÔÇ“O9à·X©mjp6Ýjv\h‹Æo÷¬ªE‚Z}V:=<À•»*Xy]g¬Úoö EìS4 3Ý :rK\5)7éêÍÈOnï«vZ¡#Y¼šÖ'ú:ÝçÛ£Òfœ{Â3’4‘?X‘Øb,ËhÚªœ1Þ¾ê³á¬‰âe{¾27{ªÅ#òж|P Ó˜›¦œl!Ò—¨¶ z3K‚wÚ:#w/ìÔYN0ßeŠâÐ]j¹ÞÖÐ`’ÉYjYXÈu~@x…Q¤ ï§;'Ÿï”xr/G’Ìëþšµ®'ïf–iîÉÛîW2Õ‹üZ ^tiõfŽõ"ñÂÒ¸O`oaÝ뎹¼² K µ§vùfL>"±ÐK].›26Í^!Ôæ±Ü …XÏ!½ Yk•|a/?«WUš:c–U‡©„µ–ééV+Ž[Љ™ÒúRÒüõÄ46w«Ú­÷„|uÖJÞ)©ieb^‹Xi„-"á4ªîŽHIKvÄ`Ø/³§l¿ðk%ýoÚ‹DÆíŽà4@q?í‚«>¡Je uÑåÁFa‘Ë^µ-V{ÿ§ ãæO‘[?~ûR‡ß¿×Vbu˜“ôk©aU“ÏA)‹ÿvUœ‡8l<Žš#,&ÍÆ²,¨Î,4ö4¢õ¼!À½â£ñI ¶ß§ÄÕ™ﻕ!;vwô€æö$ž»÷5.CÛÃHêB ÎfFÜøñú®ÌÍ}±ƒ´æáQ¤_½ÓºFG³RCb'E0X>íX㡟4;Q}ûË\|¿T1Õ2âÒ@ÝÚŒgà¾ÑÈZIm»?[Ë*4o¤E Ñ@+êÍ([öÄ©%Žhxi YȬ"uú]LÕ"–1Tí  0tÔa"÷Óþ Ú»bŽ›O„‰¥”f¥ ‰msQôê§9†]TõoVLÛîܽ¯bÃ~—`Å胣¬PŽ@i‡¯±Ívà‡X‰îܶù•°¸Ò¾ˆcà:ߤP NÍz4ÛÀUri.G•ö¬2E®Èþ pÃÄZÅ4Ø…hµ§ r¡ñ߆Z:|ÕÆ ä­}4Ï¢Û(>ˆì4‰]X÷hX;€ì™ÿåøÖ‡(»´‚ºMÔÀ•Çz{Èáâï¦Ö“?º£øÄû­Ä±o!𺶯´À•ÂW`7ÅÙä.Yõ}gfÐ\t0NQmôy’¦ûš&DŽ{‰lûù»œ •º¿Íïè‚´Èãß“=Ú¥îþÆÅ07¡j­û7Hp> åwì°L¹÷šH°ë™q÷ϧæƒB཯›tY`ÄØ”“ÌÎ8nX $°¯€(Ùçzl ÏU™})º)Xwû4¯&[KiË¿òR“mMHýpç÷RS1j”TaL×ðï6 =ë‹[xÌVÝÈ ékÿœ2Xú¼ÊaÜÛ±’ð…¨ÃXŽ€ŽÒ¶ÝüœÔx\:šìÍ”Õû}‚ÊþWÛüŽˆäÿÅhT¤Y6»“Ù®¡o$–£YýÇêz1²ƒœKEÞ:ŠäÒ¸4(ïÞÆè¼%«Â‘™zò ­6üšô¨ê4ps2±½ªaØwÔÀ‚[Z³Fï0 MùT}Åjùî¢âÒFoq¿îý XzÈ{t ®rtDÙs^寋‡yÂ?ä’!CAq:…ÕŽ jeD­9§Ÿ"SÞŠm1ÑéGˆÜeøñ£4µZàVâI_å 2Ÿº oJ™ ã%XrÐ݈°¬dNñ/¾bØ©;¤ôÅ<²Ì=éþ # ˆ]ŠQŒ¶5"NÒ¸¯-GÃÖá¦k½àM:À#‚BéÌÙÀ‘ìÃîüsÎÒ_61Ú¦çd£ä¬›8Dò´Û‹H®r¡d%N•®¬¿rĘŠ`[ê…^3ÇÒfËê¶©Î~ìwÇ“3ìn:Ee ø¦j$ªVTiVüÆãK»Àé5F?BÏÅ‘_é IåYÆW'£Á@s†IÒÓpRšNs5‚–F[Gñ@ÓÝãG8#í ¹.Ô§°n‘TNV£bh@–)óÑòëî˜÷Ïý¦VôD]&¾÷ì¬ ¢eªäJæ,¤5-tà!Sì³~8|•À§Ùæ§ÉîµA>Uü^ÄÚ-âñÚñÒ[ýº×Å?8e·:)µQ¦¼ä©/ã|“D³¬Õ”hZ×1/(õ{·ŸR€,ÈÅÀJÌgwÔ Düœï%šûÎmcÄvJj&w§uÜ0Ør¥þF¾JºmÀ«ëÒþ<nU”p¥Bê­Á)ùÛbΔ;°ÓždÛ—ˆ/eLXe½âXÈ-±½\yûƒßaÐ3ýò©Gµ‚饸>+ ~¢S˜¾¢³Rf‘Ryñ÷Œ‹É¼‚Ū1[1…XO’KL« Ã'ÚªžòZ'Ž£î†ãº):Ïõ/£ô" 'V÷?R‹`#åÀåE©2Qh °*Zc“%%JÞ |ñLFå¾\ætÆMÆW K&óãN\˖矩fÔé•[¿,íà'oüø±¨ yqÎ’!ä("¦ŠÎ®þpߪ|²G^° Ùâ œ´ûâ¹åÞôþñfá«°ž¨bĹÕU<ï§X¾Ý©Rùë=•;ôþ5Y*æëå„§Ê'FÌ×(wl±é7)ÞÞ"€FåK¯ÝÇ Ž•ÖHœ€–f›Úͦ.™]NgzwÓ2Í€‰€48ËŸù&¤Ù¥G¨0ÝÞÈÑÂcÄð6\=b½ +†‰ñIÙ_NVŒ£Sûù§ ƒå¹5ª“QC<ê‚{?ó”Èäsíͪb~²¹Q]º=v‹G`9fy¥$êàYâ=t©¦t"u®Ìb¸òÐ'“côIç÷ª™Ñõ(¢ MËL½þ(^ö ÊëSd4Àó7ƺb€Ú9ÆLˆ ý,*å¤î¥êÇœ»¥‰dŸv†Ê:÷¸Ï±gq°uDÆcDf¤ÚUõ,ðO[‘ñÖÔQ å˜ÈÆ—Œo¯ª'Òýä-Íáš ©%“ßÕÐUDí'ñycxOâIV—Ìäq¬UäbÅ\Q*zNl4Ì ÈM?úÊ»²V˜Þ·¼íK4s(êWšª5ée̯æÕ"³Æãº|‰ßbþê•V®{‡²öK‚œ;m}Tgn,n˜r”UW‘¸P2¹€eÑZœÌŽ'ìÍ.ý¤0¤£!:$æ—,_èø Ü“qåöûß}Mˆ®ºsY³æ«/KÜаÍ— EñëIÙ噉öÌÑlzO¦P#ZöóªB‚ұΩ…Cƒ n-êd{“‘MûÑ`Mö1>,îAÀîSühCn‡ú1Hkêz½IæU›Ã|°»ì<c½vPDEø M¯éˆO@İà|7ôÏ«ž+ Sf¨óˆ;Ý){sÚ.?ÌïsMË õa×i4ØntÎ5¯Ãê"÷×ø©Ç,tØ3Š5u]:”Äúû»´Ÿ•:áÅBh€«@*i–£¤{ÌþŒ¹``ÒqQàJ¿b´%æöç&¹;ÌkÊF²‚€8•WNœ/Á¢M>8ÌdÓM&Lß΋¢¹Œid° iý±¬ZOžÌý•>wðãÛµ´|[ùžoëÞ1D}¶QpR¡OßqÒ³&íTOBçÉ&3»Éæ|⃠y“ÊÁ»”gÑ…fqر‘‹ðï¦_zœ‚681fÍ€oÕL|Q쿯d+LÄ_Z»ænpÚU»þŠh¬UÖš´S~ÄýÆ\W#*MžFK9¶dâQh-uÁ²&h),w´ûiÃÕh‘…fÚZ ’›k4\LYnïEÇê¯O:ú4r›°(Jà }ú%Ñ|wÁvÊ"¿k>g†§P…{Ìq dªÖ'rŽ1{«÷:U ‚;¹(‰™Boò}دÖ(ÖOm«“£*ïvú`÷9Ô$v¥ÜLšŸLkï¼wMpm2Dró-A8èXæ:Á»©ÞLLñXëýêÌmŽ(ÒÉ«ÂíQÔ°pfTû#ÕðµôÍSÞ«ŽæåoüeËô¼"»Þ4)rÞóàûðK\Yä,òSš×Sªû^y?ÕWzW@ßó«Ñú®"k>¦MÍâµÎŸx\üS‰†Ö6÷ üÓ¢‚såÖëŠÞÃdE¬'Ä”#rÉÏ€Ÿ,Ô#\cå¾’aîýƒ³™+¹‚Ͻ‹ŽÃÛç„ò=ŒÃ ÕݪŒÚ‹¢l—À·ÏÞw<%Ù#\ëÙ(­ubDø,‡ I®lòÆ»ÐË™6Ìð5¶¸%ÞZètvpˆø˜½åA %%åcdînëiØTïl$e’g}ƒ…d!èúbšíxsÉda¿¼¸º­Ø'¥BJy¥Æ†÷Àº#UÁ ¥N`L¾u PUB©ý æÃ¯©¿m\êF%-g™ªmÏê8)‡dê«5¦HlÐÀnÙàAuaö~òI×iñŽùk8%‚›îÚS®LЙ‡FÒyEC”µ iý[mûóá*ÕÅë¢Cëxtø_uÞÞ·)Yt®©3‹ž§†H I(┲žX%n׈FÃ*\ëZ5èЕ椆uÏn1_å¬1ø_Àø;ÁÜeÙOŽ´ Ú¯Öç¯ 1}JF¤î“%”&Õ‚‹røë~)ó ÇN¹Àì/Љm¾ñûx’k!­•¢@YìçLf„'vÂØ†ýJ.Çun[ÈÛ2‚ò“iäy¤¨P‡‚=u»+1“faTšq¿ªJ}üÀÄÔä탑, =W&ù6Aœ P[Á[E”ÌÛëo{ÙI¸¡…>9¹6NÁû²D¯ ¶™TO:Úñû×L‚+B¤‡ÂÆÇ21hÜ:~#KiûñC}ä´T°`Ÿ–m™£]ù¸Ñc¢©qäeÍ;ÖV[2ÕÖ[—N©¦œÙÝ]YÆ&³üXNotc÷fƒ™F2fa?5vá"àU¢8ÜùhDÁ/ry\¬ìĶè•91¼·5iç™{ËD!iq~Ù×kug*¾C52«Âx§è,Äjôža/¸µèè~ÂC`WŒ‡ù£ó"(æ}ª!ÕŠDèÂîÜrÌ'ø NšåsÞ³qÎËÒ¸EÊá tþŸ]#µ©ÏA3Î~µ3˜éYŸËôÑÉUo8ïc÷…\}ß9©ɹ™y.‰|.÷\e†‡efs2Ûb$èû¨r¬šŸü–d½èz;÷Xæu?Æcž‰áª ó^z$ž¼R˜x#§Û_Œå¥T9aÄEb0b&ƒL:õ”o;iÌv\ì¿NS€Ë±·.ÚIÎeƒJ;.¤µ$Õ×é¶yX%µ%4fµVÂOƒO…Qêøª»éö&AÔôAÉÙYfÓ$+ 8Ø?g+X†Wî¨pJ9YÔ'¶è}Ž…àÎ'X˜ æøÁûüÌK¯ØÛ`ëº)Þ±á-ˆ 3j¬ói#XœX¸éÚ€ÙF±:±ð“¡å®3\Ã(Çíõ1]µÆˆ§{C ‚„Y€z™‹| ®O0ò'¯åж òÝ6ïª>J )Zv Âs× y€ÓÙ„©Gy<õbº«äóf5ß:€&,À~ÿÒÄÁ[ÞËN‚‡úÊ|å©Äy¼Ã;£$O‹Ã\3Î|ŽÛŒ‡Ør¢„MÄׯ)är.âk¥ã¤W‡hŸ7&iaµhÍ®¸ÙZì|/ ïK?h”_°mïç/Ù‚²cï^ O§MùÅv€ß´Ör%’üŠë¬—0~SD>kdxÌM5,÷¼CÓÓÀQýCé »N¹°ÖÜwêä‰y¹µã«co³ª\ù¨›ðy¬õÂÍøUÃñ6MÀi3¤êë(9E‚}þB”Ò¯5l«d·j÷˜Ó× cÍʼž^ Ï ðÃEæN]¨Ì¾äïA_U<2úè±€ Ý7DÐpUII-ó®ºÈY;)Ñ“áÃÚ¿¢•XM’׳’cO:“nŸô@|çœjŸµEhÃ1G­GR‘t™bÉIÌ0wÿ{[,¤J»Ûé¾™¯Ûžƒ únòùl/G@î¼9C\ò1\³&ÔÉeÆj4èTUFœr(ˆ×hQãÕÓã›9l^½çÚªló˜¦Ûí§SæÒ¿Å>ŒZ²gë¢r^4ëþ(>»¥–¾.ô?÷ÝeHCýn³FÇiñËGh ÀjËÆüÖ¼R””¾£ù\cI´¼ô-Ü"I-D[òRÉF²ôõ þÛ'Ã+õÑ-U]½¦…²ŠÉ{7yi}à¤?:}hixÚ‰åYü§0Ñ©Û z«.1YÌÒ£«ÒÀ­–/û@¨jMÁSÀy&ÄŠ’³’ÐÒ$¶B ¢±Î‰í½·‹¾dè©@þÚ'S»$Ê3`/ Ä™IA“±Ð™…;>i¥:Âü»½‡J¾œ³+sv›sÎlØùþ±Mµ­ÁIb%Ž)^ io/òÊ…cùöì£üø†½UkÉï ©sþA‡ES×GÅ/CFŸPøßàþn›Û!6𸲷ӄWâžOÑ”ÎýÄ>‰ˆöÔD‡‹Ðùþ’1ó5éõ-‰]B%È gÂMÈí’¹¾‚#Gx0¯èÌɸícQÒD¾‡K“¸Ôqc$¾}S ¸^²y¯xIWBÛœÖýЫÓœþ-ØÅ•éܩÎé} ¼Æ? ‹_€ax’ËÈ+^¼_ ö¤ž-L/)›61² .-H°øI¿9Xì³63’#®ÇVA†Ý©žÂhkH]8ž|šŽU¶^%½AÇü“#®Ž"â83  ž'#øþ§¼,Ë3{Jô¡w´¡©¿üHÁSK‘ ×¢xËÚå÷ _‹œAøLÚñ å†í°ÞýL¥(¤h‰MëÂÂÂÕL"öäç3èr£v².YÆSzvþvþ|È0p½ª^ŸZx õ²–¯ø«¤@¸ù–=“V×Úï™Õcš`ˆX,½>†>À¥sÞ1+î•¯ÍÆµh$‡ØÍÄ>ü: ¿²NðÊ—uÜ.“²A(ÈNù „]9’ý®ˆªôÃdçÒí•“ œãÇ í‹ü_­v§ŽV2n:Lµ_¡~…7=>£ðíê§åì½ÍO˜‹ÿñ°Í38·¼ÇyT·œH¾}SG€‰7ÿùW¨ú ònpÜÔÚ[ªH70sN?“–KÈM¹Z£—Ûtl3`#›šxèÑpé´ûzSáË¥ubptÌ×¢G’ᄦ:ŽWD þà*°2“YÖ·‡Ö[*¸pÉé¤×Þ§úq¼)Ý1vì‘€6-¤s mi¾Vv)<ô¡Ô«a»Ñ‰Ç·’M0ä‘)ø8ÓæV¹ÈÇØ»MõãøÑ‹Ùâ18“hÒl¨âQmÒ³ë¤ØZk‰p´×·]„+dûÔ·ŠwÇ wR)užäðßšÂ(g¬¯ðZgÃv‘L?Âcê!N|/]´š2ïZ·LÓdÊy.ݶËX‘½ž[FæØÿÊÃ{K[–°LÏ`ãeæ#ñ!ÇK¡µþÉØ\î!ƒ$AVmé×AÍY¿” ¤¡ìùUñƒ6–ý[®;u€¾H)lÍšêï‰>•¡æ ês±p†]ƒüÐôцŸk·»”Z–€˜WŒÌ¯›+0Å$Ñ­À¥½Ymn»'1Ó—ú_Û¸>Ëc€ƒll>Ìs˜Läñ5úÑNO¿.Ë?bÅàüfüàu;p î"ªÆH±%X ÊçÝ!/w8Ç«IÕØê* ò½Ä%M6y¬€Á=ÓšŒ1SÔuظtÝ2°'`šZ­ dp÷Nûe¦%8µv8ÁZ‘°·þGwj=À±ì¾â•ý„IdÐVSˆs‚î¦Ï¨º=þ0&,J'ΉLÕ\ÐS4IôÛŒ²ž~Zy|Å88 ºÇFì!\âYï½!^Å}+¨m;’¿¸Açå+UJØõàbçöF\Ä;¡ ´ÂxŠ~Kv,§ŒAÖ¥ëYÄkÆ.ÎNßÎGú”©9Á›iØ}Åç¼mf['$XYÞhpÒ/¨Ð,à¸Ã^‹mD*µRF 'é³[—`5¨D}ï3ÐÙ—iC‹ }ă3i¿íàøå¶Š Øä6íà†‰#¤ü Bê/SkpõÊ]› ¬Òxv”@5#±ŸFÆõR‡[âœûìu¨p“;œ(XT¨=^tÖÉš^´MBP6´’³°dÂ'ÖJŽÇŸØd‚ON°ŽÄ.!f>»ø“bf÷§‡mìˆÈõ1rV‰%öܘˆ,˜²µW”,â4—V½:Ûa‚_6ó—~²Ÿó½vsW*¼ÏeaȲ¬.NÅTd ëØoCþ4§=¡È‚(óqëk•J¨:dën³Ka#Еµtd‚_§‹*Ïpr…ÌåèJ›Wöõâ\ (ý‚›í(Súu†hÛD;ˆØ¶„5Ëšü‰§{Eä°(œ†¶’6ù]ÈWjâ÷ïP4~U -púÆt-!ÿÀu®E ؃¢[í­û¿a&Þ|¨n s4^~3ŒÑÏÅ;ÈÀ–½Aß„{*W’èžØD^õQÙ‹mq)HÔfqRaê8d‘£Ÿ:ÿp @ŠÑ«°`SÁÅ# “ÆÇ[| 0\5¤É Ónù9¾ÖÝòHú¸šºÌyÁä ¤9e°1V°eƇwÍ{ÿA&7¯Ê6›hmŽ}ƒ¦Þ õàKP6•±xÚ-¦‰–þXIt¢®¶ò˜£âf4Ì£ˆß©Ö—ÚuËpódg¬]?½×¢êL–>Õ¯gäës{ùߎôàÓíú ±qgµ_‘ÔIùŠ©FJ½ùë §sÌgíô¹NC&Р·>ãIp–+7í‚æVŽÚÛ7Á%¤ëažâ²ï1$ŸI¹·ÿ ÖøðÙú™¥ðÀb/Û¬ƒ#Sý/aðF¸eÁEþÓ­zÍÐn)ܰþˆ Æ%4,¡d^k“O¨[UwªPÍÅTî0¢ù±…dïD£Úi¨Ì‹‡ºhÓ<±þ+kt{6Q®VJСȱ›é‚üƒFDzC5‡¯¬zÃ<ÀAÿ ÙêÍ@ŸègVß]‚J¹èÍö÷<ôLT„'_LÐÆûÍÊÿ.˜k endstream endobj 266 0 obj << /Type /FontDescriptor /FontName /QRWWCH+NimbusSanL-Regu /Flags 4 /FontBBox [-174 -285 1001 953] /Ascent 712 /CapHeight 712 /Descent -213 /ItalicAngle 0 /StemV 85 /XHeight 523 /CharSet (/A/B/C/D/E/F/I/L/M/O/P/R/S/T/U/V/W/Z/a/b/c/colon/d/e/eight/emdash/f/fi/five/four/g/h/hyphen/i/j/k/l/m/n/nine/o/one/p/period/r/s/seven/six/slash/t/three/two/u/v/w/x/y/z/zero) /FontFile 265 0 R >> endobj 267 0 obj << /Length1 1199 /Length2 3254 /Length3 0 /Length 4016 /Filter /FlateDecode >> stream xÚmSy<”í÷F“-âÊPYfclÙŠ¦ûcæÁ0fƘ±fIölÉRvQö’l©ŠTöDoÊš%²–¾£ÞÞ~o¿>ÏÏ}Ÿëœëœss‹‹"ѲÚ8’ O"Reár05°)ÞÃ‰æ…ÆÏÈ¢šCÓ$.n†§€?ºÐA] €¡âID= •îcæJ›`(`ySƒ©¨)Âèg˜ÂOGE Œ¤à=Hþ`$@(<‘é‘°4€HEÓÈdÀ¡/‚¼ÔÀÎô ÿœ¬K"ûQð.®T0Äe)%--óËWUU;ùýDÀz€Þ…– ¼‰¼›Na ½pÜ®/Òs ‡§î¶ †¸R©d5(”ìŒè69/g9"@…JÑ‹=EÄé’Ä€?BÎx"î{{8jNÄ{Ò#½è&Ð/› @#`ò0U˜ ð¾XWènj3?2ð„ïš1D\`™D;c^@ Þ ÿ@^oL¥Ð€À€ÿ ü÷‚ÃÁ8<– v\è#ùÅN7Î?î&*ï ¶…ÉÁ`p0l÷û÷dO.ŽD$øýr7Åx`(JWÿ” JúOýÿë©£C¢ÓÊ•UÀ²ò*Š`8œÞ§*BáwÖõø©Åw+ƒÿ§VØ/J#¢3 ¬ú£%º–?Ûò(^ô]C¾¯¶ø¿ü¦$* €!¿ÖȆ€Ñ7ˆþƒÿq½þƒÿqÉ~Ï¡O#¾+ù! ˜®‰ø xWýáì*ƒÇþ¿(Œžà÷‡¸ß-âºßáìÚD–…Ëÿ0â½ôñ¾‰§b]ìÎO¹qß(€$yáwŸ8=ÿ 3sÅc݉€—}(ß!€ˆû-é)"–„Ã]Àh*}E1Ü¿†]K£Pè:}Ÿ=öçÝO/|,hx€„Us« kÚ¨Òò‘}ߣð@É|íMDG`|'ÇÔn*à^,¤˜÷Y%%KÙíªYa»ÇdcD¢Ð©8ewUf¹}<¼áãAGø‚oØLM{že:Rš_Îvvz@J•+tá¬!!èœË«Î=˜[hdÚ~ãÖÛ}›’Æ6ûoϯó–7ó8ö<Ôá@¿Š0Qk–œ„¬‹•ÕZžµÊo¹–ÜsÞªê$ç#ŒìÉ£2½µ–¸ÅÐéë«hSó—¬.–е–,´å=«—‡¿—82´ÝÙÆÁ3Üܶ—>P•o›â VF"6tõ'´·tÒääÑi•7ín dYMËañ<¢å&šõTÿI“c^ AwиíÁ¨“e2_¼³:lÂ.þ™­Õ}pV oLeYÒ4‚÷ƒ:ʤ±žBœá·º?|–4ظ(%ÔF²Dsàĺkê®·=I÷ÿ$ж³Ÿw zÎVÎQj·ß|í’E{øÇ!Û ¡DFËãóìJI) Ášü£ì²,÷ få*@]÷+”gY§B›±-ýÞ˜ˆ¼‡í"ÈýÊ:ï/ÌݰP^Bqúf@U»÷º 4SÚ¼›¼Ùl?ž]ÑÌ~¡wYI_¢Ô<&íªkÕ²‘},Ò´èößW¦.~#'ÌÔg¶;ú%J/φÙuÌwzôF [xg56=¹ W[}à/ù;ŽVŠV3'ï¤hMÂöNë/?ÎìbæÿP§_öìðÞ‚[¡IÔm•ËÕlF " á9HúpØøÞ ÉOJŒOÅçòã‡rzö´(Z·¹·ÿÆ@W¤~î& 0 ”£ŸÕIpb™°u?)ùà–LÊ9ÊlÔäÍÀA?aùA¾”&YM~ÎÞíçnûòÄg·c;¹¥î[W‹®ùt–|}y]ñ‚*ôz­NOÎÒ¬rÈä!KñFœZò¤•¤Íj Èò[?|¢´`$߇–° ÿm¹½žXWs Ž×43¥¦ßHäY½cWÀ#Á Öm“~)å<¨Æíx-D'r„­¯{¾ ¢á~sdt[Ô*Ê >ivè³Ð™î|<÷A}Âp6%ä½ére„»†˜þ²!ªü9Ò¤UõæùY˺H|Y¿%UâéqxÓ…g{ƒUþ\–+w šÝ÷˜¡‚cÿÝÛ{⦹"{"h“噂ŠÅê“΢1…i4²aƒ›‡l*ïûÇg°¿-F&pÅ[…•,]bT{$&ÐÅIv€TÐ>Ê´KïmHoÔÃ2/”´‡ŸÙì„°_›m<”¦^ŸºP'\R3Ò‹‰]ë—h^ÈŽr~d×jðEçgªcœ‚ºUûƒãÙlsÂåªÌPø§9,˜~ôÆÁs¼¾sµ{ªœƒ÷„NJÆçºDìÙ°ã‚ñVT×”ŒÕ(6Þ¯AÕTóyIY¿ýFÈ,¾:®yÁ«ŠËØÍÉ'êaϽñjî¢ü¼mÿötÜÍšgYžEõÂ!'ç§š¾T™x·ŸiÑÔz÷¸cZÈlœð‘¦•¼c+쨗㬯×û’!>Ù{8õ}Ä1 “Ò²˜ v®Òž¯\(DŠ23¾òÉ.vEÝ]1çýtÜsÈ£ªí[hÔÄ:Ÿ3^ý<„X÷y‡`_5ô¿s hÚæéE¦…FëÍŒ‹Ô¡‚w¨›ÊÁ„ö+õçcÙÞÞq^«;bíp AŠ#;ú H$õQ\sÞ [eŸ™G¸aõÅ^'Ýå3Ÿ K§¥7Àt»c'… L…ì®:L?Ì}}ùê¢\ˆøbt/òâÖc¯EÜí‘V…óW\íýããÁ 5Û'QóÀ\ó<|ió­Ôëšo‡ž…•÷fçº×M™à²ÇÝTe¬ÖüÆíolMcíq†øÏê‡)¬:϶?w=U{%<èÔ&nNi ¤¦s~“ðeÜ|‰ÉÀ~åŒñæßòîË#Ù]¥Å_œ†к׳[ƒÊäk†zY=6¶æba¢›î=+a¼{‹æX/=ÈÇ›¸[ ?o Rº>••ørâzïëÌ ÿWöÛ†ÁSÎ;o.¼ç|¦ß Ú¤,óãîR5ýë[8K¹Õ#ÃüÂÛK§‰Žýe¥ê ~}¥°äÉ„a. ÏYDï­4ìˆæ±%Εo_Ö`µÊ‡òøŠ”Åç]ª*KSýN'¬£_H×gõv¤¬™ Ì$%jî¯' µÃ>¤/¦ e‹X¿p?uØ›}gž%:îp–î†j+‡Qäq“¶¾4oØïüÜ n‡fre¶«2¥ëL‹òëëv¥U÷Êm$*QrÛ¨HýXž›@NSR)Ò½ uìu’p·°&ꤷ»”“êSÝ´$VwôuqànEÿëCv)÷œî€ÝP³ap®®á3ݤyàk¥©†É嵿3jQÜyýŽ+‰‹³¢Þ„h¿L¹6÷wgö‰##BhÏ3J«ŒUì{z`{²ï2ÔŸ¶Á_Îm½õÅ1ë½@jåÐG¶$Ã<ˆú¨NB+ßçÚª1c8Ãóc§XZ>ɳ`9kÑä/ššŸæ|Õ‹b›‡XM>PHµa8¢GpjÛ²ñĹþ5£/’l1+*ƒÕ?zA”I[ŠÅè‹ì¨MKƒ [~û²èùzùÊÛí.Ô»›1wa;ÇS ŽSd¦Ö­|­'ó{ÿOž ¯‚5(6Ÿ_^IJ#·"WS}ÇDB¡×3^@Ó@[.ç·B/òMž­Œ_9±œJu@ðT·”Zæ%*F—ßEeKßÐÜœ w%2ž?ÛqZI™†æâ­Ãu=ÈE¼3Âø=ñvz!“ž§¿Væl~ÐÉMx«‹I_i†$w–êµãluìÇN@îÖ¶•ëi¥$s˜°) ˆš£ð‚dG•·f¤r>±Óµäz Sö\#~‹Ö’ˆc„¨¾wÐ9+rDþZÞ¤d?™/™u)¥™’¢Øàs½GBØœ]m|ŽZ*{z+î }|ÿLlc½½ýd¼0Õ7ppLU5Æ1îÍà8én%1ö!S…øäà° sc¡øíMÄ øJš[ÉŽ÷kÜ"÷µ·ù-Õ6ç;ë’è’®Œ‰ˆ §Íp£¬¦²5^a3E µ)‡°»YA´Šúæ±_DÙo?ú´U­kp¡3bY¹í+cìQî¼´Ž "DFûø–Hï¬ÖnÄÕ6ÊYsÞ¢¸#R¶Íºj õ².,‹bÖH*CT^®|+zâØycæeX»2=FÿZ à;Ngæ/Hu®ç•Â|#%CTFF|%‡žŽ/•Œûß8÷:°…¯ ߘÆ~j¼Ó³|.ú›X d” Ô·ÂxFTÄ¡+ǦD“ß«:‚®ùP³®‰Ÿø®\v 99ÎønÉ{"²†ÙxÂÙ.Z–±$\äÐ;£úþo×[øÑ¡ÐŒbÏKf“rÞ cMQc4~>ªú_öWÝ‹+ê=,G³Ç—NUcWÂï$Ø ÔïšIáQÎ~3Í|W´ˆµ7RWP7 ©ÆsüKGÎ4ÎÎu…O‹÷¨Œqïšû ‚c5ƒ’yì±.ùþÀÃP)Öy©¿zËÊ º¢íj{‹·×µ@‰}Üm|¥âEËnìÿ\9Ùµ endstream endobj 268 0 obj << /Type /FontDescriptor /FontName /RCFEMR+NimbusSanL-ReguItal /Flags 4 /FontBBox [-178 -284 1108 953] /Ascent 712 /CapHeight 712 /Descent -213 /ItalicAngle -12 /StemV 88 /XHeight 523 /CharSet (/R/a/e/eight/l/period/s/two/zero) /FontFile 267 0 R >> endobj 269 0 obj << /Length1 1626 /Length2 13716 /Length3 0 /Length 14562 /Filter /FlateDecode >> stream xÚ­wcteí–nl;íØvÅFÅF…;;ªØ¶Š“ªTlÛ¶­ŠmUÌ›ï;Ý}zœÛ÷Oßóc±ÞÉgÎg¾s¬EIª¤Ê(bbk ’´µqbdebù P°°6vvT±µV°å•c”™X>䜔”b #' [q#'Ðg€&È ØØ¬¼¼¼”1[;w 3s'ºŠ&-==Ã?%™ŒÝÿSóáéhaf úxqYÙÚYƒlœ>Bü¯UA €“9`jaˆ)*iË(Hh¤ÔR ƒ‘@ÉÙØÊ³‚lA´S[€Õ? ­‰Å_¥92}Äqí@@‹7d÷—Š`r°¶ptüxX8ÌŒlœ>zàd °°Z9›üàCnjû7 ;Û ëÝG0%[G'G ƒ…à#«’¸ä?p:™9ý•ÛÑâC °5ý°4±:ÿUÒߺ0Z'# G€ÈÍé¯\Æ €‰…£•‘ûGî`vÃpv´°1û'€ÈÌÈÁÄ äèøæ#ö_Ýùg€ÿV½‘•ûßÞ¶[ý 'G•)+ÛGN ÓGn3 æ¿fEÆÆÔÀÊò¹‰³Ýê\@7ˆæ¯™¡ýadbkcå0™"0+Ø:}¤ÐüïXfú÷‘üo øßBð¿…Þÿ?rÿ•£ÿv‰ÿï󿆖t¶²R0²þ€ìÀÇ’1²|ì€à¯Eãlý¹Y[X¹ÿ¿œþÕZô´¢¶V&ÿª“q2úh‰ˆÙ-,L,ÿZ8JZ¸L”,œ€æS#«~ý-W·19XYØ€>xý»¥FV–Ñ©™[¿ÙüEç?T “…ÿAÕßà™¥DT”Ô5éÿ‡åú·¡ÒÇ8©¹Û}`ûRämMþëðWQQ[7€'#+€‘ƒõãî}âåbñþRþˆõŸgy#' 7€îGÝ,¬WÿÏ?OzÿFÂhkòר¨:Ù˜|LÚ þR>þûòTýŸç¿gr–l|A–©iNU¸¿ÆÄu{ºX!‚í kÕòrü*l;}SÃ6yK _*ƒ™ê&>¿5»ÏÛ½îÊÒí uáXQw&ƒÎ³ ½Éi»sÐרZ¹é÷˜õ ‘ÓN4£Ç."³Ø[­ÉFU‡ÎGP`˜±t•²!!ƒ‚%â{#uf®G~že)5²1Ï(Ôf†7·Äƒübm¶Ñ“éÌ2–ñþBuF¯¡°@–of0ªÅn¨7ð“…rãØˆ¢IñQ K ÏÆ‰iÂ_ÑU:™;]âÀ¦qæÏ©äÀmv¶(Ððn“ÄÇöuN1äîúÓå½½•‰ÇÒ_eLáÊ×ȵ'#å u§±£6M,–Ûm¬SŒOÿÈòSXÄ¡ cTf^©jiŠ¥…"ž ¼áÇ’cZ…—{,ÆÅ`@²¥fØèlë"‰°Á¯pÌ­Ö/µ¯ï™$Cщ_´(iyÝ‘2»’ªU:W9ê:~$$íÐŒ}Ã+h îÛõl(ê†~/<³¯Ý»ú„²Æf‹{»Üг³&µÞ0»Ýªm¨±‘LT3ë^c(ß}î—T玸C„PÇöîã€&?ß,p…z#ºúØ.þFOvT@Ää@ûVC¦zN» £óÔ¦ŠROßÿr5s“Kƒ–û{b ãŹªg„Oö9Ý×|¢üÜSÉM¼^_îø'«»sÆ ÒÃm}4fâ„L;݆±áI:v>×n`£„t„NÀ0a´ÛWHó'ðºu§¶¦ Ä’ä„j.»NòØÏûS«Îöúæ@…î¯P¾«u›cie€U&eÿ'ÒL™ž›™¡S¿å­ÐÇ--Ê["žHá÷(y…ž µúÌÆ,ìŠn ¯ë;$‚kØEd˜¸g„ßÄÞ\58³n}çIŒû´rEM` 4pÚ6}-ÀìðE€Cé™—žœ“Ú¼/—]6–æžRõÞ‰¾L ™¹6Á²û‚è«Ô¢™W¹n@Zää!ÌÉšG2&“½?ş½¡¦âµÎ[ Ó6œSÜÔh*[AÚä1Zü¬òÔ}üpäÍm êÌáš0‘áÏõIçIŠÐQ“œ9 fiLúš®—Ò³¸¤fñslð2©Õâm¶ý&-JÕÈaÉöÜ&Ú?–€bâk¦¦fü„%ÎgƒèBÀ$É.Ijl×ñΈÂVOQSr–*.¯Kœ)‘'¿ÕP§ :Y.õWɬ,aâ•^O~,I- QCíZ¥Zú)T<Æõ²^×R+gûu{Ì ÑÞju}Á`Z¸ΌӶkásä“õÔŸ›«8ë~œÕ¬MÅâºO ¿ FP°d¯1ynµ‡ Ý¿OªŸ-q.,&ü:bÝj…}e¬+õ=÷ìö¨v;x÷Œ9\¶–ä§aë^ JªUþwÕ P*ä™` ¤ô¼Í½³`>ïoíÚ'þÔ¬´ÅÌ.}6'Ia›” ~w9–4½rŽÙÕÌêK·~°5±GšÊÓÖŠ›ÐŒX#œiÀ -¸:œ*Y\ý;`„®T£Oª>š_ÖµÊe2cÅeóPÑ@˜p+†û€ñ ¡Ìõ ‹ÆŠÏ|® 2ù”Ë&±ŠFødb)ev÷Žv•Së`ãw:‚¼6B°ºxš¨§{œ;@Ë!Z»uThœ­Ÿã’Ú V¾¬’¢1oeƦˆ æ—œú œ ãᎄ( bÝÀÒ7Y¾é&Ä¿»]}Žo7}MM®w®áñm°EU™ë±V,9\ôƒ{ `üþrHKFdΖ«V€uw¼¡à§¼¼=,ÝÁ ‰s®Ç÷p>ÕX¹™møÈ{»|=`¬@Ò¨T8“»q²ö".¹B¦3N>¦Á#E÷ª_Cà…ßlÇ2AŸñµßÆË…¨{Êa¼îgIz¡áo‘"Í.5gU¯â´ÈƒŸ­ƒ[Žá-¨¦Šø]#U­T ™›È7rY@£ìÏ!F*Z ¢[Ù}s›˜lï³…lQ÷EU޶3çUÄšY‰"D›"«££GS à—rÃË'C)T•9:÷D²ý4eµänѹ ½êsÓèƒérLjR{¶EÁˆ:Úˆ• N õ›Ì“yÐ[5µ?‹iÿd,4K%;ÇufëÍé“ÏÏ¥½åФg¸¡Ä–ëΪb4ºYžïQW¯o C¹m«g±%|‰s¬ªXñÖ|™ý‚¾9¶0¨Cá”!¢!­ æŸr! Ž/q‚ ªÊeÜü¶âé}ÓðŽö±Þ d½´O3§¾L¹Ê“$òÜ.í,‡Ã.l¼ÏŸ¿âeÆÕ‡ð×çzvû œ{–yä' º/.TÑÅZv5…æóp]â´`U­–Møƒñˆ³¸kvNuøiùxÛÙW&kaÅHï7ÚO^ÊQzx|»ÌÁëdz‹×[”ãÏîÝ_ã°þºVØ?Å(œÉJkOœ¯6z̪óHŒQ…ûV–»{/wvy2ôRµ^™¥z7Úf·Ÿ¿„Ø#RÇUÝ”ù³²~ Øe•Î~ÄÁÓÖiXŒÖ­é%ÈuˆpFo¿&¾, sƘ_ÿ2L½HÚ ÊGSütüpÒ6'kxÝ’>Ñð j†óá^“(©øñ²úÓKFeAç#p"‰;P´¿¾  ã¥<Ù½¡|Ôømk1¢«t[7­ §”tÖ<‘Hi]œõ·¯xXi™®æºÎecDɳñ« d!×Í=J?juÌq.MO”¸Úê‚p©4èj‡Œo:y£[Àp’…W”Yã{Ï“¶ŠÒk˜aEõ y–¾ËÚwÞÚ'áªF—]Ÿè\Ðo]ú‚Î §¼¼Ò±t¬z5)ÙUþˆ^Ùk<é»~XÄ1N·…ÝžstZ»#ÚšÙoŒÊ´¬/£™Ä<ã#ð¿ÔÀfÖÔV–LéZºœBE¸iÔÅΰB&í”gû–GS-Ikž›‘szÚ£ßs“`>šÀN4‰©ešmón9Ý‹@TèƒËY aÌk;´Æ²7%ÙÝUÉè—‡%í¹àÐ$–숭~Ú…oV”ê¨Ia–?£ !'V÷^ «ù–»°Ä4™Y#‹Ÿ–¯þPc ØwVYœ‚_¿Áw ·%Ë»YƒIõ¶ÁíO¾D0§ ½'÷”ä/+•ïm1¦z2 ð%¨rþ©RùÙ62Oa!÷ù«¯EĵèUúº]ææ &·Ÿæ AÊbÓ7CÚ4Ž•¸i21̈ó|aS¦+¶éÅt1qœió ¤OWó–r³ƒõ;Ø7p‘*Ås HXtKžƒš™ê ž‚ §VnÕ1°¯Çï;%¢®Y\¶wÎR¸àÌàh ° *Õ¸óÞ%‰jf^çþãçx_C‡{W@Ù6CZ`Ǹk ÐÉ…—Y@¿*ý—øŒ K´T"Ž“2¸iöÚê7^ö¯A3U{B´Ÿ|ç ÍTµ«ú{\žÂçY–‰«~rº:åO Žñ}>UÙ^s:5ש×Eteñã†mö„9í8@³ 5›:Ð"õ/»Ñú>ðFã5ˆj{Ò|–߉ÂÍI5©^h5ì›çûº¸ž³pjÎcˆ¡@Ȧœ€û¬µM=u*{¾Éq4”«¡þ§¯öK5‰qªñÂìj ºC–‚¸ͧkCïÎ×§ö—=vlå[“>ј™E–hvZ·ØJŠKKuKtC“MŽ3ÁH¹JúI§Ò7±Úî„–áé—@»ã¾ýEÄÒ=ŒçìžWªÀõÊF2äS%Á%ÆÅ®ëŸœj_öÅ`Í“.ÚÙOž25ÊÄœ»pQTÇѤCe°ÂîÀ ŠÖȆÞSã;LŽÓÆá³lÅ‘»8>6bœ¢"JÿØo›g^j✪[±ïE‹ýx 7õ–´MÈVövÁ?\èݼ"õ'ü•ûÎh]jìD” .žFî÷;X})oÌóÏ®G¸ÝXü0!©-±Ò:k¥R|)<×AÛ®›2DPÓC£É؇“ªƒ%žÇVYOJÔYøÈ¦ƒŸ|™Ùb0k½Kg5¦¡òPÔ¢yÄöÀšÛ|˜ƒ‘z‘^ä¸hËkÍ}Õ(ÆIIŒ36ƒ\ñNó×eÇ"Ýž['Uõû%‹Æ3‰ôÆû/y¡³!,Š{ü i‰å]"Ëݶ(ìù³cY²Ÿšx“D~ò‚}·-‚ó0âß—ÔP[éñÑaqèkަb7áC‘áþJX³V%Ãú#À´@™_XJ¦È´ÞÍoêR0Ÿ6˜bw‡öÙsÊVÑ7íG9úbZ?Aº"0üôõÖáÉÓŸUä3©A FßnXùÁÐ-öWœ\l‹µ‡ª¡×ä-¾ÙèaÄ&jó8+‘“éÌJ úÎ/›…t•"7³rkJ[ci[¬îS§NWri›oÔ?ð¹hW–¨õ¿cçé|yð[ O8H‘ð¼÷ òûjC¯NH£º2ðž±Ê$eõ‹šÎ½H/IOinnXz¢n<6©2´=ýÏ’Â%'Od¦ÔCr"@¡8Ö9 f®%§?ËíäJSBÄ:^@˜åiâè E¯¾1\·Ø-„¼ò¸Ûâ»O(T¨°!H}%‰V’˜eš/w° Y§{¯Óⸯ6>•ðo€Œœ =ÆJνcÌj‡å‹*°iÖ9féZ^1Ûøvþ_°¾G¼0­$èÊ7T¥»¤X·7 ûÁú™-èÇKØRžr?àJÙ‚;©¯ÝÈ>.•Ÿïjá› 0fªIá¿8CK€Éd°2rVSÃwwX:˜°g–I4¸²Z‡¸Õ+Ø|Fã´7K}_Ù›ègz—Gðù±Þ˜Q¬eY ,ÜÍݼpW$Eñ]2•¾…ž¤?c,gÎàüQÇLßÝL}ÎIIˆàOµ™f'ÏÃp¶ P~H[ÂpS"9mŒ¿ €‰sÙ¥p9!T ƒÉö³æÏ9aú3WPçÀÉ·où½Ì+;^`hsˆòškÙIäÈ“(t¤¦5©¶‹öª0°¯iž¼ÙEÙa~ˆ[é3Òt+$ÃåZ±ïb¯ Þ÷(i5e· 8(ŰÙ1F÷o# ðBôläãdŠžŽäN“ ±ÃÊ)g©ÜkŠEŸÀÞ)¶G6é1¤SvÚs \œôžnNw]‡åÁ í:l¨éQAx>~ ’ú̙މ Õ©l‰b9ÝtÐ5øEDlè&ó0#› Ê¿A}v(o‘iµ\:ŸD#l4ŵu6åWÁÜs¬˜7>à b`³ŒZÄCOÁ²Š©FSPÄÂÈÁÁwhä•Q°€à¦\¾ðzU§:wF·*ß)]pŸ1UÞ‚β¥Àîvèaw‚:‚³š%8¡qû”|qª#gTc9'šã+à æ2Í-~Lhz¨ô\<ãžZÎ0U÷mWb±Î»…°Çó)x‘g xÒ̦1dbÐÄÚ—K‹U7t¢'¸ö´;»Ž×1gêŒoN7&!¬~Î[…Ú¶çQ·#¥@Œ‚Ål‘õÇ´©5Þ4Á¼Žåf+£b"OOy|>¥®hnWRGdEmà÷}ö Bn3£óç·òבnámnŠÖ€¼ðoe´zXŒàÝŸâ˪BÕ˜‘ûÙØö鯸M°çB´û?qA³{YXÉ…6T iÚMVÖ9Ú<¡¤tÄ6{¯m±`§4ZöP怗¦Î’‹æ†ñ-Ùì£BRwãcÍ[*‹ÖìíDÓÐ¥›e|'Á¹v€ð°<¢þ|\‹D êášÍ®zv ‹=\dìߢÛÄ”ÌkRÖç½³Au„Ô3pQÏ:XMJiõ2¥ŒÛ*vMiׯÈò‹èè“Ѭ›‡Suf˜„F[Y'_H­³mâ†:U¤ùò™tO°+ÅR9T³èåàwÑw0f²„Œ¼ bd–±ìëÙ?{CžÆ³¢ :¿»;I´ü×1|DîÒXBÚëŠ\Å"{*lF¬/_Çï+ÿà:¿±U·ON;ïáò ”p(K¯½IÉÙžÙ%)C×ÌR š’UullûÔ ö1žîŠëÛ‹&Ú‡@ùx\³’­(Šçï½Ãù¿ë™ÚCe!g·%x… õºJây|QL].ƒoa…\þBfÉg9 >åy„ pú£;l‹Ê55%±D@a÷äj#Ò:IQŠšˆÛ8s¾·©ë@ó÷÷ZOùŃÏBzõí¦î®Ð ž¼?Òì»p˜®óî…×T7Êè\ÊÖwä5ä«óÖ~ÊI6ÿ1¹Ø%;Lº÷ÄBRÀ¶˜1|Z@ó÷qæPWÚ¨6Û['U¹8(,–ÂaÉñvñ«åt°¨ h/­ç uÙÇøv.û븠·÷óz«Ú.ÉÚµÑ]¹ê7\-¥K=>~E™…†A¼Œ Ï,)ù¸vi3è†j ¿s»j oš‹þ뤿$M3|Ø ‚ßÅÚK¡äŒ)wÁ‰fðàÊÑ”¢ãS?¯/2Ûïkd¡¾ëËÎjé-[Ø”µ1xd>j °bZ)ºêýçPRðΆƒžmÁF¼ÒAÌq¿L<Ñˇ—"õõ×Ô —€÷ë"’™37Cëwöº’@f¿o8%uf‚–zññ6=BTj ÊöRIÿòáÓIi1Šú”N`›ÆŸwÖ‹¶?^ðˆ0“£‘XbôÜi\ßI‹FýðæúÞY–êý®ýϨÍrøÕùâ—3zHw…–ßñøÂóïhH´Ö<ÛkS4Jåføp'›gÞ0÷¾ ±kZ&õP1Ux^'R_稿õ’Δ†+o³¸šóÛC^/÷ÓS‰£ÿV‘ žu&Úl+å”H2/hj›=° ÑAX%¼ª¼œ9ž?v¼T@â N;mWò»ýÔ(ˆãY ›KlÌC1´á›óHó³>HÌ©ƒãÕ–Fȼº‰yisqBñ‹•ORùŸÏ¿á‰\i÷ê€ÿ/ìAž4 1mOmÔ_г3=¹¹·Lû ÄÓé¹c…[%Ñ$Û¢ÛÒn69ó¤Ÿ s™š¾+ÐyDŠV­Ú™»ÕÖwk>ÒL¿Ëï>sMãq*rú‹p Û ö®M­J0ôåÈrÃÝ€ûɳqUìIÁ½ý¼•F|…q;H.MéãÂz§Prj;^=YDuà§7dFR^6Å"@Dݶ!a” »ÃÎüÝe¾ËHÎ(Ÿ); ‚/÷•pï0ضç ~«ùœú j¡ÀÞ¢d¡ßÞ±â§àŽ]!#¾;Ö¤|7þÆÑÄѯ|BúŸ¸ñ$¬üïD[( 3*eý;cÝCGO½Õaâ.F"ÓbãŸ2W§]<’â§êKysÍ=Vqzì¶%%Ͼc,=3êšB’8WïÃê—(Š3p@7åÇh9©8a¸œªØfªÝzl³„‡ß­À,cýOªÞ[¨øÎ©²`S*Ú\àáÃ9ÃŽèôædaº¾¾›%B$Þk6#ûÜ ]Þ=yB%½ßŸ+²ì*i¡5ãu¿áR¾ÛºéC ÙrL›ãFœë¹Å>,ÕûÓò—F-QbƒM–ci³oßí!sú ÒçŽsœÚRžœíà2>¸PëC8][ÒwÛã<ÿË<±~ßÁÛKÓrvÀp£òa¹{É£žÉS5F®®+,ðŸ×š=>¶‰¾5wÒ•ž>šfi—xpýlïôqXC#ñpw.",¯X²7H?q'×?'(HÝõù«g§´wµÛð›FBšS4lc¨Üy°–¿@8Ê*CK¯ã†½ÿßh ¸ÛEb<Ñ (¶‰gá ûv„'$NÖÏáû=ôÀç…xÑ”Y”CLg€ð 澂Ÿ”q‚Q0aá§œÏ^µ¼Ý‘¨îõZY¢_Æ‹ßx‹ñCu2>á™é–ÖÙJØ}îq7´Ë–»çöÂ!®N‘´/– †ËÈ>©Æ;âþoË(zXU>åD:ÒÕ×…œº 7øTȇnzo×”¿«6£‹ÒäeñRÝ®Qï§HM6ÑQü¿BJÍ“žsõ­R³ç)Ô×Ç50ë—Þ·–U"ýÎ07šó@œ %•àûi¹¹6íô¯n´ RjbØÐòѧÒL]fjr¢ †'“a›ÑG©‚‰7:ÍêðZlHˆ*WÏG×Anÿƒ¢QmrÓ/wAÉÊ¿ØÉ,¥t~Sg")\yލ7a+&DæŠçG/b¸‰`2D‹¸W.ÛÉ~'~&¬Àiq¦Â|sœžŸ#`íS)jüxÛgæ°ÛJ5Ÿ©÷2»•®¨gÇÍÚí£­‡bá{jõñMc}ͦµ=O1í˜s‘ú;ÓLº'=P.Ë`L'´ÜUÒ1ú'– þQ …b³¨lBf½ÉÏ3CçÓw­ç²žíÞ{œ±Sä§bØf«~DŽ!.ògíá”Eæµl§ÞÍGFªh`½vÔêòz%3Éê]7Ò²9Iz]L<÷äð{døc› úêãB‰lˆ2®W“jÒ“LÒɽj(<ýJm3àn“­ºi6à$æ”6€ƒŽþ†ªuV¨Ï¦]“ßCŠ;4¡8ÒuÜó°»KêM/©•n¸¹:imÏö9Åô&|}®uê‚ê •–QÌê›5æÕŒ©I2g°/®Ë2ØkN»>Aj“é«7Œ«ÜɪVz>‰di“p‘ÙÞ|Û…4}dÓ ±†Ë«·Þ7P@×% «’#`¨¬îÆ’ÊdMÛ>SQ g£`DSöÒá7¯£vTU€©8åk=Tá«CÜô¥¦_ ÓBx7M¦—ý’pBR´ÌƒW!ð ÁÕBÌØ~ˆjï=@Eç†"•&)¡ÎñDO·äºâ›Êõ:QzòÚìoœˆ}ßšƒòìÅòÌH\¾Ï?,îž—D=ZàîJšÍÿ&À­ÉÜÒ«ª# ´UU¤w €¬7áGàÿ, ÎëʦœìaÖµ?¡Åí´+•ÆòF’T»/ ƒq"rÝ¢«ï žúh?Ÿ»{–pi5•k‰érT/΃×Ín… §Çñîß Pc¥· ]ÛºkÆGüÞ× y\%3‰i6ÏúgK¾ðĨåR$+4¦|ûX¬F(à÷íI±ÑK¿}]Rm2uVXÐÙà0¹Äy0[·ÕC¡ÏÒ VÕM0i!uPf#$f5á!ù­°ó¨Ö$7¼'#Ýÿ´n/C™nxºb†©"€¦ï¦]Ö V¨±}o[ «R”ƒÝ•ÀEÁU6ÐMøGYƒ¯ ÖNYˆ9L Éõ•ØK gqJÄ®€·Ýøº6–°%ã²tÅ«M¤±ÔþWzê÷+%’cIvæ_°“R¡"AˆE(¥yŒÂ540ý¥F58Èœ-ÌæÆK .Ž]û±óE,ïÅð|:,‹ÖÄ”›âžÞç¬!×¶:´ŒåW+»Ïå«¿9ÔW2FäïfJ¼äeè蕾<#»ÉéÙì þSq^"¦mš‚q¢d5ž ŽDvΙ%üô6!’h¹·'-9rå÷ÚÆ#ñ¼RlŠ$@—Ät¹ãi”… ¤éJ§¸ª¹»´‘k³+ FraLUÞË…Þ UX±é ÄVYv4¦#< X]Ù m±[­/ݘY€šT?Tæ'DÎLVk™wK¬8Úê ÿ„¬?Ç7ÀçÜ|Ó°O¨s¦X>ê»IÀ‰è„Ù7Ðãû3ªšµ!ÓçZþ8 šž›Ã¼ˆhÓšîô,ö1KÀå!/wgògê ñ‚aè°P‰àé¸?N;î'‡Cýiee„BÒ—†,»6ÂGý “ã?¦=?ª‹^9ºŸ`À° ék 4‹DZV_µâèö"Úä¥ûã²c-ÈNXrky¥LÆu£Ô¿v°çý¾ß7yÖõ"ë›g›Ñ¡•÷­§sü\.dòMMweƒ—VXz †â¼oQšÈÿ;ä‘ÛçÏ^æHèßó)¤ˆf,`¦í3ëKúTéõ ´Ò€Ê_í{z>o ]Œ # á•ÈÝ[Kq}×€x=.¾§oq0ÜäÔË€o劀ŒfkÃ6ŸÂþ飚ûÞRC_ÿs¸Êæ°V/Çl°×½NÔŽµâ}¶¿Œæ±­¶’…‘d8Éòj ÕÓ團b¶.Ÿ‰”‹:ŒúïìRq Ñ@’ñÖV»ì›p  ïYW ýyƒ‡9—_ßÑ$bJ¥øfQ¿œØ#+Sª@8’ÏŽúÀÑΩw)çqå¡ÍÈ8·µ7©Ø¡äUf3¼+NÏßr~Û—Àìí{£=möõFtŽÚ÷¯Ïˆ¸Ò¬»k·£®Kÿöq%j·vº”DçnK6®rFÔŸ.õΰ–õ®€³ rqX⨚&-~¥{:¤ÞaeòØî&Ž„0z·JË¢Ýê¾(¨ÄEVŽðU^¨™áî©pëÎb‹,›ºŽòÌ~îÏw–wÐÒó®½ÍÃI„²mÒyX^²c:›„5 ¨3ü{Îg³_.ÍÛ´èsG”‘”CÍ›±BMh (ÄÓ7 úgÌ˧˜™ úl‘CÔeB¼"‹éêjþÅ‚¯cHõW¡^ £½Škr)•æ_][$tµë…˽¢o€C¿Æà"!Çõ¤¸tR?ɸ¯ü¤b+$RVU]{U ,Hß¿ñÒ3Æ•ÎpÛn,•6q¤ y,UBúÕDKÿp2å±?c2%*¢2¾w$z³0[c04öyþa^ŒI,·&Êt@/[½TPP…=0§ÊÔ'Ī웮b½P¸)ž¿\ƵªÇÝÀ}Àcd'6”¦ ìPÂ*œ45ºC€ˆ©íZ–]ËÇ®a8¹OÁž9nÕä×A°¡k9½ËÞ=¾ùJÓ÷b¡f*Ìd×ÓËè(•Õû¡»ªwËhÞàWòeeWpµjxôk¾ù$§˜»M”‰.¸lëð×ì„/"Óµãò>5$ k))ØêòØ4«‡¶ŽËËua•ÈÙ–u:š×Õk…:+‹-óïÙ §åÉ 3©æµîÔm°_ú=€ù¤ÇDîî›g’F}Ï gAÚ¹ÄÔfý1˜©·MÐ×ôsÅòWu’¤µ#]öG5SPiÒɉ¢”þt˜R«ã,ëXñF -ëTžÅ§?·q[dϘ‹JÈ;ºªH”,ºÕMïëtimýqõåÛ“ý§û4¹«Nƒ½>Wh ^åeà,’+Ì´i²)9¤¬î?N—E™ÕwÑ­}ï_cd?Õ.”}õÿ}Ðm¡_#áDS(‹2é` øALíú®Ož¦4 ؾ¸ôeã7¹Š´`nJãñŸíLÕ½Œ° $´ÜЈ–Ú8L í¢`\C“áxÝ›?5Á9›¾-Ïï&]Ütk¾çŠ\¿&E?P5sÀ\{qü  ErqyŸkˆUêÍY¿Ö¸‹¿¹‡D¾ªÕ@0›@j^r£¬·“%‰P…9·*î½Põë=PaðŒXN7…ÉÈ.ž®®Ó᥯:Лc1ùI‡±bK,I¤t;<™LWÉšzÕò—³*êR2gèx{C\íȆç&#]_S Üô¸¹ÒÊOp8ÀTœ¸_Ÿ~¤I’ÉÑép~¾V6b¿¡ä¶ç­ûBOQr;iô¥@n^jýG6ƒK¥ Æ kÒ|rNϤV.f² í!µ/VFwݶ'}íÖ“Ž:oÈ`õ:Ò‹Í¤Æ A«ŒCt«8±»ÆWÞCÓ´*ÇžY÷‡±,ŸyMú㙌%+Æ–“‹!³À‰çÅK[Ñmü­$ÿBôôžŽ€§Ø‘ª)Ž©¹,´Œ‘–á‰4(Ö¬^¥q»ü TË–ÿ¾FÓKš=eŽt° •0\l‡4V¿Ãj˜ênã’jèEP¡²»¡X鯴ŠŽî&çYŸï>!ažcÞ§/B°ºŒ1”5GÑÇ:\îŸÝ1³pÞ¬>«ˆsߟñò`)ÖKî›â5ðçM?²F,®öRþ\x1¼˜s™ÿBÓ†w>å­½ hëOH€¤“X·Þ•tX50ÇyÌÙ+×¼ú^—ýE3íµ‰ö¸PÞorÔ‘£ØOøŽa4ª%X8¯˜ñ*84ÛµL1@;­L§×`4Ÿéz£?éú;µËè)i’¹I›Ç{ë¼eÕfÞ$ðG!ñ VFqÄÅâ%ñÍrËU®T“Ëþ5OêÙEq˜avXTEû&Áðõ¶°Ï€oEÎAÈ‹ø!¼  È3­8Å aÀî’fÄ<-½k¼Ñ-¼“~Œ5S÷)©§£<,zû“-ºêÜéX£ ï»_óöDx–LjR%ôaø½”©K’ŸË½¾ÁÔ¬7nüAô©fìÏÖh¢òË ØPH!{M!Ÿ¤ú#Å*/ãYÜ©¢l'[ó•B„`b¬wÖºUƒPÒ'4Õð"èhÛYÖAþͼ}ÿ¼O2ÆëvHh|Éë$MehÛçþ­c ëj §ÂŽò#XÎÔ˜AÖÂèz•EãèWà{t3¡©CŸ¸å/ Ÿ*åt¯©„ROœ–é“2D’ÃÆÈžè®Ú7ƒR¢à¬Ï«y­(NlU³7Œ)s²bq_}¿ø¥ Ë}ö™Tg Ò7£¨$º›ÿp@‚’“æ6[Ô ›ÓŽú¤­½£­:ȸw©P¡Ô†?¨:Ýbÿì¤U:åwbÊ!@á—á”9ª…äYÊ ©…жGHÍÏÕÙCáY¦¯ 2»M­Oªÿâˆ#©w¢®ª¸’2]¹’,P’¦¿xÁçθº×xöˆÝˆæñ›“Üdakîtfì£í„;ëì‰jOy€ÚANªˆJ8HuŽ6Þ5غ_"õÒTäœäiÚl‘õ¦îëSQÕ"m0^lÇ"=ê0Šñt9‘´hÛ¶çâe™é%ã<ÛµlÉ…E¬Wözß&Ħ»qwPFúGÔè‚hõþùàQ«…ÍæS/>¥ÞSKÆ«ïSw¸‹œÀ ]66?¬"]k›~·ÿÎ#Ø›T ‘Y8 ëeŸíäuï>°Ñ˜*’ü3Zg°àî9¦™û3‰º£¢¨™I̾È:b$âQ;V8´†Û_jÁ–vÒ‡l¡Î¾âÒ®¾ÌføŠ÷•ƒéõœOz[ c_=ÚB&ŽNöüšþ|ê"‰²Š£—4Ä/OÐŽá‹nûÖ¹UÅ“ãÚH¶–‰þã¦î2x ££Í! ,AKBÎÏÒùýa±ð„%s©¬H6YÏp[[‰+?õ„~U¿†i±Íýþqu˜ýËÆ„ByõIb“ÆS!¤µ÷g["½ü"Ž%‘²ýxÌÒk,ð.zé(’?ýËKFƒzùà@D{ê×R™Ökɪ‚zá©_ê®ZÑ:«< €n–xî¶µd{ŒivŒQ&­®Fók,uœ§\¥—ŒhLy ²ÏÐóSƒ°>´Xòð‹ºÁã/S«ÉÀg¶o_ådVÙ&«~¿Øõ®,=kü) Ýì‘­•ŒêÌwDÑr±ÝeE²¥%„ó¸V˜ã!Ç Ö+/¹¥i¸ÀÂ@9"`¬tå?ˆ]¢&1 ó³Ç‚D"¾9'Á6jT LÚ+¶n³£5ê:|L¢S<úC3±íWe´^¸ívÛüÙ5÷·Óïä ¿Ämx™£j’ÙR©l[6ðá§bóŠÌêI”¿Ì$?ø¦Ë²dg\æNÝNûCvFg()ž&òÏý~‹i{mf¯â?â£-@‰‚ǃ' 4¦† ~UÌý£ò5„*¦9 Iœ_S·F™‰"¶ÖX®xϸ7¹M¢T”Š œÆ&- gwݹ»¼|çJÈ߯XÊb3¿_çZÒ—ù-| ÎNzÓ5—ùzŒ¯ý;2'P“ºéW9XÖCº|ˆ²,oôç*RèG²Ay˜oÔÏY†`hE$rŽ7 ç¬Fdc%ÜÖP¡•Ö ´?]K!ÖÓ窿¥£Cìœ//URíÚÒúI¿ò¡¾Ük¶ŽfÌsÈ\ÊSåïFF8Â`Ñ©5¥ùÝ+ÑédzHªÐ´õ9Íð-ʹ¡CuZ­&ê¶d«Çæ¬l',Ýæø£æ(Ž}¸‹´Žô øôÖeýf¡àQ]ÖØžCЮ6°4 U¼(àÚ¥Dîa7ëSçŽqœñ(iõ}óÇ [>þ²ÂF›^Ä[æ.(˜ìÙÃèÒ´iÎU$%[e¢rZþ°¬æ}U³½Ù× G½¬,é´‚^Лt¾ÛqÍ%:_¼(/ç/¥õc|Æão(z*½"óø|)I½¿ÍÅÊÁ"E‰ðk’%ÖŸcã1uLÉtlšQó:0,„â9Fïu—m+jØZæm4­Ïd¡i6<¹$ku.ߪ™TzÍŠö—–uzÑÝ0ÊØ1¥EIÇEñ_k NDOµßм¯  ¼vùv<„%ä21§ÇÃGÊL½\åkouýŸyà´Kß Ÿ?/×¥#ΈmôЇ ñãrzQ+ÐÈp~© ß~|õ;·]ûÀ¨oç,#¹—¶¬ïÏ<ý! J u-‰d'z9Þ%Åš’°ùg}_—Óó-0õXu†&Pq6ìÊ Zš%¾V‘#x£5‚r—÷ Ú㼉U[%@"kô'/Ö%Á*ÃÛxE +7Çc7äs05k(®Ž_  ‘Z¦Ùé+£©¶ˆI÷½a±mͶ4,2ø†ÑßµdY[ç ÉîÍ{‘1üÖÎÜ eÍRï¼3@›â7TPsý?ÂÐ endstream endobj 270 0 obj << /Type /FontDescriptor /FontName /GARPUW+NimbusRomNo9L-Medi /Flags 4 /FontBBox [-168 -341 1000 960] /Ascent 690 /CapHeight 690 /Descent -209 /ItalicAngle 0 /StemV 140 /XHeight 461 /CharSet (/A/B/C/D/E/F/I/N/R/S/T/U/V/W/Z/a/b/c/colon/d/e/eight/emdash/f/fi/five/fl/four/g/h/hyphen/i/k/l/m/n/nine/o/one/p/parenleft/parenright/period/r/s/seven/six/t/three/two/u/v/x/y/z/zero) /FontFile 269 0 R >> endobj 271 0 obj << /Length1 1630 /Length2 17552 /Length3 0 /Length 18400 /Filter /FlateDecode >> stream xÚ¬·ctf]·&œ¤b›•äŽmÛ¶m;•ܱ+¶m;۩ضm;©|õ¼oŸ>=Î×ý§ûüØcì5qM\sͱ7‘¢ ©ƒ±™¸ƒ=މž‘ oegìâ¬ì`'ïÀ%K§lfáø+gƒ%#q23Z9Ø‹͸f¦Q333€‰‹‹ – âàèádea Pª)kPÑÐÐþ§ä€±Çhþz:[YØÈÿ¾¸šÙ:8Ú™ÙÿBü_;ª˜™€–fs+[3€ˆ‚¢–”¼€RB^ afoædd Pt1¶µ2ÈZ™˜Ù;›QÌœ¶ÿ>LìM­þ)Í™þ/–3Ààìhfbõ×ÍÌÝÄÌñ-ÀÑÌÉÎÊÙùï;ÀÊ`áddüÛ ÀÊÞÄÖÅôŸþÊÍþ•£“Ã_ »¿º¿`ŠÎ@g'+G àoTEQñç ´4þÛÙê¯à`þ×ÒÔÁÄ埒þ¥û óW 4²²wÍÜÿÄ26˜Z9;ÚyüýÌÑÉê_i¸8[Ù[üg´'3 #'S[3gç¿0±ÿéÎÖ ø_ª7rt´õø—·Ã¿¬þgV@g3[szX&æ¿1M€c[XÙÃ2ü3+Röæ&ÆËM]ÿCçjæô¯Qþ33T“02u°·õ˜š™Ã2È;ÿ†Pþß±LÿßGòÅÿ-ÿ·ÐûÿFîåè¹Äÿ¯÷ù¿B‹»ØÚÊÙý€ïÀß%cdø»g²€­‘ÓÿÏÇÈÎÊÖãÿäõ_­5ÌþîÿL hô·-Bö©a¤gü·ÐÊYÜÊÝÌTÑ hb 07²ýÛ³ÉÕìMÍœl­ìÍþrû¯¶è˜ÿ‹NÕÒÊÄÆþØþ­2³7ý¯ü¥ë_ù3(ˆKI Ñüoì¿ ÿPÕÃñonÿ£9ÓÿyøFXØÁàEÇÄÎ  cædú{ÿþ&ÄÅÌêó¿ ù/ ¦ÿ<ˬÜ:ëfdúWõÿãùϓ޳7q0ýgtT€Fö¦§í þQ›¸89ý%ù_ àoÕÿqþ×Ü›™¹›™À®.9˜ðY§e¦k±r‡&Duú{™¾ ;–6¨ø×8ôø¥…ípU~ü ¦oœâþÓæ±xæøy M}8Ò‹iKÑ“bv•ÿ݇„ª¯e“¼ƒƒæð'ƒ~)Bú¹F”×õ‚ì6¸6;£úáî„’²~É$þT‹ôõ3•?‰k?:é“#¢¯Ij},F'r#jmáÙ9yâÉóÅïÑá¡Áž[ˆ¾<šœX2WpòHÿ9xÚj•^1Ô‚‡qB RwFZ/‡2*¤–ûVf…!â-O…'JŸÈcEÛ}‡â sFŠö8ÊŒŠOè’ÅFÕÑôD×/ÅŽã,â°@Û°DðÞT•þåØÚ±ä »ÃÖARÚ@Cß\çmsHàö 7¦]ywøÃY— ¹Y ’CS'sB *c?êáRÖ J¾s ÐsÏÙ såÔ€¬^GfèW-Ge›ÞšR¤PQ’& >5:K¯ˆ±ï«8³ÂâD»²ÈÈøšäyhÁÖ‹˜*³æ~~2ÒßÓ¹8˧ ¸«Æ¥î+¹Wõî ø¿ 8/âúw—QÎKX}@tX'g}edÖûí"cPóß=»$ÁlÁ"“׈@ïîÂ:èL‹£ï5‘oRék02Ý%ÅÊI-Ìóµ]^üqäÛq—e rÃFư÷Ö‰D…ÜÈ…;)ù…§¡•‘RA Ù2ûih×ø–•FnÂÕ±Œ…ÆÕ†i©ªZ‡¹Þ5¢ìq[æýñ’CìGÚërþ—g¶Ás•—…Çí÷L(–p>Éf¼°Z&ð:a µ¬"FZ‚ç¬ÞOd’Â×^‚pz­‰Ò~C”Öæ¹¬þÜaŠ„ÏâãÅ•˜×ØìöøY®,Õýʤ‚¥áß2jüUÀE}G œDÂ×ûÆÑ»ˆYW'•Y}ÎÚêÎùM3`]H{õgêcMàê0²ˆ¹Æ±ù —ưXx Ç7mP.‰§08ÇCknAYãE¯ lJÙn$Í mDÔÑv!égš÷GÐG­âS§vhÎÂáÑs— i„¼bª®¯ž8”Cu…YO°¾0Ã_6!× {}d–¶ú(òÉÄ#z§ƒá=‡UàþÓϘ¬Êfs"úkGÞ’ :½1ž’KUëñ;âYðá8ËÜzPùuÎûˆ®–°ã0`E°ü(ñFi]V·öa,HŠVtZX§9ž  Bü¤§1EAš©G5rV©Í'Oß“ O1­‚ÇiæROª>ì§éˆªƒ¬l¼m›‚º‚'íèÕóüdëñ¨²°Óħ`¼ï7²¿ö™Ò â\樛®ƒ2YA~c ™–þœQ“Z§tºM¬ë£gëoÐ:Šoiê'N^Æœ4™ŸZõÎ\øÏ:%Á^¼}§ž1 ‰óŒoþîX-ámo?4+a[ž½ÕÚæCT]3XEX({(†ÄûjCr½'¾§’0é ²8"üâe+"Vȵu¢{„ƒœ=X[Œ:£ûº¼h!Æÿ6ÔH_òÈêo¾ë<8S+í“'Qa1é¯?¨DÜ¡9i±¹Cäq‰¡úqÝ…šm‚œ®£¹m—Ë8ÄIÂE‘ áqö¡¶-×líçòLiL†÷ñ¼Aa¸ÍßÞ¨9©kѦéáx é ;êšq ~Bý4Œÿ¼26"c*`77½¤JŽëÚ‚ ŽéüÕ´¼'Oƒý† íUÑJÇœ::µx5ü^þ§bÅ#`iÍ)+ðNù K èî ¶ö~·Ô¦€zpƹN©sHÃg;åSBvË—¥(néÂçqZ¯Ï£›Oªë-ƒ=ë(^à™µ¨@G–òþF kniJ ‹!Lh@¬¶M~ÅÓR‰¾`6ލ`L$z€q¦ºñž»²ª¼©ý @Ši}ãâÔ<©XÄÓœœd7KQõ¨/~Ñ~Sͼ¢tÚ̦òî—kͶbœùQâq9åÉø™F‡MÒ0n2j) ÄK›`RdÇ =¶ñßoÍ®ý¶ìÄRUSßÈ«,nZÛš}¢^À!3:ãSV~6Hæ ªB˜Œñ晤Y¸Œ¯ÞÂ’Â% åðó‚ _K_þôµ›¼<`ãò´‹ïiðÝ|à <ª~bæVY—ijî…3W)¢þÞ…g»·ŒDëBÍ(IŸýyÚ *%mÏ×Å O*Î!\Fê™ÇTç ¸–² “`q—Ÿˆvº³;³i³·[´H¨n[’Ø2„¡œQM)á)C g'öÒ€ÌÁ y˜Sùá7V+z8Ï>€—w ö)HN¿’MYÎÀÊÚ°rM¿‡ƒüÖ{¹ñãÍW¢>ÇìcÞA¬a¦–"9L|;Ê ¥eÌÉUÖ2»ÜpÏ?²¤áUÊö.¤ùèV+'ò¡éÐïÐ,®÷@á'û£çÀ”vÎYGÎñŠ;­o>O%‡Ò©FÖOœM§ˆsí'ˆ 6’ÕÅ‘xoRZ°B&{•h`o/f(8n-—›à8L¿MÕÀ´/'ý ËV?U¤½nöØœc âqm¦™^ùeÐÇÀþ°uÃH—ø«h5(Ã{'b Ôª"•¤cʧPð–ùÛepÑÝ2v‘ÂÉã8ˆYV™^~òÓM'iˆ5²PnÞ'þuÚ¦‡5O–Bnè][m½Â‰%Þ³ïÅ^ïi&ªð¡•Eá‚9C#lÔ,MƉlVñêþ†t)t@EöäïÝ ’@5Ðé Ói¹M­'â­=6›$B*R²!^˜my8´z©\hµ GPq@‡Ø«`Ö4h#h “_f©1§y.<…–AâjZš91È<מ~¿ÿ„ÅúÍrVÕ[š8µôíòÅ1Ô«®51ôä®ÉÂ,¸ë¾Rö¾ª!NeÞí/$Îèepõ Á½xëF¹ÏÄžíQ³ȺjÃa¬{vÞ*P%X¨§f6XXƒ4fäGö ÈÔ‰×ÚÚf**'½&TÕy5n™dF¯..eNýÕîXSíÌ›¨ßщAB_²ƒÌ:öh¸cã˜1À½dÏîu  Ã» ´™£ådϰRˆ‰9Ñ.w×+TÑ9ƒõ¢±iר*¼‘#쮪ÞYpï}U6bÓuó2Ì(…¿ÉÃÝwcjùð¬Kñ=DT/4i}«|ya‰‘4,2x¸ àï8Y[Ë­Iw¬9I}\®½Z€žƒ ŽÙ¦vÿÖX+j¦"²¡iB>Œ•YüHÚ<¦¯:½¡º³“ûò˜£×'$âPÍá~x(ûh(/OIú‹:ñ “ÿž½Úðé‚5 ýÁGÚ¾[}¢_ê¶øç㢮©nTò+œ_‚m˜¶šØîò3½c£ ì9§±×iÇ!ýå0,ƒãxÎÜ|!xw,c8mÄ`p3W̸²”´Ì­„Å £0ëUaÚ Iõr:¤èHû»˜ïLnVß›FƒîCœ*–†¼46vaö«àI€zú¡•×þT ÚRµãX‹W¬õ¦¿(ÀAù¥UÏÝ_{_`ËšaEó…ꦛ¯¹Î|;j‚ “)MÑÚy!ö§ ù@¶­$Ø`«ñÚËT”â›m±5–ÑÁZbë$ø¨CC3¬Û¹~j¯º¢ÔÔ0:°a²LP~l«Øç€÷w]}Ç›¸úÚ¼²r‹d± ¢z9„Âz²ZüM¤ßnCµkìoS?PH…&X)÷û•‰äÁÓGñ¬zI3ÀÄIŸ+¹› qjÛe‹’ùñÛÁ9•<6"[« £i´½ÖꋇXÃÄ®*ãn²äz°Ñ°æÄâ4À‹¿I×ö9ñ®5%ûlV÷ÑÑÀ"kõ,§v‡z~JòxÒ‹-Ž×" Æl•T PVnš}[n'òiê%qôÞ>ÛD¸ï¢ƒcÉäeiØiþü+9Á$§¯ÜÛ_bCÔæ–ß(¿xÒÊwš ¤5ví óµTJçx­Uî±ÿôÒlšïðæù# Eœõƒ‰ä‡©ÉavN>p|J6›QÆ“ÿýdØ„¢kC&_HÇ‘×y^‹˜(éCŽN„„x2ãC†ø ;ÌË‚(ÓH¸é’…rGhé:ð¥ã»2E8»c€ÅKmn„EÙÿœX F;À¡{-§_wyÛ•É `Wû#2·ø¼sLùHQ®Ó[v:(*3WÆI'Å]JŽ«‡”N*°¬ÃL3´²LýÜÚr»¹bôÄýH‰WºR´ˆˆ†ƒÕeµ\ÙÄL‚T?žÑÁxÝ“ˆÈ`ÿvcÏË7·Nz© BÌ8L´XØq‹jDÞ!…$`ÐY¨¢¸ ÞaÙ&®z#» ã}ÖÿiÍŽ¼ÐQhyc´ÿ·Q:f`ÃJ,{½Xßø;©WÏÐ+­Á©•Ú:Žèoºõô+!^J4ͧß{F2ŠT)cï2ä,…µNúä·©âãLiŒlµQS<Ê&¥èQ·öCX×ì^GV'hEø {K{È´~uŒ­¦€”†¹˜k5ŒåO1f("~´ÀBº)ðßo2¾»ή«ºƒ”¾¡ «àã»ÄK!Yy!›Ýª( QÊ]t℺8ªÝ­¯Õ_ãIÖ2Ž7t«mLÓ¢¿„º7Zãž`{}C š rÎtƒ‘ÚªF(&÷hBßaYU$²¢ÉC‚x³³ú…˜Cl‘œ^B„ 8£»;#07¦h€#½‰ÃF–Œxé~O•Z°{˦quú#u`eh±¦ÿ6ÖZ›Tç¼§Þ.à m3ÞãÝ´šfbÊTuÓ8äGIÍëðï­ô$šNÊ?4ŒcÓ^ys\o£3<Ÿz^izŇõ|—”ô«°ö@”y¨/tñ•Óô= uÒ1ýÔ¨Š¯ùxs§¨ÕçUX»_âHá ]¹RL–”|û3K•þñ®»ÿr‚ £¸bU‡âÌZ$†RFeiôüÀ–}œM 6Hô£%·£°-ÏQ1ÙE ´°š Yÿ.íÆUÇ^qºUS‰,ˆ ¾·å£x¿-oH`£IɃYtÁ>÷sî:N]·ZÕ0ì” R"íg± ´0ö}æ«\.Û™w˜ë± $¿Ÿ|é¹j2Øüd‚¾l8õ7ñWÒÊñx}vß´c®0Y=5nRÚ°vwú'âq¡ŒÐOZ×½ÛþqgóÕHô“¼KÎ¥ÛÊ¿…eb*Q)®|¹Õt‘­¶H˜èö.ªÉ?{µµÞ]ÓüÎm›$ÍØip:;…°Âjw6|äËø1wP¥%£›ŽÙ:ukе»B¡\ÇiÄs‡šn(†Ó>:²ø·ÈuÍé‚6a>/nö®¬æé]ñªœ£T:c²B<¬~1=.žb¢"ëTz‰“½:½Äš†yÛ3¤L\÷¾dÏPÙ(èO._x8¶Zà©î°ÈE-÷?¼q„^®ƒ°¬Ü¼éå¶W½'t˜è2K8iß mýöðØ@pfBµ¢v]«‚…ù‡<~­%_ ¥¿Ñ»Œe‹Œ‘ãÜ´œÓܘυèØxì3/ óCÕ&¬ìÅÈkŸ=Ò÷Ê Ÿ„t áV&#Òß¾[²ÁQ~/؈åöSY“â‹é-¨Ó´QÄïs¦Ã?-¹×r«ãüP½£Ü.°` ä´“1© †>o壌$…ü€G¦‡j½{+Ëq|¦\qÐ[Kã?øÏR³À “ ˜"J­çùQ‘`Ô^£ôjý£Â…ùia‰e0½µlèøþNä~B,üHÀ®È(9ËI“•ø•šB<ÔHY3¹ZSHÇ€;œßßbô*A+:"U†”,fNÙùT߻՗¬¢ŸY©\ÜX#q/–¿AÀ° òrùNˆ[²ò™åkòÍŒ B&;oÀÑÀcdà\£äÌ9Òçž0^ {ÂJiŸfd€>ˆ2|iàÅXw‚oK«b¨eÂÉ)f*!®‡ÇŒØHw¸óZíÖékƒkû>ÿÂoV„j›†\L3øj135H7¨\— /A©ÃEÍ›Ã.B„!ã$9ýS[ï¶ÌÃâ7;/op=›£ O¬œfó’.»2³Ò]E_Й9hê@‡ÚG5”Mø½~N¯fº칎>{?ääògcÝû”䎡KXÃhöÖmo6nr‹-æòèK[WeFÝ+ÒFåÔy2óã7$lö-š\ÐiÃF4î7ð†c{â‰vxz$4úUÑäNì„Ë(…»ÈëSÐfI‰ÊƆYÇ~—¶Û*&õe&œèª=†¸Ëµý•óˆ ’£v^]”‰ö°tü.fÒ3 ÿqÛž³åà æŽ\áXa¶¶A tòõŒG¨]SÂß6†HÿÛàd b2_Ir×Ìÿ QîKâÏð€4vZæs¡Gh¸sÈs«,Ò ªw‰—uŠÙ~yZXBúT' “‹†Ø•P†©†Hj"~]ÀË7´+[ЀžªÇ(æéøBBÎî„V³Á˜…8Ç‚ª„²208ÆÄ¸˜Ñ2ëÔ¥\õúÑo O/%ê[w_¼WR6QêºE•@²ÙQΤqZz~‚­û:ÎѦsêçÉÛŠ¨-ÄÚ™°_5ŽêF&ÇpFõ £É¦ÔÞ¦fAÝò] ƒ]?ñ ÜÎ?ìwéÎÄDL?Tî©[ö÷ ökê$-þ€Í+œåßìb?LC®¬‹”å²®ïfrœ¿Û2äl}§{-ÍïKu8?âE ôÞ2øF),ÛfuûÉž"o–'kEÿêä(TŸ,+¹;v®t_=9‹êœ ¾¼¿-g£ˆ>ËÒuùÖù£««­^½v8Kx ‰ x¸w´XJOÝ) )\»áTμyå'ì•Uq2K!‹ªóún&µÎK!%ÇtVøøÃ”Ï\ádl\÷Ù]¬ñÜÆÙA8h,äê¸D)uR˜µÞëšïŸÒÔsî’7h[‘LûNnøx1Cþ¸l¸Z”êºþžÃŸPPîôP×éa,Ê_S‰x†(šøñŸ—õÌ„°ìuu½§lUAV¶Ú} ½ÎbOúðgȽ ±Hßx½ÖŽûh&VÆZ~=3!)Õ|Ë{Ê$9x$Ñ&§QÔ©A ¤Š‚%³·gÕàñÝã&è=\»;ŽÈZÕ^j³U(¼ŠSÿŽìÜùäºòš”ÒëhN|•²1OJ³;<·‡\ÁòÂÜõ¨ªÐøô[±u4húšgs´Èt.Öá¸_µsÔ›~3,öøÔÞY”ÒÅ8¥‰c<ºÅ~‰¶4àa˜åûÉÅ©Æ}&v^ÿäîÿ@p2˜º£“gi¡Y¹¤¼e'ç1oñ#Öçä*áW{ç‚vK0p}¶v‡  4—ŽE¿(MX/kB)aÍ0yò’+Û7ý3²§e6­x¤,iC â ™øÇê“×Ò|tƒ1ÁQRUòs¢˜"÷Ÿ-‘CIA»ÃUX,¨vO¥©…½uW(Ï@ŽÙS.|—·‘dmœËsõÕß®ùË2^ìëõÁ–7ÄÑF<û®™õ ºøxaX¼£þ$:¿?þ¡.)\ZO¸ëÕT 7€7Ê9Œo^=¾¬‰‘Ä0œ×Sèüï3œ«6"¥Ó‘8<îV4.zÇìyB:rÿâÎ&pÖv‘¯ç–¯@ÕËçHNØ­8‰F\µúÆtV>§²ŸÏYïg;Òk@á¦TÎÝðªX_ÅZ”nèÐö´Ñä$+59Ýî¿(°€·Øãýê'{R¾ :“@¤ ÿ]b˜RT9‡'g…Ê•ö jEg»€î•iþ‚ Öu/õ&$Ô’U֢͘D¼aãÏÄAv]úkÐ[k¹]ÿŒ²:¶6'“€žbÌdázðz{±á¨”Ér¥]øU7ÐeØÄãiN³ÏÉþ9æ9$R2商ï N¼”õ(j¬½@?YÍœpjCâÂjž]mÃâ£#|.q¡-¢šÌ0áíÃV(–wÄ+òñc…j"³?ü¥aócž"}TOÓ‰æ0*è¹·/uÈêåÅ`ÃŒ¬½W¬ÝËç™xÌ€F¨ÞÊ]]ß»=á¬Þe;<®lÜ6r§ˆ …*.¦ÜÞ‹°$!ÇêþÛëåæòCš­U¹u0ð*Úh €Aèõ¥E]SnL;æ× ÆËwðmTÀq* "@—v`—ÝI¸uc-e‰ „<·Ÿo†äÆÜ_Ä.Üâ®ê,âVéx÷F$³ñéÆ¿Ð‘´±‰Ô­´‡÷q7D$(®Ø~¥ðTs¬{•’{ÃË´‡Vðc9¶Â9«€étñx­_eUùº-g–^˪Ž èèd5ÌÐ8R9§Ñ‹HÞÊ&Ý Ó#NÔ'P{ï"ç wœ¶TÐ>TÆ `_ùîï&K(¯•˵‰ˆžžJ¨H.¡à¹jÂý.uÝ i´S¶A {\µa¬à‚ºp>l|mAQoÐx"‡ŽËôUØ•c³œd1Jµ»¢±#îSí(âAsh~x•6ǶÅrãÿÃöŠoÅ\³5¡C^HgÙÍ&„ábk(~ŠñI·çNwY!~”$ë¡ÏV™E™*» ËÎÁ›¯ògS ô…ÏyŒæ7þäM2à%QÉjYº~íÆ+×í$QTSíìýZ4Ç[¼ùt4…x¯žþ9‰þŽÞ´"E†³"Ë%ÔN>ÀK½Ç§G‰Ã.R¹eyasZÇ Tmí›R•TÙ!ŠÐí§Û«Ÿ<´$zœ—ÌT±†í.z¾´5(bíÑÕ‰RíÞTD­W`?¢”ç¬\+Þ¾ÖF_µ:3ó _ÎÄÓJßwB^ÚhË Ž7:éµTxÜùf¥âíêÊQêS ÃXÀý¤’èú>Ôyº#Çý¹z2g¡wH;u¥¼„¡Ý³õçEKœo>NkHÎ1O´QfÐæ “Þ»…+u‡N‹ÉC"MUVÂGÛº'šmuaÇnÄ`dc?Ôã.}Fè—ýÊAÓñ»Ì eÃý²ÚÀ÷®‹Ž–÷1<±dÐçH°ÑÝ”sަ6‹Pªn6§µý ™ypoç–ƒ ø/!Vª†ñ-rÊÆBÐß5W÷³`ðUÚ›ÇbMÊËC£A–U%™ëNN…¶tþÇûÔÎôÓœê ŒæEZÌ”3ß±„U™ýµÉxÿÔóêçMÞè¿f3OQai#ú¤† O/Ni UûI3 b©«xi· © +®2£_A„¯sÈuœ¼¸t®#ohúàcº}BžÂÑ»qtž–×õdC$ì_.„¬ó3 jÿ&–ôŒÉ”£úóTÒ%¢¯pOðŸSÄ«|},ϗꉄ÷¡ $ß gÐV€Žp5çè+mÁdZÇ7¯ÍŒŠ2FK^[/úøµCØ÷·Æéc)Ã\´&Õ`Rü˜ Í9¿ö|ÑÍ_âXN\ò¼æ³ï‰;fy†"£‚Lî\Y"†­òy¥û9¥õü‚ô>é’0äðŽM½¦•!ÖéËaVÓŸÐt¹à Žg¥•åüè|ˆb ¤ŠüûÙªÉìj’üÓ¶‚ˆç@¬q1,+•qIw!™nwIf‚$pH²éæiãç}Å a"ßžõÇ:¡xX½}íÍ…È(«Xâ¾$„EòcVâ G×~~yÑ(XHzÆFLš\—šy¯¬»:Ãy߸ÁÊ)¶ÕN·š¡~ÊqíMŠá—'vÑå|ÄÆ&ÜAo›Û˜ FxHt+5¥&ÇUÈ ªÖ»ßµnI•Ññ–½ªJ¨!½,hF¶ûý@¡ºY†(X…¤ ?á#Ã…·ûÐÉ :ß±KÛÃù‹  ¤ÍLÃOGÊ}dr Å KHCTùï}¤ïGûscEi‘Q «ê @TE·o°ï§…F„Ζn×Vl„åùؾæá¦ç$÷Üw‹…T`ÍíOk £‡Ç(`¯¼]ŠÛûåí<‚•SÝ·´£m|£w TyÅvCZÏú&PøýŒŠhqÈY 9¶"J«¤ff=,E12~$šÑÞšÊåˆÒ”©=¨¤·Gm—éd˜Ô³ Šy…™ ÷9R=†Ÿ¿•c.s]šøOÌÒYLدyÓ½ä~IJx¿íÞw¯Õ -Vå{/nØr®|¿s“Ü…šèÇŸJ⇋®¿I¯ÎD‹ã—“GóÕu†ÝhŸ mu^0=½â¨ ³ß @¦øüã(©ºR¾]çRµ¦CâH³¼²—Í>tÀàÝê\Áãû€X%Ëp×@Uùkè÷ôÑfäÐ>ßdЦoG;Œ„­¢ü‹h_Õ'ãþPåZemàcÿ1C‹Ü¸áÓ½ÒO–Í&Aâ¾ùî–#´?òë-Ek²éæx#K&˜à3„âÏ8dà]M`ÜuëÊLçµ ×kÁ©õÐÍÑ38bd¡E’F¾tVmæðoRI –M¢½Yúæi°S6Æü€¢lDàRsÜ”9çoËì.>Rî,îJ“»¼Ô‡ÐáT€`å߀Ò9ôÄW7ñ©rGÍÊ2ÿ¶àÁ¢åÈÓ8oè ­»N9ùšf%†é×Õ …ÉÎß!èðÌ€—Ñ¡býáýfª%¦Ÿì7_î:? rpž ¿ö¢ˆéeûµ°á+«#ˆ‰çmORÙá®mt™iSq| R5¯'·ëø® eŸÔ _ú3²{ºmUÅô¨šì— …&GðƒpuŒE©b|[¿ÝŒƒö«²—‘ÖM “þGÒP¿Á„Ô•r¯Ð÷·ƒçQ‰O«N«þÔŸd8m®µCø€eïìÜ´çÍNÐ× ¾3}Û@{8/ô6xòM8ýw#ÈógMKceÆ® y!jŒaw¼6PòÄ?‘ׇŠ j5–F˜Oϵ¨ô%˜XP­æC R}´G s|Î¥Í}w¯G½è`‚y _x·Tì±…*ržy3ê}#q“ pYF úG\} ‰óNþM·¹öZ®²sBs¸;®ÀÎÑ@Ód8E°-;{ø–Ž$á®Ï|mµÖx "õ–íÀo TQ½Z»»àìZ‘}<ä‘fCÑp*_Á•Z½ûi±”5]BNûw>Õ°þ†~<×ßM±½™:¯ÄJÈ@c~Pk~±ŸHÙ°Šûð>}×õômîàý¥•Ï¿±Žå½¦5Ç{ô'}@ òì´[Rwé*ÆøáØj~,ï!ÿFÆeÓ,>Ä>vª¨ȵ6ºÒ SÁƒ”^ i^p7<§ñŠa3;Åþ½ËaäáX¼Û)uM©1³2þæZU"TÚŠwaù’fÑë4䙞!þ˜–ûçÑL‹b­R‹Áv«eƒPíÂ1˜VW#®L(–Ê…TïðêAw§Çç›Ë’u —Õ»3è}bº•Ò”¾a³LsLâɱå’Pvé‚íÙVšN’êéÉ ÃBÔƒ"ãß4>³8Š&º×úëó®Øs™˜£üäNHý´ž‹ h%¢—)Þ÷ZX 9Á£ˆÌ|}i]'6“ö)ÓX+Ll=&/ÚéÙ)pñßTÐÞj‡¹Í§ãDho?Å&¹¦³ãgLSŒèËõt‚‰y€›ûà~Ÿ„þöˆÕÓ5™H÷)û¨Í¦=W¦µ¥ªÚŽ´}{lœ²ö(©m9~-<óµΗ$-±¼!®Ûª½§¢Ž˜…†Çv Õ—JŠ ›²s31Ïç­Bñ÷– ÿUm+UaÖåê-ä*iÀ”V­úqªq•+šÀak­Vée ੨n*ûB}šRÖ¿–fÁ´”^xëÝT› ë*\¬ãt­9#«ááH½øÙNW'RKÆ#É:ñ ÁþÂÕÄÚ¸19ATù“Ih"֛ެ,_î‘zº<âÕ×&”©Ú¯Ù_3†wÀu„÷Q³ªöû^þäÂé %š0E†T^‡c¬”ûgýœ™¶-ÖØó:ÿÛÒ•Š»èðZV1Äa–'IyFS_éÁùÁÅ¡Ñ+Y:I(¦3H¶ƒ`jXJ«!VVP•q·"@¦Z5¼\‡FïTG ¤‚¯ãèT𦼣RJ}uɺˆ/€ì½›÷ÃL-’H#÷º£+} îµm„\žî¡=dz7>'±ß:jÁ¦8¾{b„`èðŽÒ`ï~x¥è]ˆähÎ2æÑmDÏ\E·Ö@…à”·³ÃVñ••3¾oOà5|D€1Î@*`_.U•^Oøm2GìÑL’Ù«$Òt¦Üg }ý¶½?ì¬i»5Ÿëc*â>-8œª*6r€}€õViNGáÁ›Þ6r\@iÚ¬NªÉÎieÞøGùGo3LoD%›"~½t ~4S{[kF0-öøãmÖA`R umï³ÃªƒJ—x,ÅNá7$à Ð÷\š”+å ªˆHK$;;:q¦²ý_]¼ÎÀU—¾¿|VŒÎnXUÛ[9æ;ÆÀ‡W¦+±O9U+5æs¿Ÿô7Ì’ûË=äk?ÖW®X¸ðIXg|FgÔƒbE¬—Ô½6¾-ªÎ©­ƒãgëXJòn…!x@6êw*iJ#:ÊõÌ÷4žb”¬œ·ÍˆäûÔÆ¹P¡ðavîÑhRÓ¬Hìä//ÄÞ!ÄjóÝâ]MXwü©Ú_&#üš ›-/÷MEg-mioyòÐÇœ)ÞUwSÒµ˜U6{£`Xšš×9.DÈÙÒvÞ †·9î|88Âádüò‹³f¯KsñáöÏßàõÉiyÙIM„¡2x>8íÎIÕ«R~õ6ü’Ñ—5°WW²Ž±‹ž»Ü¸6j;D.úa©D§Åç×â_@6Þ*H÷[ü„ÇûWú r å= ãxÜ箬„iþ.A…äh“Zø·2¬‘íO$•Ëê Ç †ŸÍÔ¬y9¸Ÿ\’üŽ  Yc™í®¡°7Q`ƒ·&ò>°Ö“T££Ï¥õ°  'j3uR6c)°÷M„ÑQ3éL0+îÉpÄÊ‹TNû½/—c…ñžðwÈT\GtC…Êl½%$%òô=Š û7ØÏˆº¬7—M²­Ð IG6ë_¦8õü¨â€)>ð˜/•PÜÉÖc4ÿ({í$@„>qŸ‰S& ×Aã ÛÙdÕ£ ¾Œë¦·=þX¿IÙf.ä=£HdâÓU»Ì3~V´²à,øíP¹£uÙ%ýÓÿþ*sÕÞB EaÚ–ë´]ýz‰çÞÄ+5âf½- l—àK,\=Ú³Gý­S!+ÛGÙœÏÍ«†”麕ÏÂŒˆt/1Xι_*£ÄBc28Yþ"Swê´ˆäF*/‚Sí K ŸmâÀ:›-‚Äw!\´‰«óç 3 ,ÑBuޱtúŽDþB~Ç×2RŠIØA±t¢餀_iÊÇX]ë‹©Êæ˜&o¼<œ4ìœU`*Á ~‹ˆ8*?ΠDNŠ"¼à “@™øØÂî’CÝ„¶Soëzîæ=G´$ÞÌßâ4J-Œž¡úÊ?cçÅp}²‘”L÷Áoòd1¢¢ð¼^˜º2îJ£ ‡àò¹ÿ™FÖ‚ÌqÔ$©œß÷¹gãî6<öÃdä¶ øÃÌɼ8E ¿%8£ G¿6ö[œ*Ø%y–a(y ëõ›‰ä0J^Š2Ô’O$å`;W ^uÁhº#üЃrgKË-ã:àFÃ…ŸØ§¦J^b“&è¯w'B˜ó•\Î&­Uÿ—;Á]ŽæïÓ¨hõérÇhþxÔÌ6Ú—¡E玡î8©Íçþ1ø ’¸o1•ˆ¨äêœ{_'ß5ÄíâøóêfëÖ i‘ï I®•uðùgãÈI1d>ã%¶«íË›Æ<| ¹çª¼´ªÐ𠼈Ú-f,?©éßÁÝ‚ý(Ž,ž"ë›Of¿ÌyÇ;ç_·ß[²-"-¶-¶f¥Nâ¡§wÇFó³èÑp566¿t»m‰‰ØªC™D7q¤Ï¬–§»(æ!Clž0JèìÜC0&ýD>žÌ$Šýr+9sXÝÒ–H<…—\?Ê~\ió©k²1â]ÂM„—7êÚÿ Δ‘Rê“A¢=ýcÂÈ {¯zûOÐKLùð7}ý ðøOÜ¢a¹Î=LŠë‡%zõ­4½ºÖƒæáH ¢–‚Íõ}$WŒ¤LD¯¶¨/Ë »¬·à(œjõJø¬Jäj™ü>Ò.¾ù+“T&½KPnÏ'¢Û$'Ä'š&ÁN(S‰†ÝàÚòƒ!»NkDµCQÂ'(Ú0XÆiÌ–xn¯¥Üà…— ðîW‚ƒà¹“$'ÿ$Ì‚q-Ü‚ò«¡éR‹µŠˆ×ñ’ã$ÖëM|ü­•¶ž4È3û‰†ûÌHv€ÈÏ>Iâ¦+ÇB´ølôÞ ª©ƒxÌÐb{áfOǼ²\§å*"ãKCQD€ØRHTZ9l_5ÊFÝ^aa{Õ{e5ÿÃV¡ÙÈ_ÎÏf¥fr¾><­ÍÑn»´ª02ßÞGTøíœãku‡þ.±w[g ÆÕêÝëšÕD²ù ùžôíè¬yR·y,‹‚jðEëVþì7ÿÎÙ_1Þlì)õÔ[+øÒaÞ¾VÂû¼IÕ©rÔ9óº3ÆÊ1ÐéLÚ«ÅD€am /°­¤¼^ŒEÐ-΢ݯ7uò½b¤e+½Ó[Zé 1{TÇEîÏ}ædJÒ%lœÎ`ŒäÈørYçj’AføÎÌÚa½ŸCÞÍ gbÞò^6d×¢jFX¤¡'¶Ù ˆ–…Ú\ ß›ÖÞˆÞˆ®ž¥ŸC+4Ës#„ÔNC¯ÞQ‰ëá‚Ë+òÎ=é;ãyçÈÍgØR †Óëæ®Ø’þþÊôcý2·Õæ ÁÀ)ï˜íÌå .¯Р×ݯF:¸eÙœÇ ”‰¾ëí¥ˆ'ªuqâé|S'\-š‡ ý ¡ +0ìK/†æxèíïû¹ÊQäPLÒ Åd›—à·o{sýî!š'åŽ ()ZEj¯Þrßžr€ðàF2hIJ®$£òW¶×¾'ÞçÍé–õ‰ù_ò*[q{/^Èšrºkʘs-Y-=©#^4¬í›-œš¯9ºXnt**/HÓðpä+ž&*|´]ÂùáÐ}z)èQnÁÛ|Ñú^6Æ]m=Iåcµ¦ÚCÀ“tc¢/ÝDJK"˜‹T“@÷úÖ;: r–šÔñù!ë;XÙg¥lre‚·ß§1C‹¹±­Z׊ù˃m7Ù$eƒbX¡ÊýXÍ('&ž€X±f"+Þ݇án] ‚ÞÛvÁ|Œe!‰å!/2:%°°Mì£*áÛÒcüçÍ@kGpÂ"˜RÏÆë§ôXà×ii‹Î@mz„ž…ÈH.G¡«eE h¡++ :C/¿‡LmýØ#hpÓuÉ•0Èê`dü¦‹–:s¦ÆoÔPwQ¨ÖgOo~zµüú×g©~S7n#Ýò÷§øl¿OŸÊp3¸kãdï{!Ì«e´rniÇ:ÁŸw¬Ïf¡£®§Ï}p‹õèõ²¾[Ë<»M¢—c#6omµ¡ y Sc›•ßüÛý"„9ºUlþXnßâµÜ±sFoùŽô^yw(VaEqs Eä)íÂÙ:»7£æÈÁù2À·¡ÙÁ .-€õÕbóxd¶0¾ùÐÑC1‘µ­vÃóŽ×øÕ>M ½jB´Ùï{0ÕêÞ±Ždï —èbØ8£ßu¦5±À.r‹ÀÇ”°ó§‰ZœK½-µ¹3ªC{.yëW»æÜ¼~L\yA›néÙ n*6iuÐß/öJCez­lšíÏ´™ÂÁâd>‰|92s‡§"ä<Â|ê«§nBÖ5h¢~9ë3|æU“à÷MÀ,ð.ÂÖ‹ŠÄMdÔWAîä-v½9éQ¥³Ìð¢èºÌש½šêä®D)-ú£¹O|ü2îeÓ0ÜŸóâÛ3¯odòfGY®Ô-‚æ` ‡‘ÞbB\Íí¿Ïú†1¨D³~Ž­'³N;,k"õÄO'Ñ)H@×ãÈ`_ Ã3GëÁOdN;÷'YôT¢ïÞãÓÄ¡y5mÃìteÓ`øœ ÉŽÇ‹Ã¨ Š¿~3šÂC•Ÿcü :¥nõ…H]wä·%YhÀÓeiñkÇüµÊ åè³îFð)㛺6ÜϨôq~’š$„œ¾¶ ìøÁ *P–õ×*»xÓ= ¼á%¾]&Aûç¦æA{€M2 öìz¤VÈâ5:Ö“LÙÔ –°N) Œ¹N#06ýWÐD´Ê¶-ƒíÁ±†ÍuVžøå†D&,Šê¬,eŒm¾Ëxˆþ×ñ«T—»­¶ƒ9'b0¿¤2Šj‰hÿŽ7Ôߊ«J ‚ sº$«ÛóÑàÎõ¯V>g+,§|@·Ä’>xM6Õ²HXé±)_ˆ2«/p_&DÔôFk—?€Âz´lš)grâ¨YðÖi3­‘ðƒV³(Ù¥_§sÚ˜Â/¸(+m,õÜŒÕ@(š‘.ØÇ«hbÓ“Ì+æý—q(š×ÙÖ3Etäº"¶©„ñ÷öQ|u[A¼"0'Á$r»ÇÔbÁß)2¿„ûåô¦èŠýcdÉ<‰k÷£9:6Ò–IÁ^š=¤c`»¼Z¶Ø&½¼âg'kêû*ÑP7Ae¤ú׃2j§ÌªöË4ßÁ7†÷’ÒïänCý{íÅ5s_48h¢Fæ*ñ×Q:u¼óã=bý:gŽ® ȹÉsÁÙó%Ì”«ð ž¬ÍÁ¯,W¿ J9Ê‚.ZøÇçå—î¥môQoz3޽¥ÚÄ‚,òób]»ëž·ÉðTO,–Û¦Eî¶òOX¿ß®Ð·œ¥þ¾]œ™5Ú¢ Û£óD½½7ÌÇć›C÷íG승ð¾sòØ0öC08þUö n9‘|øÊ.ý+çŽuLJ'0äJc²]m> ¦àŒ¿²É-I0ecú•Ò-ÒX„o“ïOê¾²G‰ÕËÂÓ£(Wn‚€J`Fü*2ë5zßçFz!Á Þ·,羿=Bùöëæ\­£iú)°„èZ§5°Ü«ÀÄäué‡þOé`à4”Æêé¦*/<Ò…8{Æá-§èþ¶4Ÿd§ªyUêãËCâ€<µK«A°_*½ W› ”ŸjÐ8hzf€›€Z ÏDÍ4v¦È»ÃúVáJõ_EOnº¤ãª#dsófFZ ¿ø¦-©…ð¶žÿ¿Ý"ùË»)Ëù Ø…s,s™<&?³<ó3O¿µW\Ë»çÂÑâÓµ`ÏÚYb¡·ªø7ة޾©Ñ¤ÃQ3ñ­…F>Ö3ªWbhM2^ø,Ù=°8SVf$ûú™àX› e¹q¤ ã €ËDcÕä¸ya[2±[¹W³LŒ•úýöhîI‡“]ÃðO FëÆ€nì)áâ~wÛGT±±çpõŸÛ…mT©- á$³hˆ xØÊs‡¿hø=«þ ãÕõÚúI¾àPs‚av§|E\ºI®‘öm°èäV'#û‚MŽåüUÆ»-z ®¾`À2|g=üs~~ïó,WrÚªuôrº¨Ý(j¦šfvÛ]f[ŸâæÌü¶„÷Ž-Ugº…Pe„{÷æÛ}ÌpÖ;þ-vfI³<‡2/Á~ÄqXr1YlÄ¡rϱ’ °?0+8a’÷mÉà„ç+3bÛ u"ˆÑ~`ùŠ Lù5/•'‡‚ã!ÄÊŠ4:D²¥Tµ€ô\Þ1½£È.-fZP`wzƒå]o‹]ñ7··f›fb€ïì2²Q†±/À:ÈÁ? Ù3a½Ú›º’À“Õ\¦þ)ï’«?ôra¥3r&ˆz›ôÁÔDmqÒ<#÷ÏZ&}ÞÆè±¢œa$Œa e¬Ùîš 6w† ›(†’¬œPkôA¢ pÇÁ8“ÎÅìW}±|ÕfX…¦*'®ù1•ÆF¤É¿EGUùÉËé¬]%K—r–ó{™Ñƒ$Ô#4Ma|ØÔrQ$ Hט‰.žp$©£­r0_©‡ð´©ø%Häòëi¡…I#©ÌúÏæîýŒûÕð=šžÐØ5't[Pó6´¨ÉŠßÎYiÉì2|z ¤¬‹7f<¯Ð!/ÌÚ<Ò©Á“[|™39‘‘ÃsVí´¢¸¤oà±bëþæ|EØò&lk’ŸûG‚F›æ»‰Líáb Ájò'2W*Å/EÜ1-J¹wp‡5q&¯SQáëgIvuž‹­Ïº,«FÁëî|²chLˆu÷5ÜŸ“UŠ4wYöëqƒ ;ž aœ™f;pÜ—£‰3óåBςҊƒÏ¢òØ4žö ‚ìä_ÇÕmpayœ3´ñÃõ-,mΚ Ið{ªü´}Û]×õÅ-ÚŽ}üÉ#Ý<÷SeåF1^oÔŸj®xðÏ{GèRC=®gµ#ì¨nI`RÐë§ði:3ï‡ZäTìàxwښع8:´35˜beS¹ìÄ{À»nb”lÎ8%530E"pågS]® aÀË7ž 7U‚Á¬Gˆh…ÔÛ;˜j÷¨´?”c*‡Ÿ00„2Qûw©³oXßÀ‹ÐwݹªÕoúTD: Zì wùîšXþâ{l^¶%µ¼¦ܘx¦!lü9µ–Á|å •·˜lÌHGÏ ±Éq<‡¸ò­ýÈ΂ài.~µ“IKY“j§ü}”¢ï®ð U ³0âFbÚ†d—Sps•ÚÌìšS—ö?4À0±L®™¨îûÏ@5B¾â3(ŸAîü”ïÅðרSÜ(òW0IõQä[à dtƘ‡Åz¶][· JŸB<ªA+pJh¿(gB€ã&<áE8À¯Œ¸®º™¤ Ðã š3ÕVø ¶ °Ovÿù`* À´3‡Ê Ê 3'é€;Ö<€•Y±7 »= 䤱¼™Ã~p¤çÅžÉòºå;ºÑ±á2M ’‘4ÔÔ.+?û;HÄ ¬úV為ÿòݽÑ/ -‰;X$‚þ¼ÑMÍjÙuiy‡Kˆ–z÷’§—,¤Úõä'ÅGµx>£7+Ñð¿|LoO˫ĪM.d½ãŒ×;Ó{q‚yøOH endstream endobj 272 0 obj << /Type /FontDescriptor /FontName /OCBIJA+NimbusRomNo9L-Regu /Flags 4 /FontBBox [-168 -281 1000 924] /Ascent 678 /CapHeight 651 /Descent -216 /ItalicAngle 0 /StemV 85 /XHeight 450 /CharSet (/A/B/C/D/E/F/H/I/K/L/M/N/O/P/R/S/T/U/V/W/X/Y/Z/a/b/bracketleft/bracketright/c/colon/comma/d/e/eight/emdash/exclamdown/f/fi/five/fl/four/g/h/hyphen/i/j/k/l/m/n/nine/o/one/p/parenleft/parenright/period/q/questiondown/quotedblleft/quotedblright/quoteleft/quoteright/r/s/semicolon/seven/t/three/two/u/v/w/x/y/z/zero) /FontFile 271 0 R >> endobj 273 0 obj << /Length1 1647 /Length2 10064 /Length3 0 /Length 10907 /Filter /FlateDecode >> stream xÚ­uUTÜ’5î\‚»{ð Áƒ»4Ðh»—àîÁ5hÐàî\‚;C¾oîÜY÷ÿçeæ>t¯>µ«vU]§š†REEÜÜÁ(í`aá`e(ìL]œÕì”>°¨-]ä &¶€WŒ…†FÒ h9ØK™@€‚- 9@ hàäp Ð$ÀN K+€^CM‹‰‰ùŸ–?.S ¯‘Î K{íëW ­Øhy¥ø_ªˆ`²$•Utä”dô2J =Ðéµ S[àÈ hï dX88lÿ>ÌìÍAZsf}åw˜œÁ@3ÐkÐÝ þ1À@';³óëoÈ`édby½ˆdofëbþ§€W»…Ã_^=ì^±W2gˆ³™ ¼fU‘’þ»Nˆ• äOngÐ+ p°xõ4w0sùÓÒ_Ø+Í+ 1Ù; @wÈŸ\¦@€9Èlkâñšû• ìú« g½å?+`8-MœÌmÎί4¯ÜnçŸ}þ[÷&`°­Ç_ÑyýW ˆ3ÐÖ‚…ƒó5§ä5·%È…íϼÈÙ[88Øÿ¶›»€ÿ¹þº ú?3ÃðZ„‰¹ƒ½­Àh¦äyM  ÿß©Ìúïùß ñ¿Eà‹¼ÿ7qÿU£ÿöˆÿ¯ïù_©¥]lm•Lì^àï=x]4&ö€×]øø³llMœÈìÿ 5±ÙzüOÁÿê­ü»êÿäüWøïâö–¯ ±pð°òüm9KƒÜæ* ˆ™ÀÂÄöõòþ²kØ›lAöÀW‘ÿºß× vöÁ>ZÌlìÿ¨Áó7´7ÿ×^uû«6ñj ZLÿöýËYåu* =À@ÀfÒRt0ÿ¯Ã* w€ ¯€…“ýõ1¾>GNnŸÿOÚ¿ˆ8þyV48Üzì¬ìì€×ï|þy2øš÷öfææHbboþ:zÿeø›¹89½*þ×6xíüç¿è4CY˜u0 ¶NÍHƒÔäôHé}ïâ€íÿ.©ûX˜ï_åÐé—¶.PnüXý‰µ~Lð¹Ùcæü´-ϸ3Ø…oK×™ <É#õyËнBÛÊÇ´ÈfX‚žv¨åu:ýa N——]sgcDUͰøl¬•Ë éô†Áÿ­k¾?.õ5Ã×,åk,^V=NMÁÁ!mÂÞÍ5]ïþ¾ÎßðÝÛ$LÙ±È4B®pè´‘þŽÙ$¢ºå^1o?|+›JïçY„"OãîN-¹k>†¢ÿb˜¾G¹6PÐ*¤ðaÛûÖk†ùY1 ªöä+oůœëÃ_͉Å(W³Þ#íšn úÜöõýLËäç¬ë>e ôkÜgqÔHÇæìÍ+ÁsOXÇ<{Q{/{eÖ7áy–¥K OáñËAí*Ï›ÓcÛ>Êü I›$i•oL{†ùwá‡v© øbŠ‚ÈäÖAöF)fÍ·Ô’Ä'ÿ $0’|¨“s© 3·æÎ)„Ã^h'D$2ïÓ¡ÎChТÚ€j?>Aï£oÈõ•_ä§Aèô§_©}3¦ÇYÈG"e$ì0ÔՀùËMÇÅàº8E2ÿZºþ„8›V>F¨7ëå‰gß=¤µù':•÷Á{YƒÝÖ/ŸF[œÖ¯Ø•n&B„-¡| è%xøØPÀ?p6É??ÜâàëEßáŒC‹ÐQ r{X†ÚZU}G<˜ø Ž3Å‚ý`! Ÿ•BbõðˆÍ±å×í~˜Òý5÷ʹra®«3Q Èà¬x[rúQV¯€O¿#›3\ÒY¸‡©[aPïA>9M"n€£*ø“„ú'ŽQS·±&Ùm«gÉÊs’\u#y¸„®qΪx:.}ü&Œ‹ò$÷X]_:#\—M¹ç6Fìj¼N·k°© r¨?ËGµ^orT#*–ýܓꢹä>]rò³ªc’¡ÞËOUGÚÄYþ—„Ž+…F§äU}á„©^‡ŠÙÜútâ9¿ eÕëáùæ©¶”¯·ÓM’_#¦âèìå`‰åÏ!ÍIók—©¸¤÷f]®¬~EÎw6n§AªÈ½´¦N‡oPÛè$±IŽöš}5°¿0~ß/!¬¡n£½œú; µú˜¡ˆ¶3øD~)¢Ö‡/ËMû ¼j@üÄl o×gä6ª§{e™'û&ð*tÍ•îŠÝwÜLfŽÊÂ×ÈjÛúævÅTK:‚¼;/-µ°L‰ï™6eËÞO¸æ7=Ý(Ü@<ƒá^®§¤æ‚5ZEÉ2>^ºÛvwb%¹ø—ñ[}eFÝ\Cïš*oáçë¾0êô¶ŠmÃõŠFküoEL%óF׆s¬zÙa83ðN€Uî—¡%K´M|<øoB¦Á©w¬7ˆ •¨oLjJ/‡ÖS1:L"riÍux õ);ƒ‘Ð/«~Ÿó>÷—uHÕÿÒí þ|Ó×Ïr˵”qƒû¾›×2Do*»°cœ·ÓµòçX^¼e-]•¢Ý ¯Å³ ís´uXˆh­(íÙ ó‹†ëîÂ*+'*¿óân*`˜?ïe³ ÞX`WEbæó¹bìþ:Ï‘¡²=Q¹Ãfƒ“AŒjkØ…·Ë¤°óËb~ýÞð1[“ãg©ñJ1ÆN*¸óÌž¸• ÁDÊÎø± ‹®eÌa”nÓô3ËšCO^_¬â[êZja¨›W’óxV©ëÉ“1løíû‡1|—Odˆ Ÿ–€Fæ o—‚ó‰gY¬,ǤzÊó'u6=h&¾E¤œwawh™•Y?¹ák)Ò=Þ»(½ÇX¶‚ùN¢a²Ô]›q/űÙgîž0à…¤Ãñ<£Ì’Zc{Ü3T!ü­8>Y(^ TIÄg»¿rû1­ªÏ¼é Uð£/þ—‘¶ýmé±z-Ç»ôÕþðáÊ5´µ¼ZeúóM$"ÿ®-îõã>—Õ4g ;œó œf‘›_‘#Ïgµ²žß 1#í²P0nG Òè;ØOáiüc»¥@߃ À úJ@4¥Á.÷\ˆYw½ï]y“-ÌÀš\À2ÊûºŠîX+`ë ³Ê’ølÈÝbd?ϳÁÍä€ÍÅXE_£ƒl0~iœWéP©ñ¼Îk¦ôVà“·¡Ïƒàúµ7ã’«gà dÎêÂÏHº¨&Â#®7ØÁVŒdýCÉ2uV„Ð5f3´÷iö}‹sÉ•e™Ë¬Ž0û}À›{"pÜ+´ª'(¾^öR%f+_/¦â¶-µ"í~JÁís~ÇÝO3àð£ÂLoíζÇ/cˆuoW- ³ÀšT™š±1ÞagžmÓÞ ¤îrq׃¤ÑÏŲ¢ïŸ“•i+Pq¸úx ÌÛ;2³K?äN.¯&¾tþ™—ß$ÅžîÙçK>vzqÞÀ²ÛBAÝ:v2Þ<÷Þ“'fqÞSé‰sïÂÕˆv ½o¶¹(‡Ãj |±Äv+L{mÚÈ'<Ž'ÈE¦·„ãëE¿[ö´™ŠÔBª*ê+|§‹¡°›Ót¦Ô¥ ZT)G,‚Ѭ]æL²Tx™SƽüÒÕ)Ã;Ù‚« =è¹#¯é"™‰ÅLàE¿ÀˆÉwÎ’¢6›j¡!²‘Õ°¢Pò¥9©Âøääß NL˜c9|Ô¨ºú}°µýc–¶yªúr>Ìô=%Üg*=¤<^Ôiù`jBÊ®ŠE9„t¦FP»ŽZ¸ÇÞÈ™B¾(llþu@«·D9þk¡¢²ªóɳ~¹èiÃÆ¯™IÙgšƒ6º‹)&B‰ÓúÍ)d‹YÖoÏ,bý¡˜Çz„Ç&¡¬‘ÛHÓ,ø 1ÔåËvšÝ@rX;k¶î„]Ú>Øôß4|¸ØŒ"³^0ÎÈ/„b±ÄÖªy£1ÏÜ5_¨+žmT HÈí>aû½[æÅŠ>tT¬˜/1{÷Ø{¡$C ½kM»åj¿ÍÁŒUå:æX@´yl‘Èþ`äY LXûðvz%]ÒÑtTÅ¥>k¤¿£+tsÐk=R kŒ¼*«û€59$ѬnLK+‹‹ðLÕ4ŠÒ¸²DY¯0T"\¯ »SH¼ŒÀ[}/ø™£d†q*¨òôPˆ6ôÖí2)^3?ºâ…ä Ù oø#ÛQ)#åáÚMrNMÃh™”t¥ŠY~]»h~ÝúºÒBMà•ªîEŠ¡-g¦à! 7ÚyÞ x u€žÎñ‘Úi ~d¦‹Á›µ<Õ‰}ží„0C­9?Í{tV¿5»@€ú’@I5÷Ü£ õö–2žÿ}|%ø¥Qè¨Ôo6r—¸D t<Ʋ–$lÿÃÝê1ŒˆÒ‹È°û~â"Û_Pi¬Äxsmï1õµÑÊ5hJîã[ôþ}A7g]ÑÏö`3„VN%¨)¨’ÈTù•`RÇ¥X£UÿI5Íç¢FñrЪ4ç§™¹Ñ'2›ÓoµÉ¸J0sö7¹¨–A˜hŸ#­¹š2b܆ ÕèÓSÃ2ìj¤M^1Çž=}°{YW*ëAŒ5Öå˜ð9Y!·àK tŠ!€VŽ©æä>ø ’,Ó' ‡’ýí¸»‘ž;§iV­/àuüEŸyççôC‰‰ø\.á7¹8sÆôqöÍÂB+ªŽw^3)ôïÌUŸu¢YÈëß>.ûÛ(< xšøX'æeyZk2yA¶G†ýE|ÀÚ¸©Õjnôm%ª=3]].µ XŸ,+ŠêÁ‹ÙõøÜ4ïÄ<‡ž}ÇPÁÆ+2t:PƸ½õIÎ`A=Y*ÿ´·utÛ½ƒ—ØßÁ“ƒòÐ@5ivËw³8vmäy&Œ[ÚÝkÚE'¬£iƤŸkáÒÄçàQ›Æ¿§á-éÞ»ßį×l›ì2àé†%}SËO]ËRÛ4·ˆUP^üú­–X¦eðSÄã4w»;fˆíÕï¡cJ b¿O&ß5/JyŠÌ> ÂQ‰sùHœøÍ¾¢ŽO»Áƒ ]E5õ 0å»8㮫><}'µ º_;踆ÂÈ»ñçL‡Þ_su¦hZÙ‚èv;fo¶%¢Y7Ò$\¾;³™ØÜ壧·TõŒ= ±E›\5ŽBô…Lj`ê\Åô9$X|Xê/·Òuáð“_ Èœ£²`ÊÊ  W/PxšKMt?uþ4Å'<®te1wTS£í^×§‹å‘¢ªW‡{ë¤1Z›•”Þ‰üþi³[îí˜f¦—y­h(‘>þyCå€wÌ™Waø½ø¡ŠËÝc8¡É#~ÿk#ÿ¤ˆª8+*ó ¾©ðûË0“A-S[- Ôç–Wõìâê7ZßwͽØkK’â ãÓÙ¥–m}ªÃ9* ¡CiÞ½CWãƒú<Š‚ŸR1ôPoˆ[PeWL‚µ/z‘z¤YÚ+YšˆˆLvV-«Ø ­±5™»6¡uëL—³VÂáϦ9"˜Ö?¿kòU3¨ÓÛpMò„Iñì÷'ì›í¶×cò!›7êÅT°«I—z„Â%]Š8! š¬ Ð`V1· 3ljuc>FByŸvi'ó¡gä²ÿzÆ`_ùJuÍAݶMõ³kO;wѶŸn öÎvÑ>Ng©!`ãëVóa×ÀW¾{ØqX NÃ,¥}0Ò¦r“ÁzöŠŒ0‰ïq­&ÿ&20y'.§pчÓÝ ö[T1zw*÷dœ’CÐHNR8xÑYÌp §¼Öaky¿¢&Dª2¥‚q>ÕÓ֪ဠ¡=×JjçàðYM/®­¯±¸\”ÚEc·q¸'Þ‘z¹ûV¡²‰×¥äü>b½ÐTGþá #²Ë£U­µ_õÊJ7rîã·gòü™½ Ž_>ñ)¨4ó˜S7 «ùz':Ð)9!@`á·"C²X©Sû. £b—JšõDk¢oúùΨPPsÍö],ÐÙ$U¡›ž çZ ^°?}øjð¸m&P¦½[ß,_‹ yô^õÛC¸B®É?"ß mÁ;Û¯Áxý^^µ’÷©Ç°ï©Òé'FFPx•HÙ½=Ò…è’Œ÷S„i+ô)ú]M;]k]¥æÚËF ‰@Ž…,†/ _Šê›ÛÚË_‚ /lZÚ7‰ÐÊËaLÙ¿ðŸùÅ$9+¢žø\›vNÚ–BÓ¡Iìöøl;±g¤®û`(“ùÚ…Õ[ôy€ß©áB]¶È­ÂE“Ô®øߟzÐã…¦´Œ·{Ï>ÔSÜ4Íf0VÏ¡Ëa Ù?zäg:xƒÞPZ.¢€òÇFe,8Tp°á.tø6Ý+#!xƒI¶mR™î‘¾º/Ó­â©WÛ]’“Þ)õ`,µ,¼Ä¸y,¥ÉYÓò𕀋³fÍÖ ~ŒPUg:Ö÷oéÚ(÷gö‡¤»°ŽXF:5¢y{Ôäí/êè—V’šQŽ øw<e_ºÙߊd•_*æ–$Ëy¦›Ç68Xá½–Ðä¶Í'(‘vŠúǵD4Ñœ¥°Ì¸ò¨W0½¹3¹h&Ԍ҈Bšé—d¯•&kK²Ó•áG´Òoú­Äᵎê^ƒØGüùøñw}µÓÁ1™.ÔbÃ]ƒc'/s<§¦äÄÁ)Jï›”'ûJÑï%ûñýÃ{•=1Q§éoÂÍñßôÛŸKïã{¢$ 0<Ç[ѱôÍ¿=d’Úš:EºˆA^š¿sçË5ä)\Q}«ÔV¥ ˜Y@¹_‹è\Ÿ?þðåÓ®8Yà˜Ë>f{Å©<–àDûd ‰³O¦´ûÃVhõ³Ž]G0xjÿÐzÚÊ×! ûyo=s£ÆDÐÜ+(>U;Ò1(·vmRÞ¡“¡*bp¬Þ¯?|Ì“BCÑ«e“²’¥ñšl€üؘ*åF¾#m-´t Uf'Pû”ésbÙº±î“Épl'ýÌvóV£ÿ.–ÍÜfŽY;ø6Ì'xõûúØt¡8äç-×jÍ8ÀLB´æËò¼‹Õð<ÃO'<ù2O?q‰LASúö K†•s‰ žVà*n–K£¼R%{n:rîG+‘dÝgÅ2©"«l¶Níq$™ŒPB¶õèP9>ƒxr'£‚(îb~ FÇ ±{éŒl&c?MrÓ}ùMê/\pºè¢±ÄÚýþH´o&¼èQXMÑ€Bˆ†‡'<“}§_/ÆÍÚ£>>=Gv€»êßf4ìð†aaÁ±súè‚$úÁâ>¸gÜ øÆÃI Ð\é+€ÿ²u\˜­ˆ ÀÓ®È|âOBR!x UóáàÀX0Èz‡oÓz~x=í[òãhñ-W•DHžŽ’í –"cÕ^Úªˆü0 ™y³qAÒnzx/ž¯}%€ÍãÈ¡œ3ÇlQº_­^tsøY•zQùK€¶]Ñ?˜°arUk“#źRaúÀŽw±ã¢4†ÑûvËIr¨Àý‰¿éÍY‹™’„ëB„'4¢÷ÃD¼Ççhw} nÞÀ4‰·;È u,…ûö­7o¿d¬=Ö/W€C'`hð|qˆÇãò<%,J2ÑŒ62wYÀ­©™ÙêjØÓé[1›;~n F¬›Ï$òì)P/¢g$ñÔnz?áQEréÆþ¯ï«÷a´7JÕóÔëË|E1ó°Paâ,{ßñ„ANr›û¾ŸÏãGeoÒ4ÐX¨zŒqè®çU«Ž"^ÜÐûväÀÙß˃|ž y)®'Ò)ë4*ªƒ¤HaÔ°/x³äæs7Èc5ÀzIQ‚è\$Ì_ªŒ0úRS—×ß›”­æ««xpMúÀÙØx°â&†dLR«„q˜ñ†îê\ÜN*7U3šñéö<†¯Ú‘<LJMŋԘŸŒ£`í“§.Ua oFd æuÇ"ñ!gÛ$-úÃñÔajÌ©óu²ûœ¿?w µRl8\Gæ9÷x’G¸7¥86ßÕa2°5q EÁ£1òŸ0è<š‡3 ±Q’à¡gfvø‘6k\¥ÕFZ܆&)J[H–»jÝåÛ‹£Ú¬$.˜Êb†êx‹xÁB”/:Ä”¾ë¶È(“ȹpª!pÞh]v4“ÇWäIY)óUG({°æ6äÌuèÌŠ×yƒ7gý»É)jl"ßH-mÒˆëGÞšãö—±´dïšÈHŽª:‹íe~e©³£âWP¥XDIií,ü…¨?¤´—v¥·ƒG§sé³rŽ…i‘‡ZsZT½JŠËf`ÒÚ¸)EB}@cƒ»îñH½§©_hÓ)eêXˆ4°”ù7L{ND g éüÂZ¶-11ùÔ‰7üÆÕAdžùaêGó;}íÇJÆðŽŒûøQžr‹d®vóÄ}úòÖcØÀ’SAæ§ìZñA5ÞEM?“‘‚’æ/á hÌ hIK¨¨ÅÅ@[(ù˜ ƃƳ«“u®’ܬÚóšÜ5“‚ŠqJü·þfd,:éqÛìêˆéœ«‰šïékQ2´Ì ÷/wånª Sg¾íVF ×1 ²Pg ‰î¸9iWÓ²Ø ×ˆE×ò9§vÌ^Ê‘R*]¤óDi[¤ÊH…™÷|&¸¢î<™}æêÛu›ƒúÜ“^«Htü}Ê·TBà#ËùÆ{Ùu똺ßÈa@o|¦¼Òd°´r_*4¾Æãã%ÄV{“8âw0Z»?=ç‹Sf,ZÍ­‡›ˆßfèGò„ØZ»µV±º„ï{¨N ÿkM!X—¿ƒZ©á’tü´ÑtÆxëWÝÂŽ|³HO:}aù³’_Þü¼€†?‘}ÑQ’ìŒ,íi¼("s¢¬¶Ø™Á“Þ ]w$–ð¾xë —Éa— ‘§©¦4ž*Æ5öäy×%µ‡r•½Íˆy‡¾s4Áå2íŠ{¡ÐƒêŇ|=ýg-ѺïïàÇï÷Î:¯›bG@bÉŽð×KŒÂÖålP(™?ož[DM¦twsÙ%&Ï•š¥Ëe®dò ñVsIJÂQ±ß §‰:yŽŽÀ«Ba©$Ep1)˜‰óUhÕ¼Xïç„­TÌΜ‚¶³ÚsòlÎÝž8ïq´ ŽFÕ£›Æx?þ¬•r–#‘j¢Í¥Óq9CàªÀç’e0OœÇ±?Q§ÕñT÷q©•T  [u&ásœø¯±¼üðíŒÞ½ñŽ0Ìÿ‘ÃPŸÏ’>ÁUvÂ\b®X Ï[3;Ç"Ò.üñ©¢]ß;f=ýùê¶I‡Ü¾Ýׇ¸hmÜö®Rbt"ka÷”*ÙÅñsÒBîg®BíÓÅá2Yh]¹DÇé™>§XšÒ÷‰{]8ˆÂh\‚bª9Ó/ ÆüÃEŽ™™³ pŸ³³W-vx?^‰Ÿ©ú“¾H<Û|¸Hé!þ<âøƒ¬w’ õÄ›ÔÖÛÔú°åz°.廸Zƒh^].ïÖèõ›†gví¸lAQ «ÏÔnA>p‡ –rUÁ¬©Kâc•kª”";/õs‚MW¿OµÝ\ ®m»&\äV¢€\íHL½çî×ãZØ‚1¨âPÔ&é¹™aråh+zߥTãMÛJ$[Ù±‚®ln²;ÆoJFU†rVÇB[Áâ…oŸÇ9ׯÞþ†L4ùÅè" ³ÁÖ%Ï_QÂ~†EQö‹°”‰nÛ¦åÏ;³´2ÛÞk<ÉÆó H!¶Z4 ßDáãÃÖŒhÕû‘Nzæßײ ¸ô<$›Î–+úôºþ Ñø†Ì«<6Îä¶Ð:¥éøkB)6HÉo䃚€v&"Uí†û„­³ŒùÌqÖ„âO³7¬ºdO•8JÖ»tt(dG‡»çÚnŸ˜‰¯ž¤QϺOï]Å3Å(ãSl]Tò’¿k=RRÏ鉬ßã=A—Ä èã9LÉkÅ(+I!X]Žqþ œ80ÒЖ^HاñQ†ù©&¼€âšOïVÚõDf"ö‚¿Ž_µ •-›3P0MÄ‘ 5Ò« ©èÓjÇLnJSíþÍE1øD Ǽ˜ÿm¸¬%¦A€²,×M@xßôP=™8Dx¤Ön~“*ñA>ObË4Û+T ¶:¥m^45¾³1ˆøÒ炦=èÔCîÕÑèã †åí®òÁt (¢ö ¬wŒ¥:ç¿„ç•xú¹* p(ÚÈ$›fT¯åAuН‘ ô&O ÎKÚ~å ®CÝoÄù¡ÈlÄÊÏ_˜\«ÁXü¿å1¡éáï åÄ0»”¼5ãNeΣ‹Ð|–rÃ@í?¦ž‘=sÙ0²Œ,¾.!p[ÊÌwLQ$j~·ýÅqäûJ<ø|î‹ü’É·¾ ‘ỡBîè½ed­NnÒŽ<k’'ßÙ$™t7¡#]Õ #çNúZÖŒzoqš¦øÀ.iE«F­%!gNGœ\Ö›_¾Ü ŠNúÈHP¸ÈH¨px~btê qa•|ñW¨/ŸvÐËXi~ÊÔsMIzéÈÊBØ Ö jZ6PÎP©Mø|F†Mµ 7Söfæs›2¸RPm´yŠ Õ86òʶ¦,ðkïw„ñŒÉèˆýú@ɡط“ ÜHƒlËy…1ß–fôè#Þ”kÞæóDnRKE=?p‘´-YŠ^’^æÚÔò-CQ‚~¡~èÔ¶OöÂÏÿ…µ]"ì㳃œHáxX:ï<냲Cð³”LÜ~ uËü“ø °G‚œ XÞpæÝŒ-wèi,÷»3ƒÈvÁ «.¬µ+*)å¢Íé)¯KБ%¬k§äF=aæMW tlò5ÂÆPþ¬OD>ŠJhP„† â½v€ØÅ‹^üQbQ  ç]6óB¦º@>®¸{ƒ€¸.Úë¥ ¬ Ë·'AÀm¾EÞ“³qö.fŽ [9u) B܉*i-†v‘¶äçOôÇXM½AÈUÆ=²J1Bdãm¾%º(zmÁI¿™¾Їë³ÐVÁU·lP‡½-Œ÷‰>J̪Ôjøb²@VÊ­«.\{ÿ£8/•5íSF]¢+/2öò˜O9Any®‡a©¢à¥½¼ÏÉo‡çmêÒ>2ÜbJÎS6XL}sщ6C„iéÀ×uš·öéJu¡ÙÓ·yMpúXZ<_O|Ø…æHýËÒ5Ó«ºëÄ¥ˆ®öÔ¤*›A¦ápc`"z ™‹É:6`•8­úÌå ê@×b6ôÀüp ž/€ é…<Ô²žÍÊV0^—zÚV$åïqÒ^£,Sb±…µ"Qq.¬çó;VB•D¢@¯j‡’|»<Š)ÈüB.À¡ÊêÀÝ6ü½&»ÓèA׫jþSúQß̪Sþº=‰;‰÷Ú²´Ä¹ˆ†ÕDÓ®ðÒKfXl–"ä’a|Éi…3´cÑQñ"—éù©¯ºú­4û Øpï5ûÌ$KeGsA±Ð¾¼`8çl†Óü™ç,ä7†ˆ§GïøGÞõÛ¤‰ÅüõЋÈ©/„e5-D ãN¯s@&¯’J4‚Jmïi20XŽÝ¼'ÓÚ¬„§vãKÒO”-"°¼¥íÐëc‹£Ø¹O*·¹t¯üèu¹­ ¹„’¯ôÖ×Þ;SO3WhYß¶œ”G#·£·×ûŠâB™ôÂÇ :T±=ô8§~ÉKx¡Óy‚ã_<Îtñó¿Q*5êƒýøã(<ûÐ’X£Þ9Fq½H|}ØÛC{ÉØZŠï2}æmØ¥‰™b ‡‰º)ïY˜-|Ó!è]œ¨„* ×Rëýƒô³ß¤„Ÿ–ü³Gäû™tFCNžô´€FMxz^õ~°¸ì 9N*Kxï½WŸ¥Â]#Ùc”߃_d¡ÃM€c ¿$'ºš¹m,¢‡¹ CÝ..ÛO;mçâQþtŒrŽöj2'+E9$ÙÕXúë¯$þ™ö !m[xuI&ÿÍÀõñ\˜JEcÐ6•¯Æt'éÉB£Ziç/—½DîhçèmÉ7Ê—Í¢¤ÔŸñD¼Yì¹ìð[ª×wÊá ‹•iö¦G¬¨Ÿ ªì$áNZÞ2ý<ZôƒÓbOøöÍÉ`bá°õ LågÞ¨Ø/-€só¯|Îüp®Y6{9üSUw)`¦œ‹´’5øyˆsNǘû‘¹VÀ•©pŽàBÌsþEþ\^Ñö½ûVF2é7)ï#;@bÿïõCVC²CÛo÷VxÎ`ô%Vž¥¹jµÕ*à­lK8þ‘T\ÝXn4À®ÚuôCOåKu¶¥UzðÓo[~Ž7¸d"ˆQÌ F%œþ6£]R»Eý¤ a¾^Ñ»Öc%À›«ÒóçŽ)ï2²à í‚bé0’ϦÆÖúsuúÏà+&„„ßûÍÉFv~IRV¯p}4Ý–³8Œ6±äìœµŠ‚ÏÆìƒdc~Š}1ö¦)Êž\ü¾+ÿ¦ÓûÐ<"mNÍ^“¬ü´Ò9HCZÂë™Pæ0lië}¡·çmÜzÏ"¿¸»šÅö–~YËe_಺M‰Â =Š7È$a£¨–¥õ i¥ã'OÑ3¢To%Æš#Ü©=i^ ŠwE©š„¯…ï0ú5{$õ»›Ì7O@“Ò܇«¡ž ö© ;SW1<6Ôº-êâÑ\—)3Τzlòƒ"þ{ŠwãÍÙó †ÌcÊ•™Ë=–ò`'cçë/¤ÉÈ•)p5ó×zÅMXÉ¡ocxز¾R¨Y)”àIq‹=š£:43q…³o4„JÜWi„¨8{=á™`˜Ú{¯LN L!±w£.‹ùyÃ>DT¢°fƒ!„é.»œªŸgB¶c† mv~-Æ·YNaüÑÜ-Z/³5ùfÅa/v*¶Òý|6‚L¢%é~“„¾Œž£¿ýLŠ7ϼžÄR‚ÿžŽcñÉgçk ~!ÑHo«J“×;œ6[½à ´ÄÞfw2æý¥ý;/Ô€Ã[6"¢{aWÊp-àti]¼ö·à ®™6tÔt?8DØ*ƒþø§VÒ[Ö’ÏÃÉ3µK˜ÌK+Ž…¨EI®bï3tÌÝ명ü2ï-÷zÆÉþo%åxì„sø™Ë¤åpìÆo¦²(*zH£~æÒ&²Z61ܲ z§¨žuÒÍI‘‹•äîÚVÞלçö¹[3,jÅ8Û_7eî7œÄé .Ø\éãË1Ôø.ÆRõ$Ï3&‰{+ &«€¾óe䯢µOÿž÷ÈÂ&ËýØÿÙ½þ¤‹ÎᎷš&V+Öæ?ž…¯e endstream endobj 274 0 obj << /Type /FontDescriptor /FontName /ALRKBW+NimbusRomNo9L-ReguItal /Flags 4 /FontBBox [-169 -270 1010 924] /Ascent 669 /CapHeight 669 /Descent -193 /ItalicAngle -15 /StemV 78 /XHeight 441 /CharSet (/A/L/P/R/S/a/b/c/comma/d/e/f/fi/g/h/hyphen/i/k/l/m/n/o/p/period/r/s/t/u/v/x/y) /FontFile 273 0 R >> endobj 247 0 obj << /Type /Encoding /Differences [2/fi/fl 33/exclam/quotedbl/numbersign/dollar/percent 39/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I 75/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft 93/bracketright 95/underscore/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/braceleft/bar/braceright 147/quotedblleft/quotedblright 151/emdash 161/exclamdown 191/questiondown] >> endobj 70 0 obj << /Type /Font /Subtype /Type1 /BaseFont /SYFPBV+CMMI10 /FontDescriptor 256 0 R /FirstChar 60 /LastChar 62 /Widths 246 0 R >> endobj 158 0 obj << /Type /Font /Subtype /Type1 /BaseFont /HAJUBM+CMSY10 /FontDescriptor 258 0 R /FirstChar 110 /LastChar 110 /Widths 244 0 R >> endobj 99 0 obj << /Type /Font /Subtype /Type1 /BaseFont /IUEMPT+CMSY5 /FontDescriptor 260 0 R /FirstChar 99 /LastChar 100 /Widths 245 0 R >> endobj 33 0 obj << /Type /Font /Subtype /Type1 /BaseFont /HRAHZV+NimbusMonL-Bold /FontDescriptor 262 0 R /FirstChar 46 /LastChar 122 /Widths 250 0 R /Encoding 247 0 R >> endobj 34 0 obj << /Type /Font /Subtype /Type1 /BaseFont /VQGLEG+NimbusMonL-Regu /FontDescriptor 264 0 R /FirstChar 33 /LastChar 125 /Widths 249 0 R /Encoding 247 0 R >> endobj 29 0 obj << /Type /Font /Subtype /Type1 /BaseFont /QRWWCH+NimbusSanL-Regu /FontDescriptor 266 0 R /FirstChar 2 /LastChar 151 /Widths 254 0 R /Encoding 247 0 R >> endobj 30 0 obj << /Type /Font /Subtype /Type1 /BaseFont /RCFEMR+NimbusSanL-ReguItal /FontDescriptor 268 0 R /FirstChar 46 /LastChar 115 /Widths 253 0 R /Encoding 247 0 R >> endobj 32 0 obj << /Type /Font /Subtype /Type1 /BaseFont /GARPUW+NimbusRomNo9L-Medi /FontDescriptor 270 0 R /FirstChar 2 /LastChar 151 /Widths 251 0 R /Encoding 247 0 R >> endobj 31 0 obj << /Type /Font /Subtype /Type1 /BaseFont /OCBIJA+NimbusRomNo9L-Regu /FontDescriptor 272 0 R /FirstChar 2 /LastChar 191 /Widths 252 0 R /Encoding 247 0 R >> endobj 56 0 obj << /Type /Font /Subtype /Type1 /BaseFont /ALRKBW+NimbusRomNo9L-ReguItal /FontDescriptor 274 0 R /FirstChar 2 /LastChar 121 /Widths 248 0 R /Encoding 247 0 R >> endobj 35 0 obj << /Type /Pages /Count 6 /Parent 275 0 R /Kids [27 0 R 53 0 R 59 0 R 62 0 R 66 0 R 73 0 R] >> endobj 80 0 obj << /Type /Pages /Count 6 /Parent 275 0 R /Kids [77 0 R 82 0 R 87 0 R 92 0 R 96 0 R 101 0 R] >> endobj 109 0 obj << /Type /Pages /Count 6 /Parent 275 0 R /Kids [105 0 R 112 0 R 116 0 R 119 0 R 126 0 R 129 0 R] >> endobj 135 0 obj << /Type /Pages /Count 6 /Parent 275 0 R /Kids [133 0 R 137 0 R 142 0 R 146 0 R 151 0 R 156 0 R] >> endobj 163 0 obj << /Type /Pages /Count 3 /Parent 275 0 R /Kids [160 0 R 165 0 R 241 0 R] >> endobj 275 0 obj << /Type /Pages /Count 27 /Kids [35 0 R 80 0 R 109 0 R 135 0 R 163 0 R] >> endobj 276 0 obj << /Type /Outlines /First 169 0 R /Last 238 0 R /Count 12 >> endobj 238 0 obj << /Title 239 0 R /A 237 0 R /Parent 276 0 R /Prev 232 0 R >> endobj 235 0 obj << /Title 236 0 R /A 234 0 R /Parent 232 0 R >> endobj 232 0 obj << /Title 233 0 R /A 231 0 R /Parent 276 0 R /Prev 229 0 R /Next 238 0 R /First 235 0 R /Last 235 0 R /Count -1 >> endobj 229 0 obj << /Title 230 0 R /A 228 0 R /Parent 276 0 R /Prev 223 0 R /Next 232 0 R >> endobj 226 0 obj << /Title 227 0 R /A 225 0 R /Parent 223 0 R >> endobj 223 0 obj << /Title 224 0 R /A 222 0 R /Parent 276 0 R /Prev 220 0 R /Next 229 0 R /First 226 0 R /Last 226 0 R /Count -1 >> endobj 220 0 obj << /Title 221 0 R /A 219 0 R /Parent 276 0 R /Prev 214 0 R /Next 223 0 R >> endobj 217 0 obj << /Title 218 0 R /A 216 0 R /Parent 214 0 R >> endobj 214 0 obj << /Title 215 0 R /A 213 0 R /Parent 276 0 R /Prev 211 0 R /Next 220 0 R /First 217 0 R /Last 217 0 R /Count -1 >> endobj 211 0 obj << /Title 212 0 R /A 210 0 R /Parent 276 0 R /Prev 196 0 R /Next 214 0 R >> endobj 208 0 obj << /Title 209 0 R /A 207 0 R /Parent 205 0 R >> endobj 205 0 obj << /Title 206 0 R /A 204 0 R /Parent 196 0 R /Prev 199 0 R /First 208 0 R /Last 208 0 R /Count -1 >> endobj 202 0 obj << /Title 203 0 R /A 201 0 R /Parent 199 0 R >> endobj 199 0 obj << /Title 200 0 R /A 198 0 R /Parent 196 0 R /Next 205 0 R /First 202 0 R /Last 202 0 R /Count -1 >> endobj 196 0 obj << /Title 197 0 R /A 195 0 R /Parent 276 0 R /Prev 193 0 R /Next 211 0 R /First 199 0 R /Last 205 0 R /Count -2 >> endobj 193 0 obj << /Title 194 0 R /A 192 0 R /Parent 276 0 R /Prev 181 0 R /Next 196 0 R >> endobj 190 0 obj << /Title 191 0 R /A 189 0 R /Parent 181 0 R /Prev 187 0 R >> endobj 187 0 obj << /Title 188 0 R /A 186 0 R /Parent 181 0 R /Prev 184 0 R /Next 190 0 R >> endobj 184 0 obj << /Title 185 0 R /A 183 0 R /Parent 181 0 R /Next 187 0 R >> endobj 181 0 obj << /Title 182 0 R /A 180 0 R /Parent 276 0 R /Prev 172 0 R /Next 193 0 R /First 184 0 R /Last 190 0 R /Count -3 >> endobj 178 0 obj << /Title 179 0 R /A 177 0 R /Parent 172 0 R /Prev 175 0 R >> endobj 175 0 obj << /Title 176 0 R /A 174 0 R /Parent 172 0 R /Next 178 0 R >> endobj 172 0 obj << /Title 173 0 R /A 171 0 R /Parent 276 0 R /Prev 169 0 R /Next 181 0 R /First 175 0 R /Last 178 0 R /Count -2 >> endobj 169 0 obj << /Title 170 0 R /A 168 0 R /Parent 276 0 R /Next 172 0 R >> endobj 277 0 obj << /Names [(label-basic-mapping) 108 0 R (label-elements) 69 0 R (label-intro) 55 0 R (label-module-ZConfig) 90 0 R (label-module-ZConfig.cmdline) 153 0 R (label-module-ZConfig.datatypes) 144 0 R] /Limits [(label-basic-mapping) (label-module-ZConfig.datatypes)] >> endobj 278 0 obj << /Names [(label-module-ZConfig.loader) 148 0 R (label-module-ZConfig.substitution) 71 0 R (label-schema-components) 94 0 R (label-schema-dtd) 162 0 R (label-standard-components) 107 0 R (label-standard-datatypes) 98 0 R] /Limits [(label-module-ZConfig.loader) (label-standard-datatypes)] >> endobj 279 0 obj << /Names [(label-syntax) 57 0 R (label-writing-schema) 68 0 R (page002) 36 0 R (page003) 37 0 R (page004) 38 0 R (page005) 39 0 R] /Limits [(label-syntax) (page005)] >> endobj 280 0 obj << /Names [(page006) 75 0 R (page007) 79 0 R (page008) 84 0 R (page009) 89 0 R (page010) 40 0 R (page011) 41 0 R] /Limits [(page006) (page011)] >> endobj 281 0 obj << /Names [(page012) 103 0 R (page013) 42 0 R (page014) 114 0 R (page015) 43 0 R (page016) 121 0 R (page017) 44 0 R] /Limits [(page012) (page017)] >> endobj 282 0 obj << /Names [(page018) 131 0 R (page019) 45 0 R (page020) 139 0 R (page021) 46 0 R (page022) 47 0 R (page023) 48 0 R] /Limits [(page018) (page023)] >> endobj 283 0 obj << /Names [(page024) 49 0 R (page025) 50 0 R (page026) 167 0 R (page027) 243 0 R] /Limits [(page024) (page027)] >> endobj 284 0 obj << /Kids [277 0 R 278 0 R 279 0 R 280 0 R 281 0 R 282 0 R] /Limits [(label-basic-mapping) (page023)] >> endobj 285 0 obj << /Kids [283 0 R] /Limits [(page024) (page027)] >> endobj 286 0 obj << /Kids [284 0 R 285 0 R] /Limits [(label-basic-mapping) (page027)] >> endobj 287 0 obj << /Dests 286 0 R >> endobj 288 0 obj << /Type /Catalog /Pages 275 0 R /Outlines 276 0 R /Names 287 0 R /PageMode /UseOutlines >> endobj 289 0 obj << /Producer (pdfTeX-1.40.3) /Author (Zope Corporation) /Title (ZConfig Package Reference) /Creator (TeX) /CreationDate (D:20100413091707-04'00') /ModDate (D:20100413091707-04'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX using libpoppler, Version 3.141592-1.40.3-2.2 (Web2C 7.5.6) kpathsea version 3.5.6) >> endobj xref 0 290 0000000000 65535 f 0000002420 00000 n 0000002571 00000 n 0000002702 00000 n 0000002832 00000 n 0000002967 00000 n 0000003102 00000 n 0000003233 00000 n 0000003367 00000 n 0000003502 00000 n 0000003637 00000 n 0000003768 00000 n 0000003900 00000 n 0000004033 00000 n 0000004170 00000 n 0000004305 00000 n 0000004442 00000 n 0000004574 00000 n 0000004706 00000 n 0000004842 00000 n 0000004974 00000 n 0000005106 00000 n 0000005241 00000 n 0000005373 00000 n 0000005503 00000 n 0000008282 00000 n 0000005636 00000 n 0000002141 00000 n 0000000015 00000 n 0000181001 00000 n 0000181170 00000 n 0000181516 00000 n 0000181344 00000 n 0000180661 00000 n 0000180831 00000 n 0000181864 00000 n 0000008598 00000 n 0000010770 00000 n 0000013016 00000 n 0000016433 00000 n 0000032885 00000 n 0000036184 00000 n 0000041742 00000 n 0000046227 00000 n 0000051321 00000 n 0000056137 00000 n 0000062115 00000 n 0000065874 00000 n 0000069424 00000 n 0000072769 00000 n 0000074534 00000 n 0000008413 00000 n 0000008772 00000 n 0000008148 00000 n 0000005766 00000 n 0000008653 00000 n 0000181688 00000 n 0000008713 00000 n 0000010825 00000 n 0000010662 00000 n 0000008890 00000 n 0000013071 00000 n 0000012908 00000 n 0000010931 00000 n 0000016274 00000 n 0000016608 00000 n 0000016147 00000 n 0000013177 00000 n 0000016488 00000 n 0000016548 00000 n 0000180232 00000 n 0000072825 00000 n 0000019688 00000 n 0000019525 00000 n 0000016750 00000 n 0000019633 00000 n 0000023132 00000 n 0000022969 00000 n 0000019818 00000 n 0000023077 00000 n 0000181974 00000 n 0000026095 00000 n 0000025932 00000 n 0000023274 00000 n 0000026040 00000 n 0000029530 00000 n 0000029733 00000 n 0000029403 00000 n 0000026225 00000 n 0000029678 00000 n 0000056193 00000 n 0000033000 00000 n 0000032777 00000 n 0000029875 00000 n 0000032940 00000 n 0000036299 00000 n 0000036076 00000 n 0000033142 00000 n 0000036239 00000 n 0000180519 00000 n 0000039124 00000 n 0000038956 00000 n 0000036453 00000 n 0000039067 00000 n 0000041922 00000 n 0000041630 00000 n 0000039243 00000 n 0000041798 00000 n 0000041860 00000 n 0000182085 00000 n 0000046046 00000 n 0000043413 00000 n 0000043244 00000 n 0000042029 00000 n 0000043356 00000 n 0000046283 00000 n 0000045914 00000 n 0000043520 00000 n 0000048138 00000 n 0000047969 00000 n 0000046402 00000 n 0000048081 00000 n 0000050602 00000 n 0000050778 00000 n 0000050947 00000 n 0000051377 00000 n 0000050454 00000 n 0000048245 00000 n 0000053407 00000 n 0000053238 00000 n 0000051496 00000 n 0000053350 00000 n 0000056249 00000 n 0000056025 00000 n 0000053514 00000 n 0000182202 00000 n 0000060052 00000 n 0000059883 00000 n 0000056380 00000 n 0000059995 00000 n 0000061964 00000 n 0000062228 00000 n 0000061832 00000 n 0000060183 00000 n 0000062171 00000 n 0000065987 00000 n 0000065762 00000 n 0000062347 00000 n 0000065930 00000 n 0000069268 00000 n 0000069537 00000 n 0000069136 00000 n 0000066118 00000 n 0000069480 00000 n 0000072618 00000 n 0000072881 00000 n 0000072486 00000 n 0000069668 00000 n 0000180374 00000 n 0000074652 00000 n 0000074422 00000 n 0000073025 00000 n 0000074590 00000 n 0000182319 00000 n 0000075754 00000 n 0000075585 00000 n 0000074747 00000 n 0000075697 00000 n 0000075837 00000 n 0000184795 00000 n 0000075882 00000 n 0000075917 00000 n 0000184663 00000 n 0000075962 00000 n 0000076005 00000 n 0000184584 00000 n 0000076050 00000 n 0000076108 00000 n 0000184505 00000 n 0000076153 00000 n 0000076207 00000 n 0000184373 00000 n 0000076252 00000 n 0000076303 00000 n 0000184294 00000 n 0000076348 00000 n 0000076388 00000 n 0000184201 00000 n 0000076433 00000 n 0000076475 00000 n 0000184122 00000 n 0000076520 00000 n 0000076574 00000 n 0000184029 00000 n 0000076619 00000 n 0000076667 00000 n 0000183897 00000 n 0000076712 00000 n 0000076769 00000 n 0000183779 00000 n 0000076814 00000 n 0000076862 00000 n 0000183714 00000 n 0000076907 00000 n 0000076952 00000 n 0000183596 00000 n 0000076997 00000 n 0000077046 00000 n 0000183531 00000 n 0000077091 00000 n 0000077139 00000 n 0000183438 00000 n 0000077184 00000 n 0000077239 00000 n 0000183306 00000 n 0000077284 00000 n 0000077345 00000 n 0000183241 00000 n 0000077390 00000 n 0000077425 00000 n 0000183148 00000 n 0000077470 00000 n 0000077540 00000 n 0000183016 00000 n 0000077585 00000 n 0000077650 00000 n 0000182951 00000 n 0000077695 00000 n 0000077733 00000 n 0000182858 00000 n 0000077778 00000 n 0000077850 00000 n 0000182726 00000 n 0000077895 00000 n 0000077962 00000 n 0000182661 00000 n 0000078007 00000 n 0000078040 00000 n 0000182582 00000 n 0000078085 00000 n 0000078594 00000 n 0000078425 00000 n 0000078139 00000 n 0000078537 00000 n 0000078677 00000 n 0000078700 00000 n 0000078731 00000 n 0000179697 00000 n 0000078766 00000 n 0000079237 00000 n 0000079628 00000 n 0000079955 00000 n 0000080534 00000 n 0000081264 00000 n 0000081564 00000 n 0000082142 00000 n 0000083950 00000 n 0000084183 00000 n 0000085420 00000 n 0000085650 00000 n 0000086997 00000 n 0000087239 00000 n 0000099950 00000 n 0000100258 00000 n 0000119676 00000 n 0000120262 00000 n 0000129395 00000 n 0000129795 00000 n 0000133931 00000 n 0000134197 00000 n 0000148880 00000 n 0000149292 00000 n 0000167813 00000 n 0000168355 00000 n 0000179383 00000 n 0000182412 00000 n 0000182504 00000 n 0000184874 00000 n 0000185156 00000 n 0000185466 00000 n 0000185653 00000 n 0000185817 00000 n 0000185984 00000 n 0000186150 00000 n 0000186282 00000 n 0000186403 00000 n 0000186472 00000 n 0000186561 00000 n 0000186599 00000 n 0000186708 00000 n trailer << /Size 290 /Root 288 0 R /Info 289 0 R /ID [<90874B4389AB3A5518592246B83738E2> <90874B4389AB3A5518592246B83738E2>] >> startxref 187044 %%EOF zope2.13-2.13.21/source/ZConfig/doc/README.txt0000644000175000017500000000121612214017462017240 0ustar arnauarnauThe zconfig.tex document in this directory contains the reference documentation for the ZConfig package. This documentation is written using the Python LaTeX styles. To format the documentation, get a copy of the Python documentation tools (the Doc/ directory from the Python sources), and create a symlink to the tools/mkhowto script from some convenient bin/ directory. You will need to have a fairly complete set of documentation tools installed on your platform; see http://www.python.org/doc/current/doc/doc.html for more information on the tools. This documentation requires the latest version of the Python documentation tools from CVS. zope2.13-2.13.21/source/ZConfig/doc/Makefile0000644000175000017500000000273212214017462017206 0ustar arnauarnau############################################################################## # # Copyright (c) 2002, 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # Rules to convert the documentation to a single PDF file. # # PostScript, HTML, and plain text output are also supported, though # PDF is the default. # # See the README.txt file for information on the mkhowto program used # to generate the formatted versions of the documentation. .PHONY: default all html pdf ps text default: pdf all: html pdf ps text html: zconfig/zconfig.html pdf: zconfig.pdf ps: zconfig.ps text: zconfig.txt zconfig/zconfig.html: zconfig.tex schema.dtd xmlmarkup.perl mkhowto --html $< zconfig.pdf: zconfig.tex schema.dtd xmlmarkup.sty mkhowto --pdf $< zconfig.ps: zconfig.tex schema.dtd xmlmarkup.sty mkhowto --postscript $< zconfig.txt: zconfig.tex schema.dtd xmlmarkup.sty mkhowto --text $< clean: rm -f zconfig.l2h zconfig.l2h~ clobber: clean rm -f zconfig.pdf zconfig.ps zconfig.txt rm -rf zconfig zope2.13-2.13.21/source/ZConfig/doc/xmlmarkup.perl0000644000175000017500000000350512214017462020451 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # LaTeX2HTML support for the xmlmarkup package. Doesn't do indexing. package main; sub do_cmd_element{ local($_) = @_; my $name = next_argument(); return "$name" . $_; } sub do_cmd_attribute{ local($_) = @_; my $name = next_argument(); return "$name" . $_; } sub do_env_attributedesc{ local($_) = @_; my $name = next_argument(); my $valuetype = next_argument(); return ("\n
" . "\n
$name" . "   ($valuetype)" . "\n
" . $_ . "
"); } sub do_env_elementdesc{ local($_) = @_; my $name = next_argument(); my $contentmodel = next_argument(); return ("\n
" . "\n
<" . "$name>" . "\n
$contentmodel" . "\n
</" . "$name>" . "\n
" . $_ . "
"); } 1; # Must end with this, because Perl is bogus. zope2.13-2.13.21/source/ZConfig/doc/schema.dtd0000644000175000017500000000655412214017462017511 0ustar arnauarnau zope2.13-2.13.21/source/ZConfig/COPYRIGHT.txt0000644000175000017500000000004012214017462017100 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/ZConfig/buildout.cfg0000644000175000017500000000017712214017462017312 0ustar arnauarnau[buildout] develop = . parts = test prefer-final = true [test] recipe = zc.recipe.testrunner eggs = ZConfig defaults = ['-1'] zope2.13-2.13.21/source/ZConfig/bootstrap.py0000644000175000017500000002352212214017462017370 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess from optparse import OptionParser if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: quote = str # See zc.buildout.easy_install._has_broken_dash_S for motivation and comments. stdout, stderr = subprocess.Popen( [sys.executable, '-Sc', 'try:\n' ' import ConfigParser\n' 'except ImportError:\n' ' print 1\n' 'else:\n' ' print 0\n'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() has_broken_dash_S = bool(int(stdout.strip())) # In order to be more robust in the face of system Pythons, we want to # run without site-packages loaded. This is somewhat tricky, in # particular because Python 2.6's distutils imports site, so starting # with the -S flag is not sufficient. However, we'll start with that: if not has_broken_dash_S and 'site' in sys.modules: # We will restart with python -S. args = sys.argv[:] args[0:0] = [sys.executable, '-S'] args = map(quote, args) os.execv(sys.executable, args) # Now we are running with -S. We'll get the clean sys.path, import site # because distutils will do it later, and then reset the path and clean # out any namespace packages from site-packages that might have been # loaded by .pth files. clean_path = sys.path[:] import site sys.path[:] = clean_path for k, v in sys.modules.items(): if k in ('setuptools', 'pkg_resources') or ( hasattr(v, '__path__') and len(v.__path__)==1 and not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))): # This is a namespace package. Remove it. sys.modules.pop(k) is_jython = sys.platform.startswith('java') setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py' distribute_source = 'http://python-distribute.org/distribute_setup.py' # parsing arguments def normalize_to_url(option, opt_str, value, parser): if value: if '://' not in value: # It doesn't smell like a URL. value = 'file://%s' % ( urllib.pathname2url( os.path.abspath(os.path.expanduser(value))),) if opt_str == '--download-base' and not value.endswith('/'): # Download base needs a trailing slash to make the world happy. value += '/' else: value = None name = opt_str[2:].replace('-', '_') setattr(parser.values, name, value) usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --setup-source and --download-base to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="use_distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("--setup-source", action="callback", dest="setup_source", callback=normalize_to_url, nargs=1, type="string", help=("Specify a URL or file location for the setup file. " "If you use Setuptools, this will default to " + setuptools_source + "; if you use Distribute, this " "will default to " + distribute_source +".")) parser.add_option("--download-base", action="callback", dest="download_base", callback=normalize_to_url, nargs=1, type="string", help=("Specify a URL or directory for downloading " "zc.buildout and either Setuptools or Distribute. " "Defaults to PyPI.")) parser.add_option("--eggs", help=("Specify a directory for storing eggs. Defaults to " "a temporary directory that is deleted when the " "bootstrap script completes.")) parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, help=("Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas.")) parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout's main function if options.config_file is not None: args += ['-c', options.config_file] if options.eggs: eggs_dir = os.path.abspath(os.path.expanduser(options.eggs)) else: eggs_dir = tempfile.mkdtemp() if options.setup_source is None: if options.use_distribute: options.setup_source = distribute_source else: options.setup_source = setuptools_source if options.accept_buildout_test_releases: args.append('buildout:accept-buildout-test-releases=true') args.append('bootstrap') try: import pkg_resources import setuptools # A flag. Sometimes pkg_resources is installed alone. if not hasattr(pkg_resources, '_distribute'): raise ImportError except ImportError: ez_code = urllib2.urlopen( options.setup_source).read().replace('\r\n', '\n') ez = {} exec ez_code in ez setup_args = dict(to_dir=eggs_dir, download_delay=0) if options.download_base: setup_args['download_base'] = options.download_base if options.use_distribute: setup_args['no_fake'] = True ez['use_setuptools'](**setup_args) if 'pkg_resources' in sys.modules: reload(sys.modules['pkg_resources']) import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) cmd = [quote(sys.executable), '-c', quote('from setuptools.command.easy_install import main; main()'), '-mqNxd', quote(eggs_dir)] if not has_broken_dash_S: cmd.insert(1, '-S') find_links = options.download_base if not find_links: find_links = os.environ.get('bootstrap-testing-find-links') if find_links: cmd.extend(['-f', quote(find_links)]) if options.use_distribute: setup_requirement = 'distribute' else: setup_requirement = 'setuptools' ws = pkg_resources.working_set setup_requirement_path = ws.find( pkg_resources.Requirement.parse(setup_requirement)).location env = dict( os.environ, PYTHONPATH=setup_requirement_path) requirement = 'zc.buildout' version = options.version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): for part in parsed_version: if (part[:1] == '*') and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex( search_path=[setup_requirement_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = '=='.join((requirement, version)) cmd.append(requirement) if is_jython: import subprocess exitcode = subprocess.Popen(cmd, env=env).wait() else: # Windows prefers this, apparently; otherwise we would prefer subprocess exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env])) if exitcode != 0: sys.stdout.flush() sys.stderr.flush() print ("An error occurred when trying to install zc.buildout. " "Look above this message for any errors that " "were output by easy_install.") sys.exit(exitcode) ws.add_entry(eggs_dir) ws.require(requirement) import zc.buildout.buildout zc.buildout.buildout.main(args) if not options.eggs: # clean up temporary egg directory shutil.rmtree(eggs_dir) zope2.13-2.13.21/source/transaction/0000755000175000017500000000000012214017513015760 5ustar arnauarnauzope2.13-2.13.21/source/transaction/setup.py0000644000175000017500000000360212214017513017473 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## __version__ = '1.1.1' import os from ez_setup import use_setuptools use_setuptools() from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) README = (open(os.path.join(here, 'README.txt')).read() + '\n\n' + open(os.path.join(here, 'CHANGES.txt')).read()) setup(name='transaction', version=__version__, description='Transaction management for Python', long_description=README, classifiers=[ "Development Status :: 6 - Mature", "License :: OSI Approved :: Zope Public License", "Programming Language :: Python", "Topic :: Database", "Topic :: Software Development :: Libraries :: Python Modules", "Operating System :: Microsoft :: Windows", "Operating System :: Unix", ], author="Zope Corporation", author_email="zodb-dev@zope.org", url="http://www.zope.org/Products/ZODB", license="ZPL 2.1", platforms=["any"], packages=find_packages(), include_package_data=True, zip_safe=False, test_suite="transaction.tests", tests_require = [ 'zope.interface', ], install_requires=[ 'zope.interface', ], entry_points = """\ """ ) zope2.13-2.13.21/source/transaction/PKG-INFO0000644000175000017500000000743712214017513017070 0ustar arnauarnauMetadata-Version: 1.0 Name: transaction Version: 1.1.1 Summary: Transaction management for Python Home-page: http://www.zope.org/Products/ZODB Author: Zope Corporation Author-email: zodb-dev@zope.org License: ZPL 2.1 Description: ============ Transactions ============ This package contains a generic transaction implementation for Python. It is mainly used by the ZODB, though. Note that the data manager API, ``transaction.interfaces.IDataManager``, is syntactically simple, but semantically complex. The semantics were not easy to express in the interface. This could probably use more work. The semantics are presented in detail through examples of a sample data manager in ``transaction.tests.test_SampleDataManager``. Changes ======= 1.1.1 (2010-09-16) ------------------ Bug Fixes: - Code in ``_transaction.py`` held on to local references to traceback objects after calling ``sys.exc_info()`` to get one, causing potential reference leakages. - Fixed ``hexlify`` NameError in ``transaction._transaction.oid_repr`` and add test. 1.1.0 (1010-05-12) ------------------ New Features: - Transaction managers and the transaction module can be used with the with statement to define transaction boundaries, as in:: with transaction: ... do some things ... See transaction/tests/convenience.txt for more details. - There is a new iterator function that automates dealing with transient errors (such as ZODB confict errors). For example, in:: for attempt in transaction.attempts(5): with attempt: ... do some things .. If the work being done raises transient errors, the transaction will be retried up to 5 times. See transaction/tests/convenience.txt for more details. Bugs fixed: - Fixed a bug that caused extra commit calls to be made on data managers under certain special circumstances. https://mail.zope.org/pipermail/zodb-dev/2010-May/013329.html - When threads were reused, transaction data could leak accross them, causing subtle application bugs. https://bugs.launchpad.net/zodb/+bug/239086 1.0.1 (2010-05-07) ------------------ - LP #142464: remove double newline between log entries: it makes doing smarter formatting harder. - Updated tests to remove use of deprecated ``zope.testing.doctest``. 1.0.0 (2009-07-24) ------------------ - Fix test that incorrectly relied on the order of a list that was generated from a dict. - Remove crufty DEPENDENCIES.cfg left over from zpkg. 1.0a1 (2007-12-18) ------------------ = Initial release, branched from ZODB trunk on 2007-11-08 (aka "3.9.0dev"). - Remove (deprecated) support for beforeCommitHook alias to addBeforeCommitHook. - Add weakset tests. - Remove unit tests that depend on ZODB.tests.utils from test_transaction (these are actually integration tests). Platform: any Classifier: Development Status :: 6 - Mature Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Topic :: Database Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: Unix zope2.13-2.13.21/source/transaction/transaction.egg-info/0000755000175000017500000000000012214017513021777 5ustar arnauarnauzope2.13-2.13.21/source/transaction/transaction.egg-info/PKG-INFO0000644000175000017500000000743712214017513023107 0ustar arnauarnauMetadata-Version: 1.0 Name: transaction Version: 1.1.1 Summary: Transaction management for Python Home-page: http://www.zope.org/Products/ZODB Author: Zope Corporation Author-email: zodb-dev@zope.org License: ZPL 2.1 Description: ============ Transactions ============ This package contains a generic transaction implementation for Python. It is mainly used by the ZODB, though. Note that the data manager API, ``transaction.interfaces.IDataManager``, is syntactically simple, but semantically complex. The semantics were not easy to express in the interface. This could probably use more work. The semantics are presented in detail through examples of a sample data manager in ``transaction.tests.test_SampleDataManager``. Changes ======= 1.1.1 (2010-09-16) ------------------ Bug Fixes: - Code in ``_transaction.py`` held on to local references to traceback objects after calling ``sys.exc_info()`` to get one, causing potential reference leakages. - Fixed ``hexlify`` NameError in ``transaction._transaction.oid_repr`` and add test. 1.1.0 (1010-05-12) ------------------ New Features: - Transaction managers and the transaction module can be used with the with statement to define transaction boundaries, as in:: with transaction: ... do some things ... See transaction/tests/convenience.txt for more details. - There is a new iterator function that automates dealing with transient errors (such as ZODB confict errors). For example, in:: for attempt in transaction.attempts(5): with attempt: ... do some things .. If the work being done raises transient errors, the transaction will be retried up to 5 times. See transaction/tests/convenience.txt for more details. Bugs fixed: - Fixed a bug that caused extra commit calls to be made on data managers under certain special circumstances. https://mail.zope.org/pipermail/zodb-dev/2010-May/013329.html - When threads were reused, transaction data could leak accross them, causing subtle application bugs. https://bugs.launchpad.net/zodb/+bug/239086 1.0.1 (2010-05-07) ------------------ - LP #142464: remove double newline between log entries: it makes doing smarter formatting harder. - Updated tests to remove use of deprecated ``zope.testing.doctest``. 1.0.0 (2009-07-24) ------------------ - Fix test that incorrectly relied on the order of a list that was generated from a dict. - Remove crufty DEPENDENCIES.cfg left over from zpkg. 1.0a1 (2007-12-18) ------------------ = Initial release, branched from ZODB trunk on 2007-11-08 (aka "3.9.0dev"). - Remove (deprecated) support for beforeCommitHook alias to addBeforeCommitHook. - Add weakset tests. - Remove unit tests that depend on ZODB.tests.utils from test_transaction (these are actually integration tests). Platform: any Classifier: Development Status :: 6 - Mature Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Topic :: Database Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: Unix zope2.13-2.13.21/source/transaction/transaction.egg-info/dependency_links.txt0000644000175000017500000000000112214017513026045 0ustar arnauarnau zope2.13-2.13.21/source/transaction/transaction.egg-info/requires.txt0000644000175000017500000000001612214017513024374 0ustar arnauarnauzope.interfacezope2.13-2.13.21/source/transaction/transaction.egg-info/entry_points.txt0000644000175000017500000000000612214017513025271 0ustar arnauarnau zope2.13-2.13.21/source/transaction/transaction.egg-info/top_level.txt0000644000175000017500000000001412214017513024524 0ustar arnauarnautransaction zope2.13-2.13.21/source/transaction/transaction.egg-info/SOURCES.txt0000644000175000017500000000164012214017513023664 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg ez_setup.py setup.py transaction/__init__.py transaction/_manager.py transaction/_transaction.py transaction/interfaces.py transaction/weakset.py transaction.egg-info/PKG-INFO transaction.egg-info/SOURCES.txt transaction.egg-info/dependency_links.txt transaction.egg-info/entry_points.txt transaction.egg-info/not-zip-safe transaction.egg-info/requires.txt transaction.egg-info/top_level.txt transaction/tests/__init__.py transaction/tests/convenience.txt transaction/tests/doom.txt transaction/tests/sampledm.py transaction/tests/savepoint.txt transaction/tests/savepointsample.py transaction/tests/test_SampleDataManager.py transaction/tests/test_SampleResourceManager.py transaction/tests/test_register_compat.py transaction/tests/test_savepoint.py transaction/tests/test_transaction.py transaction/tests/test_weakset.py transaction/tests/warnhook.pyzope2.13-2.13.21/source/transaction/transaction.egg-info/not-zip-safe0000644000175000017500000000000112214017513024225 0ustar arnauarnau zope2.13-2.13.21/source/transaction/pip-egg-info/0000755000175000017500000000000012214017513020241 5ustar arnauarnauzope2.13-2.13.21/source/transaction/pip-egg-info/transaction.egg-info/0000755000175000017500000000000012214017513024260 5ustar arnauarnauzope2.13-2.13.21/source/transaction/pip-egg-info/transaction.egg-info/PKG-INFO0000644000175000017500000000755412214017513025370 0ustar arnauarnauMetadata-Version: 1.1 Name: transaction Version: 1.1.1 Summary: Transaction management for Python Home-page: http://www.zope.org/Products/ZODB Author: Zope Corporation Author-email: zodb-dev@zope.org License: ZPL 2.1 Description: ============ Transactions ============ This package contains a generic transaction implementation for Python. It is mainly used by the ZODB, though. Note that the data manager API, ``transaction.interfaces.IDataManager``, is syntactically simple, but semantically complex. The semantics were not easy to express in the interface. This could probably use more work. The semantics are presented in detail through examples of a sample data manager in ``transaction.tests.test_SampleDataManager``. Changes ======= 1.1.1 (2010-09-16) ------------------ Bug Fixes: - Code in ``_transaction.py`` held on to local references to traceback objects after calling ``sys.exc_info()`` to get one, causing potential reference leakages. - Fixed ``hexlify`` NameError in ``transaction._transaction.oid_repr`` and add test. 1.1.0 (1010-05-12) ------------------ New Features: - Transaction managers and the transaction module can be used with the with statement to define transaction boundaries, as in:: with transaction: ... do some things ... See transaction/tests/convenience.txt for more details. - There is a new iterator function that automates dealing with transient errors (such as ZODB confict errors). For example, in:: for attempt in transaction.attempts(5): with attempt: ... do some things .. If the work being done raises transient errors, the transaction will be retried up to 5 times. See transaction/tests/convenience.txt for more details. Bugs fixed: - Fixed a bug that caused extra commit calls to be made on data managers under certain special circumstances. https://mail.zope.org/pipermail/zodb-dev/2010-May/013329.html - When threads were reused, transaction data could leak accross them, causing subtle application bugs. https://bugs.launchpad.net/zodb/+bug/239086 1.0.1 (2010-05-07) ------------------ - LP #142464: remove double newline between log entries: it makes doing smarter formatting harder. - Updated tests to remove use of deprecated ``zope.testing.doctest``. 1.0.0 (2009-07-24) ------------------ - Fix test that incorrectly relied on the order of a list that was generated from a dict. - Remove crufty DEPENDENCIES.cfg left over from zpkg. 1.0a1 (2007-12-18) ------------------ = Initial release, branched from ZODB trunk on 2007-11-08 (aka "3.9.0dev"). - Remove (deprecated) support for beforeCommitHook alias to addBeforeCommitHook. - Add weakset tests. - Remove unit tests that depend on ZODB.tests.utils from test_transaction (these are actually integration tests). Platform: any Classifier: Development Status :: 6 - Mature Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Topic :: Database Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: Unix zope2.13-2.13.21/source/transaction/pip-egg-info/transaction.egg-info/dependency_links.txt0000644000175000017500000000000112214017513030326 0ustar arnauarnau zope2.13-2.13.21/source/transaction/pip-egg-info/transaction.egg-info/requires.txt0000644000175000017500000000001612214017513026655 0ustar arnauarnauzope.interfacezope2.13-2.13.21/source/transaction/pip-egg-info/transaction.egg-info/entry_points.txt0000644000175000017500000000000612214017513027552 0ustar arnauarnau zope2.13-2.13.21/source/transaction/pip-egg-info/transaction.egg-info/top_level.txt0000644000175000017500000000001412214017513027005 0ustar arnauarnautransaction zope2.13-2.13.21/source/transaction/pip-egg-info/transaction.egg-info/SOURCES.txt0000644000175000017500000000152312214017513026145 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/transaction.egg-info/PKG-INFO pip-egg-info/transaction.egg-info/SOURCES.txt pip-egg-info/transaction.egg-info/dependency_links.txt pip-egg-info/transaction.egg-info/entry_points.txt pip-egg-info/transaction.egg-info/not-zip-safe pip-egg-info/transaction.egg-info/requires.txt pip-egg-info/transaction.egg-info/top_level.txt transaction/__init__.py transaction/_manager.py transaction/_transaction.py transaction/interfaces.py transaction/weakset.py transaction/tests/__init__.py transaction/tests/sampledm.py transaction/tests/savepointsample.py transaction/tests/test_SampleDataManager.py transaction/tests/test_SampleResourceManager.py transaction/tests/test_register_compat.py transaction/tests/test_savepoint.py transaction/tests/test_transaction.py transaction/tests/test_weakset.py transaction/tests/warnhook.pyzope2.13-2.13.21/source/transaction/pip-egg-info/transaction.egg-info/not-zip-safe0000644000175000017500000000000112214017513026506 0ustar arnauarnau zope2.13-2.13.21/source/transaction/transaction/0000755000175000017500000000000012214017513020305 5ustar arnauarnauzope2.13-2.13.21/source/transaction/transaction/_manager.py0000644000175000017500000001073012214017513022431 0ustar arnauarnau############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################ """A TransactionManager controls transaction boundaries. It coordinates application code and resource managers, so that they are associated with the right transaction. """ from transaction.weakset import WeakSet from transaction._transaction import Transaction from transaction.interfaces import TransientError import threading # We have to remember sets of synch objects, especially Connections. # But we don't want mere registration with a transaction manager to # keep a synch object alive forever; in particular, it's common # practice not to explicitly close Connection objects, and keeping # a Connection alive keeps a potentially huge number of other objects # alive (e.g., the cache, and everything reachable from it too). # Therefore we use "weak sets" internally. # Call the ISynchronizer newTransaction() method on every element of # WeakSet synchs. # A transaction manager needs to do this whenever begin() is called. # Since it would be good if tm.get() returned the new transaction while # newTransaction() is running, calling this has to be delayed until after # the transaction manager has done whatever it needs to do to make its # get() return the new txn. def _new_transaction(txn, synchs): if synchs: synchs.map(lambda s: s.newTransaction(txn)) # Important: we must always pass a WeakSet (even if empty) to the Transaction # constructor: synchronizers are registered with the TM, but the # ISynchronizer xyzCompletion() methods are called by Transactions without # consulting the TM, so we need to pass a mutable collection of synchronizers # so that Transactions "see" synchronizers that get registered after the # Transaction object is constructed. class TransactionManager(object): def __init__(self): self._txn = None self._synchs = WeakSet() def begin(self): if self._txn is not None: self._txn.abort() txn = self._txn = Transaction(self._synchs, self) _new_transaction(txn, self._synchs) return txn __enter__ = lambda self: self.begin() def get(self): if self._txn is None: self._txn = Transaction(self._synchs, self) return self._txn def free(self, txn): assert txn is self._txn self._txn = None def registerSynch(self, synch): self._synchs.add(synch) def unregisterSynch(self, synch): self._synchs.remove(synch) def isDoomed(self): return self.get().isDoomed() def doom(self): return self.get().doom() def commit(self): return self.get().commit() def abort(self): return self.get().abort() def __exit__(self, t, v, tb): if v is None: self.commit() else: self.abort() def savepoint(self, optimistic=False): return self.get().savepoint(optimistic) def attempts(self, number=3): assert number > 0 while number: number -= 1 if number: yield Attempt(self) else: yield self def _retryable(self, error_type, error): if issubclass(error_type, TransientError): return True for dm in self.get()._resources: should_retry = getattr(dm, 'should_retry', None) if (should_retry is not None) and should_retry(error): return True class ThreadTransactionManager(TransactionManager, threading.local): """Thread-aware transaction manager. Each thread is associated with a unique transaction. """ class Attempt(object): def __init__(self, manager): self.manager = manager def __enter__(self): return self.manager.__enter__() def __exit__(self, t, v, tb): if v is None: self.manager.commit() else: retry = self.manager._retryable(t, v) self.manager.abort() return retry zope2.13-2.13.21/source/transaction/transaction/_transaction.py0000644000175000017500000006217412214017513023355 0ustar arnauarnau############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################ """Transaction objects manage resources for an individual activity. Compatibility issues -------------------- The implementation of Transaction objects involves two layers of backwards compatibility, because this version of transaction supports both ZODB 3 and ZODB 4. Zope is evolving towards the ZODB4 interfaces. Transaction has two methods for a resource manager to call to participate in a transaction -- register() and join(). join() takes a resource manager and adds it to the list of resources. register() is for backwards compatibility. It takes a persistent object and registers its _p_jar attribute. TODO: explain adapter Two-phase commit ---------------- A transaction commit involves an interaction between the transaction object and one or more resource managers. The transaction manager calls the following four methods on each resource manager; it calls tpc_begin() on each resource manager before calling commit() on any of them. 1. tpc_begin(txn) 2. commit(txn) 3. tpc_vote(txn) 4. tpc_finish(txn) Before-commit hook ------------------ Sometimes, applications want to execute some code when a transaction is committed. For example, one might want to delay object indexing until a transaction commits, rather than indexing every time an object is changed. Or someone might want to check invariants only after a set of operations. A pre-commit hook is available for such use cases: use addBeforeCommitHook(), passing it a callable and arguments. The callable will be called with its arguments at the start of the commit (but not for substransaction commits). After-commit hook ------------------ Sometimes, applications want to execute code after a transaction is committed or aborted. For example, one might want to launch non transactional code after a successful commit. Or still someone might want to launch asynchronous code after. A post-commit hook is available for such use cases: use addAfterCommitHook(), passing it a callable and arguments. The callable will be called with a Boolean value representing the status of the commit operation as first argument (true if successfull or false iff aborted) preceding its arguments at the start of the commit (but not for substransaction commits). Error handling -------------- When errors occur during two-phase commit, the transaction manager aborts all the resource managers. The specific methods it calls depend on whether the error occurs before or after the call to tpc_vote() on that transaction manager. If the resource manager has not voted, then the resource manager will have one or more uncommitted objects. There are two cases that lead to this state; either the transaction manager has not called commit() for any objects on this resource manager or the call that failed was a commit() for one of the objects of this resource manager. For each uncommitted object, including the object that failed in its commit(), call abort(). Once uncommitted objects are aborted, tpc_abort() or abort_sub() is called on each resource manager. Synchronization --------------- You can register sychronization objects (synchronizers) with the tranasction manager. The synchronizer must implement beforeCompletion() and afterCompletion() methods. The transaction manager calls beforeCompletion() when it starts a top-level two-phase commit. It calls afterCompletion() when a top-level transaction is committed or aborted. The methods are passed the current Transaction as their only argument. """ import binascii import logging import sys import thread import weakref import traceback from cStringIO import StringIO from zope import interface from transaction.weakset import WeakSet from transaction.interfaces import TransactionFailedError from transaction import interfaces _marker = object() # The point of this is to avoid hiding exceptions (which the builtin # hasattr() does). def myhasattr(obj, attr): return getattr(obj, attr, _marker) is not _marker class Status: # ACTIVE is the initial state. ACTIVE = "Active" COMMITTING = "Committing" COMMITTED = "Committed" DOOMED = "Doomed" # commit() or commit(True) raised an exception. All further attempts # to commit or join this transaction will raise TransactionFailedError. COMMITFAILED = "Commit failed" class Transaction(object): interface.implements(interfaces.ITransaction, interfaces.ITransactionDeprecated) # Assign an index to each savepoint so we can invalidate later savepoints # on rollback. The first index assigned is 1, and it goes up by 1 each # time. _savepoint_index = 0 # If savepoints are used, keep a weak key dict of them. This maps a # savepoint to its index (see above). _savepoint2index = None # Meta data. ._extension is also metadata, but is initialized to an # emtpy dict in __init__. user = "" description = "" def __init__(self, synchronizers=None, manager=None): self.status = Status.ACTIVE # List of resource managers, e.g. MultiObjectResourceAdapters. self._resources = [] # Weak set of synchronizer objects to call. if synchronizers is None: synchronizers = WeakSet() self._synchronizers = synchronizers self._manager = manager # _adapters: Connection/_p_jar -> MultiObjectResourceAdapter[Sub] self._adapters = {} self._voted = {} # id(Connection) -> boolean, True if voted # _voted and other dictionaries use the id() of the resource # manager as a key, because we can't guess whether the actual # resource managers will be safe to use as dict keys. # The user, description, and _extension attributes are accessed # directly by storages, leading underscore notwithstanding. self._extension = {} self.log = logging.getLogger("txn.%d" % thread.get_ident()) self.log.debug("new transaction") # If a commit fails, the traceback is saved in _failure_traceback. # If another attempt is made to commit, TransactionFailedError is # raised, incorporating this traceback. self._failure_traceback = None # List of (hook, args, kws) tuples added by addBeforeCommitHook(). self._before_commit = [] # List of (hook, args, kws) tuples added by addAfterCommitHook(). self._after_commit = [] def isDoomed(self): return self.status is Status.DOOMED def doom(self): if self.status is not Status.DOOMED: if self.status is not Status.ACTIVE: # should not doom transactions in the middle, # or after, a commit raise AssertionError() self.status = Status.DOOMED # Raise TransactionFailedError, due to commit()/join()/register() # getting called when the current transaction has already suffered # a commit/savepoint failure. def _prior_operation_failed(self): assert self._failure_traceback is not None raise TransactionFailedError("An operation previously failed, " "with traceback:\n\n%s" % self._failure_traceback.getvalue()) def join(self, resource): if self.status is Status.COMMITFAILED: self._prior_operation_failed() # doesn't return if (self.status is not Status.ACTIVE and self.status is not Status.DOOMED): # TODO: Should it be possible to join a committing transaction? # I think some users want it. raise ValueError("expected txn status %r or %r, but it's %r" % ( Status.ACTIVE, Status.DOOMED, self.status)) # TODO: the prepare check is a bit of a hack, perhaps it would # be better to use interfaces. If this is a ZODB4-style # resource manager, it needs to be adapted, too. if myhasattr(resource, "prepare"): # TODO: deprecate 3.6 resource = DataManagerAdapter(resource) self._resources.append(resource) if self._savepoint2index: # A data manager has joined a transaction *after* a savepoint # was created. A couple of things are different in this case: # # 1. We need to add its savepoint to all previous savepoints. # so that if they are rolled back, we roll this one back too. # # 2. We don't actually need to ask the data manager for a # savepoint: because it's just joining, we can just abort it to # roll back to the current state, so we simply use an # AbortSavepoint. datamanager_savepoint = AbortSavepoint(resource, self) for transaction_savepoint in self._savepoint2index.keys(): transaction_savepoint._savepoints.append( datamanager_savepoint) def _unjoin(self, resource): # Leave a transaction because a savepoint was rolled back on a resource # that joined later. # Don't use remove. We don't want to assume anything about __eq__. self._resources = [r for r in self._resources if r is not resource] def savepoint(self, optimistic=False): if self.status is Status.COMMITFAILED: self._prior_operation_failed() # doesn't return, it raises try: savepoint = Savepoint(self, optimistic, *self._resources) except: self._cleanup(self._resources) self._saveAndRaiseCommitishError() # reraises! if self._savepoint2index is None: self._savepoint2index = weakref.WeakKeyDictionary() self._savepoint_index += 1 self._savepoint2index[savepoint] = self._savepoint_index return savepoint # Remove and invalidate all savepoints we know about with an index # larger than `savepoint`'s. This is what's needed when a rollback # _to_ `savepoint` is done. def _remove_and_invalidate_after(self, savepoint): savepoint2index = self._savepoint2index index = savepoint2index[savepoint] # use items() to make copy to avoid mutating while iterating for savepoint, i in savepoint2index.items(): if i > index: savepoint.transaction = None # invalidate del savepoint2index[savepoint] # Invalidate and forget about all savepoints. def _invalidate_all_savepoints(self): for savepoint in self._savepoint2index.keys(): savepoint.transaction = None # invalidate self._savepoint2index.clear() def register(self, obj): # The old way of registering transaction participants. # # register() is passed either a persisent object or a # resource manager like the ones defined in ZODB.DB. # If it is passed a persistent object, that object should # be stored when the transaction commits. For other # objects, the object implements the standard two-phase # commit protocol. manager = getattr(obj, "_p_jar", obj) if manager is None: raise ValueError("Register with no manager") adapter = self._adapters.get(manager) if adapter is None: adapter = MultiObjectResourceAdapter(manager) adapter.objects.append(obj) self._adapters[manager] = adapter self.join(adapter) else: # TODO: comment out this expensive assert later # Use id() to guard against proxies. assert id(obj) not in map(id, adapter.objects) adapter.objects.append(obj) def commit(self): if self.status is Status.DOOMED: raise interfaces.DoomedTransaction() if self._savepoint2index: self._invalidate_all_savepoints() if self.status is Status.COMMITFAILED: self._prior_operation_failed() # doesn't return self._callBeforeCommitHooks() self._synchronizers.map(lambda s: s.beforeCompletion(self)) self.status = Status.COMMITTING try: self._commitResources() self.status = Status.COMMITTED except: t = None v = None tb = None try: t, v, tb = self._saveAndGetCommitishError() self._callAfterCommitHooks(status=False) raise t, v, tb finally: del t, v, tb else: if self._manager: self._manager.free(self) self._synchronizers.map(lambda s: s.afterCompletion(self)) self._callAfterCommitHooks(status=True) self.log.debug("commit") def _saveAndGetCommitishError(self): self.status = Status.COMMITFAILED # Save the traceback for TransactionFailedError. ft = self._failure_traceback = StringIO() t = None v = None tb = None try: t, v, tb = sys.exc_info() # Record how we got into commit(). traceback.print_stack(sys._getframe(1), None, ft) # Append the stack entries from here down to the exception. traceback.print_tb(tb, None, ft) # Append the exception type and value. ft.writelines(traceback.format_exception_only(t, v)) return t, v, tb finally: del t, v, tb def _saveAndRaiseCommitishError(self): t = None v = None tb = None try: t, v, tb = self._saveAndGetCommitishError() raise t, v, tb finally: del t, v, tb def getBeforeCommitHooks(self): return iter(self._before_commit) def addBeforeCommitHook(self, hook, args=(), kws=None): if kws is None: kws = {} self._before_commit.append((hook, tuple(args), kws)) def _callBeforeCommitHooks(self): # Call all hooks registered, allowing further registrations # during processing. Note that calls to addBeforeCommitHook() may # add additional hooks while hooks are running, and iterating over a # growing list is well-defined in Python. for hook, args, kws in self._before_commit: hook(*args, **kws) self._before_commit = [] def getAfterCommitHooks(self): return iter(self._after_commit) def addAfterCommitHook(self, hook, args=(), kws=None): if kws is None: kws = {} self._after_commit.append((hook, tuple(args), kws)) def _callAfterCommitHooks(self, status=True): # Avoid to abort anything at the end if no hooks are registred. if not self._after_commit: return # Call all hooks registered, allowing further registrations # during processing. Note that calls to addAterCommitHook() may # add additional hooks while hooks are running, and iterating over a # growing list is well-defined in Python. for hook, args, kws in self._after_commit: # The first argument passed to the hook is a Boolean value, # true if the commit succeeded, or false if the commit aborted. try: hook(status, *args, **kws) except: # We need to catch the exceptions if we want all hooks # to be called self.log.error("Error in after commit hook exec in %s ", hook, exc_info=sys.exc_info()) # The transaction is already committed. It must not have # further effects after the commit. for rm in self._resources: try: rm.abort(self) except: # XXX should we take further actions here ? self.log.error("Error in abort() on manager %s", rm, exc_info=sys.exc_info()) self._after_commit = [] self._before_commit = [] def _commitResources(self): # Execute the two-phase commit protocol. L = list(self._resources) L.sort(rm_cmp) try: for rm in L: rm.tpc_begin(self) for rm in L: rm.commit(self) self.log.debug("commit %r" % rm) for rm in L: rm.tpc_vote(self) self._voted[id(rm)] = True try: for rm in L: rm.tpc_finish(self) except: # TODO: do we need to make this warning stronger? # TODO: It would be nice if the system could be configured # to stop committing transactions at this point. self.log.critical("A storage error occurred during the second " "phase of the two-phase commit. Resources " "may be in an inconsistent state.") raise except: # If an error occurs committing a transaction, we try # to revert the changes in each of the resource managers. t, v, tb = sys.exc_info() try: try: self._cleanup(L) finally: self._synchronizers.map(lambda s: s.afterCompletion(self)) raise t, v, tb finally: del t, v, tb def _cleanup(self, L): # Called when an exception occurs during tpc_vote or tpc_finish. for rm in L: if id(rm) not in self._voted: try: rm.abort(self) except Exception: self.log.error("Error in abort() on manager %s", rm, exc_info=sys.exc_info()) for rm in L: try: rm.tpc_abort(self) except Exception: self.log.error("Error in tpc_abort() on manager %s", rm, exc_info=sys.exc_info()) def abort(self): if self._savepoint2index: self._invalidate_all_savepoints() self._synchronizers.map(lambda s: s.beforeCompletion(self)) try: t = None v = None tb = None for rm in self._resources: try: rm.abort(self) except: if tb is None: t, v, tb = sys.exc_info() self.log.error("Failed to abort resource manager: %s", rm, exc_info=sys.exc_info()) if self._manager: self._manager.free(self) self._synchronizers.map(lambda s: s.afterCompletion(self)) self.log.debug("abort") if tb is not None: raise t, v, tb finally: del t, v, tb def note(self, text): text = text.strip() if self.description: self.description += "\n" + text else: self.description = text def setUser(self, user_name, path="/"): self.user = "%s %s" % (path, user_name) def setExtendedInfo(self, name, value): self._extension[name] = value # TODO: We need a better name for the adapters. class MultiObjectResourceAdapter(object): """Adapt the old-style register() call to the new-style join(). With join(), a resource mananger like a Connection registers with the transaction manager. With register(), an individual object is passed to register(). """ def __init__(self, jar): self.manager = jar self.objects = [] self.ncommitted = 0 def __repr__(self): return "<%s for %s at %s>" % (self.__class__.__name__, self.manager, id(self)) def sortKey(self): return self.manager.sortKey() def tpc_begin(self, txn): self.manager.tpc_begin(txn) def tpc_finish(self, txn): self.manager.tpc_finish(txn) def tpc_abort(self, txn): self.manager.tpc_abort(txn) def commit(self, txn): for o in self.objects: self.manager.commit(o, txn) self.ncommitted += 1 def tpc_vote(self, txn): self.manager.tpc_vote(txn) def abort(self, txn): t = None v = None tb = None try: for o in self.objects: try: self.manager.abort(o, txn) except: # Capture the first exception and re-raise it after # aborting all the other objects. if tb is None: t, v, tb = sys.exc_info() txn.log.error("Failed to abort object: %s", object_hint(o), exc_info=sys.exc_info()) if tb is not None: raise t, v, tb finally: del t, v, tb def rm_cmp(rm1, rm2): return cmp(rm1.sortKey(), rm2.sortKey()) def object_hint(o): """Return a string describing the object. This function does not raise an exception. """ # We should always be able to get __class__. klass = o.__class__.__name__ # oid would be great, but may this isn't a persistent object. oid = getattr(o, "_p_oid", _marker) if oid is not _marker: oid = oid_repr(oid) return "%s oid=%s" % (klass, oid) def oid_repr(oid): if isinstance(oid, str) and len(oid) == 8: # Convert to hex and strip leading zeroes. as_hex = binascii.hexlify(oid).lstrip('0') # Ensure two characters per input byte. if len(as_hex) & 1: as_hex = '0' + as_hex elif as_hex == '': as_hex = '00' return '0x' + as_hex else: return repr(oid) # TODO: deprecate for 3.6. class DataManagerAdapter(object): """Adapt zodb 4-style data managers to zodb3 style Adapt transaction.interfaces.IDataManager to ZODB.interfaces.IPureDatamanager """ # Note that it is pretty important that this does not have a _p_jar # attribute. This object will be registered with a zodb3 TM, which # will then try to get a _p_jar from it, using it as the default. # (Objects without a _p_jar are their own data managers.) def __init__(self, datamanager): self._datamanager = datamanager # TODO: I'm not sure why commit() doesn't do anything def commit(self, transaction): # We don't do anything here because ZODB4-style data managers # didn't have a separate commit step pass def abort(self, transaction): self._datamanager.abort(transaction) def tpc_begin(self, transaction): # We don't do anything here because ZODB4-style data managers # didn't have a separate tpc_begin step pass def tpc_abort(self, transaction): self._datamanager.abort(transaction) def tpc_finish(self, transaction): self._datamanager.commit(transaction) def tpc_vote(self, transaction): self._datamanager.prepare(transaction) def sortKey(self): return self._datamanager.sortKey() class Savepoint: """Transaction savepoint. Transaction savepoints coordinate savepoints for data managers participating in a transaction. """ interface.implements(interfaces.ISavepoint) valid = property(lambda self: self.transaction is not None) def __init__(self, transaction, optimistic, *resources): self.transaction = transaction self._savepoints = savepoints = [] for datamanager in resources: try: savepoint = datamanager.savepoint except AttributeError: if not optimistic: raise TypeError("Savepoints unsupported", datamanager) savepoint = NoRollbackSavepoint(datamanager) else: savepoint = savepoint() savepoints.append(savepoint) def rollback(self): transaction = self.transaction if transaction is None: raise interfaces.InvalidSavepointRollbackError transaction._remove_and_invalidate_after(self) try: for savepoint in self._savepoints: savepoint.rollback() except: # Mark the transaction as failed. transaction._saveAndRaiseCommitishError() # reraises! class AbortSavepoint: def __init__(self, datamanager, transaction): self.datamanager = datamanager self.transaction = transaction def rollback(self): self.datamanager.abort(self.transaction) self.transaction._unjoin(self.datamanager) class NoRollbackSavepoint: def __init__(self, datamanager): self.datamanager = datamanager def rollback(self): raise TypeError("Savepoints unsupported", self.datamanager) zope2.13-2.13.21/source/transaction/transaction/interfaces.py0000644000175000017500000004532512214017513023013 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import zope.interface class ITransactionManager(zope.interface.Interface): """An object that manages a sequence of transactions. Applications use transaction managers to establish transaction boundaries. """ def begin(): """Begin a new transaction. If an existing transaction is in progress, it will be aborted. The newTransaction() method of registered synchronizers is called, passing the new transaction object. """ def get(): """Get the current transaction. """ def commit(): """Commit the current transaction. """ def abort(): """Abort the current transaction. """ def doom(): """Doom the current transaction. """ def isDoomed(): """Returns True if the current transaction is doomed, otherwise False. """ def savepoint(optimistic=False): """Create a savepoint from the current transaction. If the optimistic argument is true, then data managers that don't support savepoints can be used, but an error will be raised if the savepoint is rolled back. An ISavepoint object is returned. """ def registerSynch(synch): """Register an ISynchronizer. Synchronizers are notified about some major events in a transaction's life. See ISynchronizer for details. """ def unregisterSynch(synch): """Unregister an ISynchronizer. Synchronizers are notified about some major events in a transaction's life. See ISynchronizer for details. """ class ITransaction(zope.interface.Interface): """Object representing a running transaction. Objects with this interface may represent different transactions during their lifetime (.begin() can be called to start a new transaction using the same instance, although that example is deprecated and will go away in ZODB 3.6). """ user = zope.interface.Attribute( """A user name associated with the transaction. The format of the user name is defined by the application. The value is of Python type str. Storages record the user value, as meta-data, when a transaction commits. A storage may impose a limit on the size of the value; behavior is undefined if such a limit is exceeded (for example, a storage may raise an exception, or truncate the value). """) description = zope.interface.Attribute( """A textual description of the transaction. The value is of Python type str. Method note() is the intended way to set the value. Storages record the description, as meta-data, when a transaction commits. A storage may impose a limit on the size of the description; behavior is undefined if such a limit is exceeded (for example, a storage may raise an exception, or truncate the value). """) def commit(): """Finalize the transaction. This executes the two-phase commit algorithm for all IDataManager objects associated with the transaction. """ def abort(): """Abort the transaction. This is called from the application. This can only be called before the two-phase commit protocol has been started. """ def doom(): """Doom the transaction. Dooms the current transaction. This will cause DoomedTransactionException to be raised on any attempt to commit the transaction. Otherwise the transaction will behave as if it was active. """ def savepoint(optimistic=False): """Create a savepoint. If the optimistic argument is true, then data managers that don't support savepoints can be used, but an error will be raised if the savepoint is rolled back. An ISavepoint object is returned. """ def join(datamanager): """Add a data manager to the transaction. `datamanager` must provide the transactions.interfaces.IDataManager interface. """ def note(text): """Add text to the transaction description. This modifies the `.description` attribute; see its docs for more detail. First surrounding whitespace is stripped from `text`. If `.description` is currently an empty string, then the stripped text becomes its value, else two newlines and the stripped text are appended to `.description`. """ def setUser(user_name, path="/"): """Set the user name. path should be provided if needed to further qualify the identified user. This is a convenience method used by Zope. It sets the .user attribute to str(path) + " " + str(user_name). This sets the `.user` attribute; see its docs for more detail. """ def setExtendedInfo(name, value): """Add extension data to the transaction. name is the name of the extension property to set, of Python type str; value must be picklable. Multiple calls may be made to set multiple extension properties, provided the names are distinct. Storages record the extension data, as meta-data, when a transaction commits. A storage may impose a limit on the size of extension data; behavior is undefined if such a limit is exceeded (for example, a storage may raise an exception, or remove `` pairs). """ # deprecated38 def beforeCommitHook(__hook, *args, **kws): """Register a hook to call before the transaction is committed. THIS IS DEPRECATED IN ZODB 3.6. Use addBeforeCommitHook() instead. The specified hook function will be called after the transaction's commit method has been called, but before the commit process has been started. The hook will be passed the specified positional and keyword arguments. Multiple hooks can be registered and will be called in the order they were registered (first registered, first called). This method can also be called from a hook: an executing hook can register more hooks. Applications should take care to avoid creating infinite loops by recursively registering hooks. Hooks are called only for a top-level commit. A savepoint does not call any hooks. If the transaction is aborted, hooks are not called, and are discarded. Calling a hook "consumes" its registration too: hook registrations do not persist across transactions. If it's desired to call the same hook on every transaction commit, then beforeCommitHook() must be called with that hook during every transaction; in such a case consider registering a synchronizer object via a TransactionManager's registerSynch() method instead. """ def addBeforeCommitHook(hook, args=(), kws=None): """Register a hook to call before the transaction is committed. The specified hook function will be called after the transaction's commit method has been called, but before the commit process has been started. The hook will be passed the specified positional (`args`) and keyword (`kws`) arguments. `args` is a sequence of positional arguments to be passed, defaulting to an empty tuple (no positional arguments are passed). `kws` is a dictionary of keyword argument names and values to be passed, or the default None (no keyword arguments are passed). Multiple hooks can be registered and will be called in the order they were registered (first registered, first called). This method can also be called from a hook: an executing hook can register more hooks. Applications should take care to avoid creating infinite loops by recursively registering hooks. Hooks are called only for a top-level commit. A savepoint creation does not call any hooks. If the transaction is aborted, hooks are not called, and are discarded. Calling a hook "consumes" its registration too: hook registrations do not persist across transactions. If it's desired to call the same hook on every transaction commit, then addBeforeCommitHook() must be called with that hook during every transaction; in such a case consider registering a synchronizer object via a TransactionManager's registerSynch() method instead. """ def getBeforeCommitHooks(): """Return iterable producing the registered addBeforeCommit hooks. A triple (hook, args, kws) is produced for each registered hook. The hooks are produced in the order in which they would be invoked by a top-level transaction commit. """ def addAfterCommitHook(hook, args=(), kws=None): """Register a hook to call after a transaction commit attempt. The specified hook function will be called after the transaction commit succeeds or aborts. The first argument passed to the hook is a Boolean value, true if the commit succeeded, or false if the commit aborted. `args` specifies additional positional, and `kws` keyword, arguments to pass to the hook. `args` is a sequence of positional arguments to be passed, defaulting to an empty tuple (only the true/false success argument is passed). `kws` is a dictionary of keyword argument names and values to be passed, or the default None (no keyword arguments are passed). Multiple hooks can be registered and will be called in the order they were registered (first registered, first called). This method can also be called from a hook: an executing hook can register more hooks. Applications should take care to avoid creating infinite loops by recursively registering hooks. Hooks are called only for a top-level commit. A savepoint creation does not call any hooks. Calling a hook "consumes" its registration: hook registrations do not persist across transactions. If it's desired to call the same hook on every transaction commit, then addAfterCommitHook() must be called with that hook during every transaction; in such a case consider registering a synchronizer object via a TransactionManager's registerSynch() method instead. """ def getAfterCommitHooks(): """Return iterable producing the registered addAfterCommit hooks. A triple (hook, args, kws) is produced for each registered hook. The hooks are produced in the order in which they would be invoked by a top-level transaction commit. """ class ITransactionDeprecated(zope.interface.Interface): """Deprecated parts of the transaction API.""" def begin(info=None): """Begin a new transaction. If the transaction is in progress, it is aborted and a new transaction is started using the same transaction object. """ # TODO: deprecate this for 3.6. def register(object): """Register the given object for transaction control.""" class IDataManager(zope.interface.Interface): """Objects that manage transactional storage. These objects may manage data for other objects, or they may manage non-object storages, such as relational databases. For example, a ZODB.Connection. Note that when some data is modified, that data's data manager should join a transaction so that data can be committed when the user commits the transaction. """ transaction_manager = zope.interface.Attribute( """The transaction manager (TM) used by this data manager. This is a public attribute, intended for read-only use. The value is an instance of ITransactionManager, typically set by the data manager's constructor. """) def abort(transaction): """Abort a transaction and forget all changes. Abort must be called outside of a two-phase commit. Abort is called by the transaction manager to abort transactions that are not yet in a two-phase commit. It may also be called when rolling back a savepoint made before the data manager joined the transaction. In any case, after abort is called, the data manager is no longer participating in the transaction. If there are new changes, the data manager must rejoin the transaction. """ # Two-phase commit protocol. These methods are called by the ITransaction # object associated with the transaction being committed. The sequence # of calls normally follows this regular expression: # tpc_begin commit tpc_vote (tpc_finish | tpc_abort) def tpc_begin(transaction): """Begin commit of a transaction, starting the two-phase commit. transaction is the ITransaction instance associated with the transaction being committed. """ def commit(transaction): """Commit modifications to registered objects. Save changes to be made persistent if the transaction commits (if tpc_finish is called later). If tpc_abort is called later, changes must not persist. This includes conflict detection and handling. If no conflicts or errors occur, the data manager should be prepared to make the changes persist when tpc_finish is called. """ def tpc_vote(transaction): """Verify that a data manager can commit the transaction. This is the last chance for a data manager to vote 'no'. A data manager votes 'no' by raising an exception. transaction is the ITransaction instance associated with the transaction being committed. """ def tpc_finish(transaction): """Indicate confirmation that the transaction is done. Make all changes to objects modified by this transaction persist. transaction is the ITransaction instance associated with the transaction being committed. This should never fail. If this raises an exception, the database is not expected to maintain consistency; it's a serious error. """ def tpc_abort(transaction): """Abort a transaction. This is called by a transaction manager to end a two-phase commit on the data manager. Abandon all changes to objects modified by this transaction. transaction is the ITransaction instance associated with the transaction being committed. This should never fail. """ def sortKey(): """Return a key to use for ordering registered DataManagers. ZODB uses a global sort order to prevent deadlock when it commits transactions involving multiple resource managers. The resource manager must define a sortKey() method that provides a global ordering for resource managers. """ # Alternate version: #"""Return a consistent sort key for this connection. # #This allows ordering multiple connections that use the same storage in #a consistent manner. This is unique for the lifetime of a connection, #which is good enough to avoid ZEO deadlocks. #""" class ISavepointDataManager(IDataManager): def savepoint(): """Return a data-manager savepoint (IDataManagerSavepoint). """ class IDataManagerSavepoint(zope.interface.Interface): """Savepoint for data-manager changes for use in transaction savepoints. Datamanager savepoints are used by, and only by, transaction savepoints. Note that data manager savepoints don't have any notion of, or responsibility for, validity. It isn't the responsibility of data-manager savepoints to prevent multiple rollbacks or rollbacks after transaction termination. Preventing invalid savepoint rollback is the responsibility of transaction rollbacks. Application code should never use data-manager savepoints. """ def rollback(): """Rollback any work done since the savepoint. """ class ISavepoint(zope.interface.Interface): """A transaction savepoint. """ def rollback(): """Rollback any work done since the savepoint. InvalidSavepointRollbackError is raised if the savepoint isn't valid. """ valid = zope.interface.Attribute( "Boolean indicating whether the savepoint is valid") class InvalidSavepointRollbackError(Exception): """Attempt to rollback an invalid savepoint. A savepoint may be invalid because: - The surrounding transaction has committed or aborted. - An earlier savepoint in the same transaction has been rolled back. """ class ISynchronizer(zope.interface.Interface): """Objects that participate in the transaction-boundary notification API. """ def beforeCompletion(transaction): """Hook that is called by the transaction at the start of a commit. """ def afterCompletion(transaction): """Hook that is called by the transaction after completing a commit. """ def newTransaction(transaction): """Hook that is called at the start of a transaction. This hook is called when, and only when, a transaction manager's begin() method is called explictly. """ class TransactionError(Exception): """An error occurred due to normal transaction processing.""" class TransactionFailedError(TransactionError): """Cannot perform an operation on a transaction that previously failed. An attempt was made to commit a transaction, or to join a transaction, but this transaction previously raised an exception during an attempt to commit it. The transaction must be explicitly aborted, either by invoking abort() on the transaction, or begin() on its transaction manager. """ class DoomedTransaction(TransactionError): """A commit was attempted on a transaction that was doomed.""" class TransientError(TransactionError): """An error has occured when performing a transaction. It's possible that retrying the transaction will succeed. """ zope2.13-2.13.21/source/transaction/transaction/__init__.py0000644000175000017500000000221512214017513022416 0ustar arnauarnau############################################################################ # # Copyright (c) 2001, 2002, 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################ """Exported transaction functions. $Id: __init__.py 112264 2010-05-12 20:36:53Z jim $ """ from transaction._transaction import Transaction from transaction._manager import TransactionManager from transaction._manager import ThreadTransactionManager manager = ThreadTransactionManager() get = __enter__ = manager.get begin = manager.begin commit = manager.commit abort = manager.abort __exit__ = manager.__exit__ doom = manager.doom isDoomed = manager.isDoomed savepoint = manager.savepoint attempts = manager.attempts zope2.13-2.13.21/source/transaction/transaction/weakset.py0000644000175000017500000000671612214017513022334 0ustar arnauarnau############################################################################ # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################ import weakref # A simple implementation of weak sets, supplying just enough of Python's # sets.Set interface for our needs. class WeakSet(object): """A set of objects that doesn't keep its elements alive. The objects in the set must be weakly referencable. The objects need not be hashable, and need not support comparison. Two objects are considered to be the same iff their id()s are equal. When the only references to an object are weak references (including those from WeakSets), the object can be garbage-collected, and will vanish from any WeakSets it may be a member of at that time. """ def __init__(self): # Map id(obj) to obj. By using ids as keys, we avoid requiring # that the elements be hashable or comparable. self.data = weakref.WeakValueDictionary() def __len__(self): return len(self.data) def __contains__(self, obj): return id(obj) in self.data # Same as a Set, add obj to the collection. def add(self, obj): self.data[id(obj)] = obj # Same as a Set, remove obj from the collection, and raise # KeyError if obj not in the collection. def remove(self, obj): del self.data[id(obj)] # f is a one-argument function. Execute f(elt) for each elt in the # set. f's return value is ignored. def map(self, f): for wr in self.as_weakref_list(): elt = wr() if elt is not None: f(elt) # Return a list of weakrefs to all the objects in the collection. # Because a weak dict is used internally, iteration is dicey (the # underlying dict may change size during iteration, due to gc or # activity from other threads). as_weakef_list() is safe. # # Something like this should really be a method of Python's weak dicts. # If we invoke self.data.values() instead, we get back a list of live # objects instead of weakrefs. If gc occurs while this list is alive, # all the objects move to an older generation (because they're strongly # referenced by the list!). They can't get collected then, until a # less frequent collection of the older generation. Before then, if we # invoke self.data.values() again, they're still alive, and if gc occurs # while that list is alive they're all moved to yet an older generation. # And so on. Stress tests showed that it was easy to get into a state # where a WeakSet grows without bounds, despite that almost all its # elements are actually trash. By returning a list of weakrefs instead, # we avoid that, although the decision to use weakrefs is now# very # visible to our clients. def as_weakref_list(self): # We're cheating by breaking into the internals of Python's # WeakValueDictionary here (accessing its .data attribute). return self.data.data.values() zope2.13-2.13.21/source/transaction/transaction/tests/0000755000175000017500000000000012214017513021447 5ustar arnauarnauzope2.13-2.13.21/source/transaction/transaction/tests/savepointsample.py0000644000175000017500000001570412214017513025242 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Savepoint data manager implementation example. Sample data manager implementation that illustrates how to implement savepoints. See savepoint.txt in the transaction package. """ import UserDict from zope import interface import transaction.interfaces class SampleDataManager(UserDict.DictMixin): """Sample implementation of data manager that doesn't support savepoints This data manager stores named simple values, like strings and numbers. """ interface.implements(transaction.interfaces.IDataManager) def __init__(self, transaction_manager=None): if transaction_manager is None: # Use the thread-local transaction manager if none is provided: import transaction transaction_manager = transaction.manager self.transaction_manager = transaction_manager # Our committed and uncommitted data: self.committed = {} self.uncommitted = self.committed.copy() # Our transaction state: # # If our uncommitted data is modified, we'll join a transaction # and keep track of the transaction we joined. Any commit # related messages we get should be for this same transaction self.transaction = None # What phase, if any, of two-phase commit we are in: self.tpc_phase = None ####################################################################### # Provide a mapping interface to uncommitted data. We provide # a basic subset of the interface. DictMixin does the rest. def __getitem__(self, name): return self.uncommitted[name] def __setitem__(self, name, value): self._join() # join the current transaction, if we haven't already self.uncommitted[name] = value def __delitem__(self, name): self._join() # join the current transaction, if we haven't already del self.uncommitted[name] def keys(self): return self.uncommitted.keys() # ####################################################################### ####################################################################### # Transaction methods def _join(self): # If this is the first change in the transaction, join the transaction if self.transaction is None: self.transaction = self.transaction_manager.get() self.transaction.join(self) def _resetTransaction(self): self.last_note = getattr(self.transaction, 'description', None) self.transaction = None self.tpc_phase = None def abort(self, transaction): """Throw away changes made before the commit process has started """ assert ((transaction is self.transaction) or (self.transaction is None) ), "Must not change transactions" assert self.tpc_phase is None, "Must be called outside of tpc" self.uncommitted = self.committed.copy() self._resetTransaction() def tpc_begin(self, transaction): """Enter two-phase commit """ assert transaction is self.transaction, "Must not change transactions" assert self.tpc_phase is None, "Must be called outside of tpc" self.tpc_phase = 1 def commit(self, transaction): """Record data modified during the transaction """ assert transaction is self.transaction, "Must not change transactions" assert self.tpc_phase == 1, "Must be called in first phase of tpc" # In our simple example, we don't need to do anything. # A more complex data manager would typically write to some sort # of log. def tpc_vote(self, transaction): assert transaction is self.transaction, "Must not change transactions" assert self.tpc_phase == 1, "Must be called in first phase of tpc" # This particular data manager is always ready to vote. # Real data managers will usually need to take some steps to # make sure that the finish will succeed self.tpc_phase = 2 def tpc_finish(self, transaction): assert transaction is self.transaction, "Must not change transactions" assert self.tpc_phase == 2, "Must be called in second phase of tpc" self.committed = self.uncommitted.copy() self._resetTransaction() def tpc_abort(self, transaction): assert transaction is self.transaction, "Must not change transactions" assert self.tpc_phase is not None, "Must be called inside of tpc" self.uncommitted = self.committed.copy() self._resetTransaction() # ####################################################################### ####################################################################### # Other data manager methods def sortKey(self): # Commit operations on multiple data managers are performed in # sort key order. This important to avoid deadlock when data # managers are shared among multiple threads or processes and # use locks to manage that sharing. We aren't going to bother # with that here. return str(id(self)) # ####################################################################### class SampleSavepointDataManager(SampleDataManager): """Sample implementation of a savepoint-supporting data manager This extends the basic data manager with savepoint support. """ interface.implements(transaction.interfaces.ISavepointDataManager) def savepoint(self): # When we create the savepoint, we save the existing database state. return SampleSavepoint(self, self.uncommitted.copy()) def _rollback_savepoint(self, savepoint): # When we rollback the savepoint, we restore the saved data. # Caution: without the copy(), further changes to the database # could reflect in savepoint.data, and then `savepoint` would no # longer contain the originally saved data, and so `savepoint` # couldn't restore the original state if a rollback to this # savepoint was done again. IOW, copy() is necessary. self.uncommitted = savepoint.data.copy() class SampleSavepoint: interface.implements(transaction.interfaces.IDataManagerSavepoint) def __init__(self, data_manager, data): self.data_manager = data_manager self.data = data def rollback(self): self.data_manager._rollback_savepoint(self) zope2.13-2.13.21/source/transaction/transaction/tests/test_savepoint.py0000644000175000017500000000470212214017513025073 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of savepoint feature """ import unittest import doctest def testRollbackRollsbackDataManagersThatJoinedLater(): """ A savepoint needs to not just rollback it's savepoints, but needs to rollback savepoints for data managers that joined savepoints after the savepoint: >>> import transaction >>> from transaction.tests import savepointsample >>> dm = savepointsample.SampleSavepointDataManager() >>> dm['name'] = 'bob' >>> sp1 = transaction.savepoint() >>> dm['job'] = 'geek' >>> sp2 = transaction.savepoint() >>> dm['salary'] = 'fun' >>> dm2 = savepointsample.SampleSavepointDataManager() >>> dm2['name'] = 'sally' >>> 'name' in dm True >>> 'job' in dm True >>> 'salary' in dm True >>> 'name' in dm2 True >>> sp1.rollback() >>> 'name' in dm True >>> 'job' in dm False >>> 'salary' in dm False >>> 'name' in dm2 False """ def test_commit_after_rollback_for_dm_that_joins_after_savepoint(): """ There was a problem handling data managers that joined after a savepoint. If the savepoint was rolled back and then changes made, the dm would end up being joined twice, leading to extra tpc calls and pain. >>> import transaction >>> sp = transaction.savepoint() >>> from transaction.tests import savepointsample >>> dm = savepointsample.SampleSavepointDataManager() >>> dm['name'] = 'bob' >>> sp.rollback() >>> dm['name'] = 'Bob' >>> transaction.commit() >>> dm['name'] 'Bob' """ def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('savepoint.txt'), doctest.DocTestSuite(), )) # additional_tests is for setuptools "setup.py test" support additional_tests = test_suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/transaction/transaction/tests/convenience.txt0000644000175000017500000001121112214017513024500 0ustar arnauarnauTransaction convenience support =============================== (We *really* need to write proper documentation for the transaction package, but I don't want to block the conveniences documented here for that.) with support ------------ We can now use the with statement to define transaction boundaries. >>> import transaction.tests.savepointsample >>> dm = transaction.tests.savepointsample.SampleSavepointDataManager() >>> dm.keys() [] We can use the transaction module directly: >>> with transaction as t: ... dm['z'] = 1 ... t.note('test 1') >>> dm['z'] 1 >>> dm.last_note 'test 1' >>> with transaction: ... dm['z'] = 2 ... xxx Traceback (most recent call last): ... NameError: name 'xxx' is not defined >>> dm['z'] 1 We can use it with a manager: >>> with transaction.manager as t: ... dm['z'] = 3 ... t.note('test 3') >>> dm['z'] 3 >>> dm.last_note 'test 3' >>> with transaction: ... dm['z'] = 4 ... xxx Traceback (most recent call last): ... NameError: name 'xxx' is not defined >>> dm['z'] 3 Retries ------- Commits can fail for transient reasons, especially conflicts. Applications will often retry transactions some number of times to overcome transient failures. This typically looks something like:: for i in range(3): try: with transaction: ... some something ... except SomeTransientException: contine else: break This is rather ugly. Transaction managers provide a helper for this case. To show this, we'll use a contrived example: >>> ntry = 0 >>> with transaction: ... dm['ntry'] = 0 >>> import transaction.interfaces >>> class Retry(transaction.interfaces.TransientError): ... pass >>> for attempt in transaction.manager.attempts(): ... with attempt as t: ... t.note('test') ... print dm['ntry'], ntry ... ntry += 1 ... dm['ntry'] = ntry ... if ntry % 3: ... raise Retry(ntry) 0 0 0 1 0 2 The raising of a subclass of TransientError is critical here. It's what signals that the transaction should be retried. It is generally up to the data manager to signal that a transaction should try again by raising a subclass of TransientError (or TransientError itself, of course). You shouldn't make any assumptions about the object returned by the iterator. (It isn't a transaction or transaction manager, as far as you know. :) If you use the ``as`` keyword in the ``with`` statement, a transaction object will be assigned to the variable named. By default, it tries 3 times. We can tell it how many times to try: >>> for attempt in transaction.manager.attempts(2): ... with attempt: ... ntry += 1 ... if ntry % 3: ... raise Retry(ntry) Traceback (most recent call last): ... Retry: 5 It it doesn't succeed in that many times, the exception will be propagated. Of course, other errors are propagated directly: >>> ntry = 0 >>> for attempt in transaction.manager.attempts(): ... with attempt: ... ntry += 1 ... if ntry == 3: ... raise ValueError(ntry) Traceback (most recent call last): ... ValueError: 3 We can use the default transaction manager: >>> for attempt in transaction.attempts(): ... with attempt as t: ... t.note('test') ... print dm['ntry'], ntry ... ntry += 1 ... dm['ntry'] = ntry ... if ntry % 3: ... raise Retry(ntry) 3 3 3 4 3 5 Sometimes, a data manager doesn't raise exceptions directly, but wraps other other systems that raise exceptions outside of it's control. Data managers can provide a should_retry method that takes an exception instance and returns True if the transaction should be attempted again. >>> class DM(transaction.tests.savepointsample.SampleSavepointDataManager): ... def should_retry(self, e): ... if 'should retry' in str(e): ... return True >>> ntry = 0 >>> dm2 = DM() >>> with transaction: ... dm2['ntry'] = 0 >>> for attempt in transaction.manager.attempts(): ... with attempt: ... print dm['ntry'], ntry ... ntry += 1 ... dm['ntry'] = ntry ... dm2['ntry'] = ntry ... if ntry % 3: ... raise ValueError('we really should retry this') 6 0 6 1 6 2 >>> dm2['ntry'] 3 zope2.13-2.13.21/source/transaction/transaction/tests/test_weakset.py0000644000175000017500000000434412214017513024530 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import unittest from transaction.weakset import WeakSet class Dummy: pass class WeakSetTests(unittest.TestCase): def test_contains(self): w = WeakSet() dummy = Dummy() w.add(dummy) self.assertEqual(dummy in w, True) dummy2 = Dummy() self.assertEqual(dummy2 in w, False) def test_len(self): w = WeakSet() d1 = Dummy() d2 = Dummy() w.add(d1) w.add(d2) self.assertEqual(len(w), 2) del d1 self.assertEqual(len(w), 1) def test_remove(self): w = WeakSet() dummy = Dummy() w.add(dummy) self.assertEqual(dummy in w, True) w.remove(dummy) self.assertEqual(dummy in w, False) def test_as_weakref_list(self): w = WeakSet() dummy = Dummy() dummy2 = Dummy() dummy3 = Dummy() w.add(dummy) w.add(dummy2) w.add(dummy3) del dummy3 L = [x() for x in w.as_weakref_list()] # L is a list, but it does not have a guaranteed order. self.assert_(list, type(L)) self.assertEqual(set(L), set([dummy, dummy2])) def test_map(self): w = WeakSet() dummy = Dummy() dummy2 = Dummy() dummy3 = Dummy() w.add(dummy) w.add(dummy2) w.add(dummy3) def poker(x): x.poked = 1 w.map(poker) for thing in dummy, dummy2, dummy3: self.assertEqual(thing.poked, 1) def test_suite(): return unittest.makeSuite(WeakSetTests) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/transaction/transaction/tests/sampledm.py0000644000175000017500000002505312214017513023630 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample objects for use in tests $Id: sampledm.py 29896 2005-04-07 04:48:06Z tim_one $ """ class DataManager(object): """Sample data manager This class provides a trivial data-manager implementation and doc strings to illustrate the the protocol and to provide a tool for writing tests. Our sample data manager has state that is updated through an inc method and through transaction operations. When we create a sample data manager: >>> dm = DataManager() It has two bits of state, state: >>> dm.state 0 and delta: >>> dm.delta 0 Both of which are initialized to 0. state is meant to model committed state, while delta represents tentative changes within a transaction. We change the state by calling inc: >>> dm.inc() which updates delta: >>> dm.delta 1 but state isn't changed until we commit the transaction: >>> dm.state 0 To commit the changes, we use 2-phase commit. We execute the first stage by calling prepare. We need to pass a transation. Our sample data managers don't really use the transactions for much, so we'll be lazy and use strings for transactions: >>> t1 = '1' >>> dm.prepare(t1) The sample data manager updates the state when we call prepare: >>> dm.state 1 >>> dm.delta 1 This is mainly so we can detect some affect of calling the methods. Now if we call commit: >>> dm.commit(t1) Our changes are"permanent". The state reflects the changes and the delta has been reset to 0. >>> dm.state 1 >>> dm.delta 0 """ def __init__(self): self.state = 0 self.sp = 0 self.transaction = None self.delta = 0 self.prepared = False def inc(self, n=1): self.delta += n def prepare(self, transaction): """Prepare to commit data >>> dm = DataManager() >>> dm.inc() >>> t1 = '1' >>> dm.prepare(t1) >>> dm.commit(t1) >>> dm.state 1 >>> dm.inc() >>> t2 = '2' >>> dm.prepare(t2) >>> dm.abort(t2) >>> dm.state 1 It is en error to call prepare more than once without an intervening commit or abort: >>> dm.prepare(t1) >>> dm.prepare(t1) Traceback (most recent call last): ... TypeError: Already prepared >>> dm.prepare(t2) Traceback (most recent call last): ... TypeError: Already prepared >>> dm.abort(t1) If there was a preceeding savepoint, the transaction must match: >>> rollback = dm.savepoint(t1) >>> dm.prepare(t2) Traceback (most recent call last): ,,, TypeError: ('Transaction missmatch', '2', '1') >>> dm.prepare(t1) """ if self.prepared: raise TypeError('Already prepared') self._checkTransaction(transaction) self.prepared = True self.transaction = transaction self.state += self.delta def _checkTransaction(self, transaction): if (transaction is not self.transaction and self.transaction is not None): raise TypeError("Transaction missmatch", transaction, self.transaction) def abort(self, transaction): """Abort a transaction The abort method can be called before two-phase commit to throw away work done in the transaction: >>> dm = DataManager() >>> dm.inc() >>> dm.state, dm.delta (0, 1) >>> t1 = '1' >>> dm.abort(t1) >>> dm.state, dm.delta (0, 0) The abort method also throws away work done in savepoints: >>> dm.inc() >>> r = dm.savepoint(t1) >>> dm.inc() >>> r = dm.savepoint(t1) >>> dm.state, dm.delta (0, 2) >>> dm.abort(t1) >>> dm.state, dm.delta (0, 0) If savepoints are used, abort must be passed the same transaction: >>> dm.inc() >>> r = dm.savepoint(t1) >>> t2 = '2' >>> dm.abort(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '1') >>> dm.abort(t1) The abort method is also used to abort a two-phase commit: >>> dm.inc() >>> dm.state, dm.delta (0, 1) >>> dm.prepare(t1) >>> dm.state, dm.delta (1, 1) >>> dm.abort(t1) >>> dm.state, dm.delta (0, 0) Of course, the transactions passed to prepare and abort must match: >>> dm.prepare(t1) >>> dm.abort(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '1') >>> dm.abort(t1) """ self._checkTransaction(transaction) if self.transaction is not None: self.transaction = None if self.prepared: self.state -= self.delta self.prepared = False self.delta = 0 def commit(self, transaction): """Complete two-phase commit >>> dm = DataManager() >>> dm.state 0 >>> dm.inc() We start two-phase commit by calling prepare: >>> t1 = '1' >>> dm.prepare(t1) We complete it by calling commit: >>> dm.commit(t1) >>> dm.state 1 It is an error ro call commit without calling prepare first: >>> dm.inc() >>> t2 = '2' >>> dm.commit(t2) Traceback (most recent call last): ... TypeError: Not prepared to commit >>> dm.prepare(t2) >>> dm.commit(t2) If course, the transactions given to prepare and commit must be the same: >>> dm.inc() >>> t3 = '3' >>> dm.prepare(t3) >>> dm.commit(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '3') """ if not self.prepared: raise TypeError('Not prepared to commit') self._checkTransaction(transaction) self.delta = 0 self.transaction = None self.prepared = False def savepoint(self, transaction): """Provide the ability to rollback transaction state Savepoints provide a way to: - Save partial transaction work. For some data managers, this could allow resources to be used more efficiently. - Provide the ability to revert state to a point in a transaction without aborting the entire transaction. In other words, savepoints support partial aborts. Savepoints don't use two-phase commit. If there are errors in setting or rolling back to savepoints, the application should abort the containing transaction. This is *not* the responsibility of the data manager. Savepoints are always associated with a transaction. Any work done in a savepoint's transaction is tentative until the transaction is committed using two-phase commit. >>> dm = DataManager() >>> dm.inc() >>> t1 = '1' >>> r = dm.savepoint(t1) >>> dm.state, dm.delta (0, 1) >>> dm.inc() >>> dm.state, dm.delta (0, 2) >>> r.rollback() >>> dm.state, dm.delta (0, 1) >>> dm.prepare(t1) >>> dm.commit(t1) >>> dm.state, dm.delta (1, 0) Savepoints must have the same transaction: >>> r1 = dm.savepoint(t1) >>> dm.state, dm.delta (1, 0) >>> dm.inc() >>> dm.state, dm.delta (1, 1) >>> t2 = '2' >>> r2 = dm.savepoint(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '1') >>> r2 = dm.savepoint(t1) >>> dm.inc() >>> dm.state, dm.delta (1, 2) If we rollback to an earlier savepoint, we discard all work done later: >>> r1.rollback() >>> dm.state, dm.delta (1, 0) and we can no longer rollback to the later savepoint: >>> r2.rollback() Traceback (most recent call last): ... TypeError: ('Attempt to roll back to invalid save point', 3, 2) We can roll back to a savepoint as often as we like: >>> r1.rollback() >>> r1.rollback() >>> r1.rollback() >>> dm.state, dm.delta (1, 0) >>> dm.inc() >>> dm.inc() >>> dm.inc() >>> dm.state, dm.delta (1, 3) >>> r1.rollback() >>> dm.state, dm.delta (1, 0) But we can't rollback to a savepoint after it has been committed: >>> dm.prepare(t1) >>> dm.commit(t1) >>> r1.rollback() Traceback (most recent call last): ... TypeError: Attempt to rollback stale rollback """ if self.prepared: raise TypeError("Can't get savepoint during two-phase commit") self._checkTransaction(transaction) self.transaction = transaction self.sp += 1 return Rollback(self) class Rollback(object): def __init__(self, dm): self.dm = dm self.sp = dm.sp self.delta = dm.delta self.transaction = dm.transaction def rollback(self): if self.transaction is not self.dm.transaction: raise TypeError("Attempt to rollback stale rollback") if self.dm.sp < self.sp: raise TypeError("Attempt to roll back to invalid save point", self.sp, self.dm.sp) self.dm.sp = self.sp self.dm.delta = self.delta def test_suite(): from doctest import DocTestSuite return DocTestSuite() if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/transaction/transaction/tests/savepoint.txt0000644000175000017500000001774212214017513024233 0ustar arnauarnauSavepoints ========== Savepoints provide a way to save to disk intermediate work done during a transaction allowing: - partial transaction (subtransaction) rollback (abort) - state of saved objects to be freed, freeing on-line memory for other uses Savepoints make it possible to write atomic subroutines that don't make top-level transaction commitments. Applications ------------ To demonstrate how savepoints work with transactions, we've provided a sample data manager implementation that provides savepoint support. The primary purpose of this data manager is to provide code that can be read to understand how savepoints work. The secondary purpose is to provide support for demonstrating the correct operation of savepoint support within the transaction system. This data manager is very simple. It provides flat storage of named immutable values, like strings and numbers. >>> import transaction >>> from transaction.tests import savepointsample >>> dm = savepointsample.SampleSavepointDataManager() >>> dm['name'] = 'bob' As with other data managers, we can commit changes: >>> transaction.commit() >>> dm['name'] 'bob' and abort changes: >>> dm['name'] = 'sally' >>> dm['name'] 'sally' >>> transaction.abort() >>> dm['name'] 'bob' Now, let's look at an application that manages funds for people. It allows deposits and debits to be entered for multiple people. It accepts a sequence of entries and generates a sequence of status messages. For each entry, it applies the change and then validates the user's account. If the user's account is invalid, we roll back the change for that entry. The success or failure of an entry is indicated in the output status. First we'll initialize some accounts: >>> dm['bob-balance'] = 0.0 >>> dm['bob-credit'] = 0.0 >>> dm['sally-balance'] = 0.0 >>> dm['sally-credit'] = 100.0 >>> transaction.commit() Now, we'll define a validation function to validate an account: >>> def validate_account(name): ... if dm[name+'-balance'] + dm[name+'-credit'] < 0: ... raise ValueError('Overdrawn', name) And a function to apply entries. If the function fails in some unexpected way, it rolls back all of its changes and prints the error: >>> def apply_entries(entries): ... savepoint = transaction.savepoint() ... try: ... for name, amount in entries: ... entry_savepoint = transaction.savepoint() ... try: ... dm[name+'-balance'] += amount ... validate_account(name) ... except ValueError, error: ... entry_savepoint.rollback() ... print 'Error', str(error) ... else: ... print 'Updated', name ... except Exception, error: ... savepoint.rollback() ... print 'Unexpected exception', error Now let's try applying some entries: >>> apply_entries([ ... ('bob', 10.0), ... ('sally', 10.0), ... ('bob', 20.0), ... ('sally', 10.0), ... ('bob', -100.0), ... ('sally', -100.0), ... ]) Updated bob Updated sally Updated bob Updated sally Error ('Overdrawn', 'bob') Updated sally >>> dm['bob-balance'] 30.0 >>> dm['sally-balance'] -80.0 If we provide entries that cause an unexpected error: >>> apply_entries([ ... ('bob', 10.0), ... ('sally', 10.0), ... ('bob', '20.0'), ... ('sally', 10.0), ... ]) Updated bob Updated sally Unexpected exception unsupported operand type(s) for +=: 'float' and 'str' Because the apply_entries used a savepoint for the entire function, it was able to rollback the partial changes without rolling back changes made in the previous call to ``apply_entries``: >>> dm['bob-balance'] 30.0 >>> dm['sally-balance'] -80.0 If we now abort the outer transactions, the earlier changes will go away: >>> transaction.abort() >>> dm['bob-balance'] 0.0 >>> dm['sally-balance'] 0.0 Savepoint invalidation ---------------------- A savepoint can be used any number of times: >>> dm['bob-balance'] = 100.0 >>> dm['bob-balance'] 100.0 >>> savepoint = transaction.savepoint() >>> dm['bob-balance'] = 200.0 >>> dm['bob-balance'] 200.0 >>> savepoint.rollback() >>> dm['bob-balance'] 100.0 >>> savepoint.rollback() # redundant, but should be harmless >>> dm['bob-balance'] 100.0 >>> dm['bob-balance'] = 300.0 >>> dm['bob-balance'] 300.0 >>> savepoint.rollback() >>> dm['bob-balance'] 100.0 However, using a savepoint invalidates any savepoints that come after it: >>> dm['bob-balance'] = 200.0 >>> dm['bob-balance'] 200.0 >>> savepoint1 = transaction.savepoint() >>> dm['bob-balance'] = 300.0 >>> dm['bob-balance'] 300.0 >>> savepoint2 = transaction.savepoint() >>> savepoint.rollback() >>> dm['bob-balance'] 100.0 >>> savepoint2.rollback() Traceback (most recent call last): ... InvalidSavepointRollbackError >>> savepoint1.rollback() Traceback (most recent call last): ... InvalidSavepointRollbackError >>> transaction.abort() Databases without savepoint support ----------------------------------- Normally it's an error to use savepoints with databases that don't support savepoints: >>> dm_no_sp = savepointsample.SampleDataManager() >>> dm_no_sp['name'] = 'bob' >>> transaction.commit() >>> dm_no_sp['name'] = 'sally' >>> savepoint = transaction.savepoint() Traceback (most recent call last): ... TypeError: ('Savepoints unsupported', {'name': 'bob'}) >>> transaction.abort() However, a flag can be passed to the transaction savepoint method to indicate that databases without savepoint support should be tolerated until a savepoint is rolled back. This allows transactions to proceed if there are no reasons to roll back: >>> dm_no_sp['name'] = 'sally' >>> savepoint = transaction.savepoint(1) >>> dm_no_sp['name'] = 'sue' >>> transaction.commit() >>> dm_no_sp['name'] 'sue' >>> dm_no_sp['name'] = 'sam' >>> savepoint = transaction.savepoint(1) >>> savepoint.rollback() Traceback (most recent call last): ... TypeError: ('Savepoints unsupported', {'name': 'sam'}) Failures -------- If a failure occurs when creating or rolling back a savepoint, the transaction state will be uncertain and the transaction will become uncommitable. From that point on, most transaction operations, including commit, will fail until the transaction is aborted. In the previous example, we got an error when we tried to rollback the savepoint. If we try to commit the transaction, the commit will fail: >>> transaction.commit() # doctest: +ELLIPSIS Traceback (most recent call last): ... TransactionFailedError: An operation previously failed, with traceback: ... TypeError: ('Savepoints unsupported', {'name': 'sam'}) We have to abort it to make any progress: >>> transaction.abort() Similarly, in our earlier example, where we tried to take a savepoint with a data manager that didn't support savepoints: >>> dm_no_sp['name'] = 'sally' >>> dm['name'] = 'sally' >>> savepoint = transaction.savepoint() Traceback (most recent call last): ... TypeError: ('Savepoints unsupported', {'name': 'sue'}) >>> transaction.commit() # doctest: +ELLIPSIS Traceback (most recent call last): ... TransactionFailedError: An operation previously failed, with traceback: ... TypeError: ('Savepoints unsupported', {'name': 'sue'}) >>> transaction.abort() After clearing the transaction with an abort, we can get on with new transactions: >>> dm_no_sp['name'] = 'sally' >>> dm['name'] = 'sally' >>> transaction.commit() >>> dm_no_sp['name'] 'sally' >>> dm['name'] 'sally' zope2.13-2.13.21/source/transaction/transaction/tests/test_SampleDataManager.py0000644000175000017500000002511412214017513026371 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample objects for use in tests """ from doctest import DocTestSuite class DataManager(object): """Sample data manager This class provides a trivial data-manager implementation and doc strings to illustrate the the protocol and to provide a tool for writing tests. Our sample data manager has state that is updated through an inc method and through transaction operations. When we create a sample data manager: >>> dm = DataManager() It has two bits of state, state: >>> dm.state 0 and delta: >>> dm.delta 0 Both of which are initialized to 0. state is meant to model committed state, while delta represents tentative changes within a transaction. We change the state by calling inc: >>> dm.inc() which updates delta: >>> dm.delta 1 but state isn't changed until we commit the transaction: >>> dm.state 0 To commit the changes, we use 2-phase commit. We execute the first stage by calling prepare. We need to pass a transation. Our sample data managers don't really use the transactions for much, so we'll be lazy and use strings for transactions: >>> t1 = '1' >>> dm.prepare(t1) The sample data manager updates the state when we call prepare: >>> dm.state 1 >>> dm.delta 1 This is mainly so we can detect some affect of calling the methods. Now if we call commit: >>> dm.commit(t1) Our changes are"permanent". The state reflects the changes and the delta has been reset to 0. >>> dm.state 1 >>> dm.delta 0 """ def __init__(self): self.state = 0 self.sp = 0 self.transaction = None self.delta = 0 self.prepared = False def inc(self, n=1): self.delta += n def prepare(self, transaction): """Prepare to commit data >>> dm = DataManager() >>> dm.inc() >>> t1 = '1' >>> dm.prepare(t1) >>> dm.commit(t1) >>> dm.state 1 >>> dm.inc() >>> t2 = '2' >>> dm.prepare(t2) >>> dm.abort(t2) >>> dm.state 1 It is en error to call prepare more than once without an intervening commit or abort: >>> dm.prepare(t1) >>> dm.prepare(t1) Traceback (most recent call last): ... TypeError: Already prepared >>> dm.prepare(t2) Traceback (most recent call last): ... TypeError: Already prepared >>> dm.abort(t1) If there was a preceeding savepoint, the transaction must match: >>> rollback = dm.savepoint(t1) >>> dm.prepare(t2) Traceback (most recent call last): ,,, TypeError: ('Transaction missmatch', '2', '1') >>> dm.prepare(t1) """ if self.prepared: raise TypeError('Already prepared') self._checkTransaction(transaction) self.prepared = True self.transaction = transaction self.state += self.delta def _checkTransaction(self, transaction): if (transaction is not self.transaction and self.transaction is not None): raise TypeError("Transaction missmatch", transaction, self.transaction) def abort(self, transaction): """Abort a transaction The abort method can be called before two-phase commit to throw away work done in the transaction: >>> dm = DataManager() >>> dm.inc() >>> dm.state, dm.delta (0, 1) >>> t1 = '1' >>> dm.abort(t1) >>> dm.state, dm.delta (0, 0) The abort method also throws away work done in savepoints: >>> dm.inc() >>> r = dm.savepoint(t1) >>> dm.inc() >>> r = dm.savepoint(t1) >>> dm.state, dm.delta (0, 2) >>> dm.abort(t1) >>> dm.state, dm.delta (0, 0) If savepoints are used, abort must be passed the same transaction: >>> dm.inc() >>> r = dm.savepoint(t1) >>> t2 = '2' >>> dm.abort(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '1') >>> dm.abort(t1) The abort method is also used to abort a two-phase commit: >>> dm.inc() >>> dm.state, dm.delta (0, 1) >>> dm.prepare(t1) >>> dm.state, dm.delta (1, 1) >>> dm.abort(t1) >>> dm.state, dm.delta (0, 0) Of course, the transactions passed to prepare and abort must match: >>> dm.prepare(t1) >>> dm.abort(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '1') >>> dm.abort(t1) """ self._checkTransaction(transaction) if self.transaction is not None: self.transaction = None if self.prepared: self.state -= self.delta self.prepared = False self.delta = 0 def commit(self, transaction): """Complete two-phase commit >>> dm = DataManager() >>> dm.state 0 >>> dm.inc() We start two-phase commit by calling prepare: >>> t1 = '1' >>> dm.prepare(t1) We complete it by calling commit: >>> dm.commit(t1) >>> dm.state 1 It is an error ro call commit without calling prepare first: >>> dm.inc() >>> t2 = '2' >>> dm.commit(t2) Traceback (most recent call last): ... TypeError: Not prepared to commit >>> dm.prepare(t2) >>> dm.commit(t2) If course, the transactions given to prepare and commit must be the same: >>> dm.inc() >>> t3 = '3' >>> dm.prepare(t3) >>> dm.commit(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '3') """ if not self.prepared: raise TypeError('Not prepared to commit') self._checkTransaction(transaction) self.delta = 0 self.transaction = None self.prepared = False def savepoint(self, transaction): """Provide the ability to rollback transaction state Savepoints provide a way to: - Save partial transaction work. For some data managers, this could allow resources to be used more efficiently. - Provide the ability to revert state to a point in a transaction without aborting the entire transaction. In other words, savepoints support partial aborts. Savepoints don't use two-phase commit. If there are errors in setting or rolling back to savepoints, the application should abort the containing transaction. This is *not* the responsibility of the data manager. Savepoints are always associated with a transaction. Any work done in a savepoint's transaction is tentative until the transaction is committed using two-phase commit. >>> dm = DataManager() >>> dm.inc() >>> t1 = '1' >>> r = dm.savepoint(t1) >>> dm.state, dm.delta (0, 1) >>> dm.inc() >>> dm.state, dm.delta (0, 2) >>> r.rollback() >>> dm.state, dm.delta (0, 1) >>> dm.prepare(t1) >>> dm.commit(t1) >>> dm.state, dm.delta (1, 0) Savepoints must have the same transaction: >>> r1 = dm.savepoint(t1) >>> dm.state, dm.delta (1, 0) >>> dm.inc() >>> dm.state, dm.delta (1, 1) >>> t2 = '2' >>> r2 = dm.savepoint(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '1') >>> r2 = dm.savepoint(t1) >>> dm.inc() >>> dm.state, dm.delta (1, 2) If we rollback to an earlier savepoint, we discard all work done later: >>> r1.rollback() >>> dm.state, dm.delta (1, 0) and we can no longer rollback to the later savepoint: >>> r2.rollback() Traceback (most recent call last): ... TypeError: ('Attempt to roll back to invalid save point', 3, 2) We can roll back to a savepoint as often as we like: >>> r1.rollback() >>> r1.rollback() >>> r1.rollback() >>> dm.state, dm.delta (1, 0) >>> dm.inc() >>> dm.inc() >>> dm.inc() >>> dm.state, dm.delta (1, 3) >>> r1.rollback() >>> dm.state, dm.delta (1, 0) But we can't rollback to a savepoint after it has been committed: >>> dm.prepare(t1) >>> dm.commit(t1) >>> r1.rollback() Traceback (most recent call last): ... TypeError: Attempt to rollback stale rollback """ if self.prepared: raise TypeError("Can't get savepoint during two-phase commit") self._checkTransaction(transaction) self.transaction = transaction self.sp += 1 return Rollback(self) class Rollback(object): def __init__(self, dm): self.dm = dm self.sp = dm.sp self.delta = dm.delta self.transaction = dm.transaction def rollback(self): if self.transaction is not self.dm.transaction: raise TypeError("Attempt to rollback stale rollback") if self.dm.sp < self.sp: raise TypeError("Attempt to roll back to invalid save point", self.sp, self.dm.sp) self.dm.sp = self.sp self.dm.delta = self.delta def test_suite(): return DocTestSuite() # additional_tests is for setuptools "setup.py test" support additional_tests = test_suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/transaction/transaction/tests/__init__.py0000644000175000017500000000000212214017513023550 0ustar arnauarnau# zope2.13-2.13.21/source/transaction/transaction/tests/test_SampleResourceManager.py0000644000175000017500000002750212214017513027312 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample objects for use in tests $Id: test_SampleResourceManager.py 112140 2010-05-07 15:29:36Z tseaver $ """ class ResourceManager(object): """Sample resource manager. This class provides a trivial resource-manager implementation and doc strings to illustrate the protocol and to provide a tool for writing tests. Our sample resource manager has state that is updated through an inc method and through transaction operations. When we create a sample resource manager: >>> rm = ResourceManager() It has two pieces state, state and delta, both initialized to 0: >>> rm.state 0 >>> rm.delta 0 state is meant to model committed state, while delta represents tentative changes within a transaction. We change the state by calling inc: >>> rm.inc() which updates delta: >>> rm.delta 1 but state isn't changed until we commit the transaction: >>> rm.state 0 To commit the changes, we use 2-phase commit. We execute the first stage by calling prepare. We need to pass a transation. Our sample resource managers don't really use the transactions for much, so we'll be lazy and use strings for transactions. The sample resource manager updates the state when we call tpc_vote: >>> t1 = '1' >>> rm.tpc_begin(t1) >>> rm.state, rm.delta (0, 1) >>> rm.tpc_vote(t1) >>> rm.state, rm.delta (1, 1) Now if we call tpc_finish: >>> rm.tpc_finish(t1) Our changes are "permanent". The state reflects the changes and the delta has been reset to 0. >>> rm.state, rm.delta (1, 0) """ def __init__(self): self.state = 0 self.sp = 0 self.transaction = None self.delta = 0 self.txn_state = None def _check_state(self, *ok_states): if self.txn_state not in ok_states: raise ValueError("txn in state %r but expected one of %r" % (self.txn_state, ok_states)) def _checkTransaction(self, transaction): if (transaction is not self.transaction and self.transaction is not None): raise TypeError("Transaction missmatch", transaction, self.transaction) def inc(self, n=1): self.delta += n def tpc_begin(self, transaction): """Prepare to commit data. >>> rm = ResourceManager() >>> rm.inc() >>> t1 = '1' >>> rm.tpc_begin(t1) >>> rm.tpc_vote(t1) >>> rm.tpc_finish(t1) >>> rm.state 1 >>> rm.inc() >>> t2 = '2' >>> rm.tpc_begin(t2) >>> rm.tpc_vote(t2) >>> rm.tpc_abort(t2) >>> rm.state 1 It is an error to call tpc_begin more than once without completing two-phase commit: >>> rm.tpc_begin(t1) >>> rm.tpc_begin(t1) Traceback (most recent call last): ... ValueError: txn in state 'tpc_begin' but expected one of (None,) >>> rm.tpc_abort(t1) If there was a preceeding savepoint, the transaction must match: >>> rollback = rm.savepoint(t1) >>> rm.tpc_begin(t2) Traceback (most recent call last): ,,, TypeError: ('Transaction missmatch', '2', '1') >>> rm.tpc_begin(t1) """ self._checkTransaction(transaction) self._check_state(None) self.transaction = transaction self.txn_state = 'tpc_begin' def tpc_vote(self, transaction): """Verify that a data manager can commit the transaction. This is the last chance for a data manager to vote 'no'. A data manager votes 'no' by raising an exception. transaction is the ITransaction instance associated with the transaction being committed. """ self._checkTransaction(transaction) self._check_state('tpc_begin') self.state += self.delta self.txn_state = 'tpc_vote' def tpc_finish(self, transaction): """Complete two-phase commit >>> rm = ResourceManager() >>> rm.state 0 >>> rm.inc() We start two-phase commit by calling prepare: >>> t1 = '1' >>> rm.tpc_begin(t1) >>> rm.tpc_vote(t1) We complete it by calling tpc_finish: >>> rm.tpc_finish(t1) >>> rm.state 1 It is an error ro call tpc_finish without calling tpc_vote: >>> rm.inc() >>> t2 = '2' >>> rm.tpc_begin(t2) >>> rm.tpc_finish(t2) Traceback (most recent call last): ... ValueError: txn in state 'tpc_begin' but expected one of ('tpc_vote',) >>> rm.tpc_abort(t2) # clean slate >>> rm.tpc_begin(t2) >>> rm.tpc_vote(t2) >>> rm.tpc_finish(t2) Of course, the transactions given to tpc_begin and tpc_finish must be the same: >>> rm.inc() >>> t3 = '3' >>> rm.tpc_begin(t3) >>> rm.tpc_vote(t3) >>> rm.tpc_finish(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '3') """ self._checkTransaction(transaction) self._check_state('tpc_vote') self.delta = 0 self.transaction = None self.prepared = False self.txn_state = None def tpc_abort(self, transaction): """Abort a transaction The abort method can be called before two-phase commit to throw away work done in the transaction: >>> rm = ResourceManager() >>> rm.inc() >>> rm.state, rm.delta (0, 1) >>> t1 = '1' >>> rm.tpc_abort(t1) >>> rm.state, rm.delta (0, 0) The abort method also throws away work done in savepoints: >>> rm.inc() >>> r = rm.savepoint(t1) >>> rm.inc() >>> r = rm.savepoint(t1) >>> rm.state, rm.delta (0, 2) >>> rm.tpc_abort(t1) >>> rm.state, rm.delta (0, 0) If savepoints are used, abort must be passed the same transaction: >>> rm.inc() >>> r = rm.savepoint(t1) >>> t2 = '2' >>> rm.tpc_abort(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '1') >>> rm.tpc_abort(t1) The abort method is also used to abort a two-phase commit: >>> rm.inc() >>> rm.state, rm.delta (0, 1) >>> rm.tpc_begin(t1) >>> rm.state, rm.delta (0, 1) >>> rm.tpc_vote(t1) >>> rm.state, rm.delta (1, 1) >>> rm.tpc_abort(t1) >>> rm.state, rm.delta (0, 0) Of course, the transactions passed to prepare and abort must match: >>> rm.tpc_begin(t1) >>> rm.tpc_abort(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '1') >>> rm.tpc_abort(t1) This should never fail. """ self._checkTransaction(transaction) if self.transaction is not None: self.transaction = None if self.txn_state == 'tpc_vote': self.state -= self.delta self.txn_state = None self.delta = 0 def savepoint(self, transaction): """Provide the ability to rollback transaction state Savepoints provide a way to: - Save partial transaction work. For some resource managers, this could allow resources to be used more efficiently. - Provide the ability to revert state to a point in a transaction without aborting the entire transaction. In other words, savepoints support partial aborts. Savepoints don't use two-phase commit. If there are errors in setting or rolling back to savepoints, the application should abort the containing transaction. This is *not* the responsibility of the resource manager. Savepoints are always associated with a transaction. Any work done in a savepoint's transaction is tentative until the transaction is committed using two-phase commit. >>> rm = ResourceManager() >>> rm.inc() >>> t1 = '1' >>> r = rm.savepoint(t1) >>> rm.state, rm.delta (0, 1) >>> rm.inc() >>> rm.state, rm.delta (0, 2) >>> r.rollback() >>> rm.state, rm.delta (0, 1) >>> rm.tpc_begin(t1) >>> rm.tpc_vote(t1) >>> rm.tpc_finish(t1) >>> rm.state, rm.delta (1, 0) Savepoints must have the same transaction: >>> r1 = rm.savepoint(t1) >>> rm.state, rm.delta (1, 0) >>> rm.inc() >>> rm.state, rm.delta (1, 1) >>> t2 = '2' >>> r2 = rm.savepoint(t2) Traceback (most recent call last): ... TypeError: ('Transaction missmatch', '2', '1') >>> r2 = rm.savepoint(t1) >>> rm.inc() >>> rm.state, rm.delta (1, 2) If we rollback to an earlier savepoint, we discard all work done later: >>> r1.rollback() >>> rm.state, rm.delta (1, 0) and we can no longer rollback to the later savepoint: >>> r2.rollback() Traceback (most recent call last): ... TypeError: ('Attempt to roll back to invalid save point', 3, 2) We can roll back to a savepoint as often as we like: >>> r1.rollback() >>> r1.rollback() >>> r1.rollback() >>> rm.state, rm.delta (1, 0) >>> rm.inc() >>> rm.inc() >>> rm.inc() >>> rm.state, rm.delta (1, 3) >>> r1.rollback() >>> rm.state, rm.delta (1, 0) But we can't rollback to a savepoint after it has been committed: >>> rm.tpc_begin(t1) >>> rm.tpc_vote(t1) >>> rm.tpc_finish(t1) >>> r1.rollback() Traceback (most recent call last): ... TypeError: Attempt to rollback stale rollback """ if self.txn_state is not None: raise TypeError("Can't get savepoint during two-phase commit") self._checkTransaction(transaction) self.transaction = transaction self.sp += 1 return SavePoint(self) def discard(self, transaction): pass class SavePoint(object): def __init__(self, rm): self.rm = rm self.sp = rm.sp self.delta = rm.delta self.transaction = rm.transaction def rollback(self): if self.transaction is not self.rm.transaction: raise TypeError("Attempt to rollback stale rollback") if self.rm.sp < self.sp: raise TypeError("Attempt to roll back to invalid save point", self.sp, self.rm.sp) self.rm.sp = self.sp self.rm.delta = self.delta def discard(self): pass def test_suite(): from doctest import DocTestSuite return DocTestSuite() # additional_tests is for setuptools "setup.py test" support additional_tests = test_suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/transaction/transaction/tests/warnhook.py0000644000175000017500000000410212214017513023646 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import warnings class WarningsHook: """Hook to capture warnings generated by Python. The function warnings.showwarning() is designed to be hooked by application code, allowing the application to customize the way it handles warnings. This hook captures the unformatted warning information and stores it in a list. A test can inspect this list after the test is over. Issues: The warnings module has lots of delicate internal state. If a warning has been reported once, it won't be reported again. It may be necessary to extend this class with a mechanism for modifying the internal state so that we can be guaranteed a warning will be reported. If Python is run with a warnings filter, e.g. python -Werror, then a test that is trying to inspect a particular warning will fail. Perhaps this class can be extended to install more-specific filters the test to work anyway. """ def __init__(self): self.original = None self.warnings = [] def install(self): self.original = warnings.showwarning warnings.showwarning = self.showwarning def uninstall(self): assert self.original is not None warnings.showwarning = self.original self.original = None def showwarning(self, message, category, filename, lineno): self.warnings.append((str(message), category, filename, lineno)) def clear(self): self.warnings = [] zope2.13-2.13.21/source/transaction/transaction/tests/doom.txt0000644000175000017500000001004712214017513023150 0ustar arnauarnauDooming Transactions ==================== A doomed transaction behaves exactly the same way as an active transaction but raises an error on any attempt to commit it, thus forcing an abort. Doom is useful in places where abort is unsafe and an exception cannot be raised. This occurs when the programmer wants the code following the doom to run but not commit. It is unsafe to abort in these circumstances as a following get() may implicitly open a new transaction. Any attempt to commit a doomed transaction will raise a DoomedTransaction exception. An example of such a use case can be found in zope/app/form/browser/editview.py. Here a form validation failure must doom the transaction as committing the transaction may have side-effects. However, the form code must continue to calculate a form containing the error messages to return. For Zope in general, code running within a request should always doom transactions rather than aborting them. It is the responsibilty of the publication to either abort() or commit() the transaction. Application code can use savepoints and doom() safely. To see how it works we first need to create a stub data manager: >>> from transaction.interfaces import IDataManager >>> from zope.interface import implements >>> class DataManager: ... implements(IDataManager) ... def __init__(self): ... self.attr_counter = {} ... def __getattr__(self, name): ... def f(transaction): ... self.attr_counter[name] = self.attr_counter.get(name, 0) + 1 ... return f ... def total(self): ... count = 0 ... for access_count in self.attr_counter.values(): ... count += access_count ... return count ... def sortKey(self): ... return 1 Start a new transaction: >>> import transaction >>> txn = transaction.begin() >>> dm = DataManager() >>> txn.join(dm) We can ask a transaction if it is doomed to avoid expensive operations. An example of a use case is an object-relational mapper where a pre-commit hook sends all outstanding SQL to a relational database for objects changed during the transaction. This expensive operation is not necessary if the transaction has been doomed. A non-doomed transaction should return False: >>> txn.isDoomed() False We can doom a transaction by calling .doom() on it: >>> txn.doom() >>> txn.isDoomed() True We can doom it again if we like: >>> txn.doom() The data manager is unchanged at this point: >>> dm.total() 0 Attempting to commit a doomed transaction any number of times raises a DoomedTransaction: >>> txn.commit() # doctest: +ELLIPSIS Traceback (most recent call last): ... DoomedTransaction >>> txn.commit() # doctest: +ELLIPSIS Traceback (most recent call last): ... DoomedTransaction But still leaves the data manager unchanged: >>> dm.total() 0 But the doomed transaction can be aborted: >>> txn.abort() Which aborts the data manager: >>> dm.total() 1 >>> dm.attr_counter['abort'] 1 Dooming the current transaction can also be done directly from the transaction module. We can also begin a new transaction directly after dooming the old one: >>> txn = transaction.begin() >>> transaction.isDoomed() False >>> transaction.doom() >>> transaction.isDoomed() True >>> txn = transaction.begin() After committing a transaction we get an assertion error if we try to doom the transaction. This could be made more specific, but trying to doom a transaction after it's been committed is probably a programming error: >>> txn = transaction.begin() >>> txn.commit() >>> txn.doom() Traceback (most recent call last): ... AssertionError A doomed transaction should act the same as an active transaction, so we should be able to join it: >>> txn = transaction.begin() >>> txn.doom() >>> dm2 = DataManager() >>> txn.join(dm2) Clean up: >>> txn = transaction.begin() >>> txn.abort() zope2.13-2.13.21/source/transaction/transaction/tests/test_transaction.py0000644000175000017500000005232512214017513025414 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Test transaction behavior for variety of cases. I wrote these unittests to investigate some odd transaction behavior when doing unittests of integrating non sub transaction aware objects, and to insure proper txn behavior. these tests test the transaction system independent of the rest of the zodb. you can see the method calls to a jar by passing the keyword arg tracing to the modify method of a dataobject. the value of the arg is a prefix used for tracing print calls to that objects jar. the number of times a jar method was called can be inspected by looking at an attribute of the jar that is the method name prefixed with a c (count/check). i've included some tracing examples for tests that i thought were illuminating as doc strings below. TODO add in tests for objects which are modified multiple times, for example an object that gets modified in multiple sub txns. """ from doctest import DocTestSuite, DocFileSuite import struct import sys import unittest import transaction _ADDRESS_MASK = 256 ** struct.calcsize('P') def positive_id(obj): """Return id(obj) as a non-negative integer.""" result = id(obj) if result < 0: result += _ADDRESS_MASK assert result > 0 return result class TransactionTests(unittest.TestCase): def setUp(self): mgr = self.transaction_manager = transaction.TransactionManager() self.sub1 = DataObject(mgr) self.sub2 = DataObject(mgr) self.sub3 = DataObject(mgr) self.nosub1 = DataObject(mgr, nost=1) # basic tests with two sub trans jars # really we only need one, so tests for # sub1 should identical to tests for sub2 def testTransactionCommit(self): self.sub1.modify() self.sub2.modify() self.transaction_manager.commit() assert self.sub1._p_jar.ccommit_sub == 0 assert self.sub1._p_jar.ctpc_finish == 1 def testTransactionAbort(self): self.sub1.modify() self.sub2.modify() self.transaction_manager.abort() assert self.sub2._p_jar.cabort == 1 def testTransactionNote(self): t = self.transaction_manager.get() t.note('This is a note.') self.assertEqual(t.description, 'This is a note.') t.note('Another.') self.assertEqual(t.description, 'This is a note.\nAnother.') t.abort() # repeat adding in a nonsub trans jars def testNSJTransactionCommit(self): self.nosub1.modify() self.transaction_manager.commit() assert self.nosub1._p_jar.ctpc_finish == 1 def testNSJTransactionAbort(self): self.nosub1.modify() self.transaction_manager.abort() assert self.nosub1._p_jar.ctpc_finish == 0 assert self.nosub1._p_jar.cabort == 1 ### Failure Mode Tests # # ok now we do some more interesting # tests that check the implementations # error handling by throwing errors from # various jar methods ### # first the recoverable errors def testExceptionInAbort(self): self.sub1._p_jar = BasicJar(errors='abort') self.nosub1.modify() self.sub1.modify(nojar=1) self.sub2.modify() try: self.transaction_manager.abort() except TestTxnException: pass assert self.nosub1._p_jar.cabort == 1 assert self.sub2._p_jar.cabort == 1 def testExceptionInCommit(self): self.sub1._p_jar = BasicJar(errors='commit') self.nosub1.modify() self.sub1.modify(nojar=1) try: self.transaction_manager.commit() except TestTxnException: pass assert self.nosub1._p_jar.ctpc_finish == 0 assert self.nosub1._p_jar.ccommit == 1 assert self.nosub1._p_jar.ctpc_abort == 1 def testExceptionInTpcVote(self): self.sub1._p_jar = BasicJar(errors='tpc_vote') self.nosub1.modify() self.sub1.modify(nojar=1) try: self.transaction_manager.commit() except TestTxnException: pass assert self.nosub1._p_jar.ctpc_finish == 0 assert self.nosub1._p_jar.ccommit == 1 assert self.nosub1._p_jar.ctpc_abort == 1 assert self.sub1._p_jar.ctpc_abort == 1 def testExceptionInTpcBegin(self): """ ok this test reveals a bug in the TM.py as the nosub tpc_abort there is ignored. nosub calling method tpc_begin nosub calling method commit sub calling method tpc_begin sub calling method abort sub calling method tpc_abort nosub calling method tpc_abort """ self.sub1._p_jar = BasicJar(errors='tpc_begin') self.nosub1.modify() self.sub1.modify(nojar=1) try: self.transaction_manager.commit() except TestTxnException: pass assert self.nosub1._p_jar.ctpc_abort == 1 assert self.sub1._p_jar.ctpc_abort == 1 def testExceptionInTpcAbort(self): self.sub1._p_jar = BasicJar(errors=('tpc_abort', 'tpc_vote')) self.nosub1.modify() self.sub1.modify(nojar=1) try: self.transaction_manager.commit() except TestTxnException: pass assert self.nosub1._p_jar.ctpc_abort == 1 # last test, check the hosing mechanism ## def testHoserStoppage(self): ## # It's hard to test the "hosed" state of the database, where ## # hosed means that a failure occurred in the second phase of ## # the two phase commit. It's hard because the database can ## # recover from such an error if it occurs during the very first ## # tpc_finish() call of the second phase. ## for obj in self.sub1, self.sub2: ## j = HoserJar(errors='tpc_finish') ## j.reset() ## obj._p_jar = j ## obj.modify(nojar=1) ## try: ## transaction.commit() ## except TestTxnException: ## pass ## self.assert_(Transaction.hosed) ## self.sub2.modify() ## try: ## transaction.commit() ## except Transaction.POSException.TransactionError: ## pass ## else: ## self.fail("Hosed Application didn't stop commits") class Test_oid_repr(unittest.TestCase): def _callFUT(self, oid): from transaction._transaction import oid_repr return oid_repr(oid) def test_as_nonstring(self): self.assertEqual(self._callFUT(123), '123') def test_as_string_not_8_chars(self): self.assertEqual(self._callFUT('a'), "'a'") def test_as_string_z64(self): s = '\0'*8 self.assertEqual(self._callFUT(s), '0x00') def test_as_string_all_Fs(self): s = '\1'*8 self.assertEqual(self._callFUT(s), '0x0101010101010101') class DataObject: def __init__(self, transaction_manager, nost=0): self.transaction_manager = transaction_manager self.nost = nost self._p_jar = None def modify(self, nojar=0, tracing=0): if not nojar: if self.nost: self._p_jar = BasicJar(tracing=tracing) else: self._p_jar = BasicJar(tracing=tracing) self.transaction_manager.get().join(self._p_jar) class TestTxnException(Exception): pass class BasicJar: def __init__(self, errors=(), tracing=0): if not isinstance(errors, tuple): errors = errors, self.errors = errors self.tracing = tracing self.cabort = 0 self.ccommit = 0 self.ctpc_begin = 0 self.ctpc_abort = 0 self.ctpc_vote = 0 self.ctpc_finish = 0 self.cabort_sub = 0 self.ccommit_sub = 0 def __repr__(self): return "<%s %X %s>" % (self.__class__.__name__, positive_id(self), self.errors) def sortKey(self): # All these jars use the same sort key, and Python's list.sort() # is stable. These two return self.__class__.__name__ def check(self, method): if self.tracing: print '%s calling method %s'%(str(self.tracing),method) if method in self.errors: raise TestTxnException("error %s" % method) ## basic jar txn interface def abort(self, *args): self.check('abort') self.cabort += 1 def commit(self, *args): self.check('commit') self.ccommit += 1 def tpc_begin(self, txn, sub=0): self.check('tpc_begin') self.ctpc_begin += 1 def tpc_vote(self, *args): self.check('tpc_vote') self.ctpc_vote += 1 def tpc_abort(self, *args): self.check('tpc_abort') self.ctpc_abort += 1 def tpc_finish(self, *args): self.check('tpc_finish') self.ctpc_finish += 1 class HoserJar(BasicJar): # The HoserJars coordinate their actions via the class variable # committed. The check() method will only raise its exception # if committed > 0. committed = 0 def reset(self): # Calling reset() on any instance will reset the class variable. HoserJar.committed = 0 def check(self, method): if HoserJar.committed > 0: BasicJar.check(self, method) def tpc_finish(self, *args): self.check('tpc_finish') self.ctpc_finish += 1 HoserJar.committed += 1 def test_join(): """White-box test of the join method The join method is provided for "backward-compatability" with ZODB 4 data managers. The argument to join must be a zodb4 data manager, transaction.interfaces.IDataManager. >>> from transaction.tests.sampledm import DataManager >>> from transaction._transaction import DataManagerAdapter >>> t = transaction.Transaction() >>> dm = DataManager() >>> t.join(dm) The end result is that a data manager adapter is one of the transaction's objects: >>> isinstance(t._resources[0], DataManagerAdapter) True >>> t._resources[0]._datamanager is dm True """ def hook(): pass def test_addBeforeCommitHook(): """Test addBeforeCommitHook. Let's define a hook to call, and a way to see that it was called. >>> log = [] >>> def reset_log(): ... del log[:] >>> def hook(arg='no_arg', kw1='no_kw1', kw2='no_kw2'): ... log.append("arg %r kw1 %r kw2 %r" % (arg, kw1, kw2)) Now register the hook with a transaction. >>> import transaction >>> t = transaction.begin() >>> t.addBeforeCommitHook(hook, '1') We can see that the hook is indeed registered. >>> [(hook.func_name, args, kws) ... for hook, args, kws in t.getBeforeCommitHooks()] [('hook', ('1',), {})] When transaction commit starts, the hook is called, with its arguments. >>> log [] >>> t.commit() >>> log ["arg '1' kw1 'no_kw1' kw2 'no_kw2'"] >>> reset_log() A hook's registration is consumed whenever the hook is called. Since the hook above was called, it's no longer registered: >>> len(list(t.getBeforeCommitHooks())) 0 >>> transaction.commit() >>> log [] The hook is only called for a full commit, not for a savepoint. >>> t = transaction.begin() >>> t.addBeforeCommitHook(hook, 'A', dict(kw1='B')) >>> dummy = t.savepoint() >>> log [] >>> t.commit() >>> log ["arg 'A' kw1 'B' kw2 'no_kw2'"] >>> reset_log() If a transaction is aborted, no hook is called. >>> t = transaction.begin() >>> t.addBeforeCommitHook(hook, ["OOPS!"]) >>> transaction.abort() >>> log [] >>> transaction.commit() >>> log [] The hook is called before the commit does anything, so even if the commit fails the hook will have been called. To provoke failures in commit, we'll add failing resource manager to the transaction. >>> class CommitFailure(Exception): ... pass >>> class FailingDataManager: ... def tpc_begin(self, txn, sub=False): ... raise CommitFailure ... def abort(self, txn): ... pass >>> t = transaction.begin() >>> t.join(FailingDataManager()) >>> t.addBeforeCommitHook(hook, '2') >>> t.commit() Traceback (most recent call last): ... CommitFailure >>> log ["arg '2' kw1 'no_kw1' kw2 'no_kw2'"] >>> reset_log() Let's register several hooks. >>> t = transaction.begin() >>> t.addBeforeCommitHook(hook, '4', dict(kw1='4.1')) >>> t.addBeforeCommitHook(hook, '5', dict(kw2='5.2')) They are returned in the same order by getBeforeCommitHooks. >>> [(hook.func_name, args, kws) #doctest: +NORMALIZE_WHITESPACE ... for hook, args, kws in t.getBeforeCommitHooks()] [('hook', ('4',), {'kw1': '4.1'}), ('hook', ('5',), {'kw2': '5.2'})] And commit also calls them in this order. >>> t.commit() >>> len(log) 2 >>> log #doctest: +NORMALIZE_WHITESPACE ["arg '4' kw1 '4.1' kw2 'no_kw2'", "arg '5' kw1 'no_kw1' kw2 '5.2'"] >>> reset_log() While executing, a hook can itself add more hooks, and they will all be called before the real commit starts. >>> def recurse(txn, arg): ... log.append('rec' + str(arg)) ... if arg: ... txn.addBeforeCommitHook(hook, '-') ... txn.addBeforeCommitHook(recurse, (txn, arg-1)) >>> t = transaction.begin() >>> t.addBeforeCommitHook(recurse, (t, 3)) >>> transaction.commit() >>> log #doctest: +NORMALIZE_WHITESPACE ['rec3', "arg '-' kw1 'no_kw1' kw2 'no_kw2'", 'rec2', "arg '-' kw1 'no_kw1' kw2 'no_kw2'", 'rec1', "arg '-' kw1 'no_kw1' kw2 'no_kw2'", 'rec0'] >>> reset_log() """ def test_addAfterCommitHook(): """Test addAfterCommitHook. Let's define a hook to call, and a way to see that it was called. >>> log = [] >>> def reset_log(): ... del log[:] >>> def hook(status, arg='no_arg', kw1='no_kw1', kw2='no_kw2'): ... log.append("%r arg %r kw1 %r kw2 %r" % (status, arg, kw1, kw2)) Now register the hook with a transaction. >>> import transaction >>> t = transaction.begin() >>> t.addAfterCommitHook(hook, '1') We can see that the hook is indeed registered. >>> [(hook.func_name, args, kws) ... for hook, args, kws in t.getAfterCommitHooks()] [('hook', ('1',), {})] When transaction commit is done, the hook is called, with its arguments. >>> log [] >>> t.commit() >>> log ["True arg '1' kw1 'no_kw1' kw2 'no_kw2'"] >>> reset_log() A hook's registration is consumed whenever the hook is called. Since the hook above was called, it's no longer registered: >>> len(list(t.getAfterCommitHooks())) 0 >>> transaction.commit() >>> log [] The hook is only called after a full commit, not for a savepoint. >>> t = transaction.begin() >>> t.addAfterCommitHook(hook, 'A', dict(kw1='B')) >>> dummy = t.savepoint() >>> log [] >>> t.commit() >>> log ["True arg 'A' kw1 'B' kw2 'no_kw2'"] >>> reset_log() If a transaction is aborted, no hook is called. >>> t = transaction.begin() >>> t.addAfterCommitHook(hook, ["OOPS!"]) >>> transaction.abort() >>> log [] >>> transaction.commit() >>> log [] The hook is called after the commit is done, so even if the commit fails the hook will have been called. To provoke failures in commit, we'll add failing resource manager to the transaction. >>> class CommitFailure(Exception): ... pass >>> class FailingDataManager: ... def tpc_begin(self, txn): ... raise CommitFailure ... def abort(self, txn): ... pass >>> t = transaction.begin() >>> t.join(FailingDataManager()) >>> t.addAfterCommitHook(hook, '2') >>> t.commit() Traceback (most recent call last): ... CommitFailure >>> log ["False arg '2' kw1 'no_kw1' kw2 'no_kw2'"] >>> reset_log() Let's register several hooks. >>> t = transaction.begin() >>> t.addAfterCommitHook(hook, '4', dict(kw1='4.1')) >>> t.addAfterCommitHook(hook, '5', dict(kw2='5.2')) They are returned in the same order by getAfterCommitHooks. >>> [(hook.func_name, args, kws) #doctest: +NORMALIZE_WHITESPACE ... for hook, args, kws in t.getAfterCommitHooks()] [('hook', ('4',), {'kw1': '4.1'}), ('hook', ('5',), {'kw2': '5.2'})] And commit also calls them in this order. >>> t.commit() >>> len(log) 2 >>> log #doctest: +NORMALIZE_WHITESPACE ["True arg '4' kw1 '4.1' kw2 'no_kw2'", "True arg '5' kw1 'no_kw1' kw2 '5.2'"] >>> reset_log() While executing, a hook can itself add more hooks, and they will all be called before the real commit starts. >>> def recurse(status, txn, arg): ... log.append('rec' + str(arg)) ... if arg: ... txn.addAfterCommitHook(hook, '-') ... txn.addAfterCommitHook(recurse, (txn, arg-1)) >>> t = transaction.begin() >>> t.addAfterCommitHook(recurse, (t, 3)) >>> transaction.commit() >>> log #doctest: +NORMALIZE_WHITESPACE ['rec3', "True arg '-' kw1 'no_kw1' kw2 'no_kw2'", 'rec2', "True arg '-' kw1 'no_kw1' kw2 'no_kw2'", 'rec1', "True arg '-' kw1 'no_kw1' kw2 'no_kw2'", 'rec0'] >>> reset_log() If an after commit hook is raising an exception then it will log a message at error level so that if other hooks are registered they can be executed. We don't support execution dependencies at this level. >>> mgr = transaction.TransactionManager() >>> do = DataObject(mgr) >>> def hookRaise(status, arg='no_arg', kw1='no_kw1', kw2='no_kw2'): ... raise TypeError("Fake raise") >>> t = transaction.begin() >>> t.addAfterCommitHook(hook, ('-', 1)) >>> t.addAfterCommitHook(hookRaise, ('-', 2)) >>> t.addAfterCommitHook(hook, ('-', 3)) >>> transaction.commit() >>> log ["True arg '-' kw1 1 kw2 'no_kw2'", "True arg '-' kw1 3 kw2 'no_kw2'"] >>> reset_log() Test that the associated transaction manager has been cleanup when after commit hooks are registered >>> mgr = transaction.TransactionManager() >>> do = DataObject(mgr) >>> t = transaction.begin() >>> t._manager._txn is not None True >>> t.addAfterCommitHook(hook, ('-', 1)) >>> transaction.commit() >>> log ["True arg '-' kw1 1 kw2 'no_kw2'"] >>> t._manager._txn is not None False >>> reset_log() """ def bug239086(): """ The original implementation of thread transaction manager made invalid assumptions about thread ids. >>> import transaction.tests.savepointsample >>> dm = transaction.tests.savepointsample.SampleSavepointDataManager() >>> dm.keys() [] >>> class Sync: ... def __init__(self, label): ... self.label = label ... def beforeCompletion(self, t): ... print self.label, 'before' ... def afterCompletion(self, t): ... print self.label, 'after' ... def newTransaction(self, t): ... print self.label, 'new' >>> sync = Sync(1) >>> import threading >>> def run_in_thread(f): ... t = threading.Thread(target=f) ... t.start() ... t.join() >>> @run_in_thread ... def first(): ... transaction.manager.registerSynch(sync) ... transaction.manager.begin() ... dm['a'] = 1 1 new >>> @run_in_thread ... def second(): ... transaction.abort() # should do nothing. >>> dm.keys() ['a'] >>> dm = transaction.tests.savepointsample.SampleSavepointDataManager() >>> dm.keys() [] >>> @run_in_thread ... def first(): ... dm['a'] = 1 >>> transaction.abort() # should do nothing >>> dm.keys() ['a'] """ def test_suite(): suite = unittest.TestSuite(( DocFileSuite('doom.txt'), DocTestSuite(), unittest.makeSuite(TransactionTests), unittest.makeSuite(Test_oid_repr), )) if sys.version_info >= (2, 6): suite.addTest(DocFileSuite('convenience.txt')) return suite # additional_tests is for setuptools "setup.py test" support additional_tests = test_suite if __name__ == '__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/transaction/transaction/tests/test_register_compat.py0000644000175000017500000000764712214017513026265 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test backwards compatibility for resource managers using register(). The transaction package supports several different APIs for resource managers. The original ZODB3 API was implemented by ZODB.Connection. The Connection passed persistent objects to a Transaction's register() method. It's possible that third-party code also used this API, hence these tests that the code that adapts the old interface to the current API works. These tests use a TestConnection object that implements the old API. They check that the right methods are called and in roughly the right order. Common cases ------------ First, check that a basic transaction commit works. >>> cn = TestConnection() >>> cn.register(Object()) >>> cn.register(Object()) >>> cn.register(Object()) >>> transaction.commit() >>> len(cn.committed) 3 >>> len(cn.aborted) 0 >>> cn.calls ['begin', 'vote', 'finish'] Second, check that a basic transaction abort works. If the application calls abort(), then the transaction never gets into the two-phase commit. It just aborts each object. >>> cn = TestConnection() >>> cn.register(Object()) >>> cn.register(Object()) >>> cn.register(Object()) >>> transaction.abort() >>> len(cn.committed) 0 >>> len(cn.aborted) 3 >>> cn.calls [] Error handling -------------- The tricky part of the implementation is recovering from an error that occurs during the two-phase commit. We override the commit() and abort() methods of Object to cause errors during commit. Note that the implementation uses lists internally, so that objects are committed in the order they are registered. (In the presence of multiple resource managers, objects from a single resource manager are committed in order. I'm not sure if this is an accident of the implementation or a feature that should be supported by any implementation.) The order of resource managers depends on sortKey(). >>> cn = TestConnection() >>> cn.register(Object()) >>> cn.register(CommitError()) >>> cn.register(Object()) >>> transaction.commit() Traceback (most recent call last): ... RuntimeError: commit >>> len(cn.committed) 1 >>> len(cn.aborted) 3 Clean up: >>> transaction.abort() """ import doctest import transaction class Object(object): def commit(self): pass def abort(self): pass class CommitError(Object): def commit(self): raise RuntimeError("commit") class AbortError(Object): def abort(self): raise RuntimeError("abort") class BothError(CommitError, AbortError): pass class TestConnection: def __init__(self): self.committed = [] self.aborted = [] self.calls = [] def register(self, obj): obj._p_jar = self transaction.get().register(obj) def sortKey(self): return str(id(self)) def tpc_begin(self, txn): self.calls.append("begin") def tpc_vote(self, txn): self.calls.append("vote") def tpc_finish(self, txn): self.calls.append("finish") def tpc_abort(self, txn): self.calls.append("abort") def commit(self, obj, txn): obj.commit() self.committed.append(obj) def abort(self, obj, txn): obj.abort() self.aborted.append(obj) def test_suite(): return doctest.DocTestSuite() # additional_tests is for setuptools "setup.py test" support additional_tests = test_suite zope2.13-2.13.21/source/transaction/LICENSE.txt0000644000175000017500000000402612214017513017605 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/transaction/README.txt0000644000175000017500000000076212214017513017463 0ustar arnauarnau============ Transactions ============ This package contains a generic transaction implementation for Python. It is mainly used by the ZODB, though. Note that the data manager API, ``transaction.interfaces.IDataManager``, is syntactically simple, but semantically complex. The semantics were not easy to express in the interface. This could probably use more work. The semantics are presented in detail through examples of a sample data manager in ``transaction.tests.test_SampleDataManager``. zope2.13-2.13.21/source/transaction/setup.cfg0000644000175000017500000000007312214017513017601 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/transaction/COPYRIGHT.txt0000644000175000017500000000004012214017513020063 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/transaction/ez_setup.py0000644000175000017500000002140012214017513020165 0ustar arnauarnau#!python """Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import sys DEFAULT_VERSION = "0.6c7" DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] md5_data = { 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', } import sys, os def _validate_md5(egg_name, data): if egg_name in md5_data: from md5 import md5 digest = md5(data).hexdigest() if digest != md5_data[egg_name]: print >>sys.stderr, ( "md5 validation of %s failed! (Possible download problem?)" % egg_name ) sys.exit(2) return data def use_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, download_delay=15 ): """Automatically find/download setuptools and make it available on sys.path `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where setuptools will be downloaded, if it is not already available. If `download_delay` is specified, it should be the number of seconds that will be paused before initiating a download, should one be required. If an older version of setuptools is installed, this routine will print a message to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling script. """ try: import setuptools if setuptools.__version__ == '0.0.1': print >>sys.stderr, ( "You have an obsolete version of setuptools installed. Please\n" "remove it from your system entirely before rerunning this script." ) sys.exit(2) except ImportError: egg = download_setuptools(version, download_base, to_dir, download_delay) sys.path.insert(0, egg) import setuptools; setuptools.bootstrap_install_from = egg import pkg_resources try: pkg_resources.require("setuptools>="+version) except pkg_resources.VersionConflict, e: # XXX could we install in a subprocess here? print >>sys.stderr, ( "The required version of setuptools (>=%s) is not available, and\n" "can't be installed while this script is running. Please install\n" " a more recent version first.\n\n(Currently using %r)" ) % (version, e.args[0]) sys.exit(2) def download_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay = 15 ): """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. """ import urllib2, shutil egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) url = download_base + egg_name saveto = os.path.join(to_dir, egg_name) src = dst = None if not os.path.exists(saveto): # Avoid repeated downloads try: from distutils import log if delay: log.warn(""" --------------------------------------------------------------------------- This script requires setuptools version %s to run (even to display help). I will attempt to download it for you (from %s), but you may need to enable firewall access for this script first. I will start the download in %d seconds. (Note: if this machine does not have network access, please obtain the file %s and place it in this directory before rerunning this script.) ---------------------------------------------------------------------------""", version, download_base, delay, url ); from time import sleep; sleep(delay) log.warn("Downloading %s", url) src = urllib2.urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. data = _validate_md5(egg_name, src.read()) dst = open(saveto,"wb"); dst.write(data) finally: if src: src.close() if dst: dst.close() return os.path.realpath(saveto) def main(argv, version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" try: import setuptools except ImportError: egg = None try: egg = download_setuptools(version, delay=0) sys.path.insert(0,egg) from setuptools.command.easy_install import main return main(list(argv)+[egg]) # we're done here finally: if egg and os.path.exists(egg): os.unlink(egg) else: if setuptools.__version__ == '0.0.1': # tell the user to uninstall obsolete version use_setuptools(version) req = "setuptools>="+version import pkg_resources try: pkg_resources.require(req) except pkg_resources.VersionConflict: try: from setuptools.command.easy_install import main except ImportError: from easy_install import main main(list(argv)+[download_setuptools(delay=0)]) sys.exit(0) # try to force an exit else: if argv: from setuptools.command.easy_install import main main(argv) else: print "Setuptools version",version,"or greater has been installed." print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' def update_md5(filenames): """Update our built-in md5 registry""" import re from md5 import md5 for name in filenames: base = os.path.basename(name) f = open(name,'rb') md5_data[base] = md5(f.read()).hexdigest() f.close() data = [" %r: %r,\n" % it for it in md5_data.items()] data.sort() repl = "".join(data) import inspect srcfile = inspect.getsourcefile(sys.modules[__name__]) f = open(srcfile, 'rb'); src = f.read(); f.close() match = re.search("\nmd5_data = {\n([^}]+)}", src) if not match: print >>sys.stderr, "Internal error!" sys.exit(2) src = src[:match.start(1)] + repl + src[match.end(1):] f = open(srcfile,'w') f.write(src) f.close() if __name__=='__main__': if len(sys.argv)>2 and sys.argv[1]=='--md5update': update_md5(sys.argv[2:]) else: main(sys.argv[1:]) zope2.13-2.13.21/source/transaction/buildout.cfg0000644000175000017500000000013512214017513020267 0ustar arnauarnau[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = transaction zope2.13-2.13.21/source/transaction/bootstrap.py0000644000175000017500000000417512214017513020356 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 112140 2010-05-07 15:29:36Z tseaver $ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources is_jython = sys.platform.startswith('java') if is_jython: import subprocess cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set if is_jython: assert subprocess.Popen( [sys.executable] + ['-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout'], env = dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/transaction/CHANGES.txt0000644000175000017500000000406712214017513017600 0ustar arnauarnauChanges ======= 1.1.1 (2010-09-16) ------------------ Bug Fixes: - Code in ``_transaction.py`` held on to local references to traceback objects after calling ``sys.exc_info()`` to get one, causing potential reference leakages. - Fixed ``hexlify`` NameError in ``transaction._transaction.oid_repr`` and add test. 1.1.0 (1010-05-12) ------------------ New Features: - Transaction managers and the transaction module can be used with the with statement to define transaction boundaries, as in:: with transaction: ... do some things ... See transaction/tests/convenience.txt for more details. - There is a new iterator function that automates dealing with transient errors (such as ZODB confict errors). For example, in:: for attempt in transaction.attempts(5): with attempt: ... do some things .. If the work being done raises transient errors, the transaction will be retried up to 5 times. See transaction/tests/convenience.txt for more details. Bugs fixed: - Fixed a bug that caused extra commit calls to be made on data managers under certain special circumstances. https://mail.zope.org/pipermail/zodb-dev/2010-May/013329.html - When threads were reused, transaction data could leak accross them, causing subtle application bugs. https://bugs.launchpad.net/zodb/+bug/239086 1.0.1 (2010-05-07) ------------------ - LP #142464: remove double newline between log entries: it makes doing smarter formatting harder. - Updated tests to remove use of deprecated ``zope.testing.doctest``. 1.0.0 (2009-07-24) ------------------ - Fix test that incorrectly relied on the order of a list that was generated from a dict. - Remove crufty DEPENDENCIES.cfg left over from zpkg. 1.0a1 (2007-12-18) ------------------ = Initial release, branched from ZODB trunk on 2007-11-08 (aka "3.9.0dev"). - Remove (deprecated) support for beforeCommitHook alias to addBeforeCommitHook. - Add weakset tests. - Remove unit tests that depend on ZODB.tests.utils from test_transaction (these are actually integration tests). zope2.13-2.13.21/source/transaction/ez_setup.pyc0000644000175000017500000002140012214017513020330 0ustar arnauarnauó K0Rc@s²dZddlZdZdejd Zidd6dd 6d d 6d d 6dd6dd6dd6dd6dd6dd6dd6dd6dd6d d!6d"d#6d$d%6d&d'6d(d)6d*d+6d,d-6d.d/6d0d16d2d36d4d56d6d76d8d96d:d;6ZddlZddlZd<„Zeeejd=d>„Z eeejd=d?„Z ed@„Z dA„Z e dBkr®eejƒdCkršejdDdEkrše ejdCƒq®e ejdDƒndS(Fs Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. iÿÿÿÿNs0.6c7s0http://pypi.python.org/packages/%s/s/setuptools/it 8822caf901250d848b996b7f25c6e6cassetuptools-0.6b1-py2.3.eggt b79a8a403e4502fbb85ee3f1941735cbssetuptools-0.6b1-py2.4.eggt 5657759d8a6d8fc44070a9d07272d99bssetuptools-0.6b2-py2.3.eggt 4996a8d169d2be661fa32a6e52e4f82assetuptools-0.6b2-py2.4.eggt bb31c0fc7399a63579975cad9f5a0618ssetuptools-0.6b3-py2.3.eggt 38a8c6b3d6ecd22247f179f7da669facssetuptools-0.6b3-py2.4.eggt 62045a24ed4e1ebc77fe039aa4e6f7e5ssetuptools-0.6b4-py2.3.eggt 4cb2a185d228dacffb2d17f103b3b1c4ssetuptools-0.6b4-py2.4.eggt b3f2b5539d65cb7f74ad79127f1a908cssetuptools-0.6c1-py2.3.eggt b45adeda0667d2d2ffe14009364f2a4bssetuptools-0.6c1-py2.4.eggt f0064bf6aa2b7d0f3ba0b43f20817c27ssetuptools-0.6c2-py2.3.eggt 616192eec35f47e8ea16cd6a122b7277ssetuptools-0.6c2-py2.4.eggt f181fa125dfe85a259c9cd6f1d7b78fassetuptools-0.6c3-py2.3.eggt e0ed74682c998bfb73bf803a50e7b71essetuptools-0.6c3-py2.4.eggt abef16fdd61955514841c7c6bd98965essetuptools-0.6c3-py2.5.eggt b0b9131acab32022bfac7f44c5d7971fssetuptools-0.6c4-py2.3.eggt 2a1f9656d4fbf3c97bf946c0a124e6e2ssetuptools-0.6c4-py2.4.eggt 8f5a052e32cdb9c72bcf4b5526f28afcssetuptools-0.6c4-py2.5.eggt ee9fd80965da04f2f3e6b3576e9d8167ssetuptools-0.6c5-py2.3.eggt afe2adf1c01701ee841761f5bcd8aa64ssetuptools-0.6c5-py2.4.eggt a8d3f61494ccaa8714dfed37bccd3d5dssetuptools-0.6c5-py2.5.eggt 35686b78116a668847237b69d549ec20ssetuptools-0.6c6-py2.3.eggt 3c56af57be3225019260a644430065abssetuptools-0.6c6-py2.4.eggt b2f8a7520709a5b34f80946de5f02f53ssetuptools-0.6c6-py2.5.eggt 209fdf9adc3a615e5115b725658e13e2ssetuptools-0.6c7-py2.3.eggt 5a8f954807d46a0fb67cf1f26c55a82essetuptools-0.6c7-py2.4.eggt 45d2ad28f9750e7434111fde831e8372ssetuptools-0.6c7-py2.5.eggcCsf|tkrbddlm}||ƒjƒ}|t|krbtjd|IJtjdƒqbn|S(Niÿÿÿÿ(tmd5s:md5 validation of %s failed! (Possible download problem?)i(tmd5_dataRt hexdigesttsyststderrtexit(tegg_nametdataRtdigest((s ez_setup.pyt _validate_md54s icCsöy<ddl}|jdkr;tjdIJtjdƒnWnNtk rŒt||||ƒ}tjjd|ƒddl}||_ nXddl }y|j d|ƒWnA|j k rñ}tjd||j dfIJtjdƒnXdS( sŸAutomatically find/download setuptools and make it available on sys.path `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where setuptools will be downloaded, if it is not already available. If `download_delay` is specified, it should be the number of seconds that will be paused before initiating a download, should one be required. If an older version of setuptools is installed, this routine will print a message to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling script. iÿÿÿÿNs0.0.1sYou have an obsolete version of setuptools installed. Please remove it from your system entirely before rerunning this script.iis setuptools>=s³The required version of setuptools (>=%s) is not available, and can't be installed while this script is running. Please install a more recent version first. (Currently using %r)(t setuptoolst __version__RRR t ImportErrortdownload_setuptoolstpathtinserttbootstrap_install_fromt pkg_resourcestrequiretVersionConflicttargs(tversiont download_basetto_dirtdownload_delayR%teggR,te((s ez_setup.pytuse_setuptoolsAs&    cCsGddl}ddl}d|tjd f}||}tjj||ƒ}d} } tjj|ƒs7z ddl m } |r¹| j d||||ƒddl m } | |ƒn| j d|ƒ|j|ƒ} t|| jƒƒ} t|d ƒ} | j| ƒWd| r | jƒn| r3| jƒnXntjj|ƒS( s€Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. iÿÿÿÿNssetuptools-%s-py%s.eggi(tlogs --------------------------------------------------------------------------- This script requires setuptools version %s to run (even to display help). I will attempt to download it for you (from %s), but you may need to enable firewall access for this script first. I will start the download in %d seconds. (Note: if this machine does not have network access, please obtain the file %s and place it in this directory before rerunning this script.) ---------------------------------------------------------------------------(tsleepsDownloading %stwb(turllib2tshutilRR0tosR)tjointNonetexistst distutilsR7twarnttimeR8turlopenR$treadtopentwritetclosetrealpath(R0R1R2tdelayR:R;R!turltsavetotsrctdstR7R8R"((s ez_setup.pyR(js0      cCsžyddl}Wn“tk r¥d}zPt|ddƒ}tjjd|ƒddlm}|t |ƒ|gƒSWd|r¡t jj |ƒr¡t j |ƒnXnX|j dkrÂt|ƒnd|}ddl}y|j|ƒWny|jk rdyddlm}Wn!tk r3ddlm}nX|t |ƒtddƒgƒtjdƒn6X|rˆddlm}||ƒndG|Gd GHd GHdS( s-Install or upgrade setuptools and EasyInstalliÿÿÿÿNRIi(tmains0.0.1s setuptools>=sSetuptools versionsor greater has been installed.s:(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)(R%R'R>R(RR)R*tsetuptools.command.easy_installRNtlistR<R?tunlinkR&R6R,R-R.t easy_installR (targvR0R%R4RNtreqR,((s ez_setup.pyRN™s:        c Cs~ddl}ddlm}xU|D]M}tjj|ƒ}t|dƒ}||jƒƒjƒt|<|j ƒq#Wgtj ƒD]}d|^q}|j ƒdj |ƒ}ddl } | jtjtƒ} t| dƒ}|jƒ} |j ƒ|jd| ƒ} | s,tjdIJtjd ƒn| | jd ƒ || | jd ƒ} t| d ƒ}|j| ƒ|j ƒdS( s Update our built-in md5 registryiÿÿÿÿN(Rtrbs %r: %r, ts md5_data = { ([^}]+)}sInternal error!iitw(treRR<R)tbasenameRERDRRRGtitemstsortR=tinspectt getsourcefileRtmodulest__name__tsearchRR tstarttendRF( t filenamesRXRtnametbasetftitR"treplR\tsrcfileRLtmatch((s ez_setup.pyt update_md5Âs.  #    ( t__main__iis --md5update(t__doc__RtDEFAULT_VERSIONR0t DEFAULT_URLRR<R$tcurdirR6R(RNRkR_tlenRS(((s ez_setup.pytsV     (  - )  (zope2.13-2.13.21/source/zope.container/0000755000175000017500000000000012214017546016377 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/setup.py0000644000175000017500000000662612214017546020123 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.container package""" import os from setuptools import setup, find_packages, Extension def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.container', version='3.11.2', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', description='Zope Container', long_description=( read('README.txt') + '\n\n' + '.. contents::\n' + '\n\n' + read('src', 'zope', 'container', 'constraints.txt') + '\n\n' + read('CHANGES.txt') ), keywords = "zope container", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.container', license='ZPL 2.1', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope'], ext_modules=[Extension("zope.container._zope_container_contained", [os.path.join("src", "zope", "container", "_zope_container_contained.c") ], include_dirs=['include']), ], extras_require=dict( test=[ 'zope.configuration', 'zope.security', 'zope.testing', ]), install_requires=['setuptools', 'zope.interface', 'zope.dottedname', 'zope.schema', 'zope.component', 'zope.event', 'zope.location>=3.5.4', 'zope.security', 'zope.lifecycleevent>=3.5.2', 'zope.i18nmessageid', 'zope.filerepresentation', 'zope.size', 'zope.traversing', 'zope.publisher', 'zope.broken', 'ZODB3', ], include_package_data = True, zip_safe = False, ) zope2.13-2.13.21/source/zope.container/compat.cfg0000644000175000017500000000030012214017546020334 0ustar arnauarnau[buildout] extends = buildout.cfg develop = . parts = test graph compat versions = versions [versions] ZODB3 = 3.8 zope.app.apidoc = 3.5 [compat] recipe = z3c.recipe.compattest max_jobs = 5 zope2.13-2.13.21/source/zope.container/PKG-INFO0000644000175000017500000002531012214017546017475 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.container Version: 3.11.2 Summary: Zope Container Home-page: http://pypi.python.org/pypi/zope.container Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: This package define interfaces of container components, and provides container implementations such as a BTreeContainer and OrderedContainer, as well as the base class used by ``zope.site.folder`` for the Folder implementation. .. contents:: ========================= Containment constraints ========================= Containment constraints allow us to express restrictions on the types of items that can be placed in containers or on the types of containers an item can be placed in. We express these constraints in interfaces. Let's define some container and item interfaces: >>> from zope.container.interfaces import IContainer >>> from zope.location.interfaces import IContained >>> from zope.container.constraints import containers, contains >>> class IBuddyFolder(IContainer): ... contains('.IBuddy') In this example, we used the contains function to declare that objects that provide IBuddyFolder can only contain items that provide IBuddy. Note that we used a string containing a dotted name for the IBuddy interface. This is because IBuddy hasn't been defined yet. When we define IBuddy, we can use IBuddyFolder directly: >>> class IBuddy(IContained): ... containers(IBuddyFolder) Now, with these interfaces in place, we can define Buddy and BuddyFolder classes and verify that we can put buddies in buddy folders: >>> from zope import interface >>> class Buddy: ... interface.implements(IBuddy) >>> class BuddyFolder: ... interface.implements(IBuddyFolder) >>> from zope.container.constraints import checkObject, checkFactory >>> from zope.component.factory import Factory >>> checkObject(BuddyFolder(), 'x', Buddy()) >>> checkFactory(BuddyFolder(), 'x', Factory(Buddy)) True If we try to use other containers or folders, we'll get errors: >>> class Container: ... interface.implements(IContainer) >>> class Contained: ... interface.implements(IContained) >>> checkObject(Container(), 'x', Buddy()) ... # doctest: +ELLIPSIS Traceback (most recent call last): InvalidContainerType: ... >>> checkFactory(Container(), 'x', Factory(Buddy)) False >>> checkObject(BuddyFolder(), 'x', Contained()) ... # doctest: +ELLIPSIS Traceback (most recent call last): InvalidItemType: ... >>> checkFactory(BuddyFolder(), 'x', Factory(Contained)) False In the example, we defined the container first and then the items. We could have defined these in the opposite order: >>> class IContact(IContained): ... containers('.IContacts') >>> class IContacts(IContainer): ... contains(IContact) >>> class Contact: ... interface.implements(IContact) >>> class Contacts: ... interface.implements(IContacts) >>> checkObject(Contacts(), 'x', Contact()) >>> checkFactory(Contacts(), 'x', Factory(Contact)) True >>> checkObject(Contacts(), 'x', Buddy()) ... # doctest: +ELLIPSIS Traceback (most recent call last): InvalidItemType: ... >>> checkFactory(Contacts(), 'x', Factory(Buddy)) False ======= CHANGES ======= 3.11.2 (2010-09-25) ------------------- - Added not declared, but needed test dependency on `zope.testing`. 3.11.1 (2010-04-30) ------------------- - Prefer the standard libraries doctest module to the one from zope.testing. - Added compatibility with ZODB3 3.10 by importing the IBroken interface from it directly. Once we can rely on the new ZODB3 version exclusively, we can remove the dependency onto the zope.broken distribution. - Never fail if the suggested name is in a wrong type (#227617) - ``checkName`` first checks the parameter type before the emptiness. 3.11.0 (2009-12-31) ------------------- - Copy two trivial classes from zope.cachedescriptors into this package, which allows us to remove that dependency. We didn't actually use any caching properties as the dependency suggested. 3.10.1 (2009-12-29) ------------------- - Moved zope.copypastemove related tests into that package. - Removed no longer used zcml prefix from the configure file. - Stop importing DocTestSuite from zope.testing.doctestunit. Fixes compatibility problems with zope.testing 3.8.4. 3.10.0 (2009-12-15) ------------------- - Break testing dependency on zope.app.testing. - Break testing dependency on zope.app.dependable by moving the code and tests into that package. - Import ISite from zope.component after it was moved there from zope.location. 3.9.1 (2009-10-18) ------------------ - Rerelease 3.9.0 as it had a broken Windows 2.6 egg. - Marked as part of the ZTK. 3.9.0 (2009-08-28) ------------------ - Previous releases should be versioned 3.9.0 as they are not pure bugfix releases and worth a "feature" release, increasing feature version. Packages that depend on any changes introduced in version 3.8.2 or 3.8.3 should depend on version 3.9 or greater. 3.8.3 (2009-08-27) ------------------ - Move IXMLRPCPublisher ZCML registrations for containers from zope.app.publisher.xmlrpc to zope.container for now. 3.8.2 (2009-05-17) ------------------ - Rid ourselves of ``IContained`` interface. This interface was moved to ``zope.location.interfaces``. A b/w compat import still exists to keep old code running. Depend on ``zope.location``>=3.5.4. - Rid ourselves of the implementations of ``IObjectMovedEvent``, ``IObjectAddedEvent``, ``IObjectRemovedEvent`` interfaces and ``ObjectMovedEvent``, ``ObjectAddedEvent`` and ``ObjectRemovedEvent`` classes. B/w compat imports still exist. All of these were moved to ``zope.lifecycleevent``. Depend on ``zope.lifecycleevent``>=3.5.2. - Fix a bug in OrderedContainer where trying to set the value for a key that already exists (duplication error) would actually delete the key from the order, leaving a dangling reference. - Partially break dependency on ``zope.traversing`` by disusing zope.traversing.api.getPath in favor of using ILocationInfo(object).getPath(). The rest of the runtime dependencies on zope.traversing are currently interface dependencies. - Break runtime dependency on ``zope.app.dependable`` by using a zcml condition on the qsubscriber ZCML directive that registers the CheckDependency handler for IObjectRemovedEvent. If ``zope.app.dependable`` is not installed, this subscriber will never be registered. ``zope.app.dependable`` is now a testing dependency only. 3.8.1 (2009-04-03) ------------------ - Fixed misspackaged 3.8.0 3.8.0 (2009-04-03) ------------------ - Change configure.zcml to not depend on zope.app.component. Fixes: https://bugs.launchpad.net/bugs/348329 - Moved the declaration of ``IOrderedContainer.updateOrder`` to a new, basic ``IOrdered`` interface and let ``IOrderedContainer`` inherit it. This allows easier reuse of the declaration. 3.7.2 (2009-03-12) ------------------ - Fix: added missing ComponentLookupError, missing since revision 95429 and missing in last release. - Adapt to the move of IDefaultViewName from zope.component.interfaces to zope.publisher.interfaces. - Add support for reserved names for containers. To specify reserved names for some container, you need to provide an adapter from the container to the ``zope.container.interfaces.IReservedNames`` interface. The default NameChooser is now also aware of reserved names. 3.7.1 (2009-02-05) ------------------ - Raise more "Pythonic" errors from ``__setitem__``, losing the dependency on ``zope.exceptions``: o ``zope.exceptions.DuplicationError`` -> ``KeyError`` o ``zope.exceptions.UserError`` -> ``ValueError`` - Moved import of ``IBroken`` interface to use new ``zope.broken`` package, which has no dependencies beyond ``zope.interface``. - Made ``test`` part pull in the extra test requirements of this package. - Split the ``z3c.recipe.compattest`` configuration out into a new file, ``compat.cfg``, to reduce the burden of doing standard unit tests. - Stripped out bogus develop eggs from ``buildout.cfg``. 3.7.0 (2009-01-31) ------------------ - Split this package off ``zope.app.container``. This package is intended to have far less dependencies than ``zope.app.container``. - This package also contains the container implementation that used to be in ``zope.app.folder``. Keywords: zope container Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.container/pip-egg-info/0000755000175000017500000000000012214017547020661 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/pip-egg-info/zope.container.egg-info/0000755000175000017500000000000012214017547025311 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/pip-egg-info/zope.container.egg-info/PKG-INFO0000644000175000017500000002575112214017547026420 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.container Version: 3.11.2 Summary: Zope Container Home-page: http://pypi.python.org/pypi/zope.container Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: This package define interfaces of container components, and provides container implementations such as a BTreeContainer and OrderedContainer, as well as the base class used by ``zope.site.folder`` for the Folder implementation. .. contents:: ========================= Containment constraints ========================= Containment constraints allow us to express restrictions on the types of items that can be placed in containers or on the types of containers an item can be placed in. We express these constraints in interfaces. Let's define some container and item interfaces: >>> from zope.container.interfaces import IContainer >>> from zope.location.interfaces import IContained >>> from zope.container.constraints import containers, contains >>> class IBuddyFolder(IContainer): ... contains('.IBuddy') In this example, we used the contains function to declare that objects that provide IBuddyFolder can only contain items that provide IBuddy. Note that we used a string containing a dotted name for the IBuddy interface. This is because IBuddy hasn't been defined yet. When we define IBuddy, we can use IBuddyFolder directly: >>> class IBuddy(IContained): ... containers(IBuddyFolder) Now, with these interfaces in place, we can define Buddy and BuddyFolder classes and verify that we can put buddies in buddy folders: >>> from zope import interface >>> class Buddy: ... interface.implements(IBuddy) >>> class BuddyFolder: ... interface.implements(IBuddyFolder) >>> from zope.container.constraints import checkObject, checkFactory >>> from zope.component.factory import Factory >>> checkObject(BuddyFolder(), 'x', Buddy()) >>> checkFactory(BuddyFolder(), 'x', Factory(Buddy)) True If we try to use other containers or folders, we'll get errors: >>> class Container: ... interface.implements(IContainer) >>> class Contained: ... interface.implements(IContained) >>> checkObject(Container(), 'x', Buddy()) ... # doctest: +ELLIPSIS Traceback (most recent call last): InvalidContainerType: ... >>> checkFactory(Container(), 'x', Factory(Buddy)) False >>> checkObject(BuddyFolder(), 'x', Contained()) ... # doctest: +ELLIPSIS Traceback (most recent call last): InvalidItemType: ... >>> checkFactory(BuddyFolder(), 'x', Factory(Contained)) False In the example, we defined the container first and then the items. We could have defined these in the opposite order: >>> class IContact(IContained): ... containers('.IContacts') >>> class IContacts(IContainer): ... contains(IContact) >>> class Contact: ... interface.implements(IContact) >>> class Contacts: ... interface.implements(IContacts) >>> checkObject(Contacts(), 'x', Contact()) >>> checkFactory(Contacts(), 'x', Factory(Contact)) True >>> checkObject(Contacts(), 'x', Buddy()) ... # doctest: +ELLIPSIS Traceback (most recent call last): InvalidItemType: ... >>> checkFactory(Contacts(), 'x', Factory(Buddy)) False ======= CHANGES ======= 3.11.2 (2010-09-25) ------------------- - Added not declared, but needed test dependency on `zope.testing`. 3.11.1 (2010-04-30) ------------------- - Prefer the standard libraries doctest module to the one from zope.testing. - Added compatibility with ZODB3 3.10 by importing the IBroken interface from it directly. Once we can rely on the new ZODB3 version exclusively, we can remove the dependency onto the zope.broken distribution. - Never fail if the suggested name is in a wrong type (#227617) - ``checkName`` first checks the parameter type before the emptiness. 3.11.0 (2009-12-31) ------------------- - Copy two trivial classes from zope.cachedescriptors into this package, which allows us to remove that dependency. We didn't actually use any caching properties as the dependency suggested. 3.10.1 (2009-12-29) ------------------- - Moved zope.copypastemove related tests into that package. - Removed no longer used zcml prefix from the configure file. - Stop importing DocTestSuite from zope.testing.doctestunit. Fixes compatibility problems with zope.testing 3.8.4. 3.10.0 (2009-12-15) ------------------- - Break testing dependency on zope.app.testing. - Break testing dependency on zope.app.dependable by moving the code and tests into that package. - Import ISite from zope.component after it was moved there from zope.location. 3.9.1 (2009-10-18) ------------------ - Rerelease 3.9.0 as it had a broken Windows 2.6 egg. - Marked as part of the ZTK. 3.9.0 (2009-08-28) ------------------ - Previous releases should be versioned 3.9.0 as they are not pure bugfix releases and worth a "feature" release, increasing feature version. Packages that depend on any changes introduced in version 3.8.2 or 3.8.3 should depend on version 3.9 or greater. 3.8.3 (2009-08-27) ------------------ - Move IXMLRPCPublisher ZCML registrations for containers from zope.app.publisher.xmlrpc to zope.container for now. 3.8.2 (2009-05-17) ------------------ - Rid ourselves of ``IContained`` interface. This interface was moved to ``zope.location.interfaces``. A b/w compat import still exists to keep old code running. Depend on ``zope.location``>=3.5.4. - Rid ourselves of the implementations of ``IObjectMovedEvent``, ``IObjectAddedEvent``, ``IObjectRemovedEvent`` interfaces and ``ObjectMovedEvent``, ``ObjectAddedEvent`` and ``ObjectRemovedEvent`` classes. B/w compat imports still exist. All of these were moved to ``zope.lifecycleevent``. Depend on ``zope.lifecycleevent``>=3.5.2. - Fix a bug in OrderedContainer where trying to set the value for a key that already exists (duplication error) would actually delete the key from the order, leaving a dangling reference. - Partially break dependency on ``zope.traversing`` by disusing zope.traversing.api.getPath in favor of using ILocationInfo(object).getPath(). The rest of the runtime dependencies on zope.traversing are currently interface dependencies. - Break runtime dependency on ``zope.app.dependable`` by using a zcml condition on the qsubscriber ZCML directive that registers the CheckDependency handler for IObjectRemovedEvent. If ``zope.app.dependable`` is not installed, this subscriber will never be registered. ``zope.app.dependable`` is now a testing dependency only. 3.8.1 (2009-04-03) ------------------ - Fixed misspackaged 3.8.0 3.8.0 (2009-04-03) ------------------ - Change configure.zcml to not depend on zope.app.component. Fixes: https://bugs.launchpad.net/bugs/348329 - Moved the declaration of ``IOrderedContainer.updateOrder`` to a new, basic ``IOrdered`` interface and let ``IOrderedContainer`` inherit it. This allows easier reuse of the declaration. 3.7.2 (2009-03-12) ------------------ - Fix: added missing ComponentLookupError, missing since revision 95429 and missing in last release. - Adapt to the move of IDefaultViewName from zope.component.interfaces to zope.publisher.interfaces. - Add support for reserved names for containers. To specify reserved names for some container, you need to provide an adapter from the container to the ``zope.container.interfaces.IReservedNames`` interface. The default NameChooser is now also aware of reserved names. 3.7.1 (2009-02-05) ------------------ - Raise more "Pythonic" errors from ``__setitem__``, losing the dependency on ``zope.exceptions``: o ``zope.exceptions.DuplicationError`` -> ``KeyError`` o ``zope.exceptions.UserError`` -> ``ValueError`` - Moved import of ``IBroken`` interface to use new ``zope.broken`` package, which has no dependencies beyond ``zope.interface``. - Made ``test`` part pull in the extra test requirements of this package. - Split the ``z3c.recipe.compattest`` configuration out into a new file, ``compat.cfg``, to reduce the burden of doing standard unit tests. - Stripped out bogus develop eggs from ``buildout.cfg``. 3.7.0 (2009-01-31) ------------------ - Split this package off ``zope.app.container``. This package is intended to have far less dependencies than ``zope.app.container``. - This package also contains the container implementation that used to be in ``zope.app.folder``. Keywords: zope container Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.container/pip-egg-info/zope.container.egg-info/dependency_links.txt0000644000175000017500000000000112214017547031357 0ustar arnauarnau zope2.13-2.13.21/source/zope.container/pip-egg-info/zope.container.egg-info/requires.txt0000644000175000017500000000045112214017547027711 0ustar arnauarnausetuptools zope.interface zope.dottedname zope.schema zope.component zope.event zope.location>=3.5.4 zope.security zope.lifecycleevent>=3.5.2 zope.i18nmessageid zope.filerepresentation zope.size zope.traversing zope.publisher zope.broken ZODB3 [test] zope.configuration zope.security zope.testingzope2.13-2.13.21/source/zope.container/pip-egg-info/zope.container.egg-info/namespace_packages.txt0000644000175000017500000000000512214017547031637 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.container/pip-egg-info/zope.container.egg-info/top_level.txt0000644000175000017500000000000512214017547030036 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.container/pip-egg-info/zope.container.egg-info/SOURCES.txt0000644000175000017500000000267212214017547027204 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.container.egg-info/PKG-INFO pip-egg-info/zope.container.egg-info/SOURCES.txt pip-egg-info/zope.container.egg-info/dependency_links.txt pip-egg-info/zope.container.egg-info/namespace_packages.txt pip-egg-info/zope.container.egg-info/not-zip-safe pip-egg-info/zope.container.egg-info/requires.txt pip-egg-info/zope.container.egg-info/top_level.txt src/zope/__init__.py src/zope/container/__init__.py src/zope/container/_zope_container_contained.c src/zope/container/btree.py src/zope/container/constraints.py src/zope/container/contained.py src/zope/container/dependency.py src/zope/container/directory.py src/zope/container/find.py src/zope/container/folder.py src/zope/container/i18n.py src/zope/container/interfaces.py src/zope/container/ordered.py src/zope/container/sample.py src/zope/container/size.py src/zope/container/testing.py src/zope/container/traversal.py src/zope/container/tests/__init__.py src/zope/container/tests/test_btree.py src/zope/container/tests/test_constraints.py src/zope/container/tests/test_contained.py src/zope/container/tests/test_containertraversable.py src/zope/container/tests/test_containertraverser.py src/zope/container/tests/test_dependencies.py src/zope/container/tests/test_directory.py src/zope/container/tests/test_find.py src/zope/container/tests/test_folder.py src/zope/container/tests/test_icontainer.py src/zope/container/tests/test_ordered.py src/zope/container/tests/test_size.pyzope2.13-2.13.21/source/zope.container/pip-egg-info/zope.container.egg-info/not-zip-safe0000644000175000017500000000000112214017547027537 0ustar arnauarnau zope2.13-2.13.21/source/zope.container/README.txt0000644000175000017500000000034412214017546020076 0ustar arnauarnauThis package define interfaces of container components, and provides container implementations such as a BTreeContainer and OrderedContainer, as well as the base class used by ``zope.site.folder`` for the Folder implementation. zope2.13-2.13.21/source/zope.container/setup.cfg0000644000175000017500000000007312214017546020220 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.container/include/0000755000175000017500000000000012214017546020022 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/include/persistent/0000755000175000017500000000000012214017546022222 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/include/persistent/dict.py0000644000175000017500000000541212214017546023521 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Python implementation of persistent container type $Id: dict.py 40330 2005-11-22 20:55:47Z tlotze $ """ import persistent from UserDict import IterableUserDict __metaclass__ = type class PersistentDict(persistent.Persistent, IterableUserDict): """A persistent wrapper for mapping objects. This class allows wrapping of mapping objects so that object changes are registered. As a side effect, mapping objects may be subclassed. """ # IterableUserDict provides all of the mapping behavior. The # PersistentDict class is responsible marking the persistent # state as changed when a method actually changes the state. At # the mapping API evolves, we may need to add more methods here. __super_delitem = IterableUserDict.__delitem__ __super_setitem = IterableUserDict.__setitem__ __super_clear = IterableUserDict.clear __super_update = IterableUserDict.update __super_setdefault = IterableUserDict.setdefault __super_pop = IterableUserDict.pop __super_popitem = IterableUserDict.popitem __super_p_init = persistent.Persistent.__init__ __super_init = IterableUserDict.__init__ def __init__(self, dict=None): self.__super_init(dict) self.__super_p_init() def __delitem__(self, key): self.__super_delitem(key) self._p_changed = True def __setitem__(self, key, v): self.__super_setitem(key, v) self._p_changed = True def clear(self): self.__super_clear() self._p_changed = True def update(self, b): self.__super_update(b) self._p_changed = True def setdefault(self, key, failobj=None): # We could inline all of UserDict's implementation into the # method here, but I'd rather not depend at all on the # implementation in UserDict (simple as it is). if not self.has_key(key): self._p_changed = True return self.__super_setdefault(key, failobj) def pop(self, key, *args): self._p_changed = True return self.__super_pop(key, *args) def popitem(self): self._p_changed = True return self.__super_popitem() zope2.13-2.13.21/source/zope.container/include/persistent/SETUP.cfg0000644000175000017500000000121012214017546023575 0ustar arnauarnau# Extension information for zpkg. # Mark an "exported" header for use from other packages. # This is not needed for headers only used within the package. # header cPersistence.h # This is included by cPersistence.h, so all users of cPersistence.h # have to be able to include this indirectly. # header ring.h source cPersistence.c source ring.c depends-on cPersistence.h depends-on ring.h source cPickleCache.c source ring.c depends-on cPersistence.h depends-on ring.h source TimeStamp.c zope2.13-2.13.21/source/zope.container/include/persistent/ring.c0000644000175000017500000000346412214017546023334 0ustar arnauarnau/***************************************************************************** Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #define RING_C "$Id: ring.c 25186 2004-06-02 15:07:33Z jim $\n" /* Support routines for the doubly-linked list of cached objects. The cache stores a doubly-linked list of persistent objects, with space for the pointers allocated in the objects themselves. The cache stores the distinguished head of the list, which is not a valid persistent object. The next pointers traverse the ring in order starting with the least recently used object. The prev pointers traverse the ring in order starting with the most recently used object. */ #include "Python.h" #include "ring.h" void ring_add(CPersistentRing *ring, CPersistentRing *elt) { assert(!elt->r_next); elt->r_next = ring; elt->r_prev = ring->r_prev; ring->r_prev->r_next = elt; ring->r_prev = elt; } void ring_del(CPersistentRing *elt) { elt->r_next->r_prev = elt->r_prev; elt->r_prev->r_next = elt->r_next; elt->r_next = NULL; elt->r_prev = NULL; } void ring_move_to_head(CPersistentRing *ring, CPersistentRing *elt) { elt->r_prev->r_next = elt->r_next; elt->r_next->r_prev = elt->r_prev; elt->r_next = ring; elt->r_prev = ring->r_prev; ring->r_prev->r_next = elt; ring->r_prev = elt; } zope2.13-2.13.21/source/zope.container/include/persistent/list.py0000644000175000017500000000556112214017546023556 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Python implementation of persistent list. $Id: list.py 25186 2004-06-02 15:07:33Z jim $""" import persistent from UserList import UserList class PersistentList(UserList, persistent.Persistent): __super_setitem = UserList.__setitem__ __super_delitem = UserList.__delitem__ __super_setslice = UserList.__setslice__ __super_delslice = UserList.__delslice__ __super_iadd = UserList.__iadd__ __super_imul = UserList.__imul__ __super_append = UserList.append __super_insert = UserList.insert __super_pop = UserList.pop __super_remove = UserList.remove __super_reverse = UserList.reverse __super_sort = UserList.sort __super_extend = UserList.extend def __setitem__(self, i, item): self.__super_setitem(i, item) self._p_changed = 1 def __delitem__(self, i): self.__super_delitem(i) self._p_changed = 1 def __setslice__(self, i, j, other): self.__super_setslice(i, j, other) self._p_changed = 1 def __delslice__(self, i, j): self.__super_delslice(i, j) self._p_changed = 1 def __iadd__(self, other): L = self.__super_iadd(other) self._p_changed = 1 return L def __imul__(self, n): L = self.__super_imul(n) self._p_changed = 1 return L def append(self, item): self.__super_append(item) self._p_changed = 1 def insert(self, i, item): self.__super_insert(i, item) self._p_changed = 1 def pop(self, i=-1): rtn = self.__super_pop(i) self._p_changed = 1 return rtn def remove(self, item): self.__super_remove(item) self._p_changed = 1 def reverse(self): self.__super_reverse() self._p_changed = 1 def sort(self, *args): self.__super_sort(*args) self._p_changed = 1 def extend(self, other): self.__super_extend(other) self._p_changed = 1 # This works around a bug in Python 2.1.x (up to 2.1.2 at least) where the # __cmp__ bogusly raises a RuntimeError, and because this is an extension # class, none of the rich comparison stuff works anyway. def __cmp__(self, other): return cmp(self.data, self._UserList__cast(other)) zope2.13-2.13.21/source/zope.container/include/persistent/mapping.py0000644000175000017500000001014112214017546024224 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Python implementation of persistent base types $Id: mapping.py 66125 2006-03-22 15:43:22Z tseaver $""" import persistent from UserDict import UserDict class PersistentMapping(UserDict, persistent.Persistent): """A persistent wrapper for mapping objects. This class allows wrapping of mapping objects so that object changes are registered. As a side effect, mapping objects may be subclassed. A subclass of PersistentMapping or any code that adds new attributes should not create an attribute named _container. This is reserved for backwards compatibility reasons. """ # UserDict provides all of the mapping behavior. The # PersistentMapping class is responsible marking the persistent # state as changed when a method actually changes the state. At # the mapping API evolves, we may need to add more methods here. __super_delitem = UserDict.__delitem__ __super_setitem = UserDict.__setitem__ __super_clear = UserDict.clear __super_update = UserDict.update __super_setdefault = UserDict.setdefault __super_pop = UserDict.pop __super_popitem = UserDict.popitem def __delitem__(self, key): self.__super_delitem(key) self._p_changed = 1 def __setitem__(self, key, v): self.__super_setitem(key, v) self._p_changed = 1 def clear(self): self.__super_clear() self._p_changed = 1 def update(self, b): self.__super_update(b) self._p_changed = 1 def setdefault(self, key, failobj=None): # We could inline all of UserDict's implementation into the # method here, but I'd rather not depend at all on the # implementation in UserDict (simple as it is). if not self.has_key(key): self._p_changed = 1 return self.__super_setdefault(key, failobj) def pop(self, key, *args): self._p_changed = 1 return self.__super_pop(key, *args) def popitem(self): self._p_changed = 1 return self.__super_popitem() # __iter__ was added in ZODB 3.4.2, but should have been added long # before. We could inherit from Python's IterableUserDict instead # (which just adds __iter__ to Python's UserDict), but that class isn't # documented, and it would add another level of lookup for all the # other methods. def __iter__(self): return iter(self.data) # If the internal representation of PersistentMapping changes, # it causes compatibility problems for pickles generated by # different versions of the code. Compatibility works in both # directions, because an application may want to share a database # between applications using different versions of the code. # Effectively, the original rep is part of the "API." To provide # full compatibility, the getstate and setstate must read and # write objects using the old rep. # As a result, the PersistentMapping must save and restore the # actual internal dictionary using the name _container. def __getstate__(self): state = dict([x for x in self.__dict__.items() if not x[0].startswith('_v_')]) state['_container'] = state['data'] del state['data'] return state def __setstate__(self, state): if state.has_key('_container'): self.data = state['_container'] del state['_container'] elif not state.has_key('data'): self.data = {} self.__dict__.update(state) zope2.13-2.13.21/source/zope.container/include/persistent/cPersistence.c0000644000175000017500000007430012214017546025021 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ static char cPersistence_doc_string[] = "Defines Persistent mixin class for persistent objects.\n" "\n" "$Id: cPersistence.c 38459 2005-09-13 22:38:19Z tim_one $\n"; #include "cPersistence.h" #include "structmember.h" struct ccobject_head_struct { CACHE_HEAD }; /* These two objects are initialized when the module is loaded */ static PyObject *TimeStamp, *py_simple_new; /* Strings initialized by init_strings() below. */ static PyObject *py_keys, *py_setstate, *py___dict__, *py_timeTime; static PyObject *py__p_changed, *py__p_deactivate; static PyObject *py___getattr__, *py___setattr__, *py___delattr__; static PyObject *py___slotnames__, *copy_reg_slotnames, *__newobj__; static PyObject *py___getnewargs__, *py___getstate__; static int init_strings(void) { #define INIT_STRING(S) \ if (!(py_ ## S = PyString_InternFromString(#S))) \ return -1; INIT_STRING(keys); INIT_STRING(setstate); INIT_STRING(timeTime); INIT_STRING(__dict__); INIT_STRING(_p_changed); INIT_STRING(_p_deactivate); INIT_STRING(__getattr__); INIT_STRING(__setattr__); INIT_STRING(__delattr__); INIT_STRING(__slotnames__); INIT_STRING(__getnewargs__); INIT_STRING(__getstate__); #undef INIT_STRING return 0; } #ifdef Py_DEBUG static void fatal_1350(cPersistentObject *self, const char *caller, const char *detail) { char buf[1000]; PyOS_snprintf(buf, sizeof(buf), "cPersistence.c %s(): object at %p with type %.200s\n" "%s.\n" "The only known cause is multiple threads trying to ghost and\n" "unghost the object simultaneously.\n" "That's not legal, but ZODB can't stop it.\n" "See Collector #1350.\n", caller, self, self->ob_type->tp_name, detail); Py_FatalError(buf); } #endif static void ghostify(cPersistentObject*); /* Load the state of the object, unghostifying it. Upon success, return 1. * If an error occurred, re-ghostify the object and return -1. */ static int unghostify(cPersistentObject *self) { if (self->state < 0 && self->jar) { PyObject *r; /* Is it ever possibly to not have a cache? */ if (self->cache) { /* Create a node in the ring for this unghostified object. */ self->cache->non_ghost_count++; ring_add(&self->cache->ring_home, &self->ring); Py_INCREF(self); } /* set state to CHANGED while setstate() call is in progress to prevent a recursive call to _PyPersist_Load(). */ self->state = cPersistent_CHANGED_STATE; /* Call the object's __setstate__() */ r = PyObject_CallMethod(self->jar, "setstate", "O", (PyObject *)self); if (r == NULL) { ghostify(self); return -1; } self->state = cPersistent_UPTODATE_STATE; Py_DECREF(r); if (self->cache && self->ring.r_next == NULL) { #ifdef Py_DEBUG fatal_1350(self, "unghostify", "is not in the cache despite that we just " "unghostified it"); #else PyErr_Format(PyExc_SystemError, "object at %p with type " "%.200s not in the cache despite that we just " "unghostified it", self, self->ob_type->tp_name); return -1; #endif } } return 1; } /****************************************************************************/ static PyTypeObject Pertype; static void accessed(cPersistentObject *self) { /* Do nothing unless the object is in a cache and not a ghost. */ if (self->cache && self->state >= 0 && self->ring.r_next) ring_move_to_head(&self->cache->ring_home, &self->ring); } static void unlink_from_ring(cPersistentObject *self) { /* If the cache has been cleared, then a non-ghost object isn't in the ring any longer. */ if (self->ring.r_next == NULL) return; /* if we're ghostifying an object, we better have some non-ghosts */ assert(self->cache->non_ghost_count > 0); self->cache->non_ghost_count--; ring_del(&self->ring); } static void ghostify(cPersistentObject *self) { PyObject **dictptr; /* are we already a ghost? */ if (self->state == cPersistent_GHOST_STATE) return; /* Is it ever possible to not have a cache? */ if (self->cache == NULL) { self->state = cPersistent_GHOST_STATE; return; } if (self->ring.r_next == NULL) { /* There's no way to raise an error in this routine. */ #ifdef Py_DEBUG fatal_1350(self, "ghostify", "claims to be in a cache but isn't"); #else return; #endif } /* If we're ghostifying an object, we better have some non-ghosts. */ assert(self->cache->non_ghost_count > 0); self->cache->non_ghost_count--; ring_del(&self->ring); self->state = cPersistent_GHOST_STATE; dictptr = _PyObject_GetDictPtr((PyObject *)self); if (dictptr && *dictptr) { Py_DECREF(*dictptr); *dictptr = NULL; } /* We remove the reference to the just ghosted object that the ring * holds. Note that the dictionary of oids->objects has an uncounted * reference, so if the ring's reference was the only one, this frees * the ghost object. Note further that the object's dealloc knows to * inform the dictionary that it is going away. */ Py_DECREF(self); } static int changed(cPersistentObject *self) { if ((self->state == cPersistent_UPTODATE_STATE || self->state == cPersistent_STICKY_STATE) && self->jar) { PyObject *meth, *arg, *result; static PyObject *s_register; if (s_register == NULL) s_register = PyString_InternFromString("register"); meth = PyObject_GetAttr((PyObject *)self->jar, s_register); if (meth == NULL) return -1; arg = PyTuple_New(1); if (arg == NULL) { Py_DECREF(meth); return -1; } Py_INCREF(self); PyTuple_SET_ITEM(arg, 0, (PyObject *)self); result = PyEval_CallObject(meth, arg); Py_DECREF(arg); Py_DECREF(meth); if (result == NULL) return -1; Py_DECREF(result); self->state = cPersistent_CHANGED_STATE; } return 0; } static PyObject * Per__p_deactivate(cPersistentObject *self) { if (self->state == cPersistent_UPTODATE_STATE && self->jar) { PyObject **dictptr = _PyObject_GetDictPtr((PyObject *)self); if (dictptr && *dictptr) { Py_DECREF(*dictptr); *dictptr = NULL; } /* Note that we need to set to ghost state unless we are called directly. Methods that override this need to do the same! */ ghostify(self); } Py_INCREF(Py_None); return Py_None; } static PyObject * Per__p_activate(cPersistentObject *self) { if (unghostify(self) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static int Per_set_changed(cPersistentObject *self, PyObject *v); static PyObject * Per__p_invalidate(cPersistentObject *self) { signed char old_state = self->state; if (old_state != cPersistent_GHOST_STATE) { if (Per_set_changed(self, NULL) < 0) return NULL; ghostify(self); } Py_INCREF(Py_None); return Py_None; } static PyObject * pickle_slotnames(PyTypeObject *cls) { PyObject *slotnames; slotnames = PyDict_GetItem(cls->tp_dict, py___slotnames__); if (slotnames) { Py_INCREF(slotnames); return slotnames; } slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames, (PyObject*)cls, NULL); if (slotnames && !(slotnames == Py_None || PyList_Check(slotnames))) { PyErr_SetString(PyExc_TypeError, "copy_reg._slotnames didn't return a list or None"); Py_DECREF(slotnames); return NULL; } return slotnames; } static PyObject * pickle_copy_dict(PyObject *state) { PyObject *copy, *key, *value; char *ckey; int pos = 0; copy = PyDict_New(); if (!copy) return NULL; if (!state) return copy; while (PyDict_Next(state, &pos, &key, &value)) { if (key && PyString_Check(key)) { ckey = PyString_AS_STRING(key); if (*ckey == '_' && (ckey[1] == 'v' || ckey[1] == 'p') && ckey[2] == '_') /* skip volatile and persistent */ continue; } if (PyObject_SetItem(copy, key, value) < 0) goto err; } return copy; err: Py_DECREF(copy); return NULL; } static char pickle___getstate__doc[] = "Get the object serialization state\n" "\n" "If the object has no assigned slots and has no instance dictionary, then \n" "None is returned.\n" "\n" "If the object has no assigned slots and has an instance dictionary, then \n" "the a copy of the instance dictionary is returned. The copy has any items \n" "with names starting with '_v_' or '_p_' ommitted.\n" "\n" "If the object has assigned slots, then a two-element tuple is returned. \n" "The first element is either None or a copy of the instance dictionary, \n" "as described above. The second element is a dictionary with items \n" "for each of the assigned slots.\n" ; static PyObject * pickle___getstate__(PyObject *self) { PyObject *slotnames=NULL, *slots=NULL, *state=NULL; PyObject **dictp; int n=0; slotnames = pickle_slotnames(self->ob_type); if (!slotnames) return NULL; dictp = _PyObject_GetDictPtr(self); if (dictp) state = pickle_copy_dict(*dictp); else { state = Py_None; Py_INCREF(state); } if (slotnames != Py_None) { int i; slots = PyDict_New(); if (!slots) goto end; for (i = 0; i < PyList_GET_SIZE(slotnames); i++) { PyObject *name, *value; char *cname; name = PyList_GET_ITEM(slotnames, i); if (PyString_Check(name)) { cname = PyString_AS_STRING(name); if (*cname == '_' && (cname[1] == 'v' || cname[1] == 'p') && cname[2] == '_') /* skip volatile and persistent */ continue; } /* Unclear: Will this go through our getattr hook? */ value = PyObject_GetAttr(self, name); if (value == NULL) PyErr_Clear(); else { int err = PyDict_SetItem(slots, name, value); Py_DECREF(value); if (err < 0) goto end; n++; } } } if (n) state = Py_BuildValue("(NO)", state, slots); end: Py_XDECREF(slotnames); Py_XDECREF(slots); return state; } static int pickle_setattrs_from_dict(PyObject *self, PyObject *dict) { PyObject *key, *value; int pos = 0; if (!PyDict_Check(dict)) { PyErr_SetString(PyExc_TypeError, "Expected dictionary"); return -1; } while (PyDict_Next(dict, &pos, &key, &value)) { if (PyObject_SetAttr(self, key, value) < 0) return -1; } return 0; } static char pickle___setstate__doc[] = "Set the object serialization state\n\n" "The state should be in one of 3 forms:\n\n" "- None\n\n" " Ignored\n\n" "- A dictionary\n\n" " In this case, the object's instance dictionary will be cleared and \n" " updated with the new state.\n\n" "- A two-tuple with a string as the first element. \n\n" " In this case, the method named by the string in the first element will be\n" " called with the second element.\n\n" " This form supports migration of data formats.\n\n" "- A two-tuple with None or a Dictionary as the first element and\n" " with a dictionary as the second element.\n\n" " If the first element is not None, then the object's instance dictionary \n" " will be cleared and updated with the value.\n\n" " The items in the second element will be assigned as attributes.\n" ; static PyObject * pickle___setstate__(PyObject *self, PyObject *state) { PyObject *slots=NULL; if (PyTuple_Check(state)) { if (!PyArg_ParseTuple(state, "OO:__setstate__", &state, &slots)) return NULL; } if (state != Py_None) { PyObject **dict; dict = _PyObject_GetDictPtr(self); if (dict) { if (!*dict) { *dict = PyDict_New(); if (!*dict) return NULL; } } if (*dict) { PyDict_Clear(*dict); if (PyDict_Update(*dict, state) < 0) return NULL; } else if (pickle_setattrs_from_dict(self, state) < 0) return NULL; } if (slots && pickle_setattrs_from_dict(self, slots) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static char pickle___reduce__doc[] = "Reduce an object to contituent parts for serialization\n" ; static PyObject * pickle___reduce__(PyObject *self) { PyObject *args=NULL, *bargs=NULL, *state=NULL, *getnewargs=NULL; int l, i; getnewargs = PyObject_GetAttr(self, py___getnewargs__); if (getnewargs) { bargs = PyObject_CallFunctionObjArgs(getnewargs, NULL); Py_DECREF(getnewargs); if (!bargs) return NULL; l = PyTuple_Size(bargs); if (l < 0) goto end; } else { PyErr_Clear(); l = 0; } args = PyTuple_New(l+1); if (args == NULL) goto end; Py_INCREF(self->ob_type); PyTuple_SET_ITEM(args, 0, (PyObject*)(self->ob_type)); for (i = 0; i < l; i++) { Py_INCREF(PyTuple_GET_ITEM(bargs, i)); PyTuple_SET_ITEM(args, i+1, PyTuple_GET_ITEM(bargs, i)); } state = PyObject_CallMethodObjArgs(self, py___getstate__, NULL); if (!state) goto end; state = Py_BuildValue("(OON)", __newobj__, args, state); end: Py_XDECREF(bargs); Py_XDECREF(args); return state; } /* Return the object's state, a dict or None. If the object has no dict, it's state is None. Otherwise, return a dict containing all the attributes that don't start with "_v_". The caller should not modify this dict, as it may be a reference to the object's __dict__. */ static PyObject * Per__getstate__(cPersistentObject *self) { /* TODO: Should it be an error to call __getstate__() on a ghost? */ if (unghostify(self) < 0) return NULL; /* TODO: should we increment stickyness? Tim doesn't understand that question. S*/ return pickle___getstate__((PyObject*)self); } /* The Persistent base type provides a traverse function, but not a clear function. An instance of a Persistent subclass will have its dict cleared through subtype_clear(). There is always a cycle between a persistent object and its cache. When the cycle becomes unreachable, the clear function for the cache will break the cycle. Thus, the persistent object need not have a clear function. It would be complex to write a clear function for the objects, if we needed one, because of the reference count tricks done by the cache. */ static void Per_dealloc(cPersistentObject *self) { if (self->state >= 0) unlink_from_ring(self); if (self->cache) cPersistenceCAPI->percachedel(self->cache, self->oid); Py_XDECREF(self->cache); Py_XDECREF(self->jar); Py_XDECREF(self->oid); self->ob_type->tp_free(self); } static int Per_traverse(cPersistentObject *self, visitproc visit, void *arg) { int err; #define VISIT(SLOT) \ if (SLOT) { \ err = visit((PyObject *)(SLOT), arg); \ if (err) \ return err; \ } VISIT(self->jar); VISIT(self->oid); VISIT(self->cache); #undef VISIT return 0; } /* convert_name() returns a new reference to a string name or sets an exception and returns NULL. */ static PyObject * convert_name(PyObject *name) { #ifdef Py_USING_UNICODE /* The Unicode to string conversion is done here because the existing tp_setattro slots expect a string object as name and we wouldn't want to break those. */ if (PyUnicode_Check(name)) { name = PyUnicode_AsEncodedString(name, NULL, NULL); } else #endif if (!PyString_Check(name)) { PyErr_SetString(PyExc_TypeError, "attribute name must be a string"); return NULL; } else Py_INCREF(name); return name; } /* Returns true if the object requires unghostification. There are several special attributes that we allow access to without requiring that the object be unghostified: __class__ __del__ __dict__ __of__ __setstate__ */ static int unghost_getattr(const char *s) { if (*s++ != '_') return 1; if (*s == 'p') { s++; if (*s == '_') return 0; /* _p_ */ else return 1; } else if (*s == '_') { s++; switch (*s) { case 'c': return strcmp(s, "class__"); case 'd': s++; if (!strcmp(s, "el__")) return 0; /* __del__ */ if (!strcmp(s, "ict__")) return 0; /* __dict__ */ return 1; case 'o': return strcmp(s, "of__"); case 's': return strcmp(s, "setstate__"); default: return 1; } } return 1; } static PyObject* Per_getattro(cPersistentObject *self, PyObject *name) { PyObject *result = NULL; /* guilty until proved innocent */ char *s; name = convert_name(name); if (!name) goto Done; s = PyString_AS_STRING(name); if (unghost_getattr(s)) { if (unghostify(self) < 0) goto Done; accessed(self); } result = PyObject_GenericGetAttr((PyObject *)self, name); Done: Py_XDECREF(name); return result; } /* Exposed as _p_getattr method. Test whether base getattr should be used */ static PyObject * Per__p_getattr(cPersistentObject *self, PyObject *name) { PyObject *result = NULL; /* guilty until proved innocent */ char *s; name = convert_name(name); if (!name) goto Done; s = PyString_AS_STRING(name); if (*s != '_' || unghost_getattr(s)) { if (unghostify(self) < 0) goto Done; accessed(self); result = Py_False; } else result = Py_True; Py_INCREF(result); Done: Py_XDECREF(name); return result; } /* TODO: we should probably not allow assignment of __class__ and __dict__. */ static int Per_setattro(cPersistentObject *self, PyObject *name, PyObject *v) { int result = -1; /* guilty until proved innocent */ char *s; name = convert_name(name); if (!name) goto Done; s = PyString_AS_STRING(name); if (strncmp(s, "_p_", 3) != 0) { if (unghostify(self) < 0) goto Done; accessed(self); if (strncmp(s, "_v_", 3) != 0 && self->state != cPersistent_CHANGED_STATE) { if (changed(self) < 0) goto Done; } } result = PyObject_GenericSetAttr((PyObject *)self, name, v); Done: Py_XDECREF(name); return result; } static int Per_p_set_or_delattro(cPersistentObject *self, PyObject *name, PyObject *v) { int result = -1; /* guilty until proved innocent */ char *s; name = convert_name(name); if (!name) goto Done; s = PyString_AS_STRING(name); if (strncmp(s, "_p_", 3)) { if (unghostify(self) < 0) goto Done; accessed(self); result = 0; } else { if (PyObject_GenericSetAttr((PyObject *)self, name, v) < 0) goto Done; result = 1; } Done: Py_XDECREF(name); return result; } static PyObject * Per__p_setattr(cPersistentObject *self, PyObject *args) { PyObject *name, *v, *result; int r; if (!PyArg_ParseTuple(args, "OO:_p_setattr", &name, &v)) return NULL; r = Per_p_set_or_delattro(self, name, v); if (r < 0) return NULL; result = r ? Py_True : Py_False; Py_INCREF(result); return result; } static PyObject * Per__p_delattr(cPersistentObject *self, PyObject *name) { int r; PyObject *result; r = Per_p_set_or_delattro(self, name, NULL); if (r < 0) return NULL; result = r ? Py_True : Py_False; Py_INCREF(result); return result; } static PyObject * Per_get_changed(cPersistentObject *self) { if (self->state < 0) { Py_INCREF(Py_None); return Py_None; } return PyBool_FromLong(self->state == cPersistent_CHANGED_STATE); } static int Per_set_changed(cPersistentObject *self, PyObject *v) { int deactivate = 0; int true; if (!v) { /* delattr is used to invalidate an object even if it has changed. */ if (self->state != cPersistent_GHOST_STATE) self->state = cPersistent_UPTODATE_STATE; deactivate = 1; } else if (v == Py_None) deactivate = 1; if (deactivate) { PyObject *res, *meth; meth = PyObject_GetAttr((PyObject *)self, py__p_deactivate); if (meth == NULL) return -1; res = PyObject_CallObject(meth, NULL); if (res) Py_DECREF(res); else { /* an error occured in _p_deactivate(). It's not clear what we should do here. The code is obviously ignoring the exception, but it shouldn't return 0 for a getattr and set an exception. The simplest change is to clear the exception, but that simply masks the error. This prints an error to stderr just like exceptions in __del__(). It would probably be better to log it but that would be painful from C. */ PyErr_WriteUnraisable(meth); } Py_DECREF(meth); return 0; } /* !deactivate. If passed a true argument, mark self as changed (starting * with ZODB 3.6, that includes activating the object if it's a ghost). * If passed a false argument, and the object isn't a ghost, set the * state as up-to-date. */ true = PyObject_IsTrue(v); if (true == -1) return -1; if (true) { if (self->state < 0) { if (unghostify(self) < 0) return -1; } return changed(self); } /* We were passed a false, non-None argument. If we're not a ghost, * mark self as up-to-date. */ if (self->state >= 0) self->state = cPersistent_UPTODATE_STATE; return 0; } static PyObject * Per_get_oid(cPersistentObject *self) { PyObject *oid = self->oid ? self->oid : Py_None; Py_INCREF(oid); return oid; } static int Per_set_oid(cPersistentObject *self, PyObject *v) { if (self->cache) { int result; if (v == NULL) { PyErr_SetString(PyExc_ValueError, "can't delete _p_oid of cached object"); return -1; } if (PyObject_Cmp(self->oid, v, &result) < 0) return -1; if (result) { PyErr_SetString(PyExc_ValueError, "can not change _p_oid of cached object"); return -1; } } Py_XDECREF(self->oid); Py_XINCREF(v); self->oid = v; return 0; } static PyObject * Per_get_jar(cPersistentObject *self) { PyObject *jar = self->jar ? self->jar : Py_None; Py_INCREF(jar); return jar; } static int Per_set_jar(cPersistentObject *self, PyObject *v) { if (self->cache) { int result; if (v == NULL) { PyErr_SetString(PyExc_ValueError, "can't delete _p_jar of cached object"); return -1; } if (PyObject_Cmp(self->jar, v, &result) < 0) return -1; if (result) { PyErr_SetString(PyExc_ValueError, "can not change _p_jar of cached object"); return -1; } } Py_XDECREF(self->jar); Py_XINCREF(v); self->jar = v; return 0; } static PyObject * Per_get_serial(cPersistentObject *self) { return PyString_FromStringAndSize(self->serial, 8); } static int Per_set_serial(cPersistentObject *self, PyObject *v) { if (v) { if (PyString_Check(v) && PyString_GET_SIZE(v) == 8) memcpy(self->serial, PyString_AS_STRING(v), 8); else { PyErr_SetString(PyExc_ValueError, "_p_serial must be an 8-character string"); return -1; } } else memset(self->serial, 0, 8); return 0; } static PyObject * Per_get_mtime(cPersistentObject *self) { PyObject *t, *v; if (unghostify(self) < 0) return NULL; accessed(self); if (memcmp(self->serial, "\0\0\0\0\0\0\0\0", 8) == 0) { Py_INCREF(Py_None); return Py_None; } t = PyObject_CallFunction(TimeStamp, "s#", self->serial, 8); if (!t) return NULL; v = PyObject_CallMethod(t, "timeTime", ""); Py_DECREF(t); return v; } static PyObject * Per_get_state(cPersistentObject *self) { return PyInt_FromLong(self->state); } static PyGetSetDef Per_getsets[] = { {"_p_changed", (getter)Per_get_changed, (setter)Per_set_changed}, {"_p_jar", (getter)Per_get_jar, (setter)Per_set_jar}, {"_p_mtime", (getter)Per_get_mtime}, {"_p_oid", (getter)Per_get_oid, (setter)Per_set_oid}, {"_p_serial", (getter)Per_get_serial, (setter)Per_set_serial}, {"_p_state", (getter)Per_get_state}, {NULL} }; static struct PyMethodDef Per_methods[] = { {"_p_deactivate", (PyCFunction)Per__p_deactivate, METH_NOARGS, "_p_deactivate() -- Deactivate the object"}, {"_p_activate", (PyCFunction)Per__p_activate, METH_NOARGS, "_p_activate() -- Activate the object"}, {"_p_invalidate", (PyCFunction)Per__p_invalidate, METH_NOARGS, "_p_invalidate() -- Invalidate the object"}, {"_p_getattr", (PyCFunction)Per__p_getattr, METH_O, "_p_getattr(name) -- Test whether the base class must handle the name\n" "\n" "The method unghostifies the object, if necessary.\n" "The method records the object access, if necessary.\n" "\n" "This method should be called by subclass __getattribute__\n" "implementations before doing anything else. If the method\n" "returns True, then __getattribute__ implementations must delegate\n" "to the base class, Persistent.\n" }, {"_p_setattr", (PyCFunction)Per__p_setattr, METH_VARARGS, "_p_setattr(name, value) -- Save persistent meta data\n" "\n" "This method should be called by subclass __setattr__ implementations\n" "before doing anything else. If it returns true, then the attribute\n" "was handled by the base class.\n" "\n" "The method unghostifies the object, if necessary.\n" "The method records the object access, if necessary.\n" }, {"_p_delattr", (PyCFunction)Per__p_delattr, METH_O, "_p_delattr(name) -- Delete persistent meta data\n" "\n" "This method should be called by subclass __delattr__ implementations\n" "before doing anything else. If it returns true, then the attribute\n" "was handled by the base class.\n" "\n" "The method unghostifies the object, if necessary.\n" "The method records the object access, if necessary.\n" }, {"__getstate__", (PyCFunction)Per__getstate__, METH_NOARGS, pickle___getstate__doc }, {"__setstate__", (PyCFunction)pickle___setstate__, METH_O, pickle___setstate__doc}, {"__reduce__", (PyCFunction)pickle___reduce__, METH_NOARGS, pickle___reduce__doc}, {NULL, NULL} /* sentinel */ }; /* This module is compiled as a shared library. Some compilers don't allow addresses of Python objects defined in other libraries to be used in static initializers here. The DEFERRED_ADDRESS macro is used to tag the slots where such addresses appear; the module init function must fill in the tagged slots at runtime. The argument is for documentation -- the macro ignores it. */ #define DEFERRED_ADDRESS(ADDR) 0 static PyTypeObject Pertype = { PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyPersist_MetaType)) 0, /* ob_size */ "persistent.Persistent", /* tp_name */ sizeof(cPersistentObject), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Per_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ (getattrofunc)Per_getattro, /* tp_getattro */ (setattrofunc)Per_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ (traverseproc)Per_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Per_methods, /* tp_methods */ 0, /* tp_members */ Per_getsets, /* tp_getset */ }; /* End of code for Persistent objects */ /* -------------------------------------------------------- */ typedef int (*intfunctionwithpythonarg)(PyObject*); /* Load the object's state if necessary and become sticky */ static int Per_setstate(cPersistentObject *self) { if (unghostify(self) < 0) return -1; self->state = cPersistent_STICKY_STATE; return 0; } static PyObject * simple_new(PyObject *self, PyObject *type_object) { return PyType_GenericNew((PyTypeObject *)type_object, NULL, NULL); } static PyMethodDef cPersistence_methods[] = { {"simple_new", simple_new, METH_O, "Create an object by simply calling a class's __new__ method without " "arguments."}, {NULL, NULL} }; static cPersistenceCAPIstruct truecPersistenceCAPI = { &Pertype, (getattrofunc)Per_getattro, /*tp_getattr with object key*/ (setattrofunc)Per_setattro, /*tp_setattr with object key*/ changed, accessed, ghostify, (intfunctionwithpythonarg)Per_setstate, NULL /* The percachedel slot is initialized in cPickleCache.c when the module is loaded. It uses a function in a different shared library. */ }; void initcPersistence(void) { PyObject *m, *s; PyObject *copy_reg; if (init_strings() < 0) return; m = Py_InitModule3("cPersistence", cPersistence_methods, cPersistence_doc_string); Pertype.ob_type = &PyType_Type; Pertype.tp_new = PyType_GenericNew; if (PyType_Ready(&Pertype) < 0) return; if (PyModule_AddObject(m, "Persistent", (PyObject *)&Pertype) < 0) return; cPersistenceCAPI = &truecPersistenceCAPI; s = PyCObject_FromVoidPtr(cPersistenceCAPI, NULL); if (!s) return; if (PyModule_AddObject(m, "CAPI", s) < 0) return; if (PyModule_AddIntConstant(m, "GHOST", cPersistent_GHOST_STATE) < 0) return; if (PyModule_AddIntConstant(m, "UPTODATE", cPersistent_UPTODATE_STATE) < 0) return; if (PyModule_AddIntConstant(m, "CHANGED", cPersistent_CHANGED_STATE) < 0) return; if (PyModule_AddIntConstant(m, "STICKY", cPersistent_STICKY_STATE) < 0) return; py_simple_new = PyObject_GetAttrString(m, "simple_new"); if (!py_simple_new) return; copy_reg = PyImport_ImportModule("copy_reg"); if (!copy_reg) return; copy_reg_slotnames = PyObject_GetAttrString(copy_reg, "_slotnames"); if (!copy_reg_slotnames) { Py_DECREF(copy_reg); return; } __newobj__ = PyObject_GetAttrString(copy_reg, "__newobj__"); if (!__newobj__) { Py_DECREF(copy_reg); return; } if (!TimeStamp) { m = PyImport_ImportModule("persistent.TimeStamp"); if (!m) return; TimeStamp = PyObject_GetAttrString(m, "TimeStamp"); Py_DECREF(m); /* fall through to immediate return on error */ } } zope2.13-2.13.21/source/zope.container/include/persistent/cPersistence.h0000644000175000017500000000736012214017546025030 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2002 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #ifndef CPERSISTENCE_H #define CPERSISTENCE_H #include "Python.h" #include "ring.h" #define CACHE_HEAD \ PyObject_HEAD \ CPersistentRing ring_home; \ int non_ghost_count; struct ccobject_head_struct; typedef struct ccobject_head_struct PerCache; /* How big is a persistent object? 12 PyGC_Head is two pointers and an int 8 PyObject_HEAD is an int and a pointer 12 jar, oid, cache pointers 8 ring struct 8 serialno 4 state + extra (52) so far 4 dict ptr 4 weaklist ptr ------------------------- 64 only need 62, but obmalloc rounds up to multiple of eight Even a ghost requires 64 bytes. It's possible to make a persistent instance with slots and no dict, which changes the storage needed. */ #define cPersistent_HEAD \ PyObject_HEAD \ PyObject *jar; \ PyObject *oid; \ PerCache *cache; \ CPersistentRing ring; \ char serial[8]; \ signed char state; \ unsigned char reserved[3]; #define cPersistent_GHOST_STATE -1 #define cPersistent_UPTODATE_STATE 0 #define cPersistent_CHANGED_STATE 1 #define cPersistent_STICKY_STATE 2 typedef struct { cPersistent_HEAD } cPersistentObject; typedef void (*percachedelfunc)(PerCache *, PyObject *); typedef struct { PyTypeObject *pertype; getattrofunc getattro; setattrofunc setattro; int (*changed)(cPersistentObject*); void (*accessed)(cPersistentObject*); void (*ghostify)(cPersistentObject*); int (*setstate)(PyObject*); percachedelfunc percachedel; } cPersistenceCAPIstruct; #define cPersistenceType cPersistenceCAPI->pertype #ifndef DONT_USE_CPERSISTENCECAPI static cPersistenceCAPIstruct *cPersistenceCAPI; #endif #define cPersistanceModuleName "cPersistence" #define PER_TypeCheck(O) PyObject_TypeCheck((O), cPersistenceCAPI->pertype) #define PER_USE_OR_RETURN(O,R) {if((O)->state==cPersistent_GHOST_STATE && cPersistenceCAPI->setstate((PyObject*)(O)) < 0) return (R); else if ((O)->state==cPersistent_UPTODATE_STATE) (O)->state=cPersistent_STICKY_STATE;} #define PER_CHANGED(O) (cPersistenceCAPI->changed((cPersistentObject*)(O))) #define PER_GHOSTIFY(O) (cPersistenceCAPI->ghostify((cPersistentObject*)(O))) /* If the object is sticky, make it non-sticky, so that it can be ghostified. The value is not meaningful */ #define PER_ALLOW_DEACTIVATION(O) ((O)->state==cPersistent_STICKY_STATE && ((O)->state=cPersistent_UPTODATE_STATE)) #define PER_PREVENT_DEACTIVATION(O) ((O)->state==cPersistent_UPTODATE_STATE && ((O)->state=cPersistent_STICKY_STATE)) /* Make a persistent object usable from C by: - Making sure it is not a ghost - Making it sticky. IMPORTANT: If you call this and don't call PER_ALLOW_DEACTIVATION, your object will not be ghostified. PER_USE returns a 1 on success and 0 failure, where failure means error. */ #define PER_USE(O) \ (((O)->state != cPersistent_GHOST_STATE \ || (cPersistenceCAPI->setstate((PyObject*)(O)) >= 0)) \ ? (((O)->state==cPersistent_UPTODATE_STATE) \ ? ((O)->state=cPersistent_STICKY_STATE) : 1) : 0) #define PER_ACCESSED(O) (cPersistenceCAPI->accessed((cPersistentObject*)(O))) #endif zope2.13-2.13.21/source/zope.container/include/persistent/DEPENDENCIES.cfg0000644000175000017500000000012512214017546024527 0ustar arnauarnau# the following are needed by the tests transaction ZODB zope.interface zope.testing zope2.13-2.13.21/source/zope.container/include/persistent/interfaces.py0000644000175000017500000002447412214017546024732 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Persistence Interfaces $Id: interfaces.py 40664 2005-12-09 16:19:03Z tim_one $ """ from zope.interface import Interface from zope.interface import Attribute class IPersistent(Interface): """Python persistent interface A persistent object can be in one of several states: - Unsaved The object has been created but not saved in a data manager. In this state, the _p_changed attribute is non-None and false and the _p_jar attribute is None. - Saved The object has been saved and has not been changed since it was saved. In this state, the _p_changed attribute is non-None and false and the _p_jar attribute is set to a data manager. - Sticky This state is identical to the saved state except that the object cannot transition to the ghost state. This is a special state used by C methods of persistent objects to make sure that state is not unloaded in the middle of computation. In this state, the _p_changed attribute is non-None and false and the _p_jar attribute is set to a data manager. There is no Python API for detecting whether an object is in the sticky state. - Changed The object has been changed. In this state, the _p_changed attribute is true and the _p_jar attribute is set to a data manager. - Ghost the object is in memory but its state has not been loaded from the database (or its state has been unloaded). In this state, the object doesn't contain any application data. In this state, the _p_changed attribute is None, and the _p_jar attribute is set to the data manager from which the object was obtained. In all the above, _p_oid (the persistent object id) is set when _p_jar first gets set. The following state transitions are possible: - Unsaved -> Saved This transition occurs when an object is saved in the database. This usually happens when an unsaved object is added to (e.g. as an attribute or item of) a saved (or changed) object and the transaction is committed. - Saved -> Changed Sticky -> Changed Ghost -> Changed This transition occurs when someone sets an attribute or sets _p_changed to a true value on a saved, sticky or ghost object. When the transition occurs, the persistent object is required to call the register() method on its data manager, passing itself as the only argument. Prior to ZODB 3.6, setting _p_changed to a true value on a ghost object was ignored (the object remained a ghost, and getting its _p_changed attribute continued to return None). - Saved -> Sticky This transition occurs when C code marks the object as sticky to prevent its deactivation. - Saved -> Ghost This transition occurs when a saved object is deactivated or invalidated. See discussion below. - Sticky -> Saved This transition occurs when C code unmarks the object as sticky to allow its deactivation. - Changed -> Saved This transition occurs when a transaction is committed. After saving the state of a changed object during transaction commit, the data manager sets the object's _p_changed to a non-None false value. - Changed -> Ghost This transition occurs when a transaction is aborted. All changed objects are invalidated by the data manager by an abort. - Ghost -> Saved This transition occurs when an attribute or operation of a ghost is accessed and the object's state is loaded from the database. Note that there is a separate C API that is not included here. The C API requires a specific data layout and defines the sticky state. About Invalidation, Deactivation and the Sticky & Ghost States The sticky state is intended to be a short-lived state, to prevent an object's state from being discarded while we're in C routines. It is an error to invalidate an object in the sticky state. Deactivation is a request that an object discard its state (become a ghost). Deactivation is an optimization, and a request to deactivate may be ignored. There are two equivalent ways to request deactivation: - call _p_deactivate() - set _p_changed to None There are two ways to invalidate an object: call the _p_invalidate() method (preferred) or delete its _p_changed attribute. This cannot be ignored, and is used when semantics require invalidation. Normally, an invalidated object transitions to the ghost state. However, some objects cannot be ghosts. When these objects are invalidated, they immediately reload their state from their data manager, and are then in the saved state. """ _p_jar = Attribute( """The data manager for the object. The data manager implements the IPersistentDataManager interface. If there is no data manager, then this is None. """) _p_oid = Attribute( """The object id. It is up to the data manager to assign this. The special value None is reserved to indicate that an object id has not been assigned. Non-None object ids must be non-empty strings. The 8-byte string '\0'*8 (8 NUL bytes) is reserved to identify the database root object. """) _p_changed = Attribute( """The persistent state of the object. This is one of: None -- The object is a ghost. false but not None -- The object is saved (or has never been saved). true -- The object has been modified since it was last saved. The object state may be changed by assigning or deleting this attribute; however, assigning None is ignored if the object is not in the saved state, and may be ignored even if the object is in the saved state. At and after ZODB 3.6, setting _p_changed to a true value for a ghost object activates the object; prior to 3.6, setting _p_changed to a true value on a ghost object was ignored. Note that an object can transition to the changed state only if it has a data manager. When such a state change occurs, the 'register' method of the data manager must be called, passing the persistent object. Deleting this attribute forces invalidation independent of existing state, although it is an error if the sticky state is current. """) _p_serial = Attribute( """The object serial number. This member is used by the data manager to distiguish distinct revisions of a given persistent object. This is an 8-byte string (not Unicode). """) def __getstate__(): """Get the object data. The state should not include persistent attributes ("_p_name"). The result must be picklable. """ def __setstate__(state): """Set the object data. """ def _p_activate(): """Activate the object. Change the object to the saved state if it is a ghost. """ def _p_deactivate(): """Deactivate the object. Possibly change an object in the saved state to the ghost state. It may not be possible to make some persistent objects ghosts, and, for optimization reasons, the implementation may choose to keep an object in the saved state. """ def _p_invalidate(): """Invalidate the object. Invalidate the object. This causes any data to be thrown away, even if the object is in the changed state. The object is moved to the ghost state; further accesses will cause object data to be reloaded. """ class IPersistentNoReadConflicts(IPersistent): def _p_independent(): """Hook for subclasses to prevent read conflict errors. A specific persistent object type can define this method and have it return true if the data manager should ignore read conflicts for this object. """ # TODO: document conflict resolution. class IPersistentDataManager(Interface): """Provide services for managing persistent state. This interface is used by a persistent object to interact with its data manager in the context of a transaction. """ def setstate(object): """Load the state for the given object. The object should be in the ghost state. The object's state will be set and the object will end up in the saved state. The object must provide the IPersistent interface. """ def oldstate(obj, tid): """Return copy of 'obj' that was written by transaction 'tid'. The returned object does not have the typical metadata (_p_jar, _p_oid, _p_serial) set. I'm not sure how references to other peristent objects are handled. Parameters obj: a persistent object from this Connection. tid: id of a transaction that wrote an earlier revision. Raises KeyError if tid does not exist or if tid deleted a revision of obj. """ def register(object): """Register an IPersistent with the current transaction. This method must be called when the object transitions to the changed state. A subclass could override this method to customize the default policy of one transaction manager for each thread. """ # Maybe later: ## def mtime(object): ## """Return the modification time of the object. ## The modification time may not be known, in which case None ## is returned. If non-None, the return value is the kind of ## timestamp supplied by Python's time.time(). ## """ zope2.13-2.13.21/source/zope.container/include/persistent/__init__.py0000644000175000017500000000227312214017546024337 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Provide access to Persistent and PersistentMapping. $Id: __init__.py 25186 2004-06-02 15:07:33Z jim $ """ from cPersistence import Persistent, GHOST, UPTODATE, CHANGED, STICKY from cPickleCache import PickleCache from cPersistence import simple_new import copy_reg copy_reg.constructor(simple_new) # Make an interface declaration for Persistent, # if zope.interface is available. try: from zope.interface import classImplements except ImportError: pass else: from persistent.interfaces import IPersistent classImplements(Persistent, IPersistent) zope2.13-2.13.21/source/zope.container/include/persistent/README.txt0000644000175000017500000000134212214017546023720 0ustar arnauarnau=================== Persistence support =================== (This document is under construction. More basic documentation will eventually appear here.) Overriding `__getattr__`, `__getattribute__`, `__setattr__`, and `__delattr__` ------------------------------------------------------------------------------ Subclasses can override the attribute-management methods. For the `__getattr__` method, the behavior is like that for regular Python classes and for earlier versions of ZODB 3. For `__getattribute__`, __setattr__`, and `__delattr__`, it is necessary to call certain methods defined by `persistent.Persistent`. Detailed examples and documentation is provided in the test module, `persistent.tests.test_overriding_attrs`. zope2.13-2.13.21/source/zope.container/include/persistent/ring.h0000644000175000017500000000512012214017546023330 0ustar arnauarnau/***************************************************************************** Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /* Support routines for the doubly-linked list of cached objects. The cache stores a headed, doubly-linked, circular list of persistent objects, with space for the pointers allocated in the objects themselves. The cache stores the distinguished head of the list, which is not a valid persistent object. The other list members are non-ghost persistent objects, linked in LRU (least-recently used) order. The r_next pointers traverse the ring starting with the least recently used object. The r_prev pointers traverse the ring starting with the most recently used object. Obscure: While each object is pointed at twice by list pointers (once by its predecessor's r_next, again by its successor's r_prev), the refcount on the object is bumped only by 1. This leads to some possibly surprising sequences of incref and decref code. Note that since the refcount is bumped at least once, the list does hold a strong reference to each object in it. */ typedef struct CPersistentRing_struct { struct CPersistentRing_struct *r_prev; struct CPersistentRing_struct *r_next; } CPersistentRing; /* The list operations here take constant time independent of the * number of objects in the list: */ /* Add elt as the most recently used object. elt must not already be * in the list, although this isn't checked. */ void ring_add(CPersistentRing *ring, CPersistentRing *elt); /* Remove elt from the list. elt must already be in the list, although * this isn't checked. */ void ring_del(CPersistentRing *elt); /* elt must already be in the list, although this isn't checked. It's * unlinked from its current position, and relinked into the list as the * most recently used object (which is arguably the tail of the list * instead of the head -- but the name of this function could be argued * either way). This is equivalent to * * ring_del(elt); * ring_add(ring, elt); * * but may be a little quicker. */ void ring_move_to_head(CPersistentRing *ring, CPersistentRing *elt); zope2.13-2.13.21/source/zope.container/include/persistent/cPickleCache.c0000644000175000017500000007727512214017546024706 0ustar arnauarnau /***************************************************************************** Copyright (c) 2001, 2002 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /* Objects are stored under three different regimes: Regime 1: Persistent Classes Persistent Classes are part of ZClasses. They are stored in the self->data dictionary, and are never garbage collected. The klass_items() method returns a sequence of (oid,object) tuples for every Persistent Class, which should make it possible to implement garbage collection in Python if necessary. Regime 2: Ghost Objects There is no benefit to keeping a ghost object which has no external references, therefore a weak reference scheme is used to ensure that ghost objects are removed from memory as soon as possible, when the last external reference is lost. Ghost objects are stored in the self->data dictionary. Normally a dictionary keeps a strong reference on its values, however this reference count is 'stolen'. This weak reference scheme leaves a dangling reference, in the dictionary, when the last external reference is lost. To clean up this dangling reference the persistent object dealloc function calls self->cache->_oid_unreferenced(self->oid). The cache looks up the oid in the dictionary, ensures it points to an object whose reference count is zero, then removes it from the dictionary. Before removing the object from the dictionary it must temporarily resurrect the object in much the same way that class instances are resurrected before their __del__ is called. Since ghost objects are stored under a different regime to non-ghost objects, an extra ghostify function in cPersistenceAPI replaces self->state=GHOST_STATE assignments that were common in other persistent classes (such as BTrees). Regime 3: Non-Ghost Objects Non-ghost objects are stored in two data structures: the dictionary mapping oids to objects and a doubly-linked list that encodes the order in which the objects were accessed. The dictionary reference is borrowed, as it is for ghosts. The list reference is a new reference; the list stores recently used objects, even if they are otherwise unreferenced, to avoid loading the object from the database again. The doubly-link-list nodes contain next and previous pointers linking together the cache and all non-ghost persistent objects. The node embedded in the cache is the home position. On every attribute access a non-ghost object will relink itself just behind the home position in the ring. Objects accessed least recently will eventually find themselves positioned after the home position. Occasionally other nodes are temporarily inserted in the ring as position markers. The cache contains a ring_lock flag which must be set and unset before and after doing so. Only if the flag is unset can the cache assume that all nodes are either his own home node, or nodes from persistent objects. This assumption is useful during the garbage collection process. The number of non-ghost objects is counted in self->non_ghost_count. The garbage collection process consists of traversing the ring, and deactivating (that is, turning into a ghost) every object until self->non_ghost_count is down to the target size, or until it reaches the home position again. Note that objects in the sticky or changed states are still kept in the ring, however they can not be deactivated. The garbage collection process must skip such objects, rather than deactivating them. */ static char cPickleCache_doc_string[] = "Defines the PickleCache used by ZODB Connection objects.\n" "\n" "$Id: cPickleCache.c 29896 2005-04-07 04:48:06Z tim_one $\n"; #define DONT_USE_CPERSISTENCECAPI #include "cPersistence.h" #include "structmember.h" #include #include #undef Py_FindMethod /* Python string objects to speed lookups; set by module init. */ static PyObject *py__p_changed; static PyObject *py__p_deactivate; static PyObject *py__p_jar; static PyObject *py__p_oid; static cPersistenceCAPIstruct *capi; /* This object is the pickle cache. The CACHE_HEAD macro guarantees that layout of this struct is the same as the start of ccobject_head in cPersistence.c */ typedef struct { CACHE_HEAD int klass_count; /* count of persistent classes */ PyObject *data; /* oid -> object dict */ PyObject *jar; /* Connection object */ int cache_size; /* target number of items in cache */ /* Most of the time the ring contains only: * many nodes corresponding to persistent objects * one 'home' node from the cache. In some cases it is handy to temporarily add other types of node into the ring as placeholders. 'ring_lock' is a boolean indicating that someone has already done this. Currently this is only used by the garbage collection code. */ int ring_lock; /* 'cache_drain_resistance' controls how quickly the cache size will drop when it is smaller than the configured size. A value of zero means it will not drop below the configured size (suitable for most caches). Otherwise, it will remove cache_non_ghost_count/cache_drain_resistance items from the cache every time (suitable for rarely used caches, such as those associated with Zope versions. */ int cache_drain_resistance; } ccobject; static int cc_ass_sub(ccobject *self, PyObject *key, PyObject *v); /* ---------------------------------------------------------------- */ #define OBJECT_FROM_RING(SELF, HERE) \ ((cPersistentObject *)(((char *)here) - offsetof(cPersistentObject, ring))) /* Insert self into the ring, following after. */ static void insert_after(CPersistentRing *self, CPersistentRing *after) { assert(self != NULL); assert(after != NULL); self->r_prev = after; self->r_next = after->r_next; after->r_next->r_prev = self; after->r_next = self; } /* Remove self from the ring. */ static void unlink_from_ring(CPersistentRing *self) { assert(self != NULL); self->r_prev->r_next = self->r_next; self->r_next->r_prev = self->r_prev; } static int scan_gc_items(ccobject *self, int target) { /* This function must only be called with the ring lock held, because it places non-object placeholders in the ring. */ cPersistentObject *object; CPersistentRing *here; CPersistentRing before_original_home; int result = -1; /* guilty until proved innocent */ /* Scan the ring, from least to most recently used, deactivating * up-to-date objects, until we either find the ring_home again or * or we've ghosted enough objects to reach the target size. * Tricky: __getattr__ and __del__ methods can do anything, and in * particular if we ghostify an object with a __del__ method, that method * can load the object again, putting it back into the MRU part of the * ring. Waiting to find ring_home again can thus cause an infinite * loop (Collector #1208). So before_original_home records the MRU * position we start with, and we stop the scan when we reach that. */ insert_after(&before_original_home, self->ring_home.r_prev); here = self->ring_home.r_next; /* least recently used object */ while (here != &before_original_home && self->non_ghost_count > target) { assert(self->ring_lock); assert(here != &self->ring_home); /* At this point we know that the ring only contains nodes from persistent objects, plus our own home node. We know this because the ring lock is held. We can safely assume the current ring node is a persistent object now we know it is not the home */ object = OBJECT_FROM_RING(self, here); if (object->state == cPersistent_UPTODATE_STATE) { CPersistentRing placeholder; PyObject *method; PyObject *temp; int error_occurred = 0; /* deactivate it. This is the main memory saver. */ /* Add a placeholder, a dummy node in the ring. We need to do this to mark our position in the ring. It is possible that the PyObject_GetAttr() call below will invoke a __getattr__() hook in Python. Also possible that deactivation will lead to a __del__ method call. So another thread might run, and mutate the ring as a side effect of object accesses. There's no predicting then where in the ring here->next will point after that. The placeholder won't move as a side effect of calling Python code. */ insert_after(&placeholder, here); method = PyObject_GetAttr((PyObject *)object, py__p_deactivate); if (method == NULL) error_occurred = 1; else { temp = PyObject_CallObject(method, NULL); Py_DECREF(method); if (temp == NULL) error_occurred = 1; } here = placeholder.r_next; unlink_from_ring(&placeholder); if (error_occurred) goto Done; } else here = here->r_next; } result = 0; Done: unlink_from_ring(&before_original_home); return result; } static PyObject * lockgc(ccobject *self, int target_size) { /* This is thread-safe because of the GIL, and there's nothing * in between checking the ring_lock and acquiring it that calls back * into Python. */ if (self->ring_lock) { Py_INCREF(Py_None); return Py_None; } self->ring_lock = 1; if (scan_gc_items(self, target_size) < 0) { self->ring_lock = 0; return NULL; } self->ring_lock = 0; Py_INCREF(Py_None); return Py_None; } static PyObject * cc_incrgc(ccobject *self, PyObject *args) { int obsolete_arg = -999; int starting_size = self->non_ghost_count; int target_size = self->cache_size; if (self->cache_drain_resistance >= 1) { /* This cache will gradually drain down to a small size. Check a (small) number of objects proportional to the current size */ int target_size_2 = (starting_size - 1 - starting_size / self->cache_drain_resistance); if (target_size_2 < target_size) target_size = target_size_2; } if (!PyArg_ParseTuple(args, "|i:incrgc", &obsolete_arg)) return NULL; if (obsolete_arg != -999 && (PyErr_Warn(PyExc_DeprecationWarning, "No argument expected") < 0)) return NULL; return lockgc(self, target_size); } static PyObject * cc_full_sweep(ccobject *self, PyObject *args) { int dt = -999; /* TODO: This should be deprecated; */ if (!PyArg_ParseTuple(args, "|i:full_sweep", &dt)) return NULL; if (dt == -999) return lockgc(self, 0); else return cc_incrgc(self, args); } static PyObject * cc_minimize(ccobject *self, PyObject *args) { int ignored = -999; if (!PyArg_ParseTuple(args, "|i:minimize", &ignored)) return NULL; if (ignored != -999 && (PyErr_Warn(PyExc_DeprecationWarning, "No argument expected") < 0)) return NULL; return lockgc(self, 0); } static int _invalidate(ccobject *self, PyObject *key) { static PyObject *_p_invalidate = NULL; PyObject *meth, *v; v = PyDict_GetItem(self->data, key); if (v == NULL) return 0; if (_p_invalidate == NULL) { _p_invalidate = PyString_InternFromString("_p_invalidate"); if (_p_invalidate == NULL) { /* It doesn't make any sense to ignore this error, but the caller ignores all errors. TODO: and why does it do that? This should be fixed */ return -1; } } if (v->ob_refcnt <= 1 && PyType_Check(v)) { /* This looks wrong, but it isn't. We use strong references to types because they don't have the ring members. The result is that we *never* remove classes unless they are modified. We can fix this by using wekrefs uniformly. */ self->klass_count--; return PyDict_DelItem(self->data, key); } meth = PyObject_GetAttr(v, _p_invalidate); if (meth == NULL) return -1; v = PyObject_CallObject(meth, NULL); Py_DECREF(meth); return v == NULL ? -1 : 0; } static PyObject * cc_invalidate(ccobject *self, PyObject *inv) { PyObject *key, *v; int i = 0; if (PyDict_Check(inv)) { while (PyDict_Next(inv, &i, &key, &v)) { if (_invalidate(self, key) < 0) return NULL; } PyDict_Clear(inv); } else { if (PyString_Check(inv)) { if (_invalidate(self, inv) < 0) return NULL; } else { int l, r; l = PyObject_Length(inv); if (l < 0) return NULL; for (i=l; --i >= 0; ) { key = PySequence_GetItem(inv, i); if (!key) return NULL; r = _invalidate(self, key); Py_DECREF(key); if (r < 0) return NULL; } /* Dubious: modifying the input may be an unexpected side effect. */ PySequence_DelSlice(inv, 0, l); } } Py_INCREF(Py_None); return Py_None; } static PyObject * cc_get(ccobject *self, PyObject *args) { PyObject *r, *key, *d = NULL; if (!PyArg_ParseTuple(args, "O|O:get", &key, &d)) return NULL; r = PyDict_GetItem(self->data, key); if (!r) { if (d) r = d; else r = Py_None; } Py_INCREF(r); return r; } static PyObject * cc_items(ccobject *self) { return PyObject_CallMethod(self->data, "items", ""); } static PyObject * cc_klass_items(ccobject *self) { PyObject *l,*k,*v; int p = 0; l = PyList_New(0); if (l == NULL) return NULL; while (PyDict_Next(self->data, &p, &k, &v)) { if(PyType_Check(v)) { v = Py_BuildValue("OO", k, v); if (v == NULL) { Py_DECREF(l); return NULL; } if (PyList_Append(l, v) < 0) { Py_DECREF(v); Py_DECREF(l); return NULL; } Py_DECREF(v); } } return l; } static PyObject * cc_debug_info(ccobject *self) { PyObject *l,*k,*v; int p = 0; l = PyList_New(0); if (l == NULL) return NULL; while (PyDict_Next(self->data, &p, &k, &v)) { if (v->ob_refcnt <= 0) v = Py_BuildValue("Oi", k, v->ob_refcnt); else if (! PyType_Check(v) && (v->ob_type->tp_basicsize >= sizeof(cPersistentObject)) ) v = Py_BuildValue("Oisi", k, v->ob_refcnt, v->ob_type->tp_name, ((cPersistentObject*)v)->state); else v = Py_BuildValue("Ois", k, v->ob_refcnt, v->ob_type->tp_name); if (v == NULL) goto err; if (PyList_Append(l, v) < 0) goto err; } return l; err: Py_DECREF(l); return NULL; } static PyObject * cc_lru_items(ccobject *self) { PyObject *l; CPersistentRing *here; if (self->ring_lock) { /* When the ring lock is held, we have no way of know which ring nodes belong to persistent objects, and which a placeholders. */ PyErr_SetString(PyExc_ValueError, ".lru_items() is unavailable during garbage collection"); return NULL; } l = PyList_New(0); if (l == NULL) return NULL; here = self->ring_home.r_next; while (here != &self->ring_home) { PyObject *v; cPersistentObject *object = OBJECT_FROM_RING(self, here); if (object == NULL) { Py_DECREF(l); return NULL; } v = Py_BuildValue("OO", object->oid, object); if (v == NULL) { Py_DECREF(l); return NULL; } if (PyList_Append(l, v) < 0) { Py_DECREF(v); Py_DECREF(l); return NULL; } Py_DECREF(v); here = here->r_next; } return l; } static void cc_oid_unreferenced(ccobject *self, PyObject *oid) { /* This is called by the persistent object deallocation function when the reference count on a persistent object reaches zero. We need to fix up our dictionary; its reference is now dangling because we stole its reference count. Be careful to not release the global interpreter lock until this is complete. */ PyObject *v; /* If the cache has been cleared by GC, data will be NULL. */ if (!self->data) return; v = PyDict_GetItem(self->data, oid); assert(v); assert(v->ob_refcnt == 0); /* Need to be very hairy here because a dictionary is about to decref an already deleted object. */ #ifdef Py_TRACE_REFS /* This is called from the deallocation function after the interpreter has untracked the reference. Track it again. */ _Py_NewReference(v); /* Don't increment total refcount as a result of the shenanigans played in this function. The _Py_NewReference() call above creates artificial references to v. */ _Py_RefTotal--; assert(v->ob_type); #else Py_INCREF(v); #endif assert(v->ob_refcnt == 1); /* Incremement the refcount again, because delitem is going to DECREF it. If it's refcount reached zero again, we'd call back to the dealloc function that called us. */ Py_INCREF(v); /* TODO: Should we call _Py_ForgetReference() on error exit? */ if (PyDict_DelItem(self->data, oid) < 0) return; Py_DECREF((ccobject *)((cPersistentObject *)v)->cache); ((cPersistentObject *)v)->cache = NULL; assert(v->ob_refcnt == 1); /* Undo the temporary resurrection. Don't DECREF the object, because this function is called from the object's dealloc function. If the refcnt reaches zero, it will all be invoked recursively. */ _Py_ForgetReference(v); } static PyObject * cc_ringlen(ccobject *self) { CPersistentRing *here; int c = 0; for (here = self->ring_home.r_next; here != &self->ring_home; here = here->r_next) c++; return PyInt_FromLong(c); } static struct PyMethodDef cc_methods[] = { {"items", (PyCFunction)cc_items, METH_NOARGS, "Return list of oid, object pairs for all items in cache."}, {"lru_items", (PyCFunction)cc_lru_items, METH_NOARGS, "List (oid, object) pairs from the lru list, as 2-tuples."}, {"klass_items", (PyCFunction)cc_klass_items, METH_NOARGS, "List (oid, object) pairs of cached persistent classes."}, {"full_sweep", (PyCFunction)cc_full_sweep, METH_VARARGS, "full_sweep() -- Perform a full sweep of the cache."}, {"minimize", (PyCFunction)cc_minimize, METH_VARARGS, "minimize([ignored]) -- Remove as many objects as possible\n\n" "Ghostify all objects that are not modified. Takes an optional\n" "argument, but ignores it."}, {"incrgc", (PyCFunction)cc_incrgc, METH_VARARGS, "incrgc() -- Perform incremental garbage collection\n\n" "This method had been depricated!" "Some other implementations support an optional parameter 'n' which\n" "indicates a repetition count; this value is ignored."}, {"invalidate", (PyCFunction)cc_invalidate, METH_O, "invalidate(oids) -- invalidate one, many, or all ids"}, {"get", (PyCFunction)cc_get, METH_VARARGS, "get(key [, default]) -- get an item, or a default"}, {"ringlen", (PyCFunction)cc_ringlen, METH_NOARGS, "ringlen() -- Returns number of non-ghost items in cache."}, {"debug_info", (PyCFunction)cc_debug_info, METH_NOARGS, "debug_info() -- Returns debugging data about objects in the cache."}, {NULL, NULL} /* sentinel */ }; static int cc_init(ccobject *self, PyObject *args, PyObject *kwds) { int cache_size = 100; PyObject *jar; if (!PyArg_ParseTuple(args, "O|i", &jar, &cache_size)) return -1; self->jar = NULL; self->data = PyDict_New(); if (self->data == NULL) { Py_DECREF(self); return -1; } /* Untrack the dict mapping oids to objects. The dict contains uncounted references to ghost objects, so it isn't safe for GC to visit it. If GC finds an object with more referents that refcounts, it will die with an assertion failure. When the cache participates in GC, it will need to traverse the objects in the doubly-linked list, which will account for all the non-ghost objects. */ PyObject_GC_UnTrack((void *)self->data); self->jar = jar; Py_INCREF(jar); self->cache_size = cache_size; self->non_ghost_count = 0; self->klass_count = 0; self->cache_drain_resistance = 0; self->ring_lock = 0; self->ring_home.r_next = &self->ring_home; self->ring_home.r_prev = &self->ring_home; return 0; } static void cc_dealloc(ccobject *self) { Py_XDECREF(self->data); Py_XDECREF(self->jar); PyObject_GC_Del(self); } static int cc_clear(ccobject *self) { int pos = 0; PyObject *k, *v; /* Clearing the cache is delicate. A non-ghost object will show up in the ring and in the dict. If we deallocating the dict before clearing the ring, the GC will decref each object in the dict. Since the dict references are uncounted, this will lead to objects having negative refcounts. Freeing the non-ghost objects should eliminate many objects from the cache, but there may still be ghost objects left. It's not safe to decref the dict until it's empty, so we need to manually clear those out of the dict, too. We accomplish that by replacing all the ghost objects with None. */ /* We don't need to lock the ring, because the cache is unreachable. It should be impossible for anyone to be modifying the cache. */ assert(! self->ring_lock); while (self->ring_home.r_next != &self->ring_home) { CPersistentRing *here = self->ring_home.r_next; cPersistentObject *o = OBJECT_FROM_RING(self, here); if (o->cache) { Py_INCREF(o); /* account for uncounted reference */ if (PyDict_DelItem(self->data, o->oid) < 0) return -1; } o->cache = NULL; Py_DECREF(self); self->ring_home.r_next = here->r_next; o->ring.r_prev = NULL; o->ring.r_next = NULL; Py_DECREF(o); here = here->r_next; } Py_XDECREF(self->jar); while (PyDict_Next(self->data, &pos, &k, &v)) { Py_INCREF(v); if (PyDict_SetItem(self->data, k, Py_None) < 0) return -1; } Py_XDECREF(self->data); self->data = NULL; self->jar = NULL; return 0; } static int cc_traverse(ccobject *self, visitproc visit, void *arg) { int err; CPersistentRing *here; /* If we're in the midst of cleaning up old objects, the ring contains * assorted junk we must not pass on to the visit() callback. This * should be rare (our cleanup code would need to have called back * into Python, which in turn triggered Python's gc). When it happens, * simply don't chase any pointers. The cache will appear to be a * source of external references then, and at worst we miss cleaning * up a dead cycle until the next time Python's gc runs. */ if (self->ring_lock) return 0; #define VISIT(SLOT) \ if (SLOT) { \ err = visit((PyObject *)(SLOT), arg); \ if (err) \ return err; \ } VISIT(self->jar); here = self->ring_home.r_next; /* It is possible that an object is traversed after it is cleared. In that case, there is no ring. */ if (!here) return 0; while (here != &self->ring_home) { cPersistentObject *o = OBJECT_FROM_RING(self, here); VISIT(o); here = here->r_next; } #undef VISIT return 0; } static int cc_length(ccobject *self) { return PyObject_Length(self->data); } static PyObject * cc_subscript(ccobject *self, PyObject *key) { PyObject *r; r = PyDict_GetItem(self->data, key); if (r == NULL) { PyErr_SetObject(PyExc_KeyError, key); return NULL; } Py_INCREF(r); return r; } static int cc_add_item(ccobject *self, PyObject *key, PyObject *v) { int result; PyObject *oid, *object_again, *jar; cPersistentObject *p; /* Sanity check the value given to make sure it is allowed in the cache */ if (PyType_Check(v)) { /* Its a persistent class, such as a ZClass. Thats ok. */ } else if (v->ob_type->tp_basicsize < sizeof(cPersistentObject)) { /* If it's not an instance of a persistent class, (ie Python classes that derive from persistent.Persistent, BTrees, etc), report an error. TODO: checking sizeof() seems a poor test. */ PyErr_SetString(PyExc_TypeError, "Cache values must be persistent objects."); return -1; } /* Can't access v->oid directly because the object might be a * persistent class. */ oid = PyObject_GetAttr(v, py__p_oid); if (oid == NULL) return -1; if (! PyString_Check(oid)) { PyErr_Format(PyExc_TypeError, "Cached object oid must be a string, not a %s", oid->ob_type->tp_name); return -1; } /* we know they are both strings. * now check if they are the same string. */ result = PyObject_Compare(key, oid); if (PyErr_Occurred()) { Py_DECREF(oid); return -1; } Py_DECREF(oid); if (result) { PyErr_SetString(PyExc_ValueError, "Cache key does not match oid"); return -1; } /* useful sanity check, but not strictly an invariant of this class */ jar = PyObject_GetAttr(v, py__p_jar); if (jar == NULL) return -1; if (jar==Py_None) { Py_DECREF(jar); PyErr_SetString(PyExc_ValueError, "Cached object jar missing"); return -1; } Py_DECREF(jar); object_again = PyDict_GetItem(self->data, key); if (object_again) { if (object_again != v) { PyErr_SetString(PyExc_ValueError, "A different object already has the same oid"); return -1; } else { /* re-register under the same oid - no work needed */ return 0; } } if (PyType_Check(v)) { if (PyDict_SetItem(self->data, key, v) < 0) return -1; self->klass_count++; return 0; } else { PerCache *cache = ((cPersistentObject *)v)->cache; if (cache) { if (cache != (PerCache *)self) /* This object is already in a different cache. */ PyErr_SetString(PyExc_ValueError, "Cache values may only be in one cache."); return -1; } /* else: This object is already one of ours, which is ok. It would be very strange if someone was trying to register the same object under a different key. */ } if (PyDict_SetItem(self->data, key, v) < 0) return -1; /* the dict should have a borrowed reference */ Py_DECREF(v); p = (cPersistentObject *)v; Py_INCREF(self); p->cache = (PerCache *)self; if (p->state >= 0) { /* insert this non-ghost object into the ring just behind the home position. */ self->non_ghost_count++; ring_add(&self->ring_home, &p->ring); /* this list should have a new reference to the object */ Py_INCREF(v); } return 0; } static int cc_del_item(ccobject *self, PyObject *key) { PyObject *v; cPersistentObject *p; /* unlink this item from the ring */ v = PyDict_GetItem(self->data, key); if (v == NULL) { PyErr_SetObject(PyExc_KeyError, key); return -1; } if (PyType_Check(v)) { self->klass_count--; } else { p = (cPersistentObject *)v; if (p->state >= 0) { self->non_ghost_count--; ring_del(&p->ring); /* The DelItem below will account for the reference held by the list. */ } else { /* This is a ghost object, so we haven't kept a reference count on it. For it have stayed alive this long someone else must be keeping a reference to it. Therefore we need to temporarily give it back a reference count before calling DelItem below */ Py_INCREF(v); } Py_DECREF((PyObject *)p->cache); p->cache = NULL; } if (PyDict_DelItem(self->data, key) < 0) { PyErr_SetString(PyExc_RuntimeError, "unexpectedly couldn't remove key in cc_ass_sub"); return -1; } return 0; } static int cc_ass_sub(ccobject *self, PyObject *key, PyObject *v) { if (!PyString_Check(key)) { PyErr_Format(PyExc_TypeError, "cPickleCache key must be a string, not a %s", key->ob_type->tp_name); return -1; } if (v) return cc_add_item(self, key, v); else return cc_del_item(self, key); } static PyMappingMethods cc_as_mapping = { (inquiry)cc_length, /*mp_length*/ (binaryfunc)cc_subscript, /*mp_subscript*/ (objobjargproc)cc_ass_sub, /*mp_ass_subscript*/ }; static PyObject * cc_cache_data(ccobject *self, void *context) { return PyDict_Copy(self->data); } static PyGetSetDef cc_getsets[] = { {"cache_data", (getter)cc_cache_data}, {NULL} }; static PyMemberDef cc_members[] = { {"cache_size", T_INT, offsetof(ccobject, cache_size)}, {"cache_drain_resistance", T_INT, offsetof(ccobject, cache_drain_resistance)}, {"cache_non_ghost_count", T_INT, offsetof(ccobject, non_ghost_count), RO}, {"cache_klass_count", T_INT, offsetof(ccobject, klass_count), RO}, {NULL} }; /* This module is compiled as a shared library. Some compilers don't allow addresses of Python objects defined in other libraries to be used in static initializers here. The DEFERRED_ADDRESS macro is used to tag the slots where such addresses appear; the module init function must fill in the tagged slots at runtime. The argument is for documentation -- the macro ignores it. */ #define DEFERRED_ADDRESS(ADDR) 0 static PyTypeObject Cctype = { PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type)) 0, /* ob_size */ "persistent.PickleCache", /* tp_name */ sizeof(ccobject), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)cc_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ &cc_as_mapping, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ (traverseproc)cc_traverse, /* tp_traverse */ (inquiry)cc_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ cc_methods, /* tp_methods */ cc_members, /* tp_members */ cc_getsets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)cc_init, /* tp_init */ }; void initcPickleCache(void) { PyObject *m; Cctype.ob_type = &PyType_Type; Cctype.tp_new = &PyType_GenericNew; if (PyType_Ready(&Cctype) < 0) { return; } m = Py_InitModule3("cPickleCache", NULL, cPickleCache_doc_string); capi = (cPersistenceCAPIstruct *)PyCObject_Import( "persistent.cPersistence", "CAPI"); if (!capi) return; capi->percachedel = (percachedelfunc)cc_oid_unreferenced; py__p_changed = PyString_InternFromString("_p_changed"); if (!py__p_changed) return; py__p_deactivate = PyString_InternFromString("_p_deactivate"); if (!py__p_deactivate) return; py__p_jar = PyString_InternFromString("_p_jar"); if (!py__p_jar) return; py__p_oid = PyString_InternFromString("_p_oid"); if (!py__p_oid) return; if (PyModule_AddStringConstant(m, "cache_variant", "stiff/c") < 0) return; /* This leaks a reference to Cctype, but it doesn't matter. */ if (PyModule_AddObject(m, "PickleCache", (PyObject *)&Cctype) < 0) return; } zope2.13-2.13.21/source/zope.container/include/persistent/TimeStamp.c0000644000175000017500000002446212214017546024301 0ustar arnauarnau/***************************************************************************** Copyright (c) 2001, 2004 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #include "Python.h" #include PyObject *TimeStamp_FromDate(int, int, int, int, int, double); PyObject *TimeStamp_FromString(const char *); static char TimeStampModule_doc[] = "A 64-bit TimeStamp used as a ZODB serial number.\n" "\n" "$Id: TimeStamp.c 41599 2006-02-11 21:33:49Z tseaver $\n"; typedef struct { PyObject_HEAD unsigned char data[8]; } TimeStamp; /* The first dimension of the arrays below is non-leapyear / leapyear */ static char month_len[2][12]={ {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} }; static short joff[2][12] = { {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335} }; static double gmoff=0; /* TODO: May be better (faster) to store in a file static. */ #define SCONV ((double)60) / ((double)(1<<16)) / ((double)(1<<16)) static int leap(int year) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } static int days_in_month(int year, int month) { return month_len[leap(year)][month]; } static double TimeStamp_yad(int y) { double d, s; y -= 1900; d = (y - 1) * 365; if (y > 0) { s = 1.0; y -= 1; } else { s = -1.0; y = -y; } return d + s * (y / 4 - y / 100 + (y + 300) / 400); } static double TimeStamp_abst(int y, int mo, int d, int m, int s) { return (TimeStamp_yad(y) + joff[leap(y)][mo] + d) * 86400 + m * 60 + s; } static int TimeStamp_init_gmoff(void) { struct tm *t; time_t z=0; t = gmtime(&z); if (t == NULL) { PyErr_SetString(PyExc_SystemError, "gmtime failed"); return -1; } gmoff = TimeStamp_abst(t->tm_year+1900, t->tm_mon, t->tm_mday - 1, t->tm_hour * 60 + t->tm_min, t->tm_sec); return 0; } static void TimeStamp_dealloc(TimeStamp *ts) { PyObject_Del(ts); } static int TimeStamp_compare(TimeStamp *v, TimeStamp *w) { int cmp = memcmp(v->data, w->data, 8); if (cmp < 0) return -1; if (cmp > 0) return 1; return 0; } static long TimeStamp_hash(TimeStamp *self) { register unsigned char *p = (unsigned char *)self->data; register int len = 8; register long x = *p << 7; while (--len >= 0) x = (1000003*x) ^ *p++; x ^= 8; if (x == -1) x = -2; return x; } typedef struct { /* TODO: reverse-engineer what's in these things and comment them */ int y; int m; int d; int mi; } TimeStampParts; static void TimeStamp_unpack(TimeStamp *self, TimeStampParts *p) { unsigned long v; v = (self->data[0] * 16777216 + self->data[1] * 65536 + self->data[2] * 256 + self->data[3]); p->y = v / 535680 + 1900; p->m = (v % 535680) / 44640 + 1; p->d = (v % 44640) / 1440 + 1; p->mi = v % 1440; } static double TimeStamp_sec(TimeStamp *self) { unsigned int v; v = (self->data[4] * 16777216 + self->data[5] * 65536 + self->data[6] * 256 + self->data[7]); return SCONV * v; } static PyObject * TimeStamp_year(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); return PyInt_FromLong(p.y); } static PyObject * TimeStamp_month(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); return PyInt_FromLong(p.m); } static PyObject * TimeStamp_day(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); return PyInt_FromLong(p.d); } static PyObject * TimeStamp_hour(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); return PyInt_FromLong(p.mi / 60); } static PyObject * TimeStamp_minute(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); return PyInt_FromLong(p.mi % 60); } static PyObject * TimeStamp_second(TimeStamp *self) { return PyFloat_FromDouble(TimeStamp_sec(self)); } static PyObject * TimeStamp_timeTime(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); return PyFloat_FromDouble(TimeStamp_abst(p.y, p.m - 1, p.d - 1, p.mi, 0) + TimeStamp_sec(self) - gmoff); } static PyObject * TimeStamp_raw(TimeStamp *self) { return PyString_FromStringAndSize((const char*)self->data, 8); } static PyObject * TimeStamp_str(TimeStamp *self) { char buf[128]; TimeStampParts p; int len; TimeStamp_unpack(self, &p); len =sprintf(buf, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%09.6f", p.y, p.m, p.d, p.mi / 60, p.mi % 60, TimeStamp_sec(self)); return PyString_FromStringAndSize(buf, len); } static PyObject * TimeStamp_laterThan(TimeStamp *self, PyObject *obj) { TimeStamp *o = NULL; TimeStampParts p; unsigned char new[8]; int i; if (obj->ob_type != self->ob_type) { PyErr_SetString(PyExc_TypeError, "expected TimeStamp object"); return NULL; } o = (TimeStamp *)obj; if (memcmp(self->data, o->data, 8) > 0) { Py_INCREF(self); return (PyObject *)self; } memcpy(new, o->data, 8); for (i = 7; i > 3; i--) { if (new[i] == 255) new[i] = 0; else { new[i]++; return TimeStamp_FromString((const char*)new); } } /* All but the first two bytes are the same. Need to increment the year, month, and day explicitly. */ TimeStamp_unpack(o, &p); if (p.mi >= 1439) { p.mi = 0; if (p.d == month_len[leap(p.y)][p.m - 1]) { p.d = 1; if (p.m == 12) { p.m = 1; p.y++; } else p.m++; } else p.d++; } else p.mi++; return TimeStamp_FromDate(p.y, p.m, p.d, p.mi / 60, p.mi % 60, 0); } static struct PyMethodDef TimeStamp_methods[] = { {"year", (PyCFunction)TimeStamp_year, METH_NOARGS}, {"minute", (PyCFunction)TimeStamp_minute, METH_NOARGS}, {"month", (PyCFunction)TimeStamp_month, METH_NOARGS}, {"day", (PyCFunction)TimeStamp_day, METH_NOARGS}, {"hour", (PyCFunction)TimeStamp_hour, METH_NOARGS}, {"second", (PyCFunction)TimeStamp_second, METH_NOARGS}, {"timeTime",(PyCFunction)TimeStamp_timeTime, METH_NOARGS}, {"laterThan", (PyCFunction)TimeStamp_laterThan, METH_O}, {"raw", (PyCFunction)TimeStamp_raw, METH_NOARGS}, {NULL, NULL}, }; static PyTypeObject TimeStamp_type = { PyObject_HEAD_INIT(NULL) 0, "persistent.TimeStamp", sizeof(TimeStamp), 0, (destructor)TimeStamp_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ (cmpfunc)TimeStamp_compare, /* tp_compare */ (reprfunc)TimeStamp_raw, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)TimeStamp_hash, /* tp_hash */ 0, /* tp_call */ (reprfunc)TimeStamp_str, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ TimeStamp_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ }; PyObject * TimeStamp_FromString(const char *buf) { /* buf must be exactly 8 characters */ TimeStamp *ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type); memcpy(ts->data, buf, 8); return (PyObject *)ts; } #define CHECK_RANGE(VAR, LO, HI) if ((VAR) < (LO) || (VAR) > (HI)) { \ return PyErr_Format(PyExc_ValueError, \ # VAR " must be between %d and %d: %d", \ (LO), (HI), (VAR)); \ } PyObject * TimeStamp_FromDate(int year, int month, int day, int hour, int min, double sec) { TimeStamp *ts = NULL; int d; unsigned int v; if (year < 1900) return PyErr_Format(PyExc_ValueError, "year must be greater than 1900: %d", year); CHECK_RANGE(month, 1, 12); d = days_in_month(year, month - 1); if (day < 1 || day > d) return PyErr_Format(PyExc_ValueError, "day must be between 1 and %d: %d", d, day); CHECK_RANGE(hour, 0, 23); CHECK_RANGE(min, 0, 59); /* Seconds are allowed to be anything, so chill If we did want to be pickly, 60 would be a better choice. if (sec < 0 || sec > 59) return PyErr_Format(PyExc_ValueError, "second must be between 0 and 59: %f", sec); */ ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type); v = (((year - 1900) * 12 + month - 1) * 31 + day - 1); v = (v * 24 + hour) * 60 + min; ts->data[0] = v / 16777216; ts->data[1] = (v % 16777216) / 65536; ts->data[2] = (v % 65536) / 256; ts->data[3] = v % 256; sec /= SCONV; v = (unsigned int)sec; ts->data[4] = v / 16777216; ts->data[5] = (v % 16777216) / 65536; ts->data[6] = (v % 65536) / 256; ts->data[7] = v % 256; return (PyObject *)ts; } PyObject * TimeStamp_TimeStamp(PyObject *obj, PyObject *args) { char *buf = NULL; int len = 0, y, mo, d, h = 0, m = 0; double sec = 0; if (PyArg_ParseTuple(args, "s#:TimeStamp", &buf, &len)) { if (len != 8) { PyErr_SetString(PyExc_ValueError, "8-character string expected"); return NULL; } return TimeStamp_FromString(buf); } PyErr_Clear(); if (!PyArg_ParseTuple(args, "iii|iid", &y, &mo, &d, &h, &m, &sec)) return NULL; return TimeStamp_FromDate(y, mo, d, h, m, sec); } static PyMethodDef TimeStampModule_functions[] = { {"TimeStamp", TimeStamp_TimeStamp, METH_VARARGS}, {NULL, NULL}, }; void initTimeStamp(void) { PyObject *m; if (TimeStamp_init_gmoff() < 0) return; m = Py_InitModule4("TimeStamp", TimeStampModule_functions, TimeStampModule_doc, NULL, PYTHON_API_VERSION); if (m == NULL) return; TimeStamp_type.ob_type = &PyType_Type; TimeStamp_type.tp_getattro = PyObject_GenericGetAttr; } zope2.13-2.13.21/source/zope.container/include/persistent/wref.py0000644000175000017500000002010212214017546023532 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZODB-based persistent weakrefs $Id: wref.py 29450 2005-03-11 23:53:09Z tim_one $ """ from persistent import Persistent WeakRefMarker = object() class WeakRef(object): """Persistent weak references Persistent weak references are used much like Python weak references. The major difference is that you can't specify an object to be called when the object is removed from the database. Here's an example. We'll start by creating a persistent object and a refernce to it: >>> import persistent.list >>> import ZODB.tests.util >>> ob = persistent.list.PersistentList() >>> ref = WeakRef(ob) >>> ref() is ob True The hash of the ref if the same as the hash of the referenced object: >>> hash(ref) == hash(ob) True Two refs to the same object are equal: >>> WeakRef(ob) == ref True >>> ob2 = persistent.list.PersistentList([1]) >>> WeakRef(ob2) == ref False Lets save the reference and the referenced object in a database: >>> db = ZODB.tests.util.DB() >>> conn1 = db.open() >>> conn1.root()['ob'] = ob >>> conn1.root()['ref'] = ref >>> ZODB.tests.util.commit() If we open a new connection, we can use the reference: >>> conn2 = db.open() >>> conn2.root()['ref']() is conn2.root()['ob'] True >>> hash(conn2.root()['ref']) == hash(conn2.root()['ob']) True But if we delete the referenced object and pack: >>> del conn2.root()['ob'] >>> ZODB.tests.util.commit() >>> ZODB.tests.util.pack(db) And then look in a new connection: >>> conn3 = db.open() >>> conn3.root()['ob'] Traceback (most recent call last): ... KeyError: 'ob' Trying to dereference the reference returns None: >>> conn3.root()['ref']() Trying to get a hash, raises a type error: >>> hash(conn3.root()['ref']) Traceback (most recent call last): ... TypeError: Weakly-referenced object has gone away Always explicitly close databases: :) >>> db.close() """ # We set _p_oid to a marker so that the serialization system can # provide special handling of weakrefs. _p_oid = WeakRefMarker def __init__(self, ob): self._v_ob = ob self.oid = ob._p_oid self.dm = ob._p_jar def __call__(self): try: return self._v_ob except AttributeError: try: self._v_ob = self.dm[self.oid] except KeyError: return None return self._v_ob def __hash__(self): self = self() if self is None: raise TypeError('Weakly-referenced object has gone away') return hash(self) def __eq__(self, other): self = self() if self is None: raise TypeError('Weakly-referenced object has gone away') other = other() if other is None: raise TypeError('Weakly-referenced object has gone away') return self == other class PersistentWeakKeyDictionary(Persistent): """Persistent weak key dictionary This is akin to WeakKeyDictionaries. Note, however, that removal of items is extremely lazy. See below. We'll start by creating a PersistentWeakKeyDictionary and adding some persistent objects to it. >>> d = PersistentWeakKeyDictionary() >>> import ZODB.tests.util >>> p1 = ZODB.tests.util.P('p1') >>> p2 = ZODB.tests.util.P('p2') >>> p3 = ZODB.tests.util.P('p3') >>> d[p1] = 1 >>> d[p2] = 2 >>> d[p3] = 3 We'll create an extra persistent object that's not in the dict: >>> p4 = ZODB.tests.util.P('p4') Now we'll excercise iteration and item access: >>> l = [(str(k), d[k], d.get(k)) for k in d] >>> l.sort() >>> l [('P(p1)', 1, 1), ('P(p2)', 2, 2), ('P(p3)', 3, 3)] And the containment operator: >>> [p in d for p in [p1, p2, p3, p4]] [True, True, True, False] We can add the dict and the referenced objects to a database: >>> db = ZODB.tests.util.DB() >>> conn1 = db.open() >>> conn1.root()['p1'] = p1 >>> conn1.root()['d'] = d >>> conn1.root()['p2'] = p2 >>> conn1.root()['p3'] = p3 >>> ZODB.tests.util.commit() And things still work, as before: >>> l = [(str(k), d[k], d.get(k)) for k in d] >>> l.sort() >>> l [('P(p1)', 1, 1), ('P(p2)', 2, 2), ('P(p3)', 3, 3)] >>> [p in d for p in [p1, p2, p3, p4]] [True, True, True, False] Likewise, we can read the objects from another connection and things still work. >>> conn2 = db.open() >>> d = conn2.root()['d'] >>> p1 = conn2.root()['p1'] >>> p2 = conn2.root()['p2'] >>> p3 = conn2.root()['p3'] >>> l = [(str(k), d[k], d.get(k)) for k in d] >>> l.sort() >>> l [('P(p1)', 1, 1), ('P(p2)', 2, 2), ('P(p3)', 3, 3)] >>> [p in d for p in [p1, p2, p3, p4]] [True, True, True, False] Now, we'll delete one of the objects from the database, but *not* from the dictionary: >>> del conn2.root()['p2'] >>> ZODB.tests.util.commit() And pack the database, so that the no-longer referenced p2 is actually removed from the database. >>> ZODB.tests.util.pack(db) Now if we access the dictionary in a new connection, it no longer has p2: >>> conn3 = db.open() >>> d = conn3.root()['d'] >>> l = [(str(k), d[k], d.get(k)) for k in d] >>> l.sort() >>> l [('P(p1)', 1, 1), ('P(p3)', 3, 3)] It's worth nothing that that the versions of the dictionary in conn1 and conn2 still have p2, because p2 is still in the caches for those connections. Always explicitly close databases: :) >>> db.close() """ # TODO: It's expensive trying to load dead objects from the database. # It would be helpful if the data manager/connection cached these. def __init__(self, adict=None, **kwargs): self.data = {} if adict is not None: keys = getattr(adict, "keys", None) if keys is None: adict = dict(adict) self.update(adict) if kwargs: self.update(kwargs) def __getstate__(self): state = Persistent.__getstate__(self) state['data'] = state['data'].items() return state def __setstate__(self, state): state['data'] = dict([ (k, v) for (k, v) in state['data'] if k() is not None ]) Persistent.__setstate__(self, state) def __setitem__(self, key, value): self.data[WeakRef(key)] = value def __getitem__(self, key): return self.data[WeakRef(key)] def __delitem__(self, key): del self.data[WeakRef(key)] def get(self, key, default=None): """D.get(k[, d]) -> D[k] if k in D, else d. >>> import ZODB.tests.util >>> key = ZODB.tests.util.P("key") >>> missing = ZODB.tests.util.P("missing") >>> d = PersistentWeakKeyDictionary([(key, 1)]) >>> d.get(key) 1 >>> d.get(missing) >>> d.get(missing, 12) 12 """ return self.data.get(WeakRef(key), default) def __contains__(self, key): return WeakRef(key) in self.data def __iter__(self): for k in self.data: yield k() def update(self, adict): if isinstance(adict, PersistentWeakKeyDictionary): self.data.update(adict.update) else: for k, v in adict.items(): self.data[WeakRef(k)] = v # TODO: May need more methods, and tests. zope2.13-2.13.21/source/zope.container/include/persistent/tests/0000755000175000017500000000000012214017546023364 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/include/persistent/tests/persistenttestbase.py0000644000175000017500000002605212214017546027676 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from persistent import Persistent from persistent.interfaces import IPersistent # Confusing: ZODB doesn't use this file. It appears to be used only # by Zope3, where it's imported by zope/app/schema/tests/test_wrapper.py. try: import zope.interface except ImportError: interfaces = False else: interfaces = True class Test(unittest.TestCase): klass = None # override in subclass def testSaved(self): p = self.klass() p._p_oid = '\0\0\0\0\0\0hi' dm = DM() p._p_jar = dm self.assertEqual(p._p_changed, 0) self.assertEqual(dm.called, 0) p.inc() self.assertEqual(p._p_changed, 1) self.assertEqual(dm.called, 1) p.inc() self.assertEqual(p._p_changed, 1) self.assertEqual(dm.called, 1) p._p_deactivate() self.assertEqual(p._p_changed, 1) self.assertEqual(dm.called, 1) p._p_deactivate() self.assertEqual(p._p_changed, 1) self.assertEqual(dm.called, 1) del p._p_changed # deal with current cPersistence implementation if p._p_changed != 3: self.assertEqual(p._p_changed, None) self.assertEqual(dm.called, 1) p.inc() self.assertEqual(p.x, 43) self.assertEqual(p._p_changed, 1) self.assertEqual(dm.called, 2) p._p_changed = 0 self.assertEqual(p._p_changed, 0) self.assertEqual(dm.called, 2) self.assertEqual(p.x, 43) p.inc() self.assertEqual(p._p_changed, 1) self.assertEqual(dm.called, 3) def testUnsaved(self): p = self.klass() self.assertEqual(p.x, 0) self.assertEqual(p._p_changed, 0) self.assertEqual(p._p_jar, None) self.assertEqual(p._p_oid, None) p.inc() p.inc() self.assertEqual(p.x, 2) self.assertEqual(p._p_changed, 0) p._p_deactivate() self.assertEqual(p._p_changed, 0) p._p_changed = 1 self.assertEqual(p._p_changed, 0) p._p_deactivate() self.assertEqual(p._p_changed, 0) del p._p_changed self.assertEqual(p._p_changed, 0) if self.has_dict: self.failUnless(p.__dict__) self.assertEqual(p.x, 2) def testState(self): p = self.klass() self.assertEqual(p.__getstate__(), {'x': 0}) self.assertEqual(p._p_changed, 0) p.__setstate__({'x':5}) self.assertEqual(p._p_changed, 0) if self.has_dict: p._v_foo = 2 self.assertEqual(p.__getstate__(), {'x': 5}) self.assertEqual(p._p_changed, 0) def testSetStateSerial(self): p = self.klass() p._p_serial = '00000012' p.__setstate__(p.__getstate__()) self.assertEqual(p._p_serial, '00000012') def testDirectChanged(self): p = self.klass() p._p_oid = 1 dm = DM() p._p_jar = dm self.assertEqual(p._p_changed, 0) self.assertEqual(dm.called, 0) p._p_changed = 1 self.assertEqual(dm.called, 1) def testGhostChanged(self): # If an object is a ghost and its _p_changed is set to True (any # true value), it should activate (unghostify) the object. This # behavior is new in ZODB 3.6; before then, an attempt to do # "ghost._p_changed = True" was ignored. p = self.klass() p._p_oid = 1 dm = DM() p._p_jar = dm p._p_deactivate() self.assertEqual(p._p_changed, None) p._p_changed = True self.assertEqual(p._p_changed, 1) def testRegistrationFailure(self): p = self.klass() p._p_oid = 1 dm = BrokenDM() p._p_jar = dm self.assertEqual(p._p_changed, 0) self.assertEqual(dm.called, 0) try: p._p_changed = 1 except NotImplementedError: pass else: raise AssertionError("Exception not propagated") self.assertEqual(dm.called, 1) self.assertEqual(p._p_changed, 0) def testLoadFailure(self): p = self.klass() p._p_oid = 1 dm = BrokenDM() p._p_jar = dm p._p_deactivate() # make it a ghost try: p._p_activate() except NotImplementedError: pass else: raise AssertionError("Exception not propagated") self.assertEqual(p._p_changed, None) def testActivate(self): p = self.klass() dm = DM() p._p_oid = 1 p._p_jar = dm p._p_changed = 0 p._p_deactivate() # Unsure: does this really test the activate method? p._p_activate() self.assertEqual(p._p_changed, 0) self.assertEqual(p.x, 42) def testDeactivate(self): p = self.klass() dm = DM() p._p_oid = 1 p._p_deactivate() # this deactive has no effect self.assertEqual(p._p_changed, 0) p._p_jar = dm p._p_changed = 0 p._p_deactivate() self.assertEqual(p._p_changed, None) p._p_activate() self.assertEqual(p._p_changed, 0) self.assertEqual(p.x, 42) if interfaces: def testInterface(self): self.assert_(IPersistent.implementedBy(Persistent), "%s does not implement IPersistent" % Persistent) p = Persistent() self.assert_(IPersistent.providedBy(p), "%s does not implement IPersistent" % p) self.assert_(IPersistent.implementedBy(P), "%s does not implement IPersistent" % P) p = self.klass() self.assert_(IPersistent.providedBy(p), "%s does not implement IPersistent" % p) def testDataManagerAndAttributes(self): # Test to cover an odd bug where the instance __dict__ was # set at the same location as the data manager in the C type. p = P() p.inc() p.inc() self.assert_('x' in p.__dict__) self.assert_(p._p_jar is None) def testMultipleInheritance(self): # make sure it is possible to inherit from two different # subclasses of persistent. class A(Persistent): pass class B(Persistent): pass class C(A, B): pass class D(object): pass class E(D, B): pass def testMultipleMeta(self): # make sure it's possible to define persistent classes # with a base whose metaclass is different class alternateMeta(type): pass class alternate(object): __metaclass__ = alternateMeta class mixedMeta(alternateMeta, type): pass class mixed(alternate,Persistent): __metaclass__ = mixedMeta def testSlots(self): # Verify that Persistent classes behave the same way # as pure Python objects where '__slots__' and '__dict__' # are concerned. class noDict(object): __slots__ = ['foo'] class shouldHaveDict(noDict): pass class p_noDict(Persistent): __slots__ = ['foo'] class p_shouldHaveDict(p_noDict): pass self.assertEqual(noDict.__dictoffset__, 0) self.assertEqual(p_noDict.__dictoffset__, 0) self.assert_(shouldHaveDict.__dictoffset__ <> 0) self.assert_(p_shouldHaveDict.__dictoffset__ <> 0) def testBasicTypeStructure(self): # test that a persistent class has a sane C type structure # use P (defined below) as simplest example self.assertEqual(Persistent.__dictoffset__, 0) self.assertEqual(Persistent.__weakrefoffset__, 0) self.assert_(Persistent.__basicsize__ > object.__basicsize__) self.assert_(P.__dictoffset__) self.assert_(P.__weakrefoffset__) self.assert_(P.__dictoffset__ < P.__weakrefoffset__) self.assert_(P.__basicsize__ > Persistent.__basicsize__) # Unsure: Can anyone defend/explain the test below? The tests classes defined # here don't define __call__, so this weird test will always pass, but to what # end? If a klass is given that happens to define __call__, the test *may* # mysteriously fail. Who cares? ## def testDeactivateErrors(self): ## p = self.klass() ## p._p_oid = '\0\0\0\0\0\0hi' ## dm = DM() ## p._p_jar = dm ## def typeerr(*args, **kwargs): ## self.assertRaises(TypeError, p, *args, **kwargs) ## typeerr(1) ## typeerr(1, 2) ## typeerr(spam=1) ## typeerr(spam=1, force=1) ## p._p_changed = True ## class Err(object): ## def __nonzero__(self): ## raise RuntimeError ## typeerr(force=Err()) class P(Persistent): def __init__(self): self.x = 0 def inc(self): self.x += 1 class P2(P): def __getstate__(self): return 42 def __setstate__(self, v): self.v = v class B(Persistent): __slots__ = ["x", "_p_serial"] def __init__(self): self.x = 0 def inc(self): self.x += 1 def __getstate__(self): return {'x': self.x} def __setstate__(self, state): self.x = state['x'] class DM: def __init__(self): self.called = 0 def register(self, ob): self.called += 1 def setstate(self, ob): ob.__setstate__({'x': 42}) class BrokenDM(DM): def register(self,ob): self.called += 1 raise NotImplementedError def setstate(self,ob): raise NotImplementedError class PersistentTest(Test): klass = P has_dict = 1 def testPicklable(self): import pickle p = self.klass() p.inc() p2 = pickle.loads(pickle.dumps(p)) self.assertEqual(p2.__class__, self.klass) # verify that the inc is reflected: self.assertEqual(p2.x, p.x) # This assertion would be invalid. Interfaces # are compared by identity and copying doesn't # preserve identity. We would get false negatives due # to the differing identities of the original and copied # PersistentInterface: # self.assertEqual(p2.__dict__, p.__dict__) def testPicklableWCustomState(self): import pickle p = P2() p2 = pickle.loads(pickle.dumps(p)) self.assertEqual(p2.__class__, P2); self.assertEqual(p2.__dict__, {'v': 42}) class BasePersistentTest(Test): klass = B has_dict = 0 zope2.13-2.13.21/source/zope.container/include/persistent/tests/test_list.py0000644000175000017500000001375112214017546025757 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for PersistentList """ import unittest l0 = [] l1 = [0] l2 = [0, 1] class TestPList(unittest.TestCase): def _getTargetClass(self): from persistent.list import PersistentList return PersistentList def test_volatile_attributes_not_persisted(self): # http://www.zope.org/Collectors/Zope/2052 m = self._getTargetClass()() m.foo = 'bar' m._v_baz = 'qux' state = m.__getstate__() self.failUnless('foo' in state) self.failIf('_v_baz' in state) def testTheWorld(self): # Test constructors pl = self._getTargetClass() u = pl() u0 = pl(l0) u1 = pl(l1) u2 = pl(l2) uu = pl(u) uu0 = pl(u0) uu1 = pl(u1) uu2 = pl(u2) v = pl(tuple(u)) class OtherList: def __init__(self, initlist): self.__data = initlist def __len__(self): return len(self.__data) def __getitem__(self, i): return self.__data[i] v0 = pl(OtherList(u0)) vv = pl("this is also a sequence") # Test __repr__ eq = self.assertEqual eq(str(u0), str(l0), "str(u0) == str(l0)") eq(repr(u1), repr(l1), "repr(u1) == repr(l1)") eq(`u2`, `l2`, "`u2` == `l2`") # Test __cmp__ and __len__ def mycmp(a, b): r = cmp(a, b) if r < 0: return -1 if r > 0: return 1 return r all = [l0, l1, l2, u, u0, u1, u2, uu, uu0, uu1, uu2] for a in all: for b in all: eq(mycmp(a, b), mycmp(len(a), len(b)), "mycmp(a, b) == mycmp(len(a), len(b))") # Test __getitem__ for i in range(len(u2)): eq(u2[i], i, "u2[i] == i") # Test __setitem__ uu2[0] = 0 uu2[1] = 100 try: uu2[2] = 200 except IndexError: pass else: raise TestFailed("uu2[2] shouldn't be assignable") # Test __delitem__ del uu2[1] del uu2[0] try: del uu2[0] except IndexError: pass else: raise TestFailed("uu2[0] shouldn't be deletable") # Test __getslice__ for i in range(-3, 4): eq(u2[:i], l2[:i], "u2[:i] == l2[:i]") eq(u2[i:], l2[i:], "u2[i:] == l2[i:]") for j in range(-3, 4): eq(u2[i:j], l2[i:j], "u2[i:j] == l2[i:j]") # Test __setslice__ for i in range(-3, 4): u2[:i] = l2[:i] eq(u2, l2, "u2 == l2") u2[i:] = l2[i:] eq(u2, l2, "u2 == l2") for j in range(-3, 4): u2[i:j] = l2[i:j] eq(u2, l2, "u2 == l2") uu2 = u2[:] uu2[:0] = [-2, -1] eq(uu2, [-2, -1, 0, 1], "uu2 == [-2, -1, 0, 1]") uu2[0:] = [] eq(uu2, [], "uu2 == []") # Test __contains__ for i in u2: self.failUnless(i in u2, "i in u2") for i in min(u2)-1, max(u2)+1: self.failUnless(i not in u2, "i not in u2") # Test __delslice__ uu2 = u2[:] del uu2[1:2] del uu2[0:1] eq(uu2, [], "uu2 == []") uu2 = u2[:] del uu2[1:] del uu2[:1] eq(uu2, [], "uu2 == []") # Test __add__, __radd__, __mul__ and __rmul__ #self.failUnless(u1 + [] == [] + u1 == u1, "u1 + [] == [] + u1 == u1") self.failUnless(u1 + [1] == u2, "u1 + [1] == u2") #self.failUnless([-1] + u1 == [-1, 0], "[-1] + u1 == [-1, 0]") self.failUnless(u2 == u2*1 == 1*u2, "u2 == u2*1 == 1*u2") self.failUnless(u2+u2 == u2*2 == 2*u2, "u2+u2 == u2*2 == 2*u2") self.failUnless(u2+u2+u2 == u2*3 == 3*u2, "u2+u2+u2 == u2*3 == 3*u2") # Test append u = u1[:] u.append(1) eq(u, u2, "u == u2") # Test insert u = u2[:] u.insert(0, -1) eq(u, [-1, 0, 1], "u == [-1, 0, 1]") # Test pop u = pl([0, -1, 1]) u.pop() eq(u, [0, -1], "u == [0, -1]") u.pop(0) eq(u, [-1], "u == [-1]") # Test remove u = u2[:] u.remove(1) eq(u, u1, "u == u1") # Test count u = u2*3 eq(u.count(0), 3, "u.count(0) == 3") eq(u.count(1), 3, "u.count(1) == 3") eq(u.count(2), 0, "u.count(2) == 0") # Test index eq(u2.index(0), 0, "u2.index(0) == 0") eq(u2.index(1), 1, "u2.index(1) == 1") try: u2.index(2) except ValueError: pass else: raise TestFailed("expected ValueError") # Test reverse u = u2[:] u.reverse() eq(u, [1, 0], "u == [1, 0]") u.reverse() eq(u, u2, "u == u2") # Test sort u = pl([1, 0]) u.sort() eq(u, u2, "u == u2") # Test extend u = u1[:] u.extend(u2) eq(u, u1 + u2, "u == u1 + u2") # Test iadd u = u1[:] u += u2 eq(u, u1 + u2, "u == u1 + u2") # Test imul u = u1[:] u *= 3 eq(u, u1 + u1 + u1, "u == u1 + u1 + u1") def test_suite(): return unittest.makeSuite(TestPList) if __name__ == "__main__": loader = unittest.TestLoader() unittest.main(testLoader=loader) zope2.13-2.13.21/source/zope.container/include/persistent/tests/test_wref.py0000644000175000017500000000155012214017546025741 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ $Id: test_wref.py 29896 2005-04-07 04:48:06Z tim_one $ """ import unittest from zope.testing.doctest import DocTestSuite def test_suite(): return DocTestSuite('persistent.wref') if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.container/include/persistent/tests/__init__.py0000644000175000017500000000001212214017546025466 0ustar arnauarnau# package zope2.13-2.13.21/source/zope.container/include/persistent/tests/persistent.txt0000644000175000017500000002411512214017546026330 0ustar arnauarnauTests for `persistent.Persistent` ================================= This document is an extended doc test that covers the basics of the Persistent base class. The test expects a class named `P` to be provided in its globals. The `P` class implements the `Persistent` interface. Test framework -------------- The class `P` needs to behave like `ExampleP`. (Note that the code below is *not* part of the tests.) :: class ExampleP(Persistent): def __init__(self): self.x = 0 def inc(self): self.x += 1 The tests use stub data managers. A data manager is responsible for loading and storing the state of a persistent object. It's stored in the ``_p_jar`` attribute of a persistent object. >>> class DM: ... def __init__(self): ... self.called = 0 ... def register(self, ob): ... self.called += 1 ... def setstate(self, ob): ... ob.__setstate__({'x': 42}) >>> class BrokenDM(DM): ... def register(self,ob): ... self.called += 1 ... raise NotImplementedError ... def setstate(self,ob): ... raise NotImplementedError >>> from persistent import Persistent Test Persistent without Data Manager ------------------------------------ First do some simple tests of a Persistent instance that does not have a data manager (``_p_jar``). >>> p = P() >>> p.x 0 >>> p._p_changed False >>> p._p_state 0 >>> p._p_jar >>> p._p_oid Verify that modifications have no effect on ``_p_state`` of ``_p_changed``. >>> p.inc() >>> p.inc() >>> p.x 2 >>> p._p_changed False >>> p._p_state 0 Try all sorts of different ways to change the object's state. >>> p._p_deactivate() >>> p._p_state 0 >>> p._p_changed = True >>> p._p_state 0 >>> del p._p_changed >>> p._p_changed False >>> p._p_state 0 >>> p.x 2 Test Persistent with Data Manager --------------------------------- Next try some tests of an object with a data manager. The `DM` class is a simple testing stub. >>> p = P() >>> dm = DM() >>> p._p_oid = "00000012" >>> p._p_jar = dm >>> p._p_changed 0 >>> dm.called 0 Modifying the object marks it as changed and registers it with the data manager. Subsequent modifications don't have additional side-effects. >>> p.inc() >>> p._p_changed 1 >>> dm.called 1 >>> p.inc() >>> p._p_changed 1 >>> dm.called 1 It's not possible to deactivate a modified object. >>> p._p_deactivate() >>> p._p_changed 1 It is possible to invalidate it. That's the key difference between deactivation and invalidation. >>> p._p_invalidate() >>> p._p_state -1 Now that the object is a ghost, any attempt to modify it will require that it be unghosted first. The test data manager has the odd property that it sets the object's ``x`` attribute to ``42`` when it is unghosted. >>> p.inc() >>> p.x 43 >>> dm.called 2 You can manually reset the changed field to ``False``, although it's not clear why you would want to do that. The object changes to the ``UPTODATE`` state but retains its modifications. >>> p._p_changed = False >>> p._p_state 0 >>> p._p_changed False >>> p.x 43 >>> p.inc() >>> p._p_changed True >>> dm.called 3 ``__getstate__()`` and ``__setstate__()`` ----------------------------------------- The next several tests cover the ``__getstate__()`` and ``__setstate__()`` implementations. >>> p = P() >>> state = p.__getstate__() >>> isinstance(state, dict) True >>> state['x'] 0 >>> p._p_state 0 Calling setstate always leaves the object in the uptodate state? (I'm not entirely clear on this one.) >>> p.__setstate__({'x': 5}) >>> p._p_state 0 Assigning to a volatile attribute has no effect on the object state. >>> p._v_foo = 2 >>> p.__getstate__() {'x': 5} >>> p._p_state 0 The ``_p_serial`` attribute is not affected by calling setstate. >>> p._p_serial = "00000012" >>> p.__setstate__(p.__getstate__()) >>> p._p_serial '00000012' Change Ghost test ----------------- If an object is a ghost and its ``_p_changed`` is set to ``True`` (any true value), it should activate (unghostify) the object. This behavior is new in ZODB 3.6; before then, an attempt to do ``ghost._p_changed = True`` was ignored. >>> p = P() >>> p._p_jar = DM() >>> p._p_oid = 1 >>> p._p_deactivate() >>> p._p_changed # None >>> p._p_state # ghost state -1 >>> p._p_changed = True >>> p._p_changed 1 >>> p._p_state # changed state 1 >>> p.x 42 Activate, deactivate, and invalidate ------------------------------------ Some of these tests are redundant, but are included to make sure there are explicit and simple tests of ``_p_activate()``, ``_p_deactivate()``, and ``_p_invalidate()``. >>> p = P() >>> p._p_oid = 1 >>> p._p_jar = DM() >>> p._p_deactivate() >>> p._p_state -1 >>> p._p_activate() >>> p._p_state 0 >>> p.x 42 >>> p.inc() >>> p.x 43 >>> p._p_state 1 >>> p._p_invalidate() >>> p._p_state -1 >>> p.x 42 Test failures ------------- The following tests cover various errors cases. When an object is modified, it registers with its data manager. If that registration fails, the exception is propagated and the object stays in the up-to-date state. It shouldn't change to the modified state, because it won't be saved when the transaction commits. >>> p = P() >>> p._p_oid = 1 >>> p._p_jar = BrokenDM() >>> p._p_state 0 >>> p._p_jar.called 0 >>> p._p_changed = 1 Traceback (most recent call last): ... NotImplementedError >>> p._p_jar.called 1 >>> p._p_state 0 Make sure that exceptions that occur inside the data manager's ``setstate()`` method propagate out to the caller. >>> p = P() >>> p._p_oid = 1 >>> p._p_jar = BrokenDM() >>> p._p_deactivate() >>> p._p_state -1 >>> p._p_activate() Traceback (most recent call last): ... NotImplementedError >>> p._p_state -1 Special test to cover layout of ``__dict__`` -------------------------------------------- We once had a bug in the `Persistent` class that calculated an incorrect offset for the ``__dict__`` attribute. It assigned ``__dict__`` and ``_p_jar`` to the same location in memory. This is a simple test to make sure they have different locations. >>> p = P() >>> p.inc() >>> p.inc() >>> 'x' in p.__dict__ True >>> p._p_jar Inheritance and metaclasses --------------------------- Simple tests to make sure it's possible to inherit from the `Persistent` base class multiple times. There used to be metaclasses involved in `Persistent` that probably made this a more interesting test. >>> class A(Persistent): ... pass >>> class B(Persistent): ... pass >>> class C(A, B): ... pass >>> class D(object): ... pass >>> class E(D, B): ... pass >>> a = A() >>> b = B() >>> c = C() >>> d = D() >>> e = E() Also make sure that it's possible to define `Persistent` classes that have a custom metaclass. >>> class alternateMeta(type): ... type >>> class alternate(object): ... __metaclass__ = alternateMeta >>> class mixedMeta(alternateMeta, type): ... pass >>> class mixed(alternate, Persistent): ... pass >>> class mixed(Persistent, alternate): ... pass Basic type structure -------------------- >>> Persistent.__dictoffset__ 0 >>> Persistent.__weakrefoffset__ 0 >>> Persistent.__basicsize__ > object.__basicsize__ True >>> P.__dictoffset__ > 0 True >>> P.__weakrefoffset__ > 0 True >>> P.__dictoffset__ < P.__weakrefoffset__ True >>> P.__basicsize__ > Persistent.__basicsize__ True Slots ----- These are some simple tests of classes that have an ``__slots__`` attribute. Some of the classes should have slots, others shouldn't. >>> class noDict(object): ... __slots__ = ['foo'] >>> class p_noDict(Persistent): ... __slots__ = ['foo'] >>> class p_shouldHaveDict(p_noDict): ... pass >>> p_noDict.__dictoffset__ 0 >>> x = p_noDict() >>> x.foo = 1 >>> x.foo 1 >>> x.bar = 1 Traceback (most recent call last): ... AttributeError: 'p_noDict' object has no attribute 'bar' >>> x._v_bar = 1 Traceback (most recent call last): ... AttributeError: 'p_noDict' object has no attribute '_v_bar' >>> x.__dict__ Traceback (most recent call last): ... AttributeError: 'p_noDict' object has no attribute '__dict__' The various _p_ attributes are unaffected by slots. >>> p._p_oid >>> p._p_jar >>> p._p_state 0 If the most-derived class does not specify >>> p_shouldHaveDict.__dictoffset__ > 0 True >>> x = p_shouldHaveDict() >>> isinstance(x.__dict__, dict) True Pickling -------- There's actually a substantial effort involved in making subclasses of `Persistent` work with plain-old pickle. The ZODB serialization layer never calls pickle on an object; it pickles the object's class description and its state as two separate pickles. >>> import pickle >>> p = P() >>> p.inc() >>> p2 = pickle.loads(pickle.dumps(p)) >>> p2.__class__ is P True >>> p2.x == p.x True We should also test that pickle works with custom getstate and setstate. Perhaps even reduce. The problem is that pickling depends on finding the class in a particular module, and classes defined here won't appear in any module. We could require each user of the tests to define a base class, but that might be tedious. Interfaces ---------- Some versions of Zope and ZODB have the `zope.interfaces` package available. If it is available, then persistent will be associated with several interfaces. It's hard to write a doctest test that runs the tests only if `zope.interface` is available, so this test looks a little unusual. One problem is that the assert statements won't do anything if you run with `-O`. >>> try: ... import zope.interface ... except ImportError: ... pass ... else: ... from persistent.interfaces import IPersistent ... assert IPersistent.implementedBy(Persistent) ... p = Persistent() ... assert IPersistent.providedBy(p) ... assert IPersistent.implementedBy(P) ... p = P() ... assert IPersistent.providedBy(p) zope2.13-2.13.21/source/zope.container/include/persistent/tests/test_overriding_attrs.py0000644000175000017500000002600712214017546030367 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Overriding attr methods This module tests and documents, through example, overriding attribute access methods. $Id: test_overriding_attrs.py 38214 2005-08-31 20:43:54Z mj $ """ from persistent import Persistent import transaction from ZODB.tests.util import DB class SampleOverridingGetattr(Persistent): """Example of overriding __getattr__ """ def __getattr__(self, name): """Get attributes that can't be gotten the usual way The __getattr__ method works pretty much the same for persistent classes as it does for other classes. No special handling is needed. If an object is a ghost, then it will be activated before __getattr__ is called. In this example, our objects returns a tuple with the attribute name, converted to upper case and the value of _p_changed, for any attribute that isn't handled by the default machinery. >>> o = SampleOverridingGetattr() >>> o._p_changed False >>> o._p_oid >>> o._p_jar >>> o.spam ('SPAM', False) >>> o.spam = 1 >>> o.spam 1 We'll save the object, so it can be deactivated: >>> db = DB() >>> conn = db.open() >>> conn.root()['o'] = o >>> transaction.commit() >>> o._p_deactivate() >>> o._p_changed And now, if we ask for an attribute it doesn't have, >>> o.eggs ('EGGS', False) And we see that the object was activated before calling the __getattr__ method. We always close databases after we use them: >>> db.close() """ # Don't pretend we have any special attributes. if name.startswith("__") and name.endswrith("__"): raise AttributeError(name) else: return name.upper(), self._p_changed class SampleOverridingGetattributeSetattrAndDelattr(Persistent): """Example of overriding __getattribute__, __setattr__, and __delattr__ In this example, we'll provide an example that shows how to override the __getattribute__, __setattr__, and __delattr__ methods. We'll create a class that stores it's attributes in a secret dictionary within it's instance dictionary. The class will have the policy that variables with names starting with 'tmp_' will be volatile. """ def __init__(self, **kw): self.__dict__['__secret__'] = kw.copy() def __getattribute__(self, name): """Get an attribute value The __getattribute__ method is called for all attribute accesses. It overrides the attribute access support inherited from Persistent. Our sample class let's us provide initial values as keyword arguments to the constructor: >>> o = SampleOverridingGetattributeSetattrAndDelattr(x=1) >>> o._p_changed 0 >>> o._p_oid >>> o._p_jar >>> o.x 1 >>> o.y Traceback (most recent call last): ... AttributeError: y Next, we'll save the object in a database so that we can deactivate it: >>> db = DB() >>> conn = db.open() >>> conn.root()['o'] = o >>> transaction.commit() >>> o._p_deactivate() >>> o._p_changed And we'll get some data: >>> o.x 1 which activates the object: >>> o._p_changed 0 It works for missing attribes too: >>> o._p_deactivate() >>> o._p_changed >>> o.y Traceback (most recent call last): ... AttributeError: y >>> o._p_changed 0 See the very important note in the comment below! We always close databases after we use them: >>> db.close() """ ################################################################# # IMPORTANT! READ THIS! 8-> # # We *always* give Persistent a chance first. # Persistent handles certain special attributes, like _p_ # attributes. In particular, the base class handles __dict__ # and __class__. # # We call _p_getattr. If it returns True, then we have to # use Persistent.__getattribute__ to get the value. # ################################################################# if Persistent._p_getattr(self, name): return Persistent.__getattribute__(self, name) # Data should be in our secret dictionary: secret = self.__dict__['__secret__'] if name in secret: return secret[name] # Maybe it's a method: meth = getattr(self.__class__, name, None) if meth is None: raise AttributeError(name) return meth.__get__(self, self.__class__) def __setattr__(self, name, value): """Set an attribute value The __setattr__ method is called for all attribute assignments. It overrides the attribute assignment support inherited from Persistent. Implementors of __setattr__ methods: 1. Must call Persistent._p_setattr first to allow it to handle some attributes and to make sure that the object is activated if necessary, and 2. Must set _p_changed to mark objects as changed. See the comments in the source below. >>> o = SampleOverridingGetattributeSetattrAndDelattr() >>> o._p_changed 0 >>> o._p_oid >>> o._p_jar >>> o.x Traceback (most recent call last): ... AttributeError: x >>> o.x = 1 >>> o.x 1 Because the implementation doesn't store attributes directly in the instance dictionary, we don't have a key for the attribute: >>> 'x' in o.__dict__ False Next, we'll save the object in a database so that we can deactivate it: >>> db = DB() >>> conn = db.open() >>> conn.root()['o'] = o >>> transaction.commit() >>> o._p_deactivate() >>> o._p_changed We'll modify an attribute >>> o.y = 2 >>> o.y 2 which reactivates it, and markes it as modified, because our implementation marked it as modified: >>> o._p_changed 1 Now, if commit: >>> transaction.commit() >>> o._p_changed 0 And deactivate the object: >>> o._p_deactivate() >>> o._p_changed and then set a variable with a name starting with 'tmp_', The object will be activated, but not marked as modified, because our __setattr__ implementation doesn't mark the object as changed if the name starts with 'tmp_': >>> o.tmp_foo = 3 >>> o._p_changed 0 >>> o.tmp_foo 3 We always close databases after we use them: >>> db.close() """ ################################################################# # IMPORTANT! READ THIS! 8-> # # We *always* give Persistent a chance first. # Persistent handles certain special attributes, like _p_ # attributes. # # We call _p_setattr. If it returns True, then we are done. # It has already set the attribute. # ################################################################# if Persistent._p_setattr(self, name, value): return self.__dict__['__secret__'][name] = value if not name.startswith('tmp_'): self._p_changed = 1 def __delattr__(self, name): """Delete an attribute value The __delattr__ method is called for all attribute deletions. It overrides the attribute deletion support inherited from Persistent. Implementors of __delattr__ methods: 1. Must call Persistent._p_delattr first to allow it to handle some attributes and to make sure that the object is activated if necessary, and 2. Must set _p_changed to mark objects as changed. See the comments in the source below. >>> o = SampleOverridingGetattributeSetattrAndDelattr( ... x=1, y=2, tmp_z=3) >>> o._p_changed 0 >>> o._p_oid >>> o._p_jar >>> o.x 1 >>> del o.x >>> o.x Traceback (most recent call last): ... AttributeError: x Next, we'll save the object in a database so that we can deactivate it: >>> db = DB() >>> conn = db.open() >>> conn.root()['o'] = o >>> transaction.commit() >>> o._p_deactivate() >>> o._p_changed If we delete an attribute: >>> del o.y The object is activated. It is also marked as changed because our implementation marked it as changed. >>> o._p_changed 1 >>> o.y Traceback (most recent call last): ... AttributeError: y >>> o.tmp_z 3 Now, if commit: >>> transaction.commit() >>> o._p_changed 0 And deactivate the object: >>> o._p_deactivate() >>> o._p_changed and then delete a variable with a name starting with 'tmp_', The object will be activated, but not marked as modified, because our __delattr__ implementation doesn't mark the object as changed if the name starts with 'tmp_': >>> del o.tmp_z >>> o._p_changed 0 >>> o.tmp_z Traceback (most recent call last): ... AttributeError: tmp_z We always close databases after we use them: >>> db.close() """ ################################################################# # IMPORTANT! READ THIS! 8-> # # We *always* give Persistent a chance first. # Persistent handles certain special attributes, like _p_ # attributes. # # We call _p_delattr. If it returns True, then we are done. # It has already deleted the attribute. # ################################################################# if Persistent._p_delattr(self, name): return del self.__dict__['__secret__'][name] if not name.startswith('tmp_'): self._p_changed = 1 def test_suite(): from zope.testing.doctest import DocTestSuite return DocTestSuite() zope2.13-2.13.21/source/zope.container/include/persistent/tests/test_pickle.py0000644000175000017500000001434512214017546026253 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Basic pickling tests $Id: test_pickle.py 29896 2005-04-07 04:48:06Z tim_one $ """ from persistent import Persistent import pickle def print_dict(d): d = d.items() d.sort() print '{%s}' % (', '.join( [('%r: %r' % (k, v)) for (k, v) in d] )) def cmpattrs(self, other, *attrs): for attr in attrs: if attr[:3] in ('_v_', '_p_'): continue c = cmp(getattr(self, attr, None), getattr(other, attr, None)) if c: return c return 0 class Simple(Persistent): def __init__(self, name, **kw): self.__name__ = name self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', *(self.__dict__.keys())) def test_basic_pickling(): """ >>> x = Simple('x', aaa=1, bbb='foo') >>> print_dict(x.__getstate__()) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> f, (c,), state = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Simple' >>> print_dict(state) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.__setstate__({'z': 1}) >>> x.__dict__ {'z': 1} """ class Custom(Simple): def __new__(cls, x, y): r = Persistent.__new__(cls) r.x, r.y = x, y return r def __init__(self, x, y): self.a = 42 def __getnewargs__(self): return self.x, self.y def __getstate__(self): return self.a def __setstate__(self, a): self.a = a def test_pickling_w_overrides(): """ >>> x = Custom('x', 'y') >>> x.a = 99 >>> (f, (c, ax, ay), a) = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Custom' >>> ax, ay, a ('x', 'y', 99) >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class Slotted(Persistent): __slots__ = 's1', 's2', '_p_splat', '_v_eek' def __init__(self, s1, s2): self.s1, self.s2 = s1, s2 self._v_eek = 1 self._p_splat = 2 class SubSlotted(Slotted): __slots__ = 's3', 's4' def __init__(self, s1, s2, s3): Slotted.__init__(self, s1, s2) self.s3 = s3 def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4') def test_pickling_w_slots_only(): """ >>> x = SubSlotted('x', 'y', 'z') >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class SubSubSlotted(SubSlotted): def __init__(self, s1, s2, s3, **kw): SubSlotted.__init__(self, s1, s2, s3) self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4', *(self.__dict__.keys())) def test_pickling_w_slots(): """ >>> x = SubSubSlotted('x', 'y', 'z', aaa=1, bbb='foo') >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ def test_pickling_w_slots_w_empty_dict(): """ >>> x = SubSubSlotted('x', 'y', 'z') >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ from zope.testing.doctest import DocTestSuite import unittest def test_suite(): return unittest.TestSuite(( DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.container/include/persistent/tests/test_persistent.py0000644000175000017500000000160312214017546027175 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.testing import doctest from persistent import Persistent class P(Persistent): def __init__(self): self.x = 0 def inc(self): self.x += 1 def test_suite(): return doctest.DocFileSuite("persistent.txt", globs={"P": P}) zope2.13-2.13.21/source/zope.container/include/persistent/tests/test_PickleCache.py0000644000175000017500000000263112214017546027132 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unit tests for PickleCache $Id: test_PickleCache.py 29896 2005-04-07 04:48:06Z tim_one $ """ class DummyConnection: def setklassstate(self, obj): """Method used by PickleCache.""" def test_delitem(): """ >>> from persistent import PickleCache >>> conn = DummyConnection() >>> cache = PickleCache(conn) >>> del cache[''] Traceback (most recent call last): ... KeyError: '' >>> from persistent import Persistent >>> p = Persistent() >>> p._p_oid = 'foo' >>> p._p_jar = conn >>> cache['foo'] = p >>> del cache['foo'] """ from zope.testing.doctest import DocTestSuite import unittest def test_suite(): return unittest.TestSuite(( DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.container/include/persistent/tests/test_mapping.py0000644000175000017500000001062612214017546026435 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test PersistentMapping """ import unittest l0 = {} l1 = {0:0} l2 = {0:0, 1:1} class MappingTests(unittest.TestCase): def _getTargetClass(self): from persistent.mapping import PersistentMapping return PersistentMapping def test_volatile_attributes_not_persisted(self): # http://www.zope.org/Collectors/Zope/2052 m = self._getTargetClass()() m.foo = 'bar' m._v_baz = 'qux' state = m.__getstate__() self.failUnless('foo' in state) self.failIf('_v_baz' in state) def testTheWorld(self): # Test constructors pm = self._getTargetClass() u = pm() u0 = pm(l0) u1 = pm(l1) u2 = pm(l2) uu = pm(u) uu0 = pm(u0) uu1 = pm(u1) uu2 = pm(u2) class OtherMapping: def __init__(self, initmapping): self.__data = initmapping def items(self): return self.__data.items() v0 = pm(OtherMapping(u0)) vv = pm([(0, 0), (1, 1)]) # Test __repr__ eq = self.assertEqual eq(str(u0), str(l0), "str(u0) == str(l0)") eq(repr(u1), repr(l1), "repr(u1) == repr(l1)") eq(`u2`, `l2`, "`u2` == `l2`") # Test __cmp__ and __len__ def mycmp(a, b): r = cmp(a, b) if r < 0: return -1 if r > 0: return 1 return r all = [l0, l1, l2, u, u0, u1, u2, uu, uu0, uu1, uu2] for a in all: for b in all: eq(mycmp(a, b), mycmp(len(a), len(b)), "mycmp(a, b) == mycmp(len(a), len(b))") # Test __getitem__ for i in range(len(u2)): eq(u2[i], i, "u2[i] == i") # Test get for i in range(len(u2)): eq(u2.get(i), i, "u2.get(i) == i") eq(u2.get(i, 5), i, "u2.get(i, 5) == i") for i in min(u2)-1, max(u2)+1: eq(u2.get(i), None, "u2.get(i) == None") eq(u2.get(i, 5), 5, "u2.get(i, 5) == 5") # Test __setitem__ uu2[0] = 0 uu2[1] = 100 uu2[2] = 200 # Test __delitem__ del uu2[1] del uu2[0] try: del uu2[0] except KeyError: pass else: raise TestFailed("uu2[0] shouldn't be deletable") # Test __contains__ for i in u2: self.failUnless(i in u2, "i in u2") for i in min(u2)-1, max(u2)+1: self.failUnless(i not in u2, "i not in u2") # Test update l = {"a":"b"} u = pm(l) u.update(u2) for i in u: self.failUnless(i in l or i in u2, "i in l or i in u2") for i in l: self.failUnless(i in u, "i in u") for i in u2: self.failUnless(i in u, "i in u") # Test setdefault x = u2.setdefault(0, 5) eq(x, 0, "u2.setdefault(0, 5) == 0") x = u2.setdefault(5, 5) eq(x, 5, "u2.setdefault(5, 5) == 5") self.failUnless(5 in u2, "5 in u2") # Test pop x = u2.pop(1) eq(x, 1, "u2.pop(1) == 1") self.failUnless(1 not in u2, "1 not in u2") try: u2.pop(1) except KeyError: pass else: raise TestFailed("1 should not be poppable from u2") x = u2.pop(1, 7) eq(x, 7, "u2.pop(1, 7) == 7") # Test popitem items = u2.items() key, value = u2.popitem() self.failUnless((key, value) in items, "key, value in items") self.failUnless(key not in u2, "key not in u2") # Test clear u2.clear() eq(u2, {}, "u2 == {}") def test_suite(): return unittest.makeSuite(MappingTests) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.container/include/persistent/tests/testPersistent.py0000644000175000017500000001701612214017546027003 0ustar arnauarnau############################################################################# # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import pickle import time import unittest from persistent import Persistent, GHOST, UPTODATE, CHANGED, STICKY from persistent.cPickleCache import PickleCache from persistent.TimeStamp import TimeStamp from ZODB.utils import p64 class Jar(object): """Testing stub for _p_jar attribute.""" def __init__(self): self.cache = PickleCache(self) self.oid = 1 self.registered = {} def add(self, obj): obj._p_oid = p64(self.oid) self.oid += 1 obj._p_jar = self self.cache[obj._p_oid] = obj def close(self): pass # the following methods must be implemented to be a jar def setklassstate(self): # I don't know what this method does, but the pickle cache # constructor calls it. pass def register(self, obj): self.registered[obj] = 1 def setstate(self, obj): # Trivial setstate() implementation that just re-initializes # the object. This isn't what setstate() is supposed to do, # but it suffices for the tests. obj.__class__.__init__(obj) class P(Persistent): pass class H1(Persistent): def __init__(self): self.n = 0 def __getattr__(self, attr): self.n += 1 return self.n class H2(Persistent): def __init__(self): self.n = 0 def __getattribute__(self, attr): supergetattr = super(H2, self).__getattribute__ try: return supergetattr(attr) except AttributeError: n = supergetattr("n") self.n = n + 1 return n + 1 class PersistenceTest(unittest.TestCase): def setUp(self): self.jar = Jar() def tearDown(self): self.jar.close() def testOidAndJarAttrs(self): obj = P() self.assertEqual(obj._p_oid, None) obj._p_oid = 12 self.assertEqual(obj._p_oid, 12) del obj._p_oid self.jar.add(obj) # Can't change oid of cache object. def deloid(): del obj._p_oid self.assertRaises(ValueError, deloid) def setoid(): obj._p_oid = 12 self.assertRaises(ValueError, setoid) def deloid(): del obj._p_jar self.assertRaises(ValueError, deloid) def setoid(): obj._p_jar = 12 self.assertRaises(ValueError, setoid) def testChangedAndState(self): obj = P() self.jar.add(obj) # The value returned for _p_changed can be one of: # 0 -- it is not changed # 1 -- it is changed # None -- it is a ghost obj.x = 1 self.assertEqual(obj._p_changed, 1) self.assertEqual(obj._p_state, CHANGED) self.assert_(obj in self.jar.registered) obj._p_changed = 0 self.assertEqual(obj._p_changed, 0) self.assertEqual(obj._p_state, UPTODATE) self.jar.registered.clear() obj._p_changed = 1 self.assertEqual(obj._p_changed, 1) self.assertEqual(obj._p_state, CHANGED) self.assert_(obj in self.jar.registered) # setting obj._p_changed to None ghostifies if the # object is in the up-to-date state, but not otherwise. obj._p_changed = None self.assertEqual(obj._p_changed, 1) self.assertEqual(obj._p_state, CHANGED) obj._p_changed = 0 # Now it's a ghost. obj._p_changed = None self.assertEqual(obj._p_changed, None) self.assertEqual(obj._p_state, GHOST) obj = P() self.jar.add(obj) obj._p_changed = 1 # You can transition directly from modified to ghost if # you delete the _p_changed attribute. del obj._p_changed self.assertEqual(obj._p_changed, None) self.assertEqual(obj._p_state, GHOST) def testStateReadonly(self): # make sure we can't write to _p_state; we don't want yet # another way to change state! obj = P() def setstate(value): obj._p_state = value self.assertRaises(TypeError, setstate, GHOST) self.assertRaises(TypeError, setstate, UPTODATE) self.assertRaises(TypeError, setstate, CHANGED) self.assertRaises(TypeError, setstate, STICKY) def testInvalidate(self): obj = P() self.jar.add(obj) self.assertEqual(obj._p_changed, 0) self.assertEqual(obj._p_state, UPTODATE) obj._p_invalidate() self.assertEqual(obj._p_changed, None) self.assertEqual(obj._p_state, GHOST) obj._p_activate() obj.x = 1 obj._p_invalidate() self.assertEqual(obj._p_changed, None) self.assertEqual(obj._p_state, GHOST) def testSerial(self): noserial = "\000" * 8 obj = P() self.assertEqual(obj._p_serial, noserial) def set(val): obj._p_serial = val self.assertRaises(ValueError, set, 1) self.assertRaises(ValueError, set, "0123") self.assertRaises(ValueError, set, "012345678") self.assertRaises(ValueError, set, u"01234567") obj._p_serial = "01234567" del obj._p_serial self.assertEqual(obj._p_serial, noserial) def testMTime(self): obj = P() self.assertEqual(obj._p_mtime, None) t = int(time.time()) ts = TimeStamp(*time.gmtime(t)[:6]) obj._p_serial = repr(ts) self.assertEqual(obj._p_mtime, t) self.assert_(isinstance(obj._p_mtime, float)) def testPicklable(self): obj = P() obj.attr = "test" s = pickle.dumps(obj) obj2 = pickle.loads(s) self.assertEqual(obj.attr, obj2.attr) def testGetattr(self): obj = H1() self.assertEqual(obj.larry, 1) self.assertEqual(obj.curly, 2) self.assertEqual(obj.moe, 3) self.jar.add(obj) obj._p_deactivate() # The simple Jar used for testing re-initializes the object. self.assertEqual(obj.larry, 1) # The getattr hook modified the object, so it should now be # in the changed state. self.assertEqual(obj._p_changed, 1) self.assertEqual(obj._p_state, CHANGED) self.assertEqual(obj.curly, 2) self.assertEqual(obj.moe, 3) def testGetattribute(self): obj = H2() self.assertEqual(obj.larry, 1) self.assertEqual(obj.curly, 2) self.assertEqual(obj.moe, 3) self.jar.add(obj) obj._p_deactivate() # The simple Jar used for testing re-initializes the object. self.assertEqual(obj.larry, 1) # The getattr hook modified the object, so it should now be # in the changed state. self.assertEqual(obj._p_changed, 1) self.assertEqual(obj._p_state, CHANGED) self.assertEqual(obj.curly, 2) self.assertEqual(obj.moe, 3) # TODO: Need to decide how __setattr__ and __delattr__ should work, # then write tests. def test_suite(): return unittest.makeSuite(PersistenceTest) zope2.13-2.13.21/source/zope.container/include/zope.proxy/0000755000175000017500000000000012214017546022157 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/include/zope.proxy/decorator.py0000644000175000017500000000636512214017546024525 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Decorator support Decorators are proxies that are mostly transparent but that may provide additional features. """ __docformat__ = "reStructuredText" from zope.proxy import getProxiedObject, ProxyBase from zope.interface.declarations import ObjectSpecificationDescriptor from zope.interface.declarations import getObjectSpecification from zope.interface.declarations import ObjectSpecification from zope.interface import providedBy class DecoratorSpecificationDescriptor(ObjectSpecificationDescriptor): """Support for interface declarations on decorators >>> from zope.interface import * >>> class I1(Interface): ... pass >>> class I2(Interface): ... pass >>> class I3(Interface): ... pass >>> class I4(Interface): ... pass >>> class D1(SpecificationDecoratorBase): ... implements(I1) >>> class D2(SpecificationDecoratorBase): ... implements(I2) >>> class X(object): ... implements(I3) >>> x = X() >>> directlyProvides(x, I4) Interfaces of X are ordered with the directly-provided interfaces first >>> [interface.getName() for interface in list(providedBy(x))] ['I4', 'I3'] When we decorate objects, what order should the interfaces come in? One could argue that decorators are less specific, so they should come last. >>> [interface.getName() for interface in list(providedBy(D1(x)))] ['I4', 'I3', 'I1'] >>> [interface.getName() for interface in list(providedBy(D2(D1(x))))] ['I4', 'I3', 'I1', 'I2'] SpecificationDecorators also work with old-style classes: >>> class X: ... implements(I3) >>> x = X() >>> directlyProvides(x, I4) >>> [interface.getName() for interface in list(providedBy(x))] ['I4', 'I3'] >>> [interface.getName() for interface in list(providedBy(D1(x)))] ['I4', 'I3', 'I1'] >>> [interface.getName() for interface in list(providedBy(D2(D1(x))))] ['I4', 'I3', 'I1', 'I2'] """ def __get__(self, inst, cls=None): if inst is None: return getObjectSpecification(cls) else: provided = providedBy(getProxiedObject(inst)) # Use type rather than __class__ because inst is a proxy and # will return the proxied object's class. cls = type(inst) return ObjectSpecification(provided, cls) def __set__(self, inst, value): raise TypeError("Can't set __providedBy__ on a decorated object") class SpecificationDecoratorBase(ProxyBase): """Base class for a proxy that provides additional interfaces.""" __providedBy__ = DecoratorSpecificationDescriptor() zope2.13-2.13.21/source/zope.container/include/zope.proxy/proxy.h0000644000175000017500000000361112214017546023512 0ustar arnauarnau#ifndef _proxy_H_ #define _proxy_H_ 1 #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) typedef int Py_ssize_t; #define PY_SSIZE_T_MAX INT_MAX #define PY_SSIZE_T_MIN INT_MIN typedef Py_ssize_t (*lenfunc)(PyObject *); typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t); typedef PyObject *(*ssizessizeargfunc)(PyObject *, Py_ssize_t, Py_ssize_t); typedef int(*ssizeobjargproc)(PyObject *, Py_ssize_t, PyObject *); typedef int(*ssizessizeobjargproc)(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); #endif typedef struct { PyObject_HEAD PyObject *proxy_object; } ProxyObject; #define Proxy_GET_OBJECT(ob) (((ProxyObject *)(ob))->proxy_object) typedef struct { PyTypeObject *proxytype; int (*check)(PyObject *obj); PyObject *(*create)(PyObject *obj); PyObject *(*getobject)(PyObject *proxy); } ProxyInterface; #ifndef PROXY_MODULE /* These are only defined in the public interface, and are not * available within the module implementation. There we use the * classic Python/C API only. */ static ProxyInterface *_proxy_api = NULL; static int Proxy_Import(void) { if (_proxy_api == NULL) { PyObject *m = PyImport_ImportModule("zope.proxy"); if (m != NULL) { PyObject *tmp = PyObject_GetAttrString(m, "_CAPI"); if (tmp != NULL) { if (PyCObject_Check(tmp)) _proxy_api = (ProxyInterface *) PyCObject_AsVoidPtr(tmp); Py_DECREF(tmp); } } } return (_proxy_api == NULL) ? -1 : 0; } #define ProxyType (*_proxy_api->proxytype) #define Proxy_Check(obj) (_proxy_api->check((obj))) #define Proxy_CheckExact(obj) ((obj)->ob_type == ProxyType) #define Proxy_New(obj) (_proxy_api->create((obj))) #define Proxy_GetObject(proxy) (_proxy_api->getobject((proxy))) #endif /* PROXY_MODULE */ #endif /* _proxy_H_ */ zope2.13-2.13.21/source/zope.container/include/zope.proxy/_zope_proxy_proxy.c0000644000175000017500000007066512214017546026157 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ /* * This file is also used as a really extensive macro in * ../container/_zope_container_contained.c. If you need to * change this file, you need to "svn copy" it to ../container/. * * This approach is taken to allow the sources for the two packages * to be compilable when the relative locations of these aren't * related in the same way as they are in a checkout. * * This will be revisited in the future, but works for now. */ #include "Python.h" #include "modsupport.h" #define PROXY_MODULE #include "proxy.h" static PyTypeObject ProxyType; #define Proxy_Check(wrapper) (PyObject_TypeCheck((wrapper), &ProxyType)) static PyObject * empty_tuple = NULL; /* * Slot methods. */ static PyObject * wrap_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *result = NULL; PyObject *object; if (PyArg_UnpackTuple(args, "__new__", 1, 1, &object)) { if (kwds != NULL && PyDict_Size(kwds) != 0) { PyErr_SetString(PyExc_TypeError, "proxy.__new__ does not accept keyword args"); return NULL; } result = PyType_GenericNew(type, args, kwds); if (result != NULL) { ProxyObject *wrapper = (ProxyObject *) result; Py_INCREF(object); wrapper->proxy_object = object; } } return result; } static int wrap_init(PyObject *self, PyObject *args, PyObject *kwds) { int result = -1; PyObject *object; if (PyArg_UnpackTuple(args, "__init__", 1, 1, &object)) { ProxyObject *wrapper = (ProxyObject *)self; if (kwds != NULL && PyDict_Size(kwds) != 0) { PyErr_SetString(PyExc_TypeError, "proxy.__init__ does not accept keyword args"); return -1; } /* If the object in this proxy is not the one we * received in args, replace it with the new one. */ if (wrapper->proxy_object != object) { PyObject *temp = wrapper->proxy_object; Py_INCREF(object); wrapper->proxy_object = object; Py_DECREF(temp); } result = 0; } return result; } static int wrap_traverse(PyObject *self, visitproc visit, void *arg) { PyObject *ob = Proxy_GET_OBJECT(self); if (ob != NULL) return visit(ob, arg); else return 0; } static int wrap_clear(PyObject *self) { ProxyObject *proxy = (ProxyObject *)self; PyObject *temp = proxy->proxy_object; if (temp != NULL) { proxy->proxy_object = NULL; Py_DECREF(temp); } return 0; } static PyObject * wrap_richcompare(PyObject* self, PyObject* other, int op) { if (Proxy_Check(self)) { self = Proxy_GET_OBJECT(self); } else { other = Proxy_GET_OBJECT(other); } return PyObject_RichCompare(self, other, op); } static PyObject * wrap_iter(PyObject *self) { return PyObject_GetIter(Proxy_GET_OBJECT(self)); } static PyObject * wrap_iternext(PyObject *self) { return PyIter_Next(Proxy_GET_OBJECT(self)); } static void wrap_dealloc(PyObject *self) { (void) wrap_clear(self); self->ob_type->tp_free(self); } /* A variant of _PyType_Lookup that doesn't look in ProxyType. * * If argument search_wrappertype is nonzero, we can look in WrapperType. */ PyObject * WrapperType_Lookup(PyTypeObject *type, PyObject *name) { int i, n; PyObject *mro, *res, *base, *dict; /* Look in tp_dict of types in MRO */ mro = type->tp_mro; /* If mro is NULL, the type is either not yet initialized by PyType_Ready(), or already cleared by type_clear(). Either way the safest thing to do is to return NULL. */ if (mro == NULL) return NULL; assert(PyTuple_Check(mro)); n = PyTuple_GET_SIZE(mro) - 1; /* We don't want to look at the last item, which is object. */ for (i = 0; i < n; i++) { base = PyTuple_GET_ITEM(mro, i); if (((PyTypeObject *)base) != &ProxyType) { if (PyClass_Check(base)) dict = ((PyClassObject *)base)->cl_dict; else { assert(PyType_Check(base)); dict = ((PyTypeObject *)base)->tp_dict; } assert(dict && PyDict_Check(dict)); res = PyDict_GetItem(dict, name); if (res != NULL) return res; } } return NULL; } static PyObject * wrap_getattro(PyObject *self, PyObject *name) { PyObject *wrapped; PyObject *descriptor; PyObject *res = NULL; char *name_as_string; int maybe_special_name; #ifdef Py_USING_UNICODE /* The Unicode to string conversion is done here because the existing tp_getattro slots expect a string object as name and we wouldn't want to break those. */ if (PyUnicode_Check(name)) { name = PyUnicode_AsEncodedString(name, NULL, NULL); if (name == NULL) return NULL; } else #endif if (!PyString_Check(name)){ PyErr_SetString(PyExc_TypeError, "attribute name must be string"); return NULL; } else Py_INCREF(name); name_as_string = PyString_AS_STRING(name); wrapped = Proxy_GET_OBJECT(self); if (wrapped == NULL) { PyErr_Format(PyExc_RuntimeError, "object is NULL; requested to get attribute '%s'", name_as_string); goto finally; } maybe_special_name = name_as_string[0] == '_' && name_as_string[1] == '_'; if (!(maybe_special_name && strcmp(name_as_string, "__class__") == 0)) { descriptor = WrapperType_Lookup(self->ob_type, name); if (descriptor != NULL) { if (PyType_HasFeature(descriptor->ob_type, Py_TPFLAGS_HAVE_CLASS) && descriptor->ob_type->tp_descr_get != NULL) { if (descriptor->ob_type->tp_descr_set == NULL) { res = PyObject_GetAttr(wrapped, name); if (res != NULL) goto finally; if (PyErr_ExceptionMatches(PyExc_AttributeError)) PyErr_Clear(); else goto finally; } res = descriptor->ob_type->tp_descr_get( descriptor, self, (PyObject *)self->ob_type); } else { Py_INCREF(descriptor); res = descriptor; } goto finally; } } res = PyObject_GetAttr(wrapped, name); finally: Py_DECREF(name); return res; } static int wrap_setattro(PyObject *self, PyObject *name, PyObject *value) { PyObject *wrapped; PyObject *descriptor; int res = -1; #ifdef Py_USING_UNICODE /* The Unicode to string conversion is done here because the existing tp_setattro slots expect a string object as name and we wouldn't want to break those. */ if (PyUnicode_Check(name)) { name = PyUnicode_AsEncodedString(name, NULL, NULL); if (name == NULL) return -1; } else #endif if (!PyString_Check(name)){ PyErr_SetString(PyExc_TypeError, "attribute name must be string"); return -1; } else Py_INCREF(name); descriptor = WrapperType_Lookup(self->ob_type, name); if (descriptor != NULL && PyType_HasFeature(descriptor->ob_type, Py_TPFLAGS_HAVE_CLASS) && descriptor->ob_type->tp_descr_set != NULL) { res = descriptor->ob_type->tp_descr_set(descriptor, self, value); goto finally; } wrapped = Proxy_GET_OBJECT(self); if (wrapped == NULL) { PyErr_Format(PyExc_RuntimeError, "object is NULL; requested to set attribute '%s'", PyString_AS_STRING(name)); goto finally; } res = PyObject_SetAttr(wrapped, name, value); finally: Py_DECREF(name); return res; } static int wrap_print(PyObject *wrapper, FILE *fp, int flags) { return PyObject_Print(Proxy_GET_OBJECT(wrapper), fp, flags); } static PyObject * wrap_str(PyObject *wrapper) { return PyObject_Str(Proxy_GET_OBJECT(wrapper)); } static PyObject * wrap_repr(PyObject *wrapper) { return PyObject_Repr(Proxy_GET_OBJECT(wrapper)); } static int wrap_compare(PyObject *wrapper, PyObject *v) { return PyObject_Compare(Proxy_GET_OBJECT(wrapper), v); } static long wrap_hash(PyObject *self) { return PyObject_Hash(Proxy_GET_OBJECT(self)); } static PyObject * wrap_call(PyObject *self, PyObject *args, PyObject *kw) { if (kw) return PyEval_CallObjectWithKeywords(Proxy_GET_OBJECT(self), args, kw); else return PyObject_CallObject(Proxy_GET_OBJECT(self), args); } /* * Number methods */ /* * Number methods. */ static PyObject * call_int(PyObject *self) { PyNumberMethods *nb = self->ob_type->tp_as_number; if (nb == NULL || nb->nb_int == NULL) { PyErr_SetString(PyExc_TypeError, "object can't be converted to int"); return NULL; } return nb->nb_int(self); } static PyObject * call_long(PyObject *self) { PyNumberMethods *nb = self->ob_type->tp_as_number; if (nb == NULL || nb->nb_long == NULL) { PyErr_SetString(PyExc_TypeError, "object can't be converted to long"); return NULL; } return nb->nb_long(self); } static PyObject * call_float(PyObject *self) { PyNumberMethods *nb = self->ob_type->tp_as_number; if (nb == NULL || nb->nb_float== NULL) { PyErr_SetString(PyExc_TypeError, "object can't be converted to float"); return NULL; } return nb->nb_float(self); } static PyObject * call_oct(PyObject *self) { PyNumberMethods *nb = self->ob_type->tp_as_number; if (nb == NULL || nb->nb_oct== NULL) { PyErr_SetString(PyExc_TypeError, "object can't be converted to oct"); return NULL; } return nb->nb_oct(self); } static PyObject * call_hex(PyObject *self) { PyNumberMethods *nb = self->ob_type->tp_as_number; if (nb == NULL || nb->nb_hex == NULL) { PyErr_SetString(PyExc_TypeError, "object can't be converted to hex"); return NULL; } return nb->nb_hex(self); } static PyObject * call_ipow(PyObject *self, PyObject *other) { /* PyNumber_InPlacePower has three args. How silly. :-) */ return PyNumber_InPlacePower(self, other, Py_None); } typedef PyObject *(*function1)(PyObject *); static PyObject * check1(ProxyObject *self, char *opname, function1 operation) { PyObject *result = NULL; result = operation(Proxy_GET_OBJECT(self)); #if 0 if (result != NULL) /* ??? create proxy for result? */ ; #endif return result; } static PyObject * check2(PyObject *self, PyObject *other, char *opname, char *ropname, binaryfunc operation) { PyObject *result = NULL; PyObject *object; if (Proxy_Check(self)) { object = Proxy_GET_OBJECT(self); result = operation(object, other); } else if (Proxy_Check(other)) { object = Proxy_GET_OBJECT(other); result = operation(self, object); } else { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } #if 0 if (result != NULL) /* ??? create proxy for result? */ ; #endif return result; } static PyObject * check2i(ProxyObject *self, PyObject *other, char *opname, binaryfunc operation) { PyObject *result = NULL; PyObject *object = Proxy_GET_OBJECT(self); result = operation(object, other); if (result == object) { /* If the operation was really carried out inplace, don't create a new proxy, but use the old one. */ Py_INCREF(self); Py_DECREF(object); result = (PyObject *)self; } #if 0 else if (result != NULL) /* ??? create proxy for result? */ ; #endif return result; } #define UNOP(NAME, CALL) \ static PyObject *wrap_##NAME(PyObject *self) \ { return check1((ProxyObject *)self, "__"#NAME"__", CALL); } #define BINOP(NAME, CALL) \ static PyObject *wrap_##NAME(PyObject *self, PyObject *other) \ { return check2(self, other, "__"#NAME"__", "__r"#NAME"__", CALL); } #define INPLACE(NAME, CALL) \ static PyObject *wrap_i##NAME(PyObject *self, PyObject *other) \ { return check2i((ProxyObject *)self, other, "__i"#NAME"__", CALL); } BINOP(add, PyNumber_Add) BINOP(sub, PyNumber_Subtract) BINOP(mul, PyNumber_Multiply) BINOP(div, PyNumber_Divide) BINOP(mod, PyNumber_Remainder) BINOP(divmod, PyNumber_Divmod) static PyObject * wrap_pow(PyObject *self, PyObject *other, PyObject *modulus) { PyObject *result = NULL; PyObject *object; if (Proxy_Check(self)) { object = Proxy_GET_OBJECT(self); result = PyNumber_Power(object, other, modulus); } else if (Proxy_Check(other)) { object = Proxy_GET_OBJECT(other); result = PyNumber_Power(self, object, modulus); } else if (modulus != NULL && Proxy_Check(modulus)) { object = Proxy_GET_OBJECT(modulus); result = PyNumber_Power(self, other, modulus); } else { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } return result; } BINOP(lshift, PyNumber_Lshift) BINOP(rshift, PyNumber_Rshift) BINOP(and, PyNumber_And) BINOP(xor, PyNumber_Xor) BINOP(or, PyNumber_Or) static int wrap_coerce(PyObject **p_self, PyObject **p_other) { PyObject *self = *p_self; PyObject *other = *p_other; PyObject *object; PyObject *left; PyObject *right; int r; assert(Proxy_Check(self)); object = Proxy_GET_OBJECT(self); left = object; right = other; r = PyNumber_CoerceEx(&left, &right); if (r != 0) return r; /* Now left and right have been INCREF'ed. Any new value that comes out is proxied; any unchanged value is left unchanged. */ if (left == object) { /* Keep the old proxy */ Py_INCREF(self); Py_DECREF(left); left = self; } #if 0 else { /* ??? create proxy for left? */ } if (right != other) { /* ??? create proxy for right? */ } #endif *p_self = left; *p_other = right; return 0; } UNOP(neg, PyNumber_Negative) UNOP(pos, PyNumber_Positive) UNOP(abs, PyNumber_Absolute) UNOP(invert, PyNumber_Invert) UNOP(int, call_int) UNOP(long, call_long) UNOP(float, call_float) UNOP(oct, call_oct) UNOP(hex, call_hex) INPLACE(add, PyNumber_InPlaceAdd) INPLACE(sub, PyNumber_InPlaceSubtract) INPLACE(mul, PyNumber_InPlaceMultiply) INPLACE(div, PyNumber_InPlaceDivide) INPLACE(mod, PyNumber_InPlaceRemainder) INPLACE(pow, call_ipow) INPLACE(lshift, PyNumber_InPlaceLshift) INPLACE(rshift, PyNumber_InPlaceRshift) INPLACE(and, PyNumber_InPlaceAnd) INPLACE(xor, PyNumber_InPlaceXor) INPLACE(or, PyNumber_InPlaceOr) BINOP(floordiv, PyNumber_FloorDivide) BINOP(truediv, PyNumber_TrueDivide) INPLACE(floordiv, PyNumber_InPlaceFloorDivide) INPLACE(truediv, PyNumber_InPlaceTrueDivide) static int wrap_nonzero(PyObject *self) { return PyObject_IsTrue(Proxy_GET_OBJECT(self)); } /* * Sequence methods */ static Py_ssize_t wrap_length(PyObject *self) { return PyObject_Length(Proxy_GET_OBJECT(self)); } static PyObject * wrap_slice(PyObject *self, Py_ssize_t start, Py_ssize_t end) { PyObject *obj = Proxy_GET_OBJECT(self); if (PyList_Check(obj)) { return PyList_GetSlice(obj, start, end); } else if (PyTuple_Check(obj)) { return PyTuple_GetSlice(obj, start, end); } else { return PySequence_GetSlice(obj, start, end); } } static int wrap_ass_slice(PyObject *self, Py_ssize_t i, Py_ssize_t j, PyObject *value) { PyObject *obj = Proxy_GET_OBJECT(self); if (PyList_Check(obj)) { return PyList_SetSlice(obj, i, j, value); } else { return PySequence_SetSlice(obj, i, j, value); } } static int wrap_contains(PyObject *self, PyObject *value) { return PySequence_Contains(Proxy_GET_OBJECT(self), value); } /* * Mapping methods */ static PyObject * wrap_getitem(PyObject *wrapper, PyObject *v) { return PyObject_GetItem(Proxy_GET_OBJECT(wrapper), v); } static int wrap_setitem(PyObject *self, PyObject *key, PyObject *value) { if (value == NULL) return PyObject_DelItem(Proxy_GET_OBJECT(self), key); else return PyObject_SetItem(Proxy_GET_OBJECT(self), key, value); } /* * Normal methods */ static char reduce__doc__[] = "__reduce__()\n" "Raise an exception; this prevents proxies from being picklable by\n" "default, even if the underlying object is picklable."; static PyObject * wrap_reduce(PyObject *self) { PyObject *pickle_error = NULL; PyObject *pickle = PyImport_ImportModule("pickle"); if (pickle == NULL) PyErr_Clear(); else { pickle_error = PyObject_GetAttrString(pickle, "PicklingError"); if (pickle_error == NULL) PyErr_Clear(); } if (pickle_error == NULL) { pickle_error = PyExc_RuntimeError; Py_INCREF(pickle_error); } PyErr_SetString(pickle_error, "proxy instances cannot be pickled"); Py_DECREF(pickle_error); return NULL; } static PyNumberMethods wrap_as_number = { wrap_add, /* nb_add */ wrap_sub, /* nb_subtract */ wrap_mul, /* nb_multiply */ wrap_div, /* nb_divide */ wrap_mod, /* nb_remainder */ wrap_divmod, /* nb_divmod */ wrap_pow, /* nb_power */ wrap_neg, /* nb_negative */ wrap_pos, /* nb_positive */ wrap_abs, /* nb_absolute */ wrap_nonzero, /* nb_nonzero */ wrap_invert, /* nb_invert */ wrap_lshift, /* nb_lshift */ wrap_rshift, /* nb_rshift */ wrap_and, /* nb_and */ wrap_xor, /* nb_xor */ wrap_or, /* nb_or */ wrap_coerce, /* nb_coerce */ wrap_int, /* nb_int */ wrap_long, /* nb_long */ wrap_float, /* nb_float */ wrap_oct, /* nb_oct */ wrap_hex, /* nb_hex */ /* Added in release 2.0 */ /* These require the Py_TPFLAGS_HAVE_INPLACEOPS flag */ wrap_iadd, /* nb_inplace_add */ wrap_isub, /* nb_inplace_subtract */ wrap_imul, /* nb_inplace_multiply */ wrap_idiv, /* nb_inplace_divide */ wrap_imod, /* nb_inplace_remainder */ (ternaryfunc)wrap_ipow, /* nb_inplace_power */ wrap_ilshift, /* nb_inplace_lshift */ wrap_irshift, /* nb_inplace_rshift */ wrap_iand, /* nb_inplace_and */ wrap_ixor, /* nb_inplace_xor */ wrap_ior, /* nb_inplace_or */ /* Added in release 2.2 */ /* These require the Py_TPFLAGS_HAVE_CLASS flag */ wrap_floordiv, /* nb_floor_divide */ wrap_truediv, /* nb_true_divide */ wrap_ifloordiv, /* nb_inplace_floor_divide */ wrap_itruediv, /* nb_inplace_true_divide */ }; static PySequenceMethods wrap_as_sequence = { wrap_length, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ wrap_slice, /* sq_slice */ 0, /* sq_ass_item */ wrap_ass_slice, /* sq_ass_slice */ wrap_contains, /* sq_contains */ }; static PyMappingMethods wrap_as_mapping = { wrap_length, /* mp_length */ wrap_getitem, /* mp_subscript */ wrap_setitem, /* mp_ass_subscript */ }; static PyMethodDef wrap_methods[] = { {"__reduce__", (PyCFunction)wrap_reduce, METH_NOARGS, reduce__doc__}, {NULL, NULL}, }; /* * Note that the numeric methods are not supported. This is primarily * because of the way coercion-less operations are performed with * new-style numbers; since we can't tell which side of the operation * is 'self', we can't ensure we'd unwrap the right thing to perform * the actual operation. We also can't afford to just unwrap both * sides the way weakrefs do, since we don't know what semantics will * be associated with the wrapper itself. */ statichere PyTypeObject ProxyType = { PyObject_HEAD_INIT(NULL) /* PyObject_HEAD_INIT(&PyType_Type) */ 0, "zope.proxy.ProxyBase", sizeof(ProxyObject), 0, wrap_dealloc, /* tp_dealloc */ wrap_print, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ wrap_compare, /* tp_compare */ wrap_repr, /* tp_repr */ &wrap_as_number, /* tp_as_number */ &wrap_as_sequence, /* tp_as_sequence */ &wrap_as_mapping, /* tp_as_mapping */ wrap_hash, /* tp_hash */ wrap_call, /* tp_call */ wrap_str, /* tp_str */ wrap_getattro, /* tp_getattro */ wrap_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ wrap_traverse, /* tp_traverse */ wrap_clear, /* tp_clear */ wrap_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ wrap_iter, /* tp_iter */ wrap_iternext, /* tp_iternext */ wrap_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ wrap_init, /* tp_init */ 0, /* tp_alloc */ wrap_new, /* tp_new */ 0, /*_PyObject_GC_Del,*/ /* tp_free */ }; static PyObject * create_proxy(PyObject *object) { PyObject *result = NULL; PyObject *args; args = PyTuple_New(1); if (args != NULL) { Py_INCREF(object); PyTuple_SET_ITEM(args, 0, object); result = PyObject_CallObject((PyObject *)&ProxyType, args); Py_DECREF(args); } return result; } static int api_check(PyObject *obj) { return obj ? Proxy_Check(obj) : 0; } static PyObject * api_create(PyObject *object) { if (object == NULL) { PyErr_SetString(PyExc_ValueError, "cannot create proxy around NULL"); return NULL; } return create_proxy(object); } static PyObject * api_getobject(PyObject *proxy) { if (proxy == NULL) { PyErr_SetString(PyExc_RuntimeError, "cannot pass NULL to ProxyAPI.getobject()"); return NULL; } if (Proxy_Check(proxy)) return Proxy_GET_OBJECT(proxy); else { PyErr_Format(PyExc_TypeError, "expected proxy object, got %s", proxy->ob_type->tp_name); return NULL; } } static ProxyInterface wrapper_capi = { &ProxyType, api_check, api_create, api_getobject, }; static PyObject *api_object = NULL; static char getobject__doc__[] = "getProxiedObject(proxy) --> object\n" "\n" "Get the underlying object for proxy, or the object itself, if it is\n" "not a proxy."; static PyObject * wrapper_getobject(PyObject *unused, PyObject *obj) { if (Proxy_Check(obj)) obj = Proxy_GET_OBJECT(obj); if (obj == NULL) obj = Py_None; Py_INCREF(obj); return obj; } static char setobject__doc__[] = "setProxiedObject(proxy, object) --> object\n" "\n" "Set the underlying object for proxy, returning the old proxied object.\n" "Raises TypeError if proxy is not a proxy.\n"; static PyObject * wrapper_setobject(PyObject *unused, PyObject *args) { PyObject *proxy; PyObject *object; PyObject *result = NULL; if (PyArg_ParseTuple(args, "O!O:setProxiedObject", &ProxyType, &proxy, &object)) { result = Proxy_GET_OBJECT(proxy); Py_INCREF(object); ((ProxyObject *) proxy)->proxy_object = object; } return result; } static char isProxy__doc__[] = "Check whether the given object is a proxy\n" "\n" "If proxytype is not None, checkes whether the object is\n" "proxied by the given proxytype.\n" ; static PyObject * wrapper_isProxy(PyObject *unused, PyObject *args) { PyObject *obj, *result; PyTypeObject *proxytype=&ProxyType; if (! PyArg_ParseTuple(args, "O|O!:isProxy", &obj, &PyType_Type, &proxytype) ) return NULL; while (obj && Proxy_Check(obj)) { if (PyObject_TypeCheck(obj, proxytype)) { result = Py_True; Py_INCREF(result); return result; } obj = Proxy_GET_OBJECT(obj); } result = Py_False; Py_INCREF(result); return result; } static char removeAllProxies__doc__[] = "removeAllProxies(proxy) --> object\n" "\n" "Get the proxied object with no proxies\n" "\n" "If obj is not a proxied object, return obj.\n" "\n" "The returned object has no proxies.\n" ; static PyObject * wrapper_removeAllProxies(PyObject *unused, PyObject *obj) { while (obj && Proxy_Check(obj)) obj = Proxy_GET_OBJECT(obj); if (obj == NULL) obj = Py_None; Py_INCREF(obj); return obj; } static char sameProxiedObjects__doc__[] = "Check whether two objects are the same or proxies of the same object"; static PyObject * wrapper_sameProxiedObjects(PyObject *unused, PyObject *args) { PyObject *ob1, *ob2; if (! PyArg_ParseTuple(args, "OO:sameProxiedObjects", &ob1, &ob2)) return NULL; while (ob1 && Proxy_Check(ob1)) ob1 = Proxy_GET_OBJECT(ob1); while (ob2 && Proxy_Check(ob2)) ob2 = Proxy_GET_OBJECT(ob2); if (ob1 == ob2) ob1 = Py_True; else ob1 = Py_False; Py_INCREF(ob1); return ob1; } static char queryProxy__doc__[] = "Look for a proxy of the given type around the object\n" "\n" "If no such proxy can be found, return the default.\n" ; static PyObject * wrapper_queryProxy(PyObject *unused, PyObject *args) { PyObject *obj, *result=Py_None; PyTypeObject *proxytype=&ProxyType; if (! PyArg_ParseTuple(args, "O|O!O:queryProxy", &obj, &PyType_Type, &proxytype, &result) ) return NULL; while (obj && Proxy_Check(obj)) { if (PyObject_TypeCheck(obj, proxytype)) { Py_INCREF(obj); return obj; } obj = Proxy_GET_OBJECT(obj); } Py_INCREF(result); return result; } static char queryInnerProxy__doc__[] = "Look for the inner-most proxy of the given type around the object\n" "\n" "If no such proxy can be found, return the default.\n" "\n" "If there is such a proxy, return the inner-most one.\n" ; static PyObject * wrapper_queryInnerProxy(PyObject *unused, PyObject *args) { PyObject *obj, *result=Py_None; PyTypeObject *proxytype=&ProxyType; if (! PyArg_ParseTuple(args, "O|O!O:queryInnerProxy", &obj, &PyType_Type, &proxytype, &result) ) return NULL; while (obj && Proxy_Check(obj)) { if (PyObject_TypeCheck(obj, proxytype)) result = obj; obj = Proxy_GET_OBJECT(obj); } Py_INCREF(result); return result; } static char module___doc__[] = "Association between an object, a context object, and a dictionary.\n\ \n\ The context object and dictionary give additional context information\n\ associated with a reference to the basic object. The wrapper objects\n\ act as proxies for the original object."; static PyMethodDef module_functions[] = { {"getProxiedObject", wrapper_getobject, METH_O, getobject__doc__}, {"setProxiedObject", wrapper_setobject, METH_VARARGS, setobject__doc__}, {"isProxy", wrapper_isProxy, METH_VARARGS, isProxy__doc__}, {"sameProxiedObjects", wrapper_sameProxiedObjects, METH_VARARGS, sameProxiedObjects__doc__}, {"queryProxy", wrapper_queryProxy, METH_VARARGS, queryProxy__doc__}, {"queryInnerProxy", wrapper_queryInnerProxy, METH_VARARGS, queryInnerProxy__doc__}, {"removeAllProxies", wrapper_removeAllProxies, METH_O, removeAllProxies__doc__}, {NULL} }; void init_zope_proxy_proxy(void) { PyObject *m = Py_InitModule3("_zope_proxy_proxy", module_functions, module___doc__); if (m == NULL) return; if (empty_tuple == NULL) empty_tuple = PyTuple_New(0); ProxyType.tp_free = _PyObject_GC_Del; if (PyType_Ready(&ProxyType) < 0) return; Py_INCREF(&ProxyType); PyModule_AddObject(m, "ProxyBase", (PyObject *)&ProxyType); if (api_object == NULL) { api_object = PyCObject_FromVoidPtr(&wrapper_capi, NULL); if (api_object == NULL) return; } Py_INCREF(api_object); PyModule_AddObject(m, "_CAPI", api_object); } zope2.13-2.13.21/source/zope.container/include/zope.proxy/interfaces.py0000644000175000017500000000414312214017546024656 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Proxy-related interfaces. """ from zope.interface import Interface class IProxyIntrospection(Interface): """Provides methods for indentifying proxies and extracting proxied objects """ def isProxy(obj, proxytype=None): """Check whether the given object is a proxy If proxytype is not None, checkes whether the object is proxied by the given proxytype. """ def sameProxiedObjects(ob1, ob2): """Check whether ob1 and ob2 are the same or proxies of the same object """ def getProxiedObject(obj): """Get the proxied Object If the object isn't proxied, then just return the object. """ def setProxiedObject(ob1, ob2): """Set the underlying object for ob1 to ob2, returning the old object. Raises TypeError if ob1 is not a proxy. """ def removeAllProxies(obj): """Get the proxied object with no proxies If obj is not a proxied object, return obj. The returned object has no proxies. """ def queryProxy(obj, proxytype, default=None): """Look for a proxy of the given type around the object If no such proxy can be found, return the default. """ def queryInnerProxy(obj, proxytype, default=None): """Look for the inner-most proxy of the given type around the object If no such proxy can be found, return the default. If there is such a proxy, return the inner-most one. """ zope2.13-2.13.21/source/zope.container/include/zope.proxy/__init__.py0000644000175000017500000000216612214017546024275 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """More convenience functions for dealing with proxies. """ from zope.interface import moduleProvides from zope.proxy.interfaces import IProxyIntrospection from zope.proxy._zope_proxy_proxy import * from zope.proxy._zope_proxy_proxy import _CAPI moduleProvides(IProxyIntrospection) __all__ = tuple(IProxyIntrospection) def ProxyIterator(p): yield p while isProxy(p): p = getProxiedObject(p) yield p def non_overridable(func): return property(lambda self: func.__get__(self)) zope2.13-2.13.21/source/zope.container/include/zope.proxy/tests/0000755000175000017500000000000012214017546023321 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/include/zope.proxy/tests/test_proxy.py0000644000175000017500000004503612214017546026123 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test base proxy class. """ from doctest import DocTestSuite import pickle import sys import unittest from zope.proxy import ProxyBase import zope.proxy class Thing: """This class is expected to be a classic class.""" class Comparable(object): def __init__(self, value): self.value = value def __eq__(self, other): if hasattr(other, "value"): other = other.value return self.value == other def __ne__(self, other): return not self.__eq__(other) def __lt__(self, other): if hasattr(other, "value"): other = other.value return self.value < other def __ge__(self, other): return not self.__lt__(other) def __le__(self, other): if hasattr(other, "value"): other = other.value return self.value <= other def __gt__(self, other): return not self.__le__(other) def __repr__(self): return "" % self.value class ProxyTestCase(unittest.TestCase): proxy_class = ProxyBase def setUp(self): self.x = Thing() self.p = self.new_proxy(self.x) def new_proxy(self, o): return self.proxy_class(o) def test_constructor(self): o = object() self.assertRaises(TypeError, self.proxy_class, o, o) self.assertRaises(TypeError, self.proxy_class, o, key='value') self.assertRaises(TypeError, self.proxy_class, key='value') def test_subclass_constructor(self): class MyProxy(self.proxy_class): def __new__(cls, *args, **kwds): return super(MyProxy, cls).__new__(cls, *args, **kwds) def __init__(self, *args, **kwds): super(MyProxy, self).__init__(*args, **kwds) o1 = object() o2 = object() o = MyProxy((o1, o2)) self.assertEquals(o1, o[0]) self.assertEquals(o2, o[1]) self.assertRaises(TypeError, MyProxy, o1, o2) self.assertRaises(TypeError, MyProxy, o1, key='value') self.assertRaises(TypeError, MyProxy, key='value') # Check that are passed to __init__() overrides what's passed # to __new__(). class MyProxy2(self.proxy_class): def __new__(cls, *args, **kwds): return super(MyProxy2, cls).__new__(cls, 'value') p = MyProxy2('splat!') self.assertEquals(list(p), list('splat!')) class MyProxy3(MyProxy2): def __init__(self, arg): if list(self) != list('value'): raise AssertionError("list(self) != list('value')") super(MyProxy3, self).__init__('another') p = MyProxy3('notused') self.assertEquals(list(p), list('another')) def test_proxy_attributes(self): o = Thing() o.foo = 1 w = self.new_proxy(o) self.assert_(w.foo == 1) def test___class__(self): o = object() w = self.new_proxy(o) self.assert_(w.__class__ is o.__class__) def test_pickle_prevention(self): w = self.new_proxy(Thing()) self.assertRaises(pickle.PicklingError, pickle.dumps, w) def test_proxy_equality(self): w = self.new_proxy('foo') self.assertEquals(w, 'foo') o1 = Comparable(1) o2 = Comparable(1.0) o3 = Comparable("splat!") w1 = self.new_proxy(o1) w2 = self.new_proxy(o2) w3 = self.new_proxy(o3) self.assertEquals(o1, w1) self.assertEquals(o1, w2) self.assertEquals(o2, w1) self.assertEquals(w1, o2) self.assertEquals(w2, o1) self.assertNotEquals(o3, w1) self.assertNotEquals(w1, o3) self.assertNotEquals(w3, o1) self.assertNotEquals(o1, w3) def test_proxy_ordering_lt(self): o1 = Comparable(1) o2 = Comparable(2.0) w1 = self.new_proxy(o1) w2 = self.new_proxy(o2) self.assert_(w1 < w2) self.assert_(w1 <= w2) self.assert_(o1 < w2) self.assert_(o1 <= w2) self.assert_(w1 < o2) self.assert_(w2 <= o2) def test_proxy_callable(self): w = self.new_proxy({}.get) self.assert_(callable(w)) def test_proxy_item_protocol(self): w = self.new_proxy({}) self.assertRaises(KeyError, lambda: w[1]) w[1] = 'a' self.assertEquals(w[1], 'a') del w[1] self.assertRaises(KeyError, lambda: w[1]) def del_w_1(): del w[1] self.assertRaises(KeyError, del_w_1) def test_wrapped_iterable(self): a = [1, 2, 3] b = [] for x in self.new_proxy(a): b.append(x) self.assertEquals(a, b) def test_iteration_over_proxy(self): # Wrap an iterator before starting iteration. # PyObject_GetIter() will still be called on the proxy. a = [1, 2, 3] b = [] for x in self.new_proxy(iter(a)): b.append(x) self.assertEquals(a, b) t = tuple(self.new_proxy(iter(a))) self.assertEquals(t, (1, 2, 3)) def test_iteration_using_proxy(self): # Wrap an iterator within the iteration protocol, expecting it # still to work. PyObject_GetIter() will not be called on the # proxy, so the tp_iter slot won't unwrap it. class Iterable(object): def __init__(self, test, data): self.test = test self.data = data def __iter__(self): return self.test.new_proxy(iter(self.data)) a = [1, 2, 3] b = [] for x in Iterable(self, a): b.append(x) self.assertEquals(a, b) def test_bool_wrapped_None(self): w = self.new_proxy(None) self.assertEquals(not w, 1) # Numeric ops. unops = [ "-x", "+x", "abs(x)", "~x", "int(x)", "long(x)", "float(x)", ] def test_unops(self): P = self.new_proxy for expr in self.unops: x = 1 y = eval(expr) x = P(1) z = eval(expr) self.assertEqual(z, y, "x=%r; expr=%r" % (x, expr)) def test_odd_unops(self): # unops that don't return a proxy P = self.new_proxy for func in hex, oct, lambda x: not x: self.assertEqual(func(P(100)), func(100)) binops = [ "x+y", "x-y", "x*y", "x/y", "divmod(x, y)", "x**y", "x//y", "x<>y", "x&y", "x|y", "x^y", ] def test_binops(self): P = self.new_proxy for expr in self.binops: first = 1 for x in [1, P(1)]: for y in [2, P(2)]: if first: z = eval(expr) first = 0 else: self.assertEqual(eval(expr), z, "x=%r; y=%r; expr=%r" % (x, y, expr)) def test_inplace(self): # TODO: should test all inplace operators... P = self.new_proxy pa = P(1) pa += 2 self.assertEqual(pa, 3) a = [1, 2, 3] pa = qa = P(a) pa += [4, 5, 6] self.failUnless(pa is qa) self.assertEqual(a, [1, 2, 3, 4, 5, 6]) pa = P(2) pa **= 2 self.assertEqual(pa, 4) def test_coerce(self): P = self.new_proxy # Before 2.3, coerce() of two proxies returns them unchanged fixed_coerce = sys.version_info >= (2, 3, 0) x = P(1) y = P(2) a, b = coerce(x, y) self.failUnless(a is x and b is y) x = P(1) y = P(2.1) a, b = coerce(x, y) self.failUnless(a == 1.0) self.failUnless(b is y) if fixed_coerce: self.failUnless(a.__class__ is float, a.__class__) x = P(1.1) y = P(2) a, b = coerce(x, y) self.failUnless(a is x) self.failUnless(b == 2.0) if fixed_coerce: self.failUnless(b.__class__ is float, b.__class__) x = P(1) y = 2 a, b = coerce(x, y) self.failUnless(a is x) self.failUnless(b is y) x = P(1) y = 2.1 a, b = coerce(x, y) self.failUnless(a.__class__ is float, a.__class__) self.failUnless(b is y) x = P(1.1) y = 2 a, b = coerce(x, y) self.failUnless(a is x) self.failUnless(b.__class__ is float, b.__class__) x = 1 y = P(2) a, b = coerce(x, y) self.failUnless(a is x) self.failUnless(b is y) x = 1.1 y = P(2) a, b = coerce(x, y) self.failUnless(a is x) self.failUnless(b.__class__ is float, b.__class__) x = 1 y = P(2.1) a, b = coerce(x, y) self.failUnless(a.__class__ is float, a.__class__) self.failUnless(b is y) def test_getslice(self): # Lists have special slicing bahvior. pList = self.new_proxy([1, 2]) self.assertEqual(pList[-1:], [2]) self.assertEqual(pList[-2:], [1, 2]) self.assertEqual(pList[-3:], [1, 2]) # Tuples also have special slicing behavior. pTuple = self.new_proxy((1, 2)) self.assertEqual(pTuple[-1:], (2,)) self.assertEqual(pTuple[-2:], (1, 2)) self.assertEqual(pTuple[-3:], (1, 2)) # This behavior should be true for all list- and tuple-derived classes. class DerivedList(list): def __getslice__(self, start, end, step=None): return (start, end, step) pList = self.new_proxy(DerivedList([1, 2])) self.assertEqual(pList[-1:], [2]) self.assertEqual(pList[-2:], [1, 2]) self.assertEqual(pList[-3:], [1, 2]) # Another sort of sequence has a different slicing interpretation. class Slicer(object): def __len__(self): return 2 def __getslice__(self, start, end, step=None): return (start, end, step) pSlicer = self.new_proxy(Slicer()) self.assertEqual(pSlicer[-1:][0], 1) self.assertEqual(pSlicer[-2:][0], 0) # Note that for non-lists and non-tuples the slice is computed # differently self.assertEqual(pSlicer[-3:][0], 1) def test_setslice(self): # Lists have special slicing bahvior for assignment as well. pList = self.new_proxy([1, 2]) pList[-1:] = [3, 4] self.assertEqual(pList, [1, 3, 4]) pList = self.new_proxy([1, 2]) pList[-2:] = [3, 4] self.assertEqual(pList, [3, 4]) pList = self.new_proxy([1, 2]) pList[-3:] = [3, 4] self.assertEqual(pList, [3, 4]) # This behavior should be true for all list-derived classes. class DerivedList(list): pass pList = self.new_proxy(DerivedList([1, 2])) pList[-1:] = [3, 4] self.assertEqual(pList, [1, 3, 4]) pList = self.new_proxy(DerivedList([1, 2])) pList[-2:] = [3, 4] self.assertEqual(pList, [3, 4]) pList = self.new_proxy(DerivedList([1, 2])) pList[-3:] = [3, 4] self.assertEqual(pList, [3, 4]) def test_isProxy(): """ >>> from zope.proxy import ProxyBase, isProxy >>> class P1(ProxyBase): ... pass >>> class P2(ProxyBase): ... pass >>> class C(object): ... pass >>> c = C() >>> int(isProxy(c)) 0 >>> p = P1(c) >>> int(isProxy(p)) 1 >>> int(isProxy(p, P1)) 1 >>> int(isProxy(p, P2)) 0 >>> p = P2(p) >>> int(isProxy(p, P1)) 1 >>> int(isProxy(p, P2)) 1 """ def test_getProxiedObject(): """ >>> from zope.proxy import ProxyBase, getProxiedObject >>> class C(object): ... pass >>> c = C() >>> int(getProxiedObject(c) is c) 1 >>> p = ProxyBase(c) >>> int(getProxiedObject(p) is c) 1 >>> p2 = ProxyBase(p) >>> int(getProxiedObject(p2) is p) 1 """ def test_ProxyIterator(): """ >>> from zope.proxy import ProxyBase, ProxyIterator >>> class C(object): ... pass >>> c = C() >>> p1 = ProxyBase(c) >>> class P(ProxyBase): ... pass >>> p2 = P(p1) >>> p3 = ProxyBase(p2) >>> list(ProxyIterator(p3)) == [p3, p2, p1, c] 1 """ def test_removeAllProxies(): """ >>> from zope.proxy import ProxyBase, removeAllProxies >>> class C(object): ... pass >>> c = C() >>> int(removeAllProxies(c) is c) 1 >>> p = ProxyBase(c) >>> int(removeAllProxies(p) is c) 1 >>> p2 = ProxyBase(p) >>> int(removeAllProxies(p2) is c) 1 """ def test_queryProxy(): """ >>> from zope.proxy import ProxyBase, queryProxy >>> class P1(ProxyBase): ... pass >>> class P2(ProxyBase): ... pass >>> class C(object): ... pass >>> c = C() >>> queryProxy(c, P1) >>> queryProxy(c, P1, 42) 42 >>> p1 = P1(c) >>> int(queryProxy(p1, P1) is p1) 1 >>> queryProxy(c, P2) >>> queryProxy(c, P2, 42) 42 >>> p2 = P2(p1) >>> int(queryProxy(p2, P1) is p1) 1 >>> int(queryProxy(p2, P2) is p2) 1 >>> int(queryProxy(p2, ProxyBase) is p2) 1 """ def test_queryInnerProxy(): """ >>> from zope.proxy import ProxyBase, queryProxy, queryInnerProxy >>> class P1(ProxyBase): ... pass >>> class P2(ProxyBase): ... pass >>> class C(object): ... pass >>> c = C() >>> queryInnerProxy(c, P1) >>> queryInnerProxy(c, P1, 42) 42 >>> p1 = P1(c) >>> int(queryProxy(p1, P1) is p1) 1 >>> queryInnerProxy(c, P2) >>> queryInnerProxy(c, P2, 42) 42 >>> p2 = P2(p1) >>> int(queryInnerProxy(p2, P1) is p1) 1 >>> int(queryInnerProxy(p2, P2) is p2) 1 >>> int(queryInnerProxy(p2, ProxyBase) is p1) 1 >>> p3 = P1(p2) >>> int(queryProxy(p3, P1) is p3) 1 >>> int(queryInnerProxy(p3, P1) is p1) 1 >>> int(queryInnerProxy(p3, P2) is p2) 1 """ def test_sameProxiedObjects(): """ >>> from zope.proxy import ProxyBase, sameProxiedObjects >>> class C(object): ... pass >>> c1 = C() >>> c2 = C() >>> int(sameProxiedObjects(c1, c1)) 1 >>> int(sameProxiedObjects(ProxyBase(c1), c1)) 1 >>> int(sameProxiedObjects(ProxyBase(c1), ProxyBase(c1))) 1 >>> int(sameProxiedObjects(ProxyBase(ProxyBase(c1)), c1)) 1 >>> int(sameProxiedObjects(c1, ProxyBase(c1))) 1 >>> int(sameProxiedObjects(c1, ProxyBase(ProxyBase(c1)))) 1 >>> int(sameProxiedObjects(c1, c2)) 0 >>> int(sameProxiedObjects(ProxyBase(c1), c2)) 0 >>> int(sameProxiedObjects(ProxyBase(c1), ProxyBase(c2))) 0 >>> int(sameProxiedObjects(ProxyBase(ProxyBase(c1)), c2)) 0 >>> int(sameProxiedObjects(c1, ProxyBase(c2))) 0 >>> int(sameProxiedObjects(c1, ProxyBase(ProxyBase(c2)))) 0 """ def test_subclassing_proxies(): """You can subclass ProxyBase If you subclass a proxy, instances of the subclass have access to data defined in the class, including descriptors. Your subclass instances don't get instance dictionaries, but they can have slots. >>> class MyProxy(ProxyBase): ... __slots__ = 'x', 'y' ... ... def f(self): ... return self.x >>> l = [1, 2, 3] >>> p = MyProxy(l) I can use attributes defined by the class, including slots: >>> p.x = 'x' >>> p.x 'x' >>> p.f() 'x' I can also use attributes of the proxied object: >>> p [1, 2, 3] >>> p.pop() 3 >>> p [1, 2] """ def test_get_descriptors_in_proxy_class(): """ A non-data descriptor in a proxy class doesn't hide an attribute on a proxied object or prevent writing the attribute. >>> class ReadDescr(object): ... def __get__(self, i, c): ... return 'read' >>> class MyProxy(ProxyBase): ... __slots__ = () ... ... z = ReadDescr() ... q = ReadDescr() >>> class MyOb: ... q = 1 >>> o = MyOb() >>> p = MyProxy(o) >>> p.q 1 >>> p.z 'read' >>> p.z = 1 >>> o.z, p.z (1, 1) """ def test_non_overridable(): """ Normally, methods defined in proxies are overridden by methods of proxied objects. This applies to all non-data descriptors. The non_overridable function can be used to convert a non-data descriptor to a data descriptor that disallows writes. This function can be used as a decorator to make functions defined in proxy classes take precedence over functions defined in proxied objects. >>> class MyProxy(ProxyBase): ... __slots__ = () ... ... @zope.proxy.non_overridable ... def foo(self): ... return 'MyProxy foo' >>> class MyOb: ... def foo(self): ... return 'MyOb foo' >>> o = MyOb() >>> p = MyProxy(o) >>> p.foo() 'MyProxy foo' """ def test_setProxiedObject(): """ >>> from zope.proxy import ProxyBase >>> from zope.proxy import setProxiedObject, getProxiedObject >>> class C(object): ... pass >>> c1 = C() >>> c2 = C() >>> p = ProxyBase(c1) `setProxiedObject()` allows us to change the object a proxy refers to, returning the previous referent: >>> old = setProxiedObject(p, c2) >>> old is c1 True >>> getProxiedObject(p) is c2 True The first argument to `setProxiedObject()` must be a proxy; other objects cause it to raise an exception: >>> try: ... setProxiedObject(c1, None) ... except TypeError: ... print "TypeError raised" ... else: ... print "Expected TypeError not raised" TypeError raised """ def test_suite(): suite = unittest.makeSuite(ProxyTestCase) suite.addTest(DocTestSuite()) return suite if __name__ == "__main__": runner = unittest.TextTestRunner(sys.stdout) result = runner.run(test_suite()) newerrs = len(result.errors) + len(result.failures) sys.exit(newerrs and 1 or 0) zope2.13-2.13.21/source/zope.container/include/zope.proxy/tests/__init__.py0000644000175000017500000000007512214017546025434 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.container/include/zope.proxy/tests/test_decorator.py0000644000175000017500000000145112214017546026715 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test Harness """ from doctest import DocTestSuite def test_suite(): suite = DocTestSuite() suite.addTest(DocTestSuite('zope.proxy.decorator')) return suite zope2.13-2.13.21/source/zope.container/buildout.cfg0000644000175000017500000000071412214017546020711 0ustar arnauarnau[buildout] develop = . parts = test graph coverage-test coverage-report [test] recipe = zc.recipe.testrunner eggs = zope.container[test] [graph] recipe = zc.recipe.egg eggs = ${test:eggs} tl.eggdeps [coverage-test] recipe = zc.recipe.testrunner eggs = ${test:eggs} defaults = ['--coverage', '../../coverage'] [coverage-report] recipe = zc.recipe.egg eggs = z3c.coverage scripts = coverage=coverage-report arguments = ('coverage', 'coverage/report') zope2.13-2.13.21/source/zope.container/bootstrap.py0000644000175000017500000000742212214017546020773 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 111712 2010-04-30 20:39:57Z hannosch $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.container/CHANGES.txt0000644000175000017500000001214612214017546020214 0ustar arnauarnau======= CHANGES ======= 3.11.2 (2010-09-25) ------------------- - Added not declared, but needed test dependency on `zope.testing`. 3.11.1 (2010-04-30) ------------------- - Prefer the standard libraries doctest module to the one from zope.testing. - Added compatibility with ZODB3 3.10 by importing the IBroken interface from it directly. Once we can rely on the new ZODB3 version exclusively, we can remove the dependency onto the zope.broken distribution. - Never fail if the suggested name is in a wrong type (#227617) - ``checkName`` first checks the parameter type before the emptiness. 3.11.0 (2009-12-31) ------------------- - Copy two trivial classes from zope.cachedescriptors into this package, which allows us to remove that dependency. We didn't actually use any caching properties as the dependency suggested. 3.10.1 (2009-12-29) ------------------- - Moved zope.copypastemove related tests into that package. - Removed no longer used zcml prefix from the configure file. - Stop importing DocTestSuite from zope.testing.doctestunit. Fixes compatibility problems with zope.testing 3.8.4. 3.10.0 (2009-12-15) ------------------- - Break testing dependency on zope.app.testing. - Break testing dependency on zope.app.dependable by moving the code and tests into that package. - Import ISite from zope.component after it was moved there from zope.location. 3.9.1 (2009-10-18) ------------------ - Rerelease 3.9.0 as it had a broken Windows 2.6 egg. - Marked as part of the ZTK. 3.9.0 (2009-08-28) ------------------ - Previous releases should be versioned 3.9.0 as they are not pure bugfix releases and worth a "feature" release, increasing feature version. Packages that depend on any changes introduced in version 3.8.2 or 3.8.3 should depend on version 3.9 or greater. 3.8.3 (2009-08-27) ------------------ - Move IXMLRPCPublisher ZCML registrations for containers from zope.app.publisher.xmlrpc to zope.container for now. 3.8.2 (2009-05-17) ------------------ - Rid ourselves of ``IContained`` interface. This interface was moved to ``zope.location.interfaces``. A b/w compat import still exists to keep old code running. Depend on ``zope.location``>=3.5.4. - Rid ourselves of the implementations of ``IObjectMovedEvent``, ``IObjectAddedEvent``, ``IObjectRemovedEvent`` interfaces and ``ObjectMovedEvent``, ``ObjectAddedEvent`` and ``ObjectRemovedEvent`` classes. B/w compat imports still exist. All of these were moved to ``zope.lifecycleevent``. Depend on ``zope.lifecycleevent``>=3.5.2. - Fix a bug in OrderedContainer where trying to set the value for a key that already exists (duplication error) would actually delete the key from the order, leaving a dangling reference. - Partially break dependency on ``zope.traversing`` by disusing zope.traversing.api.getPath in favor of using ILocationInfo(object).getPath(). The rest of the runtime dependencies on zope.traversing are currently interface dependencies. - Break runtime dependency on ``zope.app.dependable`` by using a zcml condition on the qsubscriber ZCML directive that registers the CheckDependency handler for IObjectRemovedEvent. If ``zope.app.dependable`` is not installed, this subscriber will never be registered. ``zope.app.dependable`` is now a testing dependency only. 3.8.1 (2009-04-03) ------------------ - Fixed misspackaged 3.8.0 3.8.0 (2009-04-03) ------------------ - Change configure.zcml to not depend on zope.app.component. Fixes: https://bugs.launchpad.net/bugs/348329 - Moved the declaration of ``IOrderedContainer.updateOrder`` to a new, basic ``IOrdered`` interface and let ``IOrderedContainer`` inherit it. This allows easier reuse of the declaration. 3.7.2 (2009-03-12) ------------------ - Fix: added missing ComponentLookupError, missing since revision 95429 and missing in last release. - Adapt to the move of IDefaultViewName from zope.component.interfaces to zope.publisher.interfaces. - Add support for reserved names for containers. To specify reserved names for some container, you need to provide an adapter from the container to the ``zope.container.interfaces.IReservedNames`` interface. The default NameChooser is now also aware of reserved names. 3.7.1 (2009-02-05) ------------------ - Raise more "Pythonic" errors from ``__setitem__``, losing the dependency on ``zope.exceptions``: o ``zope.exceptions.DuplicationError`` -> ``KeyError`` o ``zope.exceptions.UserError`` -> ``ValueError`` - Moved import of ``IBroken`` interface to use new ``zope.broken`` package, which has no dependencies beyond ``zope.interface``. - Made ``test`` part pull in the extra test requirements of this package. - Split the ``z3c.recipe.compattest`` configuration out into a new file, ``compat.cfg``, to reduce the burden of doing standard unit tests. - Stripped out bogus develop eggs from ``buildout.cfg``. 3.7.0 (2009-01-31) ------------------ - Split this package off ``zope.app.container``. This package is intended to have far less dependencies than ``zope.app.container``. - This package also contains the container implementation that used to be in ``zope.app.folder``. zope2.13-2.13.21/source/zope.container/src/0000755000175000017500000000000012214017546017166 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/src/zope/0000755000175000017500000000000012214017546020143 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/src/zope/container/0000755000175000017500000000000012214017546022125 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/src/zope/container/sample.py0000644000175000017500000000550312214017546023763 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample container implementation. This is primarily for testing purposes. It might be useful as a mix-in for some classes, but many classes will need a very different implementation. $Id: sample.py 95341 2009-01-28 15:59:18Z wosc $ """ __docformat__ = 'restructuredtext' from zope.container.interfaces import IContainer from zope.interface import implements from zope.container.contained import Contained, setitem, uncontained class SampleContainer(Contained): """Sample container implementation suitable for testing. It is not suitable, directly as a base class unless the subclass overrides `_newContainerData` to return a persistent mapping object. """ implements(IContainer) def __init__(self): self.__data = self._newContainerData() def _newContainerData(self): """Construct an item-data container Subclasses should override this if they want different data. The value returned is a mapping object that also has `get`, `has_key`, `keys`, `items`, and `values` methods. """ return {} def keys(self): '''See interface `IReadContainer`''' return self.__data.keys() def __iter__(self): return iter(self.__data) def __getitem__(self, key): '''See interface `IReadContainer`''' return self.__data[key] def get(self, key, default=None): '''See interface `IReadContainer`''' return self.__data.get(key, default) def values(self): '''See interface `IReadContainer`''' return self.__data.values() def __len__(self): '''See interface `IReadContainer`''' return len(self.__data) def items(self): '''See interface `IReadContainer`''' return self.__data.items() def __contains__(self, key): '''See interface `IReadContainer`''' return self.__data.has_key(key) has_key = __contains__ def __setitem__(self, key, object): '''See interface `IWriteContainer`''' setitem(self, self.__data.__setitem__, key, object) def __delitem__(self, key): '''See interface `IWriteContainer`''' uncontained(self.__data[key], self, key) del self.__data[key] zope2.13-2.13.21/source/zope.container/src/zope/container/constraints.py0000644000175000017500000003265112214017546025055 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Support for containment constraints Either a container or an object can provide constraints on the containment relationship. A container expresses constraints through a precondition on it's `__setitem__` method in it's interface. Preconditions can be simple callable objects, like functions. They should raise a ``zope.interface.Invalid`` exception to indicate that a constraint isn't satisfied: >>> def preNoZ(container, name, ob): ... "Silly precondition example" ... if name.startswith("Z"): ... raise zope.interface.Invalid("Names can not start with Z") >>> class I1(zope.interface.Interface): ... def __setitem__(name, on): ... "Add an item" ... __setitem__.precondition = preNoZ >>> from zope.container.interfaces import IContainer >>> class C1(object): ... zope.interface.implements(I1, IContainer) ... def __repr__(self): ... return 'C1' Given such a precondition, we can then check whether an object can be added: >>> c1 = C1() >>> checkObject(c1, "bob", None) >>> checkObject(c1, "Zbob", None) Traceback (most recent call last): ... Invalid: Names can not start with Z We can also express constaints on the containers an object can be added to. We do this by setting a field constraint on an object's `__parent__` attribute: >>> import zope.schema A field constraint is a callable object that returns a boolean value: >>> def con1(container): ... "silly container constraint" ... if not hasattr(container, 'x'): ... return False ... return True >>> class I2(zope.interface.Interface): ... __parent__ = zope.schema.Field(constraint = con1) >>> class O(object): ... zope.interface.implements(I2) If the constraint isn't satisfied, we'll get a validation error when we check whether the object can be added: >>> checkObject(c1, "bob", O()) Traceback (most recent call last): ... ConstraintNotSatisfied: C1 Note that the validation error isn't very informative. For that reason, it's better for constraints to raise Invalid errors when they aren't satisfied: >>> def con1(container): ... "silly container constraint" ... if not hasattr(container, 'x'): ... raise zope.interface.Invalid("What, no x?") ... return True >>> class I2(zope.interface.Interface): ... __parent__ = zope.schema.Field(constraint = con1) >>> class O(object): ... zope.interface.implements(I2) >>> checkObject(c1, "bob", O()) Traceback (most recent call last): ... Invalid: What, no x? >>> c1.x = 1 >>> checkObject(c1, "bob", O()) The `checkObject` function is handy when checking whether we can add an existing object to a container, but, sometimes, we want to check whether an object produced by a factory can be added. To do this, we use `checkFactory`: >>> class Factory(object): ... def __call__(self): ... return O() ... def getInterfaces(self): ... return zope.interface.implementedBy(O) >>> factory = Factory() >>> checkFactory(c1, "bob", factory) True >>> del c1.x >>> checkFactory(c1, "bob", factory) False Unlike `checkObject`, `checkFactory`: - Returns a boolean value - Takes a factory (e.g. a class) rather than an argument. The container constraint we defined for C1 isn't actually used to check the factory: >>> c1.x = 1 >>> checkFactory(c1, "Zbob", factory) True To work with `checkFactory`, a container precondition has to implement a factory method. This is because a factory, rather than an object is passed. To illustrate this, we'll make preNoZ its own factory method: >>> preNoZ.factory = preNoZ We can do this (silly thing) because preNoZ doesn't use the object argument. >>> checkFactory(c1, "Zbob", factory) False $Id: constraints.py 107468 2009-12-31 20:12:53Z hannosch $ """ __docformat__ = 'restructuredtext' import sys from zope.dottedname.resolve import resolve import zope.schema from zope.interface import providedBy from zope.container.interfaces import InvalidItemType, InvalidContainerType from zope.container.i18n import ZopeMessageFactory as _ from zope.container.interfaces import IContainer def checkObject(container, name, object): """Check containement constraints for an object and container """ # check __setitem__ precondition containerProvided = providedBy(container) __setitem__ = containerProvided.get('__setitem__') if __setitem__ is not None: precondition = __setitem__.queryTaggedValue('precondition') if precondition is not None: precondition(container, name, object) # check the constraint on __parent__ __parent__ = providedBy(object).get('__parent__') if __parent__ is not None: try: validate = __parent__.validate except AttributeError: pass else: validate(container) if not containerProvided.extends(IContainer): # If it doesn't implement IContainer, it can't contain stuff. raise TypeError( _('Container is not a valid Zope container.') ) def checkFactory(container, name, factory): __setitem__ = providedBy(container).get('__setitem__') if __setitem__ is not None: precondition = __setitem__.queryTaggedValue('precondition') if precondition is not None: try: precondition = precondition.factory except AttributeError: pass else: try: precondition(container, name, factory) except zope.interface.Invalid: return False # check the constraint on __parent__ __parent__ = factory.getInterfaces().get('__parent__') if __parent__ is not None: try: validate = __parent__.validate except AttributeError: pass else: try: validate(container) except zope.interface.Invalid: return False return True class readproperty(object): def __init__(self, func): self.func = func def __get__(self, inst, class_): if inst is None: return self func = self.func return func(inst) class IItemTypePrecondition(zope.interface.Interface): def __call__(container, name, object): """Test whether container setitem arguments are valid. Raise zope.interface.Invalid if the object is invalid. """ def factory(container, name, factory): """Test whether objects provided by the factory are acceptable Return a boolean value. """ class _TypesBased(object): @readproperty def types(self): raw_types, module = self.raw_types types = [] for t in raw_types: if isinstance(t, str): t = resolve(t, module) types.append(t) self.types = types return types def __init__(self, *types, **kw): if [t for t in types if isinstance(t, str)]: # have dotted names module = kw.get('module', sys._getframe(1).f_globals['__name__']) self.raw_types = types, module else: self.types = types class ItemTypePrecondition(_TypesBased): """Specify a `__setitem__` precondition that restricts item types Items must be one of the given types. >>> class I1(zope.interface.Interface): ... pass >>> class I2(zope.interface.Interface): ... pass >>> precondition = ItemTypePrecondition(I1, I2) >>> class Ob(object): ... pass >>> ob = Ob() >>> class Factory(object): ... def __call__(self): ... return Ob() ... def getInterfaces(self): ... return zope.interface.implementedBy(Ob) >>> factory = Factory() >>> try: ... precondition(None, 'foo', ob) ... except InvalidItemType, v: ... print v[0], (v[1] is ob), (v[2] == (I1, I2)) ... else: ... print 'Should have failed' None True True >>> try: ... precondition.factory(None, 'foo', factory) ... except InvalidItemType, v: ... print v[0], (v[1] is factory), (v[2] == (I1, I2)) ... else: ... print 'Should have failed' None True True >>> zope.interface.classImplements(Ob, I2) >>> precondition(None, 'foo', ob) >>> precondition.factory(None, 'foo', factory) """ zope.interface.implements(IItemTypePrecondition) def __call__(self, container, name, object): for iface in self.types: if iface.providedBy(object): return raise InvalidItemType(container, object, self.types) def factory(self, container, name, factory): implemented = factory.getInterfaces() for iface in self.types: if implemented.isOrExtends(iface): return raise InvalidItemType(container, factory, self.types) def contains(*types): """Declare that a container type contains only the given types This is used within a class suite defining an interface to create a __setitem__ specification with a precondition allowing only the given types: >>> class IFoo(zope.interface.Interface): ... pass >>> class IBar(zope.interface.Interface): ... pass >>> class IFooBarContainer(IContainer): ... contains(IFoo, IBar) >>> __setitem__ = IFooBarContainer['__setitem__'] >>> __setitem__.getTaggedValue('precondition').types == (IFoo, IBar) True It is invalid to call contains outside a class suite: >>> contains(IFoo, IBar) Traceback (most recent call last): ... TypeError: contains not called from suite """ frame = sys._getframe(1) f_locals = frame.f_locals f_globals = frame.f_globals if not (f_locals is not f_globals and f_locals.get('__module__') and f_locals.get('__module__') == f_globals.get('__name__') ): raise TypeError("contains not called from suite") def __setitem__(key, value): pass __setitem__.__doc__ = IContainer['__setitem__'].__doc__ __setitem__.precondition = ItemTypePrecondition( *types, **dict(module=f_globals['__name__']) ) f_locals['__setitem__'] = __setitem__ class IContainerTypesConstraint(zope.interface.Interface): def __call__(object): """Test whether object is valid. Return True if valid. Raise zope.interface.Invalid if the objet is invalid. """ class ContainerTypesConstraint(_TypesBased): """Constrain a container to be one of a number of types >>> class I1(zope.interface.Interface): ... pass >>> class I2(zope.interface.Interface): ... pass >>> class Ob(object): ... pass >>> ob = Ob() >>> constraint = ContainerTypesConstraint(I1, I2) >>> try: ... constraint(ob) ... except InvalidContainerType, v: ... print (v[0] is ob), (v[1] == (I1, I2)) ... else: ... print 'Should have failed' True True >>> zope.interface.classImplements(Ob, I2) >>> constraint(Ob()) True """ zope.interface.implements(IContainerTypesConstraint) def __call__(self, object): for iface in self.types: if iface.providedBy(object): return True else: raise InvalidContainerType(object, self.types) def containers(*types): """Declare the container types a type can be contained in This is used within a class suite defining an interface to create a __parent__ specification with a constraint allowing only the given types: >>> class IFoo(IContainer): ... pass >>> class IBar(IContainer): ... pass >>> from zope.location.interfaces import IContained >>> class IFooBarContained(IContained): ... containers(IFoo, IBar) >>> __parent__ = IFooBarContained['__parent__'] >>> __parent__.constraint.types == (IFoo, IBar) True It is invalid to call containers outside a class suite: >>> containers(IFoo, IBar) Traceback (most recent call last): ... TypeError: containers not called from suite """ frame = sys._getframe(1) f_locals = frame.f_locals f_globals = frame.f_globals if not (f_locals is not f_globals and f_locals.get('__module__') and f_locals.get('__module__') == f_globals.get('__name__') ): raise TypeError("containers not called from suite") __parent__ = zope.schema.Field( constraint = ContainerTypesConstraint( *types, **dict(module=f_globals['__name__']) ) ) f_locals['__parent__'] = __parent__ zope2.13-2.13.21/source/zope.container/src/zope/container/configure.zcml0000644000175000017500000000627112214017546025003 0ustar arnauarnau Handler dispatches moved events to sublocations of the original object. zope2.13-2.13.21/source/zope.container/src/zope/container/ordered.py0000644000175000017500000002061712214017546024131 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Ordered container implementation. $Id: ordered.py 99988 2009-05-15 22:10:08Z pcardune $ """ __docformat__ = 'restructuredtext' from zope.container.interfaces import IOrderedContainer from zope.interface import implements from persistent import Persistent from persistent.dict import PersistentDict from persistent.list import PersistentList from types import StringTypes, TupleType, ListType from zope.container.contained import Contained, setitem, uncontained from zope.container.contained import notifyContainerModified class OrderedContainer(Persistent, Contained): """ `OrderedContainer` maintains entries' order as added and moved. >>> oc = OrderedContainer() >>> int(IOrderedContainer.providedBy(oc)) 1 >>> len(oc) 0 """ implements(IOrderedContainer) def __init__(self): self._data = PersistentDict() self._order = PersistentList() def keys(self): """ See `IOrderedContainer`. >>> oc = OrderedContainer() >>> oc.keys() [] >>> oc['foo'] = 'bar' >>> oc.keys() ['foo'] >>> oc['baz'] = 'quux' >>> oc.keys() ['foo', 'baz'] >>> int(len(oc._order) == len(oc._data)) 1 """ return self._order[:] def __iter__(self): """ See `IOrderedContainer`. >>> oc = OrderedContainer() >>> oc.keys() [] >>> oc['foo'] = 'bar' >>> oc['baz'] = 'quux' >>> [i for i in oc] ['foo', 'baz'] >>> int(len(oc._order) == len(oc._data)) 1 """ return iter(self.keys()) def __getitem__(self, key): """ See `IOrderedContainer`. >>> oc = OrderedContainer() >>> oc['foo'] = 'bar' >>> oc['foo'] 'bar' """ return self._data[key] def get(self, key, default=None): """ See `IOrderedContainer`. >>> oc = OrderedContainer() >>> oc['foo'] = 'bar' >>> oc.get('foo') 'bar' >>> oc.get('funky', 'No chance, dude.') 'No chance, dude.' """ return self._data.get(key, default) def values(self): """ See `IOrderedContainer`. >>> oc = OrderedContainer() >>> oc.keys() [] >>> oc['foo'] = 'bar' >>> oc.values() ['bar'] >>> oc['baz'] = 'quux' >>> oc.values() ['bar', 'quux'] >>> int(len(oc._order) == len(oc._data)) 1 """ return [self._data[i] for i in self._order] def __len__(self): """ See `IOrderedContainer`. >>> oc = OrderedContainer() >>> int(len(oc) == 0) 1 >>> oc['foo'] = 'bar' >>> int(len(oc) == 1) 1 """ return len(self._data) def items(self): """ See `IOrderedContainer`. >>> oc = OrderedContainer() >>> oc.keys() [] >>> oc['foo'] = 'bar' >>> oc.items() [('foo', 'bar')] >>> oc['baz'] = 'quux' >>> oc.items() [('foo', 'bar'), ('baz', 'quux')] >>> int(len(oc._order) == len(oc._data)) 1 """ return [(i, self._data[i]) for i in self._order] def __contains__(self, key): """ See `IOrderedContainer`. >>> oc = OrderedContainer() >>> oc['foo'] = 'bar' >>> int('foo' in oc) 1 >>> int('quux' in oc) 0 """ return self._data.has_key(key) has_key = __contains__ def __setitem__(self, key, object): """ See `IOrderedContainer`. >>> oc = OrderedContainer() >>> oc.keys() [] >>> oc['foo'] = 'bar' >>> oc._order ['foo'] >>> oc['baz'] = 'quux' >>> oc._order ['foo', 'baz'] >>> int(len(oc._order) == len(oc._data)) 1 >>> oc['foo'] = 'baz' Traceback (most recent call last): ... KeyError: u'foo' >>> oc._order ['foo', 'baz'] """ existed = self._data.has_key(key) bad = False if isinstance(key, StringTypes): try: unicode(key) except UnicodeError: bad = True else: bad = True if bad: raise TypeError("'%s' is invalid, the key must be an " "ascii or unicode string" % key) if len(key) == 0: raise ValueError("The key cannot be an empty string") # We have to first update the order, so that the item is available, # otherwise most API functions will lie about their available values # when an event subscriber tries to do something with the container. if not existed: self._order.append(key) # This function creates a lot of events that other code listens to. try: setitem(self, self._data.__setitem__, key, object) except Exception, e: if not existed: self._order.remove(key) raise e return key def __delitem__(self, key): """ See `IOrderedContainer`. >>> oc = OrderedContainer() >>> oc.keys() [] >>> oc['foo'] = 'bar' >>> oc['baz'] = 'quux' >>> oc['zork'] = 'grue' >>> oc.items() [('foo', 'bar'), ('baz', 'quux'), ('zork', 'grue')] >>> int(len(oc._order) == len(oc._data)) 1 >>> del oc['baz'] >>> oc.items() [('foo', 'bar'), ('zork', 'grue')] >>> int(len(oc._order) == len(oc._data)) 1 """ uncontained(self._data[key], self, key) del self._data[key] self._order.remove(key) def updateOrder(self, order): """ See `IOrderedContainer`. >>> oc = OrderedContainer() >>> oc['foo'] = 'bar' >>> oc['baz'] = 'quux' >>> oc['zork'] = 'grue' >>> oc.keys() ['foo', 'baz', 'zork'] >>> oc.updateOrder(['baz', 'foo', 'zork']) >>> oc.keys() ['baz', 'foo', 'zork'] >>> oc.updateOrder(['baz', 'zork', 'foo']) >>> oc.keys() ['baz', 'zork', 'foo'] >>> oc.updateOrder(['baz', 'zork', 'foo']) >>> oc.keys() ['baz', 'zork', 'foo'] >>> oc.updateOrder(('zork', 'foo', 'baz')) >>> oc.keys() ['zork', 'foo', 'baz'] >>> oc.updateOrder(['baz', 'zork']) Traceback (most recent call last): ... ValueError: Incompatible key set. >>> oc.updateOrder(['foo', 'bar', 'baz', 'quux']) Traceback (most recent call last): ... ValueError: Incompatible key set. >>> oc.updateOrder(1) Traceback (most recent call last): ... TypeError: order must be a tuple or a list. >>> oc.updateOrder('bar') Traceback (most recent call last): ... TypeError: order must be a tuple or a list. >>> oc.updateOrder(['baz', 'zork', 'quux']) Traceback (most recent call last): ... ValueError: Incompatible key set. >>> del oc['baz'] >>> del oc['zork'] >>> del oc['foo'] >>> len(oc) 0 """ if not isinstance(order, ListType) and \ not isinstance(order, TupleType): raise TypeError('order must be a tuple or a list.') if len(order) != len(self._order): raise ValueError("Incompatible key set.") was_dict = {} will_be_dict = {} new_order = PersistentList() for i in range(len(order)): was_dict[self._order[i]] = 1 will_be_dict[order[i]] = 1 new_order.append(order[i]) if will_be_dict != was_dict: raise ValueError("Incompatible key set.") self._order = new_order notifyContainerModified(self) zope2.13-2.13.21/source/zope.container/src/zope/container/i18n.py0000644000175000017500000000165312214017546023263 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Customization of zope.i18n for the Zope application server $Id: i18n.py 73547 2007-03-25 09:03:04Z dobe $ """ __docformat__ = 'restructuredtext' # import this as _ to create i18n messages in the zope domain from zope.i18nmessageid import MessageFactory ZopeMessageFactory = MessageFactory('zope') zope2.13-2.13.21/source/zope.container/src/zope/container/_zope_container_contained.c0000644000175000017500000002271212214017546027477 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ #define _ZOPE_CONTAINER_CONTAINED_C "$Id: _zope_container_contained.c 95350 2009-01-28 17:01:17Z wosc $\n" /* Contained Proxy Base class Contained proxies provide __parent__ and __name__ attributes for objects without them. There is something strange and, possibly cool, going on here, wrt persistence. To reuse the base proxy implementation we don't treat the proxied object as part of the persistent state of the proxy. This means that the proxy still operates as a proxy even if it is a ghost. The proxy will only be unghostified if you need to access one of the attributes provided by the proxy. */ #include "Python.h" #include "persistent/cPersistence.h" static PyObject *str_p_deactivate; #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) typedef int Py_ssize_t; #define PY_SSIZE_T_MAX INT_MAX #define PY_SSIZE_T_MIN INT_MIN typedef Py_ssize_t (*lenfunc)(PyObject *); typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t); typedef PyObject *(*ssizessizeargfunc)(PyObject *, Py_ssize_t, Py_ssize_t); typedef int(*ssizeobjargproc)(PyObject *, Py_ssize_t, PyObject *); typedef int(*ssizessizeobjargproc)(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); #endif typedef struct { cPersistent_HEAD PyObject *po_weaklist; PyObject *proxy_object; PyObject *__parent__; PyObject *__name__; } ProxyObject; typedef struct { PyTypeObject *proxytype; int (*check)(PyObject *obj); PyObject *(*create)(PyObject *obj); PyObject *(*getobject)(PyObject *proxy); } ProxyInterface; #define OBJECT(O) ((PyObject*)(O)) #define Proxy_GET_OBJECT(ob) (((ProxyObject *)(ob))->proxy_object) #define CLEAR(O) \ if (O) {PyObject *clr__tmp = O; O = NULL; Py_DECREF(clr__tmp); } /* Supress inclusion of the original proxy.h */ #define _proxy_H_ 1 /* Incude the proxy C source */ #include "_zope_proxy_proxy.c" #define SPECIAL(NAME) ( \ *(NAME) == '_' && \ (((NAME)[1] == 'p' && (NAME)[2] == '_') \ || \ ((NAME)[1] == '_' && ( \ strcmp((NAME), "__parent__") == 0 \ || \ strcmp((NAME), "__name__") == 0 \ || \ strcmp((NAME), "__getstate__") == 0 \ || \ strcmp((NAME), "__setstate__") == 0 \ || \ strcmp((NAME), "__getnewargs__") == 0 \ || \ strcmp((NAME), "__reduce__") == 0 \ || \ strcmp((NAME), "__reduce_ex__") == 0 \ )) \ )) static PyObject * CP_getattro(PyObject *self, PyObject *name) { char *cname; cname = PyString_AsString(name); if (cname == NULL) return NULL; if (SPECIAL(cname)) /* delegate to persistent */ return cPersistenceCAPI->pertype->tp_getattro(self, name); /* Use the wrapper version to delegate */ return wrap_getattro(self, name); } static int CP_setattro(PyObject *self, PyObject *name, PyObject *v) { char *cname; cname = PyString_AsString(name); if (cname == NULL) return -1; if (SPECIAL(cname)) /* delegate to persistent */ return cPersistenceCAPI->pertype->tp_setattro(self, name, v); /* Use the wrapper version to delegate */ return wrap_setattro(self, name, v); } static PyObject * CP_getstate(ProxyObject *self) { return Py_BuildValue("OO", self->__parent__ ? self->__parent__ : Py_None, self->__name__ ? self->__name__ : Py_None ); } static PyObject * CP_getnewargs(ProxyObject *self) { return Py_BuildValue("(O)", self->proxy_object); } static PyObject * CP_setstate(ProxyObject *self, PyObject *state) { PyObject *parent, *name; if(! PyArg_ParseTuple(state, "OO", &parent, &name)) return NULL; CLEAR(self->__parent__); CLEAR(self->__name__); Py_INCREF(parent); Py_INCREF(name); self->__parent__ = parent; self->__name__ = name; Py_INCREF(Py_None); return Py_None; } static PyObject * CP_reduce(ProxyObject *self) { PyObject *result; if (! PER_USE(self)) return NULL; result = Py_BuildValue("O(O)(OO)", self->ob_type, self->proxy_object, self->__parent__ ? self->__parent__ : Py_None, self->__name__ ? self->__name__ : Py_None ); PER_ALLOW_DEACTIVATION(self); return result; } static PyObject * CP_reduce_ex(ProxyObject *self, PyObject *proto) { return CP_reduce(self); } static PyObject * CP__p_deactivate(ProxyObject *self) { PyObject *result; result = PyObject_CallMethodObjArgs(OBJECT(cPersistenceCAPI->pertype), str_p_deactivate, self, NULL); if (result == NULL) return NULL; if (self->jar && self->oid && self->state == cPersistent_UPTODATE_STATE) { Py_XDECREF(self->__parent__); self->__parent__ = NULL; Py_XDECREF(self->__name__); self->__name__ = NULL; } return result; } static PyMethodDef CP_methods[] = { {"__getstate__", (PyCFunction)CP_getstate, METH_NOARGS, "Get the object state"}, {"__setstate__", (PyCFunction)CP_setstate, METH_O, "Set the object state"}, {"__getnewargs__", (PyCFunction)CP_getnewargs, METH_NOARGS, "Get the arguments that must be passed to __new__"}, {"__reduce__", (PyCFunction)CP_reduce, METH_NOARGS, "Reduce the object to constituent parts."}, {"__reduce_ex__", (PyCFunction)CP_reduce_ex, METH_O, "Reduce the object to constituent parts."}, {"_p_deactivate", (PyCFunction)CP__p_deactivate, METH_NOARGS, "Deactivate the object."}, {NULL, NULL}, }; /* Code to access structure members by accessing attributes */ #include "structmember.h" static PyMemberDef CP_members[] = { {"__parent__", T_OBJECT, offsetof(ProxyObject, __parent__)}, {"__name__", T_OBJECT, offsetof(ProxyObject, __name__)}, {NULL} /* Sentinel */ }; static int CP_traverse(ProxyObject *self, visitproc visit, void *arg) { if (cPersistenceCAPI->pertype->tp_traverse((PyObject *)self, visit, arg) < 0) return -1; if (self->proxy_object != NULL && visit(self->proxy_object, arg) < 0) return -1; if (self->__parent__ != NULL && visit(self->__parent__, arg) < 0) return -1; if (self->__name__ != NULL && visit(self->__name__, arg) < 0) return -1; return 0; } static int CP_clear(ProxyObject *self) { /* Drop references that may have created reference cycles. Immutable objects do not have to define this method since they can never directly create reference cycles. Note that the object must still be valid after calling this method (don't just call Py_DECREF() on a reference). The collector will call this method if it detects that this object is involved in a reference cycle. */ if (cPersistenceCAPI->pertype->tp_clear != NULL) cPersistenceCAPI->pertype->tp_clear((PyObject*)self); CLEAR(self->proxy_object); CLEAR(self->__parent__); CLEAR(self->__name__); return 0; } static void CP_dealloc(ProxyObject *self) { if (self->po_weaklist != NULL) PyObject_ClearWeakRefs((PyObject *)self); CLEAR(self->proxy_object); CLEAR(self->__parent__); CLEAR(self->__name__); cPersistenceCAPI->pertype->tp_dealloc((PyObject*)self); } #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC init_zope_container_contained(void) { PyObject *m; str_p_deactivate = PyString_FromString("_p_deactivate"); if (str_p_deactivate == NULL) return; /* Try to fake out compiler nag function */ if (0) init_zope_proxy_proxy(); m = Py_InitModule3("_zope_container_contained", module_functions, module___doc__); if (m == NULL) return; if (empty_tuple == NULL) empty_tuple = PyTuple_New(0); /* Initialize the PyPersist_C_API and the type objects. */ cPersistenceCAPI = PyCObject_Import("persistent.cPersistence", "CAPI"); if (cPersistenceCAPI == NULL) return; ProxyType.tp_name = "zope.container.contained.ContainedProxyBase"; ProxyType.ob_type = &PyType_Type; ProxyType.tp_base = cPersistenceCAPI->pertype; ProxyType.tp_getattro = CP_getattro; ProxyType.tp_setattro = CP_setattro; ProxyType.tp_members = CP_members; ProxyType.tp_methods = CP_methods; ProxyType.tp_traverse = (traverseproc) CP_traverse; ProxyType.tp_clear = (inquiry) CP_clear; ProxyType.tp_dealloc = (destructor) CP_dealloc; ProxyType.tp_weaklistoffset = offsetof(ProxyObject, po_weaklist); if (PyType_Ready(&ProxyType) < 0) return; Py_INCREF(&ProxyType); PyModule_AddObject(m, "ContainedProxyBase", (PyObject *)&ProxyType); } zope2.13-2.13.21/source/zope.container/src/zope/container/btree.py0000644000175000017500000000733312214017546023606 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """This module provides a sample btree container implementation. $Id: btree.py 107468 2009-12-31 20:12:53Z hannosch $ """ __docformat__ = 'restructuredtext' from persistent import Persistent from BTrees.OOBTree import OOBTree from BTrees.Length import Length from zope.container.interfaces import IBTreeContainer from zope.container.contained import Contained, setitem, uncontained from zope.interface import implements class Lazy(object): """Lazy Attributes. """ def __init__(self, func, name=None): if name is None: name = func.__name__ self.data = (func, name) def __get__(self, inst, class_): if inst is None: return self func, name = self.data value = func(inst) inst.__dict__[name] = value return value class BTreeContainer(Contained, Persistent): implements(IBTreeContainer) def __init__(self): # We keep the previous attribute to store the data # for backward compatibility self._SampleContainer__data = self._newContainerData() self.__len = Length() def _newContainerData(self): """Construct an item-data container Subclasses should override this if they want different data. The value returned is a mapping object that also has get, has_key, keys, items, and values methods. The default implementation uses an OOBTree. """ return OOBTree() def __contains__(self, key): '''See interface IReadContainer >>> c = BTreeContainer() >>> "a" in c False >>> c["a"] = 1 >>> "a" in c True >>> "A" in c False ''' return key in self._SampleContainer__data @Lazy def _BTreeContainer__len(self): l = Length() ol = len(self._SampleContainer__data) if ol > 0: l.change(ol) self._p_changed = True return l def __len__(self): return self.__len() def _setitemf(self, key, value): # make sure our lazy property gets set l = self.__len self._SampleContainer__data[key] = value l.change(1) def __iter__(self): return iter(self._SampleContainer__data) def __getitem__(self, key): '''See interface `IReadContainer`''' return self._SampleContainer__data[key] def get(self, key, default=None): '''See interface `IReadContainer`''' return self._SampleContainer__data.get(key, default) def __setitem__(self, key, value): setitem(self, self._setitemf, key, value) def __delitem__(self, key): # make sure our lazy property gets set l = self.__len uncontained(self._SampleContainer__data[key], self, key) del self._SampleContainer__data[key] l.change(-1) has_key = __contains__ def items(self, key=None): return self._SampleContainer__data.items(key) def keys(self, key=None): return self._SampleContainer__data.keys(key) def values(self, key=None): return self._SampleContainer__data.values(key) zope2.13-2.13.21/source/zope.container/src/zope/container/find.py0000644000175000017500000000532112214017546023420 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Find Support $Id: find.py 68442 2006-06-01 12:54:41Z mj $ """ __docformat__ = 'restructuredtext' from zope.interface import implements from interfaces import IFind, IIdFindFilter, IObjectFindFilter from interfaces import IReadContainer class FindAdapter(object): implements(IFind) __used_for__ = IReadContainer def __init__(self, context): self._context = context def find(self, id_filters=None, object_filters=None): 'See IFind' id_filters = id_filters or [] object_filters = object_filters or [] result = [] container = self._context for id, object in container.items(): _find_helper(id, object, container, id_filters, object_filters, result) return result def _find_helper(id, object, container, id_filters, object_filters, result): for id_filter in id_filters: if not id_filter.matches(id): break else: # if we didn't break out of the loop, all name filters matched # now check all object filters for object_filter in object_filters: if not object_filter.matches(object): break else: # if we didn't break out of the loop, all filters matched result.append(object) if not IReadContainer.providedBy(object): return container = object for id, object in container.items(): _find_helper(id, object, container, id_filters, object_filters, result) class SimpleIdFindFilter(object): implements(IIdFindFilter) def __init__(self, ids): self._ids = ids def matches(self, id): 'See INameFindFilter' return id in self._ids class SimpleInterfacesFindFilter(object): """Filter objects on the provided interfaces""" implements(IObjectFindFilter) def __init__(self, *interfaces): self.interfaces = interfaces def matches(self, object): for iface in self.interfaces: if iface.providedBy(object): return True return False zope2.13-2.13.21/source/zope.container/src/zope/container/_zope_proxy_proxy.c0000644000175000017500000006643012214017546026120 0ustar arnauarnau/*############################################################################ # # Copyright (c) 2004 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################*/ /* * This file is also used as a really extensive macro in * ../app/container/_zope_container_contained.c. If you need to * change this file, you need to "svn copy" it to ../container/. * * This approach is taken to allow the sources for the two packages * to be compilable when the relative locations of these aren't * related in the same way as they are in a checkout. * * This will be revisited in the future, but works for now. */ #include "Python.h" #include "modsupport.h" #define PROXY_MODULE #include "zope.proxy/proxy.h" static PyTypeObject ProxyType; #define Proxy_Check(wrapper) (PyObject_TypeCheck((wrapper), &ProxyType)) static PyObject * empty_tuple = NULL; /* * Slot methods. */ static PyObject * wrap_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *result = NULL; PyObject *object; if (PyArg_UnpackTuple(args, "__new__", 1, 1, &object)) { if (kwds != NULL && PyDict_Size(kwds) != 0) { PyErr_SetString(PyExc_TypeError, "proxy.__new__ does not accept keyword args"); return NULL; } result = PyType_GenericNew(type, args, kwds); if (result != NULL) { ProxyObject *wrapper = (ProxyObject *) result; Py_INCREF(object); wrapper->proxy_object = object; } } return result; } static int wrap_init(PyObject *self, PyObject *args, PyObject *kwds) { int result = -1; PyObject *object; if (PyArg_UnpackTuple(args, "__init__", 1, 1, &object)) { ProxyObject *wrapper = (ProxyObject *)self; if (kwds != NULL && PyDict_Size(kwds) != 0) { PyErr_SetString(PyExc_TypeError, "proxy.__init__ does not accept keyword args"); return -1; } /* If the object in this proxy is not the one we * received in args, replace it with the new one. */ if (wrapper->proxy_object != object) { PyObject *temp = wrapper->proxy_object; Py_INCREF(object); wrapper->proxy_object = object; Py_DECREF(temp); } result = 0; } return result; } static int wrap_traverse(PyObject *self, visitproc visit, void *arg) { PyObject *ob = Proxy_GET_OBJECT(self); if (ob != NULL) return visit(ob, arg); else return 0; } static int wrap_clear(PyObject *self) { ProxyObject *proxy = (ProxyObject *)self; PyObject *temp = proxy->proxy_object; if (temp != NULL) { proxy->proxy_object = NULL; Py_DECREF(temp); } return 0; } static PyObject * wrap_richcompare(PyObject* self, PyObject* other, int op) { if (Proxy_Check(self)) { self = Proxy_GET_OBJECT(self); } else { other = Proxy_GET_OBJECT(other); } return PyObject_RichCompare(self, other, op); } static PyObject * wrap_iter(PyObject *self) { return PyObject_GetIter(Proxy_GET_OBJECT(self)); } static PyObject * wrap_iternext(PyObject *self) { return PyIter_Next(Proxy_GET_OBJECT(self)); } static void wrap_dealloc(PyObject *self) { (void) wrap_clear(self); self->ob_type->tp_free(self); } /* A variant of _PyType_Lookup that doesn't look in ProxyType. * * If argument search_wrappertype is nonzero, we can look in WrapperType. */ PyObject * WrapperType_Lookup(PyTypeObject *type, PyObject *name) { int i, n; PyObject *mro, *res, *base, *dict; /* Look in tp_dict of types in MRO */ mro = type->tp_mro; /* If mro is NULL, the type is either not yet initialized by PyType_Ready(), or already cleared by type_clear(). Either way the safest thing to do is to return NULL. */ if (mro == NULL) return NULL; assert(PyTuple_Check(mro)); n = PyTuple_GET_SIZE(mro) - 1; /* We don't want to look at the last item, which is object. */ for (i = 0; i < n; i++) { base = PyTuple_GET_ITEM(mro, i); if (((PyTypeObject *)base) != &ProxyType) { if (PyClass_Check(base)) dict = ((PyClassObject *)base)->cl_dict; else { assert(PyType_Check(base)); dict = ((PyTypeObject *)base)->tp_dict; } assert(dict && PyDict_Check(dict)); res = PyDict_GetItem(dict, name); if (res != NULL) return res; } } return NULL; } static PyObject * wrap_getattro(PyObject *self, PyObject *name) { PyObject *wrapped; PyObject *descriptor; PyObject *res = NULL; char *name_as_string; int maybe_special_name; #ifdef Py_USING_UNICODE /* The Unicode to string conversion is done here because the existing tp_getattro slots expect a string object as name and we wouldn't want to break those. */ if (PyUnicode_Check(name)) { name = PyUnicode_AsEncodedString(name, NULL, NULL); if (name == NULL) return NULL; } else #endif if (!PyString_Check(name)){ PyErr_SetString(PyExc_TypeError, "attribute name must be string"); return NULL; } else Py_INCREF(name); name_as_string = PyString_AS_STRING(name); wrapped = Proxy_GET_OBJECT(self); if (wrapped == NULL) { PyErr_Format(PyExc_RuntimeError, "object is NULL; requested to get attribute '%s'", name_as_string); goto finally; } maybe_special_name = name_as_string[0] == '_' && name_as_string[1] == '_'; if (!(maybe_special_name && strcmp(name_as_string, "__class__") == 0)) { descriptor = WrapperType_Lookup(self->ob_type, name); if (descriptor != NULL) { if (PyType_HasFeature(descriptor->ob_type, Py_TPFLAGS_HAVE_CLASS) && descriptor->ob_type->tp_descr_get != NULL) { res = descriptor->ob_type->tp_descr_get( descriptor, self, (PyObject *)self->ob_type); } else { Py_INCREF(descriptor); res = descriptor; } goto finally; } } res = PyObject_GetAttr(wrapped, name); finally: Py_DECREF(name); return res; } static int wrap_setattro(PyObject *self, PyObject *name, PyObject *value) { PyObject *wrapped; PyObject *descriptor; int res = -1; #ifdef Py_USING_UNICODE /* The Unicode to string conversion is done here because the existing tp_setattro slots expect a string object as name and we wouldn't want to break those. */ if (PyUnicode_Check(name)) { name = PyUnicode_AsEncodedString(name, NULL, NULL); if (name == NULL) return -1; } else #endif if (!PyString_Check(name)){ PyErr_SetString(PyExc_TypeError, "attribute name must be string"); return -1; } else Py_INCREF(name); descriptor = WrapperType_Lookup(self->ob_type, name); if (descriptor != NULL) { if (PyType_HasFeature(descriptor->ob_type, Py_TPFLAGS_HAVE_CLASS) && descriptor->ob_type->tp_descr_set != NULL) { res = descriptor->ob_type->tp_descr_set(descriptor, self, value); } else { PyErr_Format(PyExc_TypeError, "Tried to set attribute '%s' on wrapper, but it is not" " a data descriptor", PyString_AS_STRING(name)); } goto finally; } wrapped = Proxy_GET_OBJECT(self); if (wrapped == NULL) { PyErr_Format(PyExc_RuntimeError, "object is NULL; requested to set attribute '%s'", PyString_AS_STRING(name)); goto finally; } res = PyObject_SetAttr(wrapped, name, value); finally: Py_DECREF(name); return res; } static int wrap_print(PyObject *wrapper, FILE *fp, int flags) { return PyObject_Print(Proxy_GET_OBJECT(wrapper), fp, flags); } static PyObject * wrap_str(PyObject *wrapper) { return PyObject_Str(Proxy_GET_OBJECT(wrapper)); } static PyObject * wrap_repr(PyObject *wrapper) { return PyObject_Repr(Proxy_GET_OBJECT(wrapper)); } static int wrap_compare(PyObject *wrapper, PyObject *v) { return PyObject_Compare(Proxy_GET_OBJECT(wrapper), v); } static long wrap_hash(PyObject *self) { return PyObject_Hash(Proxy_GET_OBJECT(self)); } static PyObject * wrap_call(PyObject *self, PyObject *args, PyObject *kw) { if (kw) return PyEval_CallObjectWithKeywords(Proxy_GET_OBJECT(self), args, kw); else return PyObject_CallObject(Proxy_GET_OBJECT(self), args); } /* * Number methods */ /* * Number methods. */ static PyObject * call_int(PyObject *self) { PyNumberMethods *nb = self->ob_type->tp_as_number; if (nb == NULL || nb->nb_int == NULL) { PyErr_SetString(PyExc_TypeError, "object can't be converted to int"); return NULL; } return nb->nb_int(self); } static PyObject * call_long(PyObject *self) { PyNumberMethods *nb = self->ob_type->tp_as_number; if (nb == NULL || nb->nb_long == NULL) { PyErr_SetString(PyExc_TypeError, "object can't be converted to long"); return NULL; } return nb->nb_long(self); } static PyObject * call_float(PyObject *self) { PyNumberMethods *nb = self->ob_type->tp_as_number; if (nb == NULL || nb->nb_float== NULL) { PyErr_SetString(PyExc_TypeError, "object can't be converted to float"); return NULL; } return nb->nb_float(self); } static PyObject * call_oct(PyObject *self) { PyNumberMethods *nb = self->ob_type->tp_as_number; if (nb == NULL || nb->nb_oct== NULL) { PyErr_SetString(PyExc_TypeError, "object can't be converted to oct"); return NULL; } return nb->nb_oct(self); } static PyObject * call_hex(PyObject *self) { PyNumberMethods *nb = self->ob_type->tp_as_number; if (nb == NULL || nb->nb_hex == NULL) { PyErr_SetString(PyExc_TypeError, "object can't be converted to hex"); return NULL; } return nb->nb_hex(self); } static PyObject * call_ipow(PyObject *self, PyObject *other) { /* PyNumber_InPlacePower has three args. How silly. :-) */ return PyNumber_InPlacePower(self, other, Py_None); } typedef PyObject *(*function1)(PyObject *); static PyObject * check1(ProxyObject *self, char *opname, function1 operation) { PyObject *result = NULL; result = operation(Proxy_GET_OBJECT(self)); #if 0 if (result != NULL) /* ??? create proxy for result? */ ; #endif return result; } static PyObject * check2(PyObject *self, PyObject *other, char *opname, char *ropname, binaryfunc operation) { PyObject *result = NULL; PyObject *object; if (Proxy_Check(self)) { object = Proxy_GET_OBJECT(self); result = operation(object, other); } else if (Proxy_Check(other)) { object = Proxy_GET_OBJECT(other); result = operation(self, object); } else { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } #if 0 if (result != NULL) /* ??? create proxy for result? */ ; #endif return result; } static PyObject * check2i(ProxyObject *self, PyObject *other, char *opname, binaryfunc operation) { PyObject *result = NULL; PyObject *object = Proxy_GET_OBJECT(self); result = operation(object, other); if (result == object) { /* If the operation was really carried out inplace, don't create a new proxy, but use the old one. */ Py_INCREF(self); Py_DECREF(object); result = (PyObject *)self; } #if 0 else if (result != NULL) /* ??? create proxy for result? */ ; #endif return result; } #define UNOP(NAME, CALL) \ static PyObject *wrap_##NAME(PyObject *self) \ { return check1((ProxyObject *)self, "__"#NAME"__", CALL); } #define BINOP(NAME, CALL) \ static PyObject *wrap_##NAME(PyObject *self, PyObject *other) \ { return check2(self, other, "__"#NAME"__", "__r"#NAME"__", CALL); } #define INPLACE(NAME, CALL) \ static PyObject *wrap_i##NAME(PyObject *self, PyObject *other) \ { return check2i((ProxyObject *)self, other, "__i"#NAME"__", CALL); } BINOP(add, PyNumber_Add) BINOP(sub, PyNumber_Subtract) BINOP(mul, PyNumber_Multiply) BINOP(div, PyNumber_Divide) BINOP(mod, PyNumber_Remainder) BINOP(divmod, PyNumber_Divmod) static PyObject * wrap_pow(PyObject *self, PyObject *other, PyObject *modulus) { PyObject *result = NULL; PyObject *object; if (Proxy_Check(self)) { object = Proxy_GET_OBJECT(self); result = PyNumber_Power(object, other, modulus); } else if (Proxy_Check(other)) { object = Proxy_GET_OBJECT(other); result = PyNumber_Power(self, object, modulus); } else if (modulus != NULL && Proxy_Check(modulus)) { object = Proxy_GET_OBJECT(modulus); result = PyNumber_Power(self, other, modulus); } else { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } return result; } BINOP(lshift, PyNumber_Lshift) BINOP(rshift, PyNumber_Rshift) BINOP(and, PyNumber_And) BINOP(xor, PyNumber_Xor) BINOP(or, PyNumber_Or) static int wrap_coerce(PyObject **p_self, PyObject **p_other) { PyObject *self = *p_self; PyObject *other = *p_other; PyObject *object; PyObject *left; PyObject *right; int r; assert(Proxy_Check(self)); object = Proxy_GET_OBJECT(self); left = object; right = other; r = PyNumber_CoerceEx(&left, &right); if (r != 0) return r; /* Now left and right have been INCREF'ed. Any new value that comes out is proxied; any unchanged value is left unchanged. */ if (left == object) { /* Keep the old proxy */ Py_INCREF(self); Py_DECREF(left); left = self; } #if 0 else { /* ??? create proxy for left? */ } if (right != other) { /* ??? create proxy for right? */ } #endif *p_self = left; *p_other = right; return 0; } UNOP(neg, PyNumber_Negative) UNOP(pos, PyNumber_Positive) UNOP(abs, PyNumber_Absolute) UNOP(invert, PyNumber_Invert) UNOP(int, call_int) UNOP(long, call_long) UNOP(float, call_float) UNOP(oct, call_oct) UNOP(hex, call_hex) INPLACE(add, PyNumber_InPlaceAdd) INPLACE(sub, PyNumber_InPlaceSubtract) INPLACE(mul, PyNumber_InPlaceMultiply) INPLACE(div, PyNumber_InPlaceDivide) INPLACE(mod, PyNumber_InPlaceRemainder) INPLACE(pow, call_ipow) INPLACE(lshift, PyNumber_InPlaceLshift) INPLACE(rshift, PyNumber_InPlaceRshift) INPLACE(and, PyNumber_InPlaceAnd) INPLACE(xor, PyNumber_InPlaceXor) INPLACE(or, PyNumber_InPlaceOr) BINOP(floordiv, PyNumber_FloorDivide) BINOP(truediv, PyNumber_TrueDivide) INPLACE(floordiv, PyNumber_InPlaceFloorDivide) INPLACE(truediv, PyNumber_InPlaceTrueDivide) static int wrap_nonzero(PyObject *self) { return PyObject_IsTrue(Proxy_GET_OBJECT(self)); } /* * Sequence methods */ static Py_ssize_t wrap_length(PyObject *self) { return PyObject_Length(Proxy_GET_OBJECT(self)); } static PyObject * wrap_slice(PyObject *self, Py_ssize_t start, Py_ssize_t end) { return PySequence_GetSlice(Proxy_GET_OBJECT(self), start, end); } static int wrap_ass_slice(PyObject *self, Py_ssize_t i, Py_ssize_t j, PyObject *value) { return PySequence_SetSlice(Proxy_GET_OBJECT(self), i, j, value); } static int wrap_contains(PyObject *self, PyObject *value) { return PySequence_Contains(Proxy_GET_OBJECT(self), value); } /* * Mapping methods */ static PyObject * wrap_getitem(PyObject *wrapper, PyObject *v) { return PyObject_GetItem(Proxy_GET_OBJECT(wrapper), v); } static int wrap_setitem(PyObject *self, PyObject *key, PyObject *value) { if (value == NULL) return PyObject_DelItem(Proxy_GET_OBJECT(self), key); else return PyObject_SetItem(Proxy_GET_OBJECT(self), key, value); } /* * Normal methods */ static char reduce__doc__[] = "__reduce__()\n" "Raise an exception; this prevents proxies from being picklable by\n" "default, even if the underlying object is picklable."; static PyObject * wrap_reduce(PyObject *self) { PyObject *pickle_error = NULL; PyObject *pickle = PyImport_ImportModule("pickle"); if (pickle == NULL) PyErr_Clear(); else { pickle_error = PyObject_GetAttrString(pickle, "PicklingError"); if (pickle_error == NULL) PyErr_Clear(); } if (pickle_error == NULL) { pickle_error = PyExc_RuntimeError; Py_INCREF(pickle_error); } PyErr_SetString(pickle_error, "proxy instances cannot be pickled"); Py_DECREF(pickle_error); return NULL; } static PyNumberMethods wrap_as_number = { wrap_add, /* nb_add */ wrap_sub, /* nb_subtract */ wrap_mul, /* nb_multiply */ wrap_div, /* nb_divide */ wrap_mod, /* nb_remainder */ wrap_divmod, /* nb_divmod */ wrap_pow, /* nb_power */ wrap_neg, /* nb_negative */ wrap_pos, /* nb_positive */ wrap_abs, /* nb_absolute */ wrap_nonzero, /* nb_nonzero */ wrap_invert, /* nb_invert */ wrap_lshift, /* nb_lshift */ wrap_rshift, /* nb_rshift */ wrap_and, /* nb_and */ wrap_xor, /* nb_xor */ wrap_or, /* nb_or */ wrap_coerce, /* nb_coerce */ wrap_int, /* nb_int */ wrap_long, /* nb_long */ wrap_float, /* nb_float */ wrap_oct, /* nb_oct */ wrap_hex, /* nb_hex */ /* Added in release 2.0 */ /* These require the Py_TPFLAGS_HAVE_INPLACEOPS flag */ wrap_iadd, /* nb_inplace_add */ wrap_isub, /* nb_inplace_subtract */ wrap_imul, /* nb_inplace_multiply */ wrap_idiv, /* nb_inplace_divide */ wrap_imod, /* nb_inplace_remainder */ (ternaryfunc)wrap_ipow, /* nb_inplace_power */ wrap_ilshift, /* nb_inplace_lshift */ wrap_irshift, /* nb_inplace_rshift */ wrap_iand, /* nb_inplace_and */ wrap_ixor, /* nb_inplace_xor */ wrap_ior, /* nb_inplace_or */ /* Added in release 2.2 */ /* These require the Py_TPFLAGS_HAVE_CLASS flag */ wrap_floordiv, /* nb_floor_divide */ wrap_truediv, /* nb_true_divide */ wrap_ifloordiv, /* nb_inplace_floor_divide */ wrap_itruediv, /* nb_inplace_true_divide */ }; static PySequenceMethods wrap_as_sequence = { wrap_length, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ wrap_slice, /* sq_slice */ 0, /* sq_ass_item */ wrap_ass_slice, /* sq_ass_slice */ wrap_contains, /* sq_contains */ }; static PyMappingMethods wrap_as_mapping = { wrap_length, /* mp_length */ wrap_getitem, /* mp_subscript */ wrap_setitem, /* mp_ass_subscript */ }; static PyMethodDef wrap_methods[] = { {"__reduce__", (PyCFunction)wrap_reduce, METH_NOARGS, reduce__doc__}, {NULL, NULL}, }; /* * Note that the numeric methods are not supported. This is primarily * because of the way coercion-less operations are performed with * new-style numbers; since we can't tell which side of the operation * is 'self', we can't ensure we'd unwrap the right thing to perform * the actual operation. We also can't afford to just unwrap both * sides the way weakrefs do, since we don't know what semantics will * be associated with the wrapper itself. */ statichere PyTypeObject ProxyType = { PyObject_HEAD_INIT(NULL) /* PyObject_HEAD_INIT(&PyType_Type) */ 0, "zope.proxy.ProxyBase", sizeof(ProxyObject), 0, wrap_dealloc, /* tp_dealloc */ wrap_print, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ wrap_compare, /* tp_compare */ wrap_repr, /* tp_repr */ &wrap_as_number, /* tp_as_number */ &wrap_as_sequence, /* tp_as_sequence */ &wrap_as_mapping, /* tp_as_mapping */ wrap_hash, /* tp_hash */ wrap_call, /* tp_call */ wrap_str, /* tp_str */ wrap_getattro, /* tp_getattro */ wrap_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ wrap_traverse, /* tp_traverse */ wrap_clear, /* tp_clear */ wrap_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ wrap_iter, /* tp_iter */ wrap_iternext, /* tp_iternext */ wrap_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ wrap_init, /* tp_init */ 0, /* tp_alloc */ wrap_new, /* tp_new */ 0, /*_PyObject_GC_Del,*/ /* tp_free */ }; static PyObject * create_proxy(PyObject *object) { PyObject *result = NULL; PyObject *args; args = PyTuple_New(1); if (args != NULL) { Py_INCREF(object); PyTuple_SET_ITEM(args, 0, object); result = PyObject_CallObject((PyObject *)&ProxyType, args); Py_DECREF(args); } return result; } static int api_check(PyObject *obj) { return obj ? Proxy_Check(obj) : 0; } static PyObject * api_create(PyObject *object) { if (object == NULL) { PyErr_SetString(PyExc_ValueError, "cannot create proxy around NULL"); return NULL; } return create_proxy(object); } static PyObject * api_getobject(PyObject *proxy) { if (proxy == NULL) { PyErr_SetString(PyExc_RuntimeError, "cannot pass NULL to ProxyAPI.getobject()"); return NULL; } if (Proxy_Check(proxy)) return Proxy_GET_OBJECT(proxy); else { PyErr_Format(PyExc_TypeError, "expected proxy object, got %s", proxy->ob_type->tp_name); return NULL; } } static ProxyInterface wrapper_capi = { &ProxyType, api_check, api_create, api_getobject, }; static PyObject *api_object = NULL; static char getobject__doc__[] = "getProxiedObject(proxy) --> object\n" "\n" "Get the underlying object for proxy, or the object itself, if it is\n" "not a proxy."; static PyObject * wrapper_getobject(PyObject *unused, PyObject *obj) { if (Proxy_Check(obj)) obj = Proxy_GET_OBJECT(obj); if (obj == NULL) obj = Py_None; Py_INCREF(obj); return obj; } static char isProxy__doc__[] = "Check whether the given object is a proxy\n" "\n" "If proxytype is not None, checkes whether the object is\n" "proxied by the given proxytype.\n" ; static PyObject * wrapper_isProxy(PyObject *unused, PyObject *args) { PyObject *obj, *result; PyTypeObject *proxytype=&ProxyType; if (! PyArg_ParseTuple(args, "O|O!:isProxy", &obj, &PyType_Type, &proxytype) ) return NULL; while (obj && Proxy_Check(obj)) { if (PyObject_TypeCheck(obj, proxytype)) { result = Py_True; Py_INCREF(result); return result; } obj = Proxy_GET_OBJECT(obj); } result = Py_False; Py_INCREF(result); return result; } static char removeAllProxies__doc__[] = "removeAllProxies(proxy) --> object\n" "\n" "Get the proxied object with no proxies\n" "\n" "If obj is not a proxied object, return obj.\n" "\n" "The returned object has no proxies.\n" ; static PyObject * wrapper_removeAllProxies(PyObject *unused, PyObject *obj) { while (obj && Proxy_Check(obj)) obj = Proxy_GET_OBJECT(obj); if (obj == NULL) obj = Py_None; Py_INCREF(obj); return obj; } static char sameProxiedObjects__doc__[] = "Check whether two objects are the same or proxies of the same object"; static PyObject * wrapper_sameProxiedObjects(PyObject *unused, PyObject *args) { PyObject *ob1, *ob2; if (! PyArg_ParseTuple(args, "OO:sameProxiedObjects", &ob1, &ob2)) return NULL; while (ob1 && Proxy_Check(ob1)) ob1 = Proxy_GET_OBJECT(ob1); while (ob2 && Proxy_Check(ob2)) ob2 = Proxy_GET_OBJECT(ob2); if (ob1 == ob2) ob1 = Py_True; else ob1 = Py_False; Py_INCREF(ob1); return ob1; } static char queryProxy__doc__[] = "Look for a proxy of the given type around the object\n" "\n" "If no such proxy can be found, return the default.\n" ; static PyObject * wrapper_queryProxy(PyObject *unused, PyObject *args) { PyObject *obj, *result=Py_None; PyTypeObject *proxytype=&ProxyType; if (! PyArg_ParseTuple(args, "O|O!O:queryProxy", &obj, &PyType_Type, &proxytype, &result) ) return NULL; while (obj && Proxy_Check(obj)) { if (PyObject_TypeCheck(obj, proxytype)) { Py_INCREF(obj); return obj; } obj = Proxy_GET_OBJECT(obj); } Py_INCREF(result); return result; } static char queryInnerProxy__doc__[] = "Look for the inner-most proxy of the given type around the object\n" "\n" "If no such proxy can be found, return the default.\n" "\n" "If there is such a proxy, return the inner-most one.\n" ; static PyObject * wrapper_queryInnerProxy(PyObject *unused, PyObject *args) { PyObject *obj, *result=Py_None; PyTypeObject *proxytype=&ProxyType; if (! PyArg_ParseTuple(args, "O|O!O:queryInnerProxy", &obj, &PyType_Type, &proxytype, &result) ) return NULL; while (obj && Proxy_Check(obj)) { if (PyObject_TypeCheck(obj, proxytype)) result = obj; obj = Proxy_GET_OBJECT(obj); } Py_INCREF(result); return result; } static char module___doc__[] = "Association between an object, a context object, and a dictionary.\n\ \n\ The context object and dictionary give additional context information\n\ associated with a reference to the basic object. The wrapper objects\n\ act as proxies for the original object."; static PyMethodDef module_functions[] = { {"getProxiedObject", wrapper_getobject, METH_O, getobject__doc__}, {"isProxy", wrapper_isProxy, METH_VARARGS, isProxy__doc__}, {"sameProxiedObjects", wrapper_sameProxiedObjects, METH_VARARGS, sameProxiedObjects__doc__}, {"queryProxy", wrapper_queryProxy, METH_VARARGS, queryProxy__doc__}, {"queryInnerProxy", wrapper_queryInnerProxy, METH_VARARGS, queryInnerProxy__doc__}, {"removeAllProxies", wrapper_removeAllProxies, METH_O, removeAllProxies__doc__}, {NULL} }; void init_zope_proxy_proxy(void) { PyObject *m = Py_InitModule3("_zope_proxy_proxy", module_functions, module___doc__); if (m == NULL) return; if (empty_tuple == NULL) empty_tuple = PyTuple_New(0); ProxyType.tp_free = _PyObject_GC_Del; if (PyType_Ready(&ProxyType) < 0) return; Py_INCREF(&ProxyType); PyModule_AddObject(m, "ProxyBase", (PyObject *)&ProxyType); if (api_object == NULL) { api_object = PyCObject_FromVoidPtr(&wrapper_capi, NULL); if (api_object == NULL) return; } Py_INCREF(api_object); PyModule_AddObject(m, "_CAPI", api_object); } zope2.13-2.13.21/source/zope.container/src/zope/container/interfaces.py0000644000175000017500000002263412214017546024631 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Container-related interfaces $Id: interfaces.py 100045 2009-05-17 17:41:52Z chrism $ """ __docformat__ = 'restructuredtext' from zope.interface import Interface, Invalid from zope.interface.common.mapping import IItemMapping from zope.interface.common.mapping import IReadMapping, IEnumerableMapping from zope.location.interfaces import ILocation from zope.schema import Set from zope.lifecycleevent.interfaces import IObjectModifiedEvent # the following imports provide backwards compatibility for consumers; # do not remove them from zope.lifecycleevent.interfaces import IObjectMovedEvent from zope.lifecycleevent.interfaces import IObjectAddedEvent from zope.lifecycleevent.interfaces import IObjectRemovedEvent from zope.location.interfaces import IContained # /end backwards compatibility imports from zope.container.i18n import ZopeMessageFactory as _ class DuplicateIDError(KeyError): pass class ContainerError(Exception): """An error of a container with one of its components.""" class InvalidContainerType(Invalid, TypeError): """The type of a container is not valid.""" class InvalidItemType(Invalid, TypeError): """The type of an item is not valid.""" class InvalidType(Invalid, TypeError): """The type of an object is not valid.""" class IItemContainer(IItemMapping): """Minimal readable container.""" class ISimpleReadContainer(IItemContainer, IReadMapping): """Readable content containers.""" class IReadContainer(ISimpleReadContainer, IEnumerableMapping): """Readable containers that can be enumerated.""" class IWriteContainer(Interface): """An interface for the write aspects of a container.""" def __setitem__(name, object): """Add the given `object` to the container under the given name. Raises a ``TypeError`` if the key is not a unicode or ascii string. Raises a ``ValueError`` if the key is empty, or if the key contains a character which is not allowed in an object name. Raises a ``KeyError`` if the key violates a uniqueness constraint. The container might choose to add a different object than the one passed to this method. If the object doesn't implement `IContained`, then one of two things must be done: 1. If the object implements `ILocation`, then the `IContained` interface must be declared for the object. 2. Otherwise, a `ContainedProxy` is created for the object and stored. The object's `__parent__` and `__name__` attributes are set to the container and the given name. If the old parent was ``None``, then an `IObjectAddedEvent` is generated, otherwise, an `IObjectMovedEvent` is generated. An `IContainerModifiedEvent` is generated for the container. If the object replaces another object, then the old object is deleted before the new object is added, unless the container vetos the replacement by raising an exception. If the object's `__parent__` and `__name__` were already set to the container and the name, then no events are generated and no hooks. This allows advanced clients to take over event generation. """ def __delitem__(name): """Delete the named object from the container. Raises a ``KeyError`` if the object is not found. If the deleted object's `__parent__` and `__name__` match the container and given name, then an `IObjectRemovedEvent` is generated and the attributes are set to ``None``. If the object can be adapted to `IObjectMovedEvent`, then the adapter's `moveNotify` method is called with the event. Unless the object's `__parent__` and `__name__` attributes were initially ``None``, generate an `IContainerModifiedEvent` for the container. If the object's `__parent__` and `__name__` were already set to ``None``, then no events are generated. This allows advanced clients to take over event generation. """ class IItemWriteContainer(IWriteContainer, IItemContainer): """A write container that also supports minimal reads.""" class IContainer(IReadContainer, IWriteContainer): """Readable and writable content container.""" class IContentContainer(IContainer): """A container that is to be used as a content type.""" class IBTreeContainer(IContainer): """Container that supports BTree semantics for some methods.""" def items(key=None): """Return an iterator over the key-value pairs in the container. If ``None`` is passed as `key`, this method behaves as if no argument were passed; exactly as required for ``IContainer.items()``. If `key` is in the container, the first item provided by the iterator will correspond to that key. Otherwise, the first item will be for the key that would come next if `key` were in the container. """ def keys(key=None): """Return an iterator over the keys in the container. If ``None`` is passed as `key`, this method behaves as if no argument were passed; exactly as required for ``IContainer.keys()``. If `key` is in the container, the first key provided by the iterator will be that key. Otherwise, the first key will be the one that would come next if `key` were in the container. """ def values(key=None): """Return an iterator over the values in the container. If ``None`` is passed as `key`, this method behaves as if no argument were passed; exactly as required for ``IContainer.values()``. If `key` is in the container, the first value provided by the iterator will correspond to that key. Otherwise, the first value will be for the key that would come next if `key` were in the container. """ class IOrdered(Interface): """Objects whose contents are maintained in order.""" def updateOrder(order): """Revise the order of keys, replacing the current ordering. order is a list or a tuple containing the set of existing keys in the new order. `order` must contain ``len(keys())`` items and cannot contain duplicate keys. Raises ``TypeError`` if order is not a tuple or a list. Raises ``ValueError`` if order contains an invalid set of keys. """ class IOrderedContainer(IOrdered, IContainer): """Containers whose contents are maintained in order.""" class IContainerNamesContainer(IContainer): """Containers that always choose names for their items.""" class IReservedNames(Interface): """A sequence of names that are reserved for that container""" reservedNames = Set( title=_(u'Reserved Names'), description=_(u'Names that are not allowed for addable content'), required=True, ) class NameReserved(ValueError): __doc__ = _("""The name is reserved for this container""") ############################################################################## # Adding objects class UnaddableError(ContainerError): """An object cannot be added to a container.""" def __init__(self, container, obj, message=""): self.container = container self.obj = obj self.message = message and ": %s" % message def __str__(self): return ("%(obj)s cannot be added " "to %(container)s%(message)s" % self.__dict__) class INameChooser(Interface): def checkName(name, object): """Check whether an object name is valid. Raises a user error if the name is not valid. """ def chooseName(name, object): """Choose a unique valid name for the object. The given name and object may be taken into account when choosing the name. chooseName is expected to always choose a valid name (that would pass the checkName test) and never raise an error. """ ############################################################################## # Modifying containers class IContainerModifiedEvent(IObjectModifiedEvent): """The container has been modified. This event is specific to "containerness" modifications, which means addition, removal or reordering of sub-objects. """ ############################################################################## # Finding objects class IFind(Interface): """ Find support for containers. """ def find(id_filters=None, object_filters=None): """Find object that matches all filters in all sub-objects. This container itself is not included. """ class IObjectFindFilter(Interface): def matches(object): """Return True if the object matches the filter criteria.""" class IIdFindFilter(Interface): def matches(id): """Return True if the id matches the filter criteria.""" zope2.13-2.13.21/source/zope.container/src/zope/container/folder.py0000644000175000017500000000701412214017546023754 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """The standard Zope Folder. $Id: folder.py 96116 2009-02-05 00:47:40Z tseaver $ """ __docformat__ = 'restructuredtext' from BTrees.OOBTree import OOBTree from persistent import Persistent from zope.container.interfaces import IContainer, IContentContainer from zope.container.contained import Contained, setitem, uncontained from zope.interface import implements, directlyProvides # XXX This container implementation is really only used by # zope.site.folder.Folder. Please do not use it. # XXX Check whether this IContainer implementation cannot really # be replaced by the BTreeContainer. class Folder(Persistent, Contained): """The standard Zope Folder implementation.""" implements(IContentContainer) def __init__(self): self.data = OOBTree() def keys(self): """Return a sequence-like object containing the names associated with the objects that appear in the folder """ return self.data.keys() def __iter__(self): return iter(self.data.keys()) def values(self): """Return a sequence-like object containing the objects that appear in the folder. """ return self.data.values() def items(self): """Return a sequence-like object containing tuples of the form (name, object) for the objects that appear in the folder. """ return self.data.items() def __getitem__(self, name): """Return the named object, or raise ``KeyError`` if the object is not found. """ return self.data[name] def get(self, name, default=None): """Return the named object, or the value of the `default` argument if the object is not found. """ return self.data.get(name, default) def __contains__(self, name): """Return true if the named object appears in the folder.""" return self.data.has_key(name) def __len__(self): """Return the number of objects in the folder.""" return len(self.data) def __setitem__(self, name, object): """Add the given object to the folder under the given name.""" if not (isinstance(name, str) or isinstance(name, unicode)): raise TypeError("Name must be a string rather than a %s" % name.__class__.__name__) try: unicode(name) except UnicodeError: raise TypeError("Non-unicode names must be 7-bit-ascii only") if not name: raise TypeError("Name must not be empty") if name in self.data: raise KeyError("name, %s, is already in use" % name) setitem(self, self.data.__setitem__, name, object) def __delitem__(self, name): """Delete the named object from the folder. Raises a KeyError if the object is not found.""" uncontained(self.data[name], self, name) del self.data[name] zope2.13-2.13.21/source/zope.container/src/zope/container/__init__.py0000644000175000017500000000007512214017546024240 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.container/src/zope/container/traversal.py0000644000175000017500000001013712214017546024504 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Traversal components for containers $Id: traversal.py 97680 2009-03-09 07:32:19Z wosc $ """ __docformat__ = 'restructuredtext' from zope.interface import implements, providedBy from zope.component import queryMultiAdapter, getSiteManager from zope.component import ComponentLookupError from zope.traversing.interfaces import TraversalError, ITraversable from zope.publisher.interfaces.browser import IBrowserPublisher from zope.publisher.interfaces.xmlrpc import IXMLRPCPublisher from zope.publisher.interfaces import IDefaultViewName, NotFound from zope.container.interfaces import ISimpleReadContainer, IItemContainer from zope.container.interfaces import IReadContainer # Note that the next two classes are included here because they # can be used for multiple view types. class ContainerTraverser(object): """A traverser that knows how to look up objects by name in a container.""" implements(IBrowserPublisher, IXMLRPCPublisher) __used_for__ = ISimpleReadContainer def __init__(self, container, request): self.context = container self.request = request def publishTraverse(self, request, name): """See zope.publisher.interfaces.IPublishTraverse""" subob = self.context.get(name, None) if subob is None: view = queryMultiAdapter((self.context, request), name=name) if view is not None: return view raise NotFound(self.context, name, request) return subob def browserDefault(self, request): """See zope.publisher.browser.interfaces.IBrowserPublisher""" # XXX this re-implements zope.app.publisher.browser.getDefaultViewName() # to break our only dependency on it. view_name = getSiteManager(None).adapters.lookup( map(providedBy, (self.context, request)), IDefaultViewName) if view_name is None: raise ComponentLookupError("Couldn't find default view name", self.context, request) view_uri = "@@%s" %view_name return self.context, (view_uri,) class ItemTraverser(ContainerTraverser): """A traverser that knows how to look up objects by name in an item container.""" __used_for__ = IItemContainer def publishTraverse(self, request, name): """See zope.publisher.interfaces.IPublishTraverse""" try: return self.context[name] except KeyError: view = queryMultiAdapter((self.context, request), name=name) if view is not None: return view raise NotFound(self.context, name, request) _marker = object() class ContainerTraversable(object): """Traverses containers via `getattr` and `get`.""" implements(ITraversable) __used_for__ = IReadContainer def __init__(self, container): self._container = container def traverse(self, name, furtherPath): container = self._container v = container.get(name, _marker) if v is _marker: try: # Note that if name is a unicode string, getattr will # implicitly try to encode it using the system # encoding (usually ascii). Failure to encode means # invalid attribute name. v = getattr(container, name, _marker) except UnicodeEncodeError: raise TraversalError(container, name) if v is _marker: raise TraversalError(container, name) return v zope2.13-2.13.21/source/zope.container/src/zope/container/contained.py0000644000175000017500000006540212214017546024452 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Classes to support implementing `IContained` $Id: contained.py 111341 2010-04-24 12:01:17Z ccomb $ """ __docformat__ = 'restructuredtext' import zope.component import zope.interface.declarations from zope.interface import providedBy from zope.interface.declarations import getObjectSpecification from zope.interface.declarations import ObjectSpecification from zope.event import notify from zope.location.interfaces import ILocation, ISublocations from zope.security.checker import selectChecker, CombinedChecker from zope.lifecycleevent import ObjectModifiedEvent from zope.container.i18n import ZopeMessageFactory as _ from zope.location.interfaces import IContained from zope.container.interfaces import INameChooser from zope.container.interfaces import IReservedNames, NameReserved from zope.container.interfaces import IContainerModifiedEvent from zope.container._zope_container_contained import ContainedProxyBase from zope.container._zope_container_contained import getProxiedObject from zope.lifecycleevent import ObjectMovedEvent from zope.lifecycleevent import ObjectAddedEvent from zope.lifecycleevent import ObjectRemovedEvent # BBB ZODB3 < 3.10 try: from ZODB.interfaces import IBroken except ImportError: from zope.broken.interfaces import IBroken class Contained(object): """Stupid mix-in that defines `__parent__` and `__name__` attributes""" zope.interface.implements(IContained) __parent__ = __name__ = None class ContainerModifiedEvent(ObjectModifiedEvent): """The container has been modified.""" zope.interface.implements(IContainerModifiedEvent) def dispatchToSublocations(object, event): """Dispatch an event to sublocations of a given object When a move event happens for an object, it's important to notify subobjects as well. We do this based on locations. Suppose, for example, that we define some location objects. >>> class L(object): ... zope.interface.implements(ILocation) ... def __init__(self, name): ... self.__name__ = name ... self.__parent__ = None ... def __repr__(self): ... return '%s(%s)' % ( ... self.__class__.__name__, str(self.__name__)) >>> class C(L): ... zope.interface.implements(ISublocations) ... def __init__(self, name, *subs): ... L.__init__(self, name) ... self.subs = subs ... for sub in subs: ... sub.__parent__ = self ... def sublocations(self): ... return self.subs >>> c = C(1, ... C(11, ... L(111), ... L(112), ... ), ... C(12, ... L(121), ... L(122), ... L(123), ... L(124), ... ), ... L(13), ... ) Now, if we call the dispatcher, it should call event handlers for all of the objects. Lets create an event handler that records the objects it sees: >>> seen = [] >>> def handler(ob, event): ... seen.append((ob, event.object)) Note that we record the the object the handler is called on as well as the event object: Now we'll register it: >>> from zope import component >>> from zope.lifecycleevent.interfaces import IObjectMovedEvent >>> component.provideHandler(handler, [None, IObjectMovedEvent]) We also register our dispatcher: >>> component.provideHandler(dispatchToSublocations, ... [None, IObjectMovedEvent]) We can then call the dispatcher for the root object: >>> event = ObjectRemovedEvent(c) >>> dispatchToSublocations(c, event) Now, we should have seen all of the subobjects: >>> seenreprs = map(repr, seen) >>> seenreprs.sort() >>> seenreprs ['(C(11), C(1))', '(C(12), C(1))', '(L(111), C(1))',""" \ """ '(L(112), C(1))', '(L(121), C(1))', '(L(122), C(1))',""" \ """ '(L(123), C(1))', '(L(124), C(1))', '(L(13), C(1))'] We see that we get entries for each of the subobjects and that,for each entry, the event object is top object. This suggests that location event handlers need to be aware that the objects they are called on and the event objects could be different. """ subs = ISublocations(object, None) if subs is not None: for sub in subs.sublocations(): for ignored in zope.component.subscribers((sub, event), None): pass # They do work in the adapter fetch class ContainerSublocations(object): """Get the sublocations for a container Obviously, this is the container values: >>> class MyContainer(object): ... def __init__(self, **data): ... self.data = data ... def __iter__(self): ... return iter(self.data) ... def __getitem__(self, key): ... return self.data[key] >>> container = MyContainer(x=1, y=2, z=42) >>> adapter = ContainerSublocations(container) >>> sublocations = list(adapter.sublocations()) >>> sublocations.sort() >>> sublocations [1, 2, 42] """ def __init__(self, container): self.container = container def sublocations(self): container = self.container for key in container: yield container[key] def containedEvent(object, container, name=None): """Establish the containment of the object in the container The object and necessary event are returned. The object may be a `ContainedProxy` around the original object. The event is an added event, a moved event, or None. If the object implements `IContained`, simply set its `__parent__` and `__name__` attributes: >>> container = {} >>> item = Contained() >>> x, event = containedEvent(item, container, u'foo') >>> x is item True >>> item.__parent__ is container True >>> item.__name__ u'foo' We have an added event: >>> event.__class__.__name__ 'ObjectAddedEvent' >>> event.object is item True >>> event.newParent is container True >>> event.newName u'foo' >>> event.oldParent >>> event.oldName Now if we call contained again: >>> x2, event = containedEvent(item, container, u'foo') >>> x2 is item True >>> item.__parent__ is container True >>> item.__name__ u'foo' We don't get a new added event: >>> event If the object already had a parent but the parent or name was different, we get a moved event: >>> x, event = containedEvent(item, container, u'foo2') >>> event.__class__.__name__ 'ObjectMovedEvent' >>> event.object is item True >>> event.newParent is container True >>> event.newName u'foo2' >>> event.oldParent is container True >>> event.oldName u'foo' If the `object` implements `ILocation`, but not `IContained`, set its `__parent__` and `__name__` attributes *and* declare that it implements `IContained`: >>> from zope.location import Location >>> item = Location() >>> IContained.providedBy(item) False >>> x, event = containedEvent(item, container, 'foo') >>> x is item True >>> item.__parent__ is container True >>> item.__name__ 'foo' >>> IContained.providedBy(item) True If the `object` doesn't even implement `ILocation`, put a `ContainedProxy` around it: >>> item = [] >>> x, event = containedEvent(item, container, 'foo') >>> x is item False >>> x.__parent__ is container True >>> x.__name__ 'foo' Make sure we don't lose existing directly provided interfaces. >>> from zope.interface import Interface, directlyProvides >>> class IOther(Interface): ... pass >>> from zope.location import Location >>> item = Location() >>> directlyProvides(item, IOther) >>> IOther.providedBy(item) True >>> x, event = containedEvent(item, container, 'foo') >>> IOther.providedBy(item) True """ if not IContained.providedBy(object): if ILocation.providedBy(object): zope.interface.alsoProvides(object, IContained) else: object = ContainedProxy(object) oldparent = object.__parent__ oldname = object.__name__ if oldparent is container and oldname == name: # No events return object, None object.__parent__ = container object.__name__ = name if oldparent is None or oldname is None: event = ObjectAddedEvent(object, container, name) else: event = ObjectMovedEvent(object, oldparent, oldname, container, name) return object, event def contained(object, container, name=None): """Establish the containment of the object in the container Just return the contained object without an event. This is a convenience "macro" for: ``containedEvent(object, container, name)[0]`` This function is only used for tests. """ return containedEvent(object, container, name)[0] def notifyContainerModified(object, *descriptions): """Notify that the container was modified.""" notify(ContainerModifiedEvent(object, *descriptions)) def setitem(container, setitemf, name, object): """Helper function to set an item and generate needed events This helper is needed, in part, because the events need to get published after the `object` has been added to the `container`. If the item implements `IContained`, simply set its `__parent__` and `__name__` attributes: >>> class IItem(zope.interface.Interface): ... pass >>> class Item(Contained): ... zope.interface.implements(IItem) ... def setAdded(self, event): ... self.added = event ... def setMoved(self, event): ... self.moved = event >>> from zope.lifecycleevent.interfaces import IObjectAddedEvent >>> from zope.lifecycleevent.interfaces import IObjectMovedEvent >>> from zope import component >>> component.provideHandler(lambda obj, event: obj.setAdded(event), ... [IItem, IObjectAddedEvent]) >>> component.provideHandler(lambda obj, event: obj.setMoved(event), ... [IItem, IObjectMovedEvent]) >>> item = Item() >>> container = {} >>> setitem(container, container.__setitem__, u'c', item) >>> container[u'c'] is item 1 >>> item.__parent__ is container 1 >>> item.__name__ u'c' If we run this using the testing framework, we'll use `getEvents` to track the events generated: >>> from zope.component.eventtesting import getEvents >>> from zope.lifecycleevent.interfaces import IObjectModifiedEvent We have an added event: >>> len(getEvents(IObjectAddedEvent)) 1 >>> event = getEvents(IObjectAddedEvent)[-1] >>> event.object is item 1 >>> event.newParent is container 1 >>> event.newName u'c' >>> event.oldParent >>> event.oldName As well as a modification event for the container: >>> len(getEvents(IObjectModifiedEvent)) 1 >>> getEvents(IObjectModifiedEvent)[-1].object is container 1 The item's hooks have been called: >>> item.added is event 1 >>> item.moved is event 1 We can suppress events and hooks by setting the `__parent__` and `__name__` first: >>> item = Item() >>> item.__parent__, item.__name__ = container, 'c2' >>> setitem(container, container.__setitem__, u'c2', item) >>> len(container) 2 >>> len(getEvents(IObjectAddedEvent)) 1 >>> len(getEvents(IObjectModifiedEvent)) 1 >>> getattr(item, 'added', None) >>> getattr(item, 'moved', None) If the item had a parent or name (as in a move or rename), we generate a move event, rather than an add event: >>> setitem(container, container.__setitem__, u'c3', item) >>> len(container) 3 >>> len(getEvents(IObjectAddedEvent)) 1 >>> len(getEvents(IObjectModifiedEvent)) 2 >>> len(getEvents(IObjectMovedEvent)) 2 (Note that we have 2 move events because add are move events.) We also get the move hook called, but not the add hook: >>> event = getEvents(IObjectMovedEvent)[-1] >>> getattr(item, 'added', None) >>> item.moved is event 1 If we try to replace an item without deleting it first, we'll get an error: >>> setitem(container, container.__setitem__, u'c', []) Traceback (most recent call last): ... KeyError: u'c' >>> del container[u'c'] >>> setitem(container, container.__setitem__, u'c', []) >>> len(getEvents(IObjectAddedEvent)) 2 >>> len(getEvents(IObjectModifiedEvent)) 3 If the object implements `ILocation`, but not `IContained`, set it's `__parent__` and `__name__` attributes *and* declare that it implements `IContained`: >>> from zope.location import Location >>> item = Location() >>> IContained.providedBy(item) 0 >>> setitem(container, container.__setitem__, u'l', item) >>> container[u'l'] is item 1 >>> item.__parent__ is container 1 >>> item.__name__ u'l' >>> IContained.providedBy(item) 1 We get new added and modification events: >>> len(getEvents(IObjectAddedEvent)) 3 >>> len(getEvents(IObjectModifiedEvent)) 4 If the object doesn't even implement `ILocation`, put a `ContainedProxy` around it: >>> item = [] >>> setitem(container, container.__setitem__, u'i', item) >>> container[u'i'] [] >>> container[u'i'] is item 0 >>> item = container[u'i'] >>> item.__parent__ is container 1 >>> item.__name__ u'i' >>> IContained.providedBy(item) 1 >>> len(getEvents(IObjectAddedEvent)) 4 >>> len(getEvents(IObjectModifiedEvent)) 5 We'll get type errors if we give keys that aren't unicode or ascii keys: >>> setitem(container, container.__setitem__, 42, item) Traceback (most recent call last): ... TypeError: name not unicode or ascii string >>> setitem(container, container.__setitem__, None, item) Traceback (most recent call last): ... TypeError: name not unicode or ascii string >>> setitem(container, container.__setitem__, 'hello ' + chr(200), item) Traceback (most recent call last): ... TypeError: name not unicode or ascii string and we'll get a value error of we give an empty string or unicode: >>> setitem(container, container.__setitem__, '', item) Traceback (most recent call last): ... ValueError: empty names are not allowed >>> setitem(container, container.__setitem__, u'', item) Traceback (most recent call last): ... ValueError: empty names are not allowed """ # Do basic name check: if isinstance(name, str): try: name = unicode(name) except UnicodeError: raise TypeError("name not unicode or ascii string") elif not isinstance(name, unicode): raise TypeError("name not unicode or ascii string") if not name: raise ValueError("empty names are not allowed") old = container.get(name) if old is object: return if old is not None: raise KeyError(name) object, event = containedEvent(object, container, name) setitemf(name, object) if event: notify(event) notifyContainerModified(container) fixing_up = False def uncontained(object, container, name=None): """Clear the containment relationship between the `object` and the `container`. If we run this using the testing framework, we'll use `getEvents` to track the events generated: >>> from zope.component.eventtesting import getEvents >>> from zope.lifecycleevent.interfaces import IObjectModifiedEvent >>> from zope.lifecycleevent.interfaces import IObjectRemovedEvent We'll start by creating a container with an item: >>> class Item(Contained): ... pass >>> item = Item() >>> container = {u'foo': item} >>> x, event = containedEvent(item, container, u'foo') >>> item.__parent__ is container 1 >>> item.__name__ u'foo' Now we'll remove the item. It's parent and name are cleared: >>> uncontained(item, container, u'foo') >>> item.__parent__ >>> item.__name__ We now have a new removed event: >>> len(getEvents(IObjectRemovedEvent)) 1 >>> event = getEvents(IObjectRemovedEvent)[-1] >>> event.object is item 1 >>> event.oldParent is container 1 >>> event.oldName u'foo' >>> event.newParent >>> event.newName As well as a modification event for the container: >>> len(getEvents(IObjectModifiedEvent)) 1 >>> getEvents(IObjectModifiedEvent)[-1].object is container 1 Now if we call uncontained again: >>> uncontained(item, container, u'foo') We won't get any new events, because __parent__ and __name__ are None: >>> len(getEvents(IObjectRemovedEvent)) 1 >>> len(getEvents(IObjectModifiedEvent)) 1 But, if either the name or parent are not ``None`` and they are not the container and the old name, we'll get a modified event but not a removed event. >>> item.__parent__, item.__name__ = container, None >>> uncontained(item, container, u'foo') >>> len(getEvents(IObjectRemovedEvent)) 1 >>> len(getEvents(IObjectModifiedEvent)) 2 >>> item.__parent__, item.__name__ = None, u'bar' >>> uncontained(item, container, u'foo') >>> len(getEvents(IObjectRemovedEvent)) 1 >>> len(getEvents(IObjectModifiedEvent)) 3 """ try: oldparent = object.__parent__ oldname = object.__name__ except AttributeError: # The old object doesn't implements IContained # Maybe we're converting old data: if not fixing_up: raise oldparent = None oldname = None if oldparent is not container or oldname != name: if oldparent is not None or oldname is not None: notifyContainerModified(container) return event = ObjectRemovedEvent(object, oldparent, oldname) notify(event) if not IBroken.providedBy(object): object.__parent__ = None object.__name__ = None notifyContainerModified(container) class NameChooser(object): zope.interface.implements(INameChooser) def __init__(self, context): self.context = context def checkName(self, name, object): """See zope.container.interfaces.INameChooser We create and populate a dummy container >>> from zope.container.sample import SampleContainer >>> container = SampleContainer() >>> container['foo'] = 'bar' >>> from zope.container.contained import NameChooser An invalid name raises a ValueError: >>> NameChooser(container).checkName('+foo', object()) Traceback (most recent call last): ... ValueError: Names cannot begin with '+' or '@' or contain '/' A name that already exists raises a KeyError: >>> NameChooser(container).checkName('foo', object()) Traceback (most recent call last): ... KeyError: u'The given name is already being used' A name must be a string or unicode string: >>> NameChooser(container).checkName(2, object()) Traceback (most recent call last): ... TypeError: ('Invalid name type', ) A correct name returns True: >>> NameChooser(container).checkName('2', object()) True We can reserve some names by providing a IReservedNames adapter to a container: >>> from zope.container.interfaces import IContainer >>> class ReservedNames(object): ... zope.component.adapts(IContainer) ... zope.interface.implements(IReservedNames) ... ... def __init__(self, context): ... self.reservedNames = set(('reserved', 'other')) >>> zope.component.getSiteManager().registerAdapter(ReservedNames) >>> NameChooser(container).checkName('reserved', None) Traceback (most recent call last): ... NameReserved: reserved """ if isinstance(name, str): name = unicode(name) elif not isinstance(name, unicode): raise TypeError("Invalid name type", type(name)) if not name: raise ValueError( _("An empty name was provided. Names cannot be empty.") ) if name[:1] in '+@' or '/' in name: raise ValueError( _("Names cannot begin with '+' or '@' or contain '/'") ) reserved = IReservedNames(self.context, None) if reserved is not None: if name in reserved.reservedNames: raise NameReserved(name) if name in self.context: raise KeyError( _("The given name is already being used") ) return True def chooseName(self, name, object): """See zope.container.interfaces.INameChooser The name chooser is expected to choose a name without error We create and populate a dummy container >>> from zope.container.sample import SampleContainer >>> container = SampleContainer() >>> container['foobar.old'] = 'rst doc' >>> from zope.container.contained import NameChooser the suggested name is converted to unicode: >>> NameChooser(container).chooseName('foobar', object()) u'foobar' If it already exists, a number is appended but keeps the same extension: >>> NameChooser(container).chooseName('foobar.old', object()) u'foobar-2.old' Bad characters are turned into dashes: >>> NameChooser(container).chooseName('foo/foo', object()) u'foo-foo' If no name is suggested, it is based on the object type: >>> NameChooser(container).chooseName('', []) u'list' """ container = self.context # convert to unicode and remove characters that checkName does not allow try: name = unicode(name) except: name = u'' name = name.replace('/', '-').lstrip('+@') if not name: name = unicode(object.__class__.__name__) # for an existing name, append a number. # We should keep client's os.path.extsep (not ours), we assume it's '.' dot = name.rfind('.') if dot >= 0: suffix = name[dot:] name = name[:dot] else: suffix = '' n = name + suffix i = 1 while n in container: i += 1 n = name + u'-' + unicode(i) + suffix # Make sure the name is valid. We may have started with something bad. self.checkName(n, object) return n class DecoratorSpecificationDescriptor( zope.interface.declarations.ObjectSpecificationDescriptor): """Support for interface declarations on decorators >>> from zope.interface import * >>> class I1(Interface): ... pass >>> class I2(Interface): ... pass >>> class I3(Interface): ... pass >>> class I4(Interface): ... pass >>> class D1(ContainedProxy): ... implements(I1) >>> class D2(ContainedProxy): ... implements(I2) >>> class X: ... implements(I3) >>> x = X() >>> directlyProvides(x, I4) Interfaces of X are ordered with the directly-provided interfaces first >>> [interface.getName() for interface in list(providedBy(x))] ['I4', 'I3'] When we decorate objects, what order should the interfaces come in? One could argue that decorators are less specific, so they should come last. >>> [interface.getName() for interface in list(providedBy(D1(x)))] ['I4', 'I3', 'I1', 'IContained', 'IPersistent'] >>> [interface.getName() for interface in list(providedBy(D2(D1(x))))] ['I4', 'I3', 'I1', 'IContained', 'IPersistent', 'I2'] """ def __get__(self, inst, cls=None): if inst is None: return getObjectSpecification(cls) else: provided = providedBy(getProxiedObject(inst)) # Use type rather than __class__ because inst is a proxy and # will return the proxied object's class. cls = type(inst) return ObjectSpecification(provided, cls) class DecoratedSecurityCheckerDescriptor(object): """Descriptor for a Decorator that provides a decorated security checker. """ def __get__(self, inst, cls=None): if inst is None: return self else: proxied_object = getProxiedObject(inst) checker = getattr(proxied_object, '__Security_checker__', None) if checker is None: checker = selectChecker(proxied_object) wrapper_checker = selectChecker(inst) if wrapper_checker is None: return checker elif checker is None: return wrapper_checker else: return CombinedChecker(wrapper_checker, checker) class ContainedProxyClassProvides(zope.interface.declarations.ClassProvides): def __set__(self, inst, value): inst = getProxiedObject(inst) inst.__provides__ = value def __delete__(self, inst): inst = getProxiedObject(inst) del inst.__provides__ class ContainedProxy(ContainedProxyBase): # Prevent proxies from having their own instance dictionaries: __slots__ = () __safe_for_unpickling__ = True zope.interface.implements(IContained) __providedBy__ = DecoratorSpecificationDescriptor() __Security_checker__ = DecoratedSecurityCheckerDescriptor() ContainedProxy.__provides__ = ContainedProxyClassProvides(ContainedProxy, type) zope2.13-2.13.21/source/zope.container/src/zope/container/dependency.py0000644000175000017500000000140012214017546024610 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # BBB imports from zope.app.dependable.dependency import exception_msg from zope.app.dependable.dependency import CheckDependency zope2.13-2.13.21/source/zope.container/src/zope/container/directory.py0000644000175000017500000000714212214017546024507 0ustar arnauarnau############################################################################## # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. ############################################################################## """File-system representation adapters for containers This module includes two adapters (adapter factories, really) for providing a file-system representation for containers: `noop` Factory that "adapts" `IContainer` to `IWriteDirectory`. This is a lie, since it just returns the original object. `Cloner` An `IDirectoryFactory` adapter that just clones the original object. $Id: directory.py 105849 2009-11-19 07:04:24Z tlotze $ """ __docformat__ = 'restructuredtext' from zope.interface import implements from zope.component.interfaces import ISite from zope.security.proxy import removeSecurityProxy import zope.filerepresentation.interfaces MARKER = object() def noop(container): """Adapt an `IContainer` to an `IWriteDirectory` by just returning it This "works" because `IContainer` and `IWriteDirectory` have the same methods, however, the output doesn't actually implement `IWriteDirectory`. """ return container class Cloner(object): """`IContainer` to `IDirectoryFactory` adapter that clones This adapter provides a factory that creates a new empty container of the same class as it's context. """ implements(zope.filerepresentation.interfaces.IDirectoryFactory) def __init__(self, context): self.context = context def __call__(self, name): # We remove the security proxy so we can actually call the # class and return an unproxied new object. (We can't use a # trusted adapter, because the result must be unproxied.) By # registering this adapter, one effectively gives permission # to clone the class. Don't use this for classes that have # exciting side effects as a result of instantiation. :) return removeSecurityProxy(self.context).__class__() class RootDirectoryFactory(object): def __init__(self, context): pass def __call__(self, name): return Folder() class ReadDirectory(object): """Adapter to provide a file-system rendition of folders.""" def __init__(self, context): self.context = context def keys(self): keys = self.context.keys() if ISite.providedBy(self.context): return list(keys) + ['++etc++site'] return keys def get(self, key, default=None): if key == '++etc++site' and ISite.providedBy(self.context): return self.context.getSiteManager() return self.context.get(key, default) def __iter__(self): return iter(self.keys()) def __getitem__(self, key): v = self.get(key, MARKER) if v is MARKER: raise KeyError(key) return v def values(self): return map(self.get, self.keys()) def __len__(self): l = len(self.context) if ISite.providedBy(self.context): l += 1 return l def items(self): get = self.get return [(key, get(key)) for key in self.keys()] def __contains__(self, key): return self.get(key) is not None zope2.13-2.13.21/source/zope.container/src/zope/container/size.py0000644000175000017500000000252312214017546023453 0ustar arnauarnau ############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Adapters that give the size of an object. $Id: size.py 95341 2009-01-28 15:59:18Z wosc $ """ __docformat__ = 'restructuredtext' from zope.container.i18n import ZopeMessageFactory as _ from zope.size.interfaces import ISized from zope.interface import implements class ContainerSized(object): implements(ISized) def __init__(self, container): self._container = container def sizeForSorting(self): """See `ISized`""" return ('item', len(self._container)) def sizeForDisplay(self): """See `ISized`""" num_items = len(self._container) if num_items == 1: return _('1 item') return _('${items} items', mapping={'items': str(num_items)}) zope2.13-2.13.21/source/zope.container/src/zope/container/tests/0000755000175000017500000000000012214017546023267 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/src/zope/container/tests/directory.txt0000644000175000017500000000236612214017546026043 0ustar arnauarnau=============================== File representation for folders =============================== Folders can be represented in file-system-like protocols (e.g. FTP). An adapter abstracts some internals away and adds support for accessing the '++etc++site' folder from those protocols. >>> from zope.container.folder import Folder >>> from zope.container.directory import ReadDirectory >>> folder = Folder() The new folder isn't a site manager and doesn't have any entries: >>> fs_folder = ReadDirectory(folder) >>> list(fs_folder.keys()) [] >>> fs_folder.get('test', ) >>> fs_folder['test'] Traceback (most recent call last): KeyError: 'test' >>> list(fs_folder.__iter__()) [] >>> fs_folder.values() [] >>> len(fs_folder) 0 >>> fs_folder.items() [] >>> 'test' in fs_folder False This is a short regression test for #728: we get a KeyError when trying to access non-existing entries: >>> from zope.security.proxy import ProxyFactory >>> from zope.security.checker import NamesChecker >>> proxied_folder = ProxyFactory(fs_folder, NamesChecker(('get',))) >>> proxied_fs_folder = ReadDirectory(proxied_folder) >>> print proxied_fs_folder['i dont exist'] Traceback (most recent call last): KeyError: 'i dont exist' zope2.13-2.13.21/source/zope.container/src/zope/container/tests/test_containertraverser.py0000644000175000017500000000754012214017546030626 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Container Traverser Tests $Id: test_containertraverser.py 97680 2009-03-09 07:32:19Z wosc $ """ import unittest from zope.interface import Interface, implements from zope import component from zope.publisher.interfaces import NotFound, IDefaultViewName from zope.publisher.browser import TestRequest from zope.publisher.interfaces.browser import IDefaultBrowserLayer from zope.container.traversal import ContainerTraverser from zope.container.interfaces import IReadContainer from zope.container import testing class TestContainer(object): implements(IReadContainer) def __init__(self, **kw): for name, value in kw.items(): setattr(self, name , value) def get(self, name, default=None): return getattr(self, name, default) class View(object): def __init__(self, context, request): self.context = context self.request = request class TraverserTest(testing.ContainerPlacelessSetup, unittest.TestCase): # The following two methods exist, so that other container traversers can # use these tests as a base. def _getTraverser(self, context, request): return ContainerTraverser(context, request) def _getContainer(self, **kw): return TestContainer(**kw) def setUp(self): super(TraverserTest, self).setUp() # Create a small object tree self.container = self._getContainer() self.subcontainer = self._getContainer(Foo=self.container) # Initiate a request self.request = TestRequest() # Create the traverser self.traverser = self._getTraverser(self.subcontainer, self.request) # Define a simple view for the container component.provideAdapter( View, (IReadContainer, IDefaultBrowserLayer), Interface, name='viewfoo') def test_itemTraversal(self): self.assertEqual( self.traverser.publishTraverse(self.request, 'Foo'), self.container) self.assertRaises( NotFound, self.traverser.publishTraverse, self.request, 'morebar') def test_viewTraversal(self): self.assertEquals( self.traverser.publishTraverse(self.request, 'viewfoo').__class__, View) self.assertEquals( self.traverser.publishTraverse(self.request, 'Foo'), self.container) self.assertRaises( NotFound, self.traverser.publishTraverse, self.request, 'morebar') self.assertRaises( NotFound, self.traverser.publishTraverse, self.request, '@@morebar') def test_browserDefault_without_registration_should_raise(self): self.assertRaises(component.ComponentLookupError, self.traverser.browserDefault, self.request) def test_browserDefault(self): component.provideAdapter( 'myDefaultView', (Interface, IDefaultBrowserLayer), IDefaultViewName) self.assertEquals((self.subcontainer, ('@@myDefaultView',)), self.traverser.browserDefault(self.request)) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TraverserTest), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.container/src/zope/container/tests/test_dependencies.py0000644000175000017500000000365112214017546027333 0ustar arnauarnauimport unittest from zope.configuration.xmlconfig import XMLConfig from zope.interface import implements from zope.publisher.browser import TestRequest from zope.publisher.interfaces.browser import IBrowserPublisher from zope.container.interfaces import IItemContainer from zope.container.interfaces import ISimpleReadContainer from zope.container.traversal import ItemTraverser from zope.container.testing import ContainerPlacelessSetup class ZCMLDependencies(ContainerPlacelessSetup, unittest.TestCase): def test_zcml_can_load_with_only_zope_component_meta(self): # this is just an example. It is supposed to show that the # configure.zcml file has loaded successfully. import zope.component XMLConfig('meta.zcml', zope.component)() import zope.security XMLConfig('meta.zcml', zope.security)() XMLConfig('permissions.zcml', zope.security)() import zope.container XMLConfig('configure.zcml', zope.container)() request = TestRequest() class SampleItemContainer(object): implements(IItemContainer) sampleitemcontainer = SampleItemContainer() res = zope.component.getMultiAdapter( (sampleitemcontainer, request), IBrowserPublisher) self.failUnless(isinstance(res, ItemTraverser)) self.failUnless(res.context is sampleitemcontainer) class SampleSimpleReadContainer(object): implements(ISimpleReadContainer) samplesimplereadcontainer = SampleSimpleReadContainer() res = zope.component.getMultiAdapter( (samplesimplereadcontainer, request), IBrowserPublisher) self.failUnless(isinstance(res, ItemTraverser)) self.failUnless(res.context is samplesimplereadcontainer) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(ZCMLDependencies)) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.container/src/zope/container/tests/ftest_zcml_dependencies.zcml0000644000175000017500000000044212214017546031036 0ustar arnauarnau zope2.13-2.13.21/source/zope.container/src/zope/container/tests/test_contained.py0000644000175000017500000002731612214017546026655 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Contained Tests $Id: test_contained.py 111713 2010-04-30 20:43:09Z hannosch $ """ import doctest import gc import unittest from ZODB.DemoStorage import DemoStorage from ZODB.DB import DB import transaction from persistent import Persistent import zope.interface import zope.component from zope.container.contained import ContainedProxy, NameChooser from zope.container.sample import SampleContainer from zope.container import testing from zope.container.interfaces import NameReserved, IContainer, IReservedNames class MyOb(Persistent): pass def test_basic_proxy_attribute_management_and_picklability(): """Contained-object proxy This is a picklable proxy that can be put around objects that don't implement IContained. >>> l = [1, 2, 3] >>> p = ContainedProxy(l) >>> p.__parent__ = 'Dad' >>> p.__name__ = 'p' >>> p [1, 2, 3] >>> p.__parent__ 'Dad' >>> p.__name__ 'p' >>> import pickle >>> p2 = pickle.loads(pickle.dumps(p)) >>> p2 [1, 2, 3] >>> p2.__parent__ 'Dad' >>> p2.__name__ 'p' """ def test_basic_persistent_w_non_persistent_proxied(): """ >>> p = ContainedProxy([1]) >>> p.__parent__ = 2 >>> p.__name__ = 'test' >>> db = DB(DemoStorage('test_storage')) >>> c = db.open() >>> c.root()['p'] = p >>> transaction.commit() >>> c2 = db.open() >>> p2 = c2.root()['p'] >>> p2 [1] >>> p2.__parent__ 2 >>> p2.__name__ 'test' >>> p2._p_changed 0 >>> p2._p_deactivate() >>> p2._p_changed >>> p2.__name__ 'test' >>> db.close() """ def test_declarations_on_ContainedProxy(): r""" It is possible to make declarations on ContainedProxy objects. >>> class I1(zope.interface.Interface): ... pass >>> class C(object): ... zope.interface.implements(I1) >>> c = C() >>> p = ContainedProxy(c) ContainedProxy provides no interfaces on it's own: >>> tuple(zope.interface.providedBy(ContainedProxy)) () It implements IContained and IPersistent: >>> tuple(zope.interface.implementedBy(ContainedProxy)) (, ) A proxied object has IContainer, in addition to what the unproxied object has: >>> tuple(zope.interface.providedBy(p)) (, , ) >>> class I2(zope.interface.Interface): ... pass >>> zope.interface.directlyProvides(c, I2) >>> tuple(zope.interface.providedBy(p)) (, , , ) We can declare interfaces through the proxy: >>> class I3(zope.interface.Interface): ... pass >>> zope.interface.directlyProvides(p, I3) >>> tuple(zope.interface.providedBy(p)) (, , , ) """ def test_basic_persistent_w_persistent_proxied(): """ Here, we'll verify that shared references work and that updates to both the proxies and the proxied objects are made correctly. ---------------------- | | parent other | / ob <-------------- Here we have an object, parent, that contains ob. There is another object, other, that has a non-container reference to ob. >>> parent = MyOb() >>> parent.ob = ContainedProxy(MyOb()) >>> parent.ob.__parent__ = parent >>> parent.ob.__name__ = 'test' >>> other = MyOb() >>> other.ob = parent.ob We can change ob through either parent or other >>> parent.ob.x = 1 >>> other.ob.y = 2 Now we'll save the data: >>> db = DB(DemoStorage('test_storage')) >>> c1 = db.open() >>> c1.root()['parent'] = parent >>> c1.root()['other'] = other >>> transaction.commit() We'll open a second connection and verify that we have the data we expect: >>> c2 = db.open() >>> p2 = c2.root()['parent'] >>> p2.ob.__parent__ is p2 1 >>> p2.ob.x 1 >>> p2.ob.y 2 >>> o2 = c2.root()['other'] >>> o2.ob is p2.ob 1 >>> o2.ob is p2.ob 1 >>> o2.ob.__name__ 'test' Now we'll change things around a bit. We'll move things around a bit. We'll also add an attribute to ob >>> o2.ob.__name__ = 'test 2' >>> o2.ob.__parent__ = o2 >>> o2.ob.z = 3 >>> p2.ob.__parent__ is p2 0 >>> p2.ob.__parent__ is o2 1 And save the changes: >>> transaction.commit() Now we'll reopen the first connection and verify that we can see the changes: >>> c1.close() >>> c1 = db.open() >>> p2 = c1.root()['parent'] >>> p2.ob.__name__ 'test 2' >>> p2.ob.z 3 >>> p2.ob.__parent__ is c1.root()['other'] 1 >>> db.close() """ def test_proxy_cache_interaction(): """Test to make sure the proxy properly interacts with the object cache Persistent objects are their own weak refs. Thier deallocators need to notify their connection's cache that their object is being deallocated, so that it is removed from the cache. >>> from ZODB.tests.util import DB >>> db = DB() >>> db.setCacheSize(5) >>> conn = db.open() >>> conn.root()['p'] = ContainedProxy(None) We need to create some filler objects to push our proxy out of the cache: >>> for i in range(10): ... conn.root()[i] = MyOb() >>> transaction.commit() Let's get the oid of our proxy: >>> oid = conn.root()['p']._p_oid Now, we'll access the filler object's: >>> x = [getattr(conn.root()[i], 'x', 0) for i in range(10)] We've also accessed the root object. If we garbage-collect the cache: >>> conn._cache.incrgc() Then the root object will still be active, because it was accessed recently: >>> conn.root()._p_changed 0 And the proxy will be in the cache, because it's refernced from the root object: >>> conn._cache.get(oid) is not None True But it's a ghost: >>> conn.root()['p']._p_changed If we deactivate the root object: >>> conn.root()._p_deactivate() Then we'll release the last reference to the proxy and it should no longer be in the cache. To be sure, we'll call gc: >>> x = gc.collect() >>> conn._cache.get(oid) is not None False """ def test_ContainedProxy_instances_have_no_instance_dictionaries(): """Make sure that proxies don't introduce extra instance dictionaries >>> from zope.container.contained import ContainedProxy >>> class C: ... pass >>> c = C() >>> c.x = 1 >>> c.__dict__ {'x': 1} >>> p = ContainedProxy(c) >>> p.__dict__ {'x': 1} >>> p.y = 3 >>> p.__dict__ {'y': 3, 'x': 1} >>> c.__dict__ {'y': 3, 'x': 1} >>> p.__dict__ is c.__dict__ True """ class TestNameChooser(unittest.TestCase): def test_checkName(self): container = SampleContainer() container['foo'] = 'bar' checkName = NameChooser(container).checkName # invalid type for the name self.assertRaises(TypeError, checkName, 2, object()) self.assertRaises(TypeError, checkName, [], object()) self.assertRaises(TypeError, checkName, None, object()) self.assertRaises(TypeError, checkName, None, None) # invalid names self.assertRaises(ValueError, checkName, '+foo', object()) self.assertRaises(ValueError, checkName, '@foo', object()) self.assertRaises(ValueError, checkName, 'f/oo', object()) self.assertRaises(ValueError, checkName, '', object()) # existing names self.assertRaises(KeyError, checkName, 'foo', object()) self.assertRaises(KeyError, checkName, u'foo', object()) # correct names self.assertEqual(True, checkName('2', object())) self.assertEqual(True, checkName(u'2', object())) self.assertEqual(True, checkName('other', object())) self.assertEqual(True, checkName(u'reserved', object())) self.assertEqual(True, checkName(u'r\xe9served', object())) # reserved names class ReservedNames(object): zope.component.adapts(IContainer) zope.interface.implements(IReservedNames) def __init__(self, context): self.reservedNames = set(('reserved', 'other')) zope.component.getSiteManager().registerAdapter(ReservedNames) self.assertRaises(NameReserved, checkName, 'reserved', object()) self.assertRaises(NameReserved, checkName, 'other', object()) self.assertRaises(NameReserved, checkName, u'reserved', object()) self.assertRaises(NameReserved, checkName, u'other', object()) def test_chooseName(self): container = SampleContainer() container['foo.old.rst'] = 'rst doc' nc = NameChooser(container) # correct name without changes self.assertEqual(nc.chooseName('foobar.rst', None), u'foobar.rst') self.assertEqual(nc.chooseName(u'\xe9', None), u'\xe9') # automatically modified named self.assertEqual(nc.chooseName('foo.old.rst', None), u'foo.old-2.rst') self.assertEqual(nc.chooseName('+@+@foo.old.rst', None), u'foo.old-2.rst') self.assertEqual(nc.chooseName('+@+@foo/foo+@', None), u'foo-foo+@') # empty name self.assertEqual(nc.chooseName('', None), u'NoneType') self.assertEqual(nc.chooseName('@+@', []), u'list') # if the name is not a string it is converted self.assertEqual(nc.chooseName(None, None), u'None') self.assertEqual(nc.chooseName(2, None), u'2') self.assertEqual(nc.chooseName([], None), u'[]') container['None'] = 'something' self.assertEqual(nc.chooseName(None, None), u'None-2') container['None-2'] = 'something' self.assertEqual(nc.chooseName(None, None), u'None-3') # even if the given name cannot be converted to unicode class BadBoy: def __unicode__(self): raise Exception self.assertEqual(nc.chooseName(BadBoy(), set()), u'set') def test_suite(): return unittest.TestSuite(( doctest.DocTestSuite('zope.container.contained', setUp=testing.setUp, tearDown=testing.tearDown), doctest.DocTestSuite(optionflags=doctest.NORMALIZE_WHITESPACE), unittest.makeSuite(TestNameChooser), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.container/src/zope/container/tests/test_find.py0000644000175000017500000001417112214017546025624 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Find functionality tests $Id: test_find.py 95341 2009-01-28 15:59:18Z wosc $ """ from unittest import TestCase, main, makeSuite from zope.container.interfaces import IReadContainer from zope.container.interfaces import IObjectFindFilter from zope.container.find import FindAdapter, SimpleIdFindFilter from zope.container.find import SimpleInterfacesFindFilter from zope.interface import implements, Interface, directlyProvides class FakeContainer(object): implements(IReadContainer) def __init__(self, id, objects): self._id = id self._objects = objects def keys(self): return [object._id for object in self._objects] def values(self): return self._objects def items(self): return [(object._id, object) for object in self._objects] def __getitem__(self, id): for object in self._objects: if object._id == id: return object raise KeyError("Could not find %s" % id) def get(self, id, default=None): for object in self._objects: if object._id == id: return object return default def __contains__(self, id): for object in self._objects: if object.id == id: return True return False def __len__(self): return len(self._objects) class FakeInterfaceFoo(Interface): """Test interface Foo""" class FakeInterfaceBar(Interface): """Test interface Bar""" class FakeInterfaceSpam(Interface): """Test interface Spam""" class TestObjectFindFilter(object): implements(IObjectFindFilter) def __init__(self, count): self._count = count def matches(self, object): if IReadContainer.providedBy(object): return len(object) == self._count else: return False class Test(TestCase): def test_idFind(self): alpha = FakeContainer('alpha', []) delta = FakeContainer('delta', []) beta = FakeContainer('beta', [delta]) gamma = FakeContainer('gamma', []) tree = FakeContainer( 'tree', [alpha, beta, gamma]) find = FindAdapter(tree) # some simple searches result = find.find([SimpleIdFindFilter(['beta'])]) self.assertEquals([beta], result) result = find.find([SimpleIdFindFilter(['gamma'])]) self.assertEquals([gamma], result) result = find.find([SimpleIdFindFilter(['delta'])]) self.assertEquals([delta], result) # we should not find the container we search on result = find.find([SimpleIdFindFilter(['tree'])]) self.assertEquals([], result) # search for multiple ids result = find.find([SimpleIdFindFilter(['alpha', 'beta'])]) self.assertEquals([alpha, beta], result) result = find.find([SimpleIdFindFilter(['beta', 'delta'])]) self.assertEquals([beta, delta], result) # search without any filters, find everything result = find.find([]) self.assertEquals([alpha, beta, delta, gamma], result) # search for something that doesn't exist result = find.find([SimpleIdFindFilter(['foo'])]) self.assertEquals([], result) # find for something that has two ids at the same time, # can't ever be the case result = find.find([SimpleIdFindFilter(['alpha']), SimpleIdFindFilter(['beta'])]) self.assertEquals([], result) def test_objectFind(self): alpha = FakeContainer('alpha', []) delta = FakeContainer('delta', []) beta = FakeContainer('beta', [delta]) gamma = FakeContainer('gamma', []) tree = FakeContainer( 'tree', [alpha, beta, gamma]) find = FindAdapter(tree) result = find.find(object_filters=[TestObjectFindFilter(0)]) self.assertEquals([alpha, delta, gamma], result) result = find.find(object_filters=[TestObjectFindFilter(1)]) self.assertEquals([beta], result) result = find.find(object_filters=[TestObjectFindFilter(2)]) self.assertEquals([], result) def test_combinedFind(self): alpha = FakeContainer('alpha', []) delta = FakeContainer('delta', []) beta = FakeContainer('beta', [delta]) gamma = FakeContainer('gamma', []) tree = FakeContainer( 'tree', [alpha, beta, gamma]) find = FindAdapter(tree) result = find.find(id_filters=[SimpleIdFindFilter(['alpha'])], object_filters=[TestObjectFindFilter(0)]) self.assertEquals([alpha], result) result = find.find(id_filters=[SimpleIdFindFilter(['alpha'])], object_filters=[TestObjectFindFilter(1)]) self.assertEquals([], result) def test_interfaceFind(self): alpha = FakeContainer('alpha', []) directlyProvides(alpha, FakeInterfaceBar) delta = FakeContainer('delta', []) directlyProvides(delta, FakeInterfaceFoo) beta = FakeContainer('beta', [delta]) directlyProvides(beta, FakeInterfaceSpam) gamma = FakeContainer('gamma', []) tree = FakeContainer( 'tree', [alpha, beta, gamma]) find = FindAdapter(tree) result = find.find(object_filters=[ SimpleInterfacesFindFilter(FakeInterfaceFoo, FakeInterfaceSpam)]) self.assertEqual([beta, delta], result) def test_suite(): return makeSuite(Test) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.container/src/zope/container/tests/__init__.py0000644000175000017500000000007512214017546025402 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.container/src/zope/container/tests/test_icontainer.py0000644000175000017500000002637712214017546027052 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test the IContainer interface. $Id: test_icontainer.py 95417 2009-01-29 11:53:38Z faassen $ """ from unittest import TestCase, main, makeSuite from zope.interface.verify import verifyObject from zope.container.interfaces import IContainer from zope.container import testing def DefaultTestData(): return [('3', '0'), ('2', '1'), ('4', '2'), ('6', '3'), ('0', '4'), ('5', '5'), ('1', '6'), ('8', '7'), ('7', '8'), ('9', '9')] class BaseTestIContainer(testing.ContainerPlacelessSetup): """Base test cases for containers. Subclasses must define a makeTestObject that takes no arguments and that returns a new empty test container, and a makeTestData that also takes no arguments and returns a sequence of (key, value) pairs that may be stored in the test container. The list must be at least ten items long. 'NoSuchKey' may not be used as a key value in the returned list. """ def __setUp(self): self.__container = container = self.makeTestObject() self.__data = data = self.makeTestData() for k, v in data: container[k] = v return container, data ############################################################ # Interface-driven tests: def testIContainerVerify(self): verifyObject(IContainer, self.makeTestObject()) def test_keys(self): # See interface IReadContainer container = self.makeTestObject() keys = container.keys() self.assertEqual(list(keys), []) container, data = self.__setUp() keys = container.keys() keys = list(keys); keys.sort() # convert to sorted list ikeys = [ k for k, v in data ]; ikeys.sort() # sort input keys self.assertEqual(keys, ikeys) def test_get(self): # See interface IReadContainer default = object() data = self.makeTestData() container = self.makeTestObject() self.assertRaises(KeyError, container.__getitem__, data[0][0]) self.assertEqual(container.get(data[0][0], default), default) container, data = self.__setUp() self.assertRaises(KeyError, container.__getitem__, self.getUnknownKey()) self.assertEqual(container.get(self.getUnknownKey(), default), default) for i in (1, 8, 7, 3, 4): self.assertEqual(container.get(data[i][0], default), data[i][1]) self.assertEqual(container.get(data[i][0]), data[i][1]) def test_values(self): # See interface IReadContainer container = self.makeTestObject() values = container.values() self.assertEqual(list(values), []) container, data = self.__setUp() values = list(container.values()) for k, v in data: try: values.remove(v) except ValueError: self.fail('Value not in list') self.assertEqual(values, []) def test_len(self): # See interface IReadContainer container = self.makeTestObject() self.assertEqual(len(container), 0) container, data = self.__setUp() self.assertEqual(len(container), len(data)) def test_items(self): # See interface IReadContainer container = self.makeTestObject() items = container.items() self.assertEqual(list(items), []) container, data = self.__setUp() items = container.items() items = list(items); items.sort() # convert to sorted list data.sort() # sort input data self.assertEqual(items, data) def test___contains__(self): # See interface IReadContainer container = self.makeTestObject() data = self.makeTestData() self.assertEqual(not not (data[6][0] in container), False) container, data = self.__setUp() self.assertEqual(not not (data[6][0] in container), True) for i in (1, 8, 7, 3, 4): self.assertEqual(not not (data[i][0] in container), 1) def test_delObject(self): # See interface IWriteContainer default = object() data = self.makeTestData() container = self.makeTestObject() self.assertRaises(KeyError, container.__delitem__, data[0][0]) container, data = self.__setUp() self.assertRaises(KeyError, container.__delitem__, self.getUnknownKey()) for i in (1, 8, 7, 3, 4): del container[data[i][0]] for i in (1, 8, 7, 3, 4): self.assertRaises(KeyError, container.__getitem__, data[i][0]) self.assertEqual(container.get(data[i][0], default), default) for i in (0, 2, 9, 6, 5): self.assertEqual(container[data[i][0]], data[i][1]) ############################################################ # Tests from Folder def testEmpty(self): folder = self.makeTestObject() data = self.makeTestData() self.failIf(folder.keys()) self.failIf(folder.values()) self.failIf(folder.items()) self.failIf(len(folder)) self.failIf(data[6][0] in folder) self.assertEquals(folder.get(data[6][0], None), None) self.assertRaises(KeyError, folder.__getitem__, data[6][0]) self.assertRaises(KeyError, folder.__delitem__, data[6][0]) def testBadKeyTypes(self): folder = self.makeTestObject() data = self.makeTestData() value = data[1][1] for name in self.getBadKeyTypes(): self.assertRaises(TypeError, folder.__setitem__, name, value) def testOneItem(self): folder = self.makeTestObject() data = self.makeTestData() foo = data[0][1] name = data[0][0] folder[name] = foo self.assertEquals(len(folder.keys()), 1) self.assertEquals(folder.keys()[0], name) self.assertEquals(len(folder.values()), 1) self.assertEquals(folder.values()[0], foo) self.assertEquals(len(folder.items()), 1) self.assertEquals(folder.items()[0], (name, foo)) self.assertEquals(len(folder), 1) self.failUnless(name in folder) # Use an arbitrary id frpm the data set; don;t just use any id, since # there might be restrictions on their form self.failIf(data[6][0] in folder) self.assertEquals(folder.get(name, None), foo) self.assertEquals(folder[name], foo) self.assertRaises(KeyError, folder.__getitem__, data[6][0]) foo2 = data[1][1] name2 = data[1][0] folder[name2] = foo2 self.assertEquals(len(folder.keys()), 2) self.assertEquals(not not name2 in folder.keys(), True) self.assertEquals(len(folder.values()), 2) self.assertEquals(not not foo2 in folder.values(), True) self.assertEquals(len(folder.items()), 2) self.assertEquals(not not (name2, foo2) in folder.items(), True) self.assertEquals(len(folder), 2) del folder[name] del folder[name2] self.failIf(folder.keys()) self.failIf(folder.values()) self.failIf(folder.items()) self.failIf(len(folder)) self.failIf(name in folder) self.assertRaises(KeyError, folder.__getitem__, name) self.assertEquals(folder.get(name, None), None) self.assertRaises(KeyError, folder.__delitem__, name) def testManyItems(self): folder = self.makeTestObject() data = self.makeTestData() objects = [ data[i][1] for i in range(4) ] name0 = data[0][0] name1 = data[1][0] name2 = data[2][0] name3 = data[3][0] folder[name0] = objects[0] folder[name1] = objects[1] folder[name2] = objects[2] folder[name3] = objects[3] self.assertEquals(len(folder.keys()), len(objects)) self.failUnless(name0 in folder.keys()) self.failUnless(name1 in folder.keys()) self.failUnless(name2 in folder.keys()) self.failUnless(name3 in folder.keys()) self.assertEquals(len(folder.values()), len(objects)) self.failUnless(objects[0] in folder.values()) self.failUnless(objects[1] in folder.values()) self.failUnless(objects[2] in folder.values()) self.failUnless(objects[3] in folder.values()) self.assertEquals(len(folder.items()), len(objects)) self.failUnless((name0, objects[0]) in folder.items()) self.failUnless((name1, objects[1]) in folder.items()) self.failUnless((name2, objects[2]) in folder.items()) self.failUnless((name3, objects[3]) in folder.items()) self.assertEquals(len(folder), len(objects)) self.failUnless(name0 in folder) self.failUnless(name1 in folder) self.failUnless(name2 in folder) self.failUnless(name3 in folder) self.failIf(data[5][0] in folder) self.assertEquals(folder.get(name0, None), objects[0]) self.assertEquals(folder[name0], objects[0]) self.assertEquals(folder.get(name1, None), objects[1]) self.assertEquals(folder[name1], objects[1]) self.assertEquals(folder.get(name2, None), objects[2]) self.assertEquals(folder[name2], objects[2]) self.assertEquals(folder.get(name3, None), objects[3]) self.assertEquals(folder[name3], objects[3]) self.assertEquals(folder.get(data[5][0], None), None) self.assertRaises(KeyError, folder.__getitem__, data[5][0]) del folder[name0] self.assertEquals(len(folder), len(objects) - 1) self.failIf(name0 in folder) self.failIf(name0 in folder.keys()) self.failIf(objects[0] in folder.values()) self.failIf((name0, objects[0]) in folder.items()) self.assertEquals(folder.get(name0, None), None) self.assertRaises(KeyError, folder.__getitem__, name0) self.assertRaises(KeyError, folder.__delitem__, name0) del folder[name1] del folder[name2] del folder[name3] self.failIf(folder.keys()) self.failIf(folder.values()) self.failIf(folder.items()) self.failIf(len(folder)) self.failIf(name0 in folder) self.failIf(name1 in folder) self.failIf(name2 in folder) self.failIf(name3 in folder) class TestSampleContainer(BaseTestIContainer, TestCase): def makeTestObject(self): from zope.container.sample import SampleContainer return SampleContainer() def makeTestData(self): return DefaultTestData() def getUnknownKey(self): return '10' def getBadKeyTypes(self): return [None, ['foo'], 1, '\xf3abc'] def test_suite(): return makeSuite(TestSampleContainer) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.container/src/zope/container/tests/test_ordered.py0000644000175000017500000000774512214017546026341 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test the OrderedContainer. $Id: test_ordered.py 111713 2010-04-30 20:43:09Z hannosch $ """ import unittest from doctest import DocTestSuite from zope.component.eventtesting import getEvents, clearEvents from zope.container import testing def test_order_events(): """ Prepare the setup:: >>> from zope.container.sample import SampleContainer >>> root = SampleContainer() Prepare some objects:: >>> from zope.container.ordered import OrderedContainer >>> oc = OrderedContainer() >>> oc['foo'] = 'bar' >>> oc['baz'] = 'quux' >>> oc['zork'] = 'grue' >>> oc.keys() ['foo', 'baz', 'zork'] Now change the order:: >>> clearEvents() >>> oc.updateOrder(['baz', 'foo', 'zork']) >>> oc.keys() ['baz', 'foo', 'zork'] Check what events have been sent:: >>> events = getEvents() >>> [event.__class__.__name__ for event in events] ['ContainerModifiedEvent'] This is in fact a specialized modification event:: >>> from zope.lifecycleevent.interfaces import IObjectModifiedEvent >>> IObjectModifiedEvent.providedBy(events[0]) True """ def test_all_items_available_at_object_added_event(): """ Prepare the setup:: >>> from zope.container.sample import SampleContainer >>> root = SampleContainer() Now register an event subscriber to object added events. >>> import zope.component >>> from zope.container import interfaces >>> from zope.lifecycleevent.interfaces import IObjectAddedEvent >>> @zope.component.adapter(IObjectAddedEvent) ... def printContainerKeys(event): ... print event.newParent.keys() >>> zope.component.provideHandler(printContainerKeys) Now we are adding an object to the container. >>> from zope.container.ordered import OrderedContainer >>> oc = OrderedContainer() >>> oc['foo'] = 'FOO' ['foo'] """ def test_exception_causes_order_fix(): """ Prepare the setup:: >>> from zope.container.sample import SampleContainer >>> root = SampleContainer() Now register an event subscriber to object added events that throws an error. >>> import zope.component >>> from zope.container import interfaces >>> from zope.lifecycleevent.interfaces import IObjectAddedEvent >>> @zope.component.adapter(IObjectAddedEvent) ... def raiseException(event): ... raise Exception() >>> zope.component.provideHandler(raiseException) Now we are adding an object to the container. >>> from zope.container.ordered import OrderedContainer >>> oc = OrderedContainer() >>> oc['foo'] = 'FOO' Traceback (most recent call last): ... Exception The key 'foo' should not be around: >>> 'foo' in oc.keys() False """ def test_suite(): suite = unittest.TestSuite() suite.addTest(DocTestSuite("zope.container.ordered", setUp=testing.setUp, tearDown=testing.tearDown)) suite.addTest(DocTestSuite( setUp=testing.ContainerPlacefulSetup().setUp, tearDown=testing.ContainerPlacefulSetup().tearDown)) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.container/src/zope/container/tests/test_containertraversable.py0000644000175000017500000000453612214017546031125 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Container Traverser tests. $Id: test_containertraversable.py 95341 2009-01-28 15:59:18Z wosc $ """ import unittest from zope.testing.cleanup import CleanUp from zope.interface import implements from zope.traversing.interfaces import TraversalError from zope.container.traversal import ContainerTraversable from zope.container.interfaces import IContainer class Container(object): implements(IContainer) def __init__(self, attrs={}, objs={}): for attr,value in attrs.iteritems(): setattr(self, attr, value) self.__objs = {} for name,value in objs.iteritems(): self.__objs[name] = value def __getitem__(self, name): return self.__objs[name] def get(self, name, default=None): return self.__objs.get(name, default) def __contains__(self, name): return self.__objs.has_key(name) class Test(CleanUp, unittest.TestCase): def testAttr(self): # test container path traversal foo = Container() bar = Container() baz = Container() c = Container({'foo': foo}, {'bar': bar, 'foo': baz}) T = ContainerTraversable(c) self.failUnless(T.traverse('foo', []) is baz) self.failUnless(T.traverse('bar', []) is bar) self.assertRaises(TraversalError , T.traverse, 'morebar', []) def test_unicode_attr(self): # test traversal with unicode voila = Container() c = Container({}, {u'voil\xe0': voila}) self.failUnless(ContainerTraversable(c).traverse(u'voil\xe0', []) is voila) def test_suite(): loader = unittest.TestLoader() return loader.loadTestsFromTestCase(Test) if __name__ == '__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/zope.container/src/zope/container/tests/test_directory.py0000644000175000017500000000301312214017546026701 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """FS-based directory implementation tests for containers $Id: test_directory.py 111713 2010-04-30 20:43:09Z hannosch $ """ import doctest from unittest import TestCase, TestSuite, main, makeSuite from zope.container import testing import zope.container.directory class Directory(object): pass class Test(TestCase): def test_Cloner(self): d = Directory() d.a = 1 clone = zope.container.directory.Cloner(d)('foo') self.assert_(clone != d) self.assertEqual(clone.__class__, d.__class__) def test_suite(): flags = doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE return TestSuite(( makeSuite(Test), doctest.DocFileSuite("directory.txt", setUp=testing.setUp, tearDown=testing.tearDown, optionflags=flags), )) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.container/src/zope/container/tests/test_folder.py0000644000175000017500000000104712214017546026155 0ustar arnauarnaufrom unittest import TestCase, makeSuite from zope.container.folder import Folder from zope.container.tests.test_icontainer import BaseTestIContainer from zope.container.tests.test_icontainer import DefaultTestData class Test(BaseTestIContainer, TestCase): def makeTestObject(self): return Folder() def makeTestData(self): return DefaultTestData() def getUnknownKey(self): return '10' def getBadKeyTypes(self): return [None, ['foo'], 1, '\xf3abc'] def test_suite(): return makeSuite(Test) zope2.13-2.13.21/source/zope.container/src/zope/container/tests/test_constraints.py0000644000175000017500000000232312214017546027247 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Container constraint tests $Id: test_constraints.py 111713 2010-04-30 20:43:09Z hannosch $ """ import doctest import unittest from zope.testing import module def setUp(test): module.setUp(test, 'zope.container.constraints_txt') def tearDown(test): module.tearDown(test, 'zope.container.constraints_txt') def test_suite(): return unittest.TestSuite(( doctest.DocTestSuite('zope.container.constraints'), doctest.DocFileSuite('../constraints.txt', setUp=setUp, tearDown=tearDown), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.container/src/zope/container/tests/test_btree.py0000644000175000017500000001532712214017546026011 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """BTree Container Tests $Id: test_btree.py 111713 2010-04-30 20:43:09Z hannosch $ """ from doctest import DocTestSuite from unittest import TestCase, main, makeSuite, TestSuite from zope.interface.verify import verifyObject from zope.component.testing import setUp, tearDown from zope.container.tests.test_icontainer import TestSampleContainer from zope.container.btree import BTreeContainer from zope.container.interfaces import IBTreeContainer class TestBTreeContainer(TestSampleContainer, TestCase): def makeTestObject(self): return BTreeContainer() class TestBTreeSpecials(TestCase): def testStoredLength(self): # This is lazy for backward compatibility. If the len is not # stored already we set it to the length of the underlying # btree. bc = BTreeContainer() self.assertEqual(bc.__dict__['_BTreeContainer__len'](), 0) del bc.__dict__['_BTreeContainer__len'] self.failIf(bc.__dict__.has_key('_BTreeContainer__len')) bc['1'] = 1 self.assertEqual(len(bc), 1) self.assertEqual(bc.__dict__['_BTreeContainer__len'](), 1) # The tests which follow test the additional signatures and declarations # for the BTreeContainer that allow it to provide the IBTreeContainer # interface. def testBTreeContainerInterface(self): bc = BTreeContainer() self.assert_(verifyObject(IBTreeContainer, bc)) self.checkIterable(bc.items()) self.checkIterable(bc.keys()) self.checkIterable(bc.values()) def testEmptyItemsWithArg(self): bc = BTreeContainer() self.assertEqual(list(bc.items(None)), list(bc.items())) self.assertEqual(list(bc.items("")), []) self.assertEqual(list(bc.items("not-there")), []) self.checkIterable(bc.items(None)) self.checkIterable(bc.items("")) self.checkIterable(bc.items("not-there")) def testEmptyKeysWithArg(self): bc = BTreeContainer() self.assertEqual(list(bc.keys(None)), list(bc.keys())) self.assertEqual(list(bc.keys("")), []) self.assertEqual(list(bc.keys("not-there")), []) self.checkIterable(bc.keys(None)) self.checkIterable(bc.keys("")) self.checkIterable(bc.keys("not-there")) def testEmptyValuesWithArg(self): bc = BTreeContainer() self.assertEqual(list(bc.values(None)), list(bc.values())) self.assertEqual(list(bc.values("")), []) self.assertEqual(list(bc.values("not-there")), []) self.checkIterable(bc.values(None)) self.checkIterable(bc.values("")) self.checkIterable(bc.values("not-there")) def testNonemptyItemsWithArg(self): bc = BTreeContainer() bc["0"] = 1 bc["1"] = 2 bc["2"] = 3 self.assertEqual(list(bc.items(None)), list(bc.items())) self.assertEqual(list(bc.items("")), [("0", 1), ("1", 2), ("2", 3)]) self.assertEqual(list(bc.items("3")), []) self.assertEqual(list(bc.items("2.")), []) self.assertEqual(list(bc.items("2")), [("2", 3)]) self.assertEqual(list(bc.items("1.")), [("2", 3)]) self.assertEqual(list(bc.items("1")), [("1", 2), ("2", 3)]) self.assertEqual(list(bc.items("0.")), [("1", 2), ("2", 3)]) self.assertEqual(list(bc.items("0")), [("0", 1), ("1", 2), ("2", 3)]) self.checkIterable(bc.items(None)) self.checkIterable(bc.items("")) self.checkIterable(bc.items("0.")) self.checkIterable(bc.items("3")) def testNonemptyKeysWithArg(self): bc = BTreeContainer() bc["0"] = 1 bc["1"] = 2 bc["2"] = 3 self.assertEqual(list(bc.keys(None)), list(bc.keys())) self.assertEqual(list(bc.keys("")), ["0", "1", "2"]) self.assertEqual(list(bc.keys("3")), []) self.assertEqual(list(bc.keys("2.")), []) self.assertEqual(list(bc.keys("2")), ["2"]) self.assertEqual(list(bc.keys("1.")), ["2"]) self.assertEqual(list(bc.keys("1")), ["1", "2"]) self.assertEqual(list(bc.keys("0.")), ["1", "2"]) self.assertEqual(list(bc.keys("0")), ["0", "1", "2"]) self.checkIterable(bc.keys(None)) self.checkIterable(bc.keys("")) self.checkIterable(bc.keys("0.")) self.checkIterable(bc.keys("3")) def testNonemptyValueWithArg(self): bc = BTreeContainer() bc["0"] = 1 bc["1"] = 2 bc["2"] = 3 self.assertEqual(list(bc.values(None)), list(bc.values())) self.assertEqual(list(bc.values("")), [1, 2, 3]) self.assertEqual(list(bc.values("3")), []) self.assertEqual(list(bc.values("2.")), []) self.assertEqual(list(bc.values("2")), [3]) self.assertEqual(list(bc.values("1.")), [3]) self.assertEqual(list(bc.values("1")), [2, 3]) self.assertEqual(list(bc.values("0.")), [2, 3]) self.assertEqual(list(bc.values("0")), [1, 2, 3]) self.checkIterable(bc.values(None)) self.checkIterable(bc.values("")) self.checkIterable(bc.values("0.")) self.checkIterable(bc.values("3")) def testCorrectLengthWhenAddingExistingItem(self): """ for bug #175388 """ bc = BTreeContainer() bc[u'x'] = object() self.assertEqual(len(bc), 1) bc[u'x'] = bc[u'x'] self.assertEqual(len(bc), 1) self.assertEqual(list(bc), [u'x']) def checkIterable(self, iterable): it = iter(iterable) self.assert_(callable(it.next)) self.assert_(callable(it.__iter__)) self.assert_(iter(it) is it) # Exhaust the iterator: first_time = list(it) self.assertRaises(StopIteration, it.next) # Subsequent iterations will return the same values: self.assertEqual(list(iterable), first_time) self.assertEqual(list(iterable), first_time) def test_suite(): return TestSuite(( makeSuite(TestBTreeContainer), makeSuite(TestBTreeSpecials), DocTestSuite('zope.container.btree', setUp=setUp, tearDown=tearDown), )) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.container/src/zope/container/tests/test_size.py0000644000175000017500000000461612214017546025661 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test container ISized adapter. $Id: test_size.py 95341 2009-01-28 15:59:18Z wosc $ """ import unittest from zope.interface import implements from zope.size.interfaces import ISized from zope.container.interfaces import IContainer class DummyContainer(object): implements(IContainer) def __init__(self, numitems): self._numitems = numitems def __len__(self): return self._numitems class Test(unittest.TestCase): def testImplementsISized(self): from zope.container.size import ContainerSized sized = ContainerSized(DummyContainer(23)) self.assert_(ISized.providedBy(sized)) def testEmptyContainer(self): from zope.container.size import ContainerSized obj = DummyContainer(0) sized = ContainerSized(obj) self.assertEqual(sized.sizeForSorting(), ('item', 0)) self.assertEqual(sized.sizeForDisplay(), u'${items} items') self.assertEqual(sized.sizeForDisplay().mapping['items'], '0') def testOneItem(self): from zope.container.size import ContainerSized obj = DummyContainer(1) sized = ContainerSized(obj) self.assertEqual(sized.sizeForSorting(), ('item', 1)) self.assertEqual(sized.sizeForDisplay(), u'1 item') def testSeveralItems(self): from zope.container.size import ContainerSized obj = DummyContainer(2) sized = ContainerSized(obj) self.assertEqual(sized.sizeForSorting(), ('item', 2)) self.assertEqual(sized.sizeForDisplay(), u'${items} items') self.assertEqual(sized.sizeForDisplay().mapping['items'], '2') def test_suite(): loader = unittest.TestLoader() return loader.loadTestsFromTestCase(Test) if __name__=='__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/zope.container/src/zope/container/testing.py0000644000175000017500000000614112214017546024156 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unit test logic for setting up and tearing down basic infrastructure $Id: placelesssetup.py 95341 2009-01-28 15:59:18Z wosc $ """ from zope import component from zope.component.testing import PlacelessSetup as CAPlacelessSetup from zope.component.eventtesting import PlacelessSetup as EventPlacelessSetup from zope.traversing.interfaces import ITraversable, IContainmentRoot import zope.traversing.testing import zope.interface from zope.container.interfaces import IWriteContainer, INameChooser from zope.container.contained import NameChooser from zope.container.interfaces import ISimpleReadContainer from zope.container.traversal import ContainerTraversable from zope.container.sample import SampleContainer # XXX we would like to swap the names of the *PlacelessSetup classes # in here as that would seem to follow the convention better, but # unfortunately that would break compatibility with zope.app.testing # (which expects this PlacelessSetup) so it will have to wait. class PlacelessSetup(object): def setUp(self): component.provideAdapter(NameChooser, (IWriteContainer,), INameChooser) class ContainerPlacelessSetup(CAPlacelessSetup, EventPlacelessSetup, PlacelessSetup): def setUp(self, doctesttest=None): CAPlacelessSetup.setUp(self) EventPlacelessSetup.setUp(self) PlacelessSetup.setUp(self) ps = ContainerPlacelessSetup() setUp = ps.setUp def tearDown(): tearDown_ = ps.tearDown def tearDown(doctesttest=None): tearDown_() return tearDown tearDown = tearDown() del ps class ContainerPlacefulSetup(ContainerPlacelessSetup): def setUp(self, doctesttest=None): ContainerPlacelessSetup.setUp(self, doctesttest) zope.traversing.testing.setUp() component.provideAdapter(ContainerTraversable, (ISimpleReadContainer,), ITraversable) def tearDown(self, docttesttest=None): ContainerPlacelessSetup.tearDown(self) def buildFolders(self): root = self.rootFolder = SampleContainer() zope.interface.directlyProvides(root, IContainmentRoot) root[u'folder1'] = SampleContainer() root[u'folder1'][u'folder1_1'] = SampleContainer() root[u'folder1'][u'folder1_1'][u'folder1_1_1'] = SampleContainer() root[u'folder2'] = SampleContainer() root[u'folder2'][u'folder2_1'] = SampleContainer() root[u'folder2'][u'folder2_1'][u'folder2_1_1'] = SampleContainer() zope2.13-2.13.21/source/zope.container/src/zope/container/constraints.txt0000644000175000017500000000553112214017546025241 0ustar arnauarnau========================= Containment constraints ========================= Containment constraints allow us to express restrictions on the types of items that can be placed in containers or on the types of containers an item can be placed in. We express these constraints in interfaces. Let's define some container and item interfaces: >>> from zope.container.interfaces import IContainer >>> from zope.location.interfaces import IContained >>> from zope.container.constraints import containers, contains >>> class IBuddyFolder(IContainer): ... contains('.IBuddy') In this example, we used the contains function to declare that objects that provide IBuddyFolder can only contain items that provide IBuddy. Note that we used a string containing a dotted name for the IBuddy interface. This is because IBuddy hasn't been defined yet. When we define IBuddy, we can use IBuddyFolder directly: >>> class IBuddy(IContained): ... containers(IBuddyFolder) Now, with these interfaces in place, we can define Buddy and BuddyFolder classes and verify that we can put buddies in buddy folders: >>> from zope import interface >>> class Buddy: ... interface.implements(IBuddy) >>> class BuddyFolder: ... interface.implements(IBuddyFolder) >>> from zope.container.constraints import checkObject, checkFactory >>> from zope.component.factory import Factory >>> checkObject(BuddyFolder(), 'x', Buddy()) >>> checkFactory(BuddyFolder(), 'x', Factory(Buddy)) True If we try to use other containers or folders, we'll get errors: >>> class Container: ... interface.implements(IContainer) >>> class Contained: ... interface.implements(IContained) >>> checkObject(Container(), 'x', Buddy()) ... # doctest: +ELLIPSIS Traceback (most recent call last): InvalidContainerType: ... >>> checkFactory(Container(), 'x', Factory(Buddy)) False >>> checkObject(BuddyFolder(), 'x', Contained()) ... # doctest: +ELLIPSIS Traceback (most recent call last): InvalidItemType: ... >>> checkFactory(BuddyFolder(), 'x', Factory(Contained)) False In the example, we defined the container first and then the items. We could have defined these in the opposite order: >>> class IContact(IContained): ... containers('.IContacts') >>> class IContacts(IContainer): ... contains(IContact) >>> class Contact: ... interface.implements(IContact) >>> class Contacts: ... interface.implements(IContacts) >>> checkObject(Contacts(), 'x', Contact()) >>> checkFactory(Contacts(), 'x', Factory(Contact)) True >>> checkObject(Contacts(), 'x', Buddy()) ... # doctest: +ELLIPSIS Traceback (most recent call last): InvalidItemType: ... >>> checkFactory(Contacts(), 'x', Factory(Buddy)) False zope2.13-2.13.21/source/zope.container/src/zope/__init__.py0000644000175000017500000000007012214017546022251 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.container/src/zope.container.egg-info/0000755000175000017500000000000012214017546023616 5ustar arnauarnauzope2.13-2.13.21/source/zope.container/src/zope.container.egg-info/PKG-INFO0000644000175000017500000002531012214017546024714 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.container Version: 3.11.2 Summary: Zope Container Home-page: http://pypi.python.org/pypi/zope.container Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: This package define interfaces of container components, and provides container implementations such as a BTreeContainer and OrderedContainer, as well as the base class used by ``zope.site.folder`` for the Folder implementation. .. contents:: ========================= Containment constraints ========================= Containment constraints allow us to express restrictions on the types of items that can be placed in containers or on the types of containers an item can be placed in. We express these constraints in interfaces. Let's define some container and item interfaces: >>> from zope.container.interfaces import IContainer >>> from zope.location.interfaces import IContained >>> from zope.container.constraints import containers, contains >>> class IBuddyFolder(IContainer): ... contains('.IBuddy') In this example, we used the contains function to declare that objects that provide IBuddyFolder can only contain items that provide IBuddy. Note that we used a string containing a dotted name for the IBuddy interface. This is because IBuddy hasn't been defined yet. When we define IBuddy, we can use IBuddyFolder directly: >>> class IBuddy(IContained): ... containers(IBuddyFolder) Now, with these interfaces in place, we can define Buddy and BuddyFolder classes and verify that we can put buddies in buddy folders: >>> from zope import interface >>> class Buddy: ... interface.implements(IBuddy) >>> class BuddyFolder: ... interface.implements(IBuddyFolder) >>> from zope.container.constraints import checkObject, checkFactory >>> from zope.component.factory import Factory >>> checkObject(BuddyFolder(), 'x', Buddy()) >>> checkFactory(BuddyFolder(), 'x', Factory(Buddy)) True If we try to use other containers or folders, we'll get errors: >>> class Container: ... interface.implements(IContainer) >>> class Contained: ... interface.implements(IContained) >>> checkObject(Container(), 'x', Buddy()) ... # doctest: +ELLIPSIS Traceback (most recent call last): InvalidContainerType: ... >>> checkFactory(Container(), 'x', Factory(Buddy)) False >>> checkObject(BuddyFolder(), 'x', Contained()) ... # doctest: +ELLIPSIS Traceback (most recent call last): InvalidItemType: ... >>> checkFactory(BuddyFolder(), 'x', Factory(Contained)) False In the example, we defined the container first and then the items. We could have defined these in the opposite order: >>> class IContact(IContained): ... containers('.IContacts') >>> class IContacts(IContainer): ... contains(IContact) >>> class Contact: ... interface.implements(IContact) >>> class Contacts: ... interface.implements(IContacts) >>> checkObject(Contacts(), 'x', Contact()) >>> checkFactory(Contacts(), 'x', Factory(Contact)) True >>> checkObject(Contacts(), 'x', Buddy()) ... # doctest: +ELLIPSIS Traceback (most recent call last): InvalidItemType: ... >>> checkFactory(Contacts(), 'x', Factory(Buddy)) False ======= CHANGES ======= 3.11.2 (2010-09-25) ------------------- - Added not declared, but needed test dependency on `zope.testing`. 3.11.1 (2010-04-30) ------------------- - Prefer the standard libraries doctest module to the one from zope.testing. - Added compatibility with ZODB3 3.10 by importing the IBroken interface from it directly. Once we can rely on the new ZODB3 version exclusively, we can remove the dependency onto the zope.broken distribution. - Never fail if the suggested name is in a wrong type (#227617) - ``checkName`` first checks the parameter type before the emptiness. 3.11.0 (2009-12-31) ------------------- - Copy two trivial classes from zope.cachedescriptors into this package, which allows us to remove that dependency. We didn't actually use any caching properties as the dependency suggested. 3.10.1 (2009-12-29) ------------------- - Moved zope.copypastemove related tests into that package. - Removed no longer used zcml prefix from the configure file. - Stop importing DocTestSuite from zope.testing.doctestunit. Fixes compatibility problems with zope.testing 3.8.4. 3.10.0 (2009-12-15) ------------------- - Break testing dependency on zope.app.testing. - Break testing dependency on zope.app.dependable by moving the code and tests into that package. - Import ISite from zope.component after it was moved there from zope.location. 3.9.1 (2009-10-18) ------------------ - Rerelease 3.9.0 as it had a broken Windows 2.6 egg. - Marked as part of the ZTK. 3.9.0 (2009-08-28) ------------------ - Previous releases should be versioned 3.9.0 as they are not pure bugfix releases and worth a "feature" release, increasing feature version. Packages that depend on any changes introduced in version 3.8.2 or 3.8.3 should depend on version 3.9 or greater. 3.8.3 (2009-08-27) ------------------ - Move IXMLRPCPublisher ZCML registrations for containers from zope.app.publisher.xmlrpc to zope.container for now. 3.8.2 (2009-05-17) ------------------ - Rid ourselves of ``IContained`` interface. This interface was moved to ``zope.location.interfaces``. A b/w compat import still exists to keep old code running. Depend on ``zope.location``>=3.5.4. - Rid ourselves of the implementations of ``IObjectMovedEvent``, ``IObjectAddedEvent``, ``IObjectRemovedEvent`` interfaces and ``ObjectMovedEvent``, ``ObjectAddedEvent`` and ``ObjectRemovedEvent`` classes. B/w compat imports still exist. All of these were moved to ``zope.lifecycleevent``. Depend on ``zope.lifecycleevent``>=3.5.2. - Fix a bug in OrderedContainer where trying to set the value for a key that already exists (duplication error) would actually delete the key from the order, leaving a dangling reference. - Partially break dependency on ``zope.traversing`` by disusing zope.traversing.api.getPath in favor of using ILocationInfo(object).getPath(). The rest of the runtime dependencies on zope.traversing are currently interface dependencies. - Break runtime dependency on ``zope.app.dependable`` by using a zcml condition on the qsubscriber ZCML directive that registers the CheckDependency handler for IObjectRemovedEvent. If ``zope.app.dependable`` is not installed, this subscriber will never be registered. ``zope.app.dependable`` is now a testing dependency only. 3.8.1 (2009-04-03) ------------------ - Fixed misspackaged 3.8.0 3.8.0 (2009-04-03) ------------------ - Change configure.zcml to not depend on zope.app.component. Fixes: https://bugs.launchpad.net/bugs/348329 - Moved the declaration of ``IOrderedContainer.updateOrder`` to a new, basic ``IOrdered`` interface and let ``IOrderedContainer`` inherit it. This allows easier reuse of the declaration. 3.7.2 (2009-03-12) ------------------ - Fix: added missing ComponentLookupError, missing since revision 95429 and missing in last release. - Adapt to the move of IDefaultViewName from zope.component.interfaces to zope.publisher.interfaces. - Add support for reserved names for containers. To specify reserved names for some container, you need to provide an adapter from the container to the ``zope.container.interfaces.IReservedNames`` interface. The default NameChooser is now also aware of reserved names. 3.7.1 (2009-02-05) ------------------ - Raise more "Pythonic" errors from ``__setitem__``, losing the dependency on ``zope.exceptions``: o ``zope.exceptions.DuplicationError`` -> ``KeyError`` o ``zope.exceptions.UserError`` -> ``ValueError`` - Moved import of ``IBroken`` interface to use new ``zope.broken`` package, which has no dependencies beyond ``zope.interface``. - Made ``test`` part pull in the extra test requirements of this package. - Split the ``z3c.recipe.compattest`` configuration out into a new file, ``compat.cfg``, to reduce the burden of doing standard unit tests. - Stripped out bogus develop eggs from ``buildout.cfg``. 3.7.0 (2009-01-31) ------------------ - Split this package off ``zope.app.container``. This package is intended to have far less dependencies than ``zope.app.container``. - This package also contains the container implementation that used to be in ``zope.app.folder``. Keywords: zope container Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.container/src/zope.container.egg-info/dependency_links.txt0000644000175000017500000000000112214017546027664 0ustar arnauarnau zope2.13-2.13.21/source/zope.container/src/zope.container.egg-info/requires.txt0000644000175000017500000000045112214017546026216 0ustar arnauarnausetuptools zope.interface zope.dottedname zope.schema zope.component zope.event zope.location>=3.5.4 zope.security zope.lifecycleevent>=3.5.2 zope.i18nmessageid zope.filerepresentation zope.size zope.traversing zope.publisher zope.broken ZODB3 [test] zope.configuration zope.security zope.testingzope2.13-2.13.21/source/zope.container/src/zope.container.egg-info/namespace_packages.txt0000644000175000017500000000000512214017546030144 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.container/src/zope.container.egg-info/top_level.txt0000644000175000017500000000000512214017546026343 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.container/src/zope.container.egg-info/SOURCES.txt0000644000175000017500000000544312214017546025510 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg compat.cfg setup.py include/persistent/DEPENDENCIES.cfg include/persistent/README.txt include/persistent/SETUP.cfg include/persistent/TimeStamp.c include/persistent/__init__.py include/persistent/cPersistence.c include/persistent/cPersistence.h include/persistent/cPickleCache.c include/persistent/dict.py include/persistent/interfaces.py include/persistent/list.py include/persistent/mapping.py include/persistent/ring.c include/persistent/ring.h include/persistent/wref.py include/persistent/tests/__init__.py include/persistent/tests/persistent.txt include/persistent/tests/persistenttestbase.py include/persistent/tests/testPersistent.py include/persistent/tests/test_PickleCache.py include/persistent/tests/test_list.py include/persistent/tests/test_mapping.py include/persistent/tests/test_overriding_attrs.py include/persistent/tests/test_persistent.py include/persistent/tests/test_pickle.py include/persistent/tests/test_wref.py include/zope.proxy/__init__.py include/zope.proxy/_zope_proxy_proxy.c include/zope.proxy/decorator.py include/zope.proxy/interfaces.py include/zope.proxy/proxy.h include/zope.proxy/tests/__init__.py include/zope.proxy/tests/test_decorator.py include/zope.proxy/tests/test_proxy.py src/zope/__init__.py src/zope.container.egg-info/PKG-INFO src/zope.container.egg-info/SOURCES.txt src/zope.container.egg-info/dependency_links.txt src/zope.container.egg-info/namespace_packages.txt src/zope.container.egg-info/not-zip-safe src/zope.container.egg-info/requires.txt src/zope.container.egg-info/top_level.txt src/zope/container/__init__.py src/zope/container/_zope_container_contained.c src/zope/container/_zope_proxy_proxy.c src/zope/container/btree.py src/zope/container/configure.zcml src/zope/container/constraints.py src/zope/container/constraints.txt src/zope/container/contained.py src/zope/container/dependency.py src/zope/container/directory.py src/zope/container/find.py src/zope/container/folder.py src/zope/container/i18n.py src/zope/container/interfaces.py src/zope/container/ordered.py src/zope/container/sample.py src/zope/container/size.py src/zope/container/testing.py src/zope/container/traversal.py src/zope/container/tests/__init__.py src/zope/container/tests/directory.txt src/zope/container/tests/ftest_zcml_dependencies.zcml src/zope/container/tests/test_btree.py src/zope/container/tests/test_constraints.py src/zope/container/tests/test_contained.py src/zope/container/tests/test_containertraversable.py src/zope/container/tests/test_containertraverser.py src/zope/container/tests/test_dependencies.py src/zope/container/tests/test_directory.py src/zope/container/tests/test_find.py src/zope/container/tests/test_folder.py src/zope/container/tests/test_icontainer.py src/zope/container/tests/test_ordered.py src/zope/container/tests/test_size.pyzope2.13-2.13.21/source/zope.container/src/zope.container.egg-info/not-zip-safe0000644000175000017500000000000112214017546026044 0ustar arnauarnau zope2.13-2.13.21/source/zope.pagetemplate/0000755000175000017500000000000012214017603017057 5ustar arnauarnauzope2.13-2.13.21/source/zope.pagetemplate/setup.py0000644000175000017500000000564312214017603020601 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.pagetemplate package """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.pagetemplate', version='3.5.2', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description='Zope Page Templates', long_description=( read('README.txt') + '\n\n' + 'Detailed Documentation\n' + '----------------------' + '\n\n' + read('src', 'zope', 'pagetemplate', 'architecture.txt') + '\n\n' + read('src', 'zope', 'pagetemplate', 'readme.txt') + '\n\n' + read('CHANGES.txt')), keywords="zope3 page template", classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.pagetemplate', license='ZPL 2.1', packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope'], extras_require=dict( test=['zope.testing', 'zope.proxy', 'zope.security', ]), install_requires=['setuptools', 'zope.interface', 'zope.component', 'zope.security [untrustedpython]', 'zope.tales', 'zope.tal', 'zope.i18n', 'zope.i18nmessageid', 'zope.traversing', ], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/zope.pagetemplate/PKG-INFO0000644000175000017500000001650012214017603020156 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.pagetemplate Version: 3.5.2 Summary: Zope Page Templates Home-page: http://pypi.python.org/pypi/zope.pagetemplate Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Page Templates provide an elegant templating mechanism that achieves a clean separation of presentation and application logic while allowing for designers to work with templates in their visual editing tools (FrontPage, Dreamweaver, GoLive, etc.). Detailed Documentation ---------------------- ===================================== ZPT (Zope Page-Template) Architecture ===================================== There are a number of major components that make up the page-template architecture: - The TAL *compiler* and *interpreter*. This is responsible for compiling source files and for executing compiled templates. See the `zope.tal` package for more information. - An *expression engine* is responsible for compiling expressions and for creating expression execution contexts. It is common for applications to override expression engines to provide custom expression support or to change the way expressions are implemented. The `zope.app.pagetemplate` package uses this to implement trusted and untrusted evaluation; a different engine is used for each, with different implementations of the same type of expressions. Expression contexts support execution of expressions and provide APIs for setting up variable scopes and setting variables. The expression contexts are passed to the TAL interpreter at execution time. The most commonly used expression implementation is that found in `zope.tales`. - Page templates tie everything together. They assemble an expression engine with the TAL interpreter and orchestrate management of source and compiled template data. See `zope.pagetemplate.interfaces`. ============== Page Templates ============== Introduction ------------ Page Templates provide an elegant templating mechanism that achieves a clean separation of presentation and application logic while allowing for designers to work with templates in their visual editing tools (FrontPage, Dreamweaver, GoLive, etc.). This document focuses on usage of Page Templates outside of a Zope context, it does *not* explain how to write page templates as there are several resources on the web which do so. Simple Usage ------------ Using Page Templates outside of Zope3 is very easy and straight forward. A quick example:: >>> from zope.pagetemplate.pagetemplatefile import PageTemplateFile >>> my_pt = PageTemplateFile('hello_world.pt') >>> my_pt() u'Hello World' Subclassing PageTemplates ------------------------- Lets say we want to alter page templates such that keyword arguments appear as top level items in the namespace. We can subclass `PageTemplate` and alter the default behavior of `pt_getContext()` to add them in:: from zope.pagetemplate.pagetemplate import PageTemplate class mypt(PageTemplate): def pt_getContext(self, args=(), options={}, **kw): rval = PageTemplate.pt_getContext(self, args=args) options.update(rval) return options class foo: def getContents(self): return 'hi' So now we can bind objects in a more arbitrary fashion, like the following:: template = """ Good Stuff Here """ pt = mypt() pt.write(template) pt(das_object=foo()) See `interfaces.py`. ======= CHANGES ======= 3.5.2 (2010-07-08) ------------------ - Fixed PTRuntimeError exception messages to be consistent across Python versions, and compatibile with the output under Python 2.4. (More readable than the previous output under Python 2.6 as well.) 3.5.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctestunit' in favor of stdlib's doctest. - Added dependency on "zope.security [untrustedpython]" because the 'engine' module uses it. 3.5.0 (2009-05-25) ------------------ - Added test coverage reporting support. - Moved 'engine' module and related test scaffolding here from ``zope.app.pagetemplate`` package. 3.4.2 (2009-03-17) ------------------ - Remove old zpkg-related DEPENDENCIES.cfg file. - Change package's mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired. - Change `cheeseshop` to `pypi` in the packages' homepage url. 3.4.1 (2009-01-27) ------------------ - Fix test due to recent changes in zope.tal. 3.4.0 (2007-10-02) ------------------ - Initial release independent of the Zope 3 tree. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the zope.pagetemplate package shipped as part of the Zope 3.2.0 release. - ZPTPage macro expansion: changed label text to match the corresponding label in Zope 2 and activated the name spaces for macro expansion in 'read'. See http://www.zope.org/Collectors/Zope3-dev/199 - Coding style cleanups. 3.1.0 (2005-10-03) ------------------ - Corresponds to the verison of the zope.pagetemplate package shipped as part of the Zope 3.1.0 release. - Fixed apidoc and Cookie, which were using wrong descriptor class (changed to 'property'). See http://www.zope.org/Collectors/Zope3-dev/387 - Documentation / style / testing cleanups. 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the zope.pagetemplate package shipped as part of the Zope X3.0.0 release. Keywords: zope3 page template Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.pagetemplate/pip-egg-info/0000755000175000017500000000000012214017603021340 5ustar arnauarnauzope2.13-2.13.21/source/zope.pagetemplate/pip-egg-info/zope.pagetemplate.egg-info/0000755000175000017500000000000012214017603026456 5ustar arnauarnauzope2.13-2.13.21/source/zope.pagetemplate/pip-egg-info/zope.pagetemplate.egg-info/PKG-INFO0000644000175000017500000001650012214017603027555 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.pagetemplate Version: 3.5.2 Summary: Zope Page Templates Home-page: http://pypi.python.org/pypi/zope.pagetemplate Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Page Templates provide an elegant templating mechanism that achieves a clean separation of presentation and application logic while allowing for designers to work with templates in their visual editing tools (FrontPage, Dreamweaver, GoLive, etc.). Detailed Documentation ---------------------- ===================================== ZPT (Zope Page-Template) Architecture ===================================== There are a number of major components that make up the page-template architecture: - The TAL *compiler* and *interpreter*. This is responsible for compiling source files and for executing compiled templates. See the `zope.tal` package for more information. - An *expression engine* is responsible for compiling expressions and for creating expression execution contexts. It is common for applications to override expression engines to provide custom expression support or to change the way expressions are implemented. The `zope.app.pagetemplate` package uses this to implement trusted and untrusted evaluation; a different engine is used for each, with different implementations of the same type of expressions. Expression contexts support execution of expressions and provide APIs for setting up variable scopes and setting variables. The expression contexts are passed to the TAL interpreter at execution time. The most commonly used expression implementation is that found in `zope.tales`. - Page templates tie everything together. They assemble an expression engine with the TAL interpreter and orchestrate management of source and compiled template data. See `zope.pagetemplate.interfaces`. ============== Page Templates ============== Introduction ------------ Page Templates provide an elegant templating mechanism that achieves a clean separation of presentation and application logic while allowing for designers to work with templates in their visual editing tools (FrontPage, Dreamweaver, GoLive, etc.). This document focuses on usage of Page Templates outside of a Zope context, it does *not* explain how to write page templates as there are several resources on the web which do so. Simple Usage ------------ Using Page Templates outside of Zope3 is very easy and straight forward. A quick example:: >>> from zope.pagetemplate.pagetemplatefile import PageTemplateFile >>> my_pt = PageTemplateFile('hello_world.pt') >>> my_pt() u'Hello World' Subclassing PageTemplates ------------------------- Lets say we want to alter page templates such that keyword arguments appear as top level items in the namespace. We can subclass `PageTemplate` and alter the default behavior of `pt_getContext()` to add them in:: from zope.pagetemplate.pagetemplate import PageTemplate class mypt(PageTemplate): def pt_getContext(self, args=(), options={}, **kw): rval = PageTemplate.pt_getContext(self, args=args) options.update(rval) return options class foo: def getContents(self): return 'hi' So now we can bind objects in a more arbitrary fashion, like the following:: template = """ Good Stuff Here """ pt = mypt() pt.write(template) pt(das_object=foo()) See `interfaces.py`. ======= CHANGES ======= 3.5.2 (2010-07-08) ------------------ - Fixed PTRuntimeError exception messages to be consistent across Python versions, and compatibile with the output under Python 2.4. (More readable than the previous output under Python 2.6 as well.) 3.5.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctestunit' in favor of stdlib's doctest. - Added dependency on "zope.security [untrustedpython]" because the 'engine' module uses it. 3.5.0 (2009-05-25) ------------------ - Added test coverage reporting support. - Moved 'engine' module and related test scaffolding here from ``zope.app.pagetemplate`` package. 3.4.2 (2009-03-17) ------------------ - Remove old zpkg-related DEPENDENCIES.cfg file. - Change package's mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired. - Change `cheeseshop` to `pypi` in the packages' homepage url. 3.4.1 (2009-01-27) ------------------ - Fix test due to recent changes in zope.tal. 3.4.0 (2007-10-02) ------------------ - Initial release independent of the Zope 3 tree. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the zope.pagetemplate package shipped as part of the Zope 3.2.0 release. - ZPTPage macro expansion: changed label text to match the corresponding label in Zope 2 and activated the name spaces for macro expansion in 'read'. See http://www.zope.org/Collectors/Zope3-dev/199 - Coding style cleanups. 3.1.0 (2005-10-03) ------------------ - Corresponds to the verison of the zope.pagetemplate package shipped as part of the Zope 3.1.0 release. - Fixed apidoc and Cookie, which were using wrong descriptor class (changed to 'property'). See http://www.zope.org/Collectors/Zope3-dev/387 - Documentation / style / testing cleanups. 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the zope.pagetemplate package shipped as part of the Zope X3.0.0 release. Keywords: zope3 page template Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.pagetemplate/pip-egg-info/zope.pagetemplate.egg-info/dependency_links.txtzope2.13-2.13.21/source/zope.pagetemplate/pip-egg-info/zope.pagetemplate.egg-info/dependency_links.t0000644000175000017500000000000112214017603032150 0ustar arnauarnau zope2.13-2.13.21/source/zope.pagetemplate/pip-egg-info/zope.pagetemplate.egg-info/requires.txt0000644000175000017500000000026712214017603031063 0ustar arnauarnausetuptools zope.interface zope.component zope.security [untrustedpython] zope.tales zope.tal zope.i18n zope.i18nmessageid zope.traversing [test] zope.testing zope.proxy zope.security././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/zope.pagetemplate/pip-egg-info/zope.pagetemplate.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.pagetemplate/pip-egg-info/zope.pagetemplate.egg-info/namespace_packages0000644000175000017500000000000512214017603032166 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.pagetemplate/pip-egg-info/zope.pagetemplate.egg-info/top_level.txt0000644000175000017500000000000512214017603031203 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.pagetemplate/pip-egg-info/zope.pagetemplate.egg-info/SOURCES.txt0000644000175000017500000000222012214017603030336 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.pagetemplate.egg-info/PKG-INFO pip-egg-info/zope.pagetemplate.egg-info/SOURCES.txt pip-egg-info/zope.pagetemplate.egg-info/dependency_links.txt pip-egg-info/zope.pagetemplate.egg-info/namespace_packages.txt pip-egg-info/zope.pagetemplate.egg-info/not-zip-safe pip-egg-info/zope.pagetemplate.egg-info/requires.txt pip-egg-info/zope.pagetemplate.egg-info/top_level.txt src/zope/__init__.py src/zope/pagetemplate/__init__.py src/zope/pagetemplate/engine.py src/zope/pagetemplate/i18n.py src/zope/pagetemplate/interfaces.py src/zope/pagetemplate/pagetemplate.py src/zope/pagetemplate/pagetemplatefile.py src/zope/pagetemplate/tests/__init__.py src/zope/pagetemplate/tests/batch.py src/zope/pagetemplate/tests/test_basictemplate.py src/zope/pagetemplate/tests/test_engine.py src/zope/pagetemplate/tests/test_htmltests.py src/zope/pagetemplate/tests/test_ptfile.py src/zope/pagetemplate/tests/trusted.py src/zope/pagetemplate/tests/util.py src/zope/pagetemplate/tests/input/__init__.py src/zope/pagetemplate/tests/output/__init__.py src/zope/pagetemplate/tests/testpackage/__init__.py src/zope/pagetemplate/tests/testpackage/content.pyzope2.13-2.13.21/source/zope.pagetemplate/pip-egg-info/zope.pagetemplate.egg-info/not-zip-safe0000644000175000017500000000000112214017603030704 0ustar arnauarnau zope2.13-2.13.21/source/zope.pagetemplate/LICENSE.txt0000644000175000017500000000402612214017603020704 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.pagetemplate/README.txt0000644000175000017500000000037012214017603020555 0ustar arnauarnauPage Templates provide an elegant templating mechanism that achieves a clean separation of presentation and application logic while allowing for designers to work with templates in their visual editing tools (FrontPage, Dreamweaver, GoLive, etc.). zope2.13-2.13.21/source/zope.pagetemplate/setup.cfg0000644000175000017500000000007312214017603020700 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.pagetemplate/COPYRIGHT.txt0000644000175000017500000000004012214017603021162 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.pagetemplate/buildout.cfg0000644000175000017500000000073212214017603021371 0ustar arnauarnau[buildout] develop = . parts = py test coverage-test coverage-report [test] recipe = zc.recipe.testrunner eggs = zope.pagetemplate [test] [py] recipe = zc.recipe.egg eggs = zope.pagetemplate interpreter = py [coverage-test] recipe = zc.recipe.testrunner eggs = zope.pagetemplate [test] defaults = ['--coverage', '../../coverage'] [coverage-report] recipe = zc.recipe.egg eggs = z3c.coverage scripts = coverage=coverage-report arguments = ('coverage', 'coverage/report') zope2.13-2.13.21/source/zope.pagetemplate/bootstrap.py0000644000175000017500000000733012214017603021451 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.pagetemplate/CHANGES.txt0000644000175000017500000000365712214017603020703 0ustar arnauarnau======= CHANGES ======= 3.5.2 (2010-07-08) ------------------ - Fixed PTRuntimeError exception messages to be consistent across Python versions, and compatibile with the output under Python 2.4. (More readable than the previous output under Python 2.6 as well.) 3.5.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctestunit' in favor of stdlib's doctest. - Added dependency on "zope.security [untrustedpython]" because the 'engine' module uses it. 3.5.0 (2009-05-25) ------------------ - Added test coverage reporting support. - Moved 'engine' module and related test scaffolding here from ``zope.app.pagetemplate`` package. 3.4.2 (2009-03-17) ------------------ - Remove old zpkg-related DEPENDENCIES.cfg file. - Change package's mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired. - Change `cheeseshop` to `pypi` in the packages' homepage url. 3.4.1 (2009-01-27) ------------------ - Fix test due to recent changes in zope.tal. 3.4.0 (2007-10-02) ------------------ - Initial release independent of the Zope 3 tree. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the zope.pagetemplate package shipped as part of the Zope 3.2.0 release. - ZPTPage macro expansion: changed label text to match the corresponding label in Zope 2 and activated the name spaces for macro expansion in 'read'. See http://www.zope.org/Collectors/Zope3-dev/199 - Coding style cleanups. 3.1.0 (2005-10-03) ------------------ - Corresponds to the verison of the zope.pagetemplate package shipped as part of the Zope 3.1.0 release. - Fixed apidoc and Cookie, which were using wrong descriptor class (changed to 'property'). See http://www.zope.org/Collectors/Zope3-dev/387 - Documentation / style / testing cleanups. 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the zope.pagetemplate package shipped as part of the Zope X3.0.0 release. zope2.13-2.13.21/source/zope.pagetemplate/src/0000755000175000017500000000000012214017603017646 5ustar arnauarnauzope2.13-2.13.21/source/zope.pagetemplate/src/zope.pagetemplate.egg-info/0000755000175000017500000000000012214017603024764 5ustar arnauarnauzope2.13-2.13.21/source/zope.pagetemplate/src/zope.pagetemplate.egg-info/PKG-INFO0000644000175000017500000001650012214017603026063 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.pagetemplate Version: 3.5.2 Summary: Zope Page Templates Home-page: http://pypi.python.org/pypi/zope.pagetemplate Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Page Templates provide an elegant templating mechanism that achieves a clean separation of presentation and application logic while allowing for designers to work with templates in their visual editing tools (FrontPage, Dreamweaver, GoLive, etc.). Detailed Documentation ---------------------- ===================================== ZPT (Zope Page-Template) Architecture ===================================== There are a number of major components that make up the page-template architecture: - The TAL *compiler* and *interpreter*. This is responsible for compiling source files and for executing compiled templates. See the `zope.tal` package for more information. - An *expression engine* is responsible for compiling expressions and for creating expression execution contexts. It is common for applications to override expression engines to provide custom expression support or to change the way expressions are implemented. The `zope.app.pagetemplate` package uses this to implement trusted and untrusted evaluation; a different engine is used for each, with different implementations of the same type of expressions. Expression contexts support execution of expressions and provide APIs for setting up variable scopes and setting variables. The expression contexts are passed to the TAL interpreter at execution time. The most commonly used expression implementation is that found in `zope.tales`. - Page templates tie everything together. They assemble an expression engine with the TAL interpreter and orchestrate management of source and compiled template data. See `zope.pagetemplate.interfaces`. ============== Page Templates ============== Introduction ------------ Page Templates provide an elegant templating mechanism that achieves a clean separation of presentation and application logic while allowing for designers to work with templates in their visual editing tools (FrontPage, Dreamweaver, GoLive, etc.). This document focuses on usage of Page Templates outside of a Zope context, it does *not* explain how to write page templates as there are several resources on the web which do so. Simple Usage ------------ Using Page Templates outside of Zope3 is very easy and straight forward. A quick example:: >>> from zope.pagetemplate.pagetemplatefile import PageTemplateFile >>> my_pt = PageTemplateFile('hello_world.pt') >>> my_pt() u'Hello World' Subclassing PageTemplates ------------------------- Lets say we want to alter page templates such that keyword arguments appear as top level items in the namespace. We can subclass `PageTemplate` and alter the default behavior of `pt_getContext()` to add them in:: from zope.pagetemplate.pagetemplate import PageTemplate class mypt(PageTemplate): def pt_getContext(self, args=(), options={}, **kw): rval = PageTemplate.pt_getContext(self, args=args) options.update(rval) return options class foo: def getContents(self): return 'hi' So now we can bind objects in a more arbitrary fashion, like the following:: template = """ Good Stuff Here """ pt = mypt() pt.write(template) pt(das_object=foo()) See `interfaces.py`. ======= CHANGES ======= 3.5.2 (2010-07-08) ------------------ - Fixed PTRuntimeError exception messages to be consistent across Python versions, and compatibile with the output under Python 2.4. (More readable than the previous output under Python 2.6 as well.) 3.5.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctestunit' in favor of stdlib's doctest. - Added dependency on "zope.security [untrustedpython]" because the 'engine' module uses it. 3.5.0 (2009-05-25) ------------------ - Added test coverage reporting support. - Moved 'engine' module and related test scaffolding here from ``zope.app.pagetemplate`` package. 3.4.2 (2009-03-17) ------------------ - Remove old zpkg-related DEPENDENCIES.cfg file. - Change package's mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired. - Change `cheeseshop` to `pypi` in the packages' homepage url. 3.4.1 (2009-01-27) ------------------ - Fix test due to recent changes in zope.tal. 3.4.0 (2007-10-02) ------------------ - Initial release independent of the Zope 3 tree. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the zope.pagetemplate package shipped as part of the Zope 3.2.0 release. - ZPTPage macro expansion: changed label text to match the corresponding label in Zope 2 and activated the name spaces for macro expansion in 'read'. See http://www.zope.org/Collectors/Zope3-dev/199 - Coding style cleanups. 3.1.0 (2005-10-03) ------------------ - Corresponds to the verison of the zope.pagetemplate package shipped as part of the Zope 3.1.0 release. - Fixed apidoc and Cookie, which were using wrong descriptor class (changed to 'property'). See http://www.zope.org/Collectors/Zope3-dev/387 - Documentation / style / testing cleanups. 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the zope.pagetemplate package shipped as part of the Zope X3.0.0 release. Keywords: zope3 page template Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.pagetemplate/src/zope.pagetemplate.egg-info/dependency_links.txt0000644000175000017500000000000112214017603031032 0ustar arnauarnau zope2.13-2.13.21/source/zope.pagetemplate/src/zope.pagetemplate.egg-info/requires.txt0000644000175000017500000000026712214017603027371 0ustar arnauarnausetuptools zope.interface zope.component zope.security [untrustedpython] zope.tales zope.tal zope.i18n zope.i18nmessageid zope.traversing [test] zope.testing zope.proxy zope.securityzope2.13-2.13.21/source/zope.pagetemplate/src/zope.pagetemplate.egg-info/namespace_packages.txt0000644000175000017500000000000512214017603031312 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.pagetemplate/src/zope.pagetemplate.egg-info/top_level.txt0000644000175000017500000000000512214017603027511 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.pagetemplate/src/zope.pagetemplate.egg-info/SOURCES.txt0000644000175000017500000000535412214017603026657 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.pagetemplate.egg-info/PKG-INFO src/zope.pagetemplate.egg-info/SOURCES.txt src/zope.pagetemplate.egg-info/dependency_links.txt src/zope.pagetemplate.egg-info/namespace_packages.txt src/zope.pagetemplate.egg-info/not-zip-safe src/zope.pagetemplate.egg-info/requires.txt src/zope.pagetemplate.egg-info/top_level.txt src/zope/pagetemplate/__init__.py src/zope/pagetemplate/architecture.txt src/zope/pagetemplate/engine.py src/zope/pagetemplate/i18n.py src/zope/pagetemplate/interfaces.py src/zope/pagetemplate/pagetemplate.py src/zope/pagetemplate/pagetemplatefile.py src/zope/pagetemplate/readme.txt src/zope/pagetemplate/tests/__init__.py src/zope/pagetemplate/tests/batch.py src/zope/pagetemplate/tests/test_basictemplate.py src/zope/pagetemplate/tests/test_engine.py src/zope/pagetemplate/tests/test_htmltests.py src/zope/pagetemplate/tests/test_ptfile.py src/zope/pagetemplate/tests/trusted.py src/zope/pagetemplate/tests/util.py src/zope/pagetemplate/tests/input/__init__.py src/zope/pagetemplate/tests/input/checknotexpression.html src/zope/pagetemplate/tests/input/checknothing.html src/zope/pagetemplate/tests/input/checkpathalt.html src/zope/pagetemplate/tests/input/checkpathnothing.html src/zope/pagetemplate/tests/input/checkwithxmlheader.html src/zope/pagetemplate/tests/input/dtml1.html src/zope/pagetemplate/tests/input/dtml3.html src/zope/pagetemplate/tests/input/globalsshadowlocals.html src/zope/pagetemplate/tests/input/loop1.html src/zope/pagetemplate/tests/input/stringexpression.html src/zope/pagetemplate/tests/input/teeshop1.html src/zope/pagetemplate/tests/input/teeshop2.html src/zope/pagetemplate/tests/input/teeshoplaf.html src/zope/pagetemplate/tests/input/translation.html src/zope/pagetemplate/tests/output/__init__.py src/zope/pagetemplate/tests/output/checknotexpression.html src/zope/pagetemplate/tests/output/checknothing.html src/zope/pagetemplate/tests/output/checkpathalt.html src/zope/pagetemplate/tests/output/checkpathnothing.html src/zope/pagetemplate/tests/output/checkwithxmlheader.html src/zope/pagetemplate/tests/output/dtml1a.html src/zope/pagetemplate/tests/output/dtml1b.html src/zope/pagetemplate/tests/output/dtml3.html src/zope/pagetemplate/tests/output/globalsshadowlocals.html src/zope/pagetemplate/tests/output/loop1.html src/zope/pagetemplate/tests/output/stringexpression.html src/zope/pagetemplate/tests/output/teeshop1.html src/zope/pagetemplate/tests/output/teeshop2.html src/zope/pagetemplate/tests/output/teeshoplaf.html src/zope/pagetemplate/tests/output/translation.html src/zope/pagetemplate/tests/testpackage/__init__.py src/zope/pagetemplate/tests/testpackage/content.py src/zope/pagetemplate/tests/testpackage/view.ptzope2.13-2.13.21/source/zope.pagetemplate/src/zope.pagetemplate.egg-info/not-zip-safe0000644000175000017500000000000112214017603027212 0ustar arnauarnau zope2.13-2.13.21/source/zope.pagetemplate/src/zope/0000755000175000017500000000000012214017603020623 5ustar arnauarnauzope2.13-2.13.21/source/zope.pagetemplate/src/zope/__init__.py0000644000175000017500000000007012214017603022731 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/0000755000175000017500000000000012214017603023273 5ustar arnauarnauzope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/i18n.py0000644000175000017500000000157212214017603024431 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Customization of zope.i18n for the Zope application server """ __docformat__ = 'restructuredtext' # import this as _ to create i18n messages in the zope domain from zope.i18nmessageid import MessageFactory ZopeMessageFactory = MessageFactory('zope') zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/readme.txt0000644000175000017500000000320212214017603025266 0ustar arnauarnau============== Page Templates ============== Introduction ------------ Page Templates provide an elegant templating mechanism that achieves a clean separation of presentation and application logic while allowing for designers to work with templates in their visual editing tools (FrontPage, Dreamweaver, GoLive, etc.). This document focuses on usage of Page Templates outside of a Zope context, it does *not* explain how to write page templates as there are several resources on the web which do so. Simple Usage ------------ Using Page Templates outside of Zope3 is very easy and straight forward. A quick example:: >>> from zope.pagetemplate.pagetemplatefile import PageTemplateFile >>> my_pt = PageTemplateFile('hello_world.pt') >>> my_pt() u'Hello World' Subclassing PageTemplates ------------------------- Lets say we want to alter page templates such that keyword arguments appear as top level items in the namespace. We can subclass `PageTemplate` and alter the default behavior of `pt_getContext()` to add them in:: from zope.pagetemplate.pagetemplate import PageTemplate class mypt(PageTemplate): def pt_getContext(self, args=(), options={}, **kw): rval = PageTemplate.pt_getContext(self, args=args) options.update(rval) return options class foo: def getContents(self): return 'hi' So now we can bind objects in a more arbitrary fashion, like the following:: template = """ Good Stuff Here """ pt = mypt() pt.write(template) pt(das_object=foo()) See `interfaces.py`. zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/pagetemplate.py0000644000175000017500000001613412214017603026322 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Page Template module HTML- and XML-based template objects using TAL, TALES, and METAL. """ import sys from zope.tal.talparser import TALParser from zope.tal.htmltalparser import HTMLTALParser from zope.tal.talgenerator import TALGenerator from zope.tal.talinterpreter import TALInterpreter from zope.tales.engine import Engine # Don't use cStringIO here! It's not unicode aware. from StringIO import StringIO from zope.pagetemplate.interfaces import IPageTemplateSubclassing from zope.interface import implements _default_options = {} _error_start = '') if errend >= 0: text = text[errend + 3:] if text[:1] == "\n": text = text[1:] if self._text != text: self._text = text # Always cook on an update, even if the source is the same; # the content-type might have changed. self._cook() def read(self, request=None): """Gets the source, sometimes with macros expanded.""" self._cook_check() if not self._v_errors: if not self.expand: return self._text try: # This gets called, if macro expansion is turned on. # Note that an empty dictionary is fine for the context at # this point, since we are not evaluating the template. return self.pt_render(self.pt_getContext(self, request), source=1) except: return ('%s\n Macro expansion failed\n %s\n-->\n%s' % (_error_start, "%s: %s" % sys.exc_info()[:2], self._text) ) return ('%s\n %s\n-->\n%s' % (_error_start, '\n'.join(self._v_errors), self._text)) def pt_source_file(self): """To be overridden.""" return None def _cook_check(self): if not self._v_cooked: self._cook() def _cook(self): """Compile the TAL and METAL statments. Cooking must not fail due to compilation errors in templates. """ engine = self.pt_getEngine() source_file = self.pt_source_file() if self.content_type == 'text/html': gen = TALGenerator(engine, xml=0, source_file=source_file) parser = HTMLTALParser(gen) else: gen = TALGenerator(engine, source_file=source_file) parser = TALParser(gen) self._v_errors = () try: parser.parseString(self._text) self._v_program, self._v_macros = parser.getCode() except: etype, e = sys.exc_info()[:2] self._v_errors = ["Compilation failed", "%s.%s: %s" % (etype.__module__, etype.__name__, e)] self._v_cooked = 1 class PTRuntimeError(RuntimeError): '''The Page Template has template errors that prevent it from rendering.''' pass class PageTemplateTracebackSupplement(object): #implements(ITracebackSupplement) def __init__(self, pt, namespace): self.manageable_object = pt self.warnings = [] e = pt.pt_errors(namespace) if e: self.warnings.extend(e) zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/architecture.txt0000644000175000017500000000252212214017603026517 0ustar arnauarnau===================================== ZPT (Zope Page-Template) Architecture ===================================== There are a number of major components that make up the page-template architecture: - The TAL *compiler* and *interpreter*. This is responsible for compiling source files and for executing compiled templates. See the `zope.tal` package for more information. - An *expression engine* is responsible for compiling expressions and for creating expression execution contexts. It is common for applications to override expression engines to provide custom expression support or to change the way expressions are implemented. The `zope.app.pagetemplate` package uses this to implement trusted and untrusted evaluation; a different engine is used for each, with different implementations of the same type of expressions. Expression contexts support execution of expressions and provide APIs for setting up variable scopes and setting variables. The expression contexts are passed to the TAL interpreter at execution time. The most commonly used expression implementation is that found in `zope.tales`. - Page templates tie everything together. They assemble an expression engine with the TAL interpreter and orchestrate management of source and compiled template data. See `zope.pagetemplate.interfaces`. zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/interfaces.py0000644000175000017500000000647512214017603026004 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interface that describes the 'macros' attribute of a PageTemplate. """ from zope.interface import Interface, Attribute class IPageTemplate(Interface): """Objects that can render page templates """ def __call__(*args, **kw): """Render a page template The argument handling is specific to particular implementations. Normally, however, positional arguments are bound to the top-level `args` variable and keyword arguments are bound to the top-level `options` variable. """ def pt_edit(source, content_type): """Set the source and content type """ def pt_errors(namespace): """Return a sequence of strings that describe errors in the template. The errors may occur when the template is compiled or rendered. `namespace` is the set of names passed to the TALES expression evaluator, similar to what's returned by pt_getContext(). This can be used to let a template author know what went wrong when an attempt was made to render the template. """ def read(): """Get the template source """ macros = Attribute("An object that implements the __getitem__ " "protocol, containing page template macros.") class IPageTemplateSubclassing(IPageTemplate): """Behavior that may be overridden or used by subclasses """ def pt_getContext(**kw): """Compute a dictionary of top-level template names Responsible for returning the set of top-level names supported in path expressions """ def pt_getEngine(): """Returns the TALES expression evaluator. """ def pt_getEngineContext(namespace): """Return an execution context from the expression engine.""" def __call__(*args, **kw): """Render a page template This is sometimes overridden to provide additional argument binding. """ def pt_source_file(): """return some text describing where a bit of ZPT code came from. This could be a file path, a object path, etc. """ def _cook(): """Compile the source Results are saved in the variables: _v_errors, _v_warnings, _v_program, and _v_macros, and the flag _v_cooked is set. """ def _cook_check(): """Compiles the source if necessary Subclasses might override this to influence the decision about whether compilation is necessary. """ content_type = Attribute("The content-type of the generated output") expand = Attribute( "Flag indicating whether the read method should expand macros") zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/__init__.py0000644000175000017500000000123012214017603025400 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Page Templates """ zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/engine.py0000644000175000017500000003562012214017603025120 0ustar arnauarnau############################################################################## # # Copyright (c) 2002-2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Expression engine configuration and registration. Each expression engine can have its own expression types and base names. """ __docformat__ = 'restructuredtext' import sys from zope import component from zope.interface import implements from zope.component.interfaces import ComponentLookupError from zope.traversing.interfaces import IPathAdapter, ITraversable from zope.traversing.interfaces import TraversalError from zope.traversing.adapters import traversePathElement from zope.security.untrustedpython import rcompile from zope.security.proxy import ProxyFactory, removeSecurityProxy from zope.security.untrustedpython.builtins import SafeBuiltins from zope.i18n import translate from zope.tales.expressions import PathExpr, StringExpr, NotExpr, DeferExpr from zope.tales.expressions import SimpleModuleImporter from zope.tales.pythonexpr import PythonExpr from zope.tales.tales import ExpressionEngine, Context from zope.pagetemplate.i18n import ZopeMessageFactory as _ class InlineCodeError(Exception): pass class ZopeTraverser(object): def __init__(self, proxify=None): if proxify is None: self.proxify = lambda x: x else: self.proxify = proxify def __call__(self, object, path_items, econtext): """Traverses a sequence of names, first trying attributes then items. """ request = getattr(econtext, 'request', None) path_items = list(path_items) path_items.reverse() while path_items: name = path_items.pop() # special-case dicts for performance reasons if getattr(object, '__class__', None) == dict: object = object[name] else: object = traversePathElement(object, name, path_items, request=request) object = self.proxify(object) return object zopeTraverser = ZopeTraverser(ProxyFactory) class ZopePathExpr(PathExpr): def __init__(self, name, expr, engine): super(ZopePathExpr, self).__init__(name, expr, engine, zopeTraverser) trustedZopeTraverser = ZopeTraverser() class TrustedZopePathExpr(PathExpr): def __init__(self, name, expr, engine): super(TrustedZopePathExpr, self).__init__(name, expr, engine, trustedZopeTraverser) # Create a version of the restricted built-ins that uses a safe # version of getattr() that wraps values in security proxies where # appropriate: class ZopePythonExpr(PythonExpr): def __call__(self, econtext): __traceback_info__ = self.text vars = self._bind_used_names(econtext, SafeBuiltins) return eval(self._code, vars) def _compile(self, text, filename): return rcompile.compile(text, filename, 'eval') class ZopeContextBase(Context): """Base class for both trusted and untrusted evaluation contexts.""" def translate(self, msgid, domain=None, mapping=None, default=None): return translate(msgid, domain, mapping, context=self.request, default=default) evaluateInlineCode = False def evaluateCode(self, lang, code): if not self.evaluateInlineCode: raise InlineCodeError( _('Inline Code Evaluation is deactivated, which means that ' 'you cannot have inline code snippets in your Page ' 'Template. Activate Inline Code Evaluation and try again.')) # TODO This is only needed when self.evaluateInlineCode is true, # so should only be needed for zope.app.pythonpage. from zope.app.interpreter.interfaces import IInterpreter interpreter = component.queryUtility(IInterpreter, lang) if interpreter is None: error = _('No interpreter named "${lang_name}" was found.', mapping={'lang_name': lang}) raise InlineCodeError(error) globals = self.vars.copy() result = interpreter.evaluateRawCode(code, globals) # Add possibly new global variables. old_names = self.vars.keys() for name, value in globals.items(): if name not in old_names: self.setGlobal(name, value) return result class ZopeContext(ZopeContextBase): """Evaluation context for untrusted programs.""" def evaluateMacro(self, expr): """evaluateMacro gets security-proxied macro programs when this is run with the zopeTraverser, and in other untrusted situations. This will cause evaluation to fail in zope.tal.talinterpreter, which knows nothing of security proxies. Therefore, this method removes any proxy from the evaluated expression. >>> output = [('version', 'xxx'), ('mode', 'html'), ('other', 'things')] >>> def expression(context): ... return ProxyFactory(output) ... >>> zc = ZopeContext(ExpressionEngine, {}) >>> out = zc.evaluateMacro(expression) >>> type(out) The method does some trivial checking to make sure we are getting back a macro like we expect: it must be a sequence of sequences, in which the first sequence must start with 'version', and the second must start with 'mode'. >>> del output[0] >>> zc.evaluateMacro(expression) # doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: ('unexpected result from macro evaluation.', ...) >>> del output[:] >>> zc.evaluateMacro(expression) # doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: ('unexpected result from macro evaluation.', ...) >>> output = None >>> zc.evaluateMacro(expression) # doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: ('unexpected result from macro evaluation.', ...) """ macro = removeSecurityProxy(Context.evaluateMacro(self, expr)) # we'll do some basic checks that it is the sort of thing we expect problem = False try: problem = macro[0][0] != 'version' or macro[1][0] != 'mode' except (TypeError, IndexError): problem = True if problem: raise ValueError('unexpected result from macro evaluation.', macro) return macro def setContext(self, name, value): # Hook to allow subclasses to do things like adding security proxies Context.setContext(self, name, ProxyFactory(value)) class TrustedZopeContext(ZopeContextBase): """Evaluation context for trusted programs.""" class AdapterNamespaces(object): """Simulate tales function namespaces with adapter lookup. When we are asked for a namespace, we return an object that actually computes an adapter when called: To demonstrate this, we need to register an adapter: >>> from zope.component.testing import setUp, tearDown >>> setUp() >>> from zope.component import provideAdapter >>> def adapter1(ob): ... return 1 >>> adapter1.__component_adapts__ = (None,) >>> provideAdapter(adapter1, None, IPathAdapter, 'a1') Now, with this adapter in place, we can try out the namespaces: >>> ob = object() >>> namespaces = AdapterNamespaces() >>> namespace = namespaces['a1'] >>> namespace(ob) 1 >>> namespace = namespaces['a2'] >>> namespace(ob) Traceback (most recent call last): ... KeyError: 'a2' Cleanup: >>> tearDown() """ def __init__(self): self.namespaces = {} def __getitem__(self, name): namespace = self.namespaces.get(name) if namespace is None: def namespace(object): try: return component.getAdapter(object, IPathAdapter, name) except ComponentLookupError: raise KeyError(name) self.namespaces[name] = namespace return namespace class ZopeBaseEngine(ExpressionEngine): _create_context = ZopeContext def __init__(self): ExpressionEngine.__init__(self) self.namespaces = AdapterNamespaces() def getContext(self, __namespace=None, **namespace): if __namespace: if namespace: namespace.update(__namespace) else: namespace = __namespace context = self._create_context(self, namespace) # Put request into context so path traversal can find it if 'request' in namespace: context.request = namespace['request'] # Put context into context so path traversal can find it if 'context' in namespace: context.context = namespace['context'] return context class ZopeEngine(ZopeBaseEngine): """Untrusted expression engine. This engine does not allow modules to be imported; only modules already available may be accessed:: >>> modname = 'zope.pagetemplate.tests.trusted' >>> engine = _Engine() >>> context = engine.getContext(engine.getBaseNames()) >>> modname in sys.modules False >>> context.evaluate('modules/' + modname) Traceback (most recent call last): ... KeyError: 'zope.pagetemplate.tests.trusted' (The use of ``KeyError`` is an unfortunate implementation detail; I think this should be a ``TraversalError``.) Modules which have already been imported by trusted code are available, wrapped in security proxies:: >>> m = context.evaluate('modules/sys') >>> m.__name__ 'sys' >>> m._getframe Traceback (most recent call last): ... ForbiddenAttribute: ('_getframe', ) The results of Python expressions evaluated by this engine are wrapped in security proxies:: >>> r = context.evaluate('python: {12: object()}.values') >>> type(r) >>> r = context.evaluate('python: {12: object()}.values()[0].__class__') >>> type(r) General path expressions provide objects that are wrapped in security proxies as well:: >>> from zope.component.testing import setUp, tearDown >>> from zope.security.checker import NamesChecker, defineChecker >>> class Container(dict): ... implements(ITraversable) ... def traverse(self, name, further_path): ... return self[name] >>> setUp() >>> defineChecker(Container, NamesChecker(['traverse'])) >>> d = engine.getBaseNames() >>> foo = Container() >>> foo.__name__ = 'foo' >>> d['foo'] = ProxyFactory(foo) >>> foo['bar'] = bar = Container() >>> bar.__name__ = 'bar' >>> bar.__parent__ = foo >>> bar['baz'] = baz = Container() >>> baz.__name__ = 'baz' >>> baz.__parent__ = bar >>> context = engine.getContext(d) >>> o1 = context.evaluate('foo/bar') >>> o1.__name__ 'bar' >>> type(o1) >>> o2 = context.evaluate('foo/bar/baz') >>> o2.__name__ 'baz' >>> type(o2) >>> o3 = o2.__parent__ >>> type(o3) >>> o1 == o3 True >>> o1 is o2 False Note that this engine special-cases dicts during path traversal: it traverses only to their items, but not to their attributes (e.g. methods on dicts), because of performance reasons: >>> d = engine.getBaseNames() >>> d['adict'] = {'items': 123} >>> d['anotherdict'] = {} >>> context = engine.getContext(d) >>> context.evaluate('adict/items') 123 >>> context.evaluate('anotherdict/keys') Traceback (most recent call last): ... KeyError: 'keys' >>> tearDown() """ def getFunctionNamespace(self, namespacename): """ Returns the function namespace """ return ProxyFactory( super(ZopeEngine, self).getFunctionNamespace(namespacename)) class TrustedZopeEngine(ZopeBaseEngine): """Trusted expression engine. This engine allows modules to be imported:: >>> modname = 'zope.pagetemplate.tests.trusted' >>> engine = _TrustedEngine() >>> context = engine.getContext(engine.getBaseNames()) >>> modname in sys.modules False >>> m = context.evaluate('modules/' + modname) >>> m.__name__ == modname True >>> modname in sys.modules True Since this is trusted code, we can look at whatever is in the module, not just __name__ or what's declared in a security assertion:: >>> m.x 42 Clean up after ourselves:: >>> del sys.modules[modname] """ _create_context = TrustedZopeContext class TraversableModuleImporter(SimpleModuleImporter): implements(ITraversable) def traverse(self, name, further_path): try: return self[name] except KeyError: raise TraversalError(self, name) def _Engine(engine=None): if engine is None: engine = ZopeEngine() engine = _create_base_engine(engine, ZopePathExpr) engine.registerType('python', ZopePythonExpr) # Using a proxy around sys.modules allows page templates to use # modules for which security declarations have been made, but # disallows execution of any import-time code for modules, which # should not be allowed to happen during rendering. engine.registerBaseName('modules', ProxyFactory(sys.modules)) return engine def _TrustedEngine(engine=None): if engine is None: engine = TrustedZopeEngine() engine = _create_base_engine(engine, TrustedZopePathExpr) engine.registerType('python', PythonExpr) engine.registerBaseName('modules', TraversableModuleImporter()) return engine def _create_base_engine(engine, pathtype): for pt in pathtype._default_type_names: engine.registerType(pt, pathtype) engine.registerType('string', StringExpr) engine.registerType('not', NotExpr) engine.registerType('defer', DeferExpr) return engine Engine = _Engine() TrustedEngine = _TrustedEngine() class AppPT(object): def pt_getEngine(self): return Engine class TrustedAppPT(object): def pt_getEngine(self): return TrustedEngine zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/0000755000175000017500000000000012214017603024435 5ustar arnauarnauzope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/test_engine.py0000644000175000017500000000615412214017603027321 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Doc tests for the pagetemplate's 'engine' module """ import unittest class DummyNamespace(object): def __init__(self, context): self.context = context class EngineTests(unittest.TestCase): def setUp(self): from zope.component.testing import setUp setUp() def tearDown(self): from zope.component.testing import tearDown tearDown() def test_function_namespaces_return_secured_proxies(self): # See https://bugs.launchpad.net/zope3/+bug/98323 from zope.component import provideAdapter from zope.traversing.interfaces import IPathAdapter from zope.pagetemplate.engine import _Engine from zope.proxy import isProxy provideAdapter(DummyNamespace, (None,), IPathAdapter, name='test') engine = _Engine() namespace = engine.getFunctionNamespace('test') self.failUnless(isProxy(namespace)) class DummyEngine(object): def getTypes(self): return {} class DummyContext(object): _engine = DummyEngine() def __init__(self, **kw): self.vars = kw class ZopePythonExprTests(unittest.TestCase): def test_simple(self): from zope.pagetemplate.engine import ZopePythonExpr expr = ZopePythonExpr('python', 'max(a,b)', DummyEngine()) self.assertEqual(expr(DummyContext(a=1, b=2)), 2) def test_allowed_module_name(self): from zope.pagetemplate.engine import ZopePythonExpr expr = ZopePythonExpr('python', '__import__("sys").__name__', DummyEngine()) self.assertEqual(expr(DummyContext()), 'sys') def test_forbidden_module_name(self): from zope.pagetemplate.engine import ZopePythonExpr from zope.security.interfaces import Forbidden expr = ZopePythonExpr('python', '__import__("sys").exit', DummyEngine()) self.assertRaises(Forbidden, expr, DummyContext()) def test_disallowed_builtin(self): from zope.pagetemplate.engine import ZopePythonExpr expr = ZopePythonExpr('python', 'open("x", "w")', DummyEngine()) self.assertRaises(NameError, expr, DummyContext()) def test_suite(): from doctest import DocTestSuite suite = unittest.TestSuite() suite.addTest(DocTestSuite('zope.pagetemplate.engine')) suite.addTest(unittest.makeSuite(EngineTests)) suite.addTest(unittest.makeSuite(ZopePythonExprTests)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/test_ptfile.py0000644000175000017500000001530112214017603027331 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of PageTemplateFile. """ import os import tempfile import unittest from zope.pagetemplate.pagetemplatefile import PageTemplateFile class TypeSniffingTestCase(unittest.TestCase): TEMPFILENAME = tempfile.mktemp() def tearDown(self): if os.path.exists(self.TEMPFILENAME): os.unlink(self.TEMPFILENAME) def get_pt(self, text): f = open(self.TEMPFILENAME, "wb") f.write(text) f.close() pt = PageTemplateFile(self.TEMPFILENAME) pt.read() return pt def check_content_type(self, text, expected_type): pt = self.get_pt(text) self.assertEqual(pt.content_type, expected_type) def test_sniffer_xml_ascii(self): self.check_content_type( "", "text/xml") self.check_content_type( "", "text/xml") def test_sniffer_xml_utf8(self): # w/out byte order mark self.check_content_type( "", "text/xml") self.check_content_type( "", "text/xml") # with byte order mark self.check_content_type( "\xef\xbb\xbf", "text/xml") self.check_content_type( "\xef\xbb\xbf", "text/xml") def test_sniffer_xml_utf16_be(self): # w/out byte order mark self.check_content_type( "\0<\0?\0x\0m\0l\0 \0v\0e\0r\0s\0i\0o\0n\0=\0'\01\0.\0000\0'" "\0 \0e\0n\0c\0o\0d\0i\0n\0g\0=\0'\0u\0t\0f\0-\08\0'\0?\0>" "\0<\0d\0o\0c\0/\0>", "text/xml") self.check_content_type( "\0<\0?\0x\0m\0l\0\t\0v\0e\0r\0s\0i\0o\0n\0=\0'\01\0.\0000\0'" "\0 \0e\0n\0c\0o\0d\0i\0n\0g\0=\0'\0u\0t\0f\0-\08\0'\0?\0>" "\0<\0d\0o\0c\0/\0>", "text/xml") # with byte order mark self.check_content_type( "\xfe\xff" "\0<\0?\0x\0m\0l\0 \0v\0e\0r\0s\0i\0o\0n\0=\0'\01\0.\0000\0'" "\0 \0e\0n\0c\0o\0d\0i\0n\0g\0=\0'\0u\0t\0f\0-\08\0'\0?\0>" "\0<\0d\0o\0c\0/\0>", "text/xml") self.check_content_type( "\xfe\xff" "\0<\0?\0x\0m\0l\0\t\0v\0e\0r\0s\0i\0o\0n\0=\0'\01\0.\0000\0'" "\0 \0e\0n\0c\0o\0d\0i\0n\0g\0=\0'\0u\0t\0f\0-\08\0'\0?\0>" "\0<\0d\0o\0c\0/\0>", "text/xml") def test_sniffer_xml_utf16_le(self): # w/out byte order mark self.check_content_type( "<\0?\0x\0m\0l\0 \0v\0e\0r\0s\0i\0o\0n\0=\0'\01\0.\0000\0'\0" " \0e\0n\0c\0o\0d\0i\0n\0g\0=\0'\0u\0t\0f\0-\08\0'\0?\0>\0" "<\0d\0o\0c\0/\0>\n", "text/xml") self.check_content_type( "<\0?\0x\0m\0l\0\t\0v\0e\0r\0s\0i\0o\0n\0=\0'\01\0.\0000\0'\0" " \0e\0n\0c\0o\0d\0i\0n\0g\0=\0'\0u\0t\0f\0-\08\0'\0?\0>\0" "<\0d\0o\0c\0/\0>\0", "text/xml") # with byte order mark self.check_content_type( "\xff\xfe" "<\0?\0x\0m\0l\0 \0v\0e\0r\0s\0i\0o\0n\0=\0'\01\0.\0000\0'\0" " \0e\0n\0c\0o\0d\0i\0n\0g\0=\0'\0u\0t\0f\0-\08\0'\0?\0>\0" "<\0d\0o\0c\0/\0>\0", "text/xml") self.check_content_type( "\xff\xfe" "<\0?\0x\0m\0l\0\t\0v\0e\0r\0s\0i\0o\0n\0=\0'\01\0.\0000\0'\0" " \0e\0n\0c\0o\0d\0i\0n\0g\0=\0'\0u\0t\0f\0-\08\0'\0?\0>\0" "<\0d\0o\0c\0/\0>\0", "text/xml") HTML_PUBLIC_ID = "-//W3C//DTD HTML 4.01 Transitional//EN" HTML_SYSTEM_ID = "http://www.w3.org/TR/html4/loose.dtd" def test_sniffer_html_ascii(self): self.check_content_type( "" % self.HTML_SYSTEM_ID, "text/html") self.check_content_type( "sample document", "text/html") # TODO: This reflects a case that simply isn't handled by the # sniffer; there are many, but it gets it right more often than # before. def donttest_sniffer_xml_simple(self): self.check_content_type("", "text/xml") def test_html_default_encoding(self): pt = self.get_pt( "" # 'Test' in russian (utf-8) "\xd0\xa2\xd0\xb5\xd1\x81\xd1\x82" "") rendered = pt() self.failUnless(isinstance(rendered, unicode)) self.failUnlessEqual(rendered.strip(), u"" u"\u0422\u0435\u0441\u0442" u"") def test_html_encoding_by_meta(self): pt = self.get_pt( "" # 'Test' in russian (windows-1251) "\xd2\xe5\xf1\xf2" '' "") rendered = pt() self.failUnless(isinstance(rendered, unicode)) self.failUnlessEqual(rendered.strip(), u"" u"\u0422\u0435\u0441\u0442" u"") def test_xhtml(self): pt = self.get_pt( "" # 'Test' in russian (windows-1251) "\xd2\xe5\xf1\xf2" '' "") rendered = pt() self.failUnless(isinstance(rendered, unicode)) self.failUnlessEqual(rendered.strip(), u"" u"\u0422\u0435\u0441\u0442" u"") def test_suite(): return unittest.makeSuite(TypeSniffingTestCase) if __name__ == "__main__": unittest.main(defaultTest="test_suite") zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/0000755000175000017500000000000012214017603025574 5ustar arnauarnauzope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/checkpathnothing.html0000644000175000017500000000014712214017603032005 0ustar arnauarnau Hello World! zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/checknotexpression.html0000644000175000017500000000042612214017603032402 0ustar arnauarnau
not:python:0
not:python:1
not: python:1
not:python:range(1,20)
zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/checkwithxmlheader.html0000644000175000017500000000012112214017603032317 0ustar arnauarnau zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/dtml1.html0000644000175000017500000000124212214017603027502 0ustar arnauarnau Test of documentation templates blah
The arguments to this test program were:
  • Argument number 99 is default

No arguments were given.

And thats da trooth. zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/loop1.html0000644000175000017500000000051712214017603027517 0ustar arnauarnau Loop doc

Choose your type:

zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/teeshoplaf.html0000644000175000017500000000476112214017603030624 0ustar arnauarnau Zope Stuff

apparel mugs toys misc



This is the tee for those who LOVE Zope. Show your heart on your tee.



Copyright © 2000 4AM Productions, Inc.. All rights reserved.
Questions or problems should be directed to the webmaster, 254-412-0846.
zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/checkpathalt.html0000644000175000017500000000073112214017603031116 0ustar arnauarnau

1

2

3

4

5

Z

Z

Z

Z

zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/teeshop2.html0000644000175000017500000000014212214017603030210 0ustar arnauarnau
Body
zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/__init__.py0000644000175000017500000000007512214017603027707 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/teeshop1.html0000644000175000017500000000665712214017603030230 0ustar arnauarnau Zope Stuff

apparel mugs toys misc


Description: This is the tee for those who LOVE Zope. Show your heart on your tee.

Price:12.99

 

Copyright © 2000 4AM Productions, Inc.. All rights reserved.
Questions or problems should be directed to the webmaster, 254-412-0846.

 

zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/globalsshadowlocals.html0000644000175000017500000000051612214017603032513 0ustar arnauarnau
Should be 2 here!
Should be 1 here!
Should be 3 here!
zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/dtml3.html0000644000175000017500000000243612214017603027512 0ustar arnauarnauTest of documentation templates
The arguments were: (previous start item-previous end item)
??.
Argument 99 was ??
(next start item-next end item)

No arguments were given.

And I am 100% sure! zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/checknothing.html0000644000175000017500000000014212214017603031123 0ustar arnauarnau Hello World! zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/translation.html0000644000175000017500000000017412214017603031022 0ustar arnauarnau

Define and translate message id in ZPT

Insert Message object here

zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/input/stringexpression.html0000644000175000017500000000016312214017603032110 0ustar arnauarnau This is the title zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/testpackage/0000755000175000017500000000000012214017603026730 5ustar arnauarnauzope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/testpackage/view.pt0000644000175000017500000000005312214017603030245 0ustar arnauarnau zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/testpackage/content.py0000644000175000017500000000160212214017603030753 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Content """ class Content(object): def getSomething(self): return 42 class PTComponent(object): def __init__(self, content, request): self.content = content index = PageTemplateFile("view.pt", engine_name="unrestricted") zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/testpackage/__init__.py0000644000175000017500000000007512214017603031043 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/batch.py0000644000175000017500000000725112214017603026075 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Batching support tests """ class batch(object): """Create a sequence batch""" def __init__(self, sequence, size, start=0, end=0, orphan=3, overlap=0): start=start+1 start,end,sz=opt(start,end,size,orphan,sequence) self._last=end-1 self._first=start-1 self._sequence=sequence self._size=size self._start=start self._end=end self._orphan=orphan self._overlap=overlap def previous_sequence(self): return self._first def previous_sequence_end_number(self): start,end,spam=opt(0, self._start-1+self._overlap, self._size, self._orphan, self._sequence) return end def previous_sequence_start_number(self): start,end,spam=opt(0, self._start-1+self._overlap, self._size, self._orphan, self._sequence) return start def previous_sequence_end_item(self): start,end,spam=opt(0, self._start-1+self._overlap, self._size, self._orphan, self._sequence) return self._sequence[end-1] def previous_sequence_start_item(self): start,end,spam=opt(0, self._start-1+self._overlap, self._size, self._orphan, self._sequence) return self._sequence[start-1] def next_sequence_end_number(self): start,end,spam=opt(self._end+1-self._overlap, 0, self._size, self._orphan, self._sequence) return end def next_sequence_start_number(self): start,end,spam=opt(self._end+1-self._overlap, 0, self._size, self._orphan, self._sequence) return start def next_sequence_end_item(self): start,end,spam=opt(self._end+1-self._overlap, 0, self._size, self._orphan, self._sequence) return self._sequence[end-1] def next_sequence_start_item(self): start,end,spam=opt(self._end+1-self._overlap, 0, self._size, self._orphan, self._sequence) return self._sequence[start-1] def next_sequence(self): try: self._sequence[self._end] except IndexError: return 0 else: return 1 def __getitem__(self, index): if index > self._last: raise IndexError(index) return self._sequence[index+self._first] def opt(start,end,size,orphan,sequence): if size < 1: if start > 0 and end > 0 and end >= start: size=end+1-start else: size=7 if start > 0: try: sequence[start-1] except: start=len(sequence) if end > 0: if end < start: end=start else: end=start+size-1 try: sequence[end+orphan-1] except: end=len(sequence) elif end > 0: try: sequence[end-1] except: end=len(sequence) start=end+1-size if start - 1 < orphan: start=1 else: start=1 end=start+size-1 try: sequence[end+orphan-1] except: end=len(sequence) return start,end,size zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/trusted.py0000644000175000017500000000143112214017603026500 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample of a module imported by a trusted module. This module won't be imported by an untrusted template using a path:modules/... expression. """ x = 42 zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/__init__.py0000644000175000017500000000007512214017603026550 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/util.py0000644000175000017500000000576312214017603025777 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Utilities """ import os import re import sys class Bruce(object): __allow_access_to_unprotected_subobjects__=1 def __str__(self): return 'bruce' def __int__(self): return 42 def __float__(self): return 42.0 def keys(self): return ['bruce']*7 def values(self): return [self]*7 def items(self): return [('bruce',self)]*7 def __len__(self): return 7 def __getitem__(self,index): if ininstance(index, int) and (index < 0 or index > 6): raise IndexError(index) return self isDocTemp = 0 def __getattr__(self,name): if name.startswith('_'): raise AttributeError(name) return self bruce = Bruce() class arg(object): __allow_access_to_unprotected_subobjects__ = 1 def __init__(self,nn,aa): self.num, self.arg = nn, aa def __str__(self): return str(self.arg) class argv(object): __allow_access_to_unprotected_subobjects__ = 1 def __init__(self, argv=sys.argv[1:]): args = self.args = [] for aa in argv: args.append(arg(len(args)+1,aa)) def items(self): return map(lambda a: ('spam%d' % a.num, a), self.args) def values(self): return self.args def getRoot(self): return self context = property(lambda self: self) def nicerange(lo, hi): if hi <= lo+1: return str(lo+1) else: return "%d,%d" % (lo+1, hi) def dump(tag, x, lo, hi): for i in xrange(lo, hi): print '%s %s' % (tag, x[i]), def check_html(s1, s2): s1 = normalize_html(s1) s2 = normalize_html(s2) assert s1==s2, (s1, s2, "HTML Output Changed") def check_xml(s1, s2): s1 = normalize_xml(s1) s2 = normalize_xml(s2) assert s1==s2, ("XML Output Changed:\n%r\n\n%r" % (s1, s2)) def normalize_html(s): s = re.sub(r"[ \t]+", " ", s) s = re.sub(r"/>", ">", s) return s def normalize_xml(s): s = re.sub(r"\s+", " ", s) s = re.sub(r"(?s)\s+<", "<", s) s = re.sub(r"(?s)>\s+", ">", s) return s import zope.pagetemplate.tests dir = os.path.dirname(zope.pagetemplate.tests.__file__) input_dir = os.path.join(dir, 'input') output_dir = os.path.join(dir, 'output') def read_input(filename): filename = os.path.join(input_dir, filename) return open(filename, 'r').read() def read_output(filename): filename = os.path.join(output_dir, filename) return open(filename, 'r').read() zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/test_basictemplate.py0000644000175000017500000001342212214017603030665 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Basic Page Template tests """ import unittest from zope.pagetemplate.tests import util import zope.pagetemplate.pagetemplate class BasicTemplateTests(unittest.TestCase): def setUp(self): self.t = zope.pagetemplate.pagetemplate.PageTemplate() def test_if_in_var(self): # DTML test 1: if, in, and var: pass # for unittest """ %(comment)[ blah %(comment)] Test of documentation templates %(if args)[
The arguments to this test program were:

    %(in args)[
  • Argument number %(num)d was %(arg)s %(in args)]

%(if args)] %(else args)[ No arguments were given.

%(else args)] And thats da trooth. """ tal = util.read_input('dtml1.html') self.t.write(tal) aa = util.argv(('one', 'two', 'three', 'cha', 'cha', 'cha')) o = self.t(content=aa) expect = util.read_output('dtml1a.html') util.check_xml(expect, o) aa = util.argv(()) o = self.t(content=aa) expect = util.read_output('dtml1b.html') util.check_xml(expect, o) def test_pt_runtime_error(self): self.t.write("xyz") try: self.t.pt_render({}) except zope.pagetemplate.pagetemplate.PTRuntimeError, e: self.assertEquals( str(e), "['Compilation failed', 'zope.tal.taldefs.TALError:" " TAL attributes on require explicit" " , at line 1, column 1']") else: self.fail("expected PTRuntimeError") def test_batches_and_formatting(self): # DTML test 3: batches and formatting: pass # for unittest """ Test of documentation templates The arguments were: (- )

.
Argument was
(- )
No arguments were given.

And I\'m 100% sure! """ tal = util.read_input('dtml3.html') self.t.write(tal) aa = util.argv(('one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', 'twenty', )) from zope.pagetemplate.tests import batch o = self.t(content=aa, batch=batch.batch(aa.args, 5)) expect = util.read_output('dtml3.html') util.check_xml(expect, o) def test_on_error_in_slot_filler(self): # The `here` isn't defined, so the macro definition is # expected to catch the error that gets raised. text = '''\

cool

''' self.t.write(text) self.t() def test_on_error_in_slot_default(self): # The `here` isn't defined, so the macro definition is # expected to catch the error that gets raised. text = '''\
''' self.t.write(text) self.t() def test_unicode_html(self): text = u'

\xe4\xf6\xfc\xdf

' # test with HTML parser self.t.pt_edit(text, 'text/html') self.assertEquals(self.t().strip(), text) # test with XML parser self.t.pt_edit(text, 'text/xml') self.assertEquals(self.t().strip(), text) def test_suite(): return unittest.makeSuite(BasicTemplateTests) if __name__ == '__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/0000755000175000017500000000000012214017603025775 5ustar arnauarnauzope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/checkpathnothing.html0000644000175000017500000000010012214017603032173 0ustar arnauarnau zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/checknotexpression.html0000644000175000017500000000010712214017603032577 0ustar arnauarnau
not:python:0
zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/dtml1b.html0000644000175000017500000000024712214017603030051 0ustar arnauarnau Test of documentation templates

No arguments were given.

And thats da trooth. zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/checkwithxmlheader.html0000644000175000017500000000007212214017603032525 0ustar arnauarnau Hello! zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/loop1.html0000644000175000017500000000043612214017603027720 0ustar arnauarnau Loop doc

Choose your type:

zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/teeshoplaf.html0000644000175000017500000000467612214017603031032 0ustar arnauarnau Zope Stuff

apparel mugs toys misc



This is the tee for those who LOVE Zope. Show your heart on your tee.



Copyright © 2000 4AM Productions, Inc.. All rights reserved.
Questions or problems should be directed to the webmaster, 254-412-0846.
zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/checkpathalt.html0000644000175000017500000000025712214017603031322 0ustar arnauarnau

X

X

X

X

X

Z

Z

Z

c

zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/teeshop2.html0000644000175000017500000000313212214017603030413 0ustar arnauarnau Zope Stuff

apparel mugs toys misc


Body


Copyright © 2000 4AM Productions, Inc.. All rights reserved.
Questions or problems should be directed to the webmaster, 254-412-0846.
zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/__init__.py0000644000175000017500000000007512214017603030110 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/teeshop1.html0000644000175000017500000000771512214017603030425 0ustar arnauarnau Zope Stuff

apparel mugs toys misc


Description: This is the tee for those who LOVE Zope. Show your heart on your tee.

Price:12.99
Description: This is the tee for Jim Fulton. He's the Zope Pope!

Price:11.99


Copyright © 2000 4AM Productions, Inc.. All rights reserved.
Questions or problems should be directed to the webmaster, 254-412-0846.
././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/globalsshadowlocals.htmlzope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/globalsshadowlocals.htm0000644000175000017500000000023012214017603032531 0ustar arnauarnau
2
1
3
zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/dtml3.html0000644000175000017500000000141012214017603027702 0ustar arnauarnauTest of documentation templates
The arguments were:
one.
Argument 1 was one
two.
Argument 2 was two
three.
Argument 3 was three
four.
Argument 4 was four
five.
Argument 5 was five
(six-ten)
And I am 100% sure! zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/checknothing.html0000644000175000017500000000010012214017603031316 0ustar arnauarnau zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/translation.html0000644000175000017500000000010512214017603031215 0ustar arnauarnau

Define and translate message id in ZPT

Translate this!

zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/dtml1a.html0000644000175000017500000000134512214017603030050 0ustar arnauarnau Test of documentation templates
The arguments to this test program were:
  • Argument number 1 is one
  • Argument number 2 is two
  • Argument number 3 is three
  • Argument number 4 is cha
  • Argument number 5 is cha
  • Argument number 6 is cha
And thats da trooth. zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/output/stringexpression.html0000644000175000017500000000011412214017603032305 0ustar arnauarnau Hello World! zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/tests/test_htmltests.py0000644000175000017500000001076312214017603030104 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Page Template HTML Tests """ import unittest from zope.pagetemplate.tests import util from zope.pagetemplate.pagetemplate import PageTemplate class Folder(object): context = property(lambda self: self) class HTMLTests(unittest.TestCase): def setUp(self): self.folder = f = Folder() f.laf = PageTemplate() f.t = PageTemplate() def getProducts(self): return [ {'description': 'This is the tee for those who LOVE Zope. ' 'Show your heart on your tee.', 'price': 12.99, 'image': 'smlatee.jpg' }, {'description': 'This is the tee for Jim Fulton. ' 'He\'s the Zope Pope!', 'price': 11.99, 'image': 'smpztee.jpg' }, ] def test_1(self): laf = self.folder.laf laf.write(util.read_input('teeshoplaf.html')) expect = util.read_output('teeshoplaf.html') util.check_html(expect, laf()) def test_2(self): self.folder.laf.write(util.read_input('teeshoplaf.html')) t = self.folder.t t.write(util.read_input('teeshop2.html')) expect = util.read_output('teeshop2.html') out = t(laf = self.folder.laf, getProducts = self.getProducts) util.check_html(expect, out) def test_3(self): self.folder.laf.write(util.read_input('teeshoplaf.html')) t = self.folder.t t.write(util.read_input('teeshop1.html')) expect = util.read_output('teeshop1.html') out = t(laf = self.folder.laf, getProducts = self.getProducts) util.check_html(expect, out) def test_SimpleLoop(self): t = self.folder.t t.write(util.read_input('loop1.html')) expect = util.read_output('loop1.html') out = t() util.check_html(expect, out) def test_GlobalsShadowLocals(self): t = self.folder.t t.write(util.read_input('globalsshadowlocals.html')) expect = util.read_output('globalsshadowlocals.html') out = t() util.check_html(expect, out) def test_StringExpressions(self): t = self.folder.t t.write(util.read_input('stringexpression.html')) expect = util.read_output('stringexpression.html') out = t() util.check_html(expect, out) def test_ReplaceWithNothing(self): t = self.folder.t t.write(util.read_input('checknothing.html')) expect = util.read_output('checknothing.html') out = t() util.check_html(expect, out) def test_WithXMLHeader(self): t = self.folder.t t.write(util.read_input('checkwithxmlheader.html')) expect = util.read_output('checkwithxmlheader.html') out = t() util.check_html(expect, out) def test_NotExpression(self): t = self.folder.t t.write(util.read_input('checknotexpression.html')) expect = util.read_output('checknotexpression.html') out = t() util.check_html(expect, out) def test_PathNothing(self): t = self.folder.t t.write(util.read_input('checkpathnothing.html')) expect = util.read_output('checkpathnothing.html') out = t() util.check_html(expect, out) def test_PathAlt(self): t = self.folder.t t.write(util.read_input('checkpathalt.html')) expect = util.read_output('checkpathalt.html') out = t() util.check_html(expect, out) def test_translation(self): from zope.i18nmessageid import MessageFactory _ = MessageFactory('pttest') msg = _("Translate this!") t = self.folder.t t.write(util.read_input('translation.html')) expect = util.read_output('translation.html') out = t(msg=msg) util.check_html(expect, out) def test_suite(): return unittest.makeSuite(HTMLTests) if __name__=='__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/zope.pagetemplate/src/zope/pagetemplate/pagetemplatefile.py0000644000175000017500000001031212214017603027152 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Filesystem Page Template module Zope object encapsulating a Page Template from the filesystem. """ __all__ = ("PageTemplateFile",) import os import sys import re import logging from zope.pagetemplate.pagetemplate import PageTemplate DEFAULT_ENCODING = "utf-8" meta_pattern = re.compile( r'\s*\s*', re.IGNORECASE) def package_home(gdict): filename = gdict["__file__"] return os.path.dirname(filename) class PageTemplateFile(PageTemplate): "Zope wrapper for filesystem Page Template using TAL, TALES, and METAL" _v_last_read = 0 def __init__(self, filename, _prefix=None): path = self.get_path_from_prefix(_prefix) self.filename = os.path.join(path, filename) if not os.path.isfile(self.filename): raise ValueError("No such file", self.filename) def get_path_from_prefix(self, _prefix): if isinstance(_prefix, str): path = _prefix else: if _prefix is None: _prefix = sys._getframe(2).f_globals path = package_home(_prefix) return path def _prepare_html(self, text): match = meta_pattern.search(text) if match is not None: type_, encoding = match.groups() # TODO: Shouldn't / stripping # be in PageTemplate.__call__()? text = meta_pattern.sub("", text) else: type_ = None encoding = DEFAULT_ENCODING return unicode(text, encoding), type_ def _read_file(self): __traceback_info__ = self.filename f = open(self.filename, "rb") try: text = f.read(XML_PREFIX_MAX_LENGTH) except: f.close() raise type_ = sniff_type(text) if type_ == "text/xml": text += f.read() else: # For HTML, we really want the file read in text mode: f.close() f = open(self.filename) text = f.read() text, type_ = self._prepare_html(text) f.close() return text, type_ def _cook_check(self): if self._v_last_read and not __debug__: return __traceback_info__ = self.filename try: mtime = os.path.getmtime(self.filename) except OSError: mtime = 0 if self._v_program is not None and mtime == self._v_last_read: return text, type_ = self._read_file() self.pt_edit(text, type_) self._cook() if self._v_errors: logging.error('PageTemplateFile: Error in template %s: %s', self.filename, '\n'.join(self._v_errors)) return self._v_last_read = mtime def pt_source_file(self): return self.filename def __getstate__(self): raise TypeError("non-picklable object") XML_PREFIXES = [ " 23 Apr 2001 eg Sort(sequence, (("akey", "nocase"), ("anotherkey", "cmp", "desc"))) $Id: ssort.py 67722 2006-04-28 15:10:48Z philikon $ """ from types import TupleType def sort(sequence, sort=(), _=None, mapping=0): """ - sequence is a sequence of objects to be sorted - sort is a sequence of tuples (key,func,direction) that define the sort order: - key is the name of an attribute to sort the objects by - func is the name of a comparison function. This parameter is optional allowed values: - "cmp" -- the standard comparison function (default) - "nocase" -- case-insensitive comparison - "strcoll" or "locale" -- locale-aware string comparison - "strcoll_nocase" or "locale_nocase" -- locale-aware case-insensitive string comparison - "xxx" -- a user-defined comparison function - direction -- defines the sort direction for the key (optional). (allowed values: "asc" (default) , "desc") """ need_sortfunc = 0 if sort: for s in sort: if len(s) > 1: # extended sort if there is reference to... # ...comparison function or sort order, even if they are "cmp" and "asc" need_sortfunc = 1 break sortfields = sort # multi sort = key1,key2 multsort = len(sortfields) > 1 # flag: is multiple sort if need_sortfunc: # prepare the list of functions and sort order multipliers sf_list = make_sortfunctions(sortfields, _) # clean the mess a bit if multsort: # More than one sort key. sortfields = map(lambda x: x[0], sf_list) else: sort = sf_list[0][0] elif sort: if multsort: # More than one sort key. sortfields = map(lambda x: x[0], sort) else: sort = sort[0][0] isort=not sort s=[] for client in sequence: k = None if type(client)==TupleType and len(client)==2: if isort: k=client[0] v=client[1] else: if isort: k=client v=client if sort: if multsort: # More than one sort key. k = [] for sk in sortfields: try: if mapping: akey = v[sk] else: akey = getattr(v, sk) except (AttributeError, KeyError): akey = None if not basic_type(type(akey)): try: akey = akey() except: pass k.append(akey) else: # One sort key. try: if mapping: k = v[sort] else: k = getattr(v, sort) except (AttributeError, KeyError): k = None if not basic_type(type(k)): try: k = k() except: pass s.append((k,client)) if need_sortfunc: by = SortBy(multsort, sf_list) s.sort(by) else: s.sort() sequence=[] for k, client in s: sequence.append(client) return sequence SortEx = sort basic_type={type(''): 1, type(0): 1, type(0.0): 1, type(()): 1, type([]): 1, type(None) : 1 }.has_key def nocase(str1, str2): return cmp(str1.lower(), str2.lower()) import sys if sys.modules.has_key("locale"): # only if locale is already imported from locale import strcoll def strcoll_nocase(str1, str2): return strcoll(str1.lower(), str2.lower()) def make_sortfunctions(sortfields, _): """Accepts a list of sort fields; splits every field, finds comparison function. Returns a list of 3-tuples (field, cmp_function, asc_multplier)""" sf_list = [] for field in sortfields: f = list(field) l = len(f) if l == 1: f.append("cmp") f.append("asc") elif l == 2: f.append("asc") elif l == 3: pass else: raise SyntaxError, "sort option must contains no more than 2 fields" f_name = f[1] # predefined function? if f_name == "cmp": func = cmp # builtin elif f_name == "nocase": func = nocase elif f_name in ("locale", "strcoll"): func = strcoll elif f_name in ("locale_nocase", "strcoll_nocase"): func = strcoll_nocase else: # no - look it up in the namespace if hasattr(_, 'getitem'): # support for zope.documenttemplate.dt_util.TemplateDict func = _.getitem(f_name, 0) else: func = _[f_name] sort_order = f[2].lower() if sort_order == "asc": multiplier = +1 elif sort_order == "desc": multiplier = -1 else: raise SyntaxError, "sort direction must be either ASC or DESC" sf_list.append((f[0], func, multiplier)) return sf_list class SortBy: def __init__(self, multsort, sf_list): self.multsort = multsort self.sf_list = sf_list def __call__(self, o1, o2): multsort = self.multsort if multsort: o1 = o1[0] # if multsort - take the first element (key list) o2 = o2[0] sf_list = self.sf_list l = len(sf_list) # assert that o1 and o2 are tuples of apropriate length assert len(o1) == l + 1 - multsort, "%s, %d" % (o1, l + multsort) assert len(o2) == l + 1 - multsort, "%s, %d" % (o2, l + multsort) # now run through the list of functions in sf_list and # compare every object in o1 and o2 for i in range(l): # if multsort - we already extracted the key list # if not multsort - i is 0, and the 0th element is the key c1, c2 = o1[i], o2[i] func, multiplier = sf_list[i][1:3] n = func(c1, c2) if n: return n*multiplier # all functions returned 0 - identical sequences return 0 zope2.13-2.13.21/source/zope.sequencesort/src/zope/sequencesort/__init__.py0000644000175000017500000000124412214017631025526 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from zope.sequencesort.ssort import sort zope2.13-2.13.21/source/zope.sequencesort/src/zope/sequencesort/tests/0000755000175000017500000000000012214017631024556 5ustar arnauarnauzope2.13-2.13.21/source/zope.sequencesort/src/zope/sequencesort/tests/testssort.py0000644000175000017500000000411612214017631027204 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import os, sys, unittest from zope.sequencesort import ssort from zope.sequencesort.tests.ztestlib import * from zope.sequencesort.tests.results import * class TestCase(unittest.TestCase): """Test zope.sequencesort.sort() """ def test1(self): assert res1==ssort.sort(wordlist) def test2(self): assert res2==ssort.sort(wordlist, (("key",),), mapping=1) def test3(self): assert res3==ssort.sort(wordlist, (("key", "cmp"),), mapping=1) def test4(self): assert res4==ssort.sort(wordlist, (("key", "cmp", "desc"),), mapping=1) def test5(self): assert res5==ssort.sort(wordlist, (("weight",), ("key",)), mapping=1) def test6(self): assert res6==ssort.sort(wordlist, (("weight",), ("key", "nocase", "desc")), mapping=1) def test7(self): def myCmp(s1, s2): return -cmp(s1, s2) md = {"myCmp" : myCmp} assert res7==ssort.sort(wordlist, (("weight",), ("key", "myCmp", "desc")), md, mapping=1 ) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.sequencesort/src/zope/sequencesort/tests/results.py0000644000175000017500000001760712214017631026644 0ustar arnauarnaures1=[{'weight': 1, 'word': 'AAA', 'key': 'aaa'}, {'weight': 0, 'word': 'BBB', 'key': 'bbb'}, {'weight': 0, 'word': 'CCC', 'key': 'ccc'}, {'weight': 0, 'word': 'DDD', 'key': 'ddd'}, {'weight': 1, 'word': 'EEE', 'key': 'eee'}, {'weight': 0, 'word': 'FFF', 'key': 'fff'}, {'weight': 0, 'word': 'GGG', 'key': 'ggg'}, {'weight': 0, 'word': 'HHH', 'key': 'hhh'}, {'weight': 1, 'word': 'III', 'key': 'iii'}, {'weight': -1, 'word': 'JJJ', 'key': 'jjj'}, {'weight': 0, 'word': 'KKK', 'key': 'kkk'}, {'weight': 0, 'word': 'LLL', 'key': 'lll'}, {'weight': 0, 'word': 'MMM', 'key': 'mmm'}, {'weight': 0, 'word': 'NNN', 'key': 'nnn'}, {'weight': 1, 'word': 'OOO', 'key': 'ooo'}, {'weight': 0, 'word': 'PPP', 'key': 'ppp'}, {'weight': -1, 'word': 'QQQ', 'key': 'qqq'}, {'weight': 0, 'word': 'RRR', 'key': 'rrr'}, {'weight': 0, 'word': 'SSS', 'key': 'sss'}, {'weight': 0, 'word': 'TTT', 'key': 'ttt'}, {'weight': 1, 'word': 'UUU', 'key': 'uuu'}, {'weight': 0, 'word': 'VVV', 'key': 'vvv'}, {'weight': 0, 'word': 'WWW', 'key': 'www'}, {'weight': 0, 'word': 'XXX', 'key': 'xxx'}, {'weight': -1, 'word': 'YYY', 'key': 'yyy'}, {'weight': 0, 'word': 'ZZZ', 'key': 'zzz'}] res2=[{'weight': 1, 'word': 'AAA', 'key': 'aaa'}, {'weight': 0, 'word': 'BBB', 'key': 'bbb'}, {'weight': 0, 'word': 'CCC', 'key': 'ccc'}, {'weight': 0, 'word': 'DDD', 'key': 'ddd'}, {'weight': 1, 'word': 'EEE', 'key': 'eee'}, {'weight': 0, 'word': 'FFF', 'key': 'fff'}, {'weight': 0, 'word': 'GGG', 'key': 'ggg'}, {'weight': 0, 'word': 'HHH', 'key': 'hhh'}, {'weight': 1, 'word': 'III', 'key': 'iii'}, {'weight': -1, 'word': 'JJJ', 'key': 'jjj'}, {'weight': 0, 'word': 'KKK', 'key': 'kkk'}, {'weight': 0, 'word': 'LLL', 'key': 'lll'}, {'weight': 0, 'word': 'MMM', 'key': 'mmm'}, {'weight': 0, 'word': 'NNN', 'key': 'nnn'}, {'weight': 1, 'word': 'OOO', 'key': 'ooo'}, {'weight': 0, 'word': 'PPP', 'key': 'ppp'}, {'weight': -1, 'word': 'QQQ', 'key': 'qqq'}, {'weight': 0, 'word': 'RRR', 'key': 'rrr'}, {'weight': 0, 'word': 'SSS', 'key': 'sss'}, {'weight': 0, 'word': 'TTT', 'key': 'ttt'}, {'weight': 1, 'word': 'UUU', 'key': 'uuu'}, {'weight': 0, 'word': 'VVV', 'key': 'vvv'}, {'weight': 0, 'word': 'WWW', 'key': 'www'}, {'weight': 0, 'word': 'XXX', 'key': 'xxx'}, {'weight': -1, 'word': 'YYY', 'key': 'yyy'}, {'weight': 0, 'word': 'ZZZ', 'key': 'zzz'}] res3=[{'weight': 1, 'word': 'AAA', 'key': 'aaa'}, {'weight': 0, 'word': 'BBB', 'key': 'bbb'}, {'weight': 0, 'word': 'CCC', 'key': 'ccc'}, {'weight': 0, 'word': 'DDD', 'key': 'ddd'}, {'weight': 1, 'word': 'EEE', 'key': 'eee'}, {'weight': 0, 'word': 'FFF', 'key': 'fff'}, {'weight': 0, 'word': 'GGG', 'key': 'ggg'}, {'weight': 0, 'word': 'HHH', 'key': 'hhh'}, {'weight': 1, 'word': 'III', 'key': 'iii'}, {'weight': -1, 'word': 'JJJ', 'key': 'jjj'}, {'weight': 0, 'word': 'KKK', 'key': 'kkk'}, {'weight': 0, 'word': 'LLL', 'key': 'lll'}, {'weight': 0, 'word': 'MMM', 'key': 'mmm'}, {'weight': 0, 'word': 'NNN', 'key': 'nnn'}, {'weight': 1, 'word': 'OOO', 'key': 'ooo'}, {'weight': 0, 'word': 'PPP', 'key': 'ppp'}, {'weight': -1, 'word': 'QQQ', 'key': 'qqq'}, {'weight': 0, 'word': 'RRR', 'key': 'rrr'}, {'weight': 0, 'word': 'SSS', 'key': 'sss'}, {'weight': 0, 'word': 'TTT', 'key': 'ttt'}, {'weight': 1, 'word': 'UUU', 'key': 'uuu'}, {'weight': 0, 'word': 'VVV', 'key': 'vvv'}, {'weight': 0, 'word': 'WWW', 'key': 'www'}, {'weight': 0, 'word': 'XXX', 'key': 'xxx'}, {'weight': -1, 'word': 'YYY', 'key': 'yyy'}, {'weight': 0, 'word': 'ZZZ', 'key': 'zzz'}] res4=[{'weight': 0, 'word': 'ZZZ', 'key': 'zzz'}, {'weight': -1, 'word': 'YYY', 'key': 'yyy'}, {'weight': 0, 'word': 'XXX', 'key': 'xxx'}, {'weight': 0, 'word': 'WWW', 'key': 'www'}, {'weight': 0, 'word': 'VVV', 'key': 'vvv'}, {'weight': 1, 'word': 'UUU', 'key': 'uuu'}, {'weight': 0, 'word': 'TTT', 'key': 'ttt'}, {'weight': 0, 'word': 'SSS', 'key': 'sss'}, {'weight': 0, 'word': 'RRR', 'key': 'rrr'}, {'weight': -1, 'word': 'QQQ', 'key': 'qqq'}, {'weight': 0, 'word': 'PPP', 'key': 'ppp'}, {'weight': 1, 'word': 'OOO', 'key': 'ooo'}, {'weight': 0, 'word': 'NNN', 'key': 'nnn'}, {'weight': 0, 'word': 'MMM', 'key': 'mmm'}, {'weight': 0, 'word': 'LLL', 'key': 'lll'}, {'weight': 0, 'word': 'KKK', 'key': 'kkk'}, {'weight': -1, 'word': 'JJJ', 'key': 'jjj'}, {'weight': 1, 'word': 'III', 'key': 'iii'}, {'weight': 0, 'word': 'HHH', 'key': 'hhh'}, {'weight': 0, 'word': 'GGG', 'key': 'ggg'}, {'weight': 0, 'word': 'FFF', 'key': 'fff'}, {'weight': 1, 'word': 'EEE', 'key': 'eee'}, {'weight': 0, 'word': 'DDD', 'key': 'ddd'}, {'weight': 0, 'word': 'CCC', 'key': 'ccc'}, {'weight': 0, 'word': 'BBB', 'key': 'bbb'}, {'weight': 1, 'word': 'AAA', 'key': 'aaa'}] res5=[{'weight': -1, 'word': 'JJJ', 'key': 'jjj'}, {'weight': -1, 'word': 'QQQ', 'key': 'qqq'}, {'weight': -1, 'word': 'YYY', 'key': 'yyy'}, {'weight': 0, 'word': 'BBB', 'key': 'bbb'}, {'weight': 0, 'word': 'CCC', 'key': 'ccc'}, {'weight': 0, 'word': 'DDD', 'key': 'ddd'}, {'weight': 0, 'word': 'FFF', 'key': 'fff'}, {'weight': 0, 'word': 'GGG', 'key': 'ggg'}, {'weight': 0, 'word': 'HHH', 'key': 'hhh'}, {'weight': 0, 'word': 'KKK', 'key': 'kkk'}, {'weight': 0, 'word': 'LLL', 'key': 'lll'}, {'weight': 0, 'word': 'MMM', 'key': 'mmm'}, {'weight': 0, 'word': 'NNN', 'key': 'nnn'}, {'weight': 0, 'word': 'PPP', 'key': 'ppp'}, {'weight': 0, 'word': 'RRR', 'key': 'rrr'}, {'weight': 0, 'word': 'SSS', 'key': 'sss'}, {'weight': 0, 'word': 'TTT', 'key': 'ttt'}, {'weight': 0, 'word': 'VVV', 'key': 'vvv'}, {'weight': 0, 'word': 'WWW', 'key': 'www'}, {'weight': 0, 'word': 'XXX', 'key': 'xxx'}, {'weight': 0, 'word': 'ZZZ', 'key': 'zzz'}, {'weight': 1, 'word': 'AAA', 'key': 'aaa'}, {'weight': 1, 'word': 'EEE', 'key': 'eee'}, {'weight': 1, 'word': 'III', 'key': 'iii'}, {'weight': 1, 'word': 'OOO', 'key': 'ooo'}, {'weight': 1, 'word': 'UUU', 'key': 'uuu'}] res6=[{'weight': -1, 'word': 'YYY', 'key': 'yyy'}, {'weight': -1, 'word': 'QQQ', 'key': 'qqq'}, {'weight': -1, 'word': 'JJJ', 'key': 'jjj'}, {'weight': 0, 'word': 'ZZZ', 'key': 'zzz'}, {'weight': 0, 'word': 'XXX', 'key': 'xxx'}, {'weight': 0, 'word': 'WWW', 'key': 'www'}, {'weight': 0, 'word': 'VVV', 'key': 'vvv'}, {'weight': 0, 'word': 'TTT', 'key': 'ttt'}, {'weight': 0, 'word': 'SSS', 'key': 'sss'}, {'weight': 0, 'word': 'RRR', 'key': 'rrr'}, {'weight': 0, 'word': 'PPP', 'key': 'ppp'}, {'weight': 0, 'word': 'NNN', 'key': 'nnn'}, {'weight': 0, 'word': 'MMM', 'key': 'mmm'}, {'weight': 0, 'word': 'LLL', 'key': 'lll'}, {'weight': 0, 'word': 'KKK', 'key': 'kkk'}, {'weight': 0, 'word': 'HHH', 'key': 'hhh'}, {'weight': 0, 'word': 'GGG', 'key': 'ggg'}, {'weight': 0, 'word': 'FFF', 'key': 'fff'}, {'weight': 0, 'word': 'DDD', 'key': 'ddd'}, {'weight': 0, 'word': 'CCC', 'key': 'ccc'}, {'weight': 0, 'word': 'BBB', 'key': 'bbb'}, {'weight': 1, 'word': 'UUU', 'key': 'uuu'}, {'weight': 1, 'word': 'OOO', 'key': 'ooo'}, {'weight': 1, 'word': 'III', 'key': 'iii'}, {'weight': 1, 'word': 'EEE', 'key': 'eee'}, {'weight': 1, 'word': 'AAA', 'key': 'aaa'}] res7=[{'weight': -1, 'word': 'JJJ', 'key': 'jjj'}, {'weight': -1, 'word': 'QQQ', 'key': 'qqq'}, {'weight': -1, 'word': 'YYY', 'key': 'yyy'}, {'weight': 0, 'word': 'BBB', 'key': 'bbb'}, {'weight': 0, 'word': 'CCC', 'key': 'ccc'}, {'weight': 0, 'word': 'DDD', 'key': 'ddd'}, {'weight': 0, 'word': 'FFF', 'key': 'fff'}, {'weight': 0, 'word': 'GGG', 'key': 'ggg'}, {'weight': 0, 'word': 'HHH', 'key': 'hhh'}, {'weight': 0, 'word': 'KKK', 'key': 'kkk'}, {'weight': 0, 'word': 'LLL', 'key': 'lll'}, {'weight': 0, 'word': 'MMM', 'key': 'mmm'}, {'weight': 0, 'word': 'NNN', 'key': 'nnn'}, {'weight': 0, 'word': 'PPP', 'key': 'ppp'}, {'weight': 0, 'word': 'RRR', 'key': 'rrr'}, {'weight': 0, 'word': 'SSS', 'key': 'sss'}, {'weight': 0, 'word': 'TTT', 'key': 'ttt'}, {'weight': 0, 'word': 'VVV', 'key': 'vvv'}, {'weight': 0, 'word': 'WWW', 'key': 'www'}, {'weight': 0, 'word': 'XXX', 'key': 'xxx'}, {'weight': 0, 'word': 'ZZZ', 'key': 'zzz'}, {'weight': 1, 'word': 'AAA', 'key': 'aaa'}, {'weight': 1, 'word': 'EEE', 'key': 'eee'}, {'weight': 1, 'word': 'III', 'key': 'iii'}, {'weight': 1, 'word': 'OOO', 'key': 'ooo'}, {'weight': 1, 'word': 'UUU', 'key': 'uuu'}] zope2.13-2.13.21/source/zope.sequencesort/src/zope/sequencesort/tests/__init__.py0000644000175000017500000000003012214017631026660 0ustar arnauarnau""" Python package. """ zope2.13-2.13.21/source/zope.sequencesort/src/zope/sequencesort/tests/ztestlib.py0000644000175000017500000000350712214017631026775 0ustar arnauarnauclass standard_html: # Base class for using with ZTemplates def __init__(self, title): self.standard_html_header = """ %s """ % title self.standard_html_footer = """ """ self.title = title def test(s): outfile = open("test.out", 'w') outfile.write(s) outfile.close() def exception(): import sys, traceback exc_type, exc_value, exc_tb = sys.exc_info() outfile = open("test.err", 'w') traceback.print_exception(exc_type, exc_value, exc_tb, None, outfile) outfile.close() wordlist = [ {"key": "aaa", "word": "AAA", "weight": 1}, {"key": "bbb", "word": "BBB", "weight": 0}, {"key": "ccc", "word": "CCC", "weight": 0}, {"key": "ddd", "word": "DDD", "weight": 0}, {"key": "eee", "word": "EEE", "weight": 1}, {"key": "fff", "word": "FFF", "weight": 0}, {"key": "ggg", "word": "GGG", "weight": 0}, {"key": "hhh", "word": "HHH", "weight": 0}, {"key": "iii", "word": "III", "weight": 1}, {"key": "jjj", "word": "JJJ", "weight": -1}, {"key": "kkk", "word": "KKK", "weight": 0}, {"key": "lll", "word": "LLL", "weight": 0}, {"key": "mmm", "word": "MMM", "weight": 0}, {"key": "nnn", "word": "NNN", "weight": 0}, {"key": "ooo", "word": "OOO", "weight": 1}, {"key": "ppp", "word": "PPP", "weight": 0}, {"key": "qqq", "word": "QQQ", "weight": -1}, {"key": "rrr", "word": "RRR", "weight": 0}, {"key": "sss", "word": "SSS", "weight": 0}, {"key": "ttt", "word": "TTT", "weight": 0}, {"key": "uuu", "word": "UUU", "weight": 1}, {"key": "vvv", "word": "VVV", "weight": 0}, {"key": "www", "word": "WWW", "weight": 0}, {"key": "xxx", "word": "XXX", "weight": 0}, {"key": "yyy", "word": "YYY", "weight": -1}, {"key": "zzz", "word": "ZZZ", "weight": 0} ] zope2.13-2.13.21/source/zope.sequencesort/src/zope.sequencesort.egg-info/0000755000175000017500000000000012214017631025105 5ustar arnauarnauzope2.13-2.13.21/source/zope.sequencesort/src/zope.sequencesort.egg-info/PKG-INFO0000644000175000017500000000172412214017631026206 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.sequencesort Version: 3.4.0 Summary: Sequence Sorting Home-page: http://cheeseshop.python.org/pypi/zope.sequencesort Author: Zope Corporation and Contributors Author-email: zope3-dev@zope.org License: ZPL 2.1 Description: This package provides a very advanced sequence sorting feature. ======= CHANGES ======= 3.4.0 (2007-10-03) ------------------ - Initial release independent of the main Zope tree. Keywords: zope3 sequence sort Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.sequencesort/src/zope.sequencesort.egg-info/dependency_links.txt0000644000175000017500000000000112214017631031153 0ustar arnauarnau zope2.13-2.13.21/source/zope.sequencesort/src/zope.sequencesort.egg-info/requires.txt0000644000175000017500000000003712214017631027505 0ustar arnauarnausetuptools [test] zope.testingzope2.13-2.13.21/source/zope.sequencesort/src/zope.sequencesort.egg-info/namespace_packages.txt0000644000175000017500000000000512214017631031433 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.sequencesort/src/zope.sequencesort.egg-info/top_level.txt0000644000175000017500000000000512214017631027632 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.sequencesort/src/zope.sequencesort.egg-info/SOURCES.txt0000644000175000017500000000116212214017631026771 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.sequencesort.egg-info/PKG-INFO src/zope.sequencesort.egg-info/SOURCES.txt src/zope.sequencesort.egg-info/dependency_links.txt src/zope.sequencesort.egg-info/namespace_packages.txt src/zope.sequencesort.egg-info/not-zip-safe src/zope.sequencesort.egg-info/requires.txt src/zope.sequencesort.egg-info/top_level.txt src/zope/sequencesort/__init__.py src/zope/sequencesort/ssort.py src/zope/sequencesort/tests/__init__.py src/zope/sequencesort/tests/results.py src/zope/sequencesort/tests/testssort.py src/zope/sequencesort/tests/ztestlib.py zope2.13-2.13.21/source/zope.sequencesort/src/zope.sequencesort.egg-info/not-zip-safe0000644000175000017500000000000112214017631027333 0ustar arnauarnau zope2.13-2.13.21/source/Products.ZCatalog/0000755000175000017500000000000012214017452016743 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/setup.py0000644000175000017500000000325412214017452020461 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from setuptools import setup, find_packages setup(name='Products.ZCatalog', version = '2.13.23', url='http://pypi.python.org/pypi/Products.ZCatalog', license='ZPL 2.1', description="Zope 2's indexing and search solution.", author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=open('README.txt').read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), namespace_packages=['Products'], package_dir={'': 'src'}, install_requires=[ 'setuptools', 'AccessControl', 'Acquisition', 'DateTime', 'DocumentTemplate', 'ExtensionClass', 'Missing', 'Persistence', 'Products.ZCTextIndex', 'Record', 'RestrictedPython', 'zExceptions', 'ZODB3', 'Zope2', 'zope.dottedname', 'zope.interface', 'zope.schema', 'zope.testing', ], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/Products.ZCatalog/PKG-INFO0000644000175000017500000002216312214017452020044 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.ZCatalog Version: 2.13.23 Summary: Zope 2's indexing and search solution. Home-page: http://pypi.python.org/pypi/Products.ZCatalog Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== The ZCatalog is Zope 2’s built in search engine. It allows you to categorize and search all kinds of Zope objects. It comes with a variety of indexes for different types of data. Changelog ========= 2.13.23 (2012-04-26) -------------------- - Fixed another issue with preserving score values, when a custom index was queried first which was neither ILimitedResultIndex aware nor return scores, and a later index was of the default ZCTextIndex type. 2.13.22 (2011-11-17) -------------------- - Added a new `load_from_path` class method to the `PriorityMap`, which allows one to load a plan from a file, instead of a module via an environment var. 2.13.21 (2011-10-20) -------------------- - Refactored value index logic. Determine value indexes per catalog instead of globally. Store value index set in the priority map, so it can be seen in the ZMI and stored in the module level storage. - Added support for using ZCatalog as local utility. This feature requires the optional `five.globalrequest` dependency. 2.13.20 (2011-08-23) -------------------- - Fixed incorrect calculation of batches in the second half of the result set in sortResults. 2.13.19 (2011-08-20) -------------------- - Increase plan precision to 4 digits in its string representation. 2.13.18 (2011-07-29) -------------------- - In the string representation of a catalog plan, round the times to at most two digits after the comma. 2.13.17 (2011-07-29) -------------------- - Put back the `weightedIntersection` optimization but guard against results with values and do the appropriate fallback to the weighted version. 2.13.16 (2011-07-24) -------------------- - Restored preserving score values from ZCTextIndex indices. https://bugs.launchpad.net/zope2/+bug/815469 2.13.15 (2011-06-30) -------------------- - Fixed undefined variables in BooleanIndex inline migration code. - Fixed BooleanIndex' items method so the ZMI browse view works. 2.13.14 (2011-05-19) -------------------- - Fixed addition of two LazyCat's if any of them was already flattened. - Extend BooleanIndex by making the indexed value variable instead of hardcoding it to `True`. The indexed value will determine the smaller set automatically and choose its best value. An inline switch is done once the indexed value set grows larger than 60% of the total length. 60% was chosen to avoid constant switching for indexes that have an almost equal distribution of `True/False`. - Substitute catalog entry in UUIDIndex error message. 2.13.13 (2011-05-04) -------------------- - Optimize `Catalog.updateMetadata` avoiding a `self.uids` lookup and removing inline migration code for converting `self.data` from non-IOBTree types. - In the path index, don't update data if the value hasn't changed. 2.13.12 (2011-05-02) -------------------- - Optimize DateRangeIndex for better conflict resolution handling. It always starts out with storing an IITreeSet of the value instead of special casing storing an int for a single value. The `single value as int` optimization should be provided via a separate API to be called periodically outside the context of a normal request. - Replaced `weightedIntersection` and `weightedUnion` calls with their non-weighted version, as we didn't pass in weights. 2.13.11 (2011-05-02) -------------------- - Fix possible TypeError in `sortResults` method if only b_start but not b_size has been provided. - Prevent the new UUIDIndex from acquiring attributes via Acquisition. 2.13.10 (2011-04-21) -------------------- - Handle `TypeErrors` in the KeywordIndex if an indexed attribute is a method with required arguments. - Added reporting of the intersection time of each index' result with the result set of the other indexes and consider this time to be part of each index time for prioritizing the index. - Removed tracking of result length from the query plan. The calculation of the length of an intermediate index result can itself be expensive. 2.13.9 (2011-04-10) ------------------- - Added a floor and ceiling value to the date range index. Values outside the specified range will be interpreted the same way as passing `None`, i.e. `since the beginning of time` and `until the end of it`. This allows the index to apply its optimizations, while objects with values outside this range can still be stored in a normal date index, which omits explicitly passed in `None` values. 2.13.8 (2011-04-01) ------------------- - Fixed bug in date range index, which would omit objects exactly matching the query term if a resultset was provided. - Fixed the BooleanIndex to not index objects without the cataloged attribute. 2.13.7 (2011-02-15) ------------------- - Fixed the `DateIndex._unindex` to be of type `IIBTree` instead of `OIBTree`. It stores document ids as keys, which can only be ints. 2.13.6 (2011-02-10) ------------------- - Remove docstrings from various methods, as they shouldn't be web-publishable. 2.13.5 (2011-02-05) ------------------- - Fixed test failures introduced in 2.13.4. 2.13.4 (2011-02-05) ------------------- - Added a new UUIDIndex, based on the common UnIndex. It behaves like a FieldIndex, but can only store one document id per value, so there's a 1:1 mapping from value to document id. An error is logged if a different document id is indexed for an already taken value. The internal data structures are optimized for this and avoid storing one IITreeSet per value. - Optimize sorting in presence of batching arguments. If a batch from the end of the result set is requested, we internally reverse the sorting order and at the end reverse the lazy sequence again. In a sequence with 100 entries, if we request the batch with items 80 to 90, we now reverse sort 20 items (100 to 80), slice of the first ten items and then reverse them. Before we would had to sort the first 90 items and then slice of the last 10. - If batching arguments are provided, limit the returned lazy sequence to the items in the required batch instead of returning leading items falling outside of the requested batch. - Fixed inline `IISet` to `IITreeSet` conversion code inside DateRangeIndex' `_insertForwardIndexEntry` method. 2.13.3 (2011-01-01) ------------------- - Avoid locale-dependent test condition in `test_length_with_filter`. 2.13.2 (2010-12-31) ------------------- - Preserve `actual_result_count` on flattening nested LazyCat's. - Preserve the `actual_result_count` on all lazy return values. This allows to get proper batching information from catalog results which have been restricted by `sort_limit`. - Made sure `actual_result_count` is available on all lazy classes and falls back to `__len__` if not explicitly provided. - Optimized length calculation of Lazy classes. 2.13.1 (2010-12-25) ------------------- - Added automatic sorting limit calculation based on batch arguments. If the query contains a `b_start` and `b_size` argument and no explicit `sort_limit` is provided, the sort limit will be calculated as `b_start + b_size`. - Avoid pre-allocation of marker items in `LazyMap`. 2.13.0 (2010-12-25) ------------------- - Fix `LazyMap` to avoid unnecessary function calls. - Released as separate distribution. Platform: UNKNOWN zope2.13-2.13.21/source/Products.ZCatalog/pip-egg-info/0000755000175000017500000000000012214017453021225 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/pip-egg-info/Products.ZCatalog.egg-info/0000755000175000017500000000000012214017453026225 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/pip-egg-info/Products.ZCatalog.egg-info/PKG-INFO0000644000175000017500000002216312214017453027326 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.ZCatalog Version: 2.13.23 Summary: Zope 2's indexing and search solution. Home-page: http://pypi.python.org/pypi/Products.ZCatalog Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== The ZCatalog is Zope 2’s built in search engine. It allows you to categorize and search all kinds of Zope objects. It comes with a variety of indexes for different types of data. Changelog ========= 2.13.23 (2012-04-26) -------------------- - Fixed another issue with preserving score values, when a custom index was queried first which was neither ILimitedResultIndex aware nor return scores, and a later index was of the default ZCTextIndex type. 2.13.22 (2011-11-17) -------------------- - Added a new `load_from_path` class method to the `PriorityMap`, which allows one to load a plan from a file, instead of a module via an environment var. 2.13.21 (2011-10-20) -------------------- - Refactored value index logic. Determine value indexes per catalog instead of globally. Store value index set in the priority map, so it can be seen in the ZMI and stored in the module level storage. - Added support for using ZCatalog as local utility. This feature requires the optional `five.globalrequest` dependency. 2.13.20 (2011-08-23) -------------------- - Fixed incorrect calculation of batches in the second half of the result set in sortResults. 2.13.19 (2011-08-20) -------------------- - Increase plan precision to 4 digits in its string representation. 2.13.18 (2011-07-29) -------------------- - In the string representation of a catalog plan, round the times to at most two digits after the comma. 2.13.17 (2011-07-29) -------------------- - Put back the `weightedIntersection` optimization but guard against results with values and do the appropriate fallback to the weighted version. 2.13.16 (2011-07-24) -------------------- - Restored preserving score values from ZCTextIndex indices. https://bugs.launchpad.net/zope2/+bug/815469 2.13.15 (2011-06-30) -------------------- - Fixed undefined variables in BooleanIndex inline migration code. - Fixed BooleanIndex' items method so the ZMI browse view works. 2.13.14 (2011-05-19) -------------------- - Fixed addition of two LazyCat's if any of them was already flattened. - Extend BooleanIndex by making the indexed value variable instead of hardcoding it to `True`. The indexed value will determine the smaller set automatically and choose its best value. An inline switch is done once the indexed value set grows larger than 60% of the total length. 60% was chosen to avoid constant switching for indexes that have an almost equal distribution of `True/False`. - Substitute catalog entry in UUIDIndex error message. 2.13.13 (2011-05-04) -------------------- - Optimize `Catalog.updateMetadata` avoiding a `self.uids` lookup and removing inline migration code for converting `self.data` from non-IOBTree types. - In the path index, don't update data if the value hasn't changed. 2.13.12 (2011-05-02) -------------------- - Optimize DateRangeIndex for better conflict resolution handling. It always starts out with storing an IITreeSet of the value instead of special casing storing an int for a single value. The `single value as int` optimization should be provided via a separate API to be called periodically outside the context of a normal request. - Replaced `weightedIntersection` and `weightedUnion` calls with their non-weighted version, as we didn't pass in weights. 2.13.11 (2011-05-02) -------------------- - Fix possible TypeError in `sortResults` method if only b_start but not b_size has been provided. - Prevent the new UUIDIndex from acquiring attributes via Acquisition. 2.13.10 (2011-04-21) -------------------- - Handle `TypeErrors` in the KeywordIndex if an indexed attribute is a method with required arguments. - Added reporting of the intersection time of each index' result with the result set of the other indexes and consider this time to be part of each index time for prioritizing the index. - Removed tracking of result length from the query plan. The calculation of the length of an intermediate index result can itself be expensive. 2.13.9 (2011-04-10) ------------------- - Added a floor and ceiling value to the date range index. Values outside the specified range will be interpreted the same way as passing `None`, i.e. `since the beginning of time` and `until the end of it`. This allows the index to apply its optimizations, while objects with values outside this range can still be stored in a normal date index, which omits explicitly passed in `None` values. 2.13.8 (2011-04-01) ------------------- - Fixed bug in date range index, which would omit objects exactly matching the query term if a resultset was provided. - Fixed the BooleanIndex to not index objects without the cataloged attribute. 2.13.7 (2011-02-15) ------------------- - Fixed the `DateIndex._unindex` to be of type `IIBTree` instead of `OIBTree`. It stores document ids as keys, which can only be ints. 2.13.6 (2011-02-10) ------------------- - Remove docstrings from various methods, as they shouldn't be web-publishable. 2.13.5 (2011-02-05) ------------------- - Fixed test failures introduced in 2.13.4. 2.13.4 (2011-02-05) ------------------- - Added a new UUIDIndex, based on the common UnIndex. It behaves like a FieldIndex, but can only store one document id per value, so there's a 1:1 mapping from value to document id. An error is logged if a different document id is indexed for an already taken value. The internal data structures are optimized for this and avoid storing one IITreeSet per value. - Optimize sorting in presence of batching arguments. If a batch from the end of the result set is requested, we internally reverse the sorting order and at the end reverse the lazy sequence again. In a sequence with 100 entries, if we request the batch with items 80 to 90, we now reverse sort 20 items (100 to 80), slice of the first ten items and then reverse them. Before we would had to sort the first 90 items and then slice of the last 10. - If batching arguments are provided, limit the returned lazy sequence to the items in the required batch instead of returning leading items falling outside of the requested batch. - Fixed inline `IISet` to `IITreeSet` conversion code inside DateRangeIndex' `_insertForwardIndexEntry` method. 2.13.3 (2011-01-01) ------------------- - Avoid locale-dependent test condition in `test_length_with_filter`. 2.13.2 (2010-12-31) ------------------- - Preserve `actual_result_count` on flattening nested LazyCat's. - Preserve the `actual_result_count` on all lazy return values. This allows to get proper batching information from catalog results which have been restricted by `sort_limit`. - Made sure `actual_result_count` is available on all lazy classes and falls back to `__len__` if not explicitly provided. - Optimized length calculation of Lazy classes. 2.13.1 (2010-12-25) ------------------- - Added automatic sorting limit calculation based on batch arguments. If the query contains a `b_start` and `b_size` argument and no explicit `sort_limit` is provided, the sort limit will be calculated as `b_start + b_size`. - Avoid pre-allocation of marker items in `LazyMap`. 2.13.0 (2010-12-25) ------------------- - Fix `LazyMap` to avoid unnecessary function calls. - Released as separate distribution. Platform: UNKNOWN ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/pip-egg-info/Products.ZCatalog.egg-info/dependency_links.txtzope2.13-2.13.21/source/Products.ZCatalog/pip-egg-info/Products.ZCatalog.egg-info/dependency_links.t0000644000175000017500000000000112214017453031717 0ustar arnauarnau zope2.13-2.13.21/source/Products.ZCatalog/pip-egg-info/Products.ZCatalog.egg-info/requires.txt0000644000175000017500000000033612214017453030627 0ustar arnauarnausetuptools AccessControl Acquisition DateTime DocumentTemplate ExtensionClass Missing Persistence Products.ZCTextIndex Record RestrictedPython zExceptions ZODB3 Zope2 zope.dottedname zope.interface zope.schema zope.testing././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/pip-egg-info/Products.ZCatalog.egg-info/namespace_packages.txtzope2.13-2.13.21/source/Products.ZCatalog/pip-egg-info/Products.ZCatalog.egg-info/namespace_packages0000644000175000017500000000001112214017453031732 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.ZCatalog/pip-egg-info/Products.ZCatalog.egg-info/top_level.txt0000644000175000017500000000001112214017453030747 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.ZCatalog/pip-egg-info/Products.ZCatalog.egg-info/SOURCES.txt0000644000175000017500000000634212214017453030116 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/Products.ZCatalog.egg-info/PKG-INFO pip-egg-info/Products.ZCatalog.egg-info/SOURCES.txt pip-egg-info/Products.ZCatalog.egg-info/dependency_links.txt pip-egg-info/Products.ZCatalog.egg-info/namespace_packages.txt pip-egg-info/Products.ZCatalog.egg-info/not-zip-safe pip-egg-info/Products.ZCatalog.egg-info/requires.txt pip-egg-info/Products.ZCatalog.egg-info/top_level.txt src/Products/__init__.py src/Products/PluginIndexes/__init__.py src/Products/PluginIndexes/interfaces.py src/Products/PluginIndexes/BooleanIndex/BooleanIndex.py src/Products/PluginIndexes/BooleanIndex/__init__.py src/Products/PluginIndexes/BooleanIndex/tests.py src/Products/PluginIndexes/DateIndex/DateIndex.py src/Products/PluginIndexes/DateIndex/__init__.py src/Products/PluginIndexes/DateIndex/tests/__init__.py src/Products/PluginIndexes/DateIndex/tests/test_DateIndex.py src/Products/PluginIndexes/DateRangeIndex/DateRangeIndex.py src/Products/PluginIndexes/DateRangeIndex/__init__.py src/Products/PluginIndexes/DateRangeIndex/tests/__init__.py src/Products/PluginIndexes/DateRangeIndex/tests/test_DateRangeIndex.py src/Products/PluginIndexes/FieldIndex/FieldIndex.py src/Products/PluginIndexes/FieldIndex/__init__.py src/Products/PluginIndexes/FieldIndex/tests/__init__.py src/Products/PluginIndexes/FieldIndex/tests/testFieldIndex.py src/Products/PluginIndexes/KeywordIndex/KeywordIndex.py src/Products/PluginIndexes/KeywordIndex/__init__.py src/Products/PluginIndexes/KeywordIndex/tests/__init__.py src/Products/PluginIndexes/KeywordIndex/tests/testKeywordIndex.py src/Products/PluginIndexes/PathIndex/PathIndex.py src/Products/PluginIndexes/PathIndex/__init__.py src/Products/PluginIndexes/PathIndex/tests/__init__.py src/Products/PluginIndexes/PathIndex/tests/testPathIndex.py src/Products/PluginIndexes/TopicIndex/FilteredSet.py src/Products/PluginIndexes/TopicIndex/TopicIndex.py src/Products/PluginIndexes/TopicIndex/__init__.py src/Products/PluginIndexes/TopicIndex/tests/__init__.py src/Products/PluginIndexes/TopicIndex/tests/testTopicIndex.py src/Products/PluginIndexes/UUIDIndex/UUIDIndex.py src/Products/PluginIndexes/UUIDIndex/__init__.py src/Products/PluginIndexes/UUIDIndex/tests.py src/Products/PluginIndexes/common/ResultList.py src/Products/PluginIndexes/common/UnIndex.py src/Products/PluginIndexes/common/__init__.py src/Products/PluginIndexes/common/randid.py src/Products/PluginIndexes/common/util.py src/Products/PluginIndexes/common/tests/__init__.py src/Products/PluginIndexes/common/tests/test_UnIndex.py src/Products/PluginIndexes/common/tests/test_util.py src/Products/ZCatalog/Catalog.py src/Products/ZCatalog/CatalogAwareness.py src/Products/ZCatalog/CatalogBrains.py src/Products/ZCatalog/CatalogPathAwareness.py src/Products/ZCatalog/Lazy.py src/Products/ZCatalog/ProgressHandler.py src/Products/ZCatalog/ZCatalog.py src/Products/ZCatalog/ZCatalogIndexes.py src/Products/ZCatalog/__init__.py src/Products/ZCatalog/interfaces.py src/Products/ZCatalog/plan.py src/Products/ZCatalog/tests/__init__.py src/Products/ZCatalog/tests/queryplan.py src/Products/ZCatalog/tests/test_brains.py src/Products/ZCatalog/tests/test_catalog.py src/Products/ZCatalog/tests/test_lazy.py src/Products/ZCatalog/tests/test_plan.py src/Products/ZCatalog/tests/test_zcatalog.pyzope2.13-2.13.21/source/Products.ZCatalog/pip-egg-info/Products.ZCatalog.egg-info/not-zip-safe0000644000175000017500000000000112214017453030453 0ustar arnauarnau zope2.13-2.13.21/source/Products.ZCatalog/LICENSE.txt0000644000175000017500000000402612214017452020570 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/Products.ZCatalog/README.txt0000644000175000017500000000031112214017452020434 0ustar arnauarnauOverview ======== The ZCatalog is Zope 2’s built in search engine. It allows you to categorize and search all kinds of Zope objects. It comes with a variety of indexes for different types of data. zope2.13-2.13.21/source/Products.ZCatalog/setup.cfg0000644000175000017500000000007312214017452020564 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/Products.ZCatalog/COPYRIGHT.txt0000644000175000017500000000004012214017452021046 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/Products.ZCatalog/CHANGES.txt0000644000175000017500000001570712214017452020566 0ustar arnauarnauChangelog ========= 2.13.23 (2012-04-26) -------------------- - Fixed another issue with preserving score values, when a custom index was queried first which was neither ILimitedResultIndex aware nor return scores, and a later index was of the default ZCTextIndex type. 2.13.22 (2011-11-17) -------------------- - Added a new `load_from_path` class method to the `PriorityMap`, which allows one to load a plan from a file, instead of a module via an environment var. 2.13.21 (2011-10-20) -------------------- - Refactored value index logic. Determine value indexes per catalog instead of globally. Store value index set in the priority map, so it can be seen in the ZMI and stored in the module level storage. - Added support for using ZCatalog as local utility. This feature requires the optional `five.globalrequest` dependency. 2.13.20 (2011-08-23) -------------------- - Fixed incorrect calculation of batches in the second half of the result set in sortResults. 2.13.19 (2011-08-20) -------------------- - Increase plan precision to 4 digits in its string representation. 2.13.18 (2011-07-29) -------------------- - In the string representation of a catalog plan, round the times to at most two digits after the comma. 2.13.17 (2011-07-29) -------------------- - Put back the `weightedIntersection` optimization but guard against results with values and do the appropriate fallback to the weighted version. 2.13.16 (2011-07-24) -------------------- - Restored preserving score values from ZCTextIndex indices. https://bugs.launchpad.net/zope2/+bug/815469 2.13.15 (2011-06-30) -------------------- - Fixed undefined variables in BooleanIndex inline migration code. - Fixed BooleanIndex' items method so the ZMI browse view works. 2.13.14 (2011-05-19) -------------------- - Fixed addition of two LazyCat's if any of them was already flattened. - Extend BooleanIndex by making the indexed value variable instead of hardcoding it to `True`. The indexed value will determine the smaller set automatically and choose its best value. An inline switch is done once the indexed value set grows larger than 60% of the total length. 60% was chosen to avoid constant switching for indexes that have an almost equal distribution of `True/False`. - Substitute catalog entry in UUIDIndex error message. 2.13.13 (2011-05-04) -------------------- - Optimize `Catalog.updateMetadata` avoiding a `self.uids` lookup and removing inline migration code for converting `self.data` from non-IOBTree types. - In the path index, don't update data if the value hasn't changed. 2.13.12 (2011-05-02) -------------------- - Optimize DateRangeIndex for better conflict resolution handling. It always starts out with storing an IITreeSet of the value instead of special casing storing an int for a single value. The `single value as int` optimization should be provided via a separate API to be called periodically outside the context of a normal request. - Replaced `weightedIntersection` and `weightedUnion` calls with their non-weighted version, as we didn't pass in weights. 2.13.11 (2011-05-02) -------------------- - Fix possible TypeError in `sortResults` method if only b_start but not b_size has been provided. - Prevent the new UUIDIndex from acquiring attributes via Acquisition. 2.13.10 (2011-04-21) -------------------- - Handle `TypeErrors` in the KeywordIndex if an indexed attribute is a method with required arguments. - Added reporting of the intersection time of each index' result with the result set of the other indexes and consider this time to be part of each index time for prioritizing the index. - Removed tracking of result length from the query plan. The calculation of the length of an intermediate index result can itself be expensive. 2.13.9 (2011-04-10) ------------------- - Added a floor and ceiling value to the date range index. Values outside the specified range will be interpreted the same way as passing `None`, i.e. `since the beginning of time` and `until the end of it`. This allows the index to apply its optimizations, while objects with values outside this range can still be stored in a normal date index, which omits explicitly passed in `None` values. 2.13.8 (2011-04-01) ------------------- - Fixed bug in date range index, which would omit objects exactly matching the query term if a resultset was provided. - Fixed the BooleanIndex to not index objects without the cataloged attribute. 2.13.7 (2011-02-15) ------------------- - Fixed the `DateIndex._unindex` to be of type `IIBTree` instead of `OIBTree`. It stores document ids as keys, which can only be ints. 2.13.6 (2011-02-10) ------------------- - Remove docstrings from various methods, as they shouldn't be web-publishable. 2.13.5 (2011-02-05) ------------------- - Fixed test failures introduced in 2.13.4. 2.13.4 (2011-02-05) ------------------- - Added a new UUIDIndex, based on the common UnIndex. It behaves like a FieldIndex, but can only store one document id per value, so there's a 1:1 mapping from value to document id. An error is logged if a different document id is indexed for an already taken value. The internal data structures are optimized for this and avoid storing one IITreeSet per value. - Optimize sorting in presence of batching arguments. If a batch from the end of the result set is requested, we internally reverse the sorting order and at the end reverse the lazy sequence again. In a sequence with 100 entries, if we request the batch with items 80 to 90, we now reverse sort 20 items (100 to 80), slice of the first ten items and then reverse them. Before we would had to sort the first 90 items and then slice of the last 10. - If batching arguments are provided, limit the returned lazy sequence to the items in the required batch instead of returning leading items falling outside of the requested batch. - Fixed inline `IISet` to `IITreeSet` conversion code inside DateRangeIndex' `_insertForwardIndexEntry` method. 2.13.3 (2011-01-01) ------------------- - Avoid locale-dependent test condition in `test_length_with_filter`. 2.13.2 (2010-12-31) ------------------- - Preserve `actual_result_count` on flattening nested LazyCat's. - Preserve the `actual_result_count` on all lazy return values. This allows to get proper batching information from catalog results which have been restricted by `sort_limit`. - Made sure `actual_result_count` is available on all lazy classes and falls back to `__len__` if not explicitly provided. - Optimized length calculation of Lazy classes. 2.13.1 (2010-12-25) ------------------- - Added automatic sorting limit calculation based on batch arguments. If the query contains a `b_start` and `b_size` argument and no explicit `sort_limit` is provided, the sort limit will be calculated as `b_start + b_size`. - Avoid pre-allocation of marker items in `LazyMap`. 2.13.0 (2010-12-25) ------------------- - Fix `LazyMap` to avoid unnecessary function calls. - Released as separate distribution. zope2.13-2.13.21/source/Products.ZCatalog/src/0000755000175000017500000000000012214017452017532 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/0000755000175000017500000000000012214017452021335 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/0000755000175000017500000000000012214017452023041 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/ZCatalog.py0000644000175000017500000011036312214017452025123 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ ZCatalog product """ import logging import operator import sys import string import time import urllib from AccessControl.class_init import InitializeClass from AccessControl.Permission import name_trans from AccessControl.Permissions import manage_zcatalog_entries from AccessControl.Permissions import manage_zcatalog_indexes from AccessControl.Permissions import search_zcatalog from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import aq_base from Acquisition import aq_parent from Acquisition import Implicit from App.Dialogs import MessageDialog from App.special_dtml import DTMLFile from DateTime.DateTime import DateTime from DocumentTemplate.DT_Util import InstanceDict from DocumentTemplate.DT_Util import TemplateDict from DocumentTemplate.DT_Util import Eval from DocumentTemplate.security import RestrictedDTML from OFS.Folder import Folder from OFS.ObjectManager import ObjectManager from Persistence import Persistent from Products.PluginIndexes.interfaces import IPluggableIndex import transaction from ZODB.POSException import ConflictError from zope.interface import implements from Products.ZCatalog.Catalog import Catalog, CatalogError from Products.ZCatalog.interfaces import IZCatalog from Products.ZCatalog.ProgressHandler import ZLogHandler from Products.ZCatalog.ZCatalogIndexes import ZCatalogIndexes from .plan import PriorityMap LOG = logging.getLogger('Zope.ZCatalog') manage_addZCatalogForm = DTMLFile('dtml/addZCatalog', globals()) def manage_addZCatalog(self, id, title, vocab_id=None, REQUEST=None): """Add a ZCatalog object. The vocab_id argument is ignored. """ id = str(id) title = str(title) c = ZCatalog(id, title, container=self) self._setObject(id, c) if REQUEST is not None: return self.manage_main(self, REQUEST, update_menu=1) class ZCatalog(Folder, Persistent, Implicit): """ZCatalog object A ZCatalog contains arbirary index like references to Zope objects. ZCatalog's can index either 'Field' values of object, or 'Text' values. ZCatalog does not store references to the objects themselves, but rather to a unique identifier that defines how to get to the object. In Zope, this unique idenfier is the object's relative path to the ZCatalog (since two Zope object's cannot have the same URL, this is an excellent unique qualifier in Zope). Most of the dirty work is done in the _catalog object, which is an instance of the Catalog class. An interesting feature of this class is that it is not Zope specific. You can use it in any Python program to catalog objects. """ implements(IZCatalog) security = ClassSecurityInfo() security.setPermissionDefault(manage_zcatalog_entries, ('Manager', )) security.setPermissionDefault(manage_zcatalog_indexes, ('Manager', )) security.setPermissionDefault(search_zcatalog, ('Anonymous', 'Manager')) security.declareProtected(search_zcatalog, 'all_meta_types') meta_type = "ZCatalog" icon = 'misc_/ZCatalog/ZCatalog.gif' manage_options = ( {'label': 'Contents', 'action': 'manage_main'}, {'label': 'Catalog', 'action': 'manage_catalogView'}, {'label': 'Properties', 'action': 'manage_propertiesForm'}, {'label': 'Indexes', 'action': 'manage_catalogIndexes'}, {'label': 'Metadata', 'action': 'manage_catalogSchema'}, {'label': 'Find Objects', 'action': 'manage_catalogFind'}, {'label': 'Advanced', 'action': 'manage_catalogAdvanced'}, {'label': 'Query Report', 'action': 'manage_catalogReport'}, {'label': 'Query Plan', 'action': 'manage_catalogPlan'}, {'label': 'Undo', 'action': 'manage_UndoForm'}, {'label': 'Security', 'action': 'manage_access'}, {'label': 'Ownership', 'action': 'manage_owner'}, ) security.declareProtected(manage_zcatalog_entries, 'manage_main') security.declareProtected(manage_zcatalog_entries, 'manage_catalogView') manage_catalogView = DTMLFile('dtml/catalogView', globals()) security.declareProtected(manage_zcatalog_entries, 'manage_catalogIndexes') manage_catalogIndexes = DTMLFile('dtml/catalogIndexes', globals()) security.declareProtected(manage_zcatalog_entries, 'manage_catalogSchema') manage_catalogSchema = DTMLFile('dtml/catalogSchema', globals()) security.declareProtected(manage_zcatalog_entries, 'manage_catalogFind') manage_catalogFind = DTMLFile('dtml/catalogFind', globals()) security.declareProtected(manage_zcatalog_entries, 'manage_catalogAdvanced') manage_catalogAdvanced = DTMLFile('dtml/catalogAdvanced', globals()) security.declareProtected(manage_zcatalog_entries, 'manage_catalogReport') manage_catalogReport = DTMLFile('dtml/catalogReport', globals()) security.declareProtected(manage_zcatalog_entries, 'manage_catalogPlan') manage_catalogPlan = DTMLFile('dtml/catalogPlan', globals()) security.declareProtected(manage_zcatalog_entries, 'manage_objectInformation') manage_objectInformation = DTMLFile('dtml/catalogObjectInformation', globals()) Indexes = ZCatalogIndexes() threshold = 10000 long_query_time = 0.1 # vocabulary and vocab_id are left for backwards # compatibility only, they are not used anymore vocabulary = None vocab_id = '' _v_total = 0 _v_transaction = None def __init__(self, id, title='', vocab_id=None, container=None): # ZCatalog no longer cares about vocabularies # so the vocab_id argument is ignored (Casey) if container is not None: self = self.__of__(container) self.id=id self.title=title self.threshold = 10000 self.long_query_time = 0.1 # in seconds self._v_total = 0 self._catalog = Catalog() def __len__(self): return len(self._catalog) security.declareProtected(manage_zcatalog_entries, 'manage_edit') def manage_edit(self, RESPONSE, URL1, threshold=1000, REQUEST=None): """ edit the catalog """ if not isinstance(threshold, int): threshold = int(threshold) self.threshold = threshold RESPONSE.redirect( URL1 + '/manage_main?manage_tabs_message=Catalog%20Changed') security.declareProtected(manage_zcatalog_entries, 'manage_subbingToggle') def manage_subbingToggle(self, REQUEST, RESPONSE, URL1): """ toggle subtransactions """ if self.threshold: self.threshold = None else: self.threshold = 10000 RESPONSE.redirect( URL1 + '/manage_catalogAdvanced?manage_tabs_message=Catalog%20Changed') security.declareProtected(manage_zcatalog_entries, 'manage_catalogObject') def manage_catalogObject(self, REQUEST, RESPONSE, URL1, urls=None): """ index Zope object(s) that 'urls' point to """ if urls: if isinstance(urls, str): urls = (urls, ) for url in urls: obj = self.resolve_path(url) if obj is None and hasattr(self, 'REQUEST'): obj = self.resolve_url(url, REQUEST) if obj is not None: self.catalog_object(obj, url) RESPONSE.redirect( URL1 + '/manage_catalogView?manage_tabs_message=Object%20Cataloged') security.declareProtected(manage_zcatalog_entries, 'manage_uncatalogObject') def manage_uncatalogObject(self, REQUEST, RESPONSE, URL1, urls=None): """ removes Zope object(s) 'urls' from catalog """ if urls: if isinstance(urls, str): urls = (urls, ) for url in urls: self.uncatalog_object(url) RESPONSE.redirect( URL1 + '/manage_catalogView?manage_tabs_message=Object%20Uncataloged') security.declareProtected(manage_zcatalog_entries, 'manage_catalogReindex') def manage_catalogReindex(self, REQUEST, RESPONSE, URL1): """ clear the catalog, then re-index everything """ elapse = time.time() c_elapse = time.clock() pgthreshold = self._getProgressThreshold() handler = (pgthreshold > 0) and ZLogHandler(pgthreshold) or None self.refreshCatalog(clear=1, pghandler=handler) elapse = time.time() - elapse c_elapse = time.clock() - c_elapse RESPONSE.redirect( URL1 + '/manage_catalogAdvanced?manage_tabs_message=' + urllib.quote('Catalog Updated \n' 'Total time: %s\n' 'Total CPU time: %s' % (`elapse`, `c_elapse`))) security.declareProtected(manage_zcatalog_entries, 'refreshCatalog') def refreshCatalog(self, clear=0, pghandler=None): """ re-index everything we can find """ cat = self._catalog paths = cat.paths.values() if clear: paths = tuple(paths) cat.clear() num_objects = len(paths) if pghandler: pghandler.init('Refreshing catalog: %s' % self.absolute_url(1), num_objects) for i in xrange(num_objects): if pghandler: pghandler.report(i) p = paths[i] obj = self.resolve_path(p) if obj is None: obj = self.resolve_url(p, self.REQUEST) if obj is not None: try: self.catalog_object(obj, p, pghandler=pghandler) except ConflictError: raise except Exception: LOG.error('Recataloging object at %s failed' % p, exc_info=sys.exc_info()) if pghandler: pghandler.finish() security.declareProtected(manage_zcatalog_entries, 'manage_catalogClear') def manage_catalogClear(self, REQUEST=None, RESPONSE=None, URL1=None): """ clears the whole enchilada """ self._catalog.clear() if REQUEST and RESPONSE: RESPONSE.redirect( URL1 + '/manage_catalogAdvanced?manage_tabs_message=Catalog%20Cleared') security.declareProtected(manage_zcatalog_entries, 'manage_catalogFoundItems') def manage_catalogFoundItems(self, REQUEST, RESPONSE, URL2, URL1, obj_metatypes=None, obj_ids=None, obj_searchterm=None, obj_expr=None, obj_mtime=None, obj_mspec=None, obj_roles=None, obj_permission=None): """ Find object according to search criteria and Catalog them """ elapse = time.time() c_elapse = time.clock() obj = REQUEST.PARENTS[1] path = '/'.join(obj.getPhysicalPath()) self.ZopeFindAndApply(obj, obj_metatypes=obj_metatypes, obj_ids=obj_ids, obj_searchterm=obj_searchterm, obj_expr=obj_expr, obj_mtime=obj_mtime, obj_mspec=obj_mspec, obj_permission=obj_permission, obj_roles=obj_roles, search_sub=1, REQUEST=REQUEST, apply_func=self.catalog_object, apply_path=path) elapse = time.time() - elapse c_elapse = time.clock() - c_elapse RESPONSE.redirect( URL1 + '/manage_catalogView?manage_tabs_message=' + urllib.quote('Catalog Updated\n' 'Total time: %s\n' 'Total CPU time: %s' % (`elapse`, `c_elapse`))) security.declareProtected(manage_zcatalog_entries, 'manage_addColumn') def manage_addColumn(self, name, REQUEST=None, RESPONSE=None, URL1=None): """ add a column """ self.addColumn(name) if REQUEST and RESPONSE: RESPONSE.redirect( URL1 + '/manage_catalogSchema?manage_tabs_message=Column%20Added') security.declareProtected(manage_zcatalog_entries, 'manage_delColumn') def manage_delColumn(self, names, REQUEST=None, RESPONSE=None, URL1=None): """ delete a column or some columns """ if isinstance(names, str): names = (names, ) for name in names: self.delColumn(name) if REQUEST and RESPONSE: RESPONSE.redirect( URL1 + '/manage_catalogSchema?manage_tabs_message=Column%20Deleted') security.declareProtected(manage_zcatalog_entries, 'manage_addIndex') def manage_addIndex(self, name, type, extra=None, REQUEST=None, RESPONSE=None, URL1=None): """add an index """ self.addIndex(name, type, extra) if REQUEST and RESPONSE: RESPONSE.redirect( URL1 + '/manage_catalogIndexes?manage_tabs_message=Index%20Added') security.declareProtected(manage_zcatalog_entries, 'manage_delIndex') def manage_delIndex(self, ids=None, REQUEST=None, RESPONSE=None, URL1=None): """ delete an index or some indexes """ if not ids: return MessageDialog(title='No items specified', message='No items were specified!', action="./manage_catalogIndexes") if isinstance(ids, str): ids = (ids, ) for name in ids: self.delIndex(name) if REQUEST and RESPONSE: RESPONSE.redirect( URL1 + '/manage_catalogIndexes?manage_tabs_message=Index%20Deleted') security.declareProtected(manage_zcatalog_entries, 'manage_clearIndex') def manage_clearIndex(self, ids=None, REQUEST=None, RESPONSE=None, URL1=None): """ clear an index or some indexes """ if not ids: return MessageDialog(title='No items specified', message='No items were specified!', action="./manage_catalogIndexes") if isinstance(ids, str): ids = (ids, ) for name in ids: self.clearIndex(name) if REQUEST and RESPONSE: RESPONSE.redirect( URL1 + '/manage_catalogIndexes?manage_tabs_message=Index%20Cleared') security.declareProtected(manage_zcatalog_entries, 'reindexIndex') def reindexIndex(self, name, REQUEST, pghandler=None): if isinstance(name, str): name = (name, ) paths = self._catalog.uids.keys() i = 0 if pghandler: pghandler.init('reindexing %s' % name, len(paths)) for p in paths: i += 1 if pghandler: pghandler.report(i) obj = self.resolve_path(p) if obj is None: obj = self.resolve_url(p, REQUEST) if obj is None: LOG.error('reindexIndex could not resolve ' 'an object from the uid %r.' % p) else: # don't update metadata when only reindexing a single # index via the UI self.catalog_object(obj, p, idxs=name, update_metadata=0, pghandler=pghandler) if pghandler: pghandler.finish() security.declareProtected(manage_zcatalog_entries, 'manage_reindexIndex') def manage_reindexIndex(self, ids=None, REQUEST=None, RESPONSE=None, URL1=None): """Reindex indexe(s) from a ZCatalog""" if not ids: return MessageDialog(title='No items specified', message='No items were specified!', action="./manage_catalogIndexes") pgthreshold = self._getProgressThreshold() handler = (pgthreshold > 0) and ZLogHandler(pgthreshold) or None self.reindexIndex(ids, REQUEST, handler) if REQUEST and RESPONSE: RESPONSE.redirect( URL1 + '/manage_catalogIndexes' '?manage_tabs_message=Reindexing%20Performed') security.declareProtected(manage_zcatalog_entries, 'catalog_object') def catalog_object(self, obj, uid=None, idxs=None, update_metadata=1, pghandler=None): if uid is None: try: uid = obj.getPhysicalPath except AttributeError: raise CatalogError( "A cataloged object must support the 'getPhysicalPath' " "method if no unique id is provided when cataloging") else: uid = '/'.join(uid()) elif not isinstance(uid, str): raise CatalogError('The object unique id must be a string.') self._catalog.catalogObject(obj, uid, None, idxs, update_metadata=update_metadata) # None passed in to catalogObject as third argument indicates # that we shouldn't try to commit subtransactions within any # indexing code. We throw away the result of the call to # catalogObject (which is a word count), because it's # worthless to us here. if self.threshold is not None: # figure out whether or not to commit a subtransaction. t = id(transaction.get()) if t != self._v_transaction: self._v_total = 0 self._v_transaction = t self._v_total = self._v_total + 1 # increment the _v_total counter for this thread only and get # a reference to the current transaction. # the _v_total counter is zeroed if we notice that we're in # a different transaction than the last one that came by. # self.threshold represents the number of times that # catalog_object needs to be called in order for the catalog # to commit a subtransaction. The semantics here mean that # we should commit a subtransaction if our threshhold is # exceeded within the boundaries of the current transaction. if self._v_total > self.threshold: transaction.savepoint(optimistic=True) self._p_jar.cacheGC() self._v_total = 0 if pghandler: pghandler.info('committing subtransaction') security.declareProtected(manage_zcatalog_entries, 'uncatalog_object') def uncatalog_object(self, uid): self._catalog.uncatalogObject(uid) security.declareProtected(search_zcatalog, 'uniqueValuesFor') def uniqueValuesFor(self, name): # Return the unique values for a given FieldIndex return self._catalog.uniqueValuesFor(name) security.declareProtected(search_zcatalog, 'getpath') def getpath(self, rid): # Return the path to a cataloged object given a 'data_record_id_' return self._catalog.paths[rid] security.declareProtected(search_zcatalog, 'getrid') def getrid(self, path, default=None): # Return 'data_record_id_' the to a cataloged object given a 'path' return self._catalog.uids.get(path, default) security.declareProtected(search_zcatalog, 'getobject') def getobject(self, rid, REQUEST=None): # Return a cataloged object given a 'data_record_id_' return aq_parent(self).unrestrictedTraverse(self.getpath(rid)) security.declareProtected(search_zcatalog, 'getMetadataForUID') def getMetadataForUID(self, uid): # return the correct metadata given the uid, usually the path rid = self._catalog.uids[uid] return self._catalog.getMetadataForRID(rid) security.declareProtected(search_zcatalog, 'getIndexDataForUID') def getIndexDataForUID(self, uid): # return the current index contents given the uid, usually the path rid = self._catalog.uids[uid] return self._catalog.getIndexDataForRID(rid) security.declareProtected(search_zcatalog, 'getMetadataForRID') def getMetadataForRID(self, rid): # return the correct metadata for the cataloged record id return self._catalog.getMetadataForRID(int(rid)) security.declareProtected(search_zcatalog, 'getIndexDataForRID') def getIndexDataForRID(self, rid): # return the current index contents for the specific rid return self._catalog.getIndexDataForRID(rid) security.declareProtected(search_zcatalog, 'schema') def schema(self): return self._catalog.schema.keys() security.declareProtected(search_zcatalog, 'indexes') def indexes(self): return self._catalog.indexes.keys() security.declareProtected(search_zcatalog, 'index_objects') def index_objects(self): # This method returns unwrapped indexes! # You should probably use getIndexObjects instead return self._catalog.indexes.values() security.declareProtected(manage_zcatalog_indexes, 'getIndexObjects') def getIndexObjects(self): # Return a list of wrapped(!) indexes getIndex = self._catalog.getIndex return [getIndex(name) for name in self.indexes()] def _searchable_arguments(self): r = {} n = {'optional': 1} for name in self._catalog.indexes.keys(): r[name] = n return r def _searchable_result_columns(self): r = [] for name in self._catalog.schema.keys(): i = {} i['name'] = name i['type'] = 's' i['parser'] = str i['width'] = 8 r.append(i) r.append({'name': 'data_record_id_', 'type': 's', 'parser': str, 'width': 8}) return r security.declareProtected(search_zcatalog, 'searchResults') def searchResults(self, REQUEST=None, used=None, **kw): """Search the catalog Search terms can be passed in the REQUEST or as keyword arguments. The used argument is now deprecated and ignored """ return self._catalog.searchResults(REQUEST, used, **kw) security.declareProtected(search_zcatalog, '__call__') __call__ = searchResults security.declareProtected(search_zcatalog, 'search') def search( self, query_request, sort_index=None, reverse=0, limit=None, merge=1): """Programmatic search interface, use for searching the catalog from scripts. query_request: Dictionary containing catalog query sort_index: Name of sort index reverse: Reverse sort order? limit: Limit sorted result count (optimization hint) merge: Return merged results (like searchResults) or raw results for later merging. """ if sort_index is not None: sort_index = self._catalog.indexes[sort_index] return self._catalog.search( query_request, sort_index, reverse, limit, merge) ## this stuff is so the find machinery works meta_types=() # Sub-object types that are specific to this object security.declareProtected(search_zcatalog, 'valid_roles') def valid_roles(self): # Return list of valid roles obj=self dict={} dup =dict.has_key x=0 while x < 100: if hasattr(obj, '__ac_roles__'): roles=obj.__ac_roles__ for role in roles: if not dup(role): dict[role]=1 obj = aq_parent(obj) if obj is None: break x = x + 1 roles=dict.keys() roles.sort() return roles security.declareProtected(manage_zcatalog_entries, 'ZopeFindAndApply') def ZopeFindAndApply(self, obj, obj_ids=None, obj_metatypes=None, obj_searchterm=None, obj_expr=None, obj_mtime=None, obj_mspec=None, obj_permission=None, obj_roles=None, search_sub=0, REQUEST=None, result=None, pre='', apply_func=None, apply_path=''): """Zope Find interface and apply This is a *great* hack. Zope find just doesn't do what we need here; the ability to apply a method to all the objects *as they're found* and the need to pass the object's path into that method. """ if result is None: result = [] if obj_metatypes and 'all' in obj_metatypes: obj_metatypes = None if obj_mtime and isinstance(obj_mtime, str): obj_mtime = DateTime(obj_mtime).timeTime() if obj_permission: obj_permission = p_name(obj_permission) if obj_roles and isinstance(obj_roles, str): obj_roles = [obj_roles] if obj_expr: # Setup expr machinations md = td() obj_expr = (Eval(obj_expr), md, md._push, md._pop) base = aq_base(obj) if not hasattr(base, 'objectItems'): return result try: items = obj.objectItems() except Exception: return result try: add_result = result.append except Exception: raise AttributeError(repr(result)) for id, ob in items: if pre: p = "%s/%s" % (pre, id) else: p = id dflag = 0 if hasattr(ob, '_p_changed') and (ob._p_changed == None): dflag = 1 bs = aq_base(ob) if ( (not obj_ids or absattr(bs.id) in obj_ids) and (not obj_metatypes or (hasattr(bs, 'meta_type') and bs.meta_type in obj_metatypes)) and (not obj_searchterm or (hasattr(ob, 'PrincipiaSearchSource') and ob.PrincipiaSearchSource().find(obj_searchterm) >= 0)) and (not obj_expr or expr_match(ob, obj_expr)) and (not obj_mtime or mtime_match(ob, obj_mtime, obj_mspec)) and ((not obj_permission or not obj_roles) or role_match(ob, obj_permission, obj_roles)) ): if apply_func: apply_func(ob, (apply_path + '/' + p)) else: add_result((p, ob)) dflag = 0 if search_sub and hasattr(bs, 'objectItems'): self.ZopeFindAndApply(ob, obj_ids, obj_metatypes, obj_searchterm, obj_expr, obj_mtime, obj_mspec, obj_permission, obj_roles, search_sub, REQUEST, result, p, apply_func, apply_path) if dflag: ob._p_deactivate() return result security.declareProtected(search_zcatalog, 'resolve_url') def resolve_url(self, path, REQUEST): # Attempt to resolve a url into an object in the Zope # namespace. The url may be absolute or a catalog path # style url. If no object is found, None is returned. # No exceptions are raised. if REQUEST: script=REQUEST.script if path.find(script) != 0: path='%s/%s' % (script, path) try: return REQUEST.resolve_url(path) except Exception: pass security.declareProtected(search_zcatalog, 'resolve_path') def resolve_path(self, path): # Attempt to resolve a url into an object in the Zope # namespace. The url may be absolute or a catalog path # style url. If no object is found, None is returned. # No exceptions are raised. try: return self.unrestrictedTraverse(path) except Exception: pass security.declareProtected(manage_zcatalog_entries, 'manage_normalize_paths') def manage_normalize_paths(self, REQUEST): """Ensure that all catalog paths are full physical paths This should only be used with ZCatalogs in which all paths can be resolved with unrestrictedTraverse.""" paths = self._catalog.paths uids = self._catalog.uids unchanged = 0 fixed = [] removed = [] for path, rid in uids.items(): ob = None if path[:1] == '/': ob = self.resolve_url(path[1:], REQUEST) if ob is None: ob = self.resolve_url(path, REQUEST) if ob is None: removed.append(path) continue ppath = '/'.join(ob.getPhysicalPath()) if path != ppath: fixed.append((path, ppath)) else: unchanged = unchanged + 1 for path, ppath in fixed: rid = uids[path] del uids[path] paths[rid] = ppath uids[ppath] = rid for path in removed: self.uncatalog_object(path) return MessageDialog(title='Done Normalizing Paths', message='%s paths normalized, %s paths removed, and ' '%s unchanged.' % (len(fixed), len(removed), unchanged), action='./manage_main') security.declareProtected(manage_zcatalog_entries, 'manage_setProgress') def manage_setProgress(self, pgthreshold=0, RESPONSE=None, URL1=None): """Set parameter to perform logging of reindexing operations very 'pgthreshold' objects """ self.pgthreshold = pgthreshold if RESPONSE: RESPONSE.redirect(URL1 + '/manage_catalogAdvanced?' 'manage_tabs_message=Catalog%20Changed') def _getProgressThreshold(self): if not hasattr(self, 'pgthreshold'): self.pgthreshold = 0 return self.pgthreshold # Indexing methods security.declareProtected(manage_zcatalog_indexes, 'addIndex') def addIndex(self, name, type, extra=None): if IPluggableIndex.providedBy(type): self._catalog.addIndex(name, type) return # Convert the type by finding an appropriate product which supports # this interface by that name. Bleah products = ObjectManager.all_meta_types(self, interfaces=(IPluggableIndex, )) p = None for prod in products: if prod['name'] == type: p = prod break if p is None: raise ValueError("Index of type %s not found" % type) base = p['instance'] if base is None: raise ValueError("Index type %s does not support addIndex" % type) # This code is *really* lame but every index type has its own # function signature *sigh* and there is no common way to pass # additional parameters to the constructor. The suggested way # for new index types is to use an "extra" record. if 'extra' in base.__init__.func_code.co_varnames: index = base(name, extra=extra, caller=self) elif 'caller' in base.__init__.func_code.co_varnames: index = base(name, caller=self) else: index = base(name) self._catalog.addIndex(name, index) security.declareProtected(manage_zcatalog_indexes, 'delIndex') def delIndex(self, name): self._catalog.delIndex(name) security.declareProtected(manage_zcatalog_indexes, 'clearIndex') def clearIndex(self, name): self._catalog.getIndex(name).clear() security.declareProtected(manage_zcatalog_indexes, 'addColumn') def addColumn(self, name, default_value=None): return self._catalog.addColumn(name, default_value) security.declareProtected(manage_zcatalog_indexes, 'delColumn') def delColumn(self, name): return self._catalog.delColumn(name) # Catalog plan methods security.declareProtected(manage_zcatalog_entries, 'getCatalogPlan') def getCatalogPlan(self): """Get a string representation of a query plan""" pmap = PriorityMap.get_value() output = [] output.append('# query plan dumped at %r\n' % time.asctime()) output.append('queryplan = {') for cid, plan in sorted(pmap.items()): output.append(' %s: {' % repr(cid)) for querykey, details in sorted(plan.items()): if isinstance(details, (frozenset, set)): output.append(' %r: %r,' % (querykey, details)) else: output.append(' %s: {' % repr(querykey)) for indexname, bench in sorted(details.items()): tuplebench = (round(bench[0], 4), ) + bench[1:] output.append(' %r:\n %r,' % ( indexname, tuplebench)) output.append(' },') output.append(' },') output.append('}') return '\n'.join(output) security.declareProtected(manage_zcatalog_entries, 'getCatalogReport') def getCatalogReport(self): """Query time reporting.""" rval = self._catalog.getCatalogPlan().report() rval.sort(key=operator.itemgetter('duration'), reverse=True) return rval security.declareProtected(manage_zcatalog_entries, 'manage_resetCatalogReport') def manage_resetCatalogReport(self, REQUEST=None): """Resets the catalog report.""" self._catalog.getCatalogPlan().reset() if REQUEST is not None: REQUEST.response.redirect(REQUEST.URL1 + '/manage_catalogReport?manage_tabs_message=Report%20cleared') security.declareProtected(manage_zcatalog_entries, 'manage_editCatalogReport') def manage_editCatalogReport(self, long_query_time=0.1, REQUEST=None): """Edit the long query time.""" if not isinstance(long_query_time, float): long_query_time = float(long_query_time) self.long_query_time = long_query_time if REQUEST is not None: REQUEST.response.redirect(REQUEST.URL1 + '/manage_catalogReport?manage_tabs_message=' + 'Long%20query%20time%20changed') InitializeClass(ZCatalog) def p_name(name): return '_' + string.translate(name, name_trans) + '_Permission' def absattr(attr): if callable(attr): return attr() return attr class td(RestrictedDTML, TemplateDict): pass def expr_match(ob, ed): e, md, push, pop = ed push(InstanceDict(ob, md)) r = 0 try: r = e.eval(md) finally: pop() return r _marker = object() def mtime_match(ob, t, q): mtime = getattr(ob, '_p_mtime', _marker) if mtime is _marker(): return False return q=='<' and (mtime < t) or (mtime > t) def role_match(ob, permission, roles): pr = [] while True: p = getattr(ob, permission, _marker) if p is not _marker: if isinstance(p, list): pr.append(p) ob = aq_parent(ob) if ob is not None: continue break if isinstance(p, tuple): pr.append(p) break if p is None: pr.append(('Manager', 'Anonymous')) break ob = aq_parent(ob) if ob is not None: continue break for role in roles: if role not in pr: return False return True zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/Catalog.py0000644000175000017500000011014112214017452024763 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import types import logging import warnings from bisect import bisect from random import randint import Acquisition from Acquisition import aq_base from Acquisition import aq_parent import ExtensionClass from Missing import MV from Persistence import Persistent from Products.PluginIndexes.interfaces import ILimitedResultIndex import BTrees.Length from BTrees.IIBTree import intersection, IISet from BTrees.IIBTree import weightedIntersection from BTrees.OIBTree import OIBTree from BTrees.IOBTree import IOBTree from Lazy import LazyMap, LazyCat, LazyValues from CatalogBrains import AbstractCatalogBrain, NoBrainer from .plan import CatalogPlan LOG = logging.getLogger('Zope.ZCatalog') def safe_callable(ob): # Works with ExtensionClasses and Acquisition. if hasattr(ob, '__class__'): return hasattr(ob, '__call__') or isinstance(ob, types.ClassType) else: return callable(ob) class CatalogError(Exception): pass class Catalog(Persistent, Acquisition.Implicit, ExtensionClass.Base): """ An Object Catalog An Object Catalog maintains a table of object metadata, and a series of manageable indexes to quickly search for objects (references in the metadata) that satisfy a search query. This class is not Zope specific, and can be used in any python program to build catalogs of objects. Note that it does require the objects to be Persistent, and thus must be used with ZODB3. """ _v_brains = NoBrainer def __init__(self, vocabulary=None, brains=None): # Catalogs no longer care about vocabularies and lexicons # so the vocabulary argument is ignored. (Casey) self.schema = {} # mapping from attribute name to column number self.names = () # sequence of column names self.indexes = {} # maping from index name to index object # The catalog maintains a BTree of object meta_data for # convenient display on result pages. meta_data attributes # are turned into brain objects and returned by # searchResults. The indexing machinery indexes all records # by an integer id (rid). self.data is a mapping from the # integer id to the meta_data, self.uids is a mapping of the # object unique identifier to the rid, and self.paths is a # mapping of the rid to the unique identifier. self.clear() if brains is not None: self._v_brains = brains self.updateBrains() def __len__(self): return self._length() def clear(self): """ clear catalog """ self.data = IOBTree() # mapping of rid to meta_data self.uids = OIBTree() # mapping of uid to rid self.paths = IOBTree() # mapping of rid to uid self._length = BTrees.Length.Length() for index in self.indexes.keys(): self.getIndex(index).clear() def updateBrains(self): self.useBrains(self._v_brains) def __getitem__(self, index, ttype=type(())): """ Returns instances of self._v_brains, or whatever is passed into self.useBrains. """ if type(index) is ttype: # then it contains a score... normalized_score, score, key = index r=self._v_result_class(self.data[key]).__of__(aq_parent(self)) r.data_record_id_ = key r.data_record_score_ = score r.data_record_normalized_score_ = normalized_score else: # otherwise no score, set all scores to 1 r=self._v_result_class(self.data[index]).__of__(aq_parent(self)) r.data_record_id_ = index r.data_record_score_ = 1 r.data_record_normalized_score_ = 1 return r def __setstate__(self, state): """ initialize your brains. This method is called when the catalog is first activated (from the persistent storage) """ Persistent.__setstate__(self, state) self.updateBrains() def useBrains(self, brains): """ Sets up the Catalog to return an object (ala ZTables) that is created on the fly from the tuple stored in the self.data Btree. """ class mybrains(AbstractCatalogBrain, brains): pass scopy = self.schema.copy() scopy['data_record_id_']=len(self.schema.keys()) scopy['data_record_score_']=len(self.schema.keys())+1 scopy['data_record_normalized_score_']=len(self.schema.keys())+2 mybrains.__record_schema__ = scopy self._v_brains = brains self._v_result_class = mybrains def addColumn(self, name, default_value=None): """ adds a row to the meta data schema """ schema = self.schema names = list(self.names) if name in schema: raise CatalogError('The column %s already exists' % name) if name[0] == '_': raise CatalogError('Cannot cache fields beginning with "_"') values = schema.values() if values: schema[name] = max(values) + 1 else: schema[name] = 0 names.append(name) if default_value in (None, ''): default_value = MV for key, value in self.data.items(): rec = list(value) rec.append(default_value) self.data[key] = tuple(rec) self.names = tuple(names) self.schema = schema # new column? update the brain self.updateBrains() self._p_changed = 1 # why? def delColumn(self, name): """ deletes a row from the meta data schema """ names = list(self.names) _index = names.index(name) if not name in self.schema: LOG.error('delColumn attempted to delete nonexistent ' 'column %s.' % str(name)) return del names[_index] # rebuild the schema i = 0 schema = {} for name in names: schema[name] = i i = i + 1 self.schema = schema self.names = tuple(names) # update the brain self.updateBrains() # remove the column value from each record for key, value in self.data.items(): rec = list(value) del rec[_index] self.data[key] = tuple(rec) def addIndex(self, name, index_type): """Create a new index, given a name and a index_type. Old format: index_type was a string, 'FieldIndex' 'TextIndex' or 'KeywordIndex' is no longer valid; the actual index must be instantiated and passed in to addIndex. New format: index_type is the actual index object to be stored. """ if name in self.indexes: raise CatalogError('The index %s already exists' % name) if name.startswith('_'): raise CatalogError('Cannot index fields beginning with "_"') if not name: raise CatalogError('Name of index is empty') indexes = self.indexes if isinstance(index_type, str): raise TypeError("Catalog addIndex now requires the index type to" "be resolved prior to adding; create the proper " "index in the caller.") indexes[name] = index_type self.indexes = indexes def delIndex(self, name): """ deletes an index """ if not name in self.indexes: raise CatalogError('The index %s does not exist' % name) indexes = self.indexes del indexes[name] self.indexes = indexes def getIndex(self, name): """ get an index wrapped in the catalog """ return self.indexes[name].__of__(self) def updateMetadata(self, object, uid, index): """ Given an object and a uid, update the column data for the uid with the object data iff the object has changed """ data = self.data newDataRecord = self.recordify(object) if index is None: index = getattr(self, '_v_nextid', 0) if index % 4000 == 0: index = randint(-2000000000, 2000000000) while not data.insert(index, newDataRecord): index = randint(-2000000000, 2000000000) # We want ids to be somewhat random, but there are # advantages for having some ids generated # sequentially when many catalog updates are done at # once, such as when reindexing or bulk indexing. # We allocate ids sequentially using a volatile base, # so different threads get different bases. This # further reduces conflict and reduces churn in # here and it result sets when bulk indexing. self._v_nextid = index + 1 else: if data.get(index, 0) != newDataRecord: data[index] = newDataRecord return index # the cataloging API def catalogObject(self, object, uid, threshold=None, idxs=None, update_metadata=1): """ Adds an object to the Catalog by iteratively applying it to all indexes. 'object' is the object to be cataloged 'uid' is the unique Catalog identifier for this object If 'idxs' is specified (as a sequence), apply the object only to the named indexes. If 'update_metadata' is true (the default), also update metadata for the object. If the object is new to the catalog, this flag has no effect (metadata is always created for new objects). """ if idxs is None: idxs = [] index = self.uids.get(uid, None) if index is None: # we are inserting new data index = self.updateMetadata(object, uid, None) self._length.change(1) self.uids[uid] = index self.paths[index] = uid elif update_metadata: # we are updating and we need to update metadata self.updateMetadata(object, uid, index) # do indexing total = 0 if idxs == []: use_indexes = self.indexes.keys() else: use_indexes = idxs for name in use_indexes: x = self.getIndex(name) if hasattr(x, 'index_object'): blah = x.index_object(index, object, threshold) total = total + blah else: LOG.error('catalogObject was passed bad index ' 'object %s.' % str(x)) return total def uncatalogObject(self, uid): """ Uncatalog and object from the Catalog. and 'uid' is a unique Catalog identifier Note, the uid must be the same as when the object was catalogued, otherwise it will not get removed from the catalog This method should not raise an exception if the uid cannot be found in the catalog. """ data = self.data uids = self.uids paths = self.paths indexes = self.indexes.keys() rid = uids.get(uid, None) if rid is not None: for name in indexes: x = self.getIndex(name) if hasattr(x, 'unindex_object'): x.unindex_object(rid) del data[rid] del paths[rid] del uids[uid] self._length.change(-1) else: LOG.error('uncatalogObject unsuccessfully ' 'attempted to uncatalog an object ' 'with a uid of %s. ' % str(uid)) def uniqueValuesFor(self, name): """ return unique values for FieldIndex name """ return self.getIndex(name).uniqueValues() def hasuid(self, uid): """ return the rid if catalog contains an object with uid """ return self.uids.get(uid) def recordify(self, object): """ turns an object into a record tuple """ record = [] # the unique id is always the first element for x in self.names: attr = getattr(object, x, MV) if (attr is not MV and safe_callable(attr)): attr = attr() record.append(attr) return tuple(record) def instantiate(self, record): r = self._v_result_class(record[1]) r.data_record_id_ = record[0] return r.__of__(self) def getMetadataForRID(self, rid): record = self.data[rid] result = {} for (key, pos) in self.schema.items(): result[key] = record[pos] return result def getIndexDataForRID(self, rid): result = {} for name in self.indexes.keys(): result[name] = self.getIndex(name).getEntryForObject(rid, "") return result # This is the Catalog search engine. Most of the heavy lifting happens # below def make_query(self, request): # This is a bit of a mess, but the ZCatalog API has traditionally # supported passing in query restrictions in almost arbitary ways real_req = None if isinstance(request, dict): query = request.copy() elif isinstance(request, CatalogSearchArgumentsMap): query = {} query.update(request.keywords) real_req = request.request if isinstance(real_req, dict): query.update(real_req) real_req = None else: real_req = request if real_req: warnings.warn('You have specified a query using either a request ' 'object or a mixture of a query dict and keyword ' 'arguments. Please use only a simple query dict. ' 'Your query contained "%s". This support is ' 'deprecated and will be removed in Zope 2.14.' % repr(real_req), DeprecationWarning, stacklevel=4) known_keys = query.keys() # The request has too many places where an index restriction # might be specified. Putting all of request.form, # request.other, ... into the query isn't what we want. # So we iterate over all known indexes instead and see if they # are in the request. for iid in self.indexes.keys(): if iid in known_keys: continue value = real_req.get(iid) if value: query[iid] = value return query def _sorted_search_indexes(self, query): # Simple implementation doing no ordering. query_keys = query.keys() order = [] for name, index in self.indexes.items(): if name not in query_keys: continue order.append((ILimitedResultIndex.providedBy(index), name)) order.sort() return [i[1] for i in order] def _limit_sequence(self, sequence, slen, b_start=0, b_size=None, switched_reverse=False): if b_size is not None: sequence = sequence[b_start:b_start + b_size] if slen: slen = len(sequence) if switched_reverse: sequence.reverse() return (sequence, slen) def search(self, query, sort_index=None, reverse=0, limit=None, merge=1): """Iterate through the indexes, applying the query to each one. If merge is true then return a lazy result set (sorted if appropriate) otherwise return the raw (possibly scored) results for later merging. Limit is used in conjuntion with sorting or scored results to inform the catalog how many results you are really interested in. The catalog can then use optimizations to save time and memory. The number of results is not guaranteed to fall within the limit however, you should still slice or batch the results as usual.""" rs = None # resultset # Indexes fulfill a fairly large contract here. We hand each # index the query mapping we are given (which may be composed # of some combination of web request, kw mappings or plain old dicts) # and the index decides what to do with it. If the index finds work # for itself in the query, it returns the results and a tuple of # the attributes that were used. If the index finds nothing for it # to do then it returns None. # Canonicalize the request into a sensible query before passing it on query = self.make_query(query) cr = self.getCatalogPlan(query) cr.start() plan = cr.plan() if not plan: plan = self._sorted_search_indexes(query) indexes = self.indexes.keys() for i in plan: if i not in indexes: # We can have bogus keys or the plan can contain index names # that have been removed in the meantime continue index = self.getIndex(i) _apply_index = getattr(index, "_apply_index", None) if _apply_index is None: continue cr.start_split(i) limit_result = ILimitedResultIndex.providedBy(index) if limit_result: r = _apply_index(query, rs) else: r = _apply_index(query) if r is not None: r, u = r # Short circuit if empty result # BBB: We can remove the "r is not None" check in Zope 2.14 # once we don't need to support the "return everything" case # anymore if r is not None and not r: cr.stop_split(i, result=None, limit=limit_result) return LazyCat([]) # provide detailed info about the pure intersection time intersect_id = i + '#intersection' cr.start_split(intersect_id) # weightedIntersection preserves the values from any mappings # we get, as some indexes don't return simple sets if hasattr(rs, 'items') or hasattr(r, 'items'): _, rs = weightedIntersection(rs, r) else: rs = intersection(rs, r) cr.stop_split(intersect_id) # consider the time it takes to intersect the index result with # the total resultset to be part of the index time cr.stop_split(i, result=r, limit=limit_result) if not rs: break else: cr.stop_split(i, result=None, limit=limit_result) # Try to deduce the sort limit from batching arguments b_start = int(query.get('b_start', 0)) b_size = query.get('b_size', None) if b_size is not None: b_size = int(b_size) if b_size is not None: limit = b_start + b_size elif limit and b_size is None: b_size = limit if rs is None: # None of the indexes found anything to do with the query # We take this to mean that the query was empty (an empty filter) # and so we return everything in the catalog warnings.warn('Your query %s produced no query restriction. ' 'Currently the entire catalog content is returned. ' 'In Zope 2.14 this will result in an empty LazyCat ' 'to be returned.' % repr(cr.make_key(query)), DeprecationWarning, stacklevel=3) rlen = len(self) if sort_index is None: sequence, slen = self._limit_sequence(self.data.items(), rlen, b_start, b_size) result = LazyMap(self.instantiate, sequence, slen, actual_result_count=rlen) else: cr.start_split('sort_on') result = self.sortResults( self.data, sort_index, reverse, limit, merge, actual_result_count=rlen, b_start=b_start, b_size=b_size) cr.stop_split('sort_on', None) elif rs: # We got some results from the indexes. # Sort and convert to sequences. # XXX: The check for 'values' is really stupid since we call # items() and *not* values() rlen = len(rs) if sort_index is None and hasattr(rs, 'items'): # having a 'items' means we have a data structure with # scores. Build a new result set, sort it by score, reverse # it, compute the normalized score, and Lazify it. if not merge: # Don't bother to sort here, return a list of # three tuples to be passed later to mergeResults # note that data_record_normalized_score_ cannot be # calculated and will always be 1 in this case getitem = self.__getitem__ result = [(score, (1, score, rid), getitem) for rid, score in rs.items()] else: cr.start_split('sort_on') rs = rs.byValue(0) # sort it by score max = float(rs[0][0]) # Here we define our getter function inline so that # we can conveniently store the max value as a default arg # and make the normalized score computation lazy def getScoredResult(item, max=max, self=self): """ Returns instances of self._v_brains, or whatever is passed into self.useBrains. """ score, key = item r=self._v_result_class(self.data[key])\ .__of__(aq_parent(self)) r.data_record_id_ = key r.data_record_score_ = score r.data_record_normalized_score_ = int(100. * score / max) return r sequence, slen = self._limit_sequence(rs, rlen, b_start, b_size) result = LazyMap(getScoredResult, sequence, slen, actual_result_count=rlen) cr.stop_split('sort_on', None) elif sort_index is None and not hasattr(rs, 'values'): # no scores if hasattr(rs, 'keys'): rs = rs.keys() sequence, slen = self._limit_sequence(rs, rlen, b_start, b_size) result = LazyMap(self.__getitem__, sequence, slen, actual_result_count=rlen) else: # sort. If there are scores, then this block is not # reached, therefore 'sort-on' does not happen in the # context of a text index query. This should probably # sort by relevance first, then the 'sort-on' attribute. cr.start_split('sort_on') result = self.sortResults(rs, sort_index, reverse, limit, merge, actual_result_count=rlen, b_start=b_start, b_size=b_size) cr.stop_split('sort_on', None) else: # Empty result set result = LazyCat([]) cr.stop() return result def sortResults(self, rs, sort_index, reverse=0, limit=None, merge=1, actual_result_count=None, b_start=0, b_size=None): # Sort a result set using a sort index. Return a lazy # result set in sorted order if merge is true otherwise # returns a list of (sortkey, uid, getter_function) tuples # # The two 'for' loops in here contribute a significant # proportion of the time to perform an indexed search. # Try to avoid all non-local attribute lookup inside # those loops. _intersection = intersection _self__getitem__ = self.__getitem__ index_key_map = sort_index.documentToKeyMap() _None = None _keyerror = KeyError result = [] append = result.append if hasattr(rs, 'keys'): rs = rs.keys() if actual_result_count is None: rlen = len(rs) actual_result_count = rlen else: rlen = actual_result_count # don't limit to more than what we have if limit is not None and limit >= rlen: limit = rlen # if we want a batch from the end of the resultset, reverse sorting # order and limit it, then reverse the resultset again switched_reverse = False if b_size and b_start and b_start > rlen / 2: reverse = not reverse switched_reverse = True b_end = b_start + b_size if b_end >= rlen: overrun = rlen - b_end if b_start >= rlen: # bail out, we are outside the possible range return LazyCat([], 0, actual_result_count) else: b_size += overrun b_start = 0 else: b_start = rlen - b_end limit = b_start + b_size if merge and limit is None and ( rlen > (len(sort_index) * (rlen / 100 + 1))): # The result set is much larger than the sorted index, # so iterate over the sorted index for speed. # This is rarely exercised in practice... length = 0 try: intersection(rs, IISet(())) except TypeError: # rs is not an object in the IIBTree family. # Try to turn rs into an IISet. rs = IISet(rs) for k, intset in sort_index.items(): # We have an index that has a set of values for # each sort key, so we intersect with each set and # get a sorted sequence of the intersections. intset = _intersection(rs, intset) if intset: keys = getattr(intset, 'keys', _None) if keys is not _None: # Is this ever true? intset = keys() length += len(intset) append((k, intset, _self__getitem__)) # Note that sort keys are unique. if reverse: result.sort(reverse=True) else: result.sort() sequence, slen = self._limit_sequence(result, length, b_start, b_size, switched_reverse) result = LazyCat(LazyValues(sequence), slen, actual_result_count) elif limit is None or (limit * 4 > rlen): # Iterate over the result set getting sort keys from the index for did in rs: try: key = index_key_map[did] except _keyerror: # This document is not in the sort key index, skip it. pass else: append((key, did, _self__getitem__)) # The reference back to __getitem__ is used in case # we do not merge now and need to intermingle the # results with those of other catalogs while avoiding # the cost of instantiating a LazyMap per result if merge: if reverse: result.sort(reverse=True) else: result.sort() if limit is not None: result = result[:limit] sequence, _ = self._limit_sequence(result, 0, b_start, b_size, switched_reverse) result = LazyValues(sequence) result.actual_result_count = actual_result_count else: sequence, _ = self._limit_sequence(result, 0, b_start, b_size, switched_reverse) return sequence elif reverse: # Limit/sort results using N-Best algorithm # This is faster for large sets then a full sort # And uses far less memory keys = [] n = 0 worst = None for did in rs: try: key = index_key_map[did] except _keyerror: # This document is not in the sort key index, skip it. pass else: if n >= limit and key <= worst: continue i = bisect(keys, key) keys.insert(i, key) result.insert(i, (key, did, _self__getitem__)) if n == limit: del keys[0], result[0] else: n += 1 worst = keys[0] result.reverse() if merge: sequence, _ = self._limit_sequence(result, 0, b_start, b_size, switched_reverse) result = LazyValues(sequence) result.actual_result_count = actual_result_count else: sequence, _ = self._limit_sequence(result, 0, b_start, b_size, switched_reverse) return sequence elif not reverse: # Limit/sort results using N-Best algorithm in reverse (N-Worst?) keys = [] n = 0 best = None for did in rs: try: key = index_key_map[did] except _keyerror: # This document is not in the sort key index, skip it. pass else: if n >= limit and key >= best: continue i = bisect(keys, key) keys.insert(i, key) result.insert(i, (key, did, _self__getitem__)) if n == limit: del keys[-1], result[-1] else: n += 1 best = keys[-1] if merge: sequence, _ = self._limit_sequence(result, 0, b_start, b_size, switched_reverse) result = LazyValues(sequence) result.actual_result_count = actual_result_count else: sequence, _ = self._limit_sequence(result, 0, b_start, b_size, switched_reverse) return sequence return LazyMap(self.__getitem__, result, len(result), actual_result_count=actual_result_count) def _get_sort_attr(self, attr, kw): """Helper function to find sort-on or sort-order.""" # There are three different ways to find the attribute: # 1. kw[sort-attr] # 2. self.sort-attr # 3. kw[sort_attr] # kw may be a dict or an ExtensionClass MultiMapping, which # differ in what get() returns with no default value. name = "sort-%s" % attr val = kw.get(name, None) if val is not None: return val val = getattr(self, name, None) if val is not None: return val return kw.get("sort_%s" % attr, None) def _getSortIndex(self, args): """Returns a search index object or None.""" sort_index_name = self._get_sort_attr("on", args) if sort_index_name is not None: # self.indexes is always a dict, so get() w/ 1 arg works sort_index = self.indexes.get(sort_index_name) if sort_index is None: raise CatalogError('Unknown sort_on index (%s)' % sort_index_name) else: if not hasattr(sort_index, 'documentToKeyMap'): raise CatalogError( 'The index chosen for sort_on (%s) is not capable of ' 'being used as a sort index.' % sort_index_name) return sort_index else: return None def searchResults(self, REQUEST=None, used=None, _merge=1, **kw): # You should pass in a simple dictionary as the request argument, # which only contains the relevant query. # The used argument is deprecated and is ignored if REQUEST is None and not kw: # Try to acquire request if we get no args for bw compat warnings.warn('Calling searchResults without a query argument nor ' 'keyword arguments is deprecated. In Zope 2.14 the ' 'query will no longer be automatically taken from ' 'the acquired request.', DeprecationWarning, stacklevel=3) REQUEST = getattr(self, 'REQUEST', None) if isinstance(REQUEST, dict) and not kw: # short cut for the best practice args = REQUEST else: args = CatalogSearchArgumentsMap(REQUEST, kw) sort_index = self._getSortIndex(args) sort_limit = self._get_sort_attr('limit', args) reverse = 0 if sort_index is not None: order = self._get_sort_attr("order", args) if (isinstance(order, str) and order.lower() in ('reverse', 'descending')): reverse = 1 # Perform searches with indexes and sort_index return self.search(args, sort_index, reverse, sort_limit, _merge) __call__ = searchResults def getCatalogPlan(self, query=None): """Query time reporting and planning. """ parent = aq_base(aq_parent(self)) threshold = getattr(parent, 'long_query_time', 0.1) return CatalogPlan(self, query, threshold) class CatalogSearchArgumentsMap(object): """Multimap catalog arguments coming simultaneously from keywords and request. BBB: Values that are empty strings are treated as non-existent. This is to ignore empty values, thereby ignoring empty form fields to be consistent with hysterical behavior. This is deprecated and can be changed in Zope 2.14. """ def __init__(self, request, keywords): self.request = request or {} self.keywords = keywords or {} def __getitem__(self, key): marker = [] v = self.keywords.get(key, marker) if v is marker or v == '': v = self.request[key] if v == '': raise KeyError(key) return v def get(self, key, default=None): try: v = self[key] except KeyError: return default else: return v def has_key(self, key): try: self[key] except KeyError: return False else: return True def __contains__(self, name): return self.has_key(name) def mergeResults(results, has_sort_keys, reverse): """Sort/merge sub-results, generating a flat sequence. results is a list of result set sequences, all with or without sort keys """ if not has_sort_keys: return LazyCat(results) else: # Concatenate the catalog results into one list and sort it # Each result record consists of a list of tuples with three values: # (sortkey, docid, catalog__getitem__) combined = [] if len(results) > 1: for r in results: combined.extend(r) elif len(results) == 1: combined = results[0] else: return [] if reverse: combined.sort(reverse=True) else: combined.sort() return LazyMap(lambda rec: rec[2](rec[1]), combined, len(combined)) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/ZCatalogIndexes.py0000644000175000017500000001014112214017452026434 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Virtual container for ZCatalog indexes. """ from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.Permissions import manage_zcatalog_indexes from Acquisition import aq_base from Acquisition import aq_parent from Acquisition import Implicit from App.special_dtml import DTMLFile from OFS.Folder import Folder from OFS.ObjectManager import IFAwareObjectManager from OFS.SimpleItem import SimpleItem from Persistence import Persistent from Products.PluginIndexes.interfaces import IPluggableIndex _marker = [] class ZCatalogIndexes(IFAwareObjectManager, Folder, Persistent, Implicit): """A mapping object, responding to getattr requests by looking up the requested indexes in an object manager.""" # The interfaces we want to show up in our object manager _product_interfaces = (IPluggableIndex, ) meta_type = "ZCatalogIndex" manage_options = () security = ClassSecurityInfo() security.declareObjectProtected(manage_zcatalog_indexes) security.setPermissionDefault(manage_zcatalog_indexes, ('Manager', )) security.declareProtected(manage_zcatalog_indexes, 'addIndexForm') addIndexForm= DTMLFile('dtml/addIndexForm', globals()) # You no longer manage the Indexes here, they are managed from ZCatalog def manage_main(self, REQUEST, RESPONSE): """Redirect to the parent where the management screen now lives""" RESPONSE.redirect('../manage_catalogIndexes') manage_workspace = manage_main # # Object Manager methods # # base accessors loop back through our dictionary interface def _setOb(self, id, object): indexes = aq_parent(self)._catalog.indexes indexes[id] = object aq_base(aq_parent(self))._indexes = indexes def _delOb(self, id): indexes = aq_parent(self)._catalog.indexes del indexes[id] aq_base(aq_parent(self))._indexes = indexes def _getOb(self, id, default=_marker): indexes = aq_parent(self)._catalog.indexes if default is _marker: return indexes.get(id) return indexes.get(id, default) security.declareProtected(manage_zcatalog_indexes, 'objectIds') def objectIds(self, spec=None): indexes = aq_parent(self)._catalog.indexes if spec is not None: if isinstance(spec, str): spec = [spec] result = [] for ob in indexes.keys(): o = indexes.get(ob) meta = getattr(o, 'meta_type', None) if meta is not None and meta in spec: result.append(ob) return result return indexes.keys() # Eat _setObject calls def _setObject(self, id, object, roles=None, user=None, set_owner=1): pass # # traversal # def __bobo_traverse__(self, REQUEST, name): indexes = aq_parent(self)._catalog.indexes o = indexes.get(name, None) if o is not None: if getattr(o, 'manage_workspace', None) is None: o = OldCatalogWrapperObject(o) return o.__of__(self) return getattr(self, name) InitializeClass(ZCatalogIndexes) class OldCatalogWrapperObject(SimpleItem, Implicit): manage_options= ( {'label': 'Settings', 'action': 'manage_main'}, ) manage_main = DTMLFile('dtml/manageOldindex', globals()) manage_main._setName('manage_main') manage_workspace = manage_main def __init__(self, o): self.index = o zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/CatalogBrains.py0000644000175000017500000001005612214017452026126 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from pkg_resources import DistributionNotFound from pkg_resources import get_distribution try: get_distribution('five.globalrequest') except DistributionNotFound: _GLOBALREQUEST_INSTALLED = False else: _GLOBALREQUEST_INSTALLED = True from .interfaces import ICatalogBrain from Acquisition import aq_base from Acquisition import aq_get from Acquisition import aq_parent from Acquisition import Implicit from Record import Record if _GLOBALREQUEST_INSTALLED: from zope.globalrequest import getRequest from zope.interface import implements from ZPublisher.BaseRequest import RequestContainer class AbstractCatalogBrain(Record, Implicit): """Abstract base brain that handles looking up attributes as required, and provides just enough smarts to let us get the URL, path, and cataloged object without having to ask the catalog directly. """ implements(ICatalogBrain) def has_key(self, key): return key in self.__record_schema__ def __contains__(self, name): return name in self.__record_schema__ def getPath(self): """Get the physical path for this record""" return aq_parent(self).getpath(self.data_record_id_) def getURL(self, relative=0): """Generate a URL for this record""" request = aq_get(self, 'REQUEST', None) if request is None and _GLOBALREQUEST_INSTALLED: request = getRequest() return request.physicalPathToURL(self.getPath(), relative) def _unrestrictedGetObject(self): """Return the object for this record Same as getObject, but does not do security checks. """ parent = aq_parent(self) if (aq_get(parent, 'REQUEST', None) is None and _GLOBALREQUEST_INSTALLED): request = getRequest() if request is not None: # path should be absolute, starting at the physical root parent = self.getPhysicalRoot() request_container = RequestContainer(REQUEST=request) parent = aq_base(parent).__of__(request_container) return parent.unrestrictedTraverse(self.getPath()) def getObject(self, REQUEST=None): """Return the object for this record Will return None if the object cannot be found via its cataloged path (i.e., it was deleted or moved without recataloging), or if the user is not authorized to access the object. This method mimicks a subset of what publisher's traversal does, so it allows access if the final object can be accessed even if intermediate objects cannot. """ path = self.getPath().split('/') if not path: return None parent = aq_parent(self) if (aq_get(parent, 'REQUEST', None) is None and _GLOBALREQUEST_INSTALLED): request = getRequest() if request is not None: # path should be absolute, starting at the physical root parent = self.getPhysicalRoot() request_container = RequestContainer(REQUEST=request) parent = aq_base(parent).__of__(request_container) if len(path) > 1: parent = parent.unrestrictedTraverse(path[:-1]) return parent.restrictedTraverse(path[-1]) def getRID(self): """Return the record ID for this object.""" return self.data_record_id_ class NoBrainer: """ This is an empty class to use when no brain is specified. """ pass zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/Lazy.py0000644000175000017500000002127412214017452024340 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from itertools import islice, count _marker = object() class Lazy(object): # Allow (reluctantly) access to unprotected attributes __allow_access_to_unprotected_subobjects__ = True _len = _marker _rlen = _marker @property def actual_result_count(self): if self._rlen is not _marker: return self._rlen self._rlen = len(self) return self._rlen @actual_result_count.setter def actual_result_count(self, value): self._rlen = value def __repr__(self): return repr(list(self)) def __len__(self): # This is a worst-case len, subclasses should try to do better if self._len is not _marker: return self._len l = len(self._data) while 1: try: self[l] l = l + 1 except Exception: self._len = l return l def __add__(self, other): if not isinstance(other, Lazy): raise TypeError( "Can not concatenate objects. Both must be lazy sequences.") return LazyCat([self, other]) def __getslice__(self, i1, i2): r = [] for i in islice(count(i1), i2-i1): try: r.append(self[i]) except IndexError: return r return r slice = __getslice__ class LazyCat(Lazy): """Lazy concatenation of one or more sequences. Should be handy for accessing small parts of big searches. """ def __init__(self, sequences, length=None, actual_result_count=None): flattened_count = 0 if len(sequences) < 100: # Optimize structure of LazyCats to avoid nesting # We don't do this for large numbers of input sequences # to make instantiation faster instead flattened_seq = [] for s in sequences: if isinstance(s, LazyCat): # If one of the sequences passed is itself a LazyCat, add # its base sequences rather than nest LazyCats if getattr(s, '_seq', None) is None: flattened_seq.extend([s._data]) else: flattened_seq.extend(s._seq) flattened_count += s.actual_result_count elif isinstance(s, Lazy): flattened_seq.append(s) flattened_count += s.actual_result_count else: flattened_seq.append(s) flattened_count += len(s) sequences = flattened_seq self._seq = sequences self._data = [] self._sindex = 0 self._eindex = -1 if length is not None: self._len = length if actual_result_count is not None: self.actual_result_count = actual_result_count else: self.actual_result_count = flattened_count def __getitem__(self, index): data = self._data try: seq = self._seq except AttributeError: return data[index] i = index if i < 0: i = len(self) + i if i < 0: raise IndexError(index) ind = len(data) if i < ind: return data[i] ind = ind - 1 sindex = self._sindex try: s = seq[sindex] except Exception: raise IndexError(index) eindex = self._eindex while i > ind: try: eindex = eindex + 1 v = s[eindex] data.append(v) ind = ind + 1 except IndexError: self._sindex = sindex = sindex + 1 try: s = self._seq[sindex] except Exception: del self._seq del self._sindex del self._eindex raise IndexError(index) self._eindex = eindex = -1 self._eindex = eindex return data[i] def __len__(self): # Make len of LazyCat only as expensive as the lens # of its underlying sequences if self._len is not _marker: return self._len l = 0 try: for s in self._seq: l += len(s) except AttributeError: l = len(self._data) self._len = l return l class LazyMap(Lazy): """Act like a sequence, but get data from a filtering process. Don't access data until necessary """ def __init__(self, func, seq, length=None, actual_result_count=None): self._seq = seq self._data = {} self._func = func if length is not None: self._len = length else: self._len = len(seq) if actual_result_count is not None: self.actual_result_count = actual_result_count else: self.actual_result_count = self._len def __getitem__(self, index): data = self._data if index in data: return data[index] value = data[index] = self._func(self._seq[index]) return value class LazyFilter(Lazy): """Act like a sequence, but get data from a filtering process. Don't access data until necessary. Only data for which test(data) returns true will be considered part of the set. """ def __init__(self, test, seq): self._seq = seq self._data = [] self._eindex = -1 self._test = test def __getitem__(self, index): data = self._data try: s = self._seq except AttributeError: return data[index] i = index if i < 0: i = len(self) + i if i < 0: raise IndexError(index) ind = len(data) if i < ind: return data[i] ind = ind - 1 test = self._test e = self._eindex while i > ind: try: e = e + 1 v = s[e] if test(v): data.append(v) ind = ind + 1 except IndexError: del self._test del self._seq del self._eindex raise IndexError(index) self._eindex = e return data[i] class LazyMop(Lazy): """Act like a sequence, but get data from a filtering process. Don't access data until necessary. If the filter raises an exception for a given item, then that item isn't included in the sequence. """ def __init__(self, test, seq): self._seq = seq self._data = [] self._eindex = -1 self._test = test def __getitem__(self, index): data = self._data try: s = self._seq except AttributeError: return data[index] i = index if i < 0: i = len(self) + i if i < 0: raise IndexError(index) ind = len(data) if i < ind: return data[i] ind = ind - 1 test = self._test e = self._eindex while i > ind: try: e = e + 1 v = s[e] try: v = test(v) data.append(v) ind = ind + 1 except Exception: pass except IndexError: del self._test del self._seq del self._eindex raise IndexError(index) self._eindex = e return data[i] class LazyValues(Lazy): """Given a sequence of two tuples typically (key, value) act as though we are just a list of the values lazily""" def __init__(self, seq): self._seq = seq def __len__(self): if self._len is not _marker: return self._len self._len = len(self._seq) return self._len def __getitem__(self, index): return self._seq[index][1] def __getslice__(self, start, end): return self.__class__(self._seq[start:end]) slice = __getslice__ zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/www/0000755000175000017500000000000012214017452023665 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/www/ZCatalog.gif0000644000175000017500000000026512214017452026063 0ustar arnauarnauGIF89a³ÿÿÿÿÿÿÀÀÀÆÇÆ„†„„†ÿÿÿ„!ù,@bPHB©¼˜mFOE0E"h"•b[JI,Ljqæ9îie€A8X¹L¨!FÜB/ŒTRÃMxÊéÀíz¹/R0î›aÇ:#JO‘P Âõ|½z¨ç´T6V€1;zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/interfaces.py0000644000175000017500000002614612214017452025547 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZCatalog interfaces. """ from zope.interface import Interface class IZCatalog(Interface): """ZCatalog object A ZCatalog contains arbitrary index like references to Zope objects. ZCatalog's can index object attribute using a variety of "plug-in" index types. Several index types are included, and others may be added. Text -- Text indexes index textual content. The index can be used to search for objects containing certain words. Field -- Field indexes index atomic values. The index can be used to search for objects that have certain properties. Keyword -- Keyword indexes index sequences of values. The index can be used to search for objects that match one or more of the search terms. Path -- Path indexes index URI paths. They allow you to find objects based on their placement in a hierarchy. Date -- Date indexes index date and type data. They are a type of field index specifically optimized for indexing dates. Date Range -- Date range indexes index time intervals. They are designed for efficient searching of dates falling between two boundaries (such as effective / expiration dates). Topic -- Topic indexes store prefiltered sets of documents. They are used to optimize complex queries into a single fast query by prefiltering documents by an expression The ZCatalog can maintain a table of extra data about cataloged objects. This information can be used on search result pages to show information about a search result. The meta-data table schema is used to build the schema for ZCatalog Result objects. The objects have the same attributes as the column of the meta-data table. ZCatalog does not store references to the objects themselves, but rather to a unique identifier that defines how to get to the object. In Zope, this unique identifier is the object's relative path to the ZCatalog (since two Zope objects cannot have the same URL, this is an excellent unique qualifier in Zope). """ def catalog_object(obj, uid, idxs=None, update_metadata=1): """Catalogs the object 'obj' with the unique identifier 'uid'. The uid must be a physical path, either absolute or relative to the catalog. If provided, idxs specifies the names of indexes to update. If update_metadata is specified (the default), the object's metadata is updated. If it is not, the metadata is left untouched. This flag has no effect if the object is not yet cataloged (metadata is always added for new objects). """ def uncatalog_object(uid): """Uncatalogs the object with the unique identifier 'uid'. The uid must be a physical path, either absolute or relative to the catalog. """ def uniqueValuesFor(name): """returns the unique values for a given FieldIndex named 'name'. """ def getpath(rid): """Return the path to a cataloged object given a 'data_record_id_' """ def getrid(rid): """Return the 'data_record_id_' to a cataloged object given a path """ def getobject(rid, REQUEST=None): """Return a cataloged object given a 'data_record_id_' """ def schema(): """Get the meta-data schema Returns a sequence of names that correspond to columns in the meta-data table. """ def indexes(): """Returns a sequence of names that correspond to indexes. """ def index_objects(): """Returns a sequence of actual index objects. NOTE: This returns unwrapped indexes! You should probably use getIndexObjects instead. Some indexes expect to be wrapped. """ def getIndexObjects(): """Returns a list of acquisition wrapped index objects """ def searchResults(REQUEST=None, **kw): """Search the catalog. Search terms can be passed in the REQUEST or as keyword arguments. Search queries consist of a mapping of index names to search parameters. You can either pass a mapping to searchResults as the variable 'REQUEST' or you can use index names and search parameters as keyword arguments to the method, in other words:: searchResults(title='Elvis Exposed', author='The Great Elvonso') is the same as:: searchResults({'title' : 'Elvis Exposed', 'author : 'The Great Elvonso'}) In these examples, 'title' and 'author' are indexes. This query will return any objects that have the title *Elvis Exposed* AND also are authored by *The Great Elvonso*. Terms that are passed as keys and values in a searchResults() call are implicitly ANDed together. To OR two search results, call searchResults() twice and add concatenate the results like this:: results = ( searchResults(title='Elvis Exposed') + searchResults(author='The Great Elvonso') ) This will return all objects that have the specified title OR the specified author. There are some special index names you can pass to change the behavior of the search query: sort_on -- This parameters specifies which index to sort the results on. sort_order -- You can specify 'reverse' or 'descending'. Default behavior is to sort ascending. sort_limit -- An optimization hint to tell the catalog how many results you are really interested in. See the limit argument to the search method for more details. There are some rules to consider when querying this method: - an empty query mapping (or a bogus REQUEST) returns all items in the catalog. - results from a query involving only field/keyword indexes, e.g. {'id':'foo'} and no 'sort_on' will be returned unsorted. - results from a complex query involving a field/keyword index *and* a text index, e.g. {'id':'foo','PrincipiaSearchSource':'bar'} and no 'sort_on' will be returned unsorted. - results from a simple text index query e.g.{'PrincipiaSearchSource':'foo'} will be returned sorted in descending order by 'score'. A text index cannot beused as a 'sort_on' parameter, and attempting to do so will raise an error. Depending on the type of index you are querying, you may be able to provide more advanced search parameters that can specify range searches or wildcards. These features are documented in The Zope Book. """ def __call__(REQUEST=None, **kw): """Search the catalog, the same way as 'searchResults'. """ def search(query_request, sort_index=None, reverse=0, limit=None, merge=1): """Programmatic search interface, use for searching the catalog from scripts. query_request -- Dictionary containing catalog query. This uses the same format as searchResults. sort_index -- Name of sort index reverse -- Boolean, reverse sort order (defaults to false) limit -- Limit sorted result count to the n best records. This is an optimization hint used in conjunction with a sort_index. If possible ZCatalog will use a different sort algorithm that uses much less memory and scales better then a full sort. The actual number of records returned is not guaranteed to be <= limit. You still need to apply the same batching to the results. Since the len() of the results will no longer be the actual result count, you can use the "actual_result_count" attribute of the lazy result object instead to determine the size of the full result set. merge -- Return merged, lazy results (like searchResults) or raw results for later merging. This can be used to perform multiple queries (even across catalogs) and merge and sort the combined results. """ def refreshCatalog(clear=0, pghandler=None): """Reindex every object we can find, removing the unreachable ones from the index. clear -- values: 1|0 clear the catalog before reindexing pghandler -- optional Progresshandler as defined in ProgressHandler.py (see also README.txt) """ def reindexIndex(name, REQUEST, pghandler=None): """Reindex a single index. name -- id of index REQUEST -- REQUEST object pghandler -- optional Progresshandler as defined in ProgressHandler.py (see also README.txt) """ # This should inherit from an IRecord interface, if there ever is one. class ICatalogBrain(Interface): """Catalog brain that handles looking up attributes as required, and provides just enough smarts to let us get the URL, path, and cataloged object without having to ask the catalog directly. """ def has_key(key): """Record has this field""" def __contains__(self, name): """Record has this field""" def getPath(): """Get the physical path for this record""" def getURL(relative=0): """Generate a URL for this record""" def _unrestrictedGetObject(): """Return the object for this record Same as getObject, but does not do security checks. """ def getObject(): """Return the object for this record Will return None if the object cannot be found via its cataloged path (i.e., it was deleted or moved without recataloging), or if the user is not authorized to access the object. """ def getRID(): """Return the record ID for this object.""" class IProgressHandler(Interface): """ A handler to log progress informations for long running operations. """ def init(ident, max): """ Called at the start of the long running process. 'ident' -- a string identifying the operation 'max' -- maximum number of objects to be processed (int) """ def info(text): """ Log some 'text'""" def finish(): """ Called up termination """ def report(current, *args, **kw): """ Called for every iteration. 'current' -- an integer representing the number of objects processed so far. """ def output(text): """ Log 'text' to some output channel """ zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/ProgressHandler.py0000644000175000017500000000462612214017452026525 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import sys import time from logging import getLogger from DateTime.DateTime import DateTime from zope.interface import implements from .interfaces import IProgressHandler LOG = getLogger('ProgressHandler') class StdoutHandler(object): """ A simple progress handler """ implements(IProgressHandler) def __init__(self, steps=100): self._steps = steps def init(self, ident, max): self._ident = ident self._max = max self._start = time.time() self.fp = sys.stdout self.output('Process started (%d objects to go)' % self._max) def info(self, text): self.output(text) def finish(self): self.output('Process terminated. Duration: %0.2f seconds' % \ (time.time() -self._start)) def report(self, current, *args, **kw): if current > 0: if current % self._steps == 0: seconds_so_far = time.time() - self._start seconds_to_go = (seconds_so_far / current * (self._max - current)) end = DateTime(time.time() + seconds_to_go) self.output('%d/%d (%.2f%%) Estimated termination: %s' % \ (current, self._max, (100.0 * current / self._max), end.strftime('%Y/%m/%d %H:%M:%Sh'))) def output(self, text): print >>self.fp, '%s: %s' % (self._ident, text) class ZLogHandler(StdoutHandler): """ Use Zope logger""" def output(self, text): LOG.info(text) class FilelogHandler(StdoutHandler): """ Use a custom file for logging """ def __init__(self, filename, steps=100): StdoutHandler.__init__(self, steps) self.filename = filename def output(self, text): open(self.filename, 'a').write(text + '\n') zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/__init__.py0000644000175000017500000000200412214017452025146 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ZCatalog product""" import ZCatalog def initialize(context): # Load a default map from Products.ZCatalog.plan import PriorityMap PriorityMap.load_default() context.registerClass( ZCatalog.ZCatalog, permission='Add ZCatalogs', constructors=(ZCatalog.manage_addZCatalogForm, ZCatalog.manage_addZCatalog), icon='www/ZCatalog.gif', ) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/CatalogPathAwareness.py0000644000175000017500000001165712214017452027465 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ZCatalog Findable class """ import warnings from Acquisition import aq_base from App.special_dtml import DTMLFile class CatalogAware: """ This is a Mix-In class to make objects automaticly catalog and uncatalog themselves in Zope, and to provide some other basic attributes that are useful to catalog. Note that if your class subclasses CatalogAware, it will only catalog itself when it is added or copied in Zope. If you make changes to your own object, you are responsible for calling your object's index_object method. """ meta_type='CatalogAware' default_catalog='Catalog' manage_editCatalogerForm=DTMLFile('dtml/editCatalogerForm', globals()) def _warn_deprecated(self): warnings.warn('The Products.ZCatalog.CatalogPathAwareness module is ' 'deprecated and will be removed in Zope 2.14. Please ' 'use event subscribers for zope.lifecycle events to ' 'automatically index and unindex your objects.', DeprecationWarning, stacklevel=3) def manage_editCataloger(self, default, REQUEST=None): """ """ self.default_catalog=default message = "Your changes have been saved" if REQUEST is not None: return self.manage_main(self, REQUEST, manage_tabs_message=message) def manage_afterAdd(self, item, container): self.index_object() for object in self.objectValues(): try: s = object._p_changed except Exception: s = 0 object.manage_afterAdd(item, container) if s is None: object._p_deactivate() def manage_afterClone(self, item): self.index_object() for object in self.objectValues(): try: s = object._p_changed except Exception: s = 0 object.manage_afterClone(item) if s is None: object._p_deactivate() def manage_beforeDelete(self, item, container): self.unindex_object() for object in self.objectValues(): try: s = object._p_changed except Exception: s = 0 object.manage_beforeDelete(item, container) if s is None: object._p_deactivate() def creator(self): """Return a sequence of user names who have the local Owner role on an object. The name creator is used for this method to conform to Dublin Core.""" users=[] for user, roles in self.get_local_roles(): if 'Owner' in roles: users.append(user) return ', '.join(users) def onDeleteObject(self): """Object delete handler. I think this is obsoleted by manage_beforeDelete """ self.unindex_object() def getPath(self): """Return the physical path for an object.""" return '/'.join(self.getPhysicalPath()) def summary(self, num=200): """Return a summary of the text content of the object.""" if not hasattr(self, 'text_content'): return '' attr=getattr(self, 'text_content') if callable(attr): text=attr() else: text=attr n = min(num, len(text)) return text[:n] def index_object(self): """A common method to allow Findables to index themselves.""" self._warn_deprecated() if hasattr(self, self.default_catalog): getattr(self, self.default_catalog).catalog_object(self, self.getPath()) def unindex_object(self): """A common method to allow Findables to unindex themselves.""" self._warn_deprecated() if hasattr(self, self.default_catalog): getattr(self, self.default_catalog).uncatalog_object(self.getPath()) def reindex_object(self): """ Suprisingly useful """ self.unindex_object() self.index_object() def reindex_all(self, obj=None): """ """ if obj is None: obj = self if hasattr(aq_base(obj), 'index_object'): obj.index_object() if hasattr(aq_base(obj), 'objectValues'): for item in obj.objectValues(): self.reindex_all(item) return 'done!' zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/Catalog.gif0000644000175000017500000000026512214017452025105 0ustar arnauarnauGIF89a³ÿÿÿÿÿÿÀÀÀÆÇÆ„†„„†ÿÿÿ„!ù,@bPHB©¼˜mFOE0E"h"•b[JI,Ljqæ9îie€A8X¹L¨!FÜB/ŒTRÃMxÊéÀíz¹/R0î›aÇ:#JO‘P Âõ|½z¨ç´T6V€1;zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/plan.py0000644000175000017500000002572312214017452024356 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import os import os.path import time from collections import namedtuple from logging import getLogger from os import environ from thread import allocate_lock from Acquisition import aq_base from Acquisition import aq_parent from Products.PluginIndexes.interfaces import IUniqueValueIndex from zope.dottedname.resolve import resolve MAX_DISTINCT_VALUES = 10 REFRESH_RATE = 100 VALUE_INDEX_KEY = 'VALUE_INDEXES' Duration = namedtuple('Duration', ['start', 'end']) IndexMeasurement = namedtuple('IndexMeasurement', ['name', 'duration', 'limit']) Benchmark = namedtuple('Benchmark', ['duration', 'hits', 'limit']) RecentQuery = namedtuple('RecentQuery', ['duration', 'details']) Report = namedtuple('Report', ['hits', 'duration', 'last']) logger = getLogger('Products.ZCatalog') class NestedDict(object): """Holds a structure of two nested dicts.""" @classmethod def get(cls, key): outer = cls.value.get(key, None) if outer is None: cls.set(key, {}) outer = cls.value[key] return outer @classmethod def set(cls, key, value): with cls.lock: cls.value[key] = value @classmethod def clear(cls): with cls.lock: cls.value = {} @classmethod def get_entry(cls, key, key2): outer = cls.get(key) inner = outer.get(key2, None) if inner is None: cls.set_entry(key, key2, {}) inner = outer.get(key2) return inner @classmethod def set_entry(cls, key, key2, value): outer = cls.get(key) with cls.lock: outer[key2] = value @classmethod def clear_entry(cls, key): cls.set(key, {}) class PriorityMap(NestedDict): """This holds a structure of nested dicts. The outer dict is a mapping of catalog id to plans. The inner dict holds a query key to Benchmark mapping. """ lock = allocate_lock() value = {} @classmethod def get_value(cls): return cls.value.copy() @classmethod def load_default(cls): location = environ.get('ZCATALOGQUERYPLAN') if location: try: pmap = resolve(location) cls.load_pmap(location, pmap) except ImportError: logger.warning('could not load priority map from %s', location) @classmethod def load_from_path(cls, path): path = os.path.abspath(path) _globals = {} _locals = {} execfile(path, _globals, _locals) pmap = _locals['queryplan'].copy() cls.load_pmap(path, pmap) @classmethod def load_pmap(cls, location, pmap): logger.info('loaded priority %d map(s) from %s', len(pmap), location) # Convert the simple benchmark tuples to namedtuples new_plan = {} for cid, plan in pmap.items(): new_plan[cid] = {} for querykey, details in plan.items(): new_plan[cid][querykey] = {} if isinstance(details, (frozenset, set)): new_plan[cid][querykey] = details else: for indexname, benchmark in details.items(): new_plan[cid][querykey][indexname] = \ Benchmark(*benchmark) with cls.lock: cls.value = new_plan class Reports(NestedDict): """This holds a structure of nested dicts. The outer dict is a mapping of catalog id to reports. The inner dict holds a query key to Report mapping. """ lock = allocate_lock() value = {} class CatalogPlan(object): """Catalog plan class to measure and identify catalog queries and plan their execution. """ def __init__(self, catalog, query=None, threshold=0.1): self.catalog = catalog self.cid = self.get_id() self.query = query self.key = self.make_key(query) self.benchmark = {} self.threshold = threshold self.init_timer() def get_id(self): parent = aq_parent(self.catalog) path = getattr(aq_base(parent), 'getPhysicalPath', None) if path is None: path = ('', 'NonPersistentCatalog') else: path = tuple(parent.getPhysicalPath()) return path def init_timer(self): self.res = [] self.start_time = None self.interim = {} self.stop_time = None self.duration = None def valueindexes(self): indexes = self.catalog.indexes # This function determines all indexes whose values should be respected # in the report key. The number of unique values for the index needs to # be lower than the MAX_DISTINCT_VALUES watermark. # TODO: Ideally who would only consider those indexes with a small # number of unique values, where the number of items for each value # differs a lot. If the number of items per value is similar, the # duration of a query is likely similar as well. value_indexes = PriorityMap.get_entry(self.cid, VALUE_INDEX_KEY) if isinstance(value_indexes, (frozenset, set)): # Calculating all the value indexes is quite slow, so we do this # once for the first query. Since this is an optimization only, # slightly outdated results based on index changes in the running # process can be ignored. return value_indexes value_indexes = set() for name, index in indexes.items(): if IUniqueValueIndex.providedBy(index): values = index.uniqueValues() if values and len(list(values)) < MAX_DISTINCT_VALUES: # Only consider indexes which actually return a number # greater than zero value_indexes.add(name) value_indexes = frozenset(value_indexes) PriorityMap.set_entry(self.cid, VALUE_INDEX_KEY, value_indexes) return value_indexes def make_key(self, query): if not query: return None valueindexes = self.valueindexes() key = keys = query.keys() values = [name for name in keys if name in valueindexes] if values: # If we have indexes whose values should be considered, we first # preserve all normal indexes and then add the keys whose values # matter including their value into the key key = [name for name in keys if name not in values] for name in values: v = query.get(name, []) if isinstance(v, (tuple, list)): v = list(v) v.sort() # We need to make sure the key is immutable, repr() is an easy way # to do this without imposing restrictions on the types of values key.append((name, repr(v))) return tuple(sorted(key)) def plan(self): benchmark = PriorityMap.get_entry(self.cid, self.key) if not benchmark: return None # sort indexes on (limited result index, mean search time) # skip internal ('#') bookkeeping records ranking = [((value.limit, value.duration), name) for name, value in benchmark.items() if '#' not in name] ranking.sort() return [r[1] for r in ranking] def start(self): self.init_timer() self.start_time = time.time() def start_split(self, name): self.interim[name] = Duration(time.time(), None) def stop_split(self, name, result=None, limit=False): current = time.time() start_time, stop_time = self.interim.get(name, Duration(None, None)) self.interim[name] = Duration(start_time, current) dt = current - start_time self.res.append(IndexMeasurement( name=name, duration=dt, limit=limit)) if name == 'sort_on': # sort_on isn't an index. We only do time reporting on it return # remember index's hits, search time and calls benchmark = self.benchmark if name not in benchmark: benchmark[name] = Benchmark(duration=dt, hits=1, limit=limit) else: duration, hits, limit = benchmark[name] duration = ((duration * hits) + dt) / float(hits + 1) # reset adaption if hits % REFRESH_RATE == 0: hits = 0 hits += 1 benchmark[name] = Benchmark(duration, hits, limit) def stop(self): self.end_time = time.time() self.duration = self.end_time - self.start_time # Make absolutely sure we never omit query keys from the plan for key in self.query.keys(): if key not in self.benchmark.keys(): self.benchmark[key] = Benchmark(0, 0, False) PriorityMap.set_entry(self.cid, self.key, self.benchmark) self.log() def log(self): # result of stopwatch total = self.duration if total < self.threshold: return key = self.key recent = RecentQuery(duration=total, details=self.res) previous = Reports.get_entry(self.cid, key) if previous: counter, mean, last = previous mean = (mean * counter + total) / float(counter + 1) Reports.set_entry(self.cid, key, Report(counter + 1, mean, recent)) else: Reports.set_entry(self.cid, key, Report(1, total, recent)) def reset(self): Reports.clear_entry(self.cid) def report(self): """Returns a statistic report of catalog queries as list of dicts. The duration is provided in millisecond. """ rval = [] for key, report in Reports.get(self.cid).items(): last = report.last info = { 'query': key, 'counter': report.hits, 'duration': report.duration * 1000, 'last': {'duration': last.duration * 1000, 'details': [dict(id=d.name, duration=d.duration * 1000) for d in last.details], }, } rval.append(info) return rval # Make sure we provide test isolation from zope.testing.cleanup import addCleanUp addCleanUp(PriorityMap.clear) addCleanUp(Reports.clear) del addCleanUp zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/tests/0000755000175000017500000000000012214017452024203 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/tests/test_catalog.py0000644000175000017500000007025412214017452027236 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ Unittests for Catalog. """ import unittest from Testing.ZopeTestCase.warnhook import WarningsHook from itertools import chain import random from BTrees.IIBTree import IISet import ExtensionClass from Products.PluginIndexes.FieldIndex.FieldIndex import FieldIndex from Products.PluginIndexes.KeywordIndex.KeywordIndex import KeywordIndex from Products.ZCTextIndex.OkapiIndex import OkapiIndex from Products.ZCTextIndex.ZCTextIndex import PLexicon from Products.ZCTextIndex.ZCTextIndex import ZCTextIndex def sort(iterable, reverse=False): L = list(iterable) if reverse: L.sort(reverse=True) else: L.sort() return L class zdummy(ExtensionClass.Base): def __init__(self, num): self.num = num def title(self): return '%d' % self.num class dummy(ExtensionClass.Base): att1 = 'att1' att2 = 'att2' att3 = ['att3'] def __init__(self, num): self.num = num def col1(self): return 'col1' def col2(self): return 'col2' def col3(self): return ['col3'] class objRS(ExtensionClass.Base): def __init__(self, num): self.number = num class CatalogBase(object): def _makeOne(self): from Products.ZCatalog.Catalog import Catalog return Catalog() def setUp(self): self._catalog = self._makeOne() def tearDown(self): self._catalog = None class TestAddDelColumn(CatalogBase, unittest.TestCase): def testAdd(self): self._catalog.addColumn('id') self.assertEqual('id' in self._catalog.schema, True, 'add column failed') def testAddBad(self): from Products.ZCatalog.Catalog import CatalogError self.assertRaises(CatalogError, self._catalog.addColumn, '_id') def testDel(self): self._catalog.addColumn('id') self._catalog.delColumn('id') self.assert_('id' not in self._catalog.schema, 'del column failed') class TestAddDelIndexes(CatalogBase, unittest.TestCase): def testAddFieldIndex(self): idx = FieldIndex('id') self._catalog.addIndex('id', idx) self.assert_(isinstance(self._catalog.indexes['id'], type(FieldIndex('id'))), 'add field index failed') def testAddTextIndex(self): self._catalog.lexicon = PLexicon('lexicon') idx = ZCTextIndex('id', caller=self._catalog, index_factory=OkapiIndex, lexicon_id='lexicon') self._catalog.addIndex('id', idx) i = self._catalog.indexes['id'] self.assert_(isinstance(i, ZCTextIndex), 'add text index failed') def testAddKeywordIndex(self): idx = KeywordIndex('id') self._catalog.addIndex('id', idx) i = self._catalog.indexes['id'] self.assert_(isinstance(i, type(KeywordIndex('id'))), 'add kw index failed') def testDelFieldIndex(self): idx = FieldIndex('id') self._catalog.addIndex('id', idx) self._catalog.delIndex('id') self.assert_('id' not in self._catalog.indexes, 'del index failed') def testDelTextIndex(self): self._catalog.lexicon = PLexicon('lexicon') idx = ZCTextIndex('id', caller=self._catalog, index_factory=OkapiIndex, lexicon_id='lexicon') self._catalog.addIndex('id', idx) self._catalog.delIndex('id') self.assert_('id' not in self._catalog.indexes, 'del index failed') def testDelKeywordIndex(self): idx = KeywordIndex('id') self._catalog.addIndex('id', idx) self._catalog.delIndex('id') self.assert_('id' not in self._catalog.indexes, 'del index failed') class TestCatalog(CatalogBase, unittest.TestCase): upper = 100 nums = range(upper) for i in range(upper): j = random.randrange(0, upper) tmp = nums[i] nums[i] = nums[j] nums[j] = tmp def setUp(self): self._catalog = self._makeOne() self._catalog.lexicon = PLexicon('lexicon') col1 = FieldIndex('col1') col2 = ZCTextIndex('col2', caller=self._catalog, index_factory=OkapiIndex, lexicon_id='lexicon') col3 = KeywordIndex('col3') self._catalog.addIndex('col1', col1) self._catalog.addIndex('col2', col2) self._catalog.addIndex('col3', col3) self._catalog.addColumn('col1') self._catalog.addColumn('col2') self._catalog.addColumn('col3') att1 = FieldIndex('att1') att2 = ZCTextIndex('att2', caller=self._catalog, index_factory=OkapiIndex, lexicon_id='lexicon') att3 = KeywordIndex('att3') num = FieldIndex('num') self._catalog.addIndex('att1', att1) self._catalog.addIndex('att2', att2) self._catalog.addIndex('att3', att3) self._catalog.addIndex('num', num) self._catalog.addColumn('att1') self._catalog.addColumn('att2') self._catalog.addColumn('att3') self._catalog.addColumn('num') for x in range(0, self.upper): self._catalog.catalogObject(dummy(self.nums[x]), repr(x)) self._catalog = self._catalog.__of__(dummy('foo')) # clear # updateBrains # __getitem__ # __setstate__ # useBrains # getIndex # updateMetadata def testCatalogObjectUpdateMetadataFalse(self): ob = dummy(9999) self._catalog.catalogObject(ob, `9999`) brain = self._catalog(num=9999)[0] self.assertEqual(brain.att1, 'att1') ob.att1 = 'foobar' self._catalog.catalogObject(ob, `9999`, update_metadata=0) brain = self._catalog(num=9999)[0] self.assertEqual(brain.att1, 'att1') self._catalog.catalogObject(ob, `9999`) brain = self._catalog(num=9999)[0] self.assertEqual(brain.att1, 'foobar') def uncatalog(self): for x in range(0, self.upper): self._catalog.uncatalogObject(`x`) def testUncatalogFieldIndex(self): self.uncatalog() a = self._catalog(att1='att1') self.assertEqual(len(a), 0, 'len: %s' % len(a)) def testUncatalogTextIndex(self): self.uncatalog() a = self._catalog(att2='att2') self.assertEqual(len(a), 0, 'len: %s' % len(a)) def testUncatalogKeywordIndex(self): self.uncatalog() a = self._catalog(att3='att3') self.assertEqual(len(a), 0, 'len: %s' % len(a)) def testBadUncatalog(self): try: self._catalog.uncatalogObject('asdasdasd') except Exception: self.fail('uncatalogObject raised exception on bad uid') def testUncatalogTwice(self): self._catalog.uncatalogObject(`0`) def _second(self): self._catalog.uncatalogObject(`0`) self.assertRaises(Exception, _second) def testCatalogLength(self): for x in range(0, self.upper): self._catalog.uncatalogObject(`x`) self.assertEqual(len(self._catalog), 0) def testUniqueValuesForLength(self): a = self._catalog.uniqueValuesFor('att1') self.assertEqual(len(a), 1, 'bad number of unique values %s' % a) def testUniqueValuesForContent(self): a = self._catalog.uniqueValuesFor('att1') self.assertEqual(a[0], 'att1', 'bad content %s' % a[0]) # hasuid # recordify # instantiate # getMetadataForRID # getIndexDataForRID # make_query def test_sorted_search_indexes_empty(self): result = self._catalog._sorted_search_indexes({}) self.assertEquals(len(result), 0) def test_sorted_search_indexes_one(self): result = self._catalog._sorted_search_indexes({'att1': 'a'}) self.assertEquals(result, ['att1']) def test_sorted_search_indexes_many(self): query = {'att1': 'a', 'att2': 'b', 'num': 1} result = self._catalog._sorted_search_indexes(query) self.assertEquals(set(result), set(['att1', 'att2', 'num'])) def test_sorted_search_indexes_priority(self): # att2 and col2 don't support ILimitedResultIndex, att1 does query = {'att1': 'a', 'att2': 'b', 'col2': 'c'} result = self._catalog._sorted_search_indexes(query) self.assertEquals(result.index('att1'), 2) # search def test_sortResults(self): brains = self._catalog({'att1': 'att1'}) rs = IISet([b.getRID() for b in brains]) si = self._catalog.getIndex('num') result = self._catalog.sortResults(rs, si) self.assertEqual([r.num for r in result], range(100)) def test_sortResults_reversed(self): brains = self._catalog({'att1': 'att1'}) rs = IISet([b.getRID() for b in brains]) si = self._catalog.getIndex('num') result = self._catalog.sortResults(rs, si, reverse=True) self.assertEqual([r.num for r in result], list(reversed(range(100)))) def test_sortResults_limit(self): brains = self._catalog({'att1': 'att1'}) rs = IISet([b.getRID() for b in brains]) si = self._catalog.getIndex('num') result = self._catalog.sortResults(rs, si, limit=10) self.assertEqual(len(result), 10) self.assertEqual(result.actual_result_count, 100) self.assertEqual([r.num for r in result], range(10)) def test_sortResults_limit_reversed(self): brains = self._catalog({'att1': 'att1'}) rs = IISet([b.getRID() for b in brains]) si = self._catalog.getIndex('num') result = self._catalog.sortResults(rs, si, reverse=True, limit=10) self.assertEqual(len(result), 10) self.assertEqual(result.actual_result_count, 100) expected = list(reversed(range(90, 100))) self.assertEqual([r.num for r in result], expected) def testLargeSortedResultSetWithSmallIndex(self): # This exercises the optimization in the catalog that iterates # over the sort index rather than the result set when the result # set is much larger than the sort index. a = self._catalog(att1='att1', sort_on='att1') self.assertEqual(len(a), self.upper) self.assertEqual(a.actual_result_count, self.upper) def testSortLimit(self): full = self._catalog(att1='att1', sort_on='num') a = self._catalog(att1='att1', sort_on='num', sort_limit=10) self.assertEqual([r.num for r in a], [r.num for r in full[:10]]) self.assertEqual(a.actual_result_count, self.upper) a = self._catalog(att1='att1', sort_on='num', sort_limit=10, sort_order='reverse') rev = [r.num for r in full[-10:]] rev.reverse() self.assertEqual([r.num for r in a], rev) self.assertEqual(a.actual_result_count, self.upper) def testBigSortLimit(self): a = self._catalog(att1='att1', sort_on='num', sort_limit=self.upper*3) self.assertEqual(a.actual_result_count, self.upper) self.assertEqual(a[0].num, 0) a = self._catalog(att1='att1', sort_on='num', sort_limit=self.upper*3, sort_order='reverse') self.assertEqual(a.actual_result_count, self.upper) self.assertEqual(a[0].num, self.upper - 1) def testSortLimitViaBatchingArgsBeforeStart(self): query = dict(att1='att1', sort_on='num', b_start=-5, b_size=8) result = self._catalog(query) self.assertEqual(result.actual_result_count, 100) self.assertEqual([r.num for r in result], range(0, 3)) def testSortLimitViaBatchingArgsStart(self): query = dict(att1='att1', sort_on='num', b_start=0, b_size=5) result = self._catalog(query) self.assertEqual(result.actual_result_count, 100) self.assertEqual([r.num for r in result], range(0, 5)) def testSortLimitViaBatchingEarlyFirstHalf(self): query = dict(att1='att1', sort_on='num', b_start=11, b_size=17) result = self._catalog(query) self.assertEqual(result.actual_result_count, 100) self.assertEqual([r.num for r in result], range(11, 28)) def testSortLimitViaBatchingArgsLateFirstHalf(self): query = dict(att1='att1', sort_on='num', b_start=30, b_size=15) result = self._catalog(query) self.assertEqual(result.actual_result_count, 100) self.assertEqual([r.num for r in result], range(30, 45)) def testSortLimitViaBatchingArgsLeftMiddle(self): query = dict(att1='att1', sort_on='num', b_start=45, b_size=8) result = self._catalog(query) self.assertEqual(result.actual_result_count, 100) self.assertEqual([r.num for r in result], range(45, 53)) def testSortLimitViaBatchingArgsRightMiddle(self): query = dict(att1='att1', sort_on='num', b_start=48, b_size=8) result = self._catalog(query) self.assertEqual(result.actual_result_count, 100) self.assertEqual([r.num for r in result], range(48, 56)) def testSortLimitViaBatchingArgsEarlySecondHalf(self): query = dict(att1='att1', sort_on='num', b_start=55, b_size=15) result = self._catalog(query) self.assertEqual(result.actual_result_count, 100) self.assertEqual([r.num for r in result], range(55, 70)) def testSortLimitViaBatchingArgsSecondHalf(self): query = dict(att1='att1', sort_on='num', b_start=70, b_size=15) result = self._catalog(query) self.assertEqual(result.actual_result_count, 100) self.assertEqual([r.num for r in result], range(70, 85)) def testSortLimitViaBatchingArgsEnd(self): query = dict(att1='att1', sort_on='num', b_start=90, b_size=10) result = self._catalog(query) self.assertEqual(result.actual_result_count, 100) self.assertEqual([r.num for r in result], range(90, 100)) def testSortLimitViaBatchingArgsOverEnd(self): query = dict(att1='att1', sort_on='num', b_start=90, b_size=15) result = self._catalog(query) self.assertEqual(result.actual_result_count, 100) self.assertEqual([r.num for r in result], range(90, 100)) def testSortLimitViaBatchingArgsOutside(self): query = dict(att1='att1', sort_on='num', b_start=110, b_size=10) result = self._catalog(query) self.assertEqual(result.actual_result_count, 100) self.assertEqual([r.num for r in result], []) # _get_sort_attr # _getSortIndex # searchResults def testResultLength(self): a = self._catalog(att1='att1') self.assertEqual(len(a), self.upper, 'length should be %s, its %s' % (self.upper, len(a))) def testMappingWithEmptyKeysDoesntReturnAll(self): # Queries with empty keys used to return all, because of a bug in the # parseIndexRequest function, mistaking a CatalogSearchArgumentsMap # for a Record class a = self._catalog({'col1': '', 'col2': '', 'col3': ''}) self.assertEqual(len(a), 0, 'length should be 0, its %s' % len(a)) def testFieldIndexLength(self): a = self._catalog(att1='att1') self.assertEqual(len(a), self.upper, 'should be %s, but is %s' % (self.upper, len(a))) def testTextIndexLength(self): a = self._catalog(att2='att2') self.assertEqual(len(a), self.upper, 'should be %s, but is %s' % (self.upper, len(a))) def testKeywordIndexLength(self): a = self._catalog(att3='att3') self.assertEqual(len(a), self.upper, 'should be %s, but is %s' % (self.upper, len(a))) def testGoodSortIndex(self): upper = self.upper a = self._catalog(att1='att1', sort_on='num') self.assertEqual(len(a), upper, 'length should be %s, its %s' % (upper, len(a))) for x in range(self.upper): self.assertEqual(a[x].num, x) def testBadSortIndex(self): from Products.ZCatalog.Catalog import CatalogError def badsortindex(): self._catalog(sort_on='foofaraw') self.assertRaises(CatalogError, badsortindex) def testWrongKindOfIndexForSort(self): from Products.ZCatalog.Catalog import CatalogError def wrongsortindex(): self._catalog(sort_on='att2') self.assertRaises(CatalogError, wrongsortindex) def testTextIndexQWithSortOn(self): upper = self.upper a = self._catalog(sort_on='num', att2='att2') self.assertEqual(len(a), upper, 'length should be %s, its %s' % (upper, len(a))) for x in range(self.upper): self.assertEqual(a[x].num, x) def testTextIndexQWithoutSortOn(self): upper = self.upper a = self._catalog(att2='att2') self.assertEqual(len(a), upper, 'length should be %s, its %s' % (upper, len(a))) def testKeywordIndexWithMinRange(self): a = self._catalog(att3={'query': 'att', 'range': 'min'}) self.assertEqual(len(a), self.upper) def testKeywordIndexWithMaxRange(self): a = self._catalog(att3={'query': 'att35', 'range': ':max'}) self.assertEqual(len(a), self.upper) def testKeywordIndexWithMinMaxRangeCorrectSyntax(self): a = self._catalog(att3={'query': ['att', 'att35'], 'range': 'min:max'}) self.assertEqual(len(a), self.upper) def testKeywordIndexWithMinMaxRangeWrongSyntax(self): # checkKeywordIndex with min/max range wrong syntax. a = self._catalog(att3={'query': ['att'], 'range': 'min:max'}) self.assert_(len(a) != self.upper) def testCombinedTextandKeywordQuery(self): a = self._catalog(att3='att3', att2='att2') self.assertEqual(len(a), self.upper) class TestRangeSearch(CatalogBase, unittest.TestCase): def setUp(self): self._catalog = self._makeOne() index = FieldIndex('number') self._catalog.addIndex('number', index) self._catalog.addColumn('number') for i in range(5000): obj = objRS(random.randrange(0, 20000)) self._catalog.catalogObject(obj, i) self._catalog = self._catalog.__of__(objRS(200)) def testRangeSearch(self): for i in range(1000): m = random.randrange(0, 20000) n = m + 1000 for r in self._catalog.searchResults( number={'query': (m, n), 'range': 'min:max'}): size = r.number self.assert_(m<=size and size<=n, "%d vs [%d,%d]" % (r.number, m, n)) class TestCatalogReturnAll(CatalogBase, unittest.TestCase): def setUp(self): self.warningshook = WarningsHook() self.warningshook.install() self._catalog = self._makeOne() def testEmptyMappingReturnsAll(self): col1 = FieldIndex('col1') self._catalog.addIndex('col1', col1) for x in range(0, 10): self._catalog.catalogObject(dummy(x), repr(x)) self.assertEqual(len(self._catalog), 10) length = len(self._catalog({})) self.assertEqual(length, 10) def tearDown(self): CatalogBase.tearDown(self) self.warningshook.uninstall() class TestCatalogSearchArgumentsMap(unittest.TestCase): def _makeOne(self, request=None, keywords=None): from Products.ZCatalog.Catalog import CatalogSearchArgumentsMap return CatalogSearchArgumentsMap(request, keywords) def test_init_empty(self): argmap = self._makeOne() self.assert_(argmap) def test_init_request(self): argmap = self._makeOne(dict(foo='bar'), None) self.assertEquals(argmap.get('foo'), 'bar') def test_init_keywords(self): argmap = self._makeOne(None, dict(foo='bar')) self.assertEquals(argmap.get('foo'), 'bar') def test_getitem(self): argmap = self._makeOne(dict(a='a'), dict(b='b')) self.assertEquals(argmap['a'], 'a') self.assertEquals(argmap['b'], 'b') self.assertRaises(KeyError, argmap.__getitem__, 'c') def test_getitem_emptystring(self): argmap = self._makeOne(dict(a='', c='c'), dict(b='', c='')) self.assertRaises(KeyError, argmap.__getitem__, 'a') self.assertRaises(KeyError, argmap.__getitem__, 'b') self.assertEquals(argmap['c'], 'c') def test_get(self): argmap = self._makeOne(dict(a='a'), dict(b='b')) self.assertEquals(argmap.get('a'), 'a') self.assertEquals(argmap.get('b'), 'b') self.assertEquals(argmap.get('c'), None) self.assertEquals(argmap.get('c', 'default'), 'default') def test_keywords_precedence(self): argmap = self._makeOne(dict(a='a', c='r'), dict(b='b', c='k')) self.assertEquals(argmap.get('c'), 'k') self.assertEquals(argmap['c'], 'k') def test_haskey(self): argmap = self._makeOne(dict(a='a'), dict(b='b')) self.assert_(argmap.has_key('a')) self.assert_(argmap.has_key('b')) self.assert_(not argmap.has_key('c')) def test_contains(self): argmap = self._makeOne(dict(a='a'), dict(b='b')) self.assert_('a' in argmap) self.assert_('b' in argmap) self.assert_('c' not in argmap) class TestMergeResults(CatalogBase, unittest.TestCase): def setUp(self): self.catalogs = [] for i in range(3): cat = self._makeOne() cat.lexicon = PLexicon('lexicon') cat.addIndex('num', FieldIndex('num')) cat.addIndex('big', FieldIndex('big')) cat.addIndex('number', FieldIndex('number')) i = ZCTextIndex('title', caller=cat, index_factory=OkapiIndex, lexicon_id='lexicon') cat.addIndex('title', i) cat = cat.__of__(zdummy(16336)) for i in range(10): obj = zdummy(i) obj.big = i > 5 obj.number = True cat.catalogObject(obj, str(i)) self.catalogs.append(cat) def testNoFilterOrSort(self): from Products.ZCatalog.Catalog import mergeResults results = [cat.searchResults( dict(number=True), _merge=0) for cat in self.catalogs] merged_rids = [r.getRID() for r in mergeResults( results, has_sort_keys=False, reverse=False)] expected = [r.getRID() for r in chain(*results)] self.assertEqual(sort(merged_rids), sort(expected)) def testSortedOnly(self): from Products.ZCatalog.Catalog import mergeResults results = [cat.searchResults( dict(number=True, sort_on='num'), _merge=0) for cat in self.catalogs] merged_rids = [r.getRID() for r in mergeResults( results, has_sort_keys=True, reverse=False)] expected = sort(chain(*results)) expected = [rid for sortkey, rid, getitem in expected] self.assertEqual(merged_rids, expected) def testSortReverse(self): from Products.ZCatalog.Catalog import mergeResults results = [cat.searchResults( dict(number=True, sort_on='num'), _merge=0) for cat in self.catalogs] merged_rids = [r.getRID() for r in mergeResults( results, has_sort_keys=True, reverse=True)] expected = sort(chain(*results), reverse=True) expected = [rid for sortkey, rid, getitem in expected] self.assertEqual(merged_rids, expected) def testLimitSort(self): from Products.ZCatalog.Catalog import mergeResults results = [cat.searchResults( dict(att1='att1', number=True, sort_on='num', sort_limit=2), _merge=0) for cat in self.catalogs] merged_rids = [r.getRID() for r in mergeResults( results, has_sort_keys=True, reverse=False)] expected = sort(chain(*results)) expected = [rid for sortkey, rid, getitem in expected] self.assertEqual(merged_rids, expected) def testScored(self): from Products.ZCatalog.Catalog import mergeResults results = [cat.searchResults(title='4 or 5 or 6', _merge=0) for cat in self.catalogs] merged_rids = [r.getRID() for r in mergeResults( results, has_sort_keys=True, reverse=False)] expected = sort(chain(*results)) expected = [rid for sortkey, (nscore, score, rid), getitem in expected] self.assertEqual(merged_rids, expected) def testSmallIndexSort(self): # Test that small index sort optimization is not used for merging from Products.ZCatalog.Catalog import mergeResults results = [cat.searchResults( dict(number=True, sort_on='big'), _merge=0) for cat in self.catalogs] merged_rids = [r.getRID() for r in mergeResults( results, has_sort_keys=True, reverse=False)] expected = sort(chain(*results)) expected = [rid for sortkey, rid, getitem in expected] self.assertEqual(merged_rids, expected) class TestScoring(CatalogBase, unittest.TestCase): def _get_catalog(self): return self._catalog.__of__(zdummy(16336)) def setUp(self): self._catalog = self._makeOne() self._catalog.lexicon = PLexicon('lexicon') idx = ZCTextIndex('title', caller=self._catalog, index_factory=OkapiIndex, lexicon_id='lexicon') self._catalog.addIndex('title', idx) self._catalog.addIndex('true', FieldIndex('true')) self._catalog.addColumn('title') cat = self._get_catalog() for i in (1, 2, 3, 10, 11, 110, 111): obj = zdummy(i) obj.true = True if i == 110: obj.true = False cat.catalogObject(obj, str(i)) def test_simple_search(self): cat = self._get_catalog() brains = cat(title='10') self.assertEqual(len(brains), 1) self.assertEqual(brains[0].title, '10') def test_or_search(self): cat = self._get_catalog() brains = cat(title='2 OR 3') self.assertEqual(len(brains), 2) def test_scored_search(self): cat = self._get_catalog() brains = cat(title='1*') self.assertEqual(len(brains), 5) self.assertEqual(brains[0].title, '111') def test_combined_scored_search(self): cat = self._get_catalog() brains = cat(title='1*', true=True) self.assertEqual(len(brains), 4) self.assertEqual(brains[0].title, '111') def test_combined_scored_search_planned(self): from ..plan import Benchmark from ..plan import PriorityMap cat = self._get_catalog() query = dict(title='1*', true=True) plan = cat.getCatalogPlan() plan_key = plan.make_key(query) catalog_id = plan.get_id() # plan with title first PriorityMap.set_entry(catalog_id, plan_key, dict( title=Benchmark(1, 1, False), true=Benchmark(2, 1, False), )) brains = cat(query) self.assertEqual(len(brains), 4) self.assertEqual(brains[0].title, '111') # plan with true first PriorityMap.set_entry(catalog_id, plan_key, dict( title=Benchmark(2, 1, False), true=Benchmark(1, 1, False), )) brains = cat(query) self.assertEqual(len(brains), 4) self.assertEqual(brains[0].title, '111') def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestAddDelColumn)) suite.addTest(unittest.makeSuite(TestAddDelIndexes)) suite.addTest(unittest.makeSuite(TestCatalog)) suite.addTest(unittest.makeSuite(TestRangeSearch)) suite.addTest(unittest.makeSuite(TestCatalogReturnAll)) suite.addTest(unittest.makeSuite(TestCatalogSearchArgumentsMap)) suite.addTest(unittest.makeSuite(TestMergeResults)) suite.addTest(unittest.makeSuite(TestScoring)) return suite zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/tests/test_lazy.py0000644000175000017500000002240212214017452026573 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Unittests for Lazy sequence classes """ import unittest class BaseSequenceTest(unittest.TestCase): def _compare(self, lseq, seq): self.assertEqual(len(lseq), len(seq)) self.assertEqual(list(lseq), seq) def test_actual_result_count(self): lcat = self._createLSeq(range(10)) self.assertEqual(len(lcat), 10) self.assertEqual(lcat.actual_result_count, 10) lcat.actual_result_count = 20 self.assertEqual(len(lcat), 10) self.assertEqual(lcat.actual_result_count, 20) class TestLazyCat(BaseSequenceTest): def _createLSeq(self, *sequences): from Products.ZCatalog.Lazy import LazyCat return LazyCat(sequences) def _createLValues(self, seq): from Products.ZCatalog.Lazy import LazyValues return LazyValues(seq) def test_empty(self): lcat = self._createLSeq([]) self._compare(lcat, []) self.assertEqual(lcat.actual_result_count, 0) def test_repr(self): lcat = self._createLSeq([0, 1]) self.assertEquals(repr(lcat), repr([0, 1])) def test_init_single(self): seq = range(10) lcat = self._createLSeq(seq) self._compare(lcat, seq) self.assertEqual(lcat.actual_result_count, 10) def test_add(self): seq1 = range(10) seq2 = range(10, 20) lcat1 = self._createLSeq(seq1) lcat2 = self._createLSeq(seq2) lcat = lcat1 + lcat2 self._compare(lcat, range(20)) self.assertEqual(lcat.actual_result_count, 20) def test_add_after_getitem(self): seq1 = range(10) seq2 = range(10, 20) lcat1 = self._createLSeq(seq1) lcat2 = self._createLSeq(seq2) # turning lcat1 into a list will flatten it into _data and remove _seq list(lcat1) lcat = lcat1 + lcat2 self._compare(lcat, range(20)) self.assertEqual(lcat.actual_result_count, 20) def test_init_multiple(self): from string import hexdigits, letters seq1 = range(10) seq2 = list(hexdigits) seq3 = list(letters) lcat = self._createLSeq(seq1, seq2, seq3) self._compare(lcat, seq1 + seq2 + seq3) def test_init_nested(self): from string import hexdigits, letters seq1 = range(10) seq2 = list(hexdigits) seq3 = list(letters) lcat = apply(self._createLSeq, [self._createLSeq(seq) for seq in (seq1, seq2, seq3)]) self._compare(lcat, seq1 + seq2 + seq3) def test_slicing(self): from string import hexdigits, letters seq1 = range(10) seq2 = list(hexdigits) seq3 = list(letters) lcat = apply(self._createLSeq, [self._createLSeq(seq) for seq in (seq1, seq2, seq3)]) self._compare(lcat[5:-5], seq1[5:] + seq2 + seq3[:-5]) def test_length(self): # Unaccessed length lcat = self._createLSeq(range(10)) self.assertEqual(len(lcat), 10) self.assertEqual(lcat.actual_result_count, 10) # Accessed in the middle lcat = self._createLSeq(range(10)) lcat[4] self.assertEqual(len(lcat), 10) self.assertEqual(lcat.actual_result_count, 10) # Accessed after the lcat is accessed over the whole range lcat = self._createLSeq(range(10)) lcat[:] self.assertEqual(len(lcat), 10) self.assertEqual(lcat.actual_result_count, 10) def test_actual_result_count(self): # specify up-front lcat = self._createLSeq(range(10)) lcat.actual_result_count = 100 self.assertEqual(len(lcat), 10) self.assertEqual(lcat.actual_result_count, 100) lvalues = self._createLValues([]) self.assertEqual(len(lvalues), 0) self.assertEqual(lvalues.actual_result_count, 0) combined = lvalues + lcat self.assertEqual(len(combined), 10) self.assertEqual(combined.actual_result_count, 100) combined.actual_result_count = 5 self.assertEqual(combined.actual_result_count, 5) class TestLazyMap(TestLazyCat): def _createLSeq(self, *seq): return self._createLMap(lambda x: x, *seq) def _createLMap(self, mapfunc, *seq): from Products.ZCatalog.Lazy import LazyMap totalseq = [] for s in seq: totalseq.extend(s) return LazyMap(mapfunc, totalseq) def test_map(self): from string import hexdigits, letters seq1 = range(10) seq2 = list(hexdigits) seq3 = list(letters) filter = lambda x: str(x).lower() lmap = self._createLMap(filter, seq1, seq2, seq3) self._compare(lmap, [str(x).lower() for x in (seq1 + seq2 + seq3)]) def testMapFuncIsOnlyCalledAsNecessary(self): seq = range(10) count = [0] # closure only works with list, and `nonlocal` in py3 def func(x): count[0] += 1 return x lmap = self._createLMap(func, seq) self.assertEqual(lmap[5], 5) self.assertEqual(count[0], 1) class TestLazyFilter(TestLazyCat): def _createLSeq(self, *seq): return self._createLFilter(lambda x: True, *seq) def _createLFilter(self, filter, *seq): from Products.ZCatalog.Lazy import LazyFilter totalseq = [] for s in seq: totalseq.extend(s) return LazyFilter(filter, totalseq) def test_filter(self): from string import hexdigits, letters seq1 = range(10) seq2 = list(hexdigits) seq3 = list(letters) filter = lambda x: str(x).isalpha() lmap = self._createLFilter(filter, seq1, seq2, seq3) self._compare(lmap, seq2[10:] + seq3) def test_length_with_filter(self): from string import letters lower_length = len([x for x in letters if x.islower()]) # Unaccessed length lfilter = self._createLFilter(lambda x: x.islower(), list(letters)) self.assertEqual(len(lfilter), lower_length) # Accessed in the middle lfilter = self._createLFilter(lambda x: x.islower(), list(letters)) lfilter[13] self.assertEqual(len(lfilter), lower_length) # Accessed after the lcat is accessed over the whole range lfilter = self._createLFilter(lambda x: x.islower(), list(letters)) lfilter[:] self.assertEqual(len(lfilter), lower_length) class TestLazyMop(TestLazyCat): def _createLSeq(self, *seq): return self._createLMop(lambda x: x, *seq) def _createLMop(self, mapfunc, *seq): from Products.ZCatalog.Lazy import LazyMop totalseq = [] for s in seq: totalseq.extend(s) return LazyMop(mapfunc, totalseq) def test_mop(self): from string import hexdigits, letters seq1 = range(10) seq2 = list(hexdigits) seq3 = list(letters) def filter(x): if isinstance(x, int): raise ValueError return x.lower() lmop = self._createLMop(filter, seq1, seq2, seq3) self._compare(lmop, [str(x).lower() for x in (seq2 + seq3)]) def test_length_with_filter(self): from string import letters letter_length = len(letters) seq = range(10) + list(letters) def filter(x): if isinstance(x, int): raise ValueError return x.lower() # Unaccessed length lmop = self._createLMop(filter, seq) self.assertEqual(len(lmop), letter_length) # Accessed in the middle lmop = self._createLMop(filter, seq) lmop[26] self.assertEqual(len(lmop), letter_length) # Accessed after the lcat is accessed over the whole range lmop = self._createLMop(filter, letters) lmop[:] self.assertEqual(len(lmop), letter_length) class TestLazyValues(BaseSequenceTest): def _createLSeq(self, seq): from Products.ZCatalog.Lazy import LazyValues return LazyValues(seq) def test_empty(self): lvals = self._createLSeq([]) self._compare(lvals, []) def test_values(self): from string import letters seq = zip(letters, range(10)) lvals = self._createLSeq(seq) self._compare(lvals, range(10)) def test_slicing(self): from string import letters seq = zip(letters, range(10)) lvals = self._createLSeq(seq) self._compare(lvals[2:-2], range(2, 8)) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestLazyCat)) suite.addTest(unittest.makeSuite(TestLazyMap)) suite.addTest(unittest.makeSuite(TestLazyFilter)) suite.addTest(unittest.makeSuite(TestLazyMop)) suite.addTest(unittest.makeSuite(TestLazyValues)) return suite zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/tests/test_plan.py0000644000175000017500000002730712214017452026557 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import os import os.path import time import unittest from zope.testing import cleanup HERE = __file__ class dummy(object): def __init__(self, num): self.num = num def big(self): return self.num > 5 def numbers(self): return (self.num, self.num + 1) class TestNestedDict(unittest.TestCase): def setUp(self): self.nest = self._makeOne() def _makeOne(self): from ..plan import NestedDict return NestedDict def test_novalue(self): self.assertEquals(getattr(self.nest, 'value', None), None) def test_nolock(self): self.assertEquals(getattr(self.nest, 'lock', None), None) class TestPriorityMap(unittest.TestCase): def setUp(self): self.pmap = self._makeOne() def tearDown(self): self.pmap.clear() def _makeOne(self): from ..plan import PriorityMap return PriorityMap def test_get_value(self): self.assertEquals(self.pmap.get_value(), {}) def test_get(self): self.assertEquals(self.pmap.get('foo'), {}) def test_set(self): self.pmap.set('foo', {'bar': 1}) self.assertEquals(self.pmap.get('foo'), {'bar': 1}) def test_clear(self): self.pmap.set('foo', {'bar': 1}) self.pmap.clear() self.assertEquals(self.pmap.value, {}) def test_get_entry(self): self.assertEquals(self.pmap.get_entry('foo', 'bar'), {}) def test_set_entry(self): self.pmap.set_entry('foo', 'bar', {'baz': 1}) self.assertEquals(self.pmap.get_entry('foo', 'bar'), {'baz': 1}) def test_clear_entry(self): self.pmap.set('foo', {'bar': 1}) self.pmap.clear_entry('foo') self.assertEquals(self.pmap.get('foo'), {}) class TestPriorityMapDefault(unittest.TestCase): def setUp(self): self.pmap = self._makeOne() def tearDown(self): self.pmap.clear() def _makeOne(self): from ..plan import PriorityMap return PriorityMap def test_empty(self): self.pmap.load_default() self.assertEquals(self.pmap.get_value(), {}) def test_load_failure(self): try: os.environ['ZCATALOGQUERYPLAN'] = 'Products.ZCatalog.invalid' self.pmap.load_default() self.assertEquals(self.pmap.get_value(), {}) finally: del os.environ['ZCATALOGQUERYPLAN'] def test_load(self): from ..plan import Benchmark try: os.environ['ZCATALOGQUERYPLAN'] = \ 'Products.ZCatalog.tests.queryplan.queryplan' self.pmap.load_default() expected = {'/folder/catalog': { 'VALUE_INDEXES': frozenset(['index1']), 'index1 index2': { 'index1': Benchmark(duration=2.0, hits=3, limit=True), 'index2': Benchmark(duration=1.5, hits=2, limit=False), }}} self.assertEquals(self.pmap.get_value(), expected) finally: del os.environ['ZCATALOGQUERYPLAN'] def test_load_from_path(self): from ..plan import Benchmark path = os.path.join(os.path.dirname(HERE), 'queryplan.py') self.pmap.load_from_path(path) expected = {'/folder/catalog': { 'VALUE_INDEXES': frozenset(['index1']), 'index1 index2': { 'index1': Benchmark(duration=2.0, hits=3, limit=True), 'index2': Benchmark(duration=1.5, hits=2, limit=False), }}} self.assertEquals(self.pmap.get_value(), expected) class TestReports(unittest.TestCase): def setUp(self): self.reports = self._makeOne() def tearDown(self): self.reports.clear() def _makeOne(self): from ..plan import Reports return Reports def test_value(self): self.assertEquals(self.reports.value, {}) def test_lock(self): from thread import LockType self.assertEquals(type(self.reports.lock), LockType) class TestCatalogPlan(cleanup.CleanUp, unittest.TestCase): def setUp(self): cleanup.CleanUp.setUp(self) from Products.ZCatalog.Catalog import Catalog self.cat = Catalog('catalog') def _makeOne(self, catalog=None, query=None): from ..plan import CatalogPlan if catalog is None: catalog = self.cat return CatalogPlan(catalog, query=query) def test_get_id(self): plan = self._makeOne() self.assertEquals(plan.get_id(), ('', 'NonPersistentCatalog')) def test_get_id_persistent(self): from Products.ZCatalog.ZCatalog import ZCatalog zcat = ZCatalog('catalog') plan = self._makeOne(zcat._catalog) self.assertEquals(plan.get_id(), ('catalog', )) def test_getCatalogPlan_empty(self): from Products.ZCatalog.ZCatalog import ZCatalog zcat = ZCatalog('catalog') self._makeOne(zcat._catalog) plan_str = zcat.getCatalogPlan() self.assertTrue('queryplan = {' in plan_str) def test_getCatalogPlan_full(self): from Products.ZCatalog.ZCatalog import ZCatalog zcat = ZCatalog('catalog') plan = self._makeOne(zcat._catalog, query={'index1': 1, 'index2': 2}) plan.start() plan.start_split('index1') time.sleep(0.111111) plan.stop_split('index1') plan.start_split('index2') time.sleep(0.222222) plan.stop_split('index2') plan.stop() plan_str = zcat.getCatalogPlan() self.assertTrue('queryplan = {' in plan_str) self.assertTrue('index1' in plan_str) def test_plan_empty(self): plan = self._makeOne() self.assertEquals(plan.plan(), None) def test_start(self): plan = self._makeOne() plan.start() self.assert_(plan.start_time <= time.time()) def test_start_split(self): plan = self._makeOne() plan.start_split('index1') self.assert_('index1' in plan.interim) def test_stop_split(self): plan = self._makeOne() plan.start_split('index1') plan.stop_split('index1') self.assert_('index1' in plan.interim) i1 = plan.interim['index1'] self.assert_(i1.start <= i1.end) self.assert_('index1' in plan.benchmark) def test_stop_split_sort_on(self): plan = self._makeOne() plan.start_split('sort_on') plan.stop_split('sort_on') self.assert_('sort_on' in plan.interim) so = plan.interim['sort_on'] self.assert_(so.start <= so.end) self.assert_('sort_on' not in plan.benchmark) def test_stop(self): plan = self._makeOne(query={'index1': 1, 'index2': 2}) plan.start() plan.start_split('index1') plan.stop_split('index1') plan.start_split('index1') plan.stop_split('index1') plan.start_split('sort_on') plan.stop_split('sort_on') time.sleep(0.02) # wait at least one Windows clock tick plan.stop() self.assert_(plan.duration > 0) self.assert_('index1' in plan.benchmark) self.assertEquals(plan.benchmark['index1'].hits, 2) self.assert_('index2' in plan.benchmark) self.assertEquals(plan.benchmark['index2'].hits, 0) self.assertEquals(set(plan.plan()), set(('index1', 'index2'))) def test_log(self): plan = self._makeOne(query={'index1': 1}) plan.threshold = 0.0 plan.start() plan.start_split('index1') plan.stop_split('index1') plan.stop() plan.log() report = plan.report() self.assertEquals(len(report), 1) self.assertEquals(report[0]['counter'], 2) plan.reset() self.assertEquals(len(plan.report()), 0) def test_valueindexes_get(self): plan = self._makeOne() self.assertEquals(plan.valueindexes(), frozenset()) def test_valueindexes_set(self): from ..plan import PriorityMap from ..plan import VALUE_INDEX_KEY plan = self._makeOne() indexes = frozenset(['index1', 'index2']) PriorityMap.set_entry(plan.cid, VALUE_INDEX_KEY, indexes) self.assertEquals(plan.valueindexes(), frozenset(indexes)) # Test the actual logic for determining value indexes # Test make_key class TestCatalogReport(cleanup.CleanUp, unittest.TestCase): def setUp(self): cleanup.CleanUp.setUp(self) from Products.ZCatalog.ZCatalog import ZCatalog self.zcat = ZCatalog('catalog') self.zcat.long_query_time = 0.0 self._add_indexes() for i in range(9): obj = dummy(i) self.zcat.catalog_object(obj, str(i)) def _add_indexes(self): from Products.PluginIndexes.FieldIndex.FieldIndex import FieldIndex from Products.PluginIndexes.KeywordIndex.KeywordIndex import \ KeywordIndex num = FieldIndex('num') self.zcat._catalog.addIndex('num', num) big = FieldIndex('big') self.zcat._catalog.addIndex('big', big) numbers = KeywordIndex('numbers') self.zcat._catalog.addIndex('numbers', numbers) def test_ReportLength(self): """ tests the report aggregation """ self.zcat.manage_resetCatalogReport() self.zcat.searchResults(numbers=4, sort_on='num') self.zcat.searchResults(numbers=1, sort_on='num') self.zcat.searchResults(numbers=3, sort_on='num') self.zcat.searchResults(big=True, sort_on='num') self.zcat.searchResults(big=True, sort_on='num') self.zcat.searchResults(big=False, sort_on='num') self.zcat.searchResults(num=[5, 4, 3], sort_on='num') self.zcat.searchResults(num=(3, 4, 5), sort_on='num') self.assertEqual(4, len(self.zcat.getCatalogReport())) def test_ReportCounter(self): """ tests the counter of equal queries """ self.zcat.manage_resetCatalogReport() self.zcat.searchResults(numbers=5, sort_on='num') self.zcat.searchResults(numbers=6, sort_on='num') self.zcat.searchResults(numbers=8, sort_on='num') r = self.zcat.getCatalogReport()[0] self.assertEqual(r['counter'], 3) def test_ReportKey(self): """ tests the query keys for uniqueness """ # query key 1 key = ('sort_on', ('big', 'True')) self.zcat.manage_resetCatalogReport() self.zcat.searchResults(big=True, sort_on='num') self.zcat.searchResults(big=True, sort_on='num') r = self.zcat.getCatalogReport()[0] self.assertEqual(r['query'], key) self.assertEqual(r['counter'], 2) # query key 2 key = ('sort_on', ('big', 'False')) self.zcat.manage_resetCatalogReport() self.zcat.searchResults(big=False, sort_on='num') r = self.zcat.getCatalogReport()[0] self.assertEqual(r['query'], key) self.assertEqual(r['counter'], 1) # query key 3 key = ('sort_on', ('num', '[3, 4, 5]')) self.zcat.manage_resetCatalogReport() self.zcat.searchResults(num=[5, 4, 3], sort_on='num') self.zcat.searchResults(num=(3, 4, 5), sort_on='num') r = self.zcat.getCatalogReport()[0] self.assertEqual(r['query'], key) self.assertEqual(r['counter'], 2) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/tests/__init__.py0000644000175000017500000000000212214017452026304 0ustar arnauarnau# zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/tests/queryplan.py0000644000175000017500000000035112214017452026574 0ustar arnauarnau# dumped at some point queryplan = { '/folder/catalog': { 'VALUE_INDEXES': frozenset(['index1']), 'index1 index2': { 'index1': (2.0, 3, True), 'index2': (1.5, 2, False), }, } }zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/tests/test_brains.py0000644000175000017500000001360212214017452027074 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unittests for Catalog brains """ import unittest from Acquisition import aq_base from Acquisition import aq_parent from Acquisition import Implicit from zExceptions import Unauthorized from ZODB.POSException import ConflictError from ..CatalogBrains import _GLOBALREQUEST_INSTALLED if _GLOBALREQUEST_INSTALLED: from zope.globalrequest import clearRequest from zope.globalrequest import setRequest _marker = object() class Happy(Implicit): """Happy content""" def __init__(self, id): self.id = id def check(self): pass class Secret(Happy): """Object that raises Unauthorized when accessed""" def check(self): raise Unauthorized class Conflicter(Happy): """Object that raises ConflictError when accessed""" def check(self): raise ConflictError class DummyRequest(object): def physicalPathToURL(self, path, relative=False): if not relative: path = 'http://superbad.com' + path return path class DummyRoot(Implicit): happy = Happy('happy') secret = Secret('secret') conflicter = Conflicter('conflicter') def unrestrictedTraverse(self, path, default=None): assert path in ['', ('',), ['']], path return self def restrictedTraverse(self, path, default=_marker): if path.startswith('/'): path = path[1:] try: ob = getattr(self, path).__of__(self) ob.check() return ob except AttributeError: if default is not _marker: return default raise class DummyCatalog(Implicit): _paths = ['/conflicter', '/happy', '/secret', '/zonked'] def getPhysicalRoot(self): return aq_parent(self) def getpath(self, rid): return self._paths[rid] def getobject(self, rid): return self.restrictedTraverse(self._paths[rid]) def resolve_url(self, path, REQUEST): # strip server part path = path[path.find('/', path.find('//') + 1):] return self.restrictedTraverse(path) class ConflictingCatalog(DummyCatalog): def getpath(self, rid): raise ConflictError class TestBrains(unittest.TestCase): def setUp(self): self.root = DummyRoot() self.cat = DummyCatalog().__of__(self.root) def _makeBrain(self, rid): from Products.ZCatalog.CatalogBrains import AbstractCatalogBrain class Brain(AbstractCatalogBrain): __record_schema__ = {'test_field': 0, 'data_record_id_': 1} return Brain(('test', rid)).__of__(self.cat) def testHasKey(self): b = self._makeBrain(1) self.assertTrue('test_field' in b) self.assertTrue('data_record_id_' in b) self.assertFalse('godel' in b) def testGetPath(self): b = [self._makeBrain(rid) for rid in range(3)] self.assertEqual(b[0].getPath(), '/conflicter') self.assertEqual(b[1].getPath(), '/happy') self.assertEqual(b[2].getPath(), '/secret') def testGetPathPropagatesConflictErrors(self): self.cat = ConflictingCatalog() b = self._makeBrain(0) self.assertRaises(ConflictError, b.getPath) def testGetURL(self): request = DummyRequest() b = self._makeBrain(0) self.root.REQUEST = request self.assertEqual(b.getURL(), 'http://superbad.com/conflicter') if _GLOBALREQUEST_INSTALLED: def testGetURL_catalog_as_utility(self): request = DummyRequest() b = self._makeBrain(0) setRequest(request) self.assertEqual(b.getURL(), 'http://superbad.com/conflicter') clearRequest() def testGetRID(self): b = self._makeBrain(42) self.assertEqual(b.getRID(), 42) def testGetObjectHappy(self): request = DummyRequest() b = self._makeBrain(1) self.root.REQUEST = request self.assertEqual(b.getPath(), '/happy') self.assertEqual(b.getObject().REQUEST, request) self.assertTrue(aq_base(b.getObject()) is aq_base(self.cat.getobject(1))) if _GLOBALREQUEST_INSTALLED: def testGetObjectHappy_catalog_as_utility(self): request = DummyRequest() b = self._makeBrain(1) setRequest(request) self.assertEqual(b.getPath(), '/happy') self.assertEqual(b.getObject().REQUEST, request) self.assertTrue(aq_base(b.getObject()) is aq_base(self.cat.getobject(1))) clearRequest() def testGetObjectPropagatesConflictErrors(self): b = self._makeBrain(0) self.assertEqual(b.getPath(), '/conflicter') self.assertRaises(ConflictError, b.getObject) def testGetObjectRaisesUnauthorized(self): b = self._makeBrain(2) self.assertEqual(b.getPath(), '/secret') self.assertRaises(Unauthorized, b.getObject) def testGetObjectRaisesNotFoundForMissing(self): from zExceptions import NotFound b = self._makeBrain(3) self.assertEqual(b.getPath(), '/zonked') self.assertRaises(AttributeError, self.cat.getobject, 3) self.assertRaises((NotFound, AttributeError, KeyError), b.getObject) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestBrains)) return suite zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/tests/test_zcatalog.py0000644000175000017500000003426212214017452027427 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ Unittests for ZCatalog """ import unittest from AccessControl.SecurityManagement import setSecurityManager from AccessControl.SecurityManagement import noSecurityManager from AccessControl import Unauthorized from Acquisition import Implicit import ExtensionClass from OFS.Folder import Folder as OFS_Folder class Folder(OFS_Folder): def __init__(self, id): self._setId(id) OFS_Folder.__init__(self) class zdummy(ExtensionClass.Base): def __init__(self, num): self.num = num def title(self): return '%d' % self.num class zdummyFalse(zdummy): def __nonzero__(self): return False class dummyLenFail(zdummy): def __init__(self, num, fail): zdummy.__init__(self, num) self.fail = fail def __len__(self): self.fail("__len__() was called") class dummyNonzeroFail(zdummy): def __init__(self, num, fail): zdummy.__init__(self, num) self.fail = fail def __nonzero__(self): self.fail("__nonzero__() was called") class FakeTraversalError(KeyError): """fake traversal exception for testing""" class fakeparent(Implicit): # fake parent mapping unrestrictedTraverse to # catalog.resolve_path as simulated by TestZCatalog marker = object() def __init__(self, d): self.d = d def unrestrictedTraverse(self, path, default=marker): result = self.d.get(path, default) if result is self.marker: raise FakeTraversalError(path) return result class PickySecurityManager: def __init__(self, badnames=[]): self.badnames = badnames def validateValue(self, value): return 1 def validate(self, accessed, container, name, value): if name not in self.badnames: return 1 raise Unauthorized(name) class ZCatalogBase(object): def _makeOne(self): from Products.ZCatalog.ZCatalog import ZCatalog return ZCatalog('Catalog') def _makeOneIndex(self, name): from Products.PluginIndexes.FieldIndex.FieldIndex import FieldIndex return FieldIndex(name) def setUp(self): self._catalog = self._makeOne() def tearDown(self): self._catalog = None class TestZCatalog(ZCatalogBase, unittest.TestCase): def setUp(self): ZCatalogBase.setUp(self) self._catalog.resolve_path = self._resolve_num from Products.PluginIndexes.KeywordIndex.KeywordIndex import \ KeywordIndex title = KeywordIndex('title') self._catalog.addIndex('title', title) self._catalog.addColumn('title') self.upper = 10 self.d = {} for x in range(0, self.upper): # make uid a string of the number ob = zdummy(x) self.d[str(x)] = ob self._catalog.catalog_object(ob, str(x)) def _resolve_num(self, num): return self.d[num] def test_interfaces(self): from Products.ZCatalog.interfaces import IZCatalog from Products.ZCatalog.ZCatalog import ZCatalog from zope.interface.verify import verifyClass verifyClass(IZCatalog, ZCatalog) def test_len(self): self.assertEquals(len(self._catalog), self.upper) # manage_edit # manage_subbingToggle def testBooleanEvalOn_manage_catalogObject(self): self.d['11'] = dummyLenFail(11, self.fail) self.d['12'] = dummyNonzeroFail(12, self.fail) # create a fake response that doesn't bomb on manage_catalogObject() class myresponse: def redirect(self, url): pass # this next call should not fail self._catalog.manage_catalogObject(None, myresponse(), 'URL1', urls=('11', '12')) # manage_uncatalogObject # manage_catalogReindex def testBooleanEvalOn_refreshCatalog_getobject(self): # wrap catalog under the fake parent providing unrestrictedTraverse() catalog = self._catalog.__of__(fakeparent(self.d)) # replace entries to test refreshCatalog self.d['0'] = dummyLenFail(0, self.fail) self.d['1'] = dummyNonzeroFail(1, self.fail) # this next call should not fail catalog.refreshCatalog() for uid in ('0', '1'): rid = catalog.getrid(uid) # neither should these catalog.getobject(rid) # manage_catalogClear # manage_catalogFoundItems # manage_addColumn # manage_delColumn # manage_addIndex # manage_delIndex # manage_clearIndex def testReindexIndexDoesntDoMetadata(self): self.d['0'].num = 9999 self._catalog.reindexIndex('title', {}) data = self._catalog.getMetadataForUID('0') self.assertEqual(data['title'], '0') def testReindexIndexesFalse(self): # setup false_id = self.upper + 1 ob = zdummyFalse(false_id) self.d[str(false_id)] = ob self._catalog.catalog_object(ob, str(false_id)) # test, object evaluates to false; there was bug which caused the # object to be removed from index ob.num = 9999 self._catalog.reindexIndex('title', {}) result = self._catalog(title='9999') self.assertEquals(1, len(result)) # manage_reindexIndex # catalog_object # uncatalog_object # uniqueValuesFor # getpath # getrid def test_getobject_traversal(self): # getobject doesn't mask TraversalErrors and doesn't delegate to # resolve_url # wrap catalog under the fake parent providing unrestrictedTraverse() catalog = self._catalog.__of__(fakeparent(self.d)) # make resolve_url fail if ZCatalog falls back on it def resolve_url(path, REQUEST): self.fail(".resolve_url() should not be called by .getobject()") catalog.resolve_url = resolve_url # traversal should work at first rid0 = catalog.getrid('0') # lets set it up so the traversal fails del self.d['0'] self.assertRaises(FakeTraversalError, catalog.getobject, rid0, REQUEST=object()) # and if there is a None at the traversal point, that's where it # should return self.d['0'] = None self.assertEquals(catalog.getobject(rid0), None) def testGetMetadataForUID(self): testNum = str(self.upper - 3) # as good as any.. data = self._catalog.getMetadataForUID(testNum) self.assertEqual(data['title'], testNum) def testGetIndexDataForUID(self): testNum = str(self.upper - 3) data = self._catalog.getIndexDataForUID(testNum) self.assertEqual(data['title'][0], testNum) def testUpdateMetadata(self): self._catalog.catalog_object(zdummy(1), '1') data = self._catalog.getMetadataForUID('1') self.assertEqual(data['title'], '1') self._catalog.catalog_object(zdummy(2), '1', update_metadata=0) data = self._catalog.getMetadataForUID('1') self.assertEqual(data['title'], '1') self._catalog.catalog_object(zdummy(2), '1', update_metadata=1) data = self._catalog.getMetadataForUID('1') self.assertEqual(data['title'], '2') # update_metadata defaults to true, test that here self._catalog.catalog_object(zdummy(1), '1') data = self._catalog.getMetadataForUID('1') self.assertEqual(data['title'], '1') # getMetadataForRID # getIndexDataForRID # schema # indexes # index_objects # getIndexObjects # _searchable_arguments # _searchable_result_columns def testSearchResults(self): query = {'title': ['5', '6', '7']} sr = self._catalog.searchResults(query) self.assertEqual(len(sr), 3) def testCall(self): query = {'title': ['5', '6', '7']} sr = self._catalog(query) self.assertEqual(len(sr), 3) def testSearch(self): query = {'title': ['5', '6', '7']} sr = self._catalog.search(query) self.assertEqual(len(sr), 3) # resolve_url # resolve_path # manage_normalize_paths # manage_setProgress # _getProgressThreshold class TestAddDelColumnIndex(ZCatalogBase, unittest.TestCase): def testAddIndex(self): self._catalog.addIndex('id', self._makeOneIndex('id')) self.assert_('id' in self._catalog.indexes()) def testDelIndex(self): self._catalog.addIndex('title', self._makeOneIndex('title')) self.assert_('title' in self._catalog.indexes()) self._catalog.delIndex('title') self.assert_('title' not in self._catalog.indexes()) def testClearIndex(self): self._catalog.addIndex('title', self._makeOneIndex('title')) idx = self._catalog._catalog.getIndex('title') for x in range(10): ob = zdummy(x) self._catalog.catalog_object(ob, str(x)) self.assertEquals(len(idx), 10) self._catalog.clearIndex('title') self.assertEquals(len(idx), 0) def testAddColumn(self): self._catalog.addColumn('num', default_value=0) self.assert_('num' in self._catalog.schema()) def testDelColumn(self): self._catalog.addColumn('title') self._catalog.delColumn('title') self.assert_('title' not in self._catalog.schema()) class TestZCatalogGetObject(ZCatalogBase, unittest.TestCase): # Test what objects are returned by brain.getObject() def setUp(self): ZCatalogBase.setUp(self) self._catalog.addIndex('id', self._makeOneIndex('id')) root = Folder('') root.getPhysicalRoot = lambda: root self.root = root self.root.catalog = self._catalog def tearDown(self): ZCatalogBase.tearDown(self) noSecurityManager() def test_getObject_found(self): # Check normal traversal root = self.root catalog = root.catalog root.ob = Folder('ob') catalog.catalog_object(root.ob) brain = catalog.searchResults({'id': 'ob'})[0] self.assertEqual(brain.getPath(), '/ob') self.assertEqual(brain.getObject().getId(), 'ob') def test_getObject_missing_raises_NotFound(self): # Check that if the object is missing we raise from zExceptions import NotFound root = self.root catalog = root.catalog root.ob = Folder('ob') catalog.catalog_object(root.ob) brain = catalog.searchResults({'id': 'ob'})[0] del root.ob self.assertRaises((NotFound, AttributeError, KeyError), brain.getObject) def test_getObject_restricted_raises_Unauthorized(self): # Check that if the object's security does not allow traversal, # None is returned root = self.root catalog = root.catalog root.fold = Folder('fold') root.fold.ob = Folder('ob') catalog.catalog_object(root.fold.ob) brain = catalog.searchResults({'id': 'ob'})[0] # allow all accesses pickySecurityManager = PickySecurityManager() setSecurityManager(pickySecurityManager) self.assertEqual(brain.getObject().getId(), 'ob') # disallow just 'ob' access pickySecurityManager = PickySecurityManager(['ob']) setSecurityManager(pickySecurityManager) self.assertRaises(Unauthorized, brain.getObject) # disallow just 'fold' access pickySecurityManager = PickySecurityManager(['fold']) setSecurityManager(pickySecurityManager) ob = brain.getObject() self.assertFalse(ob is None) self.assertEqual(ob.getId(), 'ob') # Now test _unrestrictedGetObject def test_unrestrictedGetObject_found(self): # Check normal traversal root = self.root catalog = root.catalog root.ob = Folder('ob') catalog.catalog_object(root.ob) brain = catalog.searchResults({'id': 'ob'})[0] self.assertEqual(brain.getPath(), '/ob') self.assertEqual(brain._unrestrictedGetObject().getId(), 'ob') def test_unrestrictedGetObject_restricted(self): # Check that if the object's security does not allow traversal, # it's still is returned root = self.root catalog = root.catalog root.fold = Folder('fold') root.fold.ob = Folder('ob') catalog.catalog_object(root.fold.ob) brain = catalog.searchResults({'id': 'ob'})[0] # allow all accesses pickySecurityManager = PickySecurityManager() setSecurityManager(pickySecurityManager) self.assertEqual(brain._unrestrictedGetObject().getId(), 'ob') # disallow just 'ob' access pickySecurityManager = PickySecurityManager(['ob']) setSecurityManager(pickySecurityManager) self.assertEqual(brain._unrestrictedGetObject().getId(), 'ob') # disallow just 'fold' access pickySecurityManager = PickySecurityManager(['fold']) setSecurityManager(pickySecurityManager) self.assertEqual(brain._unrestrictedGetObject().getId(), 'ob') def test_unrestrictedGetObject_missing_raises_NotFound(self): # Check that if the object is missing we raise from zExceptions import NotFound root = self.root catalog = root.catalog root.ob = Folder('ob') catalog.catalog_object(root.ob) brain = catalog.searchResults({'id': 'ob'})[0] del root.ob self.assertRaises((NotFound, AttributeError, KeyError), brain._unrestrictedGetObject) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestZCatalog)) suite.addTest(unittest.makeSuite(TestAddDelColumnIndex)) suite.addTest(unittest.makeSuite(TestZCatalogGetObject)) return suite zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/0000755000175000017500000000000012214017452024001 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/catalogObjectInformation.dtml0000644000175000017500000000607712214017452031644 0ustar arnauarnau
 
 
Catalog record at

The goal of this page is to provide basic debugging information about what is in the Catalog on a specific object. Listed below is all the information that the Catalog currently contains for the above specified object. This information should match what is currently in the instance of that object.


Metadata Contents

Metadata is the information that the Catalog keeps inside of its internal structure so that it can answer questions quickly. This is then returned in the "brain" that the Catalog gives back during searches.


  Key Value
  &dtml-sequence-key; &dtml-sequence-item;

Index Contents

The following table gives information that is contained in the various indexes of the Catalog. In the case of Keyword or Text indexes, the results are returned as a tuple, and will show as '(one, two, three)', rather than in a more normal way.


  Key Value
  &dtml-sequence-key; &dtml-sequence-item;
zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/catalogAdvanced.dtml0000644000175000017500000000732412214017452027731 0ustar arnauarnau
Catalog Maintenance

Updating the catalog will update all catalog records and remove invalid records. It does this by clearing all indexes and re-cataloging all currently indexed objects.

Clearing the catalog will remove all entries.

Log progress of reindexing every N objects to the Zope logger (set to 0 to disable logging)

">
Subtransactions

Subtransactions allow Zope to commit small parts of a transaction over a period of time instead of all at once. For ZCatalog, this means using subtransactions can signficantly reduce the memory requirements needed to index huge amounts of text all at once. Currently, subtransactions are only applied to text indexes.

If enabled, subtransactions will reduce the memory requirements of ZCatalog, but at the expense of speed. If you choose to enable subtransactions, you can adjust how often ZCatalog commits a subtransactions by adjusting the threshold below.

Subtransactions are Enabled Disabled

The Subtransaction threshold is the number of objects cataloged in the context of a single transaction that the catalog will index before it commits a subtransaction. If this number is low, the Catalog will take longer to index but consume less memory. If this number is higher, the Catalog will index quickly but consume much more memory.

zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/catalogSchema.dtml0000644000175000017500000000470512214017452027424 0ustar arnauarnau

This list defines what per object meta data the Catalog will store. When objects get cataloged, the values of any attributes they may have which match a name in this list will get stored in a table in the Catalog. The Catalog then uses this information to create result objects that are returned whenever the catalog is searched. It is important to understand that when the Catalog is searched, it returns a list of result objects, not the cataloged objects themselves, so if you want to use the value of an object's attribute in the result of a search, that attribute must be in this list

It is generally a good idea to keep this list lightweight. It is useful, for example, to keep the 'summary' meta data of a text document (like the first 200 characters) but not the text content in it's entirety (it is useful in this example to index the text contents, which is configured in the Indexes View tab). This way, the summary data may be shown in the search results.

&dtml-sequence-item;
There are currently no metadata elements.

Add Metadata
zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/catalogStatus.dtml0000644000175000017500000000425712214017452027511 0ustar arnauarnau

Subtransactions allow Zope to commit small parts of a transaction over a period of time instead of all at once. For ZCatalog, this means using subtransactions can signficantly reduce the memory requirements needed to index huge amounts of text all at once.

If enabled, subtransactions will reduce the memory requirements of ZCatalog, but at the expense of speed. If you choose to enable subtransactions, you can adjust how often ZCatalog commits a subtransactions by adjusting the threshold below.

Subtransactions are Enabled Disabled

Subtransactions Subtransactions

The Subtransaction threshold is the number of words the catalog will index before it commits a subtransaction. If this number is low, the Catalog will take longer to index but consume less memory. If this number is higher, the Catalog will index quickly but consume much more memory.

Subtransaction threshold:

Index Status

  • object are indexed in
zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/editCatalogerForm.dtml0000644000175000017500000000071412214017452030260 0ustar arnauarnau

This element controls which Catalog this object will try index and unindex itself to.

Use Catalog:

zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/addZCatalog.dtml0000644000175000017500000000156512214017452027047 0ustar arnauarnau
Id
Title
zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/addIndexForm.dtml0000644000175000017500000000134512214017452027232 0ustar arnauarnau
Id
zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/catalogReport.dtml0000644000175000017500000001047512214017452027500 0ustar arnauarnau

The query report shows catalog queries that perform slowly. For each index there's an additional entry for the time the intersection of the index result with the result by the other indexes took. These are marked with a #intersection postfix. The time reported for the index is the sum of the intersection time and the time the index itself took. Subtract the intersection time, if you want to know the pure index time.

Mean duration [ms]
Hits
Query key
Recent
&dtml-counter;
&dtml-query;
ms [ &dtml-id;: ms, ]

Resetting the catalog report will reinitialize the report log.

Report is empty.
Settings
Threshold (in seconds)

Only queries whose execution takes longer than the configured threshold are considered being slow. (Default 0.1 seconds).

zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/catalogView.dtml0000644000175000017500000000744412214017452027141 0ustar arnauarnau

Path filter

Path:

The path filter is disabled. To enable the path filter, add a PathIndex called "path" to this catalog.

Objects in this catalog

The catalog "&dtml-id;" contains record(s) in the path "&dtml-filterpath;".

 
Object Identifier
Type
 
Unknown

There are no objects in the Catalog.

zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/catalogIndexes.dtml0000644000175000017500000001531312214017452027620 0ustar arnauarnau

This list defines what indexes the Catalog will contain. When objects get cataloged, the values of any attributes which match an index in this list will get indexed.

If you add indexes to a Catalog which contains indexed objects, you MUST at the least re-index your newly added index. You may want to update the whole Catalog.


 
1">
 
(pre-2.4 index)
There are currently no indexes

zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/catalogPlan.dtml0000644000175000017500000000046412214017452027114 0ustar arnauarnau

The query plan shows the actual query plan of the current process.

zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/dtml/catalogFind.dtml0000644000175000017500000000501512214017452027077 0ustar arnauarnau

Use this form to locate objects to be cataloged. Those objects which are found will be automatically added to the catalog.

Find objects of type:
with ids:
containing:
expr:
modified:
where the roles:
have permission:
zope2.13-2.13.21/source/Products.ZCatalog/src/Products/ZCatalog/CatalogAwareness.py0000644000175000017500000001311312214017452026635 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ZCatalog Findable class **NOTE**: This module is deprecated, and should only be used for backward-compatibility. All new code should use CatalogPathAwareness. """ import urllib import warnings from Acquisition import aq_base from App.special_dtml import DTMLFile class CatalogAware: """ This is a Mix-In class to make objects automaticly catalog and uncatalog themselves in Zope, and to provide some other basic attributes that are useful to catalog. Note that if your class subclasses CatalogAware, it will only catalog itself when it is added or copied in Zope. If you make changes to your own object, you are responsible for calling your object's index_object method. """ meta_type='CatalogAware' default_catalog='Catalog' manage_editCatalogerForm=DTMLFile('dtml/editCatalogerForm', globals()) def _warn_deprecated(self): warnings.warn('The Products.ZCatalog.CatalogAwareness module is ' 'deprecated and will be removed in Zope 2.14. Please ' 'use event subscribers for zope.lifecycle events to ' 'automatically index and unindex your objects.', DeprecationWarning, stacklevel=3) def manage_editCataloger(self, default, REQUEST=None): """ """ self.default_catalog=default message = "Your changes have been saved" if REQUEST is not None: return self.manage_main(self, REQUEST, manage_tabs_message=message) def manage_afterAdd(self, item, container): self.index_object() for object in self.objectValues(): try: s = object._p_changed except Exception: s = 0 object.manage_afterAdd(item, container) if s is None: object._p_deactivate() def manage_afterClone(self, item): self.index_object() for object in self.objectValues(): try: s = object._p_changed except Exception: s = 0 object.manage_afterClone(item) if s is None: object._p_deactivate() def manage_beforeDelete(self, item, container): self.unindex_object() for object in self.objectValues(): try: s = object._p_changed except Exception: s = 0 object.manage_beforeDelete(item, container) if s is None: object._p_deactivate() def creator(self): """Return a sequence of user names who have the local Owner role on an object. The name creator is used for this method to conform to Dublin Core.""" users=[] for user, roles in self.get_local_roles(): if 'Owner' in roles: users.append(user) return ', '.join(users) def onDeleteObject(self): """Object delete handler. I think this is obsoleted by manage_beforeDelete """ self.unindex_object() def url(self, ftype=urllib.splittype, fhost=urllib.splithost): """Return a SCRIPT_NAME-based url for an object.""" if hasattr(self, 'DestinationURL') and \ callable(self.DestinationURL): url='%s/%s' % (self.DestinationURL(), self.id) else: url = self.absolute_url() type, uri = ftype(url) host, uri = fhost(uri) script_name = self.REQUEST['SCRIPT_NAME'] if script_name: uri = filter(None, uri.split(script_name))[0] if not uri: uri = '/' if uri[0] != '/': uri = '/' + uri return urllib.unquote(uri) def summary(self, num=200): """Return a summary of the text content of the object.""" if not hasattr(self, 'text_content'): return '' attr = getattr(self, 'text_content') if callable(attr): text = attr() else: text = attr n = min(num, len(text)) return text[:n] def index_object(self): """A common method to allow Findables to index themselves.""" self._warn_deprecated() catalog = getattr(self, self.default_catalog, None) if catalog is not None: catalog.catalog_object(self, self.url()) def unindex_object(self): """A common method to allow Findables to unindex themselves.""" self._warn_deprecated() catalog = getattr(self, self.default_catalog, None) if catalog is not None: catalog.uncatalog_object(self.url()) def reindex_object(self): """ Suprisingly useful """ self.unindex_object() self.index_object() def reindex_all(self, obj=None): """ """ if obj is None: obj = self if hasattr(aq_base(obj), 'index_object'): obj.index_object() if hasattr(aq_base(obj), 'objectValues'): for item in obj.objectValues(): self.reindex_all(item) return 'done!' zope2.13-2.13.21/source/Products.ZCatalog/src/Products/__init__.py0000644000175000017500000000007012214017452023443 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/0000755000175000017500000000000012214017452024113 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/0000755000175000017500000000000012214017452025760 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/__init__.py0000644000175000017500000000002312214017452030064 0ustar arnauarnau# Empty on purpose zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/README.txt0000644000175000017500000000126212214017452027457 0ustar arnauarnauDateIndex README Overview Normal FieldIndexes *can* be used to index values which are DateTime instances, but they are hideously expensive: o DateTime instances are *huge*, both in RAM and on disk. o DateTime instances maintain an absurd amount of precision, far beyond any reasonable search criteria for "normal" cases. DateIndex is a pluggable index which addresses these two issues as follows: o It normalizes the indexed value to an integer representation with a granularity of one minute. o It normalizes the 'query' value into the same form. o Objects which return 'None' for the index query are omitted from the index. zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/DateIndex.py0000644000175000017500000002172712214017452030210 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Date index. """ import time from logging import getLogger from datetime import date, datetime from datetime import tzinfo, timedelta from App.special_dtml import DTMLFile from BTrees.IIBTree import IIBTree from BTrees.IIBTree import IISet from BTrees.IIBTree import union from BTrees.IIBTree import intersection from BTrees.IIBTree import multiunion from BTrees.IOBTree import IOBTree from BTrees.Length import Length from DateTime.DateTime import DateTime from OFS.PropertyManager import PropertyManager from ZODB.POSException import ConflictError from zope.interface import implements from Products.PluginIndexes.common import safe_callable from Products.PluginIndexes.common.UnIndex import UnIndex from Products.PluginIndexes.common.util import parseIndexRequest from Products.PluginIndexes.interfaces import IDateIndex LOG = getLogger('DateIndex') _marker = [] ############################################################################### # copied from Python 2.3 datetime.tzinfo docs # A class capturing the platform's idea of local time. ZERO = timedelta(0) STDOFFSET = timedelta(seconds = -time.timezone) if time.daylight: DSTOFFSET = timedelta(seconds = -time.altzone) else: DSTOFFSET = STDOFFSET DSTDIFF = DSTOFFSET - STDOFFSET MAX32 = int(2**31 - 1) class LocalTimezone(tzinfo): def utcoffset(self, dt): if self._isdst(dt): return DSTOFFSET else: return STDOFFSET def dst(self, dt): if self._isdst(dt): return DSTDIFF else: return ZERO def tzname(self, dt): return time.tzname[self._isdst(dt)] def _isdst(self, dt): tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.weekday(), 0, -1) stamp = time.mktime(tt) tt = time.localtime(stamp) return tt.tm_isdst > 0 Local = LocalTimezone() ############################################################################### class DateIndex(UnIndex, PropertyManager): """Index for dates. """ implements(IDateIndex) meta_type = 'DateIndex' query_options = ('query', 'range') index_naive_time_as_local = True # False means index as UTC _properties=({'id':'index_naive_time_as_local', 'type':'boolean', 'mode':'w'},) manage = manage_main = DTMLFile( 'dtml/manageDateIndex', globals() ) manage_browse = DTMLFile('../dtml/browseIndex', globals()) manage_main._setName( 'manage_main' ) manage_options = ( { 'label' : 'Settings' , 'action' : 'manage_main' }, {'label': 'Browse', 'action': 'manage_browse', }, ) + PropertyManager.manage_options def clear( self ): """ Complete reset """ self._index = IOBTree() self._unindex = IIBTree() self._length = Length() def index_object( self, documentId, obj, threshold=None ): """index an object, normalizing the indexed value to an integer o Normalized value has granularity of one minute. o Objects which have 'None' as indexed value are *omitted*, by design. """ returnStatus = 0 try: date_attr = getattr( obj, self.id ) if safe_callable( date_attr ): date_attr = date_attr() ConvertedDate = self._convert( value=date_attr, default=_marker ) except AttributeError: ConvertedDate = _marker oldConvertedDate = self._unindex.get( documentId, _marker ) if ConvertedDate != oldConvertedDate: if oldConvertedDate is not _marker: self.removeForwardIndexEntry(oldConvertedDate, documentId) if ConvertedDate is _marker: try: del self._unindex[documentId] except ConflictError: raise except: LOG.error("Should not happen: ConvertedDate was there," " now it's not, for document with id %s" % documentId) if ConvertedDate is not _marker: self.insertForwardIndexEntry( ConvertedDate, documentId ) self._unindex[documentId] = ConvertedDate returnStatus = 1 return returnStatus def _apply_index(self, request, resultset=None): """Apply the index to query parameters given in the argument Normalize the 'query' arguments into integer values at minute precision before querying. """ record = parseIndexRequest(request, self.id, self.query_options) if record.keys is None: return None keys = map( self._convert, record.keys ) index = self._index r = None opr = None #experimental code for specifing the operator operator = record.get( 'operator', self.useOperator ) if not operator in self.operators : raise RuntimeError("operator not valid: %s" % operator) # depending on the operator we use intersection or union if operator=="or": set_func = union else: set_func = intersection # range parameter range_arg = record.get('range',None) if range_arg: opr = "range" opr_args = [] if range_arg.find("min") > -1: opr_args.append("min") if range_arg.find("max") > -1: opr_args.append("max") if record.get('usage',None): # see if any usage params are sent to field opr = record.usage.lower().split(':') opr, opr_args = opr[0], opr[1:] if opr=="range": # range search if 'min' in opr_args: lo = min(keys) else: lo = None if 'max' in opr_args: hi = max(keys) else: hi = None if hi: setlist = index.values(lo,hi) else: setlist = index.values(lo) r = multiunion(setlist) else: # not a range search for key in keys: set = index.get(key, None) if set is not None: if isinstance(set, int): set = IISet((set,)) else: # set can't be bigger than resultset set = intersection(set, resultset) r = set_func(r, set) if isinstance(r, int): r = IISet((r,)) if r is None: return IISet(), (self.id,) else: return r, (self.id,) def _convert( self, value, default=None ): """Convert Date/Time value to our internal representation""" # XXX: Code patched 20/May/2003 by Kiran Jonnalagadda to # convert dates to UTC first. if isinstance(value, DateTime): t_tup = value.toZone('UTC').parts() elif isinstance(value, (float, int)): t_tup = time.gmtime( value ) elif isinstance(value, str) and value: t_obj = DateTime( value ).toZone('UTC') t_tup = t_obj.parts() elif isinstance(value, datetime): if self.index_naive_time_as_local and value.tzinfo is None: value = value.replace(tzinfo=Local) # else if tzinfo is None, naive time interpreted as UTC t_tup = value.utctimetuple() elif isinstance(value, date): t_tup = value.timetuple() else: return default yr = t_tup[0] mo = t_tup[1] dy = t_tup[2] hr = t_tup[3] mn = t_tup[4] t_val = ( ( ( ( yr * 12 + mo ) * 31 + dy ) * 24 + hr ) * 60 + mn ) if t_val > MAX32: # t_val must be integer fitting in the 32bit range raise OverflowError( "%s is not within the range of indexable dates (index: %s)" % (value, self.id)) return t_val manage_addDateIndexForm = DTMLFile( 'dtml/addDateIndex', globals() ) def manage_addDateIndex( self, id, REQUEST=None, RESPONSE=None, URL3=None): """Add a Date index""" return self.manage_addIndex(id, 'DateIndex', extra=None, \ REQUEST=REQUEST, RESPONSE=RESPONSE, URL1=URL3) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/tests/0000755000175000017500000000000012214017452027122 5ustar arnauarnau././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/tests/test_DateIndex.pyzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/tests/test_DateIndex.0000644000175000017500000002543312214017452032036 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """DateIndex unit tests. """ import unittest class Dummy: def __init__(self, name, date): self._name = name self._date = date def name(self): return self._name def date(self): return self._date def __str__(self): return "" % (self._name, str(self._date)) ############################################################################### # excerpted from the Python module docs ############################################################################### def _getEastern(): from datetime import date from datetime import datetime from datetime import timedelta from datetime import tzinfo ZERO = timedelta(0) HOUR = timedelta(hours=1) def first_sunday_on_or_after(dt): days_to_go = 6 - dt.weekday() if days_to_go: dt += timedelta(days_to_go) return dt # In the US, DST starts at 2am (standard time) on the first Sunday in # April... DSTSTART = datetime(1, 4, 1, 2) # and ends at 2am (DST time; 1am standard time) on the last Sunday of # October, which is the first Sunday on or after Oct 25. DSTEND = datetime(1, 10, 25, 1) class USTimeZone(tzinfo): def __init__(self, hours, reprname, stdname, dstname): self.stdoffset = timedelta(hours=hours) self.reprname = reprname self.stdname = stdname self.dstname = dstname def __repr__(self): return self.reprname def tzname(self, dt): if self.dst(dt): return self.dstname else: return self.stdname def utcoffset(self, dt): return self.stdoffset + self.dst(dt) def dst(self, dt): if dt is None or dt.tzinfo is None: # An exception may be sensible here, in one or both cases. # It depends on how you want to treat them. The default # fromutc() implementation (called by the default astimezone() # implementation) passes a datetime with dt.tzinfo is self. return ZERO assert dt.tzinfo is self # Find first Sunday in April & the last in October. start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) # Can't compare naive to aware objects, so strip the timezone from # dt first. if start <= dt.replace(tzinfo=None) < end: return HOUR else: return ZERO return USTimeZone(-5, "Eastern", "EST", "EDT") ############################################################################### class DI_Tests(unittest.TestCase): def _getTargetClass(self): from Products.PluginIndexes.DateIndex.DateIndex import DateIndex return DateIndex def _makeOne(self, id='date'): return self._getTargetClass()(id) def _getValues(self): from DateTime import DateTime from datetime import date from datetime import datetime return [ (0, Dummy('a', None)), # None (1, Dummy('b', DateTime(0))), # 1055335680 (2, Dummy('c', DateTime('2002-05-08 15:16:17'))), # 1072667236 (3, Dummy('d', DateTime('2032-05-08 15:16:17'))), # 1088737636 (4, Dummy('e', DateTime('2062-05-08 15:16:17'))), # 1018883325 (5, Dummy('e', DateTime('2062-05-08 15:16:17'))), # 1018883325 (6, Dummy('f', 1072742620.0)), # 1073545923 (7, Dummy('f', 1072742900)), # 1073545928 (8, Dummy('g', date(2034,2,5))), # 1073599200 (9, Dummy('h', datetime(2034,2,5,15,20,5))), # (varies) (10, Dummy('i', datetime(2034,2,5,10,17,5, tzinfo=_getEastern()))), # 1073600117 ] def _populateIndex(self, index): for k, v in self._getValues(): index.index_object(k, v) def _checkApply(self, index, req, expectedValues): result, used = index._apply_index(req) if hasattr(result, 'keys'): result = result.keys() self.assertEqual(used, ('date',)) self.assertEqual(len(result), len(expectedValues), '%s | %s' % (result, expectedValues)) for k, v in expectedValues: self.assertTrue(k in result) def _convert(self, dt): from time import gmtime from datetime import date from datetime import datetime from Products.PluginIndexes.DateIndex.DateIndex import Local if isinstance(dt, (float, int)): yr, mo, dy, hr, mn = gmtime(dt)[:5] elif type(dt) is date: yr, mo, dy, hr, mn = dt.timetuple()[:5] elif type(dt) is datetime: if dt.tzinfo is None: # default behavior of index dt = dt.replace(tzinfo=Local) yr, mo, dy, hr, mn = dt.utctimetuple()[:5] else: yr, mo, dy, hr, mn = dt.toZone('UTC').parts()[:5] return (((yr * 12 + mo) * 31 + dy) * 24 + hr) * 60 + mn def test_interfaces(self): from Products.PluginIndexes.interfaces import IDateIndex from Products.PluginIndexes.interfaces import IPluggableIndex from Products.PluginIndexes.interfaces import ISortIndex from Products.PluginIndexes.interfaces import IUniqueValueIndex from zope.interface.verify import verifyClass verifyClass(IDateIndex, self._getTargetClass()) verifyClass(IPluggableIndex, self._getTargetClass()) verifyClass(ISortIndex, self._getTargetClass()) verifyClass(IUniqueValueIndex, self._getTargetClass()) def test_empty(self): from DateTime import DateTime index = self._makeOne() self.assertEqual(len(index), 0) self.assertEqual(len(index.referencedObjects()), 0) self.assertTrue(index.getEntryForObject(1234) is None) marker = [] self.assertTrue(index.getEntryForObject(1234, marker) is marker) index.unindex_object(1234) # shouldn't throw self.assertTrue(index.hasUniqueValuesFor('date')) self.assertFalse(index.hasUniqueValuesFor('foo')) self.assertEqual(len(index.uniqueValues('date')), 0) self.assertTrue(index._apply_index({'zed': 12345}) is None) self._checkApply(index, {'date': DateTime(0)}, []) self._checkApply(index, {'date': {'query': DateTime('2032-05-08 15:16:17'), 'range': 'min'}}, []) self._checkApply(index, {'date': {'query': DateTime('2032-05-08 15:16:17'), 'range': 'max'}}, []) self._checkApply(index, {'date': {'query':(DateTime('2002-05-08 15:16:17'), DateTime('2062-05-08 15:16:17')), 'range': 'min:max'}}, []) def test_retrieval( self ): from DateTime import DateTime index = self._makeOne() self._populateIndex(index) values = self._getValues() self.assertEqual(len(index), len(values) - 2) # One dupe, one empty self.assertEqual(len(index.referencedObjects()), len(values) - 1) # One empty self.assertTrue(index.getEntryForObject(1234) is None) marker = [] self.assertTrue(index.getEntryForObject(1234, marker) is marker) index.unindex_object(1234) # shouldn't throw for k, v in values: if v.date(): self.assertEqual(index.getEntryForObject(k), self._convert(v.date())) self.assertEqual(len(index.uniqueValues('date')), len(values) - 2) self.assertTrue(index._apply_index({'bar': 123}) is None) self._checkApply(index, {'date': DateTime(0)}, values[1:2]) self._checkApply(index, {'date': {'query': DateTime('2032-05-08 15:16:17'), 'range': 'min'}}, values[3:6] + values[8:]) self._checkApply(index, {'date': {'query': DateTime('2032-05-08 15:16:17'), 'range': 'max'}}, values[1:4] + values[6:8]) self._checkApply(index, {'date': {'query':(DateTime('2002-05-08 15:16:17'), DateTime('2062-05-08 15:16:17')), 'range': 'min:max'}}, values[2:] ) self._checkApply(index, {'date': 1072742620.0}, [values[6]]) self._checkApply(index, {'date': 1072742900}, [values[7]]) def test_naive_convert_to_utc(self): index = self._makeOne() values = self._getValues() index.index_naive_time_as_local = False self._populateIndex(index) for k, v in values[9:]: # assert that the timezone is effectively UTC for item 9, # and still correct for item 10 yr, mo, dy, hr, mn = v.date().utctimetuple()[:5] val = (((yr * 12 + mo) * 31 + dy) * 24 + hr) * 60 + mn self.assertEqual(index.getEntryForObject(k), val) def test_removal(self): """ DateIndex would hand back spurious entries when used as a sort_index, because it previously was not removing entries from the _unindex when indexing an object with a value of None. The catalog consults a sort_index's documentToKeyMap() to build the brains. """ values = self._getValues() index = self._makeOne() self._populateIndex(index) self._checkApply(index, {'date': 1072742900}, [values[7]]) index.index_object(7, None) self.assertFalse(7 in index.documentToKeyMap().keys()) def test_suite(): suite = unittest.TestSuite() suite.addTest( unittest.makeSuite( DI_Tests ) ) return suite zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/tests/__init__.py0000644000175000017500000000125312214017452031234 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This file is needed to make this a package. zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/dtml/0000755000175000017500000000000012214017452026720 5ustar arnauarnau././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/dtml/addDateIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/dtml/addDateIndex.dtm0000644000175000017500000000172112214017452031745 0ustar arnauarnau

A DateIndex indexes DateTime attributes.

Id
Type
DateIndex
././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/dtml/manageDateIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateIndex/dtml/manageDateIndex.0000644000175000017500000000030412214017452031734 0ustar arnauarnau

Objects indexed:
Distinct values:

zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/FieldIndex/0000755000175000017500000000000012214017452026126 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/FieldIndex/__init__.py0000644000175000017500000000004712214017452030240 0ustar arnauarnau# empty comment for winzip and friends zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/FieldIndex/FieldIndex.py0000644000175000017500000000276312214017452030523 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Simple column indices. """ from App.special_dtml import DTMLFile from Products.PluginIndexes.common.UnIndex import UnIndex class FieldIndex(UnIndex): """Index for simple fields. """ meta_type="FieldIndex" manage_options= ( {'label': 'Settings', 'action': 'manage_main'}, {'label': 'Browse', 'action': 'manage_browse'}, ) query_options = ["query","range"] manage = manage_main = DTMLFile('dtml/manageFieldIndex', globals()) manage_main._setName('manage_main') manage_browse = DTMLFile('../dtml/browseIndex', globals()) manage_addFieldIndexForm = DTMLFile('dtml/addFieldIndex', globals()) def manage_addFieldIndex(self, id, extra=None, REQUEST=None, RESPONSE=None, URL3=None): """Add a field index""" return self.manage_addIndex(id, 'FieldIndex', extra=extra, \ REQUEST=REQUEST, RESPONSE=RESPONSE, URL1=URL3) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/FieldIndex/tests/0000755000175000017500000000000012214017452027270 5ustar arnauarnau././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/FieldIndex/tests/testFieldIndex.pyzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/FieldIndex/tests/testFieldIndex0000644000175000017500000001677212214017452032143 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """FieldIndex unit tests. """ import unittest from Products.PluginIndexes.FieldIndex.FieldIndex import FieldIndex class Dummy: def __init__( self, foo ): self._foo = foo def foo( self ): return self._foo def __str__( self ): return '' % self._foo __repr__ = __str__ class FieldIndexTests(unittest.TestCase): """Test FieldIndex objects. """ def setUp( self ): """ """ self._index = FieldIndex( 'foo' ) self._marker = [] self._values = [ ( 0, Dummy( 'a' ) ) , ( 1, Dummy( 'ab' ) ) , ( 2, Dummy( 'abc' ) ) , ( 3, Dummy( 'abca' ) ) , ( 4, Dummy( 'abcd' ) ) , ( 5, Dummy( 'abce' ) ) , ( 6, Dummy( 'abce' ) ) , ( 7, Dummy( 0 ) ) # Collector #1959 , ( 8, Dummy(None) )] self._forward = {} self._backward = {} for k, v in self._values: self._backward[k] = v keys = self._forward.get( v, [] ) self._forward[v] = keys self._noop_req = { 'bar': 123 } self._request = { 'foo': 'abce' } self._min_req = { 'foo': {'query': 'abc' , 'range': 'min'} } self._max_req = { 'foo': {'query': 'abc' , 'range': 'max' } } self._range_req = { 'foo': {'query': ( 'abc', 'abcd' ) , 'range': 'min:max' } } self._zero_req = { 'foo': 0 } self._none_req = { 'foo': None } def tearDown( self ): """ """ def _populateIndex( self ): for k, v in self._values: self._index.index_object( k, v ) def _checkApply( self, req, expectedValues ): result, used = self._index._apply_index( req ) if hasattr(result, 'keys'): result = result.keys() assert used == ( 'foo', ) assert len( result ) == len( expectedValues ), \ '%s | %s' % ( map( None, result ), expectedValues ) for k, v in expectedValues: assert k in result def test_interfaces(self): from Products.PluginIndexes.interfaces import IPluggableIndex from Products.PluginIndexes.interfaces import ISortIndex from Products.PluginIndexes.interfaces import IUniqueValueIndex from zope.interface.verify import verifyClass verifyClass(IPluggableIndex, FieldIndex) verifyClass(ISortIndex, FieldIndex) verifyClass(IUniqueValueIndex, FieldIndex) def testEmpty( self ): "Test an empty FieldIndex." assert len( self._index ) == 0 assert len( self._index.referencedObjects() ) == 0 self.assertEqual(self._index.numObjects(), 0) assert self._index.getEntryForObject( 1234 ) is None assert ( self._index.getEntryForObject( 1234, self._marker ) is self._marker ) self._index.unindex_object( 1234 ) # nothrow assert self._index.hasUniqueValuesFor( 'foo' ) assert not self._index.hasUniqueValuesFor( 'bar' ) assert len( self._index.uniqueValues( 'foo' ) ) == 0 assert self._index._apply_index( self._noop_req ) is None self._checkApply( self._request, [] ) self._checkApply( self._min_req, [] ) self._checkApply( self._max_req, [] ) self._checkApply( self._range_req, [] ) def testPopulated( self ): """ Test a populated FieldIndex """ self._populateIndex() values = self._values assert len( self._index ) == len( values )-1 #'abce' is duplicate assert len( self._index.referencedObjects() ) == len( values ) self.assertEqual(self._index.indexSize(), len( values )-1) assert self._index.getEntryForObject( 1234 ) is None assert ( self._index.getEntryForObject( 1234, self._marker ) is self._marker ) self._index.unindex_object( 1234 ) # nothrow for k, v in values: assert self._index.getEntryForObject( k ) == v.foo() assert len( self._index.uniqueValues( 'foo' ) ) == len( values )-1 assert self._index._apply_index( self._noop_req ) is None self._checkApply( self._request, values[ -4:-2 ] ) self._checkApply( self._min_req, values[ 2:-2 ] ) self._checkApply( self._max_req, values[ :3 ] + values[ -2: ] ) self._checkApply( self._range_req, values[ 2:5 ] ) def testZero( self ): """ Make sure 0 gets indexed """ self._populateIndex() values = self._values self._checkApply( self._zero_req, values[ -2:-1 ] ) assert 0 in self._index.uniqueValues( 'foo' ) def testNone(self): """ make sure None gets indexed """ self._populateIndex() values = self._values self._checkApply(self._none_req, values[-1:]) assert None in self._index.uniqueValues('foo') def testReindex( self ): self._populateIndex() result, used = self._index._apply_index( {'foo':'abc'} ) assert list(result)==[2] assert self._index.keyForDocument(2)=='abc' d = Dummy('world') self._index.index_object(2,d) result, used = self._index._apply_index( {'foo':'world'} ) assert list(result)==[2] assert self._index.keyForDocument(2)=='world' del d._foo self._index.index_object(2,d) result, used = self._index._apply_index( {'foo':'world'} ) assert list(result)==[] try: should_not_be = self._index.keyForDocument(2) except KeyError: # As expected, we deleted that attribute. pass else: # before Collector #291 this would be 'world' raise ValueError(repr(should_not_be)) def testRange(self): """Test a range search""" index = FieldIndex( 'foo' ) for i in range(100): index.index_object(i, Dummy(i%10)) record = { 'foo' : { 'query' : [-99, 3] , 'range' : 'min:max' } } r=index._apply_index( record ) assert tuple(r[1])==('foo',), r[1] r=list(r[0].keys()) expect=[ 0, 1, 2, 3, 10, 11, 12, 13, 20, 21, 22, 23, 30, 31, 32, 33, 40, 41, 42, 43, 50, 51, 52, 53, 60, 61, 62, 63, 70, 71, 72, 73, 80, 81, 82, 83, 90, 91, 92, 93 ] assert r==expect, r # # Make sure that range tests with incompatible paramters # don't return empty sets. # record[ 'foo' ][ 'operator' ] = 'and' r2, ignore = index._apply_index( record ) r2 = list( r2.keys() ) assert r2 == r def test_suite(): return unittest.TestSuite(( unittest.makeSuite(FieldIndexTests), )) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/FieldIndex/tests/__init__.py0000644000175000017500000000125312214017452031402 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This file is needed to make this a package. zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/FieldIndex/dtml/0000755000175000017500000000000012214017452027066 5ustar arnauarnau././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/FieldIndex/dtml/addFieldIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/FieldIndex/dtml/addFieldIndex.d0000644000175000017500000000261512214017452031723 0ustar arnauarnau

Field Indexes treat the value of an objects attributes atomically, and can be used, for example, to track only a certain subset of object values, such as 'meta_type'.

Id
Indexed attributes
attribute1,attribute2,... or leave empty
Type
Field Index
././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/FieldIndex/dtml/manageFieldIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/FieldIndex/dtml/manageFieldInde0000644000175000017500000000030412214017452032002 0ustar arnauarnau

Objects indexed:
Distinct values:

zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/BooleanIndex/0000755000175000017500000000000012214017452026462 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/BooleanIndex/__init__.py0000644000175000017500000000000012214017452030561 0ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/BooleanIndex/tests.py0000644000175000017500000002034312214017452030200 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from BTrees.IIBTree import IISet class Dummy(object): def __init__(self, docid, truth): self.id = docid self.truth = truth class TestBooleanIndex(unittest.TestCase): def _getTargetClass(self): from Products.PluginIndexes.BooleanIndex import BooleanIndex return BooleanIndex.BooleanIndex def _makeOne(self, attr='truth'): return self._getTargetClass()(attr) def test_index_true(self): index = self._makeOne() obj = Dummy(1, True) index._index_object(obj.id, obj, attr='truth') self.assertTrue(1 in index._unindex) self.assertFalse(1 in index._index) def test_index_false(self): index = self._makeOne() obj = Dummy(1, False) index._index_object(obj.id, obj, attr='truth') self.assertTrue(1 in index._unindex) self.assertFalse(1 in index._index) def test_index_missing_attribute(self): index = self._makeOne() obj = Dummy(1, True) index._index_object(obj.id, obj, attr='missing') self.assertFalse(1 in index._unindex) self.assertFalse(1 in index._index) def test_search_true(self): index = self._makeOne() obj = Dummy(1, True) index._index_object(obj.id, obj, attr='truth') obj = Dummy(2, False) index._index_object(obj.id, obj, attr='truth') res, idx = index._apply_index({'truth': True}) self.assertEqual(idx, ('truth', )) self.assertEqual(list(res), [1]) def test_search_false(self): index = self._makeOne() obj = Dummy(1, True) index._index_object(obj.id, obj, attr='truth') obj = Dummy(2, False) index._index_object(obj.id, obj, attr='truth') res, idx = index._apply_index({'truth': False}) self.assertEqual(idx, ('truth', )) self.assertEqual(list(res), [2]) def test_search_inputresult(self): index = self._makeOne() obj = Dummy(1, True) index._index_object(obj.id, obj, attr='truth') obj = Dummy(2, False) index._index_object(obj.id, obj, attr='truth') res, idx = index._apply_index({'truth': True}, resultset=IISet([])) self.assertEqual(idx, ('truth', )) self.assertEqual(list(res), []) res, idx = index._apply_index({'truth': True}, resultset=IISet([2])) self.assertEqual(idx, ('truth', )) self.assertEqual(list(res), []) res, idx = index._apply_index({'truth': True}, resultset=IISet([1])) self.assertEqual(idx, ('truth', )) self.assertEqual(list(res), [1]) res, idx = index._apply_index({'truth': True}, resultset=IISet([1, 2])) self.assertEqual(idx, ('truth', )) self.assertEqual(list(res), [1]) res, idx = index._apply_index({'truth': False}, resultset=IISet([1, 2])) self.assertEqual(idx, ('truth', )) self.assertEqual(list(res), [2]) def test_index_many_true(self): index = self._makeOne() for i in range(0, 100): obj = Dummy(i, i < 80 and True or False) index._index_object(obj.id, obj, attr='truth') self.assertEqual(list(index._index), range(80, 100)) self.assertEqual(len(index._unindex), 100) res, idx = index._apply_index({'truth': True}) self.assertEqual(list(res), range(0, 80)) res, idx = index._apply_index({'truth': False}) self.assertEqual(list(res), range(80, 100)) def test_index_many_false(self): index = self._makeOne() for i in range(0, 100): obj = Dummy(i, i >= 80 and True or False) index._index_object(obj.id, obj, attr='truth') self.assertEqual(list(index._index), range(80, 100)) self.assertEqual(len(index._unindex), 100) res, idx = index._apply_index({'truth': False}) self.assertEqual(list(res), range(0, 80)) res, idx = index._apply_index({'truth': True}) self.assertEqual(list(res), range(80, 100)) def test_index_many_change(self): index = self._makeOne() def add(i, value): obj = Dummy(i, value) index._index_object(obj.id, obj, attr='truth') # First lets index only True values for i in range(0, 4): add(i, True) self.assertEqual(list(index._index), []) self.assertEqual(len(index._unindex), 4) # Now add an equal number of False values for i in range(4, 8): add(i, False) self.assertEqual(list(index._index), range(4, 8)) self.assertEqual(len(index._unindex), 8) # Once False gets to be more than 60% of the indexed set, we switch add(8, False) self.assertEqual(list(index._index), range(4, 9)) add(9, False) self.assertEqual(list(index._index), range(0, 4)) res, idx = index._apply_index({'truth': True}) self.assertEqual(list(res), range(0, 4)) res, idx = index._apply_index({'truth': False}) self.assertEqual(list(res), range(4, 10)) # and we can again switch if the percentages change again for i in range(6, 10): index.unindex_object(i) self.assertEqual(list(index._index), range(4, 6)) self.assertEqual(len(index._unindex), 6) res, idx = index._apply_index({'truth': True}) self.assertEqual(list(res), range(0, 4)) res, idx = index._apply_index({'truth': False}) self.assertEqual(list(res), range(4, 6)) def test_items(self): index = self._makeOne() # test empty items = dict(index.items()) self.assertEqual(len(items[True]), 0) self.assertEqual(len(items[False]), 0) # test few trues for i in range(0, 20): obj = Dummy(i, i < 5 and True or False) index._index_object(obj.id, obj, attr='truth') items = dict(index.items()) self.assertEqual(len(items[True]), 5) self.assertEqual(len(items[False]), 15) # test many trues for i in range(7, 20): index.unindex_object(i) items = dict(index.items()) self.assertEqual(len(items[True]), 5) self.assertEqual(len(items[False]), 2) def test_histogram(self): index = self._makeOne() # test empty hist = index.histogram() self.assertEqual(hist[True], 0) self.assertEqual(hist[False], 0) # test few trues for i in range(0, 20): obj = Dummy(i, i < 5 and True or False) index._index_object(obj.id, obj, attr='truth') hist = index.histogram() self.assertEqual(hist[True], 5) self.assertEqual(hist[False], 15) def test_migration(self): index = self._makeOne() for i in range(0, 100): obj = Dummy(i, i < 80 and True or False) index._index_object(obj.id, obj, attr='truth') # now hack the state to match what we had before delattr(index, '_index_length') delattr(index, '_index_value') # we had True values in _index even though there was more of them index._index.clear() index._index.update(range(0, 80)) # the length only kept track of the _index index._length.change(-20) # remove one to trigger migration index.unindex_object(99) self.assertEqual(index._length.value, 99) self.assertEqual(index._index_value, 0) self.assertEqual(index._index_length.value, 19) self.assertEqual(list(index._index), range(80, 99)) def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(TestBooleanIndex)) return suite zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/BooleanIndex/BooleanIndex.py0000644000175000017500000002272112214017452031407 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from logging import getLogger from App.special_dtml import DTMLFile from BTrees.IIBTree import IIBTree, IITreeSet, IISet from BTrees.IIBTree import union, intersection, difference import BTrees.Length from ZODB.POSException import ConflictError from Products.PluginIndexes.common.util import parseIndexRequest from Products.PluginIndexes.common.UnIndex import _marker from Products.PluginIndexes.common.UnIndex import UnIndex LOG = getLogger('BooleanIndex.UnIndex') class BooleanIndex(UnIndex): """Index for booleans self._index = set([documentId1, documentId2]) self._unindex = {documentId:[True/False]} self._length is the length of the unindex self._index_length is the length of the index False doesn't have actual entries in _index. """ meta_type = "BooleanIndex" manage_options= ( {'label': 'Settings', 'action': 'manage_main'}, {'label': 'Browse', 'action': 'manage_browse'}, ) query_options = ["query"] manage = manage_main = DTMLFile('dtml/manageBooleanIndex', globals()) manage_main._setName('manage_main') manage_browse = DTMLFile('../dtml/browseIndex', globals()) _index_value = 1 _index_length = None def clear(self): self._index = IITreeSet() self._index_length = BTrees.Length.Length() self._index_value = 1 self._unindex = IIBTree() self._length = BTrees.Length.Length() def histogram(self): """Return a mapping which provides a histogram of the number of elements found at each point in the index. """ histogram = {} indexed = bool(self._index_value) histogram[indexed] = self._index_length.value histogram[not indexed] = self._length.value - self._index_length.value return histogram def _invert_index(self, documentId=None): self._index_value = indexed = int(not self._index_value) self._index.clear() length = 0 for rid, value in self._unindex.iteritems(): if value == indexed: self._index.add(rid) length += 1 # documentId is the rid of the currently processed object that # triggered the invert. in the case of unindexing, the rid hasn't # been removed from the unindex yet. While indexing, the rid will # be added to the index and unindex after this method is done if documentId is not None: self._index.remove(documentId) length -= 1 self._index_length = BTrees.Length.Length(length) def _inline_migration(self): self._length = BTrees.Length.Length(len(self._unindex.keys())) self._index_length = BTrees.Length.Length(len(self._index)) if self._index_length.value > (self._length.value / 2): self._index_value = 1 self._invert_index() else: # set an instance variable self._index_value = 1 def insertForwardIndexEntry(self, entry, documentId): """If the value matches the indexed one, insert into treeset """ # when we get the first entry, decide to index the opposite of what # we got, as indexing zero items is fewer than one length = self._length index_length = self._index_length # BBB inline migration if index_length is None: self._inline_migration() length = self._length index_length = self._index_length if length.value == 0: self._index_value = int(not bool(entry)) if bool(entry) is bool(self._index_value): # is the index (after adding the current entry) larger than 60% # of the total length? than switch the indexed value if (index_length.value + 1) >= ((length.value + 1) * 0.6): self._invert_index() return self._index.insert(documentId) index_length.change(1) def removeForwardIndexEntry(self, entry, documentId, check=True): """Take the entry provided and remove any reference to documentId in its entry in the index. """ index_length = self._index_length if index_length is None: self._inline_migration() if bool(entry) is bool(self._index_value): try: self._index.remove(documentId) # BBB inline migration length = self._index_length length.change(-1) except ConflictError: raise except Exception: LOG.exception('%s: unindex_object could not remove ' 'documentId %s from index %s. This ' 'should not happen.' % (self.__class__.__name__, str(documentId), str(self.id))) elif check: length = self._length.value index_length = self._index_length.value # is the index (after removing the current entry) larger than # 60% of the total length? than switch the indexed value if (index_length) <= ((length - 1) * 0.6): self._invert_index(documentId) return def _index_object(self, documentId, obj, threshold=None, attr=''): """ index and object 'obj' with integer id 'documentId'""" returnStatus = 0 # First we need to see if there's anything interesting to look at datum = self._get_object_datum(obj, attr) # Make it boolean, int as an optimization if datum is not _marker: datum = int(bool(datum)) # We don't want to do anything that we don't have to here, so we'll # check to see if the new and existing information is the same. oldDatum = self._unindex.get(documentId, _marker) if datum != oldDatum: if oldDatum is not _marker: self.removeForwardIndexEntry(oldDatum, documentId, check=False) if datum is _marker: try: del self._unindex[documentId] self._length.change(-1) except ConflictError: raise except Exception: LOG.error('Should not happen: oldDatum was there, now ' 'its not, for document with id %s' % documentId) if datum is not _marker: self.insertForwardIndexEntry(datum, documentId) self._unindex[documentId] = datum self._length.change(1) returnStatus = 1 return returnStatus def unindex_object(self, documentId): """ Unindex the object with integer id 'documentId' and don't raise an exception if we fail """ unindexRecord = self._unindex.get(documentId, _marker) if unindexRecord is _marker: return None self.removeForwardIndexEntry(unindexRecord, documentId) try: del self._unindex[documentId] self._length.change(-1) except ConflictError: raise except: LOG.debug('Attempt to unindex nonexistent document' ' with id %s' % documentId,exc_info=True) def _apply_index(self, request, resultset=None): record = parseIndexRequest(request, self.id, self.query_options) if record.keys is None: return None index = self._index indexed = self._index_value for key in record.keys: if bool(key) is bool(indexed): # If we match the indexed value, check index return (intersection(index, resultset), (self.id, )) else: # Otherwise, remove from resultset or _unindex if resultset is None: return (union(difference(self._unindex, index), IISet([])), (self.id, )) else: return (difference(resultset, index), (self.id, )) return (IISet(), (self.id, )) def indexSize(self): """Return distinct values, as an optimization we always claim 2.""" return 2 def items(self): # return a list of value to int set of rid tuples indexed = self._index_value items = [(bool(indexed), self._index)] false = IISet() for rid, value in self._unindex.iteritems(): if value != indexed: false.add(rid) items.append((not bool(indexed), false)) return items manage_addBooleanIndexForm = DTMLFile('dtml/addBooleanIndex', globals()) def manage_addBooleanIndex(self, id, extra=None, REQUEST=None, RESPONSE=None, URL3=None): """Add a boolean index""" return self.manage_addIndex(id, 'BooleanIndex', extra=extra, \ REQUEST=REQUEST, RESPONSE=RESPONSE, URL1=URL3) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/BooleanIndex/dtml/0000755000175000017500000000000012214017452027422 5ustar arnauarnau././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/BooleanIndex/dtml/addBooleanIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/BooleanIndex/dtml/addBooleanInd0000644000175000017500000000250712214017452032034 0ustar arnauarnau

Boolean Indexes can be used for keeping track of whether objects fulfills a certain contract, like isFolderish

Id
Indexed attributes
attribute1,attribute2,... or leave empty
Type
Boolean Index
././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/BooleanIndex/dtml/manageBooleanIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/BooleanIndex/dtml/manageBoolean0000644000175000017500000000030212214017452032070 0ustar arnauarnau

Objects indexed:
Distinct values:

zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/www/0000755000175000017500000000000012214017452024737 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/www/index.gif0000644000175000017500000000030712214017452026535 0ustar arnauarnauGIF89a„ÿ½æG^®jˆn €5qñ[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ,Dà'ŽdižèÂéKÆ ›vn—ö ɦX0‘®Ì s̤hÙÜ)¡Õϵ>}â¹™"’ÅB2I¹ß§;zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/interfaces.py0000644000175000017500000001623112214017452026613 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """PluginIndexes interfaces. """ from zope.interface import Interface from zope.schema import Bool class IPluggableIndex(Interface): def getId(): """Return Id of index.""" def getEntryForObject(documentId, default=None): """Get all information contained for 'documentId'.""" def getIndexSourceNames(): """Get a sequence of attribute names that are indexed by the index. """ def index_object(documentId, obj, threshold=None): """Index an object. - ``documentId`` is the integer ID of the document. - ``obj`` is the object to be indexed. - ``threshold`` is the number of words to process between committing subtransactions. If None, subtransactions are disabled. For each name in ``getIndexSourceNames``, try to get the named attribute from ``obj``. - If the object does not have the attribute, do not add it to the index for that name. - If the attribute is a callable, call it to get the value. If calling it raises an AttributeError, do not add it to the index. for that name. """ def unindex_object(documentId): """Remove the documentId from the index.""" def _apply_index(request): """Apply the index to query parameters given in 'request'. The argument should be a mapping object. If the request does not contain the needed parameters, then None is returned. If the request contains a parameter with the name of the column and this parameter is either a Record or a class instance then it is assumed that the parameters of this index are passed as attribute (Note: this is the recommended way to pass parameters since Zope 2.4) Otherwise two objects are returned. The first object is a ResultSet containing the record numbers of the matching records. The second object is a tuple containing the names of all data fields used. """ def numObjects(): """Return the number of indexed objects.""" def indexSize(): """Return the size of the index in terms of distinct values.""" def clear(): """Empty the index""" class ILimitedResultIndex(IPluggableIndex): def _apply_index(request, resultset=None): """Same as IPluggableIndex' _apply_index method. The additional resultset argument contains the resultset, as already calculated by ZCatalog's search method. """ class IUniqueValueIndex(IPluggableIndex): """An index which can return lists of unique values contained in it""" def hasUniqueValuesFor(name): """Return true if the index can return the unique values for name""" def uniqueValues(name=None, withLengths=0): """Return the unique values for name. If 'withLengths' is true, returns a sequence of tuples of (value, length).""" class ISortIndex(IPluggableIndex): """An index which may be used to sort a set of document ids""" def keyForDocument(documentId): """Return the sort key that cooresponds to the specified document id This method is no longer used by ZCatalog, but is left for backwards compatibility.""" def documentToKeyMap(): """Return an object that supports __getitem__ and may be used to quickly lookup the sort key given a document id""" class IDateIndex(Interface): """Index for dates. """ index_naive_time_as_local = Bool(title=u'Index naive time as local?') class IDateRangeIndex(Interface): """Index for date ranges, such as the "effective-expiration" range in CMF. Any object may return None for either the start or the end date: for the start date, this should be the logical equivalent of "since the beginning of time"; for the end date, "until the end of time". Therefore, divide the space of indexed objects into four containers: - Objects which always match (i.e., they returned None for both); - Objects which match after a given time (i.e., they returned None for the end date); - Objects which match until a given time (i.e., they returned None for the start date); - Objects which match only during a specific interval. """ def getSinceField(): """Get the name of the attribute indexed as start date. """ def getUntilField(): """Get the name of the attribute indexed as end date. """ class IPathIndex(Interface): """Index for paths returned by getPhysicalPath. A path index stores all path components of the physical path of an object. Internal datastructure: - a physical path of an object is split into its components - every component is kept as a key of a OOBTree in self._indexes - the value is a mapping 'level of the path component' to 'all docids with this path component on this level' """ def insertEntry(comp, id, level): """ Insert an entry. This method is intended for use by subclasses: it is not a normal API for the index. 'comp' is an individual path component 'id' is the docid .level'is the level of the component inside the path """ class IFilteredSet(Interface): """A pre-calculated result list based on an expression. """ def getExpression(): """Get the expression. """ def getIds(): """Get the IDs of all objects for which the expression is True. """ def setExpression(expr): """Set the expression. """ class ITopicIndex(Interface): """A TopicIndex maintains a set of FilteredSet objects. Every FilteredSet object consists of an expression and and IISet with all Ids of indexed objects that eval with this expression to 1. """ def addFilteredSet(filter_id, typeFilteredSet, expr): """Add a FilteredSet object. """ def delFilteredSet(filter_id): """Delete the FilteredSet object specified by 'filter_id'. """ def clearFilteredSet(filter_id): """Clear the FilteredSet object specified by 'filter_id'. """ # IIndexConfiguration was added on request by the GenericSetup community in # order to perform introspection on indexes in a defined way. # (ajung) class IIndexConfiguration(Interface): """ Introspection API for pluggable indexes """ def getSettings(self): """ Returns an mapping with index specific settings. E.g. {'indexed_attrs' : ('SearchableText', )}. The interface does not define any specifc mapping keys. """ zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/__init__.py0000644000175000017500000001266612214017452026237 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## def initialize(context): from Products.PluginIndexes.FieldIndex.FieldIndex import FieldIndex from Products.PluginIndexes.FieldIndex.FieldIndex \ import manage_addFieldIndex from Products.PluginIndexes.FieldIndex.FieldIndex \ import manage_addFieldIndexForm context.registerClass(FieldIndex, permission='Add Pluggable Index', constructors=(manage_addFieldIndexForm, manage_addFieldIndex), icon='www/index.gif', visibility=None, ) from Products.PluginIndexes.KeywordIndex.KeywordIndex import KeywordIndex from Products.PluginIndexes.KeywordIndex.KeywordIndex \ import manage_addKeywordIndex from Products.PluginIndexes.KeywordIndex.KeywordIndex \ import manage_addKeywordIndexForm context.registerClass(KeywordIndex, permission='Add Pluggable Index', constructors=(manage_addKeywordIndexForm, manage_addKeywordIndex), icon='www/index.gif', visibility=None, ) from Products.PluginIndexes.TopicIndex.TopicIndex import TopicIndex from Products.PluginIndexes.TopicIndex.TopicIndex \ import manage_addTopicIndex from Products.PluginIndexes.TopicIndex.TopicIndex \ import manage_addTopicIndexForm context.registerClass(TopicIndex, permission='Add Pluggable Index', constructors=(manage_addTopicIndexForm, manage_addTopicIndex), icon='www/index.gif', visibility=None, ) from Products.PluginIndexes.DateIndex.DateIndex import DateIndex from Products.PluginIndexes.DateIndex.DateIndex \ import manage_addDateIndex from Products.PluginIndexes.DateIndex.DateIndex \ import manage_addDateIndexForm context.registerClass(DateIndex, permission='Add Pluggable Index', constructors=(manage_addDateIndexForm, manage_addDateIndex), icon='www/index.gif', visibility=None, ) from Products.PluginIndexes.DateRangeIndex.DateRangeIndex \ import DateRangeIndex from Products.PluginIndexes.DateRangeIndex.DateRangeIndex \ import manage_addDateRangeIndex from Products.PluginIndexes.DateRangeIndex.DateRangeIndex \ import manage_addDateRangeIndexForm context.registerClass(DateRangeIndex, permission='Add Pluggable Index', constructors=(manage_addDateRangeIndexForm, manage_addDateRangeIndex), icon='www/index.gif', visibility=None, ) from Products.PluginIndexes.PathIndex.PathIndex import PathIndex from Products.PluginIndexes.PathIndex.PathIndex \ import manage_addPathIndex from Products.PluginIndexes.PathIndex.PathIndex \ import manage_addPathIndexForm context.registerClass(PathIndex, permission='Add Pluggable Index', constructors=(manage_addPathIndexForm, manage_addPathIndex), icon='www/index.gif', visibility=None, ) from Products.PluginIndexes.BooleanIndex.BooleanIndex import BooleanIndex from Products.PluginIndexes.BooleanIndex.BooleanIndex import \ manage_addBooleanIndex from Products.PluginIndexes.BooleanIndex.BooleanIndex import \ manage_addBooleanIndexForm context.registerClass(BooleanIndex, permission='Add Pluggable Index', constructors=(manage_addBooleanIndexForm, manage_addBooleanIndex), icon='www/index.gif', visibility=None, ) from Products.PluginIndexes.UUIDIndex.UUIDIndex import UUIDIndex from Products.PluginIndexes.UUIDIndex.UUIDIndex import \ manage_addUUIDIndex from Products.PluginIndexes.UUIDIndex.UUIDIndex import \ manage_addUUIDIndexForm context.registerClass(UUIDIndex, permission='Add Pluggable Index', constructors=(manage_addUUIDIndexForm, manage_addUUIDIndex), icon='www/index.gif', visibility=None, ) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/UUIDIndex/0000755000175000017500000000000012214017452025651 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/UUIDIndex/UUIDIndex.py0000644000175000017500000000756612214017452027777 0ustar arnauarnau############################################################################## # # Copyright (c) 2011 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from logging import getLogger from Acquisition import aq_base from App.special_dtml import DTMLFile from BTrees.IOBTree import IOBTree from BTrees.Length import Length from BTrees.OIBTree import OIBTree from Products.PluginIndexes.common.UnIndex import _marker from Products.PluginIndexes.common.UnIndex import UnIndex logger = getLogger('Products.ZCatalog') class UUIDIndex(UnIndex): """Index for uuid fields with an unique value per key. The internal structure is: self._index = {datum:documentId]} self._unindex = {documentId:datum} For each datum only one documentId can exist. """ meta_type= "UUIDIndex" manage_options= ( {'label': 'Settings', 'action': 'manage_main'}, {'label': 'Browse', 'action': 'manage_browse'}, ) query_options = ["query", "range"] manage = manage_main = DTMLFile('dtml/manageUUIDIndex', globals()) manage_main._setName('manage_main') manage_browse = DTMLFile('../dtml/browseIndex', globals()) def clear(self): self._length = Length() self._index = OIBTree() self._unindex = IOBTree() def numObjects(self): """Return the number of indexed objects. Since we have a 1:1 mapping from documents to values, we can reuse the stored length. """ return self.indexSize() def uniqueValues(self, name=None, withLengths=0): """returns the unique values for name if withLengths is true, returns a sequence of tuples of (value, length) """ if name is None: name = self.id elif name != self.id: return [] if not withLengths: return tuple(self._index.keys()) # We know the length for each value is one return [(k, 1) for k in self._index.keys()] def insertForwardIndexEntry(self, entry, documentId): """Take the entry provided and put it in the correct place in the forward index. """ if entry is None: return old_docid = self._index.get(entry, _marker) if old_docid is _marker: self._index[entry] = documentId self._length.change(1) elif old_docid != documentId: logger.error("A different document with value '%s' already " "exists in the index.'" % entry) def removeForwardIndexEntry(self, entry, documentId): """Take the entry provided and remove any reference to documentId in its entry in the index. """ old_docid = self._index.get(entry, _marker) if old_docid is not _marker: del self._index[entry] self._length.change(-1) def _get_object_datum(self, obj, attr): # for a uuid it never makes sense to acquire a parent value via # Acquisition has_attr = getattr(aq_base(obj), attr, _marker) if has_attr is _marker: return _marker return super(UUIDIndex, self)._get_object_datum(obj, attr) manage_addUUIDIndexForm = DTMLFile('dtml/addUUIDIndex', globals()) def manage_addUUIDIndex(self, id, extra=None, REQUEST=None, RESPONSE=None, URL3=None): """Add an uuid index""" return self.manage_addIndex(id, 'UUIDIndex', extra=extra, \ REQUEST=REQUEST, RESPONSE=RESPONSE, URL1=URL3) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/UUIDIndex/__init__.py0000644000175000017500000000000012214017452027750 0ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/UUIDIndex/tests.py0000644000175000017500000001071412214017452027370 0ustar arnauarnau############################################################################## # # Copyright (c) 2011 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from Products.PluginIndexes.UUIDIndex.UUIDIndex import UUIDIndex class Dummy: def __init__(self, foo): self._foo = foo def foo(self): return self._foo def __str__(self): return '' % self._foo __repr__ = __str__ class UUIDIndexTests(unittest.TestCase): def setUp(self): self._index = UUIDIndex('foo') self._marker = [] self._values = [ (0, Dummy('a')), (1, Dummy('ab')), (2, Dummy(123)), (3, Dummy(234)), (4, Dummy(0))] self._forward = {} self._backward = {} for k, v in self._values: self._backward[k] = v keys = self._forward.get(v, []) self._forward[v] = keys def tearDown(self): self._index.clear() def _populateIndex(self): for k, v in self._values: self._index.index_object(k, v) def _checkApply(self, req, expectedValues): result, used = self._index._apply_index(req) if hasattr(result, 'keys'): result = result.keys() self.assertEqual(used, ('foo', )) self.assertEqual(len(result), len(expectedValues)) for k, v in expectedValues: self.assertTrue(k in result) def test_interfaces(self): from Products.PluginIndexes.interfaces import IPluggableIndex from Products.PluginIndexes.interfaces import ISortIndex from Products.PluginIndexes.interfaces import IUniqueValueIndex from zope.interface.verify import verifyClass verifyClass(IPluggableIndex, UUIDIndex) verifyClass(ISortIndex, UUIDIndex) verifyClass(IUniqueValueIndex, UUIDIndex) def test_empty(self): self.assertEqual(len(self._index), 0) self.assertEqual(len(self._index.referencedObjects()), 0) self.assertEqual(self._index.numObjects(), 0) self._checkApply({'foo': 'a'}, []) def test_populated(self): self._populateIndex() values = self._values self.assertEqual(len(self._index), len(values)) self.assertEqual(self._index.indexSize(), len(values)) self.assertTrue(self._index.getEntryForObject(10) is None) self._checkApply({'foo': 'not'}, []) self._index.unindex_object(10) # nothrow for k, v in values: self.assertEqual(self._index.getEntryForObject(k), v.foo()) self._checkApply({'foo': 'a'}, [values[0]]) self._checkApply({'foo': 0}, [values[4]]) self._checkApply({'foo': ['a', 'ab']}, values[:2]) def test_none(self): self._index.index_object(10, Dummy(None)) self._checkApply({'foo': None}, []) self.assertFalse(None in self._index.uniqueValues('foo')) def test_reindex(self): self._populateIndex() self._checkApply({'foo': 'a'}, [self._values[0]]) d = Dummy('world') self._index.index_object(0, d) self._checkApply({'foo': 'a'}, []) self._checkApply({'foo': 'world'}, [(0, d)]) self.assertEqual(self._index.keyForDocument(0), 'world') del d._foo self._index.index_object(0, d) self._checkApply({'foo': 'world'}, []) self.assertRaises(KeyError, self._index.keyForDocument, 0) def test_range(self): values = [] for i in range(100): obj = (i, Dummy(i)) self._index.index_object(*obj) values.append(obj) query = {'foo': {'query': [10, 20], 'range': 'min:max'}} self._checkApply(query, values[10:21]) def test_non_unique(self): obj = Dummy('a') self._index.index_object(0, obj) # second index call fails and logs self._index.index_object(1, obj) self._checkApply({'foo': 'a'}, [(0, obj)]) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(UUIDIndexTests), )) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/UUIDIndex/dtml/0000755000175000017500000000000012214017452026611 5ustar arnauarnau././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/UUIDIndex/dtml/manageUUIDIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/UUIDIndex/dtml/manageUUIDIndex.0000644000175000017500000000030212214017452031514 0ustar arnauarnau

Objects indexed:
Distinct values:

././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/UUIDIndex/dtml/addUUIDIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/UUIDIndex/dtml/addUUIDIndex.dtm0000644000175000017500000000240312214017452031525 0ustar arnauarnau

An UUIDIndex can index uuid or similar unique values.

Id
Indexed attributes
attribute1,attribute2,... or leave empty
Type
UUID Index
zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/KeywordIndex/0000755000175000017500000000000012214017452026527 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/KeywordIndex/KeywordIndex.py0000644000175000017500000001221712214017452031520 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Keyword index. """ from logging import getLogger from BTrees.OOBTree import difference from BTrees.OOBTree import OOSet from App.special_dtml import DTMLFile from Products.PluginIndexes.common import safe_callable from Products.PluginIndexes.common.UnIndex import UnIndex LOG = getLogger('Zope.KeywordIndex') class KeywordIndex(UnIndex): """Like an UnIndex only it indexes sequences of items. Searches match any keyword. This should have an _apply_index that returns a relevance score """ meta_type="KeywordIndex" manage_options= ( {'label': 'Settings', 'action': 'manage_main'}, {'label': 'Browse', 'action': 'manage_browse'}, ) query_options = ("query","operator", "range") def _index_object(self, documentId, obj, threshold=None, attr=''): """ index an object 'obj' with integer id 'i' Ideally, we've been passed a sequence of some sort that we can iterate over. If however, we haven't, we should do something useful with the results. In the case of a string, this means indexing the entire string as a keyword.""" # First we need to see if there's anything interesting to look at # self.id is the name of the index, which is also the name of the # attribute we're interested in. If the attribute is callable, # we'll do so. newKeywords = self._get_object_keywords(obj, attr) oldKeywords = self._unindex.get(documentId, None) if oldKeywords is None: # we've got a new document, let's not futz around. try: for kw in newKeywords: self.insertForwardIndexEntry(kw, documentId) if newKeywords: self._unindex[documentId] = list(newKeywords) except TypeError: return 0 else: # we have an existing entry for this document, and we need # to figure out if any of the keywords have actually changed if type(oldKeywords) is not OOSet: oldKeywords = OOSet(oldKeywords) newKeywords = OOSet(newKeywords) fdiff = difference(oldKeywords, newKeywords) rdiff = difference(newKeywords, oldKeywords) if fdiff or rdiff: # if we've got forward or reverse changes if newKeywords: self._unindex[documentId] = list(newKeywords) else: del self._unindex[documentId] if fdiff: self.unindex_objectKeywords(documentId, fdiff) if rdiff: for kw in rdiff: self.insertForwardIndexEntry(kw, documentId) return 1 def _get_object_keywords(self, obj, attr): newKeywords = getattr(obj, attr, ()) if safe_callable(newKeywords): try: newKeywords = newKeywords() except (AttributeError, TypeError): return () if not newKeywords: return () elif isinstance(newKeywords, basestring): return (newKeywords,) else: unique = {} try: for k in newKeywords: unique[k] = None except TypeError: # Not a sequence return (newKeywords,) else: return unique.keys() def unindex_objectKeywords(self, documentId, keywords): """ carefully unindex the object with integer id 'documentId'""" if keywords is not None: for kw in keywords: self.removeForwardIndexEntry(kw, documentId) def unindex_object(self, documentId): """ carefully unindex the object with integer id 'documentId'""" keywords = self._unindex.get(documentId, None) self.unindex_objectKeywords(documentId, keywords) try: del self._unindex[documentId] except KeyError: LOG.debug('Attempt to unindex nonexistent' ' document id %s' % documentId) manage = manage_main = DTMLFile('dtml/manageKeywordIndex', globals()) manage_main._setName('manage_main') manage_browse = DTMLFile('../dtml/browseIndex', globals()) manage_addKeywordIndexForm = DTMLFile('dtml/addKeywordIndex', globals()) def manage_addKeywordIndex(self, id, extra=None, REQUEST=None, RESPONSE=None, URL3=None): """Add a keyword index""" return self.manage_addIndex(id, 'KeywordIndex', extra=extra, \ REQUEST=REQUEST, RESPONSE=RESPONSE, URL1=URL3) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/KeywordIndex/__init__.py0000644000175000017500000000004712214017452030641 0ustar arnauarnau# empty comment for winzip and friends zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/KeywordIndex/tests/0000755000175000017500000000000012214017452027671 5ustar arnauarnau././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/KeywordIndex/tests/testKeywordIndex.pyzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/KeywordIndex/tests/testKeywordI0000644000175000017500000002274412214017452032262 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """KeywordIndex unit tests. """ import unittest from Products.PluginIndexes.KeywordIndex.KeywordIndex import KeywordIndex class Dummy: def __init__( self, foo ): self._foo = foo def foo( self ): return self._foo def __str__( self ): return '' % self._foo __repr__ = __str__ def sortedUnique(seq): unique = {} for i in seq: unique[i] = None unique = unique.keys() unique.sort() return unique class TestKeywordIndex( unittest.TestCase ): """ Test KeywordIndex objects. """ _old_log_write = None def setUp( self ): """ """ self._index = KeywordIndex( 'foo' ) self._marker = [] self._values = [ ( 0, Dummy( ['a'] ) ) , ( 1, Dummy( ['a','b'] ) ) , ( 2, Dummy( ['a','b','c'] ) ) , ( 3, Dummy( ['a','b','c','a'] ) ) , ( 4, Dummy( ['a', 'b', 'c', 'd'] ) ) , ( 5, Dummy( ['a', 'b', 'c', 'e'] ) ) , ( 6, Dummy( ['a', 'b', 'c', 'e', 'f'] )) , ( 7, Dummy( [0] ) ) ] self._noop_req = { 'bar': 123 } self._all_req = { 'foo': ['a'] } self._some_req = { 'foo': ['e'] } self._overlap_req = { 'foo': ['c', 'e'] } self._string_req = {'foo': 'a'} self._zero_req = { 'foo': [0] } def tearDown( self ): """ """ def _populateIndex( self ): for k, v in self._values: self._index.index_object( k, v ) def _checkApply( self, req, expectedValues ): result, used = self._index._apply_index( req ) assert used == ( 'foo', ) assert len(result) == len( expectedValues ), \ '%s | %s' % ( map( None, result ), map(lambda x: x[0], expectedValues )) if hasattr(result, 'keys'): result=result.keys() for k, v in expectedValues: assert k in result def test_interfaces(self): from Products.PluginIndexes.interfaces import IPluggableIndex from Products.PluginIndexes.interfaces import ISortIndex from Products.PluginIndexes.interfaces import IUniqueValueIndex from zope.interface.verify import verifyClass verifyClass(IPluggableIndex, KeywordIndex) verifyClass(ISortIndex, KeywordIndex) verifyClass(IUniqueValueIndex, KeywordIndex) def testAddObjectWOKeywords(self): try: self._populateIndex() self._index.index_object(999, None) finally: pass def testEmpty( self ): assert len( self._index ) == 0 assert len( self._index.referencedObjects() ) == 0 self.assertEqual(self._index.numObjects(), 0) assert self._index.getEntryForObject( 1234 ) is None assert ( self._index.getEntryForObject( 1234, self._marker ) is self._marker ), self._index.getEntryForObject(1234) self._index.unindex_object( 1234 ) # nothrow assert self._index.hasUniqueValuesFor( 'foo' ) assert not self._index.hasUniqueValuesFor( 'bar' ) assert len( self._index.uniqueValues( 'foo' ) ) == 0 assert self._index._apply_index( self._noop_req ) is None self._checkApply( self._all_req, [] ) self._checkApply( self._some_req, [] ) self._checkApply( self._overlap_req, [] ) self._checkApply( self._string_req, [] ) def testPopulated( self ): self._populateIndex() values = self._values #assert len( self._index ) == len( values ) assert len( self._index.referencedObjects() ) == len( values ) assert self._index.getEntryForObject( 1234 ) is None assert ( self._index.getEntryForObject( 1234, self._marker ) is self._marker ) self._index.unindex_object( 1234 ) # nothrow self.assertEqual(self._index.indexSize(), len( values )-1) for k, v in values: entry = self._index.getEntryForObject( k ) entry.sort() kw = sortedUnique(v.foo()) self.assertEqual(entry, kw) assert len( self._index.uniqueValues( 'foo' ) ) == len( values )-1 assert self._index._apply_index( self._noop_req ) is None self._checkApply( self._all_req, values[:-1]) self._checkApply( self._some_req, values[ 5:7 ] ) self._checkApply( self._overlap_req, values[2:7] ) self._checkApply( self._string_req, values[:-1] ) def testZero( self ): self._populateIndex() values = self._values self._checkApply( self._zero_req, values[ -1: ] ) assert 0 in self._index.uniqueValues( 'foo' ) def testReindexChange(self): self._populateIndex() expected = Dummy(['x', 'y']) self._index.index_object(6, expected) result, used = self._index._apply_index({'foo': ['x', 'y']}) result=result.keys() assert len(result) == 1 assert result[0] == 6 result, used = self._index._apply_index( {'foo': ['a', 'b', 'c', 'e', 'f']} ) result = result.keys() assert 6 not in result def testReindexNoChange(self): self._populateIndex() expected = Dummy(['foo', 'bar']) self._index.index_object(8, expected) result, used = self._index._apply_index( {'foo': ['foo', 'bar']}) result = result.keys() assert len(result) == 1 assert result[0] == 8 self._index.index_object(8, expected) result, used = self._index._apply_index( {'foo': ['foo', 'bar']}) result = result.keys() assert len(result) == 1 assert result[0] == 8 def testIntersectionWithRange(self): # Test an 'and' search, ensuring that 'range' doesn't mess it up. self._populateIndex() record = { 'foo' : { 'query' : [ 'e', 'f' ] , 'operator' : 'and' } } self._checkApply( record, self._values[6:7] ) # # Make sure that 'and' tests with incompatible paramters # don't return empty sets. # record[ 'foo' ][ 'range' ] = 'min:max' self._checkApply( record, self._values[6:7] ) def testDuplicateKeywords(self): try: self._index.index_object(0, Dummy(['a', 'a', 'b', 'b'])) self._index.unindex_object(0) finally: pass def testCollectorIssue889(self) : # Test that collector issue 889 is solved values = self._values nonexistent = 'foo-bar-baz' self._populateIndex() # make sure key is not indexed result = self._index._index.get(nonexistent, self._marker) assert result is self._marker # patched _apply_index now works as expected record = {'foo' : { 'query' : [nonexistent] , 'operator' : 'and'} } self._checkApply(record, []) record = {'foo' : { 'query' : [nonexistent, 'a'] , 'operator' : 'and'} } # and does not break anything self._checkApply(record, []) record = {'foo' : { 'query' : ['d'] , 'operator' : 'and'} } self._checkApply(record, values[4:5]) record = {'foo' : { 'query' : ['a', 'e'] , 'operator' : 'and'} } self._checkApply(record, values[5:7]) def test_noindexing_when_noattribute(self): to_index = Dummy(['hello']) self._index._index_object(10, to_index, attr='UNKNOWN') self.assertFalse(self._index._unindex.get(10)) self.assertFalse(self._index.getEntryForObject(10)) def test_noindexing_when_raising_attribute(self): class FauxObject: def foo(self): raise AttributeError to_index = FauxObject() self._index._index_object(10, to_index, attr='foo') self.assertFalse(self._index._unindex.get(10)) self.assertFalse(self._index.getEntryForObject(10)) def test_noindexing_when_raising_typeeror(self): class FauxObject: def foo(self, name): return 'foo' to_index = FauxObject() self._index._index_object(10, to_index, attr='foo') self.assertFalse(self._index._unindex.get(10)) self.assertFalse(self._index.getEntryForObject(10)) def test_value_removes(self): to_index = Dummy(['hello']) self._index._index_object(10, to_index, attr='foo') self.assertTrue(self._index._unindex.get(10)) to_index = Dummy('') self._index._index_object(10, to_index, attr='foo') self.assertFalse(self._index._unindex.get(10)) def test_suite(): suite = unittest.TestSuite() suite.addTest( unittest.makeSuite( TestKeywordIndex ) ) return suite zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/KeywordIndex/tests/__init__.py0000644000175000017500000000125312214017452032003 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This file is needed to make this a package. zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/KeywordIndex/dtml/0000755000175000017500000000000012214017452027467 5ustar arnauarnau././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/KeywordIndex/dtml/addKeywordIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/KeywordIndex/dtml/addKeywordInd0000644000175000017500000000264212214017452032146 0ustar arnauarnau

Keyword Indexes index a sequence of objects that act as 'keywords' for an object. A Keyword Index will return any objects that have one or more keywords specified in a search query.

Id
Indexed attributes
attribute1,attribute2,... or leave empty
Type
Keyword Index
././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/KeywordIndex/dtml/manageKeywordIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/KeywordIndex/dtml/manageKeyword0000644000175000017500000000030412214017452032204 0ustar arnauarnau

Objects indexed:
Distinct values:

zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/0000755000175000017500000000000012214017452025777 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/PathIndex.py0000644000175000017500000002125212214017452030237 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Path index. """ from logging import getLogger from App.special_dtml import DTMLFile from OFS.SimpleItem import SimpleItem from BTrees.IIBTree import IITreeSet from BTrees.IIBTree import IISet from BTrees.IIBTree import intersection from BTrees.IIBTree import multiunion from BTrees.IIBTree import union from BTrees.IOBTree import IOBTree from BTrees.OOBTree import OOBTree from BTrees.Length import Length from Persistence import Persistent from zope.interface import implements from Products.PluginIndexes.common import safe_callable from Products.PluginIndexes.common.util import parseIndexRequest from Products.PluginIndexes.interfaces import IPathIndex from Products.PluginIndexes.interfaces import ISortIndex from Products.PluginIndexes.interfaces import IUniqueValueIndex LOG = getLogger('Zope.PathIndex') class PathIndex(Persistent, SimpleItem): """Index for paths returned by getPhysicalPath. A path index stores all path components of the physical path of an object. Internal datastructure: - a physical path of an object is split into its components - every component is kept as a key of a OOBTree in self._indexes - the value is a mapping 'level of the path component' to 'all docids with this path component on this level' """ implements(IPathIndex, IUniqueValueIndex, ISortIndex) meta_type="PathIndex" query_options = ('query', 'level', 'operator') manage_options= ( {'label': 'Settings', 'action': 'manage_main'}, ) def __init__(self,id,caller=None): self.id = id self.operators = ('or','and') self.useOperator = 'or' self.clear() def __len__(self): return self._length() # IPluggableIndex implementation def getEntryForObject(self, docid, default=None): """ See IPluggableIndex. """ try: return self._unindex[docid] except KeyError: return default def getIndexSourceNames(self): """ See IPluggableIndex. """ return (self.id, 'getPhysicalPath', ) def index_object(self, docid, obj ,threshold=100): """ See IPluggableIndex. """ f = getattr(obj, self.id, None) if f is not None: if safe_callable(f): try: path = f() except AttributeError: return 0 else: path = f if not isinstance(path, (str, tuple)): raise TypeError('path value must be string or tuple of strings') else: try: path = obj.getPhysicalPath() except AttributeError: return 0 if isinstance(path, (list, tuple)): path = '/' + '/'.join(path[1:]) comps = filter(None, path.split('/')) old_value = self._unindex.get(docid, None) if old_value == path: return 0 if old_value is None: self._length.change(1) for i in range(len(comps)): self.insertEntry(comps[i], docid, i) self._unindex[docid] = path return 1 def unindex_object(self, docid): """ See IPluggableIndex. """ if docid not in self._unindex: LOG.debug('Attempt to unindex nonexistent document with id %s' % docid) return comps = self._unindex[docid].split('/') for level in range(len(comps[1:])): comp = comps[level+1] try: self._index[comp][level].remove(docid) if not self._index[comp][level]: del self._index[comp][level] if not self._index[comp]: del self._index[comp] except KeyError: LOG.debug('Attempt to unindex document with id %s failed' % docid) self._length.change(-1) del self._unindex[docid] def _apply_index(self, request): """ See IPluggableIndex. o Unpacks args from catalog and mapps onto '_search'. """ record = parseIndexRequest(request, self.id, self.query_options) if record.keys is None: return None level = record.get("level", 0) operator = record.get('operator', self.useOperator).lower() # depending on the operator we use intersection of union if operator == "or": set_func = union else: set_func = intersection res = None for k in record.keys: rows = self._search(k,level) res = set_func(res,rows) if res: return res, (self.id,) else: return IISet(), (self.id,) def numObjects(self): """ See IPluggableIndex. """ return len(self._unindex) def indexSize(self): """ See IPluggableIndex. """ return len(self) def clear(self): """ See IPluggableIndex. """ self._depth = 0 self._index = OOBTree() self._unindex = IOBTree() self._length = Length(0) # IUniqueValueIndex implementation def hasUniqueValuesFor(self, name): """ See IUniqueValueIndex. """ return name == self.id def uniqueValues(self, name=None, withLength=0): """ See IUniqueValueIndex. """ if name in (None, self.id, 'getPhysicalPath'): if withLength: for key in self._index: yield key, len(self._search(key, -1)) else: for key in self._index.keys(): yield key # ISortIndex implementation def keyForDocument(self, documentId): """ See ISortIndex. """ return self._unindex.get(documentId) def documentToKeyMap(self): """ See ISortIndex. """ return self._unindex # IPathIndex implementation. def insertEntry(self, comp, id, level): """ See IPathIndex """ if not self._index.has_key(comp): self._index[comp] = IOBTree() if not self._index[comp].has_key(level): self._index[comp][level] = IITreeSet() self._index[comp][level].insert(id) if level > self._depth: self._depth = level # Helper methods def _search(self, path, default_level=0): """ Perform the actual search. ``path`` a string representing a relative URL, or a part of a relative URL, or a tuple ``(path, level)``. In the first two cases, use ``default_level`` as the level for the search. ``default_level`` the level to use for non-tuple queries. ``level >= 0`` => match ``path`` only at the given level. ``level < 0`` => match ``path`` at *any* level """ if isinstance(path, str): level = default_level else: level = int(path[1]) path = path[0] if level < 0: # Search at every level, return the union of all results return multiunion( [self._search(path, level) for level in xrange(self._depth + 1)]) comps = filter(None, path.split('/')) if level + len(comps) - 1 > self._depth: # Our search is for a path longer than anything in the index return IISet() if len(comps) == 0: return IISet(self._unindex.keys()) results = None for i, comp in reversed(list(enumerate(comps))): if not self._index.get(comp, {}).has_key(level+i): return IISet() results = intersection(results, self._index[comp][level+i]) return results manage = manage_main = DTMLFile('dtml/managePathIndex', globals()) manage_main._setName('manage_main') manage_addPathIndexForm = DTMLFile('dtml/addPathIndex', globals()) def manage_addPathIndex(self, id, REQUEST=None, RESPONSE=None, URL3=None): """Add a path index""" return self.manage_addIndex(id, 'PathIndex', extra=None, \ REQUEST=REQUEST, RESPONSE=RESPONSE, URL1=URL3) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/PathIndex.txt0000644000175000017500000000430412214017452030425 0ustar arnauarnauhe purpose of a PathIndex is to index Zope objects based on their physical path. This is very similiar to a substring search on strings. How it works Assume we have to index an object with id=xxx and the physical path '/zoo/animals/africa/tiger.doc'. We split the path into its components and keep track of the level of every component. Inside the index we store pairs(component,level) and the ids of the documents:: (component,level) id of document ----------------------------------------- ('zoo',0) xxx ('animals',1) xxx ('africa',2) xxx Note that we do not store the id of the objects itself inside the path index. Searching with the PathIndex The PathIndex allows you to search for all object ids whose objects match a physical path query. The query is split into components and matched against the index. E.g. '/zoo/animals' will match in the example above but not '/zoo1/animals'. The default behaviour is to start matching at level 0. To start matching on another level on can specify an additional level parameter (see API) API 'query' -- A single or list of Path component(s) to be searched. 'level' -- level to start searching (optional,default: 0). If level=-1 we search through all levels. 'operator' -- either 'or' or 'and' (optional, default: 'or') Example Objects with the following ids and physical path should be stored in the ZCatalog 'MyCAT':: id physical path ---------------------------- 1 /aa/bb/aa/1.txt 2 /aa/bb/bb/2.txt 3 /aa/bb/cc/3.txt 4 /bb/bb/aa/4.txt 5 /bb/bb/bb/5.txt 6 /bb/bb/cc/6.txt 7 /cc/bb/aa/7.txt 8 /cc/bb/bb/8.txt 9 /cc/bb/cc/9.txt Query found ids ------------------------------------------- query='/aa/bb',level=0 [1,2,3] query='/bb/bb',level=0 [4,5,6] query='/bb/bb',level=1 [2,5,8] query='/bb/bb',level=-1 [2,4,5,6,8] query='/xx' ,level=-1 [] zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/__init__.py0000644000175000017500000000004712214017452030111 0ustar arnauarnau# empty comment for winzip and friends zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/tests/0000755000175000017500000000000012214017452027141 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/tests/__init__.py0000644000175000017500000000007012214017452031247 0ustar arnauarnau# This file is needed to make this directory a package. ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/tests/testPathIndex.pyzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/tests/testPathIndex.p0000644000175000017500000004722612214017452032121 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """PathIndex unit tests. """ import unittest class Dummy: def __init__( self, path): self.path = path def getPhysicalPath(self): return self.path.split('/') DUMMIES = { 1 : Dummy("/aa/aa/aa/1.html"), 2 : Dummy("/aa/aa/bb/2.html"), 3 : Dummy("/aa/aa/cc/3.html"), 4 : Dummy("/aa/bb/aa/4.html"), 5 : Dummy("/aa/bb/bb/5.html"), 6 : Dummy("/aa/bb/cc/6.html"), 7 : Dummy("/aa/cc/aa/7.html"), 8 : Dummy("/aa/cc/bb/8.html"), 9 : Dummy("/aa/cc/cc/9.html"), 10 : Dummy("/bb/aa/aa/10.html"), 11 : Dummy("/bb/aa/bb/11.html"), 12 : Dummy("/bb/aa/cc/12.html"), 13 : Dummy("/bb/bb/aa/13.html"), 14 : Dummy("/bb/bb/bb/14.html"), 15 : Dummy("/bb/bb/cc/15.html"), 16 : Dummy("/bb/cc/aa/16.html"), 17 : Dummy("/bb/cc/bb/17.html"), 18 : Dummy("/bb/cc/cc/18.html") } def _populateIndex(index): for k, v in DUMMIES.items(): index.index_object(k, v) _marker = object() class PathIndexTests(unittest.TestCase): """ Test PathIndex objects """ def _getTargetClass(self): from Products.PluginIndexes.PathIndex.PathIndex import PathIndex return PathIndex def _makeOne(self, id='path', caller=_marker): if caller is not _marker: return self._getTargetClass()(id, caller) return self._getTargetClass()(id) def test_class_conforms_to_IPluggableIndex(self): from Products.PluginIndexes.interfaces import IPluggableIndex from zope.interface.verify import verifyClass verifyClass(IPluggableIndex, self._getTargetClass()) def test_instance_conforms_to_IPluggableIndex(self): from Products.PluginIndexes.interfaces import IPluggableIndex from zope.interface.verify import verifyObject verifyObject(IPluggableIndex, self._makeOne()) def test_class_conforms_to_IUniqueValueIndex(self): from Products.PluginIndexes.interfaces import IUniqueValueIndex from zope.interface.verify import verifyClass verifyClass(IUniqueValueIndex, self._getTargetClass()) def test_instance_conforms_to_IUniqueValueIndex(self): from Products.PluginIndexes.interfaces import IUniqueValueIndex from zope.interface.verify import verifyObject verifyObject(IUniqueValueIndex, self._makeOne()) def test_class_conforms_to_ISortIndex(self): from Products.PluginIndexes.interfaces import ISortIndex from zope.interface.verify import verifyClass verifyClass(ISortIndex, self._getTargetClass()) def test_instance_conforms_to_ISortIndex(self): from Products.PluginIndexes.interfaces import ISortIndex from zope.interface.verify import verifyObject verifyObject(ISortIndex, self._makeOne()) def test_class_conforms_to_IPathIndex(self): from Products.PluginIndexes.interfaces import IPathIndex from zope.interface.verify import verifyClass verifyClass(IPathIndex, self._getTargetClass()) def test_instance_conforms_to_IPathIndex(self): from Products.PluginIndexes.interfaces import IPathIndex from zope.interface.verify import verifyObject verifyObject(IPathIndex, self._makeOne()) def test_ctor(self): index = self._makeOne() self.assertEqual(index.id, 'path') self.assertEqual(index.operators, ('or', 'and')) self.assertEqual(index.useOperator, 'or') self.assertEqual(len(index), 0) self.assertEqual(index._depth, 0) self.assertEqual(len(index._index), 0) self.assertEqual(len(index._unindex), 0) self.assertEqual(index._length(), 0) def test_getEntryForObject_miss_no_default(self): index = self._makeOne() self.assertEqual(index.getEntryForObject(1234), None) def test_getEntryForObject_miss_w_default(self): index = self._makeOne() default = object() self.assertTrue(index.getEntryForObject(1234, default) is default) def test_getEntryForObject_hit(self): index = self._makeOne() _populateIndex(index) self.assertEqual(index.getEntryForObject(1), DUMMIES[1].path) def test_getIndexSourceNames(self): index = self._makeOne('foo') self.assertEqual(list(index.getIndexSourceNames()), ['foo', 'getPhysicalPath']) def test_index_object_broken_path_raises_TypeError(self): index = self._makeOne() doc = Dummy({}) self.assertRaises(TypeError, index.index_object, 1, doc) def test_index_object_broken_callable(self): index = self._makeOne() doc = Dummy(lambda: self.nonesuch) rc = index.index_object(1, doc) self.assertEqual(rc, 0) self.assertEqual(len(index), 0) self.assertEqual(index._depth, 0) self.assertEqual(len(index._index), 0) self.assertEqual(len(index._unindex), 0) self.assertEqual(index._length(), 0) def test_index_object_at_root(self): index = self._makeOne() doc = Dummy('/xx') rc = index.index_object(1, doc) self.assertEqual(len(index), 1) self.assertEqual(rc, 1) self.assertEqual(index._depth, 0) self.assertEqual(len(index._index), 1) self.assertEqual(list(index._index['xx'][0]), [1]) self.assertEqual(len(index._unindex), 1) self.assertEqual(index._unindex[1], '/xx') self.assertEqual(index._length(), 1) def test_index_object_at_root_callable_attr(self): index = self._makeOne() doc = Dummy(lambda: '/xx') rc = index.index_object(1, doc) self.assertEqual(len(index), 1) self.assertEqual(rc, 1) self.assertEqual(index._depth, 0) self.assertEqual(len(index._index), 1) self.assertEqual(list(index._index['xx'][0]), [1]) self.assertEqual(len(index._unindex), 1) self.assertEqual(index._unindex[1], '/xx') self.assertEqual(index._length(), 1) def test_index_object_at_root_no_attr_but_getPhysicalPath(self): class Other: def getPhysicalPath(self): return '/xx' index = self._makeOne() doc = Other() rc = index.index_object(1, doc) self.assertEqual(rc, 1) self.assertEqual(len(index), 1) self.assertEqual(index._depth, 0) self.assertEqual(len(index._index), 1) self.assertEqual(list(index._index['xx'][0]), [1]) self.assertEqual(len(index._unindex), 1) self.assertEqual(index._unindex[1], '/xx') self.assertEqual(index._length(), 1) def test_index_object_at_root_attr_as_tuple(self): index = self._makeOne() doc = Dummy(('', 'xx')) rc = index.index_object(1, doc) self.assertEqual(rc, 1) self.assertEqual(len(index), 1) self.assertEqual(index._depth, 0) self.assertEqual(len(index._index), 1) self.assertEqual(list(index._index['xx'][0]), [1]) self.assertEqual(len(index._unindex), 1) self.assertEqual(index._unindex[1], '/xx') self.assertEqual(index._length(), 1) def test_index_object_strips_empty_path_elements(self): index = self._makeOne() doc = Dummy('////xx//') rc = index.index_object(1, doc) self.assertEqual(rc, 1) self.assertEqual(len(index), 1) self.assertEqual(index._depth, 0) self.assertEqual(len(index._index), 1) self.assertEqual(list(index._index['xx'][0]), [1]) self.assertEqual(len(index._unindex), 1) self.assertEqual(index._unindex[1], '////xx//') self.assertEqual(index._length(), 1) def test_index_object_below_root(self): index = self._makeOne() doc = Dummy('/xx/yy/zz') rc = index.index_object(1, doc) self.assertEqual(rc, 1) self.assertEqual(len(index), 1) self.assertEqual(index._depth, 2) self.assertEqual(len(index._index), 3) self.assertEqual(list(index._index['xx'][0]), [1]) self.assertEqual(list(index._index['yy'][1]), [1]) self.assertEqual(list(index._index['zz'][2]), [1]) self.assertEqual(len(index._unindex), 1) self.assertEqual(index._unindex[1], '/xx/yy/zz') self.assertEqual(index._length(), 1) def test_index_object_again(self): index = self._makeOne() o = Dummy('/foo/bar') index.index_object(1234, o) self.assertEqual(len(index), 1) self.assertEqual(index.numObjects(), 1) index.index_object(1234, o) self.assertEqual(len(index), 1) self.assertEqual(index.numObjects(), 1) def test_unindex_object_nonesuch(self): index = self._makeOne() index.unindex_object( 1234 ) # nothrow def test_unindex_object_broken_path(self): index = self._makeOne() _populateIndex(index) index._unindex[1] = "/broken/thing" index.unindex_object(1) # nothrow def test_unindex_object_found(self): index = self._makeOne() _populateIndex(index) for k in DUMMIES.keys(): index.unindex_object(k) self.assertEqual(index.numObjects(), 0) self.assertEqual(len(index._index), 0) self.assertEqual(len(index._unindex), 0) def test__apply_index_no_match_in_query(self): index = self._makeOne() self.assertEqual(index._apply_index({'foo': 'xxx'}), None) def test__apply_index_nonesuch(self): index = self._makeOne() res = index._apply_index({'path': 'xxx'}) self.assertEqual(len(res[0]), 0) self.assertEqual(res[1], ('path',)) def test___apply_index_root_levelO_dict(self): index = self._makeOne() _populateIndex(index) query = {'path': {'query': '/', 'level': 0}} res = index._apply_index(query) self.assertEqual(list(res[0].keys()), range(1,19)) def test___apply_index_root_levelO_tuple(self): index = self._makeOne() _populateIndex(index) query = {'path': (('/', 0),)} res = index._apply_index(query) self.assertEqual(list(res[0].keys()), range(1,19)) def test__apply_index_simple(self): index = self._makeOne() _populateIndex(index) tests = [ # component, level, expected results ("aa", 0, [1,2,3,4,5,6,7,8,9]), ("aa", 1, [1,2,3,10,11,12] ), ("bb", 0, [10,11,12,13,14,15,16,17,18]), ("bb", 1, [4,5,6,13,14,15]), ("bb/cc", 0, [16,17,18]), ("bb/cc", 1, [6,15]), ("bb/aa", 0, [10,11,12]), ("bb/aa", 1, [4,13]), ("aa/cc", -1, [3,7,8,9,12]), ("bb/bb", -1, [5,13,14,15]), ("18.html", 3, [18]), ("18.html", -1, [18]), ("cc/18.html", -1, [18]), ("cc/18.html", 2, [18]), ] for comp, level, expected in tests: for path in [comp, "/"+comp, "/"+comp+"/"]: # Test with the level passed in as separate parameter query = {'path': {'query':path, 'level': level}} res = index._apply_index(query) self.assertEqual(list(res[0].keys()), expected) # Test with the level passed in as part of the path parameter query = {'path': ((path, level),)} res = index._apply_index(query) self.assertEqual(list(res[0].keys()), expected) def test__apply_index_ComplexOrTests(self): index = self._makeOne() _populateIndex(index) tests = [ (['aa','bb'],1,[1,2,3,4,5,6,10,11,12,13,14,15]), (['aa','bb','xx'],1,[1,2,3,4,5,6,10,11,12,13,14,15]), ([('cc',1),('cc',2)],0,[3,6,7,8,9,12,15,16,17,18]), ] for lst, level, expected in tests: query = {'path': {'query': lst, 'level': level, 'operator': 'or'}} res = index._apply_index(query) lst = list(res[0].keys()) self.assertEqual(lst, expected) def test__apply_index_ComplexANDTests(self): index = self._makeOne() _populateIndex(index) tests = [ # Path query (as list or (path, level) tuple), level, expected (['aa','bb'], 1, []), ([('aa',0), ('bb',1)], 0, [4,5,6]), ([('aa',0), ('cc',2)], 0, [3,6,9]), ] for lst, level, expected in tests: query = {'path': {'query': lst, 'level': level, 'operator': 'and'}} res = index._apply_index(query) lst = list(res[0].keys()) self.assertEqual(lst, expected) def test__apply_index_QueryPathReturnedInResult(self): index = self._makeOne() index.index_object(1, Dummy("/ff")) index.index_object(2, Dummy("/ff/gg")) index.index_object(3, Dummy("/ff/gg/3.html")) index.index_object(4, Dummy("/ff/gg/4.html")) res = index._apply_index({'path': {'query': '/ff/gg'}}) lst = list(res[0].keys()) self.assertEqual(lst, [2, 3, 4]) def test_numObjects_empty(self): index = self._makeOne() self.assertEqual(index.numObjects(), 0) def test_numObjects_filled(self): index = self._makeOne() _populateIndex(index) self.assertEqual(index.numObjects(), len(DUMMIES)) def test_indexSize_empty(self): index = self._makeOne() self.assertEqual(index.indexSize(), 0) def test_indexSize_filled(self): index = self._makeOne() _populateIndex(index) self.assertEqual(index.indexSize(), len(DUMMIES)) def test_indexSize_multiple_items_same_path(self): index = self._makeOne() doc1 = Dummy('/shared') doc2 = Dummy('/shared') index.index_object(1, doc1) index.index_object(2, doc2) self.assertEqual(len(index._index), 1) self.assertEqual(len(index), 2) self.assertEqual(index.numObjects(), 2) self.assertEqual(index.indexSize(), 2) def test_clear(self): index = self._makeOne() _populateIndex(index) index.clear() self.assertEqual(len(index), 0) self.assertEqual(index._depth, 0) self.assertEqual(len(index._index), 0) self.assertEqual(len(index._unindex), 0) self.assertEqual(index._length(), 0) def test_hasUniqueValuesFor_miss(self): index = self._makeOne() self.assertFalse(index.hasUniqueValuesFor('miss')) def test_hasUniqueValuesFor_hit(self): index = self._makeOne() self.assertTrue(index.hasUniqueValuesFor('path')) def test_uniqueValues_empty(self): index = self._makeOne() self.assertEqual(len(list(index.uniqueValues())), 0) def test_uniqueValues_miss(self): index = self._makeOne('foo') _populateIndex(index) self.assertEqual(len(list(index.uniqueValues('bar'))), 0) def test_uniqueValues_hit(self): index = self._makeOne('foo') _populateIndex(index) self.assertEqual(len(list(index.uniqueValues('foo'))), len(DUMMIES) + 3) def test_uniqueValues_hit_w_withLength(self): index = self._makeOne('foo') _populateIndex(index) results = dict(index.uniqueValues('foo', True)) self.assertEqual(len(results), len(DUMMIES) + 3) for i in range(1, 19): self.assertEqual(results['%s.html' % i], 1) self.assertEqual(results['aa'], len([x for x in DUMMIES.values() if 'aa' in x.path])) self.assertEqual(results['bb'], len([x for x in DUMMIES.values() if 'bb' in x.path])) self.assertEqual(results['cc'], len([x for x in DUMMIES.values() if 'cc' in x.path])) def test_keyForDocument_miss(self): index = self._makeOne() self.assertEqual(index.keyForDocument(1), None) def test_keyForDocument_hit(self): index = self._makeOne() _populateIndex(index) self.assertEqual(index.keyForDocument(1), DUMMIES[1].path) def test_documentToKeyMap_empty(self): index = self._makeOne() self.assertEqual(dict(index.documentToKeyMap()), {}) def test_documentToKeyMap_filled(self): index = self._makeOne() _populateIndex(index) self.assertEqual(dict(index.documentToKeyMap()), dict([(k, v.path) for k, v in DUMMIES.items()])) def test_insertEntry_empty_depth_0(self): index = self._makeOne() index.insertEntry('xx', 123, level=0) self.assertEqual(index._depth, 0) self.assertEqual(len(index._index), 1) self.assertEqual(list(index._index['xx'][0]), [123]) # insertEntry oesn't update the length or the reverse index. self.assertEqual(len(index), 0) self.assertEqual(len(index._unindex), 0) self.assertEqual(index._length(), 0) def test_insertEntry_empty_depth_1(self): index = self._makeOne() index.insertEntry('xx', 123, level=0) index.insertEntry('yy', 123, level=1) self.assertEqual(index._depth, 1) self.assertEqual(len(index._index), 2) self.assertEqual(list(index._index['xx'][0]), [123]) self.assertEqual(list(index._index['yy'][1]), [123]) def test_insertEntry_multiple(self): index = self._makeOne() index.insertEntry('xx', 123, level=0) index.insertEntry('yy', 123, level=1) index.insertEntry('aa', 456, level=0) index.insertEntry('bb', 456, level=1) index.insertEntry('cc', 456, level=2) self.assertEqual(index._depth, 2) self.assertEqual(len(index._index), 5) self.assertEqual(list(index._index['xx'][0]), [123]) self.assertEqual(list(index._index['yy'][1]), [123]) self.assertEqual(list(index._index['aa'][0]), [456]) self.assertEqual(list(index._index['bb'][1]), [456]) self.assertEqual(list(index._index['cc'][2]), [456]) def test__search_empty_index_string_query(self): index = self._makeOne() self.assertEqual(list(index._search('/xxx')), []) def test__search_empty_index_tuple_query(self): index = self._makeOne() self.assertEqual(list(index._search(('/xxx', 0))), []) def test__search_empty_path(self): index = self._makeOne() doc = Dummy('/aa') index.index_object(1, doc) self.assertEqual(list(index._search('/')), [1]) def test__search_matching_path(self): index = self._makeOne() doc = Dummy('/aa') index.index_object(1, doc) self.assertEqual(list(index._search('/aa')), [1]) def test__search_mismatched_path(self): index = self._makeOne() doc = Dummy('/aa') index.index_object(1, doc) self.assertEqual(list(index._search('/bb')), []) def test__search_w_level_0(self): index = self._makeOne() doc = Dummy('/aa/bb') index.index_object(1, doc) self.assertEqual(list(index._search('aa', 0)), [1]) self.assertEqual(list(index._search('aa', 1)), []) self.assertEqual(list(index._search('bb', 1)), [1]) self.assertEqual(list(index._search('aa/bb', 0)), [1]) self.assertEqual(list(index._search('aa/bb', 1)), []) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(PathIndexTests), )) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/dtml/0000755000175000017500000000000012214017452026737 5ustar arnauarnau././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/dtml/addPathIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/dtml/addPathIndex.dtm0000644000175000017500000000231012214017452031776 0ustar arnauarnau

A PathIndex indexes the physical path of all objects inside a catalog. It allows you to search for objects beginning or containing a special path component or a set of path component. A path component is defined as /<component1>/<component2>/..../<object_id> .

Id
Type
PathIndex
././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/dtml/managePathIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/PathIndex/dtml/managePathIndex.0000644000175000017500000000030412214017452031772 0ustar arnauarnau

Objects indexed:
Distinct values:

zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/0000755000175000017500000000000012214017452026161 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/TopicIndex.py0000644000175000017500000001612012214017452030601 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Topic index. """ from logging import getLogger from App.special_dtml import DTMLFile from BTrees.IIBTree import IITreeSet from BTrees.IIBTree import intersection from BTrees.IIBTree import union from BTrees.OOBTree import OOBTree from OFS.SimpleItem import SimpleItem from Persistence import Persistent from zope.interface import implements from Products.PluginIndexes.common.util import parseIndexRequest from Products.PluginIndexes.interfaces import IPluggableIndex from Products.PluginIndexes.interfaces import ITopicIndex from Products.PluginIndexes.TopicIndex.FilteredSet import factory _marker = [] LOG = getLogger('Zope.TopicIndex') class TopicIndex(Persistent, SimpleItem): """A TopicIndex maintains a set of FilteredSet objects. Every FilteredSet object consists of an expression and and IISet with all Ids of indexed objects that eval with this expression to 1. """ implements(ITopicIndex, IPluggableIndex) meta_type="TopicIndex" query_options = ('query', 'operator') manage_options= ( {'label': 'FilteredSets', 'action': 'manage_main'}, ) def __init__(self,id,caller=None): self.id = id self.filteredSets = OOBTree() self.operators = ('or','and') self.defaultOperator = 'or' def getId(self): return self.id def clear(self): for fs in self.filteredSets.values(): fs.clear() def index_object(self, docid, obj ,threshold=100): """ hook for (Z)Catalog """ for fid, filteredSet in self.filteredSets.items(): filteredSet.index_object(docid,obj) return 1 def unindex_object(self,docid): """ hook for (Z)Catalog """ for fs in self.filteredSets.values(): try: fs.unindex_object(docid) except KeyError: LOG.debug('Attempt to unindex document' ' with id %s failed' % docid) return 1 def numObjects(self): """Return the number of indexed objects.""" return "n/a" def indexSize(self): """Return the size of the index in terms of distinct values.""" return "n/a" def search(self,filter_id): if self.filteredSets.has_key(filter_id): return self.filteredSets[filter_id].getIds() def _apply_index(self, request): """ hook for (Z)Catalog 'request' -- mapping type (usually {"topic": "..." } """ record = parseIndexRequest(request, self.id, self.query_options) if record.keys is None: return None operator = record.get('operator', self.defaultOperator).lower() if operator == 'or': set_func = union else: set_func = intersection res = None for filter_id in record.keys: rows = self.search(filter_id) res = set_func(res,rows) if res: return res, (self.id,) else: return IITreeSet(), (self.id,) def uniqueValues(self,name=None, withLength=0): """ needed to be consistent with the interface """ return self.filteredSets.keys() def getEntryForObject(self,docid, default=_marker): """ Takes a document ID and returns all the information we have on that specific object. """ return self.filteredSets.keys() def addFilteredSet(self, filter_id, typeFilteredSet, expr): # Add a FilteredSet object. if self.filteredSets.has_key(filter_id): raise KeyError,\ 'A FilteredSet with this name already exists: %s' % filter_id self.filteredSets[filter_id] = factory(filter_id, typeFilteredSet, expr, ) def delFilteredSet(self, filter_id): # Delete the FilteredSet object specified by 'filter_id'. if not self.filteredSets.has_key(filter_id): raise KeyError,\ 'no such FilteredSet: %s' % filter_id del self.filteredSets[filter_id] def clearFilteredSet(self, filter_id): # Clear the FilteredSet object specified by 'filter_id'. if not self.filteredSets.has_key(filter_id): raise KeyError,\ 'no such FilteredSet: %s' % filter_id self.filteredSets[filter_id].clear() def manage_addFilteredSet(self, filter_id, typeFilteredSet, expr, URL1, \ REQUEST=None,RESPONSE=None): """ add a new filtered set """ if len(filter_id) == 0: raise RuntimeError,'Length of ID too short' if len(expr) == 0: raise RuntimeError,'Length of expression too short' self.addFilteredSet(filter_id, typeFilteredSet, expr) if RESPONSE: RESPONSE.redirect(URL1+'/manage_workspace?' 'manage_tabs_message=FilteredSet%20added') def manage_delFilteredSet(self, filter_ids=[], URL1=None, \ REQUEST=None,RESPONSE=None): """ delete a list of FilteredSets""" for filter_id in filter_ids: self.delFilteredSet(filter_id) if RESPONSE: RESPONSE.redirect(URL1+'/manage_workspace?' 'manage_tabs_message=FilteredSet(s)%20deleted') def manage_saveFilteredSet(self,filter_id, expr, URL1=None,\ REQUEST=None,RESPONSE=None): """ save expression for a FilteredSet """ self.filteredSets[filter_id].setExpression(expr) if RESPONSE: RESPONSE.redirect(URL1+'/manage_workspace?' 'manage_tabs_message=FilteredSet(s)%20updated') def getIndexSourceNames(self): """ return names of indexed attributes """ return ('n/a',) def manage_clearFilteredSet(self, filter_ids=[], URL1=None, \ REQUEST=None,RESPONSE=None): """ clear a list of FilteredSets""" for filter_id in filter_ids: self.clearFilteredSet(filter_id) if RESPONSE: RESPONSE.redirect(URL1+'/manage_workspace?' 'manage_tabs_message=FilteredSet(s)%20cleared') manage = manage_main = DTMLFile('dtml/manageTopicIndex',globals()) manage_main._setName('manage_main') editFilteredSet = DTMLFile('dtml/editFilteredSet',globals()) manage_addTopicIndexForm = DTMLFile('dtml/addTopicIndex', globals()) def manage_addTopicIndex(self, id, REQUEST=None, RESPONSE=None, URL3=None): """Add a TopicIndex""" return self.manage_addIndex(id, 'TopicIndex', extra=None, \ REQUEST=REQUEST, RESPONSE=RESPONSE, URL1=URL3) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/FilteredSet.py0000644000175000017500000000542212214017452030750 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Filtered set. """ from logging import getLogger import sys from BTrees.IIBTree import IITreeSet from Persistence import Persistent from RestrictedPython.Eval import RestrictionCapableEval from ZODB.POSException import ConflictError from zope.interface import implements from Products.PluginIndexes.interfaces import IFilteredSet LOG = getLogger('Zope.TopicIndex.FilteredSet') class FilteredSetBase(Persistent): # A pre-calculated result list based on an expression. implements(IFilteredSet) def __init__(self, id, expr): self.id = id self.expr = expr self.clear() def clear(self): self.ids = IITreeSet() def index_object(self, documentId, obj): raise RuntimeError,'index_object not defined' def unindex_object(self,documentId): try: self.ids.remove(documentId) except KeyError: pass def getId(self): return self.id def getExpression(self): # Get the expression. return self.expr def getIds(self): # Get the IDs of all objects for which the expression is True. return self.ids def getType(self): return self.meta_type def setExpression(self, expr): # Set the expression. self.expr = expr def __repr__(self): return '%s: (%s) %s' % (self.id,self.expr,map(None,self.ids)) __str__ = __repr__ class PythonFilteredSet(FilteredSetBase): meta_type = 'PythonFilteredSet' def index_object(self, documentId, o): try: if RestrictionCapableEval(self.expr).eval({'o': o}): self.ids.insert(documentId) else: try: self.ids.remove(documentId) except KeyError: pass except ConflictError: raise except: LOG.warn('eval() failed Object: %s, expr: %s' %\ (o.getId(),self.expr), exc_info=sys.exc_info()) def factory(f_id, f_type, expr): """ factory function for FilteredSets """ if f_type=='PythonFilteredSet': return PythonFilteredSet(f_id, expr) else: raise TypeError,'unknown type for FilteredSets: %s' % f_type zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/__init__.py0000644000175000017500000000114412214017452030272 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/README.txt0000644000175000017500000000321312214017452027656 0ustar arnauarnauTopicIndex Reference: http://dev.zope.org/Wikis/DevSite/Proposals/TopicIndexes A TopicIndex is a container for so-called FilteredSet. A FilteredSet consists of an expression and a set of internal ZCatalog document identifiers that represent a pre-calculated result list for performance reasons. Instead of executing the same query on a ZCatalog multiple times it is much faster to use a TopicIndex instead. Building up FilteredSets happens on the fly when objects are cataloged and uncatalogued. Every indexed object is evaluated against the expressions of every FilteredSet. An object is added to a FilteredSet if the expression with the object evaluates to 1. Uncatalogued objects are removed from the FilteredSet. Types of FilteredSet PythonFilteredSet A PythonFilteredSet evaluates using the eval() function inside the context of the FilteredSet class. The object to be indexes must be referenced inside the expression using "o.". Examples:: "o.meta_type=='DTML Method'" Queries on TopicIndexes A TopicIndex is queried in the same way as other ZCatalog Indexes and supports usage of the 'operator' parameter to specify how to combine search results. API The TopicIndex implements the API for pluggable Indexes. Additionally it provides the following functions to manage FilteredSets -- addFilteredSet(Id, filterType, expression): -- Id: unique Id for the FilteredSet -- filterType: 'PythonFilteredSet' -- expression: Python expression -- delFilteredSet(Id): -- clearFilteredSet(Id): zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/tests/0000755000175000017500000000000012214017452027323 5ustar arnauarnau././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/tests/testTopicIndex.pyzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/tests/testTopicIndex0000644000175000017500000000574512214017452032227 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """TopicIndex unit tests. """ import unittest from Products.PluginIndexes.TopicIndex.TopicIndex import TopicIndex class Obj: def __init__(self,id,meta_type=''): self.id = id self.meta_type = meta_type def getId(self): return self.id def getPhysicalPath(self): return self.id class TestBase(unittest.TestCase): def _searchAnd(self,query,expected): return self._search(query,'and',expected) def _searchOr(self,query,expected): return self._search(query,'or',expected) def _search(self,query,operator,expected): res = self.TI._apply_index({'topic':{'query':query,'operator':operator}}) rows = list(res[0].keys()) rows.sort() expected.sort() self.assertEqual(rows,expected,query) return rows class TestTopicIndex(TestBase): def setUp(self): self.TI = TopicIndex("topic") self.TI.addFilteredSet("doc1","PythonFilteredSet","o.meta_type=='doc1'") self.TI.addFilteredSet("doc2","PythonFilteredSet","o.meta_type=='doc2'") self.TI.index_object(0 , Obj('0',)) self.TI.index_object(1 , Obj('1','doc1')) self.TI.index_object(2 , Obj('2','doc1')) self.TI.index_object(3 , Obj('3','doc2')) self.TI.index_object(4 , Obj('4','doc2')) self.TI.index_object(5 , Obj('5','doc3')) self.TI.index_object(6 , Obj('6','doc3')) def test_interfaces(self): from Products.PluginIndexes.interfaces import ITopicIndex from Products.PluginIndexes.interfaces import IPluggableIndex from zope.interface.verify import verifyClass verifyClass(ITopicIndex, TopicIndex) verifyClass(IPluggableIndex, TopicIndex) def testOr(self): self._searchOr('doc1',[1,2]) self._searchOr(['doc1'],[1,2]) self._searchOr('doc2',[3,4]), self._searchOr(['doc2'],[3,4]) self._searchOr(['doc1','doc2'], [1,2,3,4]) def testAnd(self): self._searchAnd('doc1',[1,2]) self._searchAnd(['doc1'],[1,2]) self._searchAnd('doc2',[3,4]) self._searchAnd(['doc2'],[3,4]) self._searchAnd(['doc1','doc2'],[]) def testRemoval(self): self.TI.index_object(1, Obj('1','doc2')) self._searchOr('doc1',[2]) self._searchOr('doc2', [1,3,4]) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestTopicIndex), )) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/tests/__init__.py0000644000175000017500000000125312214017452031435 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This file is needed to make this a package. zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/dtml/0000755000175000017500000000000012214017452027121 5ustar arnauarnau././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/dtml/addTopicIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/dtml/addTopicIndex.d0000644000175000017500000000236112214017452032007 0ustar arnauarnau

A TopicIndex is a container for so-called FilteredSets that consist of an expression and a set of internal ZCatalog document identifiers that fulfill this expression. TopicIndexes are useful for performance reasons when search queries take too long and pre-calculated result sets offer a better performance.

Id
Type
TopicIndex
././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/dtml/editFilteredSet.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/dtml/editFilteredSet0000644000175000017500000000156312214017452032131 0ustar arnauarnau

Edit FilteredSet
FilteredSet Id &dtml-getId;
FilteredSet Type &dtml-getType;
FilteredSet Expression
././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/dtml/manageTopicIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/TopicIndex/dtml/manageTopicInde0000644000175000017500000000573512214017452032105 0ustar arnauarnau
0">
Defined FilteredSets
  FilteredSet Id FilteredSet Type Expression # entries
">
&dtml-getType;
&dtml-getExpression;
no FilteredSets defined

Id for FilteredSet
Type of FilteredSet
Expression
zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/common/0000755000175000017500000000000012214017452025403 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/common/randid.py0000644000175000017500000000136612214017452027224 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################# import random def randid(randint=random.randint, choice=random.choice, signs=(-1,1)): return choice(signs)*randint(1,2000000000) del random zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/common/__init__.py0000644000175000017500000000151612214017452027517 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################# import types def safe_callable(ob): # Works with ExtensionClasses and Acquisition. if hasattr(ob, '__class__'): return hasattr(ob, '__call__') or isinstance(ob, types.ClassType) else: return callable(ob) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/common/ResultList.py0000644000175000017500000000604012214017452030067 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from BTrees.IIBTree import difference from BTrees.IIBTree import IIBucket from BTrees.IIBTree import weightedIntersection from BTrees.IIBTree import weightedUnion from BTrees.OOBTree import OOSet from BTrees.OOBTree import union class ResultList: def __init__(self, d, words, index): self._index = index if type(words) is not OOSet: words=OOSet(words) self._words = words if (type(d) is tuple): d = IIBucket((d,)) elif type(d) is not IIBucket: d = IIBucket(d) self._dict=d self.__getitem__=d.__getitem__ try: self.__nonzero__=d.__nonzero__ except: pass self.get=d.get def __nonzero__(self): return not not self._dict def bucket(self): return self._dict def keys(self): return self._dict.keys() def has_key(self, key): return self._dict.has_key(key) def items(self): return self._dict.items() def __and__(self, x): return self.__class__( weightedIntersection(self._dict, x._dict), union(self._words, x._words), self._index, ) def and_not(self, x): return self.__class__( difference(self._dict, x._dict), self._words, self._index, ) def __or__(self, x): return self.__class__( weightedUnion(self._dict, x._dict), union(self._words, x._words), self._index, ) # return self.__class__(result, self._words+x._words, self._index) def near(self, x): result = IIBucket() dict = self._dict xdict = x._dict xhas = xdict.has_key positions = self._index.positions for id, score in dict.items(): if not xhas(id): continue p=(map(lambda i: (i,0), positions(id,self._words))+ map(lambda i: (i,1), positions(id,x._words))) p.sort() d = lp = 9999 li = None lsrc = None for i,src in p: if i is not li and src is not lsrc and li is not None: d = min(d,i-li) li = i lsrc = src if d==lp: score = min(score,xdict[id]) # synonyms else: score = (score+xdict[id])/d result[id] = score return self.__class__( result, union(self._words, x._words), self._index) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/common/util.py0000644000175000017500000001006412214017452026733 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """PluginIndexes utils. """ from types import InstanceType from DateTime.DateTime import DateTime class IndexRequestParseError(Exception): pass class parseIndexRequest: """ This class provides functionality to hide the internals of a request send from the Catalog/ZCatalog to an index._apply_index() method. The class understands the following type of parameters: - old-style parameters where the query for an index as value inside the request directory where the index name is the name of the key. - dictionary-style parameters specify a query for an index as an entry in the request dictionary where the key corresponds to the name of the index and the key is a dictionary with the parameters passed to the index. Allowed keys of the parameter dictionary: 'query' - contains the query (either string, list or tuple) (required) other parameters depend on the the index - record-style parameters specify a query for an index as instance of the Record class. This happens usually when parameters from a web form use the "record" type e.g. . All restrictions of the dictionary-style parameters apply to the record-style parameters """ ParserException = IndexRequestParseError def __init__(self, request, iid, options=[]): """ parse a request from the ZPublisher and return a uniform datastructure back to the _apply_index() method of the index request -- the request dictionary send from the ZPublisher iid -- Id of index options -- a list of options the index is interested in """ self.id = iid if not request.has_key(iid): self.keys = None return param = request[iid] keys = None if isinstance(param, InstanceType) and not isinstance(param, DateTime): """ query is of type record """ record = param if not hasattr(record, 'query'): raise self.ParserException( "record for '%s' *must* contain a " "'query' attribute" % self.id) keys = record.query if isinstance(keys, str): keys = [keys.strip()] for op in options: if op == "query": continue if hasattr(record, op): setattr(self, op, getattr(record, op)) elif isinstance(param, dict): """ query is a dictionary containing all parameters """ query = param.get("query", ()) if isinstance(query, (tuple, list)): keys = query else: keys = [ query ] for op in options: if op == "query": continue if param.has_key(op): setattr(self, op, param[op]) else: """ query is tuple, list, string, number, or something else """ if isinstance(param, (tuple, list)): keys = param else: keys = [param] for op in options: field = iid + "_" + op if request.has_key(field): setattr(self, op, request[field]) self.keys = keys def get(self, k, default_v=None): if hasattr(self, k): v = getattr(self, k) if v != '': return v return default_v zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/common/UnIndex.py0000644000175000017500000004252412214017452027336 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Base for bi-directional indexes. """ from cgi import escape from logging import getLogger import sys from BTrees.IIBTree import intersection from BTrees.IIBTree import IITreeSet from BTrees.IIBTree import IISet from BTrees.IIBTree import multiunion from BTrees.IOBTree import IOBTree from BTrees.Length import Length from BTrees.OOBTree import OOBTree from OFS.SimpleItem import SimpleItem from ZODB.POSException import ConflictError from zope.interface import implements from Products.PluginIndexes.common import safe_callable from Products.PluginIndexes.common.util import parseIndexRequest from Products.PluginIndexes.interfaces import ILimitedResultIndex from Products.PluginIndexes.interfaces import ISortIndex from Products.PluginIndexes.interfaces import IUniqueValueIndex _marker = [] LOG = getLogger('Zope.UnIndex') class UnIndex(SimpleItem): """Simple forward and reverse index. """ implements(ILimitedResultIndex, IUniqueValueIndex, ISortIndex) def __init__( self, id, ignore_ex=None, call_methods=None, extra=None, caller=None): """Create an unindex UnIndexes are indexes that contain two index components, the forward index (like plain index objects) and an inverted index. The inverted index is so that objects can be unindexed even when the old value of the object is not known. e.g. self._index = {datum:[documentId1, documentId2]} self._unindex = {documentId:datum} The arguments are: 'id' -- the name of the item attribute to index. This is either an attribute name or a record key. 'ignore_ex' -- should be set to true if you want the index to ignore exceptions raised while indexing instead of propagating them. 'call_methods' -- should be set to true if you want the index to call the attribute 'id' (note: 'id' should be callable!) You will also need to pass in an object in the index and uninded methods for this to work. 'extra' -- a mapping object that keeps additional index-related parameters - subitem 'indexed_attrs' can be string with comma separated attribute names or a list 'caller' -- reference to the calling object (usually a (Z)Catalog instance """ def _get(o, k, default): """ return a value for a given key of a dict/record 'o' """ if isinstance(o, dict): return o.get(k, default) else: return getattr(o, k, default) self.id = id self.ignore_ex=ignore_ex # currently unimplimented self.call_methods=call_methods self.operators = ('or', 'and') self.useOperator = 'or' # allow index to index multiple attributes ia = _get(extra, 'indexed_attrs', id) if isinstance(ia, str): self.indexed_attrs = ia.split(',') else: self.indexed_attrs = list(ia) self.indexed_attrs = [ attr.strip() for attr in self.indexed_attrs if attr ] if not self.indexed_attrs: self.indexed_attrs = [id] self.clear() def __len__(self): return self._length() def getId(self): return self.id def clear(self): self._length = Length() self._index = OOBTree() self._unindex = IOBTree() def __nonzero__(self): return not not self._unindex def histogram(self): """Return a mapping which provides a histogram of the number of elements found at each point in the index. """ histogram = {} for item in self._index.items(): if isinstance(item,int): entry = 1 # "set" length is 1 else: key, value = item entry = len(value) histogram[entry] = histogram.get(entry, 0) + 1 return histogram def referencedObjects(self): """Generate a list of IDs for which we have referenced objects.""" return self._unindex.keys() def getEntryForObject(self, documentId, default=_marker): """Takes a document ID and returns all the information we have on that specific object. """ if default is _marker: return self._unindex.get(documentId) else: return self._unindex.get(documentId, default) def removeForwardIndexEntry(self, entry, documentId): """Take the entry provided and remove any reference to documentId in its entry in the index. """ indexRow = self._index.get(entry, _marker) if indexRow is not _marker: try: indexRow.remove(documentId) if not indexRow: del self._index[entry] self._length.change(-1) except ConflictError: raise except AttributeError: # index row is an int try: del self._index[entry] except KeyError: # XXX swallow KeyError because it was probably # removed and then _length AttributeError raised pass if isinstance(self.__len__, Length): self._length = self.__len__ del self.__len__ self._length.change(-1) except: LOG.error('%s: unindex_object could not remove ' 'documentId %s from index %s. This ' 'should not happen.' % (self.__class__.__name__, str(documentId), str(self.id)), exc_info=sys.exc_info()) else: LOG.error('%s: unindex_object tried to retrieve set %s ' 'from index %s but couldn\'t. This ' 'should not happen.' % (self.__class__.__name__, repr(entry), str(self.id))) def insertForwardIndexEntry(self, entry, documentId): """Take the entry provided and put it in the correct place in the forward index. This will also deal with creating the entire row if necessary. """ indexRow = self._index.get(entry, _marker) # Make sure there's actually a row there already. If not, create # a set and stuff it in first. if indexRow is _marker: # We always use a set to avoid getting conflict errors on # multiple threads adding a new row at the same time self._index[entry] = IITreeSet((documentId, )) self._length.change(1) else: try: indexRow.insert(documentId) except AttributeError: # Inline migration: index row with one element was an int at # first (before Zope 2.13). indexRow = IITreeSet((indexRow, documentId)) self._index[entry] = indexRow def index_object(self, documentId, obj, threshold=None): """ wrapper to handle indexing of multiple attributes """ fields = self.getIndexSourceNames() res = 0 for attr in fields: res += self._index_object(documentId, obj, threshold, attr) return res > 0 def _index_object(self, documentId, obj, threshold=None, attr=''): """ index and object 'obj' with integer id 'documentId'""" returnStatus = 0 # First we need to see if there's anything interesting to look at datum = self._get_object_datum(obj, attr) # We don't want to do anything that we don't have to here, so we'll # check to see if the new and existing information is the same. oldDatum = self._unindex.get(documentId, _marker) if datum != oldDatum: if oldDatum is not _marker: self.removeForwardIndexEntry(oldDatum, documentId) if datum is _marker: try: del self._unindex[documentId] except ConflictError: raise except: LOG.error('Should not happen: oldDatum was there, now its not,' 'for document with id %s' % documentId) if datum is not _marker: self.insertForwardIndexEntry(datum, documentId) self._unindex[documentId] = datum returnStatus = 1 return returnStatus def _get_object_datum(self,obj, attr): # self.id is the name of the index, which is also the name of the # attribute we're interested in. If the attribute is callable, # we'll do so. try: datum = getattr(obj, attr) if safe_callable(datum): datum = datum() except (AttributeError, TypeError): datum = _marker return datum def numObjects(self): """Return the number of indexed objects.""" return len(self._unindex) def indexSize(self): """Return the size of the index in terms of distinct values.""" return len(self) def unindex_object(self, documentId): """ Unindex the object with integer id 'documentId' and don't raise an exception if we fail """ unindexRecord = self._unindex.get(documentId, _marker) if unindexRecord is _marker: return None self.removeForwardIndexEntry(unindexRecord, documentId) try: del self._unindex[documentId] except ConflictError: raise except: LOG.debug('Attempt to unindex nonexistent document' ' with id %s' % documentId,exc_info=True) def _apply_index(self, request, resultset=None): """Apply the index to query parameters given in the request arg. The request argument should be a mapping object. If the request does not have a key which matches the "id" of the index instance, then None is returned. If the request *does* have a key which matches the "id" of the index instance, one of a few things can happen: - if the value is a blank string, None is returned (in order to support requests from web forms where you can't tell a blank string from empty). - if the value is a nonblank string, turn the value into a single-element sequence, and proceed. - if the value is a sequence, return a union search. - If the value is a dict and contains a key of the form '_operator' this overrides the default method ('or') to combine search results. Valid values are "or" and "and". If None is not returned as a result of the abovementioned constraints, two objects are returned. The first object is a ResultSet containing the record numbers of the matching records. The second object is a tuple containing the names of all data fields used. FAQ answer: to search a Field Index for documents that have a blank string as their value, wrap the request value up in a tuple ala: request = {'id':('',)} """ record = parseIndexRequest(request, self.id, self.query_options) if record.keys is None: return None index = self._index r = None opr = None # experimental code for specifing the operator operator = record.get('operator',self.useOperator) if not operator in self.operators : raise RuntimeError("operator not valid: %s" % escape(operator)) # Range parameter range_parm = record.get('range',None) if range_parm: opr = "range" opr_args = [] if range_parm.find("min")>-1: opr_args.append("min") if range_parm.find("max")>-1: opr_args.append("max") if record.get('usage',None): # see if any usage params are sent to field opr = record.usage.lower().split(':') opr, opr_args=opr[0], opr[1:] if opr=="range": # range search if 'min' in opr_args: lo = min(record.keys) else: lo = None if 'max' in opr_args: hi = max(record.keys) else: hi = None if hi: setlist = index.values(lo,hi) else: setlist = index.values(lo) # If we only use one key, intersect and return immediately if len(setlist) == 1: result = setlist[0] if isinstance(result, int): result = IISet((result,)) return result, (self.id,) if operator == 'or': tmp = [] for s in setlist: if isinstance(s, int): s = IISet((s,)) tmp.append(s) r = multiunion(tmp) else: # For intersection, sort with smallest data set first tmp = [] for s in setlist: if isinstance(s, int): s = IISet((s,)) tmp.append(s) if len(tmp) > 2: setlist = sorted(tmp, key=len) else: setlist = tmp r = resultset for s in setlist: # the result is bound by the resultset r = intersection(r, s) else: # not a range search # Filter duplicates setlist = [] for k in record.keys: s = index.get(k, None) # If None, try to bail early if s is None: if operator == 'or': # If union, we can't possibly get a bigger result continue # If intersection, we can't possibly get a smaller result return IISet(), (self.id,) elif isinstance(s, int): s = IISet((s,)) setlist.append(s) # If we only use one key return immediately if len(setlist) == 1: result = setlist[0] if isinstance(result, int): result = IISet((result,)) return result, (self.id,) if operator == 'or': # If we already get a small result set passed in, intersecting # the various indexes with it and doing the union later is # faster than creating a multiunion first. if resultset is not None and len(resultset) < 200: smalllist = [] for s in setlist: smalllist.append(intersection(resultset, s)) r = multiunion(smalllist) else: r = multiunion(setlist) else: # For intersection, sort with smallest data set first if len(setlist) > 2: setlist = sorted(setlist, key=len) r = resultset for s in setlist: r = intersection(r, s) if isinstance(r, int): r = IISet((r, )) if r is None: return IISet(), (self.id,) else: return r, (self.id,) def hasUniqueValuesFor(self, name): """has unique values for column name""" if name == self.id: return 1 else: return 0 def getIndexSourceNames(self): """ return sequence of indexed attributes """ # BBB: older indexes didn't have 'indexed_attrs' return getattr(self, 'indexed_attrs', [self.id]) def uniqueValues(self, name=None, withLengths=0): """returns the unique values for name if withLengths is true, returns a sequence of tuples of (value, length) """ if name is None: name = self.id elif name != self.id: return [] if not withLengths: return tuple(self._index.keys()) else: rl=[] for i in self._index.keys(): set = self._index[i] if isinstance(set, int): l = 1 else: l = len(set) rl.append((i, l)) return tuple(rl) def keyForDocument(self, id): # This method is superceded by documentToKeyMap return self._unindex[id] def documentToKeyMap(self): return self._unindex def items(self): items = [] for k,v in self._index.items(): if isinstance(v, int): v = IISet((v,)) items.append((k, v)) return items zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/common/tests/0000755000175000017500000000000012214017452026545 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/common/tests/__init__.py0000644000175000017500000000114412214017452030656 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################# zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/common/tests/test_UnIndex.py0000644000175000017500000000541312214017452031533 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################# """ Tests for common UnIndex features. """ import unittest class UnIndexTests(unittest.TestCase): def _getTargetClass(self): from Products.PluginIndexes.common.UnIndex import UnIndex return UnIndex def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def _makeConflicted(self): from ZODB.POSException import ConflictError class Conflicted: def __str__(self): return 'Conflicted' __repr__ = __str__ def __getattr__(self, id, default=object()): raise ConflictError, 'testing' return Conflicted() def test_empty(self): unindex = self._makeOne(id='empty') self.assertEqual(unindex.indexed_attrs, ['empty']) def test_removeForwardIndexEntry_with_ConflictError(self): from ZODB.POSException import ConflictError unindex = self._makeOne(id='conflicted') unindex._index['conflicts'] = self._makeConflicted() self.assertRaises(ConflictError, unindex.removeForwardIndexEntry, 'conflicts', 42) def test_get_object_datum(self): from Products.PluginIndexes.common.UnIndex import _marker idx = self._makeOne('interesting') dummy = object() self.assertEquals(idx._get_object_datum(dummy, 'interesting'), _marker) class DummyContent2(object): interesting = 'GOT IT' dummy = DummyContent2() self.assertEquals(idx._get_object_datum(dummy, 'interesting'), 'GOT IT') class DummyContent3(object): exc = None def interesting(self): if self.exc: raise self.exc return 'GOT IT' dummy = DummyContent3() self.assertEquals(idx._get_object_datum(dummy, 'interesting'), 'GOT IT') dummy.exc = AttributeError self.assertEquals(idx._get_object_datum(dummy, 'interesting'), _marker) dummy.exc = TypeError self.assertEquals(idx._get_object_datum(dummy, 'interesting'), _marker) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(UnIndexTests)) return suite zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/common/tests/test_util.py0000644000175000017500000000427012214017452031136 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unit tests for util module. """ import unittest from ZPublisher.HTTPRequest import record as Record class parseIndexRequestTests(unittest.TestCase): def _getTargetClass(self): from Products.PluginIndexes.common.util import parseIndexRequest return parseIndexRequest def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def test_get_record(self): record = Record() record.query = 'foo' record.level = 0 record.operator = 'and' request = {'path': record} parser = self._makeOne(request, 'path', ('query','level','operator')) self.assertEqual(parser.get('keys'), ['foo']) self.assertEqual(parser.get('level'), 0) self.assertEqual(parser.get('operator'), 'and') def test_get_dict(self): request = {'path': {'query': 'foo', 'level': 0, 'operator': 'and'}} parser = self._makeOne(request, 'path', ('query','level','operator')) self.assertEqual(parser.get('keys'), ['foo']) self.assertEqual(parser.get('level'), 0) self.assertEqual(parser.get('operator'), 'and') def test_get_string(self): request = {'path': 'foo', 'path_level': 0, 'path_operator': 'and'} parser = self._makeOne(request, 'path', ('query','level','operator')) self.assertEqual(parser.get('keys'), ['foo']) self.assertEqual(parser.get('level'), 0) self.assertEqual(parser.get('operator'), 'and') def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(parseIndexRequestTests)) return suite zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/dtml/0000755000175000017500000000000012214017452025053 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/dtml/browseIndex.dtml0000644000175000017500000000426512214017452030235 0ustar arnauarnau

The index "&dtml-getId;" contains distinct values

&dtml-sequence-key;
zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/0000755000175000017500000000000012214017452026735 5ustar arnauarnau././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/DateRangeIndex.pyzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/DateRangeIndex.p0000644000175000017500000003603012214017452031742 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Date range index. """ import os from datetime import datetime from AccessControl.class_init import InitializeClass from AccessControl.Permissions import manage_zcatalog_indexes from AccessControl.Permissions import view from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import aq_base from Acquisition import aq_get from Acquisition import aq_inner from Acquisition import aq_parent from App.Common import package_home from App.special_dtml import DTMLFile from BTrees.IIBTree import IITreeSet from BTrees.IIBTree import difference from BTrees.IIBTree import intersection from BTrees.IIBTree import multiunion from BTrees.IOBTree import IOBTree from BTrees.Length import Length from DateTime.DateTime import DateTime from zope.interface import implements from Products.PluginIndexes.common import safe_callable from Products.PluginIndexes.common.UnIndex import UnIndex from Products.PluginIndexes.common.util import parseIndexRequest from Products.PluginIndexes.interfaces import IDateRangeIndex _dtmldir = os.path.join(package_home(globals()), 'dtml') MAX32 = int(2**31 - 1) class RequestCache(dict): def __str__(self): return "" % len(self) class DateRangeIndex(UnIndex): """Index for date ranges, such as the "effective-expiration" range in CMF. Any object may return None for either the start or the end date: for the start date, this should be the logical equivalent of "since the beginning of time"; for the end date, "until the end of time". Therefore, divide the space of indexed objects into four containers: - Objects which always match (i.e., they returned None for both); - Objects which match after a given time (i.e., they returned None for the end date); - Objects which match until a given time (i.e., they returned None for the start date); - Objects which match only during a specific interval. """ implements(IDateRangeIndex) security = ClassSecurityInfo() meta_type = "DateRangeIndex" query_options = ('query', ) manage_options= ({'label': 'Properties', 'action': 'manage_indexProperties'}, ) since_field = until_field = None # int(DateTime('1000/1/1 0:00 GMT-12').millis() / 1000 / 60) floor_value = -510162480 # int(DateTime('2499/12/31 0:00 GMT+12').millis() / 1000 / 60) ceiling_value = 278751600 def __init__(self, id, since_field=None, until_field=None, caller=None, extra=None, floor_value=None, ceiling_value=None): if extra: since_field = extra.since_field until_field = extra.until_field floor_value = getattr(extra, 'floor_value', None) ceiling_value = getattr(extra, 'ceiling_value', None) self._setId(id) self._edit(since_field, until_field, floor_value, ceiling_value) self.clear() security.declareProtected(view, 'getSinceField') def getSinceField(self): """Get the name of the attribute indexed as start date. """ return self._since_field security.declareProtected(view, 'getUntilField') def getUntilField(self): """Get the name of the attribute indexed as end date. """ return self._until_field security.declareProtected(view, 'getFloorValue') def getFloorValue(self): """""" return self.floor_value security.declareProtected(view, 'getCeilingValue') def getCeilingValue(self): """""" return self.ceiling_value manage_indexProperties = DTMLFile('manageDateRangeIndex', _dtmldir) security.declareProtected(manage_zcatalog_indexes, 'manage_edit') def manage_edit(self, since_field, until_field, floor_value, ceiling_value, REQUEST): """ """ self._edit(since_field, until_field, floor_value, ceiling_value) REQUEST['RESPONSE'].redirect('%s/manage_main' '?manage_tabs_message=Updated' % REQUEST.get('URL2')) security.declarePrivate('_edit') def _edit(self, since_field, until_field, floor_value=None, ceiling_value=None): """Update the fields used to compute the range. """ self._since_field = since_field self._until_field = until_field if floor_value is not None: self.floor_value = int(floor_value) if ceiling_value is not None: self.ceiling_value = int(ceiling_value) security.declareProtected(manage_zcatalog_indexes, 'clear') def clear(self): """ Start over fresh. """ self._always = IITreeSet() self._since_only = IOBTree() self._until_only = IOBTree() self._since = IOBTree() self._until = IOBTree() self._unindex = IOBTree() # 'datum' will be a tuple of date ints self._length = Length() # # PluggableIndexInterface implementation (XXX inherit assertions?) # def getEntryForObject(self, documentId, default=None): """ Get all information contained for the specific object identified by 'documentId'. Return 'default' if not found. """ return self._unindex.get(documentId, default) def index_object(self, documentId, obj, threshold=None): """ Index an object: - 'documentId' is the integer ID of the document - 'obj' is the object to be indexed - ignore threshold """ if self._since_field is None: return 0 since = getattr(obj, self._since_field, None) if safe_callable(since): since = since() since = self._convertDateTime(since) until = getattr(obj, self._until_field, None) if safe_callable(until): until = until() until = self._convertDateTime(until) datum = (since, until) old_datum = self._unindex.get(documentId, None) if datum == old_datum: # No change? bail out! return 0 if old_datum is not None: old_since, old_until = old_datum self._removeForwardIndexEntry(old_since, old_until, documentId) self._insertForwardIndexEntry(since, until, documentId) self._unindex[documentId] = datum return 1 def unindex_object(self, documentId): """ Remove the object corresponding to 'documentId' from the index. """ datum = self._unindex.get(documentId, None) if datum is None: return since, until = datum self._removeForwardIndexEntry(since, until, documentId) del self._unindex[documentId] def uniqueValues(self, name=None, withLengths=0): """ Return a list of unique values for 'name'. If 'withLengths' is true, return a sequence of tuples, in the form '(value, length)'. """ if not name in (self._since_field, self._until_field): return [] if name == self._since_field: t1 = self._since t2 = self._since_only else: t1 = self._until t2 = self._until_only result = [] if not withLengths: result.extend(t1.keys()) result.extend(t2.keys()) else: for key in t1.keys(): set = t1[key] if isinstance(set, int): length = 1 else: length = len(set) result.append((key, length)) for key in t2.keys(): set = t2[key] if isinstance(set, int): length = 1 else: length = len(set) result.append((key, length)) return tuple(result) def _cache_key(self, catalog): cid = catalog.getId() counter = getattr(aq_base(catalog), 'getCounter', None) if counter is not None: return '%s_%s' % (cid, counter()) return cid def _apply_index(self, request, resultset=None): """ Apply the index to query parameters given in 'request', which should be a mapping object. If the request does not contain the needed parameters, then return None. Otherwise return two objects. The first object is a ResultSet containing the record numbers of the matching records. The second object is a tuple containing the names of all data fields used. """ iid = self.id record = parseIndexRequest(request, iid, self.query_options) if record.keys is None: return None term = self._convertDateTime(record.keys[0]) REQUEST = aq_get(self, 'REQUEST', None) if REQUEST is not None: catalog = aq_parent(aq_parent(aq_inner(self))) if catalog is not None: key = self._cache_key(catalog) cache = REQUEST.get(key, None) tid = isinstance(term, int) and term / 10 or 'None' if resultset is None: cachekey = '_daterangeindex_%s_%s' % (iid, tid) else: cachekey = '_daterangeindex_inverse_%s_%s' % (iid, tid) if cache is None: cache = REQUEST[key] = RequestCache() else: cached = cache.get(cachekey, None) if cached is not None: if resultset is None: return (cached, (self._since_field, self._until_field)) else: return (difference(resultset, cached), (self._since_field, self._until_field)) if resultset is None: # Aggregate sets for each bucket separately, to avoid # large-small union penalties. until_only = multiunion(self._until_only.values(term)) since_only = multiunion(self._since_only.values(None, term)) until = multiunion(self._until.values(term)) # Total result is bound by resultset if REQUEST is None: until = intersection(resultset, until) since = multiunion(self._since.values(None, term)) bounded = intersection(until, since) # Merge from smallest to largest. result = multiunion([bounded, until_only, since_only, self._always]) if REQUEST is not None and catalog is not None: cache[cachekey] = result return (result, (self._since_field, self._until_field)) else: # Compute the inverse and subtract from res until_only = multiunion(self._until_only.values(None, term - 1)) since_only = multiunion(self._since_only.values(term + 1)) until = multiunion(self._until.values(None, term - 1)) since = multiunion(self._since.values(term + 1)) result = multiunion([until_only, since_only, until, since]) if REQUEST is not None and catalog is not None: cache[cachekey] = result return (difference(resultset, result), (self._since_field, self._until_field)) def _insert_migrate(self, tree, key, value): treeset = tree.get(key, None) if treeset is None: tree[key] = IITreeSet((value, )) else: if isinstance(treeset, IITreeSet): treeset.insert(value) elif isinstance(treeset, int): tree[key] = IITreeSet((treeset, value)) else: tree[key] = IITreeSet(treeset) tree[key].insert(value) def _insertForwardIndexEntry(self, since, until, documentId): """Insert 'documentId' into the appropriate set based on 'datum'. """ if since is None and until is None: self._always.insert(documentId) elif since is None: self._insert_migrate(self._until_only, until, documentId) elif until is None: self._insert_migrate(self._since_only, since, documentId) else: self._insert_migrate(self._since, since, documentId) self._insert_migrate(self._until, until, documentId) def _remove_delete(self, tree, key, value): treeset = tree.get(key, None) if treeset is not None: if isinstance(treeset, int): del tree[key] else: treeset.remove(value) if not treeset: del tree[key] def _removeForwardIndexEntry(self, since, until, documentId): """Remove 'documentId' from the appropriate set based on 'datum'. """ if since is None and until is None: self._always.remove(documentId) elif since is None: self._remove_delete(self._until_only, until, documentId) elif until is None: self._remove_delete(self._since_only, since, documentId) else: self._remove_delete(self._since, since, documentId) self._remove_delete(self._until, until, documentId) def _convertDateTime(self, value): if value is None: return value if isinstance(value, (str, datetime)): dt_obj = DateTime(value) value = dt_obj.millis() / 1000 / 60 # flatten to minutes elif isinstance(value, DateTime): value = value.millis() / 1000 / 60 # flatten to minutes if value > MAX32 or value < -MAX32: # t_val must be integer fitting in the 32bit range raise OverflowError('%s is not within the range of dates allowed' 'by a DateRangeIndex' % value) value = int(value) # handle values outside our specified range if value > self.ceiling_value: return None elif value < self.floor_value: return None return value InitializeClass(DateRangeIndex) manage_addDateRangeIndexForm = DTMLFile('addDateRangeIndex', _dtmldir) def manage_addDateRangeIndex(self, id, extra=None, REQUEST=None, RESPONSE=None, URL3=None): """ Add a date range index to the catalog, using the incredibly icky double-indirection-which-hides-NOTHING. """ return self.manage_addIndex(id, 'DateRangeIndex', extra, REQUEST, RESPONSE, URL3) zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/__init__.py0000644000175000017500000000002312214017452031041 0ustar arnauarnau# Empty on purpose zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/README.txt0000644000175000017500000000272012214017452030434 0ustar arnauarnauDateRangeIndex README Overview Zope applications frequently wish to perform efficient queries against a pair of date attributes/methods, representing a time interval (e.g., effective / expiration dates). This query *can* be done using a pair of indexes, but this implementation is hideously expensive: o DateTime instances are *huge*, both in RAM and on disk. o DateTime instances maintain an absurd amount of precision, far beyond any reasonable search criteria for "normal" cases. o Results must be fetched and intersected between two indexes. o Handling objects which do not specify both endpoints (i.e., where the interval is open or half-open) is iffy, as the default value needs to be coerced into a different abnormal value for each end to permit ordered comparison. o The *very* common case of the open interval (neither endpoint specified) should be optimized. DateRangeIndex is a pluggable index which addresses these issues as follows: o It groups the "open" case into a special set, '_always'. o It maintains separate ordered sets for each of the "half-open" cases. o It performs the expensive "intersect two range search" operation only on the (usually small) set of objects which provide a closed interval. o It flattens the key values into integers with granularity of one minute. o It normalizes the 'query' value into the same form. zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/tests/0000755000175000017500000000000012214017452030077 5ustar arnauarnau././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/tests/__init__.pyzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/tests/__init__.p0000644000175000017500000000125312214017452032020 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This file is needed to make this a package. ././@LongLink0000000000000000000000000000016100000000000011563 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/tests/test_DateRangeIndex.pyzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/tests/test_DateR0000644000175000017500000002216512214017452032066 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest class Dummy(object): def __init__(self, name, start, stop): self._name = name self._start = start self._stop = stop def name(self): return self._name def start(self): return self._start def stop(self): return self._stop def datum(self): return (self._start, self._stop) dummies = [ Dummy( 'a', None, None ) , Dummy( 'b', None, None ) , Dummy( 'c', 0, None ) , Dummy( 'd', 10, None ) , Dummy( 'e', None, 4 ) , Dummy( 'f', None, 11 ) , Dummy( 'g', 0, 11 ) , Dummy( 'h', 2, 9 ) ] def matchingDummies(value): result = [] for dummy in dummies: if ((dummy.start() is None or dummy.start() <= value) and (dummy.stop() is None or dummy.stop() >= value)): result.append(dummy) return result class DRI_Tests(unittest.TestCase): def _getTargetClass(self): from Products.PluginIndexes.DateRangeIndex.DateRangeIndex \ import DateRangeIndex return DateRangeIndex def _makeOne(self, id, since_field=None, until_field=None, caller=None, extra=None): klass = self._getTargetClass() return klass(id, since_field, until_field, caller, extra) def test_interfaces(self): from Products.PluginIndexes.interfaces import IDateRangeIndex from Products.PluginIndexes.interfaces import IPluggableIndex from Products.PluginIndexes.interfaces import ISortIndex from Products.PluginIndexes.interfaces import IUniqueValueIndex from zope.interface.verify import verifyClass verifyClass(IDateRangeIndex, self._getTargetClass()) verifyClass(IPluggableIndex, self._getTargetClass()) verifyClass(ISortIndex, self._getTargetClass()) verifyClass(IUniqueValueIndex, self._getTargetClass()) def test_empty(self): empty = self._makeOne('empty') self.assertTrue(empty.getEntryForObject(1234) is None) empty.unindex_object(1234) # shouldn't throw self.assertFalse(empty.uniqueValues('foo')) self.assertFalse(empty.uniqueValues('foo', 1)) self.assertTrue(empty._apply_index({'zed': 12345}) is None) result, used = empty._apply_index({'empty': 12345}) self.assertFalse(result) self.assertEqual(used, (None, None)) def test_retrieval(self): index = self._makeOne('work', 'start', 'stop') for i in range(len(dummies)): index.index_object(i, dummies[i]) for i in range(len(dummies)): self.assertEqual(index.getEntryForObject(i), dummies[i].datum()) for value in range(-1, 15): matches = matchingDummies(value) results, used = index._apply_index({'work': value}) self.assertEqual(used, ('start', 'stop')) self.assertEqual(len(matches), len(results)) matches.sort(lambda x, y: cmp(x.name(), y.name())) for result, match in map(None, results, matches): self.assertEqual(index.getEntryForObject(result), match.datum()) def test_longdates(self): too_large = long(2**31) too_small = - long(2**31) index = self._makeOne('work', 'start', 'stop') bad = Dummy('bad', too_large, too_large) self.assertRaises(OverflowError, index.index_object, 0, bad) bad = Dummy('bad', too_small, too_small) self.assertRaises(OverflowError, index.index_object, 0, bad) def test_floor_date(self): index = self._makeOne('work', 'start', 'stop') floor = index.floor_value - 1 bad = Dummy('bad', floor, None) index.index_object(0, bad) self.assertTrue(0 in index._always.keys()) def test_ceiling_date(self): index = self._makeOne('work', 'start', 'stop') ceiling = index.ceiling_value + 1 bad = Dummy('bad', None, ceiling) index.index_object(1, bad) self.assertTrue(1 in index._always.keys()) def test_datetime(self): from datetime import datetime from DateTime.DateTime import DateTime from Products.PluginIndexes.DateIndex.tests.test_DateIndex \ import _getEastern before = datetime(2009, 7, 11, 0, 0, tzinfo=_getEastern()) start = datetime(2009, 7, 13, 5, 15, tzinfo=_getEastern()) between = datetime(2009, 7, 13, 5, 45, tzinfo=_getEastern()) stop = datetime(2009, 7, 13, 6, 30, tzinfo=_getEastern()) after = datetime(2009, 7, 14, 0, 0, tzinfo=_getEastern()) dummy = Dummy('test', start, stop) index = self._makeOne('work', 'start', 'stop') index.index_object(0, dummy) self.assertEqual(index.getEntryForObject(0), (DateTime(start).millis() / 60000, DateTime(stop).millis() / 60000)) results, used = index._apply_index({'work': before}) self.assertEqual(len(results), 0) results, used = index._apply_index({'work': start}) self.assertEqual(len(results), 1) results, used = index._apply_index({'work': between}) self.assertEqual(len(results), 1) results, used = index._apply_index({'work': stop}) self.assertEqual(len(results), 1) results, used = index._apply_index({'work': after}) self.assertEqual(len(results), 0) def test_datetime_naive_timezone(self): from datetime import datetime from DateTime.DateTime import DateTime from Products.PluginIndexes.DateIndex.DateIndex import Local before = datetime(2009, 7, 11, 0, 0) start = datetime(2009, 7, 13, 5, 15) start_local = datetime(2009, 7, 13, 5, 15, tzinfo=Local) between = datetime(2009, 7, 13, 5, 45) stop = datetime(2009, 7, 13, 6, 30) stop_local = datetime(2009, 7, 13, 6, 30, tzinfo=Local) after = datetime(2009, 7, 14, 0, 0) dummy = Dummy('test', start, stop) index = self._makeOne('work', 'start', 'stop') index.index_object(0, dummy) self.assertEqual(index.getEntryForObject(0), (DateTime(start_local).millis() / 60000, DateTime(stop_local).millis() / 60000)) results, used = index._apply_index({'work': before}) self.assertEqual(len(results), 0) results, used = index._apply_index({'work': start}) self.assertEqual(len(results), 1) results, used = index._apply_index({'work': between}) self.assertEqual(len(results), 1) results, used = index._apply_index({'work': stop}) self.assertEqual(len(results), 1) results, used = index._apply_index({'work': after}) self.assertEqual(len(results), 0) def test_resultset(self): from BTrees.IIBTree import IISet index = self._makeOne('work', 'start', 'stop') for i in range(len(dummies)): index.index_object(i, dummies[i]) results, used = index._apply_index({'work': 20}) self.assertEqual(set(results), set([0, 1, 2, 3])) # a resultset with everything doesn't actually limit results, used = index._apply_index({'work': 20}, resultset=IISet(range(len(dummies)))) self.assertEqual(set(results), set([0, 1, 2, 3])) # a small resultset limits results, used = index._apply_index({'work': 20}, resultset=IISet([1, 2])) self.assertEqual(set(results), set([1, 2])) # the specified value is included results, used = index._apply_index({'work': 11}) self.assertEqual(set(results), set([0, 1, 2, 3, 5, 6])) # also for _since_only results, used = index._apply_index({'work': 10}) self.assertEqual(set(results), set([0, 1, 2, 3, 5, 6])) # the specified value is included with a large resultset results, used = index._apply_index({'work': 11}, resultset=IISet(range(len(dummies)))) self.assertEqual(set(results), set([0, 1, 2, 3, 5, 6])) # this also works for _since_only results, used = index._apply_index({'work': 10}, resultset=IISet(range(len(dummies)))) self.assertEqual(set(results), set([0, 1, 2, 3, 5, 6])) # the specified value is included with a small resultset results, used = index._apply_index({'work': 11}, resultset=IISet([0, 5, 7])) self.assertEqual(set(results), set([0, 5])) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(DRI_Tests)) return suite zope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/dtml/0000755000175000017500000000000012214017452027675 5ustar arnauarnau././@LongLink0000000000000000000000000000016300000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/dtml/manageDateRangeIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/dtml/manageDateR0000644000175000017500000000256712214017452032002 0ustar arnauarnau

You can update this DateRangeIndex by editing the following field and clicking .

Objects indexed:
Distinct values:

Since field
Until field
Floor value
Ceiling value
././@LongLink0000000000000000000000000000016000000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/dtml/addDateRangeIndex.dtmlzope2.13-2.13.21/source/Products.ZCatalog/src/Products/PluginIndexes/DateRangeIndex/dtml/addDateRang0000644000175000017500000000350512214017452031761 0ustar arnauarnau

A DateRangeIndex takes the name of two input attributes; one containing the start date of the range, the second the end of the range. This index is filled with range information based on those two markers. You can then search for objects for those where a given date falls within the range.

Id
Since field
Until field
Floor value
Ceiling value
zope2.13-2.13.21/source/Products.ZCatalog/src/Products.ZCatalog.egg-info/0000755000175000017500000000000012214017452024532 5ustar arnauarnauzope2.13-2.13.21/source/Products.ZCatalog/src/Products.ZCatalog.egg-info/PKG-INFO0000644000175000017500000002216312214017452025633 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.ZCatalog Version: 2.13.23 Summary: Zope 2's indexing and search solution. Home-page: http://pypi.python.org/pypi/Products.ZCatalog Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== The ZCatalog is Zope 2’s built in search engine. It allows you to categorize and search all kinds of Zope objects. It comes with a variety of indexes for different types of data. Changelog ========= 2.13.23 (2012-04-26) -------------------- - Fixed another issue with preserving score values, when a custom index was queried first which was neither ILimitedResultIndex aware nor return scores, and a later index was of the default ZCTextIndex type. 2.13.22 (2011-11-17) -------------------- - Added a new `load_from_path` class method to the `PriorityMap`, which allows one to load a plan from a file, instead of a module via an environment var. 2.13.21 (2011-10-20) -------------------- - Refactored value index logic. Determine value indexes per catalog instead of globally. Store value index set in the priority map, so it can be seen in the ZMI and stored in the module level storage. - Added support for using ZCatalog as local utility. This feature requires the optional `five.globalrequest` dependency. 2.13.20 (2011-08-23) -------------------- - Fixed incorrect calculation of batches in the second half of the result set in sortResults. 2.13.19 (2011-08-20) -------------------- - Increase plan precision to 4 digits in its string representation. 2.13.18 (2011-07-29) -------------------- - In the string representation of a catalog plan, round the times to at most two digits after the comma. 2.13.17 (2011-07-29) -------------------- - Put back the `weightedIntersection` optimization but guard against results with values and do the appropriate fallback to the weighted version. 2.13.16 (2011-07-24) -------------------- - Restored preserving score values from ZCTextIndex indices. https://bugs.launchpad.net/zope2/+bug/815469 2.13.15 (2011-06-30) -------------------- - Fixed undefined variables in BooleanIndex inline migration code. - Fixed BooleanIndex' items method so the ZMI browse view works. 2.13.14 (2011-05-19) -------------------- - Fixed addition of two LazyCat's if any of them was already flattened. - Extend BooleanIndex by making the indexed value variable instead of hardcoding it to `True`. The indexed value will determine the smaller set automatically and choose its best value. An inline switch is done once the indexed value set grows larger than 60% of the total length. 60% was chosen to avoid constant switching for indexes that have an almost equal distribution of `True/False`. - Substitute catalog entry in UUIDIndex error message. 2.13.13 (2011-05-04) -------------------- - Optimize `Catalog.updateMetadata` avoiding a `self.uids` lookup and removing inline migration code for converting `self.data` from non-IOBTree types. - In the path index, don't update data if the value hasn't changed. 2.13.12 (2011-05-02) -------------------- - Optimize DateRangeIndex for better conflict resolution handling. It always starts out with storing an IITreeSet of the value instead of special casing storing an int for a single value. The `single value as int` optimization should be provided via a separate API to be called periodically outside the context of a normal request. - Replaced `weightedIntersection` and `weightedUnion` calls with their non-weighted version, as we didn't pass in weights. 2.13.11 (2011-05-02) -------------------- - Fix possible TypeError in `sortResults` method if only b_start but not b_size has been provided. - Prevent the new UUIDIndex from acquiring attributes via Acquisition. 2.13.10 (2011-04-21) -------------------- - Handle `TypeErrors` in the KeywordIndex if an indexed attribute is a method with required arguments. - Added reporting of the intersection time of each index' result with the result set of the other indexes and consider this time to be part of each index time for prioritizing the index. - Removed tracking of result length from the query plan. The calculation of the length of an intermediate index result can itself be expensive. 2.13.9 (2011-04-10) ------------------- - Added a floor and ceiling value to the date range index. Values outside the specified range will be interpreted the same way as passing `None`, i.e. `since the beginning of time` and `until the end of it`. This allows the index to apply its optimizations, while objects with values outside this range can still be stored in a normal date index, which omits explicitly passed in `None` values. 2.13.8 (2011-04-01) ------------------- - Fixed bug in date range index, which would omit objects exactly matching the query term if a resultset was provided. - Fixed the BooleanIndex to not index objects without the cataloged attribute. 2.13.7 (2011-02-15) ------------------- - Fixed the `DateIndex._unindex` to be of type `IIBTree` instead of `OIBTree`. It stores document ids as keys, which can only be ints. 2.13.6 (2011-02-10) ------------------- - Remove docstrings from various methods, as they shouldn't be web-publishable. 2.13.5 (2011-02-05) ------------------- - Fixed test failures introduced in 2.13.4. 2.13.4 (2011-02-05) ------------------- - Added a new UUIDIndex, based on the common UnIndex. It behaves like a FieldIndex, but can only store one document id per value, so there's a 1:1 mapping from value to document id. An error is logged if a different document id is indexed for an already taken value. The internal data structures are optimized for this and avoid storing one IITreeSet per value. - Optimize sorting in presence of batching arguments. If a batch from the end of the result set is requested, we internally reverse the sorting order and at the end reverse the lazy sequence again. In a sequence with 100 entries, if we request the batch with items 80 to 90, we now reverse sort 20 items (100 to 80), slice of the first ten items and then reverse them. Before we would had to sort the first 90 items and then slice of the last 10. - If batching arguments are provided, limit the returned lazy sequence to the items in the required batch instead of returning leading items falling outside of the requested batch. - Fixed inline `IISet` to `IITreeSet` conversion code inside DateRangeIndex' `_insertForwardIndexEntry` method. 2.13.3 (2011-01-01) ------------------- - Avoid locale-dependent test condition in `test_length_with_filter`. 2.13.2 (2010-12-31) ------------------- - Preserve `actual_result_count` on flattening nested LazyCat's. - Preserve the `actual_result_count` on all lazy return values. This allows to get proper batching information from catalog results which have been restricted by `sort_limit`. - Made sure `actual_result_count` is available on all lazy classes and falls back to `__len__` if not explicitly provided. - Optimized length calculation of Lazy classes. 2.13.1 (2010-12-25) ------------------- - Added automatic sorting limit calculation based on batch arguments. If the query contains a `b_start` and `b_size` argument and no explicit `sort_limit` is provided, the sort limit will be calculated as `b_start + b_size`. - Avoid pre-allocation of marker items in `LazyMap`. 2.13.0 (2010-12-25) ------------------- - Fix `LazyMap` to avoid unnecessary function calls. - Released as separate distribution. Platform: UNKNOWN zope2.13-2.13.21/source/Products.ZCatalog/src/Products.ZCatalog.egg-info/dependency_links.txt0000644000175000017500000000000112214017452030600 0ustar arnauarnau zope2.13-2.13.21/source/Products.ZCatalog/src/Products.ZCatalog.egg-info/requires.txt0000644000175000017500000000033612214017452027134 0ustar arnauarnausetuptools AccessControl Acquisition DateTime DocumentTemplate ExtensionClass Missing Persistence Products.ZCTextIndex Record RestrictedPython zExceptions ZODB3 Zope2 zope.dottedname zope.interface zope.schema zope.testingzope2.13-2.13.21/source/Products.ZCatalog/src/Products.ZCatalog.egg-info/namespace_packages.txt0000644000175000017500000000001112214017452031055 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.ZCatalog/src/Products.ZCatalog.egg-info/top_level.txt0000644000175000017500000000001112214017452027254 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.ZCatalog/src/Products.ZCatalog.egg-info/SOURCES.txt0000644000175000017500000001226112214017452026420 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt setup.py src/Products/__init__.py src/Products.ZCatalog.egg-info/PKG-INFO src/Products.ZCatalog.egg-info/SOURCES.txt src/Products.ZCatalog.egg-info/dependency_links.txt src/Products.ZCatalog.egg-info/namespace_packages.txt src/Products.ZCatalog.egg-info/not-zip-safe src/Products.ZCatalog.egg-info/requires.txt src/Products.ZCatalog.egg-info/top_level.txt src/Products/PluginIndexes/__init__.py src/Products/PluginIndexes/interfaces.py src/Products/PluginIndexes/BooleanIndex/BooleanIndex.py src/Products/PluginIndexes/BooleanIndex/__init__.py src/Products/PluginIndexes/BooleanIndex/tests.py src/Products/PluginIndexes/BooleanIndex/dtml/addBooleanIndex.dtml src/Products/PluginIndexes/BooleanIndex/dtml/manageBooleanIndex.dtml src/Products/PluginIndexes/DateIndex/DateIndex.py src/Products/PluginIndexes/DateIndex/README.txt src/Products/PluginIndexes/DateIndex/__init__.py src/Products/PluginIndexes/DateIndex/dtml/addDateIndex.dtml src/Products/PluginIndexes/DateIndex/dtml/manageDateIndex.dtml src/Products/PluginIndexes/DateIndex/tests/__init__.py src/Products/PluginIndexes/DateIndex/tests/test_DateIndex.py src/Products/PluginIndexes/DateRangeIndex/DateRangeIndex.py src/Products/PluginIndexes/DateRangeIndex/README.txt src/Products/PluginIndexes/DateRangeIndex/__init__.py src/Products/PluginIndexes/DateRangeIndex/dtml/addDateRangeIndex.dtml src/Products/PluginIndexes/DateRangeIndex/dtml/manageDateRangeIndex.dtml src/Products/PluginIndexes/DateRangeIndex/tests/__init__.py src/Products/PluginIndexes/DateRangeIndex/tests/test_DateRangeIndex.py src/Products/PluginIndexes/FieldIndex/FieldIndex.py src/Products/PluginIndexes/FieldIndex/__init__.py src/Products/PluginIndexes/FieldIndex/dtml/addFieldIndex.dtml src/Products/PluginIndexes/FieldIndex/dtml/manageFieldIndex.dtml src/Products/PluginIndexes/FieldIndex/tests/__init__.py src/Products/PluginIndexes/FieldIndex/tests/testFieldIndex.py src/Products/PluginIndexes/KeywordIndex/KeywordIndex.py src/Products/PluginIndexes/KeywordIndex/__init__.py src/Products/PluginIndexes/KeywordIndex/dtml/addKeywordIndex.dtml src/Products/PluginIndexes/KeywordIndex/dtml/manageKeywordIndex.dtml src/Products/PluginIndexes/KeywordIndex/tests/__init__.py src/Products/PluginIndexes/KeywordIndex/tests/testKeywordIndex.py src/Products/PluginIndexes/PathIndex/PathIndex.py src/Products/PluginIndexes/PathIndex/PathIndex.txt src/Products/PluginIndexes/PathIndex/__init__.py src/Products/PluginIndexes/PathIndex/dtml/addPathIndex.dtml src/Products/PluginIndexes/PathIndex/dtml/managePathIndex.dtml src/Products/PluginIndexes/PathIndex/tests/__init__.py src/Products/PluginIndexes/PathIndex/tests/testPathIndex.py src/Products/PluginIndexes/TopicIndex/FilteredSet.py src/Products/PluginIndexes/TopicIndex/README.txt src/Products/PluginIndexes/TopicIndex/TopicIndex.py src/Products/PluginIndexes/TopicIndex/__init__.py src/Products/PluginIndexes/TopicIndex/dtml/addTopicIndex.dtml src/Products/PluginIndexes/TopicIndex/dtml/editFilteredSet.dtml src/Products/PluginIndexes/TopicIndex/dtml/manageTopicIndex.dtml src/Products/PluginIndexes/TopicIndex/tests/__init__.py src/Products/PluginIndexes/TopicIndex/tests/testTopicIndex.py src/Products/PluginIndexes/UUIDIndex/UUIDIndex.py src/Products/PluginIndexes/UUIDIndex/__init__.py src/Products/PluginIndexes/UUIDIndex/tests.py src/Products/PluginIndexes/UUIDIndex/dtml/addUUIDIndex.dtml src/Products/PluginIndexes/UUIDIndex/dtml/manageUUIDIndex.dtml src/Products/PluginIndexes/common/ResultList.py src/Products/PluginIndexes/common/UnIndex.py src/Products/PluginIndexes/common/__init__.py src/Products/PluginIndexes/common/randid.py src/Products/PluginIndexes/common/util.py src/Products/PluginIndexes/common/tests/__init__.py src/Products/PluginIndexes/common/tests/test_UnIndex.py src/Products/PluginIndexes/common/tests/test_util.py src/Products/PluginIndexes/dtml/browseIndex.dtml src/Products/PluginIndexes/www/index.gif src/Products/ZCatalog/Catalog.gif src/Products/ZCatalog/Catalog.py src/Products/ZCatalog/CatalogAwareness.py src/Products/ZCatalog/CatalogBrains.py src/Products/ZCatalog/CatalogPathAwareness.py src/Products/ZCatalog/Lazy.py src/Products/ZCatalog/ProgressHandler.py src/Products/ZCatalog/ZCatalog.py src/Products/ZCatalog/ZCatalogIndexes.py src/Products/ZCatalog/__init__.py src/Products/ZCatalog/interfaces.py src/Products/ZCatalog/plan.py src/Products/ZCatalog/dtml/addIndexForm.dtml src/Products/ZCatalog/dtml/addZCatalog.dtml src/Products/ZCatalog/dtml/catalogAdvanced.dtml src/Products/ZCatalog/dtml/catalogFind.dtml src/Products/ZCatalog/dtml/catalogIndexes.dtml src/Products/ZCatalog/dtml/catalogObjectInformation.dtml src/Products/ZCatalog/dtml/catalogPlan.dtml src/Products/ZCatalog/dtml/catalogReport.dtml src/Products/ZCatalog/dtml/catalogSchema.dtml src/Products/ZCatalog/dtml/catalogStatus.dtml src/Products/ZCatalog/dtml/catalogView.dtml src/Products/ZCatalog/dtml/editCatalogerForm.dtml src/Products/ZCatalog/tests/__init__.py src/Products/ZCatalog/tests/queryplan.py src/Products/ZCatalog/tests/test_brains.py src/Products/ZCatalog/tests/test_catalog.py src/Products/ZCatalog/tests/test_lazy.py src/Products/ZCatalog/tests/test_plan.py src/Products/ZCatalog/tests/test_zcatalog.py src/Products/ZCatalog/www/ZCatalog.gifzope2.13-2.13.21/source/Products.ZCatalog/src/Products.ZCatalog.egg-info/not-zip-safe0000644000175000017500000000000112214017452026760 0ustar arnauarnau zope2.13-2.13.21/source/Missing/0000755000175000017500000000000012214017442015045 5ustar arnauarnauzope2.13-2.13.21/source/Missing/setup.py0000644000175000017500000000277412214017442016571 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from os.path import join from setuptools import setup, find_packages, Extension setup(name='Missing', version = '2.13.1', url='http://pypi.python.org/pypi/Missing', license='ZPL 2.1', description="Special Missing objects used in Zope2.", author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=open('README.txt').read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), package_dir={'': 'src'}, ext_modules=[Extension( name='Missing._Missing', include_dirs=['include', 'src'], sources=[join('src', 'Missing', '_Missing.c')], depends=[join('include', 'ExtensionClass', 'ExtensionClass.h')]), ], install_requires=['ExtensionClass'], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/Missing/PKG-INFO0000644000175000017500000000126412214017442016145 0ustar arnauarnauMetadata-Version: 1.0 Name: Missing Version: 2.13.1 Summary: Special Missing objects used in Zope2. Home-page: http://pypi.python.org/pypi/Missing Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== Missing provides special objects used in some Zope2 internals like the ZCatalog. Changelog ========= 2.13.1 (2010-06-16) ------------------- - Added a ``__class__`` to Missing.Value objects. 2.13.0 (2010-03-30) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Missing/pip-egg-info/0000755000175000017500000000000012214017443017327 5ustar arnauarnauzope2.13-2.13.21/source/Missing/pip-egg-info/Missing.egg-info/0000755000175000017500000000000012214017443022432 5ustar arnauarnauzope2.13-2.13.21/source/Missing/pip-egg-info/Missing.egg-info/PKG-INFO0000644000175000017500000000126412214017443023532 0ustar arnauarnauMetadata-Version: 1.0 Name: Missing Version: 2.13.1 Summary: Special Missing objects used in Zope2. Home-page: http://pypi.python.org/pypi/Missing Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== Missing provides special objects used in some Zope2 internals like the ZCatalog. Changelog ========= 2.13.1 (2010-06-16) ------------------- - Added a ``__class__`` to Missing.Value objects. 2.13.0 (2010-03-30) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Missing/pip-egg-info/Missing.egg-info/dependency_links.txt0000644000175000017500000000000112214017443026500 0ustar arnauarnau zope2.13-2.13.21/source/Missing/pip-egg-info/Missing.egg-info/requires.txt0000644000175000017500000000001612214017443025027 0ustar arnauarnauExtensionClasszope2.13-2.13.21/source/Missing/pip-egg-info/Missing.egg-info/top_level.txt0000644000175000017500000000001012214017443025153 0ustar arnauarnauMissing zope2.13-2.13.21/source/Missing/pip-egg-info/Missing.egg-info/SOURCES.txt0000644000175000017500000000053612214017443024322 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/Missing.egg-info/PKG-INFO pip-egg-info/Missing.egg-info/SOURCES.txt pip-egg-info/Missing.egg-info/dependency_links.txt pip-egg-info/Missing.egg-info/not-zip-safe pip-egg-info/Missing.egg-info/requires.txt pip-egg-info/Missing.egg-info/top_level.txt src/Missing/_Missing.c src/Missing/__init__.py src/Missing/tests.pyzope2.13-2.13.21/source/Missing/pip-egg-info/Missing.egg-info/not-zip-safe0000644000175000017500000000000112214017443024660 0ustar arnauarnau zope2.13-2.13.21/source/Missing/LICENSE.txt0000644000175000017500000000402612214017442016672 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/Missing/README.txt0000644000175000017500000000014412214017442016542 0ustar arnauarnauOverview ======== Missing provides special objects used in some Zope2 internals like the ZCatalog. zope2.13-2.13.21/source/Missing/setup.cfg0000644000175000017500000000007312214017442016666 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/Missing/COPYRIGHT.txt0000644000175000017500000000004012214017442017150 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/Missing/include/0000755000175000017500000000000012214017442016470 5ustar arnauarnauzope2.13-2.13.21/source/Missing/include/ExtensionClass/0000755000175000017500000000000012214017442021432 5ustar arnauarnauzope2.13-2.13.21/source/Missing/include/ExtensionClass/pickle/0000755000175000017500000000000012214017442022701 5ustar arnauarnauzope2.13-2.13.21/source/Missing/include/ExtensionClass/pickle/pickle.c0000644000175000017500000002202312214017442024313 0ustar arnauarnau/* Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. */ /* Reusable pickle support code This is "includeware", meant to be used through a C include */ /* It's a dang shame we can't inherit __get/setstate__ from object :( */ static PyObject *str__slotnames__, *copy_reg_slotnames, *__newobj__; static PyObject *str__getnewargs__, *str__getstate__; #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) typedef int Py_ssize_t; #define PY_SSIZE_T_MAX INT_MAX #define PY_SSIZE_T_MIN INT_MIN #endif static int pickle_setup(void) { PyObject *copy_reg; int r = -1; #define DEFINE_STRING(S) \ if(! (str ## S = PyString_FromString(# S))) return -1 DEFINE_STRING(__slotnames__); DEFINE_STRING(__getnewargs__); DEFINE_STRING(__getstate__); #undef DEFINE_STRING copy_reg = PyImport_ImportModule("copy_reg"); if (copy_reg == NULL) return -1; copy_reg_slotnames = PyObject_GetAttrString(copy_reg, "_slotnames"); if (copy_reg_slotnames == NULL) goto end; __newobj__ = PyObject_GetAttrString(copy_reg, "__newobj__"); if (__newobj__ == NULL) goto end; r = 0; end: Py_DECREF(copy_reg); return r; } static PyObject * pickle_slotnames(PyTypeObject *cls) { PyObject *slotnames; slotnames = PyDict_GetItem(cls->tp_dict, str__slotnames__); if (slotnames != NULL) { Py_INCREF(slotnames); return slotnames; } slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames, (PyObject*)cls, NULL); if (slotnames != NULL && slotnames != Py_None && ! PyList_Check(slotnames)) { PyErr_SetString(PyExc_TypeError, "copy_reg._slotnames didn't return a list or None"); Py_DECREF(slotnames); slotnames = NULL; } return slotnames; } static PyObject * pickle_copy_dict(PyObject *state) { PyObject *copy, *key, *value; char *ckey; Py_ssize_t pos = 0; Py_ssize_t nr; copy = PyDict_New(); if (copy == NULL) return NULL; if (state == NULL) return copy; while ((nr = PyDict_Next(state, &pos, &key, &value))) { if (nr < 0) goto err; if (key && PyString_Check(key)) { ckey = PyString_AS_STRING(key); if (*ckey == '_' && (ckey[1] == 'v' || ckey[1] == 'p') && ckey[2] == '_') /* skip volatile and persistent */ continue; } if (key != NULL && value != NULL && (PyObject_SetItem(copy, key, value) < 0) ) goto err; } return copy; err: Py_DECREF(copy); return NULL; } static char pickle___getstate__doc[] = "Get the object serialization state\n" "\n" "If the object has no assigned slots and has no instance dictionary, then \n" "None is returned.\n" "\n" "If the object has no assigned slots and has an instance dictionary, then \n" "the a copy of the instance dictionary is returned. The copy has any items \n" "with names starting with '_v_' or '_p_' ommitted.\n" "\n" "If the object has assigned slots, then a two-element tuple is returned. \n" "The first element is either None or a copy of the instance dictionary, \n" "as described above. The second element is a dictionary with items \n" "for each of the assigned slots.\n" ; static PyObject * pickle___getstate__(PyObject *self) { PyObject *slotnames=NULL, *slots=NULL, *state=NULL; PyObject **dictp; int n=0; slotnames = pickle_slotnames(self->ob_type); if (slotnames == NULL) return NULL; dictp = _PyObject_GetDictPtr(self); if (dictp) state = pickle_copy_dict(*dictp); else { state = Py_None; Py_INCREF(state); } if (slotnames != Py_None) { int i; slots = PyDict_New(); if (slots == NULL) goto end; for (i = 0; i < PyList_GET_SIZE(slotnames); i++) { PyObject *name, *value; char *cname; name = PyList_GET_ITEM(slotnames, i); if (PyString_Check(name)) { cname = PyString_AS_STRING(name); if (*cname == '_' && (cname[1] == 'v' || cname[1] == 'p') && cname[2] == '_') /* skip volatile and persistent */ continue; } value = PyObject_GetAttr(self, name); if (value == NULL) PyErr_Clear(); else { int err = PyDict_SetItem(slots, name, value); Py_DECREF(value); if (err) goto end; n++; } } } if (n) state = Py_BuildValue("(NO)", state, slots); end: Py_XDECREF(slotnames); Py_XDECREF(slots); return state; } static int pickle_setattrs_from_dict(PyObject *self, PyObject *dict) { PyObject *key, *value; Py_ssize_t pos = 0; if (! PyDict_Check(dict)) { PyErr_SetString(PyExc_TypeError, "Expected dictionary"); return -1; } while (PyDict_Next(dict, &pos, &key, &value)) { if (key != NULL && value != NULL && (PyObject_SetAttr(self, key, value) < 0) ) return -1; } return 0; } static char pickle___setstate__doc[] = "Set the object serialization state\n" "\n" "The state should be in one of 3 forms:\n" "\n" "- None\n" "\n" " Ignored\n" "\n" "- A dictionary\n" "\n" " In this case, the object's instance dictionary will be cleared and \n" " updated with the new state.\n" "\n" "- A two-tuple with a string as the first element. \n" "\n" " In this case, the method named by the string in the first element will be\n" " called with the second element.\n" "\n" " This form supports migration of data formats.\n" "\n" "- A two-tuple with None or a Dictionary as the first element and\n" " with a dictionary as the second element.\n" "\n" " If the first element is not None, then the object's instance dictionary \n" " will be cleared and updated with the value.\n" "\n" " The items in the second element will be assigned as attributes.\n" ; static PyObject * pickle___setstate__(PyObject *self, PyObject *state) { PyObject *slots=NULL; if (PyTuple_Check(state)) { if (! PyArg_ParseTuple(state, "OO", &state, &slots)) return NULL; } if (state != Py_None) { PyObject **dict; dict = _PyObject_GetDictPtr(self); if (dict) { if (*dict == NULL) { *dict = PyDict_New(); if (*dict == NULL) return NULL; } } if (*dict != NULL) { PyDict_Clear(*dict); if (PyDict_Update(*dict, state) < 0) return NULL; } else if (pickle_setattrs_from_dict(self, state) < 0) return NULL; } if (slots != NULL && pickle_setattrs_from_dict(self, slots) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static char pickle___getnewargs__doc[] = "Get arguments to be passed to __new__\n" ; static PyObject * pickle___getnewargs__(PyObject *self) { return PyTuple_New(0); } static char pickle___reduce__doc[] = "Reduce an object to contituent parts for serialization\n" ; static PyObject * pickle___reduce__(PyObject *self) { PyObject *args=NULL, *bargs=0, *state=NULL; int l, i; /* we no longer require '__getnewargs__' to be defined but use it if it is */ PyObject *getnewargs=NULL; getnewargs = PyObject_GetAttr(self, str__getnewargs__); if (getnewargs) bargs = PyEval_CallObject(getnewargs, (PyObject *)NULL); else { PyErr_Clear(); bargs = PyTuple_New(0); } l = PyTuple_Size(bargs); if (l < 0) goto end; args = PyTuple_New(l+1); if (args == NULL) goto end; Py_INCREF(self->ob_type); PyTuple_SET_ITEM(args, 0, (PyObject*)(self->ob_type)); for (i = 0; i < l; i++) { Py_INCREF(PyTuple_GET_ITEM(bargs, i)); PyTuple_SET_ITEM(args, i+1, PyTuple_GET_ITEM(bargs, i)); } state = PyObject_CallMethodObjArgs(self, str__getstate__, NULL); if (state == NULL) goto end; state = Py_BuildValue("(OON)", __newobj__, args, state); end: Py_XDECREF(bargs); Py_XDECREF(args); Py_XDECREF(getnewargs); return state; } #define PICKLE_GETSTATE_DEF \ {"__getstate__", (PyCFunction)pickle___getstate__, METH_NOARGS, \ pickle___getstate__doc}, #define PICKLE_SETSTATE_DEF \ {"__setstate__", (PyCFunction)pickle___setstate__, METH_O, \ pickle___setstate__doc}, #define PICKLE_GETNEWARGS_DEF #define PICKLE_REDUCE_DEF \ {"__reduce__", (PyCFunction)pickle___reduce__, METH_NOARGS, \ pickle___reduce__doc}, #define PICKLE_METHODS PICKLE_GETSTATE_DEF PICKLE_SETSTATE_DEF \ PICKLE_GETNEWARGS_DEF PICKLE_REDUCE_DEF zope2.13-2.13.21/source/Missing/include/ExtensionClass/_ExtensionClass.c0000644000175000017500000006076512214017442024715 0ustar arnauarnau/* Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. */ static char _extensionclass_module_documentation[] = "ExtensionClass\n" "\n" "$Id: _ExtensionClass.c 109054 2010-02-14 21:35:34Z hannosch $\n" ; #include "ExtensionClass/ExtensionClass.h" #define EC PyTypeObject static PyObject *str__of__, *str__get__, *str__class_init__, *str__init__; static PyObject *str__bases__, *str__mro__, *str__new__; #define OBJECT(O) ((PyObject *)(O)) #define TYPE(O) ((PyTypeObject *)(O)) static PyTypeObject ExtensionClassType; static PyTypeObject BaseType; static PyObject * of_get(PyObject *self, PyObject *inst, PyObject *cls) { /* Descriptor slot function that calls __of__ */ if (inst && PyExtensionInstance_Check(inst)) return PyObject_CallMethodObjArgs(self, str__of__, inst, NULL); Py_INCREF(self); return self; } PyObject * Base_getattro(PyObject *obj, PyObject *name) { /* This is a modified copy of PyObject_GenericGetAttr. See the change note below. */ PyTypeObject *tp = obj->ob_type; PyObject *descr = NULL; PyObject *res = NULL; descrgetfunc f; long dictoffset; PyObject **dictptr; if (!PyString_Check(name)){ #ifdef Py_USING_UNICODE /* The Unicode to string conversion is done here because the existing tp_setattro slots expect a string object as name and we wouldn't want to break those. */ if (PyUnicode_Check(name)) { name = PyUnicode_AsEncodedString(name, NULL, NULL); if (name == NULL) return NULL; } else #endif { PyErr_SetString(PyExc_TypeError, "attribute name must be string"); return NULL; } } else Py_INCREF(name); if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) goto done; } #if !defined(Py_TPFLAGS_HAVE_VERSION_TAG) /* Inline _PyType_Lookup */ /* this is not quite _PyType_Lookup anymore */ { int i, n; PyObject *mro, *base, *dict; /* Look in tp_dict of types in MRO */ mro = tp->tp_mro; assert(mro != NULL); assert(PyTuple_Check(mro)); n = PyTuple_GET_SIZE(mro); for (i = 0; i < n; i++) { base = PyTuple_GET_ITEM(mro, i); if (PyClass_Check(base)) dict = ((PyClassObject *)base)->cl_dict; else { assert(PyType_Check(base)); dict = ((PyTypeObject *)base)->tp_dict; } assert(dict && PyDict_Check(dict)); descr = PyDict_GetItem(dict, name); if (descr != NULL) break; } } #else descr = _PyType_Lookup(tp, name); #endif Py_XINCREF(descr); f = NULL; if (descr != NULL && PyType_HasFeature(descr->ob_type, Py_TPFLAGS_HAVE_CLASS)) { f = descr->ob_type->tp_descr_get; if (f != NULL && PyDescr_IsData(descr)) { res = f(descr, obj, (PyObject *)obj->ob_type); Py_DECREF(descr); goto done; } } /* Inline _PyObject_GetDictPtr */ dictoffset = tp->tp_dictoffset; if (dictoffset != 0) { PyObject *dict; if (dictoffset < 0) { int tsize; size_t size; tsize = ((PyVarObject *)obj)->ob_size; if (tsize < 0) tsize = -tsize; size = _PyObject_VAR_SIZE(tp, tsize); dictoffset += (long)size; assert(dictoffset > 0); assert(dictoffset % SIZEOF_VOID_P == 0); } dictptr = (PyObject **) ((char *)obj + dictoffset); dict = *dictptr; if (dict != NULL) { Py_INCREF(dict); res = PyDict_GetItem(dict, name); if (res != NULL) { Py_INCREF(res); Py_XDECREF(descr); Py_DECREF(dict); /* CHANGED! If the tp_descr_get of res is of_get, then call it. */ if (PyObject_TypeCheck(res->ob_type, &ExtensionClassType) && res->ob_type->tp_descr_get != NULL) { PyObject *tres; tres = res->ob_type->tp_descr_get( res, obj, OBJECT(obj->ob_type)); Py_DECREF(res); res = tres; } goto done; } Py_DECREF(dict); } } if (f != NULL) { res = f(descr, obj, (PyObject *)obj->ob_type); Py_DECREF(descr); goto done; } if (descr != NULL) { res = descr; /* descr was already increfed above */ goto done; } /* CHANGED: Just use the name. Don't format. */ PyErr_SetObject(PyExc_AttributeError, name); done: Py_DECREF(name); return res; } #include "pickle/pickle.c" static struct PyMethodDef Base_methods[] = { PICKLE_METHODS {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static EC BaseType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "Base", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* tp_getattro */ (getattrofunc)Base_getattro, 0, 0, (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif ), "Standard ExtensionClass base type", 0, 0, 0, 0, 0, 0, Base_methods, }; static EC NoInstanceDictionaryBaseType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "NoInstanceDictionaryBase", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif ), "Base types for subclasses without instance dictionaries", }; static PyObject * EC_new(PyTypeObject *self, PyObject *args, PyObject *kw) { PyObject *name, *bases=NULL, *dict=NULL; PyObject *new_bases=NULL, *new_args, *result; int have_base = 0, i; if (kw && PyObject_IsTrue(kw)) { PyErr_SetString(PyExc_TypeError, "Keyword arguments are not supported"); return NULL; } if (!PyArg_ParseTuple(args, "O|O!O!", &name, &PyTuple_Type, &bases, &PyDict_Type, &dict)) return NULL; /* Make sure Base is in bases */ if (bases) { for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { if (PyObject_TypeCheck(PyTuple_GET_ITEM(bases, i), &ExtensionClassType)) { have_base = 1; break; } } if (! have_base) { new_bases = PyTuple_New(PyTuple_GET_SIZE(bases) + 1); if (new_bases == NULL) return NULL; for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { Py_XINCREF(PyTuple_GET_ITEM(bases, i)); PyTuple_SET_ITEM(new_bases, i, PyTuple_GET_ITEM(bases, i)); } Py_INCREF(OBJECT(&BaseType)); PyTuple_SET_ITEM(new_bases, PyTuple_GET_SIZE(bases), OBJECT(&BaseType)); } } else { new_bases = Py_BuildValue("(O)", &BaseType); if (new_bases == NULL) return NULL; } if (new_bases) { if (dict) new_args = Py_BuildValue("OOO", name, new_bases, dict); else new_args = Py_BuildValue("OO", name, new_bases); Py_DECREF(new_bases); if (new_args == NULL) return NULL; result = PyType_Type.tp_new(self, new_args, kw); Py_DECREF(new_args); } else { result = PyType_Type.tp_new(self, args, kw); /* We didn't have to add Base, so maybe NoInstanceDictionaryBase is in the bases. We need to check if it was. If it was, we need to suppress instance dictionary support. */ for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { if ( PyObject_TypeCheck(PyTuple_GET_ITEM(bases, i), &ExtensionClassType) && PyType_IsSubtype(TYPE(PyTuple_GET_ITEM(bases, i)), &NoInstanceDictionaryBaseType) ) { TYPE(result)->tp_dictoffset = 0; break; } } } return result; } /* set up __get__, if necessary */ static int EC_init_of(PyTypeObject *self) { PyObject *__of__; __of__ = PyObject_GetAttr(OBJECT(self), str__of__); if (__of__) { Py_DECREF(__of__); if (self->tp_descr_get) { if (self->tp_descr_get != of_get) { PyErr_SetString(PyExc_TypeError, "Can't mix __of__ and descriptors"); return -1; } } else self->tp_descr_get = of_get; } else { PyErr_Clear(); if (self->tp_descr_get == of_get) self->tp_descr_get = NULL; } return 0; } static int EC_init(PyTypeObject *self, PyObject *args, PyObject *kw) { PyObject *__class_init__, *r; if (PyType_Type.tp_init(OBJECT(self), args, kw) < 0) return -1; if (self->tp_dict != NULL) { r = PyDict_GetItemString(self->tp_dict, "__doc__"); if ((r == Py_None) && (PyDict_DelItemString(self->tp_dict, "__doc__") < 0) ) return -1; } if (EC_init_of(self) < 0) return -1; /* Call __class_init__ */ __class_init__ = PyObject_GetAttr(OBJECT(self), str__class_init__); if (__class_init__ == NULL) { PyErr_Clear(); return 0; } if (! (PyMethod_Check(__class_init__) && PyMethod_GET_FUNCTION(__class_init__) ) ) { Py_DECREF(__class_init__); PyErr_SetString(PyExc_TypeError, "Invalid type for __class_init__"); return -1; } r = PyObject_CallFunctionObjArgs(PyMethod_GET_FUNCTION(__class_init__), OBJECT(self), NULL); Py_DECREF(__class_init__); if (! r) return -1; Py_DECREF(r); return 0; } static int EC_setattro(PyTypeObject *type, PyObject *name, PyObject *value) { /* We want to allow setting attributes of builti-in types, because EC did in the past and there's code that relies on it. We can't really set slots though, but I don't think we need to. There's no good way to spot slots. We could use a lame rule like names that begin and end with __s and have just 4 _s smell too much like slots. */ if (! (type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { char *cname; int l; cname = PyString_AsString(name); if (cname == NULL) return -1; l = PyString_GET_SIZE(name); if (l > 4 && cname[0] == '_' && cname[1] == '_' && cname[l-1] == '_' && cname[l-2] == '_' ) { char *c; c = strchr(cname+2, '_'); if (c != NULL && (c - cname) >= (l-2)) { PyErr_Format (PyExc_TypeError, "can't set attributes of built-in/extension type '%s' if the " "attribute name begins and ends with __ and contains only " "4 _ characters", type->tp_name ); return -1; } } if (PyObject_GenericSetAttr(OBJECT(type), name, value) < 0) return -1; } else if (PyType_Type.tp_setattro(OBJECT(type), name, value) < 0) return -1; #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(type); #endif return 0; } static PyObject * inheritedAttribute(PyTypeObject *self, PyObject *name) { int i; PyObject *d, *cls; for (i = 1; i < PyTuple_GET_SIZE(self->tp_mro); i++) { cls = PyTuple_GET_ITEM(self->tp_mro, i); if (PyType_Check(cls)) d = ((PyTypeObject *)cls)->tp_dict; else if (PyClass_Check(cls)) d = ((PyClassObject *)cls)->cl_dict; else /* Unrecognized thing, punt */ d = NULL; if ((d == NULL) || (PyDict_GetItem(d, name) == NULL)) continue; return PyObject_GetAttr(cls, name); } PyErr_SetObject(PyExc_AttributeError, name); return NULL; } static PyObject * __basicnew__(PyObject *self) { return PyObject_CallMethodObjArgs(self, str__new__, self, NULL); } static int append_new(PyObject *result, PyObject *v) { int contains; if (v == OBJECT(&BaseType) || v == OBJECT(&PyBaseObject_Type)) return 0; /* Don't add these until end */ contains = PySequence_Contains(result, v); if (contains != 0) return contains; return PyList_Append(result, v); } static int copy_mro(PyObject *mro, PyObject *result) { PyObject *base; int i, l; l = PyTuple_Size(mro); if (l < 0) return -1; for (i=0; i < l; i++) { base = PyTuple_GET_ITEM(mro, i); if (append_new(result, base) < 0) return -1; } return 0; } static int copy_classic(PyObject *base, PyObject *result) { PyObject *bases, *basebase; int i, l, err=-1; if (append_new(result, base) < 0) return -1; bases = PyObject_GetAttr(base, str__bases__); if (bases == NULL) return -1; l = PyTuple_Size(bases); if (l < 0) goto end; for (i=0; i < l; i++) { basebase = PyTuple_GET_ITEM(bases, i); if (copy_classic(basebase, result) < 0) goto end; } err = 0; end: Py_DECREF(bases); return err; } static PyObject * mro(PyTypeObject *self) { /* Compute an MRO for a class */ PyObject *result, *base, *basemro, *mro=NULL; int i, l, err; result = PyList_New(0); if (result == NULL) return NULL; if (PyList_Append(result, OBJECT(self)) < 0) goto end; l = PyTuple_Size(self->tp_bases); if (l < 0) goto end; for (i=0; i < l; i++) { base = PyTuple_GET_ITEM(self->tp_bases, i); if (base == NULL) continue; basemro = PyObject_GetAttr(base, str__mro__); if (basemro != NULL) { /* Type */ err = copy_mro(basemro, result); Py_DECREF(basemro); if (err < 0) goto end; } else { PyErr_Clear(); if (copy_classic(base, result) < 0) goto end; } } if (self != &BaseType && PyList_Append(result, OBJECT(&BaseType)) < 0) goto end; if (PyList_Append(result, OBJECT(&PyBaseObject_Type)) < 0) goto end; l = PyList_GET_SIZE(result); mro = PyTuple_New(l); if (mro == NULL) goto end; for (i=0; i < l; i++) { Py_INCREF(PyList_GET_ITEM(result, i)); PyTuple_SET_ITEM(mro, i, PyList_GET_ITEM(result, i)); } end: Py_DECREF(result); return mro; } static struct PyMethodDef EC_methods[] = { {"__basicnew__", (PyCFunction)__basicnew__, METH_NOARGS, "Create a new empty object"}, {"inheritedAttribute", (PyCFunction)inheritedAttribute, METH_O, "Look up an inherited attribute"}, {"mro", (PyCFunction)mro, METH_NOARGS, "Compute an mro using the 'encalsulated base' scheme"}, {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static PyTypeObject ExtensionClassType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "ExtensionClass", /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ (destructor)0, /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, /* tp_compare */ (cmpfunc)0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ (hashfunc)0, /* tp_call */ (ternaryfunc)0, /* tp_str */ (reprfunc)0, /* tp_getattro */ (getattrofunc)0, /* tp_setattro */ (setattrofunc)EC_setattro, /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif , /* tp_doc */ "Meta-class for extension classes", /* tp_traverse */ (traverseproc)0, /* tp_clear */ (inquiry)0, /* tp_richcompare */ (richcmpfunc)0, /* tp_weaklistoffset */ (long)0, /* tp_iter */ (getiterfunc)0, /* tp_iternext */ (iternextfunc)0, /* tp_methods */ EC_methods, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* internal use */ /* tp_descr_get */ (descrgetfunc)0, /* tp_descr_set */ (descrsetfunc)0, /* tp_dictoffset */ 0, /* tp_init */ (initproc)EC_init, /* tp_alloc */ (allocfunc)0, /* tp_new */ (newfunc)EC_new, /* tp_free */ 0, /* Low-level free-mem routine */ /* tp_is_gc */ (inquiry)0, /* For PyObject_IS_GC */ }; static PyObject * debug(PyObject *self, PyObject *o) { Py_INCREF(Py_None); return Py_None; } static PyObject * pmc_init_of(PyObject *self, PyObject *args) { PyObject *o; if (! PyArg_ParseTuple(args, "O!", (PyObject *)&ExtensionClassType, &o)) return NULL; if (EC_init_of((PyTypeObject *)o) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } /* List of methods defined in the module */ static struct PyMethodDef ec_methods[] = { {"debug", (PyCFunction)debug, METH_O, ""}, {"pmc_init_of", (PyCFunction)pmc_init_of, METH_VARARGS, "Initialize __get__ for classes that define __of__"}, {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static PyObject * EC_findiattrs_(PyObject *self, char *cname) { PyObject *name, *r; name = PyString_FromString(cname); if (name == NULL) return NULL; r = ECBaseType->tp_getattro(self, name); Py_DECREF(name); return r; } static PyObject * ec_new_for_custom_dealloc(PyTypeObject *type, PyObject *args, PyObject *kw) { /* This is for EC's that have deallocs. For these, we need to incref the type when we create an instance, because the deallocs will decref the type. */ PyObject *r; r = PyType_GenericNew(type, args, kw); if (r) { Py_INCREF(type); } return r; } static int ec_init(PyObject *self, PyObject *args, PyObject *kw) { PyObject *r, *__init__; __init__ = PyObject_GetAttr(self, str__init__); if (__init__ == NULL) return -1; r = PyObject_Call(__init__, args, kw); Py_DECREF(__init__); if (r == NULL) return -1; Py_DECREF(r); return 0; } static int PyExtensionClass_Export_(PyObject *dict, char *name, PyTypeObject *typ) { long ecflags = 0; PyMethodDef *pure_methods = NULL, *mdef = NULL; PyObject *m; if (typ->tp_flags == 0) { /* Old-style EC */ if (typ->tp_traverse) { /* ExtensionClasses stick there methods in the tp_traverse slot */ mdef = (PyMethodDef *)typ->tp_traverse; if (typ->tp_basicsize <= sizeof(_emptyobject)) /* Pure mixin. We want rebindable methods */ pure_methods = mdef; else typ->tp_methods = mdef; typ->tp_traverse = NULL; /* Look for __init__ method */ for (; mdef->ml_name; mdef++) { if (strcmp(mdef->ml_name, "__init__") == 0) { /* we have an old-style __init__, install a special slot */ typ->tp_init = ec_init; break; } } } if (typ->tp_clear) { /* ExtensionClasses stick there flags in the tp_clear slot */ ecflags = (long)(typ->tp_clear); /* Some old-style flags were set */ if ((ecflags & EXTENSIONCLASS_BINDABLE_FLAG) && typ->tp_descr_get == NULL) /* We have __of__-style binding */ typ->tp_descr_get = of_get; } typ->tp_clear = NULL; typ->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; if (typ->tp_dealloc != NULL) typ->tp_new = ec_new_for_custom_dealloc; } typ->ob_type = ECExtensionClassType; if (ecflags & EXTENSIONCLASS_NOINSTDICT_FLAG) typ->tp_base = &NoInstanceDictionaryBaseType; else typ->tp_base = &BaseType; if (typ->tp_new == NULL) typ->tp_new = PyType_GenericNew; if (PyType_Ready(typ) < 0) return -1; if (pure_methods) { /* We had pure methods. We want to be able to rebind these, so we'll make them ordinary method wrappers around method descrs */ for (; pure_methods->ml_name; pure_methods++) { m = PyDescr_NewMethod(ECBaseType, pure_methods); if (! m) return -1; m = PyMethod_New((PyObject *)m, NULL, (PyObject *)ECBaseType); if (! m) return -1; if (PyDict_SetItemString(typ->tp_dict, pure_methods->ml_name, m) < 0) return -1; } #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(typ); #endif } else if (mdef && mdef->ml_name) { /* Blast, we have to stick __init__ in the dict ourselves because PyType_Ready probably stuck a wrapper for ec_init in instead. */ m = PyDescr_NewMethod(typ, mdef); if (! m) return -1; if (PyDict_SetItemString(typ->tp_dict, mdef->ml_name, m) < 0) return -1; #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(typ); #endif } if (PyMapping_SetItemString(dict, name, (PyObject*)typ) < 0) return -1; return 0; } PyObject * PyECMethod_New_(PyObject *callable, PyObject *inst) { if (! PyExtensionInstance_Check(inst)) { PyErr_SetString(PyExc_TypeError, "Can't bind non-ExtensionClass instance."); return NULL; } if (PyMethod_Check(callable)) { if (callable->ob_refcnt == 1) { Py_XDECREF(((PyMethodObject*)callable)->im_self); Py_INCREF(inst); ((PyMethodObject*)callable)->im_self = inst; Py_INCREF(callable); return callable; } else return callable->ob_type->tp_descr_get( callable, inst, ((PyMethodObject*)callable)->im_class); } else return PyMethod_New(callable, inst, (PyObject*)(ECBaseType)); } static struct ExtensionClassCAPIstruct TrueExtensionClassCAPI = { EC_findiattrs_, PyExtensionClass_Export_, PyECMethod_New_, &BaseType, &ExtensionClassType, }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC init_ExtensionClass(void) { PyObject *m, *s; if (pickle_setup() < 0) return; #define DEFINE_STRING(S) \ if(! (str ## S = PyString_FromString(# S))) return DEFINE_STRING(__of__); DEFINE_STRING(__get__); DEFINE_STRING(__class_init__); DEFINE_STRING(__init__); DEFINE_STRING(__bases__); DEFINE_STRING(__mro__); DEFINE_STRING(__new__); #undef DEFINE_STRING PyExtensionClassCAPI = &TrueExtensionClassCAPI; ExtensionClassType.ob_type = &PyType_Type; ExtensionClassType.tp_base = &PyType_Type; ExtensionClassType.tp_traverse = PyType_Type.tp_traverse; ExtensionClassType.tp_clear = PyType_Type.tp_clear; /* Initialize types: */ if (PyType_Ready(&ExtensionClassType) < 0) return; BaseType.ob_type = &ExtensionClassType; BaseType.tp_base = &PyBaseObject_Type; BaseType.tp_new = PyType_GenericNew; if (PyType_Ready(&BaseType) < 0) return; NoInstanceDictionaryBaseType.ob_type = &ExtensionClassType; NoInstanceDictionaryBaseType.tp_base = &BaseType; NoInstanceDictionaryBaseType.tp_new = PyType_GenericNew; if (PyType_Ready(&NoInstanceDictionaryBaseType) < 0) return; /* Create the module and add the functions */ m = Py_InitModule3("_ExtensionClass", ec_methods, _extensionclass_module_documentation); if (m == NULL) return; s = PyCObject_FromVoidPtr(PyExtensionClassCAPI, NULL); if (PyModule_AddObject(m, "CAPI2", s) < 0) return; /* Add types: */ if (PyModule_AddObject(m, "ExtensionClass", (PyObject *)&ExtensionClassType) < 0) return; if (PyModule_AddObject(m, "Base", (PyObject *)&BaseType) < 0) return; if (PyModule_AddObject(m, "NoInstanceDictionaryBase", (PyObject *)&NoInstanceDictionaryBaseType) < 0) return; } zope2.13-2.13.21/source/Missing/include/ExtensionClass/__init__.py0000644000175000017500000000523712214017442023552 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ExtensionClass Extension Class exists to support types derived from the old ExtensionType meta-class that preceeded Python 2.2 and new-style classes. As a meta-class, ExtensionClass provides the following features: - Support for a class initialiser: >>> from ExtensionClass import ExtensionClass, Base >>> class C(Base): ... def __class_init__(self): ... print 'class init called' ... print self.__name__ ... def bar(self): ... return 'bar called' class init called C >>> c = C() >>> int(c.__class__ is C) 1 >>> int(c.__class__ is type(c)) 1 - Making sure that every instance of the meta-class has Base as a base class: >>> class X: ... __metaclass__ = ExtensionClass >>> Base in X.__mro__ 1 - Provide an inheritedAttribute method for looking up attributes in base classes: >>> class C2(C): ... def bar(*a): ... return C2.inheritedAttribute('bar')(*a), 42 class init called C2 >>> o = C2() >>> o.bar() ('bar called', 42) This is for compatability with old code. New code should use super instead. The base class, Base, exists mainly to support the __of__ protocol. The __of__ protocol is similar to __get__ except that __of__ is called when an implementor is retrieved from an instance as well as from a class: >>> class O(Base): ... def __of__(*a): ... return a >>> o1 = O() >>> o2 = O() >>> C.o1 = o1 >>> c.o2 = o2 >>> c.o1 == (o1, c) 1 >>> C.o1 == o1 1 >>> int(c.o2 == (o2, c)) 1 We accomplish this by making a class that implements __of__ a descriptor and treating all descriptor ExtensionClasses this way. That is, if an extension class is a descriptor, it's __get__ method will be called even when it is retrieved from an instance. >>> class O(Base): ... def __get__(*a): ... return a ... >>> o1 = O() >>> o2 = O() >>> C.o1 = o1 >>> c.o2 = o2 >>> int(c.o1 == (o1, c, type(c))) 1 >>> int(C.o1 == (o1, None, type(c))) 1 >>> int(c.o2 == (o2, c, type(c))) 1 $Id: __init__.py 40218 2005-11-18 14:39:19Z andreasjung $ """ from _ExtensionClass import * zope2.13-2.13.21/source/Missing/include/ExtensionClass/tests.py0000644000175000017500000004744512214017442023164 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ $Id: tests.py 109298 2010-02-22 17:30:41Z hannosch $ """ from ExtensionClass import * import pickle def print_dict(d): d = d.items() d.sort() print '{%s}' % (', '.join( [('%r: %r' % (k, v)) for (k, v) in d] )) def test_mixing(): """Test working with a classic class >>> class Classic: ... def x(self): ... return 42 >>> class O(Base): ... def __of__(*a): ... return a >>> class O2(Classic, O): ... def __of__(*a): ... return (O2.inheritedAttribute('__of__')(*a), ... O2.inheritedAttribute('x')(a[0])) >>> class C(Base): ... def __class_init__(self): ... print 'class init called' ... print self.__name__ ... def bar(self): ... return 'bar called' class init called C >>> c = C() >>> o2 = O2() >>> c.o2 = o2 >>> int(c.o2 == ((o2, c), 42)) 1 Test working with a new style >>> class Modern(object): ... def x(self): ... return 42 >>> class O2(Modern, O): ... def __of__(*a): ... return (O2.inheritedAttribute('__of__')(*a), ... O2.inheritedAttribute('x')(a[0])) >>> o2 = O2() >>> c.o2 = o2 >>> int(c.o2 == ((o2, c), 42)) 1 """ def test_class_creation_under_stress(): """ >>> for i in range(100): ... class B(Base): ... print i, ... if i and i%20 == 0: ... print 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 >>> import gc >>> x = gc.collect() """ def old_test_add(): """test_add.py from old EC >>> class foo(Base): ... def __add__(self,other): print 'add called' >>> foo()+foo() add called """ def proper_error_on_deleattr(): """ Florent Guillaume wrote: ... Excellent. Will it also fix this particularity of ExtensionClass: >>> class A(Base): ... def foo(self): ... self.gee ... def bar(self): ... del self.gee >>> a=A() >>> a.foo() Traceback (most recent call last): ... AttributeError: gee >>> a.bar() Traceback (most recent call last): ... AttributeError: 'A' object has no attribute 'gee' I.e., the fact that KeyError is raised whereas a normal class would raise AttributeError. """ def test_NoInstanceDictionaryBase(): """ >>> class B(NoInstanceDictionaryBase): pass ... >>> B().__dict__ Traceback (most recent call last): ... AttributeError: This object has no __dict__ >>> class B(NoInstanceDictionaryBase): ... __slots__ = ('a', 'b') ... >>> class BB(B): pass ... >>> b = BB() >>> b.__dict__ Traceback (most recent call last): ... AttributeError: This object has no __dict__ >>> b.a = 1 >>> b.b = 2 >>> b.a 1 >>> b.b 2 """ def test__basicnew__(): """ >>> x = Simple.__basicnew__() >>> x.__dict__ {} """ def cmpattrs(self, other, *attrs): for attr in attrs: if attr[:3] in ('_v_', '_p_'): continue c = cmp(getattr(self, attr, None), getattr(other, attr, None)) if c: return c return 0 class Simple(Base): def __init__(self, name, **kw): self.__name__ = name self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', *(self.__dict__.keys())) def test_basic_pickling(): """ >>> x = Simple('x', aaa=1, bbb='foo') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> print_dict(x.__getstate__()) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> f, (c,), state = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Simple' >>> print_dict(state) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.__setstate__({'z': 1}) >>> x.__dict__ {'z': 1} """ class Custom(Simple): def __new__(cls, x, y): r = Base.__new__(cls) r.x, r.y = x, y return r def __init__(self, x, y): self.a = 42 def __getnewargs__(self): return self.x, self.y def __getstate__(self): return self.a def __setstate__(self, a): self.a = a def test_pickling_w_overrides(): """ >>> x = Custom('x', 'y') >>> x.a = 99 >>> (f, (c, ax, ay), a) = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Custom' >>> ax, ay, a ('x', 'y', 99) >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class Slotted(Base): __slots__ = 's1', 's2', '_p_splat', '_v_eek' def __init__(self, s1, s2): self.s1, self.s2 = s1, s2 self._v_eek = 1 self._p_splat = 2 class SubSlotted(Slotted): __slots__ = 's3', 's4' def __init__(self, s1, s2, s3): Slotted.__init__(self, s1, s2) self.s3 = s3 def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4') def test_pickling_w_slots_only(): """ >>> x = SubSlotted('x', 'y', 'z') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class SubSubSlotted(SubSlotted): def __init__(self, s1, s2, s3, **kw): SubSlotted.__init__(self, s1, s2, s3) self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4', *(self.__dict__.keys())) def test_pickling_w_slots(): """ >>> x = SubSubSlotted('x', 'y', 'z', aaa=1, bbb='foo') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ def test_pickling_w_slots_w_empty_dict(): """ >>> x = SubSubSlotted('x', 'y', 'z') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ def test_setattr_on_extension_type(): """ >>> for name in 'x', '_x', 'x_', '__x_y__', '___x__', '__x___', '_x_': ... setattr(Base, name, 1) ... print getattr(Base, name) ... delattr(Base, name) ... print getattr(Base, name, 0) 1 0 1 0 1 0 1 0 1 0 1 0 1 0 >>> Base.__foo__ = 1 Traceback (most recent call last): ... TypeError: can't set attributes of built-in/extension type """ \ """'ExtensionClass.Base' if the attribute name begins """ \ """and ends with __ and contains only 4 _ characters >>> Base.__foo__ Traceback (most recent call last): ... AttributeError: type object 'ExtensionClass.Base' """ \ """has no attribute '__foo__' >>> del Base.__foo__ Traceback (most recent call last): ... TypeError: can't set attributes of built-in/extension type """ \ """'ExtensionClass.Base' if the attribute name begins """ \ """and ends with __ and contains only 4 _ characters """ def test_mro(): """ExtensionClass method-resolution order The EC MRO is chosen to maximize backward compatibility and provide a model that is easy to reason about. The basic idea is: I'll call this the "encapsulated base" scheme. Consider: >>> class X(Base): ... pass >>> class Y(Base): ... pass >>> class Z(Base): ... pass >>> class C(X, Y, Z): ... def foo(self): ... return 42 When we look up an attribute, we do the following: - Look in C's dictionary first. - Look up the attribute in X. We don't care how we get the attribute from X. If X is a new-style-class, we use the new algorithm. If X is a classic class, we use left-to-right depth-first. If X is an nsEC, use the "encapsulated base" algorithm. If we don't find the attribute in X, look in Y and then in Z, using the same approach. This algorithm will produce backward compatible results, providing the equivalent of left-to-right depth-first for nsECs and classic classes. We'll actually do something less abstract. We'll use a simple algorthm to merge the __mro__ of the base classes, computing an __mro__ for classic classes using the left-to-right depth-first algorithm. We'll basically lay the mros end-to-end left-to-right and remove repeats, keeping the first occurence of each class. >>> [c.__name__ for c in C.__mro__] ['C', 'X', 'Y', 'Z', 'Base', 'object'] For backward-compatability's sake, we actually depart from the above description a bit. We always put Base and object last in the mro, as shown in the example above. The primary reason for this is that object provides a do-nothing __init__ method. It is common practice to mix a C-implemented base class that implements a few methods with a Python class that implements those methods and others. The idea is that the C implementation overrides selected methods in C, so the C subclass is listed first. Unfortunately, because all extension classes are required to subclass Base, and thus, object, the C subclass brings along the __init__ object from objects, which would hide any __init__ method provided by the Python mix-in. Base and object are special in that they are implied by their meta classes. For example, a new-style class always has object as an ancestor, even if it isn't listed as a base: >>> class O: ... __metaclass__ = type >>> [c.__name__ for c in O.__bases__] ['object'] >>> [c.__name__ for c in O.__mro__] ['O', 'object'] Similarly, Base is always an ancestor of an extension class: >>> class E: ... __metaclass__ = ExtensionClass >>> [c.__name__ for c in E.__bases__] ['Base'] >>> [c.__name__ for c in E.__mro__] ['E', 'Base', 'object'] Base and object are generally added soley to get a particular meta class. They aren't used to provide application functionality and really shouldn't be considered when reasoning about where attributes come from. They do provide some useful default functionality and should be included at the end of the mro. Here are more examples: >>> from ExtensionClass import Base >>> class NA(object): ... pass >>> class NB(NA): ... pass >>> class NC(NA): ... pass >>> class ND(NB, NC): ... pass >>> [c.__name__ for c in ND.__mro__] ['ND', 'NB', 'NC', 'NA', 'object'] >>> class EA(Base): ... pass >>> class EB(EA): ... pass >>> class EC(EA): ... pass >>> class ED(EB, EC): ... pass >>> [c.__name__ for c in ED.__mro__] ['ED', 'EB', 'EA', 'EC', 'Base', 'object'] >>> class EE(ED, ND): ... pass >>> [c.__name__ for c in EE.__mro__] ['EE', 'ED', 'EB', 'EA', 'EC', 'ND', 'NB', 'NC', 'NA', 'Base', 'object'] >>> class EF(ND, ED): ... pass >>> [c.__name__ for c in EF.__mro__] ['EF', 'ND', 'NB', 'NC', 'NA', 'ED', 'EB', 'EA', 'EC', 'Base', 'object'] >>> class CA: ... pass >>> class CB(CA): ... pass >>> class CC(CA): ... pass >>> class CD(CB, CC): ... pass >>> class ECD(Base, CD): ... pass >>> [c.__name__ for c in ECD.__mro__] ['ECD', 'CD', 'CB', 'CA', 'CC', 'Base', 'object'] >>> class CDE(CD, Base): ... pass >>> [c.__name__ for c in CDE.__mro__] ['CDE', 'CD', 'CB', 'CA', 'CC', 'Base', 'object'] >>> class CEND(CD, ED, ND): ... pass >>> [c.__name__ for c in CEND.__mro__] ['CEND', 'CD', 'CB', 'CA', 'CC', """ \ """'ED', 'EB', 'EA', 'EC', 'ND', 'NB', 'NC', 'NA', 'Base', 'object'] """ def test_avoiding___init__decoy_w_inheritedAttribute(): """ >>> class Decoy(Base): ... pass >>> class B(Base): ... def __init__(self, a, b): ... print '__init__', a, b >>> class C(Decoy, B): ... def __init__(self): ... print 'C init' ... C.inheritedAttribute('__init__')(self, 1, 2) >>> x = C() C init __init__ 1 2 """ def test_of_not_called_when_not_accessed_through_EC_instance(): """ >>> class Eek(Base): ... def __of__(self, parent): ... return self, parent If I define an EC instance as an attr of an ordinary class: >>> class O(object): ... eek = Eek() >>> class C: ... eek = Eek() I get the instance, without calling __of__, when I get it from either tha class: >>> O.eek is O.__dict__['eek'] True >>> C.eek is C.__dict__['eek'] True or an instance of the class: >>> O().eek is O.__dict__['eek'] True >>> C().eek is C.__dict__['eek'] True If I define an EC instance as an attr of an extension class: >>> class E(Base): ... eek = Eek() I get the instance, without calling __of__, when I get it from tha class: >>> E.eek is E.__dict__['eek'] True But __of__ is called if I go through the instance: >>> e = E() >>> e.eek == (E.__dict__['eek'], e) True """ def test_inheriting___doc__(): """Old-style ExtensionClass inherited __doc__ from base classes. >>> class E(Base): ... "eek" >>> class EE(E): ... pass >>> EE.__doc__ 'eek' >>> EE().__doc__ 'eek' """ def test___of___w_metaclass_instance(): """When looking for extension class instances, need to handle meta classes >>> class C(Base): ... pass >>> class O(Base): ... def __of__(self, parent): ... print '__of__ called on an O' >>> class M(ExtensionClass): ... pass >>> class X: ... __metaclass__ = M ... >>> class S(X, O): ... pass >>> c = C() >>> c.s = S() >>> c.s __of__ called on an O """ def test___of__set_after_creation(): """We may need to set __of__ after a class is created. Normally, in a class's __init__, the initialization code checks for an __of__ method and, if it isn't already set, sets __get__. If a class is persistent and loaded from the database, we want this to happen in __setstate__. The pmc_init_of function allws us to do that. We'll create an extension class without a __of__. We'll also give it a special meta class, just to make sure that this works with funny metaclasses too: >>> import ExtensionClass >>> class M(ExtensionClass.ExtensionClass): ... "A meta class" >>> class B(ExtensionClass.Base): ... __metaclass__ = M ... def __init__(self, name): ... self.name = name ... def __repr__(self): ... return self.name >>> B.__class__ is M True >>> x = B('x') >>> x.y = B('y') >>> x.y y We define a __of__ method for B after the fact: >>> def __of__(self, other): ... print '__of__(%r, %r)' % (self, other) ... return self >>> B.__of__ = __of__ We see that this has no effect: >>> x.y y Until we use pmc_init_of: >>> ExtensionClass.pmc_init_of(B) >>> x.y __of__(y, x) y Note that there is no harm in calling pmc_init_of multiple times: >>> ExtensionClass.pmc_init_of(B) >>> ExtensionClass.pmc_init_of(B) >>> ExtensionClass.pmc_init_of(B) >>> x.y __of__(y, x) y If we remove __of__, we'll go back to the behavior we had before: >>> del B.__of__ >>> ExtensionClass.pmc_init_of(B) >>> x.y y """ def test_Basic_gc(): """Test to make sure that EC instances participate in GC >>> from ExtensionClass import Base >>> import gc >>> class C1(Base): ... pass ... >>> class C2(Base): ... def __del__(self): ... print 'removed' ... >>> a=C1() >>> a.b = C1() >>> a.b.a = a >>> a.b.c = C2() >>> thresholds = gc.get_threshold() >>> gc.set_threshold(0) >>> ignore = gc.collect() >>> del a >>> ignored = gc.collect() removed >>> ignored > 0 True >>> gc.set_threshold(*thresholds) """ from zope.testing.doctest import DocTestSuite import unittest def test_suite(): return unittest.TestSuite(( DocTestSuite('ExtensionClass'), DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/Missing/include/ExtensionClass/ExtensionClass.h0000644000175000017500000002370012214017442024547 0ustar arnauarnau/***************************************************************************** Copyright (c) 1996-2002 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /* $Id: ExtensionClass.h 40218 2005-11-18 14:39:19Z andreasjung $ Extension Class Definitions Implementing base extension classes A base extension class is implemented in much the same way that an extension type is implemented, except: - The include file, 'ExtensionClass.h', must be included. - The type structure is declared to be of type 'PyExtensionClass', rather than of type 'PyTypeObject'. - The type structure has an additional member that must be defined after the documentation string. This extra member is a method chain ('PyMethodChain') containing a linked list of method definition ('PyMethodDef') lists. Method chains can be used to implement method inheritance in C. Most extensions don't use method chains, but simply define method lists, which are null-terminated arrays of method definitions. A macro, 'METHOD_CHAIN' is defined in 'ExtensionClass.h' that converts a method list to a method chain. (See the example below.) - Module functions that create new instances must be replaced by an '__init__' method that initializes, but does not create storage for instances. - The extension class must be initialized and exported to the module with:: PyExtensionClass_Export(d,"name",type); where 'name' is the module name and 'type' is the extension class type object. Attribute lookup Attribute lookup is performed by calling the base extension class 'getattr' operation for the base extension class that includes C data, or for the first base extension class, if none of the base extension classes include C data. 'ExtensionClass.h' defines a macro 'Py_FindAttrString' that can be used to find an object's attributes that are stored in the object's instance dictionary or in the object's class or base classes:: v = Py_FindAttrString(self,name); In addition, a macro is provided that replaces 'Py_FindMethod' calls with logic to perform the same sort of lookup that is provided by 'Py_FindAttrString'. Linking The extension class mechanism was designed to be useful with dynamically linked extension modules. Modules that implement extension classes do not have to be linked against an extension class library. The macro 'PyExtensionClass_Export' imports the 'ExtensionClass' module and uses objects imported from this module to initialize an extension class with necessary behavior. */ #ifndef EXTENSIONCLASS_H #define EXTENSIONCLASS_H #include "Python.h" #include "import.h" /* Declarations for objects of type ExtensionClass */ #define EC PyTypeObject #define PyExtensionClass PyTypeObject #define EXTENSIONCLASS_BINDABLE_FLAG 1 << 2 #define EXTENSIONCLASS_NOINSTDICT_FLAG 1 << 5 typedef struct { PyObject_HEAD } _emptyobject; static struct ExtensionClassCAPIstruct { /***************************************************************************** WARNING: THIS STRUCT IS PRIVATE TO THE EXTENSION CLASS INTERFACE IMPLEMENTATION AND IS SUBJECT TO CHANGE !!! *****************************************************************************/ PyObject *(*EC_findiattrs_)(PyObject *self, char *cname); int (*PyExtensionClass_Export_)(PyObject *dict, char *name, PyTypeObject *typ); PyObject *(*PyECMethod_New_)(PyObject *callable, PyObject *inst); PyExtensionClass *ECBaseType_; PyExtensionClass *ECExtensionClassType_; } *PyExtensionClassCAPI = NULL; #define ECBaseType (PyExtensionClassCAPI->ECBaseType_) #define ECExtensionClassType (PyExtensionClassCAPI->ECExtensionClassType_) /* Following are macros that are needed or useful for defining extension classes: */ /* This macro redefines Py_FindMethod to do attribute for an attribute name given by a C string lookup using extension class meta-data. This is used by older getattr implementations. This macro is used in base class implementations of tp_getattr to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. Note that in Python 1.4, a getattr operation may be provided that uses an object argument. Classes that support this new operation should use Py_FindAttr. */ #define EC_findiattrs (PyExtensionClassCAPI->EC_findiattrs_) #define Py_FindMethod(M,SELF,NAME) (EC_findiattrs((SELF),(NAME))) /* Do method or attribute lookup for an attribute name given by a C string using extension class meta-data. This macro is used in base class implementations of tp_getattro to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. Note that in Python 1.4, a getattr operation may be provided that uses an object argument. Classes that support this new operation should use Py_FindAttr. */ #define Py_FindAttrString(SELF,NAME) (EC_findiattrs((SELF),(NAME))) /* Do method or attribute lookup using extension class meta-data. This macro is used in base class implementations of tp_getattr to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. */ #define Py_FindAttr (ECBaseType->tp_getattro) /* Do attribute assignment for an attribute. This macro is used in base class implementations of tp_setattro to set attributes that are not managed by the base type directly. The macro is generally used to assign attributes after other attribute attempts to assign attributes have failed. */ #define PyEC_SetAttr(SELF,NAME,V) (ECBaseType->tp_setattro(SELF, NAME, V)) /* Convert a method list to a method chain. */ #define METHOD_CHAIN(DEF) (traverseproc)(DEF) /* The following macro checks whether a type is an extension class: */ #define PyExtensionClass_Check(TYPE) \ PyObject_TypeCheck((PyObject*)(TYPE), ECExtensionClassType) /* The following macro checks whether an instance is an extension instance: */ #define PyExtensionInstance_Check(INST) \ PyObject_TypeCheck(((PyObject*)(INST))->ob_type, ECExtensionClassType) #define CHECK_FOR_ERRORS(MESS) /* The following macro can be used to define an extension base class that only provides method and that is used as a pure mix-in class. */ #define PURE_MIXIN_CLASS(NAME,DOC,METHODS) \ static PyExtensionClass NAME ## Type = { PyObject_HEAD_INIT(NULL) 0, # NAME, \ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \ 0 , DOC, (traverseproc)METHODS, } /* The following macros provide limited access to extension-class method facilities. */ /* Test for an ExtensionClass method: */ #define PyECMethod_Check(O) PyMethod_Check((O)) /* Create a method object that wraps a callable object and an instance. Note that if the callable object is an extension class method, then the new method will wrap the callable object that is wrapped by the extension class method. Also note that if the callable object is an extension class method with a reference count of 1, then the callable object will be rebound to the instance and returned with an incremented reference count. */ #define PyECMethod_New(CALLABLE, INST) \ PyExtensionClassCAPI->PyECMethod_New_((CALLABLE),(INST)) /* Return the instance that is bound by an extension class method. */ #define PyECMethod_Self(M) \ (PyMethod_Check((M)) ? ((PyMethodObject*)(M))->im_self : NULL) /* Check whether an object has an __of__ method for returning itself in the context of it's container. */ #define has__of__(O) (PyObject_TypeCheck((O)->ob_type, ECExtensionClassType) \ && (O)->ob_type->tp_descr_get != NULL) /* The following macros are used to check whether an instance or a class' instanses have instance dictionaries: */ #define HasInstDict(O) (_PyObject_GetDictPtr(O) != NULL) #define ClassHasInstDict(C) ((C)->tp_dictoffset > 0)) /* Get an object's instance dictionary. Use with caution */ #define INSTANCE_DICT(inst) (_PyObject_GetDictPtr(O)) /* Test whether an ExtensionClass, S, is a subclass of ExtensionClass C. */ #define ExtensionClassSubclass_Check(S,C) PyType_IsSubtype((S), (C)) /* Test whether an ExtensionClass instance , I, is a subclass of ExtensionClass C. */ #define ExtensionClassSubclassInstance_Check(I,C) PyObject_TypeCheck((I), (C)) /* Export an Extension Base class in a given module dictionary with a given name and ExtensionClass structure. */ #define PyExtensionClass_Export(D,N,T) \ if (! ExtensionClassImported || \ PyExtensionClassCAPI->PyExtensionClass_Export_((D),(N),&(T)) < 0) return; #define ExtensionClassImported \ ((PyExtensionClassCAPI != NULL) || \ (PyExtensionClassCAPI = PyCObject_Import("ExtensionClass","CAPI2"))) /* These are being overridded to use tp_free when used with new-style classes. This is to allow old extention-class code to work. */ #undef PyMem_DEL #undef PyObject_DEL #define PyMem_DEL(O) \ if (((O)->ob_type->tp_flags & Py_TPFLAGS_HAVE_CLASS) \ && ((O)->ob_type->tp_free != NULL)) \ (O)->ob_type->tp_free((PyObject*)(O)); \ else \ PyObject_FREE((O)); #define PyObject_DEL(O) PyMem_DEL(O) #endif /* EXTENSIONCLASS_H */ zope2.13-2.13.21/source/Missing/buildout.cfg0000644000175000017500000000025712214017442017361 0ustar arnauarnau[buildout] develop = . parts = interpreter test [interpreter] recipe = zc.recipe.egg interpreter = python eggs = Missing [test] recipe = zc.recipe.testrunner eggs = Missing zope2.13-2.13.21/source/Missing/bootstrap.py0000644000175000017500000000742012214017442017437 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 108946 2010-02-12 02:40:18Z yusei $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/Missing/CHANGES.txt0000644000175000017500000000027212214017442016657 0ustar arnauarnauChangelog ========= 2.13.1 (2010-06-16) ------------------- - Added a ``__class__`` to Missing.Value objects. 2.13.0 (2010-03-30) ------------------- - Released as separate package. zope2.13-2.13.21/source/Missing/src/0000755000175000017500000000000012214017442015634 5ustar arnauarnauzope2.13-2.13.21/source/Missing/src/Missing.egg-info/0000755000175000017500000000000012214017442020737 5ustar arnauarnauzope2.13-2.13.21/source/Missing/src/Missing.egg-info/PKG-INFO0000644000175000017500000000126412214017442022037 0ustar arnauarnauMetadata-Version: 1.0 Name: Missing Version: 2.13.1 Summary: Special Missing objects used in Zope2. Home-page: http://pypi.python.org/pypi/Missing Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== Missing provides special objects used in some Zope2 internals like the ZCatalog. Changelog ========= 2.13.1 (2010-06-16) ------------------- - Added a ``__class__`` to Missing.Value objects. 2.13.0 (2010-03-30) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Missing/src/Missing.egg-info/dependency_links.txt0000644000175000017500000000000112214017442025005 0ustar arnauarnau zope2.13-2.13.21/source/Missing/src/Missing.egg-info/requires.txt0000644000175000017500000000001612214017442023334 0ustar arnauarnauExtensionClasszope2.13-2.13.21/source/Missing/src/Missing.egg-info/top_level.txt0000644000175000017500000000001012214017442023460 0ustar arnauarnauMissing zope2.13-2.13.21/source/Missing/src/Missing.egg-info/SOURCES.txt0000644000175000017500000000104212214017442022620 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py include/ExtensionClass/ExtensionClass.h include/ExtensionClass/_ExtensionClass.c include/ExtensionClass/__init__.py include/ExtensionClass/tests.py include/ExtensionClass/pickle/pickle.c src/Missing/_Missing.c src/Missing/__init__.py src/Missing/tests.py src/Missing.egg-info/PKG-INFO src/Missing.egg-info/SOURCES.txt src/Missing.egg-info/dependency_links.txt src/Missing.egg-info/not-zip-safe src/Missing.egg-info/requires.txt src/Missing.egg-info/top_level.txtzope2.13-2.13.21/source/Missing/src/Missing.egg-info/not-zip-safe0000644000175000017500000000000112214017442023165 0ustar arnauarnau zope2.13-2.13.21/source/Missing/src/Missing/0000755000175000017500000000000012214017442017245 5ustar arnauarnauzope2.13-2.13.21/source/Missing/src/Missing/__init__.py0000644000175000017500000000002712214017442021355 0ustar arnauarnaufrom _Missing import * zope2.13-2.13.21/source/Missing/src/Missing/tests.py0000644000175000017500000000244112214017442020762 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test for missing values. >>> from Missing import Value >>> Value != 12 1 >>> 12 != Value 1 >>> u"abc" != Value 1 >>> Value != u"abc" 1 >>> 1 + Value == Value 1 >>> Value + 1 == Value 1 >>> Value == 1 + Value 1 >>> Value == Value + 1 1 >>> Value.__class__ >>> type(Value()) >>> from Missing import MV >>> MV.__class__ >>> type(MV()) >>> from Missing import V >>> V.__class__ >>> type(V()) """ import unittest from doctest import DocTestSuite def test_suite(): return unittest.TestSuite(( DocTestSuite(), )) zope2.13-2.13.21/source/Missing/src/Missing/_Missing.c0000644000175000017500000001652012214017442021165 0ustar arnauarnau/***************************************************************************** Copyright (c) 1996-2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ static char Missing_module_documentation[] = "" "\n$Id: _Missing.c 113527 2010-06-16 12:59:48Z hannosch $" ; #include "ExtensionClass/ExtensionClass.h" /* Declarations for objects of type Missing */ typedef struct { PyObject_HEAD } Missing; static PyObject *vname=0, *Missing_dot_Value=0, *empty_string=0, *reduce=0; static PyObject *theValue, *notMissing; static void Missing_dealloc(Missing *self) { Py_DECREF(self->ob_type); PyObject_DEL(self); } static PyObject * Missing_repr(Missing *self) { Py_INCREF(Missing_dot_Value); return Missing_dot_Value; } static PyObject * Missing_str(Missing *self) { Py_INCREF(empty_string); return empty_string; } /* Code to access Missing objects as numbers. We must guarantee that notMissing is never returned to Python code, because it would violate the guarantee that all Python-accessible Missing values are equal to each other. */ static PyObject * Missing_bin(PyObject *v, PyObject *w) { if (v == notMissing) v = w; assert(v != notMissing); Py_INCREF(v); return v; } static PyObject * Missing_pow(PyObject *v, PyObject *w, PyObject *z) { if (v == notMissing) v = w; assert(v != notMissing); Py_INCREF(v); return v; } static PyObject * Missing_un(PyObject *v) { Py_INCREF(v); return v; } static int Missing_nonzero(PyObject *v) { return 0; } /* Always return the distinguished notMissing object as the result of the coercion. The notMissing object does not compare equal to other Missing objects. */ static int Missing_coerce(PyObject **pv, PyObject **pw) { Py_INCREF(*pv); Py_INCREF(notMissing); *pw = notMissing; return 0; } static PyNumberMethods Missing_as_number = { (binaryfunc)Missing_bin, /*nb_add*/ (binaryfunc)Missing_bin, /*nb_subtract*/ (binaryfunc)Missing_bin, /*nb_multiply*/ (binaryfunc)Missing_bin, /*nb_divide*/ (binaryfunc)Missing_bin, /*nb_remainder*/ (binaryfunc)Missing_bin, /*nb_divmod*/ (ternaryfunc)Missing_pow, /*nb_power*/ (unaryfunc)Missing_un, /*nb_negative*/ (unaryfunc)Missing_un, /*nb_positive*/ (unaryfunc)Missing_un, /*nb_absolute*/ (inquiry)Missing_nonzero, /*nb_nonzero*/ (unaryfunc)Missing_un, /*nb_invert*/ (binaryfunc)Missing_bin, /*nb_lshift*/ (binaryfunc)Missing_bin, /*nb_rshift*/ (binaryfunc)Missing_bin, /*nb_and*/ (binaryfunc)Missing_bin, /*nb_xor*/ (binaryfunc)Missing_bin, /*nb_or*/ (coercion)Missing_coerce, /*nb_coerce*/ 0, /*nb_int*/ 0, /*nb_long*/ 0, /*nb_float*/ 0, /*nb_oct*/ 0, /*nb_hex*/ #if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION != 1 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ 0, /* nb_inplace_multiply */ 0, /* nb_inplace_divide */ 0, /* nb_inplace_remainder */ 0, /* nb_inplace_power */ 0, /* nb_inplace_lshift */ 0, /* nb_inplace_rshift */ 0, /* nb_inplace_and */ 0, /* nb_inplace_xor */ 0, /* nb_inplace_or */ Missing_bin, /* nb_floor_divide */ Missing_bin, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ #endif }; /* ------------------------------------------------------- */ static PyObject * Missing_reduce(PyObject *self, PyObject *args, PyObject *kw) { if(self==theValue) { Py_INCREF(vname); return vname; } return Py_BuildValue("O()",self->ob_type); } static struct PyMethodDef reduce_ml[] = { {"__reduce__", (PyCFunction)Missing_reduce, 1, "Return a missing value reduced to standard python objects" } }; static PyObject * Missing_getattr(PyObject *self, PyObject *name) { char *c, *legal; if(!(c=PyString_AsString(name))) return NULL; legal=c; if (strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", *legal) != NULL) { for (legal++; *legal; legal++) if (strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_", *legal) == NULL) { legal=NULL; break; } } else legal=NULL; if (strcmp(c,"__class__")==0) { return Py_FindAttrString(self, c); } if(! legal) { if(strcmp(c,"__reduce__")==0) { if(self==theValue) { Py_INCREF(reduce); return reduce; } return PyCFunction_New(reduce_ml, self); } PyErr_SetObject(PyExc_AttributeError, name); return NULL; } Py_INCREF(self); return self; } static PyObject * Missing_call(PyObject *self, PyObject *args, PyObject *kw) { Py_INCREF(self); return self; } /* All Missing objects are equal to each other, except for the special notMissing object. It is returned by coerce to indicate that Missing is being compare to something else. */ static int Missing_cmp(PyObject *m1, PyObject *m2) { if (m1 == notMissing) return -1; return (m2 == notMissing); } static PyExtensionClass MissingType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "Missing.Value", /*tp_name*/ sizeof(Missing), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Missing_dealloc, /*tp_dealloc*/ (printfunc)0, /*tp_print*/ (getattrfunc)0, /*obsolete tp_getattr*/ (setattrfunc)0, /*obsolete tp_setattr*/ Missing_cmp, /*tp_compare*/ (reprfunc)Missing_repr, /*tp_repr*/ &Missing_as_number, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ (hashfunc)0, /*tp_hash*/ (ternaryfunc)Missing_call, /*tp_call*/ (reprfunc)Missing_str, /*tp_str*/ (getattrofunc)Missing_getattr, /*tp_getattro*/ (setattrofunc)0, /*tp_setattro*/ /* Space for future expansion */ 0L,0L, "Represent totally unknown quantities\n" "\n" "Missing values are used to represent numeric quantities that are\n" "unknown. They support all mathematical operations except\n" "conversions by returning themselves.\n", METHOD_CHAIN(NULL) }; /* End of code for Missing objects */ /* -------------------------------------------------------- */ /* List of methods defined in the module */ static struct PyMethodDef Module_Level__methods[] = { {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; void init_Missing(void) { PyObject *m, *d; if(! ((vname=PyString_FromString("V")) && (Missing_dot_Value=PyString_FromString("Missing.Value")) && (empty_string=PyString_FromString("")) )) return; /* Create the module and add the functions */ m = Py_InitModule4("_Missing", Module_Level__methods, Missing_module_documentation, (PyObject*)NULL,PYTHON_API_VERSION); /* Add some symbolic constants to the module */ d = PyModule_GetDict(m); PyExtensionClass_Export(d,"Missing",MissingType); theValue = PyObject_CallObject((PyObject*)&MissingType, NULL); notMissing = PyObject_CallObject((PyObject*)&MissingType, NULL); reduce=PyCFunction_New(reduce_ml, theValue); PyDict_SetItemString(d, "Value", theValue); PyDict_SetItemString(d, "V", theValue); PyDict_SetItemString(d, "MV", theValue); } zope2.13-2.13.21/source/MultiMapping/0000755000175000017500000000000012214017445016045 5ustar arnauarnauzope2.13-2.13.21/source/MultiMapping/setup.py0000644000175000017500000000304112214017445017555 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from os.path import join from setuptools import setup, find_packages, Extension setup(name='MultiMapping', version = '2.13.0', url='http://pypi.python.org/pypi/MultiMapping', license='ZPL 2.1', description="Special MultiMapping objects used in Zope2.", author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', long_description=open('README.txt').read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), package_dir={'': 'src'}, ext_modules=[Extension( name='MultiMapping._MultiMapping', include_dirs=['include', 'src'], sources=[join('src', 'MultiMapping', '_MultiMapping.c')], depends=[join('include', 'ExtensionClass', 'ExtensionClass.h')]), ], install_requires=['ExtensionClass'], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/MultiMapping/PKG-INFO0000644000175000017500000000107512214017445017145 0ustar arnauarnauMetadata-Version: 1.0 Name: MultiMapping Version: 2.13.0 Summary: Special MultiMapping objects used in Zope2. Home-page: http://pypi.python.org/pypi/MultiMapping Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== MultiMapping provides special objects used in some Zope2 internals like ZRDB. Changelog ========= 2.13.0 (2010-03-30) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/MultiMapping/pip-egg-info/0000755000175000017500000000000012214017445020326 5ustar arnauarnauzope2.13-2.13.21/source/MultiMapping/pip-egg-info/MultiMapping.egg-info/0000755000175000017500000000000012214017445024426 5ustar arnauarnauzope2.13-2.13.21/source/MultiMapping/pip-egg-info/MultiMapping.egg-info/PKG-INFO0000644000175000017500000000107512214017445025526 0ustar arnauarnauMetadata-Version: 1.0 Name: MultiMapping Version: 2.13.0 Summary: Special MultiMapping objects used in Zope2. Home-page: http://pypi.python.org/pypi/MultiMapping Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== MultiMapping provides special objects used in some Zope2 internals like ZRDB. Changelog ========= 2.13.0 (2010-03-30) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/MultiMapping/pip-egg-info/MultiMapping.egg-info/dependency_links.txt0000644000175000017500000000000112214017445030474 0ustar arnauarnau zope2.13-2.13.21/source/MultiMapping/pip-egg-info/MultiMapping.egg-info/requires.txt0000644000175000017500000000001612214017445027023 0ustar arnauarnauExtensionClasszope2.13-2.13.21/source/MultiMapping/pip-egg-info/MultiMapping.egg-info/top_level.txt0000644000175000017500000000001512214017445027154 0ustar arnauarnauMultiMapping zope2.13-2.13.21/source/MultiMapping/pip-egg-info/MultiMapping.egg-info/SOURCES.txt0000644000175000017500000000062012214017445026310 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/MultiMapping.egg-info/PKG-INFO pip-egg-info/MultiMapping.egg-info/SOURCES.txt pip-egg-info/MultiMapping.egg-info/dependency_links.txt pip-egg-info/MultiMapping.egg-info/not-zip-safe pip-egg-info/MultiMapping.egg-info/requires.txt pip-egg-info/MultiMapping.egg-info/top_level.txt src/MultiMapping/_MultiMapping.c src/MultiMapping/__init__.py src/MultiMapping/tests.pyzope2.13-2.13.21/source/MultiMapping/pip-egg-info/MultiMapping.egg-info/not-zip-safe0000644000175000017500000000000112214017445026654 0ustar arnauarnau zope2.13-2.13.21/source/MultiMapping/README.txt0000644000175000017500000000014112214017445017537 0ustar arnauarnauOverview ======== MultiMapping provides special objects used in some Zope2 internals like ZRDB. zope2.13-2.13.21/source/MultiMapping/setup.cfg0000644000175000017500000000007312214017445017666 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/MultiMapping/include/0000755000175000017500000000000012214017445017470 5ustar arnauarnauzope2.13-2.13.21/source/MultiMapping/include/ExtensionClass/0000755000175000017500000000000012214017445022432 5ustar arnauarnauzope2.13-2.13.21/source/MultiMapping/include/ExtensionClass/pickle/0000755000175000017500000000000012214017445023701 5ustar arnauarnauzope2.13-2.13.21/source/MultiMapping/include/ExtensionClass/pickle/pickle.c0000644000175000017500000002202312214017445025313 0ustar arnauarnau/* Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. */ /* Reusable pickle support code This is "includeware", meant to be used through a C include */ /* It's a dang shame we can't inherit __get/setstate__ from object :( */ static PyObject *str__slotnames__, *copy_reg_slotnames, *__newobj__; static PyObject *str__getnewargs__, *str__getstate__; #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) typedef int Py_ssize_t; #define PY_SSIZE_T_MAX INT_MAX #define PY_SSIZE_T_MIN INT_MIN #endif static int pickle_setup(void) { PyObject *copy_reg; int r = -1; #define DEFINE_STRING(S) \ if(! (str ## S = PyString_FromString(# S))) return -1 DEFINE_STRING(__slotnames__); DEFINE_STRING(__getnewargs__); DEFINE_STRING(__getstate__); #undef DEFINE_STRING copy_reg = PyImport_ImportModule("copy_reg"); if (copy_reg == NULL) return -1; copy_reg_slotnames = PyObject_GetAttrString(copy_reg, "_slotnames"); if (copy_reg_slotnames == NULL) goto end; __newobj__ = PyObject_GetAttrString(copy_reg, "__newobj__"); if (__newobj__ == NULL) goto end; r = 0; end: Py_DECREF(copy_reg); return r; } static PyObject * pickle_slotnames(PyTypeObject *cls) { PyObject *slotnames; slotnames = PyDict_GetItem(cls->tp_dict, str__slotnames__); if (slotnames != NULL) { Py_INCREF(slotnames); return slotnames; } slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames, (PyObject*)cls, NULL); if (slotnames != NULL && slotnames != Py_None && ! PyList_Check(slotnames)) { PyErr_SetString(PyExc_TypeError, "copy_reg._slotnames didn't return a list or None"); Py_DECREF(slotnames); slotnames = NULL; } return slotnames; } static PyObject * pickle_copy_dict(PyObject *state) { PyObject *copy, *key, *value; char *ckey; Py_ssize_t pos = 0; Py_ssize_t nr; copy = PyDict_New(); if (copy == NULL) return NULL; if (state == NULL) return copy; while ((nr = PyDict_Next(state, &pos, &key, &value))) { if (nr < 0) goto err; if (key && PyString_Check(key)) { ckey = PyString_AS_STRING(key); if (*ckey == '_' && (ckey[1] == 'v' || ckey[1] == 'p') && ckey[2] == '_') /* skip volatile and persistent */ continue; } if (key != NULL && value != NULL && (PyObject_SetItem(copy, key, value) < 0) ) goto err; } return copy; err: Py_DECREF(copy); return NULL; } static char pickle___getstate__doc[] = "Get the object serialization state\n" "\n" "If the object has no assigned slots and has no instance dictionary, then \n" "None is returned.\n" "\n" "If the object has no assigned slots and has an instance dictionary, then \n" "the a copy of the instance dictionary is returned. The copy has any items \n" "with names starting with '_v_' or '_p_' ommitted.\n" "\n" "If the object has assigned slots, then a two-element tuple is returned. \n" "The first element is either None or a copy of the instance dictionary, \n" "as described above. The second element is a dictionary with items \n" "for each of the assigned slots.\n" ; static PyObject * pickle___getstate__(PyObject *self) { PyObject *slotnames=NULL, *slots=NULL, *state=NULL; PyObject **dictp; int n=0; slotnames = pickle_slotnames(self->ob_type); if (slotnames == NULL) return NULL; dictp = _PyObject_GetDictPtr(self); if (dictp) state = pickle_copy_dict(*dictp); else { state = Py_None; Py_INCREF(state); } if (slotnames != Py_None) { int i; slots = PyDict_New(); if (slots == NULL) goto end; for (i = 0; i < PyList_GET_SIZE(slotnames); i++) { PyObject *name, *value; char *cname; name = PyList_GET_ITEM(slotnames, i); if (PyString_Check(name)) { cname = PyString_AS_STRING(name); if (*cname == '_' && (cname[1] == 'v' || cname[1] == 'p') && cname[2] == '_') /* skip volatile and persistent */ continue; } value = PyObject_GetAttr(self, name); if (value == NULL) PyErr_Clear(); else { int err = PyDict_SetItem(slots, name, value); Py_DECREF(value); if (err) goto end; n++; } } } if (n) state = Py_BuildValue("(NO)", state, slots); end: Py_XDECREF(slotnames); Py_XDECREF(slots); return state; } static int pickle_setattrs_from_dict(PyObject *self, PyObject *dict) { PyObject *key, *value; Py_ssize_t pos = 0; if (! PyDict_Check(dict)) { PyErr_SetString(PyExc_TypeError, "Expected dictionary"); return -1; } while (PyDict_Next(dict, &pos, &key, &value)) { if (key != NULL && value != NULL && (PyObject_SetAttr(self, key, value) < 0) ) return -1; } return 0; } static char pickle___setstate__doc[] = "Set the object serialization state\n" "\n" "The state should be in one of 3 forms:\n" "\n" "- None\n" "\n" " Ignored\n" "\n" "- A dictionary\n" "\n" " In this case, the object's instance dictionary will be cleared and \n" " updated with the new state.\n" "\n" "- A two-tuple with a string as the first element. \n" "\n" " In this case, the method named by the string in the first element will be\n" " called with the second element.\n" "\n" " This form supports migration of data formats.\n" "\n" "- A two-tuple with None or a Dictionary as the first element and\n" " with a dictionary as the second element.\n" "\n" " If the first element is not None, then the object's instance dictionary \n" " will be cleared and updated with the value.\n" "\n" " The items in the second element will be assigned as attributes.\n" ; static PyObject * pickle___setstate__(PyObject *self, PyObject *state) { PyObject *slots=NULL; if (PyTuple_Check(state)) { if (! PyArg_ParseTuple(state, "OO", &state, &slots)) return NULL; } if (state != Py_None) { PyObject **dict; dict = _PyObject_GetDictPtr(self); if (dict) { if (*dict == NULL) { *dict = PyDict_New(); if (*dict == NULL) return NULL; } } if (*dict != NULL) { PyDict_Clear(*dict); if (PyDict_Update(*dict, state) < 0) return NULL; } else if (pickle_setattrs_from_dict(self, state) < 0) return NULL; } if (slots != NULL && pickle_setattrs_from_dict(self, slots) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static char pickle___getnewargs__doc[] = "Get arguments to be passed to __new__\n" ; static PyObject * pickle___getnewargs__(PyObject *self) { return PyTuple_New(0); } static char pickle___reduce__doc[] = "Reduce an object to contituent parts for serialization\n" ; static PyObject * pickle___reduce__(PyObject *self) { PyObject *args=NULL, *bargs=0, *state=NULL; int l, i; /* we no longer require '__getnewargs__' to be defined but use it if it is */ PyObject *getnewargs=NULL; getnewargs = PyObject_GetAttr(self, str__getnewargs__); if (getnewargs) bargs = PyEval_CallObject(getnewargs, (PyObject *)NULL); else { PyErr_Clear(); bargs = PyTuple_New(0); } l = PyTuple_Size(bargs); if (l < 0) goto end; args = PyTuple_New(l+1); if (args == NULL) goto end; Py_INCREF(self->ob_type); PyTuple_SET_ITEM(args, 0, (PyObject*)(self->ob_type)); for (i = 0; i < l; i++) { Py_INCREF(PyTuple_GET_ITEM(bargs, i)); PyTuple_SET_ITEM(args, i+1, PyTuple_GET_ITEM(bargs, i)); } state = PyObject_CallMethodObjArgs(self, str__getstate__, NULL); if (state == NULL) goto end; state = Py_BuildValue("(OON)", __newobj__, args, state); end: Py_XDECREF(bargs); Py_XDECREF(args); Py_XDECREF(getnewargs); return state; } #define PICKLE_GETSTATE_DEF \ {"__getstate__", (PyCFunction)pickle___getstate__, METH_NOARGS, \ pickle___getstate__doc}, #define PICKLE_SETSTATE_DEF \ {"__setstate__", (PyCFunction)pickle___setstate__, METH_O, \ pickle___setstate__doc}, #define PICKLE_GETNEWARGS_DEF #define PICKLE_REDUCE_DEF \ {"__reduce__", (PyCFunction)pickle___reduce__, METH_NOARGS, \ pickle___reduce__doc}, #define PICKLE_METHODS PICKLE_GETSTATE_DEF PICKLE_SETSTATE_DEF \ PICKLE_GETNEWARGS_DEF PICKLE_REDUCE_DEF zope2.13-2.13.21/source/MultiMapping/include/ExtensionClass/_ExtensionClass.c0000644000175000017500000006076512214017445025715 0ustar arnauarnau/* Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. */ static char _extensionclass_module_documentation[] = "ExtensionClass\n" "\n" "$Id: _ExtensionClass.c 109054 2010-02-14 21:35:34Z hannosch $\n" ; #include "ExtensionClass/ExtensionClass.h" #define EC PyTypeObject static PyObject *str__of__, *str__get__, *str__class_init__, *str__init__; static PyObject *str__bases__, *str__mro__, *str__new__; #define OBJECT(O) ((PyObject *)(O)) #define TYPE(O) ((PyTypeObject *)(O)) static PyTypeObject ExtensionClassType; static PyTypeObject BaseType; static PyObject * of_get(PyObject *self, PyObject *inst, PyObject *cls) { /* Descriptor slot function that calls __of__ */ if (inst && PyExtensionInstance_Check(inst)) return PyObject_CallMethodObjArgs(self, str__of__, inst, NULL); Py_INCREF(self); return self; } PyObject * Base_getattro(PyObject *obj, PyObject *name) { /* This is a modified copy of PyObject_GenericGetAttr. See the change note below. */ PyTypeObject *tp = obj->ob_type; PyObject *descr = NULL; PyObject *res = NULL; descrgetfunc f; long dictoffset; PyObject **dictptr; if (!PyString_Check(name)){ #ifdef Py_USING_UNICODE /* The Unicode to string conversion is done here because the existing tp_setattro slots expect a string object as name and we wouldn't want to break those. */ if (PyUnicode_Check(name)) { name = PyUnicode_AsEncodedString(name, NULL, NULL); if (name == NULL) return NULL; } else #endif { PyErr_SetString(PyExc_TypeError, "attribute name must be string"); return NULL; } } else Py_INCREF(name); if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) goto done; } #if !defined(Py_TPFLAGS_HAVE_VERSION_TAG) /* Inline _PyType_Lookup */ /* this is not quite _PyType_Lookup anymore */ { int i, n; PyObject *mro, *base, *dict; /* Look in tp_dict of types in MRO */ mro = tp->tp_mro; assert(mro != NULL); assert(PyTuple_Check(mro)); n = PyTuple_GET_SIZE(mro); for (i = 0; i < n; i++) { base = PyTuple_GET_ITEM(mro, i); if (PyClass_Check(base)) dict = ((PyClassObject *)base)->cl_dict; else { assert(PyType_Check(base)); dict = ((PyTypeObject *)base)->tp_dict; } assert(dict && PyDict_Check(dict)); descr = PyDict_GetItem(dict, name); if (descr != NULL) break; } } #else descr = _PyType_Lookup(tp, name); #endif Py_XINCREF(descr); f = NULL; if (descr != NULL && PyType_HasFeature(descr->ob_type, Py_TPFLAGS_HAVE_CLASS)) { f = descr->ob_type->tp_descr_get; if (f != NULL && PyDescr_IsData(descr)) { res = f(descr, obj, (PyObject *)obj->ob_type); Py_DECREF(descr); goto done; } } /* Inline _PyObject_GetDictPtr */ dictoffset = tp->tp_dictoffset; if (dictoffset != 0) { PyObject *dict; if (dictoffset < 0) { int tsize; size_t size; tsize = ((PyVarObject *)obj)->ob_size; if (tsize < 0) tsize = -tsize; size = _PyObject_VAR_SIZE(tp, tsize); dictoffset += (long)size; assert(dictoffset > 0); assert(dictoffset % SIZEOF_VOID_P == 0); } dictptr = (PyObject **) ((char *)obj + dictoffset); dict = *dictptr; if (dict != NULL) { Py_INCREF(dict); res = PyDict_GetItem(dict, name); if (res != NULL) { Py_INCREF(res); Py_XDECREF(descr); Py_DECREF(dict); /* CHANGED! If the tp_descr_get of res is of_get, then call it. */ if (PyObject_TypeCheck(res->ob_type, &ExtensionClassType) && res->ob_type->tp_descr_get != NULL) { PyObject *tres; tres = res->ob_type->tp_descr_get( res, obj, OBJECT(obj->ob_type)); Py_DECREF(res); res = tres; } goto done; } Py_DECREF(dict); } } if (f != NULL) { res = f(descr, obj, (PyObject *)obj->ob_type); Py_DECREF(descr); goto done; } if (descr != NULL) { res = descr; /* descr was already increfed above */ goto done; } /* CHANGED: Just use the name. Don't format. */ PyErr_SetObject(PyExc_AttributeError, name); done: Py_DECREF(name); return res; } #include "pickle/pickle.c" static struct PyMethodDef Base_methods[] = { PICKLE_METHODS {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static EC BaseType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "Base", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* tp_getattro */ (getattrofunc)Base_getattro, 0, 0, (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif ), "Standard ExtensionClass base type", 0, 0, 0, 0, 0, 0, Base_methods, }; static EC NoInstanceDictionaryBaseType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "NoInstanceDictionaryBase", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif ), "Base types for subclasses without instance dictionaries", }; static PyObject * EC_new(PyTypeObject *self, PyObject *args, PyObject *kw) { PyObject *name, *bases=NULL, *dict=NULL; PyObject *new_bases=NULL, *new_args, *result; int have_base = 0, i; if (kw && PyObject_IsTrue(kw)) { PyErr_SetString(PyExc_TypeError, "Keyword arguments are not supported"); return NULL; } if (!PyArg_ParseTuple(args, "O|O!O!", &name, &PyTuple_Type, &bases, &PyDict_Type, &dict)) return NULL; /* Make sure Base is in bases */ if (bases) { for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { if (PyObject_TypeCheck(PyTuple_GET_ITEM(bases, i), &ExtensionClassType)) { have_base = 1; break; } } if (! have_base) { new_bases = PyTuple_New(PyTuple_GET_SIZE(bases) + 1); if (new_bases == NULL) return NULL; for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { Py_XINCREF(PyTuple_GET_ITEM(bases, i)); PyTuple_SET_ITEM(new_bases, i, PyTuple_GET_ITEM(bases, i)); } Py_INCREF(OBJECT(&BaseType)); PyTuple_SET_ITEM(new_bases, PyTuple_GET_SIZE(bases), OBJECT(&BaseType)); } } else { new_bases = Py_BuildValue("(O)", &BaseType); if (new_bases == NULL) return NULL; } if (new_bases) { if (dict) new_args = Py_BuildValue("OOO", name, new_bases, dict); else new_args = Py_BuildValue("OO", name, new_bases); Py_DECREF(new_bases); if (new_args == NULL) return NULL; result = PyType_Type.tp_new(self, new_args, kw); Py_DECREF(new_args); } else { result = PyType_Type.tp_new(self, args, kw); /* We didn't have to add Base, so maybe NoInstanceDictionaryBase is in the bases. We need to check if it was. If it was, we need to suppress instance dictionary support. */ for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { if ( PyObject_TypeCheck(PyTuple_GET_ITEM(bases, i), &ExtensionClassType) && PyType_IsSubtype(TYPE(PyTuple_GET_ITEM(bases, i)), &NoInstanceDictionaryBaseType) ) { TYPE(result)->tp_dictoffset = 0; break; } } } return result; } /* set up __get__, if necessary */ static int EC_init_of(PyTypeObject *self) { PyObject *__of__; __of__ = PyObject_GetAttr(OBJECT(self), str__of__); if (__of__) { Py_DECREF(__of__); if (self->tp_descr_get) { if (self->tp_descr_get != of_get) { PyErr_SetString(PyExc_TypeError, "Can't mix __of__ and descriptors"); return -1; } } else self->tp_descr_get = of_get; } else { PyErr_Clear(); if (self->tp_descr_get == of_get) self->tp_descr_get = NULL; } return 0; } static int EC_init(PyTypeObject *self, PyObject *args, PyObject *kw) { PyObject *__class_init__, *r; if (PyType_Type.tp_init(OBJECT(self), args, kw) < 0) return -1; if (self->tp_dict != NULL) { r = PyDict_GetItemString(self->tp_dict, "__doc__"); if ((r == Py_None) && (PyDict_DelItemString(self->tp_dict, "__doc__") < 0) ) return -1; } if (EC_init_of(self) < 0) return -1; /* Call __class_init__ */ __class_init__ = PyObject_GetAttr(OBJECT(self), str__class_init__); if (__class_init__ == NULL) { PyErr_Clear(); return 0; } if (! (PyMethod_Check(__class_init__) && PyMethod_GET_FUNCTION(__class_init__) ) ) { Py_DECREF(__class_init__); PyErr_SetString(PyExc_TypeError, "Invalid type for __class_init__"); return -1; } r = PyObject_CallFunctionObjArgs(PyMethod_GET_FUNCTION(__class_init__), OBJECT(self), NULL); Py_DECREF(__class_init__); if (! r) return -1; Py_DECREF(r); return 0; } static int EC_setattro(PyTypeObject *type, PyObject *name, PyObject *value) { /* We want to allow setting attributes of builti-in types, because EC did in the past and there's code that relies on it. We can't really set slots though, but I don't think we need to. There's no good way to spot slots. We could use a lame rule like names that begin and end with __s and have just 4 _s smell too much like slots. */ if (! (type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { char *cname; int l; cname = PyString_AsString(name); if (cname == NULL) return -1; l = PyString_GET_SIZE(name); if (l > 4 && cname[0] == '_' && cname[1] == '_' && cname[l-1] == '_' && cname[l-2] == '_' ) { char *c; c = strchr(cname+2, '_'); if (c != NULL && (c - cname) >= (l-2)) { PyErr_Format (PyExc_TypeError, "can't set attributes of built-in/extension type '%s' if the " "attribute name begins and ends with __ and contains only " "4 _ characters", type->tp_name ); return -1; } } if (PyObject_GenericSetAttr(OBJECT(type), name, value) < 0) return -1; } else if (PyType_Type.tp_setattro(OBJECT(type), name, value) < 0) return -1; #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(type); #endif return 0; } static PyObject * inheritedAttribute(PyTypeObject *self, PyObject *name) { int i; PyObject *d, *cls; for (i = 1; i < PyTuple_GET_SIZE(self->tp_mro); i++) { cls = PyTuple_GET_ITEM(self->tp_mro, i); if (PyType_Check(cls)) d = ((PyTypeObject *)cls)->tp_dict; else if (PyClass_Check(cls)) d = ((PyClassObject *)cls)->cl_dict; else /* Unrecognized thing, punt */ d = NULL; if ((d == NULL) || (PyDict_GetItem(d, name) == NULL)) continue; return PyObject_GetAttr(cls, name); } PyErr_SetObject(PyExc_AttributeError, name); return NULL; } static PyObject * __basicnew__(PyObject *self) { return PyObject_CallMethodObjArgs(self, str__new__, self, NULL); } static int append_new(PyObject *result, PyObject *v) { int contains; if (v == OBJECT(&BaseType) || v == OBJECT(&PyBaseObject_Type)) return 0; /* Don't add these until end */ contains = PySequence_Contains(result, v); if (contains != 0) return contains; return PyList_Append(result, v); } static int copy_mro(PyObject *mro, PyObject *result) { PyObject *base; int i, l; l = PyTuple_Size(mro); if (l < 0) return -1; for (i=0; i < l; i++) { base = PyTuple_GET_ITEM(mro, i); if (append_new(result, base) < 0) return -1; } return 0; } static int copy_classic(PyObject *base, PyObject *result) { PyObject *bases, *basebase; int i, l, err=-1; if (append_new(result, base) < 0) return -1; bases = PyObject_GetAttr(base, str__bases__); if (bases == NULL) return -1; l = PyTuple_Size(bases); if (l < 0) goto end; for (i=0; i < l; i++) { basebase = PyTuple_GET_ITEM(bases, i); if (copy_classic(basebase, result) < 0) goto end; } err = 0; end: Py_DECREF(bases); return err; } static PyObject * mro(PyTypeObject *self) { /* Compute an MRO for a class */ PyObject *result, *base, *basemro, *mro=NULL; int i, l, err; result = PyList_New(0); if (result == NULL) return NULL; if (PyList_Append(result, OBJECT(self)) < 0) goto end; l = PyTuple_Size(self->tp_bases); if (l < 0) goto end; for (i=0; i < l; i++) { base = PyTuple_GET_ITEM(self->tp_bases, i); if (base == NULL) continue; basemro = PyObject_GetAttr(base, str__mro__); if (basemro != NULL) { /* Type */ err = copy_mro(basemro, result); Py_DECREF(basemro); if (err < 0) goto end; } else { PyErr_Clear(); if (copy_classic(base, result) < 0) goto end; } } if (self != &BaseType && PyList_Append(result, OBJECT(&BaseType)) < 0) goto end; if (PyList_Append(result, OBJECT(&PyBaseObject_Type)) < 0) goto end; l = PyList_GET_SIZE(result); mro = PyTuple_New(l); if (mro == NULL) goto end; for (i=0; i < l; i++) { Py_INCREF(PyList_GET_ITEM(result, i)); PyTuple_SET_ITEM(mro, i, PyList_GET_ITEM(result, i)); } end: Py_DECREF(result); return mro; } static struct PyMethodDef EC_methods[] = { {"__basicnew__", (PyCFunction)__basicnew__, METH_NOARGS, "Create a new empty object"}, {"inheritedAttribute", (PyCFunction)inheritedAttribute, METH_O, "Look up an inherited attribute"}, {"mro", (PyCFunction)mro, METH_NOARGS, "Compute an mro using the 'encalsulated base' scheme"}, {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static PyTypeObject ExtensionClassType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "ExtensionClass", /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ (destructor)0, /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, /* tp_compare */ (cmpfunc)0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ (hashfunc)0, /* tp_call */ (ternaryfunc)0, /* tp_str */ (reprfunc)0, /* tp_getattro */ (getattrofunc)0, /* tp_setattro */ (setattrofunc)EC_setattro, /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif , /* tp_doc */ "Meta-class for extension classes", /* tp_traverse */ (traverseproc)0, /* tp_clear */ (inquiry)0, /* tp_richcompare */ (richcmpfunc)0, /* tp_weaklistoffset */ (long)0, /* tp_iter */ (getiterfunc)0, /* tp_iternext */ (iternextfunc)0, /* tp_methods */ EC_methods, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* internal use */ /* tp_descr_get */ (descrgetfunc)0, /* tp_descr_set */ (descrsetfunc)0, /* tp_dictoffset */ 0, /* tp_init */ (initproc)EC_init, /* tp_alloc */ (allocfunc)0, /* tp_new */ (newfunc)EC_new, /* tp_free */ 0, /* Low-level free-mem routine */ /* tp_is_gc */ (inquiry)0, /* For PyObject_IS_GC */ }; static PyObject * debug(PyObject *self, PyObject *o) { Py_INCREF(Py_None); return Py_None; } static PyObject * pmc_init_of(PyObject *self, PyObject *args) { PyObject *o; if (! PyArg_ParseTuple(args, "O!", (PyObject *)&ExtensionClassType, &o)) return NULL; if (EC_init_of((PyTypeObject *)o) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } /* List of methods defined in the module */ static struct PyMethodDef ec_methods[] = { {"debug", (PyCFunction)debug, METH_O, ""}, {"pmc_init_of", (PyCFunction)pmc_init_of, METH_VARARGS, "Initialize __get__ for classes that define __of__"}, {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static PyObject * EC_findiattrs_(PyObject *self, char *cname) { PyObject *name, *r; name = PyString_FromString(cname); if (name == NULL) return NULL; r = ECBaseType->tp_getattro(self, name); Py_DECREF(name); return r; } static PyObject * ec_new_for_custom_dealloc(PyTypeObject *type, PyObject *args, PyObject *kw) { /* This is for EC's that have deallocs. For these, we need to incref the type when we create an instance, because the deallocs will decref the type. */ PyObject *r; r = PyType_GenericNew(type, args, kw); if (r) { Py_INCREF(type); } return r; } static int ec_init(PyObject *self, PyObject *args, PyObject *kw) { PyObject *r, *__init__; __init__ = PyObject_GetAttr(self, str__init__); if (__init__ == NULL) return -1; r = PyObject_Call(__init__, args, kw); Py_DECREF(__init__); if (r == NULL) return -1; Py_DECREF(r); return 0; } static int PyExtensionClass_Export_(PyObject *dict, char *name, PyTypeObject *typ) { long ecflags = 0; PyMethodDef *pure_methods = NULL, *mdef = NULL; PyObject *m; if (typ->tp_flags == 0) { /* Old-style EC */ if (typ->tp_traverse) { /* ExtensionClasses stick there methods in the tp_traverse slot */ mdef = (PyMethodDef *)typ->tp_traverse; if (typ->tp_basicsize <= sizeof(_emptyobject)) /* Pure mixin. We want rebindable methods */ pure_methods = mdef; else typ->tp_methods = mdef; typ->tp_traverse = NULL; /* Look for __init__ method */ for (; mdef->ml_name; mdef++) { if (strcmp(mdef->ml_name, "__init__") == 0) { /* we have an old-style __init__, install a special slot */ typ->tp_init = ec_init; break; } } } if (typ->tp_clear) { /* ExtensionClasses stick there flags in the tp_clear slot */ ecflags = (long)(typ->tp_clear); /* Some old-style flags were set */ if ((ecflags & EXTENSIONCLASS_BINDABLE_FLAG) && typ->tp_descr_get == NULL) /* We have __of__-style binding */ typ->tp_descr_get = of_get; } typ->tp_clear = NULL; typ->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; if (typ->tp_dealloc != NULL) typ->tp_new = ec_new_for_custom_dealloc; } typ->ob_type = ECExtensionClassType; if (ecflags & EXTENSIONCLASS_NOINSTDICT_FLAG) typ->tp_base = &NoInstanceDictionaryBaseType; else typ->tp_base = &BaseType; if (typ->tp_new == NULL) typ->tp_new = PyType_GenericNew; if (PyType_Ready(typ) < 0) return -1; if (pure_methods) { /* We had pure methods. We want to be able to rebind these, so we'll make them ordinary method wrappers around method descrs */ for (; pure_methods->ml_name; pure_methods++) { m = PyDescr_NewMethod(ECBaseType, pure_methods); if (! m) return -1; m = PyMethod_New((PyObject *)m, NULL, (PyObject *)ECBaseType); if (! m) return -1; if (PyDict_SetItemString(typ->tp_dict, pure_methods->ml_name, m) < 0) return -1; } #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(typ); #endif } else if (mdef && mdef->ml_name) { /* Blast, we have to stick __init__ in the dict ourselves because PyType_Ready probably stuck a wrapper for ec_init in instead. */ m = PyDescr_NewMethod(typ, mdef); if (! m) return -1; if (PyDict_SetItemString(typ->tp_dict, mdef->ml_name, m) < 0) return -1; #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(typ); #endif } if (PyMapping_SetItemString(dict, name, (PyObject*)typ) < 0) return -1; return 0; } PyObject * PyECMethod_New_(PyObject *callable, PyObject *inst) { if (! PyExtensionInstance_Check(inst)) { PyErr_SetString(PyExc_TypeError, "Can't bind non-ExtensionClass instance."); return NULL; } if (PyMethod_Check(callable)) { if (callable->ob_refcnt == 1) { Py_XDECREF(((PyMethodObject*)callable)->im_self); Py_INCREF(inst); ((PyMethodObject*)callable)->im_self = inst; Py_INCREF(callable); return callable; } else return callable->ob_type->tp_descr_get( callable, inst, ((PyMethodObject*)callable)->im_class); } else return PyMethod_New(callable, inst, (PyObject*)(ECBaseType)); } static struct ExtensionClassCAPIstruct TrueExtensionClassCAPI = { EC_findiattrs_, PyExtensionClass_Export_, PyECMethod_New_, &BaseType, &ExtensionClassType, }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC init_ExtensionClass(void) { PyObject *m, *s; if (pickle_setup() < 0) return; #define DEFINE_STRING(S) \ if(! (str ## S = PyString_FromString(# S))) return DEFINE_STRING(__of__); DEFINE_STRING(__get__); DEFINE_STRING(__class_init__); DEFINE_STRING(__init__); DEFINE_STRING(__bases__); DEFINE_STRING(__mro__); DEFINE_STRING(__new__); #undef DEFINE_STRING PyExtensionClassCAPI = &TrueExtensionClassCAPI; ExtensionClassType.ob_type = &PyType_Type; ExtensionClassType.tp_base = &PyType_Type; ExtensionClassType.tp_traverse = PyType_Type.tp_traverse; ExtensionClassType.tp_clear = PyType_Type.tp_clear; /* Initialize types: */ if (PyType_Ready(&ExtensionClassType) < 0) return; BaseType.ob_type = &ExtensionClassType; BaseType.tp_base = &PyBaseObject_Type; BaseType.tp_new = PyType_GenericNew; if (PyType_Ready(&BaseType) < 0) return; NoInstanceDictionaryBaseType.ob_type = &ExtensionClassType; NoInstanceDictionaryBaseType.tp_base = &BaseType; NoInstanceDictionaryBaseType.tp_new = PyType_GenericNew; if (PyType_Ready(&NoInstanceDictionaryBaseType) < 0) return; /* Create the module and add the functions */ m = Py_InitModule3("_ExtensionClass", ec_methods, _extensionclass_module_documentation); if (m == NULL) return; s = PyCObject_FromVoidPtr(PyExtensionClassCAPI, NULL); if (PyModule_AddObject(m, "CAPI2", s) < 0) return; /* Add types: */ if (PyModule_AddObject(m, "ExtensionClass", (PyObject *)&ExtensionClassType) < 0) return; if (PyModule_AddObject(m, "Base", (PyObject *)&BaseType) < 0) return; if (PyModule_AddObject(m, "NoInstanceDictionaryBase", (PyObject *)&NoInstanceDictionaryBaseType) < 0) return; } zope2.13-2.13.21/source/MultiMapping/include/ExtensionClass/__init__.py0000644000175000017500000000523712214017445024552 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ExtensionClass Extension Class exists to support types derived from the old ExtensionType meta-class that preceeded Python 2.2 and new-style classes. As a meta-class, ExtensionClass provides the following features: - Support for a class initialiser: >>> from ExtensionClass import ExtensionClass, Base >>> class C(Base): ... def __class_init__(self): ... print 'class init called' ... print self.__name__ ... def bar(self): ... return 'bar called' class init called C >>> c = C() >>> int(c.__class__ is C) 1 >>> int(c.__class__ is type(c)) 1 - Making sure that every instance of the meta-class has Base as a base class: >>> class X: ... __metaclass__ = ExtensionClass >>> Base in X.__mro__ 1 - Provide an inheritedAttribute method for looking up attributes in base classes: >>> class C2(C): ... def bar(*a): ... return C2.inheritedAttribute('bar')(*a), 42 class init called C2 >>> o = C2() >>> o.bar() ('bar called', 42) This is for compatability with old code. New code should use super instead. The base class, Base, exists mainly to support the __of__ protocol. The __of__ protocol is similar to __get__ except that __of__ is called when an implementor is retrieved from an instance as well as from a class: >>> class O(Base): ... def __of__(*a): ... return a >>> o1 = O() >>> o2 = O() >>> C.o1 = o1 >>> c.o2 = o2 >>> c.o1 == (o1, c) 1 >>> C.o1 == o1 1 >>> int(c.o2 == (o2, c)) 1 We accomplish this by making a class that implements __of__ a descriptor and treating all descriptor ExtensionClasses this way. That is, if an extension class is a descriptor, it's __get__ method will be called even when it is retrieved from an instance. >>> class O(Base): ... def __get__(*a): ... return a ... >>> o1 = O() >>> o2 = O() >>> C.o1 = o1 >>> c.o2 = o2 >>> int(c.o1 == (o1, c, type(c))) 1 >>> int(C.o1 == (o1, None, type(c))) 1 >>> int(c.o2 == (o2, c, type(c))) 1 $Id: __init__.py 40218 2005-11-18 14:39:19Z andreasjung $ """ from _ExtensionClass import * zope2.13-2.13.21/source/MultiMapping/include/ExtensionClass/tests.py0000644000175000017500000004744512214017445024164 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ $Id: tests.py 109298 2010-02-22 17:30:41Z hannosch $ """ from ExtensionClass import * import pickle def print_dict(d): d = d.items() d.sort() print '{%s}' % (', '.join( [('%r: %r' % (k, v)) for (k, v) in d] )) def test_mixing(): """Test working with a classic class >>> class Classic: ... def x(self): ... return 42 >>> class O(Base): ... def __of__(*a): ... return a >>> class O2(Classic, O): ... def __of__(*a): ... return (O2.inheritedAttribute('__of__')(*a), ... O2.inheritedAttribute('x')(a[0])) >>> class C(Base): ... def __class_init__(self): ... print 'class init called' ... print self.__name__ ... def bar(self): ... return 'bar called' class init called C >>> c = C() >>> o2 = O2() >>> c.o2 = o2 >>> int(c.o2 == ((o2, c), 42)) 1 Test working with a new style >>> class Modern(object): ... def x(self): ... return 42 >>> class O2(Modern, O): ... def __of__(*a): ... return (O2.inheritedAttribute('__of__')(*a), ... O2.inheritedAttribute('x')(a[0])) >>> o2 = O2() >>> c.o2 = o2 >>> int(c.o2 == ((o2, c), 42)) 1 """ def test_class_creation_under_stress(): """ >>> for i in range(100): ... class B(Base): ... print i, ... if i and i%20 == 0: ... print 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 >>> import gc >>> x = gc.collect() """ def old_test_add(): """test_add.py from old EC >>> class foo(Base): ... def __add__(self,other): print 'add called' >>> foo()+foo() add called """ def proper_error_on_deleattr(): """ Florent Guillaume wrote: ... Excellent. Will it also fix this particularity of ExtensionClass: >>> class A(Base): ... def foo(self): ... self.gee ... def bar(self): ... del self.gee >>> a=A() >>> a.foo() Traceback (most recent call last): ... AttributeError: gee >>> a.bar() Traceback (most recent call last): ... AttributeError: 'A' object has no attribute 'gee' I.e., the fact that KeyError is raised whereas a normal class would raise AttributeError. """ def test_NoInstanceDictionaryBase(): """ >>> class B(NoInstanceDictionaryBase): pass ... >>> B().__dict__ Traceback (most recent call last): ... AttributeError: This object has no __dict__ >>> class B(NoInstanceDictionaryBase): ... __slots__ = ('a', 'b') ... >>> class BB(B): pass ... >>> b = BB() >>> b.__dict__ Traceback (most recent call last): ... AttributeError: This object has no __dict__ >>> b.a = 1 >>> b.b = 2 >>> b.a 1 >>> b.b 2 """ def test__basicnew__(): """ >>> x = Simple.__basicnew__() >>> x.__dict__ {} """ def cmpattrs(self, other, *attrs): for attr in attrs: if attr[:3] in ('_v_', '_p_'): continue c = cmp(getattr(self, attr, None), getattr(other, attr, None)) if c: return c return 0 class Simple(Base): def __init__(self, name, **kw): self.__name__ = name self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', *(self.__dict__.keys())) def test_basic_pickling(): """ >>> x = Simple('x', aaa=1, bbb='foo') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> print_dict(x.__getstate__()) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> f, (c,), state = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Simple' >>> print_dict(state) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.__setstate__({'z': 1}) >>> x.__dict__ {'z': 1} """ class Custom(Simple): def __new__(cls, x, y): r = Base.__new__(cls) r.x, r.y = x, y return r def __init__(self, x, y): self.a = 42 def __getnewargs__(self): return self.x, self.y def __getstate__(self): return self.a def __setstate__(self, a): self.a = a def test_pickling_w_overrides(): """ >>> x = Custom('x', 'y') >>> x.a = 99 >>> (f, (c, ax, ay), a) = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Custom' >>> ax, ay, a ('x', 'y', 99) >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class Slotted(Base): __slots__ = 's1', 's2', '_p_splat', '_v_eek' def __init__(self, s1, s2): self.s1, self.s2 = s1, s2 self._v_eek = 1 self._p_splat = 2 class SubSlotted(Slotted): __slots__ = 's3', 's4' def __init__(self, s1, s2, s3): Slotted.__init__(self, s1, s2) self.s3 = s3 def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4') def test_pickling_w_slots_only(): """ >>> x = SubSlotted('x', 'y', 'z') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class SubSubSlotted(SubSlotted): def __init__(self, s1, s2, s3, **kw): SubSlotted.__init__(self, s1, s2, s3) self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4', *(self.__dict__.keys())) def test_pickling_w_slots(): """ >>> x = SubSubSlotted('x', 'y', 'z', aaa=1, bbb='foo') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ def test_pickling_w_slots_w_empty_dict(): """ >>> x = SubSubSlotted('x', 'y', 'z') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ def test_setattr_on_extension_type(): """ >>> for name in 'x', '_x', 'x_', '__x_y__', '___x__', '__x___', '_x_': ... setattr(Base, name, 1) ... print getattr(Base, name) ... delattr(Base, name) ... print getattr(Base, name, 0) 1 0 1 0 1 0 1 0 1 0 1 0 1 0 >>> Base.__foo__ = 1 Traceback (most recent call last): ... TypeError: can't set attributes of built-in/extension type """ \ """'ExtensionClass.Base' if the attribute name begins """ \ """and ends with __ and contains only 4 _ characters >>> Base.__foo__ Traceback (most recent call last): ... AttributeError: type object 'ExtensionClass.Base' """ \ """has no attribute '__foo__' >>> del Base.__foo__ Traceback (most recent call last): ... TypeError: can't set attributes of built-in/extension type """ \ """'ExtensionClass.Base' if the attribute name begins """ \ """and ends with __ and contains only 4 _ characters """ def test_mro(): """ExtensionClass method-resolution order The EC MRO is chosen to maximize backward compatibility and provide a model that is easy to reason about. The basic idea is: I'll call this the "encapsulated base" scheme. Consider: >>> class X(Base): ... pass >>> class Y(Base): ... pass >>> class Z(Base): ... pass >>> class C(X, Y, Z): ... def foo(self): ... return 42 When we look up an attribute, we do the following: - Look in C's dictionary first. - Look up the attribute in X. We don't care how we get the attribute from X. If X is a new-style-class, we use the new algorithm. If X is a classic class, we use left-to-right depth-first. If X is an nsEC, use the "encapsulated base" algorithm. If we don't find the attribute in X, look in Y and then in Z, using the same approach. This algorithm will produce backward compatible results, providing the equivalent of left-to-right depth-first for nsECs and classic classes. We'll actually do something less abstract. We'll use a simple algorthm to merge the __mro__ of the base classes, computing an __mro__ for classic classes using the left-to-right depth-first algorithm. We'll basically lay the mros end-to-end left-to-right and remove repeats, keeping the first occurence of each class. >>> [c.__name__ for c in C.__mro__] ['C', 'X', 'Y', 'Z', 'Base', 'object'] For backward-compatability's sake, we actually depart from the above description a bit. We always put Base and object last in the mro, as shown in the example above. The primary reason for this is that object provides a do-nothing __init__ method. It is common practice to mix a C-implemented base class that implements a few methods with a Python class that implements those methods and others. The idea is that the C implementation overrides selected methods in C, so the C subclass is listed first. Unfortunately, because all extension classes are required to subclass Base, and thus, object, the C subclass brings along the __init__ object from objects, which would hide any __init__ method provided by the Python mix-in. Base and object are special in that they are implied by their meta classes. For example, a new-style class always has object as an ancestor, even if it isn't listed as a base: >>> class O: ... __metaclass__ = type >>> [c.__name__ for c in O.__bases__] ['object'] >>> [c.__name__ for c in O.__mro__] ['O', 'object'] Similarly, Base is always an ancestor of an extension class: >>> class E: ... __metaclass__ = ExtensionClass >>> [c.__name__ for c in E.__bases__] ['Base'] >>> [c.__name__ for c in E.__mro__] ['E', 'Base', 'object'] Base and object are generally added soley to get a particular meta class. They aren't used to provide application functionality and really shouldn't be considered when reasoning about where attributes come from. They do provide some useful default functionality and should be included at the end of the mro. Here are more examples: >>> from ExtensionClass import Base >>> class NA(object): ... pass >>> class NB(NA): ... pass >>> class NC(NA): ... pass >>> class ND(NB, NC): ... pass >>> [c.__name__ for c in ND.__mro__] ['ND', 'NB', 'NC', 'NA', 'object'] >>> class EA(Base): ... pass >>> class EB(EA): ... pass >>> class EC(EA): ... pass >>> class ED(EB, EC): ... pass >>> [c.__name__ for c in ED.__mro__] ['ED', 'EB', 'EA', 'EC', 'Base', 'object'] >>> class EE(ED, ND): ... pass >>> [c.__name__ for c in EE.__mro__] ['EE', 'ED', 'EB', 'EA', 'EC', 'ND', 'NB', 'NC', 'NA', 'Base', 'object'] >>> class EF(ND, ED): ... pass >>> [c.__name__ for c in EF.__mro__] ['EF', 'ND', 'NB', 'NC', 'NA', 'ED', 'EB', 'EA', 'EC', 'Base', 'object'] >>> class CA: ... pass >>> class CB(CA): ... pass >>> class CC(CA): ... pass >>> class CD(CB, CC): ... pass >>> class ECD(Base, CD): ... pass >>> [c.__name__ for c in ECD.__mro__] ['ECD', 'CD', 'CB', 'CA', 'CC', 'Base', 'object'] >>> class CDE(CD, Base): ... pass >>> [c.__name__ for c in CDE.__mro__] ['CDE', 'CD', 'CB', 'CA', 'CC', 'Base', 'object'] >>> class CEND(CD, ED, ND): ... pass >>> [c.__name__ for c in CEND.__mro__] ['CEND', 'CD', 'CB', 'CA', 'CC', """ \ """'ED', 'EB', 'EA', 'EC', 'ND', 'NB', 'NC', 'NA', 'Base', 'object'] """ def test_avoiding___init__decoy_w_inheritedAttribute(): """ >>> class Decoy(Base): ... pass >>> class B(Base): ... def __init__(self, a, b): ... print '__init__', a, b >>> class C(Decoy, B): ... def __init__(self): ... print 'C init' ... C.inheritedAttribute('__init__')(self, 1, 2) >>> x = C() C init __init__ 1 2 """ def test_of_not_called_when_not_accessed_through_EC_instance(): """ >>> class Eek(Base): ... def __of__(self, parent): ... return self, parent If I define an EC instance as an attr of an ordinary class: >>> class O(object): ... eek = Eek() >>> class C: ... eek = Eek() I get the instance, without calling __of__, when I get it from either tha class: >>> O.eek is O.__dict__['eek'] True >>> C.eek is C.__dict__['eek'] True or an instance of the class: >>> O().eek is O.__dict__['eek'] True >>> C().eek is C.__dict__['eek'] True If I define an EC instance as an attr of an extension class: >>> class E(Base): ... eek = Eek() I get the instance, without calling __of__, when I get it from tha class: >>> E.eek is E.__dict__['eek'] True But __of__ is called if I go through the instance: >>> e = E() >>> e.eek == (E.__dict__['eek'], e) True """ def test_inheriting___doc__(): """Old-style ExtensionClass inherited __doc__ from base classes. >>> class E(Base): ... "eek" >>> class EE(E): ... pass >>> EE.__doc__ 'eek' >>> EE().__doc__ 'eek' """ def test___of___w_metaclass_instance(): """When looking for extension class instances, need to handle meta classes >>> class C(Base): ... pass >>> class O(Base): ... def __of__(self, parent): ... print '__of__ called on an O' >>> class M(ExtensionClass): ... pass >>> class X: ... __metaclass__ = M ... >>> class S(X, O): ... pass >>> c = C() >>> c.s = S() >>> c.s __of__ called on an O """ def test___of__set_after_creation(): """We may need to set __of__ after a class is created. Normally, in a class's __init__, the initialization code checks for an __of__ method and, if it isn't already set, sets __get__. If a class is persistent and loaded from the database, we want this to happen in __setstate__. The pmc_init_of function allws us to do that. We'll create an extension class without a __of__. We'll also give it a special meta class, just to make sure that this works with funny metaclasses too: >>> import ExtensionClass >>> class M(ExtensionClass.ExtensionClass): ... "A meta class" >>> class B(ExtensionClass.Base): ... __metaclass__ = M ... def __init__(self, name): ... self.name = name ... def __repr__(self): ... return self.name >>> B.__class__ is M True >>> x = B('x') >>> x.y = B('y') >>> x.y y We define a __of__ method for B after the fact: >>> def __of__(self, other): ... print '__of__(%r, %r)' % (self, other) ... return self >>> B.__of__ = __of__ We see that this has no effect: >>> x.y y Until we use pmc_init_of: >>> ExtensionClass.pmc_init_of(B) >>> x.y __of__(y, x) y Note that there is no harm in calling pmc_init_of multiple times: >>> ExtensionClass.pmc_init_of(B) >>> ExtensionClass.pmc_init_of(B) >>> ExtensionClass.pmc_init_of(B) >>> x.y __of__(y, x) y If we remove __of__, we'll go back to the behavior we had before: >>> del B.__of__ >>> ExtensionClass.pmc_init_of(B) >>> x.y y """ def test_Basic_gc(): """Test to make sure that EC instances participate in GC >>> from ExtensionClass import Base >>> import gc >>> class C1(Base): ... pass ... >>> class C2(Base): ... def __del__(self): ... print 'removed' ... >>> a=C1() >>> a.b = C1() >>> a.b.a = a >>> a.b.c = C2() >>> thresholds = gc.get_threshold() >>> gc.set_threshold(0) >>> ignore = gc.collect() >>> del a >>> ignored = gc.collect() removed >>> ignored > 0 True >>> gc.set_threshold(*thresholds) """ from zope.testing.doctest import DocTestSuite import unittest def test_suite(): return unittest.TestSuite(( DocTestSuite('ExtensionClass'), DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/MultiMapping/include/ExtensionClass/ExtensionClass.h0000644000175000017500000002370012214017445025547 0ustar arnauarnau/***************************************************************************** Copyright (c) 1996-2002 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /* $Id: ExtensionClass.h 40218 2005-11-18 14:39:19Z andreasjung $ Extension Class Definitions Implementing base extension classes A base extension class is implemented in much the same way that an extension type is implemented, except: - The include file, 'ExtensionClass.h', must be included. - The type structure is declared to be of type 'PyExtensionClass', rather than of type 'PyTypeObject'. - The type structure has an additional member that must be defined after the documentation string. This extra member is a method chain ('PyMethodChain') containing a linked list of method definition ('PyMethodDef') lists. Method chains can be used to implement method inheritance in C. Most extensions don't use method chains, but simply define method lists, which are null-terminated arrays of method definitions. A macro, 'METHOD_CHAIN' is defined in 'ExtensionClass.h' that converts a method list to a method chain. (See the example below.) - Module functions that create new instances must be replaced by an '__init__' method that initializes, but does not create storage for instances. - The extension class must be initialized and exported to the module with:: PyExtensionClass_Export(d,"name",type); where 'name' is the module name and 'type' is the extension class type object. Attribute lookup Attribute lookup is performed by calling the base extension class 'getattr' operation for the base extension class that includes C data, or for the first base extension class, if none of the base extension classes include C data. 'ExtensionClass.h' defines a macro 'Py_FindAttrString' that can be used to find an object's attributes that are stored in the object's instance dictionary or in the object's class or base classes:: v = Py_FindAttrString(self,name); In addition, a macro is provided that replaces 'Py_FindMethod' calls with logic to perform the same sort of lookup that is provided by 'Py_FindAttrString'. Linking The extension class mechanism was designed to be useful with dynamically linked extension modules. Modules that implement extension classes do not have to be linked against an extension class library. The macro 'PyExtensionClass_Export' imports the 'ExtensionClass' module and uses objects imported from this module to initialize an extension class with necessary behavior. */ #ifndef EXTENSIONCLASS_H #define EXTENSIONCLASS_H #include "Python.h" #include "import.h" /* Declarations for objects of type ExtensionClass */ #define EC PyTypeObject #define PyExtensionClass PyTypeObject #define EXTENSIONCLASS_BINDABLE_FLAG 1 << 2 #define EXTENSIONCLASS_NOINSTDICT_FLAG 1 << 5 typedef struct { PyObject_HEAD } _emptyobject; static struct ExtensionClassCAPIstruct { /***************************************************************************** WARNING: THIS STRUCT IS PRIVATE TO THE EXTENSION CLASS INTERFACE IMPLEMENTATION AND IS SUBJECT TO CHANGE !!! *****************************************************************************/ PyObject *(*EC_findiattrs_)(PyObject *self, char *cname); int (*PyExtensionClass_Export_)(PyObject *dict, char *name, PyTypeObject *typ); PyObject *(*PyECMethod_New_)(PyObject *callable, PyObject *inst); PyExtensionClass *ECBaseType_; PyExtensionClass *ECExtensionClassType_; } *PyExtensionClassCAPI = NULL; #define ECBaseType (PyExtensionClassCAPI->ECBaseType_) #define ECExtensionClassType (PyExtensionClassCAPI->ECExtensionClassType_) /* Following are macros that are needed or useful for defining extension classes: */ /* This macro redefines Py_FindMethod to do attribute for an attribute name given by a C string lookup using extension class meta-data. This is used by older getattr implementations. This macro is used in base class implementations of tp_getattr to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. Note that in Python 1.4, a getattr operation may be provided that uses an object argument. Classes that support this new operation should use Py_FindAttr. */ #define EC_findiattrs (PyExtensionClassCAPI->EC_findiattrs_) #define Py_FindMethod(M,SELF,NAME) (EC_findiattrs((SELF),(NAME))) /* Do method or attribute lookup for an attribute name given by a C string using extension class meta-data. This macro is used in base class implementations of tp_getattro to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. Note that in Python 1.4, a getattr operation may be provided that uses an object argument. Classes that support this new operation should use Py_FindAttr. */ #define Py_FindAttrString(SELF,NAME) (EC_findiattrs((SELF),(NAME))) /* Do method or attribute lookup using extension class meta-data. This macro is used in base class implementations of tp_getattr to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. */ #define Py_FindAttr (ECBaseType->tp_getattro) /* Do attribute assignment for an attribute. This macro is used in base class implementations of tp_setattro to set attributes that are not managed by the base type directly. The macro is generally used to assign attributes after other attribute attempts to assign attributes have failed. */ #define PyEC_SetAttr(SELF,NAME,V) (ECBaseType->tp_setattro(SELF, NAME, V)) /* Convert a method list to a method chain. */ #define METHOD_CHAIN(DEF) (traverseproc)(DEF) /* The following macro checks whether a type is an extension class: */ #define PyExtensionClass_Check(TYPE) \ PyObject_TypeCheck((PyObject*)(TYPE), ECExtensionClassType) /* The following macro checks whether an instance is an extension instance: */ #define PyExtensionInstance_Check(INST) \ PyObject_TypeCheck(((PyObject*)(INST))->ob_type, ECExtensionClassType) #define CHECK_FOR_ERRORS(MESS) /* The following macro can be used to define an extension base class that only provides method and that is used as a pure mix-in class. */ #define PURE_MIXIN_CLASS(NAME,DOC,METHODS) \ static PyExtensionClass NAME ## Type = { PyObject_HEAD_INIT(NULL) 0, # NAME, \ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \ 0 , DOC, (traverseproc)METHODS, } /* The following macros provide limited access to extension-class method facilities. */ /* Test for an ExtensionClass method: */ #define PyECMethod_Check(O) PyMethod_Check((O)) /* Create a method object that wraps a callable object and an instance. Note that if the callable object is an extension class method, then the new method will wrap the callable object that is wrapped by the extension class method. Also note that if the callable object is an extension class method with a reference count of 1, then the callable object will be rebound to the instance and returned with an incremented reference count. */ #define PyECMethod_New(CALLABLE, INST) \ PyExtensionClassCAPI->PyECMethod_New_((CALLABLE),(INST)) /* Return the instance that is bound by an extension class method. */ #define PyECMethod_Self(M) \ (PyMethod_Check((M)) ? ((PyMethodObject*)(M))->im_self : NULL) /* Check whether an object has an __of__ method for returning itself in the context of it's container. */ #define has__of__(O) (PyObject_TypeCheck((O)->ob_type, ECExtensionClassType) \ && (O)->ob_type->tp_descr_get != NULL) /* The following macros are used to check whether an instance or a class' instanses have instance dictionaries: */ #define HasInstDict(O) (_PyObject_GetDictPtr(O) != NULL) #define ClassHasInstDict(C) ((C)->tp_dictoffset > 0)) /* Get an object's instance dictionary. Use with caution */ #define INSTANCE_DICT(inst) (_PyObject_GetDictPtr(O)) /* Test whether an ExtensionClass, S, is a subclass of ExtensionClass C. */ #define ExtensionClassSubclass_Check(S,C) PyType_IsSubtype((S), (C)) /* Test whether an ExtensionClass instance , I, is a subclass of ExtensionClass C. */ #define ExtensionClassSubclassInstance_Check(I,C) PyObject_TypeCheck((I), (C)) /* Export an Extension Base class in a given module dictionary with a given name and ExtensionClass structure. */ #define PyExtensionClass_Export(D,N,T) \ if (! ExtensionClassImported || \ PyExtensionClassCAPI->PyExtensionClass_Export_((D),(N),&(T)) < 0) return; #define ExtensionClassImported \ ((PyExtensionClassCAPI != NULL) || \ (PyExtensionClassCAPI = PyCObject_Import("ExtensionClass","CAPI2"))) /* These are being overridded to use tp_free when used with new-style classes. This is to allow old extention-class code to work. */ #undef PyMem_DEL #undef PyObject_DEL #define PyMem_DEL(O) \ if (((O)->ob_type->tp_flags & Py_TPFLAGS_HAVE_CLASS) \ && ((O)->ob_type->tp_free != NULL)) \ (O)->ob_type->tp_free((PyObject*)(O)); \ else \ PyObject_FREE((O)); #define PyObject_DEL(O) PyMem_DEL(O) #endif /* EXTENSIONCLASS_H */ zope2.13-2.13.21/source/MultiMapping/buildout.cfg0000644000175000017500000000027112214017445020355 0ustar arnauarnau[buildout] develop = . parts = interpreter test [interpreter] recipe = zc.recipe.egg interpreter = python eggs = MultiMapping [test] recipe = zc.recipe.testrunner eggs = MultiMapping zope2.13-2.13.21/source/MultiMapping/bootstrap.py0000644000175000017500000000742112214017445020440 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 108946 2010-02-12 02:40:18Z yusei $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/MultiMapping/CHANGES.txt0000644000175000017500000000013612214017445017656 0ustar arnauarnauChangelog ========= 2.13.0 (2010-03-30) ------------------- - Released as separate package. zope2.13-2.13.21/source/MultiMapping/src/0000755000175000017500000000000012214017445016634 5ustar arnauarnauzope2.13-2.13.21/source/MultiMapping/src/MultiMapping.egg-info/0000755000175000017500000000000012214017445022734 5ustar arnauarnauzope2.13-2.13.21/source/MultiMapping/src/MultiMapping.egg-info/PKG-INFO0000644000175000017500000000107512214017445024034 0ustar arnauarnauMetadata-Version: 1.0 Name: MultiMapping Version: 2.13.0 Summary: Special MultiMapping objects used in Zope2. Home-page: http://pypi.python.org/pypi/MultiMapping Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== MultiMapping provides special objects used in some Zope2 internals like ZRDB. Changelog ========= 2.13.0 (2010-03-30) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/MultiMapping/src/MultiMapping.egg-info/dependency_links.txt0000644000175000017500000000000112214017445027002 0ustar arnauarnau zope2.13-2.13.21/source/MultiMapping/src/MultiMapping.egg-info/requires.txt0000644000175000017500000000001612214017445025331 0ustar arnauarnauExtensionClasszope2.13-2.13.21/source/MultiMapping/src/MultiMapping.egg-info/top_level.txt0000644000175000017500000000001512214017445025462 0ustar arnauarnauMultiMapping zope2.13-2.13.21/source/MultiMapping/src/MultiMapping.egg-info/SOURCES.txt0000644000175000017500000000107212214017445024620 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py include/ExtensionClass/ExtensionClass.h include/ExtensionClass/_ExtensionClass.c include/ExtensionClass/__init__.py include/ExtensionClass/tests.py include/ExtensionClass/pickle/pickle.c src/MultiMapping/_MultiMapping.c src/MultiMapping/__init__.py src/MultiMapping/tests.py src/MultiMapping.egg-info/PKG-INFO src/MultiMapping.egg-info/SOURCES.txt src/MultiMapping.egg-info/dependency_links.txt src/MultiMapping.egg-info/not-zip-safe src/MultiMapping.egg-info/requires.txt src/MultiMapping.egg-info/top_level.txtzope2.13-2.13.21/source/MultiMapping/src/MultiMapping.egg-info/not-zip-safe0000644000175000017500000000000112214017445025162 0ustar arnauarnau zope2.13-2.13.21/source/MultiMapping/src/MultiMapping/0000755000175000017500000000000012214017445021242 5ustar arnauarnauzope2.13-2.13.21/source/MultiMapping/src/MultiMapping/__init__.py0000644000175000017500000000003412214017445023350 0ustar arnauarnaufrom _MultiMapping import * zope2.13-2.13.21/source/MultiMapping/src/MultiMapping/tests.py0000644000175000017500000000255112214017445022761 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Multi-mapping tests >>> from MultiMapping import * >>> def sortprint(L): ... L.sort() ... print L >>> m=MultiMapping() >>> m.push({'spam':1, 'eggs':2}) >>> print m['spam'] 1 >>> print m['eggs'] 2 >>> m.push({'spam':3}) >>> print m['spam'] 3 >>> print m['eggs'] 2 >>> sortprint(m.pop().items()) [('spam', 3)] >>> sortprint(m.pop().items()) [('eggs', 2), ('spam', 1)] >>> try: ... print m.pop() ... raise "That\'s odd", "This last pop should have failed!" ... except: # I should probably raise a specific error in this case. ... pass """ import unittest from doctest import DocTestSuite def test_suite(): return unittest.TestSuite(( DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/MultiMapping/src/MultiMapping/_MultiMapping.c0000644000175000017500000001246712214017445024165 0ustar arnauarnau/***************************************************************************** Copyright (c) 1996-2003 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #include "ExtensionClass/ExtensionClass.h" #define UNLESS(E) if(!(E)) typedef struct { PyObject_HEAD PyObject *data; } MMobject; staticforward PyExtensionClass MMtype; static PyObject * MM_push(MMobject *self, PyObject *args) { PyObject *src; UNLESS(PyArg_ParseTuple(args, "O", &src)) return NULL; UNLESS(-1 != PyList_Append(self->data,src)) return NULL; Py_INCREF(Py_None); return Py_None; } static PyObject * MM_pop(MMobject *self, PyObject *args) { int i=1, l; PyObject *r; if(args) UNLESS(PyArg_ParseTuple(args, "|i", &i)) return NULL; if((l=PyList_Size(self->data)) < 0) return NULL; i=l-i; UNLESS(r=PySequence_GetItem(self->data,l-1)) return NULL; if(PyList_SetSlice(self->data,i,l,NULL) < 0) goto err; return r; err: Py_DECREF(r); return NULL; } static PyObject * MM__init__(MMobject *self, PyObject *args) { UNLESS(self->data=PyList_New(0)) return NULL; if (args) { int l, i; if ((l=PyTuple_Size(args)) < 0) return NULL; for (i=0; i < l; i++) if (PyList_Append(self->data, PyTuple_GET_ITEM(args, i)) < 0) return NULL; } Py_INCREF(Py_None); return Py_None; } static PyObject * MM_subscript(MMobject *self, PyObject *key) { long i; PyObject *e; UNLESS(-1 != (i=PyList_Size(self->data))) return NULL; while(--i >= 0) { e=PyList_GetItem(self->data,i); if((e=PyObject_GetItem(e,key))) return e; PyErr_Clear(); } PyErr_SetObject(PyExc_KeyError,key); return NULL; } static PyObject * MM_has_key(MMobject *self, PyObject *args) { PyObject *key; UNLESS(PyArg_ParseTuple(args,"O",&key)) return NULL; if((key=MM_subscript(self, key))) { Py_DECREF(key); return PyInt_FromLong(1); } PyErr_Clear(); return PyInt_FromLong(0); } static PyObject * MM_get(MMobject *self, PyObject *args) { PyObject *key, *d=Py_None; UNLESS(PyArg_ParseTuple(args,"O|O",&key,&d)) return NULL; if((key=MM_subscript(self, key))) return key; PyErr_Clear(); Py_INCREF(d); return d; } static struct PyMethodDef MM_methods[] = { {"__init__", (PyCFunction)MM__init__, METH_VARARGS, "__init__([m1, m2, ...]) -- Create a new empty multi-mapping"}, {"get", (PyCFunction) MM_get, METH_VARARGS, "get(key,[default]) -- Return a value for the given key or a default"}, {"has_key", (PyCFunction) MM_has_key, METH_VARARGS, "has_key(key) -- Return 1 if the mapping has the key, and 0 otherwise"}, {"push", (PyCFunction) MM_push, METH_VARARGS, "push(mapping_object) -- Add a data source"}, {"pop", (PyCFunction) MM_pop, METH_VARARGS, "pop([n]) -- Remove and return the last data source added"}, {NULL, NULL} /* sentinel */ }; static void MM_dealloc(MMobject *self) { Py_XDECREF(self->data); Py_DECREF(self->ob_type); PyObject_DEL(self); } static PyObject * MM_getattr(MMobject *self, char *name) { return Py_FindMethod(MM_methods, (PyObject *)self, name); } static int MM_length(MMobject *self) { long l=0, el, i; PyObject *e=0; UNLESS(-1 != (i=PyList_Size(self->data))) return -1; while(--i >= 0) { e=PyList_GetItem(self->data,i); UNLESS(-1 != (el=PyObject_Length(e))) return -1; l+=el; } return l; } static PyMappingMethods MM_as_mapping = { (inquiry)MM_length, /*mp_length*/ (binaryfunc)MM_subscript, /*mp_subscript*/ (objobjargproc)NULL, /*mp_ass_subscript*/ }; /* -------------------------------------------------------- */ static char MMtype__doc__[] = "MultiMapping -- Combine multiple mapping objects for lookup" ; static PyExtensionClass MMtype = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "MultiMapping", /*tp_name*/ sizeof(MMobject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)MM_dealloc, /*tp_dealloc*/ (printfunc)0, /*tp_print*/ (getattrfunc)MM_getattr, /*tp_getattr*/ (setattrfunc)0, /*tp_setattr*/ (cmpfunc)0, /*tp_compare*/ (reprfunc)0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ &MM_as_mapping, /*tp_as_mapping*/ (hashfunc)0, /*tp_hash*/ (ternaryfunc)0, /*tp_call*/ (reprfunc)0, /*tp_str*/ /* Space for future expansion */ 0L,0L,0L,0L, MMtype__doc__, /* Documentation string */ METHOD_CHAIN(MM_methods) }; static struct PyMethodDef MultiMapping_methods[] = { {NULL, NULL} /* sentinel */ }; void init_MultiMapping(void) { PyObject *m, *d; m = Py_InitModule4( "_MultiMapping", MultiMapping_methods, "MultiMapping -- Wrap multiple mapping objects for lookup" "\n\n" "$Id: _MultiMapping.c 110317 2010-03-30 17:38:04Z hannosch $\n", (PyObject*)NULL,PYTHON_API_VERSION); d = PyModule_GetDict(m); PyExtensionClass_Export(d,"MultiMapping",MMtype); if (PyErr_Occurred()) Py_FatalError("can't initialize module MultiMapping"); } zope2.13-2.13.21/source/zExceptions/0000755000175000017500000000000012214017520015744 5ustar arnauarnauzope2.13-2.13.21/source/zExceptions/setup.py0000644000175000017500000000250612214017520017461 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from setuptools import setup, find_packages setup(name='zExceptions', version = '2.13.0', url='http://pypi.python.org/pypi/zExceptions', license='ZPL 2.1', description="zExceptions contains common exceptions used in Zope2.", author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=open('README.txt').read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), package_dir={'': 'src'}, install_requires=[ 'setuptools', 'zope.interface', 'zope.publisher', 'zope.security', ], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/zExceptions/PKG-INFO0000644000175000017500000000114312214017520017040 0ustar arnauarnauMetadata-Version: 1.0 Name: zExceptions Version: 2.13.0 Summary: zExceptions contains common exceptions used in Zope2. Home-page: http://pypi.python.org/pypi/zExceptions Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== zExceptions contains common exceptions and helper functions related to exceptions as used in Zope 2. Changelog ========= 2.13.0 (2010-06-05) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/zExceptions/pip-egg-info/0000755000175000017500000000000012214017520020225 5ustar arnauarnauzope2.13-2.13.21/source/zExceptions/pip-egg-info/zExceptions.egg-info/0000755000175000017500000000000012214017520024232 5ustar arnauarnauzope2.13-2.13.21/source/zExceptions/pip-egg-info/zExceptions.egg-info/PKG-INFO0000644000175000017500000000114312214017520025326 0ustar arnauarnauMetadata-Version: 1.0 Name: zExceptions Version: 2.13.0 Summary: zExceptions contains common exceptions used in Zope2. Home-page: http://pypi.python.org/pypi/zExceptions Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== zExceptions contains common exceptions and helper functions related to exceptions as used in Zope 2. Changelog ========= 2.13.0 (2010-06-05) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/zExceptions/pip-egg-info/zExceptions.egg-info/dependency_links.txt0000644000175000017500000000000112214017520030300 0ustar arnauarnau zope2.13-2.13.21/source/zExceptions/pip-egg-info/zExceptions.egg-info/requires.txt0000644000175000017500000000006612214017520026634 0ustar arnauarnausetuptools zope.interface zope.publisher zope.securityzope2.13-2.13.21/source/zExceptions/pip-egg-info/zExceptions.egg-info/top_level.txt0000644000175000017500000000001412214017520026757 0ustar arnauarnauzExceptions zope2.13-2.13.21/source/zExceptions/pip-egg-info/zExceptions.egg-info/SOURCES.txt0000644000175000017500000000120712214017520026116 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zExceptions.egg-info/PKG-INFO pip-egg-info/zExceptions.egg-info/SOURCES.txt pip-egg-info/zExceptions.egg-info/dependency_links.txt pip-egg-info/zExceptions.egg-info/not-zip-safe pip-egg-info/zExceptions.egg-info/requires.txt pip-egg-info/zExceptions.egg-info/top_level.txt src/zExceptions/ExceptionFormatter.py src/zExceptions/ITracebackSupplement.py src/zExceptions/TracebackSupplement.py src/zExceptions/__init__.py src/zExceptions/unauthorized.py src/zExceptions/tests/__init__.py src/zExceptions/tests/testExceptionFormatter.py src/zExceptions/tests/test___init__.py src/zExceptions/tests/test_unauthorized.pyzope2.13-2.13.21/source/zExceptions/pip-egg-info/zExceptions.egg-info/not-zip-safe0000644000175000017500000000000112214017520026460 0ustar arnauarnau zope2.13-2.13.21/source/zExceptions/LICENSE.txt0000644000175000017500000000402612214017520017571 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zExceptions/README.txt0000644000175000017500000000017012214017520017440 0ustar arnauarnauOverview ======== zExceptions contains common exceptions and helper functions related to exceptions as used in Zope 2. zope2.13-2.13.21/source/zExceptions/setup.cfg0000644000175000017500000000007312214017520017565 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zExceptions/COPYRIGHT.txt0000644000175000017500000000004012214017520020047 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zExceptions/buildout.cfg0000644000175000017500000000026712214017520020261 0ustar arnauarnau[buildout] develop = . parts = interpreter test [interpreter] recipe = zc.recipe.egg interpreter = python eggs = zExceptions [test] recipe = zc.recipe.testrunner eggs = zExceptions zope2.13-2.13.21/source/zExceptions/bootstrap.py0000644000175000017500000000742012214017520020336 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 108946 2010-02-12 02:40:18Z yusei $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zExceptions/CHANGES.txt0000644000175000017500000000013612214017520017555 0ustar arnauarnauChangelog ========= 2.13.0 (2010-06-05) ------------------- - Released as separate package. zope2.13-2.13.21/source/zExceptions/src/0000755000175000017500000000000012214017520016533 5ustar arnauarnauzope2.13-2.13.21/source/zExceptions/src/zExceptions.egg-info/0000755000175000017500000000000012214017520022540 5ustar arnauarnauzope2.13-2.13.21/source/zExceptions/src/zExceptions.egg-info/PKG-INFO0000644000175000017500000000114312214017520023634 0ustar arnauarnauMetadata-Version: 1.0 Name: zExceptions Version: 2.13.0 Summary: zExceptions contains common exceptions used in Zope2. Home-page: http://pypi.python.org/pypi/zExceptions Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== zExceptions contains common exceptions and helper functions related to exceptions as used in Zope 2. Changelog ========= 2.13.0 (2010-06-05) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/zExceptions/src/zExceptions.egg-info/dependency_links.txt0000644000175000017500000000000112214017520026606 0ustar arnauarnau zope2.13-2.13.21/source/zExceptions/src/zExceptions.egg-info/requires.txt0000644000175000017500000000006612214017520025142 0ustar arnauarnausetuptools zope.interface zope.publisher zope.securityzope2.13-2.13.21/source/zExceptions/src/zExceptions.egg-info/top_level.txt0000644000175000017500000000001412214017520025265 0ustar arnauarnauzExceptions zope2.13-2.13.21/source/zExceptions/src/zExceptions.egg-info/SOURCES.txt0000644000175000017500000000122012214017520024417 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zExceptions/ExceptionFormatter.py src/zExceptions/ITracebackSupplement.py src/zExceptions/TracebackSupplement.py src/zExceptions/__init__.py src/zExceptions/unauthorized.py src/zExceptions.egg-info/PKG-INFO src/zExceptions.egg-info/SOURCES.txt src/zExceptions.egg-info/dependency_links.txt src/zExceptions.egg-info/not-zip-safe src/zExceptions.egg-info/requires.txt src/zExceptions.egg-info/top_level.txt src/zExceptions/tests/__init__.py src/zExceptions/tests/testExceptionFormatter.py src/zExceptions/tests/test___init__.py src/zExceptions/tests/test_unauthorized.pyzope2.13-2.13.21/source/zExceptions/src/zExceptions.egg-info/not-zip-safe0000644000175000017500000000000112214017520024766 0ustar arnauarnau zope2.13-2.13.21/source/zExceptions/src/zExceptions/0000755000175000017500000000000012214017520021046 5ustar arnauarnauzope2.13-2.13.21/source/zExceptions/src/zExceptions/ExceptionFormatter.py0000644000175000017500000001664612214017520025257 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """An exception formatter that shows traceback supplements and traceback info, optionally in HTML. """ import sys import cgi DEBUG_EXCEPTION_FORMATTER = 1 class TextExceptionFormatter: line_sep = '\n' show_revisions = 0 def __init__(self, limit=None): self.limit = limit def escape(self, s): return s def getPrefix(self): return 'Traceback (innermost last):' def getLimit(self): limit = self.limit if limit is None: limit = getattr(sys, 'tracebacklimit', None) return limit def getRevision(self, globals): if not self.show_revisions: return None revision = globals.get('__revision__', None) if revision is None: # Incorrect but commonly used spelling revision = globals.get('__version__', None) if revision is not None: try: revision = str(revision).strip() except: revision = '???' return revision def formatSupplementLine(self, line): return ' - %s' % line def formatObject(self, object): return [self.formatSupplementLine(repr(object))] def formatSourceURL(self, url): return [self.formatSupplementLine('URL: %s' % url)] def formatSupplement(self, supplement, tb): result = [] fmtLine = self.formatSupplementLine object = getattr(supplement, 'object', None) if object is not None: result.extend(self.formatObject(object)) url = getattr(supplement, 'source_url', None) if url is not None: result.extend(self.formatSourceURL(url)) line = getattr(supplement, 'line', 0) if line == -1: line = tb.tb_lineno col = getattr(supplement, 'column', -1) if line: if col is not None and col >= 0: result.append(fmtLine('Line %s, Column %s' % ( line, col))) else: result.append(fmtLine('Line %s' % line)) elif col is not None and col >= 0: result.append(fmtLine('Column %s' % col)) expr = getattr(supplement, 'expression', None) if expr: result.append(fmtLine('Expression: %s' % expr)) warnings = getattr(supplement, 'warnings', None) if warnings: for warning in warnings: result.append(fmtLine('Warning: %s' % warning)) extra = self.formatExtraInfo(supplement) if extra: result.append(extra) return result def formatExtraInfo(self, supplement): getInfo = getattr(supplement, 'getInfo', None) if getInfo is not None: extra = getInfo() if extra: return extra return None def formatTracebackInfo(self, tbi): return self.formatSupplementLine('__traceback_info__: %s' % (tbi, )) def formatLine(self, tb): f = tb.tb_frame lineno = tb.tb_lineno co = f.f_code filename = co.co_filename name = co.co_name locals = f.f_locals globals = f.f_globals modname = globals.get('__name__', filename) s = ' Module %s, line %d' % (modname, lineno) revision = self.getRevision(globals) if revision: s = s + ', rev. %s' % revision s = s + ', in %s' % name result = [] result.append(self.escape(s)) # Output a traceback supplement, if any. if '__traceback_supplement__' in locals: # Use the supplement defined in the function. tbs = locals['__traceback_supplement__'] elif '__traceback_supplement__' in globals: # Use the supplement defined in the module. # This is used by Scripts (Python). tbs = globals['__traceback_supplement__'] else: tbs = None if tbs is not None: factory = tbs[0] args = tbs[1:] try: supp = factory(*args) result.extend(self.formatSupplement(supp, tb)) except: if DEBUG_EXCEPTION_FORMATTER: import traceback traceback.print_exc() # else just swallow the exception. try: tbi = locals.get('__traceback_info__', None) if tbi is not None: result.append(self.formatTracebackInfo(tbi)) except: pass return self.line_sep.join(result) def formatExceptionOnly(self, etype, value): import traceback return self.line_sep.join( traceback.format_exception_only(etype, value)) def formatLastLine(self, exc_line): return self.escape(exc_line) def formatException(self, etype, value, tb, limit=None): # The next line provides a way to detect recursion. __exception_formatter__ = 1 result = [self.getPrefix() + '\n'] if limit is None: limit = self.getLimit() n = 0 while tb is not None and (limit is None or n < limit): if tb.tb_frame.f_locals.get('__exception_formatter__'): # Stop recursion. result.append('(Recursive formatException() stopped)\n') break line = self.formatLine(tb) result.append(line + '\n') tb = tb.tb_next n = n + 1 exc_line = self.formatExceptionOnly(etype, value) result.append(self.formatLastLine(exc_line)) return result class HTMLExceptionFormatter (TextExceptionFormatter): line_sep = '
\r\n' def escape(self, s): return cgi.escape(s) def getPrefix(self): return '

Traceback (innermost last):

\r\n
    ' def formatSupplementLine(self, line): return '%s' % self.escape(str(line)) def formatTracebackInfo(self, tbi): s = self.escape(str(tbi)) s = s.replace('\n', self.line_sep) return '__traceback_info__: %s' % s def formatLine(self, tb): line = TextExceptionFormatter.formatLine(self, tb) return '
  • %s
  • ' % line def formatLastLine(self, exc_line): return '

%s

' % self.escape(exc_line) def formatExtraInfo(self, supplement): getInfo = getattr(supplement, 'getInfo', None) if getInfo is not None: extra = getInfo(1) if extra: return extra return None limit = 200 if hasattr(sys, 'tracebacklimit'): limit = min(limit, sys.tracebacklimit) text_formatter = TextExceptionFormatter(limit) html_formatter = HTMLExceptionFormatter(limit) def format_exception(t, v, tb, limit=None, as_html=0): if as_html: fmt = html_formatter else: fmt = text_formatter return fmt.formatException(t, v, tb, limit=limit) zope2.13-2.13.21/source/zExceptions/src/zExceptions/unauthorized.py0000644000175000017500000000527412214017520024151 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.interface import implements from zope.security.interfaces import IUnauthorized class Unauthorized(Exception): """Some user wasn't allowed to access a resource """ implements(IUnauthorized) def _get_message(self): return self._message message = property(_get_message,) def __init__(self, message=None, value=None, needed=None, name=None, **kw): """Possible signatures: Unauthorized() Unauthorized(message) # Note that message includes a space Unauthorized(name) Unauthorized(name, value) Unauthorized(name, value, needed) Unauthorized(message, value, needed, name) Where needed is a mapping objects with items representing requirements (e.g. {'permission': 'add spam'}). Any extra keyword arguments provides are added to needed. """ if name is None and ( not isinstance(message, basestring) or len(message.split()) <= 1): # First arg is a name, not a message name=message message=None self.name=name self._message=message self.value=value if kw: if needed: needed.update(kw) else: needed=kw self.needed=needed def __str__(self): if self.message is not None: return self.message if self.name is not None: return ("You are not allowed to access '%s' in this context" % self.name) elif self.value is not None: return ("You are not allowed to access '%s' in this context" % self.getValueName()) return repr(self) def __unicode__(self): result = self.__str__() if isinstance(result, unicode): return result return unicode(result, 'ascii') # override sys.getdefaultencoding() def getValueName(self): v=self.value vname=getattr(v, '__name__', None) if vname: return vname c = getattr(v, '__class__', type(v)) c = getattr(c, '__name__', 'object') return "a particular %s" % c zope2.13-2.13.21/source/zExceptions/src/zExceptions/ITracebackSupplement.py0000644000175000017500000000517412214017520025474 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ITracebackSupplement interface definition. """ from zope.interface import Interface from zope.interface import Attribute class ITracebackSupplement(Interface): """Provides valuable information to supplement an exception traceback. The interface is geared toward providing meaningful feedback when exceptions occur in user code written in mini-languages like Zope page templates and restricted Python scripts. """ source_url = Attribute( 'source_url', """Optional. Set to URL of the script where the exception occurred. Normally this generates a URL in the traceback that the user can visit to manage the object. Set to None if unknown or not available. """) object = Attribute( 'object', """Optional. Set to the script or template where the exception occurred. Set to None if unknown or not available. """) line = Attribute( 'line', """Optional. Set to the line number (>=1) where the exception occurred. Set to 0 or None if the line number is unknown. """) column = Attribute( 'column', """Optional. Set to the column offset (>=0) where the exception occurred. Set to None if the column number is unknown. """) expression = Attribute( 'expression', """Optional. Set to the expression that was being evaluated. Set to None if not available or not applicable. """) warnings = Attribute( 'warnings', """Optional. Set to a sequence of warning messages. Set to None if not available, not applicable, or if the exception itself provides enough information. """) def getInfo(as_html=0): """Optional. Returns a string containing any other useful info. If as_html is set, the implementation must HTML-quote the result (normally using cgi.escape()). Returns None to provide no extra info. """ zope2.13-2.13.21/source/zExceptions/src/zExceptions/__init__.py0000644000175000017500000000461512214017520023165 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """General exceptions that wish they were standard exceptions These exceptions are so general purpose that they don't belong in Zope application-specific packages. """ from types import ClassType import warnings from zope.interface import implements from zope.interface.common.interfaces import IException from zope.publisher.interfaces import INotFound from zope.security.interfaces import IForbidden from zExceptions.unauthorized import Unauthorized class BadRequest(Exception): implements(IException) class InternalError(Exception): implements(IException) class NotFound(Exception): implements(INotFound) class Forbidden(Exception): implements(IForbidden) class MethodNotAllowed(Exception): pass class Redirect(Exception): pass def convertExceptionType(name): import zExceptions etype = None if name in __builtins__: etype = __builtins__[name] elif hasattr(zExceptions, name): etype = getattr(zExceptions, name) if (etype is not None and isinstance(etype, (type, ClassType)) and issubclass(etype, Exception)): return etype def upgradeException(t, v): # If a string exception is found, convert it to an equivalent # exception defined either in builtins or zExceptions. If none of # that works, tehn convert it to an InternalError and keep the # original exception name as part of the exception value. if isinstance(t, basestring): warnings.warn('String exceptions are deprecated starting ' 'with Python 2.5 and will be removed in a ' 'future release', DeprecationWarning, stacklevel=2) etype = convertExceptionType(t) if etype is not None: t = etype else: v = t, v t = InternalError return t, v zope2.13-2.13.21/source/zExceptions/src/zExceptions/TracebackSupplement.py0000644000175000017500000000126612214017520025361 0ustar arnauarnau# Stock __traceback_supplement__ implementations class PathTracebackSupplement: """Implementation of ITracebackSupplement""" pp = None def __init__(self, object): self.object = object if hasattr(object, 'getPhysicalPath'): self.pp = '/'.join(object.getPhysicalPath()) if hasattr(object, 'absolute_url'): self.source_url = '%s/manage_main' % object.absolute_url() def getInfo(self, as_html=0): if self.pp is None: return if as_html: from cgi import escape return 'Physical Path:%s' % (escape(self.pp)) else: return ' - Physical Path: %s' % self.pp zope2.13-2.13.21/source/zExceptions/src/zExceptions/tests/0000755000175000017500000000000012214017520022210 5ustar arnauarnauzope2.13-2.13.21/source/zExceptions/src/zExceptions/tests/test___init__.py0000644000175000017500000000352012214017520025360 0ustar arnauarnauimport unittest class Test_convertExceptionType(unittest.TestCase): def _callFUT(self, name): from zExceptions import convertExceptionType return convertExceptionType(name) def test_name_in___builtins__(self): self.failUnless(self._callFUT('SyntaxError') is SyntaxError) def test_name_in___builtins___not_an_exception_returns_None(self): self.failUnless(self._callFUT('unichr') is None) def test_name_in_zExceptions(self): from zExceptions import Redirect self.failUnless(self._callFUT('Redirect') is Redirect) def test_name_in_zExceptions_not_an_exception_returns_None(self): self.failUnless(self._callFUT('convertExceptionType') is None) class Test_upgradeException(unittest.TestCase): def _callFUT(self, t, v): from zExceptions import upgradeException return upgradeException(t, v) def test_non_string(self): t, v = self._callFUT(SyntaxError, 'TEST') self.assertEqual(t, SyntaxError) self.assertEqual(v, 'TEST') def test_string_in___builtins__(self): t, v = self._callFUT('SyntaxError', 'TEST') self.assertEqual(t, SyntaxError) self.assertEqual(v, 'TEST') def test_string_in_zExceptions(self): from zExceptions import Redirect t, v = self._callFUT('Redirect', 'http://example.com/') self.assertEqual(t, Redirect) self.assertEqual(v, 'http://example.com/') def test_string_miss_returns_InternalError(self): from zExceptions import InternalError t, v = self._callFUT('Nonesuch', 'TEST') self.assertEqual(t, InternalError) self.assertEqual(v, ('Nonesuch', 'TEST')) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(Test_convertExceptionType), unittest.makeSuite(Test_upgradeException), )) zope2.13-2.13.21/source/zExceptions/src/zExceptions/tests/test_unauthorized.py0000644000175000017500000000762612214017520026355 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unit tests for unauthorized module. """ import unittest from zope.interface.verify import verifyClass _MESSAGE = "You are not allowed to access '%s' in this context" class UnauthorizedTests(unittest.TestCase): def _getTargetClass(self): from zExceptions.unauthorized import Unauthorized return Unauthorized def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def test_interfaces(self): from zope.security.interfaces import IUnauthorized verifyClass(IUnauthorized, self._getTargetClass()) def test_empty(self): exc = self._makeOne() self.assertEqual(exc.name, None) self.assertEqual(exc.message, None) self.assertEqual(exc.value, None) self.assertEqual(exc.needed, None) self.assertEqual(str(exc), str(repr(exc))) self.assertEqual(unicode(exc), unicode(repr(exc))) def test_ascii_message(self): arg = 'ERROR MESSAGE' exc = self._makeOne(arg) self.assertEqual(exc.name, None) self.assertEqual(exc.message, arg) self.assertEqual(exc.value, None) self.assertEqual(exc.needed, None) self.assertEqual(str(exc), arg) self.assertEqual(unicode(exc), arg.decode('ascii')) def test_encoded_message(self): arg = u'ERROR MESSAGE \u03A9'.encode('utf-8') exc = self._makeOne(arg) self.assertEqual(exc.name, None) self.assertEqual(exc.message, arg) self.assertEqual(exc.value, None) self.assertEqual(exc.needed, None) self.assertEqual(str(exc), arg) self.assertRaises(UnicodeDecodeError, unicode, exc) def test_unicode_message(self): arg = u'ERROR MESSAGE \u03A9' exc = self._makeOne(arg) self.assertEqual(exc.name, None) self.assertEqual(exc.message, arg) self.assertEqual(exc.value, None) self.assertEqual(exc.needed, None) self.assertRaises(UnicodeEncodeError, str, exc) self.assertEqual(unicode(exc), arg) def test_ascii_name(self): arg = 'ERROR_NAME' exc = self._makeOne(arg) self.assertEqual(exc.name, arg) self.assertEqual(exc.message, None) self.assertEqual(exc.value, None) self.assertEqual(exc.needed, None) self.assertEqual(str(exc), _MESSAGE % arg) self.assertEqual(unicode(exc), _MESSAGE % arg.decode('ascii')) def test_encoded_name(self): arg = u'ERROR_NAME_\u03A9'.encode('utf-8') exc = self._makeOne(arg) self.assertEqual(exc.name, arg) self.assertEqual(exc.message, None) self.assertEqual(exc.value, None) self.assertEqual(exc.needed, None) self.assertEqual(str(exc), _MESSAGE % arg) self.assertRaises(UnicodeDecodeError, unicode, exc) def test_unicode_name(self): arg = u'ERROR_NAME_\u03A9' exc = self._makeOne(arg) self.assertEqual(exc.name, arg) self.assertEqual(exc.message, None) self.assertEqual(exc.value, None) self.assertEqual(exc.needed, None) self.assertRaises(UnicodeEncodeError, str, exc) self.assertEqual(unicode(exc), _MESSAGE % arg) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(UnauthorizedTests)) return suite zope2.13-2.13.21/source/zExceptions/src/zExceptions/tests/__init__.py0000644000175000017500000000004412214017520024317 0ustar arnauarnau"""Package for zExceptions tests""" zope2.13-2.13.21/source/zExceptions/src/zExceptions/tests/testExceptionFormatter.py0000644000175000017500000001006312214017520027304 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ ExceptionFormatter tests. """ import sys from unittest import TestCase, TestSuite, makeSuite from zExceptions.ExceptionFormatter import format_exception def tb(as_html=0): t, v, b = sys.exc_info() try: return ''.join(format_exception(t, v, b, as_html=as_html)) finally: del b class ExceptionForTesting(Exception): pass class TestingTracebackSupplement: source_url = '/somepath' line = 634 column = 57 warnings = ['Repent, for the end is nigh'] def __init__(self, expression): self.expression = expression class Test(TestCase): def testBasicNamesText(self, as_html=0): try: raise ExceptionForTesting except ExceptionForTesting: s = tb(as_html) # The traceback should include the name of this function. self.assert_(s.find('testBasicNamesText') >= 0) # The traceback should include the name of the exception. self.assert_(s.find('ExceptionForTesting') >= 0) else: self.fail('no exception occurred') def testBasicNamesHTML(self): self.testBasicNamesText(1) def testSupplement(self, as_html=0): try: __traceback_supplement__ = (TestingTracebackSupplement, "You're one in a million") raise ExceptionForTesting except ExceptionForTesting: s = tb(as_html) # The source URL self.assert_(s.find('/somepath') >= 0, s) # The line number self.assert_(s.find('634') >= 0, s) # The column number self.assert_(s.find('57') >= 0, s) # The expression self.assert_(s.find("You're one in a million") >= 0, s) # The warning self.assert_(s.find("Repent, for the end is nigh") >= 0, s) else: self.fail('no exception occurred') def testSupplementHTML(self): self.testSupplement(1) def testTracebackInfo(self, as_html=0): try: __traceback_info__ = "Adam & Eve" raise ExceptionForTesting except ExceptionForTesting: s = tb(as_html) if as_html: # Be sure quoting is happening. self.assert_(s.find('Adam & Eve') >= 0, s) else: self.assert_(s.find('Adam & Eve') >= 0, s) else: self.fail('no exception occurred') def testTracebackInfoHTML(self): self.testTracebackInfo(1) def testMultipleLevels(self): # Makes sure many levels are shown in a traceback. def f(n): """Produces a (n + 1)-level traceback.""" __traceback_info__ = 'level%d' % n if n > 0: f(n - 1) else: raise ExceptionForTesting try: f(10) except ExceptionForTesting: s = tb() for n in range(11): self.assert_(s.find('level%d' % n) >= 0, s) else: self.fail('no exception occurred') def testQuoteLastLine(self): class C: pass try: raise TypeError, C() except: s = tb(1) else: self.fail('no exception occurred') self.assert_(s.find('<') >= 0, s) self.assert_(s.find('>') >= 0, s) def test_suite(): return TestSuite(( makeSuite(Test), )) zope2.13-2.13.21/source/zope.viewlet/0000755000175000017500000000000012214017662016073 5ustar arnauarnauzope2.13-2.13.21/source/zope.viewlet/setup.py0000644000175000017500000000571112214017662017611 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.viewlet package $Id: setup.py 112690 2010-05-25 14:02:00Z tseaver $ """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.viewlet', version = '3.7.2', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description='Zope Viewlets', long_description=( read('README.txt') + '\n\n' + 'Detailed Documentation\n' + '**********************\n\n' + '\n\n' + read('src', 'zope', 'viewlet', 'README.txt') + '\n\n' + read('src', 'zope', 'viewlet', 'directives.txt') + '\n\n' + read('CHANGES.txt') ), keywords = "zope web html ui viewlet pattern", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.viewlet', license='ZPL 2.1', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope'], extras_require = dict( test=[ 'zope.testing', 'zope.size', ]), install_requires=[ 'setuptools', 'zope.browserpage>=3.10.1', 'zope.component', 'zope.configuration', 'zope.contentprovider', 'zope.event', 'zope.i18nmessageid', 'zope.interface', 'zope.location', 'zope.publisher', 'zope.schema', 'zope.security', 'zope.traversing', ], include_package_data = True, zip_safe = False, ) zope2.13-2.13.21/source/zope.viewlet/PKG-INFO0000644000175000017500000017667212214017662017213 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.viewlet Version: 3.7.2 Summary: Zope Viewlets Home-page: http://pypi.python.org/pypi/zope.viewlet Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Viewlets provide a generic framework for building pluggable user interfaces. Detailed Documentation ********************** ============================= Viewlets and Viewlet Managers ============================= Let's start with some motivation. Using content providers allows us to insert one piece of HTML content. In most Web development, however, you are often interested in defining some sort of region and then allow developers to register content for those regions. >>> from zope.viewlet import interfaces Design Notes ------------ As mentioned above, besides inserting snippets of HTML at places, we more frequently want to define a region in our page and allow specialized content providers to be inserted based on configuration. Those specialized content providers are known as viewlets and are only available inside viewlet managers, which are just a more complex example of content providers. Unfortunately, the Java world does not implement this layer separately. The viewlet manager is most similar to a Java "channel", but we decided against using this name, since it is very generic and not very meaningful. The viewlet has no Java counterpart, since Java does not implement content providers using a component architecture and thus does not register content providers specifically for viewlet managers, which I believe makes the Java implementation less useful as a generic concept. In fact, the main design goal in the Java world is the implementation of reusable and sharable portlets. The scope for Zope 3 is larger, since we want to provide a generic framework for building pluggable user interfaces. The Viewlet Manager ------------------- In this implementation of viewlets, those regions are just content providers called viewlet managers that manage a special type of content providers known as viewlets. Every viewlet manager handles the viewlets registered for it: >>> class ILeftColumn(interfaces.IViewletManager): ... """Viewlet manager located in the left column.""" You can then create a viewlet manager using this interface now: >>> from zope.viewlet import manager >>> LeftColumn = manager.ViewletManager('left', ILeftColumn) Now we have to instantiate it: >>> import zope.interface >>> class Content(object): ... zope.interface.implements(zope.interface.Interface) >>> content = Content() >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> from zope.publisher.interfaces.browser import IBrowserView >>> class View(object): ... zope.interface.implements(IBrowserView) ... def __init__(self, context, request): ... pass >>> view = View(content, request) >>> leftColumn = LeftColumn(content, request, view) So initially nothing gets rendered: >>> leftColumn.update() >>> leftColumn.render() u'' But now we register some viewlets for the manager >>> import zope.component >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer >>> class WeatherBox(object): ... zope.interface.implements(interfaces.IViewlet) ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
It is sunny today!
' >>> # Create a security checker for viewlets. >>> from zope.security.checker import NamesChecker, defineChecker >>> viewletChecker = NamesChecker(('update', 'render')) >>> defineChecker(WeatherBox, viewletChecker) >>> zope.component.provideAdapter( ... WeatherBox, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, ILeftColumn), ... interfaces.IViewlet, name='weather') >>> from zope.location.interfaces import ILocation >>> class SportBox(object): ... zope.interface.implements(interfaces.IViewlet, ... ILocation) ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
Patriots (23) : Steelers (7)
' >>> defineChecker(SportBox, viewletChecker) >>> zope.component.provideAdapter( ... SportBox, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, ILeftColumn), ... interfaces.IViewlet, name='sport') and thus the left column is filled. Note that also events get fired before viewlets are updated. We register a simple handler to demonstrate this behaviour. >>> from zope.contentprovider.interfaces import IBeforeUpdateEvent >>> events = [] >>> def handler(ev): ... events.append(ev) >>> zope.component.provideHandler(handler, (IBeforeUpdateEvent,)) >>> leftColumn.update() >>> [(ev, ev.object.__class__.__name__) for ev in events] [(, 'SportBox'), (, 'WeatherBox')] >>> print leftColumn.render()
Patriots (23) : Steelers (7)
It is sunny today!
But this is of course pretty lame, since there is no way of specifying how the viewlets are put together. But we have a solution. The second argument of the ``ViewletManager()`` function is a template in which we can specify how the viewlets are put together: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> leftColTemplate = os.path.join(temp_dir, 'leftCol.pt') >>> open(leftColTemplate, 'w').write(''' ...
... ...
... ''') >>> LeftColumn = manager.ViewletManager('left', ILeftColumn, ... template=leftColTemplate) >>> leftColumn = LeftColumn(content, request, view) TODO: Fix this silly thing; viewlets should be directly available. As you can see, the viewlet manager provides a global ``options/viewlets`` variable that is an iterable of all the available viewlets in the correct order: >>> leftColumn.update() >>> print leftColumn.render().strip()
Patriots (23) : Steelers (7)
It is sunny today!
If a viewlet provides ILocation the ``__name__`` attribute of the viewlet is set to the name under which the viewlet is registered. >>> [getattr(viewlet, '__name__', None) for viewlet in leftColumn.viewlets] [u'sport', None] You can also lookup the viewlets directly for management purposes: >>> leftColumn['weather'] >>> leftColumn.get('weather') The viewlet manager also provides the __contains__ method defined in IReadMapping: >>> 'weather' in leftColumn True >>> 'unknown' in leftColumn False If the viewlet is not found, then the expected behavior is provided: >>> leftColumn['stock'] Traceback (most recent call last): ... ComponentLookupError: No provider with name `stock` found. >>> leftColumn.get('stock') is None True Customizing the default Viewlet Manager --------------------------------------- One important feature of any viewlet manager is to be able to filter and sort the viewlets it is displaying. The default viewlet manager that we have been using in the tests above, supports filtering by access availability and sorting via the viewlet's ``__cmp__()`` method (default). You can easily override this default policy by providing a base viewlet manager class. In our case we will manage the viewlets using a global list: >>> shown = ['weather', 'sport'] The viewlet manager base class now uses this list: >>> class ListViewletManager(object): ... ... def filter(self, viewlets): ... viewlets = super(ListViewletManager, self).filter(viewlets) ... return [(name, viewlet) ... for name, viewlet in viewlets ... if name in shown] ... ... def sort(self, viewlets): ... viewlets = dict(viewlets) ... return [(name, viewlets[name]) for name in shown] Let's now create a new viewlet manager: >>> LeftColumn = manager.ViewletManager( ... 'left', ILeftColumn, bases=(ListViewletManager,), ... template=leftColTemplate) >>> leftColumn = LeftColumn(content, request, view) So we get the weather box first and the sport box second: >>> leftColumn.update() >>> print leftColumn.render().strip()
It is sunny today!
Patriots (23) : Steelers (7)
Now let's change the order... >>> shown.reverse() and the order should switch as well: >>> leftColumn.update() >>> print leftColumn.render().strip()
Patriots (23) : Steelers (7)
It is sunny today!
Of course, we also can remove a shown viewlet: >>> weather = shown.pop() >>> leftColumn.update() >>> print leftColumn.render().strip()
Patriots (23) : Steelers (7)
WeightOrderedViewletManager --------------------------- The weight ordered viewlet manager offers ordering viewlets by a additional weight argument. Viewlets which doesn't provide a weight attribute will get a weight of 0 (zero). Let's define a new column: >>> class IWeightedColumn(interfaces.IViewletManager): ... """Column with weighted viewlet manager.""" First register a template for the weight ordered viewlet manager: >>> weightedColTemplate = os.path.join(temp_dir, 'weightedColTemplate.pt') >>> open(weightedColTemplate, 'w').write(''' ...
... ...
... ''') And create a new weight ordered viewlet manager: >>> from zope.viewlet.manager import WeightOrderedViewletManager >>> WeightedColumn = manager.ViewletManager( ... 'left', IWeightedColumn, bases=(WeightOrderedViewletManager,), ... template=weightedColTemplate) >>> weightedColumn = WeightedColumn(content, request, view) Let's create some viewlets: >>> from zope.viewlet import viewlet >>> class FirstViewlet(viewlet.ViewletBase): ... ... weight = 1 ... ... def render(self): ... return u'
first
' >>> class SecondViewlet(viewlet.ViewletBase): ... ... weight = 2 ... ... def render(self): ... return u'
second
' >>> class ThirdViewlet(viewlet.ViewletBase): ... ... weight = 3 ... ... def render(self): ... return u'
third
' >>> class UnWeightedViewlet(viewlet.ViewletBase): ... ... def render(self): ... return u'
unweighted
' >>> defineChecker(FirstViewlet, viewletChecker) >>> defineChecker(SecondViewlet, viewletChecker) >>> defineChecker(ThirdViewlet, viewletChecker) >>> defineChecker(UnWeightedViewlet, viewletChecker) >>> zope.component.provideAdapter( ... ThirdViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='third') >>> zope.component.provideAdapter( ... FirstViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='first') >>> zope.component.provideAdapter( ... SecondViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='second') >>> zope.component.provideAdapter( ... UnWeightedViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='unweighted') And check the order: >>> weightedColumn.update() >>> print weightedColumn.render().strip()
unweighted
first
second
third
ConditionalViewletManager ------------------------- The conditional ordered viewlet manager offers ordering viewlets by a additional weight argument and filters by the available attribute if a supported by the viewlet. Viewlets which doesn't provide a available attribute will not get skipped. The default weight value for viewlets which doesn't provide a weight attribute is 0 (zero). Let's define a new column: >>> class IConditionalColumn(interfaces.IViewletManager): ... """Column with weighted viewlet manager.""" First register a template for the weight ordered viewlet manager: >>> conditionalColTemplate = os.path.join(temp_dir, ... 'conditionalColTemplate.pt') >>> open(conditionalColTemplate, 'w').write(''' ...
... ...
... ''') And create a new conditional viewlet manager: >>> from zope.viewlet.manager import ConditionalViewletManager >>> ConditionalColumn = manager.ViewletManager( ... 'left', IConditionalColumn, bases=(ConditionalViewletManager,), ... template=conditionalColTemplate) >>> conditionalColumn = ConditionalColumn(content, request, view) Let's create some viewlets. We also use the previous viewlets supporting no weight and or no available attribute: >>> from zope.viewlet import viewlet >>> class AvailableViewlet(viewlet.ViewletBase): ... ... weight = 4 ... ... available = True ... ... def render(self): ... return u'
available
' >>> class UnAvailableViewlet(viewlet.ViewletBase): ... ... weight = 5 ... ... available = False ... ... def render(self): ... return u'
not available
' >>> defineChecker(AvailableViewlet, viewletChecker) >>> defineChecker(UnAvailableViewlet, viewletChecker) >>> zope.component.provideAdapter( ... ThirdViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='third') >>> zope.component.provideAdapter( ... FirstViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='first') >>> zope.component.provideAdapter( ... SecondViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='second') >>> zope.component.provideAdapter( ... UnWeightedViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='unweighted') >>> zope.component.provideAdapter( ... AvailableViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='available') >>> zope.component.provideAdapter( ... UnAvailableViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='unavailable') And check the order: >>> conditionalColumn.update() >>> print conditionalColumn.render().strip()
unweighted
first
second
third
available
Viewlet Base Classes -------------------- To make the creation of viewlets simpler, a set of useful base classes and helper functions are provided. The first class is a base class that simply defines the constructor: >>> base = viewlet.ViewletBase('context', 'request', 'view', 'manager') >>> base.context 'context' >>> base.request 'request' >>> base.__parent__ 'view' >>> base.manager 'manager' But a default ``render()`` method implementation is not provided: >>> base.render() Traceback (most recent call last): ... NotImplementedError: `render` method must be implemented by subclass. If you have already an existing class that produces the HTML content in some method, then the ``SimpleAttributeViewlet`` might be for you, since it can be used to convert any class quickly into a viewlet: >>> class FooViewlet(viewlet.SimpleAttributeViewlet): ... __page_attribute__ = 'foo' ... ... def foo(self): ... return 'output' The `__page_attribute__` attribute provides the name of the function to call for rendering. >>> foo = FooViewlet('context', 'request', 'view', 'manager') >>> foo.foo() 'output' >>> foo.render() 'output' If you specify `render` as the attribute an error is raised to prevent infinite recursion: >>> foo.__page_attribute__ = 'render' >>> foo.render() Traceback (most recent call last): ... AttributeError: render The same is true if the specified attribute does not exist: >>> foo.__page_attribute__ = 'bar' >>> foo.render() Traceback (most recent call last): ... AttributeError: 'FooViewlet' object has no attribute 'bar' To create simple template-based viewlets you can use the ``SimpleViewletClass()`` function. This function is very similar to its view equivalent and is used by the ZCML directives to create viewlets. The result of this function call will be a fully functional viewlet class. Let's start by simply specifying a template only: >>> template = os.path.join(temp_dir, 'demoTemplate.pt') >>> open(template, 'w').write('''
contents
''') >>> Demo = viewlet.SimpleViewletClass(template) >>> print Demo(content, request, view, manager).render()
contents
Now let's additionally specify a class that can provide additional features: >>> class MyViewlet(object): ... myAttribute = 8 >>> Demo = viewlet.SimpleViewletClass(template, bases=(MyViewlet,)) >>> MyViewlet in Demo.__bases__ True >>> Demo(content, request, view, manager).myAttribute 8 The final important feature is the ability to pass in further attributes to the class: >>> Demo = viewlet.SimpleViewletClass( ... template, attributes={'here': 'now', 'lucky': 3}) >>> demo = Demo(content, request, view, manager) >>> demo.here 'now' >>> demo.lucky 3 As for all views, they must provide a name that can also be passed to the function: >>> Demo = viewlet.SimpleViewletClass(template, name='demoViewlet') >>> demo = Demo(content, request, view, manager) >>> demo.__name__ 'demoViewlet' In addition to the the generic viewlet code above, the package comes with two viewlet base classes and helper functions for inserting CSS and Javascript links into HTML headers, since those two are so very common. I am only going to demonstrate the helper functions here, since those demonstrations will fully demonstrate the functionality of the base classes as well. The viewlet will look up the resource it was given and tries to produce the absolute URL for it: >>> class JSResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/resource.js' >>> zope.component.provideAdapter( ... JSResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='resource.js') >>> JSViewlet = viewlet.JavaScriptViewlet('resource.js') >>> print JSViewlet(content, request, view, manager).render().strip() There is also a javascript viewlet base class which knows how to render more then one javascript resource file: >>> class JSSecondResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/second-resource.js' >>> zope.component.provideAdapter( ... JSSecondResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='second-resource.js') >>> JSBundleViewlet = viewlet.JavaScriptBundleViewlet(('resource.js', ... 'second-resource.js')) >>> print JSBundleViewlet(content, request, view, manager).render().strip() The same works for the CSS resource viewlet: >>> class CSSResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/resource.css' >>> zope.component.provideAdapter( ... CSSResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='resource.css') >>> CSSViewlet = viewlet.CSSViewlet('resource.css') >>> print CSSViewlet(content, request, view, manager).render().strip() You can also change the media type and the rel attribute: >>> CSSViewlet = viewlet.CSSViewlet('resource.css', media='print', rel='css') >>> print CSSViewlet(content, request, view, manager).render().strip() There is also a bundle viewlet for CSS links: >>> class CSSPrintResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/print-resource.css' >>> zope.component.provideAdapter( ... CSSPrintResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='print-resource.css') >>> items = [] >>> items.append({'path':'resource.css', 'rel':'stylesheet', 'media':'all'}) >>> items.append({'path':'print-resource.css', 'media':'print'}) >>> CSSBundleViewlet = viewlet.CSSBundleViewlet(items) >>> print CSSBundleViewlet(content, request, view, manager).render().strip() A Complex Example ----------------- The Data ~~~~~~~~ So far we have only demonstrated simple (maybe overly trivial) use cases of the viewlet system. In the following example, we are going to develop a generic contents view for files. The step is to create a file component: >>> class IFile(zope.interface.Interface): ... data = zope.interface.Attribute('Data of file.') >>> class File(object): ... zope.interface.implements(IFile) ... def __init__(self, data=''): ... self.__name__ = '' ... self.data = data Since we want to also provide the size of a file, here a simple implementation of the ``ISized`` interface: >>> from zope import size >>> class FileSized(object): ... zope.interface.implements(size.interfaces.ISized) ... zope.component.adapts(IFile) ... ... def __init__(self, file): ... self.file = file ... ... def sizeForSorting(self): ... return 'byte', len(self.file.data) ... ... def sizeForDisplay(self): ... return '%i bytes' %len(self.file.data) >>> zope.component.provideAdapter(FileSized) We also need a container to which we can add files: >>> class Container(dict): ... def __setitem__(self, name, value): ... value.__name__ = name ... super(Container, self).__setitem__(name, value) Here is some sample data: >>> container = Container() >>> container['test.txt'] = File('Hello World!') >>> container['mypage.html'] = File('Hello World!') >>> container['data.xml'] = File('Hello World!') The View ~~~~~~~~ The contents view of the container should iterate through the container and represent the files in a table: >>> contentsTemplate = os.path.join(temp_dir, 'contents.pt') >>> open(contentsTemplate, 'w').write(''' ... ... ...

Contents

...
... ... ... ''') >>> from zope.browserpage.simpleviewclass import SimpleViewClass >>> Contents = SimpleViewClass(contentsTemplate, name='contents.html') The Viewlet Manager ~~~~~~~~~~~~~~~~~~~ Now we have to write our own viewlet manager. In this case we cannot use the default implementation, since the viewlets will be looked up for each different item: >>> shownColumns = [] >>> class ContentsViewletManager(object): ... zope.interface.implements(interfaces.IViewletManager) ... index = None ... ... def __init__(self, context, request, view): ... self.context = context ... self.request = request ... self.__parent__ = view ... ... def update(self): ... rows = [] ... for name, value in self.context.items(): ... rows.append( ... [zope.component.getMultiAdapter( ... (value, self.request, self.__parent__, self), ... interfaces.IViewlet, name=colname) ... for colname in shownColumns]) ... [entry.update() for entry in rows[-1]] ... self.rows = rows ... ... def render(self, *args, **kw): ... return self.index(*args, **kw) Now we need a template to produce the contents table: >>> tableTemplate = os.path.join(temp_dir, 'table.pt') >>> open(tableTemplate, 'w').write(''' ... ... ... ... ...
... ...
... ''') From the two pieces above, we can generate the final viewlet manager class and register it (it's a bit tedious, I know): >>> from zope.browserpage import ViewPageTemplateFile >>> ContentsViewletManager = type( ... 'ContentsViewletManager', (ContentsViewletManager,), ... {'index': ViewPageTemplateFile(tableTemplate)}) >>> zope.component.provideAdapter( ... ContentsViewletManager, ... (Container, IDefaultBrowserLayer, zope.interface.Interface), ... interfaces.IViewletManager, name='contents') Since we have not defined any viewlets yet, the table is totally empty: >>> contents = Contents(container, request) >>> print contents().strip()

Contents

The Viewlets and the Final Result ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now let's create a first viewlet for the manager... >>> class NameViewlet(object): ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... self.context = context ... ... def update(self): ... pass ... ... def render(self): ... return self.context.__name__ and register it: >>> zope.component.provideAdapter( ... NameViewlet, ... (IFile, IDefaultBrowserLayer, ... zope.interface.Interface, interfaces.IViewletManager), ... interfaces.IViewlet, name='name') Note how you register the viewlet on ``IFile`` and not on the container. Now we should be able to see the name for each file in the container: >>> print contents().strip()

Contents

Waaa, nothing there! What happened? Well, we have to tell our user preferences that we want to see the name as a column in the table: >>> shownColumns = ['name'] >>> print contents().strip()

Contents

mypage.html
data.xml
test.txt
Let's now write a second viewlet that will display the size of the object for us: >>> class SizeViewlet(object): ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... self.context = context ... ... def update(self): ... pass ... ... def render(self): ... return size.interfaces.ISized(self.context).sizeForDisplay() >>> zope.component.provideAdapter( ... SizeViewlet, ... (IFile, IDefaultBrowserLayer, ... zope.interface.Interface, interfaces.IViewletManager), ... interfaces.IViewlet, name='size') After we added it to the list of shown columns, >>> shownColumns = ['name', 'size'] we can see an entry for it: >>> print contents().strip()

Contents

mypage.html 38 bytes
data.xml 31 bytes
test.txt 12 bytes
If we switch the two columns around, >>> shownColumns = ['size', 'name'] the result will be >>> print contents().strip()

Contents

38 bytes mypage.html
31 bytes data.xml
12 bytes test.txt
Supporting Sorting ~~~~~~~~~~~~~~~~~~ Oftentimes you also want to batch and sort the entries in a table. Since those two features are not part of the view logic, they should be treated with independent components. In this example, we are going to only implement sorting using a simple utility: >>> class ISorter(zope.interface.Interface): ... ... def sort(values): ... """Sort the values.""" >>> class SortByName(object): ... zope.interface.implements(ISorter) ... ... def sort(self, values): ... return sorted(values, lambda x, y: cmp(x.__name__, y.__name__)) >>> zope.component.provideUtility(SortByName(), name='name') >>> class SortBySize(object): ... zope.interface.implements(ISorter) ... ... def sort(self, values): ... return sorted( ... values, ... lambda x, y: cmp(size.interfaces.ISized(x).sizeForSorting(), ... size.interfaces.ISized(y).sizeForSorting())) >>> zope.component.provideUtility(SortBySize(), name='size') Note that we decided to give the sorter utilities the same name as the corresponding viewlet. This convention will make our implementation of the viewlet manager much simpler: >>> sortByColumn = '' >>> class SortedContentsViewletManager(object): ... zope.interface.implements(interfaces.IViewletManager) ... index = None ... ... def __init__(self, context, request, view): ... self.context = context ... self.request = request ... self.__parent__ = view ... ... def update(self): ... values = self.context.values() ... ... if sortByColumn: ... sorter = zope.component.queryUtility(ISorter, sortByColumn) ... if sorter: ... values = sorter.sort(values) ... ... rows = [] ... for value in values: ... rows.append( ... [zope.component.getMultiAdapter( ... (value, self.request, self.__parent__, self), ... interfaces.IViewlet, name=colname) ... for colname in shownColumns]) ... [entry.update() for entry in rows[-1]] ... self.rows = rows ... ... def render(self, *args, **kw): ... return self.index(*args, **kw) As you can see, the concern of sorting is cleanly separated from generating the view code. In MVC terms that means that the controller (sort) is logically separated from the view (viewlets). Let's now do the registration dance for the new viewlet manager. We simply override the existing registration: >>> SortedContentsViewletManager = type( ... 'SortedContentsViewletManager', (SortedContentsViewletManager,), ... {'index': ViewPageTemplateFile(tableTemplate)}) >>> zope.component.provideAdapter( ... SortedContentsViewletManager, ... (Container, IDefaultBrowserLayer, zope.interface.Interface), ... interfaces.IViewletManager, name='contents') Finally we sort the contents by name: >>> shownColumns = ['name', 'size'] >>> sortByColumn = 'name' >>> print contents().strip()

Contents

data.xml 31 bytes
mypage.html 38 bytes
test.txt 12 bytes
Now let's sort by size: >>> sortByColumn = 'size' >>> print contents().strip()

Contents

test.txt 12 bytes
data.xml 31 bytes
mypage.html 38 bytes
That's it! As you can see, in a few steps we have built a pretty flexible contents view with selectable columns and sorting. However, there is a lot of room for extending this example: - Table Header: The table header cell for each column should be a different type of viewlet, but registered under the same name. The column header viewlet also adapts the container not the item. The header column should also be able to control the sorting. - Batching: A simple implementation of batching should work very similar to the sorting feature. Of course, efficient implementations should somehow combine batching and sorting more effectively. - Sorting in ascending and descending order: Currently, you can only sort from the smallest to the highest value; however, this limitation is almost superficial and can easily be removed by making the sorters a bit more flexible. - Further Columns: For a real application, you would want to implement other columns, of course. You would also probably want some sort of fallback for the case that a viewlet is not found for a particular container item and column. Cleanup ------- >>> import shutil >>> shutil.rmtree(temp_dir) ================================ The ``viewletManager`` Directive ================================ The ``viewletManager`` directive allows you to quickly register a new viewlet manager without worrying about the details of the ``adapter`` directive. Before we can use the directives, we have to register their handlers by executing the package's meta configuration: >>> from zope.configuration import xmlconfig >>> context = xmlconfig.string(''' ... ... ... ... ''') Now we can register a viewlet manager: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Let's make sure the directive has really issued a sensible adapter registration; to do that, we create some dummy content, request and view objects: >>> import zope.interface >>> class Content(object): ... zope.interface.implements(zope.interface.Interface) >>> content = Content() >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> from zope.publisher.browser import BrowserView >>> view = BrowserView(content, request) Now let's lookup the manager. This particular registration is pretty boring: >>> import zope.component >>> from zope.viewlet import interfaces >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ... interfaces.IViewletManager, name='defaultmanager') >>> manager object ...> >>> interfaces.IViewletManager.providedBy(manager) True >>> manager.template is None True >>> manager.update() >>> manager.render() u'' However, this registration is not very useful, since we did specify a specific viewlet manager interface, a specific content interface, specific view or specific layer. This means that all viewlets registered will be found. The first step to effectively using the viewlet manager directive is to define a special viewlet manager interface: >>> class ILeftColumn(interfaces.IViewletManager): ... """Left column of my page.""" Now we can register register a manager providing this interface: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> ILeftColumn.providedBy(manager) True >>> manager.template is None True >>> manager.update() >>> manager.render() u'' Next let's see what happens, if we specify a template for the viewlet manager: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> leftColumnTemplate = os.path.join(temp_dir, 'leftcolumn.pt') >>> open(leftColumnTemplate, 'w').write(''' ...
...
...
... ''') >>> context = xmlconfig.string(''' ... ... ... ... ''' %leftColumnTemplate, context=context) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> ILeftColumn.providedBy(manager) True >>> manager.template ...>> >>> manager.update() >>> print manager.render().strip()
Additionally you can specify a class that will serve as a base to the default viewlet manager or be a viewlet manager in its own right. In our case we will provide a custom implementation of the ``sort()`` method, which will sort by a weight attribute in the viewlet: >>> class WeightBasedSorting(object): ... def sort(self, viewlets): ... return sorted(viewlets, ... lambda x, y: cmp(x[1].weight, y[1].weight)) >>> context = xmlconfig.string(''' ... ... ... ... ''' %leftColumnTemplate, context=context) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> manager.__class__.__bases__ (, ) >>> ILeftColumn.providedBy(manager) True >>> manager.template ...>> >>> manager.update() >>> print manager.render().strip()
Finally, if a non-existent template is specified, an error is raised: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-7.8 ConfigurationError: ('No such file', '...foo.pt') ========================= The ``viewlet`` Directive ========================= Now that we have a viewlet manager, we have to register some viewlets for it. The ``viewlet`` directive is similar to the ``viewletManager`` directive, except that the viewlet is also registered for a particular manager interface, as seen below: >>> weatherTemplate = os.path.join(temp_dir, 'weather.pt') >>> open(weatherTemplate, 'w').write(''' ...
sunny
... ''') >>> context = xmlconfig.string(''' ... ... ... ... ''' % weatherTemplate, context=context) If we look into the adapter registry, we will find the viewlet: >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='weather') >>> viewlet.render().strip() u'
sunny
' >>> viewlet.extra_string_attributes u'can be specified' The manager now also gives us the output of the one and only viewlet: >>> manager.update() >>> print manager.render().strip()
sunny
Let's now ensure that we can also specify a viewlet class: >>> class Weather(object): ... weight = 0 >>> context = xmlconfig.string(''' ... ... ... ... ''' % weatherTemplate, context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='weather2') >>> viewlet().strip() u'
sunny
' Okay, so the template-driven cases work. But just specifying a class should also work: >>> class Sport(object): ... weight = 0 ... def __call__(self): ... return u'Red Sox vs. White Sox' >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, name='sport') >>> viewlet() u'Red Sox vs. White Sox' It should also be possible to specify an alternative attribute of the class to be rendered upon calling the viewlet: >>> class Stock(object): ... weight = 0 ... def getStockTicker(self): ... return u'SRC $5.19' >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='stock') >>> viewlet.render() u'SRC $5.19' A final feature the ``viewlet`` directive is that it supports the specification of any number of keyword arguments: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='stock2') >>> viewlet.weight u'8' Error Scenarios --------------- Neither the class or template have been specified: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-7.8 ConfigurationError: Must specify a class or template The specified attribute is not ``__call__``, but also a template has been specified: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-9.8 ConfigurationError: Attribute and template cannot be used together. Now, we are not specifying a template, but a class that does not have the specified attribute: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-9.8 ConfigurationError: The provided class doesn't have the specified attribute Cleanup ------- >>> import shutil >>> shutil.rmtree(temp_dir) ======= CHANGES ======= 3.7.2 (2010-05-25) ------------------ - Fixed unit tests broken under Python 2.4 by the switch to the standard library ``doctest`` module. 3.7.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctest' in favor of stdlib's 'doctest. - Fixed dubious quoting in metadirectives.py. Closes https://bugs.launchpad.net/zope2/+bug/143774. 3.7.0 (2009-12-22) ------------------ - Depend on zope.browserpage in favor of zope.app.pagetemplate. 3.6.1 (2009-08-29) ------------------ - Fixed unit tests in README.txt. 3.6.0 (2009-08-02) ------------------ - Optimize the the script tag for the JS viewlet. This makes YSlow happy. - Remove ZCML slugs and old zpkg-related files. - Drop all testing dependncies except ``zope.testing``. 3.5.0 (2009-01-26) ------------------ - Removed the dependency on `zope.app.publisher` by moving four simple helper functions into this package and making the interface for describing the ZCML content provider directive explicit. - Typo fix in CSSViewlet docstring. 3.4.2 (2008-01-24) ------------------ - Re-release of 3.4.1 because of brown bag release. 3.4.1 (2008-01-21) ------------------ - bugfix, implemented missing __contains__ method in IViewletManager - implemented additional viewlet managers offering weight ordered sorting - implemented additional viewlet managers offering conditional filtering 3.4.1a (2007-4-22) ------------------ - bugfix, added a missing ',' behind zope.i18nmessageid. - recreated the README.txt removing everything except for the overview. 3.4.0 (2007-10-10) ------------------ - Initial release independent of the main Zope tree. Keywords: zope web html ui viewlet pattern Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.viewlet/pip-egg-info/0000755000175000017500000000000012214017662020354 5ustar arnauarnauzope2.13-2.13.21/source/zope.viewlet/pip-egg-info/zope.viewlet.egg-info/0000755000175000017500000000000012214017662024501 5ustar arnauarnauzope2.13-2.13.21/source/zope.viewlet/pip-egg-info/zope.viewlet.egg-info/PKG-INFO0000644000175000017500000017667212214017662025621 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.viewlet Version: 3.7.2 Summary: Zope Viewlets Home-page: http://pypi.python.org/pypi/zope.viewlet Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Viewlets provide a generic framework for building pluggable user interfaces. Detailed Documentation ********************** ============================= Viewlets and Viewlet Managers ============================= Let's start with some motivation. Using content providers allows us to insert one piece of HTML content. In most Web development, however, you are often interested in defining some sort of region and then allow developers to register content for those regions. >>> from zope.viewlet import interfaces Design Notes ------------ As mentioned above, besides inserting snippets of HTML at places, we more frequently want to define a region in our page and allow specialized content providers to be inserted based on configuration. Those specialized content providers are known as viewlets and are only available inside viewlet managers, which are just a more complex example of content providers. Unfortunately, the Java world does not implement this layer separately. The viewlet manager is most similar to a Java "channel", but we decided against using this name, since it is very generic and not very meaningful. The viewlet has no Java counterpart, since Java does not implement content providers using a component architecture and thus does not register content providers specifically for viewlet managers, which I believe makes the Java implementation less useful as a generic concept. In fact, the main design goal in the Java world is the implementation of reusable and sharable portlets. The scope for Zope 3 is larger, since we want to provide a generic framework for building pluggable user interfaces. The Viewlet Manager ------------------- In this implementation of viewlets, those regions are just content providers called viewlet managers that manage a special type of content providers known as viewlets. Every viewlet manager handles the viewlets registered for it: >>> class ILeftColumn(interfaces.IViewletManager): ... """Viewlet manager located in the left column.""" You can then create a viewlet manager using this interface now: >>> from zope.viewlet import manager >>> LeftColumn = manager.ViewletManager('left', ILeftColumn) Now we have to instantiate it: >>> import zope.interface >>> class Content(object): ... zope.interface.implements(zope.interface.Interface) >>> content = Content() >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> from zope.publisher.interfaces.browser import IBrowserView >>> class View(object): ... zope.interface.implements(IBrowserView) ... def __init__(self, context, request): ... pass >>> view = View(content, request) >>> leftColumn = LeftColumn(content, request, view) So initially nothing gets rendered: >>> leftColumn.update() >>> leftColumn.render() u'' But now we register some viewlets for the manager >>> import zope.component >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer >>> class WeatherBox(object): ... zope.interface.implements(interfaces.IViewlet) ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
It is sunny today!
' >>> # Create a security checker for viewlets. >>> from zope.security.checker import NamesChecker, defineChecker >>> viewletChecker = NamesChecker(('update', 'render')) >>> defineChecker(WeatherBox, viewletChecker) >>> zope.component.provideAdapter( ... WeatherBox, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, ILeftColumn), ... interfaces.IViewlet, name='weather') >>> from zope.location.interfaces import ILocation >>> class SportBox(object): ... zope.interface.implements(interfaces.IViewlet, ... ILocation) ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
Patriots (23) : Steelers (7)
' >>> defineChecker(SportBox, viewletChecker) >>> zope.component.provideAdapter( ... SportBox, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, ILeftColumn), ... interfaces.IViewlet, name='sport') and thus the left column is filled. Note that also events get fired before viewlets are updated. We register a simple handler to demonstrate this behaviour. >>> from zope.contentprovider.interfaces import IBeforeUpdateEvent >>> events = [] >>> def handler(ev): ... events.append(ev) >>> zope.component.provideHandler(handler, (IBeforeUpdateEvent,)) >>> leftColumn.update() >>> [(ev, ev.object.__class__.__name__) for ev in events] [(, 'SportBox'), (, 'WeatherBox')] >>> print leftColumn.render()
Patriots (23) : Steelers (7)
It is sunny today!
But this is of course pretty lame, since there is no way of specifying how the viewlets are put together. But we have a solution. The second argument of the ``ViewletManager()`` function is a template in which we can specify how the viewlets are put together: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> leftColTemplate = os.path.join(temp_dir, 'leftCol.pt') >>> open(leftColTemplate, 'w').write(''' ...
... ...
... ''') >>> LeftColumn = manager.ViewletManager('left', ILeftColumn, ... template=leftColTemplate) >>> leftColumn = LeftColumn(content, request, view) TODO: Fix this silly thing; viewlets should be directly available. As you can see, the viewlet manager provides a global ``options/viewlets`` variable that is an iterable of all the available viewlets in the correct order: >>> leftColumn.update() >>> print leftColumn.render().strip()
Patriots (23) : Steelers (7)
It is sunny today!
If a viewlet provides ILocation the ``__name__`` attribute of the viewlet is set to the name under which the viewlet is registered. >>> [getattr(viewlet, '__name__', None) for viewlet in leftColumn.viewlets] [u'sport', None] You can also lookup the viewlets directly for management purposes: >>> leftColumn['weather'] >>> leftColumn.get('weather') The viewlet manager also provides the __contains__ method defined in IReadMapping: >>> 'weather' in leftColumn True >>> 'unknown' in leftColumn False If the viewlet is not found, then the expected behavior is provided: >>> leftColumn['stock'] Traceback (most recent call last): ... ComponentLookupError: No provider with name `stock` found. >>> leftColumn.get('stock') is None True Customizing the default Viewlet Manager --------------------------------------- One important feature of any viewlet manager is to be able to filter and sort the viewlets it is displaying. The default viewlet manager that we have been using in the tests above, supports filtering by access availability and sorting via the viewlet's ``__cmp__()`` method (default). You can easily override this default policy by providing a base viewlet manager class. In our case we will manage the viewlets using a global list: >>> shown = ['weather', 'sport'] The viewlet manager base class now uses this list: >>> class ListViewletManager(object): ... ... def filter(self, viewlets): ... viewlets = super(ListViewletManager, self).filter(viewlets) ... return [(name, viewlet) ... for name, viewlet in viewlets ... if name in shown] ... ... def sort(self, viewlets): ... viewlets = dict(viewlets) ... return [(name, viewlets[name]) for name in shown] Let's now create a new viewlet manager: >>> LeftColumn = manager.ViewletManager( ... 'left', ILeftColumn, bases=(ListViewletManager,), ... template=leftColTemplate) >>> leftColumn = LeftColumn(content, request, view) So we get the weather box first and the sport box second: >>> leftColumn.update() >>> print leftColumn.render().strip()
It is sunny today!
Patriots (23) : Steelers (7)
Now let's change the order... >>> shown.reverse() and the order should switch as well: >>> leftColumn.update() >>> print leftColumn.render().strip()
Patriots (23) : Steelers (7)
It is sunny today!
Of course, we also can remove a shown viewlet: >>> weather = shown.pop() >>> leftColumn.update() >>> print leftColumn.render().strip()
Patriots (23) : Steelers (7)
WeightOrderedViewletManager --------------------------- The weight ordered viewlet manager offers ordering viewlets by a additional weight argument. Viewlets which doesn't provide a weight attribute will get a weight of 0 (zero). Let's define a new column: >>> class IWeightedColumn(interfaces.IViewletManager): ... """Column with weighted viewlet manager.""" First register a template for the weight ordered viewlet manager: >>> weightedColTemplate = os.path.join(temp_dir, 'weightedColTemplate.pt') >>> open(weightedColTemplate, 'w').write(''' ...
... ...
... ''') And create a new weight ordered viewlet manager: >>> from zope.viewlet.manager import WeightOrderedViewletManager >>> WeightedColumn = manager.ViewletManager( ... 'left', IWeightedColumn, bases=(WeightOrderedViewletManager,), ... template=weightedColTemplate) >>> weightedColumn = WeightedColumn(content, request, view) Let's create some viewlets: >>> from zope.viewlet import viewlet >>> class FirstViewlet(viewlet.ViewletBase): ... ... weight = 1 ... ... def render(self): ... return u'
first
' >>> class SecondViewlet(viewlet.ViewletBase): ... ... weight = 2 ... ... def render(self): ... return u'
second
' >>> class ThirdViewlet(viewlet.ViewletBase): ... ... weight = 3 ... ... def render(self): ... return u'
third
' >>> class UnWeightedViewlet(viewlet.ViewletBase): ... ... def render(self): ... return u'
unweighted
' >>> defineChecker(FirstViewlet, viewletChecker) >>> defineChecker(SecondViewlet, viewletChecker) >>> defineChecker(ThirdViewlet, viewletChecker) >>> defineChecker(UnWeightedViewlet, viewletChecker) >>> zope.component.provideAdapter( ... ThirdViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='third') >>> zope.component.provideAdapter( ... FirstViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='first') >>> zope.component.provideAdapter( ... SecondViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='second') >>> zope.component.provideAdapter( ... UnWeightedViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='unweighted') And check the order: >>> weightedColumn.update() >>> print weightedColumn.render().strip()
unweighted
first
second
third
ConditionalViewletManager ------------------------- The conditional ordered viewlet manager offers ordering viewlets by a additional weight argument and filters by the available attribute if a supported by the viewlet. Viewlets which doesn't provide a available attribute will not get skipped. The default weight value for viewlets which doesn't provide a weight attribute is 0 (zero). Let's define a new column: >>> class IConditionalColumn(interfaces.IViewletManager): ... """Column with weighted viewlet manager.""" First register a template for the weight ordered viewlet manager: >>> conditionalColTemplate = os.path.join(temp_dir, ... 'conditionalColTemplate.pt') >>> open(conditionalColTemplate, 'w').write(''' ...
... ...
... ''') And create a new conditional viewlet manager: >>> from zope.viewlet.manager import ConditionalViewletManager >>> ConditionalColumn = manager.ViewletManager( ... 'left', IConditionalColumn, bases=(ConditionalViewletManager,), ... template=conditionalColTemplate) >>> conditionalColumn = ConditionalColumn(content, request, view) Let's create some viewlets. We also use the previous viewlets supporting no weight and or no available attribute: >>> from zope.viewlet import viewlet >>> class AvailableViewlet(viewlet.ViewletBase): ... ... weight = 4 ... ... available = True ... ... def render(self): ... return u'
available
' >>> class UnAvailableViewlet(viewlet.ViewletBase): ... ... weight = 5 ... ... available = False ... ... def render(self): ... return u'
not available
' >>> defineChecker(AvailableViewlet, viewletChecker) >>> defineChecker(UnAvailableViewlet, viewletChecker) >>> zope.component.provideAdapter( ... ThirdViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='third') >>> zope.component.provideAdapter( ... FirstViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='first') >>> zope.component.provideAdapter( ... SecondViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='second') >>> zope.component.provideAdapter( ... UnWeightedViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='unweighted') >>> zope.component.provideAdapter( ... AvailableViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='available') >>> zope.component.provideAdapter( ... UnAvailableViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='unavailable') And check the order: >>> conditionalColumn.update() >>> print conditionalColumn.render().strip()
unweighted
first
second
third
available
Viewlet Base Classes -------------------- To make the creation of viewlets simpler, a set of useful base classes and helper functions are provided. The first class is a base class that simply defines the constructor: >>> base = viewlet.ViewletBase('context', 'request', 'view', 'manager') >>> base.context 'context' >>> base.request 'request' >>> base.__parent__ 'view' >>> base.manager 'manager' But a default ``render()`` method implementation is not provided: >>> base.render() Traceback (most recent call last): ... NotImplementedError: `render` method must be implemented by subclass. If you have already an existing class that produces the HTML content in some method, then the ``SimpleAttributeViewlet`` might be for you, since it can be used to convert any class quickly into a viewlet: >>> class FooViewlet(viewlet.SimpleAttributeViewlet): ... __page_attribute__ = 'foo' ... ... def foo(self): ... return 'output' The `__page_attribute__` attribute provides the name of the function to call for rendering. >>> foo = FooViewlet('context', 'request', 'view', 'manager') >>> foo.foo() 'output' >>> foo.render() 'output' If you specify `render` as the attribute an error is raised to prevent infinite recursion: >>> foo.__page_attribute__ = 'render' >>> foo.render() Traceback (most recent call last): ... AttributeError: render The same is true if the specified attribute does not exist: >>> foo.__page_attribute__ = 'bar' >>> foo.render() Traceback (most recent call last): ... AttributeError: 'FooViewlet' object has no attribute 'bar' To create simple template-based viewlets you can use the ``SimpleViewletClass()`` function. This function is very similar to its view equivalent and is used by the ZCML directives to create viewlets. The result of this function call will be a fully functional viewlet class. Let's start by simply specifying a template only: >>> template = os.path.join(temp_dir, 'demoTemplate.pt') >>> open(template, 'w').write('''
contents
''') >>> Demo = viewlet.SimpleViewletClass(template) >>> print Demo(content, request, view, manager).render()
contents
Now let's additionally specify a class that can provide additional features: >>> class MyViewlet(object): ... myAttribute = 8 >>> Demo = viewlet.SimpleViewletClass(template, bases=(MyViewlet,)) >>> MyViewlet in Demo.__bases__ True >>> Demo(content, request, view, manager).myAttribute 8 The final important feature is the ability to pass in further attributes to the class: >>> Demo = viewlet.SimpleViewletClass( ... template, attributes={'here': 'now', 'lucky': 3}) >>> demo = Demo(content, request, view, manager) >>> demo.here 'now' >>> demo.lucky 3 As for all views, they must provide a name that can also be passed to the function: >>> Demo = viewlet.SimpleViewletClass(template, name='demoViewlet') >>> demo = Demo(content, request, view, manager) >>> demo.__name__ 'demoViewlet' In addition to the the generic viewlet code above, the package comes with two viewlet base classes and helper functions for inserting CSS and Javascript links into HTML headers, since those two are so very common. I am only going to demonstrate the helper functions here, since those demonstrations will fully demonstrate the functionality of the base classes as well. The viewlet will look up the resource it was given and tries to produce the absolute URL for it: >>> class JSResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/resource.js' >>> zope.component.provideAdapter( ... JSResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='resource.js') >>> JSViewlet = viewlet.JavaScriptViewlet('resource.js') >>> print JSViewlet(content, request, view, manager).render().strip() There is also a javascript viewlet base class which knows how to render more then one javascript resource file: >>> class JSSecondResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/second-resource.js' >>> zope.component.provideAdapter( ... JSSecondResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='second-resource.js') >>> JSBundleViewlet = viewlet.JavaScriptBundleViewlet(('resource.js', ... 'second-resource.js')) >>> print JSBundleViewlet(content, request, view, manager).render().strip() The same works for the CSS resource viewlet: >>> class CSSResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/resource.css' >>> zope.component.provideAdapter( ... CSSResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='resource.css') >>> CSSViewlet = viewlet.CSSViewlet('resource.css') >>> print CSSViewlet(content, request, view, manager).render().strip() You can also change the media type and the rel attribute: >>> CSSViewlet = viewlet.CSSViewlet('resource.css', media='print', rel='css') >>> print CSSViewlet(content, request, view, manager).render().strip() There is also a bundle viewlet for CSS links: >>> class CSSPrintResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/print-resource.css' >>> zope.component.provideAdapter( ... CSSPrintResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='print-resource.css') >>> items = [] >>> items.append({'path':'resource.css', 'rel':'stylesheet', 'media':'all'}) >>> items.append({'path':'print-resource.css', 'media':'print'}) >>> CSSBundleViewlet = viewlet.CSSBundleViewlet(items) >>> print CSSBundleViewlet(content, request, view, manager).render().strip() A Complex Example ----------------- The Data ~~~~~~~~ So far we have only demonstrated simple (maybe overly trivial) use cases of the viewlet system. In the following example, we are going to develop a generic contents view for files. The step is to create a file component: >>> class IFile(zope.interface.Interface): ... data = zope.interface.Attribute('Data of file.') >>> class File(object): ... zope.interface.implements(IFile) ... def __init__(self, data=''): ... self.__name__ = '' ... self.data = data Since we want to also provide the size of a file, here a simple implementation of the ``ISized`` interface: >>> from zope import size >>> class FileSized(object): ... zope.interface.implements(size.interfaces.ISized) ... zope.component.adapts(IFile) ... ... def __init__(self, file): ... self.file = file ... ... def sizeForSorting(self): ... return 'byte', len(self.file.data) ... ... def sizeForDisplay(self): ... return '%i bytes' %len(self.file.data) >>> zope.component.provideAdapter(FileSized) We also need a container to which we can add files: >>> class Container(dict): ... def __setitem__(self, name, value): ... value.__name__ = name ... super(Container, self).__setitem__(name, value) Here is some sample data: >>> container = Container() >>> container['test.txt'] = File('Hello World!') >>> container['mypage.html'] = File('Hello World!') >>> container['data.xml'] = File('Hello World!') The View ~~~~~~~~ The contents view of the container should iterate through the container and represent the files in a table: >>> contentsTemplate = os.path.join(temp_dir, 'contents.pt') >>> open(contentsTemplate, 'w').write(''' ... ... ...

Contents

...
... ... ... ''') >>> from zope.browserpage.simpleviewclass import SimpleViewClass >>> Contents = SimpleViewClass(contentsTemplate, name='contents.html') The Viewlet Manager ~~~~~~~~~~~~~~~~~~~ Now we have to write our own viewlet manager. In this case we cannot use the default implementation, since the viewlets will be looked up for each different item: >>> shownColumns = [] >>> class ContentsViewletManager(object): ... zope.interface.implements(interfaces.IViewletManager) ... index = None ... ... def __init__(self, context, request, view): ... self.context = context ... self.request = request ... self.__parent__ = view ... ... def update(self): ... rows = [] ... for name, value in self.context.items(): ... rows.append( ... [zope.component.getMultiAdapter( ... (value, self.request, self.__parent__, self), ... interfaces.IViewlet, name=colname) ... for colname in shownColumns]) ... [entry.update() for entry in rows[-1]] ... self.rows = rows ... ... def render(self, *args, **kw): ... return self.index(*args, **kw) Now we need a template to produce the contents table: >>> tableTemplate = os.path.join(temp_dir, 'table.pt') >>> open(tableTemplate, 'w').write(''' ... ... ... ... ...
... ...
... ''') From the two pieces above, we can generate the final viewlet manager class and register it (it's a bit tedious, I know): >>> from zope.browserpage import ViewPageTemplateFile >>> ContentsViewletManager = type( ... 'ContentsViewletManager', (ContentsViewletManager,), ... {'index': ViewPageTemplateFile(tableTemplate)}) >>> zope.component.provideAdapter( ... ContentsViewletManager, ... (Container, IDefaultBrowserLayer, zope.interface.Interface), ... interfaces.IViewletManager, name='contents') Since we have not defined any viewlets yet, the table is totally empty: >>> contents = Contents(container, request) >>> print contents().strip()

Contents

The Viewlets and the Final Result ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now let's create a first viewlet for the manager... >>> class NameViewlet(object): ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... self.context = context ... ... def update(self): ... pass ... ... def render(self): ... return self.context.__name__ and register it: >>> zope.component.provideAdapter( ... NameViewlet, ... (IFile, IDefaultBrowserLayer, ... zope.interface.Interface, interfaces.IViewletManager), ... interfaces.IViewlet, name='name') Note how you register the viewlet on ``IFile`` and not on the container. Now we should be able to see the name for each file in the container: >>> print contents().strip()

Contents

Waaa, nothing there! What happened? Well, we have to tell our user preferences that we want to see the name as a column in the table: >>> shownColumns = ['name'] >>> print contents().strip()

Contents

mypage.html
data.xml
test.txt
Let's now write a second viewlet that will display the size of the object for us: >>> class SizeViewlet(object): ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... self.context = context ... ... def update(self): ... pass ... ... def render(self): ... return size.interfaces.ISized(self.context).sizeForDisplay() >>> zope.component.provideAdapter( ... SizeViewlet, ... (IFile, IDefaultBrowserLayer, ... zope.interface.Interface, interfaces.IViewletManager), ... interfaces.IViewlet, name='size') After we added it to the list of shown columns, >>> shownColumns = ['name', 'size'] we can see an entry for it: >>> print contents().strip()

Contents

mypage.html 38 bytes
data.xml 31 bytes
test.txt 12 bytes
If we switch the two columns around, >>> shownColumns = ['size', 'name'] the result will be >>> print contents().strip()

Contents

38 bytes mypage.html
31 bytes data.xml
12 bytes test.txt
Supporting Sorting ~~~~~~~~~~~~~~~~~~ Oftentimes you also want to batch and sort the entries in a table. Since those two features are not part of the view logic, they should be treated with independent components. In this example, we are going to only implement sorting using a simple utility: >>> class ISorter(zope.interface.Interface): ... ... def sort(values): ... """Sort the values.""" >>> class SortByName(object): ... zope.interface.implements(ISorter) ... ... def sort(self, values): ... return sorted(values, lambda x, y: cmp(x.__name__, y.__name__)) >>> zope.component.provideUtility(SortByName(), name='name') >>> class SortBySize(object): ... zope.interface.implements(ISorter) ... ... def sort(self, values): ... return sorted( ... values, ... lambda x, y: cmp(size.interfaces.ISized(x).sizeForSorting(), ... size.interfaces.ISized(y).sizeForSorting())) >>> zope.component.provideUtility(SortBySize(), name='size') Note that we decided to give the sorter utilities the same name as the corresponding viewlet. This convention will make our implementation of the viewlet manager much simpler: >>> sortByColumn = '' >>> class SortedContentsViewletManager(object): ... zope.interface.implements(interfaces.IViewletManager) ... index = None ... ... def __init__(self, context, request, view): ... self.context = context ... self.request = request ... self.__parent__ = view ... ... def update(self): ... values = self.context.values() ... ... if sortByColumn: ... sorter = zope.component.queryUtility(ISorter, sortByColumn) ... if sorter: ... values = sorter.sort(values) ... ... rows = [] ... for value in values: ... rows.append( ... [zope.component.getMultiAdapter( ... (value, self.request, self.__parent__, self), ... interfaces.IViewlet, name=colname) ... for colname in shownColumns]) ... [entry.update() for entry in rows[-1]] ... self.rows = rows ... ... def render(self, *args, **kw): ... return self.index(*args, **kw) As you can see, the concern of sorting is cleanly separated from generating the view code. In MVC terms that means that the controller (sort) is logically separated from the view (viewlets). Let's now do the registration dance for the new viewlet manager. We simply override the existing registration: >>> SortedContentsViewletManager = type( ... 'SortedContentsViewletManager', (SortedContentsViewletManager,), ... {'index': ViewPageTemplateFile(tableTemplate)}) >>> zope.component.provideAdapter( ... SortedContentsViewletManager, ... (Container, IDefaultBrowserLayer, zope.interface.Interface), ... interfaces.IViewletManager, name='contents') Finally we sort the contents by name: >>> shownColumns = ['name', 'size'] >>> sortByColumn = 'name' >>> print contents().strip()

Contents

data.xml 31 bytes
mypage.html 38 bytes
test.txt 12 bytes
Now let's sort by size: >>> sortByColumn = 'size' >>> print contents().strip()

Contents

test.txt 12 bytes
data.xml 31 bytes
mypage.html 38 bytes
That's it! As you can see, in a few steps we have built a pretty flexible contents view with selectable columns and sorting. However, there is a lot of room for extending this example: - Table Header: The table header cell for each column should be a different type of viewlet, but registered under the same name. The column header viewlet also adapts the container not the item. The header column should also be able to control the sorting. - Batching: A simple implementation of batching should work very similar to the sorting feature. Of course, efficient implementations should somehow combine batching and sorting more effectively. - Sorting in ascending and descending order: Currently, you can only sort from the smallest to the highest value; however, this limitation is almost superficial and can easily be removed by making the sorters a bit more flexible. - Further Columns: For a real application, you would want to implement other columns, of course. You would also probably want some sort of fallback for the case that a viewlet is not found for a particular container item and column. Cleanup ------- >>> import shutil >>> shutil.rmtree(temp_dir) ================================ The ``viewletManager`` Directive ================================ The ``viewletManager`` directive allows you to quickly register a new viewlet manager without worrying about the details of the ``adapter`` directive. Before we can use the directives, we have to register their handlers by executing the package's meta configuration: >>> from zope.configuration import xmlconfig >>> context = xmlconfig.string(''' ... ... ... ... ''') Now we can register a viewlet manager: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Let's make sure the directive has really issued a sensible adapter registration; to do that, we create some dummy content, request and view objects: >>> import zope.interface >>> class Content(object): ... zope.interface.implements(zope.interface.Interface) >>> content = Content() >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> from zope.publisher.browser import BrowserView >>> view = BrowserView(content, request) Now let's lookup the manager. This particular registration is pretty boring: >>> import zope.component >>> from zope.viewlet import interfaces >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ... interfaces.IViewletManager, name='defaultmanager') >>> manager object ...> >>> interfaces.IViewletManager.providedBy(manager) True >>> manager.template is None True >>> manager.update() >>> manager.render() u'' However, this registration is not very useful, since we did specify a specific viewlet manager interface, a specific content interface, specific view or specific layer. This means that all viewlets registered will be found. The first step to effectively using the viewlet manager directive is to define a special viewlet manager interface: >>> class ILeftColumn(interfaces.IViewletManager): ... """Left column of my page.""" Now we can register register a manager providing this interface: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> ILeftColumn.providedBy(manager) True >>> manager.template is None True >>> manager.update() >>> manager.render() u'' Next let's see what happens, if we specify a template for the viewlet manager: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> leftColumnTemplate = os.path.join(temp_dir, 'leftcolumn.pt') >>> open(leftColumnTemplate, 'w').write(''' ...
...
...
... ''') >>> context = xmlconfig.string(''' ... ... ... ... ''' %leftColumnTemplate, context=context) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> ILeftColumn.providedBy(manager) True >>> manager.template ...>> >>> manager.update() >>> print manager.render().strip()
Additionally you can specify a class that will serve as a base to the default viewlet manager or be a viewlet manager in its own right. In our case we will provide a custom implementation of the ``sort()`` method, which will sort by a weight attribute in the viewlet: >>> class WeightBasedSorting(object): ... def sort(self, viewlets): ... return sorted(viewlets, ... lambda x, y: cmp(x[1].weight, y[1].weight)) >>> context = xmlconfig.string(''' ... ... ... ... ''' %leftColumnTemplate, context=context) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> manager.__class__.__bases__ (, ) >>> ILeftColumn.providedBy(manager) True >>> manager.template ...>> >>> manager.update() >>> print manager.render().strip()
Finally, if a non-existent template is specified, an error is raised: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-7.8 ConfigurationError: ('No such file', '...foo.pt') ========================= The ``viewlet`` Directive ========================= Now that we have a viewlet manager, we have to register some viewlets for it. The ``viewlet`` directive is similar to the ``viewletManager`` directive, except that the viewlet is also registered for a particular manager interface, as seen below: >>> weatherTemplate = os.path.join(temp_dir, 'weather.pt') >>> open(weatherTemplate, 'w').write(''' ...
sunny
... ''') >>> context = xmlconfig.string(''' ... ... ... ... ''' % weatherTemplate, context=context) If we look into the adapter registry, we will find the viewlet: >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='weather') >>> viewlet.render().strip() u'
sunny
' >>> viewlet.extra_string_attributes u'can be specified' The manager now also gives us the output of the one and only viewlet: >>> manager.update() >>> print manager.render().strip()
sunny
Let's now ensure that we can also specify a viewlet class: >>> class Weather(object): ... weight = 0 >>> context = xmlconfig.string(''' ... ... ... ... ''' % weatherTemplate, context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='weather2') >>> viewlet().strip() u'
sunny
' Okay, so the template-driven cases work. But just specifying a class should also work: >>> class Sport(object): ... weight = 0 ... def __call__(self): ... return u'Red Sox vs. White Sox' >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, name='sport') >>> viewlet() u'Red Sox vs. White Sox' It should also be possible to specify an alternative attribute of the class to be rendered upon calling the viewlet: >>> class Stock(object): ... weight = 0 ... def getStockTicker(self): ... return u'SRC $5.19' >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='stock') >>> viewlet.render() u'SRC $5.19' A final feature the ``viewlet`` directive is that it supports the specification of any number of keyword arguments: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='stock2') >>> viewlet.weight u'8' Error Scenarios --------------- Neither the class or template have been specified: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-7.8 ConfigurationError: Must specify a class or template The specified attribute is not ``__call__``, but also a template has been specified: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-9.8 ConfigurationError: Attribute and template cannot be used together. Now, we are not specifying a template, but a class that does not have the specified attribute: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-9.8 ConfigurationError: The provided class doesn't have the specified attribute Cleanup ------- >>> import shutil >>> shutil.rmtree(temp_dir) ======= CHANGES ======= 3.7.2 (2010-05-25) ------------------ - Fixed unit tests broken under Python 2.4 by the switch to the standard library ``doctest`` module. 3.7.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctest' in favor of stdlib's 'doctest. - Fixed dubious quoting in metadirectives.py. Closes https://bugs.launchpad.net/zope2/+bug/143774. 3.7.0 (2009-12-22) ------------------ - Depend on zope.browserpage in favor of zope.app.pagetemplate. 3.6.1 (2009-08-29) ------------------ - Fixed unit tests in README.txt. 3.6.0 (2009-08-02) ------------------ - Optimize the the script tag for the JS viewlet. This makes YSlow happy. - Remove ZCML slugs and old zpkg-related files. - Drop all testing dependncies except ``zope.testing``. 3.5.0 (2009-01-26) ------------------ - Removed the dependency on `zope.app.publisher` by moving four simple helper functions into this package and making the interface for describing the ZCML content provider directive explicit. - Typo fix in CSSViewlet docstring. 3.4.2 (2008-01-24) ------------------ - Re-release of 3.4.1 because of brown bag release. 3.4.1 (2008-01-21) ------------------ - bugfix, implemented missing __contains__ method in IViewletManager - implemented additional viewlet managers offering weight ordered sorting - implemented additional viewlet managers offering conditional filtering 3.4.1a (2007-4-22) ------------------ - bugfix, added a missing ',' behind zope.i18nmessageid. - recreated the README.txt removing everything except for the overview. 3.4.0 (2007-10-10) ------------------ - Initial release independent of the main Zope tree. Keywords: zope web html ui viewlet pattern Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.viewlet/pip-egg-info/zope.viewlet.egg-info/dependency_links.txt0000644000175000017500000000000112214017662030547 0ustar arnauarnau zope2.13-2.13.21/source/zope.viewlet/pip-egg-info/zope.viewlet.egg-info/requires.txt0000644000175000017500000000035512214017662027104 0ustar arnauarnausetuptools zope.browserpage>=3.10.1 zope.component zope.configuration zope.contentprovider zope.event zope.i18nmessageid zope.interface zope.location zope.publisher zope.schema zope.security zope.traversing [test] zope.testing zope.sizezope2.13-2.13.21/source/zope.viewlet/pip-egg-info/zope.viewlet.egg-info/namespace_packages.txt0000644000175000017500000000000512214017662031027 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.viewlet/pip-egg-info/zope.viewlet.egg-info/top_level.txt0000644000175000017500000000000512214017662027226 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.viewlet/pip-egg-info/zope.viewlet.egg-info/SOURCES.txt0000644000175000017500000000113212214017662026362 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.viewlet.egg-info/PKG-INFO pip-egg-info/zope.viewlet.egg-info/SOURCES.txt pip-egg-info/zope.viewlet.egg-info/dependency_links.txt pip-egg-info/zope.viewlet.egg-info/namespace_packages.txt pip-egg-info/zope.viewlet.egg-info/not-zip-safe pip-egg-info/zope.viewlet.egg-info/requires.txt pip-egg-info/zope.viewlet.egg-info/top_level.txt src/zope/__init__.py src/zope/viewlet/__init__.py src/zope/viewlet/interfaces.py src/zope/viewlet/manager.py src/zope/viewlet/metaconfigure.py src/zope/viewlet/metadirectives.py src/zope/viewlet/tests.py src/zope/viewlet/viewlet.pyzope2.13-2.13.21/source/zope.viewlet/pip-egg-info/zope.viewlet.egg-info/not-zip-safe0000644000175000017500000000000112214017662026727 0ustar arnauarnau zope2.13-2.13.21/source/zope.viewlet/LICENSE.txt0000644000175000017500000000402612214017662017720 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.viewlet/README.txt0000644000175000017500000000011512214017662017566 0ustar arnauarnauViewlets provide a generic framework for building pluggable user interfaces. zope2.13-2.13.21/source/zope.viewlet/setup.cfg0000644000175000017500000000007312214017662017714 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.viewlet/COPYRIGHT.txt0000644000175000017500000000004012214017662020176 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.viewlet/buildout.cfg0000644000175000017500000000014512214017662020403 0ustar arnauarnau[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = zope.viewlet [test] zope2.13-2.13.21/source/zope.viewlet/bootstrap.py0000644000175000017500000000742212214017662020467 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 111805 2010-04-30 22:21:55Z hannosch $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.viewlet/CHANGES.txt0000644000175000017500000000321112214017662017701 0ustar arnauarnau======= CHANGES ======= 3.7.2 (2010-05-25) ------------------ - Fixed unit tests broken under Python 2.4 by the switch to the standard library ``doctest`` module. 3.7.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctest' in favor of stdlib's 'doctest. - Fixed dubious quoting in metadirectives.py. Closes https://bugs.launchpad.net/zope2/+bug/143774. 3.7.0 (2009-12-22) ------------------ - Depend on zope.browserpage in favor of zope.app.pagetemplate. 3.6.1 (2009-08-29) ------------------ - Fixed unit tests in README.txt. 3.6.0 (2009-08-02) ------------------ - Optimize the the script tag for the JS viewlet. This makes YSlow happy. - Remove ZCML slugs and old zpkg-related files. - Drop all testing dependncies except ``zope.testing``. 3.5.0 (2009-01-26) ------------------ - Removed the dependency on `zope.app.publisher` by moving four simple helper functions into this package and making the interface for describing the ZCML content provider directive explicit. - Typo fix in CSSViewlet docstring. 3.4.2 (2008-01-24) ------------------ - Re-release of 3.4.1 because of brown bag release. 3.4.1 (2008-01-21) ------------------ - bugfix, implemented missing __contains__ method in IViewletManager - implemented additional viewlet managers offering weight ordered sorting - implemented additional viewlet managers offering conditional filtering 3.4.1a (2007-4-22) ------------------ - bugfix, added a missing ',' behind zope.i18nmessageid. - recreated the README.txt removing everything except for the overview. 3.4.0 (2007-10-10) ------------------ - Initial release independent of the main Zope tree. zope2.13-2.13.21/source/zope.viewlet/src/0000755000175000017500000000000012214017662016662 5ustar arnauarnauzope2.13-2.13.21/source/zope.viewlet/src/zope/0000755000175000017500000000000012214017662017637 5ustar arnauarnauzope2.13-2.13.21/source/zope.viewlet/src/zope/__init__.py0000644000175000017500000000007012214017662021745 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/0000755000175000017500000000000012214017662021316 5ustar arnauarnauzope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/configure.zcml0000644000175000017500000000076312214017662024174 0ustar arnauarnau zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/communicating-viewlets.txt0000644000175000017500000000704012214017662026555 0ustar arnauarnau============================================== A technique for communication between viewlets ============================================== Sometimes one wants viewlets to communicate with each other to supplement a more generic viewlet's behaviour with another viewlet. One example would be a viewlet that contains a search form created by a library such as z3c.form, plus a second viewlet that provides a list of search results. This is very simple to accomplish with zope.viewlet but has turned out not to be obvious, so here is an explicit example. It is not written as a doc test since it uses z3c.form which should not become a dependency of zope.viewlet. The viewlets ============ For the purpose of this example, we simulate a search form. Our search results are simply the characters of the search term and are stored on the viewlet as an attribute: class ISearchForm(zope.interface.Interface): searchterm = zope.schema.TextLine(title=u"Search term") class SearchForm(z3c.form.form.Form): ignoreContext = True fields = z3c.form.field.Fields(ISearchForm) results = "" @z3c.form.button.buttonAndHandler(u"Search") def search(self, action): data, errors = self.extractData() self.results = list(data["searchterm"]) (Notice that this example is minimized to point out communication between viewlets, and no care is taken to handle the form itself in the best way possible. In particular, one will probably want to make sure that the actual search is not performed more than once, which may happen with the above code.) The result list viewlet needs to display the results stored on the search form. Therefore, the result list viewlet needs to access that viewlet, which is probably the only tricky part (if there is any at all) in this example: Since viewlets know their viewlet manager which lets viewlets be looked up by ID, it is all a matter of the result list viewlet knowing the ID of the search form. We'll store the search form viewlet's ID as an attribute of the result list viewlet. Let's hard-code the ID for a start, then the result list looks like this: class ResultList(zope.viewlet.viewlet.ViewletBase): searchform_id = "searchform" def update(self): super(ResultList, self).update() searchform = self.manager[self.searchform_id] searchform.update() self.results = searchform.results def render(self): return "
    %s
" % "\n".join(u"
  • %s
  • " % x for x in self.results) Registering the viewlets ======================== As long as we treat the ID of the search form as hard-coded, we have to use the correct name when registering the two viewlets: Making the ID of the search form more flexible now doesn't even require changing any code: the viewlet directive may be passed arbitrary attributes which will be available as attributes of the ResultList objects. The attribute that holds our search form's ID is searchform_id, so we might register the viewlets like this: zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/directives.txt0000644000175000017500000003065412214017662024230 0ustar arnauarnau================================ The ``viewletManager`` Directive ================================ The ``viewletManager`` directive allows you to quickly register a new viewlet manager without worrying about the details of the ``adapter`` directive. Before we can use the directives, we have to register their handlers by executing the package's meta configuration: >>> from zope.configuration import xmlconfig >>> context = xmlconfig.string(''' ... ... ... ... ''') Now we can register a viewlet manager: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Let's make sure the directive has really issued a sensible adapter registration; to do that, we create some dummy content, request and view objects: >>> import zope.interface >>> class Content(object): ... zope.interface.implements(zope.interface.Interface) >>> content = Content() >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> from zope.publisher.browser import BrowserView >>> view = BrowserView(content, request) Now let's lookup the manager. This particular registration is pretty boring: >>> import zope.component >>> from zope.viewlet import interfaces >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ... interfaces.IViewletManager, name='defaultmanager') >>> manager object ...> >>> interfaces.IViewletManager.providedBy(manager) True >>> manager.template is None True >>> manager.update() >>> manager.render() u'' However, this registration is not very useful, since we did specify a specific viewlet manager interface, a specific content interface, specific view or specific layer. This means that all viewlets registered will be found. The first step to effectively using the viewlet manager directive is to define a special viewlet manager interface: >>> class ILeftColumn(interfaces.IViewletManager): ... """Left column of my page.""" Now we can register register a manager providing this interface: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> ILeftColumn.providedBy(manager) True >>> manager.template is None True >>> manager.update() >>> manager.render() u'' Next let's see what happens, if we specify a template for the viewlet manager: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> leftColumnTemplate = os.path.join(temp_dir, 'leftcolumn.pt') >>> open(leftColumnTemplate, 'w').write(''' ...
    ...
    ...
    ... ''') >>> context = xmlconfig.string(''' ... ... ... ... ''' %leftColumnTemplate, context=context) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> ILeftColumn.providedBy(manager) True >>> manager.template ...>> >>> manager.update() >>> print manager.render().strip()
    Additionally you can specify a class that will serve as a base to the default viewlet manager or be a viewlet manager in its own right. In our case we will provide a custom implementation of the ``sort()`` method, which will sort by a weight attribute in the viewlet: >>> class WeightBasedSorting(object): ... def sort(self, viewlets): ... return sorted(viewlets, ... lambda x, y: cmp(x[1].weight, y[1].weight)) >>> context = xmlconfig.string(''' ... ... ... ... ''' %leftColumnTemplate, context=context) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> manager.__class__.__bases__ (, ) >>> ILeftColumn.providedBy(manager) True >>> manager.template ...>> >>> manager.update() >>> print manager.render().strip()
    Finally, if a non-existent template is specified, an error is raised: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-7.8 ConfigurationError: ('No such file', '...foo.pt') ========================= The ``viewlet`` Directive ========================= Now that we have a viewlet manager, we have to register some viewlets for it. The ``viewlet`` directive is similar to the ``viewletManager`` directive, except that the viewlet is also registered for a particular manager interface, as seen below: >>> weatherTemplate = os.path.join(temp_dir, 'weather.pt') >>> open(weatherTemplate, 'w').write(''' ...
    sunny
    ... ''') >>> context = xmlconfig.string(''' ... ... ... ... ''' % weatherTemplate, context=context) If we look into the adapter registry, we will find the viewlet: >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='weather') >>> viewlet.render().strip() u'
    sunny
    ' >>> viewlet.extra_string_attributes u'can be specified' The manager now also gives us the output of the one and only viewlet: >>> manager.update() >>> print manager.render().strip()
    sunny
    Let's now ensure that we can also specify a viewlet class: >>> class Weather(object): ... weight = 0 >>> context = xmlconfig.string(''' ... ... ... ... ''' % weatherTemplate, context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='weather2') >>> viewlet().strip() u'
    sunny
    ' Okay, so the template-driven cases work. But just specifying a class should also work: >>> class Sport(object): ... weight = 0 ... def __call__(self): ... return u'Red Sox vs. White Sox' >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, name='sport') >>> viewlet() u'Red Sox vs. White Sox' It should also be possible to specify an alternative attribute of the class to be rendered upon calling the viewlet: >>> class Stock(object): ... weight = 0 ... def getStockTicker(self): ... return u'SRC $5.19' >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='stock') >>> viewlet.render() u'SRC $5.19' A final feature the ``viewlet`` directive is that it supports the specification of any number of keyword arguments: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='stock2') >>> viewlet.weight u'8' Error Scenarios --------------- Neither the class or template have been specified: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-7.8 ConfigurationError: Must specify a class or template The specified attribute is not ``__call__``, but also a template has been specified: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-9.8 ConfigurationError: Attribute and template cannot be used together. Now, we are not specifying a template, but a class that does not have the specified attribute: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-9.8 ConfigurationError: The provided class doesn't have the specified attribute Cleanup ------- >>> import shutil >>> shutil.rmtree(temp_dir) zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/interfaces.py0000644000175000017500000000406412214017662024017 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Viewlet interfaces $Id: interfaces.py 112059 2010-05-05 19:40:35Z tseaver $ """ __docformat__ = 'restructuredtext' import zope.interface from zope.contentprovider.interfaces import IContentProvider from zope.i18nmessageid import MessageFactory _ = MessageFactory('zope') class IViewlet(IContentProvider): """A content provider that is managed by another content provider, known as viewlet manager. Note that you *cannot* call viewlets directly as a provider, i.e. through the TALES ``provider`` expression, since it always has to know its manager. """ manager = zope.interface.Attribute( """The Viewlet Manager The viewlet manager for which the viewlet is registered. The viewlet manager will contain any additional data that was provided by the view, for example the TAL namespace attributes. """) class IViewletManager(IContentProvider, zope.interface.common.mapping.IReadMapping): """A component that provides access to the content providers. The viewlet manager's resposibilities are: (1) Aggregation of all viewlets registered for the manager. (2) Apply a set of filters to determine the availability of the viewlets. (3) Sort the viewlets based on some implemented policy. (4) Provide an environment in which the viewlets are rendered. (5) Render itself containing the HTML content of the viewlets. """ zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/metaconfigure.py0000644000175000017500000002044412214017662024524 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Viewlet metadconfigure $Id: metaconfigure.py 112059 2010-05-05 19:40:35Z tseaver $ """ __docformat__ = 'restructuredtext' import os from zope.security import checker from zope.configuration.exceptions import ConfigurationError from zope.interface import Interface, classImplements from zope.publisher.interfaces.browser import IDefaultBrowserLayer from zope.publisher.interfaces.browser import IBrowserPublisher from zope.publisher.interfaces.browser import IBrowserView from zope.component import zcml from zope.component.interface import provideInterface from zope.viewlet import viewlet, manager, interfaces def viewletManagerDirective( _context, name, permission, for_=Interface, layer=IDefaultBrowserLayer, view=IBrowserView, provides=interfaces.IViewletManager, class_=None, template=None, allowed_interface=None, allowed_attributes=None): # A list of attributes available under the provided permission required = {} # Get the permission; mainly to correctly handle CheckerPublic. permission = _handle_permission(_context, permission) # If class is not given we use the basic viewlet manager. if class_ is None: class_ = manager.ViewletManagerBase # Make sure that the template exists and that all low-level API methods # have the right permission. if template: template = os.path.abspath(str(_context.path(template))) if not os.path.isfile(template): raise ConfigurationError("No such file", template) required['__getitem__'] = permission # Create a new class based on the template and class. new_class = manager.ViewletManager( name, provides, template=template, bases=(class_, )) else: # Create a new class based on the class. new_class = manager.ViewletManager(name, provides, bases=(class_, )) # Register some generic attributes with the security dictionary for attr_name in ('browserDefault', 'update', 'render', 'publishTraverse'): required[attr_name] = permission # Register the ``provides`` interface and register fields in the security # dictionary _handle_allowed_interface( _context, (provides,), permission, required) # Register the allowed interface and add the field's security entries _handle_allowed_interface( _context, allowed_interface, permission, required) # Register single allowed attributes in the security dictionary _handle_allowed_attributes( _context, allowed_attributes, permission, required) # Register interfaces _handle_for(_context, for_) zcml.interface(_context, view) # Create a checker for the viewlet manager checker.defineChecker(new_class, checker.Checker(required)) # register a viewlet manager _context.action( discriminator = ('viewletManager', for_, layer, view, name), callable = zcml.handler, args = ('registerAdapter', new_class, (for_, layer, view), provides, name, _context.info),) def viewletDirective( _context, name, permission, for_=Interface, layer=IDefaultBrowserLayer, view=IBrowserView, manager=interfaces.IViewletManager, class_=None, template=None, attribute='render', allowed_interface=None, allowed_attributes=None, **kwargs): # Security map dictionary required = {} # Get the permission; mainly to correctly handle CheckerPublic. permission = _handle_permission(_context, permission) # Either the class or template must be specified. if not (class_ or template): raise ConfigurationError("Must specify a class or template") # Make sure that all the non-default attribute specifications are correct. if attribute != 'render': if template: raise ConfigurationError( "Attribute and template cannot be used together.") # Note: The previous logic forbids this condition to evere occur. if not class_: raise ConfigurationError( "A class must be provided if attribute is used") # Make sure that the template exists and that all low-level API methods # have the right permission. if template: template = os.path.abspath(str(_context.path(template))) if not os.path.isfile(template): raise ConfigurationError("No such file", template) required['__getitem__'] = permission # Make sure the has the right form, if specified. if class_: if attribute != 'render': if not hasattr(class_, attribute): raise ConfigurationError( "The provided class doesn't have the specified attribute " ) if template: # Create a new class for the viewlet template and class. new_class = viewlet.SimpleViewletClass( template, bases=(class_, ), attributes=kwargs, name=name) else: if not hasattr(class_, 'browserDefault'): cdict = {'browserDefault': lambda self, request: (getattr(self, attribute), ())} else: cdict = {} cdict['__name__'] = name cdict['__page_attribute__'] = attribute cdict.update(kwargs) new_class = type(class_.__name__, (class_, viewlet.SimpleAttributeViewlet), cdict) if hasattr(class_, '__implements__'): classImplements(new_class, IBrowserPublisher) else: # Create a new class for the viewlet template alone. new_class = viewlet.SimpleViewletClass(template, name=name, attributes=kwargs) # Set up permission mapping for various accessible attributes _handle_allowed_interface( _context, allowed_interface, permission, required) _handle_allowed_attributes( _context, allowed_attributes, permission, required) _handle_allowed_attributes( _context, kwargs.keys(), permission, required) _handle_allowed_attributes( _context, (attribute, 'browserDefault', 'update', 'render', 'publishTraverse'), permission, required) # Register the interfaces. _handle_for(_context, for_) zcml.interface(_context, view) # Create the security checker for the new class checker.defineChecker(new_class, checker.Checker(required)) # register viewlet _context.action( discriminator = ('viewlet', for_, layer, view, manager, name), callable = zcml.handler, args = ('registerAdapter', new_class, (for_, layer, view, manager), interfaces.IViewlet, name, _context.info),) def _handle_permission(_context, permission): if permission == 'zope.Public': permission = checker.CheckerPublic return permission def _handle_allowed_interface(_context, allowed_interface, permission, required): # Allow access for all names defined by named interfaces if allowed_interface: for i in allowed_interface: _context.action( discriminator = None, callable = provideInterface, args = (None, i) ) for name in i: required[name] = permission def _handle_allowed_attributes(_context, allowed_attributes, permission, required): # Allow access for all named attributes if allowed_attributes: for name in allowed_attributes: required[name] = permission def _handle_for(_context, for_): if for_ is not None: _context.action( discriminator = None, callable = provideInterface, args = ('', for_) ) zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/__init__.py0000644000175000017500000000127312214017662023432 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ $Id: __init__.py 112059 2010-05-05 19:40:35Z tseaver $ """ zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/README.txt0000644000175000017500000010652412214017662023024 0ustar arnauarnau============================= Viewlets and Viewlet Managers ============================= Let's start with some motivation. Using content providers allows us to insert one piece of HTML content. In most Web development, however, you are often interested in defining some sort of region and then allow developers to register content for those regions. >>> from zope.viewlet import interfaces Design Notes ------------ As mentioned above, besides inserting snippets of HTML at places, we more frequently want to define a region in our page and allow specialized content providers to be inserted based on configuration. Those specialized content providers are known as viewlets and are only available inside viewlet managers, which are just a more complex example of content providers. Unfortunately, the Java world does not implement this layer separately. The viewlet manager is most similar to a Java "channel", but we decided against using this name, since it is very generic and not very meaningful. The viewlet has no Java counterpart, since Java does not implement content providers using a component architecture and thus does not register content providers specifically for viewlet managers, which I believe makes the Java implementation less useful as a generic concept. In fact, the main design goal in the Java world is the implementation of reusable and sharable portlets. The scope for Zope 3 is larger, since we want to provide a generic framework for building pluggable user interfaces. The Viewlet Manager ------------------- In this implementation of viewlets, those regions are just content providers called viewlet managers that manage a special type of content providers known as viewlets. Every viewlet manager handles the viewlets registered for it: >>> class ILeftColumn(interfaces.IViewletManager): ... """Viewlet manager located in the left column.""" You can then create a viewlet manager using this interface now: >>> from zope.viewlet import manager >>> LeftColumn = manager.ViewletManager('left', ILeftColumn) Now we have to instantiate it: >>> import zope.interface >>> class Content(object): ... zope.interface.implements(zope.interface.Interface) >>> content = Content() >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> from zope.publisher.interfaces.browser import IBrowserView >>> class View(object): ... zope.interface.implements(IBrowserView) ... def __init__(self, context, request): ... pass >>> view = View(content, request) >>> leftColumn = LeftColumn(content, request, view) So initially nothing gets rendered: >>> leftColumn.update() >>> leftColumn.render() u'' But now we register some viewlets for the manager >>> import zope.component >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer >>> class WeatherBox(object): ... zope.interface.implements(interfaces.IViewlet) ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
    It is sunny today!
    ' >>> # Create a security checker for viewlets. >>> from zope.security.checker import NamesChecker, defineChecker >>> viewletChecker = NamesChecker(('update', 'render')) >>> defineChecker(WeatherBox, viewletChecker) >>> zope.component.provideAdapter( ... WeatherBox, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, ILeftColumn), ... interfaces.IViewlet, name='weather') >>> from zope.location.interfaces import ILocation >>> class SportBox(object): ... zope.interface.implements(interfaces.IViewlet, ... ILocation) ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
    Patriots (23) : Steelers (7)
    ' >>> defineChecker(SportBox, viewletChecker) >>> zope.component.provideAdapter( ... SportBox, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, ILeftColumn), ... interfaces.IViewlet, name='sport') and thus the left column is filled. Note that also events get fired before viewlets are updated. We register a simple handler to demonstrate this behaviour. >>> from zope.contentprovider.interfaces import IBeforeUpdateEvent >>> events = [] >>> def handler(ev): ... events.append(ev) >>> zope.component.provideHandler(handler, (IBeforeUpdateEvent,)) >>> leftColumn.update() >>> [(ev, ev.object.__class__.__name__) for ev in events] [(, 'SportBox'), (, 'WeatherBox')] >>> print leftColumn.render()
    Patriots (23) : Steelers (7)
    It is sunny today!
    But this is of course pretty lame, since there is no way of specifying how the viewlets are put together. But we have a solution. The second argument of the ``ViewletManager()`` function is a template in which we can specify how the viewlets are put together: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> leftColTemplate = os.path.join(temp_dir, 'leftCol.pt') >>> open(leftColTemplate, 'w').write(''' ...
    ... ...
    ... ''') >>> LeftColumn = manager.ViewletManager('left', ILeftColumn, ... template=leftColTemplate) >>> leftColumn = LeftColumn(content, request, view) TODO: Fix this silly thing; viewlets should be directly available. As you can see, the viewlet manager provides a global ``options/viewlets`` variable that is an iterable of all the available viewlets in the correct order: >>> leftColumn.update() >>> print leftColumn.render().strip()
    Patriots (23) : Steelers (7)
    It is sunny today!
    If a viewlet provides ILocation the ``__name__`` attribute of the viewlet is set to the name under which the viewlet is registered. >>> [getattr(viewlet, '__name__', None) for viewlet in leftColumn.viewlets] [u'sport', None] You can also lookup the viewlets directly for management purposes: >>> leftColumn['weather'] >>> leftColumn.get('weather') The viewlet manager also provides the __contains__ method defined in IReadMapping: >>> 'weather' in leftColumn True >>> 'unknown' in leftColumn False If the viewlet is not found, then the expected behavior is provided: >>> leftColumn['stock'] Traceback (most recent call last): ... ComponentLookupError: No provider with name `stock` found. >>> leftColumn.get('stock') is None True Customizing the default Viewlet Manager --------------------------------------- One important feature of any viewlet manager is to be able to filter and sort the viewlets it is displaying. The default viewlet manager that we have been using in the tests above, supports filtering by access availability and sorting via the viewlet's ``__cmp__()`` method (default). You can easily override this default policy by providing a base viewlet manager class. In our case we will manage the viewlets using a global list: >>> shown = ['weather', 'sport'] The viewlet manager base class now uses this list: >>> class ListViewletManager(object): ... ... def filter(self, viewlets): ... viewlets = super(ListViewletManager, self).filter(viewlets) ... return [(name, viewlet) ... for name, viewlet in viewlets ... if name in shown] ... ... def sort(self, viewlets): ... viewlets = dict(viewlets) ... return [(name, viewlets[name]) for name in shown] Let's now create a new viewlet manager: >>> LeftColumn = manager.ViewletManager( ... 'left', ILeftColumn, bases=(ListViewletManager,), ... template=leftColTemplate) >>> leftColumn = LeftColumn(content, request, view) So we get the weather box first and the sport box second: >>> leftColumn.update() >>> print leftColumn.render().strip()
    It is sunny today!
    Patriots (23) : Steelers (7)
    Now let's change the order... >>> shown.reverse() and the order should switch as well: >>> leftColumn.update() >>> print leftColumn.render().strip()
    Patriots (23) : Steelers (7)
    It is sunny today!
    Of course, we also can remove a shown viewlet: >>> weather = shown.pop() >>> leftColumn.update() >>> print leftColumn.render().strip()
    Patriots (23) : Steelers (7)
    WeightOrderedViewletManager --------------------------- The weight ordered viewlet manager offers ordering viewlets by a additional weight argument. Viewlets which doesn't provide a weight attribute will get a weight of 0 (zero). Let's define a new column: >>> class IWeightedColumn(interfaces.IViewletManager): ... """Column with weighted viewlet manager.""" First register a template for the weight ordered viewlet manager: >>> weightedColTemplate = os.path.join(temp_dir, 'weightedColTemplate.pt') >>> open(weightedColTemplate, 'w').write(''' ...
    ... ...
    ... ''') And create a new weight ordered viewlet manager: >>> from zope.viewlet.manager import WeightOrderedViewletManager >>> WeightedColumn = manager.ViewletManager( ... 'left', IWeightedColumn, bases=(WeightOrderedViewletManager,), ... template=weightedColTemplate) >>> weightedColumn = WeightedColumn(content, request, view) Let's create some viewlets: >>> from zope.viewlet import viewlet >>> class FirstViewlet(viewlet.ViewletBase): ... ... weight = 1 ... ... def render(self): ... return u'
    first
    ' >>> class SecondViewlet(viewlet.ViewletBase): ... ... weight = 2 ... ... def render(self): ... return u'
    second
    ' >>> class ThirdViewlet(viewlet.ViewletBase): ... ... weight = 3 ... ... def render(self): ... return u'
    third
    ' >>> class UnWeightedViewlet(viewlet.ViewletBase): ... ... def render(self): ... return u'
    unweighted
    ' >>> defineChecker(FirstViewlet, viewletChecker) >>> defineChecker(SecondViewlet, viewletChecker) >>> defineChecker(ThirdViewlet, viewletChecker) >>> defineChecker(UnWeightedViewlet, viewletChecker) >>> zope.component.provideAdapter( ... ThirdViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='third') >>> zope.component.provideAdapter( ... FirstViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='first') >>> zope.component.provideAdapter( ... SecondViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='second') >>> zope.component.provideAdapter( ... UnWeightedViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='unweighted') And check the order: >>> weightedColumn.update() >>> print weightedColumn.render().strip()
    unweighted
    first
    second
    third
    ConditionalViewletManager ------------------------- The conditional ordered viewlet manager offers ordering viewlets by a additional weight argument and filters by the available attribute if a supported by the viewlet. Viewlets which doesn't provide a available attribute will not get skipped. The default weight value for viewlets which doesn't provide a weight attribute is 0 (zero). Let's define a new column: >>> class IConditionalColumn(interfaces.IViewletManager): ... """Column with weighted viewlet manager.""" First register a template for the weight ordered viewlet manager: >>> conditionalColTemplate = os.path.join(temp_dir, ... 'conditionalColTemplate.pt') >>> open(conditionalColTemplate, 'w').write(''' ...
    ... ...
    ... ''') And create a new conditional viewlet manager: >>> from zope.viewlet.manager import ConditionalViewletManager >>> ConditionalColumn = manager.ViewletManager( ... 'left', IConditionalColumn, bases=(ConditionalViewletManager,), ... template=conditionalColTemplate) >>> conditionalColumn = ConditionalColumn(content, request, view) Let's create some viewlets. We also use the previous viewlets supporting no weight and or no available attribute: >>> from zope.viewlet import viewlet >>> class AvailableViewlet(viewlet.ViewletBase): ... ... weight = 4 ... ... available = True ... ... def render(self): ... return u'
    available
    ' >>> class UnAvailableViewlet(viewlet.ViewletBase): ... ... weight = 5 ... ... available = False ... ... def render(self): ... return u'
    not available
    ' >>> defineChecker(AvailableViewlet, viewletChecker) >>> defineChecker(UnAvailableViewlet, viewletChecker) >>> zope.component.provideAdapter( ... ThirdViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='third') >>> zope.component.provideAdapter( ... FirstViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='first') >>> zope.component.provideAdapter( ... SecondViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='second') >>> zope.component.provideAdapter( ... UnWeightedViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='unweighted') >>> zope.component.provideAdapter( ... AvailableViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='available') >>> zope.component.provideAdapter( ... UnAvailableViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='unavailable') And check the order: >>> conditionalColumn.update() >>> print conditionalColumn.render().strip()
    unweighted
    first
    second
    third
    available
    Viewlet Base Classes -------------------- To make the creation of viewlets simpler, a set of useful base classes and helper functions are provided. The first class is a base class that simply defines the constructor: >>> base = viewlet.ViewletBase('context', 'request', 'view', 'manager') >>> base.context 'context' >>> base.request 'request' >>> base.__parent__ 'view' >>> base.manager 'manager' But a default ``render()`` method implementation is not provided: >>> base.render() Traceback (most recent call last): ... NotImplementedError: `render` method must be implemented by subclass. If you have already an existing class that produces the HTML content in some method, then the ``SimpleAttributeViewlet`` might be for you, since it can be used to convert any class quickly into a viewlet: >>> class FooViewlet(viewlet.SimpleAttributeViewlet): ... __page_attribute__ = 'foo' ... ... def foo(self): ... return 'output' The `__page_attribute__` attribute provides the name of the function to call for rendering. >>> foo = FooViewlet('context', 'request', 'view', 'manager') >>> foo.foo() 'output' >>> foo.render() 'output' If you specify `render` as the attribute an error is raised to prevent infinite recursion: >>> foo.__page_attribute__ = 'render' >>> foo.render() Traceback (most recent call last): ... AttributeError: render The same is true if the specified attribute does not exist: >>> foo.__page_attribute__ = 'bar' >>> foo.render() Traceback (most recent call last): ... AttributeError: 'FooViewlet' object has no attribute 'bar' To create simple template-based viewlets you can use the ``SimpleViewletClass()`` function. This function is very similar to its view equivalent and is used by the ZCML directives to create viewlets. The result of this function call will be a fully functional viewlet class. Let's start by simply specifying a template only: >>> template = os.path.join(temp_dir, 'demoTemplate.pt') >>> open(template, 'w').write('''
    contents
    ''') >>> Demo = viewlet.SimpleViewletClass(template) >>> print Demo(content, request, view, manager).render()
    contents
    Now let's additionally specify a class that can provide additional features: >>> class MyViewlet(object): ... myAttribute = 8 >>> Demo = viewlet.SimpleViewletClass(template, bases=(MyViewlet,)) >>> MyViewlet in Demo.__bases__ True >>> Demo(content, request, view, manager).myAttribute 8 The final important feature is the ability to pass in further attributes to the class: >>> Demo = viewlet.SimpleViewletClass( ... template, attributes={'here': 'now', 'lucky': 3}) >>> demo = Demo(content, request, view, manager) >>> demo.here 'now' >>> demo.lucky 3 As for all views, they must provide a name that can also be passed to the function: >>> Demo = viewlet.SimpleViewletClass(template, name='demoViewlet') >>> demo = Demo(content, request, view, manager) >>> demo.__name__ 'demoViewlet' In addition to the the generic viewlet code above, the package comes with two viewlet base classes and helper functions for inserting CSS and Javascript links into HTML headers, since those two are so very common. I am only going to demonstrate the helper functions here, since those demonstrations will fully demonstrate the functionality of the base classes as well. The viewlet will look up the resource it was given and tries to produce the absolute URL for it: >>> class JSResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/resource.js' >>> zope.component.provideAdapter( ... JSResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='resource.js') >>> JSViewlet = viewlet.JavaScriptViewlet('resource.js') >>> print JSViewlet(content, request, view, manager).render().strip() There is also a javascript viewlet base class which knows how to render more then one javascript resource file: >>> class JSSecondResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/second-resource.js' >>> zope.component.provideAdapter( ... JSSecondResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='second-resource.js') >>> JSBundleViewlet = viewlet.JavaScriptBundleViewlet(('resource.js', ... 'second-resource.js')) >>> print JSBundleViewlet(content, request, view, manager).render().strip() The same works for the CSS resource viewlet: >>> class CSSResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/resource.css' >>> zope.component.provideAdapter( ... CSSResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='resource.css') >>> CSSViewlet = viewlet.CSSViewlet('resource.css') >>> print CSSViewlet(content, request, view, manager).render().strip() You can also change the media type and the rel attribute: >>> CSSViewlet = viewlet.CSSViewlet('resource.css', media='print', rel='css') >>> print CSSViewlet(content, request, view, manager).render().strip() There is also a bundle viewlet for CSS links: >>> class CSSPrintResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/print-resource.css' >>> zope.component.provideAdapter( ... CSSPrintResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='print-resource.css') >>> items = [] >>> items.append({'path':'resource.css', 'rel':'stylesheet', 'media':'all'}) >>> items.append({'path':'print-resource.css', 'media':'print'}) >>> CSSBundleViewlet = viewlet.CSSBundleViewlet(items) >>> print CSSBundleViewlet(content, request, view, manager).render().strip() A Complex Example ----------------- The Data ~~~~~~~~ So far we have only demonstrated simple (maybe overly trivial) use cases of the viewlet system. In the following example, we are going to develop a generic contents view for files. The step is to create a file component: >>> class IFile(zope.interface.Interface): ... data = zope.interface.Attribute('Data of file.') >>> class File(object): ... zope.interface.implements(IFile) ... def __init__(self, data=''): ... self.__name__ = '' ... self.data = data Since we want to also provide the size of a file, here a simple implementation of the ``ISized`` interface: >>> from zope import size >>> class FileSized(object): ... zope.interface.implements(size.interfaces.ISized) ... zope.component.adapts(IFile) ... ... def __init__(self, file): ... self.file = file ... ... def sizeForSorting(self): ... return 'byte', len(self.file.data) ... ... def sizeForDisplay(self): ... return '%i bytes' %len(self.file.data) >>> zope.component.provideAdapter(FileSized) We also need a container to which we can add files: >>> class Container(dict): ... def __setitem__(self, name, value): ... value.__name__ = name ... super(Container, self).__setitem__(name, value) Here is some sample data: >>> container = Container() >>> container['test.txt'] = File('Hello World!') >>> container['mypage.html'] = File('Hello World!') >>> container['data.xml'] = File('Hello World!') The View ~~~~~~~~ The contents view of the container should iterate through the container and represent the files in a table: >>> contentsTemplate = os.path.join(temp_dir, 'contents.pt') >>> open(contentsTemplate, 'w').write(''' ... ... ...

    Contents

    ...
    ... ... ... ''') >>> from zope.browserpage.simpleviewclass import SimpleViewClass >>> Contents = SimpleViewClass(contentsTemplate, name='contents.html') The Viewlet Manager ~~~~~~~~~~~~~~~~~~~ Now we have to write our own viewlet manager. In this case we cannot use the default implementation, since the viewlets will be looked up for each different item: >>> shownColumns = [] >>> class ContentsViewletManager(object): ... zope.interface.implements(interfaces.IViewletManager) ... index = None ... ... def __init__(self, context, request, view): ... self.context = context ... self.request = request ... self.__parent__ = view ... ... def update(self): ... rows = [] ... for name, value in self.context.items(): ... rows.append( ... [zope.component.getMultiAdapter( ... (value, self.request, self.__parent__, self), ... interfaces.IViewlet, name=colname) ... for colname in shownColumns]) ... [entry.update() for entry in rows[-1]] ... self.rows = rows ... ... def render(self, *args, **kw): ... return self.index(*args, **kw) Now we need a template to produce the contents table: >>> tableTemplate = os.path.join(temp_dir, 'table.pt') >>> open(tableTemplate, 'w').write(''' ... ... ... ... ...
    ... ...
    ... ''') From the two pieces above, we can generate the final viewlet manager class and register it (it's a bit tedious, I know): >>> from zope.browserpage import ViewPageTemplateFile >>> ContentsViewletManager = type( ... 'ContentsViewletManager', (ContentsViewletManager,), ... {'index': ViewPageTemplateFile(tableTemplate)}) >>> zope.component.provideAdapter( ... ContentsViewletManager, ... (Container, IDefaultBrowserLayer, zope.interface.Interface), ... interfaces.IViewletManager, name='contents') Since we have not defined any viewlets yet, the table is totally empty: >>> contents = Contents(container, request) >>> print contents().strip()

    Contents

    The Viewlets and the Final Result ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now let's create a first viewlet for the manager... >>> class NameViewlet(object): ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... self.context = context ... ... def update(self): ... pass ... ... def render(self): ... return self.context.__name__ and register it: >>> zope.component.provideAdapter( ... NameViewlet, ... (IFile, IDefaultBrowserLayer, ... zope.interface.Interface, interfaces.IViewletManager), ... interfaces.IViewlet, name='name') Note how you register the viewlet on ``IFile`` and not on the container. Now we should be able to see the name for each file in the container: >>> print contents().strip()

    Contents

    Waaa, nothing there! What happened? Well, we have to tell our user preferences that we want to see the name as a column in the table: >>> shownColumns = ['name'] >>> print contents().strip()

    Contents

    mypage.html
    data.xml
    test.txt
    Let's now write a second viewlet that will display the size of the object for us: >>> class SizeViewlet(object): ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... self.context = context ... ... def update(self): ... pass ... ... def render(self): ... return size.interfaces.ISized(self.context).sizeForDisplay() >>> zope.component.provideAdapter( ... SizeViewlet, ... (IFile, IDefaultBrowserLayer, ... zope.interface.Interface, interfaces.IViewletManager), ... interfaces.IViewlet, name='size') After we added it to the list of shown columns, >>> shownColumns = ['name', 'size'] we can see an entry for it: >>> print contents().strip()

    Contents

    mypage.html 38 bytes
    data.xml 31 bytes
    test.txt 12 bytes
    If we switch the two columns around, >>> shownColumns = ['size', 'name'] the result will be >>> print contents().strip()

    Contents

    38 bytes mypage.html
    31 bytes data.xml
    12 bytes test.txt
    Supporting Sorting ~~~~~~~~~~~~~~~~~~ Oftentimes you also want to batch and sort the entries in a table. Since those two features are not part of the view logic, they should be treated with independent components. In this example, we are going to only implement sorting using a simple utility: >>> class ISorter(zope.interface.Interface): ... ... def sort(values): ... """Sort the values.""" >>> class SortByName(object): ... zope.interface.implements(ISorter) ... ... def sort(self, values): ... return sorted(values, lambda x, y: cmp(x.__name__, y.__name__)) >>> zope.component.provideUtility(SortByName(), name='name') >>> class SortBySize(object): ... zope.interface.implements(ISorter) ... ... def sort(self, values): ... return sorted( ... values, ... lambda x, y: cmp(size.interfaces.ISized(x).sizeForSorting(), ... size.interfaces.ISized(y).sizeForSorting())) >>> zope.component.provideUtility(SortBySize(), name='size') Note that we decided to give the sorter utilities the same name as the corresponding viewlet. This convention will make our implementation of the viewlet manager much simpler: >>> sortByColumn = '' >>> class SortedContentsViewletManager(object): ... zope.interface.implements(interfaces.IViewletManager) ... index = None ... ... def __init__(self, context, request, view): ... self.context = context ... self.request = request ... self.__parent__ = view ... ... def update(self): ... values = self.context.values() ... ... if sortByColumn: ... sorter = zope.component.queryUtility(ISorter, sortByColumn) ... if sorter: ... values = sorter.sort(values) ... ... rows = [] ... for value in values: ... rows.append( ... [zope.component.getMultiAdapter( ... (value, self.request, self.__parent__, self), ... interfaces.IViewlet, name=colname) ... for colname in shownColumns]) ... [entry.update() for entry in rows[-1]] ... self.rows = rows ... ... def render(self, *args, **kw): ... return self.index(*args, **kw) As you can see, the concern of sorting is cleanly separated from generating the view code. In MVC terms that means that the controller (sort) is logically separated from the view (viewlets). Let's now do the registration dance for the new viewlet manager. We simply override the existing registration: >>> SortedContentsViewletManager = type( ... 'SortedContentsViewletManager', (SortedContentsViewletManager,), ... {'index': ViewPageTemplateFile(tableTemplate)}) >>> zope.component.provideAdapter( ... SortedContentsViewletManager, ... (Container, IDefaultBrowserLayer, zope.interface.Interface), ... interfaces.IViewletManager, name='contents') Finally we sort the contents by name: >>> shownColumns = ['name', 'size'] >>> sortByColumn = 'name' >>> print contents().strip()

    Contents

    data.xml 31 bytes
    mypage.html 38 bytes
    test.txt 12 bytes
    Now let's sort by size: >>> sortByColumn = 'size' >>> print contents().strip()

    Contents

    test.txt 12 bytes
    data.xml 31 bytes
    mypage.html 38 bytes
    That's it! As you can see, in a few steps we have built a pretty flexible contents view with selectable columns and sorting. However, there is a lot of room for extending this example: - Table Header: The table header cell for each column should be a different type of viewlet, but registered under the same name. The column header viewlet also adapts the container not the item. The header column should also be able to control the sorting. - Batching: A simple implementation of batching should work very similar to the sorting feature. Of course, efficient implementations should somehow combine batching and sorting more effectively. - Sorting in ascending and descending order: Currently, you can only sort from the smallest to the highest value; however, this limitation is almost superficial and can easily be removed by making the sorters a bit more flexible. - Further Columns: For a real application, you would want to implement other columns, of course. You would also probably want some sort of fallback for the case that a viewlet is not found for a particular container item and column. Cleanup ------- >>> import shutil >>> shutil.rmtree(temp_dir) zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/viewlet.py0000644000175000017500000001513612214017662023355 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Viewlet implementation $Id: viewlet.py 112059 2010-05-05 19:40:35Z tseaver $ """ __docformat__ = 'restructuredtext' import os import sys import zope.interface from zope.traversing import api from zope.publisher.browser import BrowserView from zope.viewlet import interfaces from zope.browserpage import simpleviewclass from zope.browserpage import ViewPageTemplateFile class ViewletBase(BrowserView): """Viewlet adapter class used in meta directive as a mixin class.""" zope.interface.implements(interfaces.IViewlet) def __init__(self, context, request, view, manager): super(ViewletBase, self).__init__(context, request) self.__parent__ = view self.context = context self.request = request self.manager = manager def update(self): pass def render(self): raise NotImplementedError( '`render` method must be implemented by subclass.') class SimpleAttributeViewlet(ViewletBase): """A viewlet that uses a specified method to produce its content.""" def render(self, *args, **kw): # If a class doesn't provide it's own call, then get the attribute # given by the browser default. attr = self.__page_attribute__ if attr == 'render': raise AttributeError("render") meth = getattr(self, attr) return meth(*args, **kw) class simple(simpleviewclass.simple): """Simple viewlet class supporting the ``render()`` method.""" render = simpleviewclass.simple.__call__ def SimpleViewletClass(template, offering=None, bases=(), attributes=None, name=u''): """A function that can be used to generate a viewlet from a set of information. """ # Get the current frame if offering is None: offering = sys._getframe(1).f_globals # Create the base class hierarchy bases += (simple, ViewletBase) attrs = {'index' : ViewPageTemplateFile(template, offering), '__name__' : name} if attributes: attrs.update(attributes) # Generate a derived view class. class_ = type("SimpleViewletClass from %s" % template, bases, attrs) return class_ class ResourceViewletBase(object): """A simple viewlet for inserting references to resources. This is an abstract class that is expected to be used as a base only. """ _path = None def getURL(self): resource = api.traverse(self.context, '++resource++' + self._path, request=self.request) return resource() def render(self, *args, **kw): return self.index(*args, **kw) def JavaScriptViewlet(path): """Create a viewlet that can simply insert a javascript link.""" src = os.path.join(os.path.dirname(__file__), 'javascript_viewlet.pt') klass = type('JavaScriptViewlet', (ResourceViewletBase, ViewletBase), {'index': ViewPageTemplateFile(src), '_path': path}) return klass class CSSResourceViewletBase(ResourceViewletBase): _media = 'all' _rel = 'stylesheet' def getMedia(self): return self._media def getRel(self): return self._rel def CSSViewlet(path, media="all", rel="stylesheet"): """Create a viewlet that can simply insert a CSS link.""" src = os.path.join(os.path.dirname(__file__), 'css_viewlet.pt') klass = type('CSSViewlet', (CSSResourceViewletBase, ViewletBase), {'index': ViewPageTemplateFile(src), '_path': path, '_media':media, '_rel':rel}) return klass class ResourceBundleViewletBase(object): """A simple viewlet for inserting references to different resources. This is an abstract class that is expected to be used as a base only. """ _paths = None def getResources(self): resources = [] append = resources.append for path in self._paths: append(api.traverse(self.context, '++resource++' + path, request=self.request)) return resources def render(self, *args, **kw): return self.index(*args, **kw) def JavaScriptBundleViewlet(paths): """Create a viewlet that can simply insert javascript links.""" src = os.path.join(os.path.dirname(__file__), 'javascript_bundle_viewlet.pt') klass = type('JavaScriptBundleViewlet', (ResourceBundleViewletBase, ViewletBase), {'index': ViewPageTemplateFile(src), '_paths': paths}) return klass class CSSResourceBundleViewletBase(object): """A simple viewlet for inserting css references to different resources. There is a tuple or list of dict used for the different resource descriptions. The list of dict uses the following format: ({path:'the path', media:'all', rel:'stylesheet'},...) The default values for media is ``all`` and the default value for rel is ``stylesheet``. The path must be set there is no default value for the path attribute. This is an abstract class that is expected to be used as a base only. """ _items = None def getResources(self): resources = [] append = resources.append for item in self._items: info = {} info['url'] = api.traverse(self.context, '++resource++' + item.get('path'), request=self.request) info['media'] = item.get('media', 'all') info['rel'] = item.get('rel', 'stylesheet') append(info) return resources def render(self, *args, **kw): return self.index(*args, **kw) def CSSBundleViewlet(items): """Create a viewlet that can simply insert css links.""" src = os.path.join(os.path.dirname(__file__), 'css_bundle_viewlet.pt') klass = type('CSSBundleViewlet', (CSSResourceBundleViewletBase, ViewletBase), {'index': ViewPageTemplateFile(src), '_items': items}) return klass zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/tests.py0000644000175000017500000000557112214017662023042 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Viewlet tests $Id: tests.py 112059 2010-05-05 19:40:35Z tseaver $ """ __docformat__ = 'restructuredtext' import os import doctest import sys import unittest import zope.component from zope.testing import cleanup from zope.traversing.testing import setUp as traversingSetUp from zope.component import eventtesting def setUp(test): cleanup.setUp() eventtesting.setUp() traversingSetUp() # resource namespace setup from zope.traversing.interfaces import ITraversable from zope.traversing.namespace import resource zope.component.provideAdapter( resource, (None,), ITraversable, name = "resource") zope.component.provideAdapter( resource, (None, None), ITraversable, name = "resource") from zope.browserpage import metaconfigure from zope.contentprovider import tales metaconfigure.registerType('provider', tales.TALESProviderExpression) def tearDown(test): cleanup.tearDown() class FakeModule(object): """A fake module.""" def __init__(self, dict): self.__dict = dict def __getattr__(self, name): try: return self.__dict[name] except KeyError: raise AttributeError(name) def directivesSetUp(test): setUp(test) test.globs['__name__'] = 'zope.viewlet.directives' sys.modules['zope.viewlet.directives'] = FakeModule(test.globs) def directivesTearDown(test): tearDown(test) del sys.modules[test.globs['__name__']] test.globs.clear() def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('README.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS, globs = {'__file__': os.path.join( os.path.dirname(__file__), 'README.txt')} ), doctest.DocFileSuite('directives.txt', setUp=directivesSetUp, tearDown=directivesTearDown, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS, globs = {'__file__': os.path.join( os.path.dirname(__file__), 'directives.txt')} ), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/css_viewlet.pt0000644000175000017500000000030512214017662024210 0ustar arnauarnau zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/manager.py0000644000175000017500000001460712214017662023312 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Content Provider Manager implementation $Id: manager.py 112059 2010-05-05 19:40:35Z tseaver $ """ __docformat__ = 'restructuredtext' import zope.component import zope.interface import zope.security import zope.event from zope.browserpage import ViewPageTemplateFile from zope.viewlet import interfaces from zope.location.interfaces import ILocation from zope.contentprovider.interfaces import BeforeUpdateEvent class ViewletManagerBase(object): """The Viewlet Manager Base A generic manager class which can be instantiated """ zope.interface.implements(interfaces.IViewletManager) template = None def __init__(self, context, request, view): self.__updated = False self.__parent__ = view self.context = context self.request = request def __getitem__(self, name): """See zope.interface.common.mapping.IReadMapping""" # Find the viewlet viewlet = zope.component.queryMultiAdapter( (self.context, self.request, self.__parent__, self), interfaces.IViewlet, name=name) # If the viewlet was not found, then raise a lookup error if viewlet is None: raise zope.component.interfaces.ComponentLookupError( 'No provider with name `%s` found.' %name) # If the viewlet cannot be accessed, then raise an # unauthorized error if not zope.security.canAccess(viewlet, 'render'): raise zope.security.interfaces.Unauthorized( 'You are not authorized to access the provider ' 'called `%s`.' %name) # Return the viewlet. return viewlet def get(self, name, default=None): """See zope.interface.common.mapping.IReadMapping""" try: return self[name] except (zope.component.interfaces.ComponentLookupError, zope.security.interfaces.Unauthorized): return default def __contains__(self, name): """See zope.interface.common.mapping.IReadMapping""" return bool(self.get(name, False)) def filter(self, viewlets): """Sort out all content providers ``viewlets`` is a list of tuples of the form (name, viewlet). """ # Only return viewlets accessible to the principal return [(name, viewlet) for name, viewlet in viewlets if zope.security.canAccess(viewlet, 'render')] def sort(self, viewlets): """Sort the viewlets. ``viewlets`` is a list of tuples of the form (name, viewlet). """ # By default, use the standard Python way of doing sorting. return sorted(viewlets, lambda x, y: cmp(x[1], y[1])) def update(self): """See zope.contentprovider.interfaces.IContentProvider""" self.__updated = True # Find all content providers for the region viewlets = zope.component.getAdapters( (self.context, self.request, self.__parent__, self), interfaces.IViewlet) viewlets = self.filter(viewlets) viewlets = self.sort(viewlets) # Just use the viewlets from now on self.viewlets=[] for name, viewlet in viewlets: if ILocation.providedBy(viewlet): viewlet.__name__ = name self.viewlets.append(viewlet) self._updateViewlets() def _updateViewlets(self): """Calls update on all viewlets and fires events""" for viewlet in self.viewlets: zope.event.notify(BeforeUpdateEvent(viewlet, self.request)) viewlet.update() def render(self): """See zope.contentprovider.interfaces.IContentProvider""" # Now render the view if self.template: return self.template(viewlets=self.viewlets) else: return u'\n'.join([viewlet.render() for viewlet in self.viewlets]) def ViewletManager(name, interface, template=None, bases=()): attrDict = {'__name__' : name} if template is not None: attrDict['template'] = ViewPageTemplateFile(template) if ViewletManagerBase not in bases: # Make sure that we do not get a default viewlet manager mixin, if the # provided base is already a full viewlet manager implementation. if not (len(bases) == 1 and interfaces.IViewletManager.implementedBy(bases[0])): bases = bases + (ViewletManagerBase,) ViewletManager = type( '' % interface.getName(), bases, attrDict) zope.interface.classImplements(ViewletManager, interface) return ViewletManager def getWeight((name, viewlet)): try: return int(viewlet.weight) except AttributeError: return 0 class WeightOrderedViewletManager(ViewletManagerBase): """Weight ordered viewlet managers.""" def sort(self, viewlets): return sorted(viewlets, key=getWeight) def render(self): """See zope.contentprovider.interfaces.IContentProvider""" # do not render a manager template if no viewlets are avaiable if not self.viewlets: return u'' elif self.template: return self.template(viewlets=self.viewlets) else: return u'\n'.join([viewlet.render() for viewlet in self.viewlets]) def isAvailable(viewlet): try: return zope.security.canAccess(viewlet, 'render') and viewlet.available except AttributeError: return True class ConditionalViewletManager(WeightOrderedViewletManager): """Conditional weight ordered viewlet managers.""" def filter(self, viewlets): """Sort out all viewlets which are explicit not available ``viewlets`` is a list of tuples of the form (name, viewlet). """ return [(name, viewlet) for name, viewlet in viewlets if isAvailable(viewlet)] zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/meta.zcml0000644000175000017500000000076212214017662023140 0ustar arnauarnau zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/css_bundle_viewlet.pt0000644000175000017500000000036512214017662025547 0ustar arnauarnau zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/javascript_bundle_viewlet.pt0000644000175000017500000000024312214017662027120 0ustar arnauarnau zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/metadirectives.py0000644000175000017500000001421112214017662024677 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Viewlet metadirective $Id: metadirectives.py 112059 2010-05-05 19:40:35Z tseaver $ """ __docformat__ = 'restructuredtext' import zope.configuration.fields import zope.schema from zope.publisher.interfaces import browser from zope.security.zcml import Permission from zope.i18nmessageid import MessageFactory from zope.interface import Interface _ = MessageFactory('zope') from zope.viewlet import interfaces class IContentProvider(Interface): """A directive to register a simple content provider. Content providers are registered by their context (`for` attribute), the request (`layer` attribute) and the view (`view` attribute). They also must provide a name, so that they can be found using the TALES ``provider`` namespace. Other than that, content providers are just like any other views. """ view = zope.configuration.fields.GlobalObject( title=_("The view the content provider is registered for."), description=_("The view can either be an interface or a class. By " "default the provider is registered for all views, " "the most common case."), required=False, default=browser.IBrowserView) name = zope.schema.TextLine( title=_("The name of the content provider."), description=_("The name of the content provider is used in the TALES " "``provider`` namespace to look up the content " "provider."), required=True) for_ = zope.configuration.fields.GlobalObject( title=u"The interface or class this view is for.", required=False ) permission = Permission( title=u"Permission", description=u"The permission needed to use the view.", required=True ) class_ = zope.configuration.fields.GlobalObject( title=_("Class"), description=_("A class that provides attributes used by the view."), required=False, ) layer = zope.configuration.fields.GlobalInterface( title=_("The layer the view is in."), description=_(""" A skin is composed of layers. It is common to put skin specific views in a layer named after the skin. If the 'layer' attribute is not supplied, it defaults to 'default'."""), required=False, ) allowed_interface = zope.configuration.fields.Tokens( title=_("Interface that is also allowed if user has permission."), description=_(""" By default, 'permission' only applies to viewing the view and any possible sub views. By specifying this attribute, you can make the permission also apply to everything described in the supplied interface. Multiple interfaces can be provided, separated by whitespace."""), required=False, value_type=zope.configuration.fields.GlobalInterface(), ) allowed_attributes = zope.configuration.fields.Tokens( title=_("View attributes that are also allowed if the user" " has permission."), description=_(""" By default, 'permission' only applies to viewing the view and any possible sub views. By specifying 'allowed_attributes', you can make the permission also apply to the extra attributes on the view object."""), required=False, value_type=zope.configuration.fields.PythonIdentifier(), ) class ITemplatedContentProvider(IContentProvider): """A directive for registering a content provider that uses a page template to provide its content.""" template = zope.configuration.fields.Path( title=_("Content-generating template."), description=_("Refers to a file containing a page template (should " "end in extension ``.pt`` or ``.html``)."), required=False) class IViewletManagerDirective(ITemplatedContentProvider): """A directive to register a new viewlet manager. Viewlet manager registrations are very similar to content provider registrations, since they are just a simple extension of content providers. However, viewlet managers commonly have a specific provided interface, which is used to discriminate the viewlets they are providing. """ provides = zope.configuration.fields.GlobalInterface( title=_("The interface this viewlet manager provides."), description=_("A viewlet manager can provide an interface, which " "is used to lookup its contained viewlets."), required=False, default=interfaces.IViewletManager, ) class IViewletDirective(ITemplatedContentProvider): """A directive to register a new viewlet. Viewlets are content providers that can only be displayed inside a viewlet manager. Thus they are additionally discriminated by the manager. Viewlets can rely on the specified viewlet manager interface to provide their content. The viewlet directive also supports an undefined set of keyword arguments that are set as attributes on the viewlet after creation. Those attributes can then be used to implement sorting and filtering, for example. """ manager = zope.configuration.fields.GlobalObject( title=_("view"), description=u"The interface of the view this viewlet is for. " u"(default IBrowserView)", required=False, default=interfaces.IViewletManager) # Arbitrary keys and values are allowed to be passed to the viewlet. IViewletDirective.setTaggedValue('keyword_arguments', True) zope2.13-2.13.21/source/zope.viewlet/src/zope/viewlet/javascript_viewlet.pt0000644000175000017500000000015012214017662025564 0ustar arnauarnau zope2.13-2.13.21/source/zope.viewlet/src/zope.viewlet.egg-info/0000755000175000017500000000000012214017662023007 5ustar arnauarnauzope2.13-2.13.21/source/zope.viewlet/src/zope.viewlet.egg-info/PKG-INFO0000644000175000017500000017667212214017662024127 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.viewlet Version: 3.7.2 Summary: Zope Viewlets Home-page: http://pypi.python.org/pypi/zope.viewlet Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Viewlets provide a generic framework for building pluggable user interfaces. Detailed Documentation ********************** ============================= Viewlets and Viewlet Managers ============================= Let's start with some motivation. Using content providers allows us to insert one piece of HTML content. In most Web development, however, you are often interested in defining some sort of region and then allow developers to register content for those regions. >>> from zope.viewlet import interfaces Design Notes ------------ As mentioned above, besides inserting snippets of HTML at places, we more frequently want to define a region in our page and allow specialized content providers to be inserted based on configuration. Those specialized content providers are known as viewlets and are only available inside viewlet managers, which are just a more complex example of content providers. Unfortunately, the Java world does not implement this layer separately. The viewlet manager is most similar to a Java "channel", but we decided against using this name, since it is very generic and not very meaningful. The viewlet has no Java counterpart, since Java does not implement content providers using a component architecture and thus does not register content providers specifically for viewlet managers, which I believe makes the Java implementation less useful as a generic concept. In fact, the main design goal in the Java world is the implementation of reusable and sharable portlets. The scope for Zope 3 is larger, since we want to provide a generic framework for building pluggable user interfaces. The Viewlet Manager ------------------- In this implementation of viewlets, those regions are just content providers called viewlet managers that manage a special type of content providers known as viewlets. Every viewlet manager handles the viewlets registered for it: >>> class ILeftColumn(interfaces.IViewletManager): ... """Viewlet manager located in the left column.""" You can then create a viewlet manager using this interface now: >>> from zope.viewlet import manager >>> LeftColumn = manager.ViewletManager('left', ILeftColumn) Now we have to instantiate it: >>> import zope.interface >>> class Content(object): ... zope.interface.implements(zope.interface.Interface) >>> content = Content() >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> from zope.publisher.interfaces.browser import IBrowserView >>> class View(object): ... zope.interface.implements(IBrowserView) ... def __init__(self, context, request): ... pass >>> view = View(content, request) >>> leftColumn = LeftColumn(content, request, view) So initially nothing gets rendered: >>> leftColumn.update() >>> leftColumn.render() u'' But now we register some viewlets for the manager >>> import zope.component >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer >>> class WeatherBox(object): ... zope.interface.implements(interfaces.IViewlet) ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
    It is sunny today!
    ' >>> # Create a security checker for viewlets. >>> from zope.security.checker import NamesChecker, defineChecker >>> viewletChecker = NamesChecker(('update', 'render')) >>> defineChecker(WeatherBox, viewletChecker) >>> zope.component.provideAdapter( ... WeatherBox, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, ILeftColumn), ... interfaces.IViewlet, name='weather') >>> from zope.location.interfaces import ILocation >>> class SportBox(object): ... zope.interface.implements(interfaces.IViewlet, ... ILocation) ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
    Patriots (23) : Steelers (7)
    ' >>> defineChecker(SportBox, viewletChecker) >>> zope.component.provideAdapter( ... SportBox, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, ILeftColumn), ... interfaces.IViewlet, name='sport') and thus the left column is filled. Note that also events get fired before viewlets are updated. We register a simple handler to demonstrate this behaviour. >>> from zope.contentprovider.interfaces import IBeforeUpdateEvent >>> events = [] >>> def handler(ev): ... events.append(ev) >>> zope.component.provideHandler(handler, (IBeforeUpdateEvent,)) >>> leftColumn.update() >>> [(ev, ev.object.__class__.__name__) for ev in events] [(, 'SportBox'), (, 'WeatherBox')] >>> print leftColumn.render()
    Patriots (23) : Steelers (7)
    It is sunny today!
    But this is of course pretty lame, since there is no way of specifying how the viewlets are put together. But we have a solution. The second argument of the ``ViewletManager()`` function is a template in which we can specify how the viewlets are put together: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> leftColTemplate = os.path.join(temp_dir, 'leftCol.pt') >>> open(leftColTemplate, 'w').write(''' ...
    ... ...
    ... ''') >>> LeftColumn = manager.ViewletManager('left', ILeftColumn, ... template=leftColTemplate) >>> leftColumn = LeftColumn(content, request, view) TODO: Fix this silly thing; viewlets should be directly available. As you can see, the viewlet manager provides a global ``options/viewlets`` variable that is an iterable of all the available viewlets in the correct order: >>> leftColumn.update() >>> print leftColumn.render().strip()
    Patriots (23) : Steelers (7)
    It is sunny today!
    If a viewlet provides ILocation the ``__name__`` attribute of the viewlet is set to the name under which the viewlet is registered. >>> [getattr(viewlet, '__name__', None) for viewlet in leftColumn.viewlets] [u'sport', None] You can also lookup the viewlets directly for management purposes: >>> leftColumn['weather'] >>> leftColumn.get('weather') The viewlet manager also provides the __contains__ method defined in IReadMapping: >>> 'weather' in leftColumn True >>> 'unknown' in leftColumn False If the viewlet is not found, then the expected behavior is provided: >>> leftColumn['stock'] Traceback (most recent call last): ... ComponentLookupError: No provider with name `stock` found. >>> leftColumn.get('stock') is None True Customizing the default Viewlet Manager --------------------------------------- One important feature of any viewlet manager is to be able to filter and sort the viewlets it is displaying. The default viewlet manager that we have been using in the tests above, supports filtering by access availability and sorting via the viewlet's ``__cmp__()`` method (default). You can easily override this default policy by providing a base viewlet manager class. In our case we will manage the viewlets using a global list: >>> shown = ['weather', 'sport'] The viewlet manager base class now uses this list: >>> class ListViewletManager(object): ... ... def filter(self, viewlets): ... viewlets = super(ListViewletManager, self).filter(viewlets) ... return [(name, viewlet) ... for name, viewlet in viewlets ... if name in shown] ... ... def sort(self, viewlets): ... viewlets = dict(viewlets) ... return [(name, viewlets[name]) for name in shown] Let's now create a new viewlet manager: >>> LeftColumn = manager.ViewletManager( ... 'left', ILeftColumn, bases=(ListViewletManager,), ... template=leftColTemplate) >>> leftColumn = LeftColumn(content, request, view) So we get the weather box first and the sport box second: >>> leftColumn.update() >>> print leftColumn.render().strip()
    It is sunny today!
    Patriots (23) : Steelers (7)
    Now let's change the order... >>> shown.reverse() and the order should switch as well: >>> leftColumn.update() >>> print leftColumn.render().strip()
    Patriots (23) : Steelers (7)
    It is sunny today!
    Of course, we also can remove a shown viewlet: >>> weather = shown.pop() >>> leftColumn.update() >>> print leftColumn.render().strip()
    Patriots (23) : Steelers (7)
    WeightOrderedViewletManager --------------------------- The weight ordered viewlet manager offers ordering viewlets by a additional weight argument. Viewlets which doesn't provide a weight attribute will get a weight of 0 (zero). Let's define a new column: >>> class IWeightedColumn(interfaces.IViewletManager): ... """Column with weighted viewlet manager.""" First register a template for the weight ordered viewlet manager: >>> weightedColTemplate = os.path.join(temp_dir, 'weightedColTemplate.pt') >>> open(weightedColTemplate, 'w').write(''' ...
    ... ...
    ... ''') And create a new weight ordered viewlet manager: >>> from zope.viewlet.manager import WeightOrderedViewletManager >>> WeightedColumn = manager.ViewletManager( ... 'left', IWeightedColumn, bases=(WeightOrderedViewletManager,), ... template=weightedColTemplate) >>> weightedColumn = WeightedColumn(content, request, view) Let's create some viewlets: >>> from zope.viewlet import viewlet >>> class FirstViewlet(viewlet.ViewletBase): ... ... weight = 1 ... ... def render(self): ... return u'
    first
    ' >>> class SecondViewlet(viewlet.ViewletBase): ... ... weight = 2 ... ... def render(self): ... return u'
    second
    ' >>> class ThirdViewlet(viewlet.ViewletBase): ... ... weight = 3 ... ... def render(self): ... return u'
    third
    ' >>> class UnWeightedViewlet(viewlet.ViewletBase): ... ... def render(self): ... return u'
    unweighted
    ' >>> defineChecker(FirstViewlet, viewletChecker) >>> defineChecker(SecondViewlet, viewletChecker) >>> defineChecker(ThirdViewlet, viewletChecker) >>> defineChecker(UnWeightedViewlet, viewletChecker) >>> zope.component.provideAdapter( ... ThirdViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='third') >>> zope.component.provideAdapter( ... FirstViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='first') >>> zope.component.provideAdapter( ... SecondViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='second') >>> zope.component.provideAdapter( ... UnWeightedViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IWeightedColumn), ... interfaces.IViewlet, name='unweighted') And check the order: >>> weightedColumn.update() >>> print weightedColumn.render().strip()
    unweighted
    first
    second
    third
    ConditionalViewletManager ------------------------- The conditional ordered viewlet manager offers ordering viewlets by a additional weight argument and filters by the available attribute if a supported by the viewlet. Viewlets which doesn't provide a available attribute will not get skipped. The default weight value for viewlets which doesn't provide a weight attribute is 0 (zero). Let's define a new column: >>> class IConditionalColumn(interfaces.IViewletManager): ... """Column with weighted viewlet manager.""" First register a template for the weight ordered viewlet manager: >>> conditionalColTemplate = os.path.join(temp_dir, ... 'conditionalColTemplate.pt') >>> open(conditionalColTemplate, 'w').write(''' ...
    ... ...
    ... ''') And create a new conditional viewlet manager: >>> from zope.viewlet.manager import ConditionalViewletManager >>> ConditionalColumn = manager.ViewletManager( ... 'left', IConditionalColumn, bases=(ConditionalViewletManager,), ... template=conditionalColTemplate) >>> conditionalColumn = ConditionalColumn(content, request, view) Let's create some viewlets. We also use the previous viewlets supporting no weight and or no available attribute: >>> from zope.viewlet import viewlet >>> class AvailableViewlet(viewlet.ViewletBase): ... ... weight = 4 ... ... available = True ... ... def render(self): ... return u'
    available
    ' >>> class UnAvailableViewlet(viewlet.ViewletBase): ... ... weight = 5 ... ... available = False ... ... def render(self): ... return u'
    not available
    ' >>> defineChecker(AvailableViewlet, viewletChecker) >>> defineChecker(UnAvailableViewlet, viewletChecker) >>> zope.component.provideAdapter( ... ThirdViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='third') >>> zope.component.provideAdapter( ... FirstViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='first') >>> zope.component.provideAdapter( ... SecondViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='second') >>> zope.component.provideAdapter( ... UnWeightedViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='unweighted') >>> zope.component.provideAdapter( ... AvailableViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='available') >>> zope.component.provideAdapter( ... UnAvailableViewlet, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, IConditionalColumn), ... interfaces.IViewlet, name='unavailable') And check the order: >>> conditionalColumn.update() >>> print conditionalColumn.render().strip()
    unweighted
    first
    second
    third
    available
    Viewlet Base Classes -------------------- To make the creation of viewlets simpler, a set of useful base classes and helper functions are provided. The first class is a base class that simply defines the constructor: >>> base = viewlet.ViewletBase('context', 'request', 'view', 'manager') >>> base.context 'context' >>> base.request 'request' >>> base.__parent__ 'view' >>> base.manager 'manager' But a default ``render()`` method implementation is not provided: >>> base.render() Traceback (most recent call last): ... NotImplementedError: `render` method must be implemented by subclass. If you have already an existing class that produces the HTML content in some method, then the ``SimpleAttributeViewlet`` might be for you, since it can be used to convert any class quickly into a viewlet: >>> class FooViewlet(viewlet.SimpleAttributeViewlet): ... __page_attribute__ = 'foo' ... ... def foo(self): ... return 'output' The `__page_attribute__` attribute provides the name of the function to call for rendering. >>> foo = FooViewlet('context', 'request', 'view', 'manager') >>> foo.foo() 'output' >>> foo.render() 'output' If you specify `render` as the attribute an error is raised to prevent infinite recursion: >>> foo.__page_attribute__ = 'render' >>> foo.render() Traceback (most recent call last): ... AttributeError: render The same is true if the specified attribute does not exist: >>> foo.__page_attribute__ = 'bar' >>> foo.render() Traceback (most recent call last): ... AttributeError: 'FooViewlet' object has no attribute 'bar' To create simple template-based viewlets you can use the ``SimpleViewletClass()`` function. This function is very similar to its view equivalent and is used by the ZCML directives to create viewlets. The result of this function call will be a fully functional viewlet class. Let's start by simply specifying a template only: >>> template = os.path.join(temp_dir, 'demoTemplate.pt') >>> open(template, 'w').write('''
    contents
    ''') >>> Demo = viewlet.SimpleViewletClass(template) >>> print Demo(content, request, view, manager).render()
    contents
    Now let's additionally specify a class that can provide additional features: >>> class MyViewlet(object): ... myAttribute = 8 >>> Demo = viewlet.SimpleViewletClass(template, bases=(MyViewlet,)) >>> MyViewlet in Demo.__bases__ True >>> Demo(content, request, view, manager).myAttribute 8 The final important feature is the ability to pass in further attributes to the class: >>> Demo = viewlet.SimpleViewletClass( ... template, attributes={'here': 'now', 'lucky': 3}) >>> demo = Demo(content, request, view, manager) >>> demo.here 'now' >>> demo.lucky 3 As for all views, they must provide a name that can also be passed to the function: >>> Demo = viewlet.SimpleViewletClass(template, name='demoViewlet') >>> demo = Demo(content, request, view, manager) >>> demo.__name__ 'demoViewlet' In addition to the the generic viewlet code above, the package comes with two viewlet base classes and helper functions for inserting CSS and Javascript links into HTML headers, since those two are so very common. I am only going to demonstrate the helper functions here, since those demonstrations will fully demonstrate the functionality of the base classes as well. The viewlet will look up the resource it was given and tries to produce the absolute URL for it: >>> class JSResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/resource.js' >>> zope.component.provideAdapter( ... JSResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='resource.js') >>> JSViewlet = viewlet.JavaScriptViewlet('resource.js') >>> print JSViewlet(content, request, view, manager).render().strip() There is also a javascript viewlet base class which knows how to render more then one javascript resource file: >>> class JSSecondResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/second-resource.js' >>> zope.component.provideAdapter( ... JSSecondResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='second-resource.js') >>> JSBundleViewlet = viewlet.JavaScriptBundleViewlet(('resource.js', ... 'second-resource.js')) >>> print JSBundleViewlet(content, request, view, manager).render().strip() The same works for the CSS resource viewlet: >>> class CSSResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/resource.css' >>> zope.component.provideAdapter( ... CSSResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='resource.css') >>> CSSViewlet = viewlet.CSSViewlet('resource.css') >>> print CSSViewlet(content, request, view, manager).render().strip() You can also change the media type and the rel attribute: >>> CSSViewlet = viewlet.CSSViewlet('resource.css', media='print', rel='css') >>> print CSSViewlet(content, request, view, manager).render().strip() There is also a bundle viewlet for CSS links: >>> class CSSPrintResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/print-resource.css' >>> zope.component.provideAdapter( ... CSSPrintResource, ... (IDefaultBrowserLayer,), ... zope.interface.Interface, name='print-resource.css') >>> items = [] >>> items.append({'path':'resource.css', 'rel':'stylesheet', 'media':'all'}) >>> items.append({'path':'print-resource.css', 'media':'print'}) >>> CSSBundleViewlet = viewlet.CSSBundleViewlet(items) >>> print CSSBundleViewlet(content, request, view, manager).render().strip() A Complex Example ----------------- The Data ~~~~~~~~ So far we have only demonstrated simple (maybe overly trivial) use cases of the viewlet system. In the following example, we are going to develop a generic contents view for files. The step is to create a file component: >>> class IFile(zope.interface.Interface): ... data = zope.interface.Attribute('Data of file.') >>> class File(object): ... zope.interface.implements(IFile) ... def __init__(self, data=''): ... self.__name__ = '' ... self.data = data Since we want to also provide the size of a file, here a simple implementation of the ``ISized`` interface: >>> from zope import size >>> class FileSized(object): ... zope.interface.implements(size.interfaces.ISized) ... zope.component.adapts(IFile) ... ... def __init__(self, file): ... self.file = file ... ... def sizeForSorting(self): ... return 'byte', len(self.file.data) ... ... def sizeForDisplay(self): ... return '%i bytes' %len(self.file.data) >>> zope.component.provideAdapter(FileSized) We also need a container to which we can add files: >>> class Container(dict): ... def __setitem__(self, name, value): ... value.__name__ = name ... super(Container, self).__setitem__(name, value) Here is some sample data: >>> container = Container() >>> container['test.txt'] = File('Hello World!') >>> container['mypage.html'] = File('Hello World!') >>> container['data.xml'] = File('Hello World!') The View ~~~~~~~~ The contents view of the container should iterate through the container and represent the files in a table: >>> contentsTemplate = os.path.join(temp_dir, 'contents.pt') >>> open(contentsTemplate, 'w').write(''' ... ... ...

    Contents

    ...
    ... ... ... ''') >>> from zope.browserpage.simpleviewclass import SimpleViewClass >>> Contents = SimpleViewClass(contentsTemplate, name='contents.html') The Viewlet Manager ~~~~~~~~~~~~~~~~~~~ Now we have to write our own viewlet manager. In this case we cannot use the default implementation, since the viewlets will be looked up for each different item: >>> shownColumns = [] >>> class ContentsViewletManager(object): ... zope.interface.implements(interfaces.IViewletManager) ... index = None ... ... def __init__(self, context, request, view): ... self.context = context ... self.request = request ... self.__parent__ = view ... ... def update(self): ... rows = [] ... for name, value in self.context.items(): ... rows.append( ... [zope.component.getMultiAdapter( ... (value, self.request, self.__parent__, self), ... interfaces.IViewlet, name=colname) ... for colname in shownColumns]) ... [entry.update() for entry in rows[-1]] ... self.rows = rows ... ... def render(self, *args, **kw): ... return self.index(*args, **kw) Now we need a template to produce the contents table: >>> tableTemplate = os.path.join(temp_dir, 'table.pt') >>> open(tableTemplate, 'w').write(''' ... ... ... ... ...
    ... ...
    ... ''') From the two pieces above, we can generate the final viewlet manager class and register it (it's a bit tedious, I know): >>> from zope.browserpage import ViewPageTemplateFile >>> ContentsViewletManager = type( ... 'ContentsViewletManager', (ContentsViewletManager,), ... {'index': ViewPageTemplateFile(tableTemplate)}) >>> zope.component.provideAdapter( ... ContentsViewletManager, ... (Container, IDefaultBrowserLayer, zope.interface.Interface), ... interfaces.IViewletManager, name='contents') Since we have not defined any viewlets yet, the table is totally empty: >>> contents = Contents(container, request) >>> print contents().strip()

    Contents

    The Viewlets and the Final Result ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now let's create a first viewlet for the manager... >>> class NameViewlet(object): ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... self.context = context ... ... def update(self): ... pass ... ... def render(self): ... return self.context.__name__ and register it: >>> zope.component.provideAdapter( ... NameViewlet, ... (IFile, IDefaultBrowserLayer, ... zope.interface.Interface, interfaces.IViewletManager), ... interfaces.IViewlet, name='name') Note how you register the viewlet on ``IFile`` and not on the container. Now we should be able to see the name for each file in the container: >>> print contents().strip()

    Contents

    Waaa, nothing there! What happened? Well, we have to tell our user preferences that we want to see the name as a column in the table: >>> shownColumns = ['name'] >>> print contents().strip()

    Contents

    mypage.html
    data.xml
    test.txt
    Let's now write a second viewlet that will display the size of the object for us: >>> class SizeViewlet(object): ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... self.context = context ... ... def update(self): ... pass ... ... def render(self): ... return size.interfaces.ISized(self.context).sizeForDisplay() >>> zope.component.provideAdapter( ... SizeViewlet, ... (IFile, IDefaultBrowserLayer, ... zope.interface.Interface, interfaces.IViewletManager), ... interfaces.IViewlet, name='size') After we added it to the list of shown columns, >>> shownColumns = ['name', 'size'] we can see an entry for it: >>> print contents().strip()

    Contents

    mypage.html 38 bytes
    data.xml 31 bytes
    test.txt 12 bytes
    If we switch the two columns around, >>> shownColumns = ['size', 'name'] the result will be >>> print contents().strip()

    Contents

    38 bytes mypage.html
    31 bytes data.xml
    12 bytes test.txt
    Supporting Sorting ~~~~~~~~~~~~~~~~~~ Oftentimes you also want to batch and sort the entries in a table. Since those two features are not part of the view logic, they should be treated with independent components. In this example, we are going to only implement sorting using a simple utility: >>> class ISorter(zope.interface.Interface): ... ... def sort(values): ... """Sort the values.""" >>> class SortByName(object): ... zope.interface.implements(ISorter) ... ... def sort(self, values): ... return sorted(values, lambda x, y: cmp(x.__name__, y.__name__)) >>> zope.component.provideUtility(SortByName(), name='name') >>> class SortBySize(object): ... zope.interface.implements(ISorter) ... ... def sort(self, values): ... return sorted( ... values, ... lambda x, y: cmp(size.interfaces.ISized(x).sizeForSorting(), ... size.interfaces.ISized(y).sizeForSorting())) >>> zope.component.provideUtility(SortBySize(), name='size') Note that we decided to give the sorter utilities the same name as the corresponding viewlet. This convention will make our implementation of the viewlet manager much simpler: >>> sortByColumn = '' >>> class SortedContentsViewletManager(object): ... zope.interface.implements(interfaces.IViewletManager) ... index = None ... ... def __init__(self, context, request, view): ... self.context = context ... self.request = request ... self.__parent__ = view ... ... def update(self): ... values = self.context.values() ... ... if sortByColumn: ... sorter = zope.component.queryUtility(ISorter, sortByColumn) ... if sorter: ... values = sorter.sort(values) ... ... rows = [] ... for value in values: ... rows.append( ... [zope.component.getMultiAdapter( ... (value, self.request, self.__parent__, self), ... interfaces.IViewlet, name=colname) ... for colname in shownColumns]) ... [entry.update() for entry in rows[-1]] ... self.rows = rows ... ... def render(self, *args, **kw): ... return self.index(*args, **kw) As you can see, the concern of sorting is cleanly separated from generating the view code. In MVC terms that means that the controller (sort) is logically separated from the view (viewlets). Let's now do the registration dance for the new viewlet manager. We simply override the existing registration: >>> SortedContentsViewletManager = type( ... 'SortedContentsViewletManager', (SortedContentsViewletManager,), ... {'index': ViewPageTemplateFile(tableTemplate)}) >>> zope.component.provideAdapter( ... SortedContentsViewletManager, ... (Container, IDefaultBrowserLayer, zope.interface.Interface), ... interfaces.IViewletManager, name='contents') Finally we sort the contents by name: >>> shownColumns = ['name', 'size'] >>> sortByColumn = 'name' >>> print contents().strip()

    Contents

    data.xml 31 bytes
    mypage.html 38 bytes
    test.txt 12 bytes
    Now let's sort by size: >>> sortByColumn = 'size' >>> print contents().strip()

    Contents

    test.txt 12 bytes
    data.xml 31 bytes
    mypage.html 38 bytes
    That's it! As you can see, in a few steps we have built a pretty flexible contents view with selectable columns and sorting. However, there is a lot of room for extending this example: - Table Header: The table header cell for each column should be a different type of viewlet, but registered under the same name. The column header viewlet also adapts the container not the item. The header column should also be able to control the sorting. - Batching: A simple implementation of batching should work very similar to the sorting feature. Of course, efficient implementations should somehow combine batching and sorting more effectively. - Sorting in ascending and descending order: Currently, you can only sort from the smallest to the highest value; however, this limitation is almost superficial and can easily be removed by making the sorters a bit more flexible. - Further Columns: For a real application, you would want to implement other columns, of course. You would also probably want some sort of fallback for the case that a viewlet is not found for a particular container item and column. Cleanup ------- >>> import shutil >>> shutil.rmtree(temp_dir) ================================ The ``viewletManager`` Directive ================================ The ``viewletManager`` directive allows you to quickly register a new viewlet manager without worrying about the details of the ``adapter`` directive. Before we can use the directives, we have to register their handlers by executing the package's meta configuration: >>> from zope.configuration import xmlconfig >>> context = xmlconfig.string(''' ... ... ... ... ''') Now we can register a viewlet manager: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Let's make sure the directive has really issued a sensible adapter registration; to do that, we create some dummy content, request and view objects: >>> import zope.interface >>> class Content(object): ... zope.interface.implements(zope.interface.Interface) >>> content = Content() >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> from zope.publisher.browser import BrowserView >>> view = BrowserView(content, request) Now let's lookup the manager. This particular registration is pretty boring: >>> import zope.component >>> from zope.viewlet import interfaces >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ... interfaces.IViewletManager, name='defaultmanager') >>> manager object ...> >>> interfaces.IViewletManager.providedBy(manager) True >>> manager.template is None True >>> manager.update() >>> manager.render() u'' However, this registration is not very useful, since we did specify a specific viewlet manager interface, a specific content interface, specific view or specific layer. This means that all viewlets registered will be found. The first step to effectively using the viewlet manager directive is to define a special viewlet manager interface: >>> class ILeftColumn(interfaces.IViewletManager): ... """Left column of my page.""" Now we can register register a manager providing this interface: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> ILeftColumn.providedBy(manager) True >>> manager.template is None True >>> manager.update() >>> manager.render() u'' Next let's see what happens, if we specify a template for the viewlet manager: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> leftColumnTemplate = os.path.join(temp_dir, 'leftcolumn.pt') >>> open(leftColumnTemplate, 'w').write(''' ...
    ...
    ...
    ... ''') >>> context = xmlconfig.string(''' ... ... ... ... ''' %leftColumnTemplate, context=context) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> ILeftColumn.providedBy(manager) True >>> manager.template ...>> >>> manager.update() >>> print manager.render().strip()
    Additionally you can specify a class that will serve as a base to the default viewlet manager or be a viewlet manager in its own right. In our case we will provide a custom implementation of the ``sort()`` method, which will sort by a weight attribute in the viewlet: >>> class WeightBasedSorting(object): ... def sort(self, viewlets): ... return sorted(viewlets, ... lambda x, y: cmp(x[1].weight, y[1].weight)) >>> context = xmlconfig.string(''' ... ... ... ... ''' %leftColumnTemplate, context=context) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> manager.__class__.__bases__ (, ) >>> ILeftColumn.providedBy(manager) True >>> manager.template ...>> >>> manager.update() >>> print manager.render().strip()
    Finally, if a non-existent template is specified, an error is raised: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-7.8 ConfigurationError: ('No such file', '...foo.pt') ========================= The ``viewlet`` Directive ========================= Now that we have a viewlet manager, we have to register some viewlets for it. The ``viewlet`` directive is similar to the ``viewletManager`` directive, except that the viewlet is also registered for a particular manager interface, as seen below: >>> weatherTemplate = os.path.join(temp_dir, 'weather.pt') >>> open(weatherTemplate, 'w').write(''' ...
    sunny
    ... ''') >>> context = xmlconfig.string(''' ... ... ... ... ''' % weatherTemplate, context=context) If we look into the adapter registry, we will find the viewlet: >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='weather') >>> viewlet.render().strip() u'
    sunny
    ' >>> viewlet.extra_string_attributes u'can be specified' The manager now also gives us the output of the one and only viewlet: >>> manager.update() >>> print manager.render().strip()
    sunny
    Let's now ensure that we can also specify a viewlet class: >>> class Weather(object): ... weight = 0 >>> context = xmlconfig.string(''' ... ... ... ... ''' % weatherTemplate, context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='weather2') >>> viewlet().strip() u'
    sunny
    ' Okay, so the template-driven cases work. But just specifying a class should also work: >>> class Sport(object): ... weight = 0 ... def __call__(self): ... return u'Red Sox vs. White Sox' >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, name='sport') >>> viewlet() u'Red Sox vs. White Sox' It should also be possible to specify an alternative attribute of the class to be rendered upon calling the viewlet: >>> class Stock(object): ... weight = 0 ... def getStockTicker(self): ... return u'SRC $5.19' >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='stock') >>> viewlet.render() u'SRC $5.19' A final feature the ``viewlet`` directive is that it supports the specification of any number of keyword arguments: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='stock2') >>> viewlet.weight u'8' Error Scenarios --------------- Neither the class or template have been specified: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-7.8 ConfigurationError: Must specify a class or template The specified attribute is not ``__call__``, but also a template has been specified: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-9.8 ConfigurationError: Attribute and template cannot be used together. Now, we are not specifying a template, but a class that does not have the specified attribute: >>> context = xmlconfig.string(''' ... ... ... ... ''', context=context) Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-9.8 ConfigurationError: The provided class doesn't have the specified attribute Cleanup ------- >>> import shutil >>> shutil.rmtree(temp_dir) ======= CHANGES ======= 3.7.2 (2010-05-25) ------------------ - Fixed unit tests broken under Python 2.4 by the switch to the standard library ``doctest`` module. 3.7.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctest' in favor of stdlib's 'doctest. - Fixed dubious quoting in metadirectives.py. Closes https://bugs.launchpad.net/zope2/+bug/143774. 3.7.0 (2009-12-22) ------------------ - Depend on zope.browserpage in favor of zope.app.pagetemplate. 3.6.1 (2009-08-29) ------------------ - Fixed unit tests in README.txt. 3.6.0 (2009-08-02) ------------------ - Optimize the the script tag for the JS viewlet. This makes YSlow happy. - Remove ZCML slugs and old zpkg-related files. - Drop all testing dependncies except ``zope.testing``. 3.5.0 (2009-01-26) ------------------ - Removed the dependency on `zope.app.publisher` by moving four simple helper functions into this package and making the interface for describing the ZCML content provider directive explicit. - Typo fix in CSSViewlet docstring. 3.4.2 (2008-01-24) ------------------ - Re-release of 3.4.1 because of brown bag release. 3.4.1 (2008-01-21) ------------------ - bugfix, implemented missing __contains__ method in IViewletManager - implemented additional viewlet managers offering weight ordered sorting - implemented additional viewlet managers offering conditional filtering 3.4.1a (2007-4-22) ------------------ - bugfix, added a missing ',' behind zope.i18nmessageid. - recreated the README.txt removing everything except for the overview. 3.4.0 (2007-10-10) ------------------ - Initial release independent of the main Zope tree. Keywords: zope web html ui viewlet pattern Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.viewlet/src/zope.viewlet.egg-info/dependency_links.txt0000644000175000017500000000000112214017662027055 0ustar arnauarnau zope2.13-2.13.21/source/zope.viewlet/src/zope.viewlet.egg-info/requires.txt0000644000175000017500000000035512214017662025412 0ustar arnauarnausetuptools zope.browserpage>=3.10.1 zope.component zope.configuration zope.contentprovider zope.event zope.i18nmessageid zope.interface zope.location zope.publisher zope.schema zope.security zope.traversing [test] zope.testing zope.sizezope2.13-2.13.21/source/zope.viewlet/src/zope.viewlet.egg-info/namespace_packages.txt0000644000175000017500000000000512214017662027335 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.viewlet/src/zope.viewlet.egg-info/top_level.txt0000644000175000017500000000000512214017662025534 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.viewlet/src/zope.viewlet.egg-info/SOURCES.txt0000644000175000017500000000163112214017662024674 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.viewlet.egg-info/PKG-INFO src/zope.viewlet.egg-info/SOURCES.txt src/zope.viewlet.egg-info/dependency_links.txt src/zope.viewlet.egg-info/namespace_packages.txt src/zope.viewlet.egg-info/not-zip-safe src/zope.viewlet.egg-info/requires.txt src/zope.viewlet.egg-info/top_level.txt src/zope/viewlet/README.txt src/zope/viewlet/__init__.py src/zope/viewlet/communicating-viewlets.txt src/zope/viewlet/configure.zcml src/zope/viewlet/css_bundle_viewlet.pt src/zope/viewlet/css_viewlet.pt src/zope/viewlet/directives.txt src/zope/viewlet/interfaces.py src/zope/viewlet/javascript_bundle_viewlet.pt src/zope/viewlet/javascript_viewlet.pt src/zope/viewlet/manager.py src/zope/viewlet/meta.zcml src/zope/viewlet/metaconfigure.py src/zope/viewlet/metadirectives.py src/zope/viewlet/tests.py src/zope/viewlet/viewlet.pyzope2.13-2.13.21/source/zope.viewlet/src/zope.viewlet.egg-info/not-zip-safe0000644000175000017500000000000112214017662025235 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/0000755000175000017500000000000012214017644015174 5ustar arnauarnauzope2.13-2.13.21/source/zope.tal/setup.py0000644000175000017500000000621612214017644016713 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.tal package $Id: setup.py 105409 2009-10-31 17:13:27Z fretin $ """ import os import sys from setuptools import setup, find_packages here = os.path.dirname(__file__) def read(*rnames): return open(os.path.join(here, *rnames)).read() def alltests(): # use the zope.testing testrunner machinery to find all the # test suites we've put under ourselves from zope.testing.testrunner import get_options from zope.testing.testrunner import find_suites from zope.testing.testrunner import configure_logging configure_logging() from unittest import TestSuite here = os.path.abspath(os.path.dirname(sys.argv[0])) args = sys.argv[:] src = os.path.join(here, 'src') defaults = ['--test-path', src] options = get_options(args, defaults) suites = list(find_suites(options)) return TestSuite(suites) setup(name='zope.tal', version = '3.5.2', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', description='Zope 3 Template Application Languate (TAL)', long_description=( read('README.txt') + '\n\n' + read('CHANGES.txt') ), keywords = "zope3 template xml tal", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.tal', license='ZPL 2.1', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope'], extras_require = dict( test=['zope.testing', ]), test_suite="__main__.alltests", # to support "setup.py test" tests_require = ['zope.testing'], install_requires=['setuptools', 'zope.i18nmessageid', 'zope.interface', ], include_package_data = True, zip_safe = False, ) zope2.13-2.13.21/source/zope.tal/PKG-INFO0000644000175000017500000000643112214017644016275 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.tal Version: 3.5.2 Summary: Zope 3 Template Application Languate (TAL) Home-page: http://pypi.python.org/pypi/zope.tal Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Template Attribute Language (TAL) ================================= Overview -------- The Zope3 Template Attribute Languate (TAL) specifies the custom namespace and attributes which are used by the Zope Page Templates renderer to inject dynamic markup into a page. It also includes the Macro Expansion for TAL (METAL) macro language used in page assembly. The dynamic values themselves are specified using a companion language, TALES (see the 'zope.tales' package for more). See: http://www.zope.org/Wikis/DevSite/Projects/ZPT/TAL%20Specification%201.4 ======= CHANGES ======= 3.5.2 (2009-10-31) ------------------ - In talgettext.POEngine.translate, print a warning if a msgid already exists in the domain with a different default. 3.5.1 (2009-03-08) ------------------ - Updated tests of "bad" entities for compatibility with the stricter HTMLParser module shipped with Python 2.6.x. 3.5.0 (2008-06-06) ------------------ - Removed artificial addition of a trailing newline if the output doesn't end in one; this allows the template source to be the full specification of what should be included. (See https://bugs.launchpad.net/launchpad/+bug/218706.) 3.4.1 (2007-11-16) ------------------ - Removed unnecessary ``dummyengine`` dependency on zope.i18n to simplify distribution. The ``dummyengine.DummyTranslationDomain`` class no longer implements ``zope.i18n.interfaces.ITranslationDomain`` as a result. Installing zope.tal with easy_install or buildout no longer pulls in many unrelated distributions. - Support ability to run tests using "setup.py test". - Stop pinning (no longer required) zope.traversing and zope.app.publisher versions in buildout.cfg. 3.4.0 (2007-10-03) ------------------ - Updated package meta-data. 3.4.0b1 ------- - Updated dependency for ``zope.i18n`` that requires the correct version of zope.security to avoid a hidden dependency issue in zope.security. Note: The code changes before 3.4.0b1 where not tracked as an individual package and have been documented in the Zope 3 changelog. Keywords: zope3 template xml tal Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.tal/pip-egg-info/0000755000175000017500000000000012214017644017455 5ustar arnauarnauzope2.13-2.13.21/source/zope.tal/pip-egg-info/zope.tal.egg-info/0000755000175000017500000000000012214017644022703 5ustar arnauarnauzope2.13-2.13.21/source/zope.tal/pip-egg-info/zope.tal.egg-info/PKG-INFO0000644000175000017500000000646112214017644024007 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.tal Version: 3.5.2 Summary: Zope 3 Template Application Languate (TAL) Home-page: http://pypi.python.org/pypi/zope.tal Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Template Attribute Language (TAL) ================================= Overview -------- The Zope3 Template Attribute Languate (TAL) specifies the custom namespace and attributes which are used by the Zope Page Templates renderer to inject dynamic markup into a page. It also includes the Macro Expansion for TAL (METAL) macro language used in page assembly. The dynamic values themselves are specified using a companion language, TALES (see the 'zope.tales' package for more). See: http://www.zope.org/Wikis/DevSite/Projects/ZPT/TAL%20Specification%201.4 ======= CHANGES ======= 3.5.2 (2009-10-31) ------------------ - In talgettext.POEngine.translate, print a warning if a msgid already exists in the domain with a different default. 3.5.1 (2009-03-08) ------------------ - Updated tests of "bad" entities for compatibility with the stricter HTMLParser module shipped with Python 2.6.x. 3.5.0 (2008-06-06) ------------------ - Removed artificial addition of a trailing newline if the output doesn't end in one; this allows the template source to be the full specification of what should be included. (See https://bugs.launchpad.net/launchpad/+bug/218706.) 3.4.1 (2007-11-16) ------------------ - Removed unnecessary ``dummyengine`` dependency on zope.i18n to simplify distribution. The ``dummyengine.DummyTranslationDomain`` class no longer implements ``zope.i18n.interfaces.ITranslationDomain`` as a result. Installing zope.tal with easy_install or buildout no longer pulls in many unrelated distributions. - Support ability to run tests using "setup.py test". - Stop pinning (no longer required) zope.traversing and zope.app.publisher versions in buildout.cfg. 3.4.0 (2007-10-03) ------------------ - Updated package meta-data. 3.4.0b1 ------- - Updated dependency for ``zope.i18n`` that requires the correct version of zope.security to avoid a hidden dependency issue in zope.security. Note: The code changes before 3.4.0b1 where not tracked as an individual package and have been documented in the Zope 3 changelog. Keywords: zope3 template xml tal Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.tal/pip-egg-info/zope.tal.egg-info/dependency_links.txt0000644000175000017500000000000112214017644026751 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/pip-egg-info/zope.tal.egg-info/requires.txt0000644000175000017500000000010112214017644025273 0ustar arnauarnausetuptools zope.i18nmessageid zope.interface [test] zope.testingzope2.13-2.13.21/source/zope.tal/pip-egg-info/zope.tal.egg-info/namespace_packages.txt0000644000175000017500000000000512214017644027231 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.tal/pip-egg-info/zope.tal.egg-info/top_level.txt0000644000175000017500000000000512214017644025430 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.tal/pip-egg-info/zope.tal.egg-info/SOURCES.txt0000644000175000017500000000237612214017644024577 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.tal.egg-info/PKG-INFO pip-egg-info/zope.tal.egg-info/SOURCES.txt pip-egg-info/zope.tal.egg-info/dependency_links.txt pip-egg-info/zope.tal.egg-info/namespace_packages.txt pip-egg-info/zope.tal.egg-info/not-zip-safe pip-egg-info/zope.tal.egg-info/requires.txt pip-egg-info/zope.tal.egg-info/top_level.txt src/zope/__init__.py src/zope/tal/__init__.py src/zope/tal/driver.py src/zope/tal/dummyengine.py src/zope/tal/htmltalparser.py src/zope/tal/interfaces.py src/zope/tal/ndiff.py src/zope/tal/runtest.py src/zope/tal/setpath.py src/zope/tal/taldefs.py src/zope/tal/talgenerator.py src/zope/tal/talgettext.py src/zope/tal/talinterpreter.py src/zope/tal/talparser.py src/zope/tal/timer.py src/zope/tal/translationcontext.py src/zope/tal/xmlparser.py src/zope/tal/benchmark/__init__.py src/zope/tal/tests/__init__.py src/zope/tal/tests/markbench.py src/zope/tal/tests/run.py src/zope/tal/tests/test_files.py src/zope/tal/tests/test_htmltalparser.py src/zope/tal/tests/test_sourcepos.py src/zope/tal/tests/test_talgettext.py src/zope/tal/tests/test_talinterpreter.py src/zope/tal/tests/test_talparser.py src/zope/tal/tests/test_xmlparser.py src/zope/tal/tests/utils.py src/zope/tal/tests/input/__init__.py src/zope/tal/tests/output/__init__.pyzope2.13-2.13.21/source/zope.tal/pip-egg-info/zope.tal.egg-info/not-zip-safe0000644000175000017500000000000112214017644025131 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/README.txt0000644000175000017500000000105612214017644016674 0ustar arnauarnauTemplate Attribute Language (TAL) ================================= Overview -------- The Zope3 Template Attribute Languate (TAL) specifies the custom namespace and attributes which are used by the Zope Page Templates renderer to inject dynamic markup into a page. It also includes the Macro Expansion for TAL (METAL) macro language used in page assembly. The dynamic values themselves are specified using a companion language, TALES (see the 'zope.tales' package for more). See: http://www.zope.org/Wikis/DevSite/Projects/ZPT/TAL%20Specification%201.4 zope2.13-2.13.21/source/zope.tal/setup.cfg0000644000175000017500000000007312214017644017015 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.tal/buildout.cfg0000644000175000017500000000013212214017644017500 0ustar arnauarnau[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = zope.tal zope2.13-2.13.21/source/zope.tal/bootstrap.py0000644000175000017500000000337212214017644017570 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 73407 2007-03-21 05:28:02Z baijum $ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.tal/CHANGES.txt0000644000175000017500000000275012214017644017011 0ustar arnauarnau======= CHANGES ======= 3.5.2 (2009-10-31) ------------------ - In talgettext.POEngine.translate, print a warning if a msgid already exists in the domain with a different default. 3.5.1 (2009-03-08) ------------------ - Updated tests of "bad" entities for compatibility with the stricter HTMLParser module shipped with Python 2.6.x. 3.5.0 (2008-06-06) ------------------ - Removed artificial addition of a trailing newline if the output doesn't end in one; this allows the template source to be the full specification of what should be included. (See https://bugs.launchpad.net/launchpad/+bug/218706.) 3.4.1 (2007-11-16) ------------------ - Removed unnecessary ``dummyengine`` dependency on zope.i18n to simplify distribution. The ``dummyengine.DummyTranslationDomain`` class no longer implements ``zope.i18n.interfaces.ITranslationDomain`` as a result. Installing zope.tal with easy_install or buildout no longer pulls in many unrelated distributions. - Support ability to run tests using "setup.py test". - Stop pinning (no longer required) zope.traversing and zope.app.publisher versions in buildout.cfg. 3.4.0 (2007-10-03) ------------------ - Updated package meta-data. 3.4.0b1 ------- - Updated dependency for ``zope.i18n`` that requires the correct version of zope.security to avoid a hidden dependency issue in zope.security. Note: The code changes before 3.4.0b1 where not tracked as an individual package and have been documented in the Zope 3 changelog. zope2.13-2.13.21/source/zope.tal/src/0000755000175000017500000000000012214017644015763 5ustar arnauarnauzope2.13-2.13.21/source/zope.tal/src/zope/0000755000175000017500000000000012214017644016740 5ustar arnauarnauzope2.13-2.13.21/source/zope.tal/src/zope/tal/0000755000175000017500000000000012214017644017520 5ustar arnauarnauzope2.13-2.13.21/source/zope.tal/src/zope/tal/ndiff.py0000644000175000017500000006120112214017644021160 0ustar arnauarnau#! /usr/bin/env python ############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # Module ndiff version 1.6.0 # Released to the public domain 08-Dec-2000, # by Tim Peters (tim.one@home.com). # Provided as-is; use at your own risk; no warranty; no promises; enjoy! """ndiff [-q] file1 file2 or ndiff (-r1 | -r2) < ndiff_output > file1_or_file2 Print a human-friendly file difference report to stdout. Both inter- and intra-line differences are noted. In the second form, recreate file1 (-r1) or file2 (-r2) on stdout, from an ndiff report on stdin. In the first form, if -q ("quiet") is not specified, the first two lines of output are -: file1 +: file2 Each remaining line begins with a two-letter code: "- " line unique to file1 "+ " line unique to file2 " " line common to both files "? " line not present in either input file Lines beginning with "? " attempt to guide the eye to intraline differences, and were not present in either input file. These lines can be confusing if the source files contain tab characters. The first file can be recovered by retaining only lines that begin with " " or "- ", and deleting those 2-character prefixes; use ndiff with -r1. The second file can be recovered similarly, but by retaining only " " and "+ " lines; use ndiff with -r2; or, on Unix, the second file can be recovered by piping the output through sed -n '/^[+ ] /s/^..//p' See module comments for details and programmatic interface. $Id: ndiff.py 38178 2005-08-30 21:50:19Z mj $ """ __version__ = 1, 5, 0 # SequenceMatcher tries to compute a "human-friendly diff" between # two sequences (chiefly picturing a file as a sequence of lines, # and a line as a sequence of characters, here). Unlike e.g. UNIX(tm) # diff, the fundamental notion is the longest *contiguous* & junk-free # matching subsequence. That's what catches peoples' eyes. The # Windows(tm) windiff has another interesting notion, pairing up elements # that appear uniquely in each sequence. That, and the method here, # appear to yield more intuitive difference reports than does diff. This # method appears to be the least vulnerable to synching up on blocks # of "junk lines", though (like blank lines in ordinary text files, # or maybe "

    " lines in HTML files). That may be because this is # the only method of the 3 that has a *concept* of "junk" . # # Note that ndiff makes no claim to produce a *minimal* diff. To the # contrary, minimal diffs are often counter-intuitive, because they # synch up anywhere possible, sometimes accidental matches 100 pages # apart. Restricting synch points to contiguous matches preserves some # notion of locality, at the occasional cost of producing a longer diff. # # With respect to junk, an earlier version of ndiff simply refused to # *start* a match with a junk element. The result was cases like this: # before: private Thread currentThread; # after: private volatile Thread currentThread; # If you consider whitespace to be junk, the longest contiguous match # not starting with junk is "e Thread currentThread". So ndiff reported # that "e volatil" was inserted between the 't' and the 'e' in "private". # While an accurate view, to people that's absurd. The current version # looks for matching blocks that are entirely junk-free, then extends the # longest one of those as far as possible but only with matching junk. # So now "currentThread" is matched, then extended to suck up the # preceding blank; then "private" is matched, and extended to suck up the # following blank; then "Thread" is matched; and finally ndiff reports # that "volatile " was inserted before "Thread". The only quibble # remaining is that perhaps it was really the case that " volatile" # was inserted after "private". I can live with that . # # NOTE on junk: the module-level names # IS_LINE_JUNK # IS_CHARACTER_JUNK # can be set to any functions you like. The first one should accept # a single string argument, and return true iff the string is junk. # The default is whether the regexp r"\s*#?\s*$" matches (i.e., a # line without visible characters, except for at most one splat). # The second should accept a string of length 1 etc. The default is # whether the character is a blank or tab (note: bad idea to include # newline in this!). # # After setting those, you can call fcompare(f1name, f2name) with the # names of the files you want to compare. The difference report # is sent to stdout. Or you can call main(args), passing what would # have been in sys.argv[1:] had the cmd-line form been used. TRACE = 0 # define what "junk" means import re def IS_LINE_JUNK(line, pat=re.compile(r"\s*#?\s*$").match): return pat(line) is not None def IS_CHARACTER_JUNK(ch, ws=" \t"): return ch in ws del re class SequenceMatcher(object): def __init__(self, isjunk=None, a='', b=''): # Members: # a # first sequence # b # second sequence; differences are computed as "what do # we need to do to 'a' to change it into 'b'?" # b2j # for x in b, b2j[x] is a list of the indices (into b) # at which x appears; junk elements do not appear # b2jhas # b2j.has_key # fullbcount # for x in b, fullbcount[x] == the number of times x # appears in b; only materialized if really needed (used # only for computing quick_ratio()) # matching_blocks # a list of (i, j, k) triples, where a[i:i+k] == b[j:j+k]; # ascending & non-overlapping in i and in j; terminated by # a dummy (len(a), len(b), 0) sentinel # opcodes # a list of (tag, i1, i2, j1, j2) tuples, where tag is # one of # 'replace' a[i1:i2] should be replaced by b[j1:j2] # 'delete' a[i1:i2] should be deleted # 'insert' b[j1:j2] should be inserted # 'equal' a[i1:i2] == b[j1:j2] # isjunk # a user-supplied function taking a sequence element and # returning true iff the element is "junk" -- this has # subtle but helpful effects on the algorithm, which I'll # get around to writing up someday <0.9 wink>. # DON'T USE! Only __chain_b uses this. Use isbjunk. # isbjunk # for x in b, isbjunk(x) == isjunk(x) but much faster; # it's really the has_key method of a hidden dict. # DOES NOT WORK for x in a! self.isjunk = isjunk self.a = self.b = None self.set_seqs(a, b) def set_seqs(self, a, b): self.set_seq1(a) self.set_seq2(b) def set_seq1(self, a): if a is self.a: return self.a = a self.matching_blocks = self.opcodes = None def set_seq2(self, b): if b is self.b: return self.b = b self.matching_blocks = self.opcodes = None self.fullbcount = None self.__chain_b() # For each element x in b, set b2j[x] to a list of the indices in # b where x appears; the indices are in increasing order; note that # the number of times x appears in b is len(b2j[x]) ... # when self.isjunk is defined, junk elements don't show up in this # map at all, which stops the central find_longest_match method # from starting any matching block at a junk element ... # also creates the fast isbjunk function ... # note that this is only called when b changes; so for cross-product # kinds of matches, it's best to call set_seq2 once, then set_seq1 # repeatedly def __chain_b(self): # Because isjunk is a user-defined (not C) function, and we test # for junk a LOT, it's important to minimize the number of calls. # Before the tricks described here, __chain_b was by far the most # time-consuming routine in the whole module! If anyone sees # Jim Roskind, thank him again for profile.py -- I never would # have guessed that. # The first trick is to build b2j ignoring the possibility # of junk. I.e., we don't call isjunk at all yet. Throwing # out the junk later is much cheaper than building b2j "right" # from the start. b = self.b self.b2j = b2j = {} self.b2jhas = b2jhas = b2j.has_key for i in xrange(len(b)): elt = b[i] if b2jhas(elt): b2j[elt].append(i) else: b2j[elt] = [i] # Now b2j.keys() contains elements uniquely, and especially when # the sequence is a string, that's usually a good deal smaller # than len(string). The difference is the number of isjunk calls # saved. isjunk, junkdict = self.isjunk, {} if isjunk: for elt in b2j.keys(): if isjunk(elt): junkdict[elt] = 1 # value irrelevant; it's a set del b2j[elt] # Now for x in b, isjunk(x) == junkdict.has_key(x), but the # latter is much faster. Note too that while there may be a # lot of junk in the sequence, the number of *unique* junk # elements is probably small. So the memory burden of keeping # this dict alive is likely trivial compared to the size of b2j. self.isbjunk = junkdict.has_key def find_longest_match(self, alo, ahi, blo, bhi): """Find longest matching block in a[alo:ahi] and b[blo:bhi]. If isjunk is not defined: Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where alo <= i <= i+k <= ahi blo <= j <= j+k <= bhi and for all (i',j',k') meeting those conditions, k >= k' i <= i' and if i == i', j <= j' In other words, of all maximal matching blocks, return one that starts earliest in a, and of all those maximal matching blocks that start earliest in a, return the one that starts earliest in b. If isjunk is defined, first the longest matching block is determined as above, but with the additional restriction that no junk element appears in the block. Then that block is extended as far as possible by matching (only) junk elements on both sides. So the resulting block never matches on junk except as identical junk happens to be adjacent to an "interesting" match. If no blocks match, return (alo, blo, 0). """ # CAUTION: stripping common prefix or suffix would be incorrect. # E.g., # ab # acab # Longest matching block is "ab", but if common prefix is # stripped, it's "a" (tied with "b"). UNIX(tm) diff does so # strip, so ends up claiming that ab is changed to acab by # inserting "ca" in the middle. That's minimal but unintuitive: # "it's obvious" that someone inserted "ac" at the front. # Windiff ends up at the same place as diff, but by pairing up # the unique 'b's and then matching the first two 'a's. a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.isbjunk besti, bestj, bestsize = alo, blo, 0 # find longest junk-free match # during an iteration of the loop, j2len[j] = length of longest # junk-free match ending with a[i-1] and b[j] j2len = {} nothing = [] for i in xrange(alo, ahi): # look at all instances of a[i] in b; note that because # b2j has no junk keys, the loop is skipped if a[i] is junk j2lenget = j2len.get newj2len = {} for j in b2j.get(a[i], nothing): # a[i] matches b[j] if j < blo: continue if j >= bhi: break k = newj2len[j] = j2lenget(j-1, 0) + 1 if k > bestsize: besti, bestj, bestsize = i-k+1, j-k+1, k j2len = newj2len # Now that we have a wholly interesting match (albeit possibly # empty!), we may as well suck up the matching junk on each # side of it too. Can't think of a good reason not to, and it # saves post-processing the (possibly considerable) expense of # figuring out what to do with it. In the case of an empty # interesting match, this is clearly the right thing to do, # because no other kind of match is possible in the regions. while besti > alo and bestj > blo and \ isbjunk(b[bestj-1]) and \ a[besti-1] == b[bestj-1]: besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 while besti+bestsize < ahi and bestj+bestsize < bhi and \ isbjunk(b[bestj+bestsize]) and \ a[besti+bestsize] == b[bestj+bestsize]: bestsize = bestsize + 1 if TRACE: print "get_matching_blocks", alo, ahi, blo, bhi print " returns", besti, bestj, bestsize return besti, bestj, bestsize def get_matching_blocks(self): if self.matching_blocks is not None: return self.matching_blocks self.matching_blocks = [] la, lb = len(self.a), len(self.b) self.__helper(0, la, 0, lb, self.matching_blocks) self.matching_blocks.append((la, lb, 0)) if TRACE: print '*** matching blocks', self.matching_blocks return self.matching_blocks # builds list of matching blocks covering a[alo:ahi] and # b[blo:bhi], appending them in increasing order to answer def __helper(self, alo, ahi, blo, bhi, answer): i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi) # a[alo:i] vs b[blo:j] unknown # a[i:i+k] same as b[j:j+k] # a[i+k:ahi] vs b[j+k:bhi] unknown if k: if alo < i and blo < j: self.__helper(alo, i, blo, j, answer) answer.append(x) if i+k < ahi and j+k < bhi: self.__helper(i+k, ahi, j+k, bhi, answer) def ratio(self): """Return a measure of the sequences' similarity (float in [0,1]). Where T is the total number of elements in both sequences, and M is the number of matches, this is 2*M / T. Note that this is 1 if the sequences are identical, and 0 if they have nothing in common. """ matches = reduce(lambda sum, triple: sum + triple[-1], self.get_matching_blocks(), 0) return 2.0 * matches / (len(self.a) + len(self.b)) def quick_ratio(self): """Return an upper bound on ratio() relatively quickly.""" # viewing a and b as multisets, set matches to the cardinality # of their intersection; this counts the number of matches # without regard to order, so is clearly an upper bound if self.fullbcount is None: self.fullbcount = fullbcount = {} for elt in self.b: fullbcount[elt] = fullbcount.get(elt, 0) + 1 fullbcount = self.fullbcount # avail[x] is the number of times x appears in 'b' less the # number of times we've seen it in 'a' so far ... kinda avail = {} availhas, matches = avail.has_key, 0 for elt in self.a: if availhas(elt): numb = avail[elt] else: numb = fullbcount.get(elt, 0) avail[elt] = numb - 1 if numb > 0: matches = matches + 1 return 2.0 * matches / (len(self.a) + len(self.b)) def real_quick_ratio(self): """Return an upper bound on ratio() very quickly""" la, lb = len(self.a), len(self.b) # can't have more matches than the number of elements in the # shorter sequence return 2.0 * min(la, lb) / (la + lb) def get_opcodes(self): if self.opcodes is not None: return self.opcodes i = j = 0 self.opcodes = answer = [] for ai, bj, size in self.get_matching_blocks(): # invariant: we've pumped out correct diffs to change # a[:i] into b[:j], and the next matching block is # a[ai:ai+size] == b[bj:bj+size]. So we need to pump # out a diff to change a[i:ai] into b[j:bj], pump out # the matching block, and move (i,j) beyond the match tag = '' if i < ai and j < bj: tag = 'replace' elif i < ai: tag = 'delete' elif j < bj: tag = 'insert' if tag: answer.append((tag, i, ai, j, bj)) i, j = ai+size, bj+size # the list of matching blocks is terminated by a # sentinel with size 0 if size: answer.append(('equal', ai, i, bj, j)) return answer # meant for dumping lines def dump(tag, x, lo, hi): for i in xrange(lo, hi): print tag, x[i], def plain_replace(a, alo, ahi, b, blo, bhi): assert alo < ahi and blo < bhi # dump the shorter block first -- reduces the burden on short-term # memory if the blocks are of very different sizes if bhi - blo < ahi - alo: dump('+', b, blo, bhi) dump('-', a, alo, ahi) else: dump('-', a, alo, ahi) dump('+', b, blo, bhi) # When replacing one block of lines with another, this guy searches # the blocks for *similar* lines; the best-matching pair (if any) is # used as a synch point, and intraline difference marking is done on # the similar pair. Lots of work, but often worth it. def fancy_replace(a, alo, ahi, b, blo, bhi): if TRACE: print '*** fancy_replace', alo, ahi, blo, bhi dump('>', a, alo, ahi) dump('<', b, blo, bhi) # don't synch up unless the lines have a similarity score of at # least cutoff; best_ratio tracks the best score seen so far best_ratio, cutoff = 0.74, 0.75 cruncher = SequenceMatcher(IS_CHARACTER_JUNK) eqi, eqj = None, None # 1st indices of equal lines (if any) # search for the pair that matches best without being identical # (identical lines must be junk lines, & we don't want to synch up # on junk -- unless we have to) for j in xrange(blo, bhi): bj = b[j] cruncher.set_seq2(bj) for i in xrange(alo, ahi): ai = a[i] if ai == bj: if eqi is None: eqi, eqj = i, j continue cruncher.set_seq1(ai) # computing similarity is expensive, so use the quick # upper bounds first -- have seen this speed up messy # compares by a factor of 3. # note that ratio() is only expensive to compute the first # time it's called on a sequence pair; the expensive part # of the computation is cached by cruncher if cruncher.real_quick_ratio() > best_ratio and \ cruncher.quick_ratio() > best_ratio and \ cruncher.ratio() > best_ratio: best_ratio, best_i, best_j = cruncher.ratio(), i, j if best_ratio < cutoff: # no non-identical "pretty close" pair if eqi is None: # no identical pair either -- treat it as a straight replace plain_replace(a, alo, ahi, b, blo, bhi) return # no close pair, but an identical pair -- synch up on that best_i, best_j, best_ratio = eqi, eqj, 1.0 else: # there's a close pair, so forget the identical pair (if any) eqi = None # a[best_i] very similar to b[best_j]; eqi is None iff they're not # identical if TRACE: print '*** best_ratio', best_ratio, best_i, best_j dump('>', a, best_i, best_i+1) dump('<', b, best_j, best_j+1) # pump out diffs from before the synch point fancy_helper(a, alo, best_i, b, blo, best_j) # do intraline marking on the synch pair aelt, belt = a[best_i], b[best_j] if eqi is None: # pump out a '-', '?', '+', '?' quad for the synched lines atags = btags = "" cruncher.set_seqs(aelt, belt) for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes(): la, lb = ai2 - ai1, bj2 - bj1 if tag == 'replace': atags = atags + '^' * la btags = btags + '^' * lb elif tag == 'delete': atags = atags + '-' * la elif tag == 'insert': btags = btags + '+' * lb elif tag == 'equal': atags = atags + ' ' * la btags = btags + ' ' * lb else: raise ValueError('unknown tag ' + `tag`) printq(aelt, belt, atags, btags) else: # the synch pair is identical print ' ', aelt, # pump out diffs from after the synch point fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi) def fancy_helper(a, alo, ahi, b, blo, bhi): if alo < ahi: if blo < bhi: fancy_replace(a, alo, ahi, b, blo, bhi) else: dump('-', a, alo, ahi) elif blo < bhi: dump('+', b, blo, bhi) # Crap to deal with leading tabs in "?" output. Can hurt, but will # probably help most of the time. def printq(aline, bline, atags, btags): common = min(count_leading(aline, "\t"), count_leading(bline, "\t")) common = min(common, count_leading(atags[:common], " ")) print "-", aline, if count_leading(atags, " ") < len(atags): print "?", "\t" * common + atags[common:] print "+", bline, if count_leading(btags, " ") < len(btags): print "?", "\t" * common + btags[common:] def count_leading(line, ch): i, n = 0, len(line) while i < n and line[i] == ch: i = i + 1 return i def fail(msg): import sys out = sys.stderr.write out(msg + "\n\n") out(__doc__) return 0 # open a file & return the file object; gripe and return 0 if it # couldn't be opened def fopen(fname): try: return open(fname, 'r') except IOError, detail: return fail("couldn't open " + fname + ": " + str(detail)) # open two files & spray the diff to stdout; return false iff a problem def fcompare(f1name, f2name): f1 = fopen(f1name) f2 = fopen(f2name) if not f1 or not f2: return 0 a = f1.readlines(); f1.close() b = f2.readlines(); f2.close() cruncher = SequenceMatcher(IS_LINE_JUNK, a, b) for tag, alo, ahi, blo, bhi in cruncher.get_opcodes(): if tag == 'replace': fancy_replace(a, alo, ahi, b, blo, bhi) elif tag == 'delete': dump('-', a, alo, ahi) elif tag == 'insert': dump('+', b, blo, bhi) elif tag == 'equal': dump(' ', a, alo, ahi) else: raise ValueError('unknown tag ' + `tag`) return 1 # crack args (sys.argv[1:] is normal) & compare; # return false iff a problem def main(args): import getopt try: opts, args = getopt.getopt(args, "qr:") except getopt.error, detail: return fail(str(detail)) noisy = 1 qseen = rseen = 0 for opt, val in opts: if opt == "-q": qseen = 1 noisy = 0 elif opt == "-r": rseen = 1 whichfile = val if qseen and rseen: return fail("can't specify both -q and -r") if rseen: if args: return fail("no args allowed with -r option") if whichfile in "12": restore(whichfile) return 1 return fail("-r value must be 1 or 2") if len(args) != 2: return fail("need 2 filename args") f1name, f2name = args if noisy: print '-:', f1name print '+:', f2name return fcompare(f1name, f2name) def restore(which): import sys tag = {"1": "- ", "2": "+ "}[which] prefixes = (" ", tag) for line in sys.stdin.readlines(): if line[:2] in prefixes: print line[2:], if __name__ == '__main__': import sys args = sys.argv[1:] if "-profile" in args: import profile, pstats args.remove("-profile") statf = "ndiff.pro" profile.run("main(args)", statf) stats = pstats.Stats(statf) stats.strip_dirs().sort_stats('time').print_stats() else: main(args) zope2.13-2.13.21/source/zope.tal/src/zope/tal/htmltalparser.py0000644000175000017500000002647712214017644022774 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Parse HTML and compile to TALInterpreter intermediate code. $Id: htmltalparser.py 76888 2007-06-21 10:11:17Z hdima $ """ from HTMLParser import HTMLParser, HTMLParseError from zope.tal.taldefs import (ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS, METALError, TALError, I18NError) from zope.tal.talgenerator import TALGenerator BOOLEAN_HTML_ATTRS = frozenset([ # List of Boolean attributes in HTML that may be given in # minimized form (e.g. rather than ) # From http://www.w3.org/TR/xhtml1/#guidelines (C.10) "compact", "nowrap", "ismap", "declare", "noshade", "checked", "disabled", "readonly", "multiple", "selected", "noresize", "defer" ]) EMPTY_HTML_TAGS = frozenset([ # List of HTML tags with an empty content model; these are # rendered in minimized form, e.g. . # From http://www.w3.org/TR/xhtml1/#dtds "base", "meta", "link", "hr", "br", "param", "img", "area", "input", "col", "basefont", "isindex", "frame", ]) PARA_LEVEL_HTML_TAGS = frozenset([ # List of HTML elements that close open paragraph-level elements # and are themselves paragraph-level. "h1", "h2", "h3", "h4", "h5", "h6", "p", ]) BLOCK_CLOSING_TAG_MAP = { "tr": frozenset(["tr", "td", "th"]), "td": frozenset(["td", "th"]), "th": frozenset(["td", "th"]), "li": frozenset(["li"]), "dd": frozenset(["dd", "dt"]), "dt": frozenset(["dd", "dt"]), } BLOCK_LEVEL_HTML_TAGS = frozenset([ # List of HTML tags that denote larger sections than paragraphs. "blockquote", "table", "tr", "th", "td", "thead", "tfoot", "tbody", "noframe", "ul", "ol", "li", "dl", "dt", "dd", "div", ]) SECTION_LEVEL_HTML_TAGS = PARA_LEVEL_HTML_TAGS.union(BLOCK_LEVEL_HTML_TAGS) TIGHTEN_IMPLICIT_CLOSE_TAGS = PARA_LEVEL_HTML_TAGS.union(BLOCK_CLOSING_TAG_MAP) class NestingError(HTMLParseError): """Exception raised when elements aren't properly nested.""" def __init__(self, tagstack, endtag, position=(None, None)): self.endtag = endtag if tagstack: if len(tagstack) == 1: msg = ('Open tag <%s> does not match close tag ' % (tagstack[0], endtag)) else: msg = ('Open tags <%s> do not match close tag ' % ('>, <'.join(tagstack), endtag)) else: msg = 'No tags are open to match ' % endtag HTMLParseError.__init__(self, msg, position) class EmptyTagError(NestingError): """Exception raised when empty elements have an end tag.""" def __init__(self, tag, position=(None, None)): self.tag = tag msg = 'Close tag should be removed' % tag HTMLParseError.__init__(self, msg, position) class OpenTagError(NestingError): """Exception raised when a tag is not allowed in another tag.""" def __init__(self, tagstack, tag, position=(None, None)): self.tag = tag msg = 'Tag <%s> is not allowed in <%s>' % (tag, tagstack[-1]) HTMLParseError.__init__(self, msg, position) class HTMLTALParser(HTMLParser): # External API def __init__(self, gen=None): HTMLParser.__init__(self) if gen is None: gen = TALGenerator(xml=0) self.gen = gen self.tagstack = [] self.nsstack = [] self.nsdict = {'tal': ZOPE_TAL_NS, 'metal': ZOPE_METAL_NS, 'i18n': ZOPE_I18N_NS, } def parseFile(self, file): f = open(file) data = f.read() f.close() try: self.parseString(data) except TALError, e: e.setFile(file) raise def parseString(self, data): self.feed(data) self.close() while self.tagstack: self.implied_endtag(self.tagstack[-1], 2) assert self.nsstack == [], self.nsstack def getCode(self): return self.gen.getCode() # Overriding HTMLParser methods def handle_starttag(self, tag, attrs): self.close_para_tags(tag) self.scan_xmlns(attrs) tag, attrlist, taldict, metaldict, i18ndict \ = self.process_ns(tag, attrs) if tag in EMPTY_HTML_TAGS and "content" in taldict: raise TALError( "empty HTML tags cannot use tal:content: %s" % `tag`, self.getpos()) # Support for inline Python code. if tag == 'script': type_attr = [a for a in attrlist if a[0] == "type"] if type_attr and type_attr[0][1].startswith('text/server-'): attrlist.remove(type_attr[0]) taldict = {'script': type_attr[0][1], 'omit-tag': ''} self.tagstack.append(tag) self.gen.emitStartElement(tag, attrlist, taldict, metaldict, i18ndict, self.getpos()) if tag in EMPTY_HTML_TAGS: self.implied_endtag(tag, -1) def handle_startendtag(self, tag, attrs): self.close_para_tags(tag) self.scan_xmlns(attrs) tag, attrlist, taldict, metaldict, i18ndict \ = self.process_ns(tag, attrs) if "content" in taldict: if tag in EMPTY_HTML_TAGS: raise TALError( "empty HTML tags cannot use tal:content: %s" % `tag`, self.getpos()) self.gen.emitStartElement(tag, attrlist, taldict, metaldict, i18ndict, self.getpos()) self.gen.emitEndElement(tag, implied=-1, position=self.getpos()) else: self.gen.emitStartElement(tag, attrlist, taldict, metaldict, i18ndict, self.getpos(), isend=1) self.pop_xmlns() def handle_endtag(self, tag): if tag in EMPTY_HTML_TAGS: # etc. in the source is an error raise EmptyTagError(tag, self.getpos()) self.close_enclosed_tags(tag) self.gen.emitEndElement(tag, position=self.getpos()) self.pop_xmlns() self.tagstack.pop() def close_para_tags(self, tag): if tag in EMPTY_HTML_TAGS: return close_to = -1 if tag in BLOCK_CLOSING_TAG_MAP: blocks_to_close = BLOCK_CLOSING_TAG_MAP[tag] for i, t in enumerate(self.tagstack): if t in blocks_to_close: if close_to == -1: close_to = i elif t in BLOCK_LEVEL_HTML_TAGS: close_to = -1 elif tag in SECTION_LEVEL_HTML_TAGS: for i in range(len(self.tagstack) - 1, -1, -1): closetag = self.tagstack[i] if closetag in BLOCK_LEVEL_HTML_TAGS: break elif closetag in PARA_LEVEL_HTML_TAGS: if closetag != "p": raise OpenTagError(self.tagstack, tag, self.getpos()) close_to = i if close_to >= 0: while len(self.tagstack) > close_to: self.implied_endtag(self.tagstack[-1], 1) def close_enclosed_tags(self, tag): if tag not in self.tagstack: raise NestingError(self.tagstack, tag, self.getpos()) while tag != self.tagstack[-1]: self.implied_endtag(self.tagstack[-1], 1) assert self.tagstack[-1] == tag def implied_endtag(self, tag, implied): assert tag == self.tagstack[-1] assert implied in (-1, 1, 2) isend = (implied < 0) if tag in TIGHTEN_IMPLICIT_CLOSE_TAGS: # Pick out trailing whitespace from the program, and # insert the close tag before the whitespace. white = self.gen.unEmitWhitespace() else: white = None self.gen.emitEndElement(tag, isend=isend, implied=implied, position=self.getpos()) if white: self.gen.emitRawText(white) self.tagstack.pop() self.pop_xmlns() def handle_charref(self, name): self.gen.emitRawText("&#%s;" % name) def handle_entityref(self, name): self.gen.emitRawText("&%s;" % name) def handle_data(self, data): self.gen.emitRawText(data) def handle_comment(self, data): self.gen.emitRawText("" % data) def handle_decl(self, data): self.gen.emitRawText("" % data) def handle_pi(self, data): self.gen.emitRawText("" % data) # Internal thingies def scan_xmlns(self, attrs): nsnew = {} for key, value in attrs: if key.startswith("xmlns:"): nsnew[key[6:]] = value self.nsstack.append(self.nsdict) if nsnew: self.nsdict = self.nsdict.copy() self.nsdict.update(nsnew) def pop_xmlns(self): self.nsdict = self.nsstack.pop() _namespaces = { ZOPE_TAL_NS: "tal", ZOPE_METAL_NS: "metal", ZOPE_I18N_NS: "i18n", } def fixname(self, name): if ':' in name: prefix, suffix = name.split(':', 1) if prefix == 'xmlns': nsuri = self.nsdict.get(suffix) if nsuri in self._namespaces: return name, name, prefix else: nsuri = self.nsdict.get(prefix) if nsuri in self._namespaces: return name, suffix, self._namespaces[nsuri] return name, name, 0 def process_ns(self, name, attrs): attrlist = [] taldict = {} metaldict = {} i18ndict = {} name, namebase, namens = self.fixname(name) for item in attrs: key, value = item key, keybase, keyns = self.fixname(key) ns = keyns or namens # default to tag namespace if ns and ns != 'unknown': item = (key, value, ns) if ns == 'tal': if keybase in taldict: raise TALError("duplicate TAL attribute " + repr(keybase), self.getpos()) taldict[keybase] = value elif ns == 'metal': if keybase in metaldict: raise METALError("duplicate METAL attribute " + repr(keybase), self.getpos()) metaldict[keybase] = value elif ns == 'i18n': if keybase in i18ndict: raise I18NError("duplicate i18n attribute " + repr(keybase), self.getpos()) i18ndict[keybase] = value attrlist.append(item) if namens in ('metal', 'tal', 'i18n'): taldict['tal tag'] = namens return name, attrlist, taldict, metaldict, i18ndict zope2.13-2.13.21/source/zope.tal/src/zope/tal/talparser.py0000644000175000017500000001124012214017644022065 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Parse XML and compile to TALInterpreter intermediate code. $Id: talparser.py 76887 2007-06-21 10:08:47Z hdima $ """ from zope.tal.taldefs import XML_NS, ZOPE_I18N_NS, ZOPE_METAL_NS, ZOPE_TAL_NS from zope.tal.talgenerator import TALGenerator from zope.tal.xmlparser import XMLParser class TALParser(XMLParser): ordered_attributes = 1 def __init__(self, gen=None, encoding=None): # Override XMLParser.__init__(self, encoding) if gen is None: gen = TALGenerator() self.gen = gen self.nsStack = [] self.nsDict = {XML_NS: 'xml'} self.nsNew = [] def getCode(self): return self.gen.getCode() def StartNamespaceDeclHandler(self, prefix, uri): self.nsStack.append(self.nsDict.copy()) self.nsDict[uri] = prefix self.nsNew.append((prefix, uri)) def EndNamespaceDeclHandler(self, prefix): self.nsDict = self.nsStack.pop() def StartElementHandler(self, name, attrs): if self.ordered_attributes: # attrs is a list of alternating names and values attrlist = [] for i in range(0, len(attrs), 2): key = attrs[i] value = attrs[i + 1] attrlist.append((key, value)) else: # attrs is a dict of {name: value} attrlist = attrs.items() attrlist.sort() # For definiteness name, attrlist, taldict, metaldict, i18ndict \ = self.process_ns(name, attrlist) attrlist = self.xmlnsattrs() + attrlist self.gen.emitStartElement(name, attrlist, taldict, metaldict, i18ndict, self.getpos()) def process_ns(self, name, attrlist): taldict = {} metaldict = {} i18ndict = {} fixedattrlist = [] name, namebase, namens = self.fixname(name) for key, value in attrlist: key, keybase, keyns = self.fixname(key) ns = keyns or namens # default to tag namespace item = key, value if ns == 'metal': metaldict[keybase] = value item = item + ("metal",) elif ns == 'tal': taldict[keybase] = value item = item + ("tal",) elif ns == 'i18n': i18ndict[keybase] = value item = item + ('i18n',) fixedattrlist.append(item) if namens in ('metal', 'tal', 'i18n'): taldict['tal tag'] = namens return name, fixedattrlist, taldict, metaldict, i18ndict _namespaces = { ZOPE_TAL_NS: "tal", ZOPE_METAL_NS: "metal", ZOPE_I18N_NS: "i18n", } def xmlnsattrs(self): newlist = [] for prefix, uri in self.nsNew: if prefix: key = "xmlns:" + prefix else: key = "xmlns" if uri in self._namespaces: item = (key, uri, "xmlns") else: item = (key, uri) newlist.append(item) self.nsNew = [] return newlist def fixname(self, name): if ' ' in name: uri, name = name.split(' ', 1) prefix = self.nsDict[uri] prefixed = name if prefix: prefixed = "%s:%s" % (prefix, name) ns = self._namespaces.get(uri, "x") return (prefixed, name, ns) return (name, name, None) def EndElementHandler(self, name): name = self.fixname(name)[0] self.gen.emitEndElement(name, position=self.getpos()) def DefaultHandler(self, text): self.gen.emitRawText(text) def test(): import sys p = TALParser() file = "tests/input/test01.xml" if sys.argv[1:]: file = sys.argv[1] p.parseFile(file) program, macros = p.getCode() from zope.tal.talinterpreter import TALInterpreter from zope.tal.dummyengine import DummyEngine engine = DummyEngine(macros) TALInterpreter(program, macros, engine, sys.stdout, wrap=0)() if __name__ == "__main__": test() zope2.13-2.13.21/source/zope.tal/src/zope/tal/timer.py0000644000175000017500000000333712214017644021220 0ustar arnauarnau#! /usr/bin/env python ############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Helper program to time compilation and interpretation $Id: timer.py 25177 2004-06-02 13:17:31Z jim $ """ import getopt import sys import time from cStringIO import StringIO from zope.tal.driver import FILE, compilefile, interpretit def main(): count = 10 try: opts, args = getopt.getopt(sys.argv[1:], "n:") except getopt.error, msg: print msg sys.exit(2) for o, a in opts: if o == "-n": count = int(a) if not args: args = [FILE] for file in args: print file dummyfile = StringIO() it = timefunc(count, compilefile, file) timefunc(count, interpretit, it, None, dummyfile) def timefunc(count, func, *args): sys.stderr.write("%-14s: " % func.__name__) sys.stderr.flush() t0 = time.clock() for i in range(count): result = apply(func, args) t1 = time.clock() sys.stderr.write("%6.3f secs for %d calls, i.e. %4.0f msecs per call\n" % ((t1-t0), count, 1000*(t1-t0)/count)) return result if __name__ == "__main__": main() zope2.13-2.13.21/source/zope.tal/src/zope/tal/talgettext.py0000644000175000017500000002444512214017644022270 0ustar arnauarnau#!/usr/bin/env python ############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Program to extract internationalization markup from Page Templates. Once you have marked up a Page Template file with i18n: namespace tags, use this program to extract GNU gettext .po file entries. Usage: talgettext.py [options] files Options: -h / --help Print this message and exit. -o / --output Output the translation .po file to . -u / --update Update the existing translation with any new translation strings found. $Id: talgettext.py 105021 2009-10-12 06:29:49Z fretin $ """ import sys import time import getopt import traceback from zope.interface import implements from zope.tal.htmltalparser import HTMLTALParser from zope.tal.talinterpreter import TALInterpreter, normalize from zope.tal.dummyengine import DummyEngine from zope.tal.interfaces import ITALExpressionEngine from zope.tal.taldefs import TALExpressionError from zope.i18nmessageid import Message pot_header = '''\ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\\n" "POT-Creation-Date: %(time)s\\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" "Last-Translator: FULL NAME \\n" "Language-Team: LANGUAGE \\n" "MIME-Version: 1.0\\n" "Content-Type: text/plain; charset=CHARSET\\n" "Content-Transfer-Encoding: ENCODING\\n" "Generated-By: talgettext.py %(version)s\\n" ''' NLSTR = '"\n"' def usage(code, msg=''): # Python 2.1 required print >> sys.stderr, __doc__ if msg: print >> sys.stderr, msg sys.exit(code) class POTALInterpreter(TALInterpreter): def translate(self, msgid, default=None, i18ndict=None, obj=None): if default is None: default = getattr(msgid, 'default', unicode(msgid)) # If no i18n dict exists yet, create one. if i18ndict is None: i18ndict = {} if obj: i18ndict.update(obj) # Mmmh, it seems that sometimes the msgid is None; is that really # possible? if msgid is None: return None # TODO: We need to pass in one of context or target_language return self.engine.translate(msgid, self.i18nContext.domain, i18ndict, default=default, position=self.position) class POEngine(DummyEngine): implements(ITALExpressionEngine) def __init__(self, macros=None): self.catalog = {} DummyEngine.__init__(self, macros) def evaluate(*args): # If the result of evaluate ever gets into a message ID, we want # to notice the fact in the .pot file. return '${DYNAMIC_CONTENT}' def evaluatePathOrVar(*args): # Actually this method is never called. return 'XXX' def evaluateSequence(self, expr): return (0,) # dummy def evaluateBoolean(self, expr): return True # dummy def translate(self, msgid, domain=None, mapping=None, default=None, # Position is not part of the ITALExpressionEngine # interface position=None): if default is not None: default = normalize(default) if msgid == default: default = None msgid = Message(msgid, default=default) if domain not in self.catalog: self.catalog[domain] = {} domain = self.catalog[domain] if msgid not in domain: domain[msgid] = [] else: msgids = domain.keys() idx = msgids.index(msgid) existing_msgid = msgids[idx] if msgid.default != existing_msgid.default: references = '\n'.join([location[0]+':'+str(location[1]) for location in domain[msgid]]) print >> sys.stderr, "Warning: msgid '%s' in %s already exists " \ "with a different default (bad: %s, should be: %s)\n" \ "The references for the existent value are:\n%s\n" % \ (msgid, self.file+':'+str(position), msgid.default, existing_msgid.default, references) domain[msgid].append((self.file, position)) return 'x' class UpdatePOEngine(POEngine): """A slightly-less braindead POEngine which supports loading an existing .po file first.""" def __init__ (self, macros=None, filename=None): POEngine.__init__(self, macros) self._filename = filename self._loadFile() self.base = self.catalog self.catalog = {} def __add(self, id, s, fuzzy): "Add a non-fuzzy translation to the dictionary." if not fuzzy and str: # check for multi-line values and munge them appropriately if '\n' in s: lines = s.rstrip().split('\n') s = NLSTR.join(lines) self.catalog[id] = s def _loadFile(self): # shamelessly cribbed from Python's Tools/i18n/msgfmt.py # 25-Mar-2003 Nathan R. Yergler (nathan@zope.org) # 14-Apr-2003 Hacked by Barry Warsaw (barry@zope.com) ID = 1 STR = 2 try: lines = open(self._filename).readlines() except IOError, msg: print >> sys.stderr, msg sys.exit(1) section = None fuzzy = False # Parse the catalog lno = 0 for l in lines: lno += True # If we get a comment line after a msgstr, this is a new entry if l[0] == '#' and section == STR: self.__add(msgid, msgstr, fuzzy) section = None fuzzy = False # Record a fuzzy mark if l[:2] == '#,' and l.find('fuzzy'): fuzzy = True # Skip comments if l[0] == '#': continue # Now we are in a msgid section, output previous section if l.startswith('msgid'): if section == STR: self.__add(msgid, msgstr, fuzzy) section = ID l = l[5:] msgid = msgstr = '' # Now we are in a msgstr section elif l.startswith('msgstr'): section = STR l = l[6:] # Skip empty lines if not l.strip(): continue # TODO: Does this always follow Python escape semantics? l = eval(l) if section == ID: msgid += l elif section == STR: msgstr += '%s\n' % l else: print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ 'before:' print >> sys.stderr, l sys.exit(1) # Add last entry if section == STR: self.__add(msgid, msgstr, fuzzy) def evaluate(self, expression): try: return POEngine.evaluate(self, expression) except TALExpressionError: pass def evaluatePathOrVar(self, expr): return 'who cares' def translate(self, msgid, domain=None, mapping=None, default=None, position=None): if msgid not in self.base: POEngine.translate(self, msgid, domain, mapping, default, position) return 'x' def main(): try: opts, args = getopt.getopt( sys.argv[1:], 'ho:u:', ['help', 'output=', 'update=']) except getopt.error, msg: usage(1, msg) outfile = None engine = None update_mode = False for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt in ('-o', '--output'): outfile = arg elif opt in ('-u', '--update'): update_mode = True if outfile is None: outfile = arg engine = UpdatePOEngine(filename=arg) if not args: print 'nothing to do' return # We don't care about the rendered output of the .pt file class Devnull(object): def write(self, s): pass # check if we've already instantiated an engine; # if not, use the stupidest one available if not engine: engine = POEngine() # process each file specified for filename in args: try: engine.file = filename p = HTMLTALParser() p.parseFile(filename) program, macros = p.getCode() POTALInterpreter(program, macros, engine, stream=Devnull(), metal=False)() except: # Hee hee, I love bare excepts! print 'There was an error processing', filename traceback.print_exc() # Now output the keys in the engine. Write them to a file if --output or # --update was specified; otherwise use standard out. if (outfile is None): outfile = sys.stdout else: outfile = file(outfile, update_mode and "a" or "w") catalog = {} for domain in engine.catalog.keys(): catalog.update(engine.catalog[domain]) messages = catalog.copy() try: messages.update(engine.base) except AttributeError: pass if '' not in messages: print >> outfile, pot_header % {'time': time.ctime(), 'version': __version__} msgids = catalog.keys() # TODO: You should not sort by msgid, but by filename and position. (SR) msgids.sort() for msgid in msgids: positions = engine.catalog[msgid] for filename, position in positions: outfile.write('#: %s:%s\n' % (filename, position[0])) outfile.write('msgid "%s"\n' % msgid) outfile.write('msgstr ""\n') outfile.write('\n') if __name__ == '__main__': main() zope2.13-2.13.21/source/zope.tal/src/zope/tal/talinterpreter.py0000644000175000017500000011303412214017644023140 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interpreter for a pre-compiled TAL program. $Id: talinterpreter.py 87220 2008-06-07 03:08:35Z fdrake $ """ import cgi import operator import sys import warnings # Do not use cStringIO here! It's not unicode aware. :( from StringIO import StringIO from zope.i18nmessageid import Message from zope.tal.taldefs import quote, TAL_VERSION, METALError from zope.tal.taldefs import isCurrentVersion from zope.tal.taldefs import getProgramVersion, getProgramMode from zope.tal.talgenerator import TALGenerator from zope.tal.translationcontext import TranslationContext # Avoid constructing this tuple over and over I18nMessageTypes = (Message,) TypesToTranslate = I18nMessageTypes + (str, unicode) BOOLEAN_HTML_ATTRS = frozenset([ # List of Boolean attributes in HTML that should be rendered in # minimized form (e.g. rather than ) # From http://www.w3.org/TR/xhtml1/#guidelines (C.10) # TODO: The problem with this is that this is not valid XML and # can't be parsed back! "compact", "nowrap", "ismap", "declare", "noshade", "checked", "disabled", "readonly", "multiple", "selected", "noresize", "defer" ]) _nulljoin = ''.join _spacejoin = ' '.join def normalize(text): # Now we need to normalize the whitespace in implicit message ids and # implicit $name substitution values by stripping leading and trailing # whitespace, and folding all internal whitespace to a single space. return _spacejoin(text.split()) class AltTALGenerator(TALGenerator): def __init__(self, repldict, expressionCompiler=None, xml=0): self.repldict = repldict self.enabled = 1 TALGenerator.__init__(self, expressionCompiler, xml) def enable(self, enabled): self.enabled = enabled def emit(self, *args): if self.enabled: TALGenerator.emit(self, *args) def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict, position=(None, None), isend=0): metaldict = {} taldict = {} i18ndict = {} if self.enabled and self.repldict: taldict["attributes"] = "x x" TALGenerator.emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict, position, isend) def replaceAttrs(self, attrlist, repldict): if self.enabled and self.repldict: repldict = self.repldict self.repldict = None return TALGenerator.replaceAttrs(self, attrlist, repldict) class MacroStackItem(list): # This is a `list` subclass for backward compability. """Stack entry for the TALInterpreter.macroStack. This offers convenience attributes for more readable access. """ __slots__ = () # These would be nicer using @syntax, but that would require # Python 2.4.x; this will do for now. macroName = property(lambda self: self[0]) slots = property(lambda self: self[1]) definingName = property(lambda self: self[2]) extending = property(lambda self: self[3]) entering = property(lambda self: self[4], lambda self, value: operator.setitem(self, 4, value)) i18nContext = property(lambda self: self[5]) class TALInterpreter(object): """TAL interpreter. Some notes on source annotations. They are HTML/XML comments added to the output whenever sourceFile is changed by a setSourceFile bytecode. Source annotations are disabled by default, but you can turn them on by passing a sourceAnnotations argument to the constructor. You can change the format of the annotations by overriding formatSourceAnnotation in a subclass. The output of the annotation is delayed until some actual text is output for two reasons: 1. setPosition bytecode follows setSourceFile, and we need position information to output the line number. 2. Comments are not allowed in XML documents before the declaration. For performance reasons (TODO: premature optimization?) instead of checking the value of _pending_source_annotation on every write to the output stream, the _stream_write attribute is changed to point to _annotated_stream_write method whenever _pending_source_annotation is set to True, and to _stream.write when it is False. The following invariant always holds: if self._pending_source_annotation: assert self._stream_write is self._annotated_stream_write else: assert self._stream_write is self.stream.write """ def __init__(self, program, macros, engine, stream=None, debug=0, wrap=60, metal=1, tal=1, showtal=-1, strictinsert=1, stackLimit=100, i18nInterpolate=1, sourceAnnotations=0): """Create a TAL interpreter. Optional arguments: stream -- output stream (defaults to sys.stdout). debug -- enable debugging output to sys.stderr (off by default). wrap -- try to wrap attributes on opening tags to this number of column (default: 60). metal -- enable METAL macro processing (on by default). tal -- enable TAL processing (on by default). showtal -- do not strip away TAL directives. A special value of -1 (which is the default setting) enables showtal when TAL processing is disabled, and disables showtal when TAL processing is enabled. Note that you must use 0, 1, or -1; true boolean values are not supported (TODO: why?). strictinsert -- enable TAL processing and stricter HTML/XML checking on text produced by structure inserts (on by default). Note that Zope turns this value off by default. stackLimit -- set macro nesting limit (default: 100). i18nInterpolate -- enable i18n translations (default: on). sourceAnnotations -- enable source annotations with HTML comments (default: off). """ self.program = program self.macros = macros self.engine = engine # Execution engine (aka context) self.Default = engine.getDefault() self._pending_source_annotation = False self._currentTag = "" self._stream_stack = [stream or sys.stdout] self.popStream() self.debug = debug self.wrap = wrap self.metal = metal self.tal = tal if tal: self.dispatch = self.bytecode_handlers_tal else: self.dispatch = self.bytecode_handlers assert showtal in (-1, 0, 1) if showtal == -1: showtal = (not tal) self.showtal = showtal self.strictinsert = strictinsert self.stackLimit = stackLimit self.html = 0 self.endsep = "/>" self.endlen = len(self.endsep) # macroStack entries are MacroStackItem instances; # the entries are mutated while on the stack self.macroStack = [] # `inUseDirective` is set iff we're handling either a # metal:use-macro or a metal:extend-macro self.inUseDirective = False self.position = None, None # (lineno, offset) self.col = 0 self.level = 0 self.scopeLevel = 0 self.sourceFile = None self.i18nStack = [] self.i18nInterpolate = i18nInterpolate self.i18nContext = TranslationContext() self.sourceAnnotations = sourceAnnotations def StringIO(self): # Third-party products wishing to provide a full Unicode-aware # StringIO can do so by monkey-patching this method. return FasterStringIO() def saveState(self): return (self.position, self.col, self.stream, self._stream_stack, self.scopeLevel, self.level, self.i18nContext) def restoreState(self, state): (self.position, self.col, self.stream, self._stream_stack, scopeLevel, level, i18n) = state if self._pending_source_annotation: self._stream_write = self._annotated_stream_write else: self._stream_write = self.stream.write assert self.level == level while self.scopeLevel > scopeLevel: self.engine.endScope() self.scopeLevel = self.scopeLevel - 1 self.engine.setPosition(self.position) self.i18nContext = i18n def restoreOutputState(self, state): (dummy, self.col, self.stream, self._stream_stack, scopeLevel, level, i18n) = state if self._pending_source_annotation: self._stream_write = self._annotated_stream_write else: self._stream_write = self.stream.write assert self.level == level assert self.scopeLevel == scopeLevel def pushMacro(self, macroName, slots, definingName, extending): if len(self.macroStack) >= self.stackLimit: raise METALError("macro nesting limit (%d) exceeded " "by %s" % (self.stackLimit, `macroName`)) self.macroStack.append( MacroStackItem((macroName, slots, definingName, extending, True, self.i18nContext))) def popMacro(self): return self.macroStack.pop() def __call__(self): assert self.level == 0 assert self.scopeLevel == 0 assert self.i18nContext.parent is None self.interpret(self.program) assert self.level == 0 assert self.scopeLevel == 0 assert self.i18nContext.parent is None def pushStream(self, newstream): self._stream_stack.append(self.stream) self.stream = newstream if self._pending_source_annotation: self._stream_write = self._annotated_stream_write else: self._stream_write = self.stream.write def popStream(self): self.stream = self._stream_stack.pop() if self._pending_source_annotation: self._stream_write = self._annotated_stream_write else: self._stream_write = self.stream.write def _annotated_stream_write(self, s): idx = s.find('= 0 or s.isspace(): # Do not preprend comments in front of the declaration. end_of_doctype = s.find('?>', idx) if end_of_doctype > idx: self.stream.write(s[:end_of_doctype+2]) s = s[end_of_doctype+2:] # continue else: self.stream.write(s) return self._pending_source_annotation = False self._stream_write = self.stream.write self._stream_write(self.formatSourceAnnotation()) self._stream_write(s) def formatSourceAnnotation(self): lineno = self.position[0] if lineno is None: location = self.sourceFile else: location = '%s (line %s)' % (self.sourceFile, lineno) sep = '=' * 78 return '' % (sep, location, sep) def stream_write(self, s, len=len): self._stream_write(s) i = s.rfind('\n') if i < 0: self.col = self.col + len(s) else: self.col = len(s) - (i + 1) bytecode_handlers = {} def interpret(self, program): oldlevel = self.level self.level = oldlevel + 1 handlers = self.dispatch try: if self.debug: for (opcode, args) in program: s = "%sdo_%s(%s)\n" % (" "*self.level, opcode, repr(args)) if len(s) > 80: s = s[:76] + "...\n" sys.stderr.write(s) handlers[opcode](self, args) else: for (opcode, args) in program: handlers[opcode](self, args) finally: self.level = oldlevel def do_version(self, version): assert version == TAL_VERSION bytecode_handlers["version"] = do_version def do_mode(self, mode): assert mode in ("html", "xml") self.html = (mode == "html") if self.html: self.endsep = " />" else: self.endsep = "/>" self.endlen = len(self.endsep) bytecode_handlers["mode"] = do_mode def do_setSourceFile(self, source_file): self.sourceFile = source_file self.engine.setSourceFile(source_file) if self.sourceAnnotations: self._pending_source_annotation = True self._stream_write = self._annotated_stream_write bytecode_handlers["setSourceFile"] = do_setSourceFile def do_setPosition(self, position): self.position = position self.engine.setPosition(position) bytecode_handlers["setPosition"] = do_setPosition def do_startEndTag(self, stuff): self.do_startTag(stuff, self.endsep, self.endlen) bytecode_handlers["startEndTag"] = do_startEndTag def do_startTag(self, (name, attrList), end=">", endlen=1, _len=len): # The bytecode generator does not cause calls to this method # for start tags with no attributes; those are optimized down # to rawtext events. Hence, there is no special "fast path" # for that case. self._currentTag = name L = ["<", name] append = L.append col = self.col + _len(name) + 1 wrap = self.wrap align = col + 1 if align >= wrap/2: align = 4 # Avoid a narrow column far to the right attrAction = self.dispatch[""] try: for item in attrList: if _len(item) == 2: rendered = item[1:] else: # item[2] is the 'action' field: if item[2] in ('metal', 'tal', 'xmlns', 'i18n'): if not self.showtal: continue rendered = self.attrAction(item) else: rendered = attrAction(self, item) if not rendered: continue for s in rendered: slen = _len(s) if (wrap and col >= align and col + 1 + slen > wrap): append("\n") append(" "*align) col = align + slen else: append(" ") col = col + 1 + slen append(s) append(end) col = col + endlen finally: self._stream_write(_nulljoin(L)) self.col = col bytecode_handlers["startTag"] = do_startTag def attrAction(self, item): name, value, action = item[:3] if action == 'insert': return () macs = self.macroStack if action == 'metal' and self.metal and macs: # Drop all METAL attributes at a use-depth beyond the first # use-macro and its extensions if len(macs) > 1: for macro in macs[1:]: if not macro.extending: return () if not macs[-1].entering: return () macs[-1].entering = False # Convert or drop depth-one METAL attributes. i = name.rfind(":") + 1 prefix, suffix = name[:i], name[i:] if suffix == "define-macro": # Convert define-macro as we enter depth one. useName = macs[0].macroName defName = macs[0].definingName res = [] if defName: res.append('%sdefine-macro=%s' % (prefix, quote(defName))) if useName: res.append('%suse-macro=%s' % (prefix, quote(useName))) return res elif suffix == "define-slot": name = prefix + "fill-slot" elif suffix == "fill-slot": pass else: return () if value is None: value = name else: value = "%s=%s" % (name, quote(value)) return [value] def attrAction_tal(self, item): name, value, action = item[:3] ok = 1 expr, xlat, msgid = item[3:] if self.html and name.lower() in BOOLEAN_HTML_ATTRS: evalue = self.engine.evaluateBoolean(item[3]) if evalue is self.Default: if action == 'insert': # Cancelled insert ok = 0 elif evalue: value = None else: ok = 0 elif expr is not None: evalue = self.engine.evaluateText(item[3]) if evalue is self.Default: if action == 'insert': # Cancelled insert ok = 0 else: if evalue is None: ok = 0 value = evalue if ok: if xlat: translated = self.translate(msgid or value, value) if translated is not None: value = translated elif isinstance(value, I18nMessageTypes): translated = self.translate(value) if translated is not None: value = translated if value is None: value = name return ["%s=%s" % (name, quote(value))] else: return () bytecode_handlers[""] = attrAction def no_tag(self, start, program): state = self.saveState() self.stream = stream = self.StringIO() self._stream_write = stream.write self.interpret(start) self.restoreOutputState(state) self.interpret(program) def do_optTag(self, (name, cexpr, tag_ns, isend, start, program), omit=0): if tag_ns and not self.showtal: return self.no_tag(start, program) self.interpret(start) if not isend: self.interpret(program) s = '' % name self._stream_write(s) self.col = self.col + len(s) def do_optTag_tal(self, stuff): cexpr = stuff[1] if cexpr is not None and (cexpr == '' or self.engine.evaluateBoolean(cexpr)): self.no_tag(stuff[-2], stuff[-1]) else: self.do_optTag(stuff) bytecode_handlers["optTag"] = do_optTag def do_rawtextBeginScope(self, (s, col, position, closeprev, dict)): self._stream_write(s) self.col = col self.do_setPosition(position) if closeprev: engine = self.engine engine.endScope() engine.beginScope() else: self.engine.beginScope() self.scopeLevel = self.scopeLevel + 1 def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)): self._stream_write(s) self.col = col engine = self.engine self.position = position engine.setPosition(position) if closeprev: engine.endScope() engine.beginScope() else: engine.beginScope() self.scopeLevel = self.scopeLevel + 1 engine.setLocal("attrs", dict) bytecode_handlers["rawtextBeginScope"] = do_rawtextBeginScope def do_beginScope(self, dict): self.engine.beginScope() self.scopeLevel = self.scopeLevel + 1 def do_beginScope_tal(self, dict): engine = self.engine engine.beginScope() engine.setLocal("attrs", dict) self.scopeLevel = self.scopeLevel + 1 bytecode_handlers["beginScope"] = do_beginScope def do_endScope(self, notused=None): self.engine.endScope() self.scopeLevel = self.scopeLevel - 1 bytecode_handlers["endScope"] = do_endScope def do_setLocal(self, notused): pass def do_setLocal_tal(self, (name, expr)): self.engine.setLocal(name, self.engine.evaluateValue(expr)) bytecode_handlers["setLocal"] = do_setLocal def do_setGlobal_tal(self, (name, expr)): self.engine.setGlobal(name, self.engine.evaluateValue(expr)) bytecode_handlers["setGlobal"] = do_setLocal def do_beginI18nContext(self, settings): get = settings.get self.i18nContext = TranslationContext(self.i18nContext, domain=get("domain"), source=get("source"), target=get("target")) bytecode_handlers["beginI18nContext"] = do_beginI18nContext def do_endI18nContext(self, notused=None): self.i18nContext = self.i18nContext.parent assert self.i18nContext is not None bytecode_handlers["endI18nContext"] = do_endI18nContext def do_insertText(self, stuff): self.interpret(stuff[1]) bytecode_handlers["insertText"] = do_insertText bytecode_handlers["insertI18nText"] = do_insertText def _writeText(self, text): # '&' must be done first! s = text.replace( "&", "&").replace("<", "<").replace(">", ">") self._stream_write(s) i = s.rfind('\n') if i < 0: self.col += len(s) else: self.col = len(s) - (i + 1) def do_insertText_tal(self, stuff): text = self.engine.evaluateText(stuff[0]) if text is None: return if text is self.Default: self.interpret(stuff[1]) return if isinstance(text, I18nMessageTypes): # Translate this now. text = self.translate(text) self._writeText(text) def do_insertI18nText_tal(self, stuff): # TODO: Code duplication is BAD, we need to fix it later text = self.engine.evaluateText(stuff[0]) if text is not None: if text is self.Default: self.interpret(stuff[1]) else: if isinstance(text, TypesToTranslate): text = self.translate(text) self._writeText(text) def do_i18nVariable(self, stuff): varname, program, expression, structure = stuff if expression is None: # The value is implicitly the contents of this tag, so we have to # evaluate the mini-program to get the value of the variable. state = self.saveState() try: tmpstream = self.StringIO() self.pushStream(tmpstream) try: self.interpret(program) finally: self.popStream() if self.html and self._currentTag == "pre": value = tmpstream.getvalue() else: value = normalize(tmpstream.getvalue()) finally: self.restoreState(state) else: # TODO: Seems like this branch not used anymore, we # need to remove it # Evaluate the value to be associated with the variable in the # i18n interpolation dictionary. if structure: value = self.engine.evaluateStructure(expression) else: value = self.engine.evaluate(expression) # evaluate() does not do any I18n, so we do it here. if isinstance(value, I18nMessageTypes): # Translate this now. value = self.translate(value) if not structure: value = cgi.escape(unicode(value)) # Either the i18n:name tag is nested inside an i18n:translate in which # case the last item on the stack has the i18n dictionary and string # representation, or the i18n:name and i18n:translate attributes are # in the same tag, in which case the i18nStack will be empty. In that # case we can just output the ${name} to the stream i18ndict, srepr = self.i18nStack[-1] i18ndict[varname] = value placeholder = '${%s}' % varname srepr.append(placeholder) self._stream_write(placeholder) bytecode_handlers['i18nVariable'] = do_i18nVariable def do_insertTranslation(self, stuff): i18ndict = {} srepr = [] obj = None self.i18nStack.append((i18ndict, srepr)) msgid = stuff[0] # We need to evaluate the content of the tag because that will give us # several useful pieces of information. First, the contents will # include an implicit message id, if no explicit one was given. # Second, it will evaluate any i18nVariable definitions in the body of # the translation (necessary for $varname substitutions). # # Use a temporary stream to capture the interpretation of the # subnodes, which should /not/ go to the output stream. currentTag = self._currentTag tmpstream = self.StringIO() self.pushStream(tmpstream) try: self.interpret(stuff[1]) finally: self.popStream() # We only care about the evaluated contents if we need an implicit # message id. All other useful information will be in the i18ndict on # the top of the i18nStack. default = tmpstream.getvalue() if not msgid: if self.html and currentTag == "pre": msgid = default else: msgid = normalize(default) self.i18nStack.pop() # See if there is was an i18n:data for msgid if len(stuff) > 2: obj = self.engine.evaluate(stuff[2]) xlated_msgid = self.translate(msgid, default, i18ndict, obj) # TODO: I can't decide whether we want to cgi escape the translated # string or not. OTOH not doing this could introduce a cross-site # scripting vector by allowing translators to sneak JavaScript into # translations. OTOH, for implicit interpolation values, we don't # want to escape stuff like ${name} <= "Timmy". assert xlated_msgid is not None self._stream_write(xlated_msgid) bytecode_handlers['insertTranslation'] = do_insertTranslation def do_insertStructure(self, stuff): self.interpret(stuff[2]) bytecode_handlers["insertStructure"] = do_insertStructure bytecode_handlers["insertI18nStructure"] = do_insertStructure def do_insertStructure_tal(self, (expr, repldict, block)): structure = self.engine.evaluateStructure(expr) if structure is None: return if structure is self.Default: self.interpret(block) return if isinstance(structure, I18nMessageTypes): text = self.translate(structure) else: text = unicode(structure) if not (repldict or self.strictinsert): # Take a shortcut, no error checking self.stream_write(text) return if self.html: self.insertHTMLStructure(text, repldict) else: self.insertXMLStructure(text, repldict) def do_insertI18nStructure_tal(self, (expr, repldict, block)): # TODO: Code duplication is BAD, we need to fix it later structure = self.engine.evaluateStructure(expr) if structure is not None: if structure is self.Default: self.interpret(block) else: if not isinstance(structure, TypesToTranslate): structure = unicode(structure) text = self.translate(structure) if not (repldict or self.strictinsert): # Take a shortcut, no error checking self.stream_write(text) elif self.html: self.insertHTMLStructure(text, repldict) else: self.insertXMLStructure(text, repldict) def insertHTMLStructure(self, text, repldict): from zope.tal.htmltalparser import HTMLTALParser gen = AltTALGenerator(repldict, self.engine, 0) p = HTMLTALParser(gen) # Raises an exception if text is invalid p.parseString(text) program, macros = p.getCode() self.interpret(program) def insertXMLStructure(self, text, repldict): from zope.tal.talparser import TALParser gen = AltTALGenerator(repldict, self.engine, 0) p = TALParser(gen) gen.enable(0) p.parseFragment('') gen.enable(1) p.parseFragment(text) # Raises an exception if text is invalid gen.enable(0) p.parseFragment('', 1) program, macros = gen.getCode() self.interpret(program) def do_evaluateCode(self, stuff): lang, program = stuff # Use a temporary stream to capture the interpretation of the # subnodes, which should /not/ go to the output stream. tmpstream = self.StringIO() self.pushStream(tmpstream) try: self.interpret(program) finally: self.popStream() code = tmpstream.getvalue() output = self.engine.evaluateCode(lang, code) self._stream_write(output) bytecode_handlers["evaluateCode"] = do_evaluateCode def do_loop(self, (name, expr, block)): self.interpret(block) def do_loop_tal(self, (name, expr, block)): iterator = self.engine.setRepeat(name, expr) while iterator.next(): self.interpret(block) bytecode_handlers["loop"] = do_loop def translate(self, msgid, default=None, i18ndict=None, obj=None, domain=None): if default is None: default = getattr(msgid, 'default', unicode(msgid)) if i18ndict is None: i18ndict = {} if domain is None: domain = getattr(msgid, 'domain', self.i18nContext.domain) if obj: i18ndict.update(obj) if not self.i18nInterpolate: return msgid # TODO: We need to pass in one of context or target_language return self.engine.translate(msgid, self.i18nContext.domain, i18ndict, default=default) def do_rawtextColumn(self, (s, col)): self._stream_write(s) self.col = col bytecode_handlers["rawtextColumn"] = do_rawtextColumn def do_rawtextOffset(self, (s, offset)): self._stream_write(s) self.col = self.col + offset bytecode_handlers["rawtextOffset"] = do_rawtextOffset def do_condition(self, (condition, block)): if not self.tal or self.engine.evaluateBoolean(condition): self.interpret(block) bytecode_handlers["condition"] = do_condition def do_defineMacro(self, (macroName, macro)): wasInUse = self.inUseDirective self.inUseDirective = False self.interpret(macro) self.inUseDirective = wasInUse bytecode_handlers["defineMacro"] = do_defineMacro def do_useMacro(self, (macroName, macroExpr, compiledSlots, block), definingName=None, extending=False): if not self.metal: self.interpret(block) return macro = self.engine.evaluateMacro(macroExpr) if macro is self.Default: macro = block else: if not isCurrentVersion(macro): raise METALError("macro %s has incompatible version %s" % (`macroName`, `getProgramVersion(macro)`), self.position) mode = getProgramMode(macro) if mode != (self.html and "html" or "xml"): raise METALError("macro %s has incompatible mode %s" % (`macroName`, `mode`), self.position) self.pushMacro(macroName, compiledSlots, definingName, extending) # We want 'macroname' name to be always available as a variable outer = self.engine.getValue('macroname') self.engine.setLocal('macroname', macroName.rsplit('/', 1)[-1]) prev_source = self.sourceFile wasInUse = self.inUseDirective self.inUseDirective = True self.interpret(macro) self.inUseDirective = wasInUse if self.sourceFile != prev_source: self.engine.setSourceFile(prev_source) self.sourceFile = prev_source self.popMacro() # Push the outer macroname again. self.engine.setLocal('macroname', outer) bytecode_handlers["useMacro"] = do_useMacro def do_extendMacro(self, (macroName, macroExpr, compiledSlots, block, definingName)): # extendMacro results from a combination of define-macro and # use-macro. definingName has the value of the # metal:define-macro attribute. extending = self.metal and self.inUseDirective self.do_useMacro((macroName, macroExpr, compiledSlots, block), definingName, extending) bytecode_handlers["extendMacro"] = do_extendMacro def do_fillSlot(self, (slotName, block)): # This is only executed if the enclosing 'use-macro' evaluates # to 'default'. self.interpret(block) bytecode_handlers["fillSlot"] = do_fillSlot def do_defineSlot(self, (slotName, block)): if not self.metal: self.interpret(block) return macs = self.macroStack if macs: len_macs = len(macs) # Measure the extension depth of this use-macro depth = 1 while depth < len_macs: if macs[-depth].extending: depth += 1 else: break # Search for a slot filler from the most specific to the # most general macro. The most general is at the top of # the stack. slot = None i = len_macs - 1 while i >= (len_macs - depth): slot = macs[i].slots.get(slotName) if slot is not None: break i -= 1 if slot is not None: # Found a slot filler. Temporarily chop the macro # stack starting at the macro that filled the slot and # render the slot filler. chopped = macs[i:] del macs[i:] try: self.interpret(slot) finally: # Restore the stack entries. for mac in chopped: mac.entering = False # Not entering macs.extend(chopped) return # Falling out of the 'if' allows the macro to be interpreted. self.interpret(block) bytecode_handlers["defineSlot"] = do_defineSlot def do_onError(self, (block, handler)): self.interpret(block) def do_onError_tal(self, (block, handler)): state = self.saveState() self.stream = stream = self.StringIO() self._stream_write = stream.write try: self.interpret(block) # TODO: this should not catch ZODB.POSException.ConflictError. # The ITALExpressionEngine interface should provide a way of # getting the set of exception types that should not be # handled. except: exc = sys.exc_info()[1] self.restoreState(state) engine = self.engine engine.beginScope() error = engine.createErrorInfo(exc, self.position) engine.setLocal('error', error) try: self.interpret(handler) finally: engine.endScope() else: self.restoreOutputState(state) self.stream_write(stream.getvalue()) bytecode_handlers["onError"] = do_onError bytecode_handlers_tal = bytecode_handlers.copy() bytecode_handlers_tal["rawtextBeginScope"] = do_rawtextBeginScope_tal bytecode_handlers_tal["beginScope"] = do_beginScope_tal bytecode_handlers_tal["setLocal"] = do_setLocal_tal bytecode_handlers_tal["setGlobal"] = do_setGlobal_tal bytecode_handlers_tal["insertStructure"] = do_insertStructure_tal bytecode_handlers_tal["insertI18nStructure"] = do_insertI18nStructure_tal bytecode_handlers_tal["insertText"] = do_insertText_tal bytecode_handlers_tal["insertI18nText"] = do_insertI18nText_tal bytecode_handlers_tal["loop"] = do_loop_tal bytecode_handlers_tal["onError"] = do_onError_tal bytecode_handlers_tal[""] = attrAction_tal bytecode_handlers_tal["optTag"] = do_optTag_tal class FasterStringIO(StringIO): """Append-only version of StringIO. This let's us have a much faster write() method. """ def close(self): if not self.closed: self.write = _write_ValueError StringIO.close(self) def seek(self, pos, mode=0): raise RuntimeError("FasterStringIO.seek() not allowed") def write(self, s): #assert self.pos == self.len self.buflist.append(s) self.len = self.pos = self.pos + len(s) def _write_ValueError(s): raise ValueError("I/O operation on closed file") zope2.13-2.13.21/source/zope.tal/src/zope/tal/translationcontext.py0000644000175000017500000000276312214017644024045 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Translation context object for the TALInterpreter's I18N support. The translation context provides a container for the information needed to perform translation of a marked string from a page template. $Id: translationcontext.py 26559 2004-07-15 21:22:32Z srichter $ """ DEFAULT_DOMAIN = "default" class TranslationContext(object): """Information about the I18N settings of a TAL processor.""" def __init__(self, parent=None, domain=None, target=None, source=None): if parent: if not domain: domain = parent.domain if not target: target = parent.target if not source: source = parent.source elif domain is None: domain = DEFAULT_DOMAIN self.parent = parent self.domain = domain self.target = target self.source = source zope2.13-2.13.21/source/zope.tal/src/zope/tal/DEPENDENCIES.cfg0000644000175000017500000000007512214017644022031 0ustar arnauarnauzope.i18n zope.i18nmessageid zope.interface zope.deprecation zope2.13-2.13.21/source/zope.tal/src/zope/tal/runtest.py0000644000175000017500000001061512214017644021601 0ustar arnauarnau#! /usr/bin/env python ############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Driver program to run METAL and TAL regression tests. $Id: runtest.py 29651 2005-03-23 12:56:35Z hdima $ """ import glob import os import sys import traceback from cStringIO import StringIO if __name__ == "__main__": import setpath # Local hack to tweak sys.path etc. import zope.tal.driver import zope.tal.tests.utils def showdiff(a, b): import ndiff cruncher = ndiff.SequenceMatcher(ndiff.IS_LINE_JUNK, a, b) for tag, alo, ahi, blo, bhi in cruncher.get_opcodes(): if tag == "equal": continue print nicerange(alo, ahi) + tag[0] + nicerange(blo, bhi) ndiff.dump('<', a, alo, ahi) if a and b: print '---' ndiff.dump('>', b, blo, bhi) def nicerange(lo, hi): if hi <= lo+1: return str(lo+1) else: return "%d,%d" % (lo+1, hi) def main(): opts = [] args = sys.argv[1:] quiet = 0 unittesting = 0 if args and args[0] == "-q": quiet = 1 del args[0] if args and args[0] == "-Q": unittesting = 1 del args[0] while args and args[0].startswith('-'): opts.append(args[0]) del args[0] if not args: prefix = os.path.join("tests", "input", "test*.") if zope.tal.tests.utils.skipxml: xmlargs = [] else: xmlargs = glob.glob(prefix + "xml") xmlargs.sort() htmlargs = glob.glob(prefix + "html") htmlargs.sort() args = xmlargs + htmlargs if not args: sys.stderr.write("No tests found -- please supply filenames\n") sys.exit(1) errors = 0 for arg in args: locopts = [] if arg.find("metal") >= 0 and "-m" not in opts: locopts.append("-m") if arg.find("_sa") >= 0 and "-a" not in opts: locopts.append("-a") if not unittesting: print arg, sys.stdout.flush() if zope.tal.tests.utils.skipxml and arg.endswith(".xml"): print "SKIPPED (XML parser not available)" continue save = sys.stdout, sys.argv try: try: sys.stdout = stdout = StringIO() sys.argv = [""] + opts + locopts + [arg] zope.tal.driver.main() finally: sys.stdout, sys.argv = save except SystemExit: raise except: errors = 1 if quiet: print sys.exc_type sys.stdout.flush() else: if unittesting: print else: print "Failed:" sys.stdout.flush() traceback.print_exc() continue head, tail = os.path.split(arg) outfile = os.path.join( head.replace("input", "output"), tail) try: f = open(outfile) except IOError: expected = None print "(missing file %s)" % outfile, else: expected = f.readlines() f.close() stdout.seek(0) if hasattr(stdout, "readlines"): actual = stdout.readlines() else: actual = readlines(stdout) if actual == expected: if not unittesting: print "OK" else: if unittesting: print else: print "not OK" errors = 1 if not quiet and expected is not None: showdiff(expected, actual) if errors: sys.exit(1) def readlines(f): L = [] while 1: line = f.readline() if not line: break L.append(line) return L if __name__ == "__main__": main() zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/0000755000175000017500000000000012214017644021452 5ustar arnauarnauzope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/dtml08.html0000644000175000017500000000451112214017644023451 0ustar arnauarnau &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/dtml03.html0000644000175000017500000000045012214017644023442 0ustar arnauarnau &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/dtml12.html0000644000175000017500000000061312214017644023443 0ustar arnauarnau &dtml-y0; &dtml-y1; &dtml-y2; &dtml-y3; &dtml-y4; &dtml-y5; &dtml-y6; &dtml-y7; zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/dtml11.html0000644000175000017500000000773612214017644023457 0ustar arnauarnau &dtml-x0; A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/tal06.html0000644000175000017500000000073512214017644023273 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/tal11.html0000644000175000017500000000776512214017644023301 0ustar arnauarnau A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/dtml01.html0000644000175000017500000000001112214017644023431 0ustar arnauarnaubaseline zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/dtml02.html0000644000175000017500000000764012214017644023451 0ustar arnauarnau A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/tal05.html0000644000175000017500000000061712214017644023271 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/dtml06.html0000644000175000017500000000056012214017644023447 0ustar arnauarnau &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/tal07.html0000644000175000017500000000551112214017644023271 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/dtml07.html0000644000175000017500000000451112214017644023450 0ustar arnauarnau &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/dtml05.html0000644000175000017500000000050012214017644023440 0ustar arnauarnau &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/__init__.py0000644000175000017500000000007512214017644023565 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/tal02.html0000644000175000017500000000764012214017644023271 0ustar arnauarnau A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/tal08.html0000644000175000017500000000731112214017644023272 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/tal12.html0000644000175000017500000000073712214017644023272 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/dtml09.html0000644000175000017500000000050112214017644023445 0ustar arnauarnau &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; &dtml-x4; &dtml-x5; &dtml-x6; &dtml-x7; zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/tal01.html0000644000175000017500000000001112214017644023251 0ustar arnauarnaubaseline zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/tal03.html0000644000175000017500000000055012214017644023263 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/dtml10.html0000644000175000017500000000767112214017644023454 0ustar arnauarnau A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/dtml04.html0000644000175000017500000000025412214017644023445 0ustar arnauarnau &dtml-x0; &dtml-x1; &dtml-x2; &dtml-x3; zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/tal10.html0000644000175000017500000000771012214017644023266 0ustar arnauarnau A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. A large chunk of text to be repeated. zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/tal09.html0000644000175000017500000000062012214017644023267 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/benchmark/tal04.html0000644000175000017500000000033312214017644023263 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/driver.py0000644000175000017500000001541612214017644021374 0ustar arnauarnau#!/usr/bin/env python ############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Driver program to test METAL and TAL implementation. Usage: driver.py [options] [file] Options: -h / --help Print this message and exit. -H / --html -x / --xml Explicitly choose HTML or XML input. The default is to automatically select based on the file extension. These options are mutually exclusive. -l Lenient structure insertion. -m Macro expansion only -s Print intermediate opcodes only -t Leave TAL/METAL attributes in output -i Leave I18N substitution strings un-interpolated. -a Enable source annotations $Id: driver.py 29651 2005-03-23 12:56:35Z hdima $ """ import os import sys import getopt if __name__ == "__main__": import setpath # Local hack to tweak sys.path etc. # Import local classes import zope.tal.taldefs from zope.tal.dummyengine import DummyEngine from zope.tal.dummyengine import DummyTranslationDomain FILE = "tests/input/test01.xml" class TestTranslations(DummyTranslationDomain): def translate(self, msgid, mapping=None, context=None, target_language=None, default=None): if msgid == 'timefmt': return '%(minutes)s minutes after %(hours)s %(ampm)s' % mapping elif msgid == 'jobnum': return '%(jobnum)s is the JOB NUMBER' % mapping elif msgid == 'verify': s = 'Your contact email address is recorded as %(email)s' return s % mapping elif msgid == 'mailto:${request/submitter}': return 'mailto:bperson@dom.ain' elif msgid == 'origin': return '%(name)s was born in %(country)s' % mapping return DummyTranslationDomain.translate( self, msgid, mapping, context, target_language, default=default) class TestEngine(DummyEngine): def __init__(self, macros=None): DummyEngine.__init__(self, macros) self.translationDomain = TestTranslations() def evaluatePathOrVar(self, expr): if expr == 'here/currentTime': return {'hours' : 6, 'minutes': 59, 'ampm' : 'PM', } elif expr == 'context/@@object_name': return '7' elif expr == 'request/submitter': return 'aperson@dom.ain' return DummyEngine.evaluatePathOrVar(self, expr) # This is a disgusting hack so that we can use engines that actually know # something about certain object paths. TimeEngine knows about # here/currentTime. ENGINES = {'test23.html': TestEngine, 'test24.html': TestEngine, 'test26.html': TestEngine, 'test27.html': TestEngine, 'test28.html': TestEngine, 'test29.html': TestEngine, 'test30.html': TestEngine, 'test31.html': TestEngine, 'test32.html': TestEngine, } def usage(code, msg=''): print >> sys.stderr, __doc__ if msg: print >> sys.stderr, msg sys.exit(code) def main(): macros = 0 mode = None showcode = 0 showtal = -1 sourceAnnotations = 0 strictinsert = 1 i18nInterpolate = 1 try: opts, args = getopt.getopt(sys.argv[1:], "hHxlmstia", ['help', 'html', 'xml']) except getopt.error, msg: usage(2, msg) for opt, arg in opts: if opt in ('-h', '--help'): usage(0) if opt in ('-H', '--html'): if mode == 'xml': usage(1, '--html and --xml are mutually exclusive') mode = "html" if opt == '-l': strictinsert = 0 if opt == '-m': macros = 1 if opt in ('-x', '--xml'): if mode == 'html': usage(1, '--html and --xml are mutually exclusive') mode = "xml" if opt == '-s': showcode = 1 if opt == '-t': showtal = 1 if opt == '-i': i18nInterpolate = 0 if opt == '-a': sourceAnnotations = 1 if args: file = args[0] else: file = FILE it = compilefile(file, mode) if showcode: showit(it) else: # See if we need a special engine for this test engine = None engineClass = ENGINES.get(os.path.basename(file)) if engineClass is not None: engine = engineClass(macros) interpretit(it, engine=engine, tal=(not macros), showtal=showtal, strictinsert=strictinsert, i18nInterpolate=i18nInterpolate, sourceAnnotations=sourceAnnotations) def interpretit(it, engine=None, stream=None, tal=1, showtal=-1, strictinsert=1, i18nInterpolate=1, sourceAnnotations=0): from zope.tal.talinterpreter import TALInterpreter program, macros = it assert zope.tal.taldefs.isCurrentVersion(program) if engine is None: engine = DummyEngine(macros) TALInterpreter(program, macros, engine, stream, wrap=0, tal=tal, showtal=showtal, strictinsert=strictinsert, i18nInterpolate=i18nInterpolate, sourceAnnotations=sourceAnnotations)() def compilefile(file, mode=None): assert mode in ("html", "xml", None) if mode is None: ext = os.path.splitext(file)[1] if ext.lower() in (".html", ".htm"): mode = "html" else: mode = "xml" from zope.tal.talgenerator import TALGenerator filename = os.path.abspath(file) prefix = os.path.dirname(os.path.abspath(__file__)) + os.path.sep if filename.startswith(prefix): filename = filename[len(prefix):] filename = filename.replace(os.sep, '/') # test files expect slashes if mode == "html": from zope.tal.htmltalparser import HTMLTALParser p = HTMLTALParser(gen=TALGenerator(source_file=filename, xml=0)) else: from zope.tal.talparser import TALParser p = TALParser(gen=TALGenerator(source_file=filename)) p.parseFile(file) return p.getCode() def showit(it): from pprint import pprint pprint(it) if __name__ == "__main__": main() zope2.13-2.13.21/source/zope.tal/src/zope/tal/interfaces.py0000644000175000017500000001534512214017644022225 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interface that a TAL expression implementation provides to the METAL/TAL implementation. $Id: interfaces.py 37831 2005-08-10 15:36:52Z fdrake $ """ from zope.interface import Attribute, Interface class ITALExpressionCompiler(Interface): """Compile-time interface provided by a TAL expression implementation. The TAL compiler needs an instance of this interface to support compilation of TAL expressions embedded in documents containing TAL and METAL constructs. """ def getCompilerError(): """Return the exception class raised for compilation errors. """ def compile(expression): """Return a compiled form of 'expression' for later evaluation. 'expression' is the source text of the expression. The return value may be passed to the various evaluate*() methods of the ITALExpressionEngine interface. No compatibility is required for the values of the compiled expression between different ITALExpressionEngine implementations. """ def getContext(namespace): """Create an expression execution context The given namespace provides the initial top-level names. """ class ITALExpressionEngine(Interface): """Render-time interface provided by a TAL expression implementation. The TAL interpreter uses this interface to TAL expression to support evaluation of the compiled expressions returned by ITALExpressionCompiler.compile(). """ def getDefault(): """Return the value of the 'default' TAL expression. Checking a value for a match with 'default' should be done using the 'is' operator in Python. """ def setPosition((lineno, offset)): """Inform the engine of the current position in the source file. This is used to allow the evaluation engine to report execution errors so that site developers can more easily locate the offending expression. """ def setSourceFile(filename): """Inform the engine of the name of the current source file. This is used to allow the evaluation engine to report execution errors so that site developers can more easily locate the offending expression. """ def beginScope(): """Push a new scope onto the stack of open scopes. """ def endScope(): """Pop one scope from the stack of open scopes. """ def evaluate(compiled_expression): """Evaluate an arbitrary expression. No constraints are imposed on the return value. """ def evaluateBoolean(compiled_expression): """Evaluate an expression that must return a Boolean value. """ def evaluateMacro(compiled_expression): """Evaluate an expression that must return a macro program. """ def evaluateStructure(compiled_expression): """Evaluate an expression that must return a structured document fragment. The result of evaluating 'compiled_expression' must be a string containing a parsable HTML or XML fragment. Any TAL markup contained in the result string will be interpreted. """ def evaluateText(compiled_expression): """Evaluate an expression that must return text. The returned text should be suitable for direct inclusion in the output: any HTML or XML escaping or quoting is the responsibility of the expression itself. If the expression evaluates to None, then that is returned. It represents 'nothing' in TALES. If the expression evaluates to what getDefault() of this interface returns, by comparison using 'is', then that is returned. It represents 'default' in TALES. """ def evaluateValue(compiled_expression): """Evaluate an arbitrary expression. No constraints are imposed on the return value. """ def createErrorInfo(exception, (lineno, offset)): """Returns an ITALExpressionErrorInfo object. The returned object is used to provide information about the error condition for the on-error handler. """ def setGlobal(name, value): """Set a global variable. The variable will be named 'name' and have the value 'value'. """ def setLocal(name, value): """Set a local variable in the current scope. The variable will be named 'name' and have the value 'value'. """ def getValue(name, default=None): """Get a variable by name. If the variable does not exist, return default. """ def setRepeat(name, compiled_expression): """Start a repetition, returning an ITALIterator. The engine is expected to add the a value (typically the returned iterator) for the name to the variable namespace. """ def translate(msgid, domain=None, mapping=None, default=None): """See zope.i18n.interfaces.ITranslationDomain.translate""" # NB: This differs from the Zope 2 equivalent in the order of # the arguments. This will be a (hopefully minor) issue when # creating a unified TAL implementation. def evaluateCode(lang, code): """Evaluates code of the given language. Returns whatever the code outputs. This can be defined on a per-language basis. In Python this usually everything the print statement will return. """ class ITALIterator(Interface): """A TAL iterator Not to be confused with a Python iterator. """ def next(): """Advance to the next value in the iteration, if possible Return a true value if it was possible to advance and return a false value otherwise. """ class ITALExpressionErrorInfo(Interface): type = Attribute("type", "The exception class.") value = Attribute("value", "The exception instance.") lineno = Attribute("lineno", "The line number the error occurred on in the source.") offset = Attribute("offset", "The character offset at which the error occurred.") zope2.13-2.13.21/source/zope.tal/src/zope/tal/xmlparser.py0000644000175000017500000000726612214017644022122 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Generic Expat-based XML parser base class. This creates a parser with namespace processing enabled. $Id: xmlparser.py 72023 2007-01-14 13:54:17Z philikon $ """ import logging class XMLParser(object): ordered_attributes = 0 handler_names = [ "StartElementHandler", "EndElementHandler", "ProcessingInstructionHandler", "CharacterDataHandler", "UnparsedEntityDeclHandler", "NotationDeclHandler", "StartNamespaceDeclHandler", "EndNamespaceDeclHandler", "CommentHandler", "StartCdataSectionHandler", "EndCdataSectionHandler", "DefaultHandler", "DefaultHandlerExpand", "NotStandaloneHandler", "ExternalEntityRefHandler", "XmlDeclHandler", "StartDoctypeDeclHandler", "EndDoctypeDeclHandler", "ElementDeclHandler", "AttlistDeclHandler" ] def __init__(self, encoding=None): self.parser = p = self.createParser(encoding) if self.ordered_attributes: try: self.parser.ordered_attributes = self.ordered_attributes except AttributeError: logging.warn("TAL.XMLParser: Can't set ordered_attributes") self.ordered_attributes = 0 for name in self.handler_names: method = getattr(self, name, None) if method is not None: try: setattr(p, name, method) except AttributeError: logging.error("TAL.XMLParser: Can't set " "expat handler %s" % name) def createParser(self, encoding=None): global XMLParseError from xml.parsers import expat XMLParseError = expat.ExpatError return expat.ParserCreate(encoding, ' ') def parseFile(self, filename): self.parseStream(open(filename)) def parseString(self, s): if isinstance(s, unicode): # Expat cannot deal with unicode strings, only with # encoded ones. Also, its range of encodings is rather # limited, UTF-8 is the safest bet here. s = s.encode('utf-8') self.parser.Parse(s, 1) def parseURL(self, url): import urllib self.parseStream(urllib.urlopen(url)) def parseStream(self, stream): self.parser.ParseFile(stream) def parseFragment(self, s, end=0): self.parser.Parse(s, end) def getpos(self): # Apparently ErrorLineNumber and ErrorLineNumber contain the current # position even when there was no error. This contradicts the official # documentation[1], but expat.h[2] contains the following definition: # # /* For backwards compatibility with previous versions. */ # #define XML_GetErrorLineNumber XML_GetCurrentLineNumber # # [1] http://python.org/doc/current/lib/xmlparser-objects.html # [2] http://cvs.sourceforge.net/viewcvs.py/expat/expat/lib/expat.h return (self.parser.ErrorLineNumber, self.parser.ErrorColumnNumber) zope2.13-2.13.21/source/zope.tal/src/zope/tal/__init__.py0000644000175000017500000000007512214017644021633 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.tal/src/zope/tal/setpath.py0000644000175000017500000000336512214017644021551 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Read a module search path from .path file. If .path file isn't found in the directory of the setpath.py module, then try to import ZODB. If that succeeds, we assume the path is already set up correctly. If that import fails, an IOError is raised. $Id: setpath.py 29651 2005-03-23 12:56:35Z hdima $ """ # TODO: Why does this want to find ZODB ??? import os import sys dir = os.path.dirname(__file__) path = os.path.join(dir, ".path") try: f = open(path) except IOError: try: # If we can import ZODB, our sys.path is set up well enough already import ZODB except ImportError: raise IOError("Can't find ZODB package. Please edit %s to point to " "your Zope's lib/python directory" % path) else: for line in f.readlines(): line = line.strip() if line and line[0] != '#': for dir in line.split(os.pathsep): dir = os.path.expanduser(os.path.expandvars(dir)) if dir not in sys.path: sys.path.append(dir) # Must import this first to initialize Persistence properly import ZODB zope2.13-2.13.21/source/zope.tal/src/zope/tal/taldefs.py0000644000175000017500000001336612214017644021525 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Common definitions used by TAL and METAL compilation and transformation. $Id: taldefs.py 41477 2006-01-28 19:52:03Z hdima $ """ import re from zope.tal.interfaces import ITALExpressionErrorInfo from zope.interface import implements TAL_VERSION = "1.6" XML_NS = "http://www.w3.org/XML/1998/namespace" # URI for XML namespace XMLNS_NS = "http://www.w3.org/2000/xmlns/" # URI for XML NS declarations ZOPE_TAL_NS = "http://xml.zope.org/namespaces/tal" ZOPE_METAL_NS = "http://xml.zope.org/namespaces/metal" ZOPE_I18N_NS = "http://xml.zope.org/namespaces/i18n" # This RE must exactly match the expression of the same name in the # zope.i18n.simpletranslationservice module: NAME_RE = "[a-zA-Z_][-a-zA-Z0-9_]*" KNOWN_METAL_ATTRIBUTES = frozenset([ "define-macro", "extend-macro", "use-macro", "define-slot", "fill-slot", ]) KNOWN_TAL_ATTRIBUTES = frozenset([ "define", "condition", "content", "replace", "repeat", "attributes", "on-error", "omit-tag", "script", "tal tag", # a pseudo attribute that holds the namespace of elements # like , , ]) KNOWN_I18N_ATTRIBUTES = frozenset([ "translate", "domain", "target", "source", "attributes", "data", "name", ]) class TALError(Exception): def __init__(self, msg, position=(None, None)): assert msg != "" self.msg = msg self.lineno = position[0] self.offset = position[1] self.filename = None def setFile(self, filename): self.filename = filename def __str__(self): result = self.msg if self.lineno is not None: result = result + ", at line %d" % self.lineno if self.offset is not None: result = result + ", column %d" % (self.offset + 1) if self.filename is not None: result = result + ', in file %s' % self.filename return result class METALError(TALError): pass class TALExpressionError(TALError): pass class I18NError(TALError): pass class ErrorInfo(object): implements(ITALExpressionErrorInfo) def __init__(self, err, position=(None, None)): if isinstance(err, Exception): self.type = err.__class__ self.value = err else: self.type = err self.value = None self.lineno = position[0] self.offset = position[1] _attr_re = re.compile(r"\s*([^\s]+)\s+([^\s].*)\Z", re.S) _subst_re = re.compile(r"\s*(?:(text|structure)\s+)?(.*)\Z", re.S) def parseAttributeReplacements(arg, xml): dict = {} for part in splitParts(arg): m = _attr_re.match(part) if not m: raise TALError("Bad syntax in attributes: %r" % part) name, expr = m.groups() if not xml: name = name.lower() if name in dict: raise TALError("Duplicate attribute name in attributes: %r" % part) dict[name] = expr return dict def parseSubstitution(arg, position=(None, None)): m = _subst_re.match(arg) if not m: raise TALError("Bad syntax in substitution text: %r" % arg, position) key, expr = m.groups() if not key: key = "text" return key, expr def splitParts(arg): # Break in pieces at undoubled semicolons and # change double semicolons to singles: arg = arg.replace(";;", "\0") parts = arg.split(';') parts = [p.replace("\0", ";") for p in parts] if len(parts) > 1 and not parts[-1].strip(): del parts[-1] # It ended in a semicolon return parts def isCurrentVersion(program): version = getProgramVersion(program) return version == TAL_VERSION def isinstance_(ob, type): # Proxy-friendly and faster isinstance_ check for new-style objects try: return type in ob.__class__.__mro__ except AttributeError: return False def getProgramMode(program): version = getProgramVersion(program) if (version == TAL_VERSION and isinstance_(program[1], tuple) and len(program[1]) == 2): opcode, mode = program[1] if opcode == "mode": return mode return None def getProgramVersion(program): if (len(program) >= 2 and isinstance_(program[0], tuple) and len(program[0]) == 2): opcode, version = program[0] if opcode == "version": return version return None _ent1_re = re.compile('&(?![A-Z#])', re.I) _entch_re = re.compile('&([A-Z][A-Z0-9]*)(?![A-Z0-9;])', re.I) _entn1_re = re.compile('&#(?![0-9X])', re.I) _entnx_re = re.compile('&(#X[A-F0-9]*)(?![A-F0-9;])', re.I) _entnd_re = re.compile('&(#[0-9][0-9]*)(?![0-9;])') def attrEscape(s): """Replace special characters '&<>' by character entities, except when '&' already begins a syntactically valid entity.""" s = _ent1_re.sub('&', s) s = _entch_re.sub(r'&\1', s) s = _entn1_re.sub('&#', s) s = _entnx_re.sub(r'&\1', s) s = _entnd_re.sub(r'&\1', s) s = s.replace('<', '<') s = s.replace('>', '>') s = s.replace('"', '"') return s import cgi def quote(s, escape=cgi.escape): return '"%s"' % escape(s, 1) del cgi zope2.13-2.13.21/source/zope.tal/src/zope/tal/dummyengine.py0000644000175000017500000002463312214017644022423 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002, 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Dummy TAL expression engine so that I can test out the TAL implementation. $Id: dummyengine.py 93717 2008-12-06 13:25:25Z MatthewWilkes $ """ import re from zope.interface import implements from zope.tal.taldefs import NAME_RE, TALExpressionError, ErrorInfo from zope.tal.interfaces import ITALExpressionCompiler, ITALExpressionEngine from zope.i18nmessageid import Message Default = object() name_match = re.compile(r"(?s)(%s):(.*)\Z" % NAME_RE).match class CompilerError(Exception): pass class DummyEngine(object): position = None source_file = None implements(ITALExpressionCompiler, ITALExpressionEngine) def __init__(self, macros=None): if macros is None: macros = {} self.macros = macros dict = {'nothing': None, 'default': Default} self.locals = self.globals = dict self.stack = [dict] self.translationDomain = DummyTranslationDomain() self.useEngineAttrDicts = False # zope.tal.interfaces.ITALExpressionCompiler def getCompilerError(self): return CompilerError def compile(self, expr): return "$%s$" % expr # zope.tal.interfaces.ITALExpressionEngine def setSourceFile(self, source_file): self.source_file = source_file def setPosition(self, position): self.position = position def beginScope(self): self.stack.append(self.locals) def endScope(self): assert len(self.stack) > 1, "more endScope() than beginScope() calls" self.locals = self.stack.pop() def setLocal(self, name, value): if self.locals is self.stack[-1]: # Unmerge this scope's locals from previous scope of first set self.locals = self.locals.copy() self.locals[name] = value def setGlobal(self, name, value): self.globals[name] = value def getValue(self, name, default=None): value = self.globals.get(name, default) if value is default: value = self.locals.get(name, default) return value def evaluate(self, expression): assert expression.startswith("$") and expression.endswith("$"), \ expression expression = expression[1:-1] m = name_match(expression) if m: type, expr = m.group(1, 2) else: type = "path" expr = expression if type in ("string", "str"): return expr if type in ("path", "var", "global", "local"): return self.evaluatePathOrVar(expr) if type == "not": return not self.evaluate(expr) if type == "exists": return self.locals.has_key(expr) or self.globals.has_key(expr) if type == "python": try: return eval(expr, self.globals, self.locals) except: raise TALExpressionError("evaluation error in %s" % `expr`) if type == "position": # Insert the current source file name, line number, # and column offset. if self.position: lineno, offset = self.position else: lineno, offset = None, None return '%s (%s,%s)' % (self.source_file, lineno, offset) raise TALExpressionError("unrecognized expression: " + `expression`) # implementation; can be overridden def evaluatePathOrVar(self, expr): expr = expr.strip() if self.locals.has_key(expr): return self.locals[expr] elif self.globals.has_key(expr): return self.globals[expr] else: raise TALExpressionError("unknown variable: %s" % `expr`) def evaluateValue(self, expr): return self.evaluate(expr) def evaluateBoolean(self, expr): return self.evaluate(expr) def evaluateText(self, expr): text = self.evaluate(expr) if isinstance(text, (str, unicode, Message)): return text if text is not None and text is not Default: text = str(text) return text def evaluateStructure(self, expr): # TODO Should return None or a DOM tree return self.evaluate(expr) # implementation; can be overridden def evaluateSequence(self, expr): # TODO: Should return a sequence return self.evaluate(expr) def evaluateMacro(self, macroName): assert macroName.startswith("$") and macroName.endswith("$"), \ macroName macroName = macroName[1:-1] file, localName = self.findMacroFile(macroName) if not file: # Local macro macro = self.macros[localName] else: # External macro import driver program, macros = driver.compilefile(file) macro = macros.get(localName) if not macro: raise TALExpressionError("macro %s not found in file %s" % (localName, file)) return macro # internal def findMacroFile(self, macroName): if not macroName: raise TALExpressionError("empty macro name") i = macroName.rfind('/') if i < 0: # No slash -- must be a locally defined macro return None, macroName else: # Up to last slash is the filename fileName = macroName[:i] localName = macroName[i+1:] return fileName, localName def setRepeat(self, name, expr): seq = self.evaluateSequence(expr) return Iterator(name, seq, self) def createErrorInfo(self, err, position): return ErrorInfo(err, position) def getDefault(self): return Default def translate(self, msgid, domain=None, mapping=None, default=None): self.translationDomain.domain = domain return self.translationDomain.translate( msgid, mapping, default=default) def evaluateCode(self, lang, code): # We probably implement too much, but I use the dummy engine to test # some of the issues that we will have. # For testing purposes only locals = {} globals = {} if self.useEngineAttrDicts: globals = self.globals.copy() locals = self.locals.copy() assert lang == 'text/server-python' import sys, StringIO # Removing probable comments if code.strip().startswith(''): code = code.strip()[4:-3] # Prepare code. lines = code.split('\n') lines = filter(lambda l: l.strip() != '', lines) code = '\n'.join(lines) # This saves us from all indentation issues :) if code.startswith(' ') or code.startswith('\t'): code = 'if 1 == 1:\n' + code + '\n' tmp = sys.stdout sys.stdout = StringIO.StringIO() try: exec code in globals, locals finally: result = sys.stdout sys.stdout = tmp # For testing purposes only self.codeLocals = locals self.codeGlobals = globals self.locals.update(locals) self.globals.update(globals) return result.getvalue() class Iterator(object): def __init__(self, name, seq, engine): self.name = name self.seq = seq self.engine = engine self.nextIndex = 0 def next(self): i = self.nextIndex try: item = self.seq[i] except IndexError: return 0 self.nextIndex = i+1 self.engine.setLocal(self.name, item) return 1 class DummyTranslationDomain(object): domain = '' msgids = {} def appendMsgid(self, domain, data): if not self.msgids.has_key(domain): self.msgids[domain] = [] self.msgids[domain].append(data) def getMsgids(self, domain): return self.msgids[domain] def clearMsgids(self): self.msgids = {} def translate(self, msgid, mapping=None, context=None, target_language=None, default=None): domain = self.domain # This is a fake translation service which simply uppercases non # ${name} placeholder text in the message id. # # First, transform a string with ${name} placeholders into a list of # substrings. Then upcase everything but the placeholders, then glue # things back together. # If the domain is a string method, then transform the string # by calling that method. # MessageID attributes override arguments if isinstance(msgid, Message): domain = msgid.domain mapping = msgid.mapping default = msgid.default if default is None: # Message doesn't substitute itself for default = msgid # missing default # simulate an unknown msgid by returning None if msgid == "don't translate me": text = default elif domain and hasattr('', domain): text = getattr(msgid, domain)() else: domain = 'default' text = msgid.upper() self.appendMsgid(domain, (msgid, mapping)) def repl(m): return unicode(mapping[m.group(m.lastindex).lower()]) cre = re.compile(r'\$(?:([_A-Za-z][-\w]*)|\{([_A-Za-z][-\w]*)\})') return cre.sub(repl, text) class MultipleDomainsDummyEngine(DummyEngine): def translate(self, msgid, domain=None, mapping=None, default=None): if isinstance(msgid, Message): domain = msgid.domain if domain == 'a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine': domain = 'lower' self.translationDomain.domain = domain return self.translationDomain.translate( msgid, mapping, default=default) zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/0000755000175000017500000000000012214017644020662 5ustar arnauarnauzope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/test_talinterpreter.py0000644000175000017500000010264612214017644025350 0ustar arnauarnau# -*- coding: ISO-8859-1 -*- ############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for TALInterpreter. $Id: test_talinterpreter.py 99007 2009-04-08 12:35:42Z tseaver $ """ import os import sys import unittest from StringIO import StringIO from zope.tal.taldefs import METALError, I18NError, TAL_VERSION from zope.tal.taldefs import TALExpressionError from zope.tal.htmltalparser import HTMLTALParser from zope.tal.talparser import TALParser from zope.tal.talinterpreter import TALInterpreter from zope.tal.talgenerator import TALGenerator from zope.tal.dummyengine import DummyEngine from zope.tal.dummyengine import MultipleDomainsDummyEngine from zope.tal.dummyengine import DummyTranslationDomain from zope.tal.tests import utils from zope.i18nmessageid import Message class TestCaseBase(unittest.TestCase): def _compile(self, source, source_file=None): generator = TALGenerator(xml=0, source_file=source_file) parser = HTMLTALParser(generator) parser.parseString(source) program, macros = parser.getCode() return program, macros class MacroErrorsTestCase(TestCaseBase): def setUp(self): dummy, macros = self._compile('

    Booh

    ') self.macro = macros['M'] self.engine = DummyEngine(macros) program, dummy = self._compile('

    Bah

    ') self.interpreter = TALInterpreter(program, {}, self.engine) def tearDown(self): try: self.interpreter() except METALError: pass else: self.fail("Expected METALError") def test_mode_error(self): self.macro[1] = ("mode", "duh") def test_version_error(self): self.macro[0] = ("version", "duh") class MacroFunkyErrorTest(TestCaseBase): def test_div_in_p_using_macro(self): dummy, macros = self._compile('

    Booh

    ') engine = DummyEngine(macros) program, dummy = self._compile( '

    foo

    ') interpreter = TALInterpreter(program, {}, engine) output = interpreter() self.assertEqual(output, '

    foo

    ') class MacroExtendTestCase(TestCaseBase): def setUp(self): s = self._read(('input', 'pnome_template.pt')) self.pnome_program, pnome_macros = self._compile(s) s = self._read(('input', 'acme_template.pt')) self.acme_program, acme_macros = self._compile(s) s = self._read(('input', 'document_list.pt')) self.doclist_program, doclist_macros = self._compile(s) macros = { 'pnome_macros_page': pnome_macros['page'], 'acme_macros_page': acme_macros['page'], } self.engine = DummyEngine(macros) def _read(self, path): dir = os.path.dirname(__file__) fn = os.path.join(dir, *path) f = open(fn) data = f.read() f.close() return data def test_preview_acme_template(self): # An ACME designer is previewing the ACME design. For the # purposes of this use case, extending a macro should act the # same as using a macro. result = StringIO() interpreter = TALInterpreter( self.acme_program, {}, self.engine, stream=result) interpreter() actual = result.getvalue().strip() expected = self._read(('output', 'acme_template.html')).strip() self.assertEqual(actual, expected) def test_preview_acme_template_source(self): # Render METAL attributes in acme_template result = StringIO() interpreter = TALInterpreter( self.acme_program, {}, self.engine, stream=result, tal=False) interpreter() actual = result.getvalue().strip() expected = self._read(('output', 'acme_template_source.html')).strip() self.assertEqual(actual, expected) class I18NCornerTestCaseBase(TestCaseBase): def factory(self, msgid, default, mapping={}): raise NotImplementedError("abstract method") def setUp(self): self.engine = DummyEngine() # Make sure we'll translate the msgid not its unicode representation self.engine.setLocal('foo', self.factory('FoOvAlUe${empty}', 'default', {'empty': ''})) self.engine.setLocal('bar', 'BaRvAlUe') def _check(self, program, expected): result = StringIO() self.interpreter = TALInterpreter(program, {}, self.engine, stream=result) self.interpreter() self.assertEqual(expected, result.getvalue()) def test_simple_messageid_translate(self): # This test is mainly here to make sure our DummyEngine works # correctly. program, macros = self._compile( '') self._check(program, 'FOOVALUE') program, macros = self._compile( '') self._check(program, 'FOOVALUE') # i18n messages defined in Python are translated automatically # (no i18n:translate necessary) program, macros = self._compile( '') self._check(program, 'FOOVALUE') program, macros = self._compile( '') self._check(program, 'FOOVALUE') def test_attributes_translation(self): program, macros = self._compile( '') self._check(program, '') program, macros = self._compile( '') self._check(program, '') program, macros = self._compile( '') self._check(program, '') # i18n messages defined in Python are translated automatically # (no i18n:attributes necessary) program, macros = self._compile( '') self._check(program, '') def test_text_variable_translate(self): program, macros = self._compile( '') self._check(program, 'BaRvAlUe') program, macros = self._compile( '') self._check(program, 'BARVALUE') program, macros = self._compile( '') self._check(program, 'BARVALUE') def test_text_translate(self): program, macros = self._compile( '') self._check(program, 'BaR') program, macros = self._compile( '') self._check(program, 'BAR') program, macros = self._compile( '') self._check(program, 'BAR') def test_structure_text_variable_translate(self): program, macros = self._compile( '') self._check(program, 'BaRvAlUe') program, macros = self._compile( '') self._check(program, 'BARVALUE') program, macros = self._compile( '') self._check(program, 'BARVALUE') # i18n messages defined in Python are translated automatically # (no i18n:translate necessary) program, macros = self._compile( '') self._check(program, 'FOOVALUE') program, macros = self._compile( '') self._check(program, 'FOOVALUE') def test_structure_text_translate(self): program, macros = self._compile( '') self._check(program, 'BaR') program, macros = self._compile( '') self._check(program, 'BAR') program, macros = self._compile( '') self._check(program, 'BAR') def test_replace_with_messageid_and_i18nname(self): program, macros = self._compile( '
    ' '' '
    ') self._check(program, '
    FOOVALUE
    ') def test_pythonexpr_replace_with_messageid_and_i18nname(self): program, macros = self._compile( '
    ' '' '
    ') self._check(program, '
    FOOVALUE
    ') def test_structure_replace_with_messageid_and_i18nname(self): program, macros = self._compile( '
    ' '' '
    ') self._check(program, '
    FOOVALUE
    ') def test_complex_replace_with_messageid_and_i18nname(self): program, macros = self._compile( '
    ' '' '' '' '
    ') self._check(program, '
    FOOVALUE
    ') def test_content_with_messageid_and_i18nname(self): program, macros = self._compile( '
    ' '' '
    ') self._check(program, '
    FOOVALUE
    ') def test_content_with_messageid_and_i18nname_and_i18ntranslate(self): # Let's tell the user this is incredibly silly! self.assertRaises( I18NError, self._compile, '') def test_content_with_explicit_messageid(self): # Let's tell the user this is incredibly silly! self.assertRaises( I18NError, self._compile, '') def test_content_with_plaintext_and_i18nname_and_i18ntranslate(self): # Let's tell the user this is incredibly silly! self.assertRaises( I18NError, self._compile, 'green') def test_translate_static_text_as_dynamic(self): program, macros = self._compile( '
    This is text for ' '.' '
    ') self._check(program, '
    THIS IS TEXT FOR BaRvAlUe.
    ') program, macros = self._compile( '
    This is text for ' '.' '
    ') self._check(program, '
    THIS IS TEXT FOR BARVALUE.
    ') def test_translate_static_text_as_dynamic_from_bytecode(self): program = [('version', TAL_VERSION), ('mode', 'html'), ('setPosition', (1, 0)), ('beginScope', {'i18n:translate': ''}), ('startTag', ('div', [('i18n:translate', '', 'i18n')])), ('insertTranslation', ('', [('rawtextOffset', ('This is text for ', 17)), ('setPosition', (1, 40)), ('beginScope', {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}), ('i18nVariable', ('bar_name', [('startTag', ('span', [('i18n:translate', '', 'i18n'), ('tal:content', 'bar', 'tal'), ('i18n:name', 'bar_name', 'i18n')])), ('insertTranslation', ('', [('insertText', ('$bar$', []))])), ('rawtextOffset', ('
    ', 7))], None, 0)), ('endScope', ()), ('rawtextOffset', ('.', 1))])), ('endScope', ()), ('rawtextOffset', ('
    ', 6)) ] self._check(program, '
    THIS IS TEXT FOR BARVALUE.
    ') def test_for_correct_msgids(self): self.engine.translationDomain.clearMsgids() result = StringIO() #GChapelle: #I have the feeling the i18n:translate with the i18n:name is wrong # #program, macros = self._compile( # '
    This is text for ' # '.
    ') program, macros = self._compile( '
    This is text for ' '.
    ') self.interpreter = TALInterpreter(program, {}, self.engine, stream=result) self.interpreter() msgids = self.engine.translationDomain.getMsgids('default') msgids.sort() self.assertEqual(1, len(msgids)) self.assertEqual('This is text for ${bar_name}.', msgids[0][0]) self.assertEqual({'bar_name': 'BaRvAlUe'}, msgids[0][1]) self.assertEqual( '
    THIS IS TEXT FOR BaRvAlUe.
    ', result.getvalue()) def test_for_correct_msgids_translate_name(self): self.engine.translationDomain.clearMsgids() result = StringIO() program, macros = self._compile( '
    This is text for ' '.
    ') self.interpreter = TALInterpreter(program, {}, self.engine, stream=result) self.interpreter() msgids = self.engine.translationDomain.getMsgids('default') msgids.sort() self.assertEqual(2, len(msgids)) self.assertEqual('This is text for ${bar_name}.', msgids[1][0]) self.assertEqual({'bar_name': 'BARVALUE'}, msgids[1][1]) self.assertEqual( '
    THIS IS TEXT FOR BARVALUE.
    ', result.getvalue()) def test_i18ntranslate_i18nname_and_attributes(self): # Test for Issue 301: Bug with i18n:name and i18n:translate # on the same element self.engine.translationDomain.clearMsgids() result = StringIO() program, macros = self._compile( '

    ' 'Some static text and a link text.

    ') self.interpreter = TALInterpreter(program, {}, self.engine, stream=result) self.interpreter() msgids = self.engine.translationDomain.getMsgids('default') msgids.sort() self.assertEqual(2, len(msgids)) self.assertEqual('Some static text and a ${link}.', msgids[0][0]) self.assertEqual({'link': 'LINK TEXT'}, msgids[0][1]) self.assertEqual('link text', msgids[1][0]) self.assertEqual( '

    SOME STATIC TEXT AND A LINK TEXT.

    ', result.getvalue()) def test_for_raw_msgids(self): # Test for Issue 314: i18n:translate removes line breaks from #
    ...
    contents # HTML mode self.engine.translationDomain.clearMsgids() result = StringIO() program, macros = self._compile( '
    This is text\n' ' \tfor\n div.
    ' '
     This is text\n'
                ' \tfor\n pre. 
    ') self.interpreter = TALInterpreter(program, {}, self.engine, stream=result) self.interpreter() msgids = self.engine.translationDomain.getMsgids('default') msgids.sort() self.assertEqual(2, len(msgids)) self.assertEqual(' This is text\n \tfor\n pre. ', msgids[0][0]) self.assertEqual('This is text for div.', msgids[1][0]) self.assertEqual( '
    THIS IS TEXT FOR DIV.
    ' '
     THIS IS TEXT\n \tFOR\n PRE. 
    ', result.getvalue()) # XML mode self.engine.translationDomain.clearMsgids() result = StringIO() parser = TALParser() parser.parseString( '\n' '
     This is text\n'
                ' \tfor\n barvalue. 
    ') program, macros = parser.getCode() self.interpreter = TALInterpreter(program, {}, self.engine, stream=result) self.interpreter() msgids = self.engine.translationDomain.getMsgids('default') msgids.sort() self.assertEqual(1, len(msgids)) self.assertEqual('This is text for barvalue.', msgids[0][0]) self.assertEqual( '\n' '
    THIS IS TEXT  FOR BARVALUE.
    ', result.getvalue()) def test_raw_msgids_and_i18ntranslate_i18nname(self): self.engine.translationDomain.clearMsgids() result = StringIO() program, macros = self._compile( '
    This is text\n \tfor\n' '
     \tbar\n 
    .
    ') self.interpreter = TALInterpreter(program, {}, self.engine, stream=result) self.interpreter() msgids = self.engine.translationDomain.getMsgids('default') msgids.sort() self.assertEqual(2, len(msgids)) self.assertEqual(' \tbar\n ', msgids[0][0]) self.assertEqual('This is text for ${bar}.', msgids[1][0]) self.assertEqual({'bar': '
     \tBAR\n 
    '}, msgids[1][1]) self.assertEqual( u'
    THIS IS TEXT FOR
     \tBAR\n 
    .
    ', result.getvalue()) def test_for_handling_unicode_vars(self): # Make sure that non-ASCII Unicode is substituted correctly. # http://collector.zope.org/Zope3-dev/264 program, macros = self._compile( "
    " "Foo
    ") self._check(program, u"
    FOO \u00C0
    ") class I18NCornerTestCaseMessage(I18NCornerTestCaseBase): def factory(self, msgid, default=None, mapping={}, domain=None): return Message(msgid, domain=domain, default=default, mapping=mapping) class UnusedExplicitDomainTestCase(I18NCornerTestCaseMessage): def setUp(self): # MultipleDomainsDummyEngine is a Engine # where default domain transforms to uppercase self.engine = MultipleDomainsDummyEngine() self.engine.setLocal('foo', self.factory('FoOvAlUe${empty}', 'default', {'empty': ''})) self.engine.setLocal('bar', 'BaRvAlUe') self.engine.setLocal('baz', self.factory('BaZvAlUe', 'default', {})) # Message ids with different domains self.engine.setLocal('toupper', self.factory('ToUpper', 'default', {})) self.engine.setLocal('tolower', self.factory('ToLower', 'default', {}, domain='lower')) def test_multiple_domains(self): program, macros = self._compile( '
    ') self._check(program, '
    TOUPPER
    ') program, macros = self._compile( '
    ') self._check(program, '
    tolower
    ') program, macros = self._compile( '
    ') self._check(program, '
    TOUPPER
    ') program, macros = self._compile( '
    ') self._check(program, '
    tolower
    ') program, macros = self._compile( '
    ') self._check(program, '
    TOUPPER
    ') program, macros = self._compile( '
    ') self._check(program, '
    tolower
    ') def test_unused_explicit_domain(self): #a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine #is a domain that transforms to lowercase self.engine.setLocal('othertolower', self.factory('OtherToLower', 'a_very_explicit_domain_setup_by_template_developer_that_wont_be_taken_into_account_by_the_ZPT_engine', {}, domain='lower')) program, macros = self._compile( '
    ') self._check(program, '
    othertolower
    ') #takes domain into account for strings program, macros = self._compile( '
    ') self._check(program, '
    tolower
    ') #but not for messageids program, macros = self._compile( '
    ') self._check(program, '
    BAZVALUE
    ') class ScriptTestCase(TestCaseBase): def setUp(self): self.engine = DummyEngine() def _check(self, program, expected): result = StringIO() self.interpreter = TALInterpreter(program, {}, self.engine, stream=result) self.interpreter() self.assertEqual(expected, result.getvalue()) def test_simple(self): program, macros = self._compile( '

    print "hello"

    ') self._check(program, '

    hello\n

    ') def test_script_and_tal_block(self): program, macros = self._compile( '\n' ' global x\n' ' x = 1\n' '\n' '') self._check(program, '\n1') self.assertEqual(self.engine.codeGlobals['x'], 1) def test_script_and_tal_block_having_inside_print(self): program, macros = self._compile( '\n' ' print "hello"' '') self._check(program, 'hello\n') def test_script_and_omittag(self): program, macros = self._compile( '

    \n' ' print "hello"' '

    ') self._check(program, 'hello\n') def test_script_and_inside_tags(self): program, macros = self._compile( '

    \n' ' print "hello"' '

    ') self._check(program, 'hello\n') def test_script_and_inside_tags_with_tal(self): program, macros = self._compile( '

    ') self._check(program, 'hello\n') def test_html_script(self): program, macros = self._compile( '') self._check(program, 'Hello world!\n') def test_html_script_and_javascript(self): program, macros = self._compile( '') self._check(program, ' zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test24.html0000644000175000017500000000052412214017644024035 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test07.html0000644000175000017500000000044512214017644024040 0ustar arnauarnau
    Top Left Top Right
    Bottom left Bottom Right
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test19.xml0000644000175000017500000000037712214017644023703 0ustar arnauarnau Replace this This is a translated string And another translated string zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test20.xml0000644000175000017500000000042512214017644023665 0ustar arnauarnau replaceable

    content

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_failed_attr_translation.html0000644000175000017500000000010212214017644030633 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test27.html0000644000175000017500000000030212214017644024032 0ustar arnauarnau

    Your contact email address is recorded as user@host.com

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test11.xml0000644000175000017500000000072112214017644023664 0ustar arnauarnau

    dummy text

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test23.html0000644000175000017500000000012112214017644024025 0ustar arnauarnau2:32 pm zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test21.html0000644000175000017500000000023612214017644024032 0ustar arnauarnau was born in . zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test14.xml0000644000175000017500000000044612214017644023673 0ustar arnauarnau

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test04.html0000644000175000017500000000130312214017644024027 0ustar arnauarnau
    • 1

    use-macro fill-slot

    use-macro

    define-slot

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test09.xml0000644000175000017500000000072212214017644023674 0ustar arnauarnau

    Just a bunch of text.

    more text...

    • first item
    • second item
      1. second list, first item
      2. second list, second item
        term 1
        term 2
        definition
    • Now let's have a paragraph...

      My Paragraph

    • And a table in a list item:
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test15.html0000644000175000017500000000101412214017644024030 0ustar arnauarnau INNERSLOT inner-argument
    OUTERSLOT
    outer-argument
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test25.html0000644000175000017500000000005512214017644024035 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test18.html0000644000175000017500000000101712214017644024036 0ustar arnauarnau

    Content

    Content

    Content

    No

    No

    Yes

    Yes

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_metal2.html0000644000175000017500000000021012214017644025123 0ustar arnauarnau
    OUTER INNER OUTER
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test11.html0000644000175000017500000000123712214017644024033 0ustar arnauarnau

    dummy text

    p

    rule
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_sa1.xml0000644000175000017500000000016212214017644024265 0ustar arnauarnau Simple test of source annotations

    Foo!

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/__init__.py0000644000175000017500000000007512214017644024134 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test19.html0000644000175000017500000000025112214017644024036 0ustar arnauarnauReplace this This is a translated string And another translated string zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test01.xml0000644000175000017500000000270712214017644023671 0ustar arnauarnau dadada

    This title is not displayed

    Title

     &HarryPotter;

    foo bar

    • Car Name
    python python zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test33.html0000644000175000017500000000006212214017644024032 0ustar arnauarnaudon't translate me zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_sa1.html0000644000175000017500000000013312214017644024427 0ustar arnauarnau Simple test of source annotations

    Foo!

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test09.html0000644000175000017500000000063312214017644024041 0ustar arnauarnau

    Just a bunch of text.

    more text...

    • first item
    • second item
      1. second list, first item
      2. second list, second item
        term 1
        term 2
        definition
    • Now let's have a paragraph...

      My Paragraph

    • And a table in a list item:
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test28.html0000644000175000017500000000033512214017644024041 0ustar arnauarnau

    Your contact email address is recorded as user@host.com

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test05.html0000644000175000017500000000022212214017644024027 0ustar arnauarnau

    This is the body of test5

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test30.html0000644000175000017500000000035012214017644024027 0ustar arnauarnau

    Your contact email address is recorded as user@host.com

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test12.html0000644000175000017500000000122012214017644024024 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_metal7.html0000644000175000017500000000025012214017644025134 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_metal9.html0000644000175000017500000000072212214017644025142 0ustar arnauarnau
    Default for macro1
    Macro 2's slot 1 decoration Default for macro2
    Custom slot1
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test02.xml0000644000175000017500000000717312214017644023674 0ustar arnauarnau sample1 a simple invoice
    01786 2000-03-17 55377 2000-03-15 GJ03405 DAVE 1 2000-03-17 K5211(34) 23 23
    SHIPWRIGHT RESTAURANTS LIMITED 125 NORTH SERVICE ROAD W WESTLAKE ACCESS NORTH BAY L8B1O5 ONTARIO CANADA ATTN: PAULINE DEGRASSI 1 CS DM 5309 #1013 12 OZ.MUNICH STEIN 37.72 37.72 6 DZ ON 6420 PROVINCIAL DINNER FORK 17.98 107.88 72 EA JR20643 PLASTIC HANDLED STEAK KNIFE .81 58.32 6 DZ ON 6410 PROVINCIAL TEASPOONS 12.16 72.96 0 DZ ON 6411 PROVINCIAL RD BOWL SPOON 6 17.98 0.00 1 EA DO 3218 34 OZ DUAL DIAL SCALE AM3218 70.00 5.0 66.50 1 CS DM 195 20 OZ.BEER PUB GLASS 55.90 55.90 399.28 3.50 23.75 29.61 33.84 33.84 486.48
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test17.xml0000644000175000017500000000050012214017644023665 0ustar arnauarnau No No Yes No Yes zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test03.xml0000644000175000017500000000064712214017644023674 0ustar arnauarnau

    outer variable x, first appearance inner variable x outer variable x, second appearance

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test01.html0000644000175000017500000000266012214017644024033 0ustar arnauarnau dadada

    This title is not displayed

    Title

     &HarryPotter;

    foo bar

    • Car Name
    python python zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test29.html0000644000175000017500000000025312214017644024041 0ustar arnauarnau
    At the tone the time will be 2:32 pm... beep!
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_metal3.html0000644000175000017500000000011412214017644025127 0ustar arnauarnauShould not get attr in metal zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test08.xml0000644000175000017500000000476112214017644023702 0ustar arnauarnau

    Some headline

    This is the real contents of the bottom right slot.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test22.html0000644000175000017500000000024412214017644024032 0ustar arnauarnau Jim was born in the USA. zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_sa2.xml0000644000175000017500000000032512214017644024267 0ustar arnauarnau Simple test of source annotations

    Foo!

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test06.html0000644000175000017500000000023412214017644024033 0ustar arnauarnau dummy body in test6 zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test14.html0000644000175000017500000000031712214017644024034 0ustar arnauarnau

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_sa4.html0000644000175000017500000000052012214017644024432 0ustar arnauarnau

    Some text on sa4 line 3.

    This text on sa4 line 5 will disappear. Text from sa4 line 6 is filled into slot1. This text on sa4 line 7 will disappear.

    This is some text on sa4 line 9.

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_metal1.html0000644000175000017500000000216212214017644025132 0ustar arnauarnau AAA INNER BBB AAA INNER BBB OUTERSLOT AAA INNER INNERSLOT BBB OUTERSLOT INNERSLOT INSLOT zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/pnome_template.pt0000644000175000017500000000107512214017644025402 0ustar arnauarnau Title here
    "The early bird gets the worm, but the second mouse gets the cheese."
    Preferences...
    Content here
    page footer
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test10.html0000644000175000017500000000502112214017644024025 0ustar arnauarnau

    Some headline

    This is the real contents of the bottom right slot.


    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.



    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_metal4.html0000644000175000017500000000027312214017644025136 0ustar arnauarnau Z3 UI zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test21.xml0000644000175000017500000000054212214017644023666 0ustar arnauarnau was born in . zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test06.xml0000644000175000017500000000026212214017644023670 0ustar arnauarnau dummy body in test6 zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_sa3.xml0000644000175000017500000000110312214017644024263 0ustar arnauarnau
    This is macro1 on sa3 line 4. This is slot1 on sa3 line 5. This is the end of macro1 on sa3 line 6.

    Some text on sa3 line 8.

    This text on sa3 line 10 will disappear. Text from sa3 line 11 is filled into slot1. This text on sa3 line 12 will disappear.

    This is some text on sa3 line 14.

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test34.html0000644000175000017500000000036712214017644024043 0ustar arnauarnau stuff more stuff stuff more stuff zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_metal6.html0000644000175000017500000000022012214017644025130 0ustar arnauarnau Z3 UI zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test03.html0000644000175000017500000000062012214017644024027 0ustar arnauarnau

    outer variable x, first appearance inner variable x outer variable x, second appearance

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test36.html0000644000175000017500000000041212214017644024034 0ustar arnauarnau some text zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/acme_template.pt0000644000175000017500000000061412214017644025167 0ustar arnauarnau ACME Look and Feel
    Copyright 2004 Acme Inc.
    Standard disclaimers apply.
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/document_list.pt0000644000175000017500000000100112214017644025227 0ustar arnauarnau Acme Document List

    Documents

    • Rocket Science for Dummies
    • Birds for the Gourmet Chef
    This document list is classified.
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_metal5.html0000644000175000017500000000026112214017644025134 0ustar arnauarnau Z3 UI zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test07.xml0000644000175000017500000000047412214017644023676 0ustar arnauarnau
    Top Left Top Right
    Bottom left Bottom Right
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test16.html0000644000175000017500000000014312214017644024033 0ustar arnauarnaublah, blah zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test22.xml0000644000175000017500000000035612214017644023672 0ustar arnauarnau content omit replace zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test08.html0000644000175000017500000000473312214017644024045 0ustar arnauarnau

    Some headline

    This is the real contents of the bottom right slot.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test26.html0000644000175000017500000000020512214017644024033 0ustar arnauarnau Job #NN zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/input/test_metal8.html0000644000175000017500000000040412214017644025136 0ustar arnauarnau
    Default body
    Filled-in body
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/test_talgettext.py0000644000175000017500000000507112214017644024463 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for the talgettext utility. $Id: test_talgettext.py 30452 2005-05-20 05:13:10Z fdrake $ """ import sys import unittest from StringIO import StringIO from zope.tal.htmltalparser import HTMLTALParser from zope.tal.talgettext import POTALInterpreter from zope.tal.talgettext import POEngine from zope.tal.tests import utils class test_POEngine(unittest.TestCase): """Test the PO engine functionality, which simply adds items to a catalog as .translate is called """ def test_translate(self): test_keys = ['foo', 'bar', 'blarf', 'washington'] engine = POEngine() engine.file = 'foo.pt' for key in test_keys: engine.translate(key, 'domain') for key in test_keys: self.failIf(key not in engine.catalog['domain'], "POEngine catalog does not properly store message ids" ) def test_dynamic_msgids(self): sample_source = """

    Some dynamic text.

    A link.

    """ p = HTMLTALParser() p.parseString(sample_source) program, macros = p.getCode() engine = POEngine() engine.file = 'sample_source' POTALInterpreter(program, macros, engine, stream=StringIO(), metal=False)() msgids = [] for domain in engine.catalog.values(): msgids += domain.keys() msgids.sort() self.assertEquals(msgids, ['A link.', 'Some ${DYNAMIC_CONTENT} text.']) def test_suite(): suite = unittest.makeSuite(test_POEngine) return suite if __name__ == "__main__": errs = utils.run_suite(test_suite()) sys.exit(errs and 1 or 0) zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/test_sourcepos.py0000644000175000017500000000562612214017644024326 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for TALInterpreter. $Id: test_sourcepos.py 30452 2005-05-20 05:13:10Z fdrake $ """ import unittest from StringIO import StringIO from zope.tal.htmltalparser import HTMLTALParser from zope.tal.talinterpreter import TALInterpreter from zope.tal.talgenerator import TALGenerator from zope.tal.dummyengine import DummyEngine page1 = '''
    page1=
    ''' main_template = ''' main_template=
    main_template=
    main_template= ''' footer = '''
    footer=
    ''' expected = ''' main_template=main_template (2,14)
    page1=page1 (3,6)
    main_template=main_template (4,14)
    footer=footer (2,7)
    main_template=main_template (6,14) ''' class SourcePosTestCase(unittest.TestCase): def parse(self, eng, s, fn): gen = TALGenerator(expressionCompiler=eng, xml=0, source_file=fn) parser = HTMLTALParser(gen) parser.parseString(s) program, macros = parser.getCode() return program, macros def test_source_positions(self): # Ensure source file and position are set correctly by TAL macros = {} eng = DummyEngine(macros) page1_program, page1_macros = self.parse(eng, page1, 'page1') main_template_program, main_template_macros = self.parse( eng, main_template, 'main_template') footer_program, footer_macros = self.parse(eng, footer, 'footer') macros['main'] = main_template_macros['main'] macros['foot'] = footer_macros['foot'] stream = StringIO() interp = TALInterpreter(page1_program, macros, eng, stream) interp() self.assertEqual(stream.getvalue().strip(), expected.strip(), "Got result:\n%s\nExpected:\n%s" % (stream.getvalue(), expected)) def test_suite(): return unittest.makeSuite(SourcePosTestCase) if __name__ == "__main__": unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/utils.py0000644000175000017500000000371612214017644022403 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Helper functions for the test suite. $Id: utils.py 30139 2005-04-24 07:01:43Z hdima $ """ import os import sys mydir = os.path.abspath(os.path.dirname(__file__)) codedir = os.path.dirname(os.path.dirname(os.path.dirname(mydir))) if codedir not in sys.path: sys.path.append(codedir) import unittest # Set skipxml to true if an XML parser could not be found. skipxml = 0 try: import xml.parsers.expat except ImportError: skipxml = 1 def run_suite(suite, outf=None, errf=None): if outf is None: outf = sys.stdout runner = unittest.TextTestRunner(outf) result = runner.run(suite) ## print "\n\n" ## if result.errors: ## print "Errors (unexpected exceptions):" ## map(print_error, result.errors) ## print ## if result.failures: ## print "Failures (assertion failures):" ## map(print_error, result.failures) ## print newerrs = len(result.errors) + len(result.failures) if newerrs: print "'Errors' indicate exceptions other than AssertionError." print "'Failures' indicate AssertionError" if errf is None: errf = sys.stderr errf.write("%d errors, %d failures\n" % (len(result.errors), len(result.failures))) return newerrs def print_error(info): testcase, (type, e, tb) = info zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/__init__.py0000644000175000017500000000007512214017644022775 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/markbench.py0000644000175000017500000001271512214017644023174 0ustar arnauarnau#! /usr/bin/env python ############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Run benchmarks of TAL vs. DTML $Id: markbench.py 30139 2005-04-24 07:01:43Z hdima $ """ import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) import os os.environ['NO_SECURITY'] = 'true' import getopt import sys import time from cStringIO import StringIO #from zope.documenttemplate.dt_html import HTMLFile from zope.tal.htmltalparser import HTMLTALParser from zope.tal.talinterpreter import TALInterpreter from zope.tal.dummyengine import DummyEngine def time_apply(f, args, kwargs, count): r = [None] * count for i in range(4): f(*args, **kwargs) t0 = time.clock() for i in r: pass t1 = time.clock() for i in r: f(*args, **kwargs) t = time.clock() - t1 - (t1 - t0) return t / count def time_zpt(fn, count): from zope.pagetemplate.pagetemplate import PageTemplate pt = PageTemplate() pt.write(open(fn).read()) return time_apply(pt.pt_render, (data,), {}, count) def time_tal(fn, count): p = HTMLTALParser() p.parseFile(fn) program, macros = p.getCode() engine = DummyEngine(macros) engine.globals = data tal = TALInterpreter(program, macros, engine, StringIO(), wrap=0, tal=1, strictinsert=0) return time_apply(tal, (), {}, count) def time_dtml(fn, count): html = HTMLFile(fn) return time_apply(html, (), data, count) def profile_zpt(fn, count, profiler): from zope.pagetemplate.pagetemplate import PageTemplate pt = PageTemplate() pt.write(open(fn).read()) for i in range(4): pt.pt_render(extra_context=data) r = [None] * count for i in r: profiler.runcall(pt.pt_render, 0, data) def profile_tal(fn, count, profiler): p = HTMLTALParser() p.parseFile(fn) program, macros = p.getCode() engine = DummyEngine(macros) engine.globals = data tal = TALInterpreter(program, macros, engine, StringIO(), wrap=0, tal=1, strictinsert=0) for i in range(4): tal() r = [None] * count for i in r: profiler.runcall(tal) # Figure out where the benchmark files are: try: fname = __file__ except NameError: fname = sys.argv[0] taldir = os.path.dirname(os.path.dirname(os.path.abspath(fname))) benchdir = os.path.join(taldir, 'benchmark') # Construct templates for the filenames: tal_fn = os.path.join(benchdir, 'tal%.2d.html') dtml_fn = os.path.join(benchdir, 'dtml%.2d.html') def compare(n, count, profiler=None, verbose=1): if verbose: t1 = int(time_zpt(tal_fn % n, count) * 1000 + 0.5) t2 = int(time_tal(tal_fn % n, count) * 1000 + 0.5) t3 = 'n/a' # int(time_dtml(dtml_fn % n, count) * 1000 + 0.5) print '%.2d: %10s %10s %10s' % (n, t1, t2, t3) if profiler: profile_tal(tal_fn % n, count, profiler) def main(count, profiler=None, verbose=1): n = 1 if verbose: print '##: %10s %10s %10s' % ('ZPT', 'TAL', 'DTML') while os.path.isfile(tal_fn % n) and os.path.isfile(dtml_fn % n): compare(n, count, profiler, verbose) n = n + 1 def get_signal_name(sig): import signal for name in dir(signal): if getattr(signal, name) == sig: return name return None data = {'x':'X', 'r2': range(2), 'r8': range(8), 'r64': range(64)} for i in range(10): data['x%s' % i] = 'X%s' % i if __name__ == "__main__": filename = "markbench.prof" profiler = None runtests = False verbose = True opts, args = getopt.getopt(sys.argv[1:], "pqt") for opt, arg in opts: if opt == "-p": import profile profiler = profile.Profile() elif opt == "-q": verbose = False elif opt == "-t": runtests = True if runtests: srcdir = os.path.dirname(os.path.dirname(taldir)) topdir = os.path.dirname(srcdir) pwd = os.getcwd() os.chdir(topdir) rc = os.spawnl(os.P_WAIT, sys.executable, sys.executable, "test.py", "zope.tal.tests") if rc > 0: # TODO: Failing tests don't cause test.py to report an # error; not sure why. ;-( sys.exit(rc) elif rc < 0: sig = -rc print >>sys.stderr, ( "Process exited, signal %d (%s)." % (sig, get_signal_name(sig) or "")) sys.exit(1) os.chdir(pwd) if len(args) >= 1: for arg in args: compare(int(arg), 25, profiler, verbose) else: main(25, profiler, verbose) if profiler is not None: profiler.dump_stats(filename) import pstats p = pstats.Stats(filename) p.strip_dirs() p.sort_stats('time', 'calls') try: p.print_stats(20) except IOError, e: if e.errno != errno.EPIPE: raise zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/test_files.py0000644000175000017500000000521012214017644023373 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests that run driver.py over input files comparing to output files. $Id: test_files.py 30452 2005-05-20 05:13:10Z fdrake $ """ import glob import os import sys import unittest import zope.tal.runtest from zope.tal.tests import utils class FileTestCase(unittest.TestCase): def __init__(self, file, dir): self.__file = file self.__dir = dir unittest.TestCase.__init__(self) # For unittest. def shortDescription(self): path = os.path.basename(self.__file) return '%s (%s)' % (path, self.__class__) def runTest(self): basename = os.path.basename(self.__file) #sys.stdout.write(basename + " ") sys.stdout.flush() if basename.startswith('test_sa'): sys.argv = ["", "-Q", "-a", self.__file] elif basename.startswith('test_metal'): sys.argv = ["", "-Q", "-m", self.__file] else: sys.argv = ["", "-Q", self.__file] pwd = os.getcwd() try: try: os.chdir(self.__dir) zope.tal.runtest.main() finally: os.chdir(pwd) except SystemExit, what: if what.code: self.fail("output for %s didn't match" % self.__file) try: script = __file__ except NameError: script = sys.argv[0] def test_suite(): suite = unittest.TestSuite() dir = os.path.dirname(script) dir = os.path.abspath(dir) parentdir = os.path.dirname(dir) prefix = os.path.join(dir, "input", "test*.") if utils.skipxml: xmlargs = [] else: xmlargs = glob.glob(prefix + "xml") xmlargs.sort() htmlargs = glob.glob(prefix + "html") htmlargs.sort() args = xmlargs + htmlargs if not args: sys.stderr.write("Warning: no test input files found!!!\n") for arg in args: case = FileTestCase(arg, parentdir) suite.addTest(case) return suite if __name__ == "__main__": errs = utils.run_suite(test_suite()) sys.exit(errs and 1 or 0) zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/test_htmltalparser.py0000644000175000017500000011023412214017644025156 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for the HTMLTALParser code generator. $Id: test_htmltalparser.py 38009 2005-08-19 20:29:35Z fdrake $ """ import pprint import sys import unittest from zope.tal import htmltalparser, taldefs from zope.tal.tests import utils class TestCaseBase(unittest.TestCase): prologue = "" epilogue = "" initial_program = [('version', taldefs.TAL_VERSION), ('mode', 'html')] final_program = [] def _merge(self, p1, p2): if p1 and p2: op1, args1 = p1[-1] op2, args2 = p2[0] if op1.startswith('rawtext') and op2.startswith('rawtext'): return (p1[:-1] + [rawtext(args1[0] + args2[0])] + p2[1:]) return p1+p2 def _run_check(self, source, program, macros={}): parser = htmltalparser.HTMLTALParser() parser.parseString(self.prologue + source + self.epilogue) got_program, got_macros = parser.getCode() program = self._merge(self.initial_program, program) program = self._merge(program, self.final_program) self.assert_(got_program == program, "Program:\n" + pprint.pformat(got_program) + "\nExpected:\n" + pprint.pformat(program)) self.assert_(got_macros == macros, "Macros:\n" + pprint.pformat(got_macros) + "\nExpected:\n" + pprint.pformat(macros)) def _should_error(self, source, exc=taldefs.TALError): def parse(self=self, source=source): parser = htmltalparser.HTMLTALParser() parser.parseString(self.prologue + source + self.epilogue) self.assertRaises(exc, parse) def rawtext(s): """Compile raw text to the appropriate instruction.""" if "\n" in s: return ("rawtextColumn", (s, len(s) - (s.rfind("\n") + 1))) else: return ("rawtextOffset", (s, len(s))) class HTMLTALParserTestCases(TestCaseBase): def test_code_simple_identity(self): self._run_check("""My Title</html>""", [ rawtext('<html a="b" b="c" c="d">' '<title>My Title'), ]) def test_code_implied_list_closings(self): self._run_check("""
    """, [ rawtext('
    '), ]) self._run_check("""
    """, [ rawtext('
    ' '
    '), ]) def test_code_implied_table_closings(self): self._run_check("""

    text
    head\t
    cell\t""" """""", [ rawtext('

    text

    cell \n \t \n
    ' '\t
    head
    cell\t' ' \n \t \n
    cell
    '), ]) self._run_check("""
    cell """ """
    cell
    """, [ rawtext('
    cell ' '
    cell
    '), ]) def test_code_bad_nesting(self): def check(self=self): self._run_check("", []) self.assertRaises(htmltalparser.NestingError, check) def test_code_attr_syntax(self): output = [ rawtext(''), ] self._run_check("""""", output) self._run_check("""""", output) self._run_check("""""", output) self._run_check("""""", output) def test_code_attr_values(self): self._run_check( """""", [ rawtext('')]) self._run_check("""""", [ rawtext(''), ]) def test_code_attr_entity_replacement(self): # we expect entities *not* to be replaced by HTLMParser! self._run_check("""""", [ rawtext(''), ]) self._run_check("""""", [ rawtext(''), ]) self._run_check("""""", [ rawtext(''), ]) self._run_check("""""", [ rawtext(''), ]) def test_code_attr_funky_names(self): self._run_check("""""", [ rawtext(''), ]) def test_code_pcdata_entityref(self): self._run_check(""" """, [ rawtext(' '), ]) def test_code_short_endtags(self): self._run_check("""""", [ rawtext(''), ]) class METALGeneratorTestCases(TestCaseBase): def test_null(self): self._run_check("", []) def test_define_macro(self): macro = self.initial_program + [ ('startTag', ('p', [('metal:define-macro', 'M', 'metal')])), rawtext('booh

    '), ] program = [ ('setPosition', (1, 0)), ('defineMacro', ('M', macro)), ] macros = {'M': macro} self._run_check('

    booh

    ', program, macros) def test_use_macro(self): self._run_check('

    booh

    ', [ ('setPosition', (1, 0)), ('useMacro', ('M', '$M$', {}, [('startTag', ('p', [('metal:use-macro', 'M', 'metal')])), rawtext('booh

    ')])), ]) def test_define_slot(self): macro = self.initial_program + [ ('startTag', ('p', [('metal:define-macro', 'M', 'metal')])), rawtext('foo'), ('setPosition', (1, 29)), ('defineSlot', ('S', [('startTag', ('span', [('metal:define-slot', 'S', 'metal')])), rawtext('spam')])), rawtext('bar

    '), ] program = [('setPosition', (1, 0)), ('defineMacro', ('M', macro))] macros = {'M': macro} self._run_check('

    foo' 'spambar

    ', program, macros) def test_fill_slot(self): self._run_check('

    foo' 'spambar

    ', [ ('setPosition', (1, 0)), ('useMacro', ('M', '$M$', {'S': [('startTag', ('span', [('metal:fill-slot', 'S', 'metal')])), rawtext('spam')]}, [('startTag', ('p', [('metal:use-macro', 'M', 'metal')])), rawtext('foo'), ('setPosition', (1, 26)), ('fillSlot', ('S', [('startTag', ('span', [('metal:fill-slot', 'S', 'metal')])), rawtext('spam')])), rawtext('bar

    ')])), ]) class TALGeneratorTestCases(TestCaseBase): def test_null(self): self._run_check("", []) def test_define_1(self): self._run_check("

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:define': 'xyzzy string:spam'}), ('setLocal', ('xyzzy', '$string:spam$')), ('startTag', ('p', [('tal:define', 'xyzzy string:spam', 'tal')])), ('endScope', ()), rawtext('

    '), ]) def test_define_2(self): self._run_check("

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:define': 'local xyzzy string:spam'}), ('setLocal', ('xyzzy', '$string:spam$')), ('startTag', ('p', [('tal:define', 'local xyzzy string:spam', 'tal')])), ('endScope', ()), rawtext('

    '), ]) def test_define_3(self): self._run_check("

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:define': 'global xyzzy string:spam'}), ('setGlobal', ('xyzzy', '$string:spam$')), ('startTag', ('p', [('tal:define', 'global xyzzy string:spam', 'tal')])), ('endScope', ()), rawtext('

    '), ]) def test_define_4(self): self._run_check("

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:define': 'x string:spam; y x'}), ('setLocal', ('x', '$string:spam$')), ('setLocal', ('y', '$x$')), ('startTag', ('p', [('tal:define', 'x string:spam; y x', 'tal')])), ('endScope', ()), rawtext('

    '), ]) def test_define_5(self): self._run_check("

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:define': 'x string:;;;;; y x'}), ('setLocal', ('x', '$string:;;$')), ('setLocal', ('y', '$x$')), ('startTag', ('p', [('tal:define', 'x string:;;;;; y x', 'tal')])), ('endScope', ()), rawtext('

    '), ]) def test_define_6(self): self._run_check( "

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:define': 'x string:spam; global y x; local z y'}), ('setLocal', ('x', '$string:spam$')), ('setGlobal', ('y', '$x$')), ('setLocal', ('z', '$y$')), ('startTag', ('p', [('tal:define', 'x string:spam; global y x; local z y', 'tal')])), ('endScope', ()), rawtext('

    '), ]) def test_condition(self): self._run_check( "

    foo

    ", [ rawtext('

    '), ('setPosition', (1, 3)), ('beginScope', {'tal:condition': 'python:1'}), ('condition', ('$python:1$', [('startTag', ('span', [('tal:condition', 'python:1', 'tal')])), rawtext('foo')])), ('endScope', ()), rawtext('

    '), ]) def test_content_1(self): self._run_check("

    bar

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:content': 'string:foo'}), ('startTag', ('p', [('tal:content', 'string:foo', 'tal')])), ('insertText', ('$string:foo$', [rawtext('bar')])), ('endScope', ()), rawtext('

    '), ]) def test_content_2(self): self._run_check("

    bar

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:content': 'text string:foo'}), ('startTag', ('p', [('tal:content', 'text string:foo', 'tal')])), ('insertText', ('$string:foo$', [rawtext('bar')])), ('endScope', ()), rawtext('

    '), ]) def test_content_3(self): self._run_check("

    bar

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:content': 'structure string:
    '}), ('startTag', ('p', [('tal:content', 'structure string:
    ', 'tal')])), ('insertStructure', ('$string:
    $', {}, [rawtext('bar')])), ('endScope', ()), rawtext('

    '), ]) def test_replace_1(self): self._run_check("

    bar

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:replace': 'string:foo'}), ('optTag', ('p', '', None, 0, [('startTag', ('p', [('tal:replace', 'string:foo', 'tal')]))], [('insertText', ('$string:foo$', [('rawtextOffset', ('bar', 3))]))])), ('endScope', ()), ]) def test_replace_2(self): self._run_check("

    bar

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:replace': 'text string:foo'}), ('optTag', ('p', '', None, 0, [('startTag', ('p', [('tal:replace', 'text string:foo', 'tal')]))], [('insertText', ('$string:foo$', [('rawtextOffset', ('bar', 3))]))])), ('endScope', ()), ]) def test_replace_3(self): self._run_check("

    bar

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:replace': 'structure string:
    '}), ('optTag', ('p', '', None, 0, [('startTag', ('p', [('tal:replace', 'structure string:
    ', 'tal')]))], [('insertStructure', ('$string:
    $', {}, [('rawtextOffset', ('bar', 3))]))])), ('endScope', ()), ]) def test_repeat(self): self._run_check("

    " "dummy

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:repeat': 'x python:(1,2,3)'}), ('loop', ('x', '$python:(1,2,3)$', [('startTag', ('p', [('tal:repeat', 'x python:(1,2,3)', 'tal')])), ('setPosition', (1, 33)), ('beginScope', {'tal:replace': 'x'}), ('optTag', ('span', '', None, 0, [('startTag', ('span', [('tal:replace', 'x', 'tal')]))], [('insertText', ('$x$', [('rawtextOffset', ('dummy', 5))]))])), ('endScope', ()), rawtext('

    ')])), ('endScope', ()), ]) def test_script_1(self): self._run_check('

    code

    ', [ ('setPosition', (1, 0)), ('beginScope', {'tal:script': 'text/server-python'}), ('startTag', ('p', [('tal:script', 'text/server-python', 'tal')])), ('evaluateCode', ('text/server-python', [('rawtextOffset', ('code', 4))])), ('endScope', ()), rawtext('

    '), ]) def test_script_2(self): self._run_check('' 'code' '', [ ('setPosition', (1, 0)), ('beginScope', {'script': 'text/server-python'}), ('optTag', ('tal:block', None, 'tal', 0, [('startTag', ('tal:block', [('script', 'text/server-python', 'tal')]))], [('evaluateCode', ('text/server-python', [('rawtextOffset', ('code', 4))]))])), ('endScope', ()) ]) def test_script_3(self): self._run_check('', [ ('setPosition', (1, 0)), ('beginScope', {}), ('optTag', ('script', '', None, 0, [('rawtextOffset', ('', [ ('rawtextOffset', ('', 44)) ]) def test_attributes_1(self): self._run_check("" "link", [ ('setPosition', (1, 0)), ('beginScope', {'tal:attributes': 'href string:http://www.zope.org; x string:y', 'name': 'bar', 'href': 'foo'}), ('startTag', ('a', [('href', 'foo', 'replace', '$string:http://www.zope.org$', 0, None), ('name', 'name="bar"'), ('tal:attributes', 'href string:http://www.zope.org; x string:y', 'tal'), ('x', None, 'insert', '$string:y$', 0, None)])), ('endScope', ()), rawtext('link'), ]) def test_attributes_2(self): self._run_check("

    duh

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:attributes': 'src string:foo.png', 'tal:replace': 'structure string:'}), ('optTag', ('p', '', None, 0, [('startTag', ('p', [('tal:replace', 'structure string:', 'tal'), ('tal:attributes', 'src string:foo.png', 'tal')]))], [('insertStructure', ('$string:$', {'src': ('$string:foo.png$', False, None)}, [('rawtextOffset', ('duh', 3))]))])), ('endScope', ())]) def test_on_error_1(self): self._run_check("

    okay

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:content': 'notHere', 'tal:on-error': 'string:error'}), ('onError', ([('startTag', ('p', [('tal:on-error', 'string:error', 'tal'), ('tal:content', 'notHere', 'tal')])), ('insertText', ('$notHere$', [rawtext('okay')])), rawtext('

    ')], [('startTag', ('p', [('tal:on-error', 'string:error', 'tal'), ('tal:content', 'notHere', 'tal')])), ('insertText', ('$string:error$', [])), rawtext('

    ')])), ('endScope', ()), ]) def test_on_error_2(self): self._run_check("

    okay

    ", [ ('setPosition', (1, 0)), ('beginScope', {'tal:replace': 'notHere', 'tal:on-error': 'string:error'}), ('onError', ([('optTag', ('p', '', None, 0, [('startTag', ('p', [('tal:on-error', 'string:error', 'tal'), ('tal:replace', 'notHere', 'tal')]))], [('insertText', ('$notHere$', [('rawtextOffset', ('okay', 4))]))]))], [('startTag', ('p', [('tal:on-error', 'string:error', 'tal'), ('tal:replace', 'notHere', 'tal')])), ('insertText', ('$string:error$', [])), ('rawtextOffset', ('

    ', 4))])), ('endScope', ()), ]) def test_dup_attr(self): self._should_error("") self._should_error("", taldefs.METALError) def test_tal_errors(self): self._should_error("

    ") self._should_error("

    ") self._should_error("

    ") self._should_error("

    ") self._should_error("

    ") for tag in htmltalparser.EMPTY_HTML_TAGS: self._should_error("<%s tal:content='string:foo'>" % tag) def test_metal_errors(self): exc = taldefs.METALError self._should_error(2*"

    xxx

    ", exc) self._should_error("" + 2*"

    " + "", exc) self._should_error("

    ", exc) self._should_error("

    ", exc) def test_extend_macro_errors(self): exc = taldefs.METALError # extend-macro requires define-macro: self._should_error("

    xxx

    ", exc) # extend-macro prevents use-macro: self._should_error("

    xxx

    ", exc) # use-macro doesn't co-exist with define-macro: self._should_error("

    xxx

    ", exc) # # I18N test cases # def test_i18n_attributes(self): self._run_check("foo", [ ('setPosition', (1, 0)), ('beginScope', {'alt': 'foo', 'i18n:attributes': 'alt'}), ('startTag', ('img', [('alt', 'foo', 'replace', None, 1, None), ('i18n:attributes', 'alt', 'i18n')])), ('endScope', ()), ]) self._run_check("foo", [ ('setPosition', (1, 0)), ('beginScope', {'alt': 'foo', 'i18n:attributes': 'alt foo ; bar'}), ('startTag', ('img', [('alt', 'foo', 'replace', None, 1, 'foo'), ('i18n:attributes', 'alt foo ; bar', 'i18n'), ('bar', None, 'insert', None, 1, None)])), ('endScope', ()), ]) def test_i18n_name_bad_name(self): self._should_error("") self._should_error("") def test_i18n_attributes_repeated_attr(self): self._should_error("") self._should_error("") def test_i18n_translate(self): # input/test19.html self._run_check('''\ Replace this This is a translated string And another translated string ''', [ ('setPosition', (1, 0)), ('beginScope', {'i18n:translate': ''}), ('startTag', ('span', [('i18n:translate', '', 'i18n')])), ('insertTranslation', ('', [('rawtextOffset', ('Replace this', 12))])), ('rawtextBeginScope', ('\n', 0, (2, 0), 1, {'i18n:translate': 'msgid'})), ('startTag', ('span', [('i18n:translate', 'msgid', 'i18n')])), ('insertTranslation', ('msgid', [('rawtextColumn', ('This is a\ntranslated string', 17))])), ('rawtextBeginScope', ('\n', 0, (4, 0), 1, {'i18n:translate': ''})), ('startTag', ('span', [('i18n:translate', '', 'i18n')])), ('insertTranslation', ('', [('rawtextColumn', ('And another\ntranslated string', 17))])), ('endScope', ()), ('rawtextColumn', ('\n', 0))]) def test_i18n_translate_with_nested_tal(self): self._run_check('''\ replaceable

    content

    ''', [ ('setPosition', (1, 0)), ('beginScope', {'i18n:translate': ''}), ('startTag', ('span', [('i18n:translate', '', 'i18n')])), ('insertTranslation', ('', [('rawtextOffset', ('replaceable ', 12)), ('setPosition', (1, 36)), ('beginScope', {'tal:replace': 'str:here'}), ('optTag', ('p', '', None, 0, [('startTag', ('p', [('tal:replace', 'str:here', 'tal')]))], [('insertText', ('$str:here$', [('rawtextOffset', ('content', 7))]))])), ('endScope', ())])), ('endScope', ()), ('rawtextColumn', ('\n', 0)) ]) def test_i18n_name(self): # input/test21.html self._run_check('''\ was born in . ''', [ ('setPosition', (1, 0)), ('beginScope', {'i18n:translate': ''}), ('startTag', ('span', [('i18n:translate', '', 'i18n')])), ('insertTranslation', ('', [('rawtextBeginScope', ('\n ', 2, (2, 2), 0, {'i18n:name': 'name', 'tal:replace': 'str:Lomax'})), ('i18nVariable', ('name', [('optTag', ('span', '', None, 1, [('startEndTag', ('span', [('tal:replace', 'str:Lomax', 'tal'), ('i18n:name', 'name', 'i18n')]))], [('insertText', ('$str:Lomax$', []))]))], None, 0)), ('rawtextBeginScope', (' was born in\n ', 2, (3, 2), 1, {'i18n:name': 'country', 'tal:replace': 'str:Antarctica'})), ('i18nVariable', ('country', [('optTag', ('span', '', None, 1, [('startEndTag', ('span', [('tal:replace', 'str:Antarctica', 'tal'), ('i18n:name', 'country', 'i18n')]))], [('insertText', ('$str:Antarctica$', []))]))], None, 0)), ('endScope', ()), ('rawtextColumn', ('.\n', 0))])), ('endScope', ()), ('rawtextColumn', ('\n', 0)) ]) def test_i18n_name_with_content(self): self._run_check('
    This is text for ' '.' '
    ', [ ('setPosition', (1, 0)), ('beginScope', {'i18n:translate': ''}), ('startTag', ('div', [('i18n:translate', '', 'i18n')])), ('insertTranslation', ('', [('rawtextOffset', ('This is text for ', 17)), ('setPosition', (1, 40)), ('beginScope', {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}), ('i18nVariable', ('bar_name', [('startTag', ('span', [('i18n:translate', '', 'i18n'), ('tal:content', 'bar', 'tal'), ('i18n:name', 'bar_name', 'i18n')])), ('insertI18nText', ('$bar$', [])), ('rawtextOffset', ('
    ', 7))], None, 0)), ('endScope', ()), ('rawtextOffset', ('.', 1))])), ('endScope', ()), ('rawtextOffset', ('', 6)) ]) def test_i18n_name_implicit_value(self): # input/test22.html self._run_check('''\ Jim was born in the USA. ''', [('setPosition', (1, 0)), ('beginScope', {'i18n:translate': ''}), ('startTag', ('span', [('i18n:translate', '', 'i18n')])), ('insertTranslation', ('', [('rawtextBeginScope', ('\n ', 2, (2, 2), 0, {'i18n:name': 'name', 'tal:omit-tag': ''})), ('i18nVariable', ('name', [('optTag', ('span', '', None, 0, [('startTag', ('span', [('tal:omit-tag', '', 'tal'), ('i18n:name', 'name', 'i18n')]))], [('rawtextOffset', ('Jim', 10))]))], None, 0)), ('rawtextBeginScope', (' was born in\n ', 2, (3, 2), 1, {'i18n:name': 'country', 'tal:omit-tag': ''})), ('i18nVariable', ('country', [('optTag', ('span', '', None, 0, [('startTag', ('span', [('tal:omit-tag', '', 'tal'), ('i18n:name', 'country', 'i18n')]))], [('rawtextOffset', ('the USA', 7))]))], None, 0)), ('endScope', ()), ('rawtextColumn', ('.\n', 0))])), ('endScope', ()), ('rawtextColumn', ('\n', 0)) ]) def test_i18n_context_domain(self): self._run_check("", [ ('setPosition', (1, 0)), ('beginI18nContext', {'domain': 'mydomain', 'source': None, 'target': None}), ('beginScope', {'i18n:domain': 'mydomain'}), ('startEndTag', ('span', [('i18n:domain', 'mydomain', 'i18n')])), ('endScope', ()), ('endI18nContext', ()), ]) def test_i18n_context_source(self): self._run_check("", [ ('setPosition', (1, 0)), ('beginI18nContext', {'source': 'en', 'domain': 'default', 'target': None}), ('beginScope', {'i18n:source': 'en'}), ('startEndTag', ('span', [('i18n:source', 'en', 'i18n')])), ('endScope', ()), ('endI18nContext', ()), ]) def test_i18n_context_source_target(self): self._run_check("", [ ('setPosition', (1, 0)), ('beginI18nContext', {'source': 'en', 'target': 'ru', 'domain': 'default'}), ('beginScope', {'i18n:source': 'en', 'i18n:target': 'ru'}), ('startEndTag', ('span', [('i18n:source', 'en', 'i18n'), ('i18n:target', 'ru', 'i18n')])), ('endScope', ()), ('endI18nContext', ()), ]) def test_i18n_context_in_define_slot(self): text = ("
    " "
    spam
    " "
    ") self._run_check(text, [ ('setPosition', (1, 0)), ('useMacro', ('M', '$M$', {'S': [('startTag', ('div', [('metal:fill-slot', 'S', 'metal')])), rawtext('spam')]}, [('beginI18nContext', {'domain': 'mydomain', 'source': None, 'target': None}), ('beginScope', {'i18n:domain': 'mydomain', 'metal:use-macro': 'M'}), ('startTag', ('div', [('metal:use-macro', 'M', 'metal'), ('i18n:domain', 'mydomain', 'i18n')])), ('setPosition', (1, 48)), ('fillSlot', ('S', [('startTag', ('div', [('metal:fill-slot', 'S', 'metal')])), rawtext('spam')])), ('endScope', ()), rawtext(''), ('endI18nContext', ())])), ]) def test_i18n_data(self): # input/test23.html self._run_check('''\ 2:32 pm ''', [ ('setPosition', (1, 0)), ('beginScope', {'i18n:translate': 'timefmt', 'i18n:data': 'here/currentTime'}), ('startTag', ('span', [('i18n:data', 'here/currentTime', 'i18n'), ('i18n:translate', 'timefmt', 'i18n')])), ('insertTranslation', ('timefmt', [('rawtextOffset', ('2:32 pm', 7))], '$here/currentTime$')), ('endScope', ()), ('rawtextColumn', ('
    \n', 0)) ]) def test_i18n_data_with_name(self): # input/test29.html self._run_check('''\
    At the tone the time will be 2:32 pm... beep!
    ''', [('setPosition', (1, 0)), ('beginScope', {'i18n:translate': ''}), ('startTag', ('div', [('i18n:translate', '', 'i18n')])), ('insertTranslation', ('', [('rawtextBeginScope', ('At the tone the time will be\n', 0, (2, 0), 0, {'i18n:data': 'here/currentTime', 'i18n:name': 'time', 'i18n:translate': 'timefmt'})), ('i18nVariable', ('time', [('startTag', ('span', [('i18n:data', 'here/currentTime', 'i18n'), ('i18n:translate', 'timefmt', 'i18n'), ('i18n:name', 'time', 'i18n')])), ('insertTranslation', ('timefmt', [('rawtextOffset', ('2:32 pm', 7))], '$here/currentTime$')), ('rawtextOffset', ('
    ', 7))], None, 0)), ('endScope', ()), ('rawtextOffset', ('... beep!', 9))])), ('endScope', ()), ('rawtextColumn', ('\n', 0)) ]) def test_i18n_name_around_tal_content(self): # input/test28.html self._run_check('''\

    Your contact email address is recorded as user@host.com

    ''', [('setPosition', (1, 0)), ('beginScope', {'i18n:translate': 'verify'}), ('startTag', ('p', [('i18n:translate', 'verify', 'i18n')])), ('insertTranslation', ('verify', [('rawtextBeginScope', ('Your contact email address is recorded as\n ', 4, (2, 4), 0, {'i18n:name': 'email', 'tal:omit-tag': ''})), ('i18nVariable', ('email', [('optTag', ('span', '', None, 0, [('startTag', ('span', [('tal:omit-tag', '', 'tal'), ('i18n:name', 'email', 'i18n')]))], [('rawtextBeginScope', ('\n ', 4, (3, 4), 0, {'href': 'mailto:user@example.com', 'tal:content': 'request/submitter'})), ('startTag', ('a', [('href', 'href="mailto:user@example.com"'), ('tal:content', 'request/submitter', 'tal')])), ('insertText', ('$request/submitter$', [('rawtextOffset', ('user@host.com', 13))])), ('endScope', ()), ('rawtextOffset', ('', 4))]))], None, 0)), ('endScope', ()), ('rawtextColumn', ('\n', 0))])), ('endScope', ()), ('rawtextColumn', ('

    \n', 0)) ]) def test_i18n_name_with_tal_content(self): # input/test27.html self._run_check('''\

    Your contact email address is recorded as user@host.com

    ''', [ ('setPosition', (1, 0)), ('beginScope', {'i18n:translate': 'verify'}), ('startTag', ('p', [('i18n:translate', 'verify', 'i18n')])), ('insertTranslation', ('verify', [('rawtextBeginScope', ('Your contact email address is recorded as\n ', 4, (2, 4), 0, {'href': 'mailto:user@example.com', 'i18n:name': 'email', 'tal:content': 'request/submitter'})), ('i18nVariable', ('email', [('startTag', ('a', [('href', 'href="mailto:user@example.com"'), ('tal:content', 'request/submitter', 'tal'), ('i18n:name', 'email', 'i18n')])), ('insertText', ('$request/submitter$', [('rawtextOffset', ('user@host.com', 13))])), ('rawtextOffset', ('', 4))], None, 0)), ('endScope', ()), ('rawtextColumn', ('\n', 0))])), ('endScope', ()), ('rawtextColumn', ('

    \n', 0)) ]) def test_suite(): suite = unittest.makeSuite(HTMLTALParserTestCases) suite.addTest(unittest.makeSuite(METALGeneratorTestCases)) suite.addTest(unittest.makeSuite(TALGeneratorTestCases)) return suite if __name__ == "__main__": errs = utils.run_suite(test_suite()) sys.exit(errs and 1 or 0) zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/0000755000175000017500000000000012214017644022222 5ustar arnauarnauzope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_sa3.html0000644000175000017500000000337612214017644024646 0ustar arnauarnau
    This is macro1 on sa3 line 3. This is slot1 on sa3 line 4. This is the end of macro1 on sa3 line 5.

    Some text on sa3 line 7.

    This is macro1 on sa3 line 3. Text from sa3 line 10 is filled into slot1. This is the end of macro1 on sa3 line 5.

    This is some text on sa3 line 13.

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_sa2.html0000644000175000017500000000057612214017644024644 0ustar arnauarnau Simple test of source annotations

    Foo!

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test37.html0000644000175000017500000000002612214017644024237 0ustar arnauarnau TEST zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test35.html0000644000175000017500000000002712214017644024236 0ustar arnauarnau

    page

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test05.xml0000644000175000017500000000014412214017644024067 0ustar arnauarnau

    This is the body of test5

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test04.xml0000644000175000017500000000065612214017644024076 0ustar arnauarnau
    • 0 hello world
    • 1 hello world
    • 0 goodbye cruel world
    • 1 goodbye cruel world

    define-slot

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test31.html0000644000175000017500000000015312214017644024232 0ustar arnauarnau

    Your contact email address is recorded as aperson@dom.ain

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test17.html0000644000175000017500000000002512214017644024234 0ustar arnauarnauYes Yes Yes Yes Yes zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test16.xml0000644000175000017500000000013612214017644024072 0ustar arnauarnau bar zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_domain.html0000644000175000017500000000014512214017644025416 0ustar arnauarnau
    replace this msgid and another translated string
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test32.html0000644000175000017500000000010412214017644024227 0ustar arnauarnauLomax was born in Antarctica zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test02.html0000644000175000017500000000715612214017644024242 0ustar arnauarnau sample1 a simple invoice
    01786 2000-03-17 55377 2000-03-15 GJ03405 DAVE 1 2000-03-17 K5211(34) 23 23
    SHIPWRIGHT RESTAURANTS LIMITED 125 NORTH SERVICE ROAD W WESTLAKE ACCESS NORTH BAY L8B1O5 ONTARIO CANADA ATTN: PAULINE DEGRASSI 1 CS DM 5309 #1013 12 OZ.MUNICH STEIN 37.72 37.72 6 DZ ON 6420 PROVINCIAL DINNER FORK 17.98 107.88 72 EA JR20643 PLASTIC HANDLED STEAK KNIFE .81 58.32 6 DZ ON 6410 PROVINCIAL TEASPOONS 12.16 72.96 0 DZ ON 6411 PROVINCIAL RD BOWL SPOON 6 17.98 0.00 1 EA DO 3218 34 OZ DUAL DIAL SCALE AM3218 70.00 5.0 66.50 1 CS DM 195 20 OZ.BEER PUB GLASS 55.90 55.90 399.28 3.50 23.75 29.61 33.84 33.84 486.48
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test20.html0000644000175000017500000000003612214017644024230 0ustar arnauarnauREPLACEABLE HERE zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test18.xml0000644000175000017500000000014712214017644024076 0ustar arnauarnau Content Content

    Content

    Yes Yes Yes Yes zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test13.html0000644000175000017500000000014012214017644024226 0ustar arnauarnauHere's a stray greater than: > zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test24.html0000644000175000017500000000022212214017644024231 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test07.html0000644000175000017500000000030112214017644024230 0ustar arnauarnau
    Top Left Top Right
    Bottom left Bottom Right
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test19.xml0000644000175000017500000000017512214017644024100 0ustar arnauarnau REPLACE THIS MSGID AND ANOTHER TRANSLATED STRING zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test20.xml0000644000175000017500000000010312214017644024057 0ustar arnauarnau REPLACEABLE HERE zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_failed_attr_translation.html0000644000175000017500000000004312214017644031040 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test27.html0000644000175000017500000000014712214017644024242 0ustar arnauarnau

    Your contact email address is recorded as aperson@dom.ain

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test11.xml0000644000175000017500000000014012214017644024060 0ustar arnauarnau bar

    bad boy!

    x undefined

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test23.html0000644000175000017500000000004312214017644024231 0ustar arnauarnau59 minutes after 6 PM zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test21.html0000644000175000017500000000005312214017644024230 0ustar arnauarnauLomax WAS BORN IN Antarctica. zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test14.xml0000644000175000017500000000024612214017644024072 0ustar arnauarnau
    car bike broomstick

    Harry Ron Hermione

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test04.html0000644000175000017500000000063112214017644024233 0ustar arnauarnau
    • 0 hello world
    • 1 hello world
    • 0 goodbye cruel world
    • 1 goodbye cruel world

    define-slot

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test09.xml0000644000175000017500000000072212214017644024075 0ustar arnauarnau

    Just a bunch of text.

    more text...

    • first item
    • second item
      1. second list, first item
      2. second list, second item
        term 1
        term 2
        definition
    • Now let's have a paragraph...

      My Paragraph

    • And a table in a list item:
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test15.html0000644000175000017500000000040512214017644024234 0ustar arnauarnau INNERSLOT inner-argument
    OUTERSLOT
    outer-argument
    OUTERSLOT
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test25.html0000644000175000017500000000002612214017644024234 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test18.html0000644000175000017500000000010412214017644024233 0ustar arnauarnauContent Content

    Content

    Yes Yes Yes Yes zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_metal2.html0000644000175000017500000000026312214017644025334 0ustar arnauarnau
    OUTER INNER OUTER
    OUTER INNER OUTER
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test11.html0000644000175000017500000000020512214017644024226 0ustar arnauarnau bar

    bad boy!

    x undefined

    x undefined x undefined
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_sa1.xml0000644000175000017500000000046112214017644024470 0ustar arnauarnau Simple test of source annotations

    Foo!

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/__init__.py0000644000175000017500000000007512214017644024335 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test19.html0000644000175000017500000000013012214017644024233 0ustar arnauarnauREPLACE THIS MSGID AND ANOTHER TRANSLATED STRING zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test01.xml0000644000175000017500000000173012214017644024065 0ustar arnauarnau dadada

    This Is The Replaced Title

     &HarryPotter; here/id

    5

    honda

    subaru

    acura

    foo bar

    • honda
    • subaru
    • acura
    python python  

    Header Level 3

     

    Header Level 3

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test33.html0000644000175000017500000000004012214017644024227 0ustar arnauarnaudon't translate me zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_sa1.html0000644000175000017500000000043312214017644024633 0ustar arnauarnau Simple test of source annotations

    Foo!

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test09.html0000644000175000017500000000071712214017644024245 0ustar arnauarnau

    Just a bunch of text.

    more text...

    • first item
    • second item
      1. second list, first item
      2. second list, second item
        term 1
        term 2
        definition
    • Now let's have a paragraph...

      My Paragraph

    • And a table in a list item:
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test28.html0000644000175000017500000000014712214017644024243 0ustar arnauarnau

    Your contact email address is recorded as aperson@dom.ain

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test05.html0000644000175000017500000000011512214017644024231 0ustar arnauarnau

    This is the body of test5

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test30.html0000644000175000017500000000015312214017644024231 0ustar arnauarnau

    Your contact email address is recorded as aperson@dom.ain

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/acme_template.html0000644000175000017500000000063312214017644025712 0ustar arnauarnau ACME Look and Feel
    "The early bird gets the worm, but the second mouse gets the cheese."
    Preferences...
    Content here
    Copyright 2004 Acme Inc.
    Standard disclaimers apply.
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test12.html0000644000175000017500000000034612214017644024235 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_metal7.html0000644000175000017500000000027312214017644025342 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_metal9.html0000644000175000017500000000123512214017644025343 0ustar arnauarnau
    Default for macro1
    Macro 2's slot 1 decoration Default for macro2
    Macro 2's slot 1 decoration Default for macro2
    Macro 2's slot 1 decoration Custom slot1
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/document_list_source.html0000644000175000017500000000125612214017644027345 0ustar arnauarnau Acme Document List
    "The early bird gets the worm, but the second mouse gets the cheese."
    Preferences...

    Documents

    • Rocket Science for Dummies
    • Birds for the Gourmet Chef
    Copyright 2004 Acme Inc.
    This document list is classified.
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test02.xml0000644000175000017500000000720012214017644024064 0ustar arnauarnau sample1 a simple invoice
    01786 2000-03-17 55377 2000-03-15 GJ03405 DAVE 1 2000-03-17 K5211(34) 23 23
    SHIPWRIGHT RESTAURANTS LIMITED 125 NORTH SERVICE ROAD W WESTLAKE ACCESS NORTH BAY L8B1O5 ONTARIO CANADA ATTN: PAULINE DEGRASSI 1 CS DM 5309 #1013 12 OZ.MUNICH STEIN 37.72 37.72 6 DZ ON 6420 PROVINCIAL DINNER FORK 17.98 107.88 72 EA JR20643 PLASTIC HANDLED STEAK KNIFE .81 58.32 6 DZ ON 6410 PROVINCIAL TEASPOONS 12.16 72.96 0 DZ ON 6411 PROVINCIAL RD BOWL SPOON 6 17.98 0.00 1 EA DO 3218 34 OZ DUAL DIAL SCALE AM3218 70.00 5.0 66.50 1 CS DM 195 20 OZ.BEER PUB GLASS 55.90 55.90 399.28 3.50 23.75 29.61 33.84 33.84 486.48
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test17.xml0000644000175000017500000000007212214017644024072 0ustar arnauarnau Yes Yes Yes Yes Yes zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test03.xml0000644000175000017500000000027712214017644024074 0ustar arnauarnau

    hello brave new world goodbye cruel world hello brave new world

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test01.html0000644000175000017500000000170312214017644024231 0ustar arnauarnau dadada

    This Is The Replaced Title

     &HarryPotter; here/id

    5

    honda

    subaru

    acura

    foo bar

    • honda
    • subaru
    • acura
    python python  

    Header Level 3

     

    Header Level 3

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test29.html0000644000175000017500000000012412214017644024237 0ustar arnauarnau
    AT THE TONE THE TIME WILL BE 59 minutes after 6 PM... BEEP!
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_metal3.html0000644000175000017500000000011412214017644025330 0ustar arnauarnauShould not get attr in metal zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test08.xml0000644000175000017500000000471312214017644024100 0ustar arnauarnau
    Top Left Top Right
    Bottom left

    Some headline

    This is the real contents of the bottom right slot.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test22.html0000644000175000017500000000005512214017644024233 0ustar arnauarnauJim WAS BORN IN the USA. zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_sa2.xml0000644000175000017500000000062412214017644024472 0ustar arnauarnau Simple test of source annotations

    Foo!

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/document_list.html0000644000175000017500000000103512214017644025760 0ustar arnauarnau Acme Document List
    "The early bird gets the worm, but the second mouse gets the cheese."
    Preferences...

    Documents

    • Rocket Science for Dummies
    • Birds for the Gourmet Chef
    Copyright 2004 Acme Inc.
    This document list is classified.
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test06.html0000644000175000017500000000011312214017644024230 0ustar arnauarnau

    This is the body of test5

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test14.html0000644000175000017500000000017612214017644024240 0ustar arnauarnau
    car bike broomstick

    Harry Ron Hermione

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_sa4.html0000644000175000017500000000233712214017644024643 0ustar arnauarnau

    Some text on sa4 line 3.

    This is macro1 on sa3 line 3. Text from sa4 line 6 is filled into slot1. This is the end of macro1 on sa3 line 5.

    This is some text on sa4 line 9.

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_metal1.html0000644000175000017500000000257712214017644025345 0ustar arnauarnau AAA INNER BBB AAA INNER BBB INNER AAA INNER BBB AAA INNER BBB INNER AAA OUTERSLOT BBB AAA INNER INNERSLOT BBB AAA INNER INNERSLOT BBB AAA OUTERSLOT BBB INNER INNERSLOT INNER INNERSLOT INNER INSLOT INSLOT zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test10.html0000644000175000017500000000475212214017644024240 0ustar arnauarnau
    Top Left Top Right
    Bottom left

    Some headline

    This is the real contents of the bottom right slot.


    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.



    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_metal4.html0000644000175000017500000000027312214017644025337 0ustar arnauarnau Z3 UI zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test21.xml0000644000175000017500000000012012214017644024057 0ustar arnauarnau Lomax WAS BORN IN Antarctica. zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test06.xml0000644000175000017500000000014212214017644024066 0ustar arnauarnau

    This is the body of test5

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_sa3.xml0000644000175000017500000000341612214017644024475 0ustar arnauarnau
    This is macro1 on sa3 line 4. This is slot1 on sa3 line 5. This is the end of macro1 on sa3 line 6.

    Some text on sa3 line 8.

    This is macro1 on sa3 line 4. Text from sa3 line 11 is filled into slot1. This is the end of macro1 on sa3 line 6.

    This is some text on sa3 line 14.

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test34.html0000644000175000017500000000012312214017644024232 0ustar arnauarnau stuff foobar more stuff STUFF foobar MORE STUFF zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_metal6.html0000644000175000017500000000022012214017644025331 0ustar arnauarnau Z3 UI zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test03.html0000644000175000017500000000025012214017644024227 0ustar arnauarnau

    hello brave new world goodbye cruel world hello brave new world

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test36.html0000644000175000017500000000010512214017644024234 0ustar arnauarnau<foo> <foo> some text zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/acme_template_source.html0000644000175000017500000000111712214017644027270 0ustar arnauarnau ACME Look and Feel
    "The early bird gets the worm, but the second mouse gets the cheese."
    Preferences...
    Content here
    Copyright 2004 Acme Inc.
    Standard disclaimers apply.
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_metal5.html0000644000175000017500000000026112214017644025335 0ustar arnauarnau Z3 UI zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test07.xml0000644000175000017500000000033012214017644024066 0ustar arnauarnau
    Top Left Top Right
    Bottom left Bottom Right
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test16.html0000644000175000017500000000005712214017644024240 0ustar arnauarnaublah, blah zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test22.xml0000644000175000017500000000012312214017644024063 0ustar arnauarnau content omit replace zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test08.html0000644000175000017500000000466412214017644024251 0ustar arnauarnau
    Top Left Top Right
    Bottom left

    Some headline

    This is the real contents of the bottom right slot.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    It is supposed to contain a lot of text. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb. Blah, blah, blab. Blabber, blabber, blah. Baah, baah, barb.

    zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test26.html0000644000175000017500000000004112214017644024232 0ustar arnauarnau7 is the JOB NUMBER zope2.13-2.13.21/source/zope.tal/src/zope/tal/tests/output/test_metal8.html0000644000175000017500000000046312214017644025344 0ustar arnauarnau
    Default body
    Filled-in body
    zope2.13-2.13.21/source/zope.tal/src/zope/tal/talgenerator.py0000644000175000017500000010047212214017644022565 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Code generator for TALInterpreter intermediate code. $Id: talgenerator.py 39119 2005-10-13 19:20:18Z fdrake $ """ import cgi import re from zope.tal import taldefs from zope.tal.taldefs import NAME_RE, TAL_VERSION from zope.tal.taldefs import I18NError, METALError, TALError from zope.tal.taldefs import parseSubstitution from zope.tal.translationcontext import TranslationContext, DEFAULT_DOMAIN _name_rx = re.compile(NAME_RE) class TALGenerator(object): inMacroUse = 0 inMacroDef = 0 source_file = None def __init__(self, expressionCompiler=None, xml=1, source_file=None): if not expressionCompiler: from zope.tal.dummyengine import DummyEngine expressionCompiler = DummyEngine() self.expressionCompiler = expressionCompiler self.CompilerError = expressionCompiler.getCompilerError() # This holds the emitted opcodes representing the input self.program = [] # The program stack for when we need to do some sub-evaluation for an # intermediate result. E.g. in an i18n:name tag for which the # contents describe the ${name} value. self.stack = [] # Another stack of postponed actions. Elements on this stack are a # dictionary; key/values contain useful information that # emitEndElement needs to finish its calculations self.todoStack = [] self.macros = {} # {slot-name --> default content program} self.slots = {} self.slotStack = [] self.xml = xml # true --> XML, false --> HTML self.emit("version", TAL_VERSION) self.emit("mode", xml and "xml" or "html") if source_file is not None: self.source_file = source_file self.emit("setSourceFile", source_file) self.i18nContext = TranslationContext() self.i18nLevel = 0 def getCode(self): assert not self.stack assert not self.todoStack return self.optimize(self.program), self.macros def optimize(self, program): output = [] collect = [] cursor = 0 for cursor in xrange(len(program)+1): try: item = program[cursor] except IndexError: item = (None, None) opcode = item[0] if opcode == "rawtext": collect.append(item[1]) continue if opcode == "endTag": collect.append("" % item[1]) continue if opcode == "startTag": if self.optimizeStartTag(collect, item[1], item[2], ">"): continue if opcode == "startEndTag": endsep = self.xml and "/>" or " />" if self.optimizeStartTag(collect, item[1], item[2], endsep): continue if opcode in ("beginScope", "endScope"): # Push *Scope instructions in front of any text instructions; # this allows text instructions separated only by *Scope # instructions to be joined together. output.append(self.optimizeArgsList(item)) continue if opcode == 'noop': # This is a spacer for end tags in the face of i18n:name # attributes. We can't let the optimizer collect immediately # following end tags into the same rawtextOffset. opcode = None pass text = "".join(collect) if text: i = text.rfind("\n") if i >= 0: i = len(text) - (i + 1) output.append(("rawtextColumn", (text, i))) else: output.append(("rawtextOffset", (text, len(text)))) if opcode != None: output.append(self.optimizeArgsList(item)) collect = [] return self.optimizeCommonTriple(output) def optimizeArgsList(self, item): if len(item) == 2: return item else: return item[0], tuple(item[1:]) # These codes are used to indicate what sort of special actions # are needed for each special attribute. (Simple attributes don't # get action codes.) # # The special actions (which are modal) are handled by # TALInterpreter.attrAction() and .attrAction_tal(). # # Each attribute is represented by a tuple: # # (name, value) -- a simple name/value pair, with # no special processing # # (name, value, action, *extra) -- attribute with special # processing needs, action is a # code that indicates which # branch to take, and *extra # contains additional, # action-specific information # needed by the processing # def optimizeStartTag(self, collect, name, attrlist, end): # return true if the tag can be converted to plain text if not attrlist: collect.append("<%s%s" % (name, end)) return 1 opt = 1 new = ["<" + name] for i in range(len(attrlist)): item = attrlist[i] if len(item) > 2: opt = 0 name, value, action = item[:3] attrlist[i] = (name, value, action) + item[3:] else: if item[1] is None: s = item[0] else: s = '%s="%s"' % (item[0], taldefs.attrEscape(item[1])) attrlist[i] = item[0], s new.append(" " + s) # if no non-optimizable attributes were found, convert to plain text if opt: new.append(end) collect.extend(new) return opt def optimizeCommonTriple(self, program): if len(program) < 3: return program output = program[:2] prev2, prev1 = output for item in program[2:]: if ( item[0] == "beginScope" and prev1[0] == "setPosition" and prev2[0] == "rawtextColumn"): position = output.pop()[1] text, column = output.pop()[1] prev1 = None, None closeprev = 0 if output and output[-1][0] == "endScope": closeprev = 1 output.pop() item = ("rawtextBeginScope", (text, column, position, closeprev, item[1])) output.append(item) prev2 = prev1 prev1 = item return output def todoPush(self, todo): self.todoStack.append(todo) def todoPop(self): return self.todoStack.pop() def compileExpression(self, expr): try: return self.expressionCompiler.compile(expr) except self.CompilerError, err: raise TALError('%s in expression %s' % (err.args[0], `expr`), self.position) def pushProgram(self): self.stack.append(self.program) self.program = [] def popProgram(self): program = self.program self.program = self.stack.pop() return self.optimize(program) def pushSlots(self): self.slotStack.append(self.slots) self.slots = {} def popSlots(self): slots = self.slots self.slots = self.slotStack.pop() return slots def emit(self, *instruction): self.program.append(instruction) def emitStartTag(self, name, attrlist, isend=0): if isend: opcode = "startEndTag" else: opcode = "startTag" self.emit(opcode, name, attrlist) def emitEndTag(self, name): if self.xml and self.program and self.program[-1][0] == "startTag": # Minimize empty element self.program[-1] = ("startEndTag",) + self.program[-1][1:] else: self.emit("endTag", name) def emitOptTag(self, name, optTag, isend): program = self.popProgram() #block start = self.popProgram() #start tag if (isend or not program) and self.xml: # Minimize empty element start[-1] = ("startEndTag",) + start[-1][1:] isend = 1 cexpr = optTag[0] if cexpr: cexpr = self.compileExpression(optTag[0]) self.emit("optTag", name, cexpr, optTag[1], isend, start, program) def emitRawText(self, text): self.emit("rawtext", text) def emitText(self, text): self.emitRawText(cgi.escape(text)) def emitDefines(self, defines): for part in taldefs.splitParts(defines): m = re.match( r"(?s)\s*(?:(global|local)\s+)?(%s)\s+(.*)\Z" % NAME_RE, part) if not m: raise TALError("invalid define syntax: " + `part`, self.position) scope, name, expr = m.group(1, 2, 3) scope = scope or "local" cexpr = self.compileExpression(expr) if scope == "local": self.emit("setLocal", name, cexpr) else: self.emit("setGlobal", name, cexpr) def emitOnError(self, name, onError, TALtag, isend): block = self.popProgram() key, expr = parseSubstitution(onError) cexpr = self.compileExpression(expr) if key == "text": self.emit("insertText", cexpr, []) else: assert key == "structure" self.emit("insertStructure", cexpr, {}, []) if TALtag: self.emitOptTag(name, (None, 1), isend) else: self.emitEndTag(name) handler = self.popProgram() self.emit("onError", block, handler) def emitCondition(self, expr): cexpr = self.compileExpression(expr) program = self.popProgram() self.emit("condition", cexpr, program) def emitRepeat(self, arg): m = re.match("(?s)\s*(%s)\s+(.*)\Z" % NAME_RE, arg) if not m: raise TALError("invalid repeat syntax: " + `arg`, self.position) name, expr = m.group(1, 2) cexpr = self.compileExpression(expr) program = self.popProgram() self.emit("loop", name, cexpr, program) def emitSubstitution(self, arg, attrDict={}): key, expr = parseSubstitution(arg) cexpr = self.compileExpression(expr) program = self.popProgram() if key == "text": self.emit("insertText", cexpr, program) else: assert key == "structure" self.emit("insertStructure", cexpr, attrDict, program) def emitI18nSubstitution(self, arg, attrDict={}): # TODO: Code duplication is BAD, we need to fix it later key, expr = parseSubstitution(arg) cexpr = self.compileExpression(expr) program = self.popProgram() if key == "text": self.emit("insertI18nText", cexpr, program) else: assert key == "structure" self.emit("insertI18nStructure", cexpr, attrDict, program) def emitEvaluateCode(self, lang): program = self.popProgram() self.emit('evaluateCode', lang, program) def emitI18nVariable(self, varname): # Used for i18n:name attributes. m = _name_rx.match(varname) if m is None or m.group() != varname: raise TALError("illegal i18n:name: %r" % varname, self.position) program = self.popProgram() self.emit('i18nVariable', varname, program, None, False) def emitTranslation(self, msgid, i18ndata): program = self.popProgram() if i18ndata is None: self.emit('insertTranslation', msgid, program) else: key, expr = parseSubstitution(i18ndata) cexpr = self.compileExpression(expr) assert key == 'text' self.emit('insertTranslation', msgid, program, cexpr) def emitDefineMacro(self, macroName): program = self.popProgram() macroName = macroName.strip() if self.macros.has_key(macroName): raise METALError("duplicate macro definition: %s" % `macroName`, self.position) if not re.match('%s$' % NAME_RE, macroName): raise METALError("invalid macro name: %s" % `macroName`, self.position) self.macros[macroName] = program self.inMacroDef = self.inMacroDef - 1 self.emit("defineMacro", macroName, program) def emitUseMacro(self, expr): cexpr = self.compileExpression(expr) program = self.popProgram() self.inMacroUse = 0 self.emit("useMacro", expr, cexpr, self.popSlots(), program) def emitExtendMacro(self, defineName, useExpr): cexpr = self.compileExpression(useExpr) program = self.popProgram() self.inMacroUse = 0 self.emit("extendMacro", useExpr, cexpr, self.popSlots(), program, defineName) self.emitDefineMacro(defineName) def emitDefineSlot(self, slotName): program = self.popProgram() slotName = slotName.strip() if not re.match('%s$' % NAME_RE, slotName): raise METALError("invalid slot name: %s" % `slotName`, self.position) self.emit("defineSlot", slotName, program) def emitFillSlot(self, slotName): program = self.popProgram() slotName = slotName.strip() if self.slots.has_key(slotName): raise METALError("duplicate fill-slot name: %s" % `slotName`, self.position) if not re.match('%s$' % NAME_RE, slotName): raise METALError("invalid slot name: %s" % `slotName`, self.position) self.slots[slotName] = program self.inMacroUse = 1 self.emit("fillSlot", slotName, program) def unEmitWhitespace(self): collect = [] i = len(self.program) - 1 while i >= 0: item = self.program[i] if item[0] != "rawtext": break text = item[1] if not re.match(r"\A\s*\Z", text): break collect.append(text) i = i-1 del self.program[i+1:] if i >= 0 and self.program[i][0] == "rawtext": text = self.program[i][1] m = re.search(r"\s+\Z", text) if m: self.program[i] = ("rawtext", text[:m.start()]) collect.append(m.group()) collect.reverse() return "".join(collect) def unEmitNewlineWhitespace(self): collect = [] i = len(self.program) while i > 0: i = i-1 item = self.program[i] if item[0] != "rawtext": break text = item[1] if re.match(r"\A[ \t]*\Z", text): collect.append(text) continue m = re.match(r"(?s)^(.*)(\n[ \t]*)\Z", text) if not m: break text, rest = m.group(1, 2) collect.reverse() rest = rest + "".join(collect) del self.program[i:] if text: self.emit("rawtext", text) return rest return None def replaceAttrs(self, attrlist, repldict): # Each entry in attrlist starts like (name, value). Result is # (name, value, action, expr, xlat, msgid) if there is a # tal:attributes entry for that attribute. Additional attrs # defined only by tal:attributes are added here. # # (name, value, action, expr, xlat, msgid) if not repldict: return attrlist newlist = [] for item in attrlist: key = item[0] if repldict.has_key(key): expr, xlat, msgid = repldict[key] item = item[:2] + ("replace", expr, xlat, msgid) del repldict[key] newlist.append(item) # Add dynamic-only attributes for key, (expr, xlat, msgid) in repldict.items(): newlist.append((key, None, "insert", expr, xlat, msgid)) return newlist def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict, position=(None, None), isend=0): if not taldict and not metaldict and not i18ndict: # Handle the simple, common case self.emitStartTag(name, attrlist, isend) self.todoPush({}) if isend: self.emitEndElement(name, isend) return self.position = position # TODO: Ugly hack to work around tal:replace and i18n:translate issue. # I (DV) need to cleanup the code later. replaced = False if "replace" in taldict: if "content" in taldict: raise TALError( "tal:content and tal:replace are mutually exclusive", position) taldict["omit-tag"] = taldict.get("omit-tag", "") taldict["content"] = taldict.pop("replace") replaced = True for key, value in taldict.items(): if key not in taldefs.KNOWN_TAL_ATTRIBUTES: raise TALError("bad TAL attribute: " + `key`, position) if not (value or key == 'omit-tag'): raise TALError("missing value for TAL attribute: " + `key`, position) for key, value in metaldict.items(): if key not in taldefs.KNOWN_METAL_ATTRIBUTES: raise METALError("bad METAL attribute: " + `key`, position) if not value: raise TALError("missing value for METAL attribute: " + `key`, position) for key, value in i18ndict.items(): if key not in taldefs.KNOWN_I18N_ATTRIBUTES: raise I18NError("bad i18n attribute: " + `key`, position) if not value and key in ("attributes", "data", "id"): raise I18NError("missing value for i18n attribute: " + `key`, position) todo = {} defineMacro = metaldict.get("define-macro") extendMacro = metaldict.get("extend-macro") useMacro = metaldict.get("use-macro") defineSlot = metaldict.get("define-slot") fillSlot = metaldict.get("fill-slot") define = taldict.get("define") condition = taldict.get("condition") repeat = taldict.get("repeat") content = taldict.get("content") script = taldict.get("script") attrsubst = taldict.get("attributes") onError = taldict.get("on-error") omitTag = taldict.get("omit-tag") TALtag = taldict.get("tal tag") i18nattrs = i18ndict.get("attributes") # Preserve empty string if implicit msgids are used. We'll generate # code with the msgid='' and calculate the right implicit msgid during # interpretation phase. msgid = i18ndict.get("translate") varname = i18ndict.get('name') i18ndata = i18ndict.get('data') if varname and not self.i18nLevel: raise I18NError( "i18n:name can only occur inside a translation unit", position) if i18ndata and not msgid: raise I18NError("i18n:data must be accompanied by i18n:translate", position) if extendMacro: if useMacro: raise METALError( "extend-macro cannot be used with use-macro", position) if not defineMacro: raise METALError( "extend-macro must be used with define-macro", position) if defineMacro or extendMacro or useMacro: if fillSlot or defineSlot: raise METALError( "define-slot and fill-slot cannot be used with " "define-macro, extend-macro, or use-macro", position) if defineMacro and useMacro: raise METALError( "define-macro may not be used with use-macro", position) useMacro = useMacro or extendMacro if content and msgid: raise I18NError( "explicit message id and tal:content can't be used together", position) repeatWhitespace = None if repeat: # Hack to include preceding whitespace in the loop program repeatWhitespace = self.unEmitNewlineWhitespace() if position != (None, None): # TODO: at some point we should insist on a non-trivial position self.emit("setPosition", position) if self.inMacroUse: if fillSlot: self.pushProgram() # generate a source annotation at the beginning of fill-slot if self.source_file is not None: if position != (None, None): self.emit("setPosition", position) self.emit("setSourceFile", self.source_file) todo["fillSlot"] = fillSlot self.inMacroUse = 0 else: if fillSlot: raise METALError("fill-slot must be within a use-macro", position) if not self.inMacroUse: if defineMacro: self.pushProgram() self.emit("version", TAL_VERSION) self.emit("mode", self.xml and "xml" or "html") # generate a source annotation at the beginning of the macro if self.source_file is not None: if position != (None, None): self.emit("setPosition", position) self.emit("setSourceFile", self.source_file) todo["defineMacro"] = defineMacro self.inMacroDef = self.inMacroDef + 1 if useMacro: self.pushSlots() self.pushProgram() todo["useMacro"] = useMacro self.inMacroUse = 1 if defineSlot: if not self.inMacroDef: raise METALError( "define-slot must be within a define-macro", position) self.pushProgram() todo["defineSlot"] = defineSlot if defineSlot or i18ndict: domain = i18ndict.get("domain") or self.i18nContext.domain source = i18ndict.get("source") or self.i18nContext.source target = i18ndict.get("target") or self.i18nContext.target if ( domain != DEFAULT_DOMAIN or source is not None or target is not None): self.i18nContext = TranslationContext(self.i18nContext, domain=domain, source=source, target=target) self.emit("beginI18nContext", {"domain": domain, "source": source, "target": target}) todo["i18ncontext"] = 1 if taldict or i18ndict: dict = {} for item in attrlist: key, value = item[:2] dict[key] = value self.emit("beginScope", dict) todo["scope"] = 1 if onError: self.pushProgram() # handler if TALtag: self.pushProgram() # start self.emitStartTag(name, list(attrlist)) # Must copy attrlist! if TALtag: self.pushProgram() # start self.pushProgram() # block todo["onError"] = onError if define: self.emitDefines(define) todo["define"] = define if condition: self.pushProgram() todo["condition"] = condition if repeat: todo["repeat"] = repeat self.pushProgram() if repeatWhitespace: self.emitText(repeatWhitespace) if content: if varname: todo['i18nvar'] = varname todo["content"] = content self.pushProgram() else: todo["content"] = content # i18n:name w/o tal:replace uses the content as the interpolation # dictionary values elif varname: todo['i18nvar'] = varname self.pushProgram() if msgid is not None: self.i18nLevel += 1 todo['msgid'] = msgid if i18ndata: todo['i18ndata'] = i18ndata optTag = omitTag is not None or TALtag if optTag: todo["optional tag"] = omitTag, TALtag self.pushProgram() if attrsubst or i18nattrs: if attrsubst: repldict = taldefs.parseAttributeReplacements(attrsubst, self.xml) else: repldict = {} if i18nattrs: i18nattrs = _parseI18nAttributes(i18nattrs, self.position, self.xml) else: i18nattrs = {} # Convert repldict's name-->expr mapping to a # name-->(compiled_expr, translate) mapping for key, value in repldict.items(): if i18nattrs.get(key, None): raise I18NError( "attribute [%s] cannot both be part of tal:attributes" " and have a msgid in i18n:attributes" % key, position) ce = self.compileExpression(value) repldict[key] = ce, key in i18nattrs, i18nattrs.get(key) for key in i18nattrs: if key not in repldict: repldict[key] = None, 1, i18nattrs.get(key) else: repldict = {} if replaced: todo["repldict"] = repldict repldict = {} if script: todo["script"] = script self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend) if optTag: self.pushProgram() if content and not varname: self.pushProgram() if not content and msgid is not None: self.pushProgram() if content and varname: self.pushProgram() if script: self.pushProgram() if todo and position != (None, None): todo["position"] = position self.todoPush(todo) if isend: self.emitEndElement(name, isend, position=position) def emitEndElement(self, name, isend=0, implied=0, position=(None, None)): todo = self.todoPop() if not todo: # Shortcut if not isend: self.emitEndTag(name) return self.position = todo.get("position", (None, None)) defineMacro = todo.get("defineMacro") useMacro = todo.get("useMacro") defineSlot = todo.get("defineSlot") fillSlot = todo.get("fillSlot") repeat = todo.get("repeat") content = todo.get("content") script = todo.get("script") condition = todo.get("condition") onError = todo.get("onError") repldict = todo.get("repldict", {}) scope = todo.get("scope") optTag = todo.get("optional tag") msgid = todo.get('msgid') i18ncontext = todo.get("i18ncontext") varname = todo.get('i18nvar') i18ndata = todo.get('i18ndata') if implied > 0: if defineMacro or useMacro or defineSlot or fillSlot: exc = METALError what = "METAL" else: exc = TALError what = "TAL" raise exc("%s attributes on <%s> require explicit " % (what, name, name), self.position) if script: self.emitEvaluateCode(script) # If there's no tal:content or tal:replace in the tag with the # i18n:name, tal:replace is the default. if content: if msgid is not None: self.emitI18nSubstitution(content, repldict) else: self.emitSubstitution(content, repldict) # If we're looking at an implicit msgid, emit the insertTranslation # opcode now, so that the end tag doesn't become part of the implicit # msgid. If we're looking at an explicit msgid, it's better to emit # the opcode after the i18nVariable opcode so we can better handle # tags with both of them in them (and in the latter case, the contents # would be thrown away for msgid purposes). # # Still, we should emit insertTranslation opcode before i18nVariable # in case tal:content, i18n:translate and i18n:name in the same tag if not content and msgid is not None: self.emitTranslation(msgid, i18ndata) self.i18nLevel -= 1 if optTag: self.emitOptTag(name, optTag, isend) elif not isend: # If we're processing the end tag for a tag that contained # i18n:name, we need to make sure that optimize() won't collect # immediately following end tags into the same rawtextOffset, so # put a spacer here that the optimizer will recognize. if varname: self.emit('noop') self.emitEndTag(name) if varname: self.emitI18nVariable(varname) if repeat: self.emitRepeat(repeat) if condition: self.emitCondition(condition) if onError: self.emitOnError(name, onError, optTag and optTag[1], isend) if scope: self.emit("endScope") if i18ncontext: self.emit("endI18nContext") assert self.i18nContext.parent is not None self.i18nContext = self.i18nContext.parent if defineSlot: self.emitDefineSlot(defineSlot) if fillSlot: self.emitFillSlot(fillSlot) if useMacro or defineMacro: if useMacro and defineMacro: self.emitExtendMacro(defineMacro, useMacro) elif useMacro: self.emitUseMacro(useMacro) elif defineMacro: self.emitDefineMacro(defineMacro) if useMacro or defineSlot: # generate a source annotation after define-slot or use-macro # because the source file might have changed if self.source_file is not None: if position != (None, None): self.emit("setPosition", position) self.emit("setSourceFile", self.source_file) def _parseI18nAttributes(i18nattrs, position, xml): d = {} # Filter out empty items, eg: # i18n:attributes="value msgid; name msgid2;" # would result in 3 items where the last one is empty attrs = [spec for spec in i18nattrs.split(";") if spec] for spec in attrs: parts = spec.split() if len(parts) == 2: attr, msgid = parts elif len(parts) == 1: attr = parts[0] msgid = None else: raise TALError("illegal i18n:attributes specification: %r" % spec, position) if not xml: attr = attr.lower() if attr in d: raise TALError( "attribute may only be specified once in i18n:attributes: %r" % attr, position) d[attr] = msgid return d def test(): t = TALGenerator() t.pushProgram() t.emit("bar") p = t.popProgram() t.emit("foo", p) if __name__ == "__main__": test() zope2.13-2.13.21/source/zope.tal/src/zope/__init__.py0000644000175000017500000000007012214017644021046 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.tal/src/zope.tal.egg-info/0000755000175000017500000000000012214017644021211 5ustar arnauarnauzope2.13-2.13.21/source/zope.tal/src/zope.tal.egg-info/PKG-INFO0000644000175000017500000000643112214017644022312 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.tal Version: 3.5.2 Summary: Zope 3 Template Application Languate (TAL) Home-page: http://pypi.python.org/pypi/zope.tal Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Template Attribute Language (TAL) ================================= Overview -------- The Zope3 Template Attribute Languate (TAL) specifies the custom namespace and attributes which are used by the Zope Page Templates renderer to inject dynamic markup into a page. It also includes the Macro Expansion for TAL (METAL) macro language used in page assembly. The dynamic values themselves are specified using a companion language, TALES (see the 'zope.tales' package for more). See: http://www.zope.org/Wikis/DevSite/Projects/ZPT/TAL%20Specification%201.4 ======= CHANGES ======= 3.5.2 (2009-10-31) ------------------ - In talgettext.POEngine.translate, print a warning if a msgid already exists in the domain with a different default. 3.5.1 (2009-03-08) ------------------ - Updated tests of "bad" entities for compatibility with the stricter HTMLParser module shipped with Python 2.6.x. 3.5.0 (2008-06-06) ------------------ - Removed artificial addition of a trailing newline if the output doesn't end in one; this allows the template source to be the full specification of what should be included. (See https://bugs.launchpad.net/launchpad/+bug/218706.) 3.4.1 (2007-11-16) ------------------ - Removed unnecessary ``dummyengine`` dependency on zope.i18n to simplify distribution. The ``dummyengine.DummyTranslationDomain`` class no longer implements ``zope.i18n.interfaces.ITranslationDomain`` as a result. Installing zope.tal with easy_install or buildout no longer pulls in many unrelated distributions. - Support ability to run tests using "setup.py test". - Stop pinning (no longer required) zope.traversing and zope.app.publisher versions in buildout.cfg. 3.4.0 (2007-10-03) ------------------ - Updated package meta-data. 3.4.0b1 ------- - Updated dependency for ``zope.i18n`` that requires the correct version of zope.security to avoid a hidden dependency issue in zope.security. Note: The code changes before 3.4.0b1 where not tracked as an individual package and have been documented in the Zope 3 changelog. Keywords: zope3 template xml tal Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.tal/src/zope.tal.egg-info/dependency_links.txt0000644000175000017500000000000112214017644025257 0ustar arnauarnau zope2.13-2.13.21/source/zope.tal/src/zope.tal.egg-info/requires.txt0000644000175000017500000000010112214017644023601 0ustar arnauarnausetuptools zope.i18nmessageid zope.interface [test] zope.testingzope2.13-2.13.21/source/zope.tal/src/zope.tal.egg-info/namespace_packages.txt0000644000175000017500000000000512214017644025537 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.tal/src/zope.tal.egg-info/top_level.txt0000644000175000017500000000000512214017644023736 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.tal/src/zope.tal.egg-info/SOURCES.txt0000644000175000017500000001754412214017644023110 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.tal.egg-info/PKG-INFO src/zope.tal.egg-info/SOURCES.txt src/zope.tal.egg-info/dependency_links.txt src/zope.tal.egg-info/namespace_packages.txt src/zope.tal.egg-info/not-zip-safe src/zope.tal.egg-info/requires.txt src/zope.tal.egg-info/top_level.txt src/zope/tal/DEPENDENCIES.cfg src/zope/tal/__init__.py src/zope/tal/driver.py src/zope/tal/dummyengine.py src/zope/tal/htmltalparser.py src/zope/tal/interfaces.py src/zope/tal/ndiff.py src/zope/tal/runtest.py src/zope/tal/setpath.py src/zope/tal/taldefs.py src/zope/tal/talgenerator.py src/zope/tal/talgettext.py src/zope/tal/talinterpreter.py src/zope/tal/talparser.py src/zope/tal/timer.py src/zope/tal/translationcontext.py src/zope/tal/xmlparser.py src/zope/tal/benchmark/__init__.py src/zope/tal/benchmark/dtml01.html src/zope/tal/benchmark/dtml02.html src/zope/tal/benchmark/dtml03.html src/zope/tal/benchmark/dtml04.html src/zope/tal/benchmark/dtml05.html src/zope/tal/benchmark/dtml06.html src/zope/tal/benchmark/dtml07.html src/zope/tal/benchmark/dtml08.html src/zope/tal/benchmark/dtml09.html src/zope/tal/benchmark/dtml10.html src/zope/tal/benchmark/dtml11.html src/zope/tal/benchmark/dtml12.html src/zope/tal/benchmark/tal01.html src/zope/tal/benchmark/tal02.html src/zope/tal/benchmark/tal03.html src/zope/tal/benchmark/tal04.html src/zope/tal/benchmark/tal05.html src/zope/tal/benchmark/tal06.html src/zope/tal/benchmark/tal07.html src/zope/tal/benchmark/tal08.html src/zope/tal/benchmark/tal09.html src/zope/tal/benchmark/tal10.html src/zope/tal/benchmark/tal11.html src/zope/tal/benchmark/tal12.html src/zope/tal/tests/__init__.py src/zope/tal/tests/markbench.py src/zope/tal/tests/run.py src/zope/tal/tests/test_files.py src/zope/tal/tests/test_htmltalparser.py src/zope/tal/tests/test_sourcepos.py src/zope/tal/tests/test_talgettext.py src/zope/tal/tests/test_talinterpreter.py src/zope/tal/tests/test_talparser.py src/zope/tal/tests/test_xmlparser.py src/zope/tal/tests/utils.py src/zope/tal/tests/input/__init__.py src/zope/tal/tests/input/acme_template.pt src/zope/tal/tests/input/document_list.pt src/zope/tal/tests/input/pnome_template.pt src/zope/tal/tests/input/test01.html src/zope/tal/tests/input/test01.xml src/zope/tal/tests/input/test02.html src/zope/tal/tests/input/test02.xml src/zope/tal/tests/input/test03.html src/zope/tal/tests/input/test03.xml src/zope/tal/tests/input/test04.html src/zope/tal/tests/input/test04.xml src/zope/tal/tests/input/test05.html src/zope/tal/tests/input/test05.xml src/zope/tal/tests/input/test06.html src/zope/tal/tests/input/test06.xml src/zope/tal/tests/input/test07.html src/zope/tal/tests/input/test07.xml src/zope/tal/tests/input/test08.html src/zope/tal/tests/input/test08.xml src/zope/tal/tests/input/test09.html src/zope/tal/tests/input/test09.xml src/zope/tal/tests/input/test10.html src/zope/tal/tests/input/test11.html src/zope/tal/tests/input/test11.xml src/zope/tal/tests/input/test12.html src/zope/tal/tests/input/test13.html src/zope/tal/tests/input/test14.html src/zope/tal/tests/input/test14.xml src/zope/tal/tests/input/test15.html src/zope/tal/tests/input/test16.html src/zope/tal/tests/input/test16.xml src/zope/tal/tests/input/test17.html src/zope/tal/tests/input/test17.xml src/zope/tal/tests/input/test18.html src/zope/tal/tests/input/test18.xml src/zope/tal/tests/input/test19.html src/zope/tal/tests/input/test19.xml src/zope/tal/tests/input/test20.html src/zope/tal/tests/input/test20.xml src/zope/tal/tests/input/test21.html src/zope/tal/tests/input/test21.xml src/zope/tal/tests/input/test22.html src/zope/tal/tests/input/test22.xml src/zope/tal/tests/input/test23.html src/zope/tal/tests/input/test24.html src/zope/tal/tests/input/test25.html src/zope/tal/tests/input/test26.html src/zope/tal/tests/input/test27.html src/zope/tal/tests/input/test28.html src/zope/tal/tests/input/test29.html src/zope/tal/tests/input/test30.html src/zope/tal/tests/input/test31.html src/zope/tal/tests/input/test32.html src/zope/tal/tests/input/test33.html src/zope/tal/tests/input/test34.html src/zope/tal/tests/input/test35.html src/zope/tal/tests/input/test36.html src/zope/tal/tests/input/test37.html src/zope/tal/tests/input/test_domain.html src/zope/tal/tests/input/test_failed_attr_translation.html src/zope/tal/tests/input/test_metal1.html src/zope/tal/tests/input/test_metal2.html src/zope/tal/tests/input/test_metal3.html src/zope/tal/tests/input/test_metal4.html src/zope/tal/tests/input/test_metal5.html src/zope/tal/tests/input/test_metal6.html src/zope/tal/tests/input/test_metal7.html src/zope/tal/tests/input/test_metal8.html src/zope/tal/tests/input/test_metal9.html src/zope/tal/tests/input/test_sa1.html src/zope/tal/tests/input/test_sa1.xml src/zope/tal/tests/input/test_sa2.html src/zope/tal/tests/input/test_sa2.xml src/zope/tal/tests/input/test_sa3.html src/zope/tal/tests/input/test_sa3.xml src/zope/tal/tests/input/test_sa4.html src/zope/tal/tests/output/__init__.py src/zope/tal/tests/output/acme_template.html src/zope/tal/tests/output/acme_template_source.html src/zope/tal/tests/output/document_list.html src/zope/tal/tests/output/document_list_source.html src/zope/tal/tests/output/test01.html src/zope/tal/tests/output/test01.xml src/zope/tal/tests/output/test02.html src/zope/tal/tests/output/test02.xml src/zope/tal/tests/output/test03.html src/zope/tal/tests/output/test03.xml src/zope/tal/tests/output/test04.html src/zope/tal/tests/output/test04.xml src/zope/tal/tests/output/test05.html src/zope/tal/tests/output/test05.xml src/zope/tal/tests/output/test06.html src/zope/tal/tests/output/test06.xml src/zope/tal/tests/output/test07.html src/zope/tal/tests/output/test07.xml src/zope/tal/tests/output/test08.html src/zope/tal/tests/output/test08.xml src/zope/tal/tests/output/test09.html src/zope/tal/tests/output/test09.xml src/zope/tal/tests/output/test10.html src/zope/tal/tests/output/test11.html src/zope/tal/tests/output/test11.xml src/zope/tal/tests/output/test12.html src/zope/tal/tests/output/test13.html src/zope/tal/tests/output/test14.html src/zope/tal/tests/output/test14.xml src/zope/tal/tests/output/test15.html src/zope/tal/tests/output/test16.html src/zope/tal/tests/output/test16.xml src/zope/tal/tests/output/test17.html src/zope/tal/tests/output/test17.xml src/zope/tal/tests/output/test18.html src/zope/tal/tests/output/test18.xml src/zope/tal/tests/output/test19.html src/zope/tal/tests/output/test19.xml src/zope/tal/tests/output/test20.html src/zope/tal/tests/output/test20.xml src/zope/tal/tests/output/test21.html src/zope/tal/tests/output/test21.xml src/zope/tal/tests/output/test22.html src/zope/tal/tests/output/test22.xml src/zope/tal/tests/output/test23.html src/zope/tal/tests/output/test24.html src/zope/tal/tests/output/test25.html src/zope/tal/tests/output/test26.html src/zope/tal/tests/output/test27.html src/zope/tal/tests/output/test28.html src/zope/tal/tests/output/test29.html src/zope/tal/tests/output/test30.html src/zope/tal/tests/output/test31.html src/zope/tal/tests/output/test32.html src/zope/tal/tests/output/test33.html src/zope/tal/tests/output/test34.html src/zope/tal/tests/output/test35.html src/zope/tal/tests/output/test36.html src/zope/tal/tests/output/test37.html src/zope/tal/tests/output/test_domain.html src/zope/tal/tests/output/test_failed_attr_translation.html src/zope/tal/tests/output/test_metal1.html src/zope/tal/tests/output/test_metal2.html src/zope/tal/tests/output/test_metal3.html src/zope/tal/tests/output/test_metal4.html src/zope/tal/tests/output/test_metal5.html src/zope/tal/tests/output/test_metal6.html src/zope/tal/tests/output/test_metal7.html src/zope/tal/tests/output/test_metal8.html src/zope/tal/tests/output/test_metal9.html src/zope/tal/tests/output/test_sa1.html src/zope/tal/tests/output/test_sa1.xml src/zope/tal/tests/output/test_sa2.html src/zope/tal/tests/output/test_sa2.xml src/zope/tal/tests/output/test_sa3.html src/zope/tal/tests/output/test_sa3.xml src/zope/tal/tests/output/test_sa4.htmlzope2.13-2.13.21/source/zope.tal/src/zope.tal.egg-info/not-zip-safe0000644000175000017500000000000112214017644023437 0ustar arnauarnau zope2.13-2.13.21/source/Acquisition/0000755000175000017500000000000012214017433015724 5ustar arnauarnauzope2.13-2.13.21/source/Acquisition/setup.py0000644000175000017500000000326312214017433017442 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Setup for the Acquisition distribution """ import os from setuptools import setup, find_packages, Extension setup(name='Acquisition', version = '2.13.8', url='http://pypi.python.org/pypi/Acquisition', license='ZPL 2.1', description="Acquisition is a mechanism that allows objects to obtain " "attributes from the containment hierarchy they're in.", author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=open( os.path.join('src', 'Acquisition', 'README.txt')).read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), package_dir={'': 'src'}, ext_modules=[Extension("Acquisition._Acquisition", [os.path.join('src', 'Acquisition', '_Acquisition.c')], include_dirs=['include', 'src']), ], install_requires=['ExtensionClass', 'zope.interface'], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/Acquisition/PKG-INFO0000644000175000017500000005167112214017433017033 0ustar arnauarnauMetadata-Version: 1.0 Name: Acquisition Version: 2.13.8 Summary: Acquisition is a mechanism that allows objects to obtain attributes from the containment hierarchy they're in. Home-page: http://pypi.python.org/pypi/Acquisition Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: .. contents:: Introductory Example ==================== Zope implements acquisition with "Extension Class" mix-in classes. To use acquisition your classes must inherit from an acquisition base class. For example:: >>> import ExtensionClass, Acquisition >>> class C(ExtensionClass.Base): ... color='red' >>> class A(Acquisition.Implicit): ... def report(self): ... print self.color ... >>> a = A() >>> c = C() >>> c.a = a >>> c.a.report() red >>> d = C() >>> d.color = 'green' >>> d.a = a >>> d.a.report() green >>> a.report() # raises an attribute error Traceback (most recent call last): ... AttributeError: color The class ``A`` inherits acquisition behavior from ``Acquisition.Implicit``. The object, ``a``, "has" the color of objects ``c`` and d when it is accessed through them, but it has no color by itself. The object ``a`` obtains attributes from its environment, where its environment is defined by the access path used to reach ``a``. Acquisition Wrappers ==================== When an object that supports acquisition is accessed through an extension class instance, a special object, called an acquisition wrapper, is returned. In the example above, the expression ``c.a`` returns an acquisition wrapper that contains references to both ``c`` and ``a``. It is this wrapper that performs attribute lookup in ``c`` when an attribute cannot be found in ``a``. Acquisition wrappers provide access to the wrapped objects through the attributes ``aq_parent``, ``aq_self``, ``aq_base``. Continue the example from above:: >>> c.a.aq_parent is c True >>> c.a.aq_self is a True Explicit and Implicit Acquisition ================================= Two styles of acquisition are supported: implicit and explicit acquisition. Implicit acquisition -------------------- Implicit acquisition is so named because it searches for attributes from the environment automatically whenever an attribute cannot be obtained directly from an object or through inheritance. An attribute can be implicitly acquired if its name does not begin with an underscore. To support implicit acquisition, your class should inherit from the mix-in class ``Acquisition.Implicit``. Explicit Acquisition -------------------- When explicit acquisition is used, attributes are not automatically obtained from the environment. Instead, the method aq_acquire must be used. For example:: >>> print c.a.aq_acquire('color') red To support explicit acquisition, your class should inherit from the mix-in class ``Acquisition.Explicit``. Controlling Acquisition ----------------------- A class (or instance) can provide attribute by attribute control over acquisition. Your should subclass from ``Acquisition.Explicit``, and set all attributes that should be acquired to the special value ``Acquisition.Acquired``. Setting an attribute to this value also allows inherited attributes to be overridden with acquired ones. For example:: >>> class C(Acquisition.Explicit): ... id=1 ... secret=2 ... color=Acquisition.Acquired ... __roles__=Acquisition.Acquired The only attributes that are automatically acquired from containing objects are color, and ``__roles__``. Note that the ``__roles__`` attribute is acquired even though its name begins with an underscore. In fact, the special ``Acquisition.Acquired`` value can be used in ``Acquisition.Implicit`` objects to implicitly acquire selected objects that smell like private objects. Sometimes, you want to dynamically make an implicitly acquiring object acquire explicitly. You can do this by getting the object's aq_explicit attribute. This attribute provides the object with an explicit wrapper that replaces the original implicit wrapper. Filtered Acquisition ==================== The acquisition method, ``aq_acquire``, accepts two optional arguments. The first of the additional arguments is a "filtering" function that is used when considering whether to acquire an object. The second of the additional arguments is an object that is passed as extra data when calling the filtering function and which defaults to ``None``. The filter function is called with five arguments: * The object that the aq_acquire method was called on, * The object where an object was found, * The name of the object, as passed to aq_acquire, * The object found, and * The extra data passed to aq_acquire. If the filter returns a true object that the object found is returned, otherwise, the acquisition search continues. Here's an example:: >>> from Acquisition import Explicit >>> class HandyForTesting: ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return "%s(%s)" % (self.name, self.__class__.__name__) ... __repr__=__str__ ... >>> class E(Explicit, HandyForTesting): pass ... >>> class Nice(HandyForTesting): ... isNice= 1 ... def __str__(self): ... return HandyForTesting.__str__(self)+' and I am nice!' ... __repr__=__str__ ... >>> a = E('a') >>> a.b = E('b') >>> a.b.c = E('c') >>> a.p = Nice('spam') >>> a.b.p = E('p') >>> def find_nice(self, ancestor, name, object, extra): ... return hasattr(object,'isNice') and object.isNice >>> print a.b.c.aq_acquire('p', find_nice) spam(Nice) and I am nice! The filtered acquisition in the last line skips over the first attribute it finds with the name ``p``, because the attribute doesn't satisfy the condition given in the filter. Filtered acquisition is rarely used in Zope. Acquiring from Context ====================== Normally acquisition allows objects to acquire data from their containers. However an object can acquire from objects that aren't its containers. Most of the examples we've seen so far show establishing of an acquisition context using getattr semantics. For example, ``a.b`` is a reference to ``b`` in the context of ``a``. You can also manually set acquisition context using the ``__of__`` method. For example:: >>> from Acquisition import Implicit >>> class C(Implicit): pass ... >>> a = C() >>> b = C() >>> a.color = "red" >>> print b.__of__(a).color red In this case, ``a`` does not contain ``b``, but it is put in ``b``'s context using the ``__of__`` method. Here's another subtler example that shows how you can construct an acquisition context that includes non-container objects:: >>> from Acquisition import Implicit >>> class C(Implicit): ... def __init__(self, name): ... self.name = name >>> a = C("a") >>> a.b = C("b") >>> a.b.color = "red" >>> a.x = C("x") >>> print a.b.x.color red Even though ``b`` does not contain ``x``, ``x`` can acquire the color attribute from ``b``. This works because in this case, ``x`` is accessed in the context of ``b`` even though it is not contained by ``b``. Here acquisition context is defined by the objects used to access another object. Containment Before Context ========================== If in the example above suppose both a and b have an color attribute:: >>> a = C("a") >>> a.color = "green" >>> a.b = C("b") >>> a.b.color = "red" >>> a.x = C("x") >>> print a.b.x.color green Why does ``a.b.x.color`` acquire color from ``a`` and not from ``b``? The answer is that an object acquires from its containers before non-containers in its context. To see why consider this example in terms of expressions using the ``__of__`` method:: a.x -> x.__of__(a) a.b -> b.__of__(a) a.b.x -> x.__of__(a).__of__(b.__of__(a)) Keep in mind that attribute lookup in a wrapper is done by trying to look up the attribute in the wrapped object first and then in the parent object. So in the expressions above proceeds from left to right. The upshot of these rules is that attributes are looked up by containment before context. This rule holds true also for more complex examples. For example, ``a.b.c.d.e.f.g.attribute`` would search for attribute in ``g`` and all its containers first. (Containers are searched in order from the innermost parent to the outermost container.) If the attribute is not found in ``g`` or any of its containers, then the search moves to ``f`` and all its containers, and so on. Additional Attributes and Methods ================================= You can use the special method ``aq_inner`` to access an object wrapped only by containment. So in the example above, ``a.b.x.aq_inner`` is equivalent to ``a.x``. You can find out the acquisition context of an object using the aq_chain method like so: >>> [obj.name for obj in a.b.x.aq_chain] ['x', 'b', 'a'] You can find out if an object is in the containment context of another object using the ``aq_inContextOf`` method. For example: >>> a.b.aq_inContextOf(a) 1 .. Note: as of this writing the aq_inContextOf examples don't work the way they should be working. According to Jim, this is because aq_inContextOf works by comparing object pointer addresses, which (because they are actually different wrapper objects) doesn't give you the expected results. He acknowledges that this behavior is controversial, and says that there is a collector entry to change it so that you would get the answer you expect in the above. (We just need to get to it). Acquisition Module Functions ============================ In addition to using acquisition attributes and methods directly on objects you can use similar functions defined in the ``Acquisition`` module. These functions have the advantage that you don't need to check to make sure that the object has the method or attribute before calling it. ``aq_acquire(object, name [, filter, extra, explicit, default, containment])`` Acquires an object with the given name. This function can be used to explictly acquire when using explicit acquisition and to acquire names that wouldn't normally be acquired. The function accepts a number of optional arguments: ``filter`` A callable filter object that is used to decide if an object should be acquired. The filter is called with five arguments: * The object that the aq_acquire method was called on, * The object where an object was found, * The name of the object, as passed to aq_acquire, * The object found, and * The extra argument passed to aq_acquire. If the filter returns a true object that the object found is returned, otherwise, the acquisition search continues. ``extra`` Extra data to be passed as the last argument to the filter. ``explicit`` A flag (boolean value) indicating whether explicit acquisition should be used. The default value is true. If the flag is true, then acquisition will proceed regardless of whether wrappers encountered in the search of the acquisition hierarchy are explicit or implicit wrappers. If the flag is false, then parents of explicit wrappers are not searched. This argument is useful if you want to apply a filter without overriding explicit wrappers. ``default`` A default value to return if no value can be acquired. ``containment`` A flag indicating whether the search should be limited to the containment hierarchy. In addition, arguments can be provided as keywords. ``aq_base(object)`` Return the object with all wrapping removed. ``aq_chain(object [, containment])`` Return a list containing the object and it's acquisition parents. The optional argument, containment, controls whether the containment or access hierarchy is used. ``aq_get(object, name [, default, containment])`` Acquire an attribute, name. A default value can be provided, as can a flag that limits search to the containment hierarchy. ``aq_inner(object)`` Return the object with all but the innermost layer of wrapping removed. ``aq_parent(object)`` Return the acquisition parent of the object or None if the object is unwrapped. ``aq_self(object)`` Return the object with one layer of wrapping removed, unless the object is unwrapped, in which case the object is returned. In most cases it is more convenient to use these module functions instead of the acquisition attributes and methods directly. Acquisition and Methods ======================= Python methods of objects that support acquisition can use acquired attributes. When a Python method is called on an object that is wrapped by an acquisition wrapper, the wrapper is passed to the method as the first argument. This rule also applies to user-defined method types and to C methods defined in pure mix-in classes. Unfortunately, C methods defined in extension base classes that define their own data structures, cannot use aquired attributes at this time. This is because wrapper objects do not conform to the data structures expected by these methods. In practice, you will seldom find this a problem. Conclusion ========== Acquisition provides a powerful way to dynamically share information between objects. Zope 2 uses acquisition for a number of its key features including security, object publishing, and DTML variable lookup. Acquisition also provides an elegant solution to the problem of circular references for many classes of problems. While acquisition is powerful, you should take care when using acquisition in your applications. The details can get complex, especially with the differences between acquiring from context and acquiring from containment. Changelog ========= 2.13.8 (2011-06-11) ------------------- - Fixed a segfault on 64bit platforms when providing the `explicit` argument to the aq_acquire method of an Acquisition wrapper. Thx to LP #675064 for the hint to the solution. The code passed an int instead of a pointer into a function. 2.13.7 (2011-03-02) ------------------- - Fixed bug: When an object did not implement ``__unicode__``, calling ``unicode(wrapped)`` was calling ``__str__`` with an unwrapped ``self``. 2.13.6 (2011-02-19) ------------------- - Add ``aq_explicit`` to ``IAcquisitionWrapper``. - Fixed bug: ``unicode(wrapped)`` was not calling a ``__unicode__`` method on wrapped objects. 2.13.5 (2010-09-29) ------------------- - Fixed unit tests that failed on 64bit Python on Windows machines. 2.13.4 (2010-08-31) ------------------- - LP 623665: Fixed typo in Acquisition.h. 2.13.3 (2010-04-19) ------------------- - Use the doctest module from the standard library and no longer depend on zope.testing. 2.13.2 (2010-04-04) ------------------- - Give both wrapper classes a ``__getnewargs__`` method, which causes the ZODB optimization to fail and create persistent references using the ``_p_oid`` alone. This happens to be the persistent oid of the wrapped object. This lets these objects to be persisted correctly, even though they are passed to the ZODB in a wrapped state. - Added failing tests for http://dev.plone.org/plone/ticket/10318. This shows an edge-case where AQ wrappers can be pickled using the specific combination of cPickle, pickle protocol one and a custom Pickler class with an ``inst_persistent_id`` hook. Unfortunately this is the exact combination used by ZODB3. 2.13.1 (2010-02-23) ------------------- - Update to include ExtensionClass 2.13.0. - Fix the ``tp_name`` of the ImplicitAcquisitionWrapper and ExplicitAcquisitionWrapper to match their Python visible names and thus have a correct ``__name__``. - Expand the ``tp_name`` of our extension types to hold the fully qualified name. This ensures classes have their ``__module__`` set correctly. 2.13.0 (2010-02-14) ------------------- - Added support for method cache in Acquisition. Patch contributed by Yoshinori K. Okuji. See https://bugs.launchpad.net/zope2/+bug/486182. 2.12.4 (2009-10-29) ------------------- - Fix iteration proxying to pass `self` acquisition-wrapped into both `__iter__` as well as `__getitem__` (this fixes https://bugs.launchpad.net/zope2/+bug/360761). - Add tests for the __getslice__ proxying, including open-ended slicing. 2.12.3 (2009-08-08) ------------------- - More 64-bit fixes in Py_BuildValue calls. - More 64-bit issues fixed: Use correct integer size for slice operations. 2.12.2 (2009-08-02) ------------------- - Fixed 64-bit compatibility issues for Python 2.5.x / 2.6.x. See http://www.python.org/dev/peps/pep-0353/ for details. 2.12.1 (2009-04-15) ------------------- - Update for iteration proxying: The proxy for `__iter__` must not rely on the object to have an `__iter__` itself, but also support fall-back iteration via `__getitem__` (this fixes https://bugs.launchpad.net/zope2/+bug/360761). 2.12 (2009-01-25) ----------------- - Release as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Acquisition/pip-egg-info/0000755000175000017500000000000012214017433020205 5ustar arnauarnauzope2.13-2.13.21/source/Acquisition/pip-egg-info/Acquisition.egg-info/0000755000175000017500000000000012214017433024167 5ustar arnauarnauzope2.13-2.13.21/source/Acquisition/pip-egg-info/Acquisition.egg-info/PKG-INFO0000644000175000017500000005167112214017433025276 0ustar arnauarnauMetadata-Version: 1.0 Name: Acquisition Version: 2.13.8 Summary: Acquisition is a mechanism that allows objects to obtain attributes from the containment hierarchy they're in. Home-page: http://pypi.python.org/pypi/Acquisition Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: .. contents:: Introductory Example ==================== Zope implements acquisition with "Extension Class" mix-in classes. To use acquisition your classes must inherit from an acquisition base class. For example:: >>> import ExtensionClass, Acquisition >>> class C(ExtensionClass.Base): ... color='red' >>> class A(Acquisition.Implicit): ... def report(self): ... print self.color ... >>> a = A() >>> c = C() >>> c.a = a >>> c.a.report() red >>> d = C() >>> d.color = 'green' >>> d.a = a >>> d.a.report() green >>> a.report() # raises an attribute error Traceback (most recent call last): ... AttributeError: color The class ``A`` inherits acquisition behavior from ``Acquisition.Implicit``. The object, ``a``, "has" the color of objects ``c`` and d when it is accessed through them, but it has no color by itself. The object ``a`` obtains attributes from its environment, where its environment is defined by the access path used to reach ``a``. Acquisition Wrappers ==================== When an object that supports acquisition is accessed through an extension class instance, a special object, called an acquisition wrapper, is returned. In the example above, the expression ``c.a`` returns an acquisition wrapper that contains references to both ``c`` and ``a``. It is this wrapper that performs attribute lookup in ``c`` when an attribute cannot be found in ``a``. Acquisition wrappers provide access to the wrapped objects through the attributes ``aq_parent``, ``aq_self``, ``aq_base``. Continue the example from above:: >>> c.a.aq_parent is c True >>> c.a.aq_self is a True Explicit and Implicit Acquisition ================================= Two styles of acquisition are supported: implicit and explicit acquisition. Implicit acquisition -------------------- Implicit acquisition is so named because it searches for attributes from the environment automatically whenever an attribute cannot be obtained directly from an object or through inheritance. An attribute can be implicitly acquired if its name does not begin with an underscore. To support implicit acquisition, your class should inherit from the mix-in class ``Acquisition.Implicit``. Explicit Acquisition -------------------- When explicit acquisition is used, attributes are not automatically obtained from the environment. Instead, the method aq_acquire must be used. For example:: >>> print c.a.aq_acquire('color') red To support explicit acquisition, your class should inherit from the mix-in class ``Acquisition.Explicit``. Controlling Acquisition ----------------------- A class (or instance) can provide attribute by attribute control over acquisition. Your should subclass from ``Acquisition.Explicit``, and set all attributes that should be acquired to the special value ``Acquisition.Acquired``. Setting an attribute to this value also allows inherited attributes to be overridden with acquired ones. For example:: >>> class C(Acquisition.Explicit): ... id=1 ... secret=2 ... color=Acquisition.Acquired ... __roles__=Acquisition.Acquired The only attributes that are automatically acquired from containing objects are color, and ``__roles__``. Note that the ``__roles__`` attribute is acquired even though its name begins with an underscore. In fact, the special ``Acquisition.Acquired`` value can be used in ``Acquisition.Implicit`` objects to implicitly acquire selected objects that smell like private objects. Sometimes, you want to dynamically make an implicitly acquiring object acquire explicitly. You can do this by getting the object's aq_explicit attribute. This attribute provides the object with an explicit wrapper that replaces the original implicit wrapper. Filtered Acquisition ==================== The acquisition method, ``aq_acquire``, accepts two optional arguments. The first of the additional arguments is a "filtering" function that is used when considering whether to acquire an object. The second of the additional arguments is an object that is passed as extra data when calling the filtering function and which defaults to ``None``. The filter function is called with five arguments: * The object that the aq_acquire method was called on, * The object where an object was found, * The name of the object, as passed to aq_acquire, * The object found, and * The extra data passed to aq_acquire. If the filter returns a true object that the object found is returned, otherwise, the acquisition search continues. Here's an example:: >>> from Acquisition import Explicit >>> class HandyForTesting: ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return "%s(%s)" % (self.name, self.__class__.__name__) ... __repr__=__str__ ... >>> class E(Explicit, HandyForTesting): pass ... >>> class Nice(HandyForTesting): ... isNice= 1 ... def __str__(self): ... return HandyForTesting.__str__(self)+' and I am nice!' ... __repr__=__str__ ... >>> a = E('a') >>> a.b = E('b') >>> a.b.c = E('c') >>> a.p = Nice('spam') >>> a.b.p = E('p') >>> def find_nice(self, ancestor, name, object, extra): ... return hasattr(object,'isNice') and object.isNice >>> print a.b.c.aq_acquire('p', find_nice) spam(Nice) and I am nice! The filtered acquisition in the last line skips over the first attribute it finds with the name ``p``, because the attribute doesn't satisfy the condition given in the filter. Filtered acquisition is rarely used in Zope. Acquiring from Context ====================== Normally acquisition allows objects to acquire data from their containers. However an object can acquire from objects that aren't its containers. Most of the examples we've seen so far show establishing of an acquisition context using getattr semantics. For example, ``a.b`` is a reference to ``b`` in the context of ``a``. You can also manually set acquisition context using the ``__of__`` method. For example:: >>> from Acquisition import Implicit >>> class C(Implicit): pass ... >>> a = C() >>> b = C() >>> a.color = "red" >>> print b.__of__(a).color red In this case, ``a`` does not contain ``b``, but it is put in ``b``'s context using the ``__of__`` method. Here's another subtler example that shows how you can construct an acquisition context that includes non-container objects:: >>> from Acquisition import Implicit >>> class C(Implicit): ... def __init__(self, name): ... self.name = name >>> a = C("a") >>> a.b = C("b") >>> a.b.color = "red" >>> a.x = C("x") >>> print a.b.x.color red Even though ``b`` does not contain ``x``, ``x`` can acquire the color attribute from ``b``. This works because in this case, ``x`` is accessed in the context of ``b`` even though it is not contained by ``b``. Here acquisition context is defined by the objects used to access another object. Containment Before Context ========================== If in the example above suppose both a and b have an color attribute:: >>> a = C("a") >>> a.color = "green" >>> a.b = C("b") >>> a.b.color = "red" >>> a.x = C("x") >>> print a.b.x.color green Why does ``a.b.x.color`` acquire color from ``a`` and not from ``b``? The answer is that an object acquires from its containers before non-containers in its context. To see why consider this example in terms of expressions using the ``__of__`` method:: a.x -> x.__of__(a) a.b -> b.__of__(a) a.b.x -> x.__of__(a).__of__(b.__of__(a)) Keep in mind that attribute lookup in a wrapper is done by trying to look up the attribute in the wrapped object first and then in the parent object. So in the expressions above proceeds from left to right. The upshot of these rules is that attributes are looked up by containment before context. This rule holds true also for more complex examples. For example, ``a.b.c.d.e.f.g.attribute`` would search for attribute in ``g`` and all its containers first. (Containers are searched in order from the innermost parent to the outermost container.) If the attribute is not found in ``g`` or any of its containers, then the search moves to ``f`` and all its containers, and so on. Additional Attributes and Methods ================================= You can use the special method ``aq_inner`` to access an object wrapped only by containment. So in the example above, ``a.b.x.aq_inner`` is equivalent to ``a.x``. You can find out the acquisition context of an object using the aq_chain method like so: >>> [obj.name for obj in a.b.x.aq_chain] ['x', 'b', 'a'] You can find out if an object is in the containment context of another object using the ``aq_inContextOf`` method. For example: >>> a.b.aq_inContextOf(a) 1 .. Note: as of this writing the aq_inContextOf examples don't work the way they should be working. According to Jim, this is because aq_inContextOf works by comparing object pointer addresses, which (because they are actually different wrapper objects) doesn't give you the expected results. He acknowledges that this behavior is controversial, and says that there is a collector entry to change it so that you would get the answer you expect in the above. (We just need to get to it). Acquisition Module Functions ============================ In addition to using acquisition attributes and methods directly on objects you can use similar functions defined in the ``Acquisition`` module. These functions have the advantage that you don't need to check to make sure that the object has the method or attribute before calling it. ``aq_acquire(object, name [, filter, extra, explicit, default, containment])`` Acquires an object with the given name. This function can be used to explictly acquire when using explicit acquisition and to acquire names that wouldn't normally be acquired. The function accepts a number of optional arguments: ``filter`` A callable filter object that is used to decide if an object should be acquired. The filter is called with five arguments: * The object that the aq_acquire method was called on, * The object where an object was found, * The name of the object, as passed to aq_acquire, * The object found, and * The extra argument passed to aq_acquire. If the filter returns a true object that the object found is returned, otherwise, the acquisition search continues. ``extra`` Extra data to be passed as the last argument to the filter. ``explicit`` A flag (boolean value) indicating whether explicit acquisition should be used. The default value is true. If the flag is true, then acquisition will proceed regardless of whether wrappers encountered in the search of the acquisition hierarchy are explicit or implicit wrappers. If the flag is false, then parents of explicit wrappers are not searched. This argument is useful if you want to apply a filter without overriding explicit wrappers. ``default`` A default value to return if no value can be acquired. ``containment`` A flag indicating whether the search should be limited to the containment hierarchy. In addition, arguments can be provided as keywords. ``aq_base(object)`` Return the object with all wrapping removed. ``aq_chain(object [, containment])`` Return a list containing the object and it's acquisition parents. The optional argument, containment, controls whether the containment or access hierarchy is used. ``aq_get(object, name [, default, containment])`` Acquire an attribute, name. A default value can be provided, as can a flag that limits search to the containment hierarchy. ``aq_inner(object)`` Return the object with all but the innermost layer of wrapping removed. ``aq_parent(object)`` Return the acquisition parent of the object or None if the object is unwrapped. ``aq_self(object)`` Return the object with one layer of wrapping removed, unless the object is unwrapped, in which case the object is returned. In most cases it is more convenient to use these module functions instead of the acquisition attributes and methods directly. Acquisition and Methods ======================= Python methods of objects that support acquisition can use acquired attributes. When a Python method is called on an object that is wrapped by an acquisition wrapper, the wrapper is passed to the method as the first argument. This rule also applies to user-defined method types and to C methods defined in pure mix-in classes. Unfortunately, C methods defined in extension base classes that define their own data structures, cannot use aquired attributes at this time. This is because wrapper objects do not conform to the data structures expected by these methods. In practice, you will seldom find this a problem. Conclusion ========== Acquisition provides a powerful way to dynamically share information between objects. Zope 2 uses acquisition for a number of its key features including security, object publishing, and DTML variable lookup. Acquisition also provides an elegant solution to the problem of circular references for many classes of problems. While acquisition is powerful, you should take care when using acquisition in your applications. The details can get complex, especially with the differences between acquiring from context and acquiring from containment. Changelog ========= 2.13.8 (2011-06-11) ------------------- - Fixed a segfault on 64bit platforms when providing the `explicit` argument to the aq_acquire method of an Acquisition wrapper. Thx to LP #675064 for the hint to the solution. The code passed an int instead of a pointer into a function. 2.13.7 (2011-03-02) ------------------- - Fixed bug: When an object did not implement ``__unicode__``, calling ``unicode(wrapped)`` was calling ``__str__`` with an unwrapped ``self``. 2.13.6 (2011-02-19) ------------------- - Add ``aq_explicit`` to ``IAcquisitionWrapper``. - Fixed bug: ``unicode(wrapped)`` was not calling a ``__unicode__`` method on wrapped objects. 2.13.5 (2010-09-29) ------------------- - Fixed unit tests that failed on 64bit Python on Windows machines. 2.13.4 (2010-08-31) ------------------- - LP 623665: Fixed typo in Acquisition.h. 2.13.3 (2010-04-19) ------------------- - Use the doctest module from the standard library and no longer depend on zope.testing. 2.13.2 (2010-04-04) ------------------- - Give both wrapper classes a ``__getnewargs__`` method, which causes the ZODB optimization to fail and create persistent references using the ``_p_oid`` alone. This happens to be the persistent oid of the wrapped object. This lets these objects to be persisted correctly, even though they are passed to the ZODB in a wrapped state. - Added failing tests for http://dev.plone.org/plone/ticket/10318. This shows an edge-case where AQ wrappers can be pickled using the specific combination of cPickle, pickle protocol one and a custom Pickler class with an ``inst_persistent_id`` hook. Unfortunately this is the exact combination used by ZODB3. 2.13.1 (2010-02-23) ------------------- - Update to include ExtensionClass 2.13.0. - Fix the ``tp_name`` of the ImplicitAcquisitionWrapper and ExplicitAcquisitionWrapper to match their Python visible names and thus have a correct ``__name__``. - Expand the ``tp_name`` of our extension types to hold the fully qualified name. This ensures classes have their ``__module__`` set correctly. 2.13.0 (2010-02-14) ------------------- - Added support for method cache in Acquisition. Patch contributed by Yoshinori K. Okuji. See https://bugs.launchpad.net/zope2/+bug/486182. 2.12.4 (2009-10-29) ------------------- - Fix iteration proxying to pass `self` acquisition-wrapped into both `__iter__` as well as `__getitem__` (this fixes https://bugs.launchpad.net/zope2/+bug/360761). - Add tests for the __getslice__ proxying, including open-ended slicing. 2.12.3 (2009-08-08) ------------------- - More 64-bit fixes in Py_BuildValue calls. - More 64-bit issues fixed: Use correct integer size for slice operations. 2.12.2 (2009-08-02) ------------------- - Fixed 64-bit compatibility issues for Python 2.5.x / 2.6.x. See http://www.python.org/dev/peps/pep-0353/ for details. 2.12.1 (2009-04-15) ------------------- - Update for iteration proxying: The proxy for `__iter__` must not rely on the object to have an `__iter__` itself, but also support fall-back iteration via `__getitem__` (this fixes https://bugs.launchpad.net/zope2/+bug/360761). 2.12 (2009-01-25) ----------------- - Release as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Acquisition/pip-egg-info/Acquisition.egg-info/dependency_links.txt0000644000175000017500000000000112214017433030235 0ustar arnauarnau zope2.13-2.13.21/source/Acquisition/pip-egg-info/Acquisition.egg-info/requires.txt0000644000175000017500000000003512214017433026565 0ustar arnauarnauExtensionClass zope.interfacezope2.13-2.13.21/source/Acquisition/pip-egg-info/Acquisition.egg-info/top_level.txt0000644000175000017500000000001412214017433026714 0ustar arnauarnauAcquisition zope2.13-2.13.21/source/Acquisition/pip-egg-info/Acquisition.egg-info/SOURCES.txt0000644000175000017500000000064412214017433026057 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/Acquisition.egg-info/PKG-INFO pip-egg-info/Acquisition.egg-info/SOURCES.txt pip-egg-info/Acquisition.egg-info/dependency_links.txt pip-egg-info/Acquisition.egg-info/not-zip-safe pip-egg-info/Acquisition.egg-info/requires.txt pip-egg-info/Acquisition.egg-info/top_level.txt src/Acquisition/_Acquisition.c src/Acquisition/__init__.py src/Acquisition/interfaces.py src/Acquisition/tests.pyzope2.13-2.13.21/source/Acquisition/pip-egg-info/Acquisition.egg-info/not-zip-safe0000644000175000017500000000000112214017433026415 0ustar arnauarnau zope2.13-2.13.21/source/Acquisition/LICENSE.txt0000644000175000017500000000402612214017433017551 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/Acquisition/README.txt0000644000175000017500000000005412214017433017421 0ustar arnauarnauPlease refer to src/Acquisition/README.txt. zope2.13-2.13.21/source/Acquisition/setup.cfg0000644000175000017500000000007312214017433017545 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/Acquisition/COPYRIGHT.txt0000644000175000017500000000004012214017433020027 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/Acquisition/include/0000755000175000017500000000000012214017433017347 5ustar arnauarnauzope2.13-2.13.21/source/Acquisition/include/ExtensionClass/0000755000175000017500000000000012214017433022311 5ustar arnauarnauzope2.13-2.13.21/source/Acquisition/include/ExtensionClass/pickle/0000755000175000017500000000000012214017433023560 5ustar arnauarnauzope2.13-2.13.21/source/Acquisition/include/ExtensionClass/pickle/pickle.c0000644000175000017500000002202312214017433025172 0ustar arnauarnau/* Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. */ /* Reusable pickle support code This is "includeware", meant to be used through a C include */ /* It's a dang shame we can't inherit __get/setstate__ from object :( */ static PyObject *str__slotnames__, *copy_reg_slotnames, *__newobj__; static PyObject *str__getnewargs__, *str__getstate__; #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) typedef int Py_ssize_t; #define PY_SSIZE_T_MAX INT_MAX #define PY_SSIZE_T_MIN INT_MIN #endif static int pickle_setup(void) { PyObject *copy_reg; int r = -1; #define DEFINE_STRING(S) \ if(! (str ## S = PyString_FromString(# S))) return -1 DEFINE_STRING(__slotnames__); DEFINE_STRING(__getnewargs__); DEFINE_STRING(__getstate__); #undef DEFINE_STRING copy_reg = PyImport_ImportModule("copy_reg"); if (copy_reg == NULL) return -1; copy_reg_slotnames = PyObject_GetAttrString(copy_reg, "_slotnames"); if (copy_reg_slotnames == NULL) goto end; __newobj__ = PyObject_GetAttrString(copy_reg, "__newobj__"); if (__newobj__ == NULL) goto end; r = 0; end: Py_DECREF(copy_reg); return r; } static PyObject * pickle_slotnames(PyTypeObject *cls) { PyObject *slotnames; slotnames = PyDict_GetItem(cls->tp_dict, str__slotnames__); if (slotnames != NULL) { Py_INCREF(slotnames); return slotnames; } slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames, (PyObject*)cls, NULL); if (slotnames != NULL && slotnames != Py_None && ! PyList_Check(slotnames)) { PyErr_SetString(PyExc_TypeError, "copy_reg._slotnames didn't return a list or None"); Py_DECREF(slotnames); slotnames = NULL; } return slotnames; } static PyObject * pickle_copy_dict(PyObject *state) { PyObject *copy, *key, *value; char *ckey; Py_ssize_t pos = 0; Py_ssize_t nr; copy = PyDict_New(); if (copy == NULL) return NULL; if (state == NULL) return copy; while ((nr = PyDict_Next(state, &pos, &key, &value))) { if (nr < 0) goto err; if (key && PyString_Check(key)) { ckey = PyString_AS_STRING(key); if (*ckey == '_' && (ckey[1] == 'v' || ckey[1] == 'p') && ckey[2] == '_') /* skip volatile and persistent */ continue; } if (key != NULL && value != NULL && (PyObject_SetItem(copy, key, value) < 0) ) goto err; } return copy; err: Py_DECREF(copy); return NULL; } static char pickle___getstate__doc[] = "Get the object serialization state\n" "\n" "If the object has no assigned slots and has no instance dictionary, then \n" "None is returned.\n" "\n" "If the object has no assigned slots and has an instance dictionary, then \n" "the a copy of the instance dictionary is returned. The copy has any items \n" "with names starting with '_v_' or '_p_' ommitted.\n" "\n" "If the object has assigned slots, then a two-element tuple is returned. \n" "The first element is either None or a copy of the instance dictionary, \n" "as described above. The second element is a dictionary with items \n" "for each of the assigned slots.\n" ; static PyObject * pickle___getstate__(PyObject *self) { PyObject *slotnames=NULL, *slots=NULL, *state=NULL; PyObject **dictp; int n=0; slotnames = pickle_slotnames(self->ob_type); if (slotnames == NULL) return NULL; dictp = _PyObject_GetDictPtr(self); if (dictp) state = pickle_copy_dict(*dictp); else { state = Py_None; Py_INCREF(state); } if (slotnames != Py_None) { int i; slots = PyDict_New(); if (slots == NULL) goto end; for (i = 0; i < PyList_GET_SIZE(slotnames); i++) { PyObject *name, *value; char *cname; name = PyList_GET_ITEM(slotnames, i); if (PyString_Check(name)) { cname = PyString_AS_STRING(name); if (*cname == '_' && (cname[1] == 'v' || cname[1] == 'p') && cname[2] == '_') /* skip volatile and persistent */ continue; } value = PyObject_GetAttr(self, name); if (value == NULL) PyErr_Clear(); else { int err = PyDict_SetItem(slots, name, value); Py_DECREF(value); if (err) goto end; n++; } } } if (n) state = Py_BuildValue("(NO)", state, slots); end: Py_XDECREF(slotnames); Py_XDECREF(slots); return state; } static int pickle_setattrs_from_dict(PyObject *self, PyObject *dict) { PyObject *key, *value; Py_ssize_t pos = 0; if (! PyDict_Check(dict)) { PyErr_SetString(PyExc_TypeError, "Expected dictionary"); return -1; } while (PyDict_Next(dict, &pos, &key, &value)) { if (key != NULL && value != NULL && (PyObject_SetAttr(self, key, value) < 0) ) return -1; } return 0; } static char pickle___setstate__doc[] = "Set the object serialization state\n" "\n" "The state should be in one of 3 forms:\n" "\n" "- None\n" "\n" " Ignored\n" "\n" "- A dictionary\n" "\n" " In this case, the object's instance dictionary will be cleared and \n" " updated with the new state.\n" "\n" "- A two-tuple with a string as the first element. \n" "\n" " In this case, the method named by the string in the first element will be\n" " called with the second element.\n" "\n" " This form supports migration of data formats.\n" "\n" "- A two-tuple with None or a Dictionary as the first element and\n" " with a dictionary as the second element.\n" "\n" " If the first element is not None, then the object's instance dictionary \n" " will be cleared and updated with the value.\n" "\n" " The items in the second element will be assigned as attributes.\n" ; static PyObject * pickle___setstate__(PyObject *self, PyObject *state) { PyObject *slots=NULL; if (PyTuple_Check(state)) { if (! PyArg_ParseTuple(state, "OO", &state, &slots)) return NULL; } if (state != Py_None) { PyObject **dict; dict = _PyObject_GetDictPtr(self); if (dict) { if (*dict == NULL) { *dict = PyDict_New(); if (*dict == NULL) return NULL; } } if (*dict != NULL) { PyDict_Clear(*dict); if (PyDict_Update(*dict, state) < 0) return NULL; } else if (pickle_setattrs_from_dict(self, state) < 0) return NULL; } if (slots != NULL && pickle_setattrs_from_dict(self, slots) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static char pickle___getnewargs__doc[] = "Get arguments to be passed to __new__\n" ; static PyObject * pickle___getnewargs__(PyObject *self) { return PyTuple_New(0); } static char pickle___reduce__doc[] = "Reduce an object to contituent parts for serialization\n" ; static PyObject * pickle___reduce__(PyObject *self) { PyObject *args=NULL, *bargs=0, *state=NULL; int l, i; /* we no longer require '__getnewargs__' to be defined but use it if it is */ PyObject *getnewargs=NULL; getnewargs = PyObject_GetAttr(self, str__getnewargs__); if (getnewargs) bargs = PyEval_CallObject(getnewargs, (PyObject *)NULL); else { PyErr_Clear(); bargs = PyTuple_New(0); } l = PyTuple_Size(bargs); if (l < 0) goto end; args = PyTuple_New(l+1); if (args == NULL) goto end; Py_INCREF(self->ob_type); PyTuple_SET_ITEM(args, 0, (PyObject*)(self->ob_type)); for (i = 0; i < l; i++) { Py_INCREF(PyTuple_GET_ITEM(bargs, i)); PyTuple_SET_ITEM(args, i+1, PyTuple_GET_ITEM(bargs, i)); } state = PyObject_CallMethodObjArgs(self, str__getstate__, NULL); if (state == NULL) goto end; state = Py_BuildValue("(OON)", __newobj__, args, state); end: Py_XDECREF(bargs); Py_XDECREF(args); Py_XDECREF(getnewargs); return state; } #define PICKLE_GETSTATE_DEF \ {"__getstate__", (PyCFunction)pickle___getstate__, METH_NOARGS, \ pickle___getstate__doc}, #define PICKLE_SETSTATE_DEF \ {"__setstate__", (PyCFunction)pickle___setstate__, METH_O, \ pickle___setstate__doc}, #define PICKLE_GETNEWARGS_DEF #define PICKLE_REDUCE_DEF \ {"__reduce__", (PyCFunction)pickle___reduce__, METH_NOARGS, \ pickle___reduce__doc}, #define PICKLE_METHODS PICKLE_GETSTATE_DEF PICKLE_SETSTATE_DEF \ PICKLE_GETNEWARGS_DEF PICKLE_REDUCE_DEF zope2.13-2.13.21/source/Acquisition/include/ExtensionClass/_ExtensionClass.c0000644000175000017500000006076512214017433025574 0ustar arnauarnau/* Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. */ static char _extensionclass_module_documentation[] = "ExtensionClass\n" "\n" "$Id: _ExtensionClass.c 109054 2010-02-14 21:35:34Z hannosch $\n" ; #include "ExtensionClass/ExtensionClass.h" #define EC PyTypeObject static PyObject *str__of__, *str__get__, *str__class_init__, *str__init__; static PyObject *str__bases__, *str__mro__, *str__new__; #define OBJECT(O) ((PyObject *)(O)) #define TYPE(O) ((PyTypeObject *)(O)) static PyTypeObject ExtensionClassType; static PyTypeObject BaseType; static PyObject * of_get(PyObject *self, PyObject *inst, PyObject *cls) { /* Descriptor slot function that calls __of__ */ if (inst && PyExtensionInstance_Check(inst)) return PyObject_CallMethodObjArgs(self, str__of__, inst, NULL); Py_INCREF(self); return self; } PyObject * Base_getattro(PyObject *obj, PyObject *name) { /* This is a modified copy of PyObject_GenericGetAttr. See the change note below. */ PyTypeObject *tp = obj->ob_type; PyObject *descr = NULL; PyObject *res = NULL; descrgetfunc f; long dictoffset; PyObject **dictptr; if (!PyString_Check(name)){ #ifdef Py_USING_UNICODE /* The Unicode to string conversion is done here because the existing tp_setattro slots expect a string object as name and we wouldn't want to break those. */ if (PyUnicode_Check(name)) { name = PyUnicode_AsEncodedString(name, NULL, NULL); if (name == NULL) return NULL; } else #endif { PyErr_SetString(PyExc_TypeError, "attribute name must be string"); return NULL; } } else Py_INCREF(name); if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) goto done; } #if !defined(Py_TPFLAGS_HAVE_VERSION_TAG) /* Inline _PyType_Lookup */ /* this is not quite _PyType_Lookup anymore */ { int i, n; PyObject *mro, *base, *dict; /* Look in tp_dict of types in MRO */ mro = tp->tp_mro; assert(mro != NULL); assert(PyTuple_Check(mro)); n = PyTuple_GET_SIZE(mro); for (i = 0; i < n; i++) { base = PyTuple_GET_ITEM(mro, i); if (PyClass_Check(base)) dict = ((PyClassObject *)base)->cl_dict; else { assert(PyType_Check(base)); dict = ((PyTypeObject *)base)->tp_dict; } assert(dict && PyDict_Check(dict)); descr = PyDict_GetItem(dict, name); if (descr != NULL) break; } } #else descr = _PyType_Lookup(tp, name); #endif Py_XINCREF(descr); f = NULL; if (descr != NULL && PyType_HasFeature(descr->ob_type, Py_TPFLAGS_HAVE_CLASS)) { f = descr->ob_type->tp_descr_get; if (f != NULL && PyDescr_IsData(descr)) { res = f(descr, obj, (PyObject *)obj->ob_type); Py_DECREF(descr); goto done; } } /* Inline _PyObject_GetDictPtr */ dictoffset = tp->tp_dictoffset; if (dictoffset != 0) { PyObject *dict; if (dictoffset < 0) { int tsize; size_t size; tsize = ((PyVarObject *)obj)->ob_size; if (tsize < 0) tsize = -tsize; size = _PyObject_VAR_SIZE(tp, tsize); dictoffset += (long)size; assert(dictoffset > 0); assert(dictoffset % SIZEOF_VOID_P == 0); } dictptr = (PyObject **) ((char *)obj + dictoffset); dict = *dictptr; if (dict != NULL) { Py_INCREF(dict); res = PyDict_GetItem(dict, name); if (res != NULL) { Py_INCREF(res); Py_XDECREF(descr); Py_DECREF(dict); /* CHANGED! If the tp_descr_get of res is of_get, then call it. */ if (PyObject_TypeCheck(res->ob_type, &ExtensionClassType) && res->ob_type->tp_descr_get != NULL) { PyObject *tres; tres = res->ob_type->tp_descr_get( res, obj, OBJECT(obj->ob_type)); Py_DECREF(res); res = tres; } goto done; } Py_DECREF(dict); } } if (f != NULL) { res = f(descr, obj, (PyObject *)obj->ob_type); Py_DECREF(descr); goto done; } if (descr != NULL) { res = descr; /* descr was already increfed above */ goto done; } /* CHANGED: Just use the name. Don't format. */ PyErr_SetObject(PyExc_AttributeError, name); done: Py_DECREF(name); return res; } #include "pickle/pickle.c" static struct PyMethodDef Base_methods[] = { PICKLE_METHODS {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static EC BaseType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "Base", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* tp_getattro */ (getattrofunc)Base_getattro, 0, 0, (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif ), "Standard ExtensionClass base type", 0, 0, 0, 0, 0, 0, Base_methods, }; static EC NoInstanceDictionaryBaseType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "NoInstanceDictionaryBase", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif ), "Base types for subclasses without instance dictionaries", }; static PyObject * EC_new(PyTypeObject *self, PyObject *args, PyObject *kw) { PyObject *name, *bases=NULL, *dict=NULL; PyObject *new_bases=NULL, *new_args, *result; int have_base = 0, i; if (kw && PyObject_IsTrue(kw)) { PyErr_SetString(PyExc_TypeError, "Keyword arguments are not supported"); return NULL; } if (!PyArg_ParseTuple(args, "O|O!O!", &name, &PyTuple_Type, &bases, &PyDict_Type, &dict)) return NULL; /* Make sure Base is in bases */ if (bases) { for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { if (PyObject_TypeCheck(PyTuple_GET_ITEM(bases, i), &ExtensionClassType)) { have_base = 1; break; } } if (! have_base) { new_bases = PyTuple_New(PyTuple_GET_SIZE(bases) + 1); if (new_bases == NULL) return NULL; for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { Py_XINCREF(PyTuple_GET_ITEM(bases, i)); PyTuple_SET_ITEM(new_bases, i, PyTuple_GET_ITEM(bases, i)); } Py_INCREF(OBJECT(&BaseType)); PyTuple_SET_ITEM(new_bases, PyTuple_GET_SIZE(bases), OBJECT(&BaseType)); } } else { new_bases = Py_BuildValue("(O)", &BaseType); if (new_bases == NULL) return NULL; } if (new_bases) { if (dict) new_args = Py_BuildValue("OOO", name, new_bases, dict); else new_args = Py_BuildValue("OO", name, new_bases); Py_DECREF(new_bases); if (new_args == NULL) return NULL; result = PyType_Type.tp_new(self, new_args, kw); Py_DECREF(new_args); } else { result = PyType_Type.tp_new(self, args, kw); /* We didn't have to add Base, so maybe NoInstanceDictionaryBase is in the bases. We need to check if it was. If it was, we need to suppress instance dictionary support. */ for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { if ( PyObject_TypeCheck(PyTuple_GET_ITEM(bases, i), &ExtensionClassType) && PyType_IsSubtype(TYPE(PyTuple_GET_ITEM(bases, i)), &NoInstanceDictionaryBaseType) ) { TYPE(result)->tp_dictoffset = 0; break; } } } return result; } /* set up __get__, if necessary */ static int EC_init_of(PyTypeObject *self) { PyObject *__of__; __of__ = PyObject_GetAttr(OBJECT(self), str__of__); if (__of__) { Py_DECREF(__of__); if (self->tp_descr_get) { if (self->tp_descr_get != of_get) { PyErr_SetString(PyExc_TypeError, "Can't mix __of__ and descriptors"); return -1; } } else self->tp_descr_get = of_get; } else { PyErr_Clear(); if (self->tp_descr_get == of_get) self->tp_descr_get = NULL; } return 0; } static int EC_init(PyTypeObject *self, PyObject *args, PyObject *kw) { PyObject *__class_init__, *r; if (PyType_Type.tp_init(OBJECT(self), args, kw) < 0) return -1; if (self->tp_dict != NULL) { r = PyDict_GetItemString(self->tp_dict, "__doc__"); if ((r == Py_None) && (PyDict_DelItemString(self->tp_dict, "__doc__") < 0) ) return -1; } if (EC_init_of(self) < 0) return -1; /* Call __class_init__ */ __class_init__ = PyObject_GetAttr(OBJECT(self), str__class_init__); if (__class_init__ == NULL) { PyErr_Clear(); return 0; } if (! (PyMethod_Check(__class_init__) && PyMethod_GET_FUNCTION(__class_init__) ) ) { Py_DECREF(__class_init__); PyErr_SetString(PyExc_TypeError, "Invalid type for __class_init__"); return -1; } r = PyObject_CallFunctionObjArgs(PyMethod_GET_FUNCTION(__class_init__), OBJECT(self), NULL); Py_DECREF(__class_init__); if (! r) return -1; Py_DECREF(r); return 0; } static int EC_setattro(PyTypeObject *type, PyObject *name, PyObject *value) { /* We want to allow setting attributes of builti-in types, because EC did in the past and there's code that relies on it. We can't really set slots though, but I don't think we need to. There's no good way to spot slots. We could use a lame rule like names that begin and end with __s and have just 4 _s smell too much like slots. */ if (! (type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { char *cname; int l; cname = PyString_AsString(name); if (cname == NULL) return -1; l = PyString_GET_SIZE(name); if (l > 4 && cname[0] == '_' && cname[1] == '_' && cname[l-1] == '_' && cname[l-2] == '_' ) { char *c; c = strchr(cname+2, '_'); if (c != NULL && (c - cname) >= (l-2)) { PyErr_Format (PyExc_TypeError, "can't set attributes of built-in/extension type '%s' if the " "attribute name begins and ends with __ and contains only " "4 _ characters", type->tp_name ); return -1; } } if (PyObject_GenericSetAttr(OBJECT(type), name, value) < 0) return -1; } else if (PyType_Type.tp_setattro(OBJECT(type), name, value) < 0) return -1; #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(type); #endif return 0; } static PyObject * inheritedAttribute(PyTypeObject *self, PyObject *name) { int i; PyObject *d, *cls; for (i = 1; i < PyTuple_GET_SIZE(self->tp_mro); i++) { cls = PyTuple_GET_ITEM(self->tp_mro, i); if (PyType_Check(cls)) d = ((PyTypeObject *)cls)->tp_dict; else if (PyClass_Check(cls)) d = ((PyClassObject *)cls)->cl_dict; else /* Unrecognized thing, punt */ d = NULL; if ((d == NULL) || (PyDict_GetItem(d, name) == NULL)) continue; return PyObject_GetAttr(cls, name); } PyErr_SetObject(PyExc_AttributeError, name); return NULL; } static PyObject * __basicnew__(PyObject *self) { return PyObject_CallMethodObjArgs(self, str__new__, self, NULL); } static int append_new(PyObject *result, PyObject *v) { int contains; if (v == OBJECT(&BaseType) || v == OBJECT(&PyBaseObject_Type)) return 0; /* Don't add these until end */ contains = PySequence_Contains(result, v); if (contains != 0) return contains; return PyList_Append(result, v); } static int copy_mro(PyObject *mro, PyObject *result) { PyObject *base; int i, l; l = PyTuple_Size(mro); if (l < 0) return -1; for (i=0; i < l; i++) { base = PyTuple_GET_ITEM(mro, i); if (append_new(result, base) < 0) return -1; } return 0; } static int copy_classic(PyObject *base, PyObject *result) { PyObject *bases, *basebase; int i, l, err=-1; if (append_new(result, base) < 0) return -1; bases = PyObject_GetAttr(base, str__bases__); if (bases == NULL) return -1; l = PyTuple_Size(bases); if (l < 0) goto end; for (i=0; i < l; i++) { basebase = PyTuple_GET_ITEM(bases, i); if (copy_classic(basebase, result) < 0) goto end; } err = 0; end: Py_DECREF(bases); return err; } static PyObject * mro(PyTypeObject *self) { /* Compute an MRO for a class */ PyObject *result, *base, *basemro, *mro=NULL; int i, l, err; result = PyList_New(0); if (result == NULL) return NULL; if (PyList_Append(result, OBJECT(self)) < 0) goto end; l = PyTuple_Size(self->tp_bases); if (l < 0) goto end; for (i=0; i < l; i++) { base = PyTuple_GET_ITEM(self->tp_bases, i); if (base == NULL) continue; basemro = PyObject_GetAttr(base, str__mro__); if (basemro != NULL) { /* Type */ err = copy_mro(basemro, result); Py_DECREF(basemro); if (err < 0) goto end; } else { PyErr_Clear(); if (copy_classic(base, result) < 0) goto end; } } if (self != &BaseType && PyList_Append(result, OBJECT(&BaseType)) < 0) goto end; if (PyList_Append(result, OBJECT(&PyBaseObject_Type)) < 0) goto end; l = PyList_GET_SIZE(result); mro = PyTuple_New(l); if (mro == NULL) goto end; for (i=0; i < l; i++) { Py_INCREF(PyList_GET_ITEM(result, i)); PyTuple_SET_ITEM(mro, i, PyList_GET_ITEM(result, i)); } end: Py_DECREF(result); return mro; } static struct PyMethodDef EC_methods[] = { {"__basicnew__", (PyCFunction)__basicnew__, METH_NOARGS, "Create a new empty object"}, {"inheritedAttribute", (PyCFunction)inheritedAttribute, METH_O, "Look up an inherited attribute"}, {"mro", (PyCFunction)mro, METH_NOARGS, "Compute an mro using the 'encalsulated base' scheme"}, {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static PyTypeObject ExtensionClassType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "ExtensionClass", /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ (destructor)0, /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, /* tp_compare */ (cmpfunc)0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ (hashfunc)0, /* tp_call */ (ternaryfunc)0, /* tp_str */ (reprfunc)0, /* tp_getattro */ (getattrofunc)0, /* tp_setattro */ (setattrofunc)EC_setattro, /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif , /* tp_doc */ "Meta-class for extension classes", /* tp_traverse */ (traverseproc)0, /* tp_clear */ (inquiry)0, /* tp_richcompare */ (richcmpfunc)0, /* tp_weaklistoffset */ (long)0, /* tp_iter */ (getiterfunc)0, /* tp_iternext */ (iternextfunc)0, /* tp_methods */ EC_methods, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* internal use */ /* tp_descr_get */ (descrgetfunc)0, /* tp_descr_set */ (descrsetfunc)0, /* tp_dictoffset */ 0, /* tp_init */ (initproc)EC_init, /* tp_alloc */ (allocfunc)0, /* tp_new */ (newfunc)EC_new, /* tp_free */ 0, /* Low-level free-mem routine */ /* tp_is_gc */ (inquiry)0, /* For PyObject_IS_GC */ }; static PyObject * debug(PyObject *self, PyObject *o) { Py_INCREF(Py_None); return Py_None; } static PyObject * pmc_init_of(PyObject *self, PyObject *args) { PyObject *o; if (! PyArg_ParseTuple(args, "O!", (PyObject *)&ExtensionClassType, &o)) return NULL; if (EC_init_of((PyTypeObject *)o) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } /* List of methods defined in the module */ static struct PyMethodDef ec_methods[] = { {"debug", (PyCFunction)debug, METH_O, ""}, {"pmc_init_of", (PyCFunction)pmc_init_of, METH_VARARGS, "Initialize __get__ for classes that define __of__"}, {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static PyObject * EC_findiattrs_(PyObject *self, char *cname) { PyObject *name, *r; name = PyString_FromString(cname); if (name == NULL) return NULL; r = ECBaseType->tp_getattro(self, name); Py_DECREF(name); return r; } static PyObject * ec_new_for_custom_dealloc(PyTypeObject *type, PyObject *args, PyObject *kw) { /* This is for EC's that have deallocs. For these, we need to incref the type when we create an instance, because the deallocs will decref the type. */ PyObject *r; r = PyType_GenericNew(type, args, kw); if (r) { Py_INCREF(type); } return r; } static int ec_init(PyObject *self, PyObject *args, PyObject *kw) { PyObject *r, *__init__; __init__ = PyObject_GetAttr(self, str__init__); if (__init__ == NULL) return -1; r = PyObject_Call(__init__, args, kw); Py_DECREF(__init__); if (r == NULL) return -1; Py_DECREF(r); return 0; } static int PyExtensionClass_Export_(PyObject *dict, char *name, PyTypeObject *typ) { long ecflags = 0; PyMethodDef *pure_methods = NULL, *mdef = NULL; PyObject *m; if (typ->tp_flags == 0) { /* Old-style EC */ if (typ->tp_traverse) { /* ExtensionClasses stick there methods in the tp_traverse slot */ mdef = (PyMethodDef *)typ->tp_traverse; if (typ->tp_basicsize <= sizeof(_emptyobject)) /* Pure mixin. We want rebindable methods */ pure_methods = mdef; else typ->tp_methods = mdef; typ->tp_traverse = NULL; /* Look for __init__ method */ for (; mdef->ml_name; mdef++) { if (strcmp(mdef->ml_name, "__init__") == 0) { /* we have an old-style __init__, install a special slot */ typ->tp_init = ec_init; break; } } } if (typ->tp_clear) { /* ExtensionClasses stick there flags in the tp_clear slot */ ecflags = (long)(typ->tp_clear); /* Some old-style flags were set */ if ((ecflags & EXTENSIONCLASS_BINDABLE_FLAG) && typ->tp_descr_get == NULL) /* We have __of__-style binding */ typ->tp_descr_get = of_get; } typ->tp_clear = NULL; typ->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; if (typ->tp_dealloc != NULL) typ->tp_new = ec_new_for_custom_dealloc; } typ->ob_type = ECExtensionClassType; if (ecflags & EXTENSIONCLASS_NOINSTDICT_FLAG) typ->tp_base = &NoInstanceDictionaryBaseType; else typ->tp_base = &BaseType; if (typ->tp_new == NULL) typ->tp_new = PyType_GenericNew; if (PyType_Ready(typ) < 0) return -1; if (pure_methods) { /* We had pure methods. We want to be able to rebind these, so we'll make them ordinary method wrappers around method descrs */ for (; pure_methods->ml_name; pure_methods++) { m = PyDescr_NewMethod(ECBaseType, pure_methods); if (! m) return -1; m = PyMethod_New((PyObject *)m, NULL, (PyObject *)ECBaseType); if (! m) return -1; if (PyDict_SetItemString(typ->tp_dict, pure_methods->ml_name, m) < 0) return -1; } #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(typ); #endif } else if (mdef && mdef->ml_name) { /* Blast, we have to stick __init__ in the dict ourselves because PyType_Ready probably stuck a wrapper for ec_init in instead. */ m = PyDescr_NewMethod(typ, mdef); if (! m) return -1; if (PyDict_SetItemString(typ->tp_dict, mdef->ml_name, m) < 0) return -1; #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(typ); #endif } if (PyMapping_SetItemString(dict, name, (PyObject*)typ) < 0) return -1; return 0; } PyObject * PyECMethod_New_(PyObject *callable, PyObject *inst) { if (! PyExtensionInstance_Check(inst)) { PyErr_SetString(PyExc_TypeError, "Can't bind non-ExtensionClass instance."); return NULL; } if (PyMethod_Check(callable)) { if (callable->ob_refcnt == 1) { Py_XDECREF(((PyMethodObject*)callable)->im_self); Py_INCREF(inst); ((PyMethodObject*)callable)->im_self = inst; Py_INCREF(callable); return callable; } else return callable->ob_type->tp_descr_get( callable, inst, ((PyMethodObject*)callable)->im_class); } else return PyMethod_New(callable, inst, (PyObject*)(ECBaseType)); } static struct ExtensionClassCAPIstruct TrueExtensionClassCAPI = { EC_findiattrs_, PyExtensionClass_Export_, PyECMethod_New_, &BaseType, &ExtensionClassType, }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC init_ExtensionClass(void) { PyObject *m, *s; if (pickle_setup() < 0) return; #define DEFINE_STRING(S) \ if(! (str ## S = PyString_FromString(# S))) return DEFINE_STRING(__of__); DEFINE_STRING(__get__); DEFINE_STRING(__class_init__); DEFINE_STRING(__init__); DEFINE_STRING(__bases__); DEFINE_STRING(__mro__); DEFINE_STRING(__new__); #undef DEFINE_STRING PyExtensionClassCAPI = &TrueExtensionClassCAPI; ExtensionClassType.ob_type = &PyType_Type; ExtensionClassType.tp_base = &PyType_Type; ExtensionClassType.tp_traverse = PyType_Type.tp_traverse; ExtensionClassType.tp_clear = PyType_Type.tp_clear; /* Initialize types: */ if (PyType_Ready(&ExtensionClassType) < 0) return; BaseType.ob_type = &ExtensionClassType; BaseType.tp_base = &PyBaseObject_Type; BaseType.tp_new = PyType_GenericNew; if (PyType_Ready(&BaseType) < 0) return; NoInstanceDictionaryBaseType.ob_type = &ExtensionClassType; NoInstanceDictionaryBaseType.tp_base = &BaseType; NoInstanceDictionaryBaseType.tp_new = PyType_GenericNew; if (PyType_Ready(&NoInstanceDictionaryBaseType) < 0) return; /* Create the module and add the functions */ m = Py_InitModule3("_ExtensionClass", ec_methods, _extensionclass_module_documentation); if (m == NULL) return; s = PyCObject_FromVoidPtr(PyExtensionClassCAPI, NULL); if (PyModule_AddObject(m, "CAPI2", s) < 0) return; /* Add types: */ if (PyModule_AddObject(m, "ExtensionClass", (PyObject *)&ExtensionClassType) < 0) return; if (PyModule_AddObject(m, "Base", (PyObject *)&BaseType) < 0) return; if (PyModule_AddObject(m, "NoInstanceDictionaryBase", (PyObject *)&NoInstanceDictionaryBaseType) < 0) return; } zope2.13-2.13.21/source/Acquisition/include/ExtensionClass/__init__.py0000644000175000017500000000523712214017433024431 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ExtensionClass Extension Class exists to support types derived from the old ExtensionType meta-class that preceeded Python 2.2 and new-style classes. As a meta-class, ExtensionClass provides the following features: - Support for a class initialiser: >>> from ExtensionClass import ExtensionClass, Base >>> class C(Base): ... def __class_init__(self): ... print 'class init called' ... print self.__name__ ... def bar(self): ... return 'bar called' class init called C >>> c = C() >>> int(c.__class__ is C) 1 >>> int(c.__class__ is type(c)) 1 - Making sure that every instance of the meta-class has Base as a base class: >>> class X: ... __metaclass__ = ExtensionClass >>> Base in X.__mro__ 1 - Provide an inheritedAttribute method for looking up attributes in base classes: >>> class C2(C): ... def bar(*a): ... return C2.inheritedAttribute('bar')(*a), 42 class init called C2 >>> o = C2() >>> o.bar() ('bar called', 42) This is for compatability with old code. New code should use super instead. The base class, Base, exists mainly to support the __of__ protocol. The __of__ protocol is similar to __get__ except that __of__ is called when an implementor is retrieved from an instance as well as from a class: >>> class O(Base): ... def __of__(*a): ... return a >>> o1 = O() >>> o2 = O() >>> C.o1 = o1 >>> c.o2 = o2 >>> c.o1 == (o1, c) 1 >>> C.o1 == o1 1 >>> int(c.o2 == (o2, c)) 1 We accomplish this by making a class that implements __of__ a descriptor and treating all descriptor ExtensionClasses this way. That is, if an extension class is a descriptor, it's __get__ method will be called even when it is retrieved from an instance. >>> class O(Base): ... def __get__(*a): ... return a ... >>> o1 = O() >>> o2 = O() >>> C.o1 = o1 >>> c.o2 = o2 >>> int(c.o1 == (o1, c, type(c))) 1 >>> int(C.o1 == (o1, None, type(c))) 1 >>> int(c.o2 == (o2, c, type(c))) 1 $Id: __init__.py 40218 2005-11-18 14:39:19Z andreasjung $ """ from _ExtensionClass import * zope2.13-2.13.21/source/Acquisition/include/ExtensionClass/tests.py0000644000175000017500000004744512214017433024043 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ $Id: tests.py 109298 2010-02-22 17:30:41Z hannosch $ """ from ExtensionClass import * import pickle def print_dict(d): d = d.items() d.sort() print '{%s}' % (', '.join( [('%r: %r' % (k, v)) for (k, v) in d] )) def test_mixing(): """Test working with a classic class >>> class Classic: ... def x(self): ... return 42 >>> class O(Base): ... def __of__(*a): ... return a >>> class O2(Classic, O): ... def __of__(*a): ... return (O2.inheritedAttribute('__of__')(*a), ... O2.inheritedAttribute('x')(a[0])) >>> class C(Base): ... def __class_init__(self): ... print 'class init called' ... print self.__name__ ... def bar(self): ... return 'bar called' class init called C >>> c = C() >>> o2 = O2() >>> c.o2 = o2 >>> int(c.o2 == ((o2, c), 42)) 1 Test working with a new style >>> class Modern(object): ... def x(self): ... return 42 >>> class O2(Modern, O): ... def __of__(*a): ... return (O2.inheritedAttribute('__of__')(*a), ... O2.inheritedAttribute('x')(a[0])) >>> o2 = O2() >>> c.o2 = o2 >>> int(c.o2 == ((o2, c), 42)) 1 """ def test_class_creation_under_stress(): """ >>> for i in range(100): ... class B(Base): ... print i, ... if i and i%20 == 0: ... print 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 >>> import gc >>> x = gc.collect() """ def old_test_add(): """test_add.py from old EC >>> class foo(Base): ... def __add__(self,other): print 'add called' >>> foo()+foo() add called """ def proper_error_on_deleattr(): """ Florent Guillaume wrote: ... Excellent. Will it also fix this particularity of ExtensionClass: >>> class A(Base): ... def foo(self): ... self.gee ... def bar(self): ... del self.gee >>> a=A() >>> a.foo() Traceback (most recent call last): ... AttributeError: gee >>> a.bar() Traceback (most recent call last): ... AttributeError: 'A' object has no attribute 'gee' I.e., the fact that KeyError is raised whereas a normal class would raise AttributeError. """ def test_NoInstanceDictionaryBase(): """ >>> class B(NoInstanceDictionaryBase): pass ... >>> B().__dict__ Traceback (most recent call last): ... AttributeError: This object has no __dict__ >>> class B(NoInstanceDictionaryBase): ... __slots__ = ('a', 'b') ... >>> class BB(B): pass ... >>> b = BB() >>> b.__dict__ Traceback (most recent call last): ... AttributeError: This object has no __dict__ >>> b.a = 1 >>> b.b = 2 >>> b.a 1 >>> b.b 2 """ def test__basicnew__(): """ >>> x = Simple.__basicnew__() >>> x.__dict__ {} """ def cmpattrs(self, other, *attrs): for attr in attrs: if attr[:3] in ('_v_', '_p_'): continue c = cmp(getattr(self, attr, None), getattr(other, attr, None)) if c: return c return 0 class Simple(Base): def __init__(self, name, **kw): self.__name__ = name self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', *(self.__dict__.keys())) def test_basic_pickling(): """ >>> x = Simple('x', aaa=1, bbb='foo') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> print_dict(x.__getstate__()) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> f, (c,), state = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Simple' >>> print_dict(state) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.__setstate__({'z': 1}) >>> x.__dict__ {'z': 1} """ class Custom(Simple): def __new__(cls, x, y): r = Base.__new__(cls) r.x, r.y = x, y return r def __init__(self, x, y): self.a = 42 def __getnewargs__(self): return self.x, self.y def __getstate__(self): return self.a def __setstate__(self, a): self.a = a def test_pickling_w_overrides(): """ >>> x = Custom('x', 'y') >>> x.a = 99 >>> (f, (c, ax, ay), a) = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Custom' >>> ax, ay, a ('x', 'y', 99) >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class Slotted(Base): __slots__ = 's1', 's2', '_p_splat', '_v_eek' def __init__(self, s1, s2): self.s1, self.s2 = s1, s2 self._v_eek = 1 self._p_splat = 2 class SubSlotted(Slotted): __slots__ = 's3', 's4' def __init__(self, s1, s2, s3): Slotted.__init__(self, s1, s2) self.s3 = s3 def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4') def test_pickling_w_slots_only(): """ >>> x = SubSlotted('x', 'y', 'z') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class SubSubSlotted(SubSlotted): def __init__(self, s1, s2, s3, **kw): SubSlotted.__init__(self, s1, s2, s3) self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4', *(self.__dict__.keys())) def test_pickling_w_slots(): """ >>> x = SubSubSlotted('x', 'y', 'z', aaa=1, bbb='foo') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ def test_pickling_w_slots_w_empty_dict(): """ >>> x = SubSubSlotted('x', 'y', 'z') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ def test_setattr_on_extension_type(): """ >>> for name in 'x', '_x', 'x_', '__x_y__', '___x__', '__x___', '_x_': ... setattr(Base, name, 1) ... print getattr(Base, name) ... delattr(Base, name) ... print getattr(Base, name, 0) 1 0 1 0 1 0 1 0 1 0 1 0 1 0 >>> Base.__foo__ = 1 Traceback (most recent call last): ... TypeError: can't set attributes of built-in/extension type """ \ """'ExtensionClass.Base' if the attribute name begins """ \ """and ends with __ and contains only 4 _ characters >>> Base.__foo__ Traceback (most recent call last): ... AttributeError: type object 'ExtensionClass.Base' """ \ """has no attribute '__foo__' >>> del Base.__foo__ Traceback (most recent call last): ... TypeError: can't set attributes of built-in/extension type """ \ """'ExtensionClass.Base' if the attribute name begins """ \ """and ends with __ and contains only 4 _ characters """ def test_mro(): """ExtensionClass method-resolution order The EC MRO is chosen to maximize backward compatibility and provide a model that is easy to reason about. The basic idea is: I'll call this the "encapsulated base" scheme. Consider: >>> class X(Base): ... pass >>> class Y(Base): ... pass >>> class Z(Base): ... pass >>> class C(X, Y, Z): ... def foo(self): ... return 42 When we look up an attribute, we do the following: - Look in C's dictionary first. - Look up the attribute in X. We don't care how we get the attribute from X. If X is a new-style-class, we use the new algorithm. If X is a classic class, we use left-to-right depth-first. If X is an nsEC, use the "encapsulated base" algorithm. If we don't find the attribute in X, look in Y and then in Z, using the same approach. This algorithm will produce backward compatible results, providing the equivalent of left-to-right depth-first for nsECs and classic classes. We'll actually do something less abstract. We'll use a simple algorthm to merge the __mro__ of the base classes, computing an __mro__ for classic classes using the left-to-right depth-first algorithm. We'll basically lay the mros end-to-end left-to-right and remove repeats, keeping the first occurence of each class. >>> [c.__name__ for c in C.__mro__] ['C', 'X', 'Y', 'Z', 'Base', 'object'] For backward-compatability's sake, we actually depart from the above description a bit. We always put Base and object last in the mro, as shown in the example above. The primary reason for this is that object provides a do-nothing __init__ method. It is common practice to mix a C-implemented base class that implements a few methods with a Python class that implements those methods and others. The idea is that the C implementation overrides selected methods in C, so the C subclass is listed first. Unfortunately, because all extension classes are required to subclass Base, and thus, object, the C subclass brings along the __init__ object from objects, which would hide any __init__ method provided by the Python mix-in. Base and object are special in that they are implied by their meta classes. For example, a new-style class always has object as an ancestor, even if it isn't listed as a base: >>> class O: ... __metaclass__ = type >>> [c.__name__ for c in O.__bases__] ['object'] >>> [c.__name__ for c in O.__mro__] ['O', 'object'] Similarly, Base is always an ancestor of an extension class: >>> class E: ... __metaclass__ = ExtensionClass >>> [c.__name__ for c in E.__bases__] ['Base'] >>> [c.__name__ for c in E.__mro__] ['E', 'Base', 'object'] Base and object are generally added soley to get a particular meta class. They aren't used to provide application functionality and really shouldn't be considered when reasoning about where attributes come from. They do provide some useful default functionality and should be included at the end of the mro. Here are more examples: >>> from ExtensionClass import Base >>> class NA(object): ... pass >>> class NB(NA): ... pass >>> class NC(NA): ... pass >>> class ND(NB, NC): ... pass >>> [c.__name__ for c in ND.__mro__] ['ND', 'NB', 'NC', 'NA', 'object'] >>> class EA(Base): ... pass >>> class EB(EA): ... pass >>> class EC(EA): ... pass >>> class ED(EB, EC): ... pass >>> [c.__name__ for c in ED.__mro__] ['ED', 'EB', 'EA', 'EC', 'Base', 'object'] >>> class EE(ED, ND): ... pass >>> [c.__name__ for c in EE.__mro__] ['EE', 'ED', 'EB', 'EA', 'EC', 'ND', 'NB', 'NC', 'NA', 'Base', 'object'] >>> class EF(ND, ED): ... pass >>> [c.__name__ for c in EF.__mro__] ['EF', 'ND', 'NB', 'NC', 'NA', 'ED', 'EB', 'EA', 'EC', 'Base', 'object'] >>> class CA: ... pass >>> class CB(CA): ... pass >>> class CC(CA): ... pass >>> class CD(CB, CC): ... pass >>> class ECD(Base, CD): ... pass >>> [c.__name__ for c in ECD.__mro__] ['ECD', 'CD', 'CB', 'CA', 'CC', 'Base', 'object'] >>> class CDE(CD, Base): ... pass >>> [c.__name__ for c in CDE.__mro__] ['CDE', 'CD', 'CB', 'CA', 'CC', 'Base', 'object'] >>> class CEND(CD, ED, ND): ... pass >>> [c.__name__ for c in CEND.__mro__] ['CEND', 'CD', 'CB', 'CA', 'CC', """ \ """'ED', 'EB', 'EA', 'EC', 'ND', 'NB', 'NC', 'NA', 'Base', 'object'] """ def test_avoiding___init__decoy_w_inheritedAttribute(): """ >>> class Decoy(Base): ... pass >>> class B(Base): ... def __init__(self, a, b): ... print '__init__', a, b >>> class C(Decoy, B): ... def __init__(self): ... print 'C init' ... C.inheritedAttribute('__init__')(self, 1, 2) >>> x = C() C init __init__ 1 2 """ def test_of_not_called_when_not_accessed_through_EC_instance(): """ >>> class Eek(Base): ... def __of__(self, parent): ... return self, parent If I define an EC instance as an attr of an ordinary class: >>> class O(object): ... eek = Eek() >>> class C: ... eek = Eek() I get the instance, without calling __of__, when I get it from either tha class: >>> O.eek is O.__dict__['eek'] True >>> C.eek is C.__dict__['eek'] True or an instance of the class: >>> O().eek is O.__dict__['eek'] True >>> C().eek is C.__dict__['eek'] True If I define an EC instance as an attr of an extension class: >>> class E(Base): ... eek = Eek() I get the instance, without calling __of__, when I get it from tha class: >>> E.eek is E.__dict__['eek'] True But __of__ is called if I go through the instance: >>> e = E() >>> e.eek == (E.__dict__['eek'], e) True """ def test_inheriting___doc__(): """Old-style ExtensionClass inherited __doc__ from base classes. >>> class E(Base): ... "eek" >>> class EE(E): ... pass >>> EE.__doc__ 'eek' >>> EE().__doc__ 'eek' """ def test___of___w_metaclass_instance(): """When looking for extension class instances, need to handle meta classes >>> class C(Base): ... pass >>> class O(Base): ... def __of__(self, parent): ... print '__of__ called on an O' >>> class M(ExtensionClass): ... pass >>> class X: ... __metaclass__ = M ... >>> class S(X, O): ... pass >>> c = C() >>> c.s = S() >>> c.s __of__ called on an O """ def test___of__set_after_creation(): """We may need to set __of__ after a class is created. Normally, in a class's __init__, the initialization code checks for an __of__ method and, if it isn't already set, sets __get__. If a class is persistent and loaded from the database, we want this to happen in __setstate__. The pmc_init_of function allws us to do that. We'll create an extension class without a __of__. We'll also give it a special meta class, just to make sure that this works with funny metaclasses too: >>> import ExtensionClass >>> class M(ExtensionClass.ExtensionClass): ... "A meta class" >>> class B(ExtensionClass.Base): ... __metaclass__ = M ... def __init__(self, name): ... self.name = name ... def __repr__(self): ... return self.name >>> B.__class__ is M True >>> x = B('x') >>> x.y = B('y') >>> x.y y We define a __of__ method for B after the fact: >>> def __of__(self, other): ... print '__of__(%r, %r)' % (self, other) ... return self >>> B.__of__ = __of__ We see that this has no effect: >>> x.y y Until we use pmc_init_of: >>> ExtensionClass.pmc_init_of(B) >>> x.y __of__(y, x) y Note that there is no harm in calling pmc_init_of multiple times: >>> ExtensionClass.pmc_init_of(B) >>> ExtensionClass.pmc_init_of(B) >>> ExtensionClass.pmc_init_of(B) >>> x.y __of__(y, x) y If we remove __of__, we'll go back to the behavior we had before: >>> del B.__of__ >>> ExtensionClass.pmc_init_of(B) >>> x.y y """ def test_Basic_gc(): """Test to make sure that EC instances participate in GC >>> from ExtensionClass import Base >>> import gc >>> class C1(Base): ... pass ... >>> class C2(Base): ... def __del__(self): ... print 'removed' ... >>> a=C1() >>> a.b = C1() >>> a.b.a = a >>> a.b.c = C2() >>> thresholds = gc.get_threshold() >>> gc.set_threshold(0) >>> ignore = gc.collect() >>> del a >>> ignored = gc.collect() removed >>> ignored > 0 True >>> gc.set_threshold(*thresholds) """ from zope.testing.doctest import DocTestSuite import unittest def test_suite(): return unittest.TestSuite(( DocTestSuite('ExtensionClass'), DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/Acquisition/include/ExtensionClass/ExtensionClass.h0000644000175000017500000002370012214017433025426 0ustar arnauarnau/***************************************************************************** Copyright (c) 1996-2002 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /* $Id: ExtensionClass.h 40218 2005-11-18 14:39:19Z andreasjung $ Extension Class Definitions Implementing base extension classes A base extension class is implemented in much the same way that an extension type is implemented, except: - The include file, 'ExtensionClass.h', must be included. - The type structure is declared to be of type 'PyExtensionClass', rather than of type 'PyTypeObject'. - The type structure has an additional member that must be defined after the documentation string. This extra member is a method chain ('PyMethodChain') containing a linked list of method definition ('PyMethodDef') lists. Method chains can be used to implement method inheritance in C. Most extensions don't use method chains, but simply define method lists, which are null-terminated arrays of method definitions. A macro, 'METHOD_CHAIN' is defined in 'ExtensionClass.h' that converts a method list to a method chain. (See the example below.) - Module functions that create new instances must be replaced by an '__init__' method that initializes, but does not create storage for instances. - The extension class must be initialized and exported to the module with:: PyExtensionClass_Export(d,"name",type); where 'name' is the module name and 'type' is the extension class type object. Attribute lookup Attribute lookup is performed by calling the base extension class 'getattr' operation for the base extension class that includes C data, or for the first base extension class, if none of the base extension classes include C data. 'ExtensionClass.h' defines a macro 'Py_FindAttrString' that can be used to find an object's attributes that are stored in the object's instance dictionary or in the object's class or base classes:: v = Py_FindAttrString(self,name); In addition, a macro is provided that replaces 'Py_FindMethod' calls with logic to perform the same sort of lookup that is provided by 'Py_FindAttrString'. Linking The extension class mechanism was designed to be useful with dynamically linked extension modules. Modules that implement extension classes do not have to be linked against an extension class library. The macro 'PyExtensionClass_Export' imports the 'ExtensionClass' module and uses objects imported from this module to initialize an extension class with necessary behavior. */ #ifndef EXTENSIONCLASS_H #define EXTENSIONCLASS_H #include "Python.h" #include "import.h" /* Declarations for objects of type ExtensionClass */ #define EC PyTypeObject #define PyExtensionClass PyTypeObject #define EXTENSIONCLASS_BINDABLE_FLAG 1 << 2 #define EXTENSIONCLASS_NOINSTDICT_FLAG 1 << 5 typedef struct { PyObject_HEAD } _emptyobject; static struct ExtensionClassCAPIstruct { /***************************************************************************** WARNING: THIS STRUCT IS PRIVATE TO THE EXTENSION CLASS INTERFACE IMPLEMENTATION AND IS SUBJECT TO CHANGE !!! *****************************************************************************/ PyObject *(*EC_findiattrs_)(PyObject *self, char *cname); int (*PyExtensionClass_Export_)(PyObject *dict, char *name, PyTypeObject *typ); PyObject *(*PyECMethod_New_)(PyObject *callable, PyObject *inst); PyExtensionClass *ECBaseType_; PyExtensionClass *ECExtensionClassType_; } *PyExtensionClassCAPI = NULL; #define ECBaseType (PyExtensionClassCAPI->ECBaseType_) #define ECExtensionClassType (PyExtensionClassCAPI->ECExtensionClassType_) /* Following are macros that are needed or useful for defining extension classes: */ /* This macro redefines Py_FindMethod to do attribute for an attribute name given by a C string lookup using extension class meta-data. This is used by older getattr implementations. This macro is used in base class implementations of tp_getattr to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. Note that in Python 1.4, a getattr operation may be provided that uses an object argument. Classes that support this new operation should use Py_FindAttr. */ #define EC_findiattrs (PyExtensionClassCAPI->EC_findiattrs_) #define Py_FindMethod(M,SELF,NAME) (EC_findiattrs((SELF),(NAME))) /* Do method or attribute lookup for an attribute name given by a C string using extension class meta-data. This macro is used in base class implementations of tp_getattro to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. Note that in Python 1.4, a getattr operation may be provided that uses an object argument. Classes that support this new operation should use Py_FindAttr. */ #define Py_FindAttrString(SELF,NAME) (EC_findiattrs((SELF),(NAME))) /* Do method or attribute lookup using extension class meta-data. This macro is used in base class implementations of tp_getattr to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. */ #define Py_FindAttr (ECBaseType->tp_getattro) /* Do attribute assignment for an attribute. This macro is used in base class implementations of tp_setattro to set attributes that are not managed by the base type directly. The macro is generally used to assign attributes after other attribute attempts to assign attributes have failed. */ #define PyEC_SetAttr(SELF,NAME,V) (ECBaseType->tp_setattro(SELF, NAME, V)) /* Convert a method list to a method chain. */ #define METHOD_CHAIN(DEF) (traverseproc)(DEF) /* The following macro checks whether a type is an extension class: */ #define PyExtensionClass_Check(TYPE) \ PyObject_TypeCheck((PyObject*)(TYPE), ECExtensionClassType) /* The following macro checks whether an instance is an extension instance: */ #define PyExtensionInstance_Check(INST) \ PyObject_TypeCheck(((PyObject*)(INST))->ob_type, ECExtensionClassType) #define CHECK_FOR_ERRORS(MESS) /* The following macro can be used to define an extension base class that only provides method and that is used as a pure mix-in class. */ #define PURE_MIXIN_CLASS(NAME,DOC,METHODS) \ static PyExtensionClass NAME ## Type = { PyObject_HEAD_INIT(NULL) 0, # NAME, \ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \ 0 , DOC, (traverseproc)METHODS, } /* The following macros provide limited access to extension-class method facilities. */ /* Test for an ExtensionClass method: */ #define PyECMethod_Check(O) PyMethod_Check((O)) /* Create a method object that wraps a callable object and an instance. Note that if the callable object is an extension class method, then the new method will wrap the callable object that is wrapped by the extension class method. Also note that if the callable object is an extension class method with a reference count of 1, then the callable object will be rebound to the instance and returned with an incremented reference count. */ #define PyECMethod_New(CALLABLE, INST) \ PyExtensionClassCAPI->PyECMethod_New_((CALLABLE),(INST)) /* Return the instance that is bound by an extension class method. */ #define PyECMethod_Self(M) \ (PyMethod_Check((M)) ? ((PyMethodObject*)(M))->im_self : NULL) /* Check whether an object has an __of__ method for returning itself in the context of it's container. */ #define has__of__(O) (PyObject_TypeCheck((O)->ob_type, ECExtensionClassType) \ && (O)->ob_type->tp_descr_get != NULL) /* The following macros are used to check whether an instance or a class' instanses have instance dictionaries: */ #define HasInstDict(O) (_PyObject_GetDictPtr(O) != NULL) #define ClassHasInstDict(C) ((C)->tp_dictoffset > 0)) /* Get an object's instance dictionary. Use with caution */ #define INSTANCE_DICT(inst) (_PyObject_GetDictPtr(O)) /* Test whether an ExtensionClass, S, is a subclass of ExtensionClass C. */ #define ExtensionClassSubclass_Check(S,C) PyType_IsSubtype((S), (C)) /* Test whether an ExtensionClass instance , I, is a subclass of ExtensionClass C. */ #define ExtensionClassSubclassInstance_Check(I,C) PyObject_TypeCheck((I), (C)) /* Export an Extension Base class in a given module dictionary with a given name and ExtensionClass structure. */ #define PyExtensionClass_Export(D,N,T) \ if (! ExtensionClassImported || \ PyExtensionClassCAPI->PyExtensionClass_Export_((D),(N),&(T)) < 0) return; #define ExtensionClassImported \ ((PyExtensionClassCAPI != NULL) || \ (PyExtensionClassCAPI = PyCObject_Import("ExtensionClass","CAPI2"))) /* These are being overridded to use tp_free when used with new-style classes. This is to allow old extention-class code to work. */ #undef PyMem_DEL #undef PyObject_DEL #define PyMem_DEL(O) \ if (((O)->ob_type->tp_flags & Py_TPFLAGS_HAVE_CLASS) \ && ((O)->ob_type->tp_free != NULL)) \ (O)->ob_type->tp_free((PyObject*)(O)); \ else \ PyObject_FREE((O)); #define PyObject_DEL(O) PyMem_DEL(O) #endif /* EXTENSIONCLASS_H */ zope2.13-2.13.21/source/Acquisition/buildout.cfg0000644000175000017500000000026712214017433020241 0ustar arnauarnau[buildout] develop = . parts = interpreter test [interpreter] recipe = zc.recipe.egg interpreter = python eggs = Acquisition [test] recipe = zc.recipe.testrunner eggs = Acquisition zope2.13-2.13.21/source/Acquisition/bootstrap.py0000644000175000017500000002352212214017433020317 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess from optparse import OptionParser if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: quote = str # See zc.buildout.easy_install._has_broken_dash_S for motivation and comments. stdout, stderr = subprocess.Popen( [sys.executable, '-Sc', 'try:\n' ' import ConfigParser\n' 'except ImportError:\n' ' print 1\n' 'else:\n' ' print 0\n'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() has_broken_dash_S = bool(int(stdout.strip())) # In order to be more robust in the face of system Pythons, we want to # run without site-packages loaded. This is somewhat tricky, in # particular because Python 2.6's distutils imports site, so starting # with the -S flag is not sufficient. However, we'll start with that: if not has_broken_dash_S and 'site' in sys.modules: # We will restart with python -S. args = sys.argv[:] args[0:0] = [sys.executable, '-S'] args = map(quote, args) os.execv(sys.executable, args) # Now we are running with -S. We'll get the clean sys.path, import site # because distutils will do it later, and then reset the path and clean # out any namespace packages from site-packages that might have been # loaded by .pth files. clean_path = sys.path[:] import site sys.path[:] = clean_path for k, v in sys.modules.items(): if k in ('setuptools', 'pkg_resources') or ( hasattr(v, '__path__') and len(v.__path__)==1 and not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))): # This is a namespace package. Remove it. sys.modules.pop(k) is_jython = sys.platform.startswith('java') setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py' distribute_source = 'http://python-distribute.org/distribute_setup.py' # parsing arguments def normalize_to_url(option, opt_str, value, parser): if value: if '://' not in value: # It doesn't smell like a URL. value = 'file://%s' % ( urllib.pathname2url( os.path.abspath(os.path.expanduser(value))),) if opt_str == '--download-base' and not value.endswith('/'): # Download base needs a trailing slash to make the world happy. value += '/' else: value = None name = opt_str[2:].replace('-', '_') setattr(parser.values, name, value) usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --setup-source and --download-base to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="use_distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("--setup-source", action="callback", dest="setup_source", callback=normalize_to_url, nargs=1, type="string", help=("Specify a URL or file location for the setup file. " "If you use Setuptools, this will default to " + setuptools_source + "; if you use Distribute, this " "will default to " + distribute_source +".")) parser.add_option("--download-base", action="callback", dest="download_base", callback=normalize_to_url, nargs=1, type="string", help=("Specify a URL or directory for downloading " "zc.buildout and either Setuptools or Distribute. " "Defaults to PyPI.")) parser.add_option("--eggs", help=("Specify a directory for storing eggs. Defaults to " "a temporary directory that is deleted when the " "bootstrap script completes.")) parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, help=("Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas.")) parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout's main function if options.config_file is not None: args += ['-c', options.config_file] if options.eggs: eggs_dir = os.path.abspath(os.path.expanduser(options.eggs)) else: eggs_dir = tempfile.mkdtemp() if options.setup_source is None: if options.use_distribute: options.setup_source = distribute_source else: options.setup_source = setuptools_source if options.accept_buildout_test_releases: args.append('buildout:accept-buildout-test-releases=true') args.append('bootstrap') try: import pkg_resources import setuptools # A flag. Sometimes pkg_resources is installed alone. if not hasattr(pkg_resources, '_distribute'): raise ImportError except ImportError: ez_code = urllib2.urlopen( options.setup_source).read().replace('\r\n', '\n') ez = {} exec ez_code in ez setup_args = dict(to_dir=eggs_dir, download_delay=0) if options.download_base: setup_args['download_base'] = options.download_base if options.use_distribute: setup_args['no_fake'] = True ez['use_setuptools'](**setup_args) if 'pkg_resources' in sys.modules: reload(sys.modules['pkg_resources']) import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) cmd = [quote(sys.executable), '-c', quote('from setuptools.command.easy_install import main; main()'), '-mqNxd', quote(eggs_dir)] if not has_broken_dash_S: cmd.insert(1, '-S') find_links = options.download_base if not find_links: find_links = os.environ.get('bootstrap-testing-find-links') if find_links: cmd.extend(['-f', quote(find_links)]) if options.use_distribute: setup_requirement = 'distribute' else: setup_requirement = 'setuptools' ws = pkg_resources.working_set setup_requirement_path = ws.find( pkg_resources.Requirement.parse(setup_requirement)).location env = dict( os.environ, PYTHONPATH=setup_requirement_path) requirement = 'zc.buildout' version = options.version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): for part in parsed_version: if (part[:1] == '*') and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex( search_path=[setup_requirement_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = '=='.join((requirement, version)) cmd.append(requirement) if is_jython: import subprocess exitcode = subprocess.Popen(cmd, env=env).wait() else: # Windows prefers this, apparently; otherwise we would prefer subprocess exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env])) if exitcode != 0: sys.stdout.flush() sys.stderr.flush() print ("An error occurred when trying to install zc.buildout. " "Look above this message for any errors that " "were output by easy_install.") sys.exit(exitcode) ws.add_entry(eggs_dir) ws.require(requirement) import zc.buildout.buildout zc.buildout.buildout.main(args) if not options.eggs: # clean up temporary egg directory shutil.rmtree(eggs_dir) zope2.13-2.13.21/source/Acquisition/CHANGES.txt0000644000175000017500000000626612214017433017547 0ustar arnauarnauChangelog ========= 2.13.8 (2011-06-11) ------------------- - Fixed a segfault on 64bit platforms when providing the `explicit` argument to the aq_acquire method of an Acquisition wrapper. Thx to LP #675064 for the hint to the solution. The code passed an int instead of a pointer into a function. 2.13.7 (2011-03-02) ------------------- - Fixed bug: When an object did not implement ``__unicode__``, calling ``unicode(wrapped)`` was calling ``__str__`` with an unwrapped ``self``. 2.13.6 (2011-02-19) ------------------- - Add ``aq_explicit`` to ``IAcquisitionWrapper``. - Fixed bug: ``unicode(wrapped)`` was not calling a ``__unicode__`` method on wrapped objects. 2.13.5 (2010-09-29) ------------------- - Fixed unit tests that failed on 64bit Python on Windows machines. 2.13.4 (2010-08-31) ------------------- - LP 623665: Fixed typo in Acquisition.h. 2.13.3 (2010-04-19) ------------------- - Use the doctest module from the standard library and no longer depend on zope.testing. 2.13.2 (2010-04-04) ------------------- - Give both wrapper classes a ``__getnewargs__`` method, which causes the ZODB optimization to fail and create persistent references using the ``_p_oid`` alone. This happens to be the persistent oid of the wrapped object. This lets these objects to be persisted correctly, even though they are passed to the ZODB in a wrapped state. - Added failing tests for http://dev.plone.org/plone/ticket/10318. This shows an edge-case where AQ wrappers can be pickled using the specific combination of cPickle, pickle protocol one and a custom Pickler class with an ``inst_persistent_id`` hook. Unfortunately this is the exact combination used by ZODB3. 2.13.1 (2010-02-23) ------------------- - Update to include ExtensionClass 2.13.0. - Fix the ``tp_name`` of the ImplicitAcquisitionWrapper and ExplicitAcquisitionWrapper to match their Python visible names and thus have a correct ``__name__``. - Expand the ``tp_name`` of our extension types to hold the fully qualified name. This ensures classes have their ``__module__`` set correctly. 2.13.0 (2010-02-14) ------------------- - Added support for method cache in Acquisition. Patch contributed by Yoshinori K. Okuji. See https://bugs.launchpad.net/zope2/+bug/486182. 2.12.4 (2009-10-29) ------------------- - Fix iteration proxying to pass `self` acquisition-wrapped into both `__iter__` as well as `__getitem__` (this fixes https://bugs.launchpad.net/zope2/+bug/360761). - Add tests for the __getslice__ proxying, including open-ended slicing. 2.12.3 (2009-08-08) ------------------- - More 64-bit fixes in Py_BuildValue calls. - More 64-bit issues fixed: Use correct integer size for slice operations. 2.12.2 (2009-08-02) ------------------- - Fixed 64-bit compatibility issues for Python 2.5.x / 2.6.x. See http://www.python.org/dev/peps/pep-0353/ for details. 2.12.1 (2009-04-15) ------------------- - Update for iteration proxying: The proxy for `__iter__` must not rely on the object to have an `__iter__` itself, but also support fall-back iteration via `__getitem__` (this fixes https://bugs.launchpad.net/zope2/+bug/360761). 2.12 (2009-01-25) ----------------- - Release as separate package. zope2.13-2.13.21/source/Acquisition/src/0000755000175000017500000000000012214017433016513 5ustar arnauarnauzope2.13-2.13.21/source/Acquisition/src/Acquisition.egg-info/0000755000175000017500000000000012214017433022475 5ustar arnauarnauzope2.13-2.13.21/source/Acquisition/src/Acquisition.egg-info/PKG-INFO0000644000175000017500000005167112214017433023604 0ustar arnauarnauMetadata-Version: 1.0 Name: Acquisition Version: 2.13.8 Summary: Acquisition is a mechanism that allows objects to obtain attributes from the containment hierarchy they're in. Home-page: http://pypi.python.org/pypi/Acquisition Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: .. contents:: Introductory Example ==================== Zope implements acquisition with "Extension Class" mix-in classes. To use acquisition your classes must inherit from an acquisition base class. For example:: >>> import ExtensionClass, Acquisition >>> class C(ExtensionClass.Base): ... color='red' >>> class A(Acquisition.Implicit): ... def report(self): ... print self.color ... >>> a = A() >>> c = C() >>> c.a = a >>> c.a.report() red >>> d = C() >>> d.color = 'green' >>> d.a = a >>> d.a.report() green >>> a.report() # raises an attribute error Traceback (most recent call last): ... AttributeError: color The class ``A`` inherits acquisition behavior from ``Acquisition.Implicit``. The object, ``a``, "has" the color of objects ``c`` and d when it is accessed through them, but it has no color by itself. The object ``a`` obtains attributes from its environment, where its environment is defined by the access path used to reach ``a``. Acquisition Wrappers ==================== When an object that supports acquisition is accessed through an extension class instance, a special object, called an acquisition wrapper, is returned. In the example above, the expression ``c.a`` returns an acquisition wrapper that contains references to both ``c`` and ``a``. It is this wrapper that performs attribute lookup in ``c`` when an attribute cannot be found in ``a``. Acquisition wrappers provide access to the wrapped objects through the attributes ``aq_parent``, ``aq_self``, ``aq_base``. Continue the example from above:: >>> c.a.aq_parent is c True >>> c.a.aq_self is a True Explicit and Implicit Acquisition ================================= Two styles of acquisition are supported: implicit and explicit acquisition. Implicit acquisition -------------------- Implicit acquisition is so named because it searches for attributes from the environment automatically whenever an attribute cannot be obtained directly from an object or through inheritance. An attribute can be implicitly acquired if its name does not begin with an underscore. To support implicit acquisition, your class should inherit from the mix-in class ``Acquisition.Implicit``. Explicit Acquisition -------------------- When explicit acquisition is used, attributes are not automatically obtained from the environment. Instead, the method aq_acquire must be used. For example:: >>> print c.a.aq_acquire('color') red To support explicit acquisition, your class should inherit from the mix-in class ``Acquisition.Explicit``. Controlling Acquisition ----------------------- A class (or instance) can provide attribute by attribute control over acquisition. Your should subclass from ``Acquisition.Explicit``, and set all attributes that should be acquired to the special value ``Acquisition.Acquired``. Setting an attribute to this value also allows inherited attributes to be overridden with acquired ones. For example:: >>> class C(Acquisition.Explicit): ... id=1 ... secret=2 ... color=Acquisition.Acquired ... __roles__=Acquisition.Acquired The only attributes that are automatically acquired from containing objects are color, and ``__roles__``. Note that the ``__roles__`` attribute is acquired even though its name begins with an underscore. In fact, the special ``Acquisition.Acquired`` value can be used in ``Acquisition.Implicit`` objects to implicitly acquire selected objects that smell like private objects. Sometimes, you want to dynamically make an implicitly acquiring object acquire explicitly. You can do this by getting the object's aq_explicit attribute. This attribute provides the object with an explicit wrapper that replaces the original implicit wrapper. Filtered Acquisition ==================== The acquisition method, ``aq_acquire``, accepts two optional arguments. The first of the additional arguments is a "filtering" function that is used when considering whether to acquire an object. The second of the additional arguments is an object that is passed as extra data when calling the filtering function and which defaults to ``None``. The filter function is called with five arguments: * The object that the aq_acquire method was called on, * The object where an object was found, * The name of the object, as passed to aq_acquire, * The object found, and * The extra data passed to aq_acquire. If the filter returns a true object that the object found is returned, otherwise, the acquisition search continues. Here's an example:: >>> from Acquisition import Explicit >>> class HandyForTesting: ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return "%s(%s)" % (self.name, self.__class__.__name__) ... __repr__=__str__ ... >>> class E(Explicit, HandyForTesting): pass ... >>> class Nice(HandyForTesting): ... isNice= 1 ... def __str__(self): ... return HandyForTesting.__str__(self)+' and I am nice!' ... __repr__=__str__ ... >>> a = E('a') >>> a.b = E('b') >>> a.b.c = E('c') >>> a.p = Nice('spam') >>> a.b.p = E('p') >>> def find_nice(self, ancestor, name, object, extra): ... return hasattr(object,'isNice') and object.isNice >>> print a.b.c.aq_acquire('p', find_nice) spam(Nice) and I am nice! The filtered acquisition in the last line skips over the first attribute it finds with the name ``p``, because the attribute doesn't satisfy the condition given in the filter. Filtered acquisition is rarely used in Zope. Acquiring from Context ====================== Normally acquisition allows objects to acquire data from their containers. However an object can acquire from objects that aren't its containers. Most of the examples we've seen so far show establishing of an acquisition context using getattr semantics. For example, ``a.b`` is a reference to ``b`` in the context of ``a``. You can also manually set acquisition context using the ``__of__`` method. For example:: >>> from Acquisition import Implicit >>> class C(Implicit): pass ... >>> a = C() >>> b = C() >>> a.color = "red" >>> print b.__of__(a).color red In this case, ``a`` does not contain ``b``, but it is put in ``b``'s context using the ``__of__`` method. Here's another subtler example that shows how you can construct an acquisition context that includes non-container objects:: >>> from Acquisition import Implicit >>> class C(Implicit): ... def __init__(self, name): ... self.name = name >>> a = C("a") >>> a.b = C("b") >>> a.b.color = "red" >>> a.x = C("x") >>> print a.b.x.color red Even though ``b`` does not contain ``x``, ``x`` can acquire the color attribute from ``b``. This works because in this case, ``x`` is accessed in the context of ``b`` even though it is not contained by ``b``. Here acquisition context is defined by the objects used to access another object. Containment Before Context ========================== If in the example above suppose both a and b have an color attribute:: >>> a = C("a") >>> a.color = "green" >>> a.b = C("b") >>> a.b.color = "red" >>> a.x = C("x") >>> print a.b.x.color green Why does ``a.b.x.color`` acquire color from ``a`` and not from ``b``? The answer is that an object acquires from its containers before non-containers in its context. To see why consider this example in terms of expressions using the ``__of__`` method:: a.x -> x.__of__(a) a.b -> b.__of__(a) a.b.x -> x.__of__(a).__of__(b.__of__(a)) Keep in mind that attribute lookup in a wrapper is done by trying to look up the attribute in the wrapped object first and then in the parent object. So in the expressions above proceeds from left to right. The upshot of these rules is that attributes are looked up by containment before context. This rule holds true also for more complex examples. For example, ``a.b.c.d.e.f.g.attribute`` would search for attribute in ``g`` and all its containers first. (Containers are searched in order from the innermost parent to the outermost container.) If the attribute is not found in ``g`` or any of its containers, then the search moves to ``f`` and all its containers, and so on. Additional Attributes and Methods ================================= You can use the special method ``aq_inner`` to access an object wrapped only by containment. So in the example above, ``a.b.x.aq_inner`` is equivalent to ``a.x``. You can find out the acquisition context of an object using the aq_chain method like so: >>> [obj.name for obj in a.b.x.aq_chain] ['x', 'b', 'a'] You can find out if an object is in the containment context of another object using the ``aq_inContextOf`` method. For example: >>> a.b.aq_inContextOf(a) 1 .. Note: as of this writing the aq_inContextOf examples don't work the way they should be working. According to Jim, this is because aq_inContextOf works by comparing object pointer addresses, which (because they are actually different wrapper objects) doesn't give you the expected results. He acknowledges that this behavior is controversial, and says that there is a collector entry to change it so that you would get the answer you expect in the above. (We just need to get to it). Acquisition Module Functions ============================ In addition to using acquisition attributes and methods directly on objects you can use similar functions defined in the ``Acquisition`` module. These functions have the advantage that you don't need to check to make sure that the object has the method or attribute before calling it. ``aq_acquire(object, name [, filter, extra, explicit, default, containment])`` Acquires an object with the given name. This function can be used to explictly acquire when using explicit acquisition and to acquire names that wouldn't normally be acquired. The function accepts a number of optional arguments: ``filter`` A callable filter object that is used to decide if an object should be acquired. The filter is called with five arguments: * The object that the aq_acquire method was called on, * The object where an object was found, * The name of the object, as passed to aq_acquire, * The object found, and * The extra argument passed to aq_acquire. If the filter returns a true object that the object found is returned, otherwise, the acquisition search continues. ``extra`` Extra data to be passed as the last argument to the filter. ``explicit`` A flag (boolean value) indicating whether explicit acquisition should be used. The default value is true. If the flag is true, then acquisition will proceed regardless of whether wrappers encountered in the search of the acquisition hierarchy are explicit or implicit wrappers. If the flag is false, then parents of explicit wrappers are not searched. This argument is useful if you want to apply a filter without overriding explicit wrappers. ``default`` A default value to return if no value can be acquired. ``containment`` A flag indicating whether the search should be limited to the containment hierarchy. In addition, arguments can be provided as keywords. ``aq_base(object)`` Return the object with all wrapping removed. ``aq_chain(object [, containment])`` Return a list containing the object and it's acquisition parents. The optional argument, containment, controls whether the containment or access hierarchy is used. ``aq_get(object, name [, default, containment])`` Acquire an attribute, name. A default value can be provided, as can a flag that limits search to the containment hierarchy. ``aq_inner(object)`` Return the object with all but the innermost layer of wrapping removed. ``aq_parent(object)`` Return the acquisition parent of the object or None if the object is unwrapped. ``aq_self(object)`` Return the object with one layer of wrapping removed, unless the object is unwrapped, in which case the object is returned. In most cases it is more convenient to use these module functions instead of the acquisition attributes and methods directly. Acquisition and Methods ======================= Python methods of objects that support acquisition can use acquired attributes. When a Python method is called on an object that is wrapped by an acquisition wrapper, the wrapper is passed to the method as the first argument. This rule also applies to user-defined method types and to C methods defined in pure mix-in classes. Unfortunately, C methods defined in extension base classes that define their own data structures, cannot use aquired attributes at this time. This is because wrapper objects do not conform to the data structures expected by these methods. In practice, you will seldom find this a problem. Conclusion ========== Acquisition provides a powerful way to dynamically share information between objects. Zope 2 uses acquisition for a number of its key features including security, object publishing, and DTML variable lookup. Acquisition also provides an elegant solution to the problem of circular references for many classes of problems. While acquisition is powerful, you should take care when using acquisition in your applications. The details can get complex, especially with the differences between acquiring from context and acquiring from containment. Changelog ========= 2.13.8 (2011-06-11) ------------------- - Fixed a segfault on 64bit platforms when providing the `explicit` argument to the aq_acquire method of an Acquisition wrapper. Thx to LP #675064 for the hint to the solution. The code passed an int instead of a pointer into a function. 2.13.7 (2011-03-02) ------------------- - Fixed bug: When an object did not implement ``__unicode__``, calling ``unicode(wrapped)`` was calling ``__str__`` with an unwrapped ``self``. 2.13.6 (2011-02-19) ------------------- - Add ``aq_explicit`` to ``IAcquisitionWrapper``. - Fixed bug: ``unicode(wrapped)`` was not calling a ``__unicode__`` method on wrapped objects. 2.13.5 (2010-09-29) ------------------- - Fixed unit tests that failed on 64bit Python on Windows machines. 2.13.4 (2010-08-31) ------------------- - LP 623665: Fixed typo in Acquisition.h. 2.13.3 (2010-04-19) ------------------- - Use the doctest module from the standard library and no longer depend on zope.testing. 2.13.2 (2010-04-04) ------------------- - Give both wrapper classes a ``__getnewargs__`` method, which causes the ZODB optimization to fail and create persistent references using the ``_p_oid`` alone. This happens to be the persistent oid of the wrapped object. This lets these objects to be persisted correctly, even though they are passed to the ZODB in a wrapped state. - Added failing tests for http://dev.plone.org/plone/ticket/10318. This shows an edge-case where AQ wrappers can be pickled using the specific combination of cPickle, pickle protocol one and a custom Pickler class with an ``inst_persistent_id`` hook. Unfortunately this is the exact combination used by ZODB3. 2.13.1 (2010-02-23) ------------------- - Update to include ExtensionClass 2.13.0. - Fix the ``tp_name`` of the ImplicitAcquisitionWrapper and ExplicitAcquisitionWrapper to match their Python visible names and thus have a correct ``__name__``. - Expand the ``tp_name`` of our extension types to hold the fully qualified name. This ensures classes have their ``__module__`` set correctly. 2.13.0 (2010-02-14) ------------------- - Added support for method cache in Acquisition. Patch contributed by Yoshinori K. Okuji. See https://bugs.launchpad.net/zope2/+bug/486182. 2.12.4 (2009-10-29) ------------------- - Fix iteration proxying to pass `self` acquisition-wrapped into both `__iter__` as well as `__getitem__` (this fixes https://bugs.launchpad.net/zope2/+bug/360761). - Add tests for the __getslice__ proxying, including open-ended slicing. 2.12.3 (2009-08-08) ------------------- - More 64-bit fixes in Py_BuildValue calls. - More 64-bit issues fixed: Use correct integer size for slice operations. 2.12.2 (2009-08-02) ------------------- - Fixed 64-bit compatibility issues for Python 2.5.x / 2.6.x. See http://www.python.org/dev/peps/pep-0353/ for details. 2.12.1 (2009-04-15) ------------------- - Update for iteration proxying: The proxy for `__iter__` must not rely on the object to have an `__iter__` itself, but also support fall-back iteration via `__getitem__` (this fixes https://bugs.launchpad.net/zope2/+bug/360761). 2.12 (2009-01-25) ----------------- - Release as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Acquisition/src/Acquisition.egg-info/dependency_links.txt0000644000175000017500000000000112214017433026543 0ustar arnauarnau zope2.13-2.13.21/source/Acquisition/src/Acquisition.egg-info/requires.txt0000644000175000017500000000003512214017433025073 0ustar arnauarnauExtensionClass zope.interfacezope2.13-2.13.21/source/Acquisition/src/Acquisition.egg-info/top_level.txt0000644000175000017500000000001412214017433025222 0ustar arnauarnauAcquisition zope2.13-2.13.21/source/Acquisition/src/Acquisition.egg-info/SOURCES.txt0000644000175000017500000000124112214017433024357 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py include/ExtensionClass/ExtensionClass.h include/ExtensionClass/_ExtensionClass.c include/ExtensionClass/__init__.py include/ExtensionClass/tests.py include/ExtensionClass/pickle/pickle.c src/Acquisition/Acquisition.h src/Acquisition/README.txt src/Acquisition/_Acquisition.c src/Acquisition/__init__.py src/Acquisition/interfaces.py src/Acquisition/tests.py src/Acquisition.egg-info/PKG-INFO src/Acquisition.egg-info/SOURCES.txt src/Acquisition.egg-info/dependency_links.txt src/Acquisition.egg-info/not-zip-safe src/Acquisition.egg-info/requires.txt src/Acquisition.egg-info/top_level.txtzope2.13-2.13.21/source/Acquisition/src/Acquisition.egg-info/not-zip-safe0000644000175000017500000000000112214017433024723 0ustar arnauarnau zope2.13-2.13.21/source/Acquisition/src/Acquisition/0000755000175000017500000000000012214017433021003 5ustar arnauarnauzope2.13-2.13.21/source/Acquisition/src/Acquisition/Acquisition.h0000644000175000017500000000515112214017433023446 0ustar arnauarnau/***************************************************************************** Copyright (c) 1996-2002 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #ifndef __ACQUISITION_H_ #define __ACQUISITION_H_ typedef struct { PyObject *(*AQ_Acquire) (PyObject *obj, PyObject *name, PyObject *filter, PyObject *extra, int explicit, PyObject *deflt, int containment); PyObject *(*AQ_Get) (PyObject *obj, PyObject *name, PyObject *deflt, int containment); int (*AQ_IsWrapper) (PyObject *obj); PyObject *(*AQ_Base) (PyObject *obj); PyObject *(*AQ_Parent) (PyObject *obj); PyObject *(*AQ_Self) (PyObject *obj); PyObject *(*AQ_Inner) (PyObject *obj); PyObject *(*AQ_Chain) (PyObject *obj, int containment); } ACQUISITIONCAPI; #ifndef _IN_ACQUISITION_C #define aq_Acquire(obj, name, filter, extra, explicit, deflt, containment ) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Acquire(obj, name, filter, extra, explicit, deflt, containment))) #define aq_acquire(obj, name) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Acquire(obj, name, NULL, NULL, 1, NULL, 0))) #define aq_get(obj, name, deflt, containment) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Get(obj, name, deflt, containment))) #define aq_isWrapper(obj) (AcquisitionCAPI == NULL ? -1 : (AcquisitionCAPI->AQ_IsWrapper(obj))) #define aq_base(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Base(obj))) #define aq_parent(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Parent(obj))) #define aq_self(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Self(obj))) #define aq_inner(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Inner(obj))) #define aq_chain(obj, containment) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_CHain(obj, containment))) static ACQUISITIONCAPI *AcquisitionCAPI = NULL; #define aq_init() { \ PyObject *module; \ PyObject *api; \ if (! (module = PyImport_ImportModule("Acquisition"))) return; \ if (! (api = PyObject_GetAttrString(module,"AcquisitionCAPI"))) return; \ Py_DECREF(module); \ AcquisitionCAPI = PyCObject_AsVoidPtr(api); \ Py_DECREF(api); \ } #endif #endif zope2.13-2.13.21/source/Acquisition/src/Acquisition/interfaces.py0000644000175000017500000000357312214017433023510 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Acquisition z3 interfaces. $Id: interfaces.py 119856 2011-01-23 21:46:42Z ldr $ """ from zope.interface import Attribute from zope.interface import Interface class IAcquirer(Interface): """Acquire attributes from containers. """ def __of__(context): """Get the object in a context. """ class IAcquisitionWrapper(Interface): """Wrapper object for acquisition. """ def aq_acquire(name, filter=None, extra=None, explicit=True, default=0, containment=0): """Get an attribute, acquiring it if necessary. """ def aq_inContextOf(obj, inner=1): """Test whether the object is currently in the context of the argument. """ aq_base = Attribute( """Get the object unwrapped.""" ) aq_parent = Attribute( """Get the parent of an object.""" ) aq_self = Attribute( """Get the object with the outermost wrapper removed.""" ) aq_inner = Attribute( """Get the object with all but the innermost wrapper removed.""" ) aq_chain = Attribute( """Get a list of objects in the acquisition environment.""" ) aq_explicit = Attribute( """Get the object with an explicit acquisition wrapper.""" ) zope2.13-2.13.21/source/Acquisition/src/Acquisition/__init__.py0000644000175000017500000000054012214017433023113 0ustar arnauarnaufrom zope.interface import classImplements from _Acquisition import * from interfaces import IAcquirer from interfaces import IAcquisitionWrapper classImplements(Explicit, IAcquirer) classImplements(ExplicitAcquisitionWrapper, IAcquisitionWrapper) classImplements(Implicit, IAcquirer) classImplements(ImplicitAcquisitionWrapper, IAcquisitionWrapper) zope2.13-2.13.21/source/Acquisition/src/Acquisition/README.txt0000644000175000017500000003247512214017433022514 0ustar arnauarnau.. contents:: Introductory Example ==================== Zope implements acquisition with "Extension Class" mix-in classes. To use acquisition your classes must inherit from an acquisition base class. For example:: >>> import ExtensionClass, Acquisition >>> class C(ExtensionClass.Base): ... color='red' >>> class A(Acquisition.Implicit): ... def report(self): ... print self.color ... >>> a = A() >>> c = C() >>> c.a = a >>> c.a.report() red >>> d = C() >>> d.color = 'green' >>> d.a = a >>> d.a.report() green >>> a.report() # raises an attribute error Traceback (most recent call last): ... AttributeError: color The class ``A`` inherits acquisition behavior from ``Acquisition.Implicit``. The object, ``a``, "has" the color of objects ``c`` and d when it is accessed through them, but it has no color by itself. The object ``a`` obtains attributes from its environment, where its environment is defined by the access path used to reach ``a``. Acquisition Wrappers ==================== When an object that supports acquisition is accessed through an extension class instance, a special object, called an acquisition wrapper, is returned. In the example above, the expression ``c.a`` returns an acquisition wrapper that contains references to both ``c`` and ``a``. It is this wrapper that performs attribute lookup in ``c`` when an attribute cannot be found in ``a``. Acquisition wrappers provide access to the wrapped objects through the attributes ``aq_parent``, ``aq_self``, ``aq_base``. Continue the example from above:: >>> c.a.aq_parent is c True >>> c.a.aq_self is a True Explicit and Implicit Acquisition ================================= Two styles of acquisition are supported: implicit and explicit acquisition. Implicit acquisition -------------------- Implicit acquisition is so named because it searches for attributes from the environment automatically whenever an attribute cannot be obtained directly from an object or through inheritance. An attribute can be implicitly acquired if its name does not begin with an underscore. To support implicit acquisition, your class should inherit from the mix-in class ``Acquisition.Implicit``. Explicit Acquisition -------------------- When explicit acquisition is used, attributes are not automatically obtained from the environment. Instead, the method aq_acquire must be used. For example:: >>> print c.a.aq_acquire('color') red To support explicit acquisition, your class should inherit from the mix-in class ``Acquisition.Explicit``. Controlling Acquisition ----------------------- A class (or instance) can provide attribute by attribute control over acquisition. Your should subclass from ``Acquisition.Explicit``, and set all attributes that should be acquired to the special value ``Acquisition.Acquired``. Setting an attribute to this value also allows inherited attributes to be overridden with acquired ones. For example:: >>> class C(Acquisition.Explicit): ... id=1 ... secret=2 ... color=Acquisition.Acquired ... __roles__=Acquisition.Acquired The only attributes that are automatically acquired from containing objects are color, and ``__roles__``. Note that the ``__roles__`` attribute is acquired even though its name begins with an underscore. In fact, the special ``Acquisition.Acquired`` value can be used in ``Acquisition.Implicit`` objects to implicitly acquire selected objects that smell like private objects. Sometimes, you want to dynamically make an implicitly acquiring object acquire explicitly. You can do this by getting the object's aq_explicit attribute. This attribute provides the object with an explicit wrapper that replaces the original implicit wrapper. Filtered Acquisition ==================== The acquisition method, ``aq_acquire``, accepts two optional arguments. The first of the additional arguments is a "filtering" function that is used when considering whether to acquire an object. The second of the additional arguments is an object that is passed as extra data when calling the filtering function and which defaults to ``None``. The filter function is called with five arguments: * The object that the aq_acquire method was called on, * The object where an object was found, * The name of the object, as passed to aq_acquire, * The object found, and * The extra data passed to aq_acquire. If the filter returns a true object that the object found is returned, otherwise, the acquisition search continues. Here's an example:: >>> from Acquisition import Explicit >>> class HandyForTesting: ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return "%s(%s)" % (self.name, self.__class__.__name__) ... __repr__=__str__ ... >>> class E(Explicit, HandyForTesting): pass ... >>> class Nice(HandyForTesting): ... isNice= 1 ... def __str__(self): ... return HandyForTesting.__str__(self)+' and I am nice!' ... __repr__=__str__ ... >>> a = E('a') >>> a.b = E('b') >>> a.b.c = E('c') >>> a.p = Nice('spam') >>> a.b.p = E('p') >>> def find_nice(self, ancestor, name, object, extra): ... return hasattr(object,'isNice') and object.isNice >>> print a.b.c.aq_acquire('p', find_nice) spam(Nice) and I am nice! The filtered acquisition in the last line skips over the first attribute it finds with the name ``p``, because the attribute doesn't satisfy the condition given in the filter. Filtered acquisition is rarely used in Zope. Acquiring from Context ====================== Normally acquisition allows objects to acquire data from their containers. However an object can acquire from objects that aren't its containers. Most of the examples we've seen so far show establishing of an acquisition context using getattr semantics. For example, ``a.b`` is a reference to ``b`` in the context of ``a``. You can also manually set acquisition context using the ``__of__`` method. For example:: >>> from Acquisition import Implicit >>> class C(Implicit): pass ... >>> a = C() >>> b = C() >>> a.color = "red" >>> print b.__of__(a).color red In this case, ``a`` does not contain ``b``, but it is put in ``b``'s context using the ``__of__`` method. Here's another subtler example that shows how you can construct an acquisition context that includes non-container objects:: >>> from Acquisition import Implicit >>> class C(Implicit): ... def __init__(self, name): ... self.name = name >>> a = C("a") >>> a.b = C("b") >>> a.b.color = "red" >>> a.x = C("x") >>> print a.b.x.color red Even though ``b`` does not contain ``x``, ``x`` can acquire the color attribute from ``b``. This works because in this case, ``x`` is accessed in the context of ``b`` even though it is not contained by ``b``. Here acquisition context is defined by the objects used to access another object. Containment Before Context ========================== If in the example above suppose both a and b have an color attribute:: >>> a = C("a") >>> a.color = "green" >>> a.b = C("b") >>> a.b.color = "red" >>> a.x = C("x") >>> print a.b.x.color green Why does ``a.b.x.color`` acquire color from ``a`` and not from ``b``? The answer is that an object acquires from its containers before non-containers in its context. To see why consider this example in terms of expressions using the ``__of__`` method:: a.x -> x.__of__(a) a.b -> b.__of__(a) a.b.x -> x.__of__(a).__of__(b.__of__(a)) Keep in mind that attribute lookup in a wrapper is done by trying to look up the attribute in the wrapped object first and then in the parent object. So in the expressions above proceeds from left to right. The upshot of these rules is that attributes are looked up by containment before context. This rule holds true also for more complex examples. For example, ``a.b.c.d.e.f.g.attribute`` would search for attribute in ``g`` and all its containers first. (Containers are searched in order from the innermost parent to the outermost container.) If the attribute is not found in ``g`` or any of its containers, then the search moves to ``f`` and all its containers, and so on. Additional Attributes and Methods ================================= You can use the special method ``aq_inner`` to access an object wrapped only by containment. So in the example above, ``a.b.x.aq_inner`` is equivalent to ``a.x``. You can find out the acquisition context of an object using the aq_chain method like so: >>> [obj.name for obj in a.b.x.aq_chain] ['x', 'b', 'a'] You can find out if an object is in the containment context of another object using the ``aq_inContextOf`` method. For example: >>> a.b.aq_inContextOf(a) 1 .. Note: as of this writing the aq_inContextOf examples don't work the way they should be working. According to Jim, this is because aq_inContextOf works by comparing object pointer addresses, which (because they are actually different wrapper objects) doesn't give you the expected results. He acknowledges that this behavior is controversial, and says that there is a collector entry to change it so that you would get the answer you expect in the above. (We just need to get to it). Acquisition Module Functions ============================ In addition to using acquisition attributes and methods directly on objects you can use similar functions defined in the ``Acquisition`` module. These functions have the advantage that you don't need to check to make sure that the object has the method or attribute before calling it. ``aq_acquire(object, name [, filter, extra, explicit, default, containment])`` Acquires an object with the given name. This function can be used to explictly acquire when using explicit acquisition and to acquire names that wouldn't normally be acquired. The function accepts a number of optional arguments: ``filter`` A callable filter object that is used to decide if an object should be acquired. The filter is called with five arguments: * The object that the aq_acquire method was called on, * The object where an object was found, * The name of the object, as passed to aq_acquire, * The object found, and * The extra argument passed to aq_acquire. If the filter returns a true object that the object found is returned, otherwise, the acquisition search continues. ``extra`` Extra data to be passed as the last argument to the filter. ``explicit`` A flag (boolean value) indicating whether explicit acquisition should be used. The default value is true. If the flag is true, then acquisition will proceed regardless of whether wrappers encountered in the search of the acquisition hierarchy are explicit or implicit wrappers. If the flag is false, then parents of explicit wrappers are not searched. This argument is useful if you want to apply a filter without overriding explicit wrappers. ``default`` A default value to return if no value can be acquired. ``containment`` A flag indicating whether the search should be limited to the containment hierarchy. In addition, arguments can be provided as keywords. ``aq_base(object)`` Return the object with all wrapping removed. ``aq_chain(object [, containment])`` Return a list containing the object and it's acquisition parents. The optional argument, containment, controls whether the containment or access hierarchy is used. ``aq_get(object, name [, default, containment])`` Acquire an attribute, name. A default value can be provided, as can a flag that limits search to the containment hierarchy. ``aq_inner(object)`` Return the object with all but the innermost layer of wrapping removed. ``aq_parent(object)`` Return the acquisition parent of the object or None if the object is unwrapped. ``aq_self(object)`` Return the object with one layer of wrapping removed, unless the object is unwrapped, in which case the object is returned. In most cases it is more convenient to use these module functions instead of the acquisition attributes and methods directly. Acquisition and Methods ======================= Python methods of objects that support acquisition can use acquired attributes. When a Python method is called on an object that is wrapped by an acquisition wrapper, the wrapper is passed to the method as the first argument. This rule also applies to user-defined method types and to C methods defined in pure mix-in classes. Unfortunately, C methods defined in extension base classes that define their own data structures, cannot use aquired attributes at this time. This is because wrapper objects do not conform to the data structures expected by these methods. In practice, you will seldom find this a problem. Conclusion ========== Acquisition provides a powerful way to dynamically share information between objects. Zope 2 uses acquisition for a number of its key features including security, object publishing, and DTML variable lookup. Acquisition also provides an elegant solution to the problem of circular references for many classes of problems. While acquisition is powerful, you should take care when using acquisition in your applications. The details can get complex, especially with the differences between acquiring from context and acquiring from containment. zope2.13-2.13.21/source/Acquisition/src/Acquisition/_Acquisition.c0000644000175000017500000014255612214017433023613 0ustar arnauarnau/***************************************************************************** Copyright (c) 1996-2003 Zope Foundation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ #include "ExtensionClass/ExtensionClass.h" #define _IN_ACQUISITION_C #include "Acquisition/Acquisition.h" static ACQUISITIONCAPI AcquisitionCAPI; static void PyVar_Assign(PyObject **v, PyObject *e) { Py_XDECREF(*v); *v=e; } #define ASSIGN(V,E) PyVar_Assign(&(V),(E)) #define UNLESS(E) if (!(E)) #define UNLESS_ASSIGN(V,E) ASSIGN(V,E); UNLESS(V) #define OBJECT(O) ((PyObject*)(O)) #define FORMAT_N "n" #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) typedef int Py_ssize_t; typedef Py_ssize_t (*lenfunc)(PyObject *); typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t); typedef PyObject *(*ssizessizeargfunc)(PyObject *, Py_ssize_t, Py_ssize_t); typedef int(*ssizeobjargproc)(PyObject *, Py_ssize_t, PyObject *); typedef int(*ssizessizeobjargproc)(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); #define PY_SSIZE_T_MAX INT_MAX #define PY_SSIZE_T_MIN INT_MIN #define FORMAT_N "i" #endif static PyObject *py__add__, *py__sub__, *py__mul__, *py__div__, *py__mod__, *py__pow__, *py__divmod__, *py__lshift__, *py__rshift__, *py__and__, *py__or__, *py__xor__, *py__coerce__, *py__neg__, *py__pos__, *py__abs__, *py__nonzero__, *py__invert__, *py__int__, *py__long__, *py__float__, *py__oct__, *py__hex__, *py__getitem__, *py__setitem__, *py__delitem__, *py__getslice__, *py__setslice__, *py__delslice__, *py__contains__, *py__len__, *py__of__, *py__call__, *py__repr__, *py__str__, *py__unicode__, *py__cmp__, *py__parent__, *py__iter__; static PyObject *Acquired=0; static void init_py_names(void) { #define INIT_PY_NAME(N) py ## N = PyString_FromString(#N) INIT_PY_NAME(__add__); INIT_PY_NAME(__sub__); INIT_PY_NAME(__mul__); INIT_PY_NAME(__div__); INIT_PY_NAME(__mod__); INIT_PY_NAME(__pow__); INIT_PY_NAME(__divmod__); INIT_PY_NAME(__lshift__); INIT_PY_NAME(__rshift__); INIT_PY_NAME(__and__); INIT_PY_NAME(__or__); INIT_PY_NAME(__xor__); INIT_PY_NAME(__coerce__); INIT_PY_NAME(__neg__); INIT_PY_NAME(__pos__); INIT_PY_NAME(__abs__); INIT_PY_NAME(__nonzero__); INIT_PY_NAME(__invert__); INIT_PY_NAME(__int__); INIT_PY_NAME(__long__); INIT_PY_NAME(__float__); INIT_PY_NAME(__oct__); INIT_PY_NAME(__hex__); INIT_PY_NAME(__getitem__); INIT_PY_NAME(__setitem__); INIT_PY_NAME(__delitem__); INIT_PY_NAME(__getslice__); INIT_PY_NAME(__setslice__); INIT_PY_NAME(__delslice__); INIT_PY_NAME(__contains__); INIT_PY_NAME(__len__); INIT_PY_NAME(__of__); INIT_PY_NAME(__call__); INIT_PY_NAME(__repr__); INIT_PY_NAME(__str__); INIT_PY_NAME(__unicode__); INIT_PY_NAME(__cmp__); INIT_PY_NAME(__parent__); INIT_PY_NAME(__iter__); #undef INIT_PY_NAME } static PyObject * CallMethodO(PyObject *self, PyObject *name, PyObject *args, PyObject *kw) { if (! args && PyErr_Occurred()) return NULL; UNLESS(name=PyObject_GetAttr(self,name)) { if (args) { Py_DECREF(args); } return NULL; } ASSIGN(name,PyEval_CallObjectWithKeywords(name,args,kw)); if (args) { Py_DECREF(args); } return name; } #define Build Py_BuildValue /* For obscure reasons, we need to use tp_richcompare instead of tp_compare. * The comparisons here all most naturally compute a cmp()-like result. * This little helper turns that into a bool result for rich comparisons. */ static PyObject * diff_to_bool(int diff, int op) { PyObject *result; int istrue; switch (op) { case Py_EQ: istrue = diff == 0; break; case Py_NE: istrue = diff != 0; break; case Py_LE: istrue = diff <= 0; break; case Py_GE: istrue = diff >= 0; break; case Py_LT: istrue = diff < 0; break; case Py_GT: istrue = diff > 0; break; default: assert(! "op unknown"); istrue = 0; /* To shut up compiler */ } result = istrue ? Py_True : Py_False; Py_INCREF(result); return result; } /* Declarations for objects of type Wrapper */ typedef struct { PyObject_HEAD PyObject *obj; PyObject *container; } Wrapper; staticforward PyExtensionClass Wrappertype, XaqWrappertype; #define isWrapper(O) ((O)->ob_type==(PyTypeObject*)&Wrappertype || \ (O)->ob_type==(PyTypeObject*)&XaqWrappertype) #define WRAPPER(O) ((Wrapper*)(O)) static int Wrapper__init__(Wrapper *self, PyObject *args, PyObject *kwargs) { PyObject *obj, *container; if (kwargs && PyDict_Size(kwargs) != 0) { PyErr_SetString(PyExc_TypeError, "kwyword arguments not allowed"); return -1; } UNLESS(PyArg_ParseTuple(args, "OO:__init__", &obj, &container)) return -1; if (self == WRAPPER(obj)) { PyErr_SetString(PyExc_ValueError, "Cannot wrap acquisition wrapper in itself (Wrapper__init__)"); return -1; } Py_INCREF(obj); self->obj=obj; if (container != Py_None) { Py_INCREF(container); self->container=container; } return 0; } /* ---------------------------------------------------------------- */ static PyObject * __of__(PyObject *inst, PyObject *parent) { PyObject *r, *t; UNLESS(r=PyObject_GetAttr(inst, py__of__)) return NULL; UNLESS(t=PyTuple_New(1)) goto err; Py_INCREF(parent); PyTuple_SET_ITEM(t,0,parent); ASSIGN(r,PyObject_CallObject(r,t)); Py_DECREF(t); if (r != NULL && isWrapper(r) && WRAPPER(r)->container && isWrapper(WRAPPER(r)->container) ) while (WRAPPER(r)->obj && isWrapper(WRAPPER(r)->obj) && (WRAPPER(WRAPPER(r)->obj)->container == WRAPPER(WRAPPER(r)->container)->obj) ) { if (r->ob_refcnt !=1 ) { t = PyObject_CallFunctionObjArgs((PyObject *)(r->ob_type), WRAPPER(r)->obj, WRAPPER(r)->container, NULL); Py_DECREF(r); if (t==NULL) return NULL; r = t; } /* Simplify wrapper */ Py_XINCREF(WRAPPER(WRAPPER(r)->obj)->obj); ASSIGN(WRAPPER(r)->obj, WRAPPER(WRAPPER(r)->obj)->obj); } return r; err: Py_DECREF(r); return NULL; } static PyObject * Wrapper_descrget(Wrapper *self, PyObject *inst, PyObject *cls) { if (inst == NULL) { Py_INCREF(self); return (PyObject *)self; } return __of__((PyObject *)self, inst); } #define newWrapper(obj, container, Wrappertype) \ PyObject_CallFunctionObjArgs(OBJECT(Wrappertype), obj, container, NULL) static int Wrapper_traverse(Wrapper *self, visitproc visit, void *arg) { int vret; if (self->obj) { vret = visit(self->obj, arg); if (vret != 0) return vret; } if (self->container) { vret = visit(self->container, arg); if (vret != 0) return vret; } return 0; } static int Wrapper_clear(Wrapper *self) { PyObject *tmp; tmp = self->obj; self->obj = NULL; Py_XDECREF(tmp); tmp = self->container; self->container = NULL; Py_XDECREF(tmp); return 0; } static void Wrapper_dealloc(Wrapper *self) { Wrapper_clear(self); self->ob_type->tp_free((PyObject*)self); } static PyObject * Wrapper_special(Wrapper *self, char *name, PyObject *oname) { PyObject *r=0; switch(*name) { case 'b': if (strcmp(name,"base")==0) { if (self->obj) { r=self->obj; while (isWrapper(r) && WRAPPER(r)->obj) r=WRAPPER(r)->obj; } else r=Py_None; Py_INCREF(r); return r; } break; case 'p': if (strcmp(name,"parent")==0) { if (self->container) r=self->container; else r=Py_None; Py_INCREF(r); return r; } break; case 's': if (strcmp(name,"self")==0) { if (self->obj) r=self->obj; else r=Py_None; Py_INCREF(r); return r; } break; case 'e': if (strcmp(name,"explicit")==0) { if (self->ob_type != (PyTypeObject *)&XaqWrappertype) return newWrapper(self->obj, self->container, (PyTypeObject *)&XaqWrappertype); Py_INCREF(self); return OBJECT(self); } break; case 'a': if (strcmp(name,"acquire")==0) { return Py_FindAttr(OBJECT(self),oname); } break; case 'c': if (strcmp(name,"chain")==0) { if ((r = PyList_New(0))) while (1) { if (PyList_Append(r,OBJECT(self)) >= 0) { if (isWrapper(self) && self->container) { self=WRAPPER(self->container); continue; } } else { Py_DECREF(r); } break; } return r; } break; case 'i': if (strcmp(name,"inContextOf")==0) { return Py_FindAttr(OBJECT(self),oname); } if (strcmp(name,"inner")==0) { if (self->obj) { r=self->obj; while (isWrapper(r) && WRAPPER(r)->obj) { self=WRAPPER(r); r=WRAPPER(r)->obj; } r=OBJECT(self); } else r=Py_None; Py_INCREF(r); return r; } break; case 'u': if (strcmp(name,"uncle")==0) { return PyString_FromString("Bob"); } break; } return NULL; } static int apply_filter(PyObject *filter, PyObject *inst, PyObject *oname, PyObject *r, PyObject *extra, PyObject *orig) { /* Calls the filter, passing arguments. Returns 1 if the filter accepts the value, 0 if not, -1 if an exception occurred. Note the special reference counting rule: This function decrements the refcount of 'r' when it returns 0 or -1. When it returns 1, it leaves the refcount unchanged. */ PyObject *fr; int ir; UNLESS(fr=PyTuple_New(5)) goto err; PyTuple_SET_ITEM(fr,0,orig); Py_INCREF(orig); PyTuple_SET_ITEM(fr,1,inst); Py_INCREF(inst); PyTuple_SET_ITEM(fr,2,oname); Py_INCREF(oname); PyTuple_SET_ITEM(fr,3,r); Py_INCREF(r); PyTuple_SET_ITEM(fr,4,extra); Py_INCREF(extra); UNLESS_ASSIGN(fr,PyObject_CallObject(filter, fr)) goto err; ir=PyObject_IsTrue(fr); Py_DECREF(fr); if (ir) return 1; Py_DECREF(r); return 0; err: Py_DECREF(r); return -1; } static PyObject * Wrapper_acquire(Wrapper *self, PyObject *oname, PyObject *filter, PyObject *extra, PyObject *orig, int explicit, int containment); static PyObject * Wrapper_findattr(Wrapper *self, PyObject *oname, PyObject *filter, PyObject *extra, PyObject *orig, int sob, int sco, int explicit, int containment) /* Parameters: sob Search self->obj for the 'oname' attribute sco Search self->container for the 'oname' attribute explicit Explicitly acquire 'oname' attribute from container (assumed with implicit acquisition wrapper) containment Use the innermost wrapper ("aq_inner") for looking up the 'oname' attribute. */ { PyObject *r, *v, *tb; char *name=""; if (PyString_Check(oname)) name=PyString_AS_STRING(oname); if ((*name=='a' && name[1]=='q' && name[2]=='_') || (strcmp(name, "__parent__")==0)) { /* __parent__ is an alias to aq_parent */ if (strcmp(name, "__parent__")==0) name = "parent"; else name = name + 3; if ((r=Wrapper_special(self, name, oname))) { if (filter) switch(apply_filter(filter,OBJECT(self),oname,r,extra,orig)) { case -1: return NULL; case 1: return r; } else return r; } else PyErr_Clear(); } else if (*name=='_' && name[1]=='_' && (strcmp(name+2,"reduce__")==0 || strcmp(name+2,"reduce_ex__")==0 || strcmp(name+2,"getstate__")==0 )) return PyObject_GenericGetAttr(((PyObject*)self), oname); /* If we are doing a containment search, then replace self with aq_inner */ if (containment) while (self->obj && isWrapper(self->obj)) self=WRAPPER(self->obj); if (sob && self->obj) { if (isWrapper(self->obj)) { if (self == WRAPPER(self->obj)) { PyErr_SetString(PyExc_RuntimeError, "Recursion detected in acquisition wrapper"); return NULL; } if ((r=Wrapper_findattr(WRAPPER(self->obj), oname, filter, extra, orig, 1, /* Search object container if explicit, or object is implicit acquirer */ explicit || self->obj->ob_type == (PyTypeObject*)&Wrappertype, explicit, containment))) { if (PyECMethod_Check(r) && PyECMethod_Self(r)==self->obj) ASSIGN(r,PyECMethod_New(r,OBJECT(self))); else if (has__of__(r)) ASSIGN(r,__of__(r,OBJECT(self))); return r; } PyErr_Fetch(&r,&v,&tb); if (r && (r != PyExc_AttributeError)) { PyErr_Restore(r,v,tb); return NULL; } Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb); r=NULL; } /* normal attribute lookup */ else if ((r=PyObject_GetAttr(self->obj,oname))) { if (r==Acquired) { Py_DECREF(r); return Wrapper_acquire(self, oname, filter, extra, orig, 1, containment); } if (PyECMethod_Check(r) && PyECMethod_Self(r)==self->obj) ASSIGN(r,PyECMethod_New(r,OBJECT(self))); else if (has__of__(r)) ASSIGN(r,__of__(r,OBJECT(self))); if (r && filter) switch(apply_filter(filter,OBJECT(self),oname,r,extra,orig)) { case -1: return NULL; case 1: return r; } else return r; } else { PyErr_Fetch(&r,&v,&tb); if (r != PyExc_AttributeError) { PyErr_Restore(r,v,tb); return NULL; } Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb); r=NULL; } PyErr_Clear(); } /* Lookup has failed, acquire it from parent. */ if (sco && (*name != '_' || explicit)) return Wrapper_acquire(self, oname, filter, extra, orig, explicit, containment); PyErr_SetObject(PyExc_AttributeError,oname); return NULL; } static PyObject * Wrapper_acquire(Wrapper *self, PyObject *oname, PyObject *filter, PyObject *extra, PyObject *orig, int explicit, int containment) { PyObject *r, *v, *tb; int sob=1, sco=1; if (self->container) { /* If the container has an acquisition wrapper itself, we'll use Wrapper_findattr to progress further. */ if (isWrapper(self->container)) { if (self->obj && isWrapper(self->obj)) { /* Try to optimize search by recognizing repeated objects in path. */ if (WRAPPER(self->obj)->container== WRAPPER(self->container)->container) sco=0; else if (WRAPPER(self->obj)->container== WRAPPER(self->container)->obj) sob=0; } /* Don't search the container when the container of the container is the same object as 'self'. */ if (WRAPPER(self->container)->container == WRAPPER(self)->obj) { sco=0; containment=1; } r=Wrapper_findattr((Wrapper*)self->container, oname, filter, extra, orig, sob, sco, explicit, containment); if (r && has__of__(r)) ASSIGN(r,__of__(r,OBJECT(self))); return r; } /* If the container has a __parent__ pointer, we create an acquisition wrapper for it accordingly. Then we can proceed with Wrapper_findattr, just as if the container had an acquisition wrapper in the first place (see above). */ else if ((r = PyObject_GetAttr(self->container, py__parent__))) { ASSIGN(self->container, newWrapper(self->container, r, (PyTypeObject*)&Wrappertype)); /* Don't search the container when the parent of the parent is the same object as 'self' */ if (WRAPPER(r)->obj == WRAPPER(self)->obj) sco=0; Py_DECREF(r); /* don't need __parent__ anymore */ r=Wrapper_findattr((Wrapper*)self->container, oname, filter, extra, orig, sob, sco, explicit, containment); /* There's no need to DECREF the wrapper here because it's not stored in self->container, thus 'self' owns its reference now */ return r; } /* The container is the end of the acquisition chain; if we can't look up the attribute here, we can't look it up at all. */ else { /* We need to clean up the AttributeError from the previous getattr (because it has clearly failed). */ PyErr_Fetch(&r,&v,&tb); if (r && (r != PyExc_AttributeError)) { PyErr_Restore(r,v,tb); return NULL; } Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb); r=NULL; if ((r=PyObject_GetAttr(self->container,oname))) { if (r == Acquired) { Py_DECREF(r); } else { if (filter) { switch(apply_filter(filter,self->container,oname,r, extra,orig)) { case -1: return NULL; case 1: if (has__of__(r)) ASSIGN(r,__of__(r,OBJECT(self))); return r; } } else { if (has__of__(r)) ASSIGN(r,__of__(r,OBJECT(self))); return r; } } } else { /* May be AttributeError or some other kind of error */ return NULL; } } } PyErr_SetObject(PyExc_AttributeError, oname); return NULL; } static PyObject * Wrapper_getattro(Wrapper *self, PyObject *oname) { if (self->obj || self->container) return Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 1, 0, 0); /* Maybe we are getting initialized? */ return Py_FindAttr(OBJECT(self),oname); } static PyObject * Xaq_getattro(Wrapper *self, PyObject *oname) { char *name=""; /* Special case backward-compatible acquire method. */ if (PyString_Check(oname)) name=PyString_AS_STRING(oname); if (*name=='a' && name[1]=='c' && strcmp(name+2,"quire")==0) return Py_FindAttr(OBJECT(self),oname); if (self->obj || self->container) return Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 0, 0, 0); /* Maybe we are getting initialized? */ return Py_FindAttr(OBJECT(self),oname); } static int Wrapper_setattro(Wrapper *self, PyObject *oname, PyObject *v) { char *name=""; /* Allow assignment to parent, to change context. */ if (PyString_Check(oname)) name=PyString_AS_STRING(oname); if ((*name=='a' && name[1]=='q' && name[2]=='_' && strcmp(name+3,"parent")==0) || (strcmp(name, "__parent__")==0)) { Py_XINCREF(v); ASSIGN(self->container, v); return 0; } if (self->obj) { /* Unwrap passed in wrappers! */ while (v && isWrapper(v)) v=WRAPPER(v)->obj; if (v) return PyObject_SetAttr(self->obj, oname, v); else return PyObject_DelAttr(self->obj, oname); } PyErr_SetString(PyExc_AttributeError, "Attempt to set attribute on empty acquisition wrapper"); return -1; } static int Wrapper_compare(Wrapper *self, PyObject *w) { PyObject *obj, *wobj; PyObject *m; int r; if (OBJECT(self) == w) return 0; UNLESS (m=PyObject_GetAttr(OBJECT(self), py__cmp__)) { /* Unwrap self completely -> obj. */ while (self->obj && isWrapper(self->obj)) self=WRAPPER(self->obj); obj = self->obj; /* Unwrap w completely -> wobj. */ if (isWrapper(w)) { while (WRAPPER(w)->obj && isWrapper(WRAPPER(w)->obj)) w=WRAPPER(w)->obj; wobj = WRAPPER(w)->obj; } else wobj = w; PyErr_Clear(); if (obj == wobj) return 0; return (obj < w) ? -1 : 1; } ASSIGN(m, PyObject_CallFunction(m, "O", w)); UNLESS (m) return -1; r=PyInt_AsLong(m); Py_DECREF(m); return r; } static PyObject * Wrapper_richcompare(Wrapper *self, PyObject *w, int op) { int diff = Wrapper_compare(self, w); return diff_to_bool(diff, op); } static PyObject * Wrapper_repr(Wrapper *self) { PyObject *r; if ((r=PyObject_GetAttr(OBJECT(self),py__repr__))) { ASSIGN(r,PyObject_CallFunction(r,NULL,NULL)); return r; } else { PyErr_Clear(); return PyObject_Repr(self->obj); } } static PyObject * Wrapper_str(Wrapper *self) { PyObject *r; if ((r=PyObject_GetAttr(OBJECT(self),py__str__))) { ASSIGN(r,PyObject_CallFunction(r,NULL,NULL)); return r; } else { PyErr_Clear(); return PyObject_Str(self->obj); } } static PyObject * Wrapper_unicode(Wrapper *self) { PyObject *r; if ((r=PyObject_GetAttr(OBJECT(self),py__unicode__))) { ASSIGN(r,PyObject_CallFunction(r,NULL,NULL)); return r; } else { PyErr_Clear(); return Wrapper_str(self); } } static long Wrapper_hash(Wrapper *self) { return PyObject_Hash(self->obj); } static PyObject * Wrapper_call(Wrapper *self, PyObject *args, PyObject *kw) { Py_INCREF(args); return CallMethodO(OBJECT(self),py__call__,args,kw); } /* Code to handle accessing Wrapper objects as sequence objects */ static Py_ssize_t Wrapper_length(Wrapper *self) { long l; PyObject *r; UNLESS(r=PyObject_GetAttr(OBJECT(self), py__len__)) return -1; UNLESS_ASSIGN(r,PyObject_CallObject(r,NULL)) return -1; l=PyInt_AsLong(r); Py_DECREF(r); return l; } static PyObject * Wrapper_add(Wrapper *self, PyObject *bb) { return CallMethodO(OBJECT(self),py__add__,Build("(O)", bb) ,NULL); } static PyObject * Wrapper_mul(Wrapper *self, Py_ssize_t n) { return CallMethodO(OBJECT(self),py__mul__,Build("(" FORMAT_N ")", n),NULL); } static PyObject * Wrapper_item(Wrapper *self, Py_ssize_t i) { return CallMethodO(OBJECT(self),py__getitem__, Build("(" FORMAT_N ")", i),NULL); } static PyObject * Wrapper_slice(Wrapper *self, Py_ssize_t ilow, Py_ssize_t ihigh) { return CallMethodO(OBJECT(self),py__getslice__, Build("(" FORMAT_N FORMAT_N ")", ilow, ihigh),NULL); } static int Wrapper_ass_item(Wrapper *self, Py_ssize_t i, PyObject *v) { if (v) { UNLESS(v=CallMethodO(OBJECT(self),py__setitem__, Build("(" FORMAT_N "O)", i, v),NULL)) return -1; } else { UNLESS(v=CallMethodO(OBJECT(self),py__delitem__, Build("(" FORMAT_N ")", i),NULL)) return -1; } Py_DECREF(v); return 0; } static int Wrapper_ass_slice(Wrapper *self, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) { if (v) { UNLESS(v=CallMethodO(OBJECT(self),py__setslice__, Build("(" FORMAT_N FORMAT_N "O)", ilow, ihigh, v),NULL)) return -1; } else { UNLESS(v=CallMethodO(OBJECT(self),py__delslice__, Build("(" FORMAT_N FORMAT_N ")", ilow, ihigh),NULL)) return -1; } Py_DECREF(v); return 0; } static int Wrapper_contains(Wrapper *self, PyObject *v) { long c; UNLESS(v=CallMethodO(OBJECT(self),py__contains__,Build("(O)", v) ,NULL)) return -1; c = PyInt_AsLong(v); Py_DECREF(v); return c; } /* Support for iteration cannot rely on the internal implementation of `PyObject_GetIter`, since the `self` passed into `__iter__` and `__getitem__` should be acquisition-wrapped (also see LP 360761): The wrapper obviously supports the iterator protocol so simply calling `PyObject_GetIter(OBJECT(self))` results in an infinite recursion. Instead the base object needs to be checked and the wrapper must only be used when actually calling `__getitem__` or setting up a sequence iterator. */ static PyObject * Wrapper_iter(Wrapper *self) { PyObject *obj = self->obj; PyObject *res; if ((res=PyObject_GetAttr(OBJECT(self),py__iter__))) { ASSIGN(res,PyObject_CallFunction(res,NULL,NULL)); if (res != NULL && !PyIter_Check(res)) { PyErr_Format(PyExc_TypeError, "iter() returned non-iterator " "of type '%.100s'", res->ob_type->tp_name); Py_DECREF(res); res = NULL; } } else if (PySequence_Check(obj)) { ASSIGN(res,PySeqIter_New(OBJECT(self))); } else { res = PyErr_Format(PyExc_TypeError, "iteration over non-sequence"); } return res; } static PySequenceMethods Wrapper_as_sequence = { (lenfunc)Wrapper_length, /*sq_length*/ (binaryfunc)Wrapper_add, /*sq_concat*/ (ssizeargfunc)Wrapper_mul, /*sq_repeat*/ (ssizeargfunc)Wrapper_item, /*sq_item*/ (ssizessizeargfunc)Wrapper_slice, /*sq_slice*/ (ssizeobjargproc)Wrapper_ass_item, /*sq_ass_item*/ (ssizessizeobjargproc)Wrapper_ass_slice, /*sq_ass_slice*/ (objobjproc)Wrapper_contains, /*sq_contains*/ }; /* -------------------------------------------------------------- */ /* Code to access Wrapper objects as mappings */ static PyObject * Wrapper_subscript(Wrapper *self, PyObject *key) { return CallMethodO(OBJECT(self),py__getitem__,Build("(O)", key),NULL); } static int Wrapper_ass_sub(Wrapper *self, PyObject *key, PyObject *v) { if (v) { UNLESS(v=CallMethodO(OBJECT(self),py__setitem__, Build("(OO)", key, v),NULL)) return -1; } else { UNLESS(v=CallMethodO(OBJECT(self),py__delitem__, Build("(O)", key),NULL)) return -1; } Py_XDECREF(v); return 0; } static PyMappingMethods Wrapper_as_mapping = { (lenfunc)Wrapper_length, /*mp_length*/ (binaryfunc)Wrapper_subscript, /*mp_subscript*/ (objobjargproc)Wrapper_ass_sub, /*mp_ass_subscript*/ }; /* -------------------------------------------------------------- */ /* Code to access Wrapper objects as numbers */ static PyObject * Wrapper_sub(Wrapper *self, PyObject *o) { return CallMethodO(OBJECT(self),py__sub__,Build("(O)", o),NULL); } static PyObject * Wrapper_div(Wrapper *self, PyObject *o) { return CallMethodO(OBJECT(self),py__div__,Build("(O)", o),NULL); } static PyObject * Wrapper_mod(Wrapper *self, PyObject *o) { return CallMethodO(OBJECT(self),py__mod__,Build("(O)", o),NULL); } static PyObject * Wrapper_divmod(Wrapper *self, PyObject *o) { return CallMethodO(OBJECT(self),py__divmod__,Build("(O)", o),NULL); } static PyObject * Wrapper_pow(Wrapper *self, PyObject *o, PyObject *m) { return CallMethodO(OBJECT(self),py__pow__,Build("(OO)", o, m),NULL); } static PyObject * Wrapper_neg(Wrapper *self) { return CallMethodO(OBJECT(self), py__neg__, NULL, NULL); } static PyObject * Wrapper_pos(Wrapper *self) { return CallMethodO(OBJECT(self), py__pos__, NULL, NULL); } static PyObject * Wrapper_abs(Wrapper *self) { return CallMethodO(OBJECT(self), py__abs__, NULL, NULL); } static PyObject * Wrapper_invert(Wrapper *self) { return CallMethodO(OBJECT(self), py__invert__, NULL, NULL); } static PyObject * Wrapper_lshift(Wrapper *self, PyObject *o) { return CallMethodO(OBJECT(self),py__lshift__,Build("(O)", o),NULL); } static PyObject * Wrapper_rshift(Wrapper *self, PyObject *o) { return CallMethodO(OBJECT(self),py__rshift__,Build("(O)", o),NULL); } static PyObject * Wrapper_and(Wrapper *self, PyObject *o) { return CallMethodO(OBJECT(self),py__and__,Build("(O)", o),NULL); } static PyObject * Wrapper_xor(Wrapper *self, PyObject *o) { return CallMethodO(OBJECT(self),py__xor__,Build("(O)", o),NULL); } static PyObject * Wrapper_or(Wrapper *self, PyObject *o) { return CallMethodO(OBJECT(self),py__or__,Build("(O)", o),NULL); } static int Wrapper_coerce(Wrapper **self, PyObject **o) { PyObject *m; UNLESS (m=PyObject_GetAttr(OBJECT(*self), py__coerce__)) { PyErr_Clear(); Py_INCREF(*self); Py_INCREF(*o); return 0; } ASSIGN(m, PyObject_CallFunction(m, "O", *o)); UNLESS (m) return -1; UNLESS (PyArg_ParseTuple(m,"OO", self, o)) goto err; Py_INCREF(*self); Py_INCREF(*o); Py_DECREF(m); return 0; err: Py_DECREF(m); return -1; } static PyObject * Wrapper_int(Wrapper *self) { return CallMethodO(OBJECT(self), py__int__, NULL, NULL); } static PyObject * Wrapper_long(Wrapper *self) { return CallMethodO(OBJECT(self), py__long__, NULL, NULL); } static PyObject * Wrapper_float(Wrapper *self) { return CallMethodO(OBJECT(self), py__float__, NULL, NULL); } static PyObject * Wrapper_oct(Wrapper *self) { return CallMethodO(OBJECT(self), py__oct__, NULL, NULL); } static PyObject * Wrapper_hex(Wrapper *self) { return CallMethodO(OBJECT(self), py__hex__, NULL, NULL); } static int Wrapper_nonzero(Wrapper *self) { long l; PyObject *r; UNLESS(r=PyObject_GetAttr(OBJECT(self), py__nonzero__)) { PyErr_Clear(); /* Try len */ UNLESS(r=PyObject_GetAttr(OBJECT(self), py__len__)) { /* No len, it's true :-) */ PyErr_Clear(); return 1; } } UNLESS_ASSIGN(r,PyObject_CallObject(r,NULL)) return -1; l=PyInt_AsLong(r); Py_DECREF(r); return l; } static PyNumberMethods Wrapper_as_number = { (binaryfunc)Wrapper_add, /*nb_add*/ (binaryfunc)Wrapper_sub, /*nb_subtract*/ (binaryfunc)Wrapper_mul, /*nb_multiply*/ (binaryfunc)Wrapper_div, /*nb_divide*/ (binaryfunc)Wrapper_mod, /*nb_remainder*/ (binaryfunc)Wrapper_divmod, /*nb_divmod*/ (ternaryfunc)Wrapper_pow, /*nb_power*/ (unaryfunc)Wrapper_neg, /*nb_negative*/ (unaryfunc)Wrapper_pos, /*nb_positive*/ (unaryfunc)Wrapper_abs, /*nb_absolute*/ (inquiry)Wrapper_nonzero, /*nb_nonzero*/ (unaryfunc)Wrapper_invert, /*nb_invert*/ (binaryfunc)Wrapper_lshift, /*nb_lshift*/ (binaryfunc)Wrapper_rshift, /*nb_rshift*/ (binaryfunc)Wrapper_and, /*nb_and*/ (binaryfunc)Wrapper_xor, /*nb_xor*/ (binaryfunc)Wrapper_or, /*nb_or*/ (coercion)Wrapper_coerce, /*nb_coerce*/ (unaryfunc)Wrapper_int, /*nb_int*/ (unaryfunc)Wrapper_long, /*nb_long*/ (unaryfunc)Wrapper_float, /*nb_float*/ (unaryfunc)Wrapper_oct, /*nb_oct*/ (unaryfunc)Wrapper_hex, /*nb_hex*/ }; /* -------------------------------------------------------- */ static char *acquire_args[] = {"object", "name", "filter", "extra", "explicit", "default", "containment", NULL}; static PyObject * Wrapper_acquire_method(Wrapper *self, PyObject *args, PyObject *kw) { PyObject *name, *filter=0, *extra=Py_None; PyObject *expl=0, *defalt=0; int explicit=1; int containment=0; PyObject *result; /* DM 2005-08-25: argument "default" ignored */ UNLESS (PyArg_ParseTupleAndKeywords( args, kw, "O|OOOOi", acquire_args+1, &name, &filter, &extra, &expl, &defalt, &containment )) return NULL; if (expl) explicit=PyObject_IsTrue(expl); if (filter==Py_None) filter=0; /* DM 2005-08-25: argument "default" ignored -- fix it! */ # if 0 return Wrapper_findattr(self,name,filter,extra,OBJECT(self),1, explicit || self->ob_type==(PyTypeObject*)&Wrappertype, explicit, containment); # else result = Wrapper_findattr(self,name,filter,extra,OBJECT(self),1, explicit || self->ob_type==(PyTypeObject*)&Wrappertype, explicit, containment); if (result == NULL && defalt != NULL) { /* as "Python/bltinmodule.c:builtin_getattr" turn only 'AttributeError' into a default value, such that e.g. "ConflictError" and errors raised by the filter are not mapped to the default value. */ if (PyErr_ExceptionMatches(PyExc_AttributeError)) { PyErr_Clear(); Py_INCREF(defalt); result = defalt; } } return result; # endif } /* forward declaration so that we can use it in Wrapper_inContextOf */ static PyObject * capi_aq_inContextOf(PyObject *self, PyObject *o, int inner); static PyObject * Wrapper_inContextOf(Wrapper *self, PyObject *args) { PyObject *o; int inner=1; UNLESS(PyArg_ParseTuple(args,"O|i",&o,&inner)) return NULL; return capi_aq_inContextOf((PyObject*)self, o, inner); } PyObject * Wrappers_are_not_picklable(PyObject *wrapper, PyObject *args) { PyErr_SetString(PyExc_TypeError, "Can't pickle objects in acquisition wrappers."); return NULL; } static PyObject * Wrapper___getnewargs__(PyObject *self) { return PyTuple_New(0); } static struct PyMethodDef Wrapper_methods[] = { {"acquire", (PyCFunction)Wrapper_acquire_method, METH_VARARGS|METH_KEYWORDS, "Get an attribute, acquiring it if necessary"}, {"aq_acquire", (PyCFunction)Wrapper_acquire_method, METH_VARARGS|METH_KEYWORDS, "Get an attribute, acquiring it if necessary"}, {"aq_inContextOf", (PyCFunction)Wrapper_inContextOf, METH_VARARGS, "Test whether the object is currently in the context of the argument"}, {"__getnewargs__", (PyCFunction)Wrapper___getnewargs__, METH_NOARGS, "Get arguments to be passed to __new__"}, {"__getstate__", (PyCFunction)Wrappers_are_not_picklable, METH_VARARGS, "Wrappers are not picklable"}, {"__reduce__", (PyCFunction)Wrappers_are_not_picklable, METH_VARARGS, "Wrappers are not picklable"}, {"__reduce_ex__", (PyCFunction)Wrappers_are_not_picklable, METH_VARARGS, "Wrappers are not picklable"}, {"__unicode__", (PyCFunction)Wrapper_unicode, METH_NOARGS, "Unicode"}, {NULL, NULL} /* sentinel */ }; static PyExtensionClass Wrappertype = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "Acquisition.ImplicitAcquisitionWrapper", /*tp_name*/ sizeof(Wrapper), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Wrapper_dealloc, /*tp_dealloc*/ (printfunc)0, /*tp_print*/ (getattrfunc)0, /*tp_getattr*/ (setattrfunc)0, /*tp_setattr*/ (cmpfunc)0, /*tp_compare*/ (reprfunc)Wrapper_repr, /*tp_repr*/ &Wrapper_as_number, /*tp_as_number*/ &Wrapper_as_sequence, /*tp_as_sequence*/ &Wrapper_as_mapping, /*tp_as_mapping*/ (hashfunc)Wrapper_hash, /*tp_hash*/ (ternaryfunc)Wrapper_call, /*tp_call*/ (reprfunc)Wrapper_str, /*tp_str*/ (getattrofunc)Wrapper_getattro, /*tp_getattr with object key*/ (setattrofunc)Wrapper_setattro, /*tp_setattr with object key*/ /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif , "Wrapper object for implicit acquisition", /* Documentation string */ /* tp_traverse */ (traverseproc)Wrapper_traverse, /* tp_clear */ (inquiry)Wrapper_clear, /* tp_richcompare */ (richcmpfunc)Wrapper_richcompare, /* tp_weaklistoffset */ (long)0, (getiterfunc)Wrapper_iter, /*tp_iter*/ /* tp_iternext */ (iternextfunc)0, /* tp_methods */ Wrapper_methods, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ (descrgetfunc)Wrapper_descrget, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ (initproc)Wrapper__init__, }; static PyExtensionClass XaqWrappertype = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "Acquisition.ExplicitAcquisitionWrapper", /*tp_name*/ sizeof(Wrapper), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Wrapper_dealloc, /*tp_dealloc*/ (printfunc)0, /*tp_print*/ (getattrfunc)0, /*tp_getattr*/ (setattrfunc)0, /*tp_setattr*/ (cmpfunc)0, /*tp_compare*/ (reprfunc)Wrapper_repr, /*tp_repr*/ &Wrapper_as_number, /*tp_as_number*/ &Wrapper_as_sequence, /*tp_as_sequence*/ &Wrapper_as_mapping, /*tp_as_mapping*/ (hashfunc)Wrapper_hash, /*tp_hash*/ (ternaryfunc)Wrapper_call, /*tp_call*/ (reprfunc)Wrapper_str, /*tp_str*/ (getattrofunc)Xaq_getattro, /*tp_getattr with object key*/ (setattrofunc)Wrapper_setattro, /*tp_setattr with object key*/ /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif , "Wrapper object for implicit acquisition", /* Documentation string */ /* tp_traverse */ (traverseproc)Wrapper_traverse, /* tp_clear */ (inquiry)Wrapper_clear, /* tp_richcompare */ (richcmpfunc)Wrapper_richcompare, /* tp_weaklistoffset */ (long)0, (getiterfunc)Wrapper_iter, /*tp_iter*/ /* tp_iternext */ (iternextfunc)0, /* tp_methods */ Wrapper_methods, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ (descrgetfunc)Wrapper_descrget, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ (initproc)Wrapper__init__, }; static PyObject * acquire_of(PyObject *self, PyObject *args) { PyObject *inst; UNLESS(PyArg_ParseTuple(args, "O", &inst)) return NULL; UNLESS(PyExtensionInstance_Check(inst)) { PyErr_SetString(PyExc_TypeError, "attempt to wrap extension method using an object that\n" "is not an extension class instance."); return NULL; } return newWrapper(self, inst, (PyTypeObject *)&Wrappertype); } static PyObject * xaq_of(PyObject *self, PyObject *args) { PyObject *inst; UNLESS(PyArg_ParseTuple(args, "O", &inst)) return NULL; UNLESS(PyExtensionInstance_Check(inst)) { PyErr_SetString(PyExc_TypeError, "attempt to wrap extension method using an object that\n" "is not an extension class instance."); return NULL; } return newWrapper(self, inst, (PyTypeObject *)&XaqWrappertype); } static struct PyMethodDef Acquirer_methods[] = { {"__of__",(PyCFunction)acquire_of, METH_VARARGS, "__of__(context) -- return the object in a context"}, {NULL, NULL} /* sentinel */ }; static struct PyMethodDef Xaq_methods[] = { {"__of__",(PyCFunction)xaq_of, METH_VARARGS,""}, {NULL, NULL} /* sentinel */ }; static PyObject * capi_aq_acquire(PyObject *self, PyObject *name, PyObject *filter, PyObject *extra, int explicit, PyObject *defalt, int containment) { PyObject *result, *v, *tb; if (filter==Py_None) filter=0; /* We got a wrapped object, so business as usual */ if (isWrapper(self)) return Wrapper_findattr( WRAPPER(self), name, filter, extra, OBJECT(self),1, explicit || WRAPPER(self)->ob_type==(PyTypeObject*)&Wrappertype, explicit, containment); /* Not wrapped; check if we have a __parent__ pointer. If that's the case, create a wrapper and pretend it's business as usual. */ else if ((result = PyObject_GetAttr(self, py__parent__))) { self = newWrapper(self, result, (PyTypeObject*)&Wrappertype); Py_DECREF(result); /* don't need __parent__ anymore */ result = Wrapper_findattr(WRAPPER(self), name, filter, extra, OBJECT(self), 1, 1, explicit, containment); /* Get rid of temporary wrapper */ Py_DECREF(self); return result; } /* No wrapper and no __parent__, so just getattr. */ else { /* Clean up the AttributeError from the previous getattr (because it has clearly failed). */ PyErr_Fetch(&result,&v,&tb); if (result && (result != PyExc_AttributeError)) { PyErr_Restore(result,v,tb); return NULL; } Py_XDECREF(result); Py_XDECREF(v); Py_XDECREF(tb); if (! filter) return PyObject_GetAttr(self, name); /* Crap, we've got to construct a wrapper so we can use Wrapper_findattr */ UNLESS (self=newWrapper(self, Py_None, (PyTypeObject*)&Wrappertype)) return NULL; result=Wrapper_findattr(WRAPPER(self), name, filter, extra, OBJECT(self), 1, 1, explicit, containment); /* Get rid of temporary wrapper */ Py_DECREF(self); return result; } } static PyObject * module_aq_acquire(PyObject *ignored, PyObject *args, PyObject *kw) { PyObject *self; PyObject *name, *filter=0, *extra=Py_None; PyObject *expl=0, *defalt=0; int explicit=1, containment=0; UNLESS (PyArg_ParseTupleAndKeywords( args, kw, "OO|OOOOi", acquire_args, &self, &name, &filter, &extra, &expl, &defalt, &containment )) return NULL; if (expl) explicit=PyObject_IsTrue(expl); return capi_aq_acquire(self, name, filter, extra, explicit, defalt, containment); } static PyObject * capi_aq_get(PyObject *self, PyObject *name, PyObject *defalt, int containment) { PyObject *result = NULL, *v, *tb; /* We got a wrapped object, so business as usual */ if (isWrapper(self)) result=Wrapper_findattr(WRAPPER(self), name, 0, 0, OBJECT(self), 1, 1, 1, containment); /* Not wrapped; check if we have a __parent__ pointer. If that's the case, create a wrapper and pretend it's business as usual. */ else if ((result = PyObject_GetAttr(self, py__parent__))) { self=newWrapper(self, result, (PyTypeObject*)&Wrappertype); Py_DECREF(result); /* don't need __parent__ anymore */ result=Wrapper_findattr(WRAPPER(self), name, 0, 0, OBJECT(self), 1, 1, 1, containment); Py_DECREF(self); /* Get rid of temporary wrapper. */ } else { /* Clean up the AttributeError from the previous getattr (because it has clearly failed). */ PyErr_Fetch(&result,&v,&tb); if (result && (result != PyExc_AttributeError)) { PyErr_Restore(result,v,tb); return NULL; } Py_XDECREF(result); Py_XDECREF(v); Py_XDECREF(tb); result=PyObject_GetAttr(self, name); } if (! result && defalt) { PyErr_Clear(); result=defalt; Py_INCREF(result); } return result; } static PyObject * module_aq_get(PyObject *r, PyObject *args) { PyObject *self, *name, *defalt=0; int containment=0; UNLESS (PyArg_ParseTuple(args, "OO|Oi", &self, &name, &defalt, &containment )) return NULL; return capi_aq_get(self, name, defalt, containment); } static int capi_aq_iswrapper(PyObject *self) { return isWrapper(self); } static PyObject * capi_aq_base(PyObject *self) { PyObject *result; if (! isWrapper(self)) { Py_INCREF(self); return self; } if (WRAPPER(self)->obj) { result=WRAPPER(self)->obj; while (isWrapper(result) && WRAPPER(result)->obj) result=WRAPPER(result)->obj; } else result=Py_None; Py_INCREF(result); return result; } static PyObject * module_aq_base(PyObject *ignored, PyObject *args) { PyObject *self; UNLESS (PyArg_ParseTuple(args, "O", &self)) return NULL; return capi_aq_base(self); } static PyObject * capi_aq_parent(PyObject *self) { PyObject *result, *v, *tb; if (isWrapper(self) && WRAPPER(self)->container) { result=WRAPPER(self)->container; Py_INCREF(result); return result; } else if ((result=PyObject_GetAttr(self, py__parent__))) /* We already own the reference to result (PyObject_GetAttr gives it to us), no need to INCREF here */ return result; else { /* We need to clean up the AttributeError from the previous getattr (because it has clearly failed) */ PyErr_Fetch(&result,&v,&tb); if (result && (result != PyExc_AttributeError)) { PyErr_Restore(result,v,tb); return NULL; } Py_XDECREF(result); Py_XDECREF(v); Py_XDECREF(tb); result=Py_None; Py_INCREF(result); return result; } } static PyObject * module_aq_parent(PyObject *ignored, PyObject *args) { PyObject *self; UNLESS (PyArg_ParseTuple(args, "O", &self)) return NULL; return capi_aq_parent(self); } static PyObject * capi_aq_self(PyObject *self) { PyObject *result; if (! isWrapper(self)) { Py_INCREF(self); return self; } if (WRAPPER(self)->obj) result=WRAPPER(self)->obj; else result=Py_None; Py_INCREF(result); return result; } static PyObject * module_aq_self(PyObject *ignored, PyObject *args) { PyObject *self; UNLESS (PyArg_ParseTuple(args, "O", &self)) return NULL; return capi_aq_self(self); } static PyObject * capi_aq_inner(PyObject *self) { PyObject *result; if (! isWrapper(self)) { Py_INCREF(self); return self; } if (WRAPPER(self)->obj) { result=WRAPPER(self)->obj; while (isWrapper(result) && WRAPPER(result)->obj) { self=result; result=WRAPPER(result)->obj; } result=self; } else result=Py_None; Py_INCREF(result); return result; } static PyObject * module_aq_inner(PyObject *ignored, PyObject *args) { PyObject *self; UNLESS (PyArg_ParseTuple(args, "O", &self)) return NULL; return capi_aq_inner(self); } static PyObject * capi_aq_chain(PyObject *self, int containment) { PyObject *result, *v, *tb; UNLESS (result=PyList_New(0)) return NULL; while (1) { if (isWrapper(self)) { if (WRAPPER(self)->obj) { if (containment) while (WRAPPER(self)->obj && isWrapper(WRAPPER(self)->obj)) self=WRAPPER(self)->obj; if (PyList_Append(result,OBJECT(self)) < 0) goto err; } if (WRAPPER(self)->container) { self=WRAPPER(self)->container; continue; } } else { if (PyList_Append(result, self) < 0) goto err; if ((self=PyObject_GetAttr(self, py__parent__))) { Py_DECREF(self); /* We don't need our own reference. */ if (self!=Py_None) continue; } else { PyErr_Fetch(&self,&v,&tb); if (self && (self != PyExc_AttributeError)) { PyErr_Restore(self,v,tb); return NULL; } Py_XDECREF(self); Py_XDECREF(v); Py_XDECREF(tb); } } break; } return result; err: Py_DECREF(result); return result; } static PyObject * module_aq_chain(PyObject *ignored, PyObject *args) { PyObject *self; int containment=0; UNLESS (PyArg_ParseTuple(args, "O|i", &self, &containment)) return NULL; return capi_aq_chain(self, containment); } static PyObject * capi_aq_inContextOf(PyObject *self, PyObject *o, int inner) { PyObject *next, *c; /* next = self o = aq_base(o) */ next = self; while (isWrapper(o) && WRAPPER(o)->obj) o=WRAPPER(o)->obj; while (1) { /* if aq_base(next) is o: return 1 */ c = next; while (isWrapper(c) && WRAPPER(c)->obj) c = WRAPPER(c)->obj; if (c == o) return PyInt_FromLong(1); if (inner) { self = capi_aq_inner(next); Py_DECREF(self); /* We're not holding on to the inner wrapper */ if (self == Py_None) break; } else self = next; next = capi_aq_parent(self); Py_DECREF(next); /* We're not holding on to the parent */ if (next == Py_None) break; } return PyInt_FromLong(0); } static PyObject * module_aq_inContextOf(PyObject *ignored, PyObject *args) { PyObject *self, *o; int inner=1; UNLESS (PyArg_ParseTuple(args, "OO|i", &self, &o, &inner)) return NULL; return capi_aq_inContextOf(self, o, inner); } static struct PyMethodDef methods[] = { {"aq_acquire", (PyCFunction)module_aq_acquire, METH_VARARGS|METH_KEYWORDS, "aq_acquire(ob, name [, filter, extra, explicit]) -- " "Get an attribute, acquiring it if necessary" }, {"aq_get", (PyCFunction)module_aq_get, METH_VARARGS, "aq_get(ob, name [, default]) -- " "Get an attribute, acquiring it if necessary." }, {"aq_base", (PyCFunction)module_aq_base, METH_VARARGS, "aq_base(ob) -- Get the object unwrapped"}, {"aq_parent", (PyCFunction)module_aq_parent, METH_VARARGS, "aq_parent(ob) -- Get the parent of an object"}, {"aq_self", (PyCFunction)module_aq_self, METH_VARARGS, "aq_self(ob) -- Get the object with the outermost wrapper removed"}, {"aq_inner", (PyCFunction)module_aq_inner, METH_VARARGS, "aq_inner(ob) -- " "Get the object with all but the innermost wrapper removed"}, {"aq_chain", (PyCFunction)module_aq_chain, METH_VARARGS, "aq_chain(ob [, containment]) -- " "Get a list of objects in the acquisition environment"}, {"aq_inContextOf", (PyCFunction)module_aq_inContextOf, METH_VARARGS, "aq_inContextOf(base, ob [, inner]) -- " "Determine whether the object is in the acquisition context of base."}, {NULL, NULL} }; void init_Acquisition(void) { PyObject *m, *d; PyObject *api; PURE_MIXIN_CLASS(Acquirer, "Base class for objects that implicitly" " acquire attributes from containers\n" , Acquirer_methods); PURE_MIXIN_CLASS(ExplicitAcquirer, "Base class for objects that explicitly" " acquire attributes from containers\n" , Xaq_methods); UNLESS(ExtensionClassImported) return; UNLESS(Acquired=PyString_FromStringAndSize(NULL,42)) return; strcpy(PyString_AsString(Acquired), ""); /* Create the module and add the functions */ m = Py_InitModule4("_Acquisition", methods, "Provide base classes for acquiring objects\n\n" "$Id: _Acquisition.c 121913 2011-06-11 15:19:14Z hannosch $\n", OBJECT(NULL),PYTHON_API_VERSION); d = PyModule_GetDict(m); init_py_names(); PyExtensionClass_Export(d,"Acquirer",AcquirerType); PyExtensionClass_Export(d,"ImplicitAcquisitionWrapper",Wrappertype); PyExtensionClass_Export(d,"ExplicitAcquirer",ExplicitAcquirerType); PyExtensionClass_Export(d,"ExplicitAcquisitionWrapper",XaqWrappertype); /* Create aliases */ PyDict_SetItemString(d,"Implicit",OBJECT(&AcquirerType)); PyDict_SetItemString(d,"Explicit",OBJECT(&ExplicitAcquirerType)); PyDict_SetItemString(d,"Acquired",Acquired); AcquisitionCAPI.AQ_Acquire = capi_aq_acquire; AcquisitionCAPI.AQ_Get = capi_aq_get; AcquisitionCAPI.AQ_IsWrapper = capi_aq_iswrapper; AcquisitionCAPI.AQ_Base = capi_aq_base; AcquisitionCAPI.AQ_Parent = capi_aq_parent; AcquisitionCAPI.AQ_Self = capi_aq_self; AcquisitionCAPI.AQ_Inner = capi_aq_inner; AcquisitionCAPI.AQ_Chain = capi_aq_chain; api = PyCObject_FromVoidPtr(&AcquisitionCAPI, NULL); PyDict_SetItemString(d, "AcquisitionCAPI", api); Py_DECREF(api); } zope2.13-2.13.21/source/Acquisition/src/Acquisition/tests.py0000644000175000017500000016545012214017433022532 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Acquisition test cases (and useful examples) Acquisition [1] is a mechanism that allows objects to obtain attributes from their environment. It is similar to inheritence, except that, rather than traversing an inheritence hierarchy to obtain attributes, a containment hierarchy is traversed. The "ExtensionClass":ExtensionClass.html. release includes mix-in extension base classes that can be used to add acquisition as a feature to extension subclasses. These mix-in classes use the context-wrapping feature of ExtensionClasses to implement acquisition. Consider the following example:: >>> import ExtensionClass, Acquisition >>> class C(ExtensionClass.Base): ... color='red' >>> class A(Acquisition.Implicit): ... def report(self): ... print self.color >>> a = A() >>> c = C() >>> c.a = a >>> c.a.report() red >>> d = C() >>> d.color = 'green' >>> d.a = a >>> d.a.report() green >>> a.report() # raises an attribute error Traceback (most recent call last): ... AttributeError: color The class 'A' inherits acquisition behavior from 'Acquisition.Implicit'. The object, 'a', "has" the color of objects 'c' and 'd' when it is accessed through them, but it has no color by itself. The object 'a' obtains attributes from it's environment, where it's environment is defined by the access path used to reach 'a'. Acquisition wrappers When an object that supports acquisition is accessed through an extension class instance, a special object, called an acquisition wrapper, is returned. In the example above, the expression 'c.a' returns an acquisition wrapper that contains references to both 'c' and 'a'. It is this wrapper that performs attribute lookup in 'c' when an attribute cannot be found in 'a'. Aquisition wrappers provide access to the wrapped objects through the attributes 'aq_parent', 'aq_self', 'aq_base'. In the example above, the expressions:: >>> c.a.aq_parent is c 1 and:: >>> c.a.aq_self is a 1 both evaluate to true, but the expression:: >>> c.a is a 0 evaluates to false, because the expression 'c.a' evaluates to an acquisition wrapper around 'c' and 'a', not 'a' itself. The attribute 'aq_base' is similar to 'aq_self'. Wrappers may be nested and 'aq_self' may be a wrapped object. The 'aq_base' attribute is the underlying object with all wrappers removed. Acquisition Control Two styles of acquisition are supported in the current ExtensionClass release, implicit and explicit aquisition. Implicit acquisition Implicit acquisition is so named because it searches for attributes from the environment automatically whenever an attribute cannot be obtained directly from an object or through inheritence. An attribute may be implicitly acquired if it's name does not begin with an underscore, '_'. To support implicit acquisition, an object should inherit from the mix-in class 'Acquisition.Implicit'. Explicit Acquisition When explicit acquisition is used, attributes are not automatically obtained from the environment. Instead, the method 'aq_aquire' must be used, as in:: print c.a.aq_acquire('color') To support explicit acquisition, an object should inherit from the mix-in class 'Acquisition.Explicit'. Controlled Acquisition A class (or instance) can provide attribute by attribute control over acquisition. This is done by: - subclassing from 'Acquisition.Explicit', and - setting all attributes that should be acquired to the special value: 'Acquisition.Acquired'. Setting an attribute to this value also allows inherited attributes to be overridden with acquired ones. For example, in:: >>> class E(Acquisition.Explicit): ... id = 1 ... secret = 2 ... color = Acquisition.Acquired ... __roles__ = Acquisition.Acquired The *only* attributes that are automatically acquired from containing objects are 'color', and '__roles__'. >>> c = C() >>> c.foo = 'foo' >>> c.e = E() >>> c.e.color 'red' >>> c.e.foo Traceback (most recent call last): ... AttributeError: foo Note also that the '__roles__' attribute is acquired even though it's name begins with an underscore: >>> c.__roles__ = 'Manager', 'Member' >>> c.e.__roles__ ('Manager', 'Member') In fact, the special 'Acquisition.Acquired' value can be used in 'Acquisition.Implicit' objects to implicitly acquire selected objects that smell like private objects. >>> class I(Acquisition.Implicit): ... __roles__ = Acquisition.Acquired >>> c.x = C() >>> c.x.__roles__ Traceback (most recent call last): ... AttributeError: __roles__ >>> c.x = I() >>> c.x.__roles__ ('Manager', 'Member') Filtered Acquisition The acquisition method, 'aq_acquire', accepts two optional arguments. The first of the additional arguments is a "filtering" function that is used when considering whether to acquire an object. The second of the additional arguments is an object that is passed as extra data when calling the filtering function and which defaults to 'None'. The filter function is called with five arguments: - The object that the 'aq_acquire' method was called on, - The object where an object was found, - The name of the object, as passed to 'aq_acquire', - The object found, and - The extra data passed to 'aq_acquire'. If the filter returns a true object that the object found is returned, otherwise, the acquisition search continues. For example, in:: >>> from Acquisition import Explicit >>> class HandyForTesting: ... def __init__(self, name): self.name=name ... def __str__(self): ... return "%s(%s)" % (self.name, self.__class__.__name__) ... __repr__=__str__ >>> class E(Explicit, HandyForTesting): ... pass >>> class Nice(HandyForTesting): ... isNice=1 ... def __str__(self): ... return HandyForTesting.__str__(self)+' and I am nice!' ... __repr__=__str__ >>> a = E('a') >>> a.b = E('b') >>> a.b.c = E('c') >>> a.p = Nice('spam') >>> a.b.p = E('p') >>> def find_nice(self, ancestor, name, object, extra): ... return hasattr(object,'isNice') and object.isNice >>> print a.b.c.aq_acquire('p', find_nice) spam(Nice) and I am nice! The filtered acquisition in the last line skips over the first attribute it finds with the name 'p', because the attribute doesn't satisfy the condition given in the filter. Acquisition and methods Python methods of objects that support acquisition can use acquired attributes as in the 'report' method of the first example above. When a Python method is called on an object that is wrapped by an acquisition wrapper, the wrapper is passed to the method as the first argument. This rule also applies to user-defined method types and to C methods defined in pure mix-in classes. Unfortunately, C methods defined in extension base classes that define their own data structures, cannot use aquired attributes at this time. This is because wrapper objects do not conform to the data structures expected by these methods. Acquiring Acquiring objects Consider the following example:: >>> from Acquisition import Implicit >>> class C(Implicit): ... def __init__(self, name): self.name=name ... def __str__(self): ... return "%s(%s)" % (self.name, self.__class__.__name__) ... __repr__=__str__ >>> a = C("a") >>> a.b = C("b") >>> a.b.pref = "spam" >>> a.b.c = C("c") >>> a.b.c.color = "red" >>> a.b.c.pref = "eggs" >>> a.x = C("x") >>> o = a.b.c.x The expression 'o.color' might be expected to return '"red"'. In earlier versions of ExtensionClass, however, this expression failed. Acquired acquiring objects did not acquire from the environment they were accessed in, because objects were only wrapped when they were first found, and were not rewrapped as they were passed down the acquisition tree. In the current release of ExtensionClass, the expression "o.color" does indeed return '"red"'. >>> o.color 'red' When searching for an attribute in 'o', objects are searched in the order 'x', 'a', 'b', 'c'. So, for example, the expression, 'o.pref' returns '"spam"', not '"eggs"':: >>> o.pref 'spam' In earlier releases of ExtensionClass, the attempt to get the 'pref' attribute from 'o' would have failed. If desired, the current rules for looking up attributes in complex expressions can best be understood through repeated application of the '__of__' method: 'a.x' -- 'x.__of__(a)' 'a.b' -- 'b.__of__(a)' 'a.b.x' -- 'x.__of__(a).__of__(b.__of__(a))' 'a.b.c' -- 'c.__of__(b.__of__(a))' 'a.b.c.x' -- 'x.__of__(a).__of__(b.__of__(a)).__of__(c.__of__(b.__of__(a)))' and by keeping in mind that attribute lookup in a wrapper is done by trying to lookup the attribute in the wrapped object first and then in the parent object. In the expressions above involving the '__of__' method, lookup proceeds from left to right. Note that heuristics are used to avoid most of the repeated lookups. For example, in the expression: 'a.b.c.x.foo', the object 'a' is searched no more than once, even though it is wrapped three times. .. [1] Gil, J., Lorenz, D., "Environmental Acquisition--A New Inheritance-Like Abstraction Mechanism", http://www.bell-labs.com/people/cope/oopsla/Oopsla96TechnicalProgramAbstracts.html#GilLorenz, OOPSLA '96 Proceedings, ACM SIG-PLAN, October, 1996 $Id: tests.py 121912 2011-06-11 15:09:38Z hannosch $ """ import ExtensionClass import Acquisition class I(Acquisition.Implicit): def __init__(self, id): self.id = id def __repr__(self): return self.id class E(Acquisition.Explicit): def __init__(self, id): self.id = id def __repr__(self): return self.id def test_unwrapped(): """ >>> c = I('unwrapped') >>> show(c) unwrapped >>> c.aq_parent Traceback (most recent call last): ... AttributeError: aq_parent >>> c.__parent__ Traceback (most recent call last): ... AttributeError: __parent__ >>> Acquisition.aq_acquire(c, 'id') 'unwrapped' >>> Acquisition.aq_acquire(c, 'x') Traceback (most recent call last): ... AttributeError: x >>> Acquisition.aq_acquire(c, 'id', ... lambda searched, parent, name, ob, extra: extra) Traceback (most recent call last): ... AttributeError: id >>> Acquisition.aq_acquire(c, 'id', ... lambda searched, parent, name, ob, extra: extra, ... 1) 'unwrapped' >>> Acquisition.aq_base(c) is c 1 >>> Acquisition.aq_chain(c) [unwrapped] >>> Acquisition.aq_chain(c, 1) [unwrapped] >>> Acquisition.aq_get(c, 'id') 'unwrapped' >>> Acquisition.aq_get(c, 'x') Traceback (most recent call last): ... AttributeError: x >>> Acquisition.aq_get(c, 'x', 'foo') 'foo' >>> Acquisition.aq_get(c, 'x', 'foo', 1) 'foo' >>> Acquisition.aq_inner(c) is c 1 >>> Acquisition.aq_parent(c) >>> Acquisition.aq_self(c) is c 1 """ def test_simple(): """ >>> a = I('a') >>> a.y = 42 >>> a.b = I('b') >>> a.b.c = I('c') >>> show(a.b.c) c | b | a >>> show(a.b.c.aq_parent) b | a >>> show(a.b.c.aq_self) c >>> show(a.b.c.aq_base) c >>> show(a.b.c.aq_inner) c | b | a >>> a.b.c.y 42 >>> a.b.c.aq_chain [c, b, a] >>> a.b.c.aq_inContextOf(a) 1 >>> a.b.c.aq_inContextOf(a.b) 1 >>> a.b.c.aq_inContextOf(a.b.c) 1 >>> Acquisition.aq_inContextOf(a.b.c, a) 1 >>> Acquisition.aq_inContextOf(a.b.c, a.b) 1 >>> Acquisition.aq_inContextOf(a.b.c, a.b.c) 1 >>> a.b.c.aq_acquire('y') 42 >>> a.b.c.aq_acquire('id') 'c' >>> a.b.c.aq_acquire('x') Traceback (most recent call last): ... AttributeError: x >>> a.b.c.aq_acquire('id', ... lambda searched, parent, name, ob, extra: extra) Traceback (most recent call last): ... AttributeError: id >>> Acquisition.aq_acquire(a.b.c, 'id', ... lambda searched, parent, name, ob, extra: extra, ... 1) 'c' >>> Acquisition.aq_acquire(a.b.c, 'id') 'c' >>> Acquisition.aq_acquire(a.b.c, 'x') Traceback (most recent call last): ... AttributeError: x >>> Acquisition.aq_acquire(a.b.c, 'y') 42 >>> Acquisition.aq_acquire(a.b.c, 'id', ... lambda searched, parent, name, ob, extra: extra) Traceback (most recent call last): ... AttributeError: id >>> Acquisition.aq_acquire(a.b.c, 'id', ... lambda searched, parent, name, ob, extra: extra, ... 1) 'c' >>> show(Acquisition.aq_base(a.b.c)) c >>> Acquisition.aq_chain(a.b.c) [c, b, a] >>> Acquisition.aq_chain(a.b.c, 1) [c, b, a] >>> Acquisition.aq_get(a.b.c, 'id') 'c' >>> Acquisition.aq_get(a.b.c, 'x') Traceback (most recent call last): ... AttributeError: x >>> Acquisition.aq_get(a.b.c, 'x', 'foo') 'foo' >>> Acquisition.aq_get(a.b.c, 'x', 'foo', 1) 'foo' >>> show(Acquisition.aq_inner(a.b.c)) c | b | a >>> show(Acquisition.aq_parent(a.b.c)) b | a >>> show(Acquisition.aq_self(a.b.c)) c A wrapper's __parent__ attribute (which is equivalent to its aq_parent attribute) points to the Acquisition parent. >>> a.b.c.__parent__ == a.b.c.aq_parent True >>> a.b.c.__parent__ == a.b True """ def test__of__exception(): """ Wrapper_findattr did't check for an exception in a user defined __of__ method before passing the result to the filter. In this case the 'value' argument of the filter was NULL, which caused a segfault when being accessed. >>> class UserError(Exception): ... pass ... >>> class X(Acquisition.Implicit): ... def __of__(self, parent): ... if Acquisition.aq_base(parent) is not parent: ... raise UserError, 'ack' ... return X.inheritedAttribute('__of__')(self, parent) ... >>> a = I('a') >>> a.b = I('b') >>> a.b.x = X('x') >>> Acquisition.aq_acquire(a.b, 'x', ... lambda self, object, name, value, extra: repr(value)) Traceback (most recent call last): ... UserError: ack """ def test_muliple(): r""" >>> a = I('a') >>> a.color = 'red' >>> a.a1 = I('a1') >>> a.a1.color = 'green' >>> a.a1.a11 = I('a11') >>> a.a2 = I('a2') >>> a.a2.a21 = I('a21') >>> show(a.a1.a11.a2.a21) a21 | (a2) | \ | (a2) | | \ | | a2 | | | | | a | | | a1 | | | a | a11 | a1 | a >>> a.a1.a11.a2.a21.color 'red' >>> show(a.a1.a11.a2.a21.aq_parent) (a2) | \ | (a2) | | \ | | a2 | | | | | a | | | a1 | | | a | a11 | a1 | a >>> show(a.a1.a11.a2.a21.aq_parent.aq_parent) a11 | a1 | a >>> show(a.a1.a11.a2.a21.aq_self) a21 >>> show(a.a1.a11.a2.a21.aq_parent.aq_self) (a2) | \ | a2 | | | a | a1 | a >>> show(a.a1.a11.a2.a21.aq_base) a21 >>> show(a.a1.a11.a2.a21.aq_inner) a21 | (a2) | \ | (a2) | | \ | | a2 | | | | | a | | | a1 | | | a | a11 | a1 | a >>> show(a.a1.a11.a2.a21.aq_inner.aq_parent.aq_inner) a2 | a >>> show(a.a1.a11.a2.a21.aq_inner.aq_parent.aq_inner.aq_parent) a >>> a.a1.a11.a2.a21.aq_chain [a21, a2, a11, a1, a] >>> a.a1.a11.a2.a21.aq_inContextOf(a) 1 >>> a.a1.a11.a2.a21.aq_inContextOf(a.a2) 1 >>> a.a1.a11.a2.a21.aq_inContextOf(a.a1) 0 >>> a.a1.a11.a2.a21.aq_acquire('color') 'red' >>> a.a1.a11.a2.a21.aq_acquire('id') 'a21' >>> a.a1.a11.a2.a21.aq_acquire('color', ... lambda ob, parent, name, v, extra: extra) Traceback (most recent call last): ... AttributeError: color >>> a.a1.a11.a2.a21.aq_acquire('color', ... lambda ob, parent, name, v, extra: extra, 1) 'red' >>> a.a1.y = 42 >>> a.a1.a11.a2.a21.aq_acquire('y') 42 >>> a.a1.a11.a2.a21.aq_acquire('y', containment=1) Traceback (most recent call last): ... AttributeError: y Much of the same, but with methods: >>> show(Acquisition.aq_parent(a.a1.a11.a2.a21)) (a2) | \ | (a2) | | \ | | a2 | | | | | a | | | a1 | | | a | a11 | a1 | a >>> show(Acquisition.aq_parent(a.a1.a11.a2.a21.aq_parent)) a11 | a1 | a >>> show(Acquisition.aq_self(a.a1.a11.a2.a21)) a21 >>> show(Acquisition.aq_self(a.a1.a11.a2.a21.aq_parent)) (a2) | \ | a2 | | | a | a1 | a >>> show(Acquisition.aq_base(a.a1.a11.a2.a21)) a21 >>> show(Acquisition.aq_inner(a.a1.a11.a2.a21)) a21 | (a2) | \ | (a2) | | \ | | a2 | | | | | a | | | a1 | | | a | a11 | a1 | a >>> show(Acquisition.aq_inner(a.a1.a11.a2.a21.aq_inner.aq_parent)) a2 | a >>> show(Acquisition.aq_parent( ... a.a1.a11.a2.a21.aq_inner.aq_parent.aq_inner)) a >>> Acquisition.aq_chain(a.a1.a11.a2.a21) [a21, a2, a11, a1, a] >>> Acquisition.aq_chain(a.a1.a11.a2.a21, 1) [a21, a2, a] >>> Acquisition.aq_acquire(a.a1.a11.a2.a21, 'color') 'red' >>> Acquisition.aq_acquire(a.a1.a11.a2.a21, 'id') 'a21' >>> Acquisition.aq_acquire(a.a1.a11.a2.a21, 'color', ... lambda ob, parent, name, v, extra: extra) Traceback (most recent call last): ... AttributeError: color >>> Acquisition.aq_acquire(a.a1.a11.a2.a21, 'color', ... lambda ob, parent, name, v, extra: extra, 1) 'red' >>> a.a1.y = 42 >>> Acquisition.aq_acquire(a.a1.a11.a2.a21, 'y') 42 >>> Acquisition.aq_acquire(a.a1.a11.a2.a21, 'y', containment=1) Traceback (most recent call last): ... AttributeError: y """ def test_pinball(): r""" >>> a = I('a') >>> a.a1 = I('a1') >>> a.a1.a11 = I('a11') >>> a.a1.a12 = I('a12') >>> a.a2 = I('a2') >>> a.a2.a21 = I('a21') >>> a.a2.a22 = I('a22') >>> show(a.a1.a11.a1.a12.a2.a21.a2.a22) a22 | (a2) | \ | (a2) | | \ | | a2 | | | | | a | | | (a2) | | \ | | (a2) | | | \ | | | a2 | | | | | | | a | | | | | (a1) | | | \ | | | (a1) | | | | \ | | | | a1 | | | | | | | | | a | | | | | | | a1 | | | | | | | a | | | | | a11 | | | | | a1 | | | | | a | | | a12 | | | (a1) | | \ | | (a1) | | | \ | | | a1 | | | | | | | a | | | | | a1 | | | | | a | | | a11 | | | a1 | | | a | a21 | (a2) | \ | (a2) | | \ | | a2 | | | | | a | | | (a1) | | \ | | (a1) | | | \ | | | a1 | | | | | | | a | | | | | a1 | | | | | a | | | a11 | | | a1 | | | a | a12 | (a1) | \ | (a1) | | \ | | a1 | | | | | a | | | a1 | | | a | a11 | a1 | a """ def test_explicit(): """ >>> a = E('a') >>> a.y = 42 >>> a.b = E('b') >>> a.b.c = E('c') >>> show(a.b.c) c | b | a >>> show(a.b.c.aq_parent) b | a >>> show(a.b.c.aq_self) c >>> show(a.b.c.aq_base) c >>> show(a.b.c.aq_inner) c | b | a >>> a.b.c.y Traceback (most recent call last): ... AttributeError: y >>> a.b.c.aq_chain [c, b, a] >>> a.b.c.aq_inContextOf(a) 1 >>> a.b.c.aq_inContextOf(a.b) 1 >>> a.b.c.aq_inContextOf(a.b.c) 1 >>> a.b.c.aq_acquire('y') 42 >>> a.b.c.aq_acquire('id') 'c' >>> a.b.c.aq_acquire('x') Traceback (most recent call last): ... AttributeError: x >>> a.b.c.aq_acquire('id', ... lambda searched, parent, name, ob, extra: extra) Traceback (most recent call last): ... AttributeError: id >>> Acquisition.aq_acquire(a.b.c, 'id', ... lambda searched, parent, name, ob, extra: extra, ... 1) 'c' >>> Acquisition.aq_acquire(a.b.c, 'id') 'c' >>> Acquisition.aq_acquire(a.b.c, 'x') Traceback (most recent call last): ... AttributeError: x >>> Acquisition.aq_acquire(a.b.c, 'y') 42 >>> Acquisition.aq_acquire(a.b.c, 'id', ... lambda searched, parent, name, ob, extra: extra) Traceback (most recent call last): ... AttributeError: id >>> Acquisition.aq_acquire(a.b.c, 'id', ... lambda searched, parent, name, ob, extra: extra, ... 1) 'c' >>> show(Acquisition.aq_base(a.b.c)) c >>> Acquisition.aq_chain(a.b.c) [c, b, a] >>> Acquisition.aq_chain(a.b.c, 1) [c, b, a] >>> Acquisition.aq_get(a.b.c, 'id') 'c' >>> Acquisition.aq_get(a.b.c, 'x') Traceback (most recent call last): ... AttributeError: x >>> Acquisition.aq_get(a.b.c, 'y') 42 >>> Acquisition.aq_get(a.b.c, 'x', 'foo') 'foo' >>> Acquisition.aq_get(a.b.c, 'x', 'foo', 1) 'foo' >>> show(Acquisition.aq_inner(a.b.c)) c | b | a >>> show(Acquisition.aq_parent(a.b.c)) b | a >>> show(Acquisition.aq_self(a.b.c)) c """ def test_mixed_explicit_and_explicit(): """ >>> a = I('a') >>> a.y = 42 >>> a.b = E('b') >>> a.b.z = 3 >>> a.b.c = I('c') >>> show(a.b.c) c | b | a >>> show(a.b.c.aq_parent) b | a >>> show(a.b.c.aq_self) c >>> show(a.b.c.aq_base) c >>> show(a.b.c.aq_inner) c | b | a >>> a.b.c.y 42 >>> a.b.c.z 3 >>> a.b.c.aq_chain [c, b, a] >>> a.b.c.aq_inContextOf(a) 1 >>> a.b.c.aq_inContextOf(a.b) 1 >>> a.b.c.aq_inContextOf(a.b.c) 1 >>> a.b.c.aq_acquire('y') 42 >>> a.b.c.aq_acquire('z') 3 >>> a.b.c.aq_acquire('id') 'c' >>> a.b.c.aq_acquire('x') Traceback (most recent call last): ... AttributeError: x >>> a.b.c.aq_acquire('id', ... lambda searched, parent, name, ob, extra: extra) Traceback (most recent call last): ... AttributeError: id >>> Acquisition.aq_acquire(a.b.c, 'id', ... lambda searched, parent, name, ob, extra: extra, ... 1) 'c' >>> Acquisition.aq_acquire(a.b.c, 'id') 'c' >>> Acquisition.aq_acquire(a.b.c, 'x') Traceback (most recent call last): ... AttributeError: x >>> Acquisition.aq_acquire(a.b.c, 'y') 42 >>> Acquisition.aq_acquire(a.b.c, 'id', ... lambda searched, parent, name, ob, extra: extra) Traceback (most recent call last): ... AttributeError: id >>> Acquisition.aq_acquire(a.b.c, 'id', ... lambda searched, parent, name, ob, extra: extra, ... 1) 'c' >>> show(Acquisition.aq_base(a.b.c)) c >>> Acquisition.aq_chain(a.b.c) [c, b, a] >>> Acquisition.aq_chain(a.b.c, 1) [c, b, a] >>> Acquisition.aq_get(a.b.c, 'id') 'c' >>> Acquisition.aq_get(a.b.c, 'x') Traceback (most recent call last): ... AttributeError: x >>> Acquisition.aq_get(a.b.c, 'y') 42 >>> Acquisition.aq_get(a.b.c, 'x', 'foo') 'foo' >>> Acquisition.aq_get(a.b.c, 'x', 'foo', 1) 'foo' >>> show(Acquisition.aq_inner(a.b.c)) c | b | a >>> show(Acquisition.aq_parent(a.b.c)) b | a >>> show(Acquisition.aq_self(a.b.c)) c """ def test_aq_inContextOf(): """ >>> from ExtensionClass import Base >>> import Acquisition >>> class B(Base): ... color='red' >>> class A(Acquisition.Implicit): ... def hi(self): ... print "%s()" % self.__class__.__name__, self.color >>> class Location(object): ... __parent__ = None >>> b=B() >>> b.a=A() >>> b.a.hi() A() red >>> b.a.color='green' >>> b.a.hi() A() green >>> try: ... A().hi() ... raise 'Program error', 'spam' ... except AttributeError: pass A() New test for wrapper comparisons. >>> foo = b.a >>> bar = b.a >>> foo == bar 1 >>> c = A() >>> b.c = c >>> b.c.d = c >>> b.c.d == c 1 >>> b.c.d == b.c 1 >>> b.c == c 1 >>> l = Location() >>> l.__parent__ = b.c >>> def checkContext(self, o): ... # Python equivalent to aq_inContextOf ... from Acquisition import aq_base, aq_parent, aq_inner ... next = self ... o = aq_base(o) ... while 1: ... if aq_base(next) is o: ... return 1 ... self = aq_inner(next) ... if self is None: ... break ... next = aq_parent(self) ... if next is None: ... break ... return 0 >>> checkContext(b.c, b) 1 >>> not checkContext(b.c, b.a) 1 >>> checkContext(l, b) 1 >>> checkContext(l, b.c) 1 >>> not checkContext(l, b.a) 1 Acquisition.aq_inContextOf works the same way: >>> Acquisition.aq_inContextOf(b.c, b) 1 >>> Acquisition.aq_inContextOf(b.c, b.a) 0 >>> Acquisition.aq_inContextOf(l, b) 1 >>> Acquisition.aq_inContextOf(l, b.c) 1 >>> Acquisition.aq_inContextOf(l, b.a) 0 >>> b.a.aq_inContextOf(b) 1 >>> b.c.aq_inContextOf(b) 1 >>> b.c.d.aq_inContextOf(b) 1 >>> b.c.d.aq_inContextOf(c) 1 >>> b.c.d.aq_inContextOf(b.c) 1 >>> b.c.aq_inContextOf(foo) 0 >>> b.c.aq_inContextOf(b.a) 0 >>> b.a.aq_inContextOf('somestring') 0 """ def test_AqAlg(): """ >>> A=I('A') >>> A.B=I('B') >>> A.B.color='red' >>> A.C=I('C') >>> A.C.D=I('D') >>> A A >>> Acquisition.aq_chain(A) [A] >>> Acquisition.aq_chain(A, 1) [A] >>> map(Acquisition.aq_base, Acquisition.aq_chain(A, 1)) [A] >>> A.C C >>> Acquisition.aq_chain(A.C) [C, A] >>> Acquisition.aq_chain(A.C, 1) [C, A] >>> map(Acquisition.aq_base, Acquisition.aq_chain(A.C, 1)) [C, A] >>> A.C.D D >>> Acquisition.aq_chain(A.C.D) [D, C, A] >>> Acquisition.aq_chain(A.C.D, 1) [D, C, A] >>> map(Acquisition.aq_base, Acquisition.aq_chain(A.C.D, 1)) [D, C, A] >>> A.B.C C >>> Acquisition.aq_chain(A.B.C) [C, B, A] >>> Acquisition.aq_chain(A.B.C, 1) [C, A] >>> map(Acquisition.aq_base, Acquisition.aq_chain(A.B.C, 1)) [C, A] >>> A.B.C.D D >>> Acquisition.aq_chain(A.B.C.D) [D, C, B, A] >>> Acquisition.aq_chain(A.B.C.D, 1) [D, C, A] >>> map(Acquisition.aq_base, Acquisition.aq_chain(A.B.C.D, 1)) [D, C, A] >>> A.B.C.D.color 'red' >>> Acquisition.aq_get(A.B.C.D, "color", None) 'red' >>> Acquisition.aq_get(A.B.C.D, "color", None, 1) """ def test_explicit_acquisition(): """ >>> from ExtensionClass import Base >>> import Acquisition >>> class B(Base): ... color='red' >>> class A(Acquisition.Explicit): ... def hi(self): ... print self.__class__.__name__, self.acquire('color') >>> b=B() >>> b.a=A() >>> b.a.hi() A red >>> b.a.color='green' >>> b.a.hi() A green >>> try: ... A().hi() ... raise 'Program error', 'spam' ... except AttributeError: pass A """ def test_creating_wrappers_directly(): """ >>> from ExtensionClass import Base >>> from Acquisition import ImplicitAcquisitionWrapper >>> class B(Base): ... pass >>> a = B() >>> a.color = 'red' >>> a.b = B() >>> w = ImplicitAcquisitionWrapper(a.b, a) >>> w.color 'red' >>> w = ImplicitAcquisitionWrapper(a.b) Traceback (most recent call last): ... TypeError: __init__() takes exactly 2 arguments (1 given) We can reassign aq_parent / __parent__ on a wrapper: >>> x = B() >>> x.color = 'green' >>> w.aq_parent = x >>> w.color 'green' >>> y = B() >>> y.color = 'blue' >>> w.__parent__ = y >>> w.color 'blue' Note that messing with the wrapper won't in any way affect the wrapped object: >>> Acquisition.aq_base(w).__parent__ Traceback (most recent call last): ... AttributeError: __parent__ >>> w = ImplicitAcquisitionWrapper() Traceback (most recent call last): ... TypeError: __init__() takes exactly 2 arguments (0 given) >>> w = ImplicitAcquisitionWrapper(obj=1) Traceback (most recent call last): ... TypeError: kwyword arguments not allowed """ def test_cant_pickle_acquisition_wrappers_classic(): """ >>> import pickle >>> class X: ... def __getstate__(self): ... return 1 We shouldn't be able to pickle wrappers: >>> from Acquisition import ImplicitAcquisitionWrapper >>> w = ImplicitAcquisitionWrapper(X(), X()) >>> pickle.dumps(w) Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. But that's not enough. We need to defeat persistence as well. :) This is tricky. We want to generate the error in __getstate__, not in the attr access, as attribute errors are too-often hidden: >>> getstate = w.__getstate__ >>> getstate() Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. We shouldn't be able to pickle wrappers: >>> from Acquisition import ExplicitAcquisitionWrapper >>> w = ExplicitAcquisitionWrapper(X(), X()) >>> pickle.dumps(w) Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. But that's not enough. We need to defeat persistence as well. :) This is tricky. We want to generate the error in __getstate__, not in the attr access, as attribute errors are too-often hidden: >>> getstate = w.__getstate__ >>> getstate() Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. """ def test_cant_pickle_acquisition_wrappers_newstyle(): """ >>> import pickle >>> class X(object): ... def __getstate__(self): ... return 1 We shouldn't be able to pickle wrappers: >>> from Acquisition import ImplicitAcquisitionWrapper >>> w = ImplicitAcquisitionWrapper(X(), X()) >>> pickle.dumps(w) Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. But that's not enough. We need to defeat persistence as well. :) This is tricky. We want to generate the error in __getstate__, not in the attr access, as attribute errors are too-often hidden: >>> getstate = w.__getstate__ >>> getstate() Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. We shouldn't be able to pickle wrappers: >>> from Acquisition import ExplicitAcquisitionWrapper >>> w = ExplicitAcquisitionWrapper(X(), X()) >>> pickle.dumps(w) Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. But that's not enough. We need to defeat persistence as well. :) This is tricky. We want to generate the error in __getstate__, not in the attr access, as attribute errors are too-often hidden: >>> getstate = w.__getstate__ >>> getstate() Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. """ def test_cant_persist_acquisition_wrappers_classic(): """ >>> import cPickle >>> class X: ... _p_oid = '1234' ... def __getstate__(self): ... return 1 We shouldn't be able to pickle wrappers: >>> from Acquisition import ImplicitAcquisitionWrapper >>> w = ImplicitAcquisitionWrapper(X(), X()) >>> cPickle.dumps(w) Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. Check for pickle protocol one: >>> cPickle.dumps(w, 1) Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. Check custom pickler: >>> from cStringIO import StringIO >>> file = StringIO() >>> pickler = cPickle.Pickler(file, 1) >>> pickler.dump(w) Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. Check custom pickler with a persistent_id method matching the semantics in ZODB.serialize.ObjectWriter.persistent_id: >>> file = StringIO() >>> pickler = cPickle.Pickler(file, 1) >>> def persistent_id(obj): ... klass = type(obj) ... oid = obj._p_oid ... if hasattr(klass, '__getnewargs__'): ... return oid ... return 'class_and_oid', klass >>> pickler.inst_persistent_id = persistent_id >>> _ = pickler.dump(w) >>> state = file.getvalue() >>> '1234' in state True >>> 'class_and_oid' in state False """ def test_cant_persist_acquisition_wrappers_newstyle(): """ >>> import cPickle >>> class X(object): ... _p_oid = '1234' ... def __getstate__(self): ... return 1 We shouldn't be able to pickle wrappers: >>> from Acquisition import ImplicitAcquisitionWrapper >>> w = ImplicitAcquisitionWrapper(X(), X()) >>> cPickle.dumps(w) Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. Check for pickle protocol one: >>> cPickle.dumps(w, 1) Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. Check custom pickler: >>> from cStringIO import StringIO >>> file = StringIO() >>> pickler = cPickle.Pickler(file, 1) >>> pickler.dump(w) Traceback (most recent call last): ... TypeError: Can't pickle objects in acquisition wrappers. Check custom pickler with a persistent_id method matching the semantics in ZODB.serialize.ObjectWriter.persistent_id: >>> file = StringIO() >>> pickler = cPickle.Pickler(file, 1) >>> def persistent_id(obj): ... klass = type(obj) ... oid = obj._p_oid ... if hasattr(klass, '__getnewargs__'): ... return oid ... return 'class_and_oid', klass >>> pickler.inst_persistent_id = persistent_id >>> _ = pickler.dump(w) >>> state = file.getvalue() >>> '1234' in state True >>> 'class_and_oid' in state False """ def test_interfaces(): """ >>> from zope.interface.verify import verifyClass Explicit and Implicit implement IAcquirer: >>> from Acquisition import Explicit >>> from Acquisition import Implicit >>> from Acquisition.interfaces import IAcquirer >>> verifyClass(IAcquirer, Explicit) True >>> verifyClass(IAcquirer, Implicit) True ExplicitAcquisitionWrapper and ImplicitAcquisitionWrapper implement IAcquisitionWrapper: >>> from Acquisition import ExplicitAcquisitionWrapper >>> from Acquisition import ImplicitAcquisitionWrapper >>> from Acquisition.interfaces import IAcquisitionWrapper >>> verifyClass(IAcquisitionWrapper, ExplicitAcquisitionWrapper) True >>> verifyClass(IAcquisitionWrapper, ImplicitAcquisitionWrapper) True """ def show(x): print showaq(x).strip() def showaq(m_self, indent=''): rval = '' obj = m_self base = getattr(obj, 'aq_base', obj) try: id = base.id except: id = str(base) try: id = id() except: pass if hasattr(obj, 'aq_self'): if hasattr(obj.aq_self, 'aq_self'): rval = rval + indent + "(" + id + ")\n" rval = rval + indent + "| \\\n" rval = rval + showaq(obj.aq_self, '| ' + indent) rval = rval + indent + "|\n" rval = rval + showaq(obj.aq_parent, indent) elif hasattr(obj, 'aq_parent'): rval = rval + indent + id + "\n" rval = rval + indent + "|\n" rval = rval + showaq(obj.aq_parent, indent) else: rval = rval + indent + id + "\n" return rval def test_Basic_gc(): """Test to make sure that EC instances participate in GC >>> from ExtensionClass import Base >>> import gc >>> thresholds = gc.get_threshold() >>> gc.set_threshold(0) >>> for B in I, E: ... class C1(B): ... pass ... ... class C2(Base): ... def __del__(self): ... print 'removed' ... ... a=C1('a') ... a.b = C1('a.b') ... a.b.a = a ... a.b.c = C2() ... ignore = gc.collect() ... del a ... removed = gc.collect() ... print removed > 0 removed True removed True >>> gc.set_threshold(*thresholds) """ def test_Wrapper_gc(): """Test to make sure that EC instances participate in GC >>> import gc >>> thresholds = gc.get_threshold() >>> gc.set_threshold(0) >>> for B in I, E: ... class C: ... def __del__(self): ... print 'removed' ... ... a=B('a') ... a.b = B('b') ... a.a_b = a.b # circ ref through wrapper ... a.b.c = C() ... ignored = gc.collect() ... del a ... removed = gc.collect() ... removed > 0 removed True removed True >>> gc.set_threshold(*thresholds) """ def test_proxying(): """Make sure that recent python slots are proxied. >>> import sys >>> import Acquisition >>> class Impl(Acquisition.Implicit): ... pass >>> class C(Acquisition.Implicit): ... def __getitem__(self, key): ... print 'getitem', key ... if key == 4: ... raise IndexError ... return key ... def __contains__(self, key): ... print 'contains', repr(key) ... return key == 5 ... def __iter__(self): ... print 'iterating...' ... return iter((42,)) ... def __getslice__(self, start, end): ... print 'slicing...' ... return (start, end) The naked class behaves like this: >>> c = C() >>> 3 in c contains 3 False >>> 5 in c contains 5 True >>> list(c) iterating... [42] >>> c[5:10] slicing... (5, 10) >>> c[5:] == (5, sys.maxsize) slicing... True Let's put c in the context of i: >>> i = Impl() >>> i.c = c Now check that __contains__ is properly used: >>> 3 in i.c # c.__of__(i) contains 3 False >>> 5 in i.c contains 5 True >>> list(i.c) iterating... [42] >>> i.c[5:10] slicing... (5, 10) >>> i.c[5:] == (5, sys.maxsize) slicing... True Let's let's test the same again with an explicit wrapper: >>> import Acquisition >>> class Impl(Acquisition.Explicit): ... pass >>> class C(Acquisition.Explicit): ... def __getitem__(self, key): ... print 'getitem', key ... if key == 4: ... raise IndexError ... return key ... def __contains__(self, key): ... print 'contains', repr(key) ... return key == 5 ... def __iter__(self): ... print 'iterating...' ... return iter((42,)) ... def __getslice__(self, start, end): ... print 'slicing...' ... return (start, end) The naked class behaves like this: >>> c = C() >>> 3 in c contains 3 False >>> 5 in c contains 5 True >>> list(c) iterating... [42] >>> c[5:10] slicing... (5, 10) >>> c[5:] == (5, sys.maxsize) slicing... True Let's put c in the context of i: >>> i = Impl() >>> i.c = c Now check that __contains__ is properly used: >>> 3 in i.c # c.__of__(i) contains 3 False >>> 5 in i.c contains 5 True >>> list(i.c) iterating... [42] >>> i.c[5:10] slicing... (5, 10) >>> i.c[5:] == (5, sys.maxsize) slicing... True Next let's check that the wrapper's __iter__ proxy falls back to using the object's __getitem__ if it has no __iter__. See https://bugs.launchpad.net/zope2/+bug/360761 . >>> class C(Acquisition.Implicit): ... l=[1,2,3] ... def __getitem__(self, i): ... return self.l[i] >>> c1 = C() >>> type(iter(c1)) >>> list(c1) [1, 2, 3] >>> c2 = C().__of__(c1) >>> type(iter(c2)) >>> list(c2) [1, 2, 3] The __iter__proxy should also pass the wrapped object as self to the __iter__ of objects defining __iter__:: >>> class C(Acquisition.Implicit): ... def __iter__(self): ... print 'iterating...' ... for i in range(5): ... yield i, self.aq_parent.name >>> c = C() >>> i = Impl() >>> i.c = c >>> i.name = 'i' >>> list(i.c) iterating... [(0, 'i'), (1, 'i'), (2, 'i'), (3, 'i'), (4, 'i')] And it should pass the wrapped object as self to the __getitem__ of objects without an __iter__:: >>> class C(Acquisition.Implicit): ... def __getitem__(self, i): ... return self.aq_parent.l[i] >>> c = C() >>> i = Impl() >>> i.c = c >>> i.l = range(5) >>> list(i.c) [0, 1, 2, 3, 4] Finally let's make sure errors are still correctly raised after having to use a modified version of `PyObject_GetIter` for iterator support:: >>> class C(Acquisition.Implicit): ... pass >>> c = C() >>> i = Impl() >>> i.c = c >>> list(i.c) Traceback (most recent call last): ... TypeError: iteration over non-sequence >>> class C(Acquisition.Implicit): ... def __iter__(self): ... return [42] >>> c = C() >>> i = Impl() >>> i.c = c >>> list(i.c) Traceback (most recent call last): ... TypeError: iter() returned non-iterator of type 'list' """ class Location(object): __parent__ = None class ECLocation(ExtensionClass.Base): __parent__ = None def test___parent__no_wrappers(): """ Acquisition also works with objects that aren't wrappers, as long as they have __parent__ pointers. Let's take a hierarchy like z --isParent--> y --isParent--> x: >>> x = Location() >>> y = Location() >>> z = Location() >>> x.__parent__ = y >>> y.__parent__ = z and some attributes that we want to acquire: >>> x.hello = 'world' >>> y.foo = 42 >>> z.foo = 43 # this should not be found >>> z.bar = 3.145 ``aq_acquire`` works as we know it from implicit/acquisition wrappers: >>> Acquisition.aq_acquire(x, 'hello') 'world' >>> Acquisition.aq_acquire(x, 'foo') 42 >>> Acquisition.aq_acquire(x, 'bar') 3.145 as does ``aq_get``: >>> Acquisition.aq_get(x, 'hello') 'world' >>> Acquisition.aq_get(x, 'foo') 42 >>> Acquisition.aq_get(x, 'bar') 3.145 and ``aq_parent``: >>> Acquisition.aq_parent(x) is y True >>> Acquisition.aq_parent(y) is z True as well as ``aq_chain``: >>> Acquisition.aq_chain(x) == [x, y, z] True """ def test_implicit_wrapper_as___parent__(): """ Let's do the same test again, only now not all objects are of the same kind and link to each other via __parent__ pointers. The root is a stupid ExtensionClass object: >>> class Root(ExtensionClass.Base): ... bar = 3.145 >>> z = Root() The intermediate parent is an object that supports implicit acquisition. We bind it to the root via the __of__ protocol: >>> class Impl(Acquisition.Implicit): ... foo = 42 >>> y = Impl().__of__(z) The child object is again a simple object with a simple __parent__ pointer: >>> x = Location() >>> x.hello = 'world' >>> x.__parent__ = y ``aq_acquire`` works as expected from implicit/acquisition wrappers: >>> Acquisition.aq_acquire(x, 'hello') 'world' >>> Acquisition.aq_acquire(x, 'foo') 42 >>> Acquisition.aq_acquire(x, 'bar') 3.145 as does ``aq_get``: >>> Acquisition.aq_get(x, 'hello') 'world' >>> Acquisition.aq_get(x, 'foo') 42 >>> Acquisition.aq_get(x, 'bar') 3.145 and ``aq_parent``: >>> Acquisition.aq_parent(x) is y True >>> Acquisition.aq_parent(y) is z True as well as ``aq_chain``: >>> Acquisition.aq_chain(x) == [x, y, z] True Note that also the (implicit) acquisition wrapper has a __parent__ pointer, which is automatically computed from the acquisition container (it's identical to aq_parent): >>> y.__parent__ is z True Just as much as you can assign to aq_parent, you can also assign to __parent__ to change the acquisition context of the wrapper: >>> newroot = Root() >>> y.__parent__ = newroot >>> y.__parent__ is z False >>> y.__parent__ is newroot True Note that messing with the wrapper won't in any way affect the wrapped object: >>> Acquisition.aq_base(y).__parent__ Traceback (most recent call last): ... AttributeError: __parent__ """ def test_explicit_wrapper_as___parent__(): """ Let's do this test yet another time, with an explicit wrapper: >>> class Root(ExtensionClass.Base): ... bar = 3.145 >>> z = Root() The intermediate parent is an object that supports implicit acquisition. We bind it to the root via the __of__ protocol: >>> class Expl(Acquisition.Explicit): ... foo = 42 >>> y = Expl().__of__(z) The child object is again a simple object with a simple __parent__ pointer: >>> x = Location() >>> x.hello = 'world' >>> x.__parent__ = y ``aq_acquire`` works as expected from implicit/acquisition wrappers: >>> Acquisition.aq_acquire(x, 'hello') 'world' >>> Acquisition.aq_acquire(x, 'foo') 42 >>> Acquisition.aq_acquire(x, 'bar') 3.145 as does ``aq_get``: >>> Acquisition.aq_get(x, 'hello') 'world' >>> Acquisition.aq_get(x, 'foo') 42 >>> Acquisition.aq_get(x, 'bar') 3.145 and ``aq_parent``: >>> Acquisition.aq_parent(x) is y True >>> Acquisition.aq_parent(y) is z True as well as ``aq_chain``: >>> Acquisition.aq_chain(x) == [x, y, z] True Note that also the (explicit) acquisition wrapper has a __parent__ pointer, which is automatically computed from the acquisition container (it's identical to aq_parent): >>> y.__parent__ is z True Just as much as you can assign to aq_parent, you can also assign to __parent__ to change the acquisition context of the wrapper: >>> newroot = Root() >>> y.__parent__ = newroot >>> y.__parent__ is z False >>> y.__parent__ is newroot True Note that messing with the wrapper won't in any way affect the wrapped object: >>> Acquisition.aq_base(y).__parent__ Traceback (most recent call last): ... AttributeError: __parent__ """ def test_implicit_wrapper_has_nonwrapper_as_aq_parent(): """Let's do this the other way around: The root and the intermediate parent is an object that doesn't support acquisition, >>> y = ECLocation() >>> z = Location() >>> y.__parent__ = z >>> y.foo = 42 >>> z.foo = 43 # this should not be found >>> z.bar = 3.145 only the outmost object does: >>> class Impl(Acquisition.Implicit): ... hello = 'world' >>> x = Impl().__of__(y) Again, acquiring objects works as usual: >>> Acquisition.aq_acquire(x, 'hello') 'world' >>> Acquisition.aq_acquire(x, 'foo') 42 >>> Acquisition.aq_acquire(x, 'bar') 3.145 as does ``aq_get``: >>> Acquisition.aq_get(x, 'hello') 'world' >>> Acquisition.aq_get(x, 'foo') 42 >>> Acquisition.aq_get(x, 'bar') 3.145 and ``aq_parent``: >>> Acquisition.aq_parent(x) == y True >>> x.aq_parent == y True >>> x.aq_parent.aq_parent == z True >>> Acquisition.aq_parent(y) is z True as well as ``aq_chain``: >>> Acquisition.aq_chain(x) == [x, y, z] True >>> x.aq_chain == [x, y, z] True Because the outmost object, ``x``, is wrapped in an implicit acquisition wrapper, we can also use direct attribute access: >>> x.hello 'world' >>> x.foo 42 >>> x.bar 3.145 """ def test_explicit_wrapper_has_nonwrapper_as_aq_parent(): """Let's do this the other way around: The root and the intermediate parent is an object that doesn't support acquisition, >>> y = ECLocation() >>> z = Location() >>> y.__parent__ = z >>> y.foo = 42 >>> z.foo = 43 # this should not be found >>> z.bar = 3.145 only the outmost object does: >>> class Expl(Acquisition.Explicit): ... hello = 'world' >>> x = Expl().__of__(y) Again, acquiring objects works as usual: >>> Acquisition.aq_acquire(x, 'hello') 'world' >>> Acquisition.aq_acquire(x, 'foo') 42 >>> Acquisition.aq_acquire(x, 'bar') 3.145 as does ``aq_get``: >>> Acquisition.aq_get(x, 'hello') 'world' >>> Acquisition.aq_get(x, 'foo') 42 >>> Acquisition.aq_get(x, 'bar') 3.145 and ``aq_parent``: >>> Acquisition.aq_parent(x) == y True >>> x.aq_parent == y True >>> x.aq_parent.aq_parent == z True >>> Acquisition.aq_parent(y) is z True as well as ``aq_chain``: >>> Acquisition.aq_chain(x) == [x, y, z] True >>> x.aq_chain == [x, y, z] True """ def test___parent__aq_parent_circles(): """ As a general safety belt, Acquisition won't follow a mixture of circular __parent__ pointers and aq_parent wrappers. These can occurr when code that uses implicit acquisition wrappers meets code that uses __parent__ pointers. >>> class Impl(Acquisition.Implicit): ... hello = 'world' >>> class Impl2(Acquisition.Implicit): ... hello = 'world2' ... only = 'here' >>> x = Impl() >>> y = Impl2().__of__(x) >>> x.__parent__ = y >>> x.__parent__.aq_base is y.aq_base True >>> x.__parent__.__parent__ is x True >>> x.hello 'world' >>> Acquisition.aq_acquire(x, 'hello') 'world' >>> x.only Traceback (most recent call last): ... AttributeError: only >>> Acquisition.aq_acquire(x, 'only') 'here' >>> Acquisition.aq_acquire(x, 'non_existant_attr') Traceback (most recent call last): ... AttributeError: non_existant_attr >>> Acquisition.aq_acquire(y, 'non_existant_attr') Traceback (most recent call last): ... AttributeError: non_existant_attr >>> x.non_existant_attr Traceback (most recent call last): ... AttributeError: non_existant_attr >>> y.non_existant_attr Traceback (most recent call last): ... AttributeError: non_existant_attr """ import unittest from doctest import DocTestSuite, DocFileSuite class TestParent(unittest.TestCase): def test_parent_parent_circles(self): class Impl(Acquisition.Implicit): hello = 'world' class Impl2(Acquisition.Implicit): hello = 'world2' only = 'here' x = Impl() y = Impl2() x.__parent__ = y y.__parent__ = x self.assertTrue(x.__parent__.__parent__ is x) self.assertEqual(Acquisition.aq_acquire(x, 'hello'), 'world') self.assertEqual(Acquisition.aq_acquire(x, 'only'), 'here') self.assertRaises(AttributeError, Acquisition.aq_acquire, x, 'non_existant_attr') self.assertRaises(AttributeError, Acquisition.aq_acquire, y, 'non_existant_attr') def test_parent_parent_parent_circles(self): class Impl(Acquisition.Implicit): hello = 'world' class Impl2(Acquisition.Implicit): hello = 'world' class Impl3(Acquisition.Implicit): hello = 'world2' only = 'here' a = Impl() b = Impl2() c = Impl3() a.__parent__ = b b.__parent__ = c c.__parent__ = a # This is not quite what you'd expect, an AQ circle with an # intermediate object gives strange results self.assertTrue(a.__parent__.__parent__ is a) self.assertTrue(a.__parent__.__parent__.__parent__.aq_base is b) self.assertTrue(b.__parent__.__parent__ is b) self.assertTrue(c.__parent__.__parent__ is c) self.assertEqual(Acquisition.aq_acquire(a, 'hello'), 'world') self.assertEqual(Acquisition.aq_acquire(b, 'hello'), 'world') self.assertEqual(Acquisition.aq_acquire(c, 'hello'), 'world2') self.assertRaises(AttributeError, Acquisition.aq_acquire, a, 'only') self.assertEqual(Acquisition.aq_acquire(b, 'only'), 'here') self.assertEqual(Acquisition.aq_acquire(c, 'only'), 'here') self.assertRaises(AttributeError, Acquisition.aq_acquire, a, 'non_existant_attr') self.assertRaises(AttributeError, Acquisition.aq_acquire, b, 'non_existant_attr') self.assertRaises(AttributeError, Acquisition.aq_acquire, c, 'non_existant_attr') class TestAcquire(unittest.TestCase): def setUp(self): class Impl(Acquisition.Implicit): pass class Expl(Acquisition.Explicit): pass a = Impl('a') a.y = 42 a.b = Expl('b') a.b.z = 3 a.b.c = Impl('c') self.a = a self.acquire = Acquisition.aq_acquire def test_explicit_module_default(self): self.assertEqual(self.acquire(self.a.b.c, 'z'), 3) def test_explicit_module_true(self): self.assertEqual(self.acquire(self.a.b.c, 'z', explicit=True), 3) def test_explicit_module_false(self): self.assertEqual(self.acquire(self.a.b.c, 'z', explicit=False), 3) def test_explicit_wrapper_default(self): self.assertEqual(self.a.b.c.aq_acquire('z'), 3) def test_explicit_wrapper_true(self): self.assertEqual(self.a.b.c.aq_acquire('z', explicit=True), 3) def test_explicit_wrapper_false(self): self.assertEqual(self.a.b.c.aq_acquire('z', explicit=False), 3) class TestUnicode(unittest.TestCase): def test_implicit_aq_unicode_should_be_called(self): class A(Acquisition.Implicit): def __unicode__(self): return u'unicode was called' wrapped = A().__of__(A()) self.assertEqual(u'unicode was called', unicode(wrapped)) self.assertEqual(str(wrapped), repr(wrapped)) def test_explicit_aq_unicode_should_be_called(self): class A(Acquisition.Explicit): def __unicode__(self): return u'unicode was called' wrapped = A().__of__(A()) self.assertEqual(u'unicode was called', unicode(wrapped)) self.assertEqual(str(wrapped), repr(wrapped)) def test_implicit_should_fall_back_to_str(self): class A(Acquisition.Implicit): def __str__(self): return 'str was called' wrapped = A().__of__(A()) self.assertEqual(u'str was called', unicode(wrapped)) self.assertEqual('str was called', str(wrapped)) def test_explicit_should_fall_back_to_str(self): class A(Acquisition.Explicit): def __str__(self): return 'str was called' wrapped = A().__of__(A()) self.assertEqual(u'str was called', unicode(wrapped)) self.assertEqual('str was called', str(wrapped)) def test_str_fallback_should_be_called_with_wrapped_self(self): class A(Acquisition.Implicit): def __str__(self): return str(self.aq_parent == outer) outer = A() inner = A().__of__(outer) self.assertEqual(u'True', unicode(inner)) def test_unicode_should_be_called_with_wrapped_self(self): class A(Acquisition.Implicit): def __unicode__(self): return str(self.aq_parent == outer) outer = A() inner = A().__of__(outer) self.assertEqual(u'True', unicode(inner)) def test_suite(): return unittest.TestSuite(( DocTestSuite(), DocFileSuite('README.txt', package='Acquisition'), unittest.makeSuite(TestParent), unittest.makeSuite(TestAcquire), unittest.makeSuite(TestUnicode), )) zope2.13-2.13.21/source/zope.filerepresentation/0000755000175000017500000000000012214017701020310 5ustar arnauarnauzope2.13-2.13.21/source/zope.filerepresentation/setup.py0000644000175000017500000000450712214017701022030 0ustar arnauarnau############################################################################## # # Copyright (c) 2006-2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.filerepresentation package """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.filerepresentation', version='3.6.1', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', description='File-system Representation Interfaces', long_description=( read('README.txt') + '\n\n' + read('CHANGES.txt') ), keywords="zope3 filesystem representation", classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.filerepresentation', license='ZPL 2.1', packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope'], install_requires=['setuptools', 'zope.interface', 'zope.schema', ], include_package_data=True, zip_safe=True, ) zope2.13-2.13.21/source/zope.filerepresentation/PKG-INFO0000644000175000017500000000436512214017701021415 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.filerepresentation Version: 3.6.1 Summary: File-system Representation Interfaces Home-page: http://pypi.python.org/pypi/zope.filerepresentation Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ============================== File Representation Interfaces ============================== Overview -------- File-system representation interfaces. The interfaces defined here are used for file-system and file-system-like representations of objects, such as file-system synchronization, FTP, PUT, and WebDAV. ======= CHANGES ======= 3.6.1 (2011-11-29) ------------------ - Add undeclared ``zope.schema`` dependency. - Remove ``zope.testing`` test dependency and ``test`` extra. 3.6.0 (2009-10-08) ------------------ - Added `IRawReadFile` and `IRawWriteFile` interfaces. These extend `IReadFile` and `IWritefile`, respectively, to behave pretty much like a standard Python file object with a few embellishments. This in turn allows efficient, iterator- based implementations of file reading and writing. - Removed dependency on ``zope.container``: `IReadDirectory` and `IWriteDirectory` inherit only from interfaces defined in ``zope.interface`` and ``zope.interface.common.mapping``. 3.5.0 (2009-01-31) ------------------ - Changed use of ``zope.app.container`` to ``zope.container``. 3.4.0 (2007-10-02) ------------------ - Initial Zope-independent release. Keywords: zope3 filesystem representation Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/0000755000175000017500000000000012214017702022572 5ustar arnauarnauzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/0000755000175000017500000000000012214017702031142 5ustar arnauarnau././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/PKG-INFOzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/PKG-IN0000644000175000017500000000436512214017702032022 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.filerepresentation Version: 3.6.1 Summary: File-system Representation Interfaces Home-page: http://pypi.python.org/pypi/zope.filerepresentation Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ============================== File Representation Interfaces ============================== Overview -------- File-system representation interfaces. The interfaces defined here are used for file-system and file-system-like representations of objects, such as file-system synchronization, FTP, PUT, and WebDAV. ======= CHANGES ======= 3.6.1 (2011-11-29) ------------------ - Add undeclared ``zope.schema`` dependency. - Remove ``zope.testing`` test dependency and ``test`` extra. 3.6.0 (2009-10-08) ------------------ - Added `IRawReadFile` and `IRawWriteFile` interfaces. These extend `IReadFile` and `IWritefile`, respectively, to behave pretty much like a standard Python file object with a few embellishments. This in turn allows efficient, iterator- based implementations of file reading and writing. - Removed dependency on ``zope.container``: `IReadDirectory` and `IWriteDirectory` inherit only from interfaces defined in ``zope.interface`` and ``zope.interface.common.mapping``. 3.5.0 (2009-01-31) ------------------ - Changed use of ``zope.app.container`` to ``zope.container``. 3.4.0 (2007-10-02) ------------------ - Initial Zope-independent release. Keywords: zope3 filesystem representation Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 ././@LongLink0000000000000000000000000000016300000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/dependency_links.txtzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/depend0000644000175000017500000000000112214017702032313 0ustar arnauarnau ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/requires.txtzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/requir0000644000175000017500000000004512214017702032373 0ustar arnauarnausetuptools zope.interface zope.schema././@LongLink0000000000000000000000000000016500000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/namesp0000644000175000017500000000000512214017702032343 0ustar arnauarnauzope ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/zip-safezope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/zip-sa0000644000175000017500000000000112214017702032257 0ustar arnauarnau ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/top_level.txtzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/top_le0000644000175000017500000000000512214017702032342 0ustar arnauarnauzope ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/SOURCES.txtzope2.13-2.13.21/source/zope.filerepresentation/pip-egg-info/zope.filerepresentation.egg-info/SOURCE0000644000175000017500000000104212214017702032062 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.filerepresentation.egg-info/PKG-INFO pip-egg-info/zope.filerepresentation.egg-info/SOURCES.txt pip-egg-info/zope.filerepresentation.egg-info/dependency_links.txt pip-egg-info/zope.filerepresentation.egg-info/namespace_packages.txt pip-egg-info/zope.filerepresentation.egg-info/requires.txt pip-egg-info/zope.filerepresentation.egg-info/top_level.txt pip-egg-info/zope.filerepresentation.egg-info/zip-safe src/zope/__init__.py src/zope/filerepresentation/__init__.py src/zope/filerepresentation/interfaces.pyzope2.13-2.13.21/source/zope.filerepresentation/LICENSE.txt0000644000175000017500000000402612214017701022135 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.filerepresentation/README.txt0000644000175000017500000000047212214017701022011 0ustar arnauarnau============================== File Representation Interfaces ============================== Overview -------- File-system representation interfaces. The interfaces defined here are used for file-system and file-system-like representations of objects, such as file-system synchronization, FTP, PUT, and WebDAV. zope2.13-2.13.21/source/zope.filerepresentation/setup.cfg0000644000175000017500000000007312214017701022131 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.filerepresentation/COPYRIGHT.txt0000644000175000017500000000004012214017701022413 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.filerepresentation/buildout.cfg0000644000175000017500000000015112214017701022615 0ustar arnauarnau[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = zope.filerepresentation zope2.13-2.13.21/source/zope.filerepresentation/bootstrap.py0000644000175000017500000000330212214017701022675 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.filerepresentation/CHANGES.txt0000644000175000017500000000154612214017701022127 0ustar arnauarnau======= CHANGES ======= 3.6.1 (2011-11-29) ------------------ - Add undeclared ``zope.schema`` dependency. - Remove ``zope.testing`` test dependency and ``test`` extra. 3.6.0 (2009-10-08) ------------------ - Added `IRawReadFile` and `IRawWriteFile` interfaces. These extend `IReadFile` and `IWritefile`, respectively, to behave pretty much like a standard Python file object with a few embellishments. This in turn allows efficient, iterator- based implementations of file reading and writing. - Removed dependency on ``zope.container``: `IReadDirectory` and `IWriteDirectory` inherit only from interfaces defined in ``zope.interface`` and ``zope.interface.common.mapping``. 3.5.0 (2009-01-31) ------------------ - Changed use of ``zope.app.container`` to ``zope.container``. 3.4.0 (2007-10-02) ------------------ - Initial Zope-independent release. zope2.13-2.13.21/source/zope.filerepresentation/src/0000755000175000017500000000000012214017701021077 5ustar arnauarnauzope2.13-2.13.21/source/zope.filerepresentation/src/zope.filerepresentation.egg-info/0000755000175000017500000000000012214017701027447 5ustar arnauarnauzope2.13-2.13.21/source/zope.filerepresentation/src/zope.filerepresentation.egg-info/PKG-INFO0000644000175000017500000000436512214017701030554 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.filerepresentation Version: 3.6.1 Summary: File-system Representation Interfaces Home-page: http://pypi.python.org/pypi/zope.filerepresentation Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ============================== File Representation Interfaces ============================== Overview -------- File-system representation interfaces. The interfaces defined here are used for file-system and file-system-like representations of objects, such as file-system synchronization, FTP, PUT, and WebDAV. ======= CHANGES ======= 3.6.1 (2011-11-29) ------------------ - Add undeclared ``zope.schema`` dependency. - Remove ``zope.testing`` test dependency and ``test`` extra. 3.6.0 (2009-10-08) ------------------ - Added `IRawReadFile` and `IRawWriteFile` interfaces. These extend `IReadFile` and `IWritefile`, respectively, to behave pretty much like a standard Python file object with a few embellishments. This in turn allows efficient, iterator- based implementations of file reading and writing. - Removed dependency on ``zope.container``: `IReadDirectory` and `IWriteDirectory` inherit only from interfaces defined in ``zope.interface`` and ``zope.interface.common.mapping``. 3.5.0 (2009-01-31) ------------------ - Changed use of ``zope.app.container`` to ``zope.container``. 3.4.0 (2007-10-02) ------------------ - Initial Zope-independent release. Keywords: zope3 filesystem representation Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.filerepresentation/src/zope.filerepresentation.egg-info/dependency_links.txtzope2.13-2.13.21/source/zope.filerepresentation/src/zope.filerepresentation.egg-info/dependency_link0000644000175000017500000000000112214017701032514 0ustar arnauarnau zope2.13-2.13.21/source/zope.filerepresentation/src/zope.filerepresentation.egg-info/requires.txt0000644000175000017500000000004512214017701032046 0ustar arnauarnausetuptools zope.interface zope.schema././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.filerepresentation/src/zope.filerepresentation.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.filerepresentation/src/zope.filerepresentation.egg-info/namespace_packa0000644000175000017500000000000512214017701032460 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.filerepresentation/src/zope.filerepresentation.egg-info/zip-safe0000644000175000017500000000000112214017701031077 0ustar arnauarnau zope2.13-2.13.21/source/zope.filerepresentation/src/zope.filerepresentation.egg-info/top_level.txt0000644000175000017500000000000512214017701032174 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.filerepresentation/src/zope.filerepresentation.egg-info/SOURCES.txt0000644000175000017500000000104212214017701031330 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.filerepresentation.egg-info/PKG-INFO src/zope.filerepresentation.egg-info/SOURCES.txt src/zope.filerepresentation.egg-info/dependency_links.txt src/zope.filerepresentation.egg-info/namespace_packages.txt src/zope.filerepresentation.egg-info/requires.txt src/zope.filerepresentation.egg-info/top_level.txt src/zope.filerepresentation.egg-info/zip-safe src/zope/filerepresentation/__init__.py src/zope/filerepresentation/interfaces.pyzope2.13-2.13.21/source/zope.filerepresentation/src/zope/0000755000175000017500000000000012214017701022054 5ustar arnauarnauzope2.13-2.13.21/source/zope.filerepresentation/src/zope/__init__.py0000644000175000017500000000007012214017701024162 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.filerepresentation/src/zope/filerepresentation/0000755000175000017500000000000012214017701025756 5ustar arnauarnauzope2.13-2.13.21/source/zope.filerepresentation/src/zope/filerepresentation/interfaces.py0000644000175000017500000001653712214017701030467 0ustar arnauarnau############################################################################## # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. ############################################################################## """File-system representation interfaces The interfaces defined here are used for file-system and file-system-like representations of objects, such as file-system synchronization, FTP, PUT, and WebDAV. There are three issues we need to deal with: File system representation Every object is either a directory or a file. Properties There are two kinds of proprties: - Data properties Data properties are handled directly by the object implementation. - Meta-data properties Meta data properties are handled via annotations. Completeness We must have a complete lossless data representation for file-system synchronization. This is achieved through serialization of: - All annotations (not just properties), and - Extra data. Strategies for common access mechanisms: FTP - For getting directory info (statish) information: - Use Zope DublinCore to get modification times - Show as readable if we can access a read method. - Show as writable if we can access a write method. FTP and WebDAV - Treat as a directory if there is an adapter to `IReadDirectory`. Treat as a file otherwise. - For creating objects: - Directories: Look for an `IDirectoryFactory` adapter. - Files First lookj for a `IFileFactory` adapter with a name that is the same as the extention (e.g. ".pt"). Then look for an unnamed `IFileFactory` adapter. File-system synchronization Because this must be lossless, we will use class-based adapters for this, but we want to make it as easy as possible to use other adapters as well. For reading, there must be a class adapter to `IReadSync`. We will then apply rules similar to those above. """ __docformat__ = 'restructuredtext' from zope.interface import Interface from zope import schema from zope.interface.common.mapping import IEnumerableMapping, IItemMapping, \ IReadMapping class IReadFile(Interface): """Provide read access to file data """ def read(): """Return the file data """ def size(): """Return the data length in bytes. """ class IWriteFile(Interface): def write(data): """Update the file data """ class ICommonFileOperations(Interface): """Common file operations used by IRawReadFile and IRawWriteFile """ mimeType = schema.ASCIILine( title=u"File MIME type", description=u"Provided if it makes sense for this file data" + u"May be set prior to writing data to a file that " + u"is writeable. It is an error to set this on a " + u"file that is not writable.", readonly=True, ) encoding = schema.Bool( title=u"The encoding that this file uses", description=u"Provided if it makes sense for this file data" + u"May be set prior to writing data to a file that " + u"is writeable. It is an error to set this on a " + u"file that is not writable.", required=False, ) closed = schema.Bool( title=u"Is the file closed?", required=True, ) name = schema.TextLine( title=u"A representative file name", description=u"Provided if it makes sense for this file data" + u"May be set prior to writing data to a file that " + u"is writeable. It is an error to set this on a " + u"file that is not writable.", required=False, ) def seek(offset, whence=None): """Seek the file. See Python documentation for ``file`` for details. """ def tell(): """Return the file's current position. """ def close(): """Close the file. See Python documentation for ``file`` for details. """ class IRawReadFile(IReadFile, ICommonFileOperations): """Specialisation of IReadFile to make it act more like a Python file object. """ def read(size=None): """Read at most ``size`` bytes of file data. If ``size`` is None, return all the file data. """ def readline(size=None): """Read one entire line from the file. See Python documentation for ``file`` for details. """ def readlines(sizehint=None): """Read until EOF using readline() and return a list containing the lines thus read. See Python documentation for ``file`` for details. """ def __iter__(): """Return an iterator for the file. Note that unlike a Python standard ``file``, this does not necessarily have to return data line-by-line if doing so is inefficient. """ def next(): """Iterator protocol. See Python documentation for ``file`` for details. """ class IRawWriteFile(IWriteFile, ICommonFileOperations): """Specialisation of IWriteFile to make it act more like a Python file object. """ def write(data): """Write a chunk of data to the file. See Python documentation for ``file`` for details. """ def writelines(sequence): """Write a sequence of strings to the file. See Python documentation for ``file`` for details. """ def truncate(size): """Truncate the file. See Python documentation for ``file`` for details. """ def flush(): """Flush the file. See Python documentation for ``file`` for details. """ class IReadDirectory(IEnumerableMapping, IItemMapping, IReadMapping): """Objects that should be treated as directories for reading """ class IWriteDirectory(Interface): """Objects that should be treated as directories for writing """ def __setitem__(name, object): """Add the given `object` to the directory under the given name.""" def __delitem__(name): """Delete the named object from the directory.""" class IDirectoryFactory(Interface): def __call__(name): """Create a directory where a directory is an object with adapters to IReadDirectory and IWriteDirectory. """ class IFileFactory(Interface): def __call__(name, content_type, data): """Create a file where a file is an object with adapters to `IReadFile` and `IWriteFile`. The file `name`, content `type`, and `data` are provided to help create the object. """ # TODO: we will add additional interfaces for WebDAV and File-system # synchronization. zope2.13-2.13.21/source/zope.filerepresentation/src/zope/filerepresentation/__init__.py0000644000175000017500000000001712214017701030065 0ustar arnauarnau# Import this. zope2.13-2.13.21/source/zope.site/0000755000175000017500000000000012214017634015357 5ustar arnauarnauzope2.13-2.13.21/source/zope.site/setup.py0000644000175000017500000000526112214017634017075 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.site package """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.site', version='3.9.2', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description='Local registries for zope component architecture', long_description=( read('README.txt') + '\n\n' + '.. contents::\n\n' + read('src', 'zope', 'site', 'site.txt') + '\n\n' + read('CHANGES.txt') ), keywords = "zope component architecture local", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.site', license='ZPL 2.1', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope'], extras_require=dict( test=[ 'zope.configuration', 'zope.testing', ]), install_requires=[ 'setuptools', 'zope.annotation', 'zope.container', 'zope.security', 'zope.component>=3.8.0', 'zope.event', 'zope.interface', 'zope.lifecycleevent', 'zope.location>=3.7.0', ], include_package_data = True, zip_safe = False, ) zope2.13-2.13.21/source/zope.site/PKG-INFO0000644000175000017500000004403412214017634016461 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.site Version: 3.9.2 Summary: Local registries for zope component architecture Home-page: http://pypi.python.org/pypi/zope.site Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ===================================== Zope 3's Local Component Architecture ===================================== This package provides a local and persistent site manager implementation, so that one can register local utilities and adapters. It uses local adapter registries for its adapter and utility registry. The module also provides some facilities to organize the local software and ensures the correct behavior inside the ZODB. .. contents:: ============================= Sites and Local Site Managers ============================= This is an introduction of location-based component architecture. Creating and Accessing Sites ---------------------------- *Sites* are used to provide custom component setups for parts of your application or web site. Every folder: >>> from zope.site import folder >>> myfolder = folder.rootFolder() has the potential to become a site: >>> from zope.component.interfaces import ISite, IPossibleSite >>> IPossibleSite.providedBy(myfolder) True but is not yet one: >>> ISite.providedBy(myfolder) False If you would like your custom content component to be able to become a site, you can use the `SiteManagerContainer` mix-in class: >>> from zope import site >>> class MyContentComponent(site.SiteManagerContainer): ... pass >>> myContent = MyContentComponent() >>> IPossibleSite.providedBy(myContent) True >>> ISite.providedBy(myContent) False To convert a possible site to a real site, we have to provide a site manager: >>> sm = site.LocalSiteManager(myfolder) >>> myfolder.setSiteManager(sm) >>> ISite.providedBy(myfolder) True >>> myfolder.getSiteManager() is sm True Note that an event is generated when a local site manager is created: >>> from zope.component.eventtesting import getEvents >>> from zope.site.interfaces import INewLocalSite >>> [event] = getEvents(INewLocalSite) >>> event.manager is sm True If one tries to set a bogus site manager, a `ValueError` will be raised: >>> myfolder2 = folder.Folder() >>> myfolder2.setSiteManager(object) Traceback (most recent call last): ... ValueError: setSiteManager requires an IComponentLookup If the possible site has been changed to a site already, a `TypeError` is raised when one attempts to add a new site manager: >>> myfolder.setSiteManager(site.LocalSiteManager(myfolder)) Traceback (most recent call last): ... TypeError: Already a site There is also an adapter you can use to get the next site manager from any location: >>> myfolder['mysubfolder'] = folder.Folder() >>> import zope.component >>> zope.component.interfaces.IComponentLookup(myfolder['mysubfolder']) is sm True If the location passed is a site, the site manager of that site is returned: >>> zope.component.interfaces.IComponentLookup(myfolder) is sm True Using the Site Manager ---------------------- A site manager contains several *site management folders*, which are used to logically organize the software. When a site manager is initialized, a default site management folder is created: >>> sm = myfolder.getSiteManager() >>> default = sm['default'] >>> default.__class__ However, you can tell not to create the default site manager folder on LocalSiteManager creation: >>> nodefault = site.LocalSiteManager(myfolder, default_folder=False) >>> 'default' in nodefault False Also, note that when creating LocalSiteManager, its __parent__ is set to site that was passed to constructor and the __name__ is set to ++etc++site. >>> nodefault.__parent__ is myfolder True >>> nodefault.__name__ == '++etc++site' True You can easily create a new site management folder: >>> sm['mySMF'] = site.SiteManagementFolder() >>> sm['mySMF'].__class__ Once you have your site management folder -- let's use the default one -- we can register some components. Let's start with a utility: >>> import zope.interface >>> class IMyUtility(zope.interface.Interface): ... pass >>> import persistent >>> from zope.container.contained import Contained >>> class MyUtility(persistent.Persistent, Contained): ... zope.interface.implements(IMyUtility) ... def __init__(self, title): ... self.title = title ... def __repr__(self): ... return "%s('%s')" %(self.__class__.__name__, self.title) Now we can create an instance of our utility and put it in the site management folder and register it: >>> myutil = MyUtility('My custom utility') >>> default['myutil'] = myutil >>> sm.registerUtility(myutil, IMyUtility, 'u1') Now we can ask the site manager for the utility: >>> sm.queryUtility(IMyUtility, 'u1') MyUtility('My custom utility') Of course, the local site manager has also access to the global component registrations: >>> gutil = MyUtility('Global Utility') >>> from zope.component import getGlobalSiteManager >>> gsm = getGlobalSiteManager() >>> gsm.registerUtility(gutil, IMyUtility, 'gutil') >>> sm.queryUtility(IMyUtility, 'gutil') MyUtility('Global Utility') Next let's see whether we can also successfully register an adapter as well. Here the adapter will provide the size of a file: >>> class IFile(zope.interface.Interface): ... pass >>> class ISized(zope.interface.Interface): ... pass >>> class File(object): ... zope.interface.implements(IFile) >>> class FileSize(object): ... zope.interface.implements(ISized) ... def __init__(self, context): ... self.context = context Now that we have the adapter we need to register it: >>> sm.registerAdapter(FileSize, [IFile]) Finally, we can get the adapter for a file: >>> file = File() >>> size = sm.queryAdapter(file, ISized, name='') >>> size.__class__ >>> size.context is file True By the way, once you set a site >>> from zope.component import hooks >>> hooks.setSite(myfolder) you can simply use the zope.component's `getSiteManager()` method to get the nearest site manager: >>> from zope.component import getSiteManager >>> getSiteManager() is sm True This also means that you can simply use zope.component to look up your utility >>> from zope.component import getUtility >>> getUtility(IMyUtility, 'gutil') MyUtility('Global Utility') or the adapter via the interface's `__call__` method: >>> size = ISized(file) >>> size.__class__ >>> size.context is file True Multiple Sites -------------- Until now we have only dealt with one local and the global site. But things really become interesting, once we have multiple sites. We can override other local configuration. This behaviour uses the notion of location, therefore we need to configure the zope.location package first: >>> import zope.configuration.xmlconfig >>> _ = zope.configuration.xmlconfig.string(""" ... ... ... ... ... """) Let's now create a new folder called `folder11`, add it to `myfolder` and make it a site: >>> myfolder11 = folder.Folder() >>> myfolder['myfolder11'] = myfolder11 >>> myfolder11.setSiteManager(site.LocalSiteManager(myfolder11)) >>> sm11 = myfolder11.getSiteManager() If we ask the second site manager for its next, we get >>> sm11.__bases__ == (sm, ) True and the first site manager should have the folling sub manager: >>> sm.subs == (sm11,) True If we now register a second utility with the same name and interface with the new site manager folder, >>> default11 = sm11['default'] >>> myutil11 = MyUtility('Utility, uno & uno') >>> default11['myutil'] = myutil11 >>> sm11.registerUtility(myutil11, IMyUtility, 'u1') then it will will be available in the second site manager >>> sm11.queryUtility(IMyUtility, 'u1') MyUtility('Utility, uno & uno') but not in the first one: >>> sm.queryUtility(IMyUtility, 'u1') MyUtility('My custom utility') It is also interesting to look at the use cases of moving and copying a site. To do that we create a second root folder and make it a site, so that site hierarchy is as follows: :: _____ global site _____ / \ myfolder1 myfolder2 | myfolder11 >>> myfolder2 = folder.rootFolder() >>> myfolder2.setSiteManager(site.LocalSiteManager(myfolder2)) Before we can move or copy sites, we need to register two event subscribers that manage the wiring of site managers after moving or copying: >>> from zope import container >>> gsm.registerHandler( ... site.changeSiteConfigurationAfterMove, ... (ISite, container.interfaces.IObjectMovedEvent), ... ) We only have to register one event listener, since the copy action causes an `IObjectAddedEvent` to be created, which is just a special type of `IObjectMovedEvent`. First, make sure that everything is setup correctly in the first place: >>> myfolder11.getSiteManager().__bases__ == (myfolder.getSiteManager(), ) True >>> myfolder.getSiteManager().subs[0] is myfolder11.getSiteManager() True >>> myfolder2.getSiteManager().subs () Let's now move `myfolder11` from `myfolder` to `myfolder2`: >>> myfolder2['myfolder21'] = myfolder11 >>> del myfolder['myfolder11'] Now the next site manager for `myfolder11`'s site manager should have changed: >>> myfolder21 = myfolder11 >>> myfolder21.getSiteManager().__bases__ == (myfolder2.getSiteManager(), ) True >>> myfolder2.getSiteManager().subs[0] is myfolder21.getSiteManager() True >>> myfolder.getSiteManager().subs () Make sure that our interfaces and classes are picklable: >>> import sys >>> sys.modules['zope.site.tests'].IMyUtility = IMyUtility >>> IMyUtility.__module__ = 'zope.site.tests' >>> sys.modules['zope.site.tests'].MyUtility = MyUtility >>> MyUtility.__module__ = 'zope.site.tests' >>> from pickle import dumps, loads >>> data = dumps(myfolder2['myfolder21']) >>> myfolder['myfolder11'] = loads(data) >>> myfolder11 = myfolder['myfolder11'] >>> myfolder11.getSiteManager().__bases__ == (myfolder.getSiteManager(), ) True >>> myfolder.getSiteManager().subs[0] is myfolder11.getSiteManager() True >>> myfolder2.getSiteManager().subs[0] is myfolder21.getSiteManager() True Finally, let's check that everything works fine when our folder is moved to the folder that doesn't contain any site manager. Our folder's sitemanager's bases should be set to global site manager. >>> myfolder11.getSiteManager().__bases__ == (myfolder.getSiteManager(), ) True >>> nosm = folder.Folder() >>> nosm['root'] = myfolder11 >>> myfolder11.getSiteManager().__bases__ == (gsm, ) True ======= CHANGES ======= 3.9.2 (2010-09-25) ------------------ - Added not declared, but needed test dependency on `zope.testing`. 3.9.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctest' in favor of stdlib's 'doctest. - Removed use of 'zope.testing.doctestunit' in favor of stdlib's 'doctest. 3.9.0 (2009-12-29) ------------------ - Avoid a test dependency on zope.copypastemove by testing the correct persistent behavior of a site manager using the normal pickle module. 3.8.0 (2009-12-15) ------------------ - Removed functional testing setup and dependency on zope.app.testing. 3.7.1 (2009-11-18) ------------------ - Moved the zope.site.hooks functionality to zope.component.hooks as it isn't actually dealing with zope.site's concept of a site. - Import ISite and IPossibleSite from zope.component after they were moved there from zope.location. 3.7.0 (2009-09-29) ------------------ - Cleaned up the undeclared dependency on zope.app.publication by moving the two relevant subscriber registrations and their tests to that package. - Dropped the dependency on zope.traversing which was only used to access zope.location functionality. Configure zope.location for some tests. - Demoted zope.configuration to a testing dependency. 3.6.4 (2009-09-01) ------------------ - Set __parent__ and __name__ in the LocalSiteManager's constructor after calling constructor of its superclasses, so __name__ doesn't get overwritten with empty string by the Components constructor. - Don't set __parent__ and __name__ attributes of site manager in SiteManagerContainer's ``setSiteManager`` method, as they're already set for LocalSiteManager. Other site manager implementations are not required to have those attributes at all, so we're not adding them anymore. 3.6.3 (2009-07-27) ------------------ - Propagate an ObjectRemovedEvent to the SiteManager upon removal of a SiteManagerContainer. 3.6.2 (2009-07-24) ------------------ - Fixed tests to pass with latest packages. - Removed failing test of persistent interfaces, since it did not test anything in this package and used the deprecated ``zodbcode`` module. - Fix NameError when calling ``zope.site.testing.siteSetUp(site=True)``. - The ``getNextUtility`` and ``queryNextUtility`` functions was moved to ``zope.component``. While backward-compatibility imports are provided, it's strongly recommended to update your imports. 3.6.1 (2009-02-28) ------------------ - Import symbols moved from zope.traversing to zope.location from the new location. - Don't fail when changing component registry bases while moving ISite object to non-ISite object. - Allow specify whether to create 'default' SiteManagementFolder on initializing LocalSiteManager. Use the ``default_folder`` argument. - Add a containment constraint to the SiteManagementFolder that makes it only available to be contained in ILocalSiteManagers and other ISiteManagementFolders. - Change package's mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired. - Remove old unused code. Update package description. 3.6.0 (2009-01-31) ------------------ - Use zope.container instead of zope.app.container. 3.5.1 (2009-01-27) ------------------ - Extracted from zope.app.component (trunk, 3.5.1 under development) as part of an effort to clean up dependencies between Zope packages. Keywords: zope component architecture local Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.site/pip-egg-info/0000755000175000017500000000000012214017634017640 5ustar arnauarnauzope2.13-2.13.21/source/zope.site/pip-egg-info/zope.site.egg-info/0000755000175000017500000000000012214017634023252 5ustar arnauarnauzope2.13-2.13.21/source/zope.site/pip-egg-info/zope.site.egg-info/PKG-INFO0000644000175000017500000004472212214017634024360 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.site Version: 3.9.2 Summary: Local registries for zope component architecture Home-page: http://pypi.python.org/pypi/zope.site Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ===================================== Zope 3's Local Component Architecture ===================================== This package provides a local and persistent site manager implementation, so that one can register local utilities and adapters. It uses local adapter registries for its adapter and utility registry. The module also provides some facilities to organize the local software and ensures the correct behavior inside the ZODB. .. contents:: ============================= Sites and Local Site Managers ============================= This is an introduction of location-based component architecture. Creating and Accessing Sites ---------------------------- *Sites* are used to provide custom component setups for parts of your application or web site. Every folder: >>> from zope.site import folder >>> myfolder = folder.rootFolder() has the potential to become a site: >>> from zope.component.interfaces import ISite, IPossibleSite >>> IPossibleSite.providedBy(myfolder) True but is not yet one: >>> ISite.providedBy(myfolder) False If you would like your custom content component to be able to become a site, you can use the `SiteManagerContainer` mix-in class: >>> from zope import site >>> class MyContentComponent(site.SiteManagerContainer): ... pass >>> myContent = MyContentComponent() >>> IPossibleSite.providedBy(myContent) True >>> ISite.providedBy(myContent) False To convert a possible site to a real site, we have to provide a site manager: >>> sm = site.LocalSiteManager(myfolder) >>> myfolder.setSiteManager(sm) >>> ISite.providedBy(myfolder) True >>> myfolder.getSiteManager() is sm True Note that an event is generated when a local site manager is created: >>> from zope.component.eventtesting import getEvents >>> from zope.site.interfaces import INewLocalSite >>> [event] = getEvents(INewLocalSite) >>> event.manager is sm True If one tries to set a bogus site manager, a `ValueError` will be raised: >>> myfolder2 = folder.Folder() >>> myfolder2.setSiteManager(object) Traceback (most recent call last): ... ValueError: setSiteManager requires an IComponentLookup If the possible site has been changed to a site already, a `TypeError` is raised when one attempts to add a new site manager: >>> myfolder.setSiteManager(site.LocalSiteManager(myfolder)) Traceback (most recent call last): ... TypeError: Already a site There is also an adapter you can use to get the next site manager from any location: >>> myfolder['mysubfolder'] = folder.Folder() >>> import zope.component >>> zope.component.interfaces.IComponentLookup(myfolder['mysubfolder']) is sm True If the location passed is a site, the site manager of that site is returned: >>> zope.component.interfaces.IComponentLookup(myfolder) is sm True Using the Site Manager ---------------------- A site manager contains several *site management folders*, which are used to logically organize the software. When a site manager is initialized, a default site management folder is created: >>> sm = myfolder.getSiteManager() >>> default = sm['default'] >>> default.__class__ However, you can tell not to create the default site manager folder on LocalSiteManager creation: >>> nodefault = site.LocalSiteManager(myfolder, default_folder=False) >>> 'default' in nodefault False Also, note that when creating LocalSiteManager, its __parent__ is set to site that was passed to constructor and the __name__ is set to ++etc++site. >>> nodefault.__parent__ is myfolder True >>> nodefault.__name__ == '++etc++site' True You can easily create a new site management folder: >>> sm['mySMF'] = site.SiteManagementFolder() >>> sm['mySMF'].__class__ Once you have your site management folder -- let's use the default one -- we can register some components. Let's start with a utility: >>> import zope.interface >>> class IMyUtility(zope.interface.Interface): ... pass >>> import persistent >>> from zope.container.contained import Contained >>> class MyUtility(persistent.Persistent, Contained): ... zope.interface.implements(IMyUtility) ... def __init__(self, title): ... self.title = title ... def __repr__(self): ... return "%s('%s')" %(self.__class__.__name__, self.title) Now we can create an instance of our utility and put it in the site management folder and register it: >>> myutil = MyUtility('My custom utility') >>> default['myutil'] = myutil >>> sm.registerUtility(myutil, IMyUtility, 'u1') Now we can ask the site manager for the utility: >>> sm.queryUtility(IMyUtility, 'u1') MyUtility('My custom utility') Of course, the local site manager has also access to the global component registrations: >>> gutil = MyUtility('Global Utility') >>> from zope.component import getGlobalSiteManager >>> gsm = getGlobalSiteManager() >>> gsm.registerUtility(gutil, IMyUtility, 'gutil') >>> sm.queryUtility(IMyUtility, 'gutil') MyUtility('Global Utility') Next let's see whether we can also successfully register an adapter as well. Here the adapter will provide the size of a file: >>> class IFile(zope.interface.Interface): ... pass >>> class ISized(zope.interface.Interface): ... pass >>> class File(object): ... zope.interface.implements(IFile) >>> class FileSize(object): ... zope.interface.implements(ISized) ... def __init__(self, context): ... self.context = context Now that we have the adapter we need to register it: >>> sm.registerAdapter(FileSize, [IFile]) Finally, we can get the adapter for a file: >>> file = File() >>> size = sm.queryAdapter(file, ISized, name='') >>> size.__class__ >>> size.context is file True By the way, once you set a site >>> from zope.component import hooks >>> hooks.setSite(myfolder) you can simply use the zope.component's `getSiteManager()` method to get the nearest site manager: >>> from zope.component import getSiteManager >>> getSiteManager() is sm True This also means that you can simply use zope.component to look up your utility >>> from zope.component import getUtility >>> getUtility(IMyUtility, 'gutil') MyUtility('Global Utility') or the adapter via the interface's `__call__` method: >>> size = ISized(file) >>> size.__class__ >>> size.context is file True Multiple Sites -------------- Until now we have only dealt with one local and the global site. But things really become interesting, once we have multiple sites. We can override other local configuration. This behaviour uses the notion of location, therefore we need to configure the zope.location package first: >>> import zope.configuration.xmlconfig >>> _ = zope.configuration.xmlconfig.string(""" ... ... ... ... ... """) Let's now create a new folder called `folder11`, add it to `myfolder` and make it a site: >>> myfolder11 = folder.Folder() >>> myfolder['myfolder11'] = myfolder11 >>> myfolder11.setSiteManager(site.LocalSiteManager(myfolder11)) >>> sm11 = myfolder11.getSiteManager() If we ask the second site manager for its next, we get >>> sm11.__bases__ == (sm, ) True and the first site manager should have the folling sub manager: >>> sm.subs == (sm11,) True If we now register a second utility with the same name and interface with the new site manager folder, >>> default11 = sm11['default'] >>> myutil11 = MyUtility('Utility, uno & uno') >>> default11['myutil'] = myutil11 >>> sm11.registerUtility(myutil11, IMyUtility, 'u1') then it will will be available in the second site manager >>> sm11.queryUtility(IMyUtility, 'u1') MyUtility('Utility, uno & uno') but not in the first one: >>> sm.queryUtility(IMyUtility, 'u1') MyUtility('My custom utility') It is also interesting to look at the use cases of moving and copying a site. To do that we create a second root folder and make it a site, so that site hierarchy is as follows: :: _____ global site _____ / \ myfolder1 myfolder2 | myfolder11 >>> myfolder2 = folder.rootFolder() >>> myfolder2.setSiteManager(site.LocalSiteManager(myfolder2)) Before we can move or copy sites, we need to register two event subscribers that manage the wiring of site managers after moving or copying: >>> from zope import container >>> gsm.registerHandler( ... site.changeSiteConfigurationAfterMove, ... (ISite, container.interfaces.IObjectMovedEvent), ... ) We only have to register one event listener, since the copy action causes an `IObjectAddedEvent` to be created, which is just a special type of `IObjectMovedEvent`. First, make sure that everything is setup correctly in the first place: >>> myfolder11.getSiteManager().__bases__ == (myfolder.getSiteManager(), ) True >>> myfolder.getSiteManager().subs[0] is myfolder11.getSiteManager() True >>> myfolder2.getSiteManager().subs () Let's now move `myfolder11` from `myfolder` to `myfolder2`: >>> myfolder2['myfolder21'] = myfolder11 >>> del myfolder['myfolder11'] Now the next site manager for `myfolder11`'s site manager should have changed: >>> myfolder21 = myfolder11 >>> myfolder21.getSiteManager().__bases__ == (myfolder2.getSiteManager(), ) True >>> myfolder2.getSiteManager().subs[0] is myfolder21.getSiteManager() True >>> myfolder.getSiteManager().subs () Make sure that our interfaces and classes are picklable: >>> import sys >>> sys.modules['zope.site.tests'].IMyUtility = IMyUtility >>> IMyUtility.__module__ = 'zope.site.tests' >>> sys.modules['zope.site.tests'].MyUtility = MyUtility >>> MyUtility.__module__ = 'zope.site.tests' >>> from pickle import dumps, loads >>> data = dumps(myfolder2['myfolder21']) >>> myfolder['myfolder11'] = loads(data) >>> myfolder11 = myfolder['myfolder11'] >>> myfolder11.getSiteManager().__bases__ == (myfolder.getSiteManager(), ) True >>> myfolder.getSiteManager().subs[0] is myfolder11.getSiteManager() True >>> myfolder2.getSiteManager().subs[0] is myfolder21.getSiteManager() True Finally, let's check that everything works fine when our folder is moved to the folder that doesn't contain any site manager. Our folder's sitemanager's bases should be set to global site manager. >>> myfolder11.getSiteManager().__bases__ == (myfolder.getSiteManager(), ) True >>> nosm = folder.Folder() >>> nosm['root'] = myfolder11 >>> myfolder11.getSiteManager().__bases__ == (gsm, ) True ======= CHANGES ======= 3.9.2 (2010-09-25) ------------------ - Added not declared, but needed test dependency on `zope.testing`. 3.9.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctest' in favor of stdlib's 'doctest. - Removed use of 'zope.testing.doctestunit' in favor of stdlib's 'doctest. 3.9.0 (2009-12-29) ------------------ - Avoid a test dependency on zope.copypastemove by testing the correct persistent behavior of a site manager using the normal pickle module. 3.8.0 (2009-12-15) ------------------ - Removed functional testing setup and dependency on zope.app.testing. 3.7.1 (2009-11-18) ------------------ - Moved the zope.site.hooks functionality to zope.component.hooks as it isn't actually dealing with zope.site's concept of a site. - Import ISite and IPossibleSite from zope.component after they were moved there from zope.location. 3.7.0 (2009-09-29) ------------------ - Cleaned up the undeclared dependency on zope.app.publication by moving the two relevant subscriber registrations and their tests to that package. - Dropped the dependency on zope.traversing which was only used to access zope.location functionality. Configure zope.location for some tests. - Demoted zope.configuration to a testing dependency. 3.6.4 (2009-09-01) ------------------ - Set __parent__ and __name__ in the LocalSiteManager's constructor after calling constructor of its superclasses, so __name__ doesn't get overwritten with empty string by the Components constructor. - Don't set __parent__ and __name__ attributes of site manager in SiteManagerContainer's ``setSiteManager`` method, as they're already set for LocalSiteManager. Other site manager implementations are not required to have those attributes at all, so we're not adding them anymore. 3.6.3 (2009-07-27) ------------------ - Propagate an ObjectRemovedEvent to the SiteManager upon removal of a SiteManagerContainer. 3.6.2 (2009-07-24) ------------------ - Fixed tests to pass with latest packages. - Removed failing test of persistent interfaces, since it did not test anything in this package and used the deprecated ``zodbcode`` module. - Fix NameError when calling ``zope.site.testing.siteSetUp(site=True)``. - The ``getNextUtility`` and ``queryNextUtility`` functions was moved to ``zope.component``. While backward-compatibility imports are provided, it's strongly recommended to update your imports. 3.6.1 (2009-02-28) ------------------ - Import symbols moved from zope.traversing to zope.location from the new location. - Don't fail when changing component registry bases while moving ISite object to non-ISite object. - Allow specify whether to create 'default' SiteManagementFolder on initializing LocalSiteManager. Use the ``default_folder`` argument. - Add a containment constraint to the SiteManagementFolder that makes it only available to be contained in ILocalSiteManagers and other ISiteManagementFolders. - Change package's mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired. - Remove old unused code. Update package description. 3.6.0 (2009-01-31) ------------------ - Use zope.container instead of zope.app.container. 3.5.1 (2009-01-27) ------------------ - Extracted from zope.app.component (trunk, 3.5.1 under development) as part of an effort to clean up dependencies between Zope packages. Keywords: zope component architecture local Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.site/pip-egg-info/zope.site.egg-info/dependency_links.txt0000644000175000017500000000000112214017634027320 0ustar arnauarnau zope2.13-2.13.21/source/zope.site/pip-egg-info/zope.site.egg-info/requires.txt0000644000175000017500000000027012214017634025651 0ustar arnauarnausetuptools zope.annotation zope.container zope.security zope.component>=3.8.0 zope.event zope.interface zope.lifecycleevent zope.location>=3.7.0 [test] zope.configuration zope.testingzope2.13-2.13.21/source/zope.site/pip-egg-info/zope.site.egg-info/namespace_packages.txt0000644000175000017500000000000512214017634027600 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.site/pip-egg-info/zope.site.egg-info/top_level.txt0000644000175000017500000000000512214017634025777 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.site/pip-egg-info/zope.site.egg-info/SOURCES.txt0000644000175000017500000000140712214017634025140 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.site.egg-info/PKG-INFO pip-egg-info/zope.site.egg-info/SOURCES.txt pip-egg-info/zope.site.egg-info/dependency_links.txt pip-egg-info/zope.site.egg-info/namespace_packages.txt pip-egg-info/zope.site.egg-info/not-zip-safe pip-egg-info/zope.site.egg-info/requires.txt pip-egg-info/zope.site.egg-info/top_level.txt src/zope/__init__.py src/zope/site/__init__.py src/zope/site/folder.py src/zope/site/hooks.py src/zope/site/interfaces.py src/zope/site/next.py src/zope/site/site.py src/zope/site/testing.py src/zope/site/tests/__init__.py src/zope/site/tests/test_folder.py src/zope/site/tests/test_localsitemanager.py src/zope/site/tests/test_registration.py src/zope/site/tests/test_site.py src/zope/site/tests/test_sitemanagercontainer.pyzope2.13-2.13.21/source/zope.site/pip-egg-info/zope.site.egg-info/not-zip-safe0000644000175000017500000000000112214017634025500 0ustar arnauarnau zope2.13-2.13.21/source/zope.site/LICENSE.txt0000644000175000017500000000402612214017634017204 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.site/README.txt0000644000175000017500000000066512214017634017064 0ustar arnauarnau===================================== Zope 3's Local Component Architecture ===================================== This package provides a local and persistent site manager implementation, so that one can register local utilities and adapters. It uses local adapter registries for its adapter and utility registry. The module also provides some facilities to organize the local software and ensures the correct behavior inside the ZODB. zope2.13-2.13.21/source/zope.site/setup.cfg0000644000175000017500000000007312214017634017200 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.site/COPYRIGHT.txt0000644000175000017500000000004012214017634017462 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.site/buildout.cfg0000644000175000017500000000060012214017634017663 0ustar arnauarnau[buildout] develop = . parts = test coverage-test coverage-report [test] recipe = zc.recipe.testrunner eggs = zope.site [test] [coverage-test] recipe = zc.recipe.testrunner eggs = zope.site [test] defaults = ['--coverage', '../../coverage'] [coverage-report] recipe = zc.recipe.egg eggs = z3c.coverage scripts = coverage=coverage-report arguments = ('coverage', 'coverage/report') zope2.13-2.13.21/source/zope.site/bootstrap.py0000644000175000017500000000733012214017634017751 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.site/CHANGES.txt0000644000175000017500000000653312214017634017177 0ustar arnauarnau======= CHANGES ======= 3.9.2 (2010-09-25) ------------------ - Added not declared, but needed test dependency on `zope.testing`. 3.9.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctest' in favor of stdlib's 'doctest. - Removed use of 'zope.testing.doctestunit' in favor of stdlib's 'doctest. 3.9.0 (2009-12-29) ------------------ - Avoid a test dependency on zope.copypastemove by testing the correct persistent behavior of a site manager using the normal pickle module. 3.8.0 (2009-12-15) ------------------ - Removed functional testing setup and dependency on zope.app.testing. 3.7.1 (2009-11-18) ------------------ - Moved the zope.site.hooks functionality to zope.component.hooks as it isn't actually dealing with zope.site's concept of a site. - Import ISite and IPossibleSite from zope.component after they were moved there from zope.location. 3.7.0 (2009-09-29) ------------------ - Cleaned up the undeclared dependency on zope.app.publication by moving the two relevant subscriber registrations and their tests to that package. - Dropped the dependency on zope.traversing which was only used to access zope.location functionality. Configure zope.location for some tests. - Demoted zope.configuration to a testing dependency. 3.6.4 (2009-09-01) ------------------ - Set __parent__ and __name__ in the LocalSiteManager's constructor after calling constructor of its superclasses, so __name__ doesn't get overwritten with empty string by the Components constructor. - Don't set __parent__ and __name__ attributes of site manager in SiteManagerContainer's ``setSiteManager`` method, as they're already set for LocalSiteManager. Other site manager implementations are not required to have those attributes at all, so we're not adding them anymore. 3.6.3 (2009-07-27) ------------------ - Propagate an ObjectRemovedEvent to the SiteManager upon removal of a SiteManagerContainer. 3.6.2 (2009-07-24) ------------------ - Fixed tests to pass with latest packages. - Removed failing test of persistent interfaces, since it did not test anything in this package and used the deprecated ``zodbcode`` module. - Fix NameError when calling ``zope.site.testing.siteSetUp(site=True)``. - The ``getNextUtility`` and ``queryNextUtility`` functions was moved to ``zope.component``. While backward-compatibility imports are provided, it's strongly recommended to update your imports. 3.6.1 (2009-02-28) ------------------ - Import symbols moved from zope.traversing to zope.location from the new location. - Don't fail when changing component registry bases while moving ISite object to non-ISite object. - Allow specify whether to create 'default' SiteManagementFolder on initializing LocalSiteManager. Use the ``default_folder`` argument. - Add a containment constraint to the SiteManagementFolder that makes it only available to be contained in ILocalSiteManagers and other ISiteManagementFolders. - Change package's mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired. - Remove old unused code. Update package description. 3.6.0 (2009-01-31) ------------------ - Use zope.container instead of zope.app.container. 3.5.1 (2009-01-27) ------------------ - Extracted from zope.app.component (trunk, 3.5.1 under development) as part of an effort to clean up dependencies between Zope packages. zope2.13-2.13.21/source/zope.site/src/0000755000175000017500000000000012214017634016146 5ustar arnauarnauzope2.13-2.13.21/source/zope.site/src/zope/0000755000175000017500000000000012214017634017123 5ustar arnauarnauzope2.13-2.13.21/source/zope.site/src/zope/site/0000755000175000017500000000000012214017634020067 5ustar arnauarnauzope2.13-2.13.21/source/zope.site/src/zope/site/configure.zcml0000644000175000017500000000475612214017634022753 0ustar arnauarnau zope2.13-2.13.21/source/zope.site/src/zope/site/hooks.py0000644000175000017500000000235312214017634021567 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Hooks for getting and setting a site in the thread global namespace. """ __docformat__ = 'restructuredtext' from zope.component.hooks import (read_property, SiteInfo, siteinfo, setSite, getSite, getSiteManager, adapter_hook, setHooks, resetHooks, setSite, clearSite) # BBB zope2.13-2.13.21/source/zope.site/src/zope/site/interfaces.py0000644000175000017500000000610512214017634022566 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interfaces for the Local Component Architecture """ import zope.interface import zope.component.interfaces import zope.container.interfaces import zope.container.constraints import zope.location.interfaces from zope.annotation.interfaces import IAttributeAnnotatable class INewLocalSite(zope.interface.Interface): """Event: a local site was created """ manager = zope.interface.Attribute("The new site manager") class NewLocalSite(object): """Event: a local site was created """ zope.interface.implements(INewLocalSite) def __init__(self, manager): self.manager = manager class ILocalSiteManager(zope.component.interfaces.IComponents): """Site Managers act as containers for registerable components. If a Site Manager is asked for an adapter or utility, it checks for those it contains before using a context-based lookup to find another site manager to delegate to. If no other site manager is found they defer to the global site manager which contains file based utilities and adapters. """ subs = zope.interface.Attribute( "A collection of registries that describe the next level " "of the registry tree. They are the children of this " "registry node. This attribute should never be " "manipulated manually. Use `addSub()` and `removeSub()` " "instead.") def addSub(sub): """Add a new sub-registry to the node. Important: This method should *not* be used manually. It is automatically called by `setNext()`. To add a new registry to the tree, use `sub.setNext(self, self.base)` instead! """ def removeSub(sub): """Remove a sub-registry to the node. Important: This method should *not* be used manually. It is automatically called by `setNext()`. To remove a registry from the tree, use `sub.setNext(None)` instead! """ class ISiteManagementFolder(zope.container.interfaces.IContainer): """Component and component registration containers.""" zope.container.constraints.containers( ILocalSiteManager, '.ISiteManagementFolder') class IFolder(zope.container.interfaces.IContainer, zope.component.interfaces.IPossibleSite, IAttributeAnnotatable): """The standard Zope Folder object interface.""" class IRootFolder(IFolder, zope.location.interfaces.IRoot): """The standard Zope root Folder object interface.""" zope2.13-2.13.21/source/zope.site/src/zope/site/folder.py0000644000175000017500000000457112214017634021723 0ustar arnauarnau############################################################################# # # Copyright (c) 2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import zope.component.interfaces import zope.container.folder from zope.interface import implements, directlyProvides from zope.site.interfaces import IFolder, IRootFolder from zope.site.site import SiteManagerContainer class Folder(zope.container.folder.Folder, SiteManagerContainer): implements(IFolder) def rootFolder(): f = Folder() directlyProvides(f, IRootFolder) return f class FolderSublocations(object): """Get the sublocations of a folder The subobjects of a folder include it's contents and it's site manager if it is a site. >>> from zope.container.contained import Contained >>> folder = Folder() >>> folder['ob1'] = Contained() >>> folder['ob2'] = Contained() >>> folder['ob3'] = Contained() >>> subs = list(FolderSublocations(folder).sublocations()) >>> subs.remove(folder['ob1']) >>> subs.remove(folder['ob2']) >>> subs.remove(folder['ob3']) >>> subs [] >>> sm = Contained() >>> from zope.interface import directlyProvides >>> from zope.component.interfaces import IComponentLookup >>> directlyProvides(sm, IComponentLookup) >>> folder.setSiteManager(sm) >>> directlyProvides(folder, zope.component.interfaces.ISite) >>> subs = list(FolderSublocations(folder).sublocations()) >>> subs.remove(folder['ob1']) >>> subs.remove(folder['ob2']) >>> subs.remove(folder['ob3']) >>> subs.remove(sm) >>> subs [] """ def __init__(self, folder): self.folder = folder def sublocations(self): folder = self.folder for key in folder: yield folder[key] if zope.component.interfaces.ISite.providedBy(folder): yield folder.getSiteManager() zope2.13-2.13.21/source/zope.site/src/zope/site/next.py0000644000175000017500000000010212214017634021410 0ustar arnauarnau# BBB from zope.component import getNextUtility, queryNextUtility zope2.13-2.13.21/source/zope.site/src/zope/site/__init__.py0000644000175000017500000000201712214017634022200 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Local Component Architecture """ from zope.site.site import (SiteManagerContainer, SiteManagementFolder, SiteManagerAdapter) from zope.site.site import LocalSiteManager, changeSiteConfigurationAfterMove from zope.site.site import threadSiteSubscriber from zope.site.site import clearThreadSiteSubscriber # BBB from zope.component import getNextUtility, queryNextUtility zope2.13-2.13.21/source/zope.site/src/zope/site/site.txt0000644000175000017500000002432412214017634021601 0ustar arnauarnau============================= Sites and Local Site Managers ============================= This is an introduction of location-based component architecture. Creating and Accessing Sites ---------------------------- *Sites* are used to provide custom component setups for parts of your application or web site. Every folder: >>> from zope.site import folder >>> myfolder = folder.rootFolder() has the potential to become a site: >>> from zope.component.interfaces import ISite, IPossibleSite >>> IPossibleSite.providedBy(myfolder) True but is not yet one: >>> ISite.providedBy(myfolder) False If you would like your custom content component to be able to become a site, you can use the `SiteManagerContainer` mix-in class: >>> from zope import site >>> class MyContentComponent(site.SiteManagerContainer): ... pass >>> myContent = MyContentComponent() >>> IPossibleSite.providedBy(myContent) True >>> ISite.providedBy(myContent) False To convert a possible site to a real site, we have to provide a site manager: >>> sm = site.LocalSiteManager(myfolder) >>> myfolder.setSiteManager(sm) >>> ISite.providedBy(myfolder) True >>> myfolder.getSiteManager() is sm True Note that an event is generated when a local site manager is created: >>> from zope.component.eventtesting import getEvents >>> from zope.site.interfaces import INewLocalSite >>> [event] = getEvents(INewLocalSite) >>> event.manager is sm True If one tries to set a bogus site manager, a `ValueError` will be raised: >>> myfolder2 = folder.Folder() >>> myfolder2.setSiteManager(object) Traceback (most recent call last): ... ValueError: setSiteManager requires an IComponentLookup If the possible site has been changed to a site already, a `TypeError` is raised when one attempts to add a new site manager: >>> myfolder.setSiteManager(site.LocalSiteManager(myfolder)) Traceback (most recent call last): ... TypeError: Already a site There is also an adapter you can use to get the next site manager from any location: >>> myfolder['mysubfolder'] = folder.Folder() >>> import zope.component >>> zope.component.interfaces.IComponentLookup(myfolder['mysubfolder']) is sm True If the location passed is a site, the site manager of that site is returned: >>> zope.component.interfaces.IComponentLookup(myfolder) is sm True Using the Site Manager ---------------------- A site manager contains several *site management folders*, which are used to logically organize the software. When a site manager is initialized, a default site management folder is created: >>> sm = myfolder.getSiteManager() >>> default = sm['default'] >>> default.__class__ However, you can tell not to create the default site manager folder on LocalSiteManager creation: >>> nodefault = site.LocalSiteManager(myfolder, default_folder=False) >>> 'default' in nodefault False Also, note that when creating LocalSiteManager, its __parent__ is set to site that was passed to constructor and the __name__ is set to ++etc++site. >>> nodefault.__parent__ is myfolder True >>> nodefault.__name__ == '++etc++site' True You can easily create a new site management folder: >>> sm['mySMF'] = site.SiteManagementFolder() >>> sm['mySMF'].__class__ Once you have your site management folder -- let's use the default one -- we can register some components. Let's start with a utility: >>> import zope.interface >>> class IMyUtility(zope.interface.Interface): ... pass >>> import persistent >>> from zope.container.contained import Contained >>> class MyUtility(persistent.Persistent, Contained): ... zope.interface.implements(IMyUtility) ... def __init__(self, title): ... self.title = title ... def __repr__(self): ... return "%s('%s')" %(self.__class__.__name__, self.title) Now we can create an instance of our utility and put it in the site management folder and register it: >>> myutil = MyUtility('My custom utility') >>> default['myutil'] = myutil >>> sm.registerUtility(myutil, IMyUtility, 'u1') Now we can ask the site manager for the utility: >>> sm.queryUtility(IMyUtility, 'u1') MyUtility('My custom utility') Of course, the local site manager has also access to the global component registrations: >>> gutil = MyUtility('Global Utility') >>> from zope.component import getGlobalSiteManager >>> gsm = getGlobalSiteManager() >>> gsm.registerUtility(gutil, IMyUtility, 'gutil') >>> sm.queryUtility(IMyUtility, 'gutil') MyUtility('Global Utility') Next let's see whether we can also successfully register an adapter as well. Here the adapter will provide the size of a file: >>> class IFile(zope.interface.Interface): ... pass >>> class ISized(zope.interface.Interface): ... pass >>> class File(object): ... zope.interface.implements(IFile) >>> class FileSize(object): ... zope.interface.implements(ISized) ... def __init__(self, context): ... self.context = context Now that we have the adapter we need to register it: >>> sm.registerAdapter(FileSize, [IFile]) Finally, we can get the adapter for a file: >>> file = File() >>> size = sm.queryAdapter(file, ISized, name='') >>> size.__class__ >>> size.context is file True By the way, once you set a site >>> from zope.component import hooks >>> hooks.setSite(myfolder) you can simply use the zope.component's `getSiteManager()` method to get the nearest site manager: >>> from zope.component import getSiteManager >>> getSiteManager() is sm True This also means that you can simply use zope.component to look up your utility >>> from zope.component import getUtility >>> getUtility(IMyUtility, 'gutil') MyUtility('Global Utility') or the adapter via the interface's `__call__` method: >>> size = ISized(file) >>> size.__class__ >>> size.context is file True Multiple Sites -------------- Until now we have only dealt with one local and the global site. But things really become interesting, once we have multiple sites. We can override other local configuration. This behaviour uses the notion of location, therefore we need to configure the zope.location package first: >>> import zope.configuration.xmlconfig >>> _ = zope.configuration.xmlconfig.string(""" ... ... ... ... ... """) Let's now create a new folder called `folder11`, add it to `myfolder` and make it a site: >>> myfolder11 = folder.Folder() >>> myfolder['myfolder11'] = myfolder11 >>> myfolder11.setSiteManager(site.LocalSiteManager(myfolder11)) >>> sm11 = myfolder11.getSiteManager() If we ask the second site manager for its next, we get >>> sm11.__bases__ == (sm, ) True and the first site manager should have the folling sub manager: >>> sm.subs == (sm11,) True If we now register a second utility with the same name and interface with the new site manager folder, >>> default11 = sm11['default'] >>> myutil11 = MyUtility('Utility, uno & uno') >>> default11['myutil'] = myutil11 >>> sm11.registerUtility(myutil11, IMyUtility, 'u1') then it will will be available in the second site manager >>> sm11.queryUtility(IMyUtility, 'u1') MyUtility('Utility, uno & uno') but not in the first one: >>> sm.queryUtility(IMyUtility, 'u1') MyUtility('My custom utility') It is also interesting to look at the use cases of moving and copying a site. To do that we create a second root folder and make it a site, so that site hierarchy is as follows: :: _____ global site _____ / \ myfolder1 myfolder2 | myfolder11 >>> myfolder2 = folder.rootFolder() >>> myfolder2.setSiteManager(site.LocalSiteManager(myfolder2)) Before we can move or copy sites, we need to register two event subscribers that manage the wiring of site managers after moving or copying: >>> from zope import container >>> gsm.registerHandler( ... site.changeSiteConfigurationAfterMove, ... (ISite, container.interfaces.IObjectMovedEvent), ... ) We only have to register one event listener, since the copy action causes an `IObjectAddedEvent` to be created, which is just a special type of `IObjectMovedEvent`. First, make sure that everything is setup correctly in the first place: >>> myfolder11.getSiteManager().__bases__ == (myfolder.getSiteManager(), ) True >>> myfolder.getSiteManager().subs[0] is myfolder11.getSiteManager() True >>> myfolder2.getSiteManager().subs () Let's now move `myfolder11` from `myfolder` to `myfolder2`: >>> myfolder2['myfolder21'] = myfolder11 >>> del myfolder['myfolder11'] Now the next site manager for `myfolder11`'s site manager should have changed: >>> myfolder21 = myfolder11 >>> myfolder21.getSiteManager().__bases__ == (myfolder2.getSiteManager(), ) True >>> myfolder2.getSiteManager().subs[0] is myfolder21.getSiteManager() True >>> myfolder.getSiteManager().subs () Make sure that our interfaces and classes are picklable: >>> import sys >>> sys.modules['zope.site.tests'].IMyUtility = IMyUtility >>> IMyUtility.__module__ = 'zope.site.tests' >>> sys.modules['zope.site.tests'].MyUtility = MyUtility >>> MyUtility.__module__ = 'zope.site.tests' >>> from pickle import dumps, loads >>> data = dumps(myfolder2['myfolder21']) >>> myfolder['myfolder11'] = loads(data) >>> myfolder11 = myfolder['myfolder11'] >>> myfolder11.getSiteManager().__bases__ == (myfolder.getSiteManager(), ) True >>> myfolder.getSiteManager().subs[0] is myfolder11.getSiteManager() True >>> myfolder2.getSiteManager().subs[0] is myfolder21.getSiteManager() True Finally, let's check that everything works fine when our folder is moved to the folder that doesn't contain any site manager. Our folder's sitemanager's bases should be set to global site manager. >>> myfolder11.getSiteManager().__bases__ == (myfolder.getSiteManager(), ) True >>> nosm = folder.Folder() >>> nosm['root'] = myfolder11 >>> myfolder11.getSiteManager().__bases__ == (gsm, ) True zope2.13-2.13.21/source/zope.site/src/zope/site/site.py0000644000175000017500000001727512214017634021421 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Site and Local Site Manager implementation A local site manager has a number of roles: - A local site manager, that provides a local adapter and utility registry. - A place to do TTW development and/or to manage database-based code. - A registry for persistent modules. The Zope 3 import hook uses the SiteManager to search for modules. """ import zope.event import zope.interface import zope.component import zope.component.persistentregistry import zope.component.hooks import zope.component.interfaces import zope.location import zope.location.interfaces from zope.component.interfaces import ComponentLookupError from zope.lifecycleevent import ObjectCreatedEvent from zope.filerepresentation.interfaces import IDirectoryFactory from zope.container.btree import BTreeContainer from zope.container.contained import Contained from zope.site import interfaces # BBB from zope.component.hooks import setSite class SiteManagementFolder(BTreeContainer): zope.interface.implements(interfaces.ISiteManagementFolder) class SMFolderFactory(object): zope.interface.implements(IDirectoryFactory) def __init__(self, context): self.context = context def __call__(self, name): return SiteManagementFolder() class SiteManagerContainer(Contained): """Implement access to the site manager (++etc++site). This is a mix-in that implements the IPossibleSite interface; for example, it is used by the Folder implementation. """ zope.interface.implements(zope.component.interfaces.IPossibleSite) _sm = None def getSiteManager(self): if self._sm is not None: return self._sm else: raise ComponentLookupError('no site manager defined') def setSiteManager(self, sm): if zope.component.interfaces.ISite.providedBy(self): raise TypeError("Already a site") if zope.component.interfaces.IComponentLookup.providedBy(sm): self._sm = sm else: raise ValueError('setSiteManager requires an IComponentLookup') zope.interface.directlyProvides( self, zope.component.interfaces.ISite, zope.interface.directlyProvidedBy(self)) zope.event.notify(interfaces.NewLocalSite(sm)) def _findNextSiteManager(site): while True: if zope.location.interfaces.IRoot.providedBy(site): # we're the root site, return None return None try: site = zope.location.interfaces.ILocationInfo(site).getParent() except TypeError: # there was not enough context; probably run from a test return None if zope.component.interfaces.ISite.providedBy(site): return site.getSiteManager() class _LocalAdapterRegistry( zope.component.persistentregistry.PersistentAdapterRegistry, zope.location.Location, ): pass class LocalSiteManager( BTreeContainer, zope.component.persistentregistry.PersistentComponents, ): """Local Site Manager implementation""" zope.interface.implements(interfaces.ILocalSiteManager) subs = () def _setBases(self, bases): # Update base subs for base in self.__bases__: if ((base not in bases) and interfaces.ILocalSiteManager.providedBy(base) ): base.removeSub(self) for base in bases: if ((base not in self.__bases__) and interfaces.ILocalSiteManager.providedBy(base) ): base.addSub(self) super(LocalSiteManager, self)._setBases(bases) def __init__(self, site, default_folder=True): BTreeContainer.__init__(self) zope.component.persistentregistry.PersistentComponents.__init__(self) # Locate the site manager self.__parent__ = site self.__name__ = '++etc++site' # Set base site manager next = _findNextSiteManager(site) if next is None: next = zope.component.getGlobalSiteManager() self.__bases__ = (next, ) # Setup default site management folder if requested if default_folder: folder = SiteManagementFolder() zope.event.notify(ObjectCreatedEvent(folder)) self['default'] = folder def _init_registries(self): self.adapters = _LocalAdapterRegistry() self.utilities = _LocalAdapterRegistry() self.adapters.__parent__ = self.utilities.__parent__ = self self.adapters.__name__ = u'adapters' self.utilities.__name__ = u'utilities' def addSub(self, sub): """See interfaces.registration.ILocatedRegistry""" self.subs += (sub, ) def removeSub(self, sub): """See interfaces.registration.ILocatedRegistry""" self.subs = tuple( [s for s in self.subs if s is not sub] ) def threadSiteSubscriber(ob, event): """A subscriber to BeforeTraverseEvent Sets the 'site' thread global if the object traversed is a site. """ zope.component.hooks.setSite(ob) def clearThreadSiteSubscriber(event): """A subscriber to EndRequestEvent Cleans up the site thread global after the request is processed. """ clearSite() # Clear the site thread global clearSite = zope.component.hooks.setSite try: from zope.testing.cleanup import addCleanUp except ImportError: pass else: addCleanUp(clearSite) @zope.component.adapter(zope.interface.Interface) @zope.interface.implementer(zope.component.interfaces.IComponentLookup) def SiteManagerAdapter(ob): """An adapter from ILocation to IComponentLookup. The ILocation is interpreted flexibly, we just check for ``__parent__``. """ current = ob while True: if zope.component.interfaces.ISite.providedBy(current): return current.getSiteManager() current = getattr(current, '__parent__', None) if current is None: # It is not a location or has no parent, so we return the global # site manager return zope.component.getGlobalSiteManager() def changeSiteConfigurationAfterMove(site, event): """After a site is moved, its site manager links have to be updated.""" if event.newParent is not None: next = _findNextSiteManager(site) if next is None: next = zope.component.getGlobalSiteManager() site.getSiteManager().__bases__ = (next, ) @zope.component.adapter( SiteManagerContainer, zope.container.interfaces.IObjectMovedEvent) def siteManagerContainerRemoved(container, event): # The relation between SiteManagerContainer and LocalSiteManager is a # kind of containment hierarchy, but it is not expressed via containment, # but rather via an attribute (_sm). # # When the parent is deleted, this needs to be propagated to the children, # and since we don't have "real" containment, we need to do that manually. try: sm = container.getSiteManager() except ComponentLookupError: pass else: for ignored in zope.component.subscribers((sm, event), None): pass # work happens during adapter fetch zope2.13-2.13.21/source/zope.site/src/zope/site/tests/0000755000175000017500000000000012214017634021231 5ustar arnauarnauzope2.13-2.13.21/source/zope.site/src/zope/site/tests/folder.txt0000644000175000017500000000336412214017634023253 0ustar arnauarnau=============================== File representation for Folders =============================== Folders can be represented in file-system-like protocols (e.g. FTP). An adapter abstracts some internals away and adds support for accessing the '++etc++site' folder from those protocols. >>> from zope.site.folder import rootFolder >>> from zope.container.directory import ReadDirectory >>> folder = rootFolder() >>> from zope.site.site import LocalSiteManager >>> folder.setSiteManager(LocalSiteManager(folder)) >>> fs_folder = ReadDirectory(folder) As the root folder is a site, the ++etc++site object appears: >>> fs_folder.keys() ['++etc++site'] >>> fs_folder.get('++etc++site') >>> fs_folder['++etc++site'] >>> list(fs_folder.__iter__()) ['++etc++site'] >>> fs_folder.values() [] >>> len(fs_folder) 1 >>> fs_folder.items() [('++etc++site', )] >>> '++etc++site' in fs_folder True Let's add another folder to see how a non-site folder behaves: >>> from zope.site.folder import Folder >>> folder['test'] = Folder() The site folder now contains the new folder: >>> fs_folder.keys() [u'test', '++etc++site'] >>> fs_folder.get('test') >>> fs_folder['test'] >>> list(fs_folder.__iter__()) [u'test', '++etc++site'] >>> fs_folder.values() [, ] >>> len(fs_folder) 2 >>> fs_folder.items() [(u'test', ), ('++etc++site', )] >>> 'test' in fs_folder True zope2.13-2.13.21/source/zope.site/src/zope/site/tests/test_sitemanagercontainer.py0000644000175000017500000000473312214017634027053 0ustar arnauarnau############################################################################# # # Copyright (c) 2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ """ import unittest import zope.component from zope.component import getSiteManager import zope.container.testing from zope.event import notify from zope.lifecycleevent import ObjectRemovedEvent from zope.lifecycleevent.interfaces import IObjectRemovedEvent from zope.site.folder import rootFolder from zope.site.site import SiteManagerContainer import zope.site.testing class Dummy(object): pass removed_called = False def removed_event(obj, event): global removed_called removed_called = True def dispatch_event(obj, event): sm = obj._sm if sm is not None: for k,v in sm.items(): notify(ObjectRemovedEvent(v, sm, k)) class SiteManagerContainerTest(unittest.TestCase): def setUp(self): self.root = rootFolder() zope.site.testing.siteSetUp(self.root) global removed_called removed_called = False sm = getSiteManager() sm.registerHandler(removed_event, (Dummy, IObjectRemovedEvent)) sm.registerHandler( dispatch_event, (SiteManagerContainer, IObjectRemovedEvent)) def tearDown(self): zope.site.testing.siteTearDown() def removed_event(self, event): self.removed_called = True def test_delete_smc_should_propagate_removed_event(self): container = SiteManagerContainer() self.root['container'] = container zope.site.testing.createSiteManager(container) container.getSiteManager()['child'] = Dummy() del self.root['container'] self.assert_(removed_called) def test_delete_when_smc_has_no_sitemanager(self): container = SiteManagerContainer() self.root['container'] = container try: del self.root['container'] except Exception, e: self.fail(e) def test_suite(): return unittest.makeSuite(SiteManagerContainerTest) zope2.13-2.13.21/source/zope.site/src/zope/site/tests/test_registration.py0000644000175000017500000000775712214017634025374 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Registration Tests """ __docformat__ = "reStructuredText" import doctest import os import unittest import warnings import ZODB.FileStorage import persistent import transaction import zope.component.globalregistry import zope.component.testing as placelesssetup import zope.container.contained import zope.site from ZODB.DB import DB from ZODB.DemoStorage import DemoStorage from zope import interface # test class for testing data conversion class IFoo(interface.Interface): pass class Foo(persistent.Persistent, zope.container.contained.Contained): interface.implements(IFoo) name = '' def __init__(self, name=''): self.name = name def __repr__(self): return 'Foo(%r)' % self.name def setUp(test): placelesssetup.setUp(test) test.globs['showwarning'] = warnings.showwarning warnings.showwarning = lambda *a, **k: None def tearDown(test): warnings.showwarning = test.globs['showwarning'] placelesssetup.tearDown(test) def oldfs(): return FileStorage( os.path.join(os.path.dirname(__file__), 'gen3.fs'), read_only=True, ) # Work around a bug in ZODB # XXX fix ZODB class FileStorage(ZODB.FileStorage.FileStorage): def new_oid(self): self._lock_acquire() try: last = self._oid d = ord(last[-1]) if d < 255: # fast path for the usual case last = last[:-1] + chr(d+1) else: # there's a carry out of the last byte last_as_long, = _structunpack(">Q", last) last = _structpack(">Q", last_as_long + 1) self._oid = last return last finally: self._lock_release() class GlobalRegistry: pass base = zope.component.globalregistry.GlobalAdapterRegistry( GlobalRegistry, 'adapters') GlobalRegistry.adapters = base def clear_base(): base.__init__(GlobalRegistry, 'adapters') def test_deghostification_of_persistent_adapter_registries(): """ Note that this test duplicates one from zope.component.tests. We should be able to get rid of this one when we get rid of __setstate__ implementation we have in back35. We want to make sure that we see updates corrextly. >>> import ZODB.tests.util >>> db = ZODB.tests.util.DB() >>> tm1 = transaction.TransactionManager() >>> c1 = db.open(transaction_manager=tm1) >>> r1 = zope.site.site._LocalAdapterRegistry((base,)) >>> r2 = zope.site.site._LocalAdapterRegistry((r1,)) >>> c1.root()[1] = r1 >>> c1.root()[2] = r2 >>> tm1.commit() >>> r1._p_deactivate() >>> r2._p_deactivate() >>> tm2 = transaction.TransactionManager() >>> c2 = db.open(transaction_manager=tm2) >>> r1 = c2.root()[1] >>> r2 = c2.root()[2] >>> r1.lookup((), IFoo, '') >>> base.register((), IFoo, '', Foo('')) >>> r1.lookup((), IFoo, '') Foo('') >>> r2.lookup((), IFoo, '1') >>> r1.register((), IFoo, '1', Foo('1')) >>> r2.lookup((), IFoo, '1') Foo('1') >>> r1.lookup((), IFoo, '2') >>> r2.lookup((), IFoo, '2') >>> base.register((), IFoo, '2', Foo('2')) >>> r1.lookup((), IFoo, '2') Foo('2') >>> r2.lookup((), IFoo, '2') Foo('2') Cleanup: >>> db.close() >>> clear_base() """ def test_suite(): suite = unittest.TestSuite(( doctest.DocTestSuite(setUp=setUp, tearDown=tearDown) )) return suite zope2.13-2.13.21/source/zope.site/src/zope/site/tests/__init__.py0000644000175000017500000000007512214017634023344 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.site/src/zope/site/tests/test_folder.py0000644000175000017500000000147012214017634024117 0ustar arnauarnaufrom unittest import TestSuite, makeSuite import doctest from zope.site.folder import Folder from zope.site.testing import siteSetUp, siteTearDown from zope.site.tests.test_site import BaseTestSiteManagerContainer def setUp(test=None): siteSetUp() def tearDown(test=None): siteTearDown() class FolderTest(BaseTestSiteManagerContainer): def makeTestObject(self): return Folder() def test_suite(): flags = doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE return TestSuite(( makeSuite(FolderTest), doctest.DocTestSuite('zope.site.folder', setUp=setUp, tearDown=tearDown), doctest.DocFileSuite("folder.txt", setUp=setUp, tearDown=tearDown, optionflags=flags), )) zope2.13-2.13.21/source/zope.site/src/zope/site/tests/test_site.py0000644000175000017500000001005512214017634023607 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Registration Tests """ __docformat__ = "reStructuredText" import doctest import unittest import zope.interface import zope.interface.verify from zope.component.interfaces import ISite, IPossibleSite from zope.site import folder from zope.site import interfaces from zope import site from zope.site import testing class SiteManagerStub(object): zope.interface.implements(interfaces.ILocalSiteManager) class CustomFolder(folder.Folder): def __init__(self, name): self.__name__ = name super(CustomFolder, self).__init__() def __repr__(self): return '<%s %s>' %(self.__class__.__name__, self.__name__) def test_SiteManagerAdapter(): """ The site manager adapter is used to find the nearest site for any given location. If the provided context is a site, >>> site = folder.Folder() >>> sm = SiteManagerStub() >>> site.setSiteManager(sm) then the adapter simply return's the site's site manager: >>> from zope.site import SiteManagerAdapter >>> SiteManagerAdapter(site) is sm True If the context is a location (i.e. has a `__parent__` attribute), >>> ob = folder.Folder() >>> ob.__parent__ = site >>> ob2 = folder.Folder() >>> ob2.__parent__ = ob we 'acquire' the closest site and return its site manager: >>> SiteManagerAdapter(ob) is sm True >>> SiteManagerAdapter(ob2) is sm True If we are unable to find a local site manager, then the global site manager is returned. >>> import zope.component >>> orphan = CustomFolder('orphan') >>> SiteManagerAdapter(orphan) is zope.component.getGlobalSiteManager() True """ class BaseTestSiteManagerContainer(unittest.TestCase): """This test is for objects that don't have site managers by default and that always give back the site manager they were given. Subclasses need to define a method, 'makeTestObject', that takes no arguments and that returns a new site manager container that has no site manager.""" def test_IPossibleSite_verify(self): zope.interface.verify.verifyObject(IPossibleSite, self.makeTestObject()) def test_get_and_set(self): smc = self.makeTestObject() self.failIf(ISite.providedBy(smc)) sm = site.LocalSiteManager(smc) smc.setSiteManager(sm) self.failUnless(ISite.providedBy(smc)) self.failUnless(smc.getSiteManager() is sm) zope.interface.verify.verifyObject(ISite, smc) def test_set_w_bogus_value(self): smc=self.makeTestObject() self.assertRaises(Exception, smc.setSiteManager, self) class SiteManagerContainerTest(BaseTestSiteManagerContainer): def makeTestObject(self): from zope.site import SiteManagerContainer return SiteManagerContainer() def setUp(test): testing.siteSetUp() def tearDown(test): testing.siteTearDown() class Layer(object): @staticmethod def setUp(): pass def test_suite(): site_suite = doctest.DocFileSuite('../site.txt', setUp=setUp, tearDown=tearDown) # XXX Isolate the site.txt tests within their own layer as they do some # component registration. site_suite.layer = Layer return unittest.TestSuite(( doctest.DocTestSuite(), unittest.makeSuite(SiteManagerContainerTest), site_suite, )) zope2.13-2.13.21/source/zope.site/src/zope/site/tests/test_localsitemanager.py0000644000175000017500000000333712214017634026162 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Local sitemanager tests. """ import unittest from zope.interface import Interface from zope import site from zope.site.folder import Folder import zope.site.testing class I1(Interface): pass class TestLocalSiteManager(unittest.TestCase): def setUp(self): zope.site.testing.siteSetUp() self.util = object() self.root = Folder() self.root['site'] = Folder() subfolder = self.root['site'] subfolder.setSiteManager(site.LocalSiteManager(subfolder)) subfolder.getSiteManager().registerUtility(self.util, I1) def tearDown(self): zope.site.testing.siteTearDown() def testPersistence(self): from pickle import dumps, loads self.assert_( self.root['site'].getSiteManager().getUtility(I1) is self.util) data = dumps(self.root['site']) self.root['copied_site'] = loads(data) self.assert_( self.root['copied_site'].getSiteManager().getUtility(I1) is not self.util) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestLocalSiteManager), )) zope2.13-2.13.21/source/zope.site/src/zope/site/testing.py0000644000175000017500000000421712214017634022122 0ustar arnauarnau############################################################################## # # Copyright (c) 2001-2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Reusable functionality for testing site-related code """ import zope.component import zope.component.hooks import zope.component.interfaces import zope.container.interfaces import zope.container.testing import zope.site.site from zope.component.interfaces import IComponentLookup from zope.interface import Interface from zope.site import LocalSiteManager, SiteManagerAdapter from zope.site.folder import rootFolder def createSiteManager(folder, setsite=False): if not zope.component.interfaces.ISite.providedBy(folder): folder.setSiteManager(LocalSiteManager(folder)) if setsite: zope.component.hooks.setSite(folder) return folder.getSiteManager() def addUtility(sitemanager, name, iface, utility, suffix=''): """Add a utility to a site manager This helper function is useful for tests that need to set up utilities. """ folder_name = (name or (iface.__name__ + 'Utility')) + suffix default = sitemanager['default'] default[folder_name] = utility utility = default[folder_name] sitemanager.registerUtility(utility, iface, name) return utility def siteSetUp(site=False): zope.container.testing.setUp() zope.component.hooks.setHooks() zope.component.provideAdapter( SiteManagerAdapter, (Interface,), IComponentLookup) if site: site = rootFolder() createSiteManager(site, setsite=True) return site def siteTearDown(): zope.container.testing.tearDown() zope.component.hooks.resetHooks() zope.component.hooks.setSite() zope2.13-2.13.21/source/zope.site/src/zope/__init__.py0000644000175000017500000000007012214017634021231 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.site/src/zope.site.egg-info/0000755000175000017500000000000012214017634021560 5ustar arnauarnauzope2.13-2.13.21/source/zope.site/src/zope.site.egg-info/PKG-INFO0000644000175000017500000004403412214017634022662 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.site Version: 3.9.2 Summary: Local registries for zope component architecture Home-page: http://pypi.python.org/pypi/zope.site Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ===================================== Zope 3's Local Component Architecture ===================================== This package provides a local and persistent site manager implementation, so that one can register local utilities and adapters. It uses local adapter registries for its adapter and utility registry. The module also provides some facilities to organize the local software and ensures the correct behavior inside the ZODB. .. contents:: ============================= Sites and Local Site Managers ============================= This is an introduction of location-based component architecture. Creating and Accessing Sites ---------------------------- *Sites* are used to provide custom component setups for parts of your application or web site. Every folder: >>> from zope.site import folder >>> myfolder = folder.rootFolder() has the potential to become a site: >>> from zope.component.interfaces import ISite, IPossibleSite >>> IPossibleSite.providedBy(myfolder) True but is not yet one: >>> ISite.providedBy(myfolder) False If you would like your custom content component to be able to become a site, you can use the `SiteManagerContainer` mix-in class: >>> from zope import site >>> class MyContentComponent(site.SiteManagerContainer): ... pass >>> myContent = MyContentComponent() >>> IPossibleSite.providedBy(myContent) True >>> ISite.providedBy(myContent) False To convert a possible site to a real site, we have to provide a site manager: >>> sm = site.LocalSiteManager(myfolder) >>> myfolder.setSiteManager(sm) >>> ISite.providedBy(myfolder) True >>> myfolder.getSiteManager() is sm True Note that an event is generated when a local site manager is created: >>> from zope.component.eventtesting import getEvents >>> from zope.site.interfaces import INewLocalSite >>> [event] = getEvents(INewLocalSite) >>> event.manager is sm True If one tries to set a bogus site manager, a `ValueError` will be raised: >>> myfolder2 = folder.Folder() >>> myfolder2.setSiteManager(object) Traceback (most recent call last): ... ValueError: setSiteManager requires an IComponentLookup If the possible site has been changed to a site already, a `TypeError` is raised when one attempts to add a new site manager: >>> myfolder.setSiteManager(site.LocalSiteManager(myfolder)) Traceback (most recent call last): ... TypeError: Already a site There is also an adapter you can use to get the next site manager from any location: >>> myfolder['mysubfolder'] = folder.Folder() >>> import zope.component >>> zope.component.interfaces.IComponentLookup(myfolder['mysubfolder']) is sm True If the location passed is a site, the site manager of that site is returned: >>> zope.component.interfaces.IComponentLookup(myfolder) is sm True Using the Site Manager ---------------------- A site manager contains several *site management folders*, which are used to logically organize the software. When a site manager is initialized, a default site management folder is created: >>> sm = myfolder.getSiteManager() >>> default = sm['default'] >>> default.__class__ However, you can tell not to create the default site manager folder on LocalSiteManager creation: >>> nodefault = site.LocalSiteManager(myfolder, default_folder=False) >>> 'default' in nodefault False Also, note that when creating LocalSiteManager, its __parent__ is set to site that was passed to constructor and the __name__ is set to ++etc++site. >>> nodefault.__parent__ is myfolder True >>> nodefault.__name__ == '++etc++site' True You can easily create a new site management folder: >>> sm['mySMF'] = site.SiteManagementFolder() >>> sm['mySMF'].__class__ Once you have your site management folder -- let's use the default one -- we can register some components. Let's start with a utility: >>> import zope.interface >>> class IMyUtility(zope.interface.Interface): ... pass >>> import persistent >>> from zope.container.contained import Contained >>> class MyUtility(persistent.Persistent, Contained): ... zope.interface.implements(IMyUtility) ... def __init__(self, title): ... self.title = title ... def __repr__(self): ... return "%s('%s')" %(self.__class__.__name__, self.title) Now we can create an instance of our utility and put it in the site management folder and register it: >>> myutil = MyUtility('My custom utility') >>> default['myutil'] = myutil >>> sm.registerUtility(myutil, IMyUtility, 'u1') Now we can ask the site manager for the utility: >>> sm.queryUtility(IMyUtility, 'u1') MyUtility('My custom utility') Of course, the local site manager has also access to the global component registrations: >>> gutil = MyUtility('Global Utility') >>> from zope.component import getGlobalSiteManager >>> gsm = getGlobalSiteManager() >>> gsm.registerUtility(gutil, IMyUtility, 'gutil') >>> sm.queryUtility(IMyUtility, 'gutil') MyUtility('Global Utility') Next let's see whether we can also successfully register an adapter as well. Here the adapter will provide the size of a file: >>> class IFile(zope.interface.Interface): ... pass >>> class ISized(zope.interface.Interface): ... pass >>> class File(object): ... zope.interface.implements(IFile) >>> class FileSize(object): ... zope.interface.implements(ISized) ... def __init__(self, context): ... self.context = context Now that we have the adapter we need to register it: >>> sm.registerAdapter(FileSize, [IFile]) Finally, we can get the adapter for a file: >>> file = File() >>> size = sm.queryAdapter(file, ISized, name='') >>> size.__class__ >>> size.context is file True By the way, once you set a site >>> from zope.component import hooks >>> hooks.setSite(myfolder) you can simply use the zope.component's `getSiteManager()` method to get the nearest site manager: >>> from zope.component import getSiteManager >>> getSiteManager() is sm True This also means that you can simply use zope.component to look up your utility >>> from zope.component import getUtility >>> getUtility(IMyUtility, 'gutil') MyUtility('Global Utility') or the adapter via the interface's `__call__` method: >>> size = ISized(file) >>> size.__class__ >>> size.context is file True Multiple Sites -------------- Until now we have only dealt with one local and the global site. But things really become interesting, once we have multiple sites. We can override other local configuration. This behaviour uses the notion of location, therefore we need to configure the zope.location package first: >>> import zope.configuration.xmlconfig >>> _ = zope.configuration.xmlconfig.string(""" ... ... ... ... ... """) Let's now create a new folder called `folder11`, add it to `myfolder` and make it a site: >>> myfolder11 = folder.Folder() >>> myfolder['myfolder11'] = myfolder11 >>> myfolder11.setSiteManager(site.LocalSiteManager(myfolder11)) >>> sm11 = myfolder11.getSiteManager() If we ask the second site manager for its next, we get >>> sm11.__bases__ == (sm, ) True and the first site manager should have the folling sub manager: >>> sm.subs == (sm11,) True If we now register a second utility with the same name and interface with the new site manager folder, >>> default11 = sm11['default'] >>> myutil11 = MyUtility('Utility, uno & uno') >>> default11['myutil'] = myutil11 >>> sm11.registerUtility(myutil11, IMyUtility, 'u1') then it will will be available in the second site manager >>> sm11.queryUtility(IMyUtility, 'u1') MyUtility('Utility, uno & uno') but not in the first one: >>> sm.queryUtility(IMyUtility, 'u1') MyUtility('My custom utility') It is also interesting to look at the use cases of moving and copying a site. To do that we create a second root folder and make it a site, so that site hierarchy is as follows: :: _____ global site _____ / \ myfolder1 myfolder2 | myfolder11 >>> myfolder2 = folder.rootFolder() >>> myfolder2.setSiteManager(site.LocalSiteManager(myfolder2)) Before we can move or copy sites, we need to register two event subscribers that manage the wiring of site managers after moving or copying: >>> from zope import container >>> gsm.registerHandler( ... site.changeSiteConfigurationAfterMove, ... (ISite, container.interfaces.IObjectMovedEvent), ... ) We only have to register one event listener, since the copy action causes an `IObjectAddedEvent` to be created, which is just a special type of `IObjectMovedEvent`. First, make sure that everything is setup correctly in the first place: >>> myfolder11.getSiteManager().__bases__ == (myfolder.getSiteManager(), ) True >>> myfolder.getSiteManager().subs[0] is myfolder11.getSiteManager() True >>> myfolder2.getSiteManager().subs () Let's now move `myfolder11` from `myfolder` to `myfolder2`: >>> myfolder2['myfolder21'] = myfolder11 >>> del myfolder['myfolder11'] Now the next site manager for `myfolder11`'s site manager should have changed: >>> myfolder21 = myfolder11 >>> myfolder21.getSiteManager().__bases__ == (myfolder2.getSiteManager(), ) True >>> myfolder2.getSiteManager().subs[0] is myfolder21.getSiteManager() True >>> myfolder.getSiteManager().subs () Make sure that our interfaces and classes are picklable: >>> import sys >>> sys.modules['zope.site.tests'].IMyUtility = IMyUtility >>> IMyUtility.__module__ = 'zope.site.tests' >>> sys.modules['zope.site.tests'].MyUtility = MyUtility >>> MyUtility.__module__ = 'zope.site.tests' >>> from pickle import dumps, loads >>> data = dumps(myfolder2['myfolder21']) >>> myfolder['myfolder11'] = loads(data) >>> myfolder11 = myfolder['myfolder11'] >>> myfolder11.getSiteManager().__bases__ == (myfolder.getSiteManager(), ) True >>> myfolder.getSiteManager().subs[0] is myfolder11.getSiteManager() True >>> myfolder2.getSiteManager().subs[0] is myfolder21.getSiteManager() True Finally, let's check that everything works fine when our folder is moved to the folder that doesn't contain any site manager. Our folder's sitemanager's bases should be set to global site manager. >>> myfolder11.getSiteManager().__bases__ == (myfolder.getSiteManager(), ) True >>> nosm = folder.Folder() >>> nosm['root'] = myfolder11 >>> myfolder11.getSiteManager().__bases__ == (gsm, ) True ======= CHANGES ======= 3.9.2 (2010-09-25) ------------------ - Added not declared, but needed test dependency on `zope.testing`. 3.9.1 (2010-04-30) ------------------ - Removed use of 'zope.testing.doctest' in favor of stdlib's 'doctest. - Removed use of 'zope.testing.doctestunit' in favor of stdlib's 'doctest. 3.9.0 (2009-12-29) ------------------ - Avoid a test dependency on zope.copypastemove by testing the correct persistent behavior of a site manager using the normal pickle module. 3.8.0 (2009-12-15) ------------------ - Removed functional testing setup and dependency on zope.app.testing. 3.7.1 (2009-11-18) ------------------ - Moved the zope.site.hooks functionality to zope.component.hooks as it isn't actually dealing with zope.site's concept of a site. - Import ISite and IPossibleSite from zope.component after they were moved there from zope.location. 3.7.0 (2009-09-29) ------------------ - Cleaned up the undeclared dependency on zope.app.publication by moving the two relevant subscriber registrations and their tests to that package. - Dropped the dependency on zope.traversing which was only used to access zope.location functionality. Configure zope.location for some tests. - Demoted zope.configuration to a testing dependency. 3.6.4 (2009-09-01) ------------------ - Set __parent__ and __name__ in the LocalSiteManager's constructor after calling constructor of its superclasses, so __name__ doesn't get overwritten with empty string by the Components constructor. - Don't set __parent__ and __name__ attributes of site manager in SiteManagerContainer's ``setSiteManager`` method, as they're already set for LocalSiteManager. Other site manager implementations are not required to have those attributes at all, so we're not adding them anymore. 3.6.3 (2009-07-27) ------------------ - Propagate an ObjectRemovedEvent to the SiteManager upon removal of a SiteManagerContainer. 3.6.2 (2009-07-24) ------------------ - Fixed tests to pass with latest packages. - Removed failing test of persistent interfaces, since it did not test anything in this package and used the deprecated ``zodbcode`` module. - Fix NameError when calling ``zope.site.testing.siteSetUp(site=True)``. - The ``getNextUtility`` and ``queryNextUtility`` functions was moved to ``zope.component``. While backward-compatibility imports are provided, it's strongly recommended to update your imports. 3.6.1 (2009-02-28) ------------------ - Import symbols moved from zope.traversing to zope.location from the new location. - Don't fail when changing component registry bases while moving ISite object to non-ISite object. - Allow specify whether to create 'default' SiteManagementFolder on initializing LocalSiteManager. Use the ``default_folder`` argument. - Add a containment constraint to the SiteManagementFolder that makes it only available to be contained in ILocalSiteManagers and other ISiteManagementFolders. - Change package's mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired. - Remove old unused code. Update package description. 3.6.0 (2009-01-31) ------------------ - Use zope.container instead of zope.app.container. 3.5.1 (2009-01-27) ------------------ - Extracted from zope.app.component (trunk, 3.5.1 under development) as part of an effort to clean up dependencies between Zope packages. Keywords: zope component architecture local Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.site/src/zope.site.egg-info/dependency_links.txt0000644000175000017500000000000112214017634025626 0ustar arnauarnau zope2.13-2.13.21/source/zope.site/src/zope.site.egg-info/requires.txt0000644000175000017500000000027012214017634024157 0ustar arnauarnausetuptools zope.annotation zope.container zope.security zope.component>=3.8.0 zope.event zope.interface zope.lifecycleevent zope.location>=3.7.0 [test] zope.configuration zope.testingzope2.13-2.13.21/source/zope.site/src/zope.site.egg-info/namespace_packages.txt0000644000175000017500000000000512214017634026106 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.site/src/zope.site.egg-info/top_level.txt0000644000175000017500000000000512214017634024305 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.site/src/zope.site.egg-info/SOURCES.txt0000644000175000017500000000153212214017634023445 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.site.egg-info/PKG-INFO src/zope.site.egg-info/SOURCES.txt src/zope.site.egg-info/dependency_links.txt src/zope.site.egg-info/namespace_packages.txt src/zope.site.egg-info/not-zip-safe src/zope.site.egg-info/requires.txt src/zope.site.egg-info/top_level.txt src/zope/site/__init__.py src/zope/site/configure.zcml src/zope/site/folder.py src/zope/site/hooks.py src/zope/site/interfaces.py src/zope/site/next.py src/zope/site/site.py src/zope/site/site.txt src/zope/site/testing.py src/zope/site/tests/__init__.py src/zope/site/tests/folder.txt src/zope/site/tests/test_folder.py src/zope/site/tests/test_localsitemanager.py src/zope/site/tests/test_registration.py src/zope/site/tests/test_site.py src/zope/site/tests/test_sitemanagercontainer.pyzope2.13-2.13.21/source/zope.site/src/zope.site.egg-info/not-zip-safe0000644000175000017500000000000112214017634024006 0ustar arnauarnau zope2.13-2.13.21/source/zope.ptresource/0000755000175000017500000000000012214017615016605 5ustar arnauarnauzope2.13-2.13.21/source/zope.ptresource/setup.py0000644000175000017500000000407412214017615020324 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """zope.ptresource setup """ from setuptools import setup, find_packages, Extension long_description = (open('README.txt').read() + '\n\n' + open('CHANGES.txt').read()) setup(name='zope.ptresource', version = '3.9.0', url='http://pypi.python.org/pypi/zope.ptresource/', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', classifiers = ['Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3', ], description='Page template resource plugin for zope.browserresource', long_description=long_description, packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope'], include_package_data=True, install_requires=['setuptools', 'zope.browserresource', 'zope.interface', 'zope.pagetemplate', 'zope.publisher', 'zope.security[untrustedpython]', ], extras_require={ 'test': ['zope.testing'], }, zip_safe = False, ) zope2.13-2.13.21/source/zope.ptresource/PKG-INFO0000644000175000017500000000372712214017615017713 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.ptresource Version: 3.9.0 Summary: Page template resource plugin for zope.browserresource Home-page: http://pypi.python.org/pypi/zope.ptresource/ Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: UNKNOWN Description: ======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides a "page template" resource class, a resource which content is processed with Zope Page Templates engine before returning to client. The resource factory class is registered for "pt", "zpt" and "html" file extensions in package's ``configure.zcml`` file. ======= CHANGES ======= 3.9.0 (2009-08-27) ================== Initial release. This package was splitted off zope.app.publisher as a part of refactoring process. It's now a plugin for another package that was refactored from zope.app.publisher - zope.browserresource. See its documentation for more details. Other changes: * Don't render PageTemplateResource when called as the IResource interface requires that __call__ method should return an absolute URL. When accessed by browser, it still will be rendered, because "browserDefault" method now returns a callable that will render the template to browser. Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.ptresource/pip-egg-info/0000755000175000017500000000000012214017615021066 5ustar arnauarnauzope2.13-2.13.21/source/zope.ptresource/pip-egg-info/zope.ptresource.egg-info/0000755000175000017500000000000012214017615025727 5ustar arnauarnauzope2.13-2.13.21/source/zope.ptresource/pip-egg-info/zope.ptresource.egg-info/PKG-INFO0000644000175000017500000000374112214017615027031 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.ptresource Version: 3.9.0 Summary: Page template resource plugin for zope.browserresource Home-page: http://pypi.python.org/pypi/zope.ptresource/ Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: UNKNOWN Description: ======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides a "page template" resource class, a resource which content is processed with Zope Page Templates engine before returning to client. The resource factory class is registered for "pt", "zpt" and "html" file extensions in package's ``configure.zcml`` file. ======= CHANGES ======= 3.9.0 (2009-08-27) ================== Initial release. This package was splitted off zope.app.publisher as a part of refactoring process. It's now a plugin for another package that was refactored from zope.app.publisher - zope.browserresource. See its documentation for more details. Other changes: * Don't render PageTemplateResource when called as the IResource interface requires that __call__ method should return an absolute URL. When accessed by browser, it still will be rendered, because "browserDefault" method now returns a callable that will render the template to browser. Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.ptresource/pip-egg-info/zope.ptresource.egg-info/dependency_links.txt0000644000175000017500000000000112214017615031775 0ustar arnauarnau zope2.13-2.13.21/source/zope.ptresource/pip-egg-info/zope.ptresource.egg-info/requires.txt0000644000175000017500000000020312214017615030322 0ustar arnauarnausetuptools zope.browserresource zope.interface zope.pagetemplate zope.publisher zope.security[untrustedpython] [test] zope.testingzope2.13-2.13.21/source/zope.ptresource/pip-egg-info/zope.ptresource.egg-info/namespace_packages.txt0000644000175000017500000000000512214017615032255 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.ptresource/pip-egg-info/zope.ptresource.egg-info/top_level.txt0000644000175000017500000000000512214017615030454 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.ptresource/pip-egg-info/zope.ptresource.egg-info/SOURCES.txt0000644000175000017500000000077312214017615027622 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.ptresource.egg-info/PKG-INFO pip-egg-info/zope.ptresource.egg-info/SOURCES.txt pip-egg-info/zope.ptresource.egg-info/dependency_links.txt pip-egg-info/zope.ptresource.egg-info/namespace_packages.txt pip-egg-info/zope.ptresource.egg-info/not-zip-safe pip-egg-info/zope.ptresource.egg-info/requires.txt pip-egg-info/zope.ptresource.egg-info/top_level.txt src/zope/__init__.py src/zope/ptresource/__init__.py src/zope/ptresource/ptresource.py src/zope/ptresource/tests.pyzope2.13-2.13.21/source/zope.ptresource/pip-egg-info/zope.ptresource.egg-info/not-zip-safe0000644000175000017500000000000112214017615030155 0ustar arnauarnau zope2.13-2.13.21/source/zope.ptresource/README.txt0000644000175000017500000000077512214017615020314 0ustar arnauarnau======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides a "page template" resource class, a resource which content is processed with Zope Page Templates engine before returning to client. The resource factory class is registered for "pt", "zpt" and "html" file extensions in package's ``configure.zcml`` file. zope2.13-2.13.21/source/zope.ptresource/setup.cfg0000644000175000017500000000007312214017615020426 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.ptresource/buildout.cfg0000644000175000017500000000061412214017615021116 0ustar arnauarnau[buildout] develop = . parts = test coverage-test coverage-report [test] recipe = zc.recipe.testrunner eggs = zope.ptresource [test] [coverage-test] recipe = zc.recipe.testrunner eggs = zope.ptresource [test] defaults = ['--coverage', '../../coverage'] [coverage-report] recipe = zc.recipe.egg eggs = z3c.coverage scripts = coverage=coverage-report arguments = ('coverage', 'coverage/report') zope2.13-2.13.21/source/zope.ptresource/bootstrap.py0000644000175000017500000000337312214017615021202 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 103117 2009-08-23 19:54:32Z nadako $ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.ptresource/CHANGES.txt0000644000175000017500000000115712214017615020422 0ustar arnauarnau======= CHANGES ======= 3.9.0 (2009-08-27) ================== Initial release. This package was splitted off zope.app.publisher as a part of refactoring process. It's now a plugin for another package that was refactored from zope.app.publisher - zope.browserresource. See its documentation for more details. Other changes: * Don't render PageTemplateResource when called as the IResource interface requires that __call__ method should return an absolute URL. When accessed by browser, it still will be rendered, because "browserDefault" method now returns a callable that will render the template to browser. zope2.13-2.13.21/source/zope.ptresource/src/0000755000175000017500000000000012214017615017374 5ustar arnauarnauzope2.13-2.13.21/source/zope.ptresource/src/zope/0000755000175000017500000000000012214017615020351 5ustar arnauarnauzope2.13-2.13.21/source/zope.ptresource/src/zope/__init__.py0000644000175000017500000000007012214017615022457 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.ptresource/src/zope/ptresource/0000755000175000017500000000000012214017615022544 5ustar arnauarnauzope2.13-2.13.21/source/zope.ptresource/src/zope/ptresource/configure.zcml0000644000175000017500000000137512214017615025422 0ustar arnauarnau zope2.13-2.13.21/source/zope.ptresource/src/zope/ptresource/__init__.py0000644000175000017500000000000012214017615024643 0ustar arnauarnauzope2.13-2.13.21/source/zope.ptresource/src/zope/ptresource/tests.py0000644000175000017500000000573412214017615024271 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Page Template based Resources Test $Id: tests.py 103139 2009-08-24 11:58:22Z nadako $ """ import os import tempfile import unittest from zope.component import provideAdapter from zope.publisher.browser import TestRequest from zope.publisher.interfaces import NotFound from zope.security.checker import NamesChecker from zope.testing import cleanup from zope.traversing.adapters import DefaultTraversable from zope.traversing.interfaces import ITraversable from zope.ptresource.ptresource import PageTemplateResourceFactory checker = NamesChecker(('__call__', 'request', 'publishTraverse')) class Test(cleanup.CleanUp, unittest.TestCase): def setUp(self): super(Test, self).setUp() provideAdapter(DefaultTraversable, (None,), ITraversable) def createTestFile(self, contents): fd, path = tempfile.mkstemp() os.close(fd) open(path, 'w').write(contents) return path def testNoTraversal(self): path = self.createTestFile('

    test

    ') request = TestRequest() factory = PageTemplateResourceFactory(path, checker, 'test.pt') resource = factory(request) self.assertRaises(NotFound, resource.publishTraverse, resource.request, ()) os.unlink(path) def testBrowserDefault(self): path = self.createTestFile( '') test_data = "Foobar" request = TestRequest(test_data=test_data) factory = PageTemplateResourceFactory(path, checker, 'testresource.pt') resource = factory(request) view, next = resource.browserDefault(request) self.assertEquals(view(), '%s' % test_data) self.assertEquals('text/html', request.response.getHeader('Content-Type')) self.assertEquals(next, ()) request = TestRequest(test_data=test_data, REQUEST_METHOD='HEAD') resource = factory(request) view, next = resource.browserDefault(request) self.assertEquals(view(), '') self.assertEquals('text/html', request.response.getHeader('Content-Type')) self.assertEquals(next, ()) os.unlink(path) def test_suite(): return unittest.makeSuite(Test) zope2.13-2.13.21/source/zope.ptresource/src/zope/ptresource/ptresource.py0000644000175000017500000000634612214017615025322 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Page Template Resource $Id: ptresource.py 103139 2009-08-24 11:58:22Z nadako $ """ from zope.interface import implements, classProvides from zope.pagetemplate.engine import TrustedAppPT from zope.pagetemplate.pagetemplatefile import PageTemplateFile from zope.publisher.browser import BrowserView from zope.publisher.interfaces import NotFound from zope.publisher.interfaces.browser import IBrowserPublisher from zope.browserresource.resource import Resource from zope.browserresource.interfaces import IResourceFactory from zope.browserresource.interfaces import IResourceFactoryFactory class PageTemplate(TrustedAppPT, PageTemplateFile): """ Resource that is a page template """ def __init__(self, filename, _prefix=None, content_type=None): _prefix = self.get_path_from_prefix(_prefix) super(PageTemplate, self).__init__(filename, _prefix) if content_type is not None: self.content_type = content_type def pt_getContext(self, request, **kw): namespace = super(PageTemplate, self).pt_getContext(**kw) namespace['context'] = None namespace['request'] = request return namespace def __call__(self, request, **keywords): namespace = self.pt_getContext( request=request, options=keywords ) return self.pt_render(namespace) class PageTemplateResource(BrowserView, Resource): implements(IBrowserPublisher) def publishTraverse(self, request, name): '''See interface IBrowserPublisher''' raise NotFound(None, name) def browserDefault(self, request): '''See interface IBrowserPublisher''' return getattr(self, request.method), () def HEAD(self): pt = self.context response = self.request.response if not response.getHeader("Content-Type"): response.setHeader("Content-Type", pt.content_type) return '' def GET(self): pt = self.context response = self.request.response if not response.getHeader("Content-Type"): response.setHeader("Content-Type", pt.content_type) return pt(self.request) class PageTemplateResourceFactory(object): implements(IResourceFactory) classProvides(IResourceFactoryFactory) def __init__(self, path, checker, name): self.__pt = PageTemplate(path) self.__checker = checker self.__name = name def __call__(self, request): resource = PageTemplateResource(self.__pt, request) resource.__Security_checker__ = self.__checker resource.__name__ = self.__name return resource zope2.13-2.13.21/source/zope.ptresource/src/zope.ptresource.egg-info/0000755000175000017500000000000012214017615024235 5ustar arnauarnauzope2.13-2.13.21/source/zope.ptresource/src/zope.ptresource.egg-info/PKG-INFO0000644000175000017500000000372712214017615025343 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.ptresource Version: 3.9.0 Summary: Page template resource plugin for zope.browserresource Home-page: http://pypi.python.org/pypi/zope.ptresource/ Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: UNKNOWN Description: ======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides a "page template" resource class, a resource which content is processed with Zope Page Templates engine before returning to client. The resource factory class is registered for "pt", "zpt" and "html" file extensions in package's ``configure.zcml`` file. ======= CHANGES ======= 3.9.0 (2009-08-27) ================== Initial release. This package was splitted off zope.app.publisher as a part of refactoring process. It's now a plugin for another package that was refactored from zope.app.publisher - zope.browserresource. See its documentation for more details. Other changes: * Don't render PageTemplateResource when called as the IResource interface requires that __call__ method should return an absolute URL. When accessed by browser, it still will be rendered, because "browserDefault" method now returns a callable that will render the template to browser. Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.ptresource/src/zope.ptresource.egg-info/dependency_links.txt0000644000175000017500000000000112214017615030303 0ustar arnauarnau zope2.13-2.13.21/source/zope.ptresource/src/zope.ptresource.egg-info/requires.txt0000644000175000017500000000020312214017615026630 0ustar arnauarnausetuptools zope.browserresource zope.interface zope.pagetemplate zope.publisher zope.security[untrustedpython] [test] zope.testingzope2.13-2.13.21/source/zope.ptresource/src/zope.ptresource.egg-info/namespace_packages.txt0000644000175000017500000000000512214017615030563 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.ptresource/src/zope.ptresource.egg-info/top_level.txt0000644000175000017500000000000512214017615026762 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.ptresource/src/zope.ptresource.egg-info/SOURCES.txt0000644000175000017500000000100412214017615026114 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.ptresource.egg-info/PKG-INFO src/zope.ptresource.egg-info/SOURCES.txt src/zope.ptresource.egg-info/dependency_links.txt src/zope.ptresource.egg-info/namespace_packages.txt src/zope.ptresource.egg-info/not-zip-safe src/zope.ptresource.egg-info/requires.txt src/zope.ptresource.egg-info/top_level.txt src/zope/ptresource/__init__.py src/zope/ptresource/configure.zcml src/zope/ptresource/ptresource.py src/zope/ptresource/tests.pyzope2.13-2.13.21/source/zope.ptresource/src/zope.ptresource.egg-info/not-zip-safe0000644000175000017500000000000112214017615026463 0ustar arnauarnau zope2.13-2.13.21/source/zope.traversing/0000755000175000017500000000000012214017660016576 5ustar arnauarnauzope2.13-2.13.21/source/zope.traversing/setup.py0000644000175000017500000000502512214017660020312 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.traversing package """ from setuptools import setup, find_packages long_description = (open('README.txt').read() + '\n\n' + open('CHANGES.txt').read()) setup(name='zope.traversing', version='3.13.2', url='http://pypi.python.org/pypi/zope.traversing', license='ZPL 2.1', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description="Resolving paths in the object hierarchy", long_description=long_description, packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope',], extras_require = dict(test=['zope.browserpage', 'zope.browserresource', 'zope.configuration', 'zope.container', 'zope.pagetemplate', 'zope.site', 'zope.tal >= 3.5.0', 'zope.testing', 'ZODB3', ]), install_requires=['setuptools', 'zope.component', 'zope.i18n', 'zope.i18nmessageid', 'zope.interface', 'zope.proxy', 'zope.publisher', 'zope.security', 'zope.location>=3.7.0', ], include_package_data = True, zip_safe = False, ) zope2.13-2.13.21/source/zope.traversing/PKG-INFO0000644000175000017500000002200412214017660017671 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.traversing Version: 3.13.2 Summary: Resolving paths in the object hierarchy Home-page: http://pypi.python.org/pypi/zope.traversing Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: The ``zope.traversing`` package provides adapteres for resolving object paths by traversing an object hierarchy. This also includes support for traversal namespaces (e.g. ``++view++``, ``++skin++``, etc.) as well as computing URLs via the ``@@absolute_url`` view. ======= Changes ======= 3.13.2 (2011-03-02) ------------------- - Re-release of 3.13.0. 3.13.1 introduced dependencies unsuitable for a bugfix release. 3.13.1 (2010-12-14) ------------------- - Fixed ZCML-related dependencies. 3.13 (2010-07-09) ----------------- - When a ``__parent__`` attribute is available on an object, it is always used for absolute URL construction, and no ILocation adapter lookup is performed for it. This was the previous behavior but was broken (around 3.5?) due to dependency refactoring. If the object provides no ``__parent__`` then an ILocation adapter lookup will be performed. This will always succeed as zope.location provides a default LocationProxy for everything, but more specific ILocation adapters can also be provided. 3.12.1 (2010-04-30) ------------------- - Removed use of 'zope.testing.doctestunit' in favor of stdlib's doctest. 3.12.0 (2009-12-29) ------------------- - Avoid testing dependencies on zope.securitypolicies and zope.principalregistry. 3.11.0 (2009-12-27) ------------------- - Removed testing dependency on zope.app.publication. 3.10.0 (2009-12-16) ------------------- - Removed stray test claiming a no longer existing dependency on zope.app.applicationcontrol. - Refactored functional tests to loose dependency on both zope.app.appsetup and zope.app.testing. - Simplified tests for the browser sub-package by using PlacelessSetup from zope.component.testing instead of zope.app.testing. - Simplified test_dependencies module by using zope.configuration instead of zope.app.testing.functional. - Removed testing dependency on zope.app.publisher. - Replaced testing dependency on zope.app.security with zope.securitypolicy. - Removed testing dependency on zope.app.zcmlfiles in favor of more explicit dependencies. - Removed testing dependency on zope.app.component. - Replaced a test dependency on zope.app.zptpage with a dependency on zope.pagetemplate. 3.9.0 (2009-12-15) ------------------ - Moved IBeforeTraverseEvent from zope.app.publication into this package, as we already deal with publication traversal. 3.8.0 (2009-09-29) ------------------ - In zope.traversing.api.getParent(), try to delegate to zope.location.interfaces.ILocationInfo.getParent(), analogous to getParents(). Keep returning the traversal parent as a fallback. - Brought ITraverser back from zope.location where it had been moved to invert the package interdependency, but is no longer used now. 3.7.2 (2009-08-29) ------------------ - Made virtual hosting tests compatible with zope.publisher 3.9. Redirecting to a different host requires an explicit `trusted` redirect now. 3.7.1 (2009-06-16) ------------------ - AbsoluteURL now implements the fact that __call__ returns the same as __str__ in a manner that it applies for subclasses, too, so they only have to override __str__ and not both. 3.7.0 (2009-05-23) ------------------ - Moved the publicationtraverse module to zope.traversing, removing the zope.app.publisher -> zope.app.publication dependency (which was a cycle). - Look up the application controller through a utility registration rather than a direct reference. 3.6.0 (2009-04-06) ------------------ - Change configure.zcml to not depend on zope.app.component. - This release includes the BBB-incompatible ``zope.publisher.skinnable`` change from 3.5.3. 3.5.4 (2009-04-06) ------------------ - Revert BBB-incompatible use of ``zope.publisher.skinnable``: that change belongs in a 3.6.0 release, because it requires a BBB-incompatible version of ``zope.publisher``. 3.5.3 (2009-03-10) ------------------ - Use applySkin from new location. zope.publisher.skinnable instead of zope.publisher.browser. - Use IAbsoluteURL lookup instead of the "absolute_url" view in the recursive AbsoluteURL adapters (LP: #338101). 3.5.2 (2009-02-04) ------------------ - The RootPhysicallyLocatable is not the same as LocationPhysicallyLocatable now in zope.location. Fix the import and testing setups. 3.5.1 (2009-02-02) ------------------ - The ``RootPhysicallyLocatable`` adapter has been superseded by the refactored ``zope.location.traversing.LocationPhysicallyLocatable`` that we depend on since 3.5.0a4. Remove the adapter and its registration, and making its import place pointing to ``zope.location.traversing.LocationPhysicallyLocatable`` to maintain backward-compatibility. This also fixes a bug introduced in version 3.5.0a4 when trying to call ``getParents`` function for the root object. - Use direct imports instead of compatibility ones for things that were moved to ``zope.location``. - Remove the ``zope.traversing.interfaces.INamespaceHandler`` interface, as it seems not to be used for years. - Change package's mailing list address to zope-dev at zope.org instead of retired zope3-dev at zope.org 3.5.0 (2009-01-31) ------------------ - Use zope.container instead of zope.app.container. - Use zope.site instead of zope.app.folder in the unit tests. - Reduced, but did not eliminate, test dependencies on zope.app.component. 3.5.0a4 (2008-08-01) -------------------- - Reverse dependencies between zope.location and zope.traversing. - Updated (test) dependencies and tests to expect and work with a spec compliant TAL interpreter as available in zope.tal >= 3.5.0. - Fixed deprecation warning caused by using an old module name for ZopeSecurityPolicy in ftesting.zcml - Made sure traversing doesn't raise an TypeError but a TraversalError when the traversal step before yielded a string. 3.5.0a3 (2007-12-28) -------------------- - backed out the controversial `++skin++` traverser for XML-RPC. 3.5.0a2 (2007-11-28) -------------------- - ported 3.4.1a1 to trunk - Do not use unicode strings to set the application server in the virtual host namespace. This caused absolute_url to create unicode URL's. - Added a traverer for ++skin++ for XMLRPC skins (IXMLRPCSkinType). This also means that the normal ++skin++ namespace handler is only bound to IBrowserRequest. - Resolved the dependency on zope.app.applicationcontrol by importing the application controller only if the package is available. 3.4.1 (2008-07-30) ------------------ - Fixed deprecation warning caused by using an old module name for ZopeSecurityPolicy in ftesting.zcml 3.4.1a1 (2007-11-13) -------------------- Do not use unicode strings to set the application server in the virtual host namespace. This caused absolute_url to create unicode URL's. 3.4.0 (2007-09-29) ------------------ No further changes since 3.4.0a1. 3.4.0a1 (2007-04-22) -------------------- Initial release as a separate project, corresponds to zope.traversing from Zope 3.4.0a1 Platform: UNKNOWN zope2.13-2.13.21/source/zope.traversing/pip-egg-info/0000755000175000017500000000000012214017660021057 5ustar arnauarnauzope2.13-2.13.21/source/zope.traversing/pip-egg-info/zope.traversing.egg-info/0000755000175000017500000000000012214017660025711 5ustar arnauarnauzope2.13-2.13.21/source/zope.traversing/pip-egg-info/zope.traversing.egg-info/PKG-INFO0000644000175000017500000002200412214017660027004 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.traversing Version: 3.13.2 Summary: Resolving paths in the object hierarchy Home-page: http://pypi.python.org/pypi/zope.traversing Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: The ``zope.traversing`` package provides adapteres for resolving object paths by traversing an object hierarchy. This also includes support for traversal namespaces (e.g. ``++view++``, ``++skin++``, etc.) as well as computing URLs via the ``@@absolute_url`` view. ======= Changes ======= 3.13.2 (2011-03-02) ------------------- - Re-release of 3.13.0. 3.13.1 introduced dependencies unsuitable for a bugfix release. 3.13.1 (2010-12-14) ------------------- - Fixed ZCML-related dependencies. 3.13 (2010-07-09) ----------------- - When a ``__parent__`` attribute is available on an object, it is always used for absolute URL construction, and no ILocation adapter lookup is performed for it. This was the previous behavior but was broken (around 3.5?) due to dependency refactoring. If the object provides no ``__parent__`` then an ILocation adapter lookup will be performed. This will always succeed as zope.location provides a default LocationProxy for everything, but more specific ILocation adapters can also be provided. 3.12.1 (2010-04-30) ------------------- - Removed use of 'zope.testing.doctestunit' in favor of stdlib's doctest. 3.12.0 (2009-12-29) ------------------- - Avoid testing dependencies on zope.securitypolicies and zope.principalregistry. 3.11.0 (2009-12-27) ------------------- - Removed testing dependency on zope.app.publication. 3.10.0 (2009-12-16) ------------------- - Removed stray test claiming a no longer existing dependency on zope.app.applicationcontrol. - Refactored functional tests to loose dependency on both zope.app.appsetup and zope.app.testing. - Simplified tests for the browser sub-package by using PlacelessSetup from zope.component.testing instead of zope.app.testing. - Simplified test_dependencies module by using zope.configuration instead of zope.app.testing.functional. - Removed testing dependency on zope.app.publisher. - Replaced testing dependency on zope.app.security with zope.securitypolicy. - Removed testing dependency on zope.app.zcmlfiles in favor of more explicit dependencies. - Removed testing dependency on zope.app.component. - Replaced a test dependency on zope.app.zptpage with a dependency on zope.pagetemplate. 3.9.0 (2009-12-15) ------------------ - Moved IBeforeTraverseEvent from zope.app.publication into this package, as we already deal with publication traversal. 3.8.0 (2009-09-29) ------------------ - In zope.traversing.api.getParent(), try to delegate to zope.location.interfaces.ILocationInfo.getParent(), analogous to getParents(). Keep returning the traversal parent as a fallback. - Brought ITraverser back from zope.location where it had been moved to invert the package interdependency, but is no longer used now. 3.7.2 (2009-08-29) ------------------ - Made virtual hosting tests compatible with zope.publisher 3.9. Redirecting to a different host requires an explicit `trusted` redirect now. 3.7.1 (2009-06-16) ------------------ - AbsoluteURL now implements the fact that __call__ returns the same as __str__ in a manner that it applies for subclasses, too, so they only have to override __str__ and not both. 3.7.0 (2009-05-23) ------------------ - Moved the publicationtraverse module to zope.traversing, removing the zope.app.publisher -> zope.app.publication dependency (which was a cycle). - Look up the application controller through a utility registration rather than a direct reference. 3.6.0 (2009-04-06) ------------------ - Change configure.zcml to not depend on zope.app.component. - This release includes the BBB-incompatible ``zope.publisher.skinnable`` change from 3.5.3. 3.5.4 (2009-04-06) ------------------ - Revert BBB-incompatible use of ``zope.publisher.skinnable``: that change belongs in a 3.6.0 release, because it requires a BBB-incompatible version of ``zope.publisher``. 3.5.3 (2009-03-10) ------------------ - Use applySkin from new location. zope.publisher.skinnable instead of zope.publisher.browser. - Use IAbsoluteURL lookup instead of the "absolute_url" view in the recursive AbsoluteURL adapters (LP: #338101). 3.5.2 (2009-02-04) ------------------ - The RootPhysicallyLocatable is not the same as LocationPhysicallyLocatable now in zope.location. Fix the import and testing setups. 3.5.1 (2009-02-02) ------------------ - The ``RootPhysicallyLocatable`` adapter has been superseded by the refactored ``zope.location.traversing.LocationPhysicallyLocatable`` that we depend on since 3.5.0a4. Remove the adapter and its registration, and making its import place pointing to ``zope.location.traversing.LocationPhysicallyLocatable`` to maintain backward-compatibility. This also fixes a bug introduced in version 3.5.0a4 when trying to call ``getParents`` function for the root object. - Use direct imports instead of compatibility ones for things that were moved to ``zope.location``. - Remove the ``zope.traversing.interfaces.INamespaceHandler`` interface, as it seems not to be used for years. - Change package's mailing list address to zope-dev at zope.org instead of retired zope3-dev at zope.org 3.5.0 (2009-01-31) ------------------ - Use zope.container instead of zope.app.container. - Use zope.site instead of zope.app.folder in the unit tests. - Reduced, but did not eliminate, test dependencies on zope.app.component. 3.5.0a4 (2008-08-01) -------------------- - Reverse dependencies between zope.location and zope.traversing. - Updated (test) dependencies and tests to expect and work with a spec compliant TAL interpreter as available in zope.tal >= 3.5.0. - Fixed deprecation warning caused by using an old module name for ZopeSecurityPolicy in ftesting.zcml - Made sure traversing doesn't raise an TypeError but a TraversalError when the traversal step before yielded a string. 3.5.0a3 (2007-12-28) -------------------- - backed out the controversial `++skin++` traverser for XML-RPC. 3.5.0a2 (2007-11-28) -------------------- - ported 3.4.1a1 to trunk - Do not use unicode strings to set the application server in the virtual host namespace. This caused absolute_url to create unicode URL's. - Added a traverer for ++skin++ for XMLRPC skins (IXMLRPCSkinType). This also means that the normal ++skin++ namespace handler is only bound to IBrowserRequest. - Resolved the dependency on zope.app.applicationcontrol by importing the application controller only if the package is available. 3.4.1 (2008-07-30) ------------------ - Fixed deprecation warning caused by using an old module name for ZopeSecurityPolicy in ftesting.zcml 3.4.1a1 (2007-11-13) -------------------- Do not use unicode strings to set the application server in the virtual host namespace. This caused absolute_url to create unicode URL's. 3.4.0 (2007-09-29) ------------------ No further changes since 3.4.0a1. 3.4.0a1 (2007-04-22) -------------------- Initial release as a separate project, corresponds to zope.traversing from Zope 3.4.0a1 Platform: UNKNOWN zope2.13-2.13.21/source/zope.traversing/pip-egg-info/zope.traversing.egg-info/dependency_links.txt0000644000175000017500000000000112214017660031757 0ustar arnauarnau zope2.13-2.13.21/source/zope.traversing/pip-egg-info/zope.traversing.egg-info/requires.txt0000644000175000017500000000042312214017660030310 0ustar arnauarnausetuptools zope.component zope.i18n zope.i18nmessageid zope.interface zope.proxy zope.publisher zope.security zope.location>=3.7.0 [test] zope.browserpage zope.browserresource zope.configuration zope.container zope.pagetemplate zope.site zope.tal >= 3.5.0 zope.testing ZODB3zope2.13-2.13.21/source/zope.traversing/pip-egg-info/zope.traversing.egg-info/namespace_packages.txt0000644000175000017500000000000512214017660032237 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.traversing/pip-egg-info/zope.traversing.egg-info/top_level.txt0000644000175000017500000000000512214017660030436 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.traversing/pip-egg-info/zope.traversing.egg-info/SOURCES.txt0000644000175000017500000000252012214017660027574 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.traversing.egg-info/PKG-INFO pip-egg-info/zope.traversing.egg-info/SOURCES.txt pip-egg-info/zope.traversing.egg-info/dependency_links.txt pip-egg-info/zope.traversing.egg-info/namespace_packages.txt pip-egg-info/zope.traversing.egg-info/not-zip-safe pip-egg-info/zope.traversing.egg-info/requires.txt pip-egg-info/zope.traversing.egg-info/top_level.txt src/zope/__init__.py src/zope/traversing/__init__.py src/zope/traversing/adapters.py src/zope/traversing/api.py src/zope/traversing/interfaces.py src/zope/traversing/namespace.py src/zope/traversing/publicationtraverse.py src/zope/traversing/testing.py src/zope/traversing/browser/__init__.py src/zope/traversing/browser/absoluteurl.py src/zope/traversing/browser/interfaces.py src/zope/traversing/browser/tests.py src/zope/traversing/tests/__init__.py src/zope/traversing/tests/test_conveniencefunctions.py src/zope/traversing/tests/test_dependencies.py src/zope/traversing/tests/test_lang.py src/zope/traversing/tests/test_namespacetrversal.py src/zope/traversing/tests/test_physicallocationadapters.py src/zope/traversing/tests/test_presentation.py src/zope/traversing/tests/test_publicationtraverse.py src/zope/traversing/tests/test_skin.py src/zope/traversing/tests/test_traverser.py src/zope/traversing/tests/test_vh.py src/zope/traversing/tests/test_vhosting.pyzope2.13-2.13.21/source/zope.traversing/pip-egg-info/zope.traversing.egg-info/not-zip-safe0000644000175000017500000000000112214017660030137 0ustar arnauarnau zope2.13-2.13.21/source/zope.traversing/LICENSE.txt0000644000175000017500000000402612214017660020423 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/zope.traversing/README.txt0000644000175000017500000000041112214017660020270 0ustar arnauarnauThe ``zope.traversing`` package provides adapteres for resolving object paths by traversing an object hierarchy. This also includes support for traversal namespaces (e.g. ``++view++``, ``++skin++``, etc.) as well as computing URLs via the ``@@absolute_url`` view. zope2.13-2.13.21/source/zope.traversing/setup.cfg0000644000175000017500000000007312214017660020417 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.traversing/COPYRIGHT.txt0000644000175000017500000000004012214017660020701 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/zope.traversing/buildout.cfg0000644000175000017500000000061412214017660021107 0ustar arnauarnau[buildout] develop = . parts = test coverage-test coverage-report [test] recipe = zc.recipe.testrunner eggs = zope.traversing [test] [coverage-test] recipe = zc.recipe.testrunner eggs = zope.traversing [test] defaults = ['--coverage', '../../coverage'] [coverage-report] recipe = zc.recipe.egg eggs = z3c.coverage scripts = coverage=coverage-report arguments = ('coverage', 'coverage/report') zope2.13-2.13.21/source/zope.traversing/bootstrap.py0000644000175000017500000000733012214017660021170 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.traversing/CHANGES.txt0000644000175000017500000001515312214017660020414 0ustar arnauarnau======= Changes ======= 3.13.2 (2011-03-02) ------------------- - Re-release of 3.13.0. 3.13.1 introduced dependencies unsuitable for a bugfix release. 3.13.1 (2010-12-14) ------------------- - Fixed ZCML-related dependencies. 3.13 (2010-07-09) ----------------- - When a ``__parent__`` attribute is available on an object, it is always used for absolute URL construction, and no ILocation adapter lookup is performed for it. This was the previous behavior but was broken (around 3.5?) due to dependency refactoring. If the object provides no ``__parent__`` then an ILocation adapter lookup will be performed. This will always succeed as zope.location provides a default LocationProxy for everything, but more specific ILocation adapters can also be provided. 3.12.1 (2010-04-30) ------------------- - Removed use of 'zope.testing.doctestunit' in favor of stdlib's doctest. 3.12.0 (2009-12-29) ------------------- - Avoid testing dependencies on zope.securitypolicies and zope.principalregistry. 3.11.0 (2009-12-27) ------------------- - Removed testing dependency on zope.app.publication. 3.10.0 (2009-12-16) ------------------- - Removed stray test claiming a no longer existing dependency on zope.app.applicationcontrol. - Refactored functional tests to loose dependency on both zope.app.appsetup and zope.app.testing. - Simplified tests for the browser sub-package by using PlacelessSetup from zope.component.testing instead of zope.app.testing. - Simplified test_dependencies module by using zope.configuration instead of zope.app.testing.functional. - Removed testing dependency on zope.app.publisher. - Replaced testing dependency on zope.app.security with zope.securitypolicy. - Removed testing dependency on zope.app.zcmlfiles in favor of more explicit dependencies. - Removed testing dependency on zope.app.component. - Replaced a test dependency on zope.app.zptpage with a dependency on zope.pagetemplate. 3.9.0 (2009-12-15) ------------------ - Moved IBeforeTraverseEvent from zope.app.publication into this package, as we already deal with publication traversal. 3.8.0 (2009-09-29) ------------------ - In zope.traversing.api.getParent(), try to delegate to zope.location.interfaces.ILocationInfo.getParent(), analogous to getParents(). Keep returning the traversal parent as a fallback. - Brought ITraverser back from zope.location where it had been moved to invert the package interdependency, but is no longer used now. 3.7.2 (2009-08-29) ------------------ - Made virtual hosting tests compatible with zope.publisher 3.9. Redirecting to a different host requires an explicit `trusted` redirect now. 3.7.1 (2009-06-16) ------------------ - AbsoluteURL now implements the fact that __call__ returns the same as __str__ in a manner that it applies for subclasses, too, so they only have to override __str__ and not both. 3.7.0 (2009-05-23) ------------------ - Moved the publicationtraverse module to zope.traversing, removing the zope.app.publisher -> zope.app.publication dependency (which was a cycle). - Look up the application controller through a utility registration rather than a direct reference. 3.6.0 (2009-04-06) ------------------ - Change configure.zcml to not depend on zope.app.component. - This release includes the BBB-incompatible ``zope.publisher.skinnable`` change from 3.5.3. 3.5.4 (2009-04-06) ------------------ - Revert BBB-incompatible use of ``zope.publisher.skinnable``: that change belongs in a 3.6.0 release, because it requires a BBB-incompatible version of ``zope.publisher``. 3.5.3 (2009-03-10) ------------------ - Use applySkin from new location. zope.publisher.skinnable instead of zope.publisher.browser. - Use IAbsoluteURL lookup instead of the "absolute_url" view in the recursive AbsoluteURL adapters (LP: #338101). 3.5.2 (2009-02-04) ------------------ - The RootPhysicallyLocatable is not the same as LocationPhysicallyLocatable now in zope.location. Fix the import and testing setups. 3.5.1 (2009-02-02) ------------------ - The ``RootPhysicallyLocatable`` adapter has been superseded by the refactored ``zope.location.traversing.LocationPhysicallyLocatable`` that we depend on since 3.5.0a4. Remove the adapter and its registration, and making its import place pointing to ``zope.location.traversing.LocationPhysicallyLocatable`` to maintain backward-compatibility. This also fixes a bug introduced in version 3.5.0a4 when trying to call ``getParents`` function for the root object. - Use direct imports instead of compatibility ones for things that were moved to ``zope.location``. - Remove the ``zope.traversing.interfaces.INamespaceHandler`` interface, as it seems not to be used for years. - Change package's mailing list address to zope-dev at zope.org instead of retired zope3-dev at zope.org 3.5.0 (2009-01-31) ------------------ - Use zope.container instead of zope.app.container. - Use zope.site instead of zope.app.folder in the unit tests. - Reduced, but did not eliminate, test dependencies on zope.app.component. 3.5.0a4 (2008-08-01) -------------------- - Reverse dependencies between zope.location and zope.traversing. - Updated (test) dependencies and tests to expect and work with a spec compliant TAL interpreter as available in zope.tal >= 3.5.0. - Fixed deprecation warning caused by using an old module name for ZopeSecurityPolicy in ftesting.zcml - Made sure traversing doesn't raise an TypeError but a TraversalError when the traversal step before yielded a string. 3.5.0a3 (2007-12-28) -------------------- - backed out the controversial `++skin++` traverser for XML-RPC. 3.5.0a2 (2007-11-28) -------------------- - ported 3.4.1a1 to trunk - Do not use unicode strings to set the application server in the virtual host namespace. This caused absolute_url to create unicode URL's. - Added a traverer for ++skin++ for XMLRPC skins (IXMLRPCSkinType). This also means that the normal ++skin++ namespace handler is only bound to IBrowserRequest. - Resolved the dependency on zope.app.applicationcontrol by importing the application controller only if the package is available. 3.4.1 (2008-07-30) ------------------ - Fixed deprecation warning caused by using an old module name for ZopeSecurityPolicy in ftesting.zcml 3.4.1a1 (2007-11-13) -------------------- Do not use unicode strings to set the application server in the virtual host namespace. This caused absolute_url to create unicode URL's. 3.4.0 (2007-09-29) ------------------ No further changes since 3.4.0a1. 3.4.0a1 (2007-04-22) -------------------- Initial release as a separate project, corresponds to zope.traversing from Zope 3.4.0a1 zope2.13-2.13.21/source/zope.traversing/src/0000755000175000017500000000000012214017660017365 5ustar arnauarnauzope2.13-2.13.21/source/zope.traversing/src/zope/0000755000175000017500000000000012214017660020342 5ustar arnauarnauzope2.13-2.13.21/source/zope.traversing/src/zope/__init__.py0000644000175000017500000000007012214017660022450 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/0000755000175000017500000000000012214017660022526 5ustar arnauarnauzope2.13-2.13.21/source/zope.traversing/src/zope/traversing/adapters.py0000644000175000017500000001047712214017660024714 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Adapters for the traversing mechanism """ from types import StringTypes import zope.interface import zope.component from zope.location.interfaces import ILocationInfo, LocationError from zope.traversing.interfaces import ITraversable, ITraverser from zope.traversing.namespace import namespaceLookup from zope.traversing.namespace import UnexpectedParameters from zope.traversing.namespace import nsParse from zope.location.traversing import RootPhysicallyLocatable # BBB _marker = object() # opaque marker that doesn't get security proxied class DefaultTraversable(object): """Traverses objects via attribute and item lookup""" zope.interface.implements(ITraversable) def __init__(self, subject): self._subject = subject def traverse(self, name, furtherPath): subject = self._subject __traceback_info__ = (subject, name, furtherPath) attr = getattr(subject, name, _marker) if attr is not _marker: return attr if hasattr(subject, '__getitem__'): try: return subject[name] except (KeyError, TypeError): pass raise LocationError(subject, name) class Traverser(object): """Provide traverse features""" zope.interface.implements(ITraverser) # This adapter can be used for any object. def __init__(self, wrapper): self.context = wrapper def traverse(self, path, default=_marker, request=None): if not path: return self.context if isinstance(path, StringTypes): path = path.split('/') if len(path) > 1 and not path[-1]: # Remove trailing slash path.pop() else: path = list(path) path.reverse() pop = path.pop curr = self.context if not path[-1]: # Start at the root pop() curr = ILocationInfo(self.context).getRoot() try: while path: name = pop() curr = traversePathElement(curr, name, path, request=request) return curr except LocationError: if default == _marker: raise return default def traversePathElement(obj, name, further_path, default=_marker, traversable=None, request=None): """Traverse a single step 'name' relative to the given object. 'name' must be a string. '.' and '..' are treated specially, as well as names starting with '@' or '+'. Otherwise 'name' will be treated as a single path segment. 'further_path' is a list of names still to be traversed. This method is allowed to change the contents of 'further_path'. You can explicitly pass in an ITraversable as the 'traversable' argument. If you do not, the given object will be adapted to ITraversable. 'request' is passed in when traversing from presentation code. This allows paths like @@foo to work. Raises LocationError if path cannot be found and 'default' was not provided. """ __traceback_info__ = (obj, name) if name == '.': return obj if name == '..': return obj.__parent__ if name and name[:1] in '@+': ns, nm = nsParse(name) if ns: return namespaceLookup(ns, nm, obj, request) else: nm = name if traversable is None: traversable = ITraversable(obj, None) if traversable is None: raise LocationError('No traversable adapter found', obj) try: return traversable.traverse(nm, further_path) except LocationError: if default is not _marker: return default else: raise return obj zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/configure.zcml0000644000175000017500000000661012214017660025401 0ustar arnauarnau zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/publicationtraverse.py0000644000175000017500000000776412214017660027203 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Publication Traverser """ __docformat__ = 'restructuredtext' from types import StringTypes from zope.component import queryMultiAdapter from zope.publisher.interfaces import NotFound from zope.security.checker import ProxyFactory from zope.traversing.namespace import namespaceLookup from zope.traversing.namespace import nsParse from zope.traversing.interfaces import TraversalError from zope.publisher.interfaces import IPublishTraverse from zope.publisher.interfaces.browser import IBrowserPublisher class PublicationTraverser(object): """Traversal used for publication. The significant differences from zope.traversing.adapters.traversePathElement() are: - Instead of adapting each traversed object to ITraversable, this version multi-adapts (ob, request) to IPublishTraverse. - This version wraps a security proxy around each traversed object. - This version raises NotFound rather than LocationError. - This version has a method, traverseRelativeURL(), that supports "browserDefault" traversal. """ def proxy(self, ob): return ProxyFactory(ob) def traverseName(self, request, ob, name): nm = name # the name to look up the object with if name and name[:1] in '@+': # Process URI segment parameters. ns, nm = nsParse(name) if ns: try: ob2 = namespaceLookup(ns, nm, ob, request) except TraversalError: raise NotFound(ob, name) return self.proxy(ob2) if nm == '.': return ob if IPublishTraverse.providedBy(ob): ob2 = ob.publishTraverse(request, nm) else: # self is marker adapter = queryMultiAdapter((ob, request), IPublishTraverse, default=self) if adapter is not self: ob2 = adapter.publishTraverse(request, nm) else: raise NotFound(ob, name, request) return self.proxy(ob2) def traversePath(self, request, ob, path): if isinstance(path, StringTypes): path = path.split('/') if len(path) > 1 and not path[-1]: # Remove trailing slash path.pop() else: path = list(path) # Remove single dots path = [x for x in path if x != '.'] path.reverse() # Remove double dots while '..' in path: l = path.index('..') if l < 0 or l+2 > len(path): break del path[l:l+2] pop = path.pop while path: name = pop() ob = self.traverseName(request, ob, name) return ob def traverseRelativeURL(self, request, ob, path): """Path traversal that includes browserDefault paths""" ob = self.traversePath(request, ob, path) while True: adapter = IBrowserPublisher(ob, None) if adapter is None: return ob ob, path = adapter.browserDefault(request) ob = self.proxy(ob) if not path: return ob ob = self.traversePath(request, ob, path) # alternate spelling PublicationTraverse = PublicationTraverser class PublicationTraverserWithoutProxy(PublicationTraverse): def proxy(self, ob): return ob zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/interfaces.py0000644000175000017500000001432112214017660025224 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interfaces to do with traversing. """ from zope.interface import Attribute from zope.interface import Interface from zope.interface import implements from zope.component.interfaces import IObjectEvent # BBB: Re-import symbols to their old location. from zope.location.interfaces import LocationError as TraversalError from zope.location.interfaces import IRoot as IContainmentRoot from zope.location.interfaces import ILocationInfo as IPhysicallyLocatable _RAISE_KEYERROR = object() class ITraversable(Interface): """To traverse an object, this interface must be provided""" def traverse(name, furtherPath): """Get the next item on the path Should return the item corresponding to 'name' or raise LocationError where appropriate. 'name' is an ASCII string or Unicode object. 'furtherPath' is a list of names still to be traversed. This method is allowed to change the contents of furtherPath. """ class ITraverser(Interface): """Provide traverse features""" # XXX This is used like a utility but implemented as an adapter: The # traversal policy is only implemented once and repeated for all objects # along the path. def traverse(path, default=_RAISE_KEYERROR): """Return an object given a path. Path is either an immutable sequence of strings or a slash ('/') delimited string. If the first string in the path sequence is an empty string, or the path begins with a '/', start at the root. Otherwise the path is relative to the current context. If the object is not found, return 'default' argument. """ class ITraversalAPI(Interface): """Common API functions to ease traversal computations """ def joinPath(path, *args): """Join the given relative paths to the given path. Returns a unicode path. The path should be well-formed, and not end in a '/' unless it is the root path. It can be either a string (ascii only) or unicode. The positional arguments are relative paths to be added to the path as new path segments. The path may be absolute or relative. A segment may not start with a '/' because that would be confused with an absolute path. A segment may not end with a '/' because we do not allow '/' at the end of relative paths. A segment may consist of . or .. to mean "the same place", or "the parent path" respectively. A '.' should be removed and a '..' should cause the segment to the left to be removed. joinPath('/', '..') should raise an exception. """ def getPath(obj): """Returns a string representing the physical path to the object. """ def getRoot(obj): """Returns the root of the traversal for the given object. """ def traverse(object, path, default=None, request=None): """Traverse 'path' relative to the given object. 'path' is a string with path segments separated by '/'. 'request' is passed in when traversing from presentation code. This allows paths like @@foo to work. Raises LocationError if path cannot be found Note: calling traverse with a path argument taken from an untrusted source, such as an HTTP request form variable, is a bad idea. It could allow a maliciously constructed request to call code unexpectedly. Consider using traverseName instead. """ def traverseName(obj, name, default=None, traversable=None, request=None): """Traverse a single step 'name' relative to the given object. 'name' must be a string. '.' and '..' are treated specially, as well as names starting with '@' or '+'. Otherwise 'name' will be treated as a single path segment. You can explicitly pass in an ITraversable as the 'traversable' argument. If you do not, the given object will be adapted to ITraversable. 'request' is passed in when traversing from presentation code. This allows paths like @@foo to work. Raises LocationError if path cannot be found and 'default' was not provided. """ def getName(obj): """Get the name an object was traversed via """ def getParent(obj): """Returns the container the object was traversed via. Returns None if the object is a containment root. Raises TypeError if the object doesn't have enough context to get the parent. """ def getParents(obj): """Returns a list starting with the given object's parent followed by each of its parents. Raises a TypeError if the context doesn't go all the way down to a containment root. """ def canonicalPath(path_or_object): """Returns a canonical absolute unicode path for the path or object. Resolves segments that are '.' or '..'. Raises ValueError if a badly formed path is given. """ class IPathAdapter(Interface): """Marker interface for adapters to be used in paths """ class IEtcNamespace(Interface): """Marker for utility registrations in the ++etc++ namespace """ class IBeforeTraverseEvent(IObjectEvent): """An event which gets sent on publication traverse""" request = Attribute("The current request") class BeforeTraverseEvent(object): """An event which gets sent on publication traverse""" implements(IBeforeTraverseEvent) def __init__(self, ob, request): self.object = ob self.request = request zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/namespace.py0000644000175000017500000004147712214017660025051 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """URL Namespace Implementations """ __docformat__ = 'restructuredtext' import re import zope.component import zope.interface from zope.i18n.interfaces import IModifiableUserPreferredLanguages from zope.component.interfaces import ComponentLookupError from zope.interface import providedBy, directlyProvides, directlyProvidedBy from zope.location.interfaces import IRoot, LocationError from zope.publisher.interfaces.browser import IBrowserSkinType from zope.publisher.skinnable import applySkin from zope.security.proxy import removeSecurityProxy from zope.traversing.interfaces import IEtcNamespace from zope.traversing.interfaces import IPathAdapter from zope.traversing.interfaces import ITraversable class UnexpectedParameters(LocationError): "Unexpected namespace parameters were provided." class ExcessiveDepth(LocationError): "Too many levels of containment. We don't believe them." def namespaceLookup(ns, name, object, request=None): """Lookup a value from a namespace We look up a value using a view or an adapter, depending on whether a request is passed. Let's start with adapter-based transersal: >>> class I(zope.interface.Interface): ... 'Test interface' >>> class C(object): ... zope.interface.implements(I) We'll register a simple testing adapter: >>> class Adapter(object): ... def __init__(self, context): ... self.context = context ... def traverse(self, name, remaining): ... return name+'42' >>> zope.component.provideAdapter(Adapter, (I,), ITraversable, 'foo') Then given an object, we can traverse it with a namespace-qualified name: >>> namespaceLookup('foo', 'bar', C()) 'bar42' If we give an invalid namespace, we'll get a not found error: >>> namespaceLookup('fiz', 'bar', C()) # doctest: +ELLIPSIS Traceback (most recent call last): ... LocationError: (, '++fiz++bar') We'll get the same thing if we provide a request: >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> namespaceLookup('foo', 'bar', C(), request) # doctest: +ELLIPSIS Traceback (most recent call last): ... LocationError: (, '++foo++bar') We need to provide a view: >>> class View(object): ... def __init__(self, context, request): ... pass ... def traverse(self, name, remaining): ... return name+'fromview' >>> from zope.traversing.testing import browserView >>> browserView(I, 'foo', View, providing=ITraversable) >>> namespaceLookup('foo', 'bar', C(), request) 'barfromview' Clean up: >>> from zope.testing.cleanup import cleanUp >>> cleanUp() """ if request is not None: traverser = zope.component.queryMultiAdapter((object, request), ITraversable, ns) else: traverser = zope.component.queryAdapter(object, ITraversable, ns) if traverser is None: raise LocationError(object, "++%s++%s" % (ns, name)) return traverser.traverse(name, ()) namespace_pattern = re.compile('[+][+]([a-zA-Z0-9_]+)[+][+]') def nsParse(name): """Parse a namespace-qualified name into a namespace name and a name. Returns the namespace name and a name. A namespace-qualified name is usually of the form ++ns++name, as in: >>> nsParse('++acquire++foo') ('acquire', 'foo') The part inside the +s must be an identifier, so: >>> nsParse('++hello world++foo') ('', '++hello world++foo') >>> nsParse('+++acquire+++foo') ('', '+++acquire+++foo') But it may also be a @@foo, which implies the view namespace: >>> nsParse('@@foo') ('view', 'foo') >>> nsParse('@@@foo') ('view', '@foo') >>> nsParse('@foo') ('', '@foo') """ ns = '' if name.startswith('@@'): ns = 'view' name = name[2:] else: match = namespace_pattern.match(name) if match: prefix, ns = match.group(0, 1) name = name[len(prefix):] return ns, name def getResource(site, name, request): resource = queryResource(site, name, request) if resource is None: raise LocationError(site, name) return resource def queryResource(site, name, request, default=None): resource = zope.component.queryAdapter(request, name=name) if resource is None: return default # We need to set the __parent__ and __name__. We need the unproxied # resource to do this. We still return the proxied resource. r = removeSecurityProxy(resource) r.__parent__ = site r.__name__ = name return resource # ---- namespace processors below ---- class SimpleHandler(object): zope.interface.implements(ITraversable) def __init__(self, context, request=None): """Simple handlers can be used as adapters or views They ignore their second constructor arg and store the first one in their context attr: >>> SimpleHandler(42).context 42 >>> SimpleHandler(42, 43).context 42 """ self.context = context class acquire(SimpleHandler): """Traversal adapter for the acquire namespace """ def traverse(self, name, remaining): """Acquire a name Let's set up some example data: >>> class testcontent(object): ... zope.interface.implements(ITraversable) ... def traverse(self, name, remaining): ... v = getattr(self, name, None) ... if v is None: ... raise LocationError(self, name) ... return v ... def __repr__(self): ... return 'splat' >>> ob = testcontent() >>> ob.a = 1 >>> ob.__parent__ = testcontent() >>> ob.__parent__.b = 2 >>> ob.__parent__.__parent__ = testcontent() >>> ob.__parent__.__parent__.c = 3 And acquire some names: >>> adapter = acquire(ob) >>> adapter.traverse('a', ()) 1 >>> adapter.traverse('b', ()) 2 >>> adapter.traverse('c', ()) 3 >>> adapter.traverse('d', ()) Traceback (most recent call last): ... LocationError: (splat, 'd') """ i = 0 ob = self.context while i < 200: i += 1 traversable = ITraversable(ob, None) if traversable is not None: try: # ??? what do we do if the path gets bigger? path = [] next = traversable.traverse(name, path) if path: continue except LocationError: pass else: return next ob = getattr(ob, '__parent__', None) if ob is None: raise LocationError(self.context, name) raise ExcessiveDepth(self.context, name) class attr(SimpleHandler): def traverse(self, name, ignored): """Attribute traversal adapter This adapter just provides traversal to attributes: >>> ob = {'x': 1} >>> adapter = attr(ob) >>> adapter.traverse('keys', ())() ['x'] """ return getattr(self.context, name) class item(SimpleHandler): def traverse(self, name, ignored): """Item traversal adapter This adapter just provides traversal to items: >>> ob = {'x': 42} >>> adapter = item(ob) >>> adapter.traverse('x', ()) 42 """ return self.context[name] class etc(SimpleHandler): def traverse(self, name, ignored): utility = zope.component.queryUtility(IEtcNamespace, name) if utility is not None: return utility ob = self.context if name not in ('site',): raise LocationError(ob, name) method_name = "getSiteManager" method = getattr(ob, method_name, None) if method is None: raise LocationError(ob, name) try: return method() except ComponentLookupError: raise LocationError(ob, name) class view(object): zope.interface.implements(ITraversable) def __init__(self, context, request): self.context = context self.request = request def traverse(self, name, ignored): view = zope.component.queryMultiAdapter((self.context, self.request), name=name) if view is None: raise LocationError(self.context, name) return view class resource(view): def traverse(self, name, ignored): # The context is important here, since it becomes the parent of the # resource, which is needed to generate the absolute URL. return getResource(self.context, name, self.request) class lang(view): def traverse(self, name, ignored): self.request.shiftNameToApplication() languages = IModifiableUserPreferredLanguages(self.request) languages.setPreferredLanguages([name]) return self.context class skin(view): def traverse(self, name, ignored): self.request.shiftNameToApplication() try: skin = zope.component.getUtility(IBrowserSkinType, name) except ComponentLookupError: raise LocationError("++skin++%s" % name) applySkin(self.request, skin) return self.context class vh(view): def traverse(self, name, ignored): request = self.request traversal_stack = request.getTraversalStack() app_names = [] name = name.encode('utf8') if name: try: proto, host, port = name.split(":") except ValueError: raise ValueError("Vhost directive should have the form " "++vh++protocol:host:port") request.setApplicationServer(host, proto, port) if '++' in traversal_stack: segment = traversal_stack.pop() while segment != '++': app_names.append(segment) segment = traversal_stack.pop() request.setTraversalStack(traversal_stack) else: raise ValueError( "Must have a path element '++' after a virtual host " "directive.") request.setVirtualHostRoot(app_names) return self.context class adapter(SimpleHandler): def traverse(self, name, ignored): """Adapter traversal adapter This adapter provides traversal to named adapters registered to provide IPathAdapter. To demonstrate this, we need to register some adapters: >>> def adapter1(ob): ... return 1 >>> def adapter2(ob): ... return 2 >>> zope.component.provideAdapter( ... adapter1, (None,), IPathAdapter, 'a1') >>> zope.component.provideAdapter( ... adapter2, (None,), IPathAdapter, 'a2') Now, with these adapters in place, we can use the traversal adapter: >>> ob = object() >>> adapter = adapter(ob) >>> adapter.traverse('a1', ()) 1 >>> adapter.traverse('a2', ()) 2 >>> try: ... adapter.traverse('bob', ()) ... except LocationError: ... print 'no adapter' no adapter Clean up: >>> from zope.testing.cleanup import cleanUp >>> cleanUp() """ try: return zope.component.getAdapter(self.context, IPathAdapter, name) except ComponentLookupError: raise LocationError(self.context, name) class debug(view): def traverse(self, name, ignored): """Debug traversal adapter This adapter allows debugging flags to be set in the request. See IDebugFlags. Setup for demonstration: >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> ob = object() >>> adapter = debug(ob, request) in debug mode, ++debug++source enables source annotations >>> request.debug.sourceAnnotations False >>> adapter.traverse('source', ()) is ob True >>> request.debug.sourceAnnotations True ++debug++tal enables TAL markup in output >>> request.debug.showTAL False >>> adapter.traverse('tal', ()) is ob True >>> request.debug.showTAL True ++debug++errors enables tracebacks (by switching to debug skin) >>> from zope.publisher.interfaces.browser import IBrowserRequest >>> class Debug(IBrowserRequest): ... pass >>> directlyProvides(Debug, IBrowserSkinType) >>> zope.component.provideUtility( ... Debug, IBrowserSkinType, name='Debug') >>> Debug.providedBy(request) False >>> adapter.traverse('errors', ()) is ob True >>> Debug.providedBy(request) True You can specify several flags separated by commas >>> adapter.traverse('source,tal', ()) is ob True Unknown flag names cause exceptions >>> try: ... adapter.traverse('badflag', ()) ... except ValueError: ... print 'unknown debugging flag' unknown debugging flag """ if __debug__: request = self.request for flag in name.split(','): if flag == 'source': request.debug.sourceAnnotations = True elif flag == 'tal': request.debug.showTAL = True elif flag == 'errors': # TODO: I am not sure this is the best solution. What # if we want to enable tracebacks when also trying to # debug a different skin? skin = zope.component.getUtility(IBrowserSkinType, 'Debug') directlyProvides(request, providedBy(request)+skin) else: raise ValueError("Unknown debug flag: %s" % flag) return self.context else: raise ValueError("Debug flags only allowed in debug mode") if not __debug__: # If not in debug mode, we should get an error: traverse.__doc__ = """Disabled debug traversal adapter This adapter allows debugging flags to be set in the request, but it is disabled because Python was run with -O. Setup for demonstration: >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> ob = object() >>> adapter = debug(ob, request) in debug mode, ++debug++source enables source annotations >>> request.debug.sourceAnnotations False >>> adapter.traverse('source', ()) is ob Traceback (most recent call last): ... ValueError: Debug flags only allowed in debug mode ++debug++tal enables TAL markup in output >>> request.debug.showTAL False >>> adapter.traverse('tal', ()) is ob Traceback (most recent call last): ... ValueError: Debug flags only allowed in debug mode ++debug++errors enables tracebacks (by switching to debug skin) >>> Debug.providedBy(request) False >>> adapter.traverse('errors', ()) is ob Traceback (most recent call last): ... ValueError: Debug flags only allowed in debug mode You can specify several flags separated by commas >>> adapter.traverse('source,tal', ()) is ob Traceback (most recent call last): ... ValueError: Debug flags only allowed in debug mode """ zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/__init__.py0000644000175000017500000000000212214017660024627 0ustar arnauarnau# zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/browser/0000755000175000017500000000000012214017660024211 5ustar arnauarnauzope2.13-2.13.21/source/zope.traversing/src/zope/traversing/browser/configure.zcml0000644000175000017500000000261412214017660027064 0ustar arnauarnau zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/browser/interfaces.py0000644000175000017500000000264012214017660026710 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Browser traversal interfaces """ from zope.interface import Interface class IAbsoluteURL(Interface): def __unicode__(): """Returns the URL as a unicode string.""" def __str__(): """Returns an ASCII string with all unicode characters url quoted.""" def __repr__(): """Get a string representation """ def __call__(): """Returns an ASCII string with all unicode characters url quoted.""" def breadcrumbs(): """Returns a tuple like ({'name':name, 'url':url}, ...) Name is the name to display for that segment of the breadcrumbs. URL is the link for that segment of the breadcrumbs. """ class IAbsoluteURLAPI(Interface): def absoluteURL(ob, request): """Compute the absolute URL of an object """ zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/browser/__init__.py0000644000175000017500000000153512214017660026326 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Absolute URL View components """ from zope.traversing.browser.absoluteurl import absoluteURL from zope.traversing.browser.absoluteurl import AbsoluteURL from zope.traversing.browser.absoluteurl import SiteAbsoluteURL zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/browser/tests.py0000644000175000017500000002761712214017660025742 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test the AbsoluteURL view """ from unittest import TestCase, main, makeSuite import zope.component from zope.component import getMultiAdapter, adapts from zope.component.testing import PlacelessSetup from zope.traversing.browser.absoluteurl import absoluteURL from zope.traversing.browser.interfaces import IAbsoluteURL from zope.traversing.testing import browserView from zope.i18n.interfaces import IUserPreferredCharsets from zope.interface import Interface, implements from zope.interface.verify import verifyObject from zope.publisher.browser import TestRequest from zope.publisher.http import IHTTPRequest, HTTPCharsets from zope.location.interfaces import ILocation from zope.location.location import LocationProxy from zope.container.contained import contained class IRoot(Interface): pass class Root(object): implements(IRoot) class TrivialContent(object): """Trivial content object, used because instances of object are rocks.""" class AdaptedContent(object): """A simple content object that has an ILocation adapter for it.""" class FooContent(object): """Class whose location will be provided by an adapter.""" class FooLocation(object): """Adapts FooAdapter to the ILocation protocol.""" implements(ILocation) adapts(FooContent) def __init__(self, context): self.context = context @property def __name__(self): return 'foo' @property def __parent__(self): return contained(TrivialContent(), Root(), name='bar') class TestAbsoluteURL(PlacelessSetup, TestCase): def setUp(self): PlacelessSetup.setUp(self) from zope.traversing.browser import AbsoluteURL, SiteAbsoluteURL browserView(None, 'absolute_url', AbsoluteURL) browserView(IRoot, 'absolute_url', SiteAbsoluteURL) browserView(None, '', AbsoluteURL, providing=IAbsoluteURL) browserView(IRoot, '', SiteAbsoluteURL, providing=IAbsoluteURL) zope.component.provideAdapter(FooLocation) zope.component.provideAdapter(HTTPCharsets, (IHTTPRequest,), IUserPreferredCharsets) # LocationProxy as set by zope.location # this makes a default LocationProxy for all objects that # don't define a more specific adapter zope.component.provideAdapter(LocationProxy, (Interface,), ILocation) def tearDown(self): PlacelessSetup.tearDown(self) def test_interface(self): request = TestRequest() content = contained(TrivialContent(), Root(), name='a') view = getMultiAdapter((content, request), name='absolute_url') verifyObject(IAbsoluteURL, view) def testBadObject(self): request = TestRequest() view = getMultiAdapter((42, request), name='absolute_url') self.assertRaises(TypeError, view.__str__) self.assertRaises(TypeError, absoluteURL, 42, request) def testNoContext(self): request = TestRequest() view = getMultiAdapter((Root(), request), name='absolute_url') self.assertEqual(str(view), 'http://127.0.0.1') self.assertEqual(absoluteURL(Root(), request), 'http://127.0.0.1') def testBasicContext(self): request = TestRequest() content = contained(TrivialContent(), Root(), name='a') content = contained(TrivialContent(), content, name='b') content = contained(TrivialContent(), content, name='c') view = getMultiAdapter((content, request), name='absolute_url') self.assertEqual(str(view), 'http://127.0.0.1/a/b/c') self.assertEqual(absoluteURL(content, request), 'http://127.0.0.1/a/b/c') breadcrumbs = view.breadcrumbs() self.assertEqual(breadcrumbs, ({'name': '', 'url': 'http://127.0.0.1'}, {'name': 'a', 'url': 'http://127.0.0.1/a'}, {'name': 'b', 'url': 'http://127.0.0.1/a/b'}, {'name': 'c', 'url': 'http://127.0.0.1/a/b/c'}, )) def testParentButNoLocation(self): request = TestRequest() content1 = TrivialContent() content1.__parent__ = Root() content1.__name__ = 'a' content2 = TrivialContent() content2.__parent__ = content1 content2.__name__ = 'b' content3 = TrivialContent() content3.__parent__ = content2 content3.__name__ = 'c' view = getMultiAdapter((content3, request), name='absolute_url') self.assertEqual(str(view), 'http://127.0.0.1/a/b/c') self.assertEqual(absoluteURL(content3, request), 'http://127.0.0.1/a/b/c') def testAdaptedContext(self): request = TestRequest() content = FooContent() view = getMultiAdapter((content, request), name='absolute_url') self.assertEqual(str(view), 'http://127.0.0.1/bar/foo') self.assertEqual(absoluteURL(content, request), 'http://127.0.0.1/bar/foo') breadcrumbs = view.breadcrumbs() self.assertEqual(breadcrumbs, ({'name': '', 'url': 'http://127.0.0.1'}, {'name': 'bar', 'url': 'http://127.0.0.1/bar'}, {'name': 'foo', 'url': 'http://127.0.0.1/bar/foo'}, )) def testParentTrumpsAdapter(self): # if we have a location adapter for a content object but # the object also has its own __parent__, this will trump the # adapter request = TestRequest() content = FooContent() content.__parent__ = Root() content.__name__ = 'foo' view = getMultiAdapter((content, request), name='absolute_url') self.assertEqual(str(view), 'http://127.0.0.1/foo') self.assertEqual(absoluteURL(content, request), 'http://127.0.0.1/foo') def testBasicContext_unicode(self): #Tests so that AbsoluteURL handle unicode names as well request = TestRequest() root = Root() root.__name__ = u'\u0439' content = contained(TrivialContent(), root, name=u'\u0442') content = contained(TrivialContent(), content, name=u'\u0435') content = contained(TrivialContent(), content, name=u'\u0441') view = getMultiAdapter((content, request), name='absolute_url') self.assertEqual(str(view), 'http://127.0.0.1/%D0%B9/%D1%82/%D0%B5/%D1%81') self.assertEqual(view(), 'http://127.0.0.1/%D0%B9/%D1%82/%D0%B5/%D1%81') self.assertEqual(unicode(view), u'http://127.0.0.1/\u0439/\u0442/\u0435/\u0441') self.assertEqual(absoluteURL(content, request), 'http://127.0.0.1/%D0%B9/%D1%82/%D0%B5/%D1%81') breadcrumbs = view.breadcrumbs() self.assertEqual(breadcrumbs, ({'name': '', 'url': 'http://127.0.0.1'}, {'name': u'\u0439', 'url': 'http://127.0.0.1/%D0%B9'}, {'name': u'\u0442', 'url': 'http://127.0.0.1/%D0%B9/%D1%82'}, {'name': u'\u0435', 'url': 'http://127.0.0.1/%D0%B9/%D1%82/%D0%B5'}, {'name': u'\u0441', 'url': 'http://127.0.0.1/%D0%B9/%D1%82/%D0%B5/%D1%81'}, )) def testRetainSkin(self): request = TestRequest() request._traversed_names = ('a', 'b') request._app_names = ('++skin++test', ) content = contained(TrivialContent(), Root(), name='a') content = contained(TrivialContent(), content, name='b') content = contained(TrivialContent(), content, name='c') view = getMultiAdapter((content, request), name='absolute_url') base = 'http://127.0.0.1/++skin++test' self.assertEqual(str(view), base + '/a/b/c') breadcrumbs = view.breadcrumbs() self.assertEqual(breadcrumbs, ({'name': '', 'url': base + ''}, {'name': 'a', 'url': base + '/a'}, {'name': 'b', 'url': base + '/a/b'}, {'name': 'c', 'url': base + '/a/b/c'}, )) def testVirtualHosting(self): request = TestRequest() vh_root = TrivialContent() content = contained(vh_root, Root(), name='a') request._vh_root = content content = contained(TrivialContent(), content, name='b') content = contained(TrivialContent(), content, name='c') view = getMultiAdapter((content, request), name='absolute_url') self.assertEqual(str(view), 'http://127.0.0.1/b/c') breadcrumbs = view.breadcrumbs() self.assertEqual(breadcrumbs, ({'name': '', 'url': 'http://127.0.0.1'}, {'name': 'b', 'url': 'http://127.0.0.1/b'}, {'name': 'c', 'url': 'http://127.0.0.1/b/c'}, )) def testVirtualHostingWithVHElements(self): request = TestRequest() vh_root = TrivialContent() content = contained(vh_root, Root(), name='a') request._vh_root = content content = contained(TrivialContent(), content, name='b') content = contained(TrivialContent(), content, name='c') view = getMultiAdapter((content, request), name='absolute_url') self.assertEqual(str(view), 'http://127.0.0.1/b/c') breadcrumbs = view.breadcrumbs() self.assertEqual(breadcrumbs, ({'name': '', 'url': 'http://127.0.0.1'}, {'name': 'b', 'url': 'http://127.0.0.1/b'}, {'name': 'c', 'url': 'http://127.0.0.1/b/c'}, )) def testVirtualHostingInFront(self): request = TestRequest() root = Root() request._vh_root = contained(root, root, name='') content = contained(root, None) content = contained(TrivialContent(), content, name='a') content = contained(TrivialContent(), content, name='b') content = contained(TrivialContent(), content, name='c') view = getMultiAdapter((content, request), name='absolute_url') self.assertEqual(str(view), 'http://127.0.0.1/a/b/c') breadcrumbs = view.breadcrumbs() self.assertEqual(breadcrumbs, ({'name': '', 'url': 'http://127.0.0.1'}, {'name': 'a', 'url': 'http://127.0.0.1/a'}, {'name': 'b', 'url': 'http://127.0.0.1/a/b'}, {'name': 'c', 'url': 'http://127.0.0.1/a/b/c'}, )) def testNoContextInformation(self): request = TestRequest() view = getMultiAdapter((None, request), name='absolute_url') self.assertEqual(str(view), 'http://127.0.0.1') self.assertEqual(absoluteURL(None, request), 'http://127.0.0.1') def testVirtualHostingWithoutContextInformation(self): request = TestRequest() request._vh_root = contained(TrivialContent(), Root(), name='a') view = getMultiAdapter((None, request), name='absolute_url') self.assertEqual(str(view), 'http://127.0.0.1') self.assertEqual(absoluteURL(None, request), 'http://127.0.0.1') def test_suite(): return makeSuite(TestAbsoluteURL) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/browser/absoluteurl.py0000644000175000017500000001261012214017660027124 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Absolute URL View components """ import urllib import zope.component from zope.interface import implements from zope.location.interfaces import ILocation from zope.proxy import sameProxiedObjects from zope.publisher.browser import BrowserView from zope.traversing.browser.interfaces import IAbsoluteURL from zope.i18nmessageid import MessageFactory _ = MessageFactory('zope') _insufficientContext = _("There isn't enough context to get URL information. " "This is probably due to a bug in setting up location " "information.") _safe = '@+' # Characters that we don't want to have quoted def absoluteURL(ob, request): return zope.component.getMultiAdapter((ob, request), IAbsoluteURL)() class AbsoluteURL(BrowserView): implements(IAbsoluteURL) def __unicode__(self): return urllib.unquote(self.__str__()).decode('utf-8') def __str__(self): context = self.context request = self.request # The application URL contains all the namespaces that are at the # beginning of the URL, such as skins, virtual host specifications and # so on. if (context is None or sameProxiedObjects(context, request.getVirtualHostRoot())): return request.getApplicationURL() # first try to get the __parent__ of the object, no matter whether # it provides ILocation or not. If this fails, look up an ILocation # adapter. This will always work, as a general ILocation adapter # is registered for interface in zope.location (a LocationProxy) # This proxy will return a parent of None, causing this to fail # More specific ILocation adapters can be provided however. try: container = context.__parent__ except AttributeError: # we need to assign to context here so we can get # __name__ from it below context = ILocation(context) container = context.__parent__ if container is None: raise TypeError(_insufficientContext) url = str(zope.component.getMultiAdapter((container, request), IAbsoluteURL)) name = getattr(context, '__name__', None) if name is None: raise TypeError(_insufficientContext) if name: url += '/' + urllib.quote(name.encode('utf-8'), _safe) return url def __call__(self): return self.__str__() def breadcrumbs(self): context = self.context request = self.request # We do this here do maintain the rule that we must be wrapped context = ILocation(context, context) container = getattr(context, '__parent__', None) if container is None: raise TypeError(_insufficientContext) if sameProxiedObjects(context, request.getVirtualHostRoot()) or \ isinstance(context, Exception): return ({'name':'', 'url': self.request.getApplicationURL()}, ) base = tuple(zope.component.getMultiAdapter( (container, request), IAbsoluteURL).breadcrumbs()) name = getattr(context, '__name__', None) if name is None: raise TypeError(_insufficientContext) if name: base += ({'name': name, 'url': ("%s/%s" % (base[-1]['url'], urllib.quote(name.encode('utf-8'), _safe))) }, ) return base class SiteAbsoluteURL(BrowserView): implements(IAbsoluteURL) def __unicode__(self): return urllib.unquote(self.__str__()).decode('utf-8') def __str__(self): context = self.context request = self.request if sameProxiedObjects(context, request.getVirtualHostRoot()): return request.getApplicationURL() url = request.getApplicationURL() name = getattr(context, '__name__', None) if name: url += '/' + urllib.quote(name.encode('utf-8'), _safe) return url def __call__(self): return self.__str__() def breadcrumbs(self): context = self.context request = self.request if sameProxiedObjects(context, request.getVirtualHostRoot()): return ({'name':'', 'url': self.request.getApplicationURL()}, ) base = ({'name':'', 'url': self.request.getApplicationURL()}, ) name = getattr(context, '__name__', None) if name: base += ({'name': name, 'url': ("%s/%s" % (base[-1]['url'], urllib.quote(name.encode('utf-8'), _safe))) }, ) return base zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/api.py0000644000175000017500000001636312214017660023662 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Convenience functions for traversing the object tree. """ from zope.interface import moduleProvides from zope.location.interfaces import ILocationInfo, IRoot, LocationError from zope.traversing.interfaces import ITraversalAPI, ITraverser moduleProvides(ITraversalAPI) __all__ = tuple(ITraversalAPI) _marker = object() def joinPath(path, *args): """Join the given relative paths to the given path. Returns a unicode path. The path should be well-formed, and not end in a '/' unless it is the root path. It can be either a string (ascii only) or unicode. The positional arguments are relative paths to be added to the path as new path segments. The path may be absolute or relative. A segment may not start with a '/' because that would be confused with an absolute path. A segment may not end with a '/' because we do not allow '/' at the end of relative paths. A segment may consist of . or .. to mean "the same place", or "the parent path" respectively. A '.' should be removed and a '..' should cause the segment to the left to be removed. joinPath('/', '..') should raise an exception. """ if not args: # Concatenating u'' is much quicker than unicode(path) return u'' + path if path != '/' and path.endswith('/'): raise ValueError('path must not end with a "/": %s' % path) if path != '/': path += u'/' for arg in args: if arg.startswith('/') or arg.endswith('/'): raise ValueError("Leading or trailing slashes in path elements") return _normalizePath(path + u'/'.join(args)) def getPath(obj): """Returns a string representing the physical path to the object. """ return ILocationInfo(obj).getPath() def getRoot(obj): """Returns the root of the traversal for the given object. """ return ILocationInfo(obj).getRoot() def traverse(object, path, default=_marker, request=None): """Traverse 'path' relative to the given object. 'path' is a string with path segments separated by '/'. 'request' is passed in when traversing from presentation code. This allows paths like @@foo to work. Raises LocationError if path cannot be found Note: calling traverse with a path argument taken from an untrusted source, such as an HTTP request form variable, is a bad idea. It could allow a maliciously constructed request to call code unexpectedly. Consider using traverseName instead. """ traverser = ITraverser(object) if default is _marker: return traverser.traverse(path, request=request) else: return traverser.traverse(path, default=default, request=request) def traverseName(obj, name, default=_marker, traversable=None, request=None): """Traverse a single step 'name' relative to the given object. 'name' must be a string. '.' and '..' are treated specially, as well as names starting with '@' or '+'. Otherwise 'name' will be treated as a single path segment. You can explicitly pass in an ITraversable as the 'traversable' argument. If you do not, the given object will be adapted to ITraversable. 'request' is passed in when traversing from presentation code. This allows paths like @@foo to work. Raises LocationError if path cannot be found and 'default' was not provided. """ further_path = [] if default is _marker: obj = traversePathElement(obj, name, further_path, traversable=traversable, request=request) else: obj = traversePathElement(obj, name, further_path, default=default, traversable=traversable, request=request) if further_path: raise NotImplementedError('further_path returned from traverse') else: return obj def getName(obj): """Get the name an object was traversed via """ return ILocationInfo(obj).getName() def getParent(obj): """Returns the container the object was traversed via. Returns None if the object is a containment root. Raises TypeError if the object doesn't have enough context to get the parent. """ try: location_info = ILocationInfo(obj) except TypeError: pass else: return location_info.getParent() # XXX Keep the old implementation as the fallback behaviour in the case # that obj doesn't have a location parent. This seems advisable as the # 'parent' is sometimes taken to mean the traversal parent, and the # __parent__ attribute is used for both. if IRoot.providedBy(obj): return None parent = getattr(obj, '__parent__', None) if parent is not None: return parent raise TypeError("Not enough context information to get parent", obj) def getParents(obj): """Returns a list starting with the given object's parent followed by each of its parents. Raises a TypeError if the context doesn't go all the way down to a containment root. """ return ILocationInfo(obj).getParents() def _normalizePath(path): """Normalize a path by resolving '.' and '..' path elements.""" # Special case for the root path. if path == u'/': return path new_segments = [] prefix = u'' if path.startswith('/'): prefix = u'/' path = path[1:] for segment in path.split(u'/'): if segment == u'.': continue if segment == u'..': new_segments.pop() # raises IndexError if there is nothing to pop continue if not segment: raise ValueError('path must not contain empty segments: %s' % path) new_segments.append(segment) return prefix + u'/'.join(new_segments) def canonicalPath(path_or_object): """Returns a canonical absolute unicode path for the given path or object. Resolves segments that are '.' or '..'. Raises ValueError if a badly formed path is given. """ if isinstance(path_or_object, (str, unicode)): path = path_or_object if not path: raise ValueError("path must be non-empty: %s" % path) else: path = getPath(path_or_object) path = u'' + path # Special case for the root path. if path == u'/': return path if path[0] != u'/': raise ValueError('canonical path must start with a "/": %s' % path) if path[-1] == u'/': raise ValueError('path must not end with a "/": %s' % path) # Break path into segments. Process '.' and '..' segments. return _normalizePath(path) # import this down here to avoid circular imports from zope.traversing.adapters import traversePathElement zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/0000755000175000017500000000000012214017660023670 5ustar arnauarnauzope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/ftesting.zcml0000644000175000017500000000305012214017660026400 0ustar arnauarnau zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/test_physicallocationadapters.py0000644000175000017500000000352712214017660032401 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Physical Location Adapter Tests """ from unittest import TestCase, main, makeSuite from zope.container.contained import contained from zope.interface import implements from zope.location.interfaces import ILocationInfo, IRoot from zope.site.site import LocalSiteManager from zope.site.site import SiteManagerContainer from zope.testing.cleanup import CleanUp import zope.traversing.testing class Root(object): implements(IRoot) __parent__ = None class C(object): pass class Test(CleanUp, TestCase): def test(self): zope.traversing.testing.setUp() root = Root() f1 = contained(C(), root, name='f1') f2 = contained(SiteManagerContainer(), f1, name='f2') f3 = contained(C(), f2, name='f3') adapter = ILocationInfo(f3) self.assertEqual(adapter.getPath(), '/f1/f2/f3') self.assertEqual(adapter.getName(), 'f3') self.assertEqual(adapter.getRoot(), root) self.assertEqual(adapter.getNearestSite(), root) f2.setSiteManager(LocalSiteManager(f2)) self.assertEqual(adapter.getNearestSite(), f2) def test_suite(): return makeSuite(Test) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/test_lang.py0000644000175000017500000000460412214017660026226 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Test lang traversal. """ import unittest import zope.component from zope.annotation import IAttributeAnnotatable, IAnnotations from zope.annotation.attribute import AttributeAnnotations from zope.interface import directlyProvides from zope.publisher.browser import ModifiableBrowserLanguages from zope.publisher.interfaces.http import IHTTPRequest from zope.publisher.tests import test_browserlanguages from zope.i18n.interfaces import IModifiableUserPreferredLanguages from zope.traversing.namespace import lang from zope.testing.cleanup import CleanUp class TestRequest(test_browserlanguages.TestRequest): def shiftNameToApplication(self): self.shifted = True class Test(CleanUp, unittest.TestCase): def setUp(self): super(Test, self).setUp() self.request = TestRequest("en") directlyProvides(self.request, IHTTPRequest, IAttributeAnnotatable) zope.component.provideAdapter(AttributeAnnotations, (IAttributeAnnotatable,), IAnnotations) zope.component.provideAdapter(ModifiableBrowserLanguages, (IHTTPRequest,), IModifiableUserPreferredLanguages) def test_adapter(self): request = self.request browser_languages = IModifiableUserPreferredLanguages(request) self.failUnlessEqual(["en"], browser_languages.getPreferredLanguages()) ob = object() ob2 = lang(ob, request).traverse('ru', ()) self.failUnless(ob is ob2) self.failUnless(request.shifted) self.failUnlessEqual(["ru"], browser_languages.getPreferredLanguages()) def test_suite(): return unittest.makeSuite(Test) if __name__=='__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/test_publicationtraverse.py0000644000175000017500000001524712214017660031377 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests of PublicationTraverser """ from unittest import TestCase, main, makeSuite from zope.testing.cleanup import CleanUp from zope.component import provideAdapter from zope.interface import Interface, implements from zope.publisher.browser import TestRequest from zope.publisher.interfaces import IPublishTraverse from zope.publisher.interfaces import NotFound from zope.publisher.interfaces.browser import IBrowserPublisher from zope.security.proxy import removeSecurityProxy from zope.traversing.interfaces import ITraversable class TestPublicationTraverser(CleanUp, TestCase): def testViewNotFound(self): ob = Content() from zope.traversing.publicationtraverse import PublicationTraverser t = PublicationTraverser() request = TestRequest() self.assertRaises(NotFound, t.traverseName, request, ob, '@@foo') def testViewFound(self): provideAdapter(DummyViewTraverser, (Interface, Interface), ITraversable, name='view') ob = Content() from zope.traversing.publicationtraverse import PublicationTraverser t = PublicationTraverser() request = TestRequest() proxy = t.traverseName(request, ob, '@@foo') view = removeSecurityProxy(proxy) self.assertTrue(proxy is not view) self.assertEqual(view.__class__, View) self.assertEqual(view.name, 'foo') def testDot(self): ob = Content() from zope.traversing.publicationtraverse import PublicationTraverser t = PublicationTraverser() request = TestRequest() self.assertEqual(ob, t.traverseName(request, ob, '.')) def testNameNotFound(self): ob = Content() from zope.traversing.publicationtraverse import PublicationTraverser t = PublicationTraverser() request = TestRequest() self.assertRaises(NotFound, t.traverseName, request, ob, 'foo') def testNameFound(self): provideAdapter(DummyPublishTraverse, (Interface, Interface), IPublishTraverse) ob = Content() from zope.traversing.publicationtraverse import PublicationTraverser t = PublicationTraverser() request = TestRequest() proxy = t.traverseName(request, ob, 'foo') view = removeSecurityProxy(proxy) self.assertTrue(proxy is not view) self.assertEqual(view.__class__, View) self.assertEqual(view.name, 'foo') def testDirectTraversal(self): request = TestRequest() ob = DummyPublishTraverse(Content(), request) from zope.traversing.publicationtraverse import PublicationTraverser t = PublicationTraverser() proxy = t.traverseName(request, ob, 'foo') view = removeSecurityProxy(proxy) self.assertTrue(proxy is not view) self.assertEqual(view.__class__, View) self.assertEqual(view.name, 'foo') def testPathNotFound(self): ob = Content() from zope.traversing.publicationtraverse import PublicationTraverser t = PublicationTraverser() request = TestRequest() self.assertRaises(NotFound, t.traversePath, request, ob, 'foo/bar') def testPathFound(self): provideAdapter(DummyPublishTraverse, (Interface, Interface), IPublishTraverse) ob = Content() from zope.traversing.publicationtraverse import PublicationTraverser t = PublicationTraverser() request = TestRequest() proxy = t.traversePath(request, ob, 'foo/bar') view = removeSecurityProxy(proxy) self.assertTrue(proxy is not view) self.assertEqual(view.__class__, View) self.assertEqual(view.name, 'bar') def testComplexPath(self): provideAdapter(DummyPublishTraverse, (Interface, Interface), IPublishTraverse) ob = Content() from zope.traversing.publicationtraverse import PublicationTraverser t = PublicationTraverser() request = TestRequest() proxy = t.traversePath(request, ob, 'foo/../alpha//beta/./bar') view = removeSecurityProxy(proxy) self.assertTrue(proxy is not view) self.assertEqual(view.__class__, View) self.assertEqual(view.name, 'bar') def testTraverseRelativeURL(self): provideAdapter(DummyPublishTraverse, (Interface, Interface), IPublishTraverse) provideAdapter(DummyBrowserPublisher, (Interface,), IBrowserPublisher) ob = Content() from zope.traversing.publicationtraverse import PublicationTraverser t = PublicationTraverser() request = TestRequest() proxy = t.traverseRelativeURL(request, ob, 'foo/bar') view = removeSecurityProxy(proxy) self.assertTrue(proxy is not view) self.assertEqual(view.__class__, View) self.assertEqual(view.name, 'more') def testMissingSkin(self): ob = Content() from zope.traversing.publicationtraverse import PublicationTraverser t = PublicationTraverser() request = TestRequest() self.assertRaises( NotFound, t.traversePath, request, ob, '/++skin++missingskin') class IContent(Interface): pass class Content(object): implements(IContent) class View(object): def __init__(self, name): self.name = name class DummyViewTraverser(object): implements(ITraversable) def __init__(self, content, request): self.content = content def traverse(self, name, furtherPath): return View(name) class DummyPublishTraverse(object): implements(IPublishTraverse) def __init__(self, context, request): pass def publishTraverse(self, request, name): return View(name) class DummyBrowserPublisher(object): implements(IBrowserPublisher) def __init__(self, context): self.context = removeSecurityProxy(context) def browserDefault(self, request): if self.context.name != 'more': return self.context, ['more'] else: return self.context, () def test_suite(): return makeSuite(TestPublicationTraverser) if __name__ == '__main__': main() zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/test_dependencies.py0000644000175000017500000000164112214017660027731 0ustar arnauarnauimport unittest import zope.component from zope.configuration.xmlconfig import XMLConfig from zope.publisher.browser import TestRequest from zope.traversing.interfaces import ITraversable class ZCMLDependencies(unittest.TestCase): def test_zcml_can_load_with_only_zope_component_meta(self): import zope.component XMLConfig('meta.zcml', zope.component)() import zope.traversing XMLConfig('configure.zcml', zope.traversing)() request = TestRequest() res = zope.component.getMultiAdapter( (self, request), ITraversable, 'lang') import zope.traversing.namespace self.failUnless(isinstance(res, zope.traversing.namespace.lang)) self.failUnless(res.context is self) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(ZCMLDependencies)) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/test_traverser.py0000644000175000017500000002152512214017660027323 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Traverser Adapter tests. """ import unittest import zope.component from zope.component.testing import PlacelessSetup from zope.interface import directlyProvides, implementedBy from zope.interface.verify import verifyClass from zope.location.traversing \ import LocationPhysicallyLocatable, RootPhysicallyLocatable from zope.location.interfaces \ import ILocationInfo, IRoot, LocationError from zope.security.interfaces import Unauthorized from zope.security.checker \ import ProxyFactory, defineChecker, CheckerPublic, Checker from zope.security.management import newInteraction, endInteraction from zope.traversing.adapters import Traverser, DefaultTraversable from zope.traversing.interfaces import ITraversable, ITraverser from zope.container.contained import Contained, contained class ParticipationStub(object): def __init__(self, principal): self.principal = principal self.interaction = None class C(Contained): def __init__(self, name): self.name = name class TraverserTests(PlacelessSetup, unittest.TestCase): def setUp(self): PlacelessSetup.setUp(self) # Build up a wrapper chain self.root = C('root') self.folder = contained(C('folder'), self.root, name='folder') self.item = contained(C('item'), self.folder, name='item') self.tr = Traverser(self.item) def testImplementsITraverser(self): self.failUnless(ITraverser.providedBy(self.tr)) def testVerifyInterfaces(self): for interface in implementedBy(Traverser): verifyClass(interface, Traverser) class UnrestrictedNoTraverseTests(unittest.TestCase): def setUp(self): self.root = root = C('root') directlyProvides(self.root, IRoot) self.folder = folder = C('folder') self.item = item = C('item') root.folder = folder folder.item = item self.tr = Traverser(root) def testNoTraversable(self): self.assertRaises(LocationError, self.tr.traverse, 'folder') class UnrestrictedTraverseTests(PlacelessSetup, unittest.TestCase): def setUp(self): PlacelessSetup.setUp(self) zope.component.provideAdapter(DefaultTraversable, (None,), ITraversable) zope.component.provideAdapter(LocationPhysicallyLocatable, (None,), ILocationInfo) zope.component.provideAdapter(RootPhysicallyLocatable, (IRoot,), ILocationInfo) # Build up a wrapper chain self.root = root = C('root') directlyProvides(self.root, IRoot) self.folder = folder = contained(C('folder'), root, 'folder') self.item = item = contained(C('item'), folder, 'item') root.folder = folder folder.item = item self.tr = Traverser(root) def testSimplePathString(self): tr = self.tr item = self.item self.assertEquals(tr.traverse('/folder/item'), item) self.assertEquals(tr.traverse('folder/item'), item) self.assertEquals(tr.traverse('/folder/item/'), item) def testSimplePathUnicode(self): tr = self.tr item = self.item self.assertEquals(tr.traverse(u'/folder/item'), item) self.assertEquals(tr.traverse(u'folder/item'), item) self.assertEquals(tr.traverse(u'/folder/item/'), item) def testSimplePathTuple(self): tr = self.tr item = self.item self.assertEquals(tr.traverse(('', 'folder', 'item')), item) self.assertEquals(tr.traverse(('folder', 'item')), item) def testComplexPathString(self): tr = self.tr item = self.item self.assertEquals(tr.traverse('/folder/../folder/./item'), item) def testNotFoundDefault(self): self.assertEquals(self.tr.traverse('foo', 'notFound'), 'notFound') def testNotFoundNoDefault(self): self.assertRaises(LocationError, self.tr.traverse, 'foo') def testTraverseOldStyleClass(self): class AnOldStyleClass: x = object() container = {} container['theclass'] = AnOldStyleClass tr = Traverser(container) self.assert_(tr.traverse('theclass/x') is AnOldStyleClass.x) def testTraversingDictSeesDictAPI(self): adict = { 'foo': 'bar', 'anotherdict': {'bar': 'foo'}, 'items': '123', } tr = Traverser(adict) self.assertEqual(tr.traverse('items'), adict.items) self.assertEqual(tr.traverse('anotherdict/bar'), 'foo') self.assertEqual(tr.traverse('anotherdict/items'), adict['anotherdict'].items) def testTraversingDoesntFailOnStrings(self): adict = {'foo': 'bar'} tr = Traverser(adict) # This used to raise type error before self.assertRaises(LocationError, tr.traverse, 'foo/baz') class RestrictedTraverseTests(PlacelessSetup, unittest.TestCase): _oldPolicy = None _deniedNames = () def setUp(self): PlacelessSetup.setUp(self) zope.component.provideAdapter(DefaultTraversable, (None,), ITraversable) zope.component.provideAdapter(LocationPhysicallyLocatable, (None,), ILocationInfo) zope.component.provideAdapter(RootPhysicallyLocatable, (IRoot,), ILocationInfo) self.root = root = C('root') directlyProvides(root, IRoot) self.folder = folder = contained(C('folder'), root, 'folder') self.item = item = contained(C('item'), folder, 'item') root.folder = folder folder.item = item self.tr = Traverser(ProxyFactory(root)) def testAllAllowed(self): defineChecker(C, Checker({'folder': CheckerPublic, 'item': CheckerPublic, })) tr = Traverser(ProxyFactory(self.root)) item = self.item self.assertEquals(tr.traverse(('', 'folder', 'item')), item) self.assertEquals(tr.traverse(('folder', 'item')), item) def testItemDenied(self): endInteraction() newInteraction(ParticipationStub('no one')) defineChecker(C, Checker({'item': 'Waaaa', 'folder': CheckerPublic})) tr = Traverser(ProxyFactory(self.root)) folder = self.folder self.assertRaises(Unauthorized, tr.traverse, ('', 'folder', 'item')) self.assertRaises(Unauthorized, tr.traverse, ('folder', 'item')) self.assertEquals(tr.traverse(('', 'folder')), folder) self.assertEquals(tr.traverse(('folder', '..', 'folder')), folder) self.assertEquals(tr.traverse(('folder',)), folder) class DefaultTraversableTests(unittest.TestCase): def testImplementsITraversable(self): self.failUnless(ITraversable.providedBy(DefaultTraversable(None))) def testVerifyInterfaces(self): for interface in implementedBy(DefaultTraversable): verifyClass(interface, DefaultTraversable) def testAttributeTraverse(self): root = C('root') item = C('item') root.item = item df = DefaultTraversable(root) further = [] next = df.traverse('item', further) self.failUnless(next is item) self.assertEquals(further, []) def testDictionaryTraverse(self): dict = {} foo = C('foo') dict['foo'] = foo df = DefaultTraversable(dict) further = [] next = df.traverse('foo', further) self.failUnless(next is foo) self.assertEquals(further, []) def testNotFound(self): df = DefaultTraversable(C('dummy')) self.assertRaises(LocationError, df.traverse, 'bar', []) def test_suite(): loader = unittest.TestLoader() suite = loader.loadTestsFromTestCase(TraverserTests) suite.addTest(loader.loadTestsFromTestCase(DefaultTraversableTests)) suite.addTest(loader.loadTestsFromTestCase(UnrestrictedNoTraverseTests)) suite.addTest(loader.loadTestsFromTestCase(UnrestrictedTraverseTests)) suite.addTest(loader.loadTestsFromTestCase(RestrictedTraverseTests)) return suite if __name__=='__main__': unittest.TextTestRunner().run(test_suite()) zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/__init__.py0000644000175000017500000000007512214017660026003 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/test_vh.py0000644000175000017500000000610512214017660025720 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Virtual hosting namespace tests. """ import unittest class TestRequest(object): def __init__(self, names=None, stack=None): self._traversal_stack = stack self._traversed_names = names self._app_server = 'http://server' self._app_url = '' def getTraversalStack(self): return list(self._traversal_stack) def setTraversalStack(self, stack): self._traversal_stack[:] = list(stack) def setApplicationServer(self, host, proto='http', port=None): host = "%s://%s" % (proto, host) if port: host = "%s:%s" % (host, port) self._app_server = host def setVirtualHostRoot(self, names=None): del self._traversed_names[:] self._app_names = names or [] class TestVHNamespace(unittest.TestCase): def test_vh(self): from zope.traversing.namespace import vh # GET /folder1/++vh++/x/y/z/++/folder1_1 request = TestRequest(['folder1'], ['folder1_1', '++', 'z', 'y', 'x']) ob = object() result = vh(ob, request).traverse('', ()) self.assertEqual(result, ob) self.assertEqual(request._traversal_stack, ['folder1_1']) self.assertEqual(request._traversed_names, []) self.assertEqual(request._app_names, ['x', 'y', 'z']) self.assertEqual(request._app_server, 'http://server') def test_vh_noPlusPlus(self): from zope.traversing.namespace import vh # GET /folder1/folder2/++vh++http:host:80/folder1_1 request = TestRequest(['folder1', 'folder2'], ['folder1_1']) ob = object() handler = vh(ob, request) self.assertRaises(ValueError, handler.traverse, 'http:host:80', ()) def test_vh_host(self): from zope.traversing.namespace import vh request = TestRequest(['folder1'], ['folder1_1', '++']) ob = object() result = vh(ob, request).traverse('http:www.fubarco.com:80', ()) self.assertEqual(request._app_server, 'http://www.fubarco.com:80') def test_unicode_vh_host(self): from zope.traversing.namespace import vh request = TestRequest(['folder1'], ['folder1_1', '++']) ob = object() result = vh(ob, request).traverse(u'http:www.fubarco.com:80', ()) assert(isinstance(request._app_server, str)) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestVHNamespace)) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/test_presentation.py0000644000175000017500000000334712214017660030023 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Presentation Traverser Tests """ from unittest import TestCase, main, makeSuite from zope.testing.cleanup import CleanUp from zope.interface import Interface, implements from zope.publisher.browser import TestRequest from zope.traversing.namespace import view, resource from zope.traversing.testing import browserView, browserResource class IContent(Interface): pass class Content(object): implements(IContent) class Resource(object): def __init__(self, request): pass class View(object): def __init__(self, content, request): self.content = content class Test(CleanUp, TestCase): def testView(self): browserView(IContent, 'foo', View) ob = Content() v = view(ob, TestRequest()).traverse('foo', ()) self.assertEqual(v.__class__, View) def testResource(self): browserResource('foo', Resource) ob = Content() r = resource(ob, TestRequest()).traverse('foo', ()) self.assertEqual(r.__class__, Resource) def test_suite(): return makeSuite(Test) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/test_namespacetrversal.py0000644000175000017500000000171712214017660031026 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Traversal Namespace Tests """ from unittest import main from doctest import DocTestSuite from zope.component.testing import setUp, tearDown def test_suite(): return DocTestSuite('zope.traversing.namespace', setUp=setUp, tearDown=tearDown) if __name__ == '__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/test_conveniencefunctions.py0000644000175000017500000002525312214017660031535 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test traversal convenience functions. """ from unittest import TestCase, main, makeSuite import zope.component from zope.component.testing import PlacelessSetup from zope.interface import directlyProvides from zope.location.traversing \ import LocationPhysicallyLocatable, RootPhysicallyLocatable from zope.location.interfaces import ILocationInfo, IRoot, LocationError from zope.security.proxy import Proxy from zope.security.checker import selectChecker from zope.traversing.adapters import Traverser, DefaultTraversable from zope.traversing.interfaces import ITraversable, ITraverser from zope.container.contained import contained class C(object): __parent__ = None __name__ = None def __init__(self, name): self.name = name def _proxied(*args): return Proxy(args, selectChecker(args)) class Test(PlacelessSetup, TestCase): def setUp(self): PlacelessSetup.setUp(self) # Build up a wrapper chain root = C('root') directlyProvides(root, IRoot) folder = C('folder') item = C('item') self.root = root # root is not usually wrapped self.folder = contained(folder, self.root, name='folder') self.item = contained(item, self.folder, name='item') self.unwrapped_item = item self.broken_chain_folder = contained(folder, None) self.broken_chain_item = contained(item, self.broken_chain_folder, name='item' ) root.folder = folder folder.item = item self.tr = Traverser(root) zope.component.provideAdapter(Traverser, (None,), ITraverser) zope.component.provideAdapter(DefaultTraversable, (None,), ITraversable) zope.component.provideAdapter(LocationPhysicallyLocatable, (None,), ILocationInfo) zope.component.provideAdapter(RootPhysicallyLocatable, (IRoot,), ILocationInfo) def testTraverse(self): from zope.traversing.api import traverse self.assertEqual( traverse(self.item, '/folder/item'), self.tr.traverse('/folder/item') ) def testTraverseFromUnwrapped(self): from zope.traversing.api import traverse self.assertRaises( TypeError, traverse, self.unwrapped_item, '/folder/item' ) def testTraverseName(self): from zope.traversing.api import traverseName self.assertEqual( traverseName(self.folder, 'item'), self.tr.traverse('/folder/item') ) self.assertEqual( traverseName(self.item, '.'), self.tr.traverse('/folder/item') ) self.assertEqual( traverseName(self.item, '..'), self.tr.traverse('/folder') ) # TODO test that ++names++ and @@names work too def testTraverseNameBadValue(self): from zope.traversing.api import traverseName self.assertRaises( LocationError, traverseName, self.folder, '../root' ) self.assertRaises( LocationError, traverseName, self.folder, '/root' ) self.assertRaises( LocationError, traverseName, self.folder, './item' ) def testGetName(self): from zope.traversing.api import getName self.assertEqual( getName(self.item), 'item' ) def testGetParent(self): from zope.traversing.api import getParent self.assertEqual( getParent(self.item), self.folder ) def testGetParentFromRoot(self): from zope.traversing.api import getParent self.assertEqual( getParent(self.root), None ) def testGetParentBrokenChain(self): from zope.traversing.api import getParent self.assertRaises( TypeError, getParent, self.broken_chain_folder ) def testGetParentFromUnwrapped(self): from zope.traversing.api import getParent self.assertRaises( TypeError, getParent, self.unwrapped_item ) def testGetParents(self): from zope.traversing.api import getParents self.assertEqual( getParents(self.item), [self.folder, self.root] ) def testGetParentsBrokenChain(self): from zope.traversing.api import getParents self.assertRaises( TypeError, getParents, self.broken_chain_item ) def testGetPath(self): from zope.traversing.api import getPath self.assertEqual( getPath(self.item), u'/folder/item' ) def testGetPathOfRoot(self): from zope.traversing.api import getPath self.assertEqual( getPath(self.root), u'/', ) def testGetNameOfRoot(self): from zope.traversing.api import getName self.assertEqual( getName(self.root), u'', ) def testGetRoot(self): from zope.traversing.api import getRoot self.assertEqual( getRoot(self.item), self.root ) def testCanonicalPath(self): _bad_locations = ( (ValueError, '\xa323'), (ValueError, ''), (ValueError, '//'), (ValueError, '/foo//bar'), # regarding the next two errors: # having a trailing slash on a location is undefined. # we might want to give it a particular meaning for zope3 later # for now, it is an invalid location identifier (ValueError, '/foo/bar/'), (ValueError, 'foo/bar/'), (IndexError, '/a/../..'), (ValueError, '/a//v'), ) # sequence of N-tuples: # (loc_returned_as_string, input, input, ...) # The string and tuple are tested as input as well as being the # specification for output. _good_locations = ( # location returned as string ( u'/xx/yy/zz', # arguments to try in addition to the above '/xx/yy/zz', '/xx/./yy/ww/../zz', ), ( u'/xx/yy/zz', '/xx/yy/zz', ), ( u'/xx', '/xx', ), ( u'/', '/', ), ) from zope.traversing.api import canonicalPath for error_type, value in _bad_locations: self.assertRaises(error_type, canonicalPath, value) for spec in _good_locations: correct_answer = spec[0] for argument in spec: self.assertEqual(canonicalPath(argument), correct_answer, "failure on %s" % argument) def test_normalizePath(self): _bad_locations = ( (ValueError, '//'), (ValueError, '/foo//bar'), (IndexError, '/a/../..'), (IndexError, '/a/./../..'), ) # sequence of N-tuples: # (loc_returned_as_string, input, input, ...) # The string and tuple are tested as input as well as being the # specification for output. _good_locations = ( # location returned as string ( '/xx/yy/zz', # arguments to try in addition to the above '/xx/yy/zz', '/xx/./yy/ww/../zz', '/xx/./yy/ww/./../zz', ), ( 'xx/yy/zz', # arguments to try in addition to the above 'xx/yy/zz', 'xx/./yy/ww/../zz', 'xx/./yy/ww/./../zz', ), ( '/xx/yy/zz', '/xx/yy/zz', ), ( '/xx', '/xx', ), ( '/', '/', ), ) from zope.traversing.api import _normalizePath for error_type, value in _bad_locations: self.assertRaises(error_type, _normalizePath, value) for spec in _good_locations: correct_answer = spec[0] for argument in spec: self.assertEqual(_normalizePath(argument), correct_answer, "failure on %s" % argument) def test_joinPath_slashes(self): from zope.traversing.api import joinPath path = u'/' args = ('/test', 'bla', '/foo', 'bar') self.assertRaises(ValueError, joinPath, path, *args) args = ('/test', 'bla', 'foo/', '/bar') self.assertRaises(ValueError, joinPath, path, *args) def test_joinPath(self): from zope.traversing.api import joinPath path = u'/bla' args = ('foo', 'bar', 'baz', 'bone') self.assertEqual(joinPath(path, *args), u'/bla/foo/bar/baz/bone') path = u'bla' args = ('foo', 'bar', 'baz', 'bone') self.assertEqual(joinPath(path, *args), u'bla/foo/bar/baz/bone') path = u'bla' args = ('foo', 'bar/baz', 'bone') self.assertEqual(joinPath(path, *args), u'bla/foo/bar/baz/bone') path = u'bla/' args = ('foo', 'bar', 'baz', 'bone') self.assertRaises(ValueError, joinPath, path, *args) def test_joinPath_normalize(self): from zope.traversing.api import joinPath path = u'/bla' args = ('foo', 'bar', '..', 'baz', 'bone') self.assertEqual(joinPath(path, *args), u'/bla/foo/baz/bone') path = u'bla' args = ('foo', 'bar', '.', 'baz', 'bone') self.assertEqual(joinPath(path, *args), u'bla/foo/bar/baz/bone') path = u'/' args = ('foo', 'bar', '.', 'baz', 'bone') self.assertEqual(joinPath(path, *args), u'/foo/bar/baz/bone') def test_suite(): return makeSuite(Test) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/test_vhosting.py0000644000175000017500000002451712214017660027153 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Functional tests for virtual hosting. """ import os import unittest from StringIO import StringIO import transaction from zope.browserresource.resource import Resource from zope.configuration import xmlconfig from zope.container.contained import Contained from zope.pagetemplate.pagetemplate import PageTemplate from zope.pagetemplate.engine import AppPT from zope.publisher.browser import BrowserRequest from zope.publisher.publish import publish from zope.publisher.skinnable import setDefaultSkin from zope.security.checker import defineChecker, NamesChecker, NoProxy from zope.security.checker import _checkers, undefineChecker from zope.site.folder import Folder from zope.site.folder import rootFolder from zope.testing.cleanup import cleanUp from zope.traversing.api import traverse from zope.traversing.testing import browserResource class MyObj(Contained): def __getitem__(self, key): return traverse(self, '/foo/bar/' + key) class MyPageTemplate(AppPT, PageTemplate): def pt_getContext(self, instance, request, **_kw): # instance is a View component namespace = super(MyPageTemplate, self).pt_getContext(**_kw) namespace['template'] = self namespace['request'] = request namespace['container'] = namespace['context'] = instance return namespace def render(self, instance, request, *args, **kw): return self.pt_render(self.pt_getContext(instance, request)) class MyPageEval(object): def index(self, **kw): """Call a Page Template""" template = self.context request = self.request return template.render(template.__parent__, request, **kw) class MyFolderPage(object): def index(self, **kw): """My folder page""" self.request.response.redirect('index.html') return '' class TestVirtualHosting(unittest.TestCase): def setUp(self): f = os.path.join(os.path.split(__file__)[0], 'ftesting.zcml') xmlconfig.file(f) self.app = rootFolder() defineChecker(MyObj, NoProxy) def tearDown(self): undefineChecker(MyObj) cleanUp() def makeRequest(self, path=''): env = {"HTTP_HOST": 'localhost', "HTTP_REFERER": 'localhost'} p = path.split('?') if len(p) == 1: env['PATH_INFO'] = p[0] request = BrowserRequest(StringIO(''), env) request.setPublication(DummyPublication(self.app)) setDefaultSkin(request) return request def publish(self, path): return publish(self.makeRequest(path)).response def test_request_url(self): self.addPage('/pt', u'') self.verify('/pt', 'http://localhost/pt/index.html') self.verify('/++vh++/++/pt', 'http://localhost/pt/index.html') self.verify('/++vh++https:localhost:443/++/pt', 'https://localhost/pt/index.html') self.verify('/++vh++https:localhost:443/fake/folders/++/pt', 'https://localhost/fake/folders/pt/index.html') self.addPage('/foo/bar/pt', u'') self.verify('/foo/bar/pt', 'http://localhost/foo/bar/pt/index.html') self.verify('/foo/bar/++vh++/++/pt', 'http://localhost/pt/index.html') self.verify('/foo/bar/++vh++https:localhost:443/++/pt', 'https://localhost/pt/index.html') self.verify('/foo/++vh++https:localhost:443/fake/folders/++/bar/pt', 'https://localhost/fake/folders/bar/pt/index.html') def test_request_redirect(self): self.addPage('/foo/index.html', u'Spam') self.verifyRedirect('/foo', 'http://localhost/foo/index.html') self.verifyRedirect('/++vh++https:localhost:443/++/foo', 'https://localhost/foo/index.html') self.verifyRedirect('/foo/++vh++https:localhost:443/bar/++', 'https://localhost/bar/index.html') def test_absolute_url(self): self.addPage('/pt', u'') self.verify('/pt', 'http://localhost') self.verify('/++vh++/++/pt', 'http://localhost') self.verify('/++vh++https:localhost:443/++/pt', 'https://localhost') self.verify('/++vh++https:localhost:443/fake/folders/++/pt', 'https://localhost/fake/folders') self.addPage('/foo/bar/pt', u'') self.verify('/foo/bar/pt', 'http://localhost/foo/bar') self.verify('/foo/bar/++vh++/++/pt', 'http://localhost') self.verify('/foo/bar/++vh++https:localhost:443/++/pt', 'https://localhost') self.verify('/foo/++vh++https:localhost:443/fake/folders/++/bar/pt', 'https://localhost/fake/folders/bar') def test_absolute_url_absolute_traverse(self): self.createObject('/foo/bar/obj', MyObj()) self.addPage('/foo/bar/pt', u'') self.verify('/foo/bar/pt', 'http://localhost/foo/bar/pt') self.verify('/foo/++vh++https:localhost:443/++/bar/pt', 'https://localhost/bar/pt') def test_resources(self): browserResource('quux', Resource) # Only register the checker once, so that multiple test runs pass. if Resource not in _checkers: defineChecker(Resource, NamesChecker(['__call__'])) self.addPage('/foo/bar/pt', u'') self.verify('/foo/bar/pt', 'http://localhost/@@/quux') self.verify('/foo/++vh++https:localhost:443/fake/folders/++/bar/pt', 'https://localhost/fake/folders/@@/quux') def createFolders(self, path): """addFolders('/a/b/c/d') would traverse and/or create three nested folders (a, b, c) and return a tuple (c, 'd') where c is a Folder instance at /a/b/c.""" folder = self.app #self.connection.root()['Application'] if path[0] == '/': path = path[1:] path = path.split('/') for id in path[:-1]: try: folder = folder[id] except KeyError: folder[id] = Folder() folder = folder[id] return folder, path[-1] def createObject(self, path, obj): folder, id = self.createFolders(path) folder[id] = obj transaction.commit() def addPage(self, path, content): page = MyPageTemplate() page.pt_edit(content, 'text/html') self.createObject(path, page) def verify(self, path, content): result = self.publish(path) self.assertEquals(result.getStatus(), 200) self.assertEquals(result.consumeBody(), content) def verifyRedirect(self, path, location): result = self.publish(path) self.assertEquals(result.getStatus(), 302) self.assertEquals(result.getHeader('Location'), location) class DummyPublication: def __init__(self, app): self.app = app def beforeTraversal(self, request): """Pre-traversal hook. This is called *once* before any traversal has been done. """ def getApplication(self, request): """Returns the object where traversal should commence. """ return self.app def callTraversalHooks(self, request, ob): """Invokes any traversal hooks associated with the object. This is called before traversing each object. The ob argument is the object that is about to be traversed. """ def traverseName(self, request, ob, name): """Traverses to the next object. Name must be an ASCII string or Unicode object.""" if name == 'index.html': from zope.component import queryMultiAdapter view = queryMultiAdapter((ob, request), name=name) if view is None: from zope.publisher.interfaces import NotFound raise NotFound(ob, name) return view else: from zope.traversing.publicationtraverse \ import PublicationTraverserWithoutProxy t = PublicationTraverserWithoutProxy() return t.traverseName(request, ob, name) def afterTraversal(self, request, ob): """Post-traversal hook. This is called after all traversal. """ def callObject(self, request, ob): """Call the object, returning the result. For GET/POST this means calling it, but for other methods (including those of WebDAV and FTP) this might mean invoking a method of an adapter. """ from zope.publisher.publish import mapply return mapply(ob, request.getPositionalArguments(), request) def afterCall(self, request, ob): """Post-callObject hook (if it was successful). """ def handleException(self, ob, request, exc_info, retry_allowed=1): """Handle an exception Either: - sets the body of the response, request.response, or - raises a Retry exception, or - throws another exception, which is a Bad Thing. """ import traceback traceback.print_exception(*exc_info) def endRequest(self, request, ob): """Do any end-of-request cleanup """ def getDefaultTraversal(self, request, ob): if hasattr(ob, 'index'): return ob, () else: return ob, ('index.html',) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestVirtualHosting)) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/tests/test_skin.py0000644000175000017500000000363212214017660026251 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Test skin traversal. """ from unittest import TestCase, main, makeSuite import zope.component from zope.testing.cleanup import CleanUp from zope.interface import Interface, directlyProvides from zope.publisher.interfaces.browser import IBrowserSkinType class FauxRequest(object): def shiftNameToApplication(self): self.shifted = 1 class IFoo(Interface): pass directlyProvides(IFoo, IBrowserSkinType) class Test(CleanUp, TestCase): def setUp(self): super(Test, self).setUp() zope.component.provideUtility(IFoo, IBrowserSkinType, name='foo') def test(self): from zope.traversing.namespace import skin request = FauxRequest() ob = object() ob2 = skin(ob, request).traverse('foo', ()) self.assertEqual(ob, ob2) self.assert_(IFoo.providedBy(request)) self.assertEqual(request.shifted, 1) def test_missing_skin(self): from zope.traversing.namespace import skin from zope.location.interfaces import LocationError request = FauxRequest() ob = object() traverser = skin(ob, request) self.assertRaises(LocationError, traverser.traverse, 'bar', ()) def test_suite(): return makeSuite(Test) if __name__=='__main__': main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.traversing/src/zope/traversing/testing.py0000644000175000017500000000473712214017660024570 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Traversing test fixtures """ __docformat__ = "reStructuredText" import zope.component import zope.interface from zope.publisher.interfaces.browser import IDefaultBrowserLayer from zope.location.traversing \ import LocationPhysicallyLocatable, RootPhysicallyLocatable from zope.location.interfaces import ILocationInfo, IRoot from zope.traversing.interfaces import ITraversable, ITraverser from zope.traversing.adapters import DefaultTraversable from zope.traversing.adapters import Traverser from zope.traversing.browser import SiteAbsoluteURL, AbsoluteURL from zope.traversing.browser.interfaces import IAbsoluteURL from zope.traversing.namespace import etc def setUp(): zope.component.provideAdapter(Traverser, (None,), ITraverser) zope.component.provideAdapter(DefaultTraversable, (None,), ITraversable) zope.component.provideAdapter(LocationPhysicallyLocatable, (None,), ILocationInfo) zope.component.provideAdapter(RootPhysicallyLocatable, (IRoot,), ILocationInfo) # set up the 'etc' namespace zope.component.provideAdapter(etc, (None,), ITraversable, name="etc") zope.component.provideAdapter(etc, (None, None), ITraversable, name="etc") browserView(None, "absolute_url", AbsoluteURL) browserView(IRoot, "absolute_url", SiteAbsoluteURL) browserView(None, '', AbsoluteURL, providing=IAbsoluteURL) browserView(IRoot, '', SiteAbsoluteURL, providing=IAbsoluteURL) def browserView(for_, name, factory, providing=zope.interface.Interface): zope.component.provideAdapter(factory, (for_, IDefaultBrowserLayer), providing, name=name) def browserResource(name, factory, providing=zope.interface.Interface): zope.component.provideAdapter(factory, (IDefaultBrowserLayer,), providing, name=name) zope2.13-2.13.21/source/zope.traversing/src/zope.traversing.egg-info/0000755000175000017500000000000012214017660024217 5ustar arnauarnauzope2.13-2.13.21/source/zope.traversing/src/zope.traversing.egg-info/PKG-INFO0000644000175000017500000002200412214017660025312 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.traversing Version: 3.13.2 Summary: Resolving paths in the object hierarchy Home-page: http://pypi.python.org/pypi/zope.traversing Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: The ``zope.traversing`` package provides adapteres for resolving object paths by traversing an object hierarchy. This also includes support for traversal namespaces (e.g. ``++view++``, ``++skin++``, etc.) as well as computing URLs via the ``@@absolute_url`` view. ======= Changes ======= 3.13.2 (2011-03-02) ------------------- - Re-release of 3.13.0. 3.13.1 introduced dependencies unsuitable for a bugfix release. 3.13.1 (2010-12-14) ------------------- - Fixed ZCML-related dependencies. 3.13 (2010-07-09) ----------------- - When a ``__parent__`` attribute is available on an object, it is always used for absolute URL construction, and no ILocation adapter lookup is performed for it. This was the previous behavior but was broken (around 3.5?) due to dependency refactoring. If the object provides no ``__parent__`` then an ILocation adapter lookup will be performed. This will always succeed as zope.location provides a default LocationProxy for everything, but more specific ILocation adapters can also be provided. 3.12.1 (2010-04-30) ------------------- - Removed use of 'zope.testing.doctestunit' in favor of stdlib's doctest. 3.12.0 (2009-12-29) ------------------- - Avoid testing dependencies on zope.securitypolicies and zope.principalregistry. 3.11.0 (2009-12-27) ------------------- - Removed testing dependency on zope.app.publication. 3.10.0 (2009-12-16) ------------------- - Removed stray test claiming a no longer existing dependency on zope.app.applicationcontrol. - Refactored functional tests to loose dependency on both zope.app.appsetup and zope.app.testing. - Simplified tests for the browser sub-package by using PlacelessSetup from zope.component.testing instead of zope.app.testing. - Simplified test_dependencies module by using zope.configuration instead of zope.app.testing.functional. - Removed testing dependency on zope.app.publisher. - Replaced testing dependency on zope.app.security with zope.securitypolicy. - Removed testing dependency on zope.app.zcmlfiles in favor of more explicit dependencies. - Removed testing dependency on zope.app.component. - Replaced a test dependency on zope.app.zptpage with a dependency on zope.pagetemplate. 3.9.0 (2009-12-15) ------------------ - Moved IBeforeTraverseEvent from zope.app.publication into this package, as we already deal with publication traversal. 3.8.0 (2009-09-29) ------------------ - In zope.traversing.api.getParent(), try to delegate to zope.location.interfaces.ILocationInfo.getParent(), analogous to getParents(). Keep returning the traversal parent as a fallback. - Brought ITraverser back from zope.location where it had been moved to invert the package interdependency, but is no longer used now. 3.7.2 (2009-08-29) ------------------ - Made virtual hosting tests compatible with zope.publisher 3.9. Redirecting to a different host requires an explicit `trusted` redirect now. 3.7.1 (2009-06-16) ------------------ - AbsoluteURL now implements the fact that __call__ returns the same as __str__ in a manner that it applies for subclasses, too, so they only have to override __str__ and not both. 3.7.0 (2009-05-23) ------------------ - Moved the publicationtraverse module to zope.traversing, removing the zope.app.publisher -> zope.app.publication dependency (which was a cycle). - Look up the application controller through a utility registration rather than a direct reference. 3.6.0 (2009-04-06) ------------------ - Change configure.zcml to not depend on zope.app.component. - This release includes the BBB-incompatible ``zope.publisher.skinnable`` change from 3.5.3. 3.5.4 (2009-04-06) ------------------ - Revert BBB-incompatible use of ``zope.publisher.skinnable``: that change belongs in a 3.6.0 release, because it requires a BBB-incompatible version of ``zope.publisher``. 3.5.3 (2009-03-10) ------------------ - Use applySkin from new location. zope.publisher.skinnable instead of zope.publisher.browser. - Use IAbsoluteURL lookup instead of the "absolute_url" view in the recursive AbsoluteURL adapters (LP: #338101). 3.5.2 (2009-02-04) ------------------ - The RootPhysicallyLocatable is not the same as LocationPhysicallyLocatable now in zope.location. Fix the import and testing setups. 3.5.1 (2009-02-02) ------------------ - The ``RootPhysicallyLocatable`` adapter has been superseded by the refactored ``zope.location.traversing.LocationPhysicallyLocatable`` that we depend on since 3.5.0a4. Remove the adapter and its registration, and making its import place pointing to ``zope.location.traversing.LocationPhysicallyLocatable`` to maintain backward-compatibility. This also fixes a bug introduced in version 3.5.0a4 when trying to call ``getParents`` function for the root object. - Use direct imports instead of compatibility ones for things that were moved to ``zope.location``. - Remove the ``zope.traversing.interfaces.INamespaceHandler`` interface, as it seems not to be used for years. - Change package's mailing list address to zope-dev at zope.org instead of retired zope3-dev at zope.org 3.5.0 (2009-01-31) ------------------ - Use zope.container instead of zope.app.container. - Use zope.site instead of zope.app.folder in the unit tests. - Reduced, but did not eliminate, test dependencies on zope.app.component. 3.5.0a4 (2008-08-01) -------------------- - Reverse dependencies between zope.location and zope.traversing. - Updated (test) dependencies and tests to expect and work with a spec compliant TAL interpreter as available in zope.tal >= 3.5.0. - Fixed deprecation warning caused by using an old module name for ZopeSecurityPolicy in ftesting.zcml - Made sure traversing doesn't raise an TypeError but a TraversalError when the traversal step before yielded a string. 3.5.0a3 (2007-12-28) -------------------- - backed out the controversial `++skin++` traverser for XML-RPC. 3.5.0a2 (2007-11-28) -------------------- - ported 3.4.1a1 to trunk - Do not use unicode strings to set the application server in the virtual host namespace. This caused absolute_url to create unicode URL's. - Added a traverer for ++skin++ for XMLRPC skins (IXMLRPCSkinType). This also means that the normal ++skin++ namespace handler is only bound to IBrowserRequest. - Resolved the dependency on zope.app.applicationcontrol by importing the application controller only if the package is available. 3.4.1 (2008-07-30) ------------------ - Fixed deprecation warning caused by using an old module name for ZopeSecurityPolicy in ftesting.zcml 3.4.1a1 (2007-11-13) -------------------- Do not use unicode strings to set the application server in the virtual host namespace. This caused absolute_url to create unicode URL's. 3.4.0 (2007-09-29) ------------------ No further changes since 3.4.0a1. 3.4.0a1 (2007-04-22) -------------------- Initial release as a separate project, corresponds to zope.traversing from Zope 3.4.0a1 Platform: UNKNOWN zope2.13-2.13.21/source/zope.traversing/src/zope.traversing.egg-info/dependency_links.txt0000644000175000017500000000000112214017660030265 0ustar arnauarnau zope2.13-2.13.21/source/zope.traversing/src/zope.traversing.egg-info/requires.txt0000644000175000017500000000042312214017660026616 0ustar arnauarnausetuptools zope.component zope.i18n zope.i18nmessageid zope.interface zope.proxy zope.publisher zope.security zope.location>=3.7.0 [test] zope.browserpage zope.browserresource zope.configuration zope.container zope.pagetemplate zope.site zope.tal >= 3.5.0 zope.testing ZODB3zope2.13-2.13.21/source/zope.traversing/src/zope.traversing.egg-info/namespace_packages.txt0000644000175000017500000000000512214017660030545 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.traversing/src/zope.traversing.egg-info/top_level.txt0000644000175000017500000000000512214017660026744 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.traversing/src/zope.traversing.egg-info/SOURCES.txt0000644000175000017500000000270612214017660026110 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.traversing.egg-info/PKG-INFO src/zope.traversing.egg-info/SOURCES.txt src/zope.traversing.egg-info/dependency_links.txt src/zope.traversing.egg-info/namespace_packages.txt src/zope.traversing.egg-info/not-zip-safe src/zope.traversing.egg-info/requires.txt src/zope.traversing.egg-info/top_level.txt src/zope/traversing/__init__.py src/zope/traversing/adapters.py src/zope/traversing/api.py src/zope/traversing/configure.zcml src/zope/traversing/interfaces.py src/zope/traversing/namespace.py src/zope/traversing/publicationtraverse.py src/zope/traversing/testing.py src/zope/traversing/browser/__init__.py src/zope/traversing/browser/absoluteurl.py src/zope/traversing/browser/configure.zcml src/zope/traversing/browser/interfaces.py src/zope/traversing/browser/tests.py src/zope/traversing/tests/__init__.py src/zope/traversing/tests/ftesting.zcml src/zope/traversing/tests/test_conveniencefunctions.py src/zope/traversing/tests/test_dependencies.py src/zope/traversing/tests/test_lang.py src/zope/traversing/tests/test_namespacetrversal.py src/zope/traversing/tests/test_physicallocationadapters.py src/zope/traversing/tests/test_presentation.py src/zope/traversing/tests/test_publicationtraverse.py src/zope/traversing/tests/test_skin.py src/zope/traversing/tests/test_traverser.py src/zope/traversing/tests/test_vh.py src/zope/traversing/tests/test_vhosting.pyzope2.13-2.13.21/source/zope.traversing/src/zope.traversing.egg-info/not-zip-safe0000644000175000017500000000000112214017660026445 0ustar arnauarnau zope2.13-2.13.21/source/zope.testing/0000755000175000017500000000000012214017655016073 5ustar arnauarnauzope2.13-2.13.21/source/zope.testing/setup.py0000644000175000017500000000713212214017655017610 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.testing package """ import os try: from setuptools import setup extra = dict( namespace_packages=['zope',], install_requires = ['setuptools', 'zope.exceptions', 'zope.interface'], entry_points = { 'console_scripts': ['zope-testrunner = zope.testing.testrunner:run',]}, include_package_data = True, zip_safe = False, ) except ImportError, e: from distutils.core import setup extra = {} chapters = '\n'.join([ open(os.path.join('src', 'zope', 'testing', 'testrunner', name)).read() for name in ( 'testrunner.txt', 'testrunner-simple.txt', 'testrunner-layers-api.txt', 'testrunner-layers.txt', 'testrunner-arguments.txt', 'testrunner-verbose.txt', 'testrunner-test-selection.txt', 'testrunner-progress.txt', # The following seems to cause weird unicode in the output: :( ## 'testrunner-errors.txt', 'testrunner-debugging.txt', 'testrunner-layers-ntd.txt', 'testrunner-coverage.txt', 'testrunner-profiling.txt', 'testrunner-wo-source.txt', 'testrunner-repeat.txt', 'testrunner-gc.txt', 'testrunner-leaks.txt', 'testrunner-knit.txt', )]) chapters += '\n'.join([ open(os.path.join('src', 'zope', 'testing', name)).read() for name in ( 'formparser.txt', 'setupstack.txt', )]) long_description=( open('README.txt').read() + '\n' + open('CHANGES.txt').read() + '\n' + 'Detailed Documentation\n' '**********************\n' + '\n' + chapters ) setup( name='zope.testing', version='3.9.7', url='http://pypi.python.org/pypi/zope.testing', license='ZPL 2.1', description='Zope testing framework, including the testrunner script.', long_description=long_description, author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', packages=["zope", "zope.testing"], package_dir = {'': 'src'}, classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Framework :: Zope3", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.4", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Testing", ], **extra) zope2.13-2.13.21/source/zope.testing/PKG-INFO0000644000175000017500000047576712214017655017222 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.testing Version: 3.9.7 Summary: Zope testing framework, including the testrunner script. Home-page: http://pypi.python.org/pypi/zope.testing Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ************ zope.testing ************ .. contents:: This package provides a number of testing frameworks. It includes a flexible test runner, and supports both doctest and unittest. cleanup.py Provides a mixin class for cleaning up after tests that make global changes. doctest.py Enhanced version of python's standard doctest.py. Better test count (one per block instead of one per docstring). See doctest.txt. (We need to merge this with the standard doctest module.) doctestunit.py Provides a pprint function that always sorts dictionary entries (pprint.pprint from the standard library doesn't sort very short ones, sometimes causing test failures when the internal order changes). formparser.py An HTML parser that extracts form information. This is intended to support functional tests that need to extract information from HTML forms returned by the publisher. See formparser.txt. loggingsupport.py Support for testing logging code If you want to test that your code generates proper log output, you can create and install a handler that collects output. loghandler.py Logging handler for tests that check logging output. module.py Lets a doctest pretend to be a Python module. See module.txt. renormalizing.py Regular expression pattern normalizing output checker. Useful for doctests. server.py Provides a simple HTTP server compatible with the zope.app.testing functional testing API. Lets you interactively play with the system under test. Helpful in debugging functional doctest failures. setupstack.py A simple framework for automating doctest set-up and tear-down. See setupstack.txt. testrunner The test runner package. This is typically wrapped by a test.py script that sets up options to run a particular set of tests. Getting started *************** zope.testing uses buildout. To start, run ``python bootstrap.py``. It will create a number of directories and the ``bin/buildout`` script. Next, run ``bin/buildout``. It will create a test script for you. Now, run ``bin/test`` to run the zope.testing test suite. zope.testing Changelog ********************** 3.9.7 (2011-11-02) ================== - Work around sporadic timing-related issues in the subprocess buffering tests. Thanks to Jonathan Ballet for the patch! 3.9.6 (2011-02-21) ================== - LP #719369: An `Unexpected success`_ (concept intruduced in Python 2.7) is no longer handled as success but as failure. This is a workaround. The whole unexpected success concept might be implemented later. .. _`Unexpected success`: http://www.voidspace.org.uk/python/articles/unittest2.shtml#more-skipping - Updated tests to run on Python 2.7, too. 3.9.5 (2010-05-19) ================== - LP #579019: When layers were run in parallel, their tearDown was not called. Additionally, the first layer which was run in the main thread did not have it's tearDown called either. - Deprecated zope.testing.testrunner and zope.testing.exceptions. They have been moved to a separate zope.testrunner module, and will be removed from zope.testing in 4.0.0, together with zope.testing.doctest. 3.9.4 (2010-04-13) ================== - LP #560259: Fix subunit output formatter to handle layer setup errors. - LP #399394: Added a ``--stop-on-error`` / ``--stop`` / ``-x`` option to the testrunner. - LP #498162: Added a ``--pdb`` alias for the existing ``--post-mortem`` / ``-D`` option to the testrunner. - LP #547023: Added a ``--version`` option to the testrunner. - Added tests for LP #144569 and #69988. https://bugs.launchpad.net/bugs/69988 https://bugs.launchpad.net/zope3/+bug/144569 3.9.3 (2010-03-26) ================== - zope.testing.renormalizer no longer imports zope.testing.doctest, which caused deprecation warnings. - Fix testrunner-layers-ntd.txt to suppress output to sys.stderr. - Suppress zope.testing.doctest deprecation warning when running zope.testing's own test suite. 3.9.2 (2010-03-15) ================== - Fixed broken ``from zope.testing.doctest import *`` 3.9.1 (2010-03-15) ================== - No changes; reuploaded to fix broken 3.9.0 release on PyPI. 3.9.0 (2010-03-12) ================== - Modified the testrunner to use the standard Python doctest module instead of the deprecated zope.testing.doctest. - Fix testrunner-leaks.txt to use the run_internal helper, so that sys.exit() isn't triggered during the test run. - Added support for conditionally using a subunit-based output formatter upon request if subunit and testtools are available. Patch contributed by Jonathan Lange. 3.8.6 (2009-12-23) ================== - Added MANIFEST.in and reuploaded to fix broken 3.8.5 release on PyPI. 3.8.5 (2009-12-23) ================== - Added DocFileSuite, DocTestSuite, debug_src and debug back BBB imports back into zope.testing.doctestunit; apparently many packages still import them from there! - Made zope.testing.doctest and zope.testing.doctestunit emit deprecation warnings: use the stdlib doctest instead. 3.8.4 (2009-12-18) ================== - Fixed missing imports and undefined variables reported by pyflakes, adding tests to exercise the blind spots. - Cleaned up unused imports reported by pyflakes. - Added two new options to generate randomly ordered list of tests and to select a specific order of tests. - RENormalizing checkers can be combined via ``+`` now: ``checker1 + checker2`` creates a checker with the transformations of both checkers. - Test fixes for Python 2.7. 3.8.3 (2009-09-21) ================== - Avoid a split() call or we get test failures when running from a directory with spaces in it. - Fix testrunner behavior on Windows for -j2 (or greater) combined with -v (or greater). 3.8.2 (2009-09-15) ================== - Removing hotshot profiler when using Python 2.6. That makes zope.testing compatible with Python 2.6 3.8.1 (2009-08-12) ================== - Avoid hardcoding sys.argv[0] as script; allow, for instance, Zope 2's `bin/instance test` (LP#407916). - Produce a clear error message when a subprocess doesn't follow the zope.testing.testrunner protocol (LP#407916). - Do not unnecessarily squelch verbose output in a subprocess when there are not multiple subprocesses. - Do not unnecessarily batch subprocess output, which can stymie automated and human processes for identifying hung tests. - Include incremental output when there are multiple subprocesses and a verbosity of -vv or greater is requested. This again is not batched, supporting automated processes and humans looking for hung tests. 3.8.0 (2009-07-24) ================== - Testrunner automatically picks up descendants of unittest.TestCase in test modules, so you don't have to provide a test_suite() anymore. 3.7.7 (2009-07-15) ================== - Clean up support for displaying tracebacks with supplements by turning it into an always-enabled feature and making the dependency on zope.exceptions explicit. - Fix #251759: Test runner descended into directories that aren't Python packages. - Code cleanups. 3.7.6 (2009-07-02) ================== - Add zope-testrunner console_scripts entry point. This exposes a zope-testrunner binary with default installs allowing the testrunner to be run from the command line. 3.7.5 (2009-06-08) ================== - Fix bug when running subprocesses on Windows. - The option REPORT_ONLY_FIRST_FAILURE (command line option "-1") is now respected even when a doctest declares its own REPORTING_FLAGS, such as REPORT_NDIFF. - Fixed bug that broke readline with pdb when using doctest (see http://bugs.python.org/issue5727). - Made tests pass on Windows and Linux at the same time. 3.7.4 (2009-05-01) ================== - Filenames of doctest examples now contain the line number and not only the example number. So a stack trace in pdb tells the exact line number of the current example. This fixes https://bugs.launchpad.net/bugs/339813 - Colorization of doctest output correctly handles blank lines. 3.7.3 (2009-04-22) ================== - Better deal with rogue threads by always exiting with status so even spinning daemon threads won't block the runner from exiting. This deprecated the ``--with-exit-status`` option. 3.7.2 (2009-04-13) ================== - fix test failure on Python 2.4 because of slight difference in the way coverage is reported (__init__ files with only a single comment line are now not reported) - fixed bug that caused the test runner to hang when running subprocesses (as a result Python 2.3 is no longer supported). - there is apparently a bug in Python 2.6 (related to http://bugs.python.org/issue1303673) that causes the profile tests to fail. - added explanitory notes to buildout.cfg about how to run the tests with multiple versions of Python 3.7.1 (2008-10-17) ================== - The setupstack temporary-directory support now properly handles read-only files by making them writable before removing them. 3.7.0 (2008-09-22) ================== - Added an alterate setuptools / distutils commands for running all tests using our testrunner. See 'zope.testing.testrunner.eggsupport:ftest'. - Added a setuptools-compatible test loader which skips tests with layers: the testrunner used by 'setup.py test' doesn't know about them, and those tests then fail. See 'zope.testing.testrunner.eggsupport:SkipLayers'. - Added support for Jython, when a garbage collector call is sent. - Added support to bootstrap on Jython. - Fixed NameError in StartUpFailure. - Open doctest files in universal mode, so that packages released in Windoes can be tested in Linux, for example. 3.6.0 (2008/07/10) ================== - Added -j option to parallel tests run in subprocesses. - RENormalizer accepts plain Python callables. - Added --slow-test option. - Added --no-progress and --auto-progress options. - Complete refactoring of the test runner into multiple code files and a more modular (pipeline-like) architecture. - Unified unit tests with the layer support by introducing a real unit test layer. - Added a doctest for ``zope.testing.module``. There were several bugs that were fixed: * ``README.txt`` was a really bad default argument for the module name, as it is not a proper dotted name. The code would immediately fail as it would look for the ``txt`` module in the ``README`` package. The default is now ``__main__``. * The tearDown function did not clean up the ``__name__`` entry in the global dictionary. - Fix a bug that caused a SubprocessError to be generated if a subprocess sent any output to stderr. - Fix a bug that caused the unit tests to be skipped if run in a subprocess. 3.5.1 (2007/08/14) ================== Bugs Fixed: ----------- - Post-mortem debugging wasn't invoked for layer-setup failures. 3.5.0 (2007/07/19) ================== New Features ------------ - The test runner now works on Python 2.5. - Added support for cProfile. - Added output colorizing (-c option). - Added --hide-secondary-failures and --show-secondary-failures options (https://bugs.launchpad.net/zope3/+bug/115454). Bugs Fixed: ----------- - Fix some problems with Unicode in doctests. - Fix "Error reading from subprocess" errors on Unix-like systems. 3.4 (2007/03/29) ================ New Features ------------ - Added exit-with-status support (supports use with buildbot and zc.recipe.testing) - Added a small framework for automating set up and tear down of doctest tests. See setupstack.txt. Bugs Fixed: ----------- - Fix testrunner-wo-source.txt and testrunner-errors.txt to run with a read-only source tree. 3.0 (2006/09/20) ================ - Updated the doctest copy with text-file encoding support. - Added logging-level support to loggingsuppport module. - At verbosity-level 1, dots are not output continuously, without any line breaks. - Improved output when the inability to tear down a layer causes tests to be run in a subprocess. - Made zope.exception required only if the zope_tracebacks extra is requested. 2.x.y (???) =========== - Fix the test coverage. If a module, for example `interfaces`, was in an ignored directory/package, then if a module of the same name existed in a covered directory/package, then it was also ignored there, because the ignore cache stored the result by module name and not the filename of the module. 2.0 (2006/01/05) ================ - Corresponds to the version of the zope.testing package shipped as part of the Zope 3.2.0 release. Detailed Documentation ********************** Test Runner =========== The testrunner module is used to run automated tests defined using the unittest framework. Its primary feature is that it *finds* tests by searching directory trees. It doesn't require the manual concatenation of specific test suites. It is highly customizable and should be usable with any project. In addition to finding and running tests, it provides the following additional features: - Test filtering using specifications of: o test packages within a larger tree o regular expression patterns for test modules o regular expression patterns for individual tests - Organization of tests into levels and layers Sometimes, tests take so long to run that you don't want to run them on every run of the test runner. Tests can be defined at different levels. The test runner can be configured to only run tests at a specific level or below by default. Command-line options can be used to specify a minimum level to use for a specific run, or to run all tests. Individual tests or test suites can specify their level via a 'level' attribute. where levels are integers increasing from 1. Most tests are unit tests. They don't depend on other facilities, or set up whatever dependencies they have. For larger applications, it's useful to specify common facilities that a large number of tests share. Making each test set up and and tear down these facilities is both ineffecient and inconvenient. For this reason, we've introduced the concept of layers, based on the idea of layered application architectures. Software build for a layer should be able to depend on the facilities of lower layers already being set up. For example, Zope defines a component architecture. Much Zope software depends on that architecture. We should be able to treat the component architecture as a layer that we set up once and reuse. Similarly, Zope application software should be able to depend on the Zope application server without having to set it up in each test. The test runner introduces test layers, which are objects that can set up environments for tests within the layers to use. A layer is set up before running the tests in it. Individual tests or test suites can define a layer by defining a `layer` attribute, which is a test layer. - Reporting - progress meter - summaries of tests run - Analysis of test execution - post-mortem debugging of test failures - memory leaks - code coverage - source analysis using pychecker - memory errors - execution times - profiling Simple Usage ============ The test runner consists of an importable module. The test runner is used by providing scripts that import and invoke the `run` method from the module. The `testrunner` module is controlled via command-line options. Test scripts supply base and default options by supplying a list of default command-line options that are processed before the user-supplied command-line options are provided. Typically, a test script does 2 things: - Adds the directory containing the zope package to the Python path. - Calls the test runner with default arguments and arguments supplied to the script. Normally, it just passes default/setup arguments. The test runner uses `sys.argv` to get the user's input. This testrunner_ex subdirectory contains a number of sample packages with tests. Let's run the tests found here. First though, we'll set up our default options: >>> import os.path >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] The default options are used by a script to customize the test runner for a particular application. In this case, we use two options: path Set the path where the test runner should look for tests. This path is also added to the Python path. tests-pattern Tell the test runner how to recognize modules or packages containing tests. Now, if we run the tests, without any other options: >>> from zope.testing import testrunner >>> import sys >>> sys.argv = ['test'] >>> testrunner.run_internal(defaults) Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in N.NNN seconds. Ran 9 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer11 tests: Set up samplelayers.Layer11 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer111 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer112 tests: Tear down samplelayers.Layer111 in N.NNN seconds. Set up samplelayers.Layer112 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer12 tests: Tear down samplelayers.Layer112 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer12 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer121 tests: Set up samplelayers.Layer121 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer122 tests: Tear down samplelayers.Layer121 in N.NNN seconds. Set up samplelayers.Layer122 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running zope.testing.testrunner.layer.UnitTests tests: Tear down samplelayers.Layer122 in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 405 tests, 0 failures, 0 errors in N.NNN seconds. False we see the normal testrunner output, which summarizes the tests run for each layer. For each layer, we see what layers had to be torn down or set up to run the layer and we see the number of tests run, with results. The test runner returns a boolean indicating whether there were errors. In this example, there were no errors, so it returned False. (Of course, the times shown in these examples are just examples. Times will vary depending on system speed.) Layers ====== A Layer is an object providing setup and teardown methods used to setup and teardown the environment provided by the layer. It may also provide setup and teardown methods used to reset the environment provided by the layer between each test. Layers are generally implemented as classes using class methods. >>> class BaseLayer: ... def setUp(cls): ... log('BaseLayer.setUp') ... setUp = classmethod(setUp) ... ... def tearDown(cls): ... log('BaseLayer.tearDown') ... tearDown = classmethod(tearDown) ... ... def testSetUp(cls): ... log('BaseLayer.testSetUp') ... testSetUp = classmethod(testSetUp) ... ... def testTearDown(cls): ... log('BaseLayer.testTearDown') ... testTearDown = classmethod(testTearDown) ... Layers can extend other layers. Note that they do not explicitly invoke the setup and teardown methods of other layers - the test runner does this for us in order to minimize the number of invocations. >>> class TopLayer(BaseLayer): ... def setUp(cls): ... log('TopLayer.setUp') ... setUp = classmethod(setUp) ... ... def tearDown(cls): ... log('TopLayer.tearDown') ... tearDown = classmethod(tearDown) ... ... def testSetUp(cls): ... log('TopLayer.testSetUp') ... testSetUp = classmethod(testSetUp) ... ... def testTearDown(cls): ... log('TopLayer.testTearDown') ... testTearDown = classmethod(testTearDown) ... Tests or test suites specify what layer they need by storing a reference in the 'layer' attribute. >>> import unittest >>> class TestSpecifyingBaseLayer(unittest.TestCase): ... 'This TestCase explicitly specifies its layer' ... layer = BaseLayer ... name = 'TestSpecifyingBaseLayer' # For testing only ... ... def setUp(self): ... log('TestSpecifyingBaseLayer.setUp') ... ... def tearDown(self): ... log('TestSpecifyingBaseLayer.tearDown') ... ... def test1(self): ... log('TestSpecifyingBaseLayer.test1') ... ... def test2(self): ... log('TestSpecifyingBaseLayer.test2') ... >>> class TestSpecifyingNoLayer(unittest.TestCase): ... 'This TestCase specifies no layer' ... name = 'TestSpecifyingNoLayer' # For testing only ... def setUp(self): ... log('TestSpecifyingNoLayer.setUp') ... ... def tearDown(self): ... log('TestSpecifyingNoLayer.tearDown') ... ... def test1(self): ... log('TestSpecifyingNoLayer.test') ... ... def test2(self): ... log('TestSpecifyingNoLayer.test') ... Create a TestSuite containing two test suites, one for each of TestSpecifyingBaseLayer and TestSpecifyingNoLayer. >>> umbrella_suite = unittest.TestSuite() >>> umbrella_suite.addTest(unittest.makeSuite(TestSpecifyingBaseLayer)) >>> no_layer_suite = unittest.makeSuite(TestSpecifyingNoLayer) >>> umbrella_suite.addTest(no_layer_suite) Before we can run the tests, we need to setup some helpers. >>> from zope.testing.testrunner import options >>> from zope.testing.loggingsupport import InstalledHandler >>> import logging >>> log_handler = InstalledHandler('zope.testing.tests') >>> def log(msg): ... logging.getLogger('zope.testing.tests').info(msg) >>> def fresh_options(): ... opts = options.get_options(['--test-filter', '.*']) ... opts.resume_layer = None ... opts.resume_number = 0 ... return opts Now we run the tests. Note that the BaseLayer was not setup when the TestSpecifyingNoLayer was run and setup/torn down around the TestSpecifyingBaseLayer tests. >>> from zope.testing.testrunner.runner import Runner >>> runner = Runner(options=fresh_options(), args=[], found_suites=[umbrella_suite]) >>> succeeded = runner.run() Running BaseLayer tests: Set up BaseLayer in N.NNN seconds. Ran 2 tests with 0 failures and 0 errors in N.NNN seconds. Running zope.testing.testrunner.layer.UnitTests tests: Tear down BaseLayer in N.NNN seconds. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 2 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 4 tests, 0 failures, 0 errors in N.NNN seconds. Now lets specify a layer in the suite containing TestSpecifyingNoLayer and run the tests again. This demonstrates the other method of specifying a layer. This is generally how you specify what layer doctests need. >>> no_layer_suite.layer = BaseLayer >>> runner = Runner(options=fresh_options(), args=[], found_suites=[umbrella_suite]) >>> succeeded = runner.run() Running BaseLayer tests: Set up BaseLayer in N.NNN seconds. Ran 4 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down BaseLayer in N.NNN seconds. Clear our logged output, as we want to inspect it shortly. >>> log_handler.clear() Now lets also specify a layer in the TestSpecifyingNoLayer class and rerun the tests. This demonstrates that the most specific layer is used. It also shows the behavior of nested layers - because TopLayer extends BaseLayer, both the BaseLayer and TopLayer environments are setup when the TestSpecifyingNoLayer tests are run. >>> TestSpecifyingNoLayer.layer = TopLayer >>> runner = Runner(options=fresh_options(), args=[], found_suites=[umbrella_suite]) >>> succeeded = runner.run() Running BaseLayer tests: Set up BaseLayer in N.NNN seconds. Ran 2 tests with 0 failures and 0 errors in N.NNN seconds. Running TopLayer tests: Set up TopLayer in N.NNN seconds. Ran 2 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down TopLayer in N.NNN seconds. Tear down BaseLayer in N.NNN seconds. Total: 4 tests, 0 failures, 0 errors in N.NNN seconds. If we inspect our trace of what methods got called in what order, we can see that the layer setup and teardown methods only got called once. We can also see that the layer's test setup and teardown methods got called for each test using that layer in the right order. >>> def report(): ... for record in log_handler.records: ... print record.getMessage() >>> report() BaseLayer.setUp BaseLayer.testSetUp TestSpecifyingBaseLayer.setUp TestSpecifyingBaseLayer.test1 TestSpecifyingBaseLayer.tearDown BaseLayer.testTearDown BaseLayer.testSetUp TestSpecifyingBaseLayer.setUp TestSpecifyingBaseLayer.test2 TestSpecifyingBaseLayer.tearDown BaseLayer.testTearDown TopLayer.setUp BaseLayer.testSetUp TopLayer.testSetUp TestSpecifyingNoLayer.setUp TestSpecifyingNoLayer.test TestSpecifyingNoLayer.tearDown TopLayer.testTearDown BaseLayer.testTearDown BaseLayer.testSetUp TopLayer.testSetUp TestSpecifyingNoLayer.setUp TestSpecifyingNoLayer.test TestSpecifyingNoLayer.tearDown TopLayer.testTearDown BaseLayer.testTearDown TopLayer.tearDown BaseLayer.tearDown Now lets stack a few more layers to ensure that our setUp and tearDown methods are called in the correct order. >>> from zope.testing.testrunner.find import name_from_layer >>> class A(object): ... def setUp(cls): ... log('%s.setUp' % name_from_layer(cls)) ... setUp = classmethod(setUp) ... ... def tearDown(cls): ... log('%s.tearDown' % name_from_layer(cls)) ... tearDown = classmethod(tearDown) ... ... def testSetUp(cls): ... log('%s.testSetUp' % name_from_layer(cls)) ... testSetUp = classmethod(testSetUp) ... ... def testTearDown(cls): ... log('%s.testTearDown' % name_from_layer(cls)) ... testTearDown = classmethod(testTearDown) ... >>> class B(A): pass >>> class C(B): pass >>> class D(A): pass >>> class E(D): pass >>> class F(C,E): pass >>> class DeepTest(unittest.TestCase): ... layer = F ... def test(self): ... pass >>> suite = unittest.makeSuite(DeepTest) >>> log_handler.clear() >>> runner = Runner(options=fresh_options(), args=[], found_suites=[suite]) >>> succeeded = runner.run() Running F tests: Set up A in N.NNN seconds. Set up B in N.NNN seconds. Set up C in N.NNN seconds. Set up D in N.NNN seconds. Set up E in N.NNN seconds. Set up F in N.NNN seconds. Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down F in N.NNN seconds. Tear down E in N.NNN seconds. Tear down D in N.NNN seconds. Tear down C in N.NNN seconds. Tear down B in N.NNN seconds. Tear down A in N.NNN seconds. >>> report() A.setUp B.setUp C.setUp D.setUp E.setUp F.setUp A.testSetUp B.testSetUp C.testSetUp D.testSetUp E.testSetUp F.testSetUp F.testTearDown E.testTearDown D.testTearDown C.testTearDown B.testTearDown A.testTearDown F.tearDown E.tearDown D.tearDown C.tearDown B.tearDown A.tearDown Layer Selection =============== We can select which layers to run using the --layer option: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --layer 112 --layer Unit'.split() >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running samplelayers.Layer112 tests: Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer112 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running zope.testing.testrunner.layer.UnitTests tests: Tear down samplelayers.Layer112 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 226 tests, 0 failures, 0 errors in N.NNN seconds. False We can also specify that we want to run only the unit tests: >>> sys.argv = 'test -u'.split() >>> testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Or that we want to run all of the tests except for the unit tests: >>> sys.argv = 'test -f'.split() >>> testrunner.run_internal(defaults) Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in N.NNN seconds. Ran 9 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer11 tests: Set up samplelayers.Layer11 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer111 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer112 tests: Tear down samplelayers.Layer111 in N.NNN seconds. Set up samplelayers.Layer112 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer12 tests: Tear down samplelayers.Layer112 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer12 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer121 tests: Set up samplelayers.Layer121 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer122 tests: Tear down samplelayers.Layer121 in N.NNN seconds. Set up samplelayers.Layer122 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Total: 213 tests, 0 failures, 0 errors in N.NNN seconds. False Or we can explicitly say that we want both unit and non-unit tests. >>> sys.argv = 'test -uf'.split() >>> testrunner.run_internal(defaults) Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in N.NNN seconds. Ran 9 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer11 tests: Set up samplelayers.Layer11 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer111 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer112 tests: Tear down samplelayers.Layer111 in N.NNN seconds. Set up samplelayers.Layer112 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer12 tests: Tear down samplelayers.Layer112 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer12 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer121 tests: Set up samplelayers.Layer121 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer122 tests: Tear down samplelayers.Layer121 in N.NNN seconds. Set up samplelayers.Layer122 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running zope.testing.testrunner.layer.UnitTests tests: Tear down samplelayers.Layer122 in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 405 tests, 0 failures, 0 errors in N.NNN seconds. False It is possible to force the layers to run in subprocesses and parallelize them. >>> sys.argv = [testrunner_script, '-j2'] >>> testrunner.run_internal(defaults) # doctest: +REPORT_NDIFF Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in N.NNN seconds. Ran 9 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer11 tests: Running in a subprocess. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer11 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Running samplelayers.Layer111 tests: Running in a subprocess. Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer111 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tear down samplelayers.Layer111 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Running samplelayers.Layer112 tests: Running in a subprocess. Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer112 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tear down samplelayers.Layer112 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Running samplelayers.Layer12 tests: Running in a subprocess. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer12 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Running samplelayers.Layer121 tests: Running in a subprocess. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer12 in N.NNN seconds. Set up samplelayers.Layer121 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tear down samplelayers.Layer121 in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Running samplelayers.Layer122 tests: Running in a subprocess. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer12 in N.NNN seconds. Set up samplelayers.Layer122 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tear down samplelayers.Layer122 in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Running zope.testing.testrunner.layer.UnitTests tests: Running in a subprocess. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Tearing down left over layers: Tear down samplelayers.Layer1 in N.NNN seconds. Total: 405 tests, 0 failures, 0 errors in N.NNN seconds. False Passing arguments explicitly ============================ In most of the examples here, we set up `sys.argv`. In normal usage, the testrunner just uses `sys.argv`. It is possible to pass arguments explicitly. >>> import os.path >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults, 'test --layer 111'.split()) Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer111 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down samplelayers.Layer111 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. False If options already have default values, then passing a different default will override. For example, --list-tests defaults to being turned off, but if we pass in a different default, that one takes effect. >>> defaults = [ ... '--list-tests', ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults, 'test --layer 111'.split()) Listing samplelayers.Layer111 tests: test_x1 (sample1.sampletests.test111.TestA) test_y0 (sample1.sampletests.test111.TestA) test_z0 (sample1.sampletests.test111.TestA) test_x0 (sample1.sampletests.test111.TestB) test_y1 (sample1.sampletests.test111.TestB) test_z0 (sample1.sampletests.test111.TestB) test_1 (sample1.sampletests.test111.TestNotMuch) test_2 (sample1.sampletests.test111.TestNotMuch) test_3 (sample1.sampletests.test111.TestNotMuch) test_x0 (sample1.sampletests.test111) test_y0 (sample1.sampletests.test111) test_z1 (sample1.sampletests.test111) /home/benji/workspace/zope.testing/1/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/../../sampletestsl.txt test_x1 (sampletests.test111.TestA) test_y0 (sampletests.test111.TestA) test_z0 (sampletests.test111.TestA) test_x0 (sampletests.test111.TestB) test_y1 (sampletests.test111.TestB) test_z0 (sampletests.test111.TestB) test_1 (sampletests.test111.TestNotMuch) test_2 (sampletests.test111.TestNotMuch) test_3 (sampletests.test111.TestNotMuch) test_x0 (sampletests.test111) test_y0 (sampletests.test111) test_z1 (sampletests.test111) /home/benji/workspace/zope.testing/1/src/zope/testing/testrunner/testrunner-ex/sampletests/../sampletestsl.txt False Verbose Output ============== Normally, we just get a summary. We can use the -v option to get increasingly more information. If we use a single --verbose (-v) option, we get a dot printed for each test: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --layer 122 -v'.split() >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: .................................. Ran 34 tests with 0 failures and 0 errors in 0.007 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False If there are more than 50 tests, the dots are printed in groups of 50: >>> sys.argv = 'test -uv'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: ................................................................................................................................................................................................ Ran 192 tests with 0 failures and 0 errors in 0.035 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False If the --verbose (-v) option is used twice, then the name and location of each test is printed as it is run: >>> sys.argv = 'test --layer 122 -vv'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: test_x1 (sample1.sampletests.test122.TestA) test_y0 (sample1.sampletests.test122.TestA) test_z0 (sample1.sampletests.test122.TestA) test_x0 (sample1.sampletests.test122.TestB) test_y1 (sample1.sampletests.test122.TestB) test_z0 (sample1.sampletests.test122.TestB) test_1 (sample1.sampletests.test122.TestNotMuch) test_2 (sample1.sampletests.test122.TestNotMuch) test_3 (sample1.sampletests.test122.TestNotMuch) test_x0 (sample1.sampletests.test122) test_y0 (sample1.sampletests.test122) test_z1 (sample1.sampletests.test122) testrunner-ex/sample1/sampletests/../../sampletestsl.txt test_x1 (sampletests.test122.TestA) test_y0 (sampletests.test122.TestA) test_z0 (sampletests.test122.TestA) test_x0 (sampletests.test122.TestB) test_y1 (sampletests.test122.TestB) test_z0 (sampletests.test122.TestB) test_1 (sampletests.test122.TestNotMuch) test_2 (sampletests.test122.TestNotMuch) test_3 (sampletests.test122.TestNotMuch) test_x0 (sampletests.test122) test_y0 (sampletests.test122) test_z1 (sampletests.test122) testrunner-ex/sampletests/../sampletestsl.txt Ran 34 tests with 0 failures and 0 errors in 0.009 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False if the --verbose (-v) option is used three times, then individual test-execution times are printed: >>> sys.argv = 'test --layer 122 -vvv'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: test_x1 (sample1.sampletests.test122.TestA) (0.000 s) test_y0 (sample1.sampletests.test122.TestA) (0.000 s) test_z0 (sample1.sampletests.test122.TestA) (0.000 s) test_x0 (sample1.sampletests.test122.TestB) (0.000 s) test_y1 (sample1.sampletests.test122.TestB) (0.000 s) test_z0 (sample1.sampletests.test122.TestB) (0.000 s) test_1 (sample1.sampletests.test122.TestNotMuch) (0.000 s) test_2 (sample1.sampletests.test122.TestNotMuch) (0.000 s) test_3 (sample1.sampletests.test122.TestNotMuch) (0.000 s) test_x0 (sample1.sampletests.test122) (0.001 s) test_y0 (sample1.sampletests.test122) (0.001 s) test_z1 (sample1.sampletests.test122) (0.001 s) testrunner-ex/sample1/sampletests/../../sampletestsl.txt (0.001 s) test_x1 (sampletests.test122.TestA) (0.000 s) test_y0 (sampletests.test122.TestA) (0.000 s) test_z0 (sampletests.test122.TestA) (0.000 s) test_x0 (sampletests.test122.TestB) (0.000 s) test_y1 (sampletests.test122.TestB) (0.000 s) test_z0 (sampletests.test122.TestB) (0.000 s) test_1 (sampletests.test122.TestNotMuch) (0.000 s) test_2 (sampletests.test122.TestNotMuch) (0.000 s) test_3 (sampletests.test122.TestNotMuch) (0.000 s) test_x0 (sampletests.test122) (0.001 s) test_y0 (sampletests.test122) (0.001 s) test_z1 (sampletests.test122) (0.001 s) testrunner-ex/sampletests/../sampletestsl.txt (0.001 s) Ran 34 tests with 0 failures and 0 errors in 0.009 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False Quiet output ------------ The --quiet (-q) option cancels all verbose options. It's useful when the default verbosity is non-zero: >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... '-v' ... ] >>> sys.argv = 'test -q -u'.split() >>> testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in 0.034 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Test Selection ============== We've already seen that we can select tests by layer. There are three other ways we can select tests. We can select tests by package: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --layer 122 -ssample1 -vv'.split() >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: test_x1 (sample1.sampletests.test122.TestA) test_y0 (sample1.sampletests.test122.TestA) test_z0 (sample1.sampletests.test122.TestA) test_x0 (sample1.sampletests.test122.TestB) test_y1 (sample1.sampletests.test122.TestB) test_z0 (sample1.sampletests.test122.TestB) test_1 (sample1.sampletests.test122.TestNotMuch) test_2 (sample1.sampletests.test122.TestNotMuch) test_3 (sample1.sampletests.test122.TestNotMuch) test_x0 (sample1.sampletests.test122) test_y0 (sample1.sampletests.test122) test_z1 (sample1.sampletests.test122) testrunner-ex/sample1/sampletests/../../sampletestsl.txt Ran 17 tests with 0 failures and 0 errors in 0.005 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False You can specify multiple packages: >>> sys.argv = 'test -u -vv -ssample1 -ssample2'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_x1 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletestsf.TestA) test_z0 (sample1.sampletestsf.TestA) test_x0 (sample1.sampletestsf.TestB) test_y1 (sample1.sampletestsf.TestB) test_z0 (sample1.sampletestsf.TestB) test_1 (sample1.sampletestsf.TestNotMuch) test_2 (sample1.sampletestsf.TestNotMuch) test_3 (sample1.sampletestsf.TestNotMuch) test_x0 (sample1.sampletestsf) test_y0 (sample1.sampletestsf) test_z1 (sample1.sampletestsf) testrunner-ex/sample1/../sampletests.txt test_x1 (sample1.sample11.sampletests.TestA) test_y0 (sample1.sample11.sampletests.TestA) test_z0 (sample1.sample11.sampletests.TestA) test_x0 (sample1.sample11.sampletests.TestB) test_y1 (sample1.sample11.sampletests.TestB) test_z0 (sample1.sample11.sampletests.TestB) test_1 (sample1.sample11.sampletests.TestNotMuch) test_2 (sample1.sample11.sampletests.TestNotMuch) test_3 (sample1.sample11.sampletests.TestNotMuch) test_x0 (sample1.sample11.sampletests) test_y0 (sample1.sample11.sampletests) test_z1 (sample1.sample11.sampletests) testrunner-ex/sample1/sample11/../../sampletests.txt test_x1 (sample1.sample13.sampletests.TestA) test_y0 (sample1.sample13.sampletests.TestA) test_z0 (sample1.sample13.sampletests.TestA) test_x0 (sample1.sample13.sampletests.TestB) test_y1 (sample1.sample13.sampletests.TestB) test_z0 (sample1.sample13.sampletests.TestB) test_1 (sample1.sample13.sampletests.TestNotMuch) test_2 (sample1.sample13.sampletests.TestNotMuch) test_3 (sample1.sample13.sampletests.TestNotMuch) test_x0 (sample1.sample13.sampletests) test_y0 (sample1.sample13.sampletests) test_z1 (sample1.sample13.sampletests) testrunner-ex/sample1/sample13/../../sampletests.txt test_x1 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletests.test1.TestA) test_z0 (sample1.sampletests.test1.TestA) test_x0 (sample1.sampletests.test1.TestB) test_y1 (sample1.sampletests.test1.TestB) test_z0 (sample1.sampletests.test1.TestB) test_1 (sample1.sampletests.test1.TestNotMuch) test_2 (sample1.sampletests.test1.TestNotMuch) test_3 (sample1.sampletests.test1.TestNotMuch) test_x0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test1) test_z1 (sample1.sampletests.test1) testrunner-ex/sample1/sampletests/../../sampletests.txt test_x1 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sampletests.test_one.TestA) test_z0 (sample1.sampletests.test_one.TestA) test_x0 (sample1.sampletests.test_one.TestB) test_y1 (sample1.sampletests.test_one.TestB) test_z0 (sample1.sampletests.test_one.TestB) test_1 (sample1.sampletests.test_one.TestNotMuch) test_2 (sample1.sampletests.test_one.TestNotMuch) test_3 (sample1.sampletests.test_one.TestNotMuch) test_x0 (sample1.sampletests.test_one) test_y0 (sample1.sampletests.test_one) test_z1 (sample1.sampletests.test_one) testrunner-ex/sample1/sampletests/../../sampletests.txt test_x1 (sample2.sample21.sampletests.TestA) test_y0 (sample2.sample21.sampletests.TestA) test_z0 (sample2.sample21.sampletests.TestA) test_x0 (sample2.sample21.sampletests.TestB) test_y1 (sample2.sample21.sampletests.TestB) test_z0 (sample2.sample21.sampletests.TestB) test_1 (sample2.sample21.sampletests.TestNotMuch) test_2 (sample2.sample21.sampletests.TestNotMuch) test_3 (sample2.sample21.sampletests.TestNotMuch) test_x0 (sample2.sample21.sampletests) test_y0 (sample2.sample21.sampletests) test_z1 (sample2.sample21.sampletests) testrunner-ex/sample2/sample21/../../sampletests.txt test_x1 (sample2.sampletests.test_1.TestA) test_y0 (sample2.sampletests.test_1.TestA) test_z0 (sample2.sampletests.test_1.TestA) test_x0 (sample2.sampletests.test_1.TestB) test_y1 (sample2.sampletests.test_1.TestB) test_z0 (sample2.sampletests.test_1.TestB) test_1 (sample2.sampletests.test_1.TestNotMuch) test_2 (sample2.sampletests.test_1.TestNotMuch) test_3 (sample2.sampletests.test_1.TestNotMuch) test_x0 (sample2.sampletests.test_1) test_y0 (sample2.sampletests.test_1) test_z1 (sample2.sampletests.test_1) testrunner-ex/sample2/sampletests/../../sampletests.txt test_x1 (sample2.sampletests.testone.TestA) test_y0 (sample2.sampletests.testone.TestA) test_z0 (sample2.sampletests.testone.TestA) test_x0 (sample2.sampletests.testone.TestB) test_y1 (sample2.sampletests.testone.TestB) test_z0 (sample2.sampletests.testone.TestB) test_1 (sample2.sampletests.testone.TestNotMuch) test_2 (sample2.sampletests.testone.TestNotMuch) test_3 (sample2.sampletests.testone.TestNotMuch) test_x0 (sample2.sampletests.testone) test_y0 (sample2.sampletests.testone) test_z1 (sample2.sampletests.testone) testrunner-ex/sample2/sampletests/../../sampletests.txt Ran 128 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False You can specify directory names instead of packages (useful for tab-completion): >>> subdir = os.path.join(directory_with_tests, 'sample1') >>> sys.argv = ['test', '--layer', '122', '-s', subdir, '-vv'] >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: test_x1 (sample1.sampletests.test122.TestA) test_y0 (sample1.sampletests.test122.TestA) test_z0 (sample1.sampletests.test122.TestA) test_x0 (sample1.sampletests.test122.TestB) test_y1 (sample1.sampletests.test122.TestB) test_z0 (sample1.sampletests.test122.TestB) test_1 (sample1.sampletests.test122.TestNotMuch) test_2 (sample1.sampletests.test122.TestNotMuch) test_3 (sample1.sampletests.test122.TestNotMuch) test_x0 (sample1.sampletests.test122) test_y0 (sample1.sampletests.test122) test_z1 (sample1.sampletests.test122) testrunner-ex/sample1/sampletests/../../sampletestsl.txt Ran 17 tests with 0 failures and 0 errors in 0.005 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False We can select by test module name using the --module (-m) option: >>> sys.argv = 'test -u -vv -ssample1 -m_one -mtest1'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_x1 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletests.test1.TestA) test_z0 (sample1.sampletests.test1.TestA) test_x0 (sample1.sampletests.test1.TestB) test_y1 (sample1.sampletests.test1.TestB) test_z0 (sample1.sampletests.test1.TestB) test_1 (sample1.sampletests.test1.TestNotMuch) test_2 (sample1.sampletests.test1.TestNotMuch) test_3 (sample1.sampletests.test1.TestNotMuch) test_x0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test1) test_z1 (sample1.sampletests.test1) testrunner-ex/sample1/sampletests/../../sampletests.txt test_x1 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sampletests.test_one.TestA) test_z0 (sample1.sampletests.test_one.TestA) test_x0 (sample1.sampletests.test_one.TestB) test_y1 (sample1.sampletests.test_one.TestB) test_z0 (sample1.sampletests.test_one.TestB) test_1 (sample1.sampletests.test_one.TestNotMuch) test_2 (sample1.sampletests.test_one.TestNotMuch) test_3 (sample1.sampletests.test_one.TestNotMuch) test_x0 (sample1.sampletests.test_one) test_y0 (sample1.sampletests.test_one) test_z1 (sample1.sampletests.test_one) testrunner-ex/sample1/sampletests/../../sampletests.txt Ran 32 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False and by test within the module using the --test (-t) option: >>> sys.argv = 'test -u -vv -ssample1 -m_one -mtest1 -tx0 -ty0'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_y0 (sample1.sampletests.test1.TestA) test_x0 (sample1.sampletests.test1.TestB) test_x0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test_one.TestA) test_x0 (sample1.sampletests.test_one.TestB) test_x0 (sample1.sampletests.test_one) test_y0 (sample1.sampletests.test_one) Ran 8 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False >>> sys.argv = 'test -u -vv -ssample1 -ttxt'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: testrunner-ex/sample1/../sampletests.txt testrunner-ex/sample1/sample11/../../sampletests.txt testrunner-ex/sample1/sample13/../../sampletests.txt testrunner-ex/sample1/sampletests/../../sampletests.txt testrunner-ex/sample1/sampletests/../../sampletests.txt Ran 20 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False The --module and --test options take regular expressions. If the regular expressions specified begin with '!', then tests that don't match the regular expression are selected: >>> sys.argv = 'test -u -vv -ssample1 -m!sample1[.]sample1'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_x1 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletestsf.TestA) test_z0 (sample1.sampletestsf.TestA) test_x0 (sample1.sampletestsf.TestB) test_y1 (sample1.sampletestsf.TestB) test_z0 (sample1.sampletestsf.TestB) test_1 (sample1.sampletestsf.TestNotMuch) test_2 (sample1.sampletestsf.TestNotMuch) test_3 (sample1.sampletestsf.TestNotMuch) test_x0 (sample1.sampletestsf) test_y0 (sample1.sampletestsf) test_z1 (sample1.sampletestsf) testrunner-ex/sample1/../sampletests.txt test_x1 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletests.test1.TestA) test_z0 (sample1.sampletests.test1.TestA) test_x0 (sample1.sampletests.test1.TestB) test_y1 (sample1.sampletests.test1.TestB) test_z0 (sample1.sampletests.test1.TestB) test_1 (sample1.sampletests.test1.TestNotMuch) test_2 (sample1.sampletests.test1.TestNotMuch) test_3 (sample1.sampletests.test1.TestNotMuch) test_x0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test1) test_z1 (sample1.sampletests.test1) testrunner-ex/sample1/sampletests/../../sampletests.txt test_x1 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sampletests.test_one.TestA) test_z0 (sample1.sampletests.test_one.TestA) test_x0 (sample1.sampletests.test_one.TestB) test_y1 (sample1.sampletests.test_one.TestB) test_z0 (sample1.sampletests.test_one.TestB) test_1 (sample1.sampletests.test_one.TestNotMuch) test_2 (sample1.sampletests.test_one.TestNotMuch) test_3 (sample1.sampletests.test_one.TestNotMuch) test_x0 (sample1.sampletests.test_one) test_y0 (sample1.sampletests.test_one) test_z1 (sample1.sampletests.test_one) testrunner-ex/sample1/sampletests/../../sampletests.txt Ran 48 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Module and test filters can also be given as positional arguments: >>> sys.argv = 'test -u -vv -ssample1 !sample1[.]sample1'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_x1 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletestsf.TestA) test_z0 (sample1.sampletestsf.TestA) test_x0 (sample1.sampletestsf.TestB) test_y1 (sample1.sampletestsf.TestB) test_z0 (sample1.sampletestsf.TestB) test_1 (sample1.sampletestsf.TestNotMuch) test_2 (sample1.sampletestsf.TestNotMuch) test_3 (sample1.sampletestsf.TestNotMuch) test_x0 (sample1.sampletestsf) test_y0 (sample1.sampletestsf) test_z1 (sample1.sampletestsf) testrunner-ex/sample1/../sampletests.txt test_x1 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletests.test1.TestA) test_z0 (sample1.sampletests.test1.TestA) test_x0 (sample1.sampletests.test1.TestB) test_y1 (sample1.sampletests.test1.TestB) test_z0 (sample1.sampletests.test1.TestB) test_1 (sample1.sampletests.test1.TestNotMuch) test_2 (sample1.sampletests.test1.TestNotMuch) test_3 (sample1.sampletests.test1.TestNotMuch) test_x0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test1) test_z1 (sample1.sampletests.test1) testrunner-ex/sample1/sampletests/../../sampletests.txt test_x1 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sampletests.test_one.TestA) test_z0 (sample1.sampletests.test_one.TestA) test_x0 (sample1.sampletests.test_one.TestB) test_y1 (sample1.sampletests.test_one.TestB) test_z0 (sample1.sampletests.test_one.TestB) test_1 (sample1.sampletests.test_one.TestNotMuch) test_2 (sample1.sampletests.test_one.TestNotMuch) test_3 (sample1.sampletests.test_one.TestNotMuch) test_x0 (sample1.sampletests.test_one) test_y0 (sample1.sampletests.test_one) test_z1 (sample1.sampletests.test_one) testrunner-ex/sample1/sampletests/../../sampletests.txt Ran 48 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False >>> sys.argv = 'test -u -vv -ssample1 . txt'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: testrunner-ex/sample1/../sampletests.txt testrunner-ex/sample1/sample11/../../sampletests.txt testrunner-ex/sample1/sample13/../../sampletests.txt testrunner-ex/sample1/sampletests/../../sampletests.txt testrunner-ex/sample1/sampletests/../../sampletests.txt Ran 20 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Sometimes, There are tests that you don't want to run by default. For example, you might have tests that take a long time. Tests can have a level attribute. If no level is specified, a level of 1 is assumed and, by default, only tests at level one are run. to run tests at a higher level, use the --at-level (-a) option to specify a higher level. For example, with the following options: >>> sys.argv = 'test -u -vv -t test_y1 -t test_y0'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_y0 (sampletestsf.TestA) test_y1 (sampletestsf.TestB) test_y0 (sampletestsf) test_y0 (sample1.sampletestsf.TestA) test_y1 (sample1.sampletestsf.TestB) test_y0 (sample1.sampletestsf) test_y0 (sample1.sample11.sampletests.TestA) test_y1 (sample1.sample11.sampletests.TestB) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sample13.sampletests.TestA) test_y1 (sample1.sample13.sampletests.TestB) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sampletests.test1.TestA) test_y1 (sample1.sampletests.test1.TestB) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test_one.TestA) test_y1 (sample1.sampletests.test_one.TestB) test_y0 (sample1.sampletests.test_one) test_y0 (sample2.sample21.sampletests.TestA) test_y1 (sample2.sample21.sampletests.TestB) test_y0 (sample2.sample21.sampletests) test_y0 (sample2.sampletests.test_1.TestA) test_y1 (sample2.sampletests.test_1.TestB) test_y0 (sample2.sampletests.test_1) test_y0 (sample2.sampletests.testone.TestA) test_y1 (sample2.sampletests.testone.TestB) test_y0 (sample2.sampletests.testone) test_y0 (sample3.sampletests.TestA) test_y1 (sample3.sampletests.TestB) test_y0 (sample3.sampletests) test_y0 (sampletests.test1.TestA) test_y1 (sampletests.test1.TestB) test_y0 (sampletests.test1) test_y0 (sampletests.test_one.TestA) test_y1 (sampletests.test_one.TestB) test_y0 (sampletests.test_one) Ran 36 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False We get run 36 tests. If we specify a level of 2, we get some additional tests: >>> sys.argv = 'test -u -vv -a 2 -t test_y1 -t test_y0'.split() >>> testrunner.run_internal(defaults) Running tests at level 2 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_y0 (sampletestsf.TestA) test_y0 (sampletestsf.TestA2) test_y1 (sampletestsf.TestB) test_y0 (sampletestsf) test_y0 (sample1.sampletestsf.TestA) test_y1 (sample1.sampletestsf.TestB) test_y0 (sample1.sampletestsf) test_y0 (sample1.sample11.sampletests.TestA) test_y1 (sample1.sample11.sampletests.TestB) test_y1 (sample1.sample11.sampletests.TestB2) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sample13.sampletests.TestA) test_y1 (sample1.sample13.sampletests.TestB) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sampletests.test1.TestA) test_y1 (sample1.sampletests.test1.TestB) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test_one.TestA) test_y1 (sample1.sampletests.test_one.TestB) test_y0 (sample1.sampletests.test_one) test_y0 (sample2.sample21.sampletests.TestA) test_y1 (sample2.sample21.sampletests.TestB) test_y0 (sample2.sample21.sampletests) test_y0 (sample2.sampletests.test_1.TestA) test_y1 (sample2.sampletests.test_1.TestB) test_y0 (sample2.sampletests.test_1) test_y0 (sample2.sampletests.testone.TestA) test_y1 (sample2.sampletests.testone.TestB) test_y0 (sample2.sampletests.testone) test_y0 (sample3.sampletests.TestA) test_y1 (sample3.sampletests.TestB) test_y0 (sample3.sampletests) test_y0 (sampletests.test1.TestA) test_y1 (sampletests.test1.TestB) test_y0 (sampletests.test1) test_y0 (sampletests.test_one.TestA) test_y1 (sampletests.test_one.TestB) test_y0 (sampletests.test_one) Ran 38 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False We can use the --all option to run tests at all levels: >>> sys.argv = 'test -u -vv --all -t test_y1 -t test_y0'.split() >>> testrunner.run_internal(defaults) Running tests at all levels Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_y0 (sampletestsf.TestA) test_y0 (sampletestsf.TestA2) test_y1 (sampletestsf.TestB) test_y0 (sampletestsf) test_y0 (sample1.sampletestsf.TestA) test_y1 (sample1.sampletestsf.TestB) test_y0 (sample1.sampletestsf) test_y0 (sample1.sample11.sampletests.TestA) test_y0 (sample1.sample11.sampletests.TestA3) test_y1 (sample1.sample11.sampletests.TestB) test_y1 (sample1.sample11.sampletests.TestB2) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sample13.sampletests.TestA) test_y1 (sample1.sample13.sampletests.TestB) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sampletests.test1.TestA) test_y1 (sample1.sampletests.test1.TestB) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test_one.TestA) test_y1 (sample1.sampletests.test_one.TestB) test_y0 (sample1.sampletests.test_one) test_y0 (sample2.sample21.sampletests.TestA) test_y1 (sample2.sample21.sampletests.TestB) test_y0 (sample2.sample21.sampletests) test_y0 (sample2.sampletests.test_1.TestA) test_y1 (sample2.sampletests.test_1.TestB) test_y0 (sample2.sampletests.test_1) test_y0 (sample2.sampletests.testone.TestA) test_y1 (sample2.sampletests.testone.TestB) test_y0 (sample2.sampletests.testone) test_y0 (sample3.sampletests.TestA) test_y1 (sample3.sampletests.TestB) test_y0 (sample3.sampletests) test_y0 (sampletests.test1.TestA) test_y1 (sampletests.test1.TestB) test_y0 (sampletests.test1) test_y0 (sampletests.test_one.TestA) test_y1 (sampletests.test_one.TestB) test_y0 (sampletests.test_one) Ran 39 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Listing Selected Tests ---------------------- When you're trying to figure out why the test you want is not matched by the pattern you specified, it is convenient to see which tests match your specifications. >>> sys.argv = 'test --all -m sample1 -t test_y0 --list-tests'.split() >>> testrunner.run_internal(defaults) Listing samplelayers.Layer11 tests: test_y0 (sample1.sampletests.test11.TestA) test_y0 (sample1.sampletests.test11) Listing samplelayers.Layer111 tests: test_y0 (sample1.sampletests.test111.TestA) test_y0 (sample1.sampletests.test111) Listing samplelayers.Layer112 tests: test_y0 (sample1.sampletests.test112.TestA) test_y0 (sample1.sampletests.test112) Listing samplelayers.Layer12 tests: test_y0 (sample1.sampletests.test12.TestA) test_y0 (sample1.sampletests.test12) Listing samplelayers.Layer121 tests: test_y0 (sample1.sampletests.test121.TestA) test_y0 (sample1.sampletests.test121) Listing samplelayers.Layer122 tests: test_y0 (sample1.sampletests.test122.TestA) test_y0 (sample1.sampletests.test122) Listing zope.testing.testrunner.layer.UnitTests tests: test_y0 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletestsf) test_y0 (sample1.sample11.sampletests.TestA) test_y0 (sample1.sample11.sampletests.TestA3) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sample13.sampletests.TestA) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sampletests.test_one) False Test Progress ============= If the --progress (-p) option is used, progress information is printed and a carriage return (rather than a new-line) is printed between detail lines. Let's look at the effect of --progress (-p) at different levels of verbosity. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --layer 122 -p'.split() >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: 1/34 (2.9%)##r## ##r## 2/34 (5.9%)##r## ##r## 3/34 (8.8%)##r## ##r## 4/34 (11.8%)##r## ##r## 5/34 (14.7%)##r## ##r## 6/34 (17.6%)##r## ##r## 7/34 (20.6%)##r## ##r## 8/34 (23.5%)##r## ##r## 9/34 (26.5%)##r## ##r## 10/34 (29.4%)##r## ##r## 11/34 (32.4%)##r## ##r## 12/34 (35.3%)##r## ##r## 17/34 (50.0%)##r## ##r## 18/34 (52.9%)##r## ##r## 19/34 (55.9%)##r## ##r## 20/34 (58.8%)##r## ##r## 21/34 (61.8%)##r## ##r## 22/34 (64.7%)##r## ##r## 23/34 (67.6%)##r## ##r## 24/34 (70.6%)##r## ##r## 25/34 (73.5%)##r## ##r## 26/34 (76.5%)##r## ##r## 27/34 (79.4%)##r## ##r## 28/34 (82.4%)##r## ##r## 29/34 (85.3%)##r## ##r## 34/34 (100.0%)##r## ##r## Ran 34 tests with 0 failures and 0 errors in 0.008 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False (Note that, in the examples above and below, we show "##r##" followed by new lines where carriage returns would appear in actual output.) Using a single level of verbosity causes test descriptions to be output, but only if they fit in the terminal width. The default width, when the terminal width can't be determined, is 80: >>> sys.argv = 'test --layer 122 -pv'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: 1/34 (2.9%) test_x1 (sample1.sampletests.test122.TestA)##r## ##r## 2/34 (5.9%) test_y0 (sample1.sampletests.test122.TestA)##r## ##r## 3/34 (8.8%) test_z0 (sample1.sampletests.test122.TestA)##r## ##r## 4/34 (11.8%) test_x0 (sample1.sampletests.test122.TestB)##r## ##r## 5/34 (14.7%) test_y1 (sample1.sampletests.test122.TestB)##r## ##r## 6/34 (17.6%) test_z0 (sample1.sampletests.test122.TestB)##r## ##r## 7/34 (20.6%) test_1 (sample1.sampletests.test122.TestNotMuch)##r## ##r## 8/34 (23.5%) test_2 (sample1.sampletests.test122.TestNotMuch)##r## ##r## 9/34 (26.5%) test_3 (sample1.sampletests.test122.TestNotMuch)##r## ##r## 10/34 (29.4%) test_x0 (sample1.sampletests.test122)##r## ##r## 11/34 (32.4%) test_y0 (sample1.sampletests.test122)##r## ##r## 12/34 (35.3%) test_z1 (sample1.sampletests.test122)##r## ##r## 17/34 (50.0%) ... /testrunner-ex/sample1/sampletests/../../sampletestsl.txt##r## ##r## 18/34 (52.9%) test_x1 (sampletests.test122.TestA)##r## ##r## 19/34 (55.9%) test_y0 (sampletests.test122.TestA)##r## ##r## 20/34 (58.8%) test_z0 (sampletests.test122.TestA)##r## ##r## 21/34 (61.8%) test_x0 (sampletests.test122.TestB)##r## ##r## 22/34 (64.7%) test_y1 (sampletests.test122.TestB)##r## ##r## 23/34 (67.6%) test_z0 (sampletests.test122.TestB)##r## ##r## 24/34 (70.6%) test_1 (sampletests.test122.TestNotMuch)##r## ##r## 25/34 (73.5%) test_2 (sampletests.test122.TestNotMuch)##r## ##r## 26/34 (76.5%) test_3 (sampletests.test122.TestNotMuch)##r## ##r## 27/34 (79.4%) test_x0 (sampletests.test122)##r## ##r## 28/34 (82.4%) test_y0 (sampletests.test122)##r## ##r## 29/34 (85.3%) test_z1 (sampletests.test122)##r## ##r## 34/34 (100.0%) ... pe/testing/testrunner-ex/sampletests/../sampletestsl.txt##r## ##r## Ran 34 tests with 0 failures and 0 errors in 0.008 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False The terminal width is determined using the curses module. To see that, we'll provide a fake curses module: >>> class FakeCurses: ... def setupterm(self): ... pass ... def tigetnum(self, ignored): ... return 60 >>> old_curses = sys.modules.get('curses') >>> sys.modules['curses'] = FakeCurses() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: 1/34 (2.9%) test_x1 (sample1.sampletests.test122.TestA)##r## ##r## 2/34 (5.9%) test_y0 (sample1.sampletests.test122.TestA)##r## ##r## 3/34 (8.8%) test_z0 (sample1.sampletests.test122.TestA)##r## ##r## 4/34 (11.8%) test_x0 (...le1.sampletests.test122.TestB)##r## ##r## 5/34 (14.7%) test_y1 (...le1.sampletests.test122.TestB)##r## ##r## 6/34 (17.6%) test_z0 (...le1.sampletests.test122.TestB)##r## ##r## 7/34 (20.6%) test_1 (...ampletests.test122.TestNotMuch)##r## ##r## 8/34 (23.5%) test_2 (...ampletests.test122.TestNotMuch)##r## ##r## 9/34 (26.5%) test_3 (...ampletests.test122.TestNotMuch)##r## ##r## 10/34 (29.4%) test_x0 (sample1.sampletests.test122)##r## ##r## 11/34 (32.4%) test_y0 (sample1.sampletests.test122)##r## ##r## 12/34 (35.3%) test_z1 (sample1.sampletests.test122)##r## ##r## 17/34 (50.0%) ... e1/sampletests/../../sampletestsl.txt##r## ##r## 18/34 (52.9%) test_x1 (sampletests.test122.TestA)##r## ##r## 19/34 (55.9%) test_y0 (sampletests.test122.TestA)##r## ##r## 20/34 (58.8%) test_z0 (sampletests.test122.TestA)##r## ##r## 21/34 (61.8%) test_x0 (sampletests.test122.TestB)##r## ##r## 22/34 (64.7%) test_y1 (sampletests.test122.TestB)##r## ##r## 23/34 (67.6%) test_z0 (sampletests.test122.TestB)##r## ##r## 24/34 (70.6%) test_1 (sampletests.test122.TestNotMuch)##r## ##r## 25/34 (73.5%) test_2 (sampletests.test122.TestNotMuch)##r## ##r## 26/34 (76.5%) test_3 (sampletests.test122.TestNotMuch)##r## ##r## 27/34 (79.4%) test_x0 (sampletests.test122)##r## ##r## 28/34 (82.4%) test_y0 (sampletests.test122)##r## ##r## 29/34 (85.3%) test_z1 (sampletests.test122)##r## ##r## 34/34 (100.0%) ... r-ex/sampletests/../sampletestsl.txt##r## ##r## Ran 34 tests with 0 failures and 0 errors in 0.008 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False >>> sys.modules['curses'] = old_curses If a second or third level of verbosity are added, we get additional information. >>> sys.argv = 'test --layer 122 -pvv -t !txt'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: 1/24 (4.2%) test_x1 (sample1.sampletests.test122.TestA)##r## ##r## 2/24 (8.3%) test_y0 (sample1.sampletests.test122.TestA)##r## ##r## 3/24 (12.5%) test_z0 (sample1.sampletests.test122.TestA)##r## ##r## 4/24 (16.7%) test_x0 (sample1.sampletests.test122.TestB)##r## ##r## 5/24 (20.8%) test_y1 (sample1.sampletests.test122.TestB)##r## ##r## 6/24 (25.0%) test_z0 (sample1.sampletests.test122.TestB)##r## ##r## 7/24 (29.2%) test_1 (sample1.sampletests.test122.TestNotMuch)##r## ##r## 8/24 (33.3%) test_2 (sample1.sampletests.test122.TestNotMuch)##r## ##r## 9/24 (37.5%) test_3 (sample1.sampletests.test122.TestNotMuch)##r## ##r## 10/24 (41.7%) test_x0 (sample1.sampletests.test122)##r## ##r## 11/24 (45.8%) test_y0 (sample1.sampletests.test122)##r## ##r## 12/24 (50.0%) test_z1 (sample1.sampletests.test122)##r## ##r## 13/24 (54.2%) test_x1 (sampletests.test122.TestA)##r## ##r## 14/24 (58.3%) test_y0 (sampletests.test122.TestA)##r## ##r## 15/24 (62.5%) test_z0 (sampletests.test122.TestA)##r## ##r## 16/24 (66.7%) test_x0 (sampletests.test122.TestB)##r## ##r## 17/24 (70.8%) test_y1 (sampletests.test122.TestB)##r## ##r## 18/24 (75.0%) test_z0 (sampletests.test122.TestB)##r## ##r## 19/24 (79.2%) test_1 (sampletests.test122.TestNotMuch)##r## ##r## 20/24 (83.3%) test_2 (sampletests.test122.TestNotMuch)##r## ##r## 21/24 (87.5%) test_3 (sampletests.test122.TestNotMuch)##r## ##r## 22/24 (91.7%) test_x0 (sampletests.test122)##r## ##r## 23/24 (95.8%) test_y0 (sampletests.test122)##r## ##r## 24/24 (100.0%) test_z1 (sampletests.test122)##r## ##r## Ran 24 tests with 0 failures and 0 errors in 0.006 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False Note that, in this example, we used a test-selection pattern starting with '!' to exclude tests containing the string "txt". >>> sys.argv = 'test --layer 122 -pvvv -t!(txt|NotMuch)'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: 1/18 (5.6%) test_x1 (sample1.sampletests.test122.TestA) (0.000 s)##r## ##r## 2/18 (11.1%) test_y0 (sample1.sampletests.test122.TestA) (0.000 s)##r## ##r## 3/18 (16.7%) test_z0 (sample1.sampletests.test122.TestA) (0.000 s)##r## ##r## 4/18 (22.2%) test_x0 (sample1.sampletests.test122.TestB) (0.000 s)##r## ##r## 5/18 (27.8%) test_y1 (sample1.sampletests.test122.TestB) (0.000 s)##r## ##r## 6/18 (33.3%) test_z0 (sample1.sampletests.test122.TestB) (0.000 s)##r## ##r## 7/18 (38.9%) test_x0 (sample1.sampletests.test122) (0.001 s)##r## ##r## 8/18 (44.4%) test_y0 (sample1.sampletests.test122) (0.001 s)##r## ##r## 9/18 (50.0%) test_z1 (sample1.sampletests.test122) (0.001 s)##r## ##r## 10/18 (55.6%) test_x1 (sampletests.test122.TestA) (0.000 s)##r## ##r## 11/18 (61.1%) test_y0 (sampletests.test122.TestA) (0.000 s)##r## ##r## 12/18 (66.7%) test_z0 (sampletests.test122.TestA) (0.000 s)##r## ##r## 13/18 (72.2%) test_x0 (sampletests.test122.TestB) (0.000 s)##r## ##r## 14/18 (77.8%) test_y1 (sampletests.test122.TestB) (0.000 s)##r## ##r## 15/18 (83.3%) test_z0 (sampletests.test122.TestB) (0.000 s)##r## ##r## 16/18 (88.9%) test_x0 (sampletests.test122) (0.001 s)##r## ##r## 17/18 (94.4%) test_y0 (sampletests.test122) (0.001 s)##r## ##r## 18/18 (100.0%) test_z1 (sampletests.test122) (0.001 s)##r## ##r## Ran 18 tests with 0 failures and 0 errors in 0.006 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False In this example, we also excluded tests with "NotMuch" in their names. Unfortunately, the time data above doesn't buy us much because, in practice, the line is cleared before there is time to see the times. :/ Autodetecting progress ---------------------- The --auto-progress option will determine if stdout is a terminal, and only enable progress output if so. Let's pretend we have a terminal >>> class Terminal(object): ... def __init__(self, stream): ... self._stream = stream ... def __getattr__(self, attr): ... return getattr(self._stream, attr) ... def isatty(self): ... return True >>> real_stdout = sys.stdout >>> sys.stdout = Terminal(sys.stdout) >>> sys.argv = 'test -u -t test_one.TestNotMuch --auto-progress'.split() >>> testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: 1/6 (16.7%)##r## ##r## 2/6 (33.3%)##r## ##r## 3/6 (50.0%)##r## ##r## 4/6 (66.7%)##r## ##r## 5/6 (83.3%)##r## ##r## 6/6 (100.0%)##r## ##r## Ran 6 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Let's stop pretending >>> sys.stdout = real_stdout >>> sys.argv = 'test -u -t test_one.TestNotMuch --auto-progress'.split() >>> testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 6 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Disabling progress indication ----------------------------- If -p or --progress have been previously provided on the command line (perhaps by a wrapper script) but you do not desire progress indication, you can switch it off with --no-progress: >>> sys.argv = 'test -u -t test_one.TestNotMuch -p --no-progress'.split() >>> testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 6 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Debugging ========= The testrunner module supports post-mortem debugging and debugging using `pdb.set_trace`. Let's look first at using `pdb.set_trace`. To demonstrate this, we'll provide input via helper Input objects: >>> class Input: ... def __init__(self, src): ... self.lines = src.split('\n') ... def readline(self): ... line = self.lines.pop(0) ... print line ... return line+'\n' If a test or code called by a test calls pdb.set_trace, then the runner will enter pdb at that point: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> from zope.testing import testrunner >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> real_stdin = sys.stdin >>> if sys.version_info[:2] == (2, 3): ... sys.stdin = Input('n\np x\nc') ... else: ... sys.stdin = Input('p x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t set_trace1').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +ELLIPSIS Running zope.testing.testrunner.layer.UnitTests tests: ... > testrunner-ex/sample3/sampletests_d.py(27)test_set_trace1() -> y = x (Pdb) p x 1 (Pdb) c Ran 1 tests with 0 failures and 0 errors in 0.001 seconds. ... False Note that, prior to Python 2.4, calling pdb.set_trace caused pdb to break in the pdb.set_trace function. It was necessary to use 'next' or 'up' to get to the application code that called pdb.set_trace. In Python 2.4, pdb.set_trace causes pdb to stop right after the call to pdb.set_trace. You can also do post-mortem debugging, using the --post-mortem (-D) option: >>> sys.stdin = Input('p x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t post_mortem1 -D').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +NORMALIZE_WHITESPACE +REPORT_NDIFF Running zope.testing.testrunner.layer.UnitTests tests: ... Error in test test_post_mortem1 (sample3.sampletests_d.TestSomething) Traceback (most recent call last): File "testrunner-ex/sample3/sampletests_d.py", line 34, in test_post_mortem1 raise ValueError ValueError exceptions.ValueError: > testrunner-ex/sample3/sampletests_d.py(34)test_post_mortem1() -> raise ValueError (Pdb) p x 1 (Pdb) c True Note that the test runner exits after post-mortem debugging. In the example above, we debugged an error. Failures are actually converted to errors and can be debugged the same way: >>> sys.stdin = Input('p x\np y\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t post_mortem_failure1 -D').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +NORMALIZE_WHITESPACE +REPORT_NDIFF Running zope.testing.testrunner.layer.UnitTests tests: ... Error in test test_post_mortem_failure1 (sample3.sampletests_d.TestSomething) Traceback (most recent call last): File ".../unittest.py", line 252, in debug getattr(self, self.__testMethodName)() File "testrunner-ex/sample3/sampletests_d.py", line 42, in test_post_mortem_failure1 assert x == y AssertionError ...AssertionError: > testrunner-ex/sample3/sampletests_d.py(42)test_post_mortem_failure1() -> assert x == y (Pdb) p x 1 (Pdb) p y 2 (Pdb) c True Layers that can't be torn down ============================== A layer can have a tearDown method that raises NotImplementedError. If this is the case and there are no remaining tests to run, the test runner will just note that the tear down couldn't be done: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> from zope.testing import testrunner >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test -ssample2 --tests-pattern sampletests_ntd$'.split() >>> testrunner.run_internal(defaults) Running sample2.sampletests_ntd.Layer tests: Set up sample2.sampletests_ntd.Layer in 0.000 seconds. Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. Tearing down left over layers: Tear down sample2.sampletests_ntd.Layer ... not supported False If the tearDown method raises NotImplementedError and there are remaining layers to run, the test runner will restart itself as a new process, resuming tests where it left off: >>> sys.argv = [testrunner_script, '--tests-pattern', 'sampletests_ntd$'] >>> testrunner.run_internal(defaults) Running sample1.sampletests_ntd.Layer tests: Set up sample1.sampletests_ntd.Layer in N.NNN seconds. Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Running sample2.sampletests_ntd.Layer tests: Tear down sample1.sampletests_ntd.Layer ... not supported Running in a subprocess. Set up sample2.sampletests_ntd.Layer in N.NNN seconds. Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tear down sample2.sampletests_ntd.Layer ... not supported Running sample3.sampletests_ntd.Layer tests: Running in a subprocess. Set up sample3.sampletests_ntd.Layer in N.NNN seconds. Error in test test_error1 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_error1 raise TypeError("Can we see errors") TypeError: Can we see errors Error in test test_error2 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_error2 raise TypeError("I hope so") TypeError: I hope so Failure in test test_fail1 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_fail1 self.assertEqual(1, 2) AssertionError: 1 != 2 Failure in test test_fail2 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_fail2 self.assertEqual(1, 3) AssertionError: 1 != 3 Ran 6 tests with 2 failures and 2 errors in N.NNN seconds. Tear down sample3.sampletests_ntd.Layer ... not supported Total: 8 tests, 2 failures, 2 errors in N.NNN seconds. True in the example above, some of the tests run as a subprocess had errors and failures. They were displayed as usual and the failure and error statistice were updated as usual. Note that debugging doesn't work when running tests in a subprocess: >>> sys.argv = [testrunner_script, '--tests-pattern', 'sampletests_ntd$', ... '-D', ] >>> testrunner.run_internal(defaults) Running sample1.sampletests_ntd.Layer tests: Set up sample1.sampletests_ntd.Layer in N.NNN seconds. Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Running sample2.sampletests_ntd.Layer tests: Tear down sample1.sampletests_ntd.Layer ... not supported Running in a subprocess. Set up sample2.sampletests_ntd.Layer in N.NNN seconds. Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tear down sample2.sampletests_ntd.Layer ... not supported Running sample3.sampletests_ntd.Layer tests: Running in a subprocess. Set up sample3.sampletests_ntd.Layer in N.NNN seconds. Error in test test_error1 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_error1 raise TypeError("Can we see errors") TypeError: Can we see errors ********************************************************************** Can't post-mortem debug when running a layer as a subprocess! ********************************************************************** Error in test test_error2 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_error2 raise TypeError("I hope so") TypeError: I hope so ********************************************************************** Can't post-mortem debug when running a layer as a subprocess! ********************************************************************** Error in test test_fail1 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_fail1 self.assertEqual(1, 2) AssertionError: 1 != 2 ********************************************************************** Can't post-mortem debug when running a layer as a subprocess! ********************************************************************** Error in test test_fail2 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_fail2 self.assertEqual(1, 3) AssertionError: 1 != 3 ********************************************************************** Can't post-mortem debug when running a layer as a subprocess! ********************************************************************** Ran 6 tests with 0 failures and 4 errors in N.NNN seconds. Tear down sample3.sampletests_ntd.Layer ... not supported Total: 8 tests, 0 failures, 4 errors in N.NNN seconds. True Similarly, pdb.set_trace doesn't work when running tests in a layer that is run as a subprocess: >>> sys.argv = [testrunner_script, '--tests-pattern', 'sampletests_ntds'] >>> testrunner.run_internal(defaults) Running sample1.sampletests_ntds.Layer tests: Set up sample1.sampletests_ntds.Layer in 0.000 seconds. Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. Running sample2.sampletests_ntds.Layer tests: Tear down sample1.sampletests_ntds.Layer ... not supported Running in a subprocess. Set up sample2.sampletests_ntds.Layer in 0.000 seconds. --Return-- > testrunner-ex/sample2/sampletests_ntds.py(37)test_something()->None -> import pdb; pdb.set_trace() (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** --Return-- > testrunner-ex/sample2/sampletests_ntds.py(40)test_something2()->None -> import pdb; pdb.set_trace() (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** --Return-- > testrunner-ex/sample2/sampletests_ntds.py(43)test_something3()->None -> import pdb; pdb.set_trace() (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** --Return-- > testrunner-ex/sample2/sampletests_ntds.py(46)test_something4()->None -> import pdb; pdb.set_trace() (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** --Return-- > testrunner-ex/sample2/sampletests_ntds.py(52)f()->None -> import pdb; pdb.set_trace() (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** --Return-- > doctest.py(351)set_trace()->None -> pdb.Pdb.set_trace(self) (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** --Return-- > doctest.py(351)set_trace()->None -> pdb.Pdb.set_trace(self) (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** Ran 7 tests with 0 failures and 0 errors in 0.008 seconds. Tear down sample2.sampletests_ntds.Layer ... not supported Total: 8 tests, 0 failures, 0 errors in N.NNN seconds. False If you want to use pdb from a test in a layer that is run as a subprocess, then rerun the test runner selecting *just* that layer so that it's not run as a subprocess. If a test is run in a subprocess and it generates output on stderr (as stderrtest does), the output is ignored (but it doesn't cause a SubprocessError like it once did). >>> from cStringIO import StringIO >>> real_stderr = sys.stderr >>> sys.stderr = StringIO() >>> sys.argv = [testrunner_script, '-s', 'sample2', '--tests-pattern', ... '(sampletests_ntd$|stderrtest)'] >>> testrunner.run_internal(defaults) Running sample2.sampletests_ntd.Layer tests: Set up sample2.sampletests_ntd.Layer in 0.000 seconds. Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. Running sample2.stderrtest.Layer tests: Tear down sample2.sampletests_ntd.Layer ... not supported Running in a subprocess. Set up sample2.stderrtest.Layer in 0.000 seconds. Ran 1 tests with 0 failures and 0 errors in 0.002 seconds. Tear down sample2.stderrtest.Layer in 0.000 seconds. Total: 2 tests, 0 failures, 0 errors in 0.197 seconds. False >>> print sys.stderr.getvalue() A message on stderr. Please ignore (expected in test output). >>> sys.stderr = real_stderr Code Coverage ============= If the --coverage option is used, test coverage reports will be generated. The directory name given as the parameter will be used to hold the reports. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --coverage=coverage_dir'.split() >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in 0.000 seconds. Ran 9 tests with 0 failures and 0 errors in 0.000 seconds. Running samplelayers.Layer11 tests: Set up samplelayers.Layer11 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.125 seconds. Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in 0.000 seconds. Set up samplelayers.Layer111 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.125 seconds. Running samplelayers.Layer112 tests: Tear down samplelayers.Layer111 in 0.000 seconds. Set up samplelayers.Layer112 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.125 seconds. Running samplelayers.Layer12 tests: Tear down samplelayers.Layer112 in 0.000 seconds. Tear down samplelayers.Layerx in 0.000 seconds. Tear down samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.140 seconds. Running samplelayers.Layer121 tests: Set up samplelayers.Layer121 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.125 seconds. Running samplelayers.Layer122 tests: Tear down samplelayers.Layer121 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.125 seconds. Running zope.testing.testrunner.layer.UnitTests tests: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. Set up zope.testing.testrunner.layer.UnitTests in 0.000 seconds. Ran 192 tests with 0 failures and 0 errors in 0.687 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in 0.000 seconds. lines cov% module (path) ... 48 100% sampletests.test1 (testrunner-ex/sampletests/test1.py) 74 100% sampletests.test11 (testrunner-ex/sampletests/test11.py) 74 100% sampletests.test111 (testrunner-ex/sampletests/test111.py) 76 100% sampletests.test112 (testrunner-ex/sampletests/test112.py) 74 100% sampletests.test12 (testrunner-ex/sampletests/test12.py) 74 100% sampletests.test121 (testrunner-ex/sampletests/test121.py) 74 100% sampletests.test122 (testrunner-ex/sampletests/test122.py) 48 100% sampletests.test_one (testrunner-ex/sampletests/test_one.py) 112 95% sampletestsf (testrunner-ex/sampletestsf.py) Total: 405 tests, 0 failures, 0 errors in 0.630 seconds. False The directory specified with the --coverage option will have been created and will hold the coverage reports. >>> os.path.exists('coverage_dir') True >>> os.listdir('coverage_dir') [...] (We should clean up after ourselves.) >>> import shutil >>> shutil.rmtree('coverage_dir') Ignoring Tests -------------- The ``trace`` module supports ignoring directories and modules based the test selection. Only directories selected for testing should report coverage. The test runner provides a custom implementation of the relevant API. The ``TestIgnore`` class, the class managing the ignoring, is initialized by passing the command line options. It uses the options to determine the directories that should be covered. >>> class FauxOptions(object): ... package = None ... test_path = [('/myproject/src/blah/foo', ''), ... ('/myproject/src/blah/bar', '')] >>> from zope.testing.testrunner import coverage >>> from zope.testing.testrunner.find import test_dirs >>> ignore = coverage.TestIgnore(test_dirs(FauxOptions(), {})) >>> ignore._test_dirs ['/myproject/src/blah/foo/', '/myproject/src/blah/bar/'] We can now ask whether a particular module should be ignored: >>> ignore.names('/myproject/src/blah/foo/baz.py', 'baz') False >>> ignore.names('/myproject/src/blah/bar/mine.py', 'mine') False >>> ignore.names('/myproject/src/blah/foo/__init__.py', 'foo') False >>> ignore.names('/myproject/src/blah/hello.py', 'hello') True When running the test runner, modules are sometimes created from text strings. Those should *always* be ignored: >>> ignore.names('/myproject/src/blah/hello.txt', '') True To make this check fast, the class implements a cache. In an early implementation, the result was cached by the module name, which was a problem, since a lot of modules carry the same name (not the Python dotted name here!). So just because a module has the same name in an ignored and tested directory, does not mean it is always ignored: >>> ignore.names('/myproject/src/blah/module.py', 'module') True >>> ignore.names('/myproject/src/blah/foo/module.py', 'module') False Profiling ========= The testrunner supports hotshot and cProfile profilers. Hotshot profiler support does not work with python2.6 >>> import os.path, sys >>> profiler = '--profile=hotshot' >>> if sys.hexversion >= 0x02060000: ... profiler = '--profile=cProfile' The testrunner includes the ability to profile the test execution with hotshot via the --profile option, if it a python <= 2.6 >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> sys.path.append(directory_with_tests) >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = [testrunner_script, profiler] When the tests are run, we get profiling output. >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running samplelayers.Layer1 tests: ... Running samplelayers.Layer11 tests: ... Running zope.testing.testrunner.layer.UnitTests tests: ... ncalls tottime percall cumtime percall filename:lineno(function) ... Total: ... tests, 0 failures, 0 errors in ... seconds. False Profiling also works across layers. >>> sys.argv = [testrunner_script, '-ssample2', profiler, ... '--tests-pattern', 'sampletests_ntd'] >>> testrunner.run_internal(defaults) Running... Tear down ... not supported... ncalls tottime percall cumtime percall filename:lineno(function)... The testrunner creates temnporary files containing hotshot profiler data: >>> import glob >>> files = list(glob.glob('tests_profile.*.prof')) >>> files.sort() >>> files ['tests_profile.cZj2jt.prof', 'tests_profile.yHD-so.prof'] It deletes these when rerun. We'll delete these ourselves: >>> import os >>> for f in files: ... os.unlink(f) Running Without Source Code =========================== The ``--usecompiled`` option allows running tests in a tree without .py source code, provided compiled .pyc or .pyo files exist (without ``--usecompiled``, .py files are necessary). We have a very simple directory tree, under ``usecompiled/``, to test this. Because we're going to delete its .py files, we want to work in a copy of that: >>> import os.path, shutil, sys, tempfile >>> directory_with_tests = tempfile.mkdtemp() >>> NEWNAME = "unlikely_package_name" >>> src = os.path.join(this_directory, 'testrunner-ex', 'usecompiled') >>> os.path.isdir(src) True >>> dst = os.path.join(directory_with_tests, NEWNAME) >>> os.path.isdir(dst) False Have to use our own copying code, to avoid copying read-only SVN files that can't be deleted later. >>> n = len(src) + 1 >>> for root, dirs, files in os.walk(src): ... dirs[:] = [d for d in dirs if d == "package"] # prune cruft ... os.mkdir(os.path.join(dst, root[n:])) ... for f in files: ... shutil.copy(os.path.join(root, f), ... os.path.join(dst, root[n:], f)) Now run the tests in the copy: >>> from zope.testing import testrunner >>> mydefaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^compiletest$', ... '--package', NEWNAME, ... '-vv', ... ] >>> sys.argv = ['test'] >>> testrunner.run_internal(mydefaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test1 (unlikely_package_name.compiletest.Test) test2 (unlikely_package_name.compiletest.Test) test1 (unlikely_package_name.package.compiletest.Test) test2 (unlikely_package_name.package.compiletest.Test) Ran 4 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False If we delete the source files, it's normally a disaster: the test runner doesn't believe any test files, or even packages, exist. Note that we pass ``--keepbytecode`` this time, because otherwise the test runner would delete the compiled Python files too: >>> for root, dirs, files in os.walk(dst): ... for f in files: ... if f.endswith(".py"): ... os.remove(os.path.join(root, f)) >>> testrunner.run_internal(mydefaults, ["test", "--keepbytecode"]) Running tests at level 1 Total: 0 tests, 0 failures, 0 errors in N.NNN seconds. False Finally, passing ``--usecompiled`` asks the test runner to treat .pyc and .pyo files as adequate replacements for .py files. Note that the output is the same as when running with .py source above. The absence of "removing stale bytecode ..." messages shows that ``--usecompiled`` also implies ``--keepbytecode``: >>> testrunner.run_internal(mydefaults, ["test", "--usecompiled"]) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test1 (unlikely_package_name.compiletest.Test) test2 (unlikely_package_name.compiletest.Test) test1 (unlikely_package_name.package.compiletest.Test) test2 (unlikely_package_name.package.compiletest.Test) Ran 4 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Remove the temporary directory: >>> shutil.rmtree(directory_with_tests) Repeating Tests =============== The --repeat option can be used to repeat tests some number of times. Repeating tests is useful to help make sure that tests clean up after themselves. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --layer 112 --layer UnitTests --repeat 3'.split() >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running samplelayers.Layer112 tests: Set up samplelayers.Layerx in 0.000 seconds. Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer112 in 0.000 seconds. Iteration 1 Ran 34 tests with 0 failures and 0 errors in 0.010 seconds. Iteration 2 Ran 34 tests with 0 failures and 0 errors in 0.010 seconds. Iteration 3 Ran 34 tests with 0 failures and 0 errors in 0.010 seconds. Running zope.testing.testrunner.layer.UnitTests tests: Tear down samplelayers.Layer112 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Iteration 1 Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Iteration 2 Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Iteration 3 Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 226 tests, 0 failures, 0 errors in N.NNN seconds. False The tests are repeated by layer. Layers are set up and torn down only once. Garbage Collection Control ========================== When having problems that seem to be caused my memory-management errors, it can be helpful to adjust Python's cyclic garbage collector or to get garbage colection statistics. The --gc option can be used for this purpose. If you think you are getting a test failure due to a garbage collection problem, you can try disabling garbage collection by using the --gc option with a value of zero. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = ['--path', directory_with_tests] >>> from zope.testing import testrunner >>> sys.argv = 'test --tests-pattern ^gc0$ --gc 0 -vv'.split() >>> _ = testrunner.run_internal(defaults) Cyclic garbage collection is disabled. Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: make_sure_gc_is_disabled (gc0) Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Alternatively, if you think you are having a garbage collection related problem, you can cause garbage collection to happen more often by providing a low threshold: >>> sys.argv = 'test --tests-pattern ^gc1$ --gc 1 -vv'.split() >>> _ = testrunner.run_internal(defaults) Cyclic garbage collection threshold set to: (1,) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: make_sure_gc_threshold_is_one (gc1) Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. You can specify up to 3 --gc options to set each of the 3 gc threshold values: >>> sys.argv = ('test --tests-pattern ^gcset$ --gc 701 --gc 11 --gc 9 -vv' ... .split()) >>> _ = testrunner.run_internal(defaults) Cyclic garbage collection threshold set to: (701, 11, 9) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: make_sure_gc_threshold_is_701_11_9 (gcset) Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Specifying more than 3 --gc options is not allowed: >>> from StringIO import StringIO >>> out = StringIO() >>> stdout = sys.stdout >>> sys.stdout = out >>> sys.argv = ('test --tests-pattern ^gcset$ --gc 701 --gc 42 --gc 11 --gc 9 -vv' ... .split()) >>> _ = testrunner.run_internal(defaults) Traceback (most recent call last): ... SystemExit: 1 >>> sys.stdout = stdout >>> print out.getvalue() Too many --gc options Garbage Collection Statistics ----------------------------- You can enable gc debugging statistics using the --gc-options (-G) option. You should provide names of one or more of the flags described in the library documentation for the gc module. The output statistics are written to standard error. >>> from StringIO import StringIO >>> err = StringIO() >>> stderr = sys.stderr >>> sys.stderr = err >>> sys.argv = ('test --tests-pattern ^gcstats$ -G DEBUG_STATS' ... ' -G DEBUG_COLLECTABLE -vv' ... .split()) >>> _ = testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: generate_some_gc_statistics (gcstats) Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. >>> sys.stderr = stderr >>> print err.getvalue() # doctest: +ELLIPSIS gc: collecting generation ... Debugging Memory Leaks ====================== The --report-refcounts (-r) option can be used with the --repeat (-N) option to detect and diagnose memory leaks. To use this option, you must configure Python with the --with-pydebug option. (On Unix, pass this option to configure and then build Python.) >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> from zope.testing import testrunner >>> sys.argv = 'test --layer Layer11$ --layer Layer12$ -N4 -r'.split() >>> _ = testrunner.run_internal(defaults) Running samplelayers.Layer11 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer11 in 0.000 seconds. Iteration 1 Ran 34 tests with 0 failures and 0 errors in 0.013 seconds. Iteration 2 Ran 34 tests with 0 failures and 0 errors in 0.012 seconds. sys refcount=100401 change=0 Iteration 3 Ran 34 tests with 0 failures and 0 errors in 0.012 seconds. sys refcount=100401 change=0 Iteration 4 Ran 34 tests with 0 failures and 0 errors in 0.013 seconds. sys refcount=100401 change=0 Running samplelayers.Layer12 tests: Tear down samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Iteration 1 Ran 34 tests with 0 failures and 0 errors in 0.013 seconds. Iteration 2 Ran 34 tests with 0 failures and 0 errors in 0.012 seconds. sys refcount=100411 change=0 Iteration 3 Ran 34 tests with 0 failures and 0 errors in 0.012 seconds. sys refcount=100411 change=0 Iteration 4 Ran 34 tests with 0 failures and 0 errors in 0.012 seconds. sys refcount=100411 change=0 Tearing down left over layers: Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. Total: 68 tests, 0 failures, 0 errors in N.NNN seconds. Each layer is repeated the requested number of times. For each iteration after the first, the system refcount and change in system refcount is shown. The system refcount is the total of all refcount in the system. When a refcount on any object is changed, the system refcount is changed by the same amount. Tests that don't leak show zero changes in systen refcount. Let's look at an example test that leaks: >>> sys.argv = 'test --tests-pattern leak -N4 -r'.split() >>> _ = testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests:... Iteration 1 Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. Iteration 2 Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sys refcount=92506 change=12 Iteration 3 Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sys refcount=92513 change=12 Iteration 4 Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sys refcount=92520 change=12 Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Here we see that the system refcount is increating. If we specify a verbosity greater than one, we can get details broken out by object type (or class): >>> sys.argv = 'test --tests-pattern leak -N5 -r -v'.split() >>> _ = testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests:... Iteration 1 Running: . Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. Iteration 2 Running: . Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sum detail refcount=95832 sys refcount=105668 change=16 Leak details, changes in instances and refcounts by type/class: type/class insts refs ------------------------------------------------------- ----- ---- classobj 0 1 dict 2 2 float 1 1 int 2 2 leak.ClassicLeakable 1 1 leak.Leakable 1 1 str 0 4 tuple 1 1 type 0 3 ------------------------------------------------------- ----- ---- total 8 16 Iteration 3 Running: . Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sum detail refcount=95844 sys refcount=105680 change=12 Leak details, changes in instances and refcounts by type/class: type/class insts refs ------------------------------------------------------- ----- ---- classobj 0 1 dict 2 2 float 1 1 int -1 0 leak.ClassicLeakable 1 1 leak.Leakable 1 1 str 0 4 tuple 1 1 type 0 1 ------------------------------------------------------- ----- ---- total 5 12 Iteration 4 Running: . Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sum detail refcount=95856 sys refcount=105692 change=12 Leak details, changes in instances and refcounts by type/class: type/class insts refs ------------------------------------------------------- ----- ---- classobj 0 1 dict 2 2 float 1 1 leak.ClassicLeakable 1 1 leak.Leakable 1 1 str 0 4 tuple 1 1 type 0 1 ------------------------------------------------------- ----- ---- total 6 12 Iteration 5 Running: . Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sum detail refcount=95868 sys refcount=105704 change=12 Leak details, changes in instances and refcounts by type/class: type/class insts refs ------------------------------------------------------- ----- ---- classobj 0 1 dict 2 2 float 1 1 leak.ClassicLeakable 1 1 leak.Leakable 1 1 str 0 4 tuple 1 1 type 0 1 ------------------------------------------------------- ----- ---- total 6 12 Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. It is instructive to analyze the results in some detail. The test being run was designed to intentionally leak: class ClassicLeakable: def __init__(self): self.x = 'x' class Leakable(object): def __init__(self): self.x = 'x' leaked = [] class TestSomething(unittest.TestCase): def testleak(self): leaked.append((ClassicLeakable(), Leakable(), time.time())) Let's go through this by type. float, leak.ClassicLeakable, leak.Leakable, and tuple We leak one of these every time. This is to be expected because we are adding one of these to the list every time. str We don't leak any instances, but we leak 4 references. These are due to the instance attributes avd values. dict We leak 2 of these, one for each ClassicLeakable and Leakable instance. classobj We increase the number of classobj instance references by one each time because each ClassicLeakable instance has a reference to its class. This instances increases the references in it's class, which increases the total number of references to classic classes (clasobj instances). type For most interations, we increase the number of type references by one for the same reason we increase the number of clasobj references by one. The increase of the number of type references by 3 in the second iteration is puzzling, but illustrates that this sort of data is often puzzling. int The change in the number of int instances and references in this example is a side effect of the statistics being gathered. Lots of integers are created to keep the memory statistics used here. The summary statistics include the sum of the detail refcounts. (Note that this sum is less than the system refcount. This is because the detailed analysis doesn't inspect every object. Not all objects in the system are returned by sys.getobjects.) Knitting in extra package directories ===================================== Python packages have __path__ variables that can be manipulated to add extra directories cntaining software used in the packages. The testrunner needs to be given extra information about this sort of situation. Let's look at an example. The testrunner-ex-knit-lib directory is a directory that we want to add to the Python path, but that we don't want to search for tests. It has a sample4 package and a products subpackage. The products subpackage adds the testrunner-ex-knit-products to it's __path__. We want to run tests from the testrunner-ex-knit-products directory. When we import these tests, we need to import them from the sample4.products package. We can't use the --path option to name testrunner-ex-knit-products. It isn't enough to add the containing directory to the test path because then we wouldn't be able to determine the package name properly. We might be able to use the --package option to run the tests from the sample4/products package, but we want to run tests in testrunner-ex that aren't in this package. We can use the --package-path option in this case. The --package-path option is like the --test-path option in that it defines a path to be searched for tests without affecting the python path. It differs in that it supplied a package name that is added a profex when importing any modules found. The --package-path option takes *two* arguments, a package name and file path. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> sys.path.append(os.path.join(this_directory, 'testrunner-ex-pp-lib')) >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... '--package-path', ... os.path.join(this_directory, 'testrunner-ex-pp-products'), ... 'sample4.products', ... ] >>> from zope.testing import testrunner >>> sys.argv = 'test --layer Layer111 -vv'.split() >>> _ = testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in 0.000 seconds. Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer111 in 0.000 seconds. Running: test_x1 (sample1.sampletests.test111.TestA) test_y0 (sample1.sampletests.test111.TestA) ... test_y0 (sampletests.test111) test_z1 (sampletests.test111) testrunner-ex/sampletests/../sampletestsl.txt test_extra_test_in_products (sample4.products.sampletests.Test) test_another_test_in_products (sample4.products.more.sampletests.Test) Ran 36 tests with 0 failures and 0 errors in 0.008 seconds. Tearing down left over layers: Tear down samplelayers.Layer111 in 0.000 seconds. Tear down samplelayers.Layerx in 0.000 seconds. Tear down samplelayers.Layer11 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. In the example, the last test, test_extra_test_in_products, came from the products directory. As usual, we can select the knit-in packages or individual packages within knit-in packages: >>> sys.argv = 'test --package sample4.products -vv'.split() >>> _ = testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in 0.000 seconds. Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer111 in 0.000 seconds. Running: test_extra_test_in_products (sample4.products.sampletests.Test) test_another_test_in_products (sample4.products.more.sampletests.Test) Ran 2 tests with 0 failures and 0 errors in 0.000 seconds. Tearing down left over layers: Tear down samplelayers.Layer111 in 0.000 seconds. Tear down samplelayers.Layerx in 0.000 seconds. Tear down samplelayers.Layer11 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. >>> sys.argv = 'test --package sample4.products.more -vv'.split() >>> _ = testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in 0.000 seconds. Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer111 in 0.000 seconds. Running: test_another_test_in_products (sample4.products.more.sampletests.Test) Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. Tearing down left over layers: Tear down samplelayers.Layer111 in 0.000 seconds. Tear down samplelayers.Layerx in 0.000 seconds. Tear down samplelayers.Layer11 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. Parsing HTML Forms ================== Sometimes in functional tests, information from a generated form must be extracted in order to re-submit it as part of a subsequent request. The `zope.testing.formparser` module can be used for this purpose. The scanner is implemented using the `FormParser` class. The constructor arguments are the page data containing the form and (optionally) the URL from which the page was retrieved: >>> import zope.testing.formparser >>> page_text = '''\ ... ...
    ... ... ... ... ...
    ... ... Just for fun, a second form, after specifying a base: ... ...
    ... ... ... ... ...
    ... ... ''' >>> parser = zope.testing.formparser.FormParser(page_text) >>> forms = parser.parse() >>> len(forms) 2 >>> forms.form1 is forms[0] True >>> forms.form1 is forms[1] False More often, the `parse()` convenience function is all that's needed: >>> forms = zope.testing.formparser.parse( ... page_text, "http://cgi.example.com/somewhere/form.html") >>> len(forms) 2 >>> forms.form1 is forms[0] True >>> forms.form1 is forms[1] False Once we have the form we're interested in, we can check form attributes and individual field values: >>> form = forms.form1 >>> form.enctype 'application/x-www-form-urlencoded' >>> form.method 'post' >>> keys = form.keys() >>> keys.sort() >>> keys ['do-it-now', 'f1', 'not-really', 'pick-two'] >>> not_really = form["not-really"] >>> not_really.type 'image' >>> not_really.value "Don't." >>> not_really.readonly False >>> not_really.disabled False Note that relative URLs are converted to absolute URLs based on the ```` element (if present) or using the base passed in to the constructor. >>> form.action 'http://cgi.example.com/cgi-bin/foobar.py' >>> not_really.src 'http://cgi.example.com/somewhere/dont.png' >>> forms[1].action 'http://www.example.com/base/sproing/sprung.html' >>> forms[1]["action"].src 'http://www.example.com/base/else.png' Fields which are repeated are reported as lists of objects that represent each instance of the field:: >>> field = forms[1]["multi"] >>> type(field) >>> [o.value for o in field] ['', ''] >>> [o.size for o in field] [2, 3] The `` ... ... ... ... ... ... ''' >>> parser = zope.testing.formparser.FormParser(page_text) >>> forms = parser.parse() >>> len(forms) 2 >>> forms.form1 is forms[0] True >>> forms.form1 is forms[1] False More often, the `parse()` convenience function is all that's needed: >>> forms = zope.testing.formparser.parse( ... page_text, "http://cgi.example.com/somewhere/form.html") >>> len(forms) 2 >>> forms.form1 is forms[0] True >>> forms.form1 is forms[1] False Once we have the form we're interested in, we can check form attributes and individual field values: >>> form = forms.form1 >>> form.enctype 'application/x-www-form-urlencoded' >>> form.method 'post' >>> keys = form.keys() >>> keys.sort() >>> keys ['do-it-now', 'f1', 'not-really', 'pick-two'] >>> not_really = form["not-really"] >>> not_really.type 'image' >>> not_really.value "Don't." >>> not_really.readonly False >>> not_really.disabled False Note that relative URLs are converted to absolute URLs based on the ```` element (if present) or using the base passed in to the constructor. >>> form.action 'http://cgi.example.com/cgi-bin/foobar.py' >>> not_really.src 'http://cgi.example.com/somewhere/dont.png' >>> forms[1].action 'http://www.example.com/base/sproing/sprung.html' >>> forms[1]["action"].src 'http://www.example.com/base/else.png' Fields which are repeated are reported as lists of objects that represent each instance of the field:: >>> field = forms[1]["multi"] >>> type(field) >>> [o.value for o in field] ['', ''] >>> [o.size for o in field] [2, 3] The `` ... ... ... ... ... ... ''' >>> parser = zope.testing.formparser.FormParser(page_text) >>> forms = parser.parse() >>> len(forms) 2 >>> forms.form1 is forms[0] True >>> forms.form1 is forms[1] False More often, the `parse()` convenience function is all that's needed: >>> forms = zope.testing.formparser.parse( ... page_text, "http://cgi.example.com/somewhere/form.html") >>> len(forms) 2 >>> forms.form1 is forms[0] True >>> forms.form1 is forms[1] False Once we have the form we're interested in, we can check form attributes and individual field values: >>> form = forms.form1 >>> form.enctype 'application/x-www-form-urlencoded' >>> form.method 'post' >>> keys = form.keys() >>> keys.sort() >>> keys ['do-it-now', 'f1', 'not-really', 'pick-two'] >>> not_really = form["not-really"] >>> not_really.type 'image' >>> not_really.value "Don't." >>> not_really.readonly False >>> not_really.disabled False Note that relative URLs are converted to absolute URLs based on the ```` element (if present) or using the base passed in to the constructor. >>> form.action 'http://cgi.example.com/cgi-bin/foobar.py' >>> not_really.src 'http://cgi.example.com/somewhere/dont.png' >>> forms[1].action 'http://www.example.com/base/sproing/sprung.html' >>> forms[1]["action"].src 'http://www.example.com/base/else.png' Fields which are repeated are reported as lists of objects that represent each instance of the field:: >>> field = forms[1]["multi"] >>> type(field) >>> [o.value for o in field] ['', ''] >>> [o.size for o in field] [2, 3] The `` is seen. elif tag == "base": href = d.get("href", "").strip() if href and self.base: href = urlparse.urljoin(self.base, href) self.base = href elif tag == "select": disabled = "disabled" in d multiple = "multiple" in d size = intattr(d, "size") self.select = Select(name, id, disabled, multiple, size) self._add_field(self.select) elif tag == "option": disabled = "disabled" in d selected = "selected" in d value = d.get("value") label = d.get("label") option = Option(id, value, selected, label, disabled) self.select.options.append(option) # Helpers: def _add_field(self, field): if field.name in self.current: ob = self.current[field.name] if isinstance(ob, list): ob.append(field) else: self.current[field.name] = [ob, field] else: self.current[field.name] = field def kwattr(d, name, default=None): """Return attribute, converted to lowercase.""" v = d.get(name, default) if v != default and v is not None: v = v.strip().lower() v = v or default return v def intattr(d, name): """Return attribute as an integer, or None.""" if name in d: v = d[name].strip() return int(v) else: return None class FormCollection(list): """Collection of all forms from a page.""" def __getattr__(self, name): for form in self: if form.name == name: return form raise AttributeError(name) class Form(dict): """A specific form within a page.""" # This object should provide some method to prepare a dictionary # that can be passed directly as the value of the `form` argument # to the `http()` function of the Zope functional test. # # This is probably a low priority given the availability of the # `zope.testbrowser` package. def __init__(self, name, id, method, action, enctype): super(Form, self).__init__() self.name = name self.id = id self.method = method self.action = action self.enctype = enctype class Input(object): """Input element.""" rows = None cols = None def __init__(self, name, id, type, value, checked, disabled, readonly, src, size, maxlength): super(Input, self).__init__() self.name = name self.id = id self.type = type self.value = value self.checked = checked self.disabled = disabled self.readonly = readonly self.src = src self.size = size self.maxlength = maxlength class Select(Input): """Select element.""" def __init__(self, name, id, disabled, multiple, size): super(Select, self).__init__(name, id, "select", None, None, disabled, None, None, size, None) self.options = [] self.multiple = multiple if multiple: self.value = [] class Option(object): """Individual value representation for a select element.""" def __init__(self, id, value, selected, label, disabled): super(Option, self).__init__() self.id = id self.value = value self.selected = selected self.label = label self.disabled = disabled zope2.13-2.13.21/source/zope.testing/src/zope/testing/renormalizing/0000755000175000017500000000000012214017655024174 5ustar arnauarnauzope2.13-2.13.21/source/zope.testing/src/zope/testing/renormalizing/__init__.py0000644000175000017500000002304012214017655026304 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # # This file is a package rather than a module because we want # # import doctest # # to import the stdlib version of doctest rather than the deprecated # zope.testing.doctest, and # # from __future__ import absolute_import # # is not available on Python 2.4 which we still support. # ############################################################################## r"""Regular expression pattern normalizing output checker The pattern-normalizing output checker extends the default output checker with an option to normalize expected and actual output. You specify a sequence of patterns and replacements. The replacements are applied to the expected and actual outputs before calling the default outputs checker. Let's look at an example. In this example, we have some times and addresses: >>> want = '''\ ... ... completed in 1.234 seconds. ... ... ... completed in 123.234 seconds. ... ... ... completed in .234 seconds. ... ... ... completed in 1.234 seconds. ... ... ''' >>> got = '''\ ... ... completed in 1.235 seconds. ... ... ... completed in 123.233 seconds. ... ... ... completed in .231 seconds. ... ... ... completed in 1.23 seconds. ... ... ''' We may wish to consider these two strings to match, even though they differ in actual addresses and times. The default output checker will consider them different: >>> doctest.OutputChecker().check_output(want, got, 0) False We'll use the RENormalizing to normalize both the wanted and gotten strings to ignore differences in times and addresses: >>> import re >>> checker = RENormalizing([ ... (re.compile('[0-9]*[.][0-9]* seconds'), ' seconds'), ... (re.compile('at 0x[0-9a-f]+'), 'at '), ... ]) >>> checker.check_output(want, got, 0) True Usual OutputChecker options work as expected: >>> want_ellided = '''\ ... ... completed in 1.234 seconds. ... ... ... ... completed in 1.234 seconds. ... ... ''' >>> checker.check_output(want_ellided, got, 0) False >>> checker.check_output(want_ellided, got, doctest.ELLIPSIS) True When we get differencs, we output them with normalized text: >>> source = '''\ ... >>> do_something() ... ... completed in 1.234 seconds. ... ... ... ... completed in 1.234 seconds. ... ... ''' >>> example = doctest.Example(source, want_ellided) >>> print checker.output_difference(example, got, 0) Expected: > completed in seconds. ... > completed in seconds. Got: > completed in seconds. > completed in seconds. > completed in seconds. > completed in seconds. >>> print checker.output_difference(example, got, ... doctest.REPORT_NDIFF) Differences (ndiff with -expected +actual): - > - completed in seconds. - ... > completed in seconds. + > + completed in seconds. + + > + completed in seconds. + + > + completed in seconds. + If the wanted text is empty, however, we don't transform the actual output. This is usful when writing tests. We leave the expected output empty, run the test, and use the actual output as expected, after reviewing it. >>> source = '''\ ... >>> do_something() ... ''' >>> example = doctest.Example(source, '\n') >>> print checker.output_difference(example, got, 0) Expected: Got: completed in 1.235 seconds. completed in 123.233 seconds. completed in .231 seconds. completed in 1.23 seconds. If regular expressions aren't expressive enough, you can use arbitrary Python callables to transform the text. For example, suppose you want to ignore case during comparison: >>> checker = RENormalizing([ ... lambda s: s.lower(), ... lambda s: s.replace('', ''), ... ]) >>> want = '''\ ... Usage: thundermonkey [options] [url] ... ... Options: ... -h display this help message ... ''' >>> got = '''\ ... usage: thundermonkey [options] [URL] ... ... options: ... -h Display this help message ... ''' >>> checker.check_output(want, got, 0) True Suppose we forgot that must be in upper case: >>> checker = RENormalizing([ ... lambda s: s.lower(), ... ]) >>> checker.check_output(want, got, 0) False The difference would show us that: >>> source = '''\ ... >>> print_help_message() ... ''' + want >>> example = doctest.Example(source, want) >>> print checker.output_difference(example, got, ... doctest.REPORT_NDIFF), Differences (ndiff with -expected +actual): usage: thundermonkey [options] [url] - + options: -h display this help message It is possible to combine RENormalizing checkers for easy reuse: >>> address_and_time_checker = RENormalizing([ ... (re.compile('[0-9]*[.][0-9]* seconds'), ' seconds'), ... (re.compile('at 0x[0-9a-f]+'), 'at '), ... ]) >>> lowercase_checker = RENormalizing([ ... lambda s: s.lower(), ... ]) >>> combined_checker = address_and_time_checker + lowercase_checker >>> len(combined_checker.transformers) 3 Combining a checker with something else does not work: >>> lowercase_checker + 5 Traceback (most recent call last): ... TypeError: unsupported operand type(s) for +: 'instance' and 'int' """ import doctest class RENormalizing(doctest.OutputChecker): """Pattern-normalizing outout checker """ def __init__(self, patterns): self.transformers = map(self._cook, patterns) def __add__(self, other): if not isinstance(other, RENormalizing): return NotImplemented return RENormalizing(self.transformers + other.transformers) def _cook(self, pattern): if callable(pattern): return pattern regexp, replacement = pattern return lambda text: regexp.sub(replacement, text) def check_output(self, want, got, optionflags): if got == want: return True for transformer in self.transformers: want = transformer(want) got = transformer(got) return doctest.OutputChecker.check_output(self, want, got, optionflags) def output_difference(self, example, got, optionflags): want = example.want # If want is empty, use original outputter. This is useful # when setting up tests for the first time. In that case, we # generally use the differencer to display output, which we evaluate # by hand. if not want.strip(): return doctest.OutputChecker.output_difference( self, example, got, optionflags) # Dang, this isn't as easy to override as we might wish original = want for transformer in self.transformers: want = transformer(want) got = transformer(got) # temporarily hack example with normalized want: example.want = want result = doctest.OutputChecker.output_difference( self, example, got, optionflags) example.want = original return result zope2.13-2.13.21/source/zope.testing/src/zope/testing/__init__.py0000644000175000017500000000120212214017655023420 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/0000755000175000017500000000000012214017655023525 5ustar arnauarnauzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-coverage.txt0000644000175000017500000001253112214017655030272 0ustar arnauarnauCode Coverage ============= If the --coverage option is used, test coverage reports will be generated. The directory name given as the parameter will be used to hold the reports. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --coverage=coverage_dir'.split() >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in 0.000 seconds. Ran 9 tests with 0 failures and 0 errors in 0.000 seconds. Running samplelayers.Layer11 tests: Set up samplelayers.Layer11 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.125 seconds. Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in 0.000 seconds. Set up samplelayers.Layer111 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.125 seconds. Running samplelayers.Layer112 tests: Tear down samplelayers.Layer111 in 0.000 seconds. Set up samplelayers.Layer112 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.125 seconds. Running samplelayers.Layer12 tests: Tear down samplelayers.Layer112 in 0.000 seconds. Tear down samplelayers.Layerx in 0.000 seconds. Tear down samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.140 seconds. Running samplelayers.Layer121 tests: Set up samplelayers.Layer121 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.125 seconds. Running samplelayers.Layer122 tests: Tear down samplelayers.Layer121 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.125 seconds. Running zope.testing.testrunner.layer.UnitTests tests: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. Set up zope.testing.testrunner.layer.UnitTests in 0.000 seconds. Ran 192 tests with 0 failures and 0 errors in 0.687 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in 0.000 seconds. lines cov% module (path) ... 48 100% sampletests.test1 (testrunner-ex/sampletests/test1.py) 74 100% sampletests.test11 (testrunner-ex/sampletests/test11.py) 74 100% sampletests.test111 (testrunner-ex/sampletests/test111.py) 76 100% sampletests.test112 (testrunner-ex/sampletests/test112.py) 74 100% sampletests.test12 (testrunner-ex/sampletests/test12.py) 74 100% sampletests.test121 (testrunner-ex/sampletests/test121.py) 74 100% sampletests.test122 (testrunner-ex/sampletests/test122.py) 48 100% sampletests.test_one (testrunner-ex/sampletests/test_one.py) 112 95% sampletestsf (testrunner-ex/sampletestsf.py) Total: 405 tests, 0 failures, 0 errors in 0.630 seconds. False The directory specified with the --coverage option will have been created and will hold the coverage reports. >>> os.path.exists('coverage_dir') True >>> os.listdir('coverage_dir') [...] (We should clean up after ourselves.) >>> import shutil >>> shutil.rmtree('coverage_dir') Ignoring Tests -------------- The ``trace`` module supports ignoring directories and modules based the test selection. Only directories selected for testing should report coverage. The test runner provides a custom implementation of the relevant API. The ``TestIgnore`` class, the class managing the ignoring, is initialized by passing the command line options. It uses the options to determine the directories that should be covered. >>> class FauxOptions(object): ... package = None ... test_path = [('/myproject/src/blah/foo', ''), ... ('/myproject/src/blah/bar', '')] >>> from zope.testing.testrunner import coverage >>> from zope.testing.testrunner.find import test_dirs >>> ignore = coverage.TestIgnore(test_dirs(FauxOptions(), {})) >>> ignore._test_dirs ['/myproject/src/blah/foo/', '/myproject/src/blah/bar/'] We can now ask whether a particular module should be ignored: >>> ignore.names('/myproject/src/blah/foo/baz.py', 'baz') False >>> ignore.names('/myproject/src/blah/bar/mine.py', 'mine') False >>> ignore.names('/myproject/src/blah/foo/__init__.py', 'foo') False >>> ignore.names('/myproject/src/blah/hello.py', 'hello') True When running the test runner, modules are sometimes created from text strings. Those should *always* be ignored: >>> ignore.names('/myproject/src/blah/hello.txt', '') True To make this check fast, the class implements a cache. In an early implementation, the result was cached by the module name, which was a problem, since a lot of modules carry the same name (not the Python dotted name here!). So just because a module has the same name in an ignored and tested directory, does not mean it is always ignored: >>> ignore.names('/myproject/src/blah/module.py', 'module') True >>> ignore.names('/myproject/src/blah/foo/module.py', 'module') False zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/tb_format.py0000644000175000017500000000315712214017655026062 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Set up testing environment $Id: __init__.py 68482 2006-06-04 14:58:55Z jim $ """ import sys import traceback import zope.exceptions.exceptionformatter import zope.testing.testrunner.feature def format_exception(t, v, tb, limit=None): fmt = zope.exceptions.exceptionformatter.TextExceptionFormatter( limit=None, with_filenames=True) return fmt.formatException(t, v, tb) def print_exception(t, v, tb, limit=None, file=None): if file is None: file = sys.stdout file.writelines(format_exception(t, v, tb, limit)) class Traceback(zope.testing.testrunner.feature.Feature): active = True def global_setup(self): self.old_format = traceback.format_exception traceback.format_exception = format_exception self.old_print = traceback.print_exception traceback.print_exception = print_exception def global_teardown(self): traceback.format_exception = self.old_format traceback.print_exception = self.old_print zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-coverage-win32.txt0000644000175000017500000000172712214017655031237 0ustar arnauarnauCode Coverage ============= On Windows drive names can be upper and lower case, these can be randomly passed to TestIgnore.names. Watch out for the case of the R drive! >>> from zope.testing.testrunner.coverage import TestIgnore >>> ignore = TestIgnore((('r:\\winproject\\src\\blah\\foo', ''), ... ('R:\\winproject\\src\\blah\\bar', ''))) >>> ignore._test_dirs ['r:\\winproject\\src\\blah\\foo\\', 'R:\\winproject\\src\\blah\\bar\\'] We can now ask whether a particular module should be ignored: >>> ignore.names('r:\\winproject\\src\\blah\\foo\\baz.py', 'baz') False >>> ignore.names('R:\\winproject\\src\\blah\\foo\\baz.py', 'baz') False >>> ignore.names('r:\\winproject\\src\\blah\\bar\\zab.py', 'zab') False >>> ignore.names('R:\\winproject\\src\\blah\\bar\\zab.py', 'zab') False >>> ignore.names('r:\\winproject\\src\\blah\\hello.py', 'hello') True >>> ignore.names('R:\\winproject\\src\\blah\\hello.py', 'hello') True zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-shuffle.txt0000644000175000017500000001270012214017655030131 0ustar arnauarnauShuffling tests =============== By default, every time you launch the testrunner it will run the tests in a specific order. However, if you want to ensure that your tests are well isolated then running them in a varying order can be helpful. (Proper isolation meaning your tests don't depend on each others outcomes or side effects and thus the setup and tear down methods are written properly.) The ``--shuffle`` option tells the test runner to shuffle the list of tests before running them: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> from zope.testing import testrunner >>> default_argv = 'test -u -m sample1 -t test_y0 --list-tests ' Running shuffled tests ---------------------- When specifying the ``--shuffle`` option tests are ordered differently each time you run the tests: >>> argv = (default_argv + '--shuffle').split() >>> testrunner.run_internal(defaults, argv) Tests were shuffled using seed number ... Listing zope.testing.testrunner.layer.UnitTests tests: ... False Note: The runner prints out a new piece of information which is the seed number used to generate the shuffled list of tests. This seed number can later be used to re-run the tests in exactly the same order to support debugging. Specifying a seed number to control tests shuffling --------------------------------------------------- Along with the ``--shuffle`` option comes the ``--shuffle-seed`` option which takes a seed number as an argument. If you want to reproduce test failures that happened during a randomly shuffled test run then simply write down the seed number that was printed out and run it again using the ``--shuffle-seed`` option. The order is guaranteed to be the same. For example, using the seed number 0 will give us the following, stable, list of tests: >>> argv = (default_argv + '--shuffle --shuffle-seed 0').split() >>> testrunner.run_internal(defaults, argv) Tests were shuffled using seed number 0. Listing zope.testing.testrunner.layer.UnitTests tests: test_y0 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletests.test_one) test_y0 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletestsf) test_y0 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sample13.sampletests.TestA) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sample11.sampletests.TestA) test_y0 (sample1.sampletests.test1) False >>> testrunner.run_internal(defaults, argv) Tests were shuffled using seed number 0. Listing zope.testing.testrunner.layer.UnitTests tests: test_y0 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletests.test_one) test_y0 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletestsf) test_y0 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sample13.sampletests.TestA) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sample11.sampletests.TestA) test_y0 (sample1.sampletests.test1) False Whereas using the seed number 42 will give us the following, different but stable, list of tests: >>> argv = (default_argv + '--shuffle --shuffle-seed 42').split() >>> testrunner.run_internal(defaults, argv) Tests were shuffled using seed number 42. Listing zope.testing.testrunner.layer.UnitTests tests: test_y0 (sample1.sample13.sampletests.TestA) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test1.TestA) test_y0 (sample1.sample11.sampletests.TestA) test_y0 (sample1.sampletestsf) test_y0 (sample1.sampletests.test_one) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletests.test_one.TestA) False >>> testrunner.run_internal(defaults, argv) Tests were shuffled using seed number 42. Listing zope.testing.testrunner.layer.UnitTests tests: test_y0 (sample1.sample13.sampletests.TestA) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test1.TestA) test_y0 (sample1.sample11.sampletests.TestA) test_y0 (sample1.sampletestsf) test_y0 (sample1.sampletests.test_one) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletests.test_one.TestA) False Selecting a seed number without ``--shuffle`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Note that the ``--shuffle-seed`` option must be used along with ``--shuffle`` option or tests will not be re-ordered: >>> argv = (default_argv + '--shuffle-seed 42').split() >>> testrunner.run_internal(defaults, argv) Listing zope.testing.testrunner.layer.UnitTests tests: test_y0 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletestsf) test_y0 (sample1.sample11.sampletests.TestA) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sample13.sampletests.TestA) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sampletests.test_one) False zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/process.py0000644000175000017500000000401212214017655025552 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Subprocess support. $Id: __init__.py 86218 2008-05-03 14:17:26Z ctheune $ """ import sys import zope.testing.testrunner.feature class SubProcess(zope.testing.testrunner.feature.Feature): """Lists all tests in the report instead of running the tests.""" def __init__(self, runner): super(SubProcess, self).__init__(runner) self.active = bool(runner.options.resume_layer) def global_setup(self): self.original_stderr = sys.stderr sys.stderr = sys.stdout if self.runner.options.processes > 1: # If we only have one subprocess, there's absolutely # no reason to squelch. We will let the messages through in a # timely manner, if they have been requested. On the other hand, if # there are multiple processes, we do squelch to 0. self.runner.options.verbose = 0 self.progress = False def report(self): sys.stdout.close() # Communicate with the parent. The protocol is obvious: print >> self.original_stderr, self.runner.ran, \ len(self.runner.failures), len(self.runner.errors) for test, exc_info in self.runner.failures: print >> self.original_stderr, ' '.join(str(test).strip().split('\n')) for test, exc_info in self.runner.errors: print >> self.original_stderr, ' '.join(str(test).strip().split('\n')) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-layers-api.txt0000644000175000017500000002076612214017655030556 0ustar arnauarnauLayers ====== A Layer is an object providing setup and teardown methods used to setup and teardown the environment provided by the layer. It may also provide setup and teardown methods used to reset the environment provided by the layer between each test. Layers are generally implemented as classes using class methods. >>> class BaseLayer: ... def setUp(cls): ... log('BaseLayer.setUp') ... setUp = classmethod(setUp) ... ... def tearDown(cls): ... log('BaseLayer.tearDown') ... tearDown = classmethod(tearDown) ... ... def testSetUp(cls): ... log('BaseLayer.testSetUp') ... testSetUp = classmethod(testSetUp) ... ... def testTearDown(cls): ... log('BaseLayer.testTearDown') ... testTearDown = classmethod(testTearDown) ... Layers can extend other layers. Note that they do not explicitly invoke the setup and teardown methods of other layers - the test runner does this for us in order to minimize the number of invocations. >>> class TopLayer(BaseLayer): ... def setUp(cls): ... log('TopLayer.setUp') ... setUp = classmethod(setUp) ... ... def tearDown(cls): ... log('TopLayer.tearDown') ... tearDown = classmethod(tearDown) ... ... def testSetUp(cls): ... log('TopLayer.testSetUp') ... testSetUp = classmethod(testSetUp) ... ... def testTearDown(cls): ... log('TopLayer.testTearDown') ... testTearDown = classmethod(testTearDown) ... Tests or test suites specify what layer they need by storing a reference in the 'layer' attribute. >>> import unittest >>> class TestSpecifyingBaseLayer(unittest.TestCase): ... 'This TestCase explicitly specifies its layer' ... layer = BaseLayer ... name = 'TestSpecifyingBaseLayer' # For testing only ... ... def setUp(self): ... log('TestSpecifyingBaseLayer.setUp') ... ... def tearDown(self): ... log('TestSpecifyingBaseLayer.tearDown') ... ... def test1(self): ... log('TestSpecifyingBaseLayer.test1') ... ... def test2(self): ... log('TestSpecifyingBaseLayer.test2') ... >>> class TestSpecifyingNoLayer(unittest.TestCase): ... 'This TestCase specifies no layer' ... name = 'TestSpecifyingNoLayer' # For testing only ... def setUp(self): ... log('TestSpecifyingNoLayer.setUp') ... ... def tearDown(self): ... log('TestSpecifyingNoLayer.tearDown') ... ... def test1(self): ... log('TestSpecifyingNoLayer.test') ... ... def test2(self): ... log('TestSpecifyingNoLayer.test') ... Create a TestSuite containing two test suites, one for each of TestSpecifyingBaseLayer and TestSpecifyingNoLayer. >>> umbrella_suite = unittest.TestSuite() >>> umbrella_suite.addTest(unittest.makeSuite(TestSpecifyingBaseLayer)) >>> no_layer_suite = unittest.makeSuite(TestSpecifyingNoLayer) >>> umbrella_suite.addTest(no_layer_suite) Before we can run the tests, we need to setup some helpers. >>> from zope.testing.testrunner import options >>> from zope.testing.loggingsupport import InstalledHandler >>> import logging >>> log_handler = InstalledHandler('zope.testing.tests') >>> def log(msg): ... logging.getLogger('zope.testing.tests').info(msg) >>> def fresh_options(): ... opts = options.get_options(['--test-filter', '.*']) ... opts.resume_layer = None ... opts.resume_number = 0 ... return opts Now we run the tests. Note that the BaseLayer was not setup when the TestSpecifyingNoLayer was run and setup/torn down around the TestSpecifyingBaseLayer tests. >>> from zope.testing.testrunner.runner import Runner >>> runner = Runner(options=fresh_options(), args=[], found_suites=[umbrella_suite]) >>> succeeded = runner.run() Running BaseLayer tests: Set up BaseLayer in N.NNN seconds. Ran 2 tests with 0 failures and 0 errors in N.NNN seconds. Running zope.testing.testrunner.layer.UnitTests tests: Tear down BaseLayer in N.NNN seconds. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 2 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 4 tests, 0 failures, 0 errors in N.NNN seconds. Now lets specify a layer in the suite containing TestSpecifyingNoLayer and run the tests again. This demonstrates the other method of specifying a layer. This is generally how you specify what layer doctests need. >>> no_layer_suite.layer = BaseLayer >>> runner = Runner(options=fresh_options(), args=[], found_suites=[umbrella_suite]) >>> succeeded = runner.run() Running BaseLayer tests: Set up BaseLayer in N.NNN seconds. Ran 4 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down BaseLayer in N.NNN seconds. Clear our logged output, as we want to inspect it shortly. >>> log_handler.clear() Now lets also specify a layer in the TestSpecifyingNoLayer class and rerun the tests. This demonstrates that the most specific layer is used. It also shows the behavior of nested layers - because TopLayer extends BaseLayer, both the BaseLayer and TopLayer environments are setup when the TestSpecifyingNoLayer tests are run. >>> TestSpecifyingNoLayer.layer = TopLayer >>> runner = Runner(options=fresh_options(), args=[], found_suites=[umbrella_suite]) >>> succeeded = runner.run() Running BaseLayer tests: Set up BaseLayer in N.NNN seconds. Ran 2 tests with 0 failures and 0 errors in N.NNN seconds. Running TopLayer tests: Set up TopLayer in N.NNN seconds. Ran 2 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down TopLayer in N.NNN seconds. Tear down BaseLayer in N.NNN seconds. Total: 4 tests, 0 failures, 0 errors in N.NNN seconds. If we inspect our trace of what methods got called in what order, we can see that the layer setup and teardown methods only got called once. We can also see that the layer's test setup and teardown methods got called for each test using that layer in the right order. >>> def report(): ... for record in log_handler.records: ... print record.getMessage() >>> report() BaseLayer.setUp BaseLayer.testSetUp TestSpecifyingBaseLayer.setUp TestSpecifyingBaseLayer.test1 TestSpecifyingBaseLayer.tearDown BaseLayer.testTearDown BaseLayer.testSetUp TestSpecifyingBaseLayer.setUp TestSpecifyingBaseLayer.test2 TestSpecifyingBaseLayer.tearDown BaseLayer.testTearDown TopLayer.setUp BaseLayer.testSetUp TopLayer.testSetUp TestSpecifyingNoLayer.setUp TestSpecifyingNoLayer.test TestSpecifyingNoLayer.tearDown TopLayer.testTearDown BaseLayer.testTearDown BaseLayer.testSetUp TopLayer.testSetUp TestSpecifyingNoLayer.setUp TestSpecifyingNoLayer.test TestSpecifyingNoLayer.tearDown TopLayer.testTearDown BaseLayer.testTearDown TopLayer.tearDown BaseLayer.tearDown Now lets stack a few more layers to ensure that our setUp and tearDown methods are called in the correct order. >>> from zope.testing.testrunner.find import name_from_layer >>> class A(object): ... def setUp(cls): ... log('%s.setUp' % name_from_layer(cls)) ... setUp = classmethod(setUp) ... ... def tearDown(cls): ... log('%s.tearDown' % name_from_layer(cls)) ... tearDown = classmethod(tearDown) ... ... def testSetUp(cls): ... log('%s.testSetUp' % name_from_layer(cls)) ... testSetUp = classmethod(testSetUp) ... ... def testTearDown(cls): ... log('%s.testTearDown' % name_from_layer(cls)) ... testTearDown = classmethod(testTearDown) ... >>> class B(A): pass >>> class C(B): pass >>> class D(A): pass >>> class E(D): pass >>> class F(C,E): pass >>> class DeepTest(unittest.TestCase): ... layer = F ... def test(self): ... pass >>> suite = unittest.makeSuite(DeepTest) >>> log_handler.clear() >>> runner = Runner(options=fresh_options(), args=[], found_suites=[suite]) >>> succeeded = runner.run() Running F tests: Set up A in N.NNN seconds. Set up B in N.NNN seconds. Set up C in N.NNN seconds. Set up D in N.NNN seconds. Set up E in N.NNN seconds. Set up F in N.NNN seconds. Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down F in N.NNN seconds. Tear down E in N.NNN seconds. Tear down D in N.NNN seconds. Tear down C in N.NNN seconds. Tear down B in N.NNN seconds. Tear down A in N.NNN seconds. >>> report() A.setUp B.setUp C.setUp D.setUp E.setUp F.setUp A.testSetUp B.testSetUp C.testSetUp D.testSetUp E.testSetUp F.testSetUp F.testTearDown E.testTearDown D.testTearDown C.testTearDown B.testTearDown A.testTearDown F.tearDown E.tearDown D.tearDown C.tearDown B.tearDown A.tearDown zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/logsupport.py0000644000175000017500000000405212214017655026316 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Logging support. This code is pretty much untested and was only mechanically refactored. The module name is not 'logging' because of a name collision with Python's logging module. $Id: __init__.py 86218 2008-05-03 14:17:26Z ctheune $ """ import logging import logging.config import os.path import zope.testing.testrunner.feature class Logging(zope.testing.testrunner.feature.Feature): active = True def global_setup(self): # Get the log.ini file from the current directory instead of # possibly buried in the build directory. TODO: This isn't # perfect because if log.ini specifies a log file, it'll be # relative to the build directory. Hmm... logini = # os.path.abspath("log.ini") logini = os.path.abspath("log.ini") if os.path.exists(logini): logging.config.fileConfig(logini) else: # If there's no log.ini, cause the logging package to be # silent during testing. root = logging.getLogger() root.addHandler(NullHandler()) logging.basicConfig() if os.environ.has_key("LOGGING"): level = int(os.environ["LOGGING"]) logging.getLogger().setLevel(level) class NullHandler(logging.Handler): """Logging handler that drops everything on the floor. We require silence in the test environment. Hush. """ def emit(self, record): pass zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-layers-ntd.txt0000644000175000017500000002617012214017655030565 0ustar arnauarnauLayers that can't be torn down ============================== A layer can have a tearDown method that raises NotImplementedError. If this is the case and there are no remaining tests to run, the test runner will just note that the tear down couldn't be done: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> from zope.testing import testrunner >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test -ssample2 --tests-pattern sampletests_ntd$'.split() >>> testrunner.run_internal(defaults) Running sample2.sampletests_ntd.Layer tests: Set up sample2.sampletests_ntd.Layer in 0.000 seconds. Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. Tearing down left over layers: Tear down sample2.sampletests_ntd.Layer ... not supported False If the tearDown method raises NotImplementedError and there are remaining layers to run, the test runner will restart itself as a new process, resuming tests where it left off: >>> sys.argv = [testrunner_script, '--tests-pattern', 'sampletests_ntd$'] >>> testrunner.run_internal(defaults) Running sample1.sampletests_ntd.Layer tests: Set up sample1.sampletests_ntd.Layer in N.NNN seconds. Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Running sample2.sampletests_ntd.Layer tests: Tear down sample1.sampletests_ntd.Layer ... not supported Running in a subprocess. Set up sample2.sampletests_ntd.Layer in N.NNN seconds. Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tear down sample2.sampletests_ntd.Layer ... not supported Running sample3.sampletests_ntd.Layer tests: Running in a subprocess. Set up sample3.sampletests_ntd.Layer in N.NNN seconds. Error in test test_error1 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_error1 raise TypeError("Can we see errors") TypeError: Can we see errors Error in test test_error2 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_error2 raise TypeError("I hope so") TypeError: I hope so Failure in test test_fail1 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_fail1 self.assertEqual(1, 2) AssertionError: 1 != 2 Failure in test test_fail2 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_fail2 self.assertEqual(1, 3) AssertionError: 1 != 3 Ran 6 tests with 2 failures and 2 errors in N.NNN seconds. Tear down sample3.sampletests_ntd.Layer ... not supported Total: 8 tests, 2 failures, 2 errors in N.NNN seconds. True in the example above, some of the tests run as a subprocess had errors and failures. They were displayed as usual and the failure and error statistice were updated as usual. Note that debugging doesn't work when running tests in a subprocess: >>> sys.argv = [testrunner_script, '--tests-pattern', 'sampletests_ntd$', ... '-D', ] >>> testrunner.run_internal(defaults) Running sample1.sampletests_ntd.Layer tests: Set up sample1.sampletests_ntd.Layer in N.NNN seconds. Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Running sample2.sampletests_ntd.Layer tests: Tear down sample1.sampletests_ntd.Layer ... not supported Running in a subprocess. Set up sample2.sampletests_ntd.Layer in N.NNN seconds. Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tear down sample2.sampletests_ntd.Layer ... not supported Running sample3.sampletests_ntd.Layer tests: Running in a subprocess. Set up sample3.sampletests_ntd.Layer in N.NNN seconds. Error in test test_error1 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_error1 raise TypeError("Can we see errors") TypeError: Can we see errors ********************************************************************** Can't post-mortem debug when running a layer as a subprocess! ********************************************************************** Error in test test_error2 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_error2 raise TypeError("I hope so") TypeError: I hope so ********************************************************************** Can't post-mortem debug when running a layer as a subprocess! ********************************************************************** Error in test test_fail1 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_fail1 self.assertEqual(1, 2) AssertionError: 1 != 2 ********************************************************************** Can't post-mortem debug when running a layer as a subprocess! ********************************************************************** Error in test test_fail2 (sample3.sampletests_ntd.TestSomething) Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_fail2 self.assertEqual(1, 3) AssertionError: 1 != 3 ********************************************************************** Can't post-mortem debug when running a layer as a subprocess! ********************************************************************** Ran 6 tests with 0 failures and 4 errors in N.NNN seconds. Tear down sample3.sampletests_ntd.Layer ... not supported Total: 8 tests, 0 failures, 4 errors in N.NNN seconds. True Similarly, pdb.set_trace doesn't work when running tests in a layer that is run as a subprocess: >>> sys.argv = [testrunner_script, '--tests-pattern', 'sampletests_ntds'] >>> testrunner.run_internal(defaults) Running sample1.sampletests_ntds.Layer tests: Set up sample1.sampletests_ntds.Layer in 0.000 seconds. Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. Running sample2.sampletests_ntds.Layer tests: Tear down sample1.sampletests_ntds.Layer ... not supported Running in a subprocess. Set up sample2.sampletests_ntds.Layer in 0.000 seconds. --Return-- > testrunner-ex/sample2/sampletests_ntds.py(37)test_something()->None -> import pdb; pdb.set_trace() (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** --Return-- > testrunner-ex/sample2/sampletests_ntds.py(40)test_something2()->None -> import pdb; pdb.set_trace() (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** --Return-- > testrunner-ex/sample2/sampletests_ntds.py(43)test_something3()->None -> import pdb; pdb.set_trace() (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** --Return-- > testrunner-ex/sample2/sampletests_ntds.py(46)test_something4()->None -> import pdb; pdb.set_trace() (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** --Return-- > testrunner-ex/sample2/sampletests_ntds.py(52)f()->None -> import pdb; pdb.set_trace() (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** --Return-- > doctest.py(351)set_trace()->None -> pdb.Pdb.set_trace(self) (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** --Return-- > doctest.py(351)set_trace()->None -> pdb.Pdb.set_trace(self) (Pdb) c ********************************************************************** Can't use pdb.set_trace when running a layer as a subprocess! ********************************************************************** Ran 7 tests with 0 failures and 0 errors in 0.008 seconds. Tear down sample2.sampletests_ntds.Layer ... not supported Total: 8 tests, 0 failures, 0 errors in N.NNN seconds. False If you want to use pdb from a test in a layer that is run as a subprocess, then rerun the test runner selecting *just* that layer so that it's not run as a subprocess. If a test is run in a subprocess and it generates output on stderr (as stderrtest does), the output is ignored (but it doesn't cause a SubprocessError like it once did). >>> from cStringIO import StringIO >>> real_stderr = sys.stderr >>> sys.stderr = StringIO() >>> sys.argv = [testrunner_script, '-s', 'sample2', '--tests-pattern', ... '(sampletests_ntd$|stderrtest)'] >>> testrunner.run_internal(defaults) Running sample2.sampletests_ntd.Layer tests: Set up sample2.sampletests_ntd.Layer in 0.000 seconds. Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. Running sample2.stderrtest.Layer tests: Tear down sample2.sampletests_ntd.Layer ... not supported Running in a subprocess. Set up sample2.stderrtest.Layer in 0.000 seconds. Ran 1 tests with 0 failures and 0 errors in 0.002 seconds. Tear down sample2.stderrtest.Layer in 0.000 seconds. Total: 2 tests, 0 failures, 0 errors in 0.197 seconds. False >>> print sys.stderr.getvalue() A message on stderr. Please ignore (expected in test output). >>> sys.stderr = real_stderr zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-tb-format.txt0000644000175000017500000000000012214017655030356 0ustar arnauarnauzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/options.py0000644000175000017500000005657412214017655025613 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Command-line option parsing $Id: __init__.py 86231 2008-05-03 15:03:27Z ctheune $ """ import optparse import re import os import sys import pkg_resources from zope.testing.testrunner.profiling import available_profilers from zope.testing.testrunner.formatter import ( OutputFormatter, ColorfulOutputFormatter, SubunitOutputFormatter, ) from zope.testing.testrunner.formatter import terminal_has_colors parser = optparse.OptionParser("Usage: %prog [options] [MODULE] [TEST]") ###################################################################### # Searching and filtering searching = optparse.OptionGroup(parser, "Searching and filtering", """\ Options in this group are used to define which tests to run. """) searching.add_option( '--package', '--dir', '-s', action="append", dest='package', help="""\ Search the given package's directories for tests. This can be specified more than once to run tests in multiple parts of the source tree. For example, if refactoring interfaces, you don't want to see the way you have broken setups for tests in other packages. You *just* want to run the interface tests. Packages are supplied as dotted names. For compatibility with the old test runner, forward and backward slashed in package names are converted to dots. (In the special case of packages spread over multiple directories, only directories within the test search path are searched. See the --path option.) """) searching.add_option( '--module', '-m', action="append", dest='module', help="""\ Specify a test-module filter as a regular expression. This is a case-sensitive regular expression, used in search (not match) mode, to limit which test modules are searched for tests. The regular expressions are checked against dotted module names. In an extension of Python regexp notation, a leading "!" is stripped and causes the sense of the remaining regexp to be negated (so "!bc" matches any string that does not match "bc", and vice versa). The option can be specified multiple test-module filters. Test modules matching any of the test filters are searched. If no test-module filter is specified, then all test modules are used. """) searching.add_option( '--test', '-t', action="append", dest='test', help="""\ Specify a test filter as a regular expression. This is a case-sensitive regular expression, used in search (not match) mode, to limit which tests are run. In an extension of Python regexp notation, a leading "!" is stripped and causes the sense of the remaining regexp to be negated (so "!bc" matches any string that does not match "bc", and vice versa). The option can be specified multiple test filters. Tests matching any of the test filters are included. If no test filter is specified, then all tests are run. """) searching.add_option( '--unit', '-u', action="store_true", dest='unit', help="""\ Run only unit tests, ignoring any layer options. """) searching.add_option( '--non-unit', '-f', action="store_true", dest='non_unit', help="""\ Run tests other than unit tests. """) searching.add_option( '--layer', action="append", dest='layer', help="""\ Specify a test layer to run. The option can be given multiple times to specify more than one layer. If not specified, all layers are run. It is common for the running script to provide default values for this option. Layers are specified regular expressions, used in search mode, for dotted names of objects that define a layer. In an extension of Python regexp notation, a leading "!" is stripped and causes the sense of the remaining regexp to be negated (so "!bc" matches any string that does not match "bc", and vice versa). The layer named 'zope.testing.testrunner.layer.UnitTests' is reserved for unit tests, however, take note of the --unit and non-unit options. """) searching.add_option( '-a', '--at-level', type='int', dest='at_level', help="""\ Run the tests at the given level. Any test at a level at or below this is run, any test at a level above this is not run. Level 0 runs all tests. """) searching.add_option( '--all', action="store_true", dest='all', help="Run tests at all levels.") searching.add_option( '--list-tests', action="store_true", dest='list_tests', help="List all tests that matched your filters. Do not run any tests.") parser.add_option_group(searching) ###################################################################### # Reporting reporting = optparse.OptionGroup(parser, "Reporting", """\ Reporting options control basic aspects of test-runner output """) reporting.add_option( '--verbose', '-v', action="count", dest='verbose', help="""\ Make output more verbose. Increment the verbosity level. """) reporting.add_option( '--quiet', '-q', action="store_true", dest='quiet', help="""\ Make the output minimal, overriding any verbosity options. """) reporting.add_option( '--progress', '-p', action="store_true", dest='progress', help="""\ Output progress status """) reporting.add_option( '--no-progress',action="store_false", dest='progress', help="""\ Do not output progress status. This is the default, but can be used to counter a previous use of --progress or -p. """) # We use a noop callback because the actual processing will be done in the # get_options function, but we want optparse to generate appropriate help info # for us, so we add an option anyway. reporting.add_option( '--auto-progress', action="callback", callback=lambda *args: None, help="""\ Output progress status, but only when stdout is a terminal. """) reporting.add_option( '--color', '-c', action="store_true", dest='color', help="""\ Colorize the output. """) reporting.add_option( '--no-color', '-C', action="store_false", dest='color', help="""\ Do not colorize the output. This is the default, but can be used to counter a previous use of --color or -c. """) # We use a noop callback because the actual processing will be done in the # get_options function, but we want optparse to generate appropriate help info # for us, so we add an option anyway. reporting.add_option( '--auto-color', action="callback", callback=lambda *args: None, help="""\ Colorize the output, but only when stdout is a terminal. """) reporting.add_option( '--subunit', action="store_true", dest='subunit', help="""\ Use subunit output. Will not be colorized. """) reporting.add_option( '--slow-test', type='float', dest='slow_test_threshold', metavar='N', help="""\ With -c and -vvv, highlight tests that take longer than N seconds (default: %default). """) reporting.add_option( '-1', '--hide-secondary-failures', action="store_true", dest='report_only_first_failure', help="""\ Report only the first failure in a doctest. (Examples after the failure are still executed, in case they do any cleanup.) """) reporting.add_option( '--show-secondary-failures', action="store_false", dest='report_only_first_failure', help="""\ Report all failures in a doctest. This is the default, but can be used to counter a default use of -1 or --hide-secondary-failures. """) reporting.add_option( '--ndiff', action="store_true", dest="ndiff", help="""\ When there is a doctest failure, show it as a diff using the ndiff.py utility. """) reporting.add_option( '--udiff', action="store_true", dest="udiff", help="""\ When there is a doctest failure, show it as a unified diff. """) reporting.add_option( '--cdiff', action="store_true", dest="cdiff", help="""\ When there is a doctest failure, show it as a context diff. """) parser.add_option_group(reporting) ###################################################################### # Analysis analysis = optparse.OptionGroup(parser, "Analysis", """\ Analysis options provide tools for analysing test output. """) analysis.add_option( '--stop-on-error', '--stop', '-x', action="store_true", dest='stop_on_error', help="Stop running tests after first test failure or error." ) analysis.add_option( '--post-mortem', '--pdb', '-D', action="store_true", dest='post_mortem', help="Enable post-mortem debugging of test failures" ) analysis.add_option( '--gc', '-g', action="append", dest='gc', type="int", help="""\ Set the garbage collector generation threshold. This can be used to stress memory and gc correctness. Some crashes are only reproducible when the threshold is set to 1 (aggressive garbage collection). Do "--gc 0" to disable garbage collection altogether. The --gc option can be used up to 3 times to specify up to 3 of the 3 Python gc_threshold settings. """) analysis.add_option( '--gc-option', '-G', action="append", dest='gc_option', type="choice", choices=['DEBUG_STATS', 'DEBUG_COLLECTABLE', 'DEBUG_UNCOLLECTABLE', 'DEBUG_INSTANCES', 'DEBUG_OBJECTS', 'DEBUG_SAVEALL', 'DEBUG_LEAK'], help="""\ Set a Python gc-module debug flag. This option can be used more than once to set multiple flags. """) analysis.add_option( '--repeat', '-N', action="store", type="int", dest='repeat', help="""\ Repeat the tests the given number of times. This option is used to make sure that tests leave their environment in the state they found it and, with the --report-refcounts option to look for memory leaks. """) analysis.add_option( '--report-refcounts', '-r', action="store_true", dest='report_refcounts', help="""\ After each run of the tests, output a report summarizing changes in refcounts by object type. This option that requires that Python was built with the --with-pydebug option to configure. """) analysis.add_option( '--coverage', action="store", type='string', dest='coverage', help="""\ Perform code-coverage analysis, saving trace data to the directory with the given name. A code coverage summary is printed to standard out. """) analysis.add_option( '--profile', action="store", dest='profile', type="choice", choices=available_profilers.keys(), help="""\ Run the tests under cProfiler or hotshot and display the top 50 stats, sorted by cumulative time and number of calls. """) def do_pychecker(*args): if not os.environ.get("PYCHECKER"): os.environ["PYCHECKER"] = "-q" import pychecker.checker analysis.add_option( '--pychecker', action="callback", callback=do_pychecker, help="""\ Run the tests under pychecker """) parser.add_option_group(analysis) ###################################################################### # Setup setup = optparse.OptionGroup(parser, "Setup", """\ Setup options are normally supplied by the testrunner script, although they can be overridden by users. """) setup.add_option( '--path', action="append", dest='path', help="""\ Specify a path to be added to Python's search path. This option can be used multiple times to specify multiple search paths. The path is usually specified by the test-runner script itself, rather than by users of the script, although it can be overridden by users. Only tests found in the path will be run. This option also specifies directories to be searched for tests. See the search_directory. """) setup.add_option( '--test-path', action="append", dest='test_path', help="""\ Specify a path to be searched for tests, but not added to the Python search path. This option can be used multiple times to specify multiple search paths. The path is usually specified by the test-runner script itself, rather than by users of the script, although it can be overridden by users. Only tests found in the path will be run. """) setup.add_option( '--package-path', action="append", dest='package_path', nargs=2, help="""\ Specify a path to be searched for tests, but not added to the Python search path. Also specify a package for files found in this path. This is used to deal with directories that are stitched into packages that are not otherwise searched for tests. This option takes 2 arguments. The first is a path name. The second is the package name. This option can be used multiple times to specify multiple search paths. The path is usually specified by the test-runner script itself, rather than by users of the script, although it can be overridden by users. Only tests found in the path will be run. """) setup.add_option( '--tests-pattern', action="store", dest='tests_pattern', help="""\ The test runner looks for modules containing tests. It uses this pattern to identify these modules. The modules may be either packages or python files. If a test module is a package, it uses the value given by the test-file-pattern to identify python files within the package containing tests. """) setup.add_option( '--suite-name', action="store", dest='suite_name', help="""\ Specify the name of the object in each test_module that contains the module's test suite. """) setup.add_option( '--test-file-pattern', action="store", dest='test_file_pattern', help="""\ Specify a pattern for identifying python files within a tests package. See the documentation for the --tests-pattern option. """) setup.add_option( '--ignore_dir', action="append", dest='ignore_dir', help="""\ Specifies the name of a directory to ignore when looking for tests. """) setup.add_option( '--shuffle', action="store_true", dest='shuffle', help="""\ Shuffles the order in which tests are ran. """) setup.add_option( '--shuffle-seed', action="store", dest='shuffle_seed', type="int", help="""\ Value used to initialize the tests shuffler. Specify a value to create repeatable random ordered tests. """) parser.add_option_group(setup) ###################################################################### # Other other = optparse.OptionGroup(parser, "Other", "Other options") other.add_option( '--version', action="store_true", dest='showversion', help="Print the version of the testrunner, and exit.") other.add_option( '-j', action="store", type="int", dest='processes', help="""\ Use up to given number of parallel processes to execute tests. May decrease test run time substantially. Defaults to %default. """) other.add_option( '--keepbytecode', '-k', action="store_true", dest='keepbytecode', help="""\ Normally, the test runner scans the test paths and the test directories looking for and deleting pyc or pyo files without corresponding py files. This is to prevent spurious test failures due to finding compiled modules where source modules have been deleted. This scan can be time consuming. Using this option disables this scan. If you know you haven't removed any modules since last running the tests, can make the test run go much faster. """) other.add_option( '--usecompiled', action="store_true", dest='usecompiled', help="""\ Normally, a package must contain an __init__.py file, and only .py files can contain test code. When this option is specified, compiled Python files (.pyc and .pyo) can be used instead: a directory containing __init__.pyc or __init__.pyo is also considered to be a package, and if file XYZ.py contains tests but is absent while XYZ.pyc or XYZ.pyo exists then the compiled files will be used. This is necessary when running tests against a tree where the .py files have been removed after compilation to .pyc/.pyo. Use of this option implies --keepbytecode. """) other.add_option( '--exit-with-status', action="store_true", dest='exitwithstatus', help="""DEPRECATED: The test runner will always exit with a status.\ """) parser.add_option_group(other) ###################################################################### # Default values parser.set_defaults( ignore_dir=['.svn', 'CVS', '{arch}', '.arch-ids', '_darcs'], tests_pattern='^tests$', at_level=1, test_file_pattern='^test', suite_name='test_suite', list_tests=False, slow_test_threshold=10, processes=1, ) ###################################################################### # Command-line processing def compile_filter(pattern): if pattern.startswith('!'): pattern = re.compile(pattern[1:]).search return (lambda s: not pattern(s)) return re.compile(pattern).search def merge_options(options, defaults): odict = options.__dict__ for name, value in defaults.__dict__.items(): if (value is not None) and (odict[name] is None): odict[name] = value def get_options(args=None, defaults=None): # Because we want to inspect stdout and decide to colorize or not, we # replace the --auto-color option with the appropriate --color or # --no-color option. That way the subprocess doesn't have to decide (which # it would do incorrectly anyway because stdout would be a pipe). def apply_auto_color(args): if args and '--auto-color' in args: if sys.stdout.isatty() and terminal_has_colors(): colorization = '--color' else: colorization = '--no-color' args[:] = [arg.replace('--auto-color', colorization) for arg in args] # The comment of apply_auto_color applies here as well def apply_auto_progress(args): if args and '--auto-progress' in args: if sys.stdout.isatty(): progress = '--progress' else: progress = '--no-progress' args[:] = [arg.replace('--auto-progress', progress) for arg in args] apply_auto_color(args) apply_auto_color(defaults) apply_auto_progress(args) apply_auto_progress(defaults) if defaults: defaults, _ = parser.parse_args(defaults) assert not _ else: defaults = None if args is None: args = sys.argv options, positional = parser.parse_args(args[1:], defaults) options.original_testrunner_args = args if options.showversion: dist = pkg_resources.require('zope.testing')[0] print 'zope.app.testrunner version %s' % dist.version options.fail = True return options if options.subunit: try: import subunit except ImportError: print """\ Subunit is not installed. Please install Subunit to generate subunit output. """ options.fail = True return options else: options.output = SubunitOutputFormatter(options) elif options.color: options.output = ColorfulOutputFormatter(options) options.output.slow_test_threshold = options.slow_test_threshold else: options.output = OutputFormatter(options) options.fail = False if positional: module_filter = positional.pop(0) if module_filter != '.': if options.module: options.module.append(module_filter) else: options.module = [module_filter] if positional: test_filter = positional.pop(0) if options.test: options.test.append(test_filter) else: options.test = [test_filter] if positional: parser.error("Too many positional arguments") options.ignore_dir = dict([(d,1) for d in options.ignore_dir]) options.test_file_pattern = re.compile(options.test_file_pattern).search options.tests_pattern = re.compile(options.tests_pattern).search options.test = map(compile_filter, options.test or ('.')) options.module = map(compile_filter, options.module or ('.')) options.path = map(os.path.abspath, options.path or ()) options.test_path = map(os.path.abspath, options.test_path or ()) options.test_path += options.path options.test_path = ([(path, '') for path in options.test_path] + [(os.path.abspath(path), package) for (path, package) in options.package_path or () ]) if options.package: pkgmap = dict(options.test_path) options.package = [normalize_package(p, pkgmap) for p in options.package] options.prefix = [(path + os.path.sep, package) for (path, package) in options.test_path] if options.all: options.at_level = sys.maxint if options.unit and options.non_unit: # The test runner interprets this as "run only those tests that are # both unit and non-unit at the same time". The user, however, wants # to run both unit and non-unit tests. Disable the filtering so that # the user will get what she wants: options.unit = options.non_unit = False if options.unit: # XXX Argh. options.layer = ['zope.testing.testrunner.layer.UnitTests'] if options.layer: options.layer = map(compile_filter, options.layer) options.layer = options.layer and dict([(l, 1) for l in options.layer]) if options.usecompiled: options.keepbytecode = options.usecompiled if options.quiet: options.verbose = 0 if options.report_refcounts and options.repeat < 2: print """\ You must use the --repeat (-N) option to specify a repeat count greater than 1 when using the --report_refcounts (-r) option. """ options.fail = True return options if options.report_refcounts and not hasattr(sys, "gettotalrefcount"): print """\ The Python you are running was not configured with --with-pydebug. This is required to use the --report-refcounts option. """ options.fail = True return options return options def normalize_package(package, package_map={}): r"""Normalize package name passed to the --package option. >>> normalize_package('zope.testing') 'zope.testing' Converts path names into package names for compatibility with the old test runner. >>> normalize_package('zope/testing') 'zope.testing' >>> normalize_package('zope/testing/') 'zope.testing' >>> normalize_package('zope\\testing') 'zope.testing' Can use a map of absolute pathnames to package names >>> a = os.path.abspath >>> normalize_package('src/zope/testing/', ... {a('src'): ''}) 'zope.testing' >>> normalize_package('src/zope_testing/', ... {a('src/zope_testing'): 'zope.testing'}) 'zope.testing' >>> normalize_package('src/zope_something/tests', ... {a('src/zope_something'): 'zope.something', ... a('src'): ''}) 'zope.something.tests' """ package = package.replace('\\', '/') if package.endswith('/'): package = package[:-1] bits = package.split('/') for n in range(len(bits), 0, -1): pkg = package_map.get(os.path.abspath('/'.join(bits[:n]))) if pkg is not None: bits = bits[n:] if pkg: bits = [pkg] + bits return '.'.join(bits) return package.replace('/', '.') zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/formatter.py0000644000175000017500000011301112214017655026077 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Output formatting. $Id: __init__.py 86207 2008-05-03 13:25:02Z ctheune $ """ import doctest import os import sys import re import tempfile import traceback from datetime import datetime, timedelta from zope.testing.exceptions import DocTestFailureException doctest_template = """ File "%s", line %s, in %s %s Want: %s Got: %s """ class OutputFormatter(object): """Test runner output formatter.""" # Implementation note: be careful about printing stuff to sys.stderr. # It is used for interprocess communication between the parent and the # child test runner, when you run some test layers in a subprocess. # resume_layer() reasigns sys.stderr for this reason, but be careful # and don't store the original one in __init__ or something. max_width = 80 def __init__(self, options): self.options = options self.last_width = 0 self.compute_max_width() progress = property(lambda self: self.options.progress) verbose = property(lambda self: self.options.verbose) in_subprocess = property( lambda self: self.options.resume_layer is not None and self.options.processes > 1) def compute_max_width(self): """Try to determine the terminal width.""" # Note that doing this every time is more test friendly. self.max_width = tigetnum('cols', self.max_width) def getShortDescription(self, test, room): """Return a description of a test that fits in ``room`` characters.""" room -= 1 s = str(test) if len(s) > room: pos = s.find(" (") if pos >= 0: w = room - (pos + 5) if w < 1: # first portion (test method name) is too long s = s[:room-3] + "..." else: pre = s[:pos+2] post = s[-w:] s = "%s...%s" % (pre, post) else: w = room - 4 s = '... ' + s[-w:] return ' ' + s[:room] def info(self, message): """Print an informative message.""" print message def info_suboptimal(self, message): """Print an informative message about losing some of the features. For example, when you run some tests in a subprocess, you lose the ability to use the debugger. """ print message def error(self, message): """Report an error.""" print message def error_with_banner(self, message): """Report an error with a big ASCII banner.""" print print '*'*70 self.error(message) print '*'*70 print def profiler_stats(self, stats): """Report profiler stats.""" stats.print_stats(50) def import_errors(self, import_errors): """Report test-module import errors (if any).""" if import_errors: print "Test-module import failures:" for error in import_errors: self.print_traceback("Module: %s\n" % error.module, error.exc_info), print def tests_with_errors(self, errors): """Report names of tests with errors (if any).""" if errors: print print "Tests with errors:" for test, exc_info in errors: print " ", test def tests_with_failures(self, failures): """Report names of tests with failures (if any).""" if failures: print print "Tests with failures:" for test, exc_info in failures: print " ", test def modules_with_import_problems(self, import_errors): """Report names of modules with import problems (if any).""" if import_errors: print print "Test-modules with import problems:" for test in import_errors: print " " + test.module def format_seconds(self, n_seconds): """Format a time in seconds.""" if n_seconds >= 60: n_minutes, n_seconds = divmod(n_seconds, 60) return "%d minutes %.3f seconds" % (n_minutes, n_seconds) else: return "%.3f seconds" % n_seconds def format_seconds_short(self, n_seconds): """Format a time in seconds (short version).""" return "%.3f s" % n_seconds def summary(self, n_tests, n_failures, n_errors, n_seconds): """Summarize the results of a single test layer.""" print (" Ran %s tests with %s failures and %s errors in %s." % (n_tests, n_failures, n_errors, self.format_seconds(n_seconds))) def totals(self, n_tests, n_failures, n_errors, n_seconds): """Summarize the results of all layers.""" print ("Total: %s tests, %s failures, %s errors in %s." % (n_tests, n_failures, n_errors, self.format_seconds(n_seconds))) def list_of_tests(self, tests, layer_name): """Report a list of test names.""" print "Listing %s tests:" % layer_name for test in tests: print ' ', test def garbage(self, garbage): """Report garbage generated by tests.""" if garbage: print "Tests generated new (%d) garbage:" % len(garbage) print garbage def test_garbage(self, test, garbage): """Report garbage generated by a test.""" if garbage: print "The following test left garbage:" print test print garbage def test_threads(self, test, new_threads): """Report threads left behind by a test.""" if new_threads: print "The following test left new threads behind:" print test print "New thread(s):", new_threads def refcounts(self, rc, prev): """Report a change in reference counts.""" print " sys refcount=%-8d change=%-6d" % (rc, rc - prev) def detailed_refcounts(self, track, rc, prev): """Report a change in reference counts, with extra detail.""" print (" sum detail refcount=%-8d" " sys refcount=%-8d" " change=%-6d" % (track.n, rc, rc - prev)) track.output() def start_set_up(self, layer_name): """Report that we're setting up a layer. The next output operation should be stop_set_up(). """ print " Set up %s" % layer_name, sys.stdout.flush() def stop_set_up(self, seconds): """Report that we've set up a layer. Should be called right after start_set_up(). """ print "in %s." % self.format_seconds(seconds) def start_tear_down(self, layer_name): """Report that we're tearing down a layer. The next output operation should be stop_tear_down() or tear_down_not_supported(). """ print " Tear down %s" % layer_name, sys.stdout.flush() def stop_tear_down(self, seconds): """Report that we've tore down a layer. Should be called right after start_tear_down(). """ print "in %s." % self.format_seconds(seconds) def tear_down_not_supported(self): """Report that we could not tear down a layer. Should be called right after start_tear_down(). """ print "... not supported" def start_test(self, test, tests_run, total_tests): """Report that we're about to run a test. The next output operation should be test_success(), test_error(), or test_failure(). """ self.test_width = 0 if self.progress: if self.last_width: sys.stdout.write('\r' + (' ' * self.last_width) + '\r') s = " %d/%d (%.1f%%)" % (tests_run, total_tests, tests_run * 100.0 / total_tests) sys.stdout.write(s) self.test_width += len(s) if self.verbose == 1: room = self.max_width - self.test_width - 1 s = self.getShortDescription(test, room) sys.stdout.write(s) self.test_width += len(s) elif self.verbose == 1: sys.stdout.write('.' * test.countTestCases()) elif self.in_subprocess: sys.stdout.write('.' * test.countTestCases()) # Give the parent process a new line so it sees the progress # in a timely manner. sys.stdout.write('\n') if self.verbose > 1: s = str(test) sys.stdout.write(' ') sys.stdout.write(s) self.test_width += len(s) + 1 sys.stdout.flush() def test_success(self, test, seconds): """Report that a test was successful. Should be called right after start_test(). The next output operation should be stop_test(). """ if self.verbose > 2: s = " (%s)" % self.format_seconds_short(seconds) sys.stdout.write(s) self.test_width += len(s) + 1 def test_error(self, test, seconds, exc_info): """Report that an error occurred while running a test. Should be called right after start_test(). The next output operation should be stop_test(). """ if self.verbose > 2: print " (%s)" % self.format_seconds_short(seconds) print self.print_traceback("Error in test %s" % test, exc_info) self.test_width = self.last_width = 0 def test_failure(self, test, seconds, exc_info): """Report that a test failed. Should be called right after start_test(). The next output operation should be stop_test(). """ if self.verbose > 2: print " (%s)" % self.format_seconds_short(seconds) print self.print_traceback("Failure in test %s" % test, exc_info) self.test_width = self.last_width = 0 def print_traceback(self, msg, exc_info): """Report an error with a traceback.""" print print msg print self.format_traceback(exc_info) def format_traceback(self, exc_info): """Format the traceback.""" v = exc_info[1] if isinstance(v, DocTestFailureException): tb = v.args[0] elif isinstance(v, doctest.DocTestFailure): tb = doctest_template % ( v.test.filename, v.test.lineno + v.example.lineno + 1, v.test.name, v.example.source, v.example.want, v.got, ) else: tb = "".join(traceback.format_exception(*exc_info)) return tb def stop_test(self, test): """Clean up the output state after a test.""" if self.progress: self.last_width = self.test_width elif self.verbose > 1: print sys.stdout.flush() def stop_tests(self): """Clean up the output state after a collection of tests.""" if self.progress and self.last_width: sys.stdout.write('\r' + (' ' * self.last_width) + '\r') if self.verbose == 1 or self.progress: print def tigetnum(attr, default=None): """Return a value from the terminfo database. Terminfo is used on Unix-like systems to report various terminal attributes (such as width, height or the number of supported colors). Returns ``default`` when the ``curses`` module is not available, or when sys.stdout is not a terminal. """ try: import curses except ImportError: # avoid reimporting a broken module in python 2.3 sys.modules['curses'] = None else: try: curses.setupterm() except (curses.error, TypeError): # You get curses.error when $TERM is set to an unknown name # You get TypeError when sys.stdout is not a real file object # (e.g. in unit tests that use various wrappers). pass else: return curses.tigetnum(attr) return default def terminal_has_colors(): """Determine whether the terminal supports colors. Some terminals (e.g. the emacs built-in one) don't. """ return tigetnum('colors', -1) >= 8 class ColorfulOutputFormatter(OutputFormatter): """Output formatter that uses ANSI color codes. Like syntax highlighting in your text editor, colorizing test failures helps the developer. """ # These colors are carefully chosen to have enough contrast # on terminals with both black and white background. colorscheme = {'normal': 'normal', 'default': 'default', 'info': 'normal', 'suboptimal-behaviour': 'magenta', 'error': 'brightred', 'number': 'green', 'slow-test': 'brightmagenta', 'ok-number': 'green', 'error-number': 'brightred', 'filename': 'lightblue', 'lineno': 'lightred', 'testname': 'lightcyan', 'failed-example': 'cyan', 'expected-output': 'green', 'actual-output': 'red', 'character-diffs': 'magenta', 'diff-chunk': 'magenta', 'exception': 'red'} # Map prefix character to color in diff output. This handles ndiff and # udiff correctly, but not cdiff. In cdiff we ought to highlight '!' as # expected-output until we see a '-', then highlight '!' as actual-output, # until we see a '*', then switch back to highlighting '!' as # expected-output. Nevertheless, coloried cdiffs are reasonably readable, # so I'm not going to fix this. # -- mgedmin diff_color = {'-': 'expected-output', '+': 'actual-output', '?': 'character-diffs', '@': 'diff-chunk', '*': 'diff-chunk', '!': 'actual-output',} prefixes = [('dark', '0;'), ('light', '1;'), ('bright', '1;'), ('bold', '1;'),] colorcodes = {'default': 0, 'normal': 0, 'black': 30, 'red': 31, 'green': 32, 'brown': 33, 'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36, 'grey': 37, 'gray': 37, 'white': 37} slow_test_threshold = 10.0 # seconds def color_code(self, color): """Convert a color description (e.g. 'lightgray') to a terminal code.""" prefix_code = '' for prefix, code in self.prefixes: if color.startswith(prefix): color = color[len(prefix):] prefix_code = code break color_code = self.colorcodes[color] return '\033[%s%sm' % (prefix_code, color_code) def color(self, what): """Pick a named color from the color scheme""" return self.color_code(self.colorscheme[what]) def colorize(self, what, message, normal='normal'): """Wrap message in color.""" return self.color(what) + message + self.color(normal) def error_count_color(self, n): """Choose a color for the number of errors.""" if n: return self.color('error-number') else: return self.color('ok-number') def info(self, message): """Print an informative message.""" print self.colorize('info', message) def info_suboptimal(self, message): """Print an informative message about losing some of the features. For example, when you run some tests in a subprocess, you lose the ability to use the debugger. """ print self.colorize('suboptimal-behaviour', message) def error(self, message): """Report an error.""" print self.colorize('error', message) def error_with_banner(self, message): """Report an error with a big ASCII banner.""" print print self.colorize('error', '*'*70) self.error(message) print self.colorize('error', '*'*70) print def tear_down_not_supported(self): """Report that we could not tear down a layer. Should be called right after start_tear_down(). """ print "...", self.colorize('suboptimal-behaviour', "not supported") def format_seconds(self, n_seconds, normal='normal'): """Format a time in seconds.""" if n_seconds >= 60: n_minutes, n_seconds = divmod(n_seconds, 60) return "%s minutes %s seconds" % ( self.colorize('number', '%d' % n_minutes, normal), self.colorize('number', '%.3f' % n_seconds, normal)) else: return "%s seconds" % ( self.colorize('number', '%.3f' % n_seconds, normal)) def format_seconds_short(self, n_seconds): """Format a time in seconds (short version).""" if n_seconds >= self.slow_test_threshold: color = 'slow-test' else: color = 'number' return self.colorize(color, "%.3f s" % n_seconds) def summary(self, n_tests, n_failures, n_errors, n_seconds): """Summarize the results.""" sys.stdout.writelines([ self.color('info'), ' Ran ', self.color('number'), str(n_tests), self.color('info'), ' tests with ', self.error_count_color(n_failures), str(n_failures), self.color('info'), ' failures and ', self.error_count_color(n_errors), str(n_errors), self.color('info'), ' errors in ', self.format_seconds(n_seconds, 'info'), '.', self.color('normal'), '\n']) def totals(self, n_tests, n_failures, n_errors, n_seconds): """Report totals (number of tests, failures, and errors).""" sys.stdout.writelines([ self.color('info'), 'Total: ', self.color('number'), str(n_tests), self.color('info'), ' tests, ', self.error_count_color(n_failures), str(n_failures), self.color('info'), ' failures, ', self.error_count_color(n_errors), str(n_errors), self.color('info'), ' errors in ', self.format_seconds(n_seconds, 'info'), '.', self.color('normal'), '\n']) def print_traceback(self, msg, exc_info): """Report an error with a traceback.""" print print self.colorize('error', msg) v = exc_info[1] if isinstance(v, DocTestFailureException): self.print_doctest_failure(v.args[0]) elif isinstance(v, doctest.DocTestFailure): # I don't think these are ever used... -- mgedmin tb = self.format_traceback(exc_info) print tb else: tb = self.format_traceback(exc_info) self.print_colorized_traceback(tb) def print_doctest_failure(self, formatted_failure): """Report a doctest failure. ``formatted_failure`` is a string -- that's what DocTestSuite/DocFileSuite gives us. """ color_of_indented_text = 'normal' colorize_diff = False for line in formatted_failure.splitlines(): if line.startswith('File '): m = re.match(r'File "(.*)", line (\d*), in (.*)$', line) if m: filename, lineno, test = m.groups() sys.stdout.writelines([ self.color('normal'), 'File "', self.color('filename'), filename, self.color('normal'), '", line ', self.color('lineno'), lineno, self.color('normal'), ', in ', self.color('testname'), test, self.color('normal'), '\n']) else: print line elif line.startswith(' ') or line.strip() == '': if colorize_diff and len(line) > 4: color = self.diff_color.get(line[4], color_of_indented_text) print self.colorize(color, line) else: if line.strip() != '': print self.colorize(color_of_indented_text, line) else: print line else: colorize_diff = False if line.startswith('Failed example'): color_of_indented_text = 'failed-example' elif line.startswith('Expected:'): color_of_indented_text = 'expected-output' elif line.startswith('Got:'): color_of_indented_text = 'actual-output' elif line.startswith('Exception raised:'): color_of_indented_text = 'exception' elif line.startswith('Differences '): color_of_indented_text = 'normal' colorize_diff = True else: color_of_indented_text = 'normal' print line print def print_colorized_traceback(self, formatted_traceback): """Report a test failure. ``formatted_traceback`` is a string. """ for line in formatted_traceback.splitlines(): if line.startswith(' File'): m = re.match(r' File "(.*)", line (\d*), in (.*)$', line) if m: filename, lineno, test = m.groups() sys.stdout.writelines([ self.color('normal'), ' File "', self.color('filename'), filename, self.color('normal'), '", line ', self.color('lineno'), lineno, self.color('normal'), ', in ', self.color('testname'), test, self.color('normal'), '\n']) else: print line elif line.startswith(' '): print self.colorize('failed-example', line) elif line.startswith('Traceback (most recent call last)'): print line else: print self.colorize('exception', line) print class FakeTest(object): """A fake test object that only has an id.""" failureException = None def __init__(self, test_id): self._id = test_id def id(self): return self._id # Conditional imports, we don't want zope.testing to have a hard dependency on # subunit. try: import subunit from subunit.iso8601 import Utc except ImportError: subunit = None # testtools is a hard dependency of subunit itself, guarding separately for # richer error messages. try: from testtools import content except ImportError: content = None class SubunitOutputFormatter(object): """A subunit output formatter. This output formatter generates subunit compatible output (see https://launchpad.net/subunit). Subunit output is essentially a stream of results of unit tests. In this formatter, non-test events (such as layer set up) are encoded as specially tagged tests and summary-generating methods (such as modules_with_import_problems) deliberately do nothing. In particular, for a layer 'foo', the fake tests related to layer set up and tear down are tagged with 'zope:layer' and are called 'foo:setUp' and 'foo:tearDown'. Any tests within layer 'foo' are tagged with 'zope:layer:foo'. Note that all tags specific to this formatter begin with 'zope:' """ # subunit output is designed for computers, so displaying a progress bar # isn't helpful. progress = False verbose = property(lambda self: self.options.verbose) TAG_INFO_SUBOPTIMAL = 'zope:info_suboptimal' TAG_ERROR_WITH_BANNER = 'zope:error_with_banner' TAG_LAYER = 'zope:layer' TAG_IMPORT_ERROR = 'zope:import_error' TAG_PROFILER_STATS = 'zope:profiler_stats' TAG_GARBAGE = 'zope:garbage' TAG_THREADS = 'zope:threads' TAG_REFCOUNTS = 'zope:refcounts' def __init__(self, options): if subunit is None: raise Exception("Requires subunit 0.0.5 or better") if content is None: raise Exception("Requires testtools 0.9.2 or better") self.options = options self._stream = sys.stdout self._subunit = subunit.TestProtocolClient(self._stream) # Used to track the last layer that was set up or torn down. Either # None or (layer_name, last_touched_time). self._last_layer = None self.UTC = Utc() # Content types used in the output. self.TRACEBACK_CONTENT_TYPE = content.ContentType( 'text', 'x-traceback', dict(language='python', charset='utf8')) self.PROFILE_CONTENT_TYPE = content.ContentType( 'application', 'x-binary-profile') self.PLAIN_TEXT = content.ContentType( 'text', 'plain', {'charset': 'utf8'}) @property def _time_tests(self): return self.verbose > 2 def _emit_timestamp(self, now=None): """Emit a timestamp to the subunit stream. If 'now' is not specified, use the current time on the system clock. """ if now is None: now = datetime.now(self.UTC) self._subunit.time(now) return now def _emit_tag(self, tag): self._stream.write('tags: %s\n' % (tag,)) def _stop_tag(self, tag): self._stream.write('tags: -%s\n' % (tag,)) def _emit_fake_test(self, message, tag, details=None): """Emit a successful fake test to the subunit stream. Use this to print tagged informative messages. """ test = FakeTest(message) self._subunit.startTest(test) self._emit_tag(tag) self._subunit.addSuccess(test, details) def _emit_error(self, error_id, tag, exc_info): """Emit an error to the subunit stream. Use this to pass on information about errors that occur outside of tests. """ test = FakeTest(error_id) self._subunit.startTest(test) self._emit_tag(tag) self._subunit.addError(test, exc_info) def info(self, message): """Print an informative message, but only if verbose.""" # info() output is not relevant to actual test results. It only says # things like "Running tests" or "Tearing down left over layers", # things that are communicated already by the subunit stream. Just # suppress the info() output. pass def info_suboptimal(self, message): """Print an informative message about losing some of the features. For example, when you run some tests in a subprocess, you lose the ability to use the debugger. """ # Used _only_ to indicate running in a subprocess. self._emit_fake_test(message.strip(), self.TAG_INFO_SUBOPTIMAL) def error(self, message): """Report an error.""" # XXX: Mostly used for user errors, sometimes used for errors in the # test framework, sometimes used to record layer setUp failure (!!!). self._stream.write('%s\n' % (message,)) def error_with_banner(self, message): """Report an error with a big ASCII banner.""" # Either "Could not communicate with subprocess" # Or "Can't post-mortem debug when running a layer as a subprocess!" self._emit_fake_test(message, self.TAG_ERROR_WITH_BANNER) def _enter_layer(self, layer_name): """Signal in the subunit stream that we are entering a layer.""" self._emit_tag('zope:layer:%s' % (layer_name,)) def _exit_layer(self, layer_name): """Signal in the subunit stream that we are leaving a layer.""" self._stop_tag('zope:layer:%s' % (layer_name,)) def start_set_up(self, layer_name): """Report that we're setting up a layer. We do this by emitting a tag of the form 'layer:$LAYER_NAME'. """ now = self._emit_timestamp() self._subunit.startTest(FakeTest('%s:setUp' % (layer_name,))) self._emit_tag(self.TAG_LAYER) self._last_layer = (layer_name, now) def stop_set_up(self, seconds): layer_name, start_time = self._last_layer self._last_layer = None self._emit_timestamp(start_time + timedelta(seconds=seconds)) self._subunit.addSuccess(FakeTest('%s:setUp' % (layer_name,))) self._enter_layer(layer_name) def start_tear_down(self, layer_name): """Report that we're tearing down a layer. We do this by removing a tag of the form 'layer:$LAYER_NAME'. """ self._exit_layer(layer_name) now = self._emit_timestamp() self._subunit.startTest(FakeTest('%s:tearDown' % (layer_name,))) self._emit_tag(self.TAG_LAYER) self._last_layer = (layer_name, now) def stop_tear_down(self, seconds): layer_name, start_time = self._last_layer self._last_layer = None self._emit_timestamp(start_time + timedelta(seconds=seconds)) self._subunit.addSuccess(FakeTest('%s:tearDown' % (layer_name,))) def tear_down_not_supported(self): """Report that we could not tear down a layer. Should be called right after start_tear_down(). """ layer_name, start_time = self._last_layer self._last_layer = None self._emit_timestamp(datetime.now(self.UTC)) self._subunit.addSkip( FakeTest('%s:tearDown' % (layer_name,)), "tearDown not supported") def summary(self, n_tests, n_failures, n_errors, n_seconds): """Print out a summary. Since subunit is a stream protocol format, it has no need for a summary. When the stream is finished other tools can generate a summary if so desired. """ pass def start_test(self, test, tests_run, total_tests): """Report that we're about to run a test. The next output operation should be test_success(), test_error(), or test_failure(). """ if self._time_tests: self._emit_timestamp() # Note that this always emits newlines, so it will function as well as # other start_test implementations if we are running in a subprocess. self._subunit.startTest(test) def stop_test(self, test): """Clean up the output state after a test.""" self._subunit.stopTest(test) self._stream.flush() def stop_tests(self): """Clean up the output state after a collection of tests.""" self._stream.flush() def test_success(self, test, seconds): if self._time_tests: self._emit_timestamp() self._subunit.addSuccess(test) def import_errors(self, import_errors): """Report test-module import errors (if any).""" if not import_errors: return for error in import_errors: self._emit_error( error.module, self.TAG_IMPORT_ERROR, error.exc_info) def modules_with_import_problems(self, import_errors): """Report names of modules with import problems (if any).""" # This is simply a summary method, and subunit output doesn't benefit # from summaries. pass def _exc_info_to_details(self, exc_info): """Translate 'exc_info' into a details dictionary usable with subunit. """ # In an ideal world, we'd use the pre-bundled 'TracebackContent' class # from testtools. However, 'OutputFormatter' contains special logic to # handle errors from doctests, so we have to use that and manually # create an object equivalent to an instance of 'TracebackContent'. formatter = OutputFormatter(None) traceback = formatter.format_traceback(exc_info) return { 'traceback': content.Content( self.TRACEBACK_CONTENT_TYPE, lambda: [traceback.encode('utf8')])} def test_error(self, test, seconds, exc_info): """Report that an error occurred while running a test. Should be called right after start_test(). The next output operation should be stop_test(). """ if self._time_tests: self._emit_timestamp() details = self._exc_info_to_details(exc_info) self._subunit.addError(test, details=details) def test_failure(self, test, seconds, exc_info): """Report that a test failed. Should be called right after start_test(). The next output operation should be stop_test(). """ if self._time_tests: self._emit_timestamp() details = self._exc_info_to_details(exc_info) self._subunit.addFailure(test, details=details) def profiler_stats(self, stats): """Report profiler stats.""" fd, filename = tempfile.mkstemp() os.close(fd) try: stats.dump_stats(filename) stats_dump = open(filename) try: profile_content = content.Content( self.PROFILE_CONTENT_TYPE, stats_dump.read) details = {'profiler-stats': profile_content} # Name the test 'zope:profiler_stats' just like its tag. self._emit_fake_test( self.TAG_PROFILER_STATS, self.TAG_PROFILER_STATS, details) finally: stats_dump.close() finally: os.unlink(filename) def tests_with_errors(self, errors): """Report tests with errors. Simply not supported by the subunit formatter. Fancy summary output doesn't make sense. """ pass def tests_with_failures(self, failures): """Report tests with failures. Simply not supported by the subunit formatter. Fancy summary output doesn't make sense. """ pass def totals(self, n_tests, n_failures, n_errors, n_seconds): """Summarize the results of all layers. Simply not supported by the subunit formatter. Fancy summary output doesn't make sense. """ pass def list_of_tests(self, tests, layer_name): """Report a list of test names.""" self._enter_layer(layer_name) for test in tests: self._subunit.startTest(test) self._subunit.addSuccess(test) self._exit_layer(layer_name) def _get_text_details(self, name, text): """Get a details dictionary that just has some plain text.""" return { name: content.Content( self.PLAIN_TEXT, lambda: [text.encode('utf8')])} def garbage(self, garbage): """Report garbage generated by tests.""" # XXX: Really, 'garbage', 'profiler_stats' and the 'refcounts' twins # ought to add extra details to a fake test that represents the # summary information for the whole suite. However, there's no event # on output formatters for "everything is really finished, honest". -- # jml, 2010-02-14 details = self._get_text_details('garbage', unicode(garbage)) self._emit_fake_test( self.TAG_GARBAGE, self.TAG_GARBAGE, details) def test_garbage(self, test, garbage): """Report garbage generated by a test. Encoded in the subunit stream as a test error. Clients can filter out these tests based on the tag if they don't think garbage should fail the test run. """ # XXX: Perhaps 'test_garbage' and 'test_threads' ought to be within # the output for the actual test, appended as details to whatever # result the test gets. Not an option with the present API, as there's # no event for "no more output for this test". -- jml, 2010-02-14 self._subunit.startTest(test) self._emit_tag(self.TAG_GARBAGE) self._subunit.addError( test, self._get_text_details('garbage', unicode(garbage))) def test_threads(self, test, new_threads): """Report threads left behind by a test. Encoded in the subunit stream as a test error. Clients can filter out these tests based on the tag if they don't think left-over threads should fail the test run. """ self._subunit.startTest(test) self._emit_tag(self.TAG_THREADS) self._subunit.addError( test, self._get_text_details('garbage', unicode(new_threads))) def refcounts(self, rc, prev): """Report a change in reference counts.""" details = self._get_text_details('sys-refcounts', str(rc)) details.update( self._get_text_details('changes', str(rc - prev))) # XXX: Emit the details dict as JSON? self._emit_fake_test( self.TAG_REFCOUNTS, self.TAG_REFCOUNTS, details) def detailed_refcounts(self, track, rc, prev): """Report a change in reference counts, with extra detail.""" details = self._get_text_details('sys-refcounts', str(rc)) details.update( self._get_text_details('changes', str(rc - prev))) details.update( self._get_text_details('track', str(track.delta))) self._emit_fake_test( self.TAG_REFCOUNTS, self.TAG_REFCOUNTS, details) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-debugging.txt0000644000175000017500000000735112214017655030436 0ustar arnauarnauDebugging ========= The testrunner module supports post-mortem debugging and debugging using `pdb.set_trace`. Let's look first at using `pdb.set_trace`. To demonstrate this, we'll provide input via helper Input objects: >>> class Input: ... def __init__(self, src): ... self.lines = src.split('\n') ... def readline(self): ... line = self.lines.pop(0) ... print line ... return line+'\n' If a test or code called by a test calls pdb.set_trace, then the runner will enter pdb at that point: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> from zope.testing import testrunner >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> real_stdin = sys.stdin >>> if sys.version_info[:2] == (2, 3): ... sys.stdin = Input('n\np x\nc') ... else: ... sys.stdin = Input('p x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t set_trace1').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +ELLIPSIS Running zope.testing.testrunner.layer.UnitTests tests: ... > testrunner-ex/sample3/sampletests_d.py(27)test_set_trace1() -> y = x (Pdb) p x 1 (Pdb) c Ran 1 tests with 0 failures and 0 errors in 0.001 seconds. ... False Note that, prior to Python 2.4, calling pdb.set_trace caused pdb to break in the pdb.set_trace function. It was necessary to use 'next' or 'up' to get to the application code that called pdb.set_trace. In Python 2.4, pdb.set_trace causes pdb to stop right after the call to pdb.set_trace. You can also do post-mortem debugging, using the --post-mortem (-D) option: >>> sys.stdin = Input('p x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t post_mortem1 -D').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +NORMALIZE_WHITESPACE +REPORT_NDIFF Running zope.testing.testrunner.layer.UnitTests tests: ... Error in test test_post_mortem1 (sample3.sampletests_d.TestSomething) Traceback (most recent call last): File "testrunner-ex/sample3/sampletests_d.py", line 34, in test_post_mortem1 raise ValueError ValueError exceptions.ValueError: > testrunner-ex/sample3/sampletests_d.py(34)test_post_mortem1() -> raise ValueError (Pdb) p x 1 (Pdb) c True Note that the test runner exits after post-mortem debugging. In the example above, we debugged an error. Failures are actually converted to errors and can be debugged the same way: >>> sys.stdin = Input('p x\np y\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t post_mortem_failure1 -D').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +NORMALIZE_WHITESPACE +REPORT_NDIFF Running zope.testing.testrunner.layer.UnitTests tests: ... Error in test test_post_mortem_failure1 (sample3.sampletests_d.TestSomething) Traceback (most recent call last): File ".../unittest.py", line 252, in debug getattr(self, self.__testMethodName)() File "testrunner-ex/sample3/sampletests_d.py", line 42, in test_post_mortem_failure1 assert x == y AssertionError ...AssertionError: > testrunner-ex/sample3/sampletests_d.py(42)test_post_mortem_failure1() -> assert x == y (Pdb) p x 1 (Pdb) p y 2 (Pdb) c True zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-subunit-leaks.txt0000644000175000017500000000707612214017655031275 0ustar arnauarnauDebugging Memory Leaks with subunit output ========================================== The --report-refcounts (-r) option can be used with the --repeat (-N) option to detect and diagnose memory leaks. To use this option, you must configure Python with the --with-pydebug option. (On Unix, pass this option to configure and then build Python.) For more detailed documentation, see testrunner-leaks.txt. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> from zope.testing import testrunner Each layer is repeated the requested number of times. For each iteration after the first, the system refcount and change in system refcount is shown. The system refcount is the total of all refcount in the system. When a refcount on any object is changed, the system refcount is changed by the same amount. Tests that don't leak show zero changes in system refcount. Let's look at an example test that leaks: >>> sys.argv = 'test --subunit --tests-pattern leak -N2 -r'.split() >>> _ = testrunner.run_internal(defaults) time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: zope.testing.testrunner.layer.UnitTests:setUp tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: zope.testing.testrunner.layer.UnitTests:setUp tags: zope:layer:zope.testing.testrunner.layer.UnitTests test: leak.TestSomething.testleak successful: leak.TestSomething.testleak test: leak.TestSomething.testleak successful: leak.TestSomething.testleak test: zope:refcounts tags: zope:refcounts successful: zope:refcounts [ multipart Content-Type: text/plain;charset=utf8 ... ...\r ...\r Content-Type: text/plain;charset=utf8 ... ...\r ...\r ] tags: -zope:layer:zope.testing.testrunner.layer.UnitTests time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: zope.testing.testrunner.layer.UnitTests:tearDown tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: zope.testing.testrunner.layer.UnitTests:tearDown Here we see that the system refcount is increasing. If we specify a verbosity greater than one, we can get details broken out by object type (or class): >>> sys.argv = 'test --subunit --tests-pattern leak -N2 -v -r'.split() >>> _ = testrunner.run_internal(defaults) time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: zope.testing.testrunner.layer.UnitTests:setUp tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: zope.testing.testrunner.layer.UnitTests:setUp tags: zope:layer:zope.testing.testrunner.layer.UnitTests test: leak.TestSomething.testleak successful: leak.TestSomething.testleak test: leak.TestSomething.testleak successful: leak.TestSomething.testleak test: zope:refcounts tags: zope:refcounts successful: zope:refcounts [ multipart Content-Type: text/plain;charset=utf8 ... ...\r ...\r Content-Type: text/plain;charset=utf8 ... ...\r ...\r Content-Type: text/plain;charset=utf8 ... ...\r ... ] tags: -zope:layer:zope.testing.testrunner.layer.UnitTests time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: zope.testing.testrunner.layer.UnitTests:tearDown tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: zope.testing.testrunner.layer.UnitTests:tearDown zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-layers.txt0000644000175000017500000002225512214017655030002 0ustar arnauarnauLayer Selection =============== We can select which layers to run using the --layer option: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --layer 112 --layer Unit'.split() >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running samplelayers.Layer112 tests: Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer112 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running zope.testing.testrunner.layer.UnitTests tests: Tear down samplelayers.Layer112 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 226 tests, 0 failures, 0 errors in N.NNN seconds. False We can also specify that we want to run only the unit tests: >>> sys.argv = 'test -u'.split() >>> testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Or that we want to run all of the tests except for the unit tests: >>> sys.argv = 'test -f'.split() >>> testrunner.run_internal(defaults) Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in N.NNN seconds. Ran 9 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer11 tests: Set up samplelayers.Layer11 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer111 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer112 tests: Tear down samplelayers.Layer111 in N.NNN seconds. Set up samplelayers.Layer112 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer12 tests: Tear down samplelayers.Layer112 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer12 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer121 tests: Set up samplelayers.Layer121 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer122 tests: Tear down samplelayers.Layer121 in N.NNN seconds. Set up samplelayers.Layer122 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Total: 213 tests, 0 failures, 0 errors in N.NNN seconds. False Or we can explicitly say that we want both unit and non-unit tests. >>> sys.argv = 'test -uf'.split() >>> testrunner.run_internal(defaults) Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in N.NNN seconds. Ran 9 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer11 tests: Set up samplelayers.Layer11 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer111 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer112 tests: Tear down samplelayers.Layer111 in N.NNN seconds. Set up samplelayers.Layer112 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer12 tests: Tear down samplelayers.Layer112 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer12 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer121 tests: Set up samplelayers.Layer121 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer122 tests: Tear down samplelayers.Layer121 in N.NNN seconds. Set up samplelayers.Layer122 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running zope.testing.testrunner.layer.UnitTests tests: Tear down samplelayers.Layer122 in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 405 tests, 0 failures, 0 errors in N.NNN seconds. False It is possible to force the layers to run in subprocesses and parallelize them. >>> sys.argv = [testrunner_script, '-j2'] >>> testrunner.run_internal(defaults) # doctest: +REPORT_NDIFF Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in N.NNN seconds. Ran 9 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer11 tests: Running in a subprocess. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer11 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Running samplelayers.Layer111 tests: Running in a subprocess. Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer111 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tear down samplelayers.Layer111 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Running samplelayers.Layer112 tests: Running in a subprocess. Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer112 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tear down samplelayers.Layer112 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Running samplelayers.Layer12 tests: Running in a subprocess. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer12 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Running samplelayers.Layer121 tests: Running in a subprocess. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer12 in N.NNN seconds. Set up samplelayers.Layer121 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tear down samplelayers.Layer121 in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Running samplelayers.Layer122 tests: Running in a subprocess. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer12 in N.NNN seconds. Set up samplelayers.Layer122 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tear down samplelayers.Layer122 in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Running zope.testing.testrunner.layer.UnitTests tests: Running in a subprocess. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Tearing down left over layers: Tear down samplelayers.Layer1 in N.NNN seconds. Total: 405 tests, 0 failures, 0 errors in N.NNN seconds. False zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/runner.py0000644000175000017500000006572412214017655025426 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test execution $Id: __init__.py 86232 2008-05-03 15:09:33Z ctheune $ """ import subprocess import cStringIO import errno import gc import Queue import re import sys import threading import time import traceback import unittest from zope.testing.testrunner.find import import_name from zope.testing.testrunner.find import name_from_layer, _layer_name_cache from zope.testing.testrunner.refcount import TrackRefs from zope.testing.testrunner.options import get_options import zope.testing.testrunner.coverage import zope.testing.testrunner._doctest import zope.testing.testrunner.logsupport import zope.testing.testrunner.selftest import zope.testing.testrunner.profiling import zope.testing.testrunner.filter import zope.testing.testrunner.garbagecollection import zope.testing.testrunner.listing import zope.testing.testrunner.statistics import zope.testing.testrunner.process import zope.testing.testrunner.interfaces import zope.testing.testrunner.debug import zope.testing.testrunner.tb_format import zope.testing.testrunner.shuffle PYREFCOUNT_PATTERN = re.compile('\[[0-9]+ refs\]') is_jython = sys.platform.startswith('java') class SubprocessError(Exception): """An error occurred when running a subprocess """ def __init__(self, reason, stderr): self.reason = reason self.stderr = stderr def __str__(self): return '%s: %s' % (self.reason, self.stderr) class CanNotTearDown(Exception): "Couldn't tear down a test" class Runner(object): """The test runner. It is the central point of this package and responsible for finding and executing tests as well as configuring itself from the (command-line) options passed into it. """ def __init__(self, defaults=None, args=None, found_suites=None, options=None, script_parts=None): self.defaults = defaults self.args = args self.found_suites = found_suites self.options = options self.script_parts = script_parts self.failed = True self.ran = 0 self.failures = [] self.errors = [] self.show_report = True self.do_run_tests = True self.features = [] self.tests_by_layer_name = {} def ordered_layers(self): layer_names = dict([(layer_from_name(layer_name), layer_name) for layer_name in self.tests_by_layer_name]) for layer in order_by_bases(layer_names): layer_name = layer_names[layer] yield layer_name, layer, self.tests_by_layer_name[layer_name] def register_tests(self, tests): """Registers tests.""" # XXX To support multiple features that find tests this shouldn't be # an update but merge the various layers individually. self.tests_by_layer_name.update(tests) def run(self): self.configure() if self.options.fail: return True # XXX Hacky to support existing code. self.layer_name_cache = _layer_name_cache self.layer_name_cache.clear() # Global setup for feature in self.features: feature.global_setup() # Late setup # # Some system tools like profilers are really bad with stack frames. # E.g. hotshot doesn't like it when we leave the stack frame that we # called start() from. for feature in self.features: feature.late_setup() try: if self.do_run_tests: self.run_tests() finally: # Early teardown for feature in reversed(self.features): feature.early_teardown() # Global teardown for feature in reversed(self.features): feature.global_teardown() if self.show_report: for feature in self.features: feature.report() def configure(self): if self.args is None: self.args = sys.argv[:] # Check to see if we are being run as a subprocess. If we are, # then use the resume-layer and defaults passed in. if len(self.args) > 1 and self.args[1] == '--resume-layer': self.args.pop(1) resume_layer = self.args.pop(1) resume_number = int(self.args.pop(1)) self.defaults = [] while len(self.args) > 1 and self.args[1] == '--default': self.args.pop(1) self.defaults.append(self.args.pop(1)) sys.stdin = FakeInputContinueGenerator() else: resume_layer = resume_number = None options = get_options(self.args, self.defaults) options.testrunner_defaults = self.defaults options.resume_layer = resume_layer options.resume_number = resume_number self.options = options self.features.append(zope.testing.testrunner.selftest.SelfTest(self)) self.features.append(zope.testing.testrunner.logsupport.Logging(self)) self.features.append(zope.testing.testrunner.coverage.Coverage(self)) self.features.append(zope.testing.testrunner._doctest.DocTest(self)) self.features.append(zope.testing.testrunner.profiling.Profiling(self)) if is_jython: # Jython GC support is not yet implemented pass else: self.features.append( zope.testing.testrunner.garbagecollection.Threshold(self)) self.features.append( zope.testing.testrunner.garbagecollection.Debug(self)) self.features.append(zope.testing.testrunner.find.Find(self)) self.features.append(zope.testing.testrunner.shuffle.Shuffle(self)) self.features.append(zope.testing.testrunner.process.SubProcess(self)) self.features.append(zope.testing.testrunner.filter.Filter(self)) self.features.append(zope.testing.testrunner.listing.Listing(self)) self.features.append( zope.testing.testrunner.statistics.Statistics(self)) self.features.append(zope.testing.testrunner.tb_format.Traceback(self)) # Remove all features that aren't activated self.features = [f for f in self.features if f.active] def run_tests(self): """Run all tests that were registered. Returns True if there where failures or False if all tests passed. """ setup_layers = {} layers_to_run = list(self.ordered_layers()) should_resume = False while layers_to_run: layer_name, layer, tests = layers_to_run[0] for feature in self.features: feature.layer_setup(layer) try: self.ran += run_layer(self.options, layer_name, layer, tests, setup_layers, self.failures, self.errors) except zope.testing.testrunner.interfaces.EndRun: self.failed = True return except CanNotTearDown: if not self.options.resume_layer: should_resume = True break layers_to_run.pop(0) if self.options.processes > 1: should_resume = True break if should_resume: if layers_to_run: self.ran += resume_tests( self.script_parts, self.options, self.features, layers_to_run, self.failures, self.errors) if setup_layers: if self.options.resume_layer is None: self.options.output.info("Tearing down left over layers:") tear_down_unneeded(self.options, (), setup_layers, True) self.failed = bool(self.import_errors or self.failures or self.errors) def run_tests(options, tests, name, failures, errors): repeat = options.repeat or 1 repeat_range = iter(range(repeat)) ran = 0 output = options.output if is_jython: # Jython has no GC suppport - set count to 0 lgarbage = 0 else: gc.collect() lgarbage = len(gc.garbage) sumrc = 0 if options.report_refcounts: if options.verbose: # XXX This code path is untested track = TrackRefs() rc = sys.gettotalrefcount() for iteration in repeat_range: if repeat > 1: output.info("Iteration %d" % (iteration + 1)) if options.verbose > 0 or options.progress: output.info(' Running:') result = TestResult(options, tests, layer_name=name) t = time.time() if options.post_mortem: # post-mortem debugging for test in tests: if result.shouldStop: break result.startTest(test) state = test.__dict__.copy() try: try: test.debug() except KeyboardInterrupt: raise except: result.addError( test, sys.exc_info()[:2] + (sys.exc_info()[2].tb_next, ), ) else: result.addSuccess(test) finally: result.stopTest(test) test.__dict__.clear() test.__dict__.update(state) else: # normal for test in tests: if result.shouldStop: break state = test.__dict__.copy() test(result) test.__dict__.clear() test.__dict__.update(state) t = time.time() - t output.stop_tests() failures.extend(result.failures) errors.extend(result.errors) output.summary(result.testsRun, len(result.failures), len(result.errors), t) ran = result.testsRun if is_jython: lgarbage = 0 else: gc.collect() if len(gc.garbage) > lgarbage: output.garbage(gc.garbage[lgarbage:]) lgarbage = len(gc.garbage) if options.report_refcounts: # If we are being tested, we don't want stdout itself to # foul up the numbers. :) try: sys.stdout.getvalue() except AttributeError: pass prev = rc rc = sys.gettotalrefcount() if options.verbose: track.update() if iteration > 0: output.detailed_refcounts(track, rc, prev) else: track.delta = None elif iteration > 0: output.refcounts(rc, prev) return ran def run_layer(options, layer_name, layer, tests, setup_layers, failures, errors): output = options.output gathered = [] gather_layers(layer, gathered) needed = dict([(l, 1) for l in gathered]) if options.resume_number != 0: output.info("Running %s tests:" % layer_name) tear_down_unneeded(options, needed, setup_layers) if options.resume_layer is not None: output.info_suboptimal(" Running in a subprocess.") try: setup_layer(options, layer, setup_layers) except zope.testing.testrunner.interfaces.EndRun: raise except Exception: f = cStringIO.StringIO() traceback.print_exc(file=f) output.error(f.getvalue()) errors.append((SetUpLayerFailure(), sys.exc_info())) return 0 else: return run_tests(options, tests, layer_name, failures, errors) class SetUpLayerFailure(unittest.TestCase): def runTest(self): "Layer set up failure." def spawn_layer_in_subprocess(result, script_parts, options, features, layer_name, layer, failures, errors, resume_number): output = options.output try: # BBB if script_parts is None: script_parts = sys.argv[0:1] args = [sys.executable] args.extend(script_parts) args.extend(['--resume-layer', layer_name, str(resume_number)]) for d in options.testrunner_defaults: args.extend(['--default', d]) args.extend(options.original_testrunner_args[1:]) # this is because of a bug in Python (http://www.python.org/sf/900092) if (options.profile == 'hotshot' and sys.version_info[:3] <= (2, 4, 1)): args.insert(1, '-O') if sys.platform.startswith('win'): args = args[0] + ' ' + ' '.join([ ('"' + a.replace('\\', '\\\\').replace('"', '\\"') + '"') for a in args[1:]]) for feature in features: feature.layer_setup(layer) child = subprocess.Popen(args, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=not sys.platform.startswith('win')) while True: try: while True: # We use readline() instead of iterating over stdout # because it appears that iterating over stdout causes a # lot more buffering to take place (probably so it can # return its lines as a batch). We don't want too much # buffering because this foils automatic and human monitors # trying to verify that the subprocess is still alive. l = child.stdout.readline() if not l: break result.write(l) except IOError, e: if e.errno == errno.EINTR: # If the subprocess dies before we finish reading its # output, a SIGCHLD signal can interrupt the reading. # The correct thing to to in that case is to retry. continue output.error( "Error reading subprocess output for %s" % layer_name) output.info(str(e)) else: break # Now stderr should be ready to read the whole thing. erriter = iter(child.stderr.read().splitlines()) nfail = nerr = 0 for line in erriter: try: result.num_ran, nfail, nerr = map(int, line.strip().split()) except ValueError: continue else: break else: output.error_with_banner("Could not communicate with subprocess") while nfail > 0: nfail -= 1 failures.append((erriter.next().strip(), None)) while nerr > 0: nerr -= 1 errors.append((erriter.next().strip(), None)) finally: result.done = True class AbstractSubprocessResult(object): """A result of a subprocess layer run.""" num_ran = 0 done = False def __init__(self, layer_name, queue): self.layer_name = layer_name self.queue = queue self.stdout = [] def write(self, out): """Receive a line of the subprocess out.""" class DeferredSubprocessResult(AbstractSubprocessResult): """Keeps stdout around for later processing,""" def write(self, out): if not _is_dots(out): self.stdout.append(out) class ImmediateSubprocessResult(AbstractSubprocessResult): """Sends complete output to queue.""" def write(self, out): sys.stdout.write(out) # Help keep-alive monitors (human or automated) keep up-to-date. sys.stdout.flush() _is_dots = re.compile(r'\.+(\r\n?|\n)').match # Windows sneaks in a \r\n. class KeepaliveSubprocessResult(AbstractSubprocessResult): "Keeps stdout for later processing; sends marks to queue to show activity." _done = False def _set_done(self, value): self._done = value assert value, 'Internal error: unexpectedly setting done to False' self.queue.put((self.layer_name, ' LAYER FINISHED')) done = property(lambda self: self._done, _set_done) def write(self, out): if _is_dots(out): self.queue.put((self.layer_name, out.strip())) else: self.stdout.append(out) def resume_tests(script_parts, options, features, layers, failures, errors): results = [] stdout_queue = None if options.processes == 1: result_factory = ImmediateSubprocessResult elif options.verbose > 1: result_factory = KeepaliveSubprocessResult stdout_queue = Queue.Queue() else: result_factory = DeferredSubprocessResult resume_number = int(options.processes > 1) ready_threads = [] for layer_name, layer, tests in layers: result = result_factory(layer_name, stdout_queue) results.append(result) ready_threads.append(threading.Thread( target=spawn_layer_in_subprocess, args=(result, script_parts, options, features, layer_name, layer, failures, errors, resume_number))) resume_number += 1 # Now start a few threads at a time. running_threads = [] results_iter = iter(results) current_result = results_iter.next() last_layer_intermediate_output = None output = None while ready_threads or running_threads: while len(running_threads) < options.processes and ready_threads: thread = ready_threads.pop(0) thread.start() running_threads.append(thread) for index, thread in reversed(list(enumerate(running_threads))): if not thread.isAlive(): del running_threads[index] # Clear out any messages in queue while stdout_queue is not None: previous_output = output try: layer_name, output = stdout_queue.get(False) except Queue.Empty: break if layer_name != last_layer_intermediate_output: # Clarify what layer is reporting activity. if previous_output is not None: sys.stdout.write(']\n') sys.stdout.write( '[Parallel tests running in %s:\n ' % (layer_name,)) last_layer_intermediate_output = layer_name sys.stdout.write(output) # Display results in the order they would have been displayed, had the # work not been done in parallel. while current_result and current_result.done: if output is not None: sys.stdout.write(']\n') output = None map(sys.stdout.write, current_result.stdout) try: current_result = results_iter.next() except StopIteration: current_result = None # Help keep-alive monitors (human or automated) keep up-to-date. sys.stdout.flush() time.sleep(0.01) # Keep the loop from being too tight. # Return the total number of tests run. return sum(r.num_ran for r in results) def tear_down_unneeded(options, needed, setup_layers, optional=False): # Tear down any layers not needed for these tests. The unneeded layers # might interfere. unneeded = [l for l in setup_layers if l not in needed] unneeded = order_by_bases(unneeded) unneeded.reverse() output = options.output for l in unneeded: output.start_tear_down(name_from_layer(l)) t = time.time() try: try: if hasattr(l, 'tearDown'): l.tearDown() except NotImplementedError: output.tear_down_not_supported() if not optional: raise CanNotTearDown(l) else: output.stop_tear_down(time.time() - t) finally: del setup_layers[l] cant_pm_in_subprocess_message = """ Can't post-mortem debug when running a layer as a subprocess! Try running layer %r by itself. """ def setup_layer(options, layer, setup_layers): assert layer is not object output = options.output if layer not in setup_layers: for base in layer.__bases__: if base is not object: setup_layer(options, base, setup_layers) output.start_set_up(name_from_layer(layer)) t = time.time() if hasattr(layer, 'setUp'): try: layer.setUp() except Exception: if options.post_mortem: if options.resume_layer: options.output.error_with_banner( cant_pm_in_subprocess_message % options.resume_layer) raise else: zope.testing.testrunner.debug.post_mortem( sys.exc_info()) else: raise output.stop_set_up(time.time() - t) setup_layers[layer] = 1 class TestResult(unittest.TestResult): # Handle unexpected success as failure: # https://bugs.launchpad.net/zope.testrunner/+bug/719369 addUnexpectedSuccess = None def __init__(self, options, tests, layer_name=None): unittest.TestResult.__init__(self) self.options = options # Calculate our list of relevant layers we need to call testSetUp # and testTearDown on. layers = [] gather_layers(layer_from_name(layer_name), layers) self.layers = order_by_bases(layers) count = 0 for test in tests: count += test.countTestCases() self.count = count def testSetUp(self): """A layer may define a setup method to be called before each individual test. """ for layer in self.layers: if hasattr(layer, 'testSetUp'): layer.testSetUp() def testTearDown(self): """A layer may define a teardown method to be called after each individual test. This is useful for clearing the state of global resources or resetting external systems such as relational databases or daemons. """ for layer in self.layers[-1::-1]: if hasattr(layer, 'testTearDown'): layer.testTearDown() def startTest(self, test): self.testSetUp() unittest.TestResult.startTest(self, test) testsRun = self.testsRun - 1 # subtract the one the base class added count = test.countTestCases() self.testsRun = testsRun + count self.options.output.start_test(test, self.testsRun, self.count) self._threads = threading.enumerate() self._start_time = time.time() def addSuccess(self, test): t = max(time.time() - self._start_time, 0.0) self.options.output.test_success(test, t) def addError(self, test, exc_info): self.options.output.test_error(test, time.time() - self._start_time, exc_info) unittest.TestResult.addError(self, test, exc_info) if self.options.post_mortem: if self.options.resume_layer: self.options.output.error_with_banner("Can't post-mortem debug" " when running a layer" " as a subprocess!") else: zope.testing.testrunner.debug.post_mortem(exc_info) elif self.options.stop_on_error: self.stop() def addFailure(self, test, exc_info): self.options.output.test_failure(test, time.time() - self._start_time, exc_info) unittest.TestResult.addFailure(self, test, exc_info) if self.options.post_mortem: # XXX: mgedmin: why isn't there a resume_layer check here like # in addError? zope.testing.testrunner.debug.post_mortem(exc_info) elif self.options.stop_on_error: self.stop() def stopTest(self, test): self.testTearDown() self.options.output.stop_test(test) if is_jython: pass else: if gc.garbage: self.options.output.test_garbage(test, gc.garbage) # TODO: Perhaps eat the garbage here, so that the garbage isn't # printed for every subsequent test. # Did the test leave any new threads behind? new_threads = [t for t in threading.enumerate() if (t.isAlive() and t not in self._threads)] if new_threads: self.options.output.test_threads(test, new_threads) def layer_from_name(layer_name): """Return the layer for the corresponding layer_name by discovering and importing the necessary module if necessary. Note that a name -> layer cache is maintained by name_from_layer to allow locating layers in cases where it would otherwise be impossible. """ if layer_name in _layer_name_cache: return _layer_name_cache[layer_name] layer_names = layer_name.split('.') layer_module, module_layer_name = layer_names[:-1], layer_names[-1] module_name = '.'.join(layer_module) module = import_name(module_name) try: return getattr(module, module_layer_name) except AttributeError, e: # the default error is very uninformative: # AttributeError: 'module' object has no attribute 'DemoLayer' # it doesn't say *which* module raise AttributeError('module %r has no attribute %r' % (module_name, module_layer_name)) def order_by_bases(layers): """Order the layers from least to most specific (bottom to top) """ named_layers = [(name_from_layer(layer), layer) for layer in layers] named_layers.sort() named_layers.reverse() gathered = [] for name, layer in named_layers: gather_layers(layer, gathered) gathered.reverse() seen = {} result = [] for layer in gathered: if layer not in seen: seen[layer] = 1 if layer in layers: result.append(layer) return result def gather_layers(layer, result): if layer is not object: result.append(layer) for b in layer.__bases__: gather_layers(b, result) class FakeInputContinueGenerator: def readline(self): print 'c\n' print '*'*70 print ("Can't use pdb.set_trace when running a layer" " as a subprocess!") print '*'*70 print return 'c\n' zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-leaks-err.txt0000644000175000017500000000146312214017655030366 0ustar arnauarnauDebugging Memory Leaks without a debug build of Python ====================================================== To use the --report-refcounts (-r) to detect or debug memory leaks, you must have a debug build of Python. Without a debug build, you will get an error message: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> from zope.testing import testrunner >>> sys.argv = 'test -r -N6'.split() >>> _ = testrunner.run_internal(defaults) The Python you are running was not configured with --with-pydebug. This is required to use the --report-refcounts option. zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-unexpected-success.txt0000644000175000017500000000307212214017655032311 0ustar arnauarnautestrunner handling of unexpected success ========================================= Python 2.7 introduced the concept of expectedFailures to unittest. See http://www.voidspace.org.uk/python/articles/unittest2.shtml#more-skipping Although testrunner is currently not able to hande unexpected successes correctly at least it does not report them as successes. This document has some edge-case examples to test various aspects of the test runner. Separating Python path and test directories ------------------------------------------- The --path option defines a directory to be searched for tests *and* a directory to be added to Python's search path. The --test-path option can be used when you want to set a test search path without also affecting the Python path: >>> import os, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex-719369') >>> from zope.testing import testrunner >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = ['test'] >>> testrunner.run_internal(defaults) ... # doctest: +ELLIPSIS Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Failure in test test_ef (sampletestsf.TestUnexpectedSuccess) Traceback (most recent call last): _UnexpectedSuccess Ran 1 tests with 1 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. True zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-discovery.txt0000644000175000017500000000346612214017655030515 0ustar arnauarnauAutomatically discovering tests =============================== You can explicitly specify which tests to run by providing a function that returns a unittest.TestSuite in the test modules (the name of the function can be configured with the --suite-name parameter, it defaults to 'test_suite'). If no such function is present, testrunner will use all classes in the module that inherit from unittest.TestCase as tests: >>> import os, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> from zope.testing import testrunner >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = ['test', ... '--tests-pattern', '^sampletests_discover$', ... ] >>> testrunner.run(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False If the module neither provides a TestSuite nor has discoverable tests, testrunner will exit with an error to prevent acidentally missing test cases: >>> sys.argv = ['test', ... '--tests-pattern', '^sampletests_discover_notests$', ... ] >>> testrunner.run(defaults) Test-module import failures: Module: sample1.sampletests_discover_notests TypeError: Module sample1.sampletests_discover_notests does not define any tests Test-modules with import problems: sample1.sampletests_discover_notests Total: 0 tests, 0 failures, 0 errors in 0.000 seconds. True zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-719369/0000755000175000017500000000000012214017655027130 5ustar arnauarnau././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-719369/sampletestsf.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-719369/sampletestsf.p0000644000175000017500000000140212214017655032020 0ustar arnauarnau############################################################################## # # Copyright (c) 2011 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest class TestUnexpectedSuccess(unittest.TestCase): @unittest.expectedFailure def test_ef(self): pass zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/eggsupport.py0000644000175000017500000000716712214017655026311 0ustar arnauarnau""" Add unit and functional testing support to setuptools-driven eggs. """ from setuptools.command.test import ScanningLoader from setuptools.command.test import test as BaseCommand def skipLayers(suite): """ Walk the suite returned by setuptools' testloader. o Skip any tests which have a 'layer' defined. """ from unittest import TestSuite result = TestSuite() for test in suite._tests: if isinstance(test, TestSuite): result.addTest(skipLayers(test)) else: layer = getattr(test, 'layer', None) if layer is None: result.addTest(test) return result class SkipLayers(ScanningLoader): """ Load only unit tests (those which have no layer associated with them). o Running the tests using 'setup.py test' cannot, by default, drive the full testrunner, with its support for layers (in functional tests). This loader allows the command to work, by running only those tests which don't need the layer support. o To run layer-dependent tests, use 'setup.py ftest' (see below for adding the command to your setup.py). o To use this loader your pacakge add the following your 'setup()' call:: setup( ... setup_requires=['eggtestinfo' # captures testing metadata in EGG-INFO ], tests_require=['zope.testing >= 3.6.1dev', #XXX fix version at release ], ... test_loader='zope.testing.testrunner.eggsupport:SkipLayers', ... ) """ def loadTestsFromModule(self, module): return skipLayers(ScanningLoader.loadTestsFromModule(self, module)) def print_usage(): print 'python setup.py ftest' print print ftest.__doc__ class ftest(BaseCommand): """ Run unit and functional tests after an in-place build. o Note that this command runs *all* tests (unit *and* functional). o This command does not provide any of the configuration options which the usual testrunner provided by 'zope.testing' offers: it is intended to allow easy verification that a package has been installed correctly via setuptools, but is not likely to be useful for developers working on the package. o Developers working on the package will likely prefer to work with the stock testrunner, e.g., by using buildout with a recipe which configures the testrunner as a standalone script. o To use this in your pacakge add the following to the 'entry_points' section:: setup( ... setup_requires=['zope.testing >= 3.6.1dev', #XXX fix version at release 'eggtestinfo' # captures testing metadata in EGG-INFO ], ... entry_points=''' [setuptools.commands] ftest = zope.testing.testrunner.eggsupport:SetuptoolsFunctionalTest ''' ... ) """ description = "Run all functional and unit tests after in-place build" user_options = [] help_options = [('usage', '?', 'Show usage', print_usage)] def initialize_options(self): pass # suppress normal handling def finalize_options(self): pass # suppress normal handling def run(self): from zope.testing.testrunner import run dist = self.distribution where = dist.package_dir or '.' args = ['IGNORE_ME', '--test-path', where] if dist.install_requires: dist.fetch_build_eggs(dist.install_requires) if dist.tests_require: dist.fetch_build_eggs(dist.tests_require) def _run(): run(args=args) self.with_project_on_sys_path(_run) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/debug.py0000644000175000017500000000375412214017655025176 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Debug functions $Id: __init__.py 86232 2008-05-03 15:09:33Z ctheune $ """ import doctest import sys import pdb import zope.testing.testrunner.interfaces def post_mortem(exc_info): err = exc_info[1] if isinstance(err, (doctest.UnexpectedException, doctest.DocTestFailure)): if isinstance(err, doctest.UnexpectedException): exc_info = err.exc_info # Print out location info if the error was in a doctest if exc_info[2].tb_frame.f_code.co_filename == '': print_doctest_location(err) else: print_doctest_location(err) # Hm, we have a DocTestFailure exception. We need to # generate our own traceback try: exec ('raise ValueError' '("Expected and actual output are different")' ) in err.test.globs except: exc_info = sys.exc_info() print "%s:" % (exc_info[0], ) print exc_info[1] pdb.post_mortem(exc_info[2]) raise zope.testing.testrunner.interfaces.EndRun() def print_doctest_location(err): # This mimics pdb's output, which gives way cool results in emacs :) filename = err.test.filename if filename.endswith('.pyc'): filename = filename[:-1] print "> %s(%s)_()" % (filename, err.test.lineno+err.example.lineno+1) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-verbose.txt0000644000175000017500000001513412214017655030146 0ustar arnauarnauVerbose Output ============== Normally, we just get a summary. We can use the -v option to get increasingly more information. If we use a single --verbose (-v) option, we get a dot printed for each test: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --layer 122 -v'.split() >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: .................................. Ran 34 tests with 0 failures and 0 errors in 0.007 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False If there are more than 50 tests, the dots are printed in groups of 50: >>> sys.argv = 'test -uv'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: ................................................................................................................................................................................................ Ran 192 tests with 0 failures and 0 errors in 0.035 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False If the --verbose (-v) option is used twice, then the name and location of each test is printed as it is run: >>> sys.argv = 'test --layer 122 -vv'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: test_x1 (sample1.sampletests.test122.TestA) test_y0 (sample1.sampletests.test122.TestA) test_z0 (sample1.sampletests.test122.TestA) test_x0 (sample1.sampletests.test122.TestB) test_y1 (sample1.sampletests.test122.TestB) test_z0 (sample1.sampletests.test122.TestB) test_1 (sample1.sampletests.test122.TestNotMuch) test_2 (sample1.sampletests.test122.TestNotMuch) test_3 (sample1.sampletests.test122.TestNotMuch) test_x0 (sample1.sampletests.test122) test_y0 (sample1.sampletests.test122) test_z1 (sample1.sampletests.test122) testrunner-ex/sample1/sampletests/../../sampletestsl.txt test_x1 (sampletests.test122.TestA) test_y0 (sampletests.test122.TestA) test_z0 (sampletests.test122.TestA) test_x0 (sampletests.test122.TestB) test_y1 (sampletests.test122.TestB) test_z0 (sampletests.test122.TestB) test_1 (sampletests.test122.TestNotMuch) test_2 (sampletests.test122.TestNotMuch) test_3 (sampletests.test122.TestNotMuch) test_x0 (sampletests.test122) test_y0 (sampletests.test122) test_z1 (sampletests.test122) testrunner-ex/sampletests/../sampletestsl.txt Ran 34 tests with 0 failures and 0 errors in 0.009 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False if the --verbose (-v) option is used three times, then individual test-execution times are printed: >>> sys.argv = 'test --layer 122 -vvv'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: test_x1 (sample1.sampletests.test122.TestA) (0.000 s) test_y0 (sample1.sampletests.test122.TestA) (0.000 s) test_z0 (sample1.sampletests.test122.TestA) (0.000 s) test_x0 (sample1.sampletests.test122.TestB) (0.000 s) test_y1 (sample1.sampletests.test122.TestB) (0.000 s) test_z0 (sample1.sampletests.test122.TestB) (0.000 s) test_1 (sample1.sampletests.test122.TestNotMuch) (0.000 s) test_2 (sample1.sampletests.test122.TestNotMuch) (0.000 s) test_3 (sample1.sampletests.test122.TestNotMuch) (0.000 s) test_x0 (sample1.sampletests.test122) (0.001 s) test_y0 (sample1.sampletests.test122) (0.001 s) test_z1 (sample1.sampletests.test122) (0.001 s) testrunner-ex/sample1/sampletests/../../sampletestsl.txt (0.001 s) test_x1 (sampletests.test122.TestA) (0.000 s) test_y0 (sampletests.test122.TestA) (0.000 s) test_z0 (sampletests.test122.TestA) (0.000 s) test_x0 (sampletests.test122.TestB) (0.000 s) test_y1 (sampletests.test122.TestB) (0.000 s) test_z0 (sampletests.test122.TestB) (0.000 s) test_1 (sampletests.test122.TestNotMuch) (0.000 s) test_2 (sampletests.test122.TestNotMuch) (0.000 s) test_3 (sampletests.test122.TestNotMuch) (0.000 s) test_x0 (sampletests.test122) (0.001 s) test_y0 (sampletests.test122) (0.001 s) test_z1 (sampletests.test122) (0.001 s) testrunner-ex/sampletests/../sampletestsl.txt (0.001 s) Ran 34 tests with 0 failures and 0 errors in 0.009 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False Quiet output ------------ The --quiet (-q) option cancels all verbose options. It's useful when the default verbosity is non-zero: >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... '-v' ... ] >>> sys.argv = 'test -q -u'.split() >>> testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in 0.034 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/layer.py0000644000175000017500000000143412214017655025215 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Layer definitions $Id: __init__.py 86223 2008-05-03 14:36:04Z ctheune $ """ class UnitTests(object): """A layer for gathering all unit tests.""" zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-profiling-cprofiler.txt0000644000175000017500000000266212214017655032457 0ustar arnauarnauProfiling ========= The testrunner includes the ability to profile the test execution with cProfile via the `--profile=cProfile` option:: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> sys.path.append(directory_with_tests) >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = [testrunner_script, '--profile=cProfile'] When the tests are run, we get profiling output:: >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running... ... ncalls tottime percall cumtime percall filename:lineno(function)... ... Profiling also works across layers:: >>> sys.argv = [testrunner_script, '-ssample2', '--profile=cProfile', ... '--tests-pattern', 'sampletests_ntd'] >>> testrunner.run_internal(defaults) Running... Tear down ... not supported... ncalls tottime percall cumtime percall filename:lineno(function)... The testrunner creates temnporary files containing cProfiler profiler data:: >>> import glob >>> files = list(glob.glob('tests_profile.*.prof')) >>> files.sort() >>> files ['tests_profile.cZj2jt.prof', 'tests_profile.yHD-so.prof'] It deletes these when rerun. We'll delete these ourselves:: >>> import os >>> for f in files: ... os.unlink(f) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/coverage.py0000644000175000017500000001155712214017655025703 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Code coverage analysis $Id: __init__.py 86218 2008-05-03 14:17:26Z ctheune $ """ import trace import sys import os.path import threading import zope.testing.testrunner.feature from zope.testing.testrunner.find import test_dirs # For some reason, the doctest module resets the trace callable randomly, thus # disabling the coverage. Simply disallow the code from doing this. A real # trace can be set, so that debugging still works. osettrace = sys.settrace def settrace(trace): if trace is None: return osettrace(trace) class TestTrace(trace.Trace): """Simple tracer. >>> tracer = TestTrace([], count=False, trace=False) Simple rules for use: you can't stop the tracer if it not started and you can't start the tracer if it already started: >>> tracer.stop() Traceback (most recent call last): File 'testrunner.py' AssertionError: can't stop if not started >>> tracer.start() >>> tracer.start() Traceback (most recent call last): File 'testrunner.py' AssertionError: can't start if already started >>> tracer.stop() >>> tracer.stop() Traceback (most recent call last): File 'testrunner.py' AssertionError: can't stop if not started """ def __init__(self, directories, **kw): trace.Trace.__init__(self, **kw) self.ignore = TestIgnore(directories) self.started = False def start(self): assert not self.started, "can't start if already started" if not self.donothing: sys.settrace = settrace sys.settrace(self.globaltrace) threading.settrace(self.globaltrace) self.started = True def stop(self): assert self.started, "can't stop if not started" if not self.donothing: sys.settrace = osettrace sys.settrace(None) threading.settrace(None) self.started = False class TestIgnore: def __init__(self, directories): self._test_dirs = [self._filenameFormat(d[0]) + os.path.sep for d in directories] self._ignore = {} self._ignored = self._ignore.get def names(self, filename, modulename): # Special case: Modules generated from text files; i.e. doctests if modulename == '': return True filename = self._filenameFormat(filename) ignore = self._ignored(filename) if ignore is None: ignore = True if filename is not None: for d in self._test_dirs: if filename.startswith(d): ignore = False break self._ignore[filename] = ignore return ignore def _filenameFormat(self, filename): return os.path.abspath(filename) if sys.platform == 'win32': #on win32 drive name can be passed with different case to `names` #that lets e.g. the coverage profiler skip complete files #_filenameFormat will make sure that all drive and filenames get lowercased #albeit trace coverage has still problems with lowercase drive letters #when determining the dotted module name OldTestIgnore = TestIgnore class TestIgnore(OldTestIgnore): def _filenameFormat(self, filename): return os.path.normcase(os.path.abspath(filename)) class Coverage(zope.testing.testrunner.feature.Feature): tracer = None directory = None def __init__(self, runner): super(Coverage, self).__init__(runner) self.active = bool(runner.options.coverage) def global_setup(self): """Executed once when the test runner is being set up.""" self.directory = os.path.join(os.getcwd(), self.runner.options.coverage) # FIXME: This shouldn't rely on the find feature directly. self.tracer = TestTrace(test_dirs(self.runner.options, {}), trace=False, count=True) self.tracer.start() def early_teardown(self): """Executed once directly after all tests.""" self.tracer.stop() def report(self): """Executed once after all tests have been run and all setup was torn down.""" r = self.tracer.results() r.write_results(summary=True, coverdir=self.directory) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/find.py0000644000175000017500000003521512214017655025025 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test discovery $Id: __init__.py 86223 2008-05-03 14:36:04Z ctheune $ """ import re import os import unittest import sys import zope.testing.testrunner.feature import zope.testing.testrunner.layer import zope.testing.testrunner.debug identifier = re.compile(r'[_a-zA-Z]\w*$').match class StartUpFailure(unittest.TestCase): """Empty test case added to the test suite to indicate import failures. >>> class Options(object): ... post_mortem = False >>> options = Options() Normally the StartUpFailure just acts as an empty test suite to satisfy the test runner and statistics: >>> s = StartUpFailure(options, None, None) >>> isinstance(s,unittest.TestCase) True However, if the post mortem option is enabled: >>> options.post_mortem = True ...then the the StartUpFailure will start the debugger and stop the test run after the debugger quits. To simulate this, we need an exception and its associated exc_info: >>> import sys >>> try: ... raise Exception() ... except: ... exc_info = sys.exc_info() To simulate the user pressing 'c' and hitting return in the debugger, we use a FakeInputContinueGenerator: >>> from zope.testing.testrunner.runner import FakeInputContinueGenerator >>> old_stdin = sys.stdin >>> sys.stdin = FakeInputContinueGenerator() Now we can see the EndRun exception that is raised by the postmortem debugger to indicate that debugging is finished and the test run should be terminated: >>> try: ... StartUpFailure(options, None, exc_info) ... finally: ... sys.stdin = old_stdin Traceback (most recent call last): EndRun Annoyingly, sometimes StartupFailures occur when postmortem debugging is enabled but no exc_info is passed. In this case, we raise a sensible exception rather than letting the debugger barf with an AttributeError: >>> options.post_mortem = True >>> StartUpFailure(options, None, exc_info[:2]+(None,)) Traceback (most recent call last): ... TypeError: If post_mortem is specified, full exc_info must be passed! """ def __init__(self, options, module, exc_info): if options.post_mortem: for item in exc_info: if item is None: raise TypeError('If post_mortem is specified, ' 'full exc_info must be passed!') zope.testing.testrunner.debug.post_mortem(exc_info) self.module = module self.exc_info = exc_info def __repr__(self): return '' % self.module def find_tests(options, found_suites=None): """Creates a dictionary mapping layer name to a suite of tests to be run in that layer. Passing a list of suites using the found_suites parameter will cause that list of suites to be used instead of attempting to load them from the filesystem. This is useful for unit testing the test runner. """ remove_stale_bytecode(options) suites = {} if found_suites is None: found_suites = find_suites(options) for suite in found_suites: for test, layer_name in tests_from_suite(suite, options): suite = suites.get(layer_name) if not suite: suite = suites[layer_name] = unittest.TestSuite() suite.addTest(test) return suites def find_suites(options): for fpath, package in find_test_files(options): for (prefix, prefix_package) in options.prefix: if fpath.startswith(prefix) and package == prefix_package: # strip prefix, strip .py suffix and convert separator to dots noprefix = fpath[len(prefix):] noext = strip_py_ext(options, noprefix) assert noext is not None module_name = noext.replace(os.path.sep, '.') if package: module_name = package + '.' + module_name for filter in options.module: if filter(module_name): break else: continue try: module = import_name(module_name) except KeyboardInterrupt: raise except: suite = StartUpFailure( options, module_name, sys.exc_info()[:2] + (sys.exc_info()[2].tb_next.tb_next,), ) else: try: if hasattr(module, options.suite_name): suite = getattr(module, options.suite_name)() else: suite = unittest.defaultTestLoader.loadTestsFromModule(module) if suite.countTestCases() == 0: raise TypeError( "Module %s does not define any tests" % module_name) if isinstance(suite, unittest.TestSuite): check_suite(suite, module_name) else: raise TypeError( "Invalid test_suite, %r, in %s" % (suite, module_name) ) except KeyboardInterrupt: raise except: suite = StartUpFailure( options, module_name, sys.exc_info()[:2]+(None,)) yield suite break def find_test_files(options): found = {} for f, package in find_test_files_(options): if f not in found: found[f] = 1 yield f, package def find_test_files_(options): tests_pattern = options.tests_pattern test_file_pattern = options.test_file_pattern # If options.usecompiled, we can accept .pyc or .pyo files instead # of .py files. We'd rather use a .py file if one exists. `root2ext` # maps a test file path, sans extension, to the path with the best # extension found (.py if it exists, else .pyc or .pyo). # Note that "py" < "pyc" < "pyo", so if more than one extension is # found, the lexicographically smaller one is best. # Found a new test file, in directory `dirname`. `noext` is the # file name without an extension, and `withext` is the file name # with its extension. def update_root2ext(dirname, noext, withext): key = os.path.join(dirname, noext) new = os.path.join(dirname, withext) if key in root2ext: root2ext[key] = min(root2ext[key], new) else: root2ext[key] = new for (p, package) in test_dirs(options, {}): for dirname, dirs, files in walk_with_symlinks(options, p): if dirname != p and not contains_init_py(options, files): # This is not a plausible test directory. Avoid descending # further. del dirs[:] continue root2ext = {} dirs[:] = filter(identifier, dirs) d = os.path.split(dirname)[1] if tests_pattern(d) and contains_init_py(options, files): # tests directory for file in files: noext = strip_py_ext(options, file) if noext and test_file_pattern(noext): update_root2ext(dirname, noext, file) for file in files: noext = strip_py_ext(options, file) if noext and tests_pattern(noext): update_root2ext(dirname, noext, file) winners = root2ext.values() winners.sort() for file in winners: yield file, package def strip_py_ext(options, path): """Return path without its .py (or .pyc or .pyo) extension, or None. If options.usecompiled is false: If path ends with ".py", the path without the extension is returned. Else None is returned. If options.usecompiled is true: If Python is running with -O, a .pyo extension is also accepted. If Python is running without -O, a .pyc extension is also accepted. """ if path.endswith(".py"): return path[:-3] if options.usecompiled: if __debug__: # Python is running without -O. ext = ".pyc" else: # Python is running with -O. ext = ".pyo" if path.endswith(ext): return path[:-len(ext)] return None def test_dirs(options, seen): if options.package: for p in options.package: p = import_name(p) for p in p.__path__: p = os.path.abspath(p) if p in seen: continue for (prefix, package) in options.prefix: if p.startswith(prefix) or p == prefix[:-1]: seen[p] = 1 yield p, package break else: for dpath in options.test_path: yield dpath def walk_with_symlinks(options, dir): # TODO -- really should have test of this that uses symlinks # this is hard on a number of levels ... for dirpath, dirs, files in os.walk(dir): dirs.sort() files.sort() dirs[:] = [d for d in dirs if d not in options.ignore_dir] yield (dirpath, dirs, files) for d in dirs: p = os.path.join(dirpath, d) if os.path.islink(p): for sdirpath, sdirs, sfiles in walk_with_symlinks(options, p): yield (sdirpath, sdirs, sfiles) compiled_suffixes = '.pyc', '.pyo' def remove_stale_bytecode(options): if options.keepbytecode: return for (p, _) in options.test_path: for dirname, dirs, files in walk_with_symlinks(options, p): for file in files: if file[-4:] in compiled_suffixes and file[:-1] not in files: fullname = os.path.join(dirname, file) options.output.info("Removing stale bytecode file %s" % fullname) os.unlink(fullname) def contains_init_py(options, fnamelist): """Return true iff fnamelist contains a suitable spelling of __init__.py. If options.usecompiled is false, this is so iff "__init__.py" is in the list. If options.usecompiled is true, then "__init__.pyo" is also acceptable if Python is running with -O, and "__init__.pyc" is also acceptable if Python is running without -O. """ if "__init__.py" in fnamelist: return True if options.usecompiled: if __debug__: # Python is running without -O. return "__init__.pyc" in fnamelist else: # Python is running with -O. return "__init__.pyo" in fnamelist return False def import_name(name): __import__(name) return sys.modules[name] def tests_from_suite(suite, options, dlevel=1, dlayer=zope.testing.testrunner.layer.UnitTests): """Returns a sequence of (test, layer_name) The tree of suites is recursively visited, with the most specific layer taking precedence. So if a TestCase with a layer of 'foo' is contained in a TestSuite with a layer of 'bar', the test case would be returned with 'foo' as the layer. Tests are also filtered out based on the test level and test selection filters stored in the options. """ level = getattr(suite, 'level', dlevel) layer = getattr(suite, 'layer', dlayer) if not isinstance(layer, basestring): layer = name_from_layer(layer) if isinstance(suite, unittest.TestSuite): for possible_suite in suite: for r in tests_from_suite(possible_suite, options, level, layer): yield r elif isinstance(suite, StartUpFailure): yield (suite, None) else: if level <= options.at_level: for pat in options.test: if pat(str(suite)): yield (suite, layer) break def check_suite(suite, module_name): """Check for bad tests in a test suite. "Bad tests" are those that do not inherit from unittest.TestCase. Note that this function is pointless on Python 2.5, because unittest itself checks for this in TestSuite.addTest. It is, however, useful on earlier Pythons. """ for x in suite: if isinstance(x, unittest.TestSuite): check_suite(x, module_name) elif not isinstance(x, unittest.TestCase): raise TypeError( "Invalid test, %r,\nin test_suite from %s" % (x, module_name) ) _layer_name_cache = {} def name_from_layer(layer): """Determine a name for the Layer using the namespace to avoid conflicts. We also cache a name -> layer mapping to enable layer_from_name to work in cases where the layer cannot be imported (such as layers defined in doctests) """ if layer.__module__ == '__builtin__': name = layer.__name__ else: name = layer.__module__ + '.' + layer.__name__ _layer_name_cache[name] = layer return name class Find(zope.testing.testrunner.feature.Feature): """Finds tests and registers them with the test runner.""" active = True def global_setup(self): # Add directories to the path for path in self.runner.options.path: if path not in sys.path: sys.path.append(path) tests = find_tests(self.runner.options, self.runner.found_suites) self.import_errors = tests.pop(None, None) self.runner.register_tests(tests) # XXX move to reporting ??? self.runner.options.output.import_errors(self.import_errors) self.runner.import_errors = bool(self.import_errors) def report(self): self.runner.options.output.modules_with_import_problems( self.import_errors) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/listing.py0000644000175000017500000000250612214017655025553 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Filter which tests to run. $Id: __init__.py 86218 2008-05-03 14:17:26Z ctheune $ """ import zope.testing.testrunner.feature class Listing(zope.testing.testrunner.feature.Feature): """Lists all tests in the report instead of running the tests.""" def __init__(self, runner): super(Listing, self).__init__(runner) self.active = bool(runner.options.list_tests) def global_setup(self): self.runner.do_run_tests = False self.runner.failed = False def report(self): layers = self.runner.tests_by_layer_name for layer_name, layer, tests in self.runner.ordered_layers(): self.runner.options.output.list_of_tests(tests, layer_name) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/interfaces.py0000644000175000017500000000563512214017655026233 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test runner interfaces XXX Note: These interfaces are still being sketched out. Please do not rely on them, yet. $Id: __init__.py 86232 2008-05-03 15:09:33Z ctheune $ """ import zope.interface class EndRun(Exception): """Indicate that the existing run call should stop Used to prevent additional test output after post-mortem debugging. """ class IFeature(zope.interface.Interface): """Features extend the test runners functionality in a pipe-lined order. """ active = zope.interface.Attribute( "Flag whether this feature is activated. If it is not activated than " "its methods won't be called by the runner.") def global_setup(): """Executed once when the test runner is being set up.""" def late_setup(): """Executed once right before the actual tests get executed and after all global setups have happened. Should do as little work as possible to avoid timing interferences with other features. It is guaranteed that the calling stack frame is not left until early_teardown was called. """ def layer_setup(layer): """Executed once after a layer was set up.""" def layer_teardown(layer): """Executed once after a layer was run.""" def test_setup(test): """Executed once before each test.""" def test_teardown(test): """Executed once after each test.""" def early_teardown(): """Executed once directly after all tests. This method should do as little as possible to avoid timing issues. It is guaranteed to be called directly from the same stack frame that called `late_setup`. """ def global_teardown(): """Executed once after all tests where run and early teardowns have happened. """ def report(): """Executed once after all tests have been run and all setup was torn down. This is the only method that should produce output. """ class ITestRunner(zope.interface.Interface): """The test runner manages test layers and their execution. The functionality of a test runner can be extended by configuring features. """ options = zope.interface.Attribute( "Provides access to configuration options.") zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-test-selection.txt0000644000175000017500000006350012214017655031443 0ustar arnauarnauTest Selection ============== We've already seen that we can select tests by layer. There are three other ways we can select tests. We can select tests by package: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --layer 122 -ssample1 -vv'.split() >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: test_x1 (sample1.sampletests.test122.TestA) test_y0 (sample1.sampletests.test122.TestA) test_z0 (sample1.sampletests.test122.TestA) test_x0 (sample1.sampletests.test122.TestB) test_y1 (sample1.sampletests.test122.TestB) test_z0 (sample1.sampletests.test122.TestB) test_1 (sample1.sampletests.test122.TestNotMuch) test_2 (sample1.sampletests.test122.TestNotMuch) test_3 (sample1.sampletests.test122.TestNotMuch) test_x0 (sample1.sampletests.test122) test_y0 (sample1.sampletests.test122) test_z1 (sample1.sampletests.test122) testrunner-ex/sample1/sampletests/../../sampletestsl.txt Ran 17 tests with 0 failures and 0 errors in 0.005 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False You can specify multiple packages: >>> sys.argv = 'test -u -vv -ssample1 -ssample2'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_x1 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletestsf.TestA) test_z0 (sample1.sampletestsf.TestA) test_x0 (sample1.sampletestsf.TestB) test_y1 (sample1.sampletestsf.TestB) test_z0 (sample1.sampletestsf.TestB) test_1 (sample1.sampletestsf.TestNotMuch) test_2 (sample1.sampletestsf.TestNotMuch) test_3 (sample1.sampletestsf.TestNotMuch) test_x0 (sample1.sampletestsf) test_y0 (sample1.sampletestsf) test_z1 (sample1.sampletestsf) testrunner-ex/sample1/../sampletests.txt test_x1 (sample1.sample11.sampletests.TestA) test_y0 (sample1.sample11.sampletests.TestA) test_z0 (sample1.sample11.sampletests.TestA) test_x0 (sample1.sample11.sampletests.TestB) test_y1 (sample1.sample11.sampletests.TestB) test_z0 (sample1.sample11.sampletests.TestB) test_1 (sample1.sample11.sampletests.TestNotMuch) test_2 (sample1.sample11.sampletests.TestNotMuch) test_3 (sample1.sample11.sampletests.TestNotMuch) test_x0 (sample1.sample11.sampletests) test_y0 (sample1.sample11.sampletests) test_z1 (sample1.sample11.sampletests) testrunner-ex/sample1/sample11/../../sampletests.txt test_x1 (sample1.sample13.sampletests.TestA) test_y0 (sample1.sample13.sampletests.TestA) test_z0 (sample1.sample13.sampletests.TestA) test_x0 (sample1.sample13.sampletests.TestB) test_y1 (sample1.sample13.sampletests.TestB) test_z0 (sample1.sample13.sampletests.TestB) test_1 (sample1.sample13.sampletests.TestNotMuch) test_2 (sample1.sample13.sampletests.TestNotMuch) test_3 (sample1.sample13.sampletests.TestNotMuch) test_x0 (sample1.sample13.sampletests) test_y0 (sample1.sample13.sampletests) test_z1 (sample1.sample13.sampletests) testrunner-ex/sample1/sample13/../../sampletests.txt test_x1 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletests.test1.TestA) test_z0 (sample1.sampletests.test1.TestA) test_x0 (sample1.sampletests.test1.TestB) test_y1 (sample1.sampletests.test1.TestB) test_z0 (sample1.sampletests.test1.TestB) test_1 (sample1.sampletests.test1.TestNotMuch) test_2 (sample1.sampletests.test1.TestNotMuch) test_3 (sample1.sampletests.test1.TestNotMuch) test_x0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test1) test_z1 (sample1.sampletests.test1) testrunner-ex/sample1/sampletests/../../sampletests.txt test_x1 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sampletests.test_one.TestA) test_z0 (sample1.sampletests.test_one.TestA) test_x0 (sample1.sampletests.test_one.TestB) test_y1 (sample1.sampletests.test_one.TestB) test_z0 (sample1.sampletests.test_one.TestB) test_1 (sample1.sampletests.test_one.TestNotMuch) test_2 (sample1.sampletests.test_one.TestNotMuch) test_3 (sample1.sampletests.test_one.TestNotMuch) test_x0 (sample1.sampletests.test_one) test_y0 (sample1.sampletests.test_one) test_z1 (sample1.sampletests.test_one) testrunner-ex/sample1/sampletests/../../sampletests.txt test_x1 (sample2.sample21.sampletests.TestA) test_y0 (sample2.sample21.sampletests.TestA) test_z0 (sample2.sample21.sampletests.TestA) test_x0 (sample2.sample21.sampletests.TestB) test_y1 (sample2.sample21.sampletests.TestB) test_z0 (sample2.sample21.sampletests.TestB) test_1 (sample2.sample21.sampletests.TestNotMuch) test_2 (sample2.sample21.sampletests.TestNotMuch) test_3 (sample2.sample21.sampletests.TestNotMuch) test_x0 (sample2.sample21.sampletests) test_y0 (sample2.sample21.sampletests) test_z1 (sample2.sample21.sampletests) testrunner-ex/sample2/sample21/../../sampletests.txt test_x1 (sample2.sampletests.test_1.TestA) test_y0 (sample2.sampletests.test_1.TestA) test_z0 (sample2.sampletests.test_1.TestA) test_x0 (sample2.sampletests.test_1.TestB) test_y1 (sample2.sampletests.test_1.TestB) test_z0 (sample2.sampletests.test_1.TestB) test_1 (sample2.sampletests.test_1.TestNotMuch) test_2 (sample2.sampletests.test_1.TestNotMuch) test_3 (sample2.sampletests.test_1.TestNotMuch) test_x0 (sample2.sampletests.test_1) test_y0 (sample2.sampletests.test_1) test_z1 (sample2.sampletests.test_1) testrunner-ex/sample2/sampletests/../../sampletests.txt test_x1 (sample2.sampletests.testone.TestA) test_y0 (sample2.sampletests.testone.TestA) test_z0 (sample2.sampletests.testone.TestA) test_x0 (sample2.sampletests.testone.TestB) test_y1 (sample2.sampletests.testone.TestB) test_z0 (sample2.sampletests.testone.TestB) test_1 (sample2.sampletests.testone.TestNotMuch) test_2 (sample2.sampletests.testone.TestNotMuch) test_3 (sample2.sampletests.testone.TestNotMuch) test_x0 (sample2.sampletests.testone) test_y0 (sample2.sampletests.testone) test_z1 (sample2.sampletests.testone) testrunner-ex/sample2/sampletests/../../sampletests.txt Ran 128 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False You can specify directory names instead of packages (useful for tab-completion): >>> subdir = os.path.join(directory_with_tests, 'sample1') >>> sys.argv = ['test', '--layer', '122', '-s', subdir, '-vv'] >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: test_x1 (sample1.sampletests.test122.TestA) test_y0 (sample1.sampletests.test122.TestA) test_z0 (sample1.sampletests.test122.TestA) test_x0 (sample1.sampletests.test122.TestB) test_y1 (sample1.sampletests.test122.TestB) test_z0 (sample1.sampletests.test122.TestB) test_1 (sample1.sampletests.test122.TestNotMuch) test_2 (sample1.sampletests.test122.TestNotMuch) test_3 (sample1.sampletests.test122.TestNotMuch) test_x0 (sample1.sampletests.test122) test_y0 (sample1.sampletests.test122) test_z1 (sample1.sampletests.test122) testrunner-ex/sample1/sampletests/../../sampletestsl.txt Ran 17 tests with 0 failures and 0 errors in 0.005 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False We can select by test module name using the --module (-m) option: >>> sys.argv = 'test -u -vv -ssample1 -m_one -mtest1'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_x1 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletests.test1.TestA) test_z0 (sample1.sampletests.test1.TestA) test_x0 (sample1.sampletests.test1.TestB) test_y1 (sample1.sampletests.test1.TestB) test_z0 (sample1.sampletests.test1.TestB) test_1 (sample1.sampletests.test1.TestNotMuch) test_2 (sample1.sampletests.test1.TestNotMuch) test_3 (sample1.sampletests.test1.TestNotMuch) test_x0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test1) test_z1 (sample1.sampletests.test1) testrunner-ex/sample1/sampletests/../../sampletests.txt test_x1 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sampletests.test_one.TestA) test_z0 (sample1.sampletests.test_one.TestA) test_x0 (sample1.sampletests.test_one.TestB) test_y1 (sample1.sampletests.test_one.TestB) test_z0 (sample1.sampletests.test_one.TestB) test_1 (sample1.sampletests.test_one.TestNotMuch) test_2 (sample1.sampletests.test_one.TestNotMuch) test_3 (sample1.sampletests.test_one.TestNotMuch) test_x0 (sample1.sampletests.test_one) test_y0 (sample1.sampletests.test_one) test_z1 (sample1.sampletests.test_one) testrunner-ex/sample1/sampletests/../../sampletests.txt Ran 32 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False and by test within the module using the --test (-t) option: >>> sys.argv = 'test -u -vv -ssample1 -m_one -mtest1 -tx0 -ty0'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_y0 (sample1.sampletests.test1.TestA) test_x0 (sample1.sampletests.test1.TestB) test_x0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test_one.TestA) test_x0 (sample1.sampletests.test_one.TestB) test_x0 (sample1.sampletests.test_one) test_y0 (sample1.sampletests.test_one) Ran 8 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False >>> sys.argv = 'test -u -vv -ssample1 -ttxt'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: testrunner-ex/sample1/../sampletests.txt testrunner-ex/sample1/sample11/../../sampletests.txt testrunner-ex/sample1/sample13/../../sampletests.txt testrunner-ex/sample1/sampletests/../../sampletests.txt testrunner-ex/sample1/sampletests/../../sampletests.txt Ran 20 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False The --module and --test options take regular expressions. If the regular expressions specified begin with '!', then tests that don't match the regular expression are selected: >>> sys.argv = 'test -u -vv -ssample1 -m!sample1[.]sample1'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_x1 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletestsf.TestA) test_z0 (sample1.sampletestsf.TestA) test_x0 (sample1.sampletestsf.TestB) test_y1 (sample1.sampletestsf.TestB) test_z0 (sample1.sampletestsf.TestB) test_1 (sample1.sampletestsf.TestNotMuch) test_2 (sample1.sampletestsf.TestNotMuch) test_3 (sample1.sampletestsf.TestNotMuch) test_x0 (sample1.sampletestsf) test_y0 (sample1.sampletestsf) test_z1 (sample1.sampletestsf) testrunner-ex/sample1/../sampletests.txt test_x1 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletests.test1.TestA) test_z0 (sample1.sampletests.test1.TestA) test_x0 (sample1.sampletests.test1.TestB) test_y1 (sample1.sampletests.test1.TestB) test_z0 (sample1.sampletests.test1.TestB) test_1 (sample1.sampletests.test1.TestNotMuch) test_2 (sample1.sampletests.test1.TestNotMuch) test_3 (sample1.sampletests.test1.TestNotMuch) test_x0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test1) test_z1 (sample1.sampletests.test1) testrunner-ex/sample1/sampletests/../../sampletests.txt test_x1 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sampletests.test_one.TestA) test_z0 (sample1.sampletests.test_one.TestA) test_x0 (sample1.sampletests.test_one.TestB) test_y1 (sample1.sampletests.test_one.TestB) test_z0 (sample1.sampletests.test_one.TestB) test_1 (sample1.sampletests.test_one.TestNotMuch) test_2 (sample1.sampletests.test_one.TestNotMuch) test_3 (sample1.sampletests.test_one.TestNotMuch) test_x0 (sample1.sampletests.test_one) test_y0 (sample1.sampletests.test_one) test_z1 (sample1.sampletests.test_one) testrunner-ex/sample1/sampletests/../../sampletests.txt Ran 48 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Module and test filters can also be given as positional arguments: >>> sys.argv = 'test -u -vv -ssample1 !sample1[.]sample1'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_x1 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletestsf.TestA) test_z0 (sample1.sampletestsf.TestA) test_x0 (sample1.sampletestsf.TestB) test_y1 (sample1.sampletestsf.TestB) test_z0 (sample1.sampletestsf.TestB) test_1 (sample1.sampletestsf.TestNotMuch) test_2 (sample1.sampletestsf.TestNotMuch) test_3 (sample1.sampletestsf.TestNotMuch) test_x0 (sample1.sampletestsf) test_y0 (sample1.sampletestsf) test_z1 (sample1.sampletestsf) testrunner-ex/sample1/../sampletests.txt test_x1 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletests.test1.TestA) test_z0 (sample1.sampletests.test1.TestA) test_x0 (sample1.sampletests.test1.TestB) test_y1 (sample1.sampletests.test1.TestB) test_z0 (sample1.sampletests.test1.TestB) test_1 (sample1.sampletests.test1.TestNotMuch) test_2 (sample1.sampletests.test1.TestNotMuch) test_3 (sample1.sampletests.test1.TestNotMuch) test_x0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test1) test_z1 (sample1.sampletests.test1) testrunner-ex/sample1/sampletests/../../sampletests.txt test_x1 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sampletests.test_one.TestA) test_z0 (sample1.sampletests.test_one.TestA) test_x0 (sample1.sampletests.test_one.TestB) test_y1 (sample1.sampletests.test_one.TestB) test_z0 (sample1.sampletests.test_one.TestB) test_1 (sample1.sampletests.test_one.TestNotMuch) test_2 (sample1.sampletests.test_one.TestNotMuch) test_3 (sample1.sampletests.test_one.TestNotMuch) test_x0 (sample1.sampletests.test_one) test_y0 (sample1.sampletests.test_one) test_z1 (sample1.sampletests.test_one) testrunner-ex/sample1/sampletests/../../sampletests.txt Ran 48 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False >>> sys.argv = 'test -u -vv -ssample1 . txt'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: testrunner-ex/sample1/../sampletests.txt testrunner-ex/sample1/sample11/../../sampletests.txt testrunner-ex/sample1/sample13/../../sampletests.txt testrunner-ex/sample1/sampletests/../../sampletests.txt testrunner-ex/sample1/sampletests/../../sampletests.txt Ran 20 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Sometimes, There are tests that you don't want to run by default. For example, you might have tests that take a long time. Tests can have a level attribute. If no level is specified, a level of 1 is assumed and, by default, only tests at level one are run. to run tests at a higher level, use the --at-level (-a) option to specify a higher level. For example, with the following options: >>> sys.argv = 'test -u -vv -t test_y1 -t test_y0'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_y0 (sampletestsf.TestA) test_y1 (sampletestsf.TestB) test_y0 (sampletestsf) test_y0 (sample1.sampletestsf.TestA) test_y1 (sample1.sampletestsf.TestB) test_y0 (sample1.sampletestsf) test_y0 (sample1.sample11.sampletests.TestA) test_y1 (sample1.sample11.sampletests.TestB) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sample13.sampletests.TestA) test_y1 (sample1.sample13.sampletests.TestB) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sampletests.test1.TestA) test_y1 (sample1.sampletests.test1.TestB) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test_one.TestA) test_y1 (sample1.sampletests.test_one.TestB) test_y0 (sample1.sampletests.test_one) test_y0 (sample2.sample21.sampletests.TestA) test_y1 (sample2.sample21.sampletests.TestB) test_y0 (sample2.sample21.sampletests) test_y0 (sample2.sampletests.test_1.TestA) test_y1 (sample2.sampletests.test_1.TestB) test_y0 (sample2.sampletests.test_1) test_y0 (sample2.sampletests.testone.TestA) test_y1 (sample2.sampletests.testone.TestB) test_y0 (sample2.sampletests.testone) test_y0 (sample3.sampletests.TestA) test_y1 (sample3.sampletests.TestB) test_y0 (sample3.sampletests) test_y0 (sampletests.test1.TestA) test_y1 (sampletests.test1.TestB) test_y0 (sampletests.test1) test_y0 (sampletests.test_one.TestA) test_y1 (sampletests.test_one.TestB) test_y0 (sampletests.test_one) Ran 36 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False We get run 36 tests. If we specify a level of 2, we get some additional tests: >>> sys.argv = 'test -u -vv -a 2 -t test_y1 -t test_y0'.split() >>> testrunner.run_internal(defaults) Running tests at level 2 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_y0 (sampletestsf.TestA) test_y0 (sampletestsf.TestA2) test_y1 (sampletestsf.TestB) test_y0 (sampletestsf) test_y0 (sample1.sampletestsf.TestA) test_y1 (sample1.sampletestsf.TestB) test_y0 (sample1.sampletestsf) test_y0 (sample1.sample11.sampletests.TestA) test_y1 (sample1.sample11.sampletests.TestB) test_y1 (sample1.sample11.sampletests.TestB2) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sample13.sampletests.TestA) test_y1 (sample1.sample13.sampletests.TestB) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sampletests.test1.TestA) test_y1 (sample1.sampletests.test1.TestB) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test_one.TestA) test_y1 (sample1.sampletests.test_one.TestB) test_y0 (sample1.sampletests.test_one) test_y0 (sample2.sample21.sampletests.TestA) test_y1 (sample2.sample21.sampletests.TestB) test_y0 (sample2.sample21.sampletests) test_y0 (sample2.sampletests.test_1.TestA) test_y1 (sample2.sampletests.test_1.TestB) test_y0 (sample2.sampletests.test_1) test_y0 (sample2.sampletests.testone.TestA) test_y1 (sample2.sampletests.testone.TestB) test_y0 (sample2.sampletests.testone) test_y0 (sample3.sampletests.TestA) test_y1 (sample3.sampletests.TestB) test_y0 (sample3.sampletests) test_y0 (sampletests.test1.TestA) test_y1 (sampletests.test1.TestB) test_y0 (sampletests.test1) test_y0 (sampletests.test_one.TestA) test_y1 (sampletests.test_one.TestB) test_y0 (sampletests.test_one) Ran 38 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False We can use the --all option to run tests at all levels: >>> sys.argv = 'test -u -vv --all -t test_y1 -t test_y0'.split() >>> testrunner.run_internal(defaults) Running tests at all levels Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test_y0 (sampletestsf.TestA) test_y0 (sampletestsf.TestA2) test_y1 (sampletestsf.TestB) test_y0 (sampletestsf) test_y0 (sample1.sampletestsf.TestA) test_y1 (sample1.sampletestsf.TestB) test_y0 (sample1.sampletestsf) test_y0 (sample1.sample11.sampletests.TestA) test_y0 (sample1.sample11.sampletests.TestA3) test_y1 (sample1.sample11.sampletests.TestB) test_y1 (sample1.sample11.sampletests.TestB2) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sample13.sampletests.TestA) test_y1 (sample1.sample13.sampletests.TestB) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sampletests.test1.TestA) test_y1 (sample1.sampletests.test1.TestB) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test_one.TestA) test_y1 (sample1.sampletests.test_one.TestB) test_y0 (sample1.sampletests.test_one) test_y0 (sample2.sample21.sampletests.TestA) test_y1 (sample2.sample21.sampletests.TestB) test_y0 (sample2.sample21.sampletests) test_y0 (sample2.sampletests.test_1.TestA) test_y1 (sample2.sampletests.test_1.TestB) test_y0 (sample2.sampletests.test_1) test_y0 (sample2.sampletests.testone.TestA) test_y1 (sample2.sampletests.testone.TestB) test_y0 (sample2.sampletests.testone) test_y0 (sample3.sampletests.TestA) test_y1 (sample3.sampletests.TestB) test_y0 (sample3.sampletests) test_y0 (sampletests.test1.TestA) test_y1 (sampletests.test1.TestB) test_y0 (sampletests.test1) test_y0 (sampletests.test_one.TestA) test_y1 (sampletests.test_one.TestB) test_y0 (sampletests.test_one) Ran 39 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Listing Selected Tests ---------------------- When you're trying to figure out why the test you want is not matched by the pattern you specified, it is convenient to see which tests match your specifications. >>> sys.argv = 'test --all -m sample1 -t test_y0 --list-tests'.split() >>> testrunner.run_internal(defaults) Listing samplelayers.Layer11 tests: test_y0 (sample1.sampletests.test11.TestA) test_y0 (sample1.sampletests.test11) Listing samplelayers.Layer111 tests: test_y0 (sample1.sampletests.test111.TestA) test_y0 (sample1.sampletests.test111) Listing samplelayers.Layer112 tests: test_y0 (sample1.sampletests.test112.TestA) test_y0 (sample1.sampletests.test112) Listing samplelayers.Layer12 tests: test_y0 (sample1.sampletests.test12.TestA) test_y0 (sample1.sampletests.test12) Listing samplelayers.Layer121 tests: test_y0 (sample1.sampletests.test121.TestA) test_y0 (sample1.sampletests.test121) Listing samplelayers.Layer122 tests: test_y0 (sample1.sampletests.test122.TestA) test_y0 (sample1.sampletests.test122) Listing zope.testing.testrunner.layer.UnitTests tests: test_y0 (sample1.sampletestsf.TestA) test_y0 (sample1.sampletestsf) test_y0 (sample1.sample11.sampletests.TestA) test_y0 (sample1.sample11.sampletests.TestA3) test_y0 (sample1.sample11.sampletests) test_y0 (sample1.sample13.sampletests.TestA) test_y0 (sample1.sample13.sampletests) test_y0 (sample1.sampletests.test1.TestA) test_y0 (sample1.sampletests.test1) test_y0 (sample1.sampletests.test_one.TestA) test_y0 (sample1.sampletests.test_one) False zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/shuffle.py0000644000175000017500000000403212214017655025532 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Shuffle tests discovered before executing them. $Id$ """ import time import random import unittest import zope.testing.testrunner.feature class Shuffle(zope.testing.testrunner.feature.Feature): """Take the tests found so far and shuffle them.""" def __init__(self, runner): super(Shuffle, self).__init__(runner) self.active = runner.options.shuffle self.seed = runner.options.shuffle_seed if self.seed is None: # We can't rely on the random modules seed initialization because # we can't introspect the seed later for reporting. This is a # simple emulation of what random.Random.seed does anyway. self.seed = long(time.time() * 256) # use fractional seconds def global_setup(self): rng = random.Random(self.seed) for layer, suite in list(self.runner.tests_by_layer_name.items()): # Test suites cannot be modified through a public API. We thus # take a mutable copy of the list of tests of that suite, shuffle # that and replace the test suite instance with a new one of the # same class. tests = list(suite) rng.shuffle(tests) self.runner.tests_by_layer_name[layer] = suite.__class__(tests) def report(self): msg = "Tests were shuffled using seed number %d." % self.seed self.runner.options.output.info(msg) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-edge-cases.txt0000644000175000017500000004236312214017655030505 0ustar arnauarnautestrunner Edge Cases ===================== This document has some edge-case examples to test various aspects of the test runner. Separating Python path and test directories ------------------------------------------- The --path option defines a directory to be searched for tests *and* a directory to be added to Python's search path. The --test-path option can be used when you want to set a test search path without also affecting the Python path: >>> import os, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> from zope.testing import testrunner >>> defaults = [ ... '--test-path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = ['test'] >>> testrunner.run_internal(defaults) ... # doctest: +ELLIPSIS Test-module import failures: Module: sampletestsf Traceback (most recent call last): ImportError: No module named sampletestsf ... >>> sys.path.append(directory_with_tests) >>> sys.argv = ['test'] >>> testrunner.run_internal(defaults) ... # doctest: +ELLIPSIS Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in 0.000 seconds. Ran 9 tests with 0 failures and 0 errors in 0.000 seconds. ... Running zope.testing.testrunner.layer.UnitTests tests: Tear down samplelayers.Layer122 in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 405 tests, 0 failures, 0 errors in N.NNN seconds. False Bug #251759: The test runner's protection against descending into non-package directories was ineffective, e.g. picking up tests from eggs that were stored close by: >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex-251759') >>> defaults = [ ... '--test-path', directory_with_tests, ... ] >>> testrunner.run_internal(defaults) Total: 0 tests, 0 failures, 0 errors in 0.000 seconds. False Debugging Edge Cases -------------------- >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--test-path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> class Input: ... def __init__(self, src): ... self.lines = src.split('\n') ... def readline(self): ... line = self.lines.pop(0) ... print line ... return line+'\n' >>> real_stdin = sys.stdin Using pdb.set_trace in a function called by an ordinary test: >>> if sys.version_info[:2] == (2, 3): ... sys.stdin = Input('n\np x\nc') ... else: ... sys.stdin = Input('p x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t set_trace2').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +ELLIPSIS Running zope.testing.testrunner.layer.UnitTests tests:... > testrunner-ex/sample3/sampletests_d.py(47)f() -> y = x (Pdb) p x 1 (Pdb) c Ran 1 tests with 0 failures and 0 errors in 0.001 seconds. ... False Using pdb.set_trace in a function called by a doctest in a doc string: >>> sys.stdin = Input('n\np x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t set_trace4').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin Running zope.testing.testrunner.layer.UnitTests tests:... --Return-- > doctest.py(351)set_trace()->None -> pdb.Pdb.set_trace(self) (Pdb) n > testrunner-ex/sample3/sampletests_d.py(42)f() -> y = x (Pdb) p x 1 (Pdb) c Ran 1 tests with 0 failures and 0 errors in 0.002 seconds. ... False Using pdb in a docstring-based doctest >>> sys.stdin = Input('n\np x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t set_trace3').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin Running zope.testing.testrunner.layer.UnitTests tests:... --Return-- > doctest.py(351)set_trace()->None -> pdb.Pdb.set_trace(self) (Pdb) n > (3)...() -> y = x (Pdb) p x 1 (Pdb) c Ran 1 tests with 0 failures and 0 errors in 0.002 seconds. ... False Using pdb.set_trace in a doc file: >>> sys.stdin = Input('n\np x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t set_trace5').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin Running zope.testing.testrunner.layer.UnitTests tests:... --Return-- > doctest.py(351)set_trace()->None -> pdb.Pdb.set_trace(self) (Pdb) n > (3)...() -> y = x (Pdb) p x 1 (Pdb) c Ran 1 tests with 0 failures and 0 errors in 0.002 seconds. ... False Using pdb.set_trace in a function called by a doctest in a doc file: >>> sys.stdin = Input('n\np x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t set_trace6').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin Running zope.testing.testrunner.layer.UnitTests tests:... --Return-- > doctest.py(351)set_trace()->None -> pdb.Pdb.set_trace(self) (Pdb) n > testrunner-ex/sample3/sampletests_d.py(42)f() -> y = x (Pdb) p x 1 (Pdb) c Ran 1 tests with 0 failures and 0 errors in 0.002 seconds. ... False Post-mortem debugging function called from ordinary test: >>> sys.stdin = Input('p x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t post_mortem2 -D').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +NORMALIZE_WHITESPACE Running zope.testing.testrunner.layer.UnitTests tests:... Error in test test_post_mortem2 (sample3.sampletests_d.TestSomething) Traceback (most recent call last): File "testrunner-ex/sample3/sampletests_d.py", line 37, in test_post_mortem2 g() File "testrunner-ex/sample3/sampletests_d.py", line 46, in g raise ValueError ValueError exceptions.ValueError: > testrunner-ex/sample3/sampletests_d.py(46)g() -> raise ValueError (Pdb) p x 1 (Pdb) c True Post-mortem debugging docstring-based doctest: >>> sys.stdin = Input('p x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t post_mortem3 -D').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +NORMALIZE_WHITESPACE Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Error in test post_mortem3 (sample3.sampletests_d) Traceback (most recent call last): File ".../zope/testing/doctest/__init__.py", Line NNN, in debug runner.run(self._dt_test, clear_globs=False) File ".../zope/testing/doctest/__init__.py", Line NNN, in run r = DocTestRunner.run(self, test, compileflags, out, False) File ".../zope/testing/doctest/__init__.py", Line NNN, in run return self.__run(test, compileflags, out) File ".../zope/testing/doctest/__init__.py", Line NNN, in __run exc_info) File ".../zope/testing/doctest/__init__.py", Line NNN, in report_unexpected_exception raise UnexpectedException(test, example, exc_info) UnexpectedException: testrunner-ex/sample3/sampletests_d.py:61 (2 examples)> exceptions.ValueError: > (1)...() (Pdb) p x 1 (Pdb) c True Post-mortem debugging function called from docstring-based doctest: >>> sys.stdin = Input('p x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t post_mortem4 -D').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +NORMALIZE_WHITESPACE Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Error in test post_mortem4 (sample3.sampletests_d) Traceback (most recent call last): File ".../zope/testing/doctest/__init__.py", Line NNN, in debug runner.run(self._dt_test, clear_globs=False) File ".../zope/testing/doctest/__init__.py", Line NNN, in run r = DocTestRunner.run(self, test, compileflags, out, False) File ".../zope/testing/doctest/__init__.py", Line NNN, in run return self.__run(test, compileflags, out) File ".../zope/testing/doctest/__init__.py", Line NNN, in __run exc_info) File ".../zope/testing/doctest/__init__.py", Line NNN, in report_unexpected_exception raise UnexpectedException(test, example, exc_info) UnexpectedException: testrunner-ex/sample3/sampletests_d.py:NNN (1 example)> exceptions.ValueError: > testrunner-ex/sample3/sampletests_d.py(NNN)g() -> raise ValueError (Pdb) p x 1 (Pdb) c True Post-mortem debugging file-based doctest: >>> sys.stdin = Input('p x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t post_mortem5 -D').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +NORMALIZE_WHITESPACE Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Error testrunner-ex/sample3/post_mortem5.txt Traceback (most recent call last): File ".../zope/testing/doctest/__init__.py", Line NNN, in debug runner.run(self._dt_test, clear_globs=False) File ".../zope/testing/doctest/__init__.py", Line NNN, in run r = DocTestRunner.run(self, test, compileflags, out, False) File ".../zope/testing/doctest/__init__.py", Line NNN, in run return self.__run(test, compileflags, out) File ".../zope/testing/doctest/__init__.py", Line NNN, in __run exc_info) File ".../zope/testing/doctest/__init__.py", Line NNN, in report_unexpected_exception raise UnexpectedException(test, example, exc_info) UnexpectedException: testrunner-ex/sample3/post_mortem5.txt:0 (2 examples)> exceptions.ValueError: > (1)...() (Pdb) p x 1 (Pdb) c True Post-mortem debugging function called from file-based doctest: >>> sys.stdin = Input('p x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t post_mortem6 -D').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +NORMALIZE_WHITESPACE Running zope.testing.testrunner.layer.UnitTests tests:... Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Error testrunner-ex/sample3/post_mortem6.txt Traceback (most recent call last): File ".../zope/testing/doctest/__init__.py", Line NNN, in debug runner.run(self._dt_test, clear_globs=False) File ".../zope/testing/doctest/__init__.py", Line NNN, in run r = DocTestRunner.run(self, test, compileflags, out, False) File ".../zope/testing/doctest/__init__.py", Line NNN, in run return self.__run(test, compileflags, out) File ".../zope/testing/doctest/__init__.py", Line NNN, in __run exc_info) File ".../zope/testing/doctest/__init__.py", Line NNN, in report_unexpected_exception raise UnexpectedException(test, example, exc_info) UnexpectedException: testrunner-ex/sample3/post_mortem6.txt:0 (2 examples)> exceptions.ValueError: > testrunner-ex/sample3/sampletests_d.py(NNN)g() -> raise ValueError (Pdb) p x 1 (Pdb) c True Post-mortem debugging of a docstring doctest failure: >>> sys.stdin = Input('p x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t post_mortem_failure2 -D').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +NORMALIZE_WHITESPACE Running zope.testing.testrunner.layer.UnitTests tests:... Error in test post_mortem_failure2 (sample3.sampletests_d) File "testrunner-ex/sample3/sampletests_d.py", line 81, in sample3.sampletests_d.post_mortem_failure2 x Want: 2 Got: 1 > testrunner-ex/sample3/sampletests_d.py(81)_() exceptions.ValueError: Expected and actual output are different > (1)...() (Pdb) p x 1 (Pdb) c True Post-mortem debugging of a docfile doctest failure: >>> sys.stdin = Input('p x\nc') >>> sys.argv = ('test -ssample3 --tests-pattern ^sampletests_d$' ... ' -t post_mortem_failure.txt -D').split() >>> try: testrunner.run_internal(defaults) ... finally: sys.stdin = real_stdin ... # doctest: +NORMALIZE_WHITESPACE Running zope.testing.testrunner.layer.UnitTests tests:... Error in test /home/jim/z3/zope.testing/src/zope/testing/testrunner-ex/sample3/post_mortem_failure.txt File "testrunner-ex/sample3/post_mortem_failure.txt", line 2, in post_mortem_failure.txt x Want: 2 Got: 1 > testrunner-ex/sample3/post_mortem_failure.txt(2)_() exceptions.ValueError: Expected and actual output are different > (1)...() (Pdb) p x 1 (Pdb) c True Post-mortem debugging with triple verbosity >>> sys.argv = 'test --layer samplelayers.Layer1$ -vvv -D'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in 0.000 seconds. Running: test_x1 (sampletestsf.TestA1) (0.000 s) test_y0 (sampletestsf.TestA1) (0.000 s) test_z0 (sampletestsf.TestA1) (0.000 s) test_x0 (sampletestsf.TestB1) (0.000 s) test_y1 (sampletestsf.TestB1) (0.000 s) test_z0 (sampletestsf.TestB1) (0.000 s) test_1 (sampletestsf.TestNotMuch1) (0.000 s) test_2 (sampletestsf.TestNotMuch1) (0.000 s) test_3 (sampletestsf.TestNotMuch1) (0.000 s) Ran 9 tests with 0 failures and 0 errors in 0.001 seconds. Tearing down left over layers: Tear down samplelayers.Layer1 in 0.000 seconds. False Test Suites with None for suites or tests ----------------------------------------- >>> sys.argv = ['test', ... '--tests-pattern', '^sampletests_none_suite$', ... ] >>> testrunner.run_internal(defaults) Test-module import failures: Module: sample1.sampletests_none_suite Traceback (most recent call last): TypeError: Invalid test_suite, None, in sample1.sampletests_none_suite Test-modules with import problems: sample1.sampletests_none_suite Total: 0 tests, 0 failures, 0 errors in N.NNN seconds. True >>> sys.argv = ['test', ... '--tests-pattern', '^sampletests_none_test$', ... ] >>> testrunner.run_internal(defaults) Test-module import failures: Module: sample1.sampletests_none_test Traceback (most recent call last): TypeError: ... Test-modules with import problems: sample1.sampletests_none_test Total: 0 tests, 0 failures, 0 errors in N.NNN seconds. True You must use --repeat with --report-refcounts --------------------------------------------- It is an error to specify --report-refcounts (-r) without specifying a repeat count greater than 1 >>> sys.argv = 'test -r'.split() >>> testrunner.run_internal(defaults) You must use the --repeat (-N) option to specify a repeat count greater than 1 when using the --report_refcounts (-r) option. True >>> sys.argv = 'test -r -N1'.split() >>> testrunner.run_internal(defaults) You must use the --repeat (-N) option to specify a repeat count greater than 1 when using the --report_refcounts (-r) option. True zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-subunit-err.txt0000644000175000017500000000127512214017655030761 0ustar arnauarnauUsing subunit output without subunit installed ============================================== To use the --subunit reporting option, you must have subunit installed. If you do not, you will get an error message: >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> from zope.testing import testrunner >>> sys.argv = 'test --subunit'.split() >>> _ = testrunner.run_internal(defaults) Subunit is not installed. Please install Subunit to generate subunit output. zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner.txt0000644000175000017500000000504412214017655026502 0ustar arnauarnauTest Runner =========== The testrunner module is used to run automated tests defined using the unittest framework. Its primary feature is that it *finds* tests by searching directory trees. It doesn't require the manual concatenation of specific test suites. It is highly customizable and should be usable with any project. In addition to finding and running tests, it provides the following additional features: - Test filtering using specifications of: o test packages within a larger tree o regular expression patterns for test modules o regular expression patterns for individual tests - Organization of tests into levels and layers Sometimes, tests take so long to run that you don't want to run them on every run of the test runner. Tests can be defined at different levels. The test runner can be configured to only run tests at a specific level or below by default. Command-line options can be used to specify a minimum level to use for a specific run, or to run all tests. Individual tests or test suites can specify their level via a 'level' attribute. where levels are integers increasing from 1. Most tests are unit tests. They don't depend on other facilities, or set up whatever dependencies they have. For larger applications, it's useful to specify common facilities that a large number of tests share. Making each test set up and and tear down these facilities is both ineffecient and inconvenient. For this reason, we've introduced the concept of layers, based on the idea of layered application architectures. Software build for a layer should be able to depend on the facilities of lower layers already being set up. For example, Zope defines a component architecture. Much Zope software depends on that architecture. We should be able to treat the component architecture as a layer that we set up once and reuse. Similarly, Zope application software should be able to depend on the Zope application server without having to set it up in each test. The test runner introduces test layers, which are objects that can set up environments for tests within the layers to use. A layer is set up before running the tests in it. Individual tests or test suites can define a layer by defining a `layer` attribute, which is a test layer. - Reporting - progress meter - summaries of tests run - Analysis of test execution - post-mortem debugging of test failures - memory leaks - code coverage - source analysis using pychecker - memory errors - execution times - profiling zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/__init__.py0000644000175000017500000000424212214017655025640 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test runner $Id: __init__.py 111515 2010-04-28 06:16:52Z regebro $ """ import sys import unittest import warnings warnings.warn('zope.testing.testrunner is deprecated in favour of ' 'zope.testrunner.', DeprecationWarning, stacklevel=2) import zope.testing.testrunner.interfaces def run(defaults=None, args=None, script_parts=None): """Main runner function which can be and is being used from main programs. Will execute the tests and exit the process according to the test result. """ failed = run_internal(defaults, args, script_parts=script_parts) sys.exit(int(failed)) def run_internal(defaults=None, args=None, script_parts=None): """Execute tests. Returns whether errors or failures occured during testing. """ # XXX Bah. Lazy import to avoid circular/early import problems from zope.testing.testrunner.runner import Runner runner = Runner(defaults, args, script_parts=script_parts) runner.run() return runner.failed ############################################################################### # Install 2.4 TestSuite __iter__ into earlier versions if sys.version_info < (2, 4): def __iter__(suite): return iter(suite._tests) unittest.TestSuite.__iter__ = __iter__ del __iter__ # Install 2.4 TestSuite __iter__ into earlier versions ############################################################################### if __name__ == '__main__': # allow people to try out the test runner with # python -m zope.testing.testrunner --test-path . run() zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/0000755000175000017500000000000012214017655026350 5ustar arnauarnauzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/usecompiled/0000755000175000017500000000000012214017655030661 5ustar arnauarnau././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/usecompiled/compiletest.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/usecompiled/compilete0000644000175000017500000000160712214017655032571 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest class Test(unittest.TestCase): def test1(self): self.assertEqual(1, 1) def test2(self): self.assertEqual(1, 1) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(Test)) return suite ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/usecompiled/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/usecompiled/__init__.0000644000175000017500000000003012214017655032412 0ustar arnauarnau# Makes this a package. ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/usecompiled/README.txtzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/usecompiled/README.tx0000644000175000017500000000014012214017655032166 0ustar arnauarnauThe tests in this subtree are trivial, and used only to test testrunner's --usecompiled option. zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/usecompiled/package/0000755000175000017500000000000012214017655032254 5ustar arnauarnau././@LongLink0000000000000000000000000000016200000000000011564 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/usecompiled/package/compiletest.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/usecompiled/package/c0000644000175000017500000000160712214017655032425 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest class Test(unittest.TestCase): def test1(self): self.assertEqual(1, 1) def test2(self): self.assertEqual(1, 1) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(Test)) return suite ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/usecompiled/package/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/usecompiled/package/_0000644000175000017500000000003012214017655032406 0ustar arnauarnau# Makes this a package. zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/0000755000175000017500000000000012214017655027713 5ustar arnauarnau././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_1.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_10000644000175000017500000000151112214017655032420 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.testing import doctest def eek(self): """ >>> x = y >>> x >>> z = x + 1 """ def test_suite(): return doctest.DocTestSuite(optionflags=doctest.REPORT_NDIFF) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/do-not-enter/0000755000175000017500000000000012214017655032226 5ustar arnauarnau././@LongLink0000000000000000000000000000016300000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/do-not-enter/sampletests.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/do-not-enter/0000644000175000017500000000231312214017655032227 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest def f(): g() def g(): x = 1 x = x + 1 x = y + 1 x = x + 1 def eek(self): """ >>> f() 1 """ class Test(unittest.TestCase): def test1(self): pass def test2(self): pass def test3(self): f() def test4(self): pass def test5(self): pass def test_suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite()) suite.addTest(unittest.makeSuite(Test)) suite.addTest(doctest.DocFileSuite('e.txt')) return suite zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/stderrtest.py0000644000175000017500000000261212214017655032471 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample tests with a layer that produces output on stderr $Id: stderrtest.py 110538 2010-04-06 03:02:54Z tseaver $ """ import unittest from zope.testing import doctest import sys class Layer: def setUp(self): pass setUp = classmethod(setUp) def tearDown(self): pass tearDown = classmethod(tearDown) def test_something(): """ >>> 1 + 1 2 """ def test_suite(): # Generate some text on stderr to be sure the test runner can handle it. sys.stderr.write('A message on stderr.' ' Please ignore (expected in test output).\n') suite = unittest.TestSuite() d = doctest.DocTestSuite() d.layer = Layer suite.addTest(d) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/e.txt0000644000175000017500000000006612214017655030702 0ustar arnauarnau >>> def f(): ... return x >>> f() zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample23/0000755000175000017500000000000012214017655031341 5ustar arnauarnau././@LongLink0000000000000000000000000000016100000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample23/sampletests_i.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample23/samp0000644000175000017500000000146312214017655032230 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest class Test(unittest.TestCase): def test(self): self.assertEqual(1,0) raise TypeError('eek') def test_suite(): return unittest.makeSuite(Test) ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample23/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample23/__in0000644000175000017500000000000212214017655032160 0ustar arnauarnau# ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_ntd.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_n0000644000175000017500000000231112214017655032514 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample tests with a layer that can't be torn down $Id: sampletests_ntd.py 110538 2010-04-06 03:02:54Z tseaver $ """ import unittest class Layer: def setUp(self): pass setUp = classmethod(setUp) def tearDown(self): raise NotImplementedError tearDown = classmethod(tearDown) class TestSomething(unittest.TestCase): layer = Layer def test_something(self): pass def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestSomething)) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample21/0000755000175000017500000000000012214017655031337 5ustar arnauarnau././@LongLink0000000000000000000000000000016100000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample21/sampletests_i.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample21/samp0000644000175000017500000000146712214017655032232 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest import zope.testing.huh class Test(unittest.TestCase): def test(self): self.assertEqual(1,0) def test_suite(): return unittest.makeSuite(Test) ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample21/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample21/__in0000644000175000017500000000000212214017655032156 0ustar arnauarnau# ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample21/sampletests.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample21/samp0000644000175000017500000000375212214017655032231 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest x=0 y=0 z=0 class TestA(unittest.TestCase): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestB(unittest.TestCase): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestNotMuch(unittest.TestCase): def test_1(self): pass def test_2(self): pass def test_3(self): pass def setUp(test): test.globs['z'] = 1 def test_y0(self): """ >>> y 0 """ def test_x0(self): """ >>> x 0 """ def test_z1(self): """ >>> z 1 """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) suite.addTest(doctest.DocTestSuite(setUp=setUp)) suite.addTest(doctest.DocFileSuite('../../sampletests.txt', setUp=setUp)) return suite zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/__init__.py0000644000175000017500000000000212214017655032014 0ustar arnauarnau# ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_f.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_f0000644000175000017500000000142712214017655032513 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest class Test(unittest.TestCase): def test(self): self.assertEqual(1,0) def test_suite(): return unittest.makeSuite(Test) ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_e.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_e0000644000175000017500000000240512214017655032507 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest def f(): g() def g(): x = 1 x = x + 1 __traceback_info__ = "I don't know what Y should be." x = y + 1 x = x + 1 def eek(self): """ >>> f() 1 """ class Test(unittest.TestCase): def test1(self): pass def test2(self): pass def test3(self): f() def test4(self): pass def test5(self): pass def test_suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite()) suite.addTest(unittest.makeSuite(Test)) suite.addTest(doctest.DocFileSuite('e.txt')) return suite ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_ntds.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_n0000644000175000017500000000342312214017655032521 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample tests with a layer that can't be torn down $Id: sampletests_ntds.py 110538 2010-04-06 03:02:54Z tseaver $ """ import unittest from zope.testing import doctest class Layer: def setUp(self): pass setUp = classmethod(setUp) def tearDown(self): raise NotImplementedError tearDown = classmethod(tearDown) class TestSomething(unittest.TestCase): layer = Layer def test_something(self): import pdb; pdb.set_trace() def test_something2(self): import pdb; pdb.set_trace() def test_something3(self): import pdb; pdb.set_trace() def test_something4(self): import pdb; pdb.set_trace() def test_something5(self): f() def f(): import pdb; pdb.set_trace() def test_set_trace(): """ >>> if 1: ... x = 1 ... import pdb; pdb.set_trace() """ def test_set_trace2(): """ >>> f() """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestSomething)) d = doctest.DocTestSuite() d.layer = Layer suite.addTest(d) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests/0000755000175000017500000000000012214017655032257 5ustar arnauarnau././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests/test_1.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests/t0000644000175000017500000000375212214017655032454 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest x=0 y=0 z=0 class TestA(unittest.TestCase): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestB(unittest.TestCase): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestNotMuch(unittest.TestCase): def test_1(self): pass def test_2(self): pass def test_3(self): pass def setUp(test): test.globs['z'] = 1 def test_y0(self): """ >>> y 0 """ def test_x0(self): """ >>> x 0 """ def test_z1(self): """ >>> z 1 """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) suite.addTest(doctest.DocTestSuite(setUp=setUp)) suite.addTest(doctest.DocFileSuite('../../sampletests.txt', setUp=setUp)) return suite ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests/_0000644000175000017500000000000212214017655032410 0ustar arnauarnau# ././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests/testone.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests/t0000644000175000017500000000375212214017655032454 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest x=0 y=0 z=0 class TestA(unittest.TestCase): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestB(unittest.TestCase): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestNotMuch(unittest.TestCase): def test_1(self): pass def test_2(self): pass def test_3(self): pass def setUp(test): test.globs['z'] = 1 def test_y0(self): """ >>> y 0 """ def test_x0(self): """ >>> x 0 """ def test_z1(self): """ >>> z 1 """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) suite.addTest(doctest.DocTestSuite(setUp=setUp)) suite.addTest(doctest.DocFileSuite('../../sampletests.txt', setUp=setUp)) return suite zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample22/0000755000175000017500000000000012214017655031340 5ustar arnauarnau././@LongLink0000000000000000000000000000016100000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample22/sampletests_i.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample22/samp0000644000175000017500000000144012214017655032222 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest class Test(unittest.TestCase): def test(self): self.assertEqual(1,0) def test_suitex(): return unittest.makeSuite(Test) ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample22/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/sample22/__in0000644000175000017500000000000212214017655032157 0ustar arnauarnau# zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/donotenter/0000755000175000017500000000000012214017655032074 5ustar arnauarnau././@LongLink0000000000000000000000000000016100000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/donotenter/sampletests.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample2/donotenter/sa0000644000175000017500000000231312214017655032421 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest def f(): g() def g(): x = 1 x = x + 1 x = y + 1 x = x + 1 def eek(self): """ >>> f() 1 """ class Test(unittest.TestCase): def test1(self): pass def test2(self): pass def test3(self): f() def test4(self): pass def test5(self): pass def test_suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite()) suite.addTest(unittest.makeSuite(Test)) suite.addTest(doctest.DocFileSuite('e.txt')) return suite zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/0000755000175000017500000000000012214017655027714 5ustar arnauarnau././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/set_trace6.txtzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/set_trace6.tx0000644000175000017500000000007012214017655032325 0ustar arnauarnau >>> from sample3.sampletests_d import f >>> f() ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/post_mortem6.txtzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/post_mortem6.0000644000175000017500000000007012214017655032350 0ustar arnauarnau >>> from sample3.sampletests_d import g >>> g() ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_n0000644000175000017500000000277012214017655032526 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample tests with a layer that can't be torn down $Id: sampletests_ntd.py 110538 2010-04-06 03:02:54Z tseaver $ """ import unittest class Layer: def setUp(self): pass setUp = classmethod(setUp) def tearDown(self): raise NotImplementedError tearDown = classmethod(tearDown) class TestSomething(unittest.TestCase): layer = Layer def test_something(self): pass def test_something_else(self): pass def test_error1(self): raise TypeError("Can we see errors") def test_error2(self): raise TypeError("I hope so") def test_fail1(self): self.assertEqual(1, 2) def test_fail2(self): self.assertEqual(1, 3) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestSomething)) return suite if __name__ == '__main__': unittest.main() ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/post_mortem_failure.txtzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/post_mortem_f0000644000175000017500000000003612214017655032513 0ustar arnauarnau >>> x = 1 >>> x 2 ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_d.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_d0000644000175000017500000000411612214017655032510 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """XXX short summary goes here. $Id: sampletests_d.py 120498 2011-02-21 12:53:04Z icemac $ """ import unittest from zope.testing import doctest class TestSomething(unittest.TestCase): def test_set_trace1(self): x = 1 import pdb; pdb.set_trace() y = x def test_set_trace2(self): f() def test_post_mortem1(self): x = 1 raise ValueError def test_post_mortem2(self): g() def test_post_mortem_failure1(self): x = 1 y = 2 assert x == y def f(): x = 1 import pdb; pdb.set_trace() y = x def g(): x = 1 raise ValueError def set_trace3(self): """ >>> x = 1 >>> if 1: ... import pdb; pdb.set_trace() ... y = x """ def set_trace4(self): """ >>> f() """ def post_mortem3(self): """ >>> x = 1 >>> raise ValueError """ def post_mortem4(self): """ >>> g() """ def post_mortem_failure2(): """ >>> x = 1 >>> x 2 """ def test_suite(): return unittest.TestSuite(( doctest.DocTestSuite(), unittest.makeSuite(TestSomething), doctest.DocFileSuite('set_trace5.txt'), doctest.DocFileSuite('set_trace6.txt'), doctest.DocFileSuite('post_mortem5.txt'), doctest.DocFileSuite('post_mortem6.txt'), doctest.DocFileSuite('post_mortem_failure.txt'), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sample31/0000755000175000017500000000000012214017655031341 5ustar arnauarnau././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sample31/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sample31/__in0000644000175000017500000000000212214017655032160 0ustar arnauarnau# zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/__init__.py0000644000175000017500000000000212214017655032015 0ustar arnauarnau# zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sample33/0000755000175000017500000000000012214017655031343 5ustar arnauarnau././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sample33/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sample33/__in0000644000175000017500000000000212214017655032162 0ustar arnauarnau# ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/post_mortem5.txtzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/post_mortem5.0000644000175000017500000000004712214017655032353 0ustar arnauarnau >>> x = 1 >>> raise ValueError zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sample32/0000755000175000017500000000000012214017655031342 5ustar arnauarnau././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sample32/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sample32/__in0000644000175000017500000000000212214017655032161 0ustar arnauarnau# ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests.p0000644000175000017500000000374712214017655032454 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest x=0 y=0 z=0 class TestA(unittest.TestCase): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestB(unittest.TestCase): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestNotMuch(unittest.TestCase): def test_1(self): pass def test_2(self): pass def test_3(self): pass def setUp(test): test.globs['z'] = 1 def test_y0(self): """ >>> y 0 """ def test_x0(self): """ >>> x 0 """ def test_z1(self): """ >>> z 1 """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) suite.addTest(doctest.DocTestSuite(setUp=setUp)) suite.addTest(doctest.DocFileSuite('../sampletests.txt', setUp=setUp)) return suite ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/set_trace5.txtzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample3/set_trace5.tx0000644000175000017500000000012612214017655032326 0ustar arnauarnau >>> x = 1 >>> if 1: ... import pdb; pdb.set_trace() ... y = x zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/unicode.py0000644000175000017500000000134012214017655030346 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.testing import doctest def test_suite(): return doctest.DocFileSuite('unicode.txt') zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/pledge.py0000644000175000017500000000224312214017655030163 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.testing import doctest pledge_template = """\ I give my pledge, as %s, to save, and faithfully, to defend from waste, the natural resources of my %s. It's soils, minerals, forests, waters, and wildlife. """ def pledge(): """ >>> print pledge_template % ('and earthling', 'planet'), I give my pledge, as an earthling, to save, and faithfully, to defend from waste, the natural resources of my planet. It's soils, minerals, forests, waters, and wildlife. """ def test_suite(): return doctest.DocTestSuite() ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests_buffering.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests_buffering0000644000175000017500000000307112214017655032667 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample tests with sleep and layers that can't be torn down $Id$ """ import unittest, time class Layer1: def setUp(self): pass setUp = classmethod(setUp) def tearDown(self): raise NotImplementedError tearDown = classmethod(tearDown) class Layer2: def setUp(self): pass setUp = classmethod(setUp) def tearDown(self): raise NotImplementedError tearDown = classmethod(tearDown) class TestSomething1(unittest.TestCase): layer = Layer1 def test_something(self): pass class TestSomething2(unittest.TestCase): layer = Layer2 def test_something(self): time.sleep(0.5) def test_something2(self): time.sleep(0.5) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestSomething1)) suite.addTest(unittest.makeSuite(TestSomething2)) return suite if __name__ == '__main__': unittest.main()zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/gcset.py0000644000175000017500000000151412214017655030030 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.testing import doctest def make_sure_gc_threshold_is_701_11_9(): """ >>> import gc >>> gc.get_threshold() (701, 11, 9) """ def test_suite(): return doctest.DocTestSuite() zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/unicode.txt0000644000175000017500000000121112214017655030532 0ustar arnauarnau========================= Errors Containing Unicode ========================= There was a unicode bug. Create a function which returns a unicode string with non us-ascii characters:: >>> def get_unicode(): ... return u'foo \u2014 bar' >>> print get_unicode() oink There was another unicode bug. When a function returned unicode some internal state switched. This broke any further test not returning unicode but a plain string with unicode characters. Make sure this works now:: >>> get_unicode() u'foo \u2014 bar' >>> print get_unicode() foo — bar Get some normal output as well:: >>> 'xyz' 123 zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/0000755000175000017500000000000012214017655027712 5ustar arnauarnauzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample11/0000755000175000017500000000000012214017655031335 5ustar arnauarnau././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample11/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample11/__in0000644000175000017500000000000212214017655032154 0ustar arnauarnau# ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample11/sampletests.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample11/samp0000644000175000017500000000530312214017655032221 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest x=0 y=0 z=0 class TestA(unittest.TestCase): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestA3(unittest.TestCase): level = 3 def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestB(unittest.TestCase): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestB2(unittest.TestCase): level = 2 def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestNotMuch(unittest.TestCase): def test_1(self): pass def test_2(self): pass def test_3(self): pass def setUp(test): test.globs['z'] = 1 def test_y0(self): """ >>> y 0 """ def test_x0(self): """ >>> x 0 """ def test_z1(self): """ >>> z 1 """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestA3)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestB2)) suite.addTest(unittest.makeSuite(TestNotMuch)) suite.addTest(doctest.DocTestSuite(setUp=setUp)) suite.addTest(doctest.DocFileSuite('../../sampletests.txt', setUp=setUp)) return suite ././@LongLink0000000000000000000000000000016000000000000011562 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests_none_test.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests_n0000644000175000017500000000155012214017655032517 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample tests with a layer that can't be torn down $Id: sampletests_none_test.py 110538 2010-04-06 03:02:54Z tseaver $ """ import unittest def test_suite(): suite = unittest.TestSuite() suite.addTest(None) return suite zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample13/0000755000175000017500000000000012214017655031337 5ustar arnauarnau././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample13/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample13/__in0000644000175000017500000000000212214017655032156 0ustar arnauarnau# ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample13/sampletests.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample13/samp0000644000175000017500000000375212214017655032231 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest x=0 y=0 z=0 class TestA(unittest.TestCase): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestB(unittest.TestCase): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestNotMuch(unittest.TestCase): def test_1(self): pass def test_2(self): pass def test_3(self): pass def setUp(test): test.globs['z'] = 1 def test_y0(self): """ >>> y 0 """ def test_x0(self): """ >>> x 0 """ def test_z1(self): """ >>> z 1 """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) suite.addTest(doctest.DocTestSuite(setUp=setUp)) suite.addTest(doctest.DocFileSuite('../../sampletests.txt', setUp=setUp)) return suite ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests_ntd.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests_n0000644000175000017500000000231112214017655032513 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample tests with a layer that can't be torn down $Id: sampletests_ntd.py 110538 2010-04-06 03:02:54Z tseaver $ """ import unittest class Layer: def setUp(self): pass setUp = classmethod(setUp) def tearDown(self): raise NotImplementedError tearDown = classmethod(tearDown) class TestSomething(unittest.TestCase): layer = Layer def test_something(self): pass def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestSomething)) return suite if __name__ == '__main__': unittest.main() ././@LongLink0000000000000000000000000000016700000000000011571 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests_discover_notests.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests_d0000644000175000017500000000010412214017655032477 0ustar arnauarnaudef test_function_that_would_never_be_run(): self.assert_(True) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/__init__.py0000644000175000017500000000000212214017655032013 0ustar arnauarnau# ././@LongLink0000000000000000000000000000016100000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests_none_suite.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests_n0000644000175000017500000000142712214017655032522 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample tests with a layer that can't be torn down $Id: sampletests_none_suite.py 110538 2010-04-06 03:02:54Z tseaver $ """ def test_suite(): pass ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests_discover.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests_d0000644000175000017500000000014612214017655032505 0ustar arnauarnauimport unittest class TestA(unittest.TestCase): def test_truth(self): self.assert_(True) ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests_ntds.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests_n0000644000175000017500000000231212214017655032514 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample tests with a layer that can't be torn down $Id: sampletests_ntds.py 110538 2010-04-06 03:02:54Z tseaver $ """ import unittest class Layer: def setUp(self): pass setUp = classmethod(setUp) def tearDown(self): raise NotImplementedError tearDown = classmethod(tearDown) class TestSomething(unittest.TestCase): layer = Layer def test_something(self): pass def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestSomething)) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample12/0000755000175000017500000000000012214017655031336 5ustar arnauarnau././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample12/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sample12/__in0000644000175000017500000000000212214017655032155 0ustar arnauarnau# zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/0000755000175000017500000000000012214017655032256 5ustar arnauarnau././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/test112.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/t0000644000175000017500000000676512214017655032462 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest import samplelayers layername = 'samplelayers.Layer112' layer = samplelayers.Layer112 x=0 y=0 z=0 class TestA(unittest.TestCase): layer = layername def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestB(unittest.TestCase): layer = layername def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestNotMuch(unittest.TestCase): layer = layername def test_1(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_2(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_3(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def setUp(test): test.globs['z'] = 1 test.globs['layer'] = layer.layer test.globs['layerx'] = layer.layerx def test_y0(self): """ >>> y 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_x0(self): """ >>> x 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_z1(self): """ >>> z 1 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) s = doctest.DocTestSuite(setUp=setUp) s.layer = layer suite.addTest(s) s = doctest.DocFileSuite('../../sampletestsl.txt', setUp=setUp) s.layer = layer suite.addTest(s) return suite ././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/test122.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/t0000644000175000017500000000676412214017655032461 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest import samplelayers layername = 'samplelayers.Layer122' layer = samplelayers.Layer122 x=0 y=0 z=0 class TestA(unittest.TestCase): layer = layername def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestB(unittest.TestCase): layer = layername def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestNotMuch(unittest.TestCase): layer = layername def test_1(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_2(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_3(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def setUp(test): test.globs['z'] = 1 test.globs['layer'] = layer.layer test.globs['layerx'] = layer.layerx def test_y0(self): """ >>> y 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_x0(self): """ >>> x 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_z1(self): """ >>> z 1 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) s = doctest.DocTestSuite(setUp=setUp) s.layer = layer suite.addTest(s) s = doctest.DocFileSuite('../../sampletestsl.txt', setUp=setUp) s.layer = layer suite.addTest(s) return suite ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/test1.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/t0000644000175000017500000000375212214017655032453 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest x=0 y=0 z=0 class TestA(unittest.TestCase): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestB(unittest.TestCase): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestNotMuch(unittest.TestCase): def test_1(self): pass def test_2(self): pass def test_3(self): pass def setUp(test): test.globs['z'] = 1 def test_y0(self): """ >>> y 0 """ def test_x0(self): """ >>> x 0 """ def test_z1(self): """ >>> z 1 """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) suite.addTest(doctest.DocTestSuite(setUp=setUp)) suite.addTest(doctest.DocFileSuite('../../sampletests.txt', setUp=setUp)) return suite ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/test11.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/t0000644000175000017500000000676212214017655032457 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest import samplelayers layername = 'samplelayers.Layer11' layer = samplelayers.Layer11 x=0 y=0 z=0 class TestA(unittest.TestCase): layer = layername def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestB(unittest.TestCase): layer = layername def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestNotMuch(unittest.TestCase): layer = layername def test_1(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_2(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_3(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def setUp(test): test.globs['z'] = 1 test.globs['layer'] = layer.layer test.globs['layerx'] = layer.layerx def test_y0(self): """ >>> y 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_x0(self): """ >>> x 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_z1(self): """ >>> z 1 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) s = doctest.DocTestSuite(setUp=setUp) s.layer = layer suite.addTest(s) s = doctest.DocFileSuite('../../sampletestsl.txt', setUp=setUp) s.layer = layer suite.addTest(s) return suite ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/_0000644000175000017500000000000212214017655032407 0ustar arnauarnau# ././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/test111.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/t0000644000175000017500000000676412214017655032461 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest import samplelayers layername = 'samplelayers.Layer111' layer = samplelayers.Layer111 x=0 y=0 z=0 class TestA(unittest.TestCase): layer = layername def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestB(unittest.TestCase): layer = layername def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestNotMuch(unittest.TestCase): layer = layername def test_1(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_2(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_3(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def setUp(test): test.globs['z'] = 1 test.globs['layer'] = layer.layer test.globs['layerx'] = layer.layerx def test_y0(self): """ >>> y 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_x0(self): """ >>> x 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_z1(self): """ >>> z 1 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) s = doctest.DocTestSuite(setUp=setUp) s.layer = layer suite.addTest(s) s = doctest.DocFileSuite('../../sampletestsl.txt', setUp=setUp) s.layer = layer suite.addTest(s) return suite ././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/test121.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/t0000644000175000017500000000676412214017655032461 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest import samplelayers layername = 'samplelayers.Layer121' layer = samplelayers.Layer121 x=0 y=0 z=0 class TestA(unittest.TestCase): layer = layername def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestB(unittest.TestCase): layer = layername def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestNotMuch(unittest.TestCase): layer = layername def test_1(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_2(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_3(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def setUp(test): test.globs['z'] = 1 test.globs['layer'] = layer.layer test.globs['layerx'] = layer.layerx def test_y0(self): """ >>> y 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_x0(self): """ >>> x 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_z1(self): """ >>> z 1 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) s = doctest.DocTestSuite(setUp=setUp) s.layer = layer suite.addTest(s) s = doctest.DocFileSuite('../../sampletestsl.txt', setUp=setUp) s.layer = layer suite.addTest(s) return suite ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/test12.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/t0000644000175000017500000000676212214017655032457 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest import samplelayers layername = 'samplelayers.Layer12' layer = samplelayers.Layer12 x=0 y=0 z=0 class TestA(unittest.TestCase): layer = layername def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestB(unittest.TestCase): layer = layername def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestNotMuch(unittest.TestCase): layer = layername def test_1(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_2(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_3(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def setUp(test): test.globs['z'] = 1 test.globs['layer'] = layer.layer test.globs['layerx'] = layer.layerx def test_y0(self): """ >>> y 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_x0(self): """ >>> x 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_z1(self): """ >>> z 1 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) s = doctest.DocTestSuite(setUp=setUp) s.layer = layer suite.addTest(s) s = doctest.DocFileSuite('../../sampletestsl.txt', setUp=setUp) s.layer = layer suite.addTest(s) return suite ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/test_one.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/t0000644000175000017500000000375212214017655032453 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest x=0 y=0 z=0 class TestA(unittest.TestCase): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestB(unittest.TestCase): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestNotMuch(unittest.TestCase): def test_1(self): pass def test_2(self): pass def test_3(self): pass def setUp(test): test.globs['z'] = 1 def test_y0(self): """ >>> y 0 """ def test_x0(self): """ >>> x 0 """ def test_z1(self): """ >>> z 1 """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) suite.addTest(doctest.DocTestSuite(setUp=setUp)) suite.addTest(doctest.DocFileSuite('../../sampletests.txt', setUp=setUp)) return suite ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletestsf.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sample1/sampletestsf.0000644000175000017500000000374712214017655032440 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest x=0 y=0 z=0 class TestA(unittest.TestCase): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestB(unittest.TestCase): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestNotMuch(unittest.TestCase): def test_1(self): pass def test_2(self): pass def test_3(self): pass def setUp(test): test.globs['z'] = 1 def test_y0(self): """ >>> y 0 """ def test_x0(self): """ >>> x 0 """ def test_z1(self): """ >>> z 1 """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) suite.addTest(doctest.DocTestSuite(setUp=setUp)) suite.addTest(doctest.DocFileSuite('../sampletests.txt', setUp=setUp)) return suite zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/leak.py0000644000175000017500000000213412214017655027636 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest, time class ClassicLeakable: def __init__(self): self.x = 'x' class Leakable(object): def __init__(self): self.x = 'x' leaked = [] class TestSomething(unittest.TestCase): def testleak(self): leaked.append((ClassicLeakable(), Leakable(), time.time())) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestSomething)) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests.txt0000644000175000017500000000023112214017655031451 0ustar arnauarnauThis is a sample doctest >>> x=1 >>> x 1 Blah blah blah >>> x 1 Blah blah blah >>> x 1 Blah blah blah >>> x 1 zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletestsl.txt0000644000175000017500000000045712214017655031637 0ustar arnauarnauThis is a sample doctest >>> x=1 >>> x 1 Blah blah blah >>> x 1 Blah blah blah >>> x 1 Blah blah blah >>> x 1 are we in the right laters? >>> import samplelayers >>> layer == samplelayers.layer True >>> layerx == samplelayers.layerx True zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/README.txt0000644000175000017500000000015012214017655030042 0ustar arnauarnauThis directory and its subdirectories contain example tests for testing the test runner, testrunner.py. zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/samplelayers.py0000644000175000017500000001043112214017655031422 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample test layers $Id: samplelayers.py 110538 2010-04-06 03:02:54Z tseaver $ """ layer = '0' # Internal to samples. Not part of layer API layerx = '0' class Layer1: # Internal to samples. Not part of layer API: layer = '1' base = '0' layerx = '0' def setUp(self): global layer if layer != self.base: raise ValueError("Bad layer, %s, for %s." % (layer, self)) layer = self.layer setUp = classmethod(setUp) def tearDown(self): global layer if layer != self.layer: raise ValueError("Bad layer, %s, for %s." % (layer, self)) layer = self.base tearDown = classmethod(tearDown) class Layerx: layerx = '1' # Internal to samples. Not part of layer API basex = '0' def setUp(self): global layerx if layerx != self.basex: raise ValueError("Bad layerx, %s, for %s." % (layerx, self)) layerx = self.layerx setUp = classmethod(setUp) def tearDown(self): global layerx if layerx != self.layerx: raise ValueError("Bad layerx, %s, for %s." % (layerx, self)) layerx = self.basex tearDown = classmethod(tearDown) class Layer11(Layer1): layer = '11' # Internal to samples. Not part of layer API base = '1' # Internal to samples. Not part of layer API class Layer12(Layer1): layer = '12' # Internal to samples. Not part of layer API base = '1' # Internal to samples. Not part of layer API class Layer111(Layerx, Layer11): layer = '111' # Internal to samples. Not part of layer API base = '11' # Internal to samples. Not part of layer API layerx = '2' # Internal to samples. Not part of layer API basex = '1' def setUp(self): global layer if layer != self.base: raise ValueError("Bad layer, %s, for %s." % (layer, self)) layer = self.layer global layerx if layerx != self.basex: raise ValueError("Bad layerx, %s, for %s." % (layerx, self)) layerx = self.layerx setUp = classmethod(setUp) def tearDown(self): global layer if layer != self.layer: raise ValueError("Bad layer, %s, for %s." % (layer, self)) layer = self.base global layerx if layerx != self.layerx: raise ValueError("Bad layerx, %s, for %s." % (layerx, self)) layerx = self.basex tearDown = classmethod(tearDown) class Layer121(Layer12): layer = '121' # Internal to samples. Not part of layer API base = '12' # Internal to samples. Not part of layer API class Layer112(Layerx, Layer11): layer = '112' # Internal to samples. Not part of layer API base = '11' # Internal to samples. Not part of layer API layerx = '2' # Internal to samples. Not part of layer API basex = '1' def setUp(self): global layer if layer != self.base: raise ValueError("Bad layer, %s, for %s." % (layer, self)) layer = self.layer global layerx if layerx != self.basex: raise ValueError("Bad layerx, %s, for %s." % (layerx, self)) layerx = self.layerx setUp = classmethod(setUp) def tearDown(self): global layer if layer != self.layer: raise ValueError("Bad layer, %s, for %s." % (layer, self)) layer = self.base global layerx if layerx != self.layerx: raise ValueError("Bad layerx, %s, for %s." % (layerx, self)) layerx = self.basex tearDown = classmethod(tearDown) class Layer122(Layer12): layer = '122' # Internal to samples. Not part of layer API base = '12' # Internal to samples. Not part of layer API zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/gc0.py0000644000175000017500000000147212214017655027377 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.testing import doctest def make_sure_gc_is_disabled(): """ >>> import gc >>> gc.get_threshold()[0] 0 """ def test_suite(): return doctest.DocTestSuite() zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/0000755000175000017500000000000012214017655030714 5ustar arnauarnau././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test112.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test112.p0000644000175000017500000000732412214017655032306 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest import samplelayers layername = 'samplelayers.Layer112' layer = samplelayers.Layer112 x=0 y=0 z=0 class TestA(unittest.TestCase): layer = layername def setUp(self): global x x = 1 self.clean = getattr(self, 'clean', 0) + 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) # This is a test that the test runner clears attributes # that are set in setUp but not cleared in tearDown. self.assertEqual(self.clean, 1) class TestB(unittest.TestCase): layer = layername def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestNotMuch(unittest.TestCase): layer = layername def test_1(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_2(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_3(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def setUp(test): test.globs['z'] = 1 test.globs['layer'] = layer.layer test.globs['layerx'] = layer.layerx def test_y0(self): """ >>> y 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_x0(self): """ >>> x 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_z1(self): """ >>> z 1 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) s = doctest.DocTestSuite(setUp=setUp) s.layer = layer suite.addTest(s) s = doctest.DocFileSuite('../sampletestsl.txt', setUp=setUp) s.layer = layer suite.addTest(s) return suite ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test122.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test122.p0000644000175000017500000000676112214017655032313 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest import samplelayers layername = 'samplelayers.Layer122' layer = samplelayers.Layer122 x=0 y=0 z=0 class TestA(unittest.TestCase): layer = layername def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestB(unittest.TestCase): layer = layername def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestNotMuch(unittest.TestCase): layer = layername def test_1(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_2(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_3(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def setUp(test): test.globs['z'] = 1 test.globs['layer'] = layer.layer test.globs['layerx'] = layer.layerx def test_y0(self): """ >>> y 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_x0(self): """ >>> x 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_z1(self): """ >>> z 1 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) s = doctest.DocTestSuite(setUp=setUp) s.layer = layer suite.addTest(s) s = doctest.DocFileSuite('../sampletestsl.txt', setUp=setUp) s.layer = layer suite.addTest(s) return suite zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test1.py0000644000175000017500000000374712214017655032341 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest x=0 y=0 z=0 class TestA(unittest.TestCase): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestB(unittest.TestCase): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestNotMuch(unittest.TestCase): def test_1(self): pass def test_2(self): pass def test_3(self): pass def setUp(test): test.globs['z'] = 1 def test_y0(self): """ >>> y 0 """ def test_x0(self): """ >>> x 0 """ def test_z1(self): """ >>> z 1 """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) suite.addTest(doctest.DocTestSuite(setUp=setUp)) suite.addTest(doctest.DocFileSuite('../sampletests.txt', setUp=setUp)) return suite zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test11.py0000644000175000017500000000675712214017655032426 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest import samplelayers layername = 'samplelayers.Layer11' layer = samplelayers.Layer11 x=0 y=0 z=0 class TestA(unittest.TestCase): layer = layername def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestB(unittest.TestCase): layer = layername def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestNotMuch(unittest.TestCase): layer = layername def test_1(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_2(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_3(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def setUp(test): test.globs['z'] = 1 test.globs['layer'] = layer.layer test.globs['layerx'] = layer.layerx def test_y0(self): """ >>> y 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_x0(self): """ >>> x 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_z1(self): """ >>> z 1 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) s = doctest.DocTestSuite(setUp=setUp) s.layer = layer suite.addTest(s) s = doctest.DocFileSuite('../sampletestsl.txt', setUp=setUp) s.layer = layer suite.addTest(s) return suite ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/__init__.0000644000175000017500000000000212214017655032444 0ustar arnauarnau# ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test111.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test111.p0000644000175000017500000000676112214017655032311 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest import samplelayers layername = 'samplelayers.Layer111' layer = samplelayers.Layer111 x=0 y=0 z=0 class TestA(unittest.TestCase): layer = layername def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestB(unittest.TestCase): layer = layername def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestNotMuch(unittest.TestCase): layer = layername def test_1(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_2(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_3(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def setUp(test): test.globs['z'] = 1 test.globs['layer'] = layer.layer test.globs['layerx'] = layer.layerx def test_y0(self): """ >>> y 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_x0(self): """ >>> x 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_z1(self): """ >>> z 1 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) s = doctest.DocTestSuite(setUp=setUp) s.layer = layer suite.addTest(s) s = doctest.DocFileSuite('../sampletestsl.txt', setUp=setUp) s.layer = layer suite.addTest(s) return suite ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test121.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test121.p0000644000175000017500000000676112214017655032312 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest import samplelayers layername = 'samplelayers.Layer121' layer = samplelayers.Layer121 x=0 y=0 z=0 class TestA(unittest.TestCase): layer = layername def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestB(unittest.TestCase): layer = layername def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestNotMuch(unittest.TestCase): layer = layername def test_1(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_2(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_3(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def setUp(test): test.globs['z'] = 1 test.globs['layer'] = layer.layer test.globs['layerx'] = layer.layerx def test_y0(self): """ >>> y 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_x0(self): """ >>> x 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_z1(self): """ >>> z 1 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) s = doctest.DocTestSuite(setUp=setUp) s.layer = layer suite.addTest(s) s = doctest.DocFileSuite('../sampletestsl.txt', setUp=setUp) s.layer = layer suite.addTest(s) return suite zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test12.py0000644000175000017500000000675712214017655032427 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest import samplelayers layername = 'samplelayers.Layer12' layer = samplelayers.Layer12 x=0 y=0 z=0 class TestA(unittest.TestCase): layer = layername def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestB(unittest.TestCase): layer = layername def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) class TestNotMuch(unittest.TestCase): layer = layername def test_1(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_2(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def test_3(self): self.assertEqual(samplelayers.layer, layer.layer) self.assertEqual(samplelayers.layerx, layer.layerx) def setUp(test): test.globs['z'] = 1 test.globs['layer'] = layer.layer test.globs['layerx'] = layer.layerx def test_y0(self): """ >>> y 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_x0(self): """ >>> x 0 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_z1(self): """ >>> z 1 >>> (layer == samplelayers.layer), (layerx == samplelayers.layerx) (True, True) """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) s = doctest.DocTestSuite(setUp=setUp) s.layer = layer suite.addTest(s) s = doctest.DocFileSuite('../sampletestsl.txt', setUp=setUp) s.layer = layer suite.addTest(s) return suite ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test_one.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletests/test_one.0000644000175000017500000000374712214017655032550 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest x=0 y=0 z=0 class TestA(unittest.TestCase): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestB(unittest.TestCase): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestNotMuch(unittest.TestCase): def test_1(self): pass def test_2(self): pass def test_3(self): pass def setUp(test): test.globs['z'] = 1 def test_y0(self): """ >>> y 0 """ def test_x0(self): """ >>> x 0 """ def test_z1(self): """ >>> z 1 """ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) suite.addTest(doctest.DocTestSuite(setUp=setUp)) suite.addTest(doctest.DocFileSuite('../sampletests.txt', setUp=setUp)) return suite zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/gcstats.py0000644000175000017500000000152512214017655030375 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.testing import doctest def generate_some_gc_statistics(): """ >>> import gc >>> l = []; l.append(l); del l >>> _ = gc.collect() """ def test_suite(): return doctest.DocTestSuite() zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/gc1.py0000644000175000017500000000147712214017655027405 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.testing import doctest def make_sure_gc_threshold_is_one(): """ >>> import gc >>> gc.get_threshold()[0] 1 """ def test_suite(): return doctest.DocTestSuite() zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex/sampletestsf.py0000644000175000017500000001045112214017655031435 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from zope.testing import doctest x=0 y=0 z=0 class TestA(unittest.TestCase): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestA2(unittest.TestCase): level = 2 def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) def test_y0(self): self.assertEqual(y, 0) def test_z0(self): self.assertEqual(z, 0) class TestB(unittest.TestCase): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) def test_x0(self): self.assertEqual(x, 0) def test_z0(self): self.assertEqual(z, 0) class TestNotMuch(unittest.TestCase): def test_1(self): pass def test_2(self): pass def test_3(self): pass def setUp(test): test.globs['z'] = 1 def test_y0(self): """ >>> y 0 """ def test_x0(self): """ >>> x 0 """ def test_z1(self): """ >>> z 1 """ import samplelayers class Layered: layer = 'samplelayers.Layer1' layerv = '1' layerx = '0' class TestA1(unittest.TestCase, Layered): def setUp(self): global x x = 1 def tearDown(self): global x x = 0 def test_x1(self): self.assertEqual(x, 1) self.assertEqual(samplelayers.layer, self.layerv) self.assertEqual(samplelayers.layerx, self.layerx) def test_y0(self): self.assertEqual(y, 0) self.assertEqual(samplelayers.layer, self.layerv) self.assertEqual(samplelayers.layerx, self.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, self.layerv) self.assertEqual(samplelayers.layerx, self.layerx) class TestB1(unittest.TestCase, Layered): def setUp(self): global y y = 1 def tearDown(self): global y y = 0 def test_y1(self): self.assertEqual(y, 1) self.assertEqual(samplelayers.layer, self.layerv) self.assertEqual(samplelayers.layerx, self.layerx) def test_x0(self): self.assertEqual(x, 0) self.assertEqual(samplelayers.layer, self.layerv) self.assertEqual(samplelayers.layerx, self.layerx) def test_z0(self): self.assertEqual(z, 0) self.assertEqual(samplelayers.layer, self.layerv) self.assertEqual(samplelayers.layerx, self.layerx) class TestNotMuch1(unittest.TestCase, Layered): def test_1(self): self.assertEqual(samplelayers.layer, self.layerv) self.assertEqual(samplelayers.layerx, self.layerx) def test_2(self): self.assertEqual(samplelayers.layer, self.layerv) self.assertEqual(samplelayers.layerx, self.layerx) def test_3(self): self.assertEqual(samplelayers.layer, self.layerv) self.assertEqual(samplelayers.layerx, self.layerx) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestA)) suite.addTest(unittest.makeSuite(TestA2)) suite.addTest(unittest.makeSuite(TestB)) suite.addTest(unittest.makeSuite(TestNotMuch)) suite.addTest(doctest.DocTestSuite(setUp=setUp)) suite.addTest(doctest.DocFileSuite('sampletests.txt', setUp=setUp)) suite.addTest(unittest.makeSuite(TestA1)) suite.addTest(unittest.makeSuite(TestB1)) suite.addTest(unittest.makeSuite(TestNotMuch1)) return suite zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-layers-buff.txt0000644000175000017500000002332712214017655030723 0ustar arnauarnauThis is a test for a fix in buffering of output from a layer in a subprocess. Prior to the change that this tests, output from within a test layer in a subprocess would be buffered. This could wreak havoc on supervising processes (or human) that would kill a test run if no output was seen for some period of time. First, we wrap stdout with an object that instruments it. It notes the time at which a given line was written. >>> import os, sys, datetime >>> class RecordingStreamWrapper: ... def __init__(self, wrapped): ... self.record = [] ... self.wrapped = wrapped ... def write(self, out): ... self.record.append((out, datetime.datetime.now())) ... self.wrapped.write(out) ... def flush(self): ... self.wrapped.flush() ... >>> sys.stdout = RecordingStreamWrapper(sys.stdout) Now we actually call our test. If you open the file to which we are referring here (zope/testing/testrunner-ex/sampletests_buffering.py) you will see two test suites, each with its own layer that does not know how to tear down. This forces the second suite to be run in a subprocess. That second suite has two tests. Both sleep for half a second each. >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> from zope.testing import testrunner >>> defaults = [ ... '--path', directory_with_tests, ... ] >>> argv = [sys.argv[0], ... '-vv', '--tests-pattern', '^sampletests_buffering.*'] >>> try: ... testrunner.run_internal(defaults, argv) ... record = sys.stdout.record ... finally: ... sys.stdout = sys.stdout.wrapped ... Running tests at level 1 Running sampletests_buffering.Layer1 tests: Set up sampletests_buffering.Layer1 in N.NNN seconds. Running: test_something (sampletests_buffering.TestSomething1) Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Running sampletests_buffering.Layer2 tests: Tear down sampletests_buffering.Layer1 ... not supported Running in a subprocess. Set up sampletests_buffering.Layer2 in N.NNN seconds. Running: test_something (sampletests_buffering.TestSomething2) test_something2 (sampletests_buffering.TestSomething2) Ran 2 tests with 0 failures and 0 errors in N.NNN seconds. Tear down sampletests_buffering.Layer2 ... not supported Total: 3 tests, 0 failures, 0 errors in N.NNN seconds. False Now we actually check the results we care about. We should see that there are two pauses of about half a second, one around the first test and one around the second. Before the change that this test verifies, there was a single pause of more than a second after the second suite ran. >>> def assert_progressive_output(): ... pause = datetime.timedelta(seconds=0.3) ... last_line, last_time = record.pop(0) ... print '---' ... for line, time in record: ... if time-last_time >= pause: ... # We paused! ... print 'PAUSE FOUND BETWEEN THESE LINES:' ... print ''.join([last_line, line, '-'*70]) ... last_line, last_time = line, time >>> assert_progressive_output() # doctest: +ELLIPSIS ---... PAUSE FOUND BETWEEN THESE LINES:... Running: test_something (sampletests_buffering.TestSomething2) ---------------------------------------------------------------------- PAUSE FOUND BETWEEN THESE LINES: test_something (sampletests_buffering.TestSomething2) test_something2 (sampletests_buffering.TestSomething2) ---... Because this is a test based on timing, it may be somewhat fragile. However, on a relatively slow machine, this timing works out fine; I'm hopeful that this test will not be a source of spurious errors. If it is, we will have to readdress. Now let's do the same thing, but with multiple processes at once. We'll get a different result that has similar characteristics. >>> sys.stdout = RecordingStreamWrapper(sys.stdout) >>> argv.extend(['-j', '2']) >>> try: ... testrunner.run_internal(defaults, argv) ... record = sys.stdout.record ... finally: ... sys.stdout = sys.stdout.wrapped ... Running tests at level 1 Running sampletests_buffering.Layer1 tests: Set up sampletests_buffering.Layer1 in N.NNN seconds. Running: test_something (sampletests_buffering.TestSomething1) Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. [Parallel tests running in sampletests_buffering.Layer2: .. LAYER FINISHED] Running sampletests_buffering.Layer2 tests: Running in a subprocess. Set up sampletests_buffering.Layer2 in N.NNN seconds. Ran 2 tests with 0 failures and 0 errors in N.NNN seconds. Tear down sampletests_buffering.Layer2 ... not supported Tearing down left over layers: Tear down sampletests_buffering.Layer1 ... not supported Total: 3 tests, 0 failures, 0 errors in N.NNN seconds. False Notice that, with a -vv (or greater) verbosity, the parallel test run includes a progress report to keep track of tests run in the various layers. Because the actual results are saved to be displayed assembled in the original test order, the progress report shows up before we are given the news that the testrunner is starting Layer2. This is counterintuitive, but lets us keep the primary reporting information for the given layer in one location, while also giving us progress reports that can be used for keepalive analysis by a human or automated agent. In particular for the second point, notice below that, as before, the progress output is not buffered. >>> def assert_progressive_output(): ... pause = datetime.timedelta(seconds=0.3) ... last_line, last_time = record.pop(0) ... print '---' ... for line, time in record: ... if time-last_time >= pause: ... # We paused! ... print 'PAUSE FOUND BETWEEN THIS OUTPUT:' ... print '\n'.join([last_line, line, '-'*70]) ... last_line, last_time = line, time >>> assert_progressive_output() # doctest: +ELLIPSIS ---... PAUSE FOUND BETWEEN THIS OUTPUT:... . . ---------------------------------------------------------------------- PAUSE FOUND BETWEEN THIS OUTPUT: . LAYER FINISHED ---... Fake an IOError reading the output of the subprocess to exercise the reporting of that error: >>> class FakeStdout(object): ... raised = False ... def __init__(self, msg): ... self.msg = msg ... def readline(self): ... if not self.raised: ... self.raised = True ... raise IOError(self.msg) >>> class FakeStderr(object): ... def __init__(self, msg): ... self.msg = msg ... def read(self): ... return self.msg >>> class FakeProcess(object): ... def __init__(self, out, err): ... self.stdout = FakeStdout(out) ... self.stderr = FakeStderr(err) >>> class FakePopen(object): ... def __init__(self, out, err): ... self.out = out ... self.err = err ... def __call__(self, *args, **kw): ... return FakeProcess(self.out, self.err) >>> import subprocess >>> Popen = subprocess.Popen >>> subprocess.Popen = FakePopen( ... "Failure triggered to verify error reporting", ... "0 0 0") >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> from zope.testing import testrunner >>> defaults = [ ... '--path', directory_with_tests, ... ] >>> argv = [sys.argv[0], ... '-vv', '--tests-pattern', '^sampletests_buffering.*'] >>> _ = testrunner.run_internal(defaults, argv) Running tests at level 1 Running sampletests_buffering.Layer1 tests: Set up sampletests_buffering.Layer1 in N.NNN seconds. Running: test_something (sampletests_buffering.TestSomething1) Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Running sampletests_buffering.Layer2 tests: Tear down sampletests_buffering.Layer1 ... not supported Error reading subprocess output for sampletests_buffering.Layer2 Failure triggered to verify error reporting Total: 1 tests, 0 failures, 0 errors in N.NNN seconds. Now fake an empty stderr to test reporting a failure when communicating with the subprocess: >>> subprocess.Popen = FakePopen( ... "Failure triggered to verify error reporting", ... "") >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> from zope.testing import testrunner >>> defaults = [ ... '--path', directory_with_tests, ... ] >>> argv = [sys.argv[0], ... '-vv', '--tests-pattern', '^sampletests_buffering.*'] >>> _ = testrunner.run_internal(defaults, argv) Running tests at level 1 Running sampletests_buffering.Layer1 tests: Set up sampletests_buffering.Layer1 in N.NNN seconds. Running: test_something (sampletests_buffering.TestSomething1) Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Running sampletests_buffering.Layer2 tests: Tear down sampletests_buffering.Layer1 ... not supported Error reading subprocess output for sampletests_buffering.Layer2 Failure triggered to verify error reporting ********************************************************************** Could not communicate with subprocess ********************************************************************** Total: 1 tests, 0 failures, 0 errors in N.NNN seconds. >>> subprocess.Popen = Popen zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/tests.py0000644000175000017500000002763012214017655025251 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test harness for the test runner itself. $Id: __init__.py 86215 2008-05-03 14:08:12Z ctheune $ """ import re import gc import os import sys import unittest from zope.testing import doctest from zope.testing import renormalizing #separated checkers for the different platform, #because it s...s to maintain just one if sys.platform == 'win32': checker = renormalizing.RENormalizing([ # 2.5 changed the way pdb reports exceptions (re.compile(r":"), r'exceptions.\1Error:'), #rewrite pdb prompt to ... the current location #windows, py2.4 pdb seems not to put the '>' on doctest locations #therefore we cut it here (re.compile('^> doctest[^\n]+->None$', re.M), '...->None'), #rewrite pdb prompt to ... the current location (re.compile('^> [^\n]+->None$', re.M), '> ...->None'), (re.compile(r""),(r'?')), (re.compile(r":"), r'exceptions.\1Error:'), # Remove '\r', since this only causes confusion. (re.compile(r'\\r', re.MULTILINE), ''), (re.compile(r'\r', re.MULTILINE), ''), # testtools content formatter is used to mime-encode # tracebacks when the SubunitOutputFormatter is used, and the # resulting text includes a size which can vary depending on # the path included in the traceback. (re.compile(r'traceback\n[A-F\d]+', re.MULTILINE), r'traceback\nNNN'), (re.compile("'[A-Za-z]:\\\\"), "'"), # hopefully, we'll make Windows happy # replaces drives with nothing (re.compile(r'\\\\'), '/'), # more Windows happiness # double backslashes in coverage??? (re.compile(r'\\'), '/'), # even more Windows happiness # replaces backslashes in paths #this is a magic to put linefeeds into the doctest (re.compile('##r##\n'), '\r'), (re.compile(r'\d+[.]\d\d\d seconds'), 'N.NNN seconds'), (re.compile(r'\d+[.]\d\d\d s'), 'N.NNN s'), (re.compile(r'\d+[.]\d\d\d{'), 'N.NNN{'), (re.compile(r'\d{4}-\d\d-\d\d \d\d:\d\d:\d\d\.\d+'), 'YYYY-MM-DD HH:MM:SS.mmmmmm'), (re.compile('( |")[^\n]+testrunner-ex'), r'\1testrunner-ex'), (re.compile('( |")[^\n]+testrunner.py'), r'\1testrunner.py'), (re.compile(r'> [^\n]*(doc|unit)test[.]py\(\d+\)'), r'\1test.py(NNN)'), (re.compile(r'[.]py\(\d+\)'), r'.py(NNN)'), (re.compile(r'[.]py:\d+'), r'.py:NNN'), (re.compile(r' line \d+,', re.IGNORECASE), r' Line NNN,'), (re.compile(r' line {([a-z]+)}\d+{', re.IGNORECASE), r' Line {\1}NNN{'), # omit traceback entries for unittest.py or doctest.py (and # their package variants) from output: (re.compile(r'^ +File "[^\n]*(doctest|unittest|case)(/__init__)?.py", [^\n]+\n[^\n]+\n', re.MULTILINE), r''), (re.compile(r'^{\w+} +File "{\w+}[^\n]*(doctest|unittest|case)(/__init__)?.py{\w+}", [^\n]+\n[^\n]+\n', re.MULTILINE), r''), #(re.compile('^> [^\n]+->None$', re.M), '> ...->None'), (re.compile('import pdb; pdb'), 'Pdb()'), # Py 2.3 ]) else: #*nix checker = renormalizing.RENormalizing([ # 2.5 changed the way pdb reports exceptions (re.compile(r":"), r'exceptions.\1Error:'), #rewrite pdb prompt to ... the current location (re.compile('^> [^\n]+->None$', re.M), '> ...->None'), (re.compile(r""),(r'?')), (re.compile(r":"), r'exceptions.\1Error:'), #this is a magic to put linefeeds into the doctest #on win it takes one step, linux is crazy about the same... (re.compile('##r##'), r'\r'), (re.compile(r'\r'), '\\\\r\n'), (re.compile(r'\d+[.]\d\d\d seconds'), 'N.NNN seconds'), (re.compile(r'\d+[.]\d\d\d s'), 'N.NNN s'), (re.compile(r'\d+[.]\d\d\d{'), 'N.NNN{'), (re.compile(r'\d{4}-\d\d-\d\d \d\d:\d\d:\d\d\.\d+'), 'YYYY-MM-DD HH:MM:SS.mmmmmm'), (re.compile('( |")[^\n]+testrunner-ex'), r'\1testrunner-ex'), (re.compile('( |")[^\n]+testrunner.py'), r'\1testrunner.py'), (re.compile(r'> [^\n]*(doc|unit)test[.]py\(\d+\)'), r'\1test.py(NNN)'), (re.compile(r'[.]py\(\d+\)'), r'.py(NNN)'), (re.compile(r'[.]py:\d+'), r'.py:NNN'), (re.compile(r' line \d+,', re.IGNORECASE), r' Line NNN,'), (re.compile(r' line {([a-z]+)}\d+{', re.IGNORECASE), r' Line {\1}NNN{'), # testtools content formatter is used to mime-encode # tracebacks when the SubunitOutputFormatter is used, and the # resulting text includes a size which can vary depending on # the path included in the traceback. (re.compile(r'traceback\n[A-F\d]+', re.MULTILINE), r'traceback\nNNN'), # omit traceback entries for unittest.py or doctest.py (and # their package variants) from output: (re.compile(r'^ +File "[^\n]*(doctest|unittest|case)(/__init__)?.py", [^\n]+\n[^\n]+\n', re.MULTILINE), r''), (re.compile(r'^{\w+} +File "{\w+}[^\n]*(doctest|unittest|case)(/__init__)?.py{\w+}", [^\n]+\n[^\n]+\n', re.MULTILINE), r''), (re.compile('import pdb; pdb'), 'Pdb()'), # Py 2.3 ]) def setUp(test): test.globs['saved-sys-info'] = ( sys.path[:], sys.argv[:], sys.modules.copy(), gc.get_threshold(), ) test.globs['this_directory'] = os.path.split(__file__)[0] test.globs['testrunner_script'] = sys.argv[0] def tearDown(test): sys.path[:], sys.argv[:] = test.globs['saved-sys-info'][:2] gc.set_threshold(*test.globs['saved-sys-info'][3]) sys.modules.clear() sys.modules.update(test.globs['saved-sys-info'][2]) def test_suite(): suites = [ doctest.DocFileSuite( 'testrunner-arguments.txt', 'testrunner-coverage.txt', 'testrunner-debugging-layer-setup.test', 'testrunner-debugging.txt', 'testrunner-edge-cases.txt', 'testrunner-errors.txt', 'testrunner-layers-buff.txt', 'testrunner-layers-ntd.txt', 'testrunner-layers.txt', 'testrunner-layers-api.txt', 'testrunner-progress.txt', 'testrunner-colors.txt', 'testrunner-simple.txt', 'testrunner-test-selection.txt', 'testrunner-verbose.txt', 'testrunner-wo-source.txt', 'testrunner-repeat.txt', 'testrunner-gc.txt', 'testrunner-knit.txt', 'testrunner-shuffle.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE, checker=checker), doctest.DocTestSuite('zope.testing.testrunner'), doctest.DocTestSuite('zope.testing.testrunner.coverage', optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE), doctest.DocTestSuite('zope.testing.testrunner.options'), doctest.DocTestSuite('zope.testing.testrunner.find'), ] if sys.platform == 'win32': suites.append( doctest.DocFileSuite( 'testrunner-coverage-win32.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE, checker=checker)) # Python <= 2.4.1 had a bug that prevented hotshot from running in # non-optimize mode if sys.version_info[:3] > (2,4,1) or not __debug__: # some Linux distributions don't include the profiling module (which # hotshot.stats depends on) try: import hotshot.stats except ImportError: pass else: suites.append( doctest.DocFileSuite( 'testrunner-profiling.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE, checker = renormalizing.RENormalizing([ (re.compile(r'tests_profile[.]\S*[.]prof'), 'tests_profile.*.prof'), ]), ) ) try: import cProfile import pstats except ImportError: pass else: suites.append( doctest.DocFileSuite( 'testrunner-profiling-cprofiler.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE, checker = renormalizing.RENormalizing([ (re.compile(r'tests_profile[.]\S*[.]prof'), 'tests_profile.*.prof'), ]), ) ) if hasattr(sys, 'gettotalrefcount'): suites.append( doctest.DocFileSuite( 'testrunner-leaks.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE, checker = renormalizing.RENormalizing([ (re.compile(r'\d+[.]\d\d\d seconds'), 'N.NNN seconds'), (re.compile(r'sys refcount=\d+ +change=\d+'), 'sys refcount=NNNNNN change=NN'), (re.compile(r'sum detail refcount=\d+ +'), 'sum detail refcount=NNNNNN '), (re.compile(r'total +\d+ +\d+'), 'total NNNN NNNN'), (re.compile(r"^ +(int|type) +-?\d+ +-?\d+ *\n", re.M), ''), ]), ) ) else: suites.append( doctest.DocFileSuite( 'testrunner-leaks-err.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE, checker=checker, ) ) try: import subunit except ImportError: suites.append( doctest.DocFileSuite( 'testrunner-subunit-err.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE, checker=checker)) else: suites.append( doctest.DocFileSuite( 'testrunner-subunit.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE, checker=checker)) if hasattr(sys, 'gettotalrefcount'): suites.append( doctest.DocFileSuite( 'testrunner-subunit-leaks.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE, checker=checker)) if sys.version_info[:3] >= (2,7,0): # Python 2.7 adds support for unittest.expectedFailure suites.append(doctest.DocFileSuite( 'testrunner-unexpected-success.txt', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE, checker=checker)) return unittest.TestSuite(suites) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/garbagecollection.py0000644000175000017500000000443712214017655027553 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Garbage collection support. $Id: __init__.py 86218 2008-05-03 14:17:26Z ctheune $ """ import gc import sys import zope.testing.testrunner.feature class Threshold(zope.testing.testrunner.feature.Feature): def __init__(self, runner): super(Threshold, self).__init__(runner) self.threshold = self.runner.options.gc self.active = bool(self.threshold) if not self.active: return if len(self.threshold) > 3: self.runner.options.output.error("Too many --gc options") sys.exit(1) def global_setup(self): self.old_threshold = gc.get_threshold() if self.threshold[0]: self.runner.options.output.info( "Cyclic garbage collection threshold set to: %s" % repr(tuple(self.threshold))) else: self.runner.options.output.info( "Cyclic garbage collection is disabled.") gc.set_threshold(*self.threshold) def global_teardown(self): gc.set_threshold(*self.old_threshold) class Debug(zope.testing.testrunner.feature.Feature): """Manages garbage collection debug flags.""" def __init__(self, runner): super(Debug, self).__init__(runner) self.flags = self.runner.options.gc_option self.active = bool(self.flags) if not self.active: return def global_setup(self): # Set garbage collection debug flags self.old_flags = gc.get_debug() new_flags = 0 for op in self.flags: new_flags |= getattr(gc, op) gc.set_debug(new_flags) def global_teardown(self): gc.set_debug(self.old_flags) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-arguments.txt0000644000175000017500000000570012214017655030504 0ustar arnauarnauPassing arguments explicitly ============================ In most of the examples here, we set up `sys.argv`. In normal usage, the testrunner just uses `sys.argv`. It is possible to pass arguments explicitly. >>> import os.path >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults, 'test --layer 111'.split()) Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer1 in N.NNN seconds. Set up samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer111 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down samplelayers.Layer111 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. False If options already have default values, then passing a different default will override. For example, --list-tests defaults to being turned off, but if we pass in a different default, that one takes effect. >>> defaults = [ ... '--list-tests', ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults, 'test --layer 111'.split()) Listing samplelayers.Layer111 tests: test_x1 (sample1.sampletests.test111.TestA) test_y0 (sample1.sampletests.test111.TestA) test_z0 (sample1.sampletests.test111.TestA) test_x0 (sample1.sampletests.test111.TestB) test_y1 (sample1.sampletests.test111.TestB) test_z0 (sample1.sampletests.test111.TestB) test_1 (sample1.sampletests.test111.TestNotMuch) test_2 (sample1.sampletests.test111.TestNotMuch) test_3 (sample1.sampletests.test111.TestNotMuch) test_x0 (sample1.sampletests.test111) test_y0 (sample1.sampletests.test111) test_z1 (sample1.sampletests.test111) /home/benji/workspace/zope.testing/1/src/zope/testing/testrunner/testrunner-ex/sample1/sampletests/../../sampletestsl.txt test_x1 (sampletests.test111.TestA) test_y0 (sampletests.test111.TestA) test_z0 (sampletests.test111.TestA) test_x0 (sampletests.test111.TestB) test_y1 (sampletests.test111.TestB) test_z0 (sampletests.test111.TestB) test_1 (sampletests.test111.TestNotMuch) test_2 (sampletests.test111.TestNotMuch) test_3 (sampletests.test111.TestNotMuch) test_x0 (sampletests.test111) test_y0 (sampletests.test111) test_z1 (sampletests.test111) /home/benji/workspace/zope.testing/1/src/zope/testing/testrunner/testrunner-ex/sampletests/../sampletestsl.txt False zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/filter.py0000644000175000017500000000571312214017655025372 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Filter which tests to run. $Id: __init__.py 86218 2008-05-03 14:17:26Z ctheune $ """ import zope.testing.testrunner.feature UNITTEST_LAYER = 'zope.testing.testrunner.layer.UnitTests' class Filter(zope.testing.testrunner.feature.Feature): """Filters and orders all tests registered until now.""" active = True def global_setup(self): layers = self.runner.tests_by_layer_name options = self.runner.options if UNITTEST_LAYER in layers: # We start out assuming unit tests should run and look for reasons # why they shouldn't be run. should_run = True if (not options.non_unit): if options.layer: should_run = False for pat in options.layer: if pat(UNITTEST_LAYER): should_run = True break else: should_run = True else: should_run = False if not should_run: layers.pop(UNITTEST_LAYER) if self.runner.options.resume_layer is not None: for name in list(layers): if name != self.runner.options.resume_layer: layers.pop(name) elif self.runner.options.layer: for name in list(layers): for pat in self.runner.options.layer: if pat(name): # This layer matches a pattern selecting this layer break else: # No pattern matched this name so we remove it layers.pop(name) if (self.runner.options.verbose and not self.runner.options.resume_layer): if self.runner.options.all: msg = "Running tests at all levels" else: msg = "Running tests at level %d" % self.runner.options.at_level self.runner.options.output.info(msg) def report(self): if not self.runner.do_run_tests: return if self.runner.options.resume_layer: return if self.runner.options.verbose: self.runner.options.output.tests_with_errors(self.runner.errors) self.runner.options.output.tests_with_failures(self.runner.failures) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-profiling.txt0000644000175000017500000000352512214017655030473 0ustar arnauarnauProfiling ========= The testrunner supports hotshot and cProfile profilers. Hotshot profiler support does not work with python2.6 >>> import os.path, sys >>> profiler = '--profile=hotshot' >>> if sys.hexversion >= 0x02060000: ... profiler = '--profile=cProfile' The testrunner includes the ability to profile the test execution with hotshot via the --profile option, if it a python <= 2.6 >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> sys.path.append(directory_with_tests) >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = [testrunner_script, profiler] When the tests are run, we get profiling output. >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running samplelayers.Layer1 tests: ... Running samplelayers.Layer11 tests: ... Running zope.testing.testrunner.layer.UnitTests tests: ... ncalls tottime percall cumtime percall filename:lineno(function) ... Total: ... tests, 0 failures, 0 errors in ... seconds. False Profiling also works across layers. >>> sys.argv = [testrunner_script, '-ssample2', profiler, ... '--tests-pattern', 'sampletests_ntd'] >>> testrunner.run_internal(defaults) Running... Tear down ... not supported... ncalls tottime percall cumtime percall filename:lineno(function)... The testrunner creates temnporary files containing hotshot profiler data: >>> import glob >>> files = list(glob.glob('tests_profile.*.prof')) >>> files.sort() >>> files ['tests_profile.cZj2jt.prof', 'tests_profile.yHD-so.prof'] It deletes these when rerun. We'll delete these ourselves: >>> import os >>> for f in files: ... os.unlink(f) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/refcount.py0000644000175000017500000000630512214017655025730 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Support for tracking reference counts. $Id: __init__.py 86246 2008-05-03 15:54:02Z ctheune $ """ import gc import sys import types class TrackRefs(object): """Object to track reference counts across test runs.""" def __init__(self): self.type2count = {} self.type2all = {} self.delta = None self.n = 0 self.update() self.delta = None def update(self): gc.collect() obs = sys.getobjects(0) type2count = {} type2all = {} n = 0 for o in obs: if type(o) is str and o == '': # avoid dictionary madness continue all = sys.getrefcount(o) - 3 n += all t = type(o) if t is types.InstanceType: t = o.__class__ if t in type2count: type2count[t] += 1 type2all[t] += all else: type2count[t] = 1 type2all[t] = all ct = [( type_or_class_title(t), type2count[t] - self.type2count.get(t, 0), type2all[t] - self.type2all.get(t, 0), ) for t in type2count.iterkeys()] ct += [( type_or_class_title(t), - self.type2count[t], - self.type2all[t], ) for t in self.type2count.iterkeys() if t not in type2count] ct.sort() self.delta = ct self.type2count = type2count self.type2all = type2all self.n = n def output(self): printed = False s1 = s2 = 0 for t, delta1, delta2 in self.delta: if delta1 or delta2: if not printed: print ( ' Leak details, changes in instances and refcounts' ' by type/class:') print " %-55s %6s %6s" % ('type/class', 'insts', 'refs') print " %-55s %6s %6s" % ('-' * 55, '-----', '----') printed = True print " %-55s %6d %6d" % (t, delta1, delta2) s1 += delta1 s2 += delta2 if printed: print " %-55s %6s %6s" % ('-' * 55, '-----', '----') print " %-55s %6s %6s" % ('total', s1, s2) self.delta = None def type_or_class_title(t): module = getattr(t, '__module__', '__builtin__') if module == '__builtin__': return t.__name__ return "%s.%s" % (module, t.__name__) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-gc.txt0000644000175000017500000001020312214017655027062 0ustar arnauarnauGarbage Collection Control ========================== When having problems that seem to be caused my memory-management errors, it can be helpful to adjust Python's cyclic garbage collector or to get garbage colection statistics. The --gc option can be used for this purpose. If you think you are getting a test failure due to a garbage collection problem, you can try disabling garbage collection by using the --gc option with a value of zero. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = ['--path', directory_with_tests] >>> from zope.testing import testrunner >>> sys.argv = 'test --tests-pattern ^gc0$ --gc 0 -vv'.split() >>> _ = testrunner.run_internal(defaults) Cyclic garbage collection is disabled. Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: make_sure_gc_is_disabled (gc0) Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Alternatively, if you think you are having a garbage collection related problem, you can cause garbage collection to happen more often by providing a low threshold: >>> sys.argv = 'test --tests-pattern ^gc1$ --gc 1 -vv'.split() >>> _ = testrunner.run_internal(defaults) Cyclic garbage collection threshold set to: (1,) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: make_sure_gc_threshold_is_one (gc1) Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. You can specify up to 3 --gc options to set each of the 3 gc threshold values: >>> sys.argv = ('test --tests-pattern ^gcset$ --gc 701 --gc 11 --gc 9 -vv' ... .split()) >>> _ = testrunner.run_internal(defaults) Cyclic garbage collection threshold set to: (701, 11, 9) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: make_sure_gc_threshold_is_701_11_9 (gcset) Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Specifying more than 3 --gc options is not allowed: >>> from StringIO import StringIO >>> out = StringIO() >>> stdout = sys.stdout >>> sys.stdout = out >>> sys.argv = ('test --tests-pattern ^gcset$ --gc 701 --gc 42 --gc 11 --gc 9 -vv' ... .split()) >>> _ = testrunner.run_internal(defaults) Traceback (most recent call last): ... SystemExit: 1 >>> sys.stdout = stdout >>> print out.getvalue() Too many --gc options Garbage Collection Statistics ----------------------------- You can enable gc debugging statistics using the --gc-options (-G) option. You should provide names of one or more of the flags described in the library documentation for the gc module. The output statistics are written to standard error. >>> from StringIO import StringIO >>> err = StringIO() >>> stderr = sys.stderr >>> sys.stderr = err >>> sys.argv = ('test --tests-pattern ^gcstats$ -G DEBUG_STATS' ... ' -G DEBUG_COLLECTABLE -vv' ... .split()) >>> _ = testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: generate_some_gc_statistics (gcstats) Ran 1 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. >>> sys.stderr = stderr >>> print err.getvalue() # doctest: +ELLIPSIS gc: collecting generation ... zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-simple.txt0000644000175000017500000001001112214017655027757 0ustar arnauarnauSimple Usage ============ The test runner consists of an importable module. The test runner is used by providing scripts that import and invoke the `run` method from the module. The `testrunner` module is controlled via command-line options. Test scripts supply base and default options by supplying a list of default command-line options that are processed before the user-supplied command-line options are provided. Typically, a test script does 2 things: - Adds the directory containing the zope package to the Python path. - Calls the test runner with default arguments and arguments supplied to the script. Normally, it just passes default/setup arguments. The test runner uses `sys.argv` to get the user's input. This testrunner_ex subdirectory contains a number of sample packages with tests. Let's run the tests found here. First though, we'll set up our default options: >>> import os.path >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] The default options are used by a script to customize the test runner for a particular application. In this case, we use two options: path Set the path where the test runner should look for tests. This path is also added to the Python path. tests-pattern Tell the test runner how to recognize modules or packages containing tests. Now, if we run the tests, without any other options: >>> from zope.testing import testrunner >>> import sys >>> sys.argv = ['test'] >>> testrunner.run_internal(defaults) Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in N.NNN seconds. Ran 9 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer11 tests: Set up samplelayers.Layer11 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in N.NNN seconds. Set up samplelayers.Layer111 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer112 tests: Tear down samplelayers.Layer111 in N.NNN seconds. Set up samplelayers.Layer112 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer12 tests: Tear down samplelayers.Layer112 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Set up samplelayers.Layer12 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer121 tests: Set up samplelayers.Layer121 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running samplelayers.Layer122 tests: Tear down samplelayers.Layer121 in N.NNN seconds. Set up samplelayers.Layer122 in N.NNN seconds. Ran 34 tests with 0 failures and 0 errors in N.NNN seconds. Running zope.testing.testrunner.layer.UnitTests tests: Tear down samplelayers.Layer122 in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 405 tests, 0 failures, 0 errors in N.NNN seconds. False we see the normal testrunner output, which summarizes the tests run for each layer. For each layer, we see what layers had to be torn down or set up to run the layer and we see the number of tests run, with results. The test runner returns a boolean indicating whether there were errors. In this example, there were no errors, so it returned False. (Of course, the times shown in these examples are just examples. Times will vary depending on system speed.) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/0000755000175000017500000000000012214017655027122 5ustar arnauarnauzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/0000755000175000017500000000000012214017655030047 5ustar arnauarnau././@LongLink0000000000000000000000000000016200000000000011564 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-1.2-py2.5.egg/zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-10000755000175000017500000000000012214017655031454 5ustar arnauarnau././@LongLink0000000000000000000000000000017100000000000011564 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-1.2-py2.5.egg/test.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-10000644000175000017500000000000012214017655031444 0ustar arnauarnau././@LongLink0000000000000000000000000000016600000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-1.2-py2.5.egg/foo/zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-10000755000175000017500000000000012214017655031454 5ustar arnauarnau././@LongLink0000000000000000000000000000017200000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-1.2-py2.5.egg/foo/bar/zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-10000755000175000017500000000000012214017655031454 5ustar arnauarnau././@LongLink0000000000000000000000000000020500000000000011562 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-1.2-py2.5.egg/foo/bar/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-10000644000175000017500000000000012214017655031444 0ustar arnauarnau././@LongLink0000000000000000000000000000020200000000000011557 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-1.2-py2.5.egg/foo/bar/tests.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-10000644000175000017500000000000012214017655031444 0ustar arnauarnau././@LongLink0000000000000000000000000000020100000000000011556 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-1.2-py2.5.egg/foo/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-251759/eggs/foo.bar-10000644000175000017500000000000012214017655031444 0ustar arnauarnauzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-progress.txt0000644000175000017500000005110112214017655030337 0ustar arnauarnauTest Progress ============= If the --progress (-p) option is used, progress information is printed and a carriage return (rather than a new-line) is printed between detail lines. Let's look at the effect of --progress (-p) at different levels of verbosity. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --layer 122 -p'.split() >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: 1/34 (2.9%)##r## ##r## 2/34 (5.9%)##r## ##r## 3/34 (8.8%)##r## ##r## 4/34 (11.8%)##r## ##r## 5/34 (14.7%)##r## ##r## 6/34 (17.6%)##r## ##r## 7/34 (20.6%)##r## ##r## 8/34 (23.5%)##r## ##r## 9/34 (26.5%)##r## ##r## 10/34 (29.4%)##r## ##r## 11/34 (32.4%)##r## ##r## 12/34 (35.3%)##r## ##r## 17/34 (50.0%)##r## ##r## 18/34 (52.9%)##r## ##r## 19/34 (55.9%)##r## ##r## 20/34 (58.8%)##r## ##r## 21/34 (61.8%)##r## ##r## 22/34 (64.7%)##r## ##r## 23/34 (67.6%)##r## ##r## 24/34 (70.6%)##r## ##r## 25/34 (73.5%)##r## ##r## 26/34 (76.5%)##r## ##r## 27/34 (79.4%)##r## ##r## 28/34 (82.4%)##r## ##r## 29/34 (85.3%)##r## ##r## 34/34 (100.0%)##r## ##r## Ran 34 tests with 0 failures and 0 errors in 0.008 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False (Note that, in the examples above and below, we show "##r##" followed by new lines where carriage returns would appear in actual output.) Using a single level of verbosity causes test descriptions to be output, but only if they fit in the terminal width. The default width, when the terminal width can't be determined, is 80: >>> sys.argv = 'test --layer 122 -pv'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: 1/34 (2.9%) test_x1 (sample1.sampletests.test122.TestA)##r## ##r## 2/34 (5.9%) test_y0 (sample1.sampletests.test122.TestA)##r## ##r## 3/34 (8.8%) test_z0 (sample1.sampletests.test122.TestA)##r## ##r## 4/34 (11.8%) test_x0 (sample1.sampletests.test122.TestB)##r## ##r## 5/34 (14.7%) test_y1 (sample1.sampletests.test122.TestB)##r## ##r## 6/34 (17.6%) test_z0 (sample1.sampletests.test122.TestB)##r## ##r## 7/34 (20.6%) test_1 (sample1.sampletests.test122.TestNotMuch)##r## ##r## 8/34 (23.5%) test_2 (sample1.sampletests.test122.TestNotMuch)##r## ##r## 9/34 (26.5%) test_3 (sample1.sampletests.test122.TestNotMuch)##r## ##r## 10/34 (29.4%) test_x0 (sample1.sampletests.test122)##r## ##r## 11/34 (32.4%) test_y0 (sample1.sampletests.test122)##r## ##r## 12/34 (35.3%) test_z1 (sample1.sampletests.test122)##r## ##r## 17/34 (50.0%) ... /testrunner-ex/sample1/sampletests/../../sampletestsl.txt##r## ##r## 18/34 (52.9%) test_x1 (sampletests.test122.TestA)##r## ##r## 19/34 (55.9%) test_y0 (sampletests.test122.TestA)##r## ##r## 20/34 (58.8%) test_z0 (sampletests.test122.TestA)##r## ##r## 21/34 (61.8%) test_x0 (sampletests.test122.TestB)##r## ##r## 22/34 (64.7%) test_y1 (sampletests.test122.TestB)##r## ##r## 23/34 (67.6%) test_z0 (sampletests.test122.TestB)##r## ##r## 24/34 (70.6%) test_1 (sampletests.test122.TestNotMuch)##r## ##r## 25/34 (73.5%) test_2 (sampletests.test122.TestNotMuch)##r## ##r## 26/34 (76.5%) test_3 (sampletests.test122.TestNotMuch)##r## ##r## 27/34 (79.4%) test_x0 (sampletests.test122)##r## ##r## 28/34 (82.4%) test_y0 (sampletests.test122)##r## ##r## 29/34 (85.3%) test_z1 (sampletests.test122)##r## ##r## 34/34 (100.0%) ... pe/testing/testrunner-ex/sampletests/../sampletestsl.txt##r## ##r## Ran 34 tests with 0 failures and 0 errors in 0.008 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False The terminal width is determined using the curses module. To see that, we'll provide a fake curses module: >>> class FakeCurses: ... def setupterm(self): ... pass ... def tigetnum(self, ignored): ... return 60 >>> old_curses = sys.modules.get('curses') >>> sys.modules['curses'] = FakeCurses() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: 1/34 (2.9%) test_x1 (sample1.sampletests.test122.TestA)##r## ##r## 2/34 (5.9%) test_y0 (sample1.sampletests.test122.TestA)##r## ##r## 3/34 (8.8%) test_z0 (sample1.sampletests.test122.TestA)##r## ##r## 4/34 (11.8%) test_x0 (...le1.sampletests.test122.TestB)##r## ##r## 5/34 (14.7%) test_y1 (...le1.sampletests.test122.TestB)##r## ##r## 6/34 (17.6%) test_z0 (...le1.sampletests.test122.TestB)##r## ##r## 7/34 (20.6%) test_1 (...ampletests.test122.TestNotMuch)##r## ##r## 8/34 (23.5%) test_2 (...ampletests.test122.TestNotMuch)##r## ##r## 9/34 (26.5%) test_3 (...ampletests.test122.TestNotMuch)##r## ##r## 10/34 (29.4%) test_x0 (sample1.sampletests.test122)##r## ##r## 11/34 (32.4%) test_y0 (sample1.sampletests.test122)##r## ##r## 12/34 (35.3%) test_z1 (sample1.sampletests.test122)##r## ##r## 17/34 (50.0%) ... e1/sampletests/../../sampletestsl.txt##r## ##r## 18/34 (52.9%) test_x1 (sampletests.test122.TestA)##r## ##r## 19/34 (55.9%) test_y0 (sampletests.test122.TestA)##r## ##r## 20/34 (58.8%) test_z0 (sampletests.test122.TestA)##r## ##r## 21/34 (61.8%) test_x0 (sampletests.test122.TestB)##r## ##r## 22/34 (64.7%) test_y1 (sampletests.test122.TestB)##r## ##r## 23/34 (67.6%) test_z0 (sampletests.test122.TestB)##r## ##r## 24/34 (70.6%) test_1 (sampletests.test122.TestNotMuch)##r## ##r## 25/34 (73.5%) test_2 (sampletests.test122.TestNotMuch)##r## ##r## 26/34 (76.5%) test_3 (sampletests.test122.TestNotMuch)##r## ##r## 27/34 (79.4%) test_x0 (sampletests.test122)##r## ##r## 28/34 (82.4%) test_y0 (sampletests.test122)##r## ##r## 29/34 (85.3%) test_z1 (sampletests.test122)##r## ##r## 34/34 (100.0%) ... r-ex/sampletests/../sampletestsl.txt##r## ##r## Ran 34 tests with 0 failures and 0 errors in 0.008 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False >>> sys.modules['curses'] = old_curses If a second or third level of verbosity are added, we get additional information. >>> sys.argv = 'test --layer 122 -pvv -t !txt'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: 1/24 (4.2%) test_x1 (sample1.sampletests.test122.TestA)##r## ##r## 2/24 (8.3%) test_y0 (sample1.sampletests.test122.TestA)##r## ##r## 3/24 (12.5%) test_z0 (sample1.sampletests.test122.TestA)##r## ##r## 4/24 (16.7%) test_x0 (sample1.sampletests.test122.TestB)##r## ##r## 5/24 (20.8%) test_y1 (sample1.sampletests.test122.TestB)##r## ##r## 6/24 (25.0%) test_z0 (sample1.sampletests.test122.TestB)##r## ##r## 7/24 (29.2%) test_1 (sample1.sampletests.test122.TestNotMuch)##r## ##r## 8/24 (33.3%) test_2 (sample1.sampletests.test122.TestNotMuch)##r## ##r## 9/24 (37.5%) test_3 (sample1.sampletests.test122.TestNotMuch)##r## ##r## 10/24 (41.7%) test_x0 (sample1.sampletests.test122)##r## ##r## 11/24 (45.8%) test_y0 (sample1.sampletests.test122)##r## ##r## 12/24 (50.0%) test_z1 (sample1.sampletests.test122)##r## ##r## 13/24 (54.2%) test_x1 (sampletests.test122.TestA)##r## ##r## 14/24 (58.3%) test_y0 (sampletests.test122.TestA)##r## ##r## 15/24 (62.5%) test_z0 (sampletests.test122.TestA)##r## ##r## 16/24 (66.7%) test_x0 (sampletests.test122.TestB)##r## ##r## 17/24 (70.8%) test_y1 (sampletests.test122.TestB)##r## ##r## 18/24 (75.0%) test_z0 (sampletests.test122.TestB)##r## ##r## 19/24 (79.2%) test_1 (sampletests.test122.TestNotMuch)##r## ##r## 20/24 (83.3%) test_2 (sampletests.test122.TestNotMuch)##r## ##r## 21/24 (87.5%) test_3 (sampletests.test122.TestNotMuch)##r## ##r## 22/24 (91.7%) test_x0 (sampletests.test122)##r## ##r## 23/24 (95.8%) test_y0 (sampletests.test122)##r## ##r## 24/24 (100.0%) test_z1 (sampletests.test122)##r## ##r## Ran 24 tests with 0 failures and 0 errors in 0.006 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False Note that, in this example, we used a test-selection pattern starting with '!' to exclude tests containing the string "txt". >>> sys.argv = 'test --layer 122 -pvvv -t!(txt|NotMuch)'.split() >>> testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Running: 1/18 (5.6%) test_x1 (sample1.sampletests.test122.TestA) (0.000 s)##r## ##r## 2/18 (11.1%) test_y0 (sample1.sampletests.test122.TestA) (0.000 s)##r## ##r## 3/18 (16.7%) test_z0 (sample1.sampletests.test122.TestA) (0.000 s)##r## ##r## 4/18 (22.2%) test_x0 (sample1.sampletests.test122.TestB) (0.000 s)##r## ##r## 5/18 (27.8%) test_y1 (sample1.sampletests.test122.TestB) (0.000 s)##r## ##r## 6/18 (33.3%) test_z0 (sample1.sampletests.test122.TestB) (0.000 s)##r## ##r## 7/18 (38.9%) test_x0 (sample1.sampletests.test122) (0.001 s)##r## ##r## 8/18 (44.4%) test_y0 (sample1.sampletests.test122) (0.001 s)##r## ##r## 9/18 (50.0%) test_z1 (sample1.sampletests.test122) (0.001 s)##r## ##r## 10/18 (55.6%) test_x1 (sampletests.test122.TestA) (0.000 s)##r## ##r## 11/18 (61.1%) test_y0 (sampletests.test122.TestA) (0.000 s)##r## ##r## 12/18 (66.7%) test_z0 (sampletests.test122.TestA) (0.000 s)##r## ##r## 13/18 (72.2%) test_x0 (sampletests.test122.TestB) (0.000 s)##r## ##r## 14/18 (77.8%) test_y1 (sampletests.test122.TestB) (0.000 s)##r## ##r## 15/18 (83.3%) test_z0 (sampletests.test122.TestB) (0.000 s)##r## ##r## 16/18 (88.9%) test_x0 (sampletests.test122) (0.001 s)##r## ##r## 17/18 (94.4%) test_y0 (sampletests.test122) (0.001 s)##r## ##r## 18/18 (100.0%) test_z1 (sampletests.test122) (0.001 s)##r## ##r## Ran 18 tests with 0 failures and 0 errors in 0.006 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False In this example, we also excluded tests with "NotMuch" in their names. Unfortunately, the time data above doesn't buy us much because, in practice, the line is cleared before there is time to see the times. :/ Autodetecting progress ---------------------- The --auto-progress option will determine if stdout is a terminal, and only enable progress output if so. Let's pretend we have a terminal >>> class Terminal(object): ... def __init__(self, stream): ... self._stream = stream ... def __getattr__(self, attr): ... return getattr(self._stream, attr) ... def isatty(self): ... return True >>> real_stdout = sys.stdout >>> sys.stdout = Terminal(sys.stdout) >>> sys.argv = 'test -u -t test_one.TestNotMuch --auto-progress'.split() >>> testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: 1/6 (16.7%)##r## ##r## 2/6 (33.3%)##r## ##r## 3/6 (50.0%)##r## ##r## 4/6 (66.7%)##r## ##r## 5/6 (83.3%)##r## ##r## 6/6 (100.0%)##r## ##r## Ran 6 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Let's stop pretending >>> sys.stdout = real_stdout >>> sys.argv = 'test -u -t test_one.TestNotMuch --auto-progress'.split() >>> testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 6 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Disabling progress indication ----------------------------- If -p or --progress have been previously provided on the command line (perhaps by a wrapper script) but you do not desire progress indication, you can switch it off with --no-progress: >>> sys.argv = 'test -u -t test_one.TestNotMuch -p --no-progress'.split() >>> testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 6 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/selftest.py0000644000175000017500000000207212214017655025731 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Self-test support. Provides setup routines that enable the test runner to test itself. $Id: __init__.py 86218 2008-05-03 14:17:26Z ctheune $ """ import pdb import zope.testing.testrunner.feature real_pdb_set_trace = pdb.set_trace class SelfTest(zope.testing.testrunner.feature.Feature): active = True def global_setup(self): # Make sure we start with real pdb.set_trace. pdb.set_trace = real_pdb_set_trace zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-errors.txt0000644000175000017500000010364312214017655030020 0ustar arnauarnauErrors and Failures =================== Let's look at tests that have errors and failures, first we need to make a temporary copy of the entire testing directory (except .svn files which may be read only): >>> import os.path, sys, tempfile, shutil >>> tmpdir = tempfile.mkdtemp() >>> directory_with_tests = os.path.join(tmpdir, 'testrunner-ex') >>> source = os.path.join(this_directory, 'testrunner-ex') >>> n = len(source) + 1 >>> for root, dirs, files in os.walk(source): ... dirs[:] = [d for d in dirs if d != ".svn"] # prune cruft ... os.mkdir(os.path.join(directory_with_tests, root[n:])) ... for f in files: ... shutil.copy(os.path.join(root, f), ... os.path.join(directory_with_tests, root[n:], f)) >>> from zope.testing import testrunner >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --tests-pattern ^sampletests(f|_e|_f)?$ '.split() >>> testrunner.run_internal(defaults) ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Running samplelayers.Layer1 tests: ... Running zope.testing.testrunner.layer.UnitTests tests: ... Failure in test eek (sample2.sampletests_e) Failed doctest test for sample2.sampletests_e.eek File "testrunner-ex/sample2/sampletests_e.py", line 28, in eek ---------------------------------------------------------------------- File "testrunner-ex/sample2/sampletests_e.py", line 30, in sample2.sampletests_e.eek Failed example: f() Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? f() File "testrunner-ex/sample2/sampletests_e.py", line 19, in f g() File "testrunner-ex/sample2/sampletests_e.py", line 24, in g x = y + 1 - __traceback_info__: I don't know what Y should be. NameError: global name 'y' is not defined Error in test test3 (sample2.sampletests_e.Test) Traceback (most recent call last): File "testrunner-ex/sample2/sampletests_e.py", line 43, in test3 f() File "testrunner-ex/sample2/sampletests_e.py", line 19, in f g() File "testrunner-ex/sample2/sampletests_e.py", line 24, in g x = y + 1 - __traceback_info__: I don't know what Y should be. NameError: global name 'y' is not defined Failure in test testrunner-ex/sample2/e.txt Failed doctest test for e.txt File "testrunner-ex/sample2/e.txt", line 0 ---------------------------------------------------------------------- File "testrunner-ex/sample2/e.txt", line 4, in e.txt Failed example: f() Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? f() File "", line 2, in f return x NameError: global name 'x' is not defined Failure in test test (sample2.sampletests_f.Test) Traceback (most recent call last): File "testrunner-ex/sample2/sampletests_f.py", line 21, in test self.assertEqual(1,0) File "/usr/local/python/2.3/lib/python2.3/unittest.py", line 302, in failUnlessEqual raise self.failureException, \ AssertionError: 1 != 0 Ran 200 tests with 3 failures and 1 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 413 tests, 3 failures, 1 errors in N.NNN seconds. True We see that we get an error report and a traceback for the failing test. In addition, the test runner returned True, indicating that there was an error. If we ask for verbosity, the dotted output will be interrupted, and there'll be a summary of the errors at the end of the test: >>> sys.argv = 'test --tests-pattern ^sampletests(f|_e|_f)?$ -uv'.split() >>> testrunner.run_internal(defaults) ... # doctest: +NORMALIZE_WHITESPACE +REPORT_NDIFF Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: ... Running: ................................................................................................. Failure in test eek (sample2.sampletests_e) Failed doctest test for sample2.sampletests_e.eek File "testrunner-ex/sample2/sampletests_e.py", line 28, in eek ---------------------------------------------------------------------- File "testrunner-ex/sample2/sampletests_e.py", line 30, in sample2.sampletests_e.eek Failed example: f() Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? f() File "testrunner-ex/sample2/sampletests_e.py", line 19, in f g() File "testrunner-ex/sample2/sampletests_e.py", line 24, in g x = y + 1 - __traceback_info__: I don't know what Y should be. NameError: global name 'y' is not defined ... Error in test test3 (sample2.sampletests_e.Test) Traceback (most recent call last): File "testrunner-ex/sample2/sampletests_e.py", line 43, in test3 f() File "testrunner-ex/sample2/sampletests_e.py", line 19, in f g() File "testrunner-ex/sample2/sampletests_e.py", line 24, in g x = y + 1 - __traceback_info__: I don't know what Y should be. NameError: global name 'y' is not defined ... Failure in test testrunner-ex/sample2/e.txt Failed doctest test for e.txt File "testrunner-ex/sample2/e.txt", line 0 ---------------------------------------------------------------------- File "testrunner-ex/sample2/e.txt", line 4, in e.txt Failed example: f() Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? f() File "", line 2, in f return x NameError: global name 'x' is not defined . Failure in test test (sample2.sampletests_f.Test) Traceback (most recent call last): File "testrunner-ex/sample2/sampletests_f.py", line 21, in test self.assertEqual(1,0) File ".../unittest.py", line 302, in failUnlessEqual raise self.failureException, \ AssertionError: 1 != 0 ................................................................................................ Ran 200 tests with 3 failures and 1 errors in 0.040 seconds. ... Tests with errors: test3 (sample2.sampletests_e.Test) Tests with failures: eek (sample2.sampletests_e) testrunner-ex/sample2/e.txt test (sample2.sampletests_f.Test) True Similarly for progress output, the progress ticker will be interrupted: >>> sys.argv = ('test --tests-pattern ^sampletests(f|_e|_f)?$ -u -ssample2' ... ' -p').split() >>> testrunner.run_internal(defaults) ... # doctest: +NORMALIZE_WHITESPACE +REPORT_NDIFF Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: 1/56 (1.8%) Failure in test eek (sample2.sampletests_e) Failed doctest test for sample2.sampletests_e.eek File "testrunner-ex/sample2/sampletests_e.py", line 28, in eek ---------------------------------------------------------------------- File "testrunner-ex/sample2/sampletests_e.py", line 30, in sample2.sampletests_e.eek Failed example: f() Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? f() File "testrunner-ex/sample2/sampletests_e.py", line 19, in f g() File "testrunner-ex/sample2/sampletests_e.py", line 24, in g x = y + 1 - __traceback_info__: I don't know what Y should be. NameError: global name 'y' is not defined 2/56 (3.6%)##r## ##r## 3/56 (5.4%)##r## ##r## 4/56 (7.1%) Error in test test3 (sample2.sampletests_e.Test) Traceback (most recent call last): File "testrunner-ex/sample2/sampletests_e.py", line 43, in test3 f() File "testrunner-ex/sample2/sampletests_e.py", line 19, in f g() File "testrunner-ex/sample2/sampletests_e.py", line 24, in g x = y + 1 - __traceback_info__: I don't know what Y should be. NameError: global name 'y' is not defined 5/56 (8.9%)##r## ##r## 6/56 (10.7%)##r## ##r## 7/56 (12.5%) Failure in test testrunner-ex/sample2/e.txt Failed doctest test for e.txt File "testrunner-ex/sample2/e.txt", line 0 ---------------------------------------------------------------------- File "testrunner-ex/sample2/e.txt", line 4, in e.txt Failed example: f() Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? f() File "", line 2, in f return x NameError: global name 'x' is not defined 8/56 (14.3%) Failure in test test (sample2.sampletests_f.Test) Traceback (most recent call last): File "testrunner-ex/sample2/sampletests_f.py", line 21, in test self.assertEqual(1,0) File ".../unittest.py", line 302, in failUnlessEqual raise self.failureException, \ AssertionError: 1 != 0 9/56 (16.1%)##r## ##r## 10/56 (17.9%)##r## ##r## 11/56 (19.6%)##r## ##r## 12/56 (21.4%)##r## ##r## 13/56 (23.2%)##r## ##r## 14/56 (25.0%)##r## ##r## 15/56 (26.8%)##r## ##r## 16/56 (28.6%)##r## ##r## 17/56 (30.4%)##r## ##r## 18/56 (32.1%)##r## ##r## 19/56 (33.9%)##r## ##r## 20/56 (35.7%)##r## ##r## 24/56 (42.9%)##r## ##r## 25/56 (44.6%)##r## ##r## 26/56 (46.4%)##r## ##r## 27/56 (48.2%)##r## ##r## 28/56 (50.0%)##r## ##r## 29/56 (51.8%)##r## ##r## 30/56 (53.6%)##r## ##r## 31/56 (55.4%)##r## ##r## 32/56 (57.1%)##r## ##r## 33/56 (58.9%)##r## ##r## 34/56 (60.7%)##r## ##r## 35/56 (62.5%)##r## ##r## 36/56 (64.3%)##r## ##r## 40/56 (71.4%)##r## ##r## 41/56 (73.2%)##r## ##r## 42/56 (75.0%)##r## ##r## 43/56 (76.8%)##r## ##r## 44/56 (78.6%)##r## ##r## 45/56 (80.4%)##r## ##r## 46/56 (82.1%)##r## ##r## 47/56 (83.9%)##r## ##r## 48/56 (85.7%)##r## ##r## 49/56 (87.5%)##r## ##r## 50/56 (89.3%)##r## ##r## 51/56 (91.1%)##r## ##r## 52/56 (92.9%)##r## ##r## 56/56 (100.0%)##r## ##r## Ran 56 tests with 3 failures and 1 errors in 0.054 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. True If you also want a summary of errors at the end, ask for verbosity as well as progress output. Suppressing multiple doctest errors ----------------------------------- Often, when a doctest example fails, the failure will cause later examples in the same test to fail. Each failure is reported: >>> sys.argv = 'test --tests-pattern ^sampletests_1$'.split() >>> testrunner.run_internal(defaults) # doctest: +NORMALIZE_WHITESPACE Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Failure in test eek (sample2.sampletests_1) Failed doctest test for sample2.sampletests_1.eek File "testrunner-ex/sample2/sampletests_1.py", line 17, in eek ---------------------------------------------------------------------- File "testrunner-ex/sample2/sampletests_1.py", line 19, in sample2.sampletests_1.eek Failed example: x = y Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? x = y NameError: name 'y' is not defined ---------------------------------------------------------------------- File "testrunner-ex/sample2/sampletests_1.py", line 21, in sample2.sampletests_1.eek Failed example: x Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? x NameError: name 'x' is not defined ---------------------------------------------------------------------- File "testrunner-ex/sample2/sampletests_1.py", line 24, in sample2.sampletests_1.eek Failed example: z = x + 1 Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? z = x + 1 NameError: name 'x' is not defined Ran 1 tests with 1 failures and 0 errors in 0.002 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. True This can be a bit confusing, especially when there are enough tests that they scroll off a screen. Often you just want to see the first failure. This can be accomplished with the -1 option (for "just show me the first failed example in a doctest" :) >>> sys.argv = 'test --tests-pattern ^sampletests_1$ -1'.split() >>> testrunner.run_internal(defaults) # doctest: Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Failure in test eek (sample2.sampletests_1) Failed doctest test for sample2.sampletests_1.eek File "testrunner-ex/sample2/sampletests_1.py", line 17, in eek ---------------------------------------------------------------------- File "testrunner-ex/sample2/sampletests_1.py", line 19, in sample2.sampletests_1.eek Failed example: x = y Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? x = y NameError: name 'y' is not defined Ran 1 tests with 1 failures and 0 errors in 0.001 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. True The --hide-secondary-failures option is an alias for -1: >>> sys.argv = ( ... 'test --tests-pattern ^sampletests_1$' ... ' --hide-secondary-failures' ... ).split() >>> testrunner.run_internal(defaults) # doctest: Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Failure in test eek (sample2.sampletests_1) Failed doctest test for sample2.sampletests_1.eek File "testrunner-ex/sample2/sampletests_1.py", line 17, in eek ---------------------------------------------------------------------- File "testrunner-ex/sample2/sampletests_1.py", line 19, in sample2.sampletests_1.eek Failed example: x = y Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? x = y NameError: name 'y' is not defined Ran 1 tests with 1 failures and 0 errors in 0.001 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. True The --show-secondary-failures option counters -1 (or it's alias), causing the second and subsequent errors to be shown. This is useful if -1 is provided by a test script by inserting it ahead of command-line options in sys.argv. >>> sys.argv = ( ... 'test --tests-pattern ^sampletests_1$' ... ' --hide-secondary-failures --show-secondary-failures' ... ).split() >>> testrunner.run_internal(defaults) # doctest: +NORMALIZE_WHITESPACE Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Failure in test eek (sample2.sampletests_1) Failed doctest test for sample2.sampletests_1.eek File "testrunner-ex/sample2/sampletests_1.py", line 17, in eek ---------------------------------------------------------------------- File "testrunner-ex/sample2/sampletests_1.py", line 19, in sample2.sampletests_1.eek Failed example: x = y Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? x = y NameError: name 'y' is not defined ---------------------------------------------------------------------- File "testrunner-ex/sample2/sampletests_1.py", line 21, in sample2.sampletests_1.eek Failed example: x Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? x NameError: name 'x' is not defined ---------------------------------------------------------------------- File "testrunner-ex/sample2/sampletests_1.py", line 24, in sample2.sampletests_1.eek Failed example: z = x + 1 Exception raised: Traceback (most recent call last): File ".../doctest/__init__.py", line 1256, in __run compileflags, 1) in test.globs File "", line 1, in ? z = x + 1 NameError: name 'x' is not defined Ran 1 tests with 1 failures and 0 errors in 0.002 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. True Getting diff output for doctest failures ---------------------------------------- If a doctest has large expected and actual output, it can be hard to see differences when expected and actual output differ. The --ndiff, --udiff, and --cdiff options can be used to get diff output of various kinds. >>> sys.argv = 'test --tests-pattern ^pledge$'.split() >>> _ = testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Failure in test pledge (pledge) Failed doctest test for pledge.pledge File "testrunner-ex/pledge.py", line 24, in pledge ---------------------------------------------------------------------- File "testrunner-ex/pledge.py", line 26, in pledge.pledge Failed example: print pledge_template % ('and earthling', 'planet'), Expected: I give my pledge, as an earthling, to save, and faithfully, to defend from waste, the natural resources of my planet. It's soils, minerals, forests, waters, and wildlife. Got: I give my pledge, as and earthling, to save, and faithfully, to defend from waste, the natural resources of my planet. It's soils, minerals, forests, waters, and wildlife. Ran 1 tests with 1 failures and 0 errors in 0.002 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Here, the actual output uses the word "and" rather than the word "an", but it's a bit hard to pick this out. We can use the various diff outputs to see this better. We could modify the test to ask for diff output, but it's easier to use one of the diff options. The --ndiff option requests a diff using Python's ndiff utility. This is the only method that marks differences within lines as well as across lines. For example, if a line of expected output contains digit 1 where actual output contains letter l, a line is inserted with a caret marking the mismatching column positions. >>> sys.argv = 'test --tests-pattern ^pledge$ --ndiff'.split() >>> _ = testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Failure in test pledge (pledge) Failed doctest test for pledge.pledge File "testrunner-ex/pledge.py", line 24, in pledge ---------------------------------------------------------------------- File "testrunner-ex/pledge.py", line 26, in pledge.pledge Failed example: print pledge_template % ('and earthling', 'planet'), Differences (ndiff with -expected +actual): - I give my pledge, as an earthling, + I give my pledge, as and earthling, ? + to save, and faithfully, to defend from waste, the natural resources of my planet. It's soils, minerals, forests, waters, and wildlife. Ran 1 tests with 1 failures and 0 errors in 0.003 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. The -udiff option requests a standard "unified" diff: >>> sys.argv = 'test --tests-pattern ^pledge$ --udiff'.split() >>> _ = testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Failure in test pledge (pledge) Failed doctest test for pledge.pledge File "testrunner-ex/pledge.py", line 24, in pledge ---------------------------------------------------------------------- File "testrunner-ex/pledge.py", line 26, in pledge.pledge Failed example: print pledge_template % ('and earthling', 'planet'), Differences (unified diff with -expected +actual): @@ -1,3 +1,3 @@ -I give my pledge, as an earthling, +I give my pledge, as and earthling, to save, and faithfully, to defend from waste, the natural resources of my planet. Ran 1 tests with 1 failures and 0 errors in 0.002 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. The -cdiff option requests a standard "context" diff: >>> sys.argv = 'test --tests-pattern ^pledge$ --cdiff'.split() >>> _ = testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Failure in test pledge (pledge) Failed doctest test for pledge.pledge File "testrunner-ex/pledge.py", line 24, in pledge ---------------------------------------------------------------------- File "testrunner-ex/pledge.py", line 26, in pledge.pledge Failed example: print pledge_template % ('and earthling', 'planet'), Differences (context diff with expected followed by actual): *************** *** 1,3 **** ! I give my pledge, as an earthling, to save, and faithfully, to defend from waste, the natural resources of my planet. --- 1,3 ---- ! I give my pledge, as and earthling, to save, and faithfully, to defend from waste, the natural resources of my planet. Ran 1 tests with 1 failures and 0 errors in 0.002 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Specifying more than one diff option at once causes an error: >>> sys.argv = 'test --tests-pattern ^pledge$ --cdiff --udiff'.split() >>> _ = testrunner.run_internal(defaults) Traceback (most recent call last): ... SystemExit: 1 >>> sys.argv = 'test --tests-pattern ^pledge$ --cdiff --ndiff'.split() >>> _ = testrunner.run_internal(defaults) Traceback (most recent call last): ... SystemExit: 1 >>> sys.argv = 'test --tests-pattern ^pledge$ --udiff --ndiff'.split() >>> _ = testrunner.run_internal(defaults) Traceback (most recent call last): ... SystemExit: 1 Testing-Module Import Errors ---------------------------- If there are errors when importing a test module, these errors are reported. In order to illustrate a module with a syntax error, we create one now: this module used to be checked in to the project, but then it was included in distributions of projects using zope.testing too, and distutils complained about the syntax error when it compiled Python files during installation of such projects. So first we create a module with bad syntax: >>> badsyntax_path = os.path.join(directory_with_tests, ... "sample2", "sampletests_i.py") >>> f = open(badsyntax_path, "w") >>> print >> f, "importx unittest" # syntax error >>> f.close() Then run the tests: >>> sys.argv = ('test --tests-pattern ^sampletests(f|_i)?$ --layer 1 ' ... ).split() >>> testrunner.run_internal(defaults) ... # doctest: +NORMALIZE_WHITESPACE Test-module import failures: Module: sample2.sampletests_i Traceback (most recent call last): File "testrunner-ex/sample2/sampletests_i.py", line 1 importx unittest ^ SyntaxError: invalid syntax Module: sample2.sample21.sampletests_i Traceback (most recent call last): File "testrunner-ex/sample2/sample21/sampletests_i.py", line 15, in ? import zope.testing.huh ImportError: No module named huh Module: sample2.sample23.sampletests_i Traceback (most recent call last): File "testrunner-ex/sample2/sample23/sampletests_i.py", line 18, in ? class Test(unittest.TestCase): File "testrunner-ex/sample2/sample23/sampletests_i.py", line 23, in Test raise TypeError('eek') TypeError: eek Running samplelayers.Layer1 tests: Set up samplelayers.Layer1 in 0.000 seconds. Ran 9 tests with 0 failures and 0 errors in 0.000 seconds. Running samplelayers.Layer11 tests: Set up samplelayers.Layer11 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.007 seconds. Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in 0.000 seconds. Set up samplelayers.Layer111 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.007 seconds. Running samplelayers.Layer112 tests: Tear down samplelayers.Layer111 in 0.000 seconds. Set up samplelayers.Layer112 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.007 seconds. Running samplelayers.Layer12 tests: Tear down samplelayers.Layer112 in 0.000 seconds. Tear down samplelayers.Layerx in 0.000 seconds. Tear down samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.007 seconds. Running samplelayers.Layer121 tests: Set up samplelayers.Layer121 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.007 seconds. Running samplelayers.Layer122 tests: Tear down samplelayers.Layer121 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.006 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. Test-modules with import problems: sample2.sampletests_i sample2.sample21.sampletests_i sample2.sample23.sampletests_i Total: 213 tests, 0 failures, 0 errors in N.NNN seconds. True Unicode Errors -------------- There was a bug preventing decent error reporting when a result contained unicode and another not: >>> sys.argv = 'test --tests-pattern ^unicode$ -u'.split() >>> testrunner.run_internal(defaults) # doctest: +REPORT_NDIFF Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Failure testrunner-ex/unicode.txt Failed doctest test for unicode.txt testrunner-ex/unicode.txt", line 0 ---------------------------------------------------------------------- File testrunner-ex/unicode.txt", Line NNN, in unicode.txt Failed example: print get_unicode() Expected: oink Got: foo — bar ---------------------------------------------------------------------- File testrunner-ex/unicode.txt", Line NNN, in unicode.txt Failed example: 'xyz' Expected: 123 Got: 'xyz' Ran 3 tests with 1 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. True Reporting Errors to Calling Processes ------------------------------------- The testrunner returns the error status, indicating that the tests failed. This can be useful for an invoking process that wants to monitor the result of a test run. This is applied when invoking the testrunner using the ``run()`` function instead of ``run_internal()``: >>> sys.argv = ( ... 'test --tests-pattern ^sampletests_1$'.split()) >>> try: ... testrunner.run(defaults) ... except SystemExit, e: ... print 'exited with code', e.code ... else: ... print 'sys.exit was not called' ... # doctest: +ELLIPSIS Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. ... Ran 1 tests with 1 failures and 0 errors in 0.002 seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. exited with code 1 Passing tests exit with code 0 according to UNIX practices: >>> sys.argv = ( ... 'test --tests-pattern ^sampletests$'.split()) >>> try: ... testrunner.run(defaults) ... except SystemExit, e2: ... print 'exited with code', e2.code ... else: ... print 'sys.exit was not called' ... # doctest: +ELLIPSIS Running samplelayers.Layer11 tests: ... Running zope.testing.testrunner.layer.UnitTests tests: Tear down samplelayers.Layer122 in N.NNN seconds. Tear down samplelayers.Layer12 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Ran 160 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 364 tests, 0 failures, 0 errors in N.NNN seconds. exited with code 0 And remove the temporary directory: >>> shutil.rmtree(tmpdir) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/profiling.py0000644000175000017500000001146012214017655026072 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Profiler support for the test runner $Id: __init__.py 86218 2008-05-03 14:17:26Z ctheune $ """ import os import glob import sys import tempfile import zope.testing.testrunner.feature available_profilers = {} try: import cProfile import pstats except ImportError: pass else: class CProfiler(object): """cProfiler""" def __init__(self, filepath): self.filepath = filepath self.profiler = cProfile.Profile() self.enable = self.profiler.enable self.disable = self.profiler.disable def finish(self): self.profiler.dump_stats(self.filepath) def loadStats(self, prof_glob): stats = None for file_name in glob.glob(prof_glob): if stats is None: stats = pstats.Stats(file_name) else: stats.add(file_name) return stats available_profilers['cProfile'] = CProfiler # some Linux distributions don't include the profiler, which hotshot uses if not sys.hexversion >= 0x02060000: # Hotshot is not maintained any longer in 2.6. It does not support # merging to hotshot files. Thus we won't use it in python2.6 and # onwards try: import hotshot import hotshot.stats except ImportError: pass else: class HotshotProfiler(object): """hotshot interface""" def __init__(self, filepath): self.profiler = hotshot.Profile(filepath) self.enable = self.profiler.start self.disable = self.profiler.stop def finish(self): self.profiler.close() def loadStats(self, prof_glob): stats = None for file_name in glob.glob(prof_glob): loaded = hotshot.stats.load(file_name) if stats is None: stats = loaded else: stats.add(loaded) return stats available_profilers['hotshot'] = HotshotProfiler class Profiling(zope.testing.testrunner.feature.Feature): def __init__(self, runner): super(Profiling, self).__init__(runner) if (self.runner.options.profile and sys.version_info[:3] <= (2,4,1) and __debug__): self.runner.options.output.error( 'Because of a bug in Python < 2.4.1, profiling ' 'during tests requires the -O option be passed to ' 'Python (not the test runner).') sys.exit() self.active = bool(self.runner.options.profile) self.profiler = self.runner.options.profile def global_setup(self): self.prof_prefix = 'tests_profile.' self.prof_suffix = '.prof' self.prof_glob = self.prof_prefix + '*' + self.prof_suffix # if we are going to be profiling, and this isn't a subprocess, # clean up any stale results files if not self.runner.options.resume_layer: for file_name in glob.glob(self.prof_glob): os.unlink(file_name) # set up the output file self.oshandle, self.file_path = tempfile.mkstemp(self.prof_suffix, self.prof_prefix, '.') self.profiler = available_profilers[self.runner.options.profile](self.file_path) # Need to do this rebinding to support the stack-frame annoyance with # hotshot. self.late_setup = self.profiler.enable self.early_teardown = self.profiler.disable def global_teardown(self): self.profiler.finish() # We must explicitly close the handle mkstemp returned, else on # Windows this dies the next time around just above due to an # attempt to unlink a still-open file. os.close(self.oshandle) if not self.runner.options.resume_layer: self.profiler_stats = self.profiler.loadStats(self.prof_glob) self.profiler_stats.sort_stats('cumulative', 'calls') def report(self): if not self.runner.options.resume_layer: self.runner.options.output.profiler_stats(self.profiler_stats) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-knit.txt0000644000175000017500000001156212214017655027447 0ustar arnauarnauKnitting in extra package directories ===================================== Python packages have __path__ variables that can be manipulated to add extra directories cntaining software used in the packages. The testrunner needs to be given extra information about this sort of situation. Let's look at an example. The testrunner-ex-knit-lib directory is a directory that we want to add to the Python path, but that we don't want to search for tests. It has a sample4 package and a products subpackage. The products subpackage adds the testrunner-ex-knit-products to it's __path__. We want to run tests from the testrunner-ex-knit-products directory. When we import these tests, we need to import them from the sample4.products package. We can't use the --path option to name testrunner-ex-knit-products. It isn't enough to add the containing directory to the test path because then we wouldn't be able to determine the package name properly. We might be able to use the --package option to run the tests from the sample4/products package, but we want to run tests in testrunner-ex that aren't in this package. We can use the --package-path option in this case. The --package-path option is like the --test-path option in that it defines a path to be searched for tests without affecting the python path. It differs in that it supplied a package name that is added a profex when importing any modules found. The --package-path option takes *two* arguments, a package name and file path. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> sys.path.append(os.path.join(this_directory, 'testrunner-ex-pp-lib')) >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... '--package-path', ... os.path.join(this_directory, 'testrunner-ex-pp-products'), ... 'sample4.products', ... ] >>> from zope.testing import testrunner >>> sys.argv = 'test --layer Layer111 -vv'.split() >>> _ = testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in 0.000 seconds. Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer111 in 0.000 seconds. Running: test_x1 (sample1.sampletests.test111.TestA) test_y0 (sample1.sampletests.test111.TestA) ... test_y0 (sampletests.test111) test_z1 (sampletests.test111) testrunner-ex/sampletests/../sampletestsl.txt test_extra_test_in_products (sample4.products.sampletests.Test) test_another_test_in_products (sample4.products.more.sampletests.Test) Ran 36 tests with 0 failures and 0 errors in 0.008 seconds. Tearing down left over layers: Tear down samplelayers.Layer111 in 0.000 seconds. Tear down samplelayers.Layerx in 0.000 seconds. Tear down samplelayers.Layer11 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. In the example, the last test, test_extra_test_in_products, came from the products directory. As usual, we can select the knit-in packages or individual packages within knit-in packages: >>> sys.argv = 'test --package sample4.products -vv'.split() >>> _ = testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in 0.000 seconds. Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer111 in 0.000 seconds. Running: test_extra_test_in_products (sample4.products.sampletests.Test) test_another_test_in_products (sample4.products.more.sampletests.Test) Ran 2 tests with 0 failures and 0 errors in 0.000 seconds. Tearing down left over layers: Tear down samplelayers.Layer111 in 0.000 seconds. Tear down samplelayers.Layerx in 0.000 seconds. Tear down samplelayers.Layer11 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. >>> sys.argv = 'test --package sample4.products.more -vv'.split() >>> _ = testrunner.run_internal(defaults) Running tests at level 1 Running samplelayers.Layer111 tests: Set up samplelayers.Layerx in 0.000 seconds. Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer111 in 0.000 seconds. Running: test_another_test_in_products (sample4.products.more.sampletests.Test) Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. Tearing down left over layers: Tear down samplelayers.Layer111 in 0.000 seconds. Tear down samplelayers.Layerx in 0.000 seconds. Tear down samplelayers.Layer11 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-subunit.txt0000644000175000017500000006406212214017655030176 0ustar arnauarnauSubunit Output ============== Subunit is a streaming protocol for interchanging test results. More information can be found at https://launchpad.net/subunit. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> from zope.testing import testrunner Basic output ------------ Subunit output is line-based, with a 'test:' line for the start of each test and a 'successful:' line for each successful test. Zope layer set up and tear down events are represented as tests tagged with 'zope:layer'. This allows them to be distinguished from actual tests, provides a place for the layer timing information in the subunit stream and allows us to include error information if necessary. Once the layer is set up, all future tests are tagged with 'zope:layer:LAYER_NAME'. >>> sys.argv = 'test --layer 122 --subunit -t TestNotMuch'.split() >>> testrunner.run_internal(defaults) time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer1:setUp tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer1:setUp tags: zope:layer:samplelayers.Layer1 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer12:setUp tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer12:setUp tags: zope:layer:samplelayers.Layer12 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer122:setUp tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer122:setUp tags: zope:layer:samplelayers.Layer122 test: sample1.sampletests.test122.TestNotMuch.test_1 successful: sample1.sampletests.test122.TestNotMuch.test_1 test: sample1.sampletests.test122.TestNotMuch.test_2 successful: sample1.sampletests.test122.TestNotMuch.test_2 test: sample1.sampletests.test122.TestNotMuch.test_3 successful: sample1.sampletests.test122.TestNotMuch.test_3 test: sampletests.test122.TestNotMuch.test_1 successful: sampletests.test122.TestNotMuch.test_1 test: sampletests.test122.TestNotMuch.test_2 successful: sampletests.test122.TestNotMuch.test_2 test: sampletests.test122.TestNotMuch.test_3 successful: sampletests.test122.TestNotMuch.test_3 tags: -zope:layer:samplelayers.Layer122 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer122:tearDown tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer122:tearDown tags: -zope:layer:samplelayers.Layer12 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer12:tearDown tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer12:tearDown tags: -zope:layer:samplelayers.Layer1 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer1:tearDown tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer1:tearDown False Timing tests ------------ When verbosity is high enough, the subunit stream includes timing information for the actual tests, as well as for the layers. >>> sys.argv = 'test --layer 122 -vvv --subunit -t TestNotMuch'.split() >>> testrunner.run_internal(defaults) time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer1:setUp tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer1:setUp tags: zope:layer:samplelayers.Layer1 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer12:setUp tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer12:setUp tags: zope:layer:samplelayers.Layer12 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer122:setUp tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer122:setUp tags: zope:layer:samplelayers.Layer122 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sample1.sampletests.test122.TestNotMuch.test_1 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: sample1.sampletests.test122.TestNotMuch.test_1 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sample1.sampletests.test122.TestNotMuch.test_2 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: sample1.sampletests.test122.TestNotMuch.test_2 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sample1.sampletests.test122.TestNotMuch.test_3 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: sample1.sampletests.test122.TestNotMuch.test_3 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sampletests.test122.TestNotMuch.test_1 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: sampletests.test122.TestNotMuch.test_1 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sampletests.test122.TestNotMuch.test_2 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: sampletests.test122.TestNotMuch.test_2 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sampletests.test122.TestNotMuch.test_3 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: sampletests.test122.TestNotMuch.test_3 tags: -zope:layer:samplelayers.Layer122 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer122:tearDown tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer122:tearDown tags: -zope:layer:samplelayers.Layer12 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer12:tearDown tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer12:tearDown tags: -zope:layer:samplelayers.Layer1 time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer1:tearDown tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer1:tearDown False Listing tests ------------- A subunit stream is a stream of test results, more or less, so the most natural way of listing tests in subunit is to simply emit successful test results without actually running the tests. Note that in this stream, we don't emit fake tests for the layer set up and tear down, because it simply doesn't happen. We also don't include the dependent layers in the stream (in this case Layer1 and Layer12), since they are not provided to the reporter. >>> sys.argv = ( ... 'test --layer 122 --list-tests --subunit -t TestNotMuch').split() >>> testrunner.run_internal(defaults) tags: zope:layer:samplelayers.Layer122 test: sample1.sampletests.test122.TestNotMuch.test_1 successful: sample1.sampletests.test122.TestNotMuch.test_1 test: sample1.sampletests.test122.TestNotMuch.test_2 successful: sample1.sampletests.test122.TestNotMuch.test_2 test: sample1.sampletests.test122.TestNotMuch.test_3 successful: sample1.sampletests.test122.TestNotMuch.test_3 test: sampletests.test122.TestNotMuch.test_1 successful: sampletests.test122.TestNotMuch.test_1 test: sampletests.test122.TestNotMuch.test_2 successful: sampletests.test122.TestNotMuch.test_2 test: sampletests.test122.TestNotMuch.test_3 successful: sampletests.test122.TestNotMuch.test_3 tags: -zope:layer:samplelayers.Layer122 False Profiling tests --------------- Test suites often cover a lot of code, and the performance of test suites themselves is often a critical part of the development process. Thus, it's good to be able to profile a test run. >>> sys.argv = ( ... 'test --layer 122 --profile=cProfile --subunit ' ... '-t TestNotMuch').split() >>> testrunner.run_internal(defaults) time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer1:setUp ... time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: samplelayers.Layer1:tearDown test: zope:profiler_stats tags: zope:profiler_stats successful: zope:profiler_stats [ multipart Content-Type: application/x-binary-profile profiler-stats ...\r ... ] False Errors ------ Errors are recorded in the subunit stream as MIME-encoded chunks of text. >>> sys.argv = [ ... 'test', '--subunit' , '--tests-pattern', '^sampletests_e$', ... ] >>> testrunner.run_internal(defaults) time: 2010-02-05 15:27:05.113541Z test: zope.testing.testrunner.layer.UnitTests:setUp tags: zope:layer time: 2010-02-05 15:27:05.113545Z successful: zope.testing.testrunner.layer.UnitTests:setUp tags: zope:layer:zope.testing.testrunner.layer.UnitTests test: sample2.sampletests_e.eek failure: sample2.sampletests_e.eek [ multipart Content-Type: text/x-traceback;charset=utf8,language=python traceback 4B6\r Failed doctest test for sample2.sampletests_e.eek File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_e.py", line 29, in eek ---------------------------------------------------------------------- File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_e.py", line 31, in sample2.sampletests_e.eek Failed example: f() Exception raised: Traceback (most recent call last): File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/doctest/__init__.py", line 1355, in __run compileflags, 1) in test.globs File "", line 1, in f() File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_e.py", line 19, in f g() File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_e.py", line 25, in g x = y + 1 - __traceback_info__: I don't know what Y should be. NameError: global name 'y' is not defined 0\r ] test: sample2.sampletests_e.Test.test1 successful: sample2.sampletests_e.Test.test1 test: sample2.sampletests_e.Test.test2 successful: sample2.sampletests_e.Test.test2 test: sample2.sampletests_e.Test.test3 error: sample2.sampletests_e.Test.test3 [ multipart Content-Type: text/x-traceback;charset=utf8,language=python traceback 29F\r Traceback (most recent call last): File "/usr/lib/python2.6/unittest.py", line 279, in run testMethod() File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_e.py", line 44, in test3 f() File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_e.py", line 19, in f g() File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample2/sampletests_e.py", line 25, in g x = y + 1 - __traceback_info__: I don't know what Y should be. NameError: global name 'y' is not defined 0\r ] test: sample2.sampletests_e.Test.test4 successful: sample2.sampletests_e.Test.test4 test: sample2.sampletests_e.Test.test5 successful: sample2.sampletests_e.Test.test5 test: e_txt failure: e_txt [ multipart Content-Type: text/x-traceback;charset=utf8,language=python traceback 329\r Failed doctest test for e.txt File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample2/e.txt", line 0 ---------------------------------------------------------------------- File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample2/e.txt", line 4, in e.txt Failed example: f() Exception raised: Traceback (most recent call last): File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/doctest/__init__.py", line 1355, in __run compileflags, 1) in test.globs File "", line 1, in f() File "", line 2, in f return x NameError: global name 'x' is not defined 0\r ] tags: -zope:layer:zope.testing.testrunner.layer.UnitTests time: 2010-02-05 15:27:05.147082Z test: zope.testing.testrunner.layer.UnitTests:tearDown tags: zope:layer time: 2010-02-05 15:27:05.147088Z successful: zope.testing.testrunner.layer.UnitTests:tearDown True Layers that can't be torn down ------------------------------ A layer can have a tearDown method that raises NotImplementedError. If this is the case and there are no remaining tests to run, the subunit stream will say that the layer skipped its tearDown. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> from zope.testing import testrunner >>> defaults = [ ... '--subunit', ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test -ssample2 --tests-pattern sampletests_ntd$'.split() >>> testrunner.run_internal(defaults) time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sample2.sampletests_ntd.Layer:setUp tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: sample2.sampletests_ntd.Layer:setUp tags: zope:layer:sample2.sampletests_ntd.Layer test: sample2.sampletests_ntd.TestSomething.test_something successful: sample2.sampletests_ntd.TestSomething.test_something tags: -zope:layer:sample2.sampletests_ntd.Layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sample2.sampletests_ntd.Layer:tearDown tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ skip: sample2.sampletests_ntd.Layer:tearDown [ tearDown not supported ] False Module import errors -------------------- We report module import errors too. They get encoded as tests with errors. The name of the test is the module that could not be imported, the test's result is an error containing the traceback. These "tests" are tagged with zope:import_error. Let's create a module with some bad syntax: >>> badsyntax_path = os.path.join(directory_with_tests, ... "sample2", "sampletests_i.py") >>> f = open(badsyntax_path, "w") >>> print >> f, "importx unittest" # syntax error >>> f.close() And then run the tests: >>> sys.argv = ( ... 'test --subunit --tests-pattern ^sampletests(f|_i)?$ --layer 1 ' ... ).split() >>> testrunner.run_internal(defaults) test: sample2.sampletests_i tags: zope:import_error error: sample2.sampletests_i [ Traceback (most recent call last): testrunner-ex/sample2/sampletests_i.py", line 1 importx unittest ^ SyntaxError: invalid syntax ] test: sample2.sample21.sampletests_i tags: zope:import_error error: sample2.sample21.sampletests_i [ Traceback (most recent call last): testrunner-ex/sample2/sample21/sampletests_i.py", Line NNN, in ? import zope.testing.huh ImportError: No module named huh ] test: sample2.sample23.sampletests_i tags: zope:import_error error: sample2.sample23.sampletests_i [ Traceback (most recent call last): testrunner-ex/sample2/sample23/sampletests_i.py", Line NNN, in ? class Test(unittest.TestCase): testrunner-ex/sample2/sample23/sampletests_i.py", Line NNN, in Test raise TypeError('eek') TypeError: eek ] time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: samplelayers.Layer1:setUp tags: zope:layer ... True Of course, because we care deeply about test isolation, we're going to have to delete the module with bad syntax now, lest it contaminate other tests or even future test runs. >>> os.unlink(badsyntax_path) Tests in subprocesses --------------------- If the tearDown method raises NotImplementedError and there are remaining layers to run, the test runner will restart itself as a new process, resuming tests where it left off: >>> sys.argv = [testrunner_script, '--tests-pattern', 'sampletests_ntd$'] >>> testrunner.run_internal(defaults) time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sample1.sampletests_ntd.Layer:setUp tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: sample1.sampletests_ntd.Layer:setUp tags: zope:layer:sample1.sampletests_ntd.Layer test: sample1.sampletests_ntd.TestSomething.test_something successful: sample1.sampletests_ntd.TestSomething.test_something tags: -zope:layer:sample1.sampletests_ntd.Layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sample1.sampletests_ntd.Layer:tearDown tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ skip: sample1.sampletests_ntd.Layer:tearDown [ tearDown not supported ] test: Running in a subprocess. tags: zope:info_suboptimal successful: Running in a subprocess. time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sample2.sampletests_ntd.Layer:setUp tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: sample2.sampletests_ntd.Layer:setUp tags: zope:layer:sample2.sampletests_ntd.Layer test: sample2.sampletests_ntd.TestSomething.test_something successful: sample2.sampletests_ntd.TestSomething.test_something tags: -zope:layer:sample2.sampletests_ntd.Layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sample2.sampletests_ntd.Layer:tearDown tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ skip: sample2.sampletests_ntd.Layer:tearDown [ tearDown not supported ] test: Running in a subprocess. tags: zope:info_suboptimal successful: Running in a subprocess. time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sample3.sampletests_ntd.Layer:setUp tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ successful: sample3.sampletests_ntd.Layer:setUp tags: zope:layer:sample3.sampletests_ntd.Layer test: sample3.sampletests_ntd.TestSomething.test_error1 error: sample3.sampletests_ntd.TestSomething.test_error1 [ multipart Content-Type: text/x-traceback;charset=utf8,language=python traceback 14F\r Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_error1 raise TypeError("Can we see errors") TypeError: Can we see errors 0\r ] test: sample3.sampletests_ntd.TestSomething.test_error2 error: sample3.sampletests_ntd.TestSomething.test_error2 [ multipart Content-Type: text/x-traceback;charset=utf8,language=python traceback 13F\r Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_error2 raise TypeError("I hope so") TypeError: I hope so 0\r ] test: sample3.sampletests_ntd.TestSomething.test_fail1 failure: sample3.sampletests_ntd.TestSomething.test_fail1 [ multipart Content-Type: text/x-traceback;charset=utf8,language=python traceback 1AA\r Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_fail1 self.assertEqual(1, 2) AssertionError: 1 != 2 0\r ] test: sample3.sampletests_ntd.TestSomething.test_fail2 failure: sample3.sampletests_ntd.TestSomething.test_fail2 [ multipart Content-Type: text/x-traceback;charset=utf8,language=python traceback 1AA\r Traceback (most recent call last): testrunner-ex/sample3/sampletests_ntd.py", Line NNN, in test_fail2 self.assertEqual(1, 3) AssertionError: 1 != 3 0\r ] test: sample3.sampletests_ntd.TestSomething.test_something successful: sample3.sampletests_ntd.TestSomething.test_something test: sample3.sampletests_ntd.TestSomething.test_something_else successful: sample3.sampletests_ntd.TestSomething.test_something_else tags: -zope:layer:sample3.sampletests_ntd.Layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ test: sample3.sampletests_ntd.Layer:tearDown tags: zope:layer time: YYYY-MM-DD HH:MM:SS.mmmmmmZ skip: sample3.sampletests_ntd.Layer:tearDown [ tearDown not supported ] True Note that debugging doesn't work when running tests in a subprocess: >>> sys.argv = [testrunner_script, '--tests-pattern', 'sampletests_ntd$', ... '-D', ] >>> testrunner.run_internal(defaults) time: 2010-02-10 22:41:25.279692Z test: sample1.sampletests_ntd.Layer:setUp tags: zope:layer time: 2010-02-10 22:41:25.279695Z successful: sample1.sampletests_ntd.Layer:setUp tags: zope:layer:sample1.sampletests_ntd.Layer test: sample1.sampletests_ntd.TestSomething.test_something successful: sample1.sampletests_ntd.TestSomething.test_something tags: -zope:layer:sample1.sampletests_ntd.Layer time: 2010-02-10 22:41:25.310078Z test: sample1.sampletests_ntd.Layer:tearDown tags: zope:layer time: 2010-02-10 22:41:25.310171Z skip: sample1.sampletests_ntd.Layer:tearDown [ tearDown not supported ] test: Running in a subprocess. tags: zope:info_suboptimal successful: Running in a subprocess. time: 2010-02-10 22:41:25.753076Z test: sample2.sampletests_ntd.Layer:setUp tags: zope:layer time: 2010-02-10 22:41:25.753079Z successful: sample2.sampletests_ntd.Layer:setUp tags: zope:layer:sample2.sampletests_ntd.Layer test: sample2.sampletests_ntd.TestSomething.test_something successful: sample2.sampletests_ntd.TestSomething.test_something tags: -zope:layer:sample2.sampletests_ntd.Layer time: 2010-02-10 22:41:25.779256Z test: sample2.sampletests_ntd.Layer:tearDown tags: zope:layer time: 2010-02-10 22:41:25.779326Z skip: sample2.sampletests_ntd.Layer:tearDown [ tearDown not supported ] test: Running in a subprocess. tags: zope:info_suboptimal successful: Running in a subprocess. time: 2010-02-10 22:41:26.310296Z test: sample3.sampletests_ntd.Layer:setUp tags: zope:layer time: 2010-02-10 22:41:26.310299Z successful: sample3.sampletests_ntd.Layer:setUp tags: zope:layer:sample3.sampletests_ntd.Layer test: sample3.sampletests_ntd.TestSomething.test_error1 error: sample3.sampletests_ntd.TestSomething.test_error1 [ multipart Content-Type: text/x-traceback;charset=utf8,language=python traceback 16A\r Traceback (most recent call last): File "/usr/lib/python2.6/unittest.py", line 305, in debug getattr(self, self._testMethodName)() File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.py", line 42, in test_error1 raise TypeError("Can we see errors") TypeError: Can we see errors 0\r ] test: Can't post-mortem debug when running a layer as a subprocess! tags: zope:error_with_banner successful: Can't post-mortem debug when running a layer as a subprocess! test: sample3.sampletests_ntd.TestSomething.test_error2 error: sample3.sampletests_ntd.TestSomething.test_error2 [ multipart Content-Type: text/x-traceback;charset=utf8,language=python traceback 15A\r Traceback (most recent call last): File "/usr/lib/python2.6/unittest.py", line 305, in debug getattr(self, self._testMethodName)() File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.py", line 45, in test_error2 raise TypeError("I hope so") TypeError: I hope so 0\r ] test: Can't post-mortem debug when running a layer as a subprocess! tags: zope:error_with_banner successful: Can't post-mortem debug when running a layer as a subprocess! test: sample3.sampletests_ntd.TestSomething.test_fail1 error: sample3.sampletests_ntd.TestSomething.test_fail1 [ multipart Content-Type: text/x-traceback;charset=utf8,language=python traceback 1C5\r Traceback (most recent call last): File "/usr/lib/python2.6/unittest.py", line 305, in debug getattr(self, self._testMethodName)() File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.py", line 48, in test_fail1 self.assertEqual(1, 2) File "/usr/lib/python2.6/unittest.py", line 350, in failUnlessEqual (msg or '%r != %r' % (first, second)) AssertionError: 1 != 2 0\r ] test: Can't post-mortem debug when running a layer as a subprocess! tags: zope:error_with_banner successful: Can't post-mortem debug when running a layer as a subprocess! test: sample3.sampletests_ntd.TestSomething.test_fail2 error: sample3.sampletests_ntd.TestSomething.test_fail2 [ multipart Content-Type: text/x-traceback;charset=utf8,language=python traceback 1C5\r Traceback (most recent call last): File "/usr/lib/python2.6/unittest.py", line 305, in debug getattr(self, self._testMethodName)() File "/home/jml/src/zope.testing/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.py", line 51, in test_fail2 self.assertEqual(1, 3) File "/usr/lib/python2.6/unittest.py", line 350, in failUnlessEqual (msg or '%r != %r' % (first, second)) AssertionError: 1 != 3 0\r ] test: Can't post-mortem debug when running a layer as a subprocess! tags: zope:error_with_banner successful: Can't post-mortem debug when running a layer as a subprocess! test: sample3.sampletests_ntd.TestSomething.test_something successful: sample3.sampletests_ntd.TestSomething.test_something test: sample3.sampletests_ntd.TestSomething.test_something_else successful: sample3.sampletests_ntd.TestSomething.test_something_else tags: -zope:layer:sample3.sampletests_ntd.Layer time: 2010-02-10 22:41:26.340878Z test: sample3.sampletests_ntd.Layer:tearDown tags: zope:layer time: 2010-02-10 22:41:26.340945Z skip: sample3.sampletests_ntd.Layer:tearDown [ tearDown not supported ] True ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-debugging-layer-setup.testzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-debugging-layer-setup.te0000644000175000017500000000724012214017655032474 0ustar arnauarnauPost-mortem debugging also works when there is a failure in layer setup. >>> import os, shutil, sys, tempfile >>> tdir = tempfile.mkdtemp() >>> dir = os.path.join(tdir, 'TESTS-DIR') >>> os.mkdir(dir) >>> open(os.path.join(dir, 'tests.py'), 'w').write( ... ''' ... import doctest ... ... class Layer: ... @classmethod ... def setUp(self): ... x = 1 ... raise ValueError ... ... def a_test(): ... """ ... >>> None ... """ ... def test_suite(): ... suite = doctest.DocTestSuite() ... suite.layer = Layer ... return suite ... ... ''') >>> class Input: ... def __init__(self, src): ... self.lines = src.split('\n') ... def readline(self): ... line = self.lines.pop(0) ... print line ... return line+'\n' >>> real_stdin = sys.stdin >>> if sys.version_info[:2] == (2, 3): ... sys.stdin = Input('n\np x\nc') ... else: ... sys.stdin = Input('p x\nc') >>> sys.argv = [testrunner_script] >>> import zope.testing.testrunner >>> try: ... zope.testing.testrunner.run_internal(['--path', dir, '-D']) ... finally: sys.stdin = real_stdin ... # doctest: +ELLIPSIS Running tests.Layer tests: Set up tests.Layer exceptions.ValueError: > ...tests.py(8)setUp() -> raise ValueError (Pdb) p x 1 (Pdb) c True Note that post-mortem debugging doesn't work when the layer is run in a subprocess: >>> if sys.version_info[:2] == (2, 3): ... sys.stdin = Input('n\np x\nc') ... else: ... sys.stdin = Input('p x\nc') >>> open(os.path.join(dir, 'tests2.py'), 'w').write( ... ''' ... import doctest, unittest ... ... class Layer1: ... @classmethod ... def setUp(self): ... pass ... ... @classmethod ... def tearDown(self): ... raise NotImplementedError ... ... class Layer2: ... @classmethod ... def setUp(self): ... x = 1 ... raise ValueError ... ... def a_test(): ... """ ... >>> None ... """ ... def test_suite(): ... suite1 = doctest.DocTestSuite() ... suite1.layer = Layer1 ... suite2 = doctest.DocTestSuite() ... suite2.layer = Layer2 ... return unittest.TestSuite((suite1, suite2)) ... ... ''') >>> import sys >>> try: ... zope.testing.testrunner.run_internal( ... ['--path', dir, '-Dvv', '--tests-pattern', 'tests2']) ... finally: sys.stdin = real_stdin ... # doctest: +ELLIPSIS +REPORT_NDIFF Running tests at level 1 Running tests2.Layer1 tests: Set up tests2.Layer1 in 0.000 seconds. Running: a_test (tests2) Ran 1 tests with 0 failures and 0 errors in 0.001 seconds. Running tests2.Layer2 tests: Tear down tests2.Layer1 ... not supported Running in a subprocess. Set up tests2.Layer2 ********************************************************************** Can't post-mortem debug when running a layer as a subprocess! Try running layer 'tests2.Layer2' by itself. ********************************************************************** Traceback (most recent call last): ... raise ValueError ValueError Tests with errors: runTest (zope.testing.testrunner.runner.SetUpLayerFailure) Total: 1 tests, 0 failures, 1 errors in 0.210 seconds. True >>> shutil.rmtree(tdir) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/_doctest.py0000644000175000017500000000355412214017655025712 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Doc test support for the test runner. $Id: __init__.py 86232 2008-05-03 15:09:33Z ctheune $ """ import sys import doctest import zope.testing.testrunner.feature class DocTest(zope.testing.testrunner.feature.Feature): active = True def global_setup(self): options = self.runner.options output = options.output self.old_reporting_flags = doctest.set_unittest_reportflags(0) reporting_flags = 0 if options.ndiff: reporting_flags = doctest.REPORT_NDIFF if options.udiff: if reporting_flags: output.error("Can only give one of --ndiff, --udiff, or --cdiff") sys.exit(1) reporting_flags = doctest.REPORT_UDIFF if options.cdiff: if reporting_flags: output.error("Can only give one of --ndiff, --udiff, or --cdiff") sys.exit(1) reporting_flags = doctest.REPORT_CDIFF if options.report_only_first_failure: reporting_flags |= doctest.REPORT_ONLY_FIRST_FAILURE if reporting_flags: doctest.set_unittest_reportflags(reporting_flags) def global_shutdown(self): doctest.set_unittest_reportflags(self.old_reporting_flags) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-wo-source.txt0000644000175000017500000000722712214017655030430 0ustar arnauarnauRunning Without Source Code =========================== The ``--usecompiled`` option allows running tests in a tree without .py source code, provided compiled .pyc or .pyo files exist (without ``--usecompiled``, .py files are necessary). We have a very simple directory tree, under ``usecompiled/``, to test this. Because we're going to delete its .py files, we want to work in a copy of that: >>> import os.path, shutil, sys, tempfile >>> directory_with_tests = tempfile.mkdtemp() >>> NEWNAME = "unlikely_package_name" >>> src = os.path.join(this_directory, 'testrunner-ex', 'usecompiled') >>> os.path.isdir(src) True >>> dst = os.path.join(directory_with_tests, NEWNAME) >>> os.path.isdir(dst) False Have to use our own copying code, to avoid copying read-only SVN files that can't be deleted later. >>> n = len(src) + 1 >>> for root, dirs, files in os.walk(src): ... dirs[:] = [d for d in dirs if d == "package"] # prune cruft ... os.mkdir(os.path.join(dst, root[n:])) ... for f in files: ... shutil.copy(os.path.join(root, f), ... os.path.join(dst, root[n:], f)) Now run the tests in the copy: >>> from zope.testing import testrunner >>> mydefaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^compiletest$', ... '--package', NEWNAME, ... '-vv', ... ] >>> sys.argv = ['test'] >>> testrunner.run_internal(mydefaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test1 (unlikely_package_name.compiletest.Test) test2 (unlikely_package_name.compiletest.Test) test1 (unlikely_package_name.package.compiletest.Test) test2 (unlikely_package_name.package.compiletest.Test) Ran 4 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False If we delete the source files, it's normally a disaster: the test runner doesn't believe any test files, or even packages, exist. Note that we pass ``--keepbytecode`` this time, because otherwise the test runner would delete the compiled Python files too: >>> for root, dirs, files in os.walk(dst): ... for f in files: ... if f.endswith(".py"): ... os.remove(os.path.join(root, f)) >>> testrunner.run_internal(mydefaults, ["test", "--keepbytecode"]) Running tests at level 1 Total: 0 tests, 0 failures, 0 errors in N.NNN seconds. False Finally, passing ``--usecompiled`` asks the test runner to treat .pyc and .pyo files as adequate replacements for .py files. Note that the output is the same as when running with .py source above. The absence of "removing stale bytecode ..." messages shows that ``--usecompiled`` also implies ``--keepbytecode``: >>> testrunner.run_internal(mydefaults, ["test", "--usecompiled"]) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Running: test1 (unlikely_package_name.compiletest.Test) test2 (unlikely_package_name.compiletest.Test) test1 (unlikely_package_name.package.compiletest.Test) test2 (unlikely_package_name.package.compiletest.Test) Ran 4 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. False Remove the temporary directory: >>> shutil.rmtree(directory_with_tests) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-lib/0000755000175000017500000000000012214017655027531 5ustar arnauarnauzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-lib/sample4/0000755000175000017500000000000012214017655031076 5ustar arnauarnau././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-lib/sample4/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-lib/sample4/__init0000644000175000017500000000000212214017655032252 0ustar arnauarnau# ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-lib/sample4/products/zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-lib/sample4/produc0000755000175000017500000000000012214017655032313 5ustar arnauarnau././@LongLink0000000000000000000000000000016300000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-lib/sample4/products/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-lib/sample4/produc0000644000175000017500000000210412214017655032312 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Sample package that knits in extra directories. $Id: __init__.py 110538 2010-04-06 03:02:54Z tseaver $ """ import os __path__.append( os.path.join( os.path.dirname( # testing os.path.dirname( # testrunner-ex-knit-lib os.path.dirname( # sample4 os.path.dirname(__file__) # products ) ) ) , "testrunner-ex-pp-products" ) ) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/statistics.py0000644000175000017500000000267512214017655026303 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test runner statistics $Id: __init__.py 86218 2008-05-03 14:17:26Z ctheune $ """ import time import zope.testing.testrunner.feature class Statistics(zope.testing.testrunner.feature.Feature): active = True layers_run = 0 tests_run = 0 def late_setup(self): self.start_time = time.time() def early_teardown(self): self.end_time = time.time() def global_teardown(self): self.total_time = self.end_time - self.start_time def layer_setup(self, layer): self.layers_run += 1 def report(self): if not self.runner.do_run_tests: return if self.layers_run == 1: return self.runner.options.output.totals( self.runner.ran, len(self.runner.failures), len(self.runner.errors), self.total_time) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-products/0000755000175000017500000000000012214017655030626 5ustar arnauarnau././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-products/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-products/__init__.0000644000175000017500000000000212214017655032356 0ustar arnauarnau# ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-products/sampletests.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-products/sampletes0000644000175000017500000000151212214017655032545 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest class Test(unittest.TestCase): layer = 'samplelayers.Layer111' def test_extra_test_in_products(self): pass def test_suite(): return unittest.makeSuite(Test) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-products/more/0000755000175000017500000000000012214017655031570 5ustar arnauarnau././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-products/more/__init__.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-products/more/__in0000644000175000017500000000000212214017655032407 0ustar arnauarnau# ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-products/more/sampletests.pyzope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-ex-pp-products/more/samp0000644000175000017500000000151412214017655032454 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest class Test(unittest.TestCase): layer = 'samplelayers.Layer111' def test_another_test_in_products(self): pass def test_suite(): return unittest.makeSuite(Test) zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-repeat.txt0000644000175000017500000000361412214017655027761 0ustar arnauarnauRepeating Tests =============== The --repeat option can be used to repeat tests some number of times. Repeating tests is useful to help make sure that tests clean up after themselves. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> sys.argv = 'test --layer 112 --layer UnitTests --repeat 3'.split() >>> from zope.testing import testrunner >>> testrunner.run_internal(defaults) Running samplelayers.Layer112 tests: Set up samplelayers.Layerx in 0.000 seconds. Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer112 in 0.000 seconds. Iteration 1 Ran 34 tests with 0 failures and 0 errors in 0.010 seconds. Iteration 2 Ran 34 tests with 0 failures and 0 errors in 0.010 seconds. Iteration 3 Ran 34 tests with 0 failures and 0 errors in 0.010 seconds. Running zope.testing.testrunner.layer.UnitTests tests: Tear down samplelayers.Layer112 in N.NNN seconds. Tear down samplelayers.Layerx in N.NNN seconds. Tear down samplelayers.Layer11 in N.NNN seconds. Tear down samplelayers.Layer1 in N.NNN seconds. Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Iteration 1 Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Iteration 2 Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Iteration 3 Ran 192 tests with 0 failures and 0 errors in N.NNN seconds. Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Total: 226 tests, 0 failures, 0 errors in N.NNN seconds. False The tests are repeated by layer. Layers are set up and torn down only once. zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/feature.py0000644000175000017500000000405712214017655025540 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Generic features for the test runner. $Id: __init__.py 86232 2008-05-03 15:09:33Z ctheune $ """ import zope.interface import zope.testing.testrunner.interfaces class Feature(object): """A base class implementing no-op methods for the IFeature interface.""" zope.interface.implements(zope.testing.testrunner.interfaces.IFeature) active = False def __init__(self, runner): self.runner = runner def global_setup(self): """Executed once when the test runner is being set up.""" pass def late_setup(self): """Executed once right before the actual tests get executed and after all global setups have happened. """ pass def layer_setup(self, layer): """Executed once after a layer was set up.""" pass def layer_teardown(self, layer): """Executed once after a layer was run.""" pass def test_setup(self): """Executed once before each test.""" pass def test_teardown(self): """Executed once after each test.""" pass def early_teardown(self): """Executed once directly after all tests.""" pass def global_teardown(self): """Executed once after all tests where run and early teardowns have happened.""" pass def report(self): """Executed once after all tests have been run and all setup was torn down.""" pass zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-colors.txt0000644000175000017500000005007612214017655030006 0ustar arnauarnauColorful output =============== If you're on a Unix-like system, you can ask for colorized output. The test runner emits terminal control sequences to highlight important pieces of information (such as the names of failing tests) in different colors. >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> from zope.testing import testrunner Since it wouldn't be a good idea to have terminal control characters in a test file, let's wrap sys.stdout in a simple terminal interpreter >>> import re >>> class Terminal(object): ... _color_regexp = re.compile('\033[[]([0-9;]*)m') ... _colors = {'0': 'normal', '1': 'bold', '30': 'black', '31': 'red', ... '32': 'green', '33': 'yellow', '34': 'blue', ... '35': 'magenta', '36': 'cyan', '37': 'grey'} ... def __init__(self, stream): ... self._stream = stream ... def __getattr__(self, attr): ... return getattr(self._stream, attr) ... def isatty(self): ... return True ... def write(self, text): ... if '\033[' in text: ... text = self._color_regexp.sub(self._color, text) ... self._stream.write(text) ... def writelines(self, lines): ... for line in lines: ... self.write(line) ... def _color(self, match): ... colorstring = '{' ... for number in match.group(1).split(';'): ... colorstring += self._colors.get(number, '?') ... return colorstring + '}' >>> real_stdout = sys.stdout >>> sys.stdout = Terminal(sys.stdout) Successful test --------------- A successful test run soothes the developer with warm green colors: >>> sys.argv = 'test --layer 122 -c'.split() >>> testrunner.run_internal(defaults) {normal}Running samplelayers.Layer122 tests:{normal} Set up samplelayers.Layer1 in {green}0.000{normal} seconds. Set up samplelayers.Layer12 in {green}0.000{normal} seconds. Set up samplelayers.Layer122 in {green}0.000{normal} seconds. {normal} Ran {green}34{normal} tests with {green}0{normal} failures and {green}0{normal} errors in {green}0.007{normal} seconds.{normal} {normal}Tearing down left over layers:{normal} Tear down samplelayers.Layer122 in {green}0.000{normal} seconds. Tear down samplelayers.Layer12 in {green}0.000{normal} seconds. Tear down samplelayers.Layer1 in {green}0.000{normal} seconds. False Failed test ----------- A failed test run highlights the failures in red: >>> sys.argv = 'test -c --tests-pattern ^sampletests(f|_e|_f)?$ '.split() >>> testrunner.run_internal(defaults) {normal}Running samplelayers.Layer1 tests:{normal} Set up samplelayers.Layer1 in {green}0.000{normal} seconds. {normal} Ran {green}9{normal} tests with {green}0{normal} failures and {green}0{normal} errors in {green}0.001{normal} seconds.{normal} {normal}Running samplelayers.Layer11 tests:{normal} Set up samplelayers.Layer11 in {green}0.000{normal} seconds. {normal} Ran {green}34{normal} tests with {green}0{normal} failures and {green}0{normal} errors in {green}0.007{normal} seconds.{normal} {normal}Running samplelayers.Layer111 tests:{normal} Set up samplelayers.Layerx in {green}0.000{normal} seconds. Set up samplelayers.Layer111 in {green}0.000{normal} seconds. {normal} Ran {green}34{normal} tests with {green}0{normal} failures and {green}0{normal} errors in {green}0.008{normal} seconds.{normal} {normal}Running samplelayers.Layer112 tests:{normal} Tear down samplelayers.Layer111 in {green}0.000{normal} seconds. Set up samplelayers.Layer112 in {green}0.000{normal} seconds. {normal} Ran {green}34{normal} tests with {green}0{normal} failures and {green}0{normal} errors in {green}0.008{normal} seconds.{normal} {normal}Running samplelayers.Layer12 tests:{normal} Tear down samplelayers.Layer112 in {green}0.000{normal} seconds. Tear down samplelayers.Layerx in {green}0.000{normal} seconds. Tear down samplelayers.Layer11 in {green}0.000{normal} seconds. Set up samplelayers.Layer12 in {green}0.000{normal} seconds. {normal} Ran {green}34{normal} tests with {green}0{normal} failures and {green}0{normal} errors in {green}0.007{normal} seconds.{normal} {normal}Running samplelayers.Layer121 tests:{normal} Set up samplelayers.Layer121 in {green}0.000{normal} seconds. {normal} Ran {green}34{normal} tests with {green}0{normal} failures and {green}0{normal} errors in {green}0.007{normal} seconds.{normal} {normal}Running samplelayers.Layer122 tests:{normal} Tear down samplelayers.Layer121 in {green}0.000{normal} seconds. Set up samplelayers.Layer122 in {green}0.000{normal} seconds. {normal} Ran {green}34{normal} tests with {green}0{normal} failures and {green}0{normal} errors in {green}0.008{normal} seconds.{normal} {normal}Running zope.testing.testrunner.layer.UnitTests tests:{normal} Tear down samplelayers.Layer122 in {green}0.000{normal} seconds. Tear down samplelayers.Layer12 in {green}0.000{normal} seconds. Tear down samplelayers.Layer1 in {green}0.000{normal} seconds. Set up zope.testing.testrunner.layer.UnitTests in {green}N.NNN{normal} seconds. {boldred}Failure in test eek (sample2.sampletests_e){normal} Failed doctest test for sample2.sampletests_e.eek File "testrunner-ex/sample2/sampletests_e.py", line 28, in eek ---------------------------------------------------------------------- {normal}File "{boldblue}testrunner-ex/sample2/sampletests_e.py{normal}", line {boldred}30{normal}, in {boldcyan}sample2.sampletests_e.eek{normal} Failed example: {cyan} f(){normal} Exception raised: {red} Traceback (most recent call last):{normal} {red} File ".../doctest/__init__.py", line 1356, in __run{normal} {red} compileflags, 1) in test.globs{normal} {red} File "", line 1, in ?{normal} {red} f(){normal} {red} File "testrunner-ex/sample2/sampletests_e.py", line 19, in f{normal} {red} g(){normal} {red} File "testrunner-ex/sample2/sampletests_e.py", line 24, in g{normal} {red} x = y + 1{normal} {red} - __traceback_info__: I don't know what Y should be.{normal} {red} NameError: global name 'y' is not defined{normal} {boldred}Error in test test3 (sample2.sampletests_e.Test){normal} Traceback (most recent call last): {normal} File "{boldblue}unittest.py{normal}", line {boldred}260{normal}, in {boldcyan}run{normal} {cyan} testMethod(){normal} {normal} File "{boldblue}testrunner-ex/sample2/sampletests_e.py{normal}", line {boldred}43{normal}, in {boldcyan}test3{normal} {cyan} f(){normal} {normal} File "{boldblue}testrunner-ex/sample2/sampletests_e.py{normal}", line {boldred}19{normal}, in {boldcyan}f{normal} {cyan} g(){normal} {normal} File "{boldblue}testrunner-ex/sample2/sampletests_e.py{normal}", line {boldred}24{normal}, in {boldcyan}g{normal} {cyan} x = y + 1{normal} {red} - __traceback_info__: I don't know what Y should be.{normal} {red}NameError: global name 'y' is not defined{normal} {boldred}Failure in test testrunner-ex/sample2/e.txt{normal} Failed doctest test for e.txt File "testrunner-ex/sample2/e.txt", line 0 ---------------------------------------------------------------------- {normal}File "{boldblue}testrunner-ex/sample2/e.txt{normal}", line {boldred}4{normal}, in {boldcyan}e.txt{normal} Failed example: {cyan} f(){normal} Exception raised: {red} Traceback (most recent call last):{normal} {red} File ".../doctest/__init__.py", line 1356, in __run{normal} {red} compileflags, 1) in test.globs{normal} {red} File "", line 1, in ?{normal} {red} f(){normal} {red} File "", line 2, in f{normal} {red} return x{normal} {red} NameError: global name 'x' is not defined{normal} {boldred}Failure in test test (sample2.sampletests_f.Test){normal} Traceback (most recent call last): {normal} File "{boldblue}unittest.py{normal}", line {boldred}260{normal}, in {boldcyan}run{normal} {cyan} testMethod(){normal} {normal} File "{boldblue}testrunner-ex/sample2/sampletests_f.py{normal}", line {boldred}21{normal}, in {boldcyan}test{normal} {cyan} self.assertEqual(1,0){normal} {normal} File "{boldblue}unittest.py{normal}", line {boldred}333{normal}, in {boldcyan}failUnlessEqual{normal} {cyan} raise self.failureException, \{normal} {red}AssertionError: 1 != 0{normal} {normal} Ran {green}200{normal} tests with {boldred}3{normal} failures and {boldred}1{normal} errors in {green}0.045{normal} seconds.{normal} {normal}Tearing down left over layers:{normal} Tear down zope.testing.testrunner.layer.UnitTests in {green}N.NNN{normal} seconds. {normal}Total: {green}413{normal} tests, {boldred}3{normal} failures, {boldred}1{normal} errors in {green}0.023{normal} seconds.{normal} True Doctest failures ---------------- The expected and actual outputs of failed doctests are shown in different colors: >>> sys.argv = 'test --tests-pattern ^pledge$ -c'.split() >>> _ = testrunner.run_internal(defaults) {normal}Running zope.testing.testrunner.layer.UnitTests tests:{normal} Set up zope.testing.testrunner.layer.UnitTests in {green}N.NNN{normal} seconds. {boldred}Failure in test pledge (pledge){normal} Failed doctest test for pledge.pledge File "testrunner-ex/pledge.py", line 24, in pledge ---------------------------------------------------------------------- {normal}File testrunner-ex/pledge.py{normal}", line {boldred}26{normal}, in {boldcyan}pledge.pledge{normal} Failed example: {cyan} print pledge_template % ('and earthling', 'planet'),{normal} Expected: {green} I give my pledge, as an earthling,{normal} {green} to save, and faithfully, to defend from waste,{normal} {green} the natural resources of my planet.{normal} {green} It's soils, minerals, forests, waters, and wildlife.{normal} Got: {red} I give my pledge, as and earthling,{normal} {red} to save, and faithfully, to defend from waste,{normal} {red} the natural resources of my planet.{normal} {red} It's soils, minerals, forests, waters, and wildlife.{normal} {normal} Ran {green}1{normal} tests with {boldred}1{normal} failures and {green}0{normal} errors in {green}0.002{normal} seconds.{normal} {normal}Tearing down left over layers:{normal} Tear down zope.testing.testrunner.layer.UnitTests in {green}N.NNN{normal} seconds. Diffs are highlighted so you can easily tell the context and the mismatches apart: >>> sys.argv = 'test --tests-pattern ^pledge$ --ndiff -c'.split() >>> _ = testrunner.run_internal(defaults) {normal}Running zope.testing.testrunner.layer.UnitTests tests:{normal} Set up zope.testing.testrunner.layer.UnitTests in {green}N.NNN{normal} seconds. {boldred}Failure in test pledge (pledge){normal} Failed doctest test for pledge.pledge File "testrunner-ex/pledge.py", line 24, in pledge ---------------------------------------------------------------------- {normal}File testrunner-ex/pledge.py{normal}", line {boldred}26{normal}, in {boldcyan}pledge.pledge{normal} Failed example: {cyan} print pledge_template % ('and earthling', 'planet'),{normal} Differences (ndiff with -expected +actual): {green} - I give my pledge, as an earthling,{normal} {red} + I give my pledge, as and earthling,{normal} {magenta} ? +{normal} {normal} to save, and faithfully, to defend from waste,{normal} {normal} the natural resources of my planet.{normal} {normal} It's soils, minerals, forests, waters, and wildlife.{normal} {normal} Ran {green}1{normal} tests with {boldred}1{normal} failures and {green}0{normal} errors in {green}0.003{normal} seconds.{normal} {normal}Tearing down left over layers:{normal} Tear down zope.testing.testrunner.layer.UnitTests in {green}N.NNN{normal} seconds. Even test failures that have actual blank lines (as opposed to ) in them are highlighted correctly. >>> import zope.testing.testrunner.formatter >>> formatter = zope.testing.testrunner.formatter.ColorfulOutputFormatter(None) >>> formatter.print_doctest_failure("""\ ... File "sometest.txt", line 221, in sometest.txt ... Failed example: ... foo() ... Expected: ... Output that contains ... ... blank lines. ... Got: ... Output that still contains ... ... blank lines.""") {normal} File "sometest.txt", line 221, in sometest.txt{normal} Failed example: {cyan} foo(){normal} Expected: {green} Output that contains{normal} {green} blank lines.{normal} Got: {red} Output that still contains{normal} {red} blank lines.{normal} Timing individual tests ----------------------- At very high verbosity levels you can see the time taken by each test >>> sys.argv = 'test -u -t test_one.TestNotMuch -c -vvv'.split() >>> testrunner.run_internal(defaults) {normal}Running tests at level 1{normal} {normal}Running zope.testing.testrunner.layer.UnitTests tests:{normal} Set up zope.testing.testrunner.layer.UnitTests in {green}N.NNN{normal} seconds. {normal} Running:{normal} test_1 (sample1.sampletests.test_one.TestNotMuch) ({green}N.NNN s{normal}) test_2 (sample1.sampletests.test_one.TestNotMuch) ({green}N.NNN s{normal}) test_3 (sample1.sampletests.test_one.TestNotMuch) ({green}N.NNN s{normal}) test_1 (sampletests.test_one.TestNotMuch) ({green}N.NNN s{normal}) test_2 (sampletests.test_one.TestNotMuch) ({green}N.NNN s{normal}) test_3 (sampletests.test_one.TestNotMuch) ({green}N.NNN s{normal}) {normal} Ran {green}6{normal} tests with {green}0{normal} failures and {green}0{normal} errors in {green}N.NNN{normal} seconds.{normal} {normal}Tearing down left over layers:{normal} Tear down zope.testing.testrunner.layer.UnitTests in {green}N.NNN{normal} seconds. False If we had very slow tests we would see their times highlighted in a different color. Instead of creating a test that waits 10 seconds, let's lower the slow test threshold in the test runner to 0 seconds to make all of the tests seem slow. >>> sys.argv = 'test -u -t test_one.TestNotMuch -c -vvv --slow-test 0'.split() >>> testrunner.run_internal(defaults) {normal}Running tests at level 1{normal} {normal}Running zope.testing.testrunner.layer.UnitTests tests:{normal} Set up zope.testing.testrunner.layer.UnitTests in {green}N.NNN{normal} seconds. {normal} Running:{normal} test_1 (sample1.sampletests.test_one.TestNotMuch) ({boldmagenta}N.NNN s{normal}) test_2 (sample1.sampletests.test_one.TestNotMuch) ({boldmagenta}N.NNN s{normal}) test_3 (sample1.sampletests.test_one.TestNotMuch) ({boldmagenta}N.NNN s{normal}) test_1 (sampletests.test_one.TestNotMuch) ({boldmagenta}N.NNN s{normal}) test_2 (sampletests.test_one.TestNotMuch) ({boldmagenta}N.NNN s{normal}) test_3 (sampletests.test_one.TestNotMuch) ({boldmagenta}N.NNN s{normal}) {normal} Ran {green}6{normal} tests with {green}0{normal} failures and {green}0{normal} errors in {green}N.NNN{normal} seconds.{normal} {normal}Tearing down left over layers:{normal} Tear down zope.testing.testrunner.layer.UnitTests in {green}N.NNN{normal} seconds. False Disabling colors ---------------- If -c or --color have been previously provided on the command line (perhaps by a test runner wrapper script), but no colorized output is desired, the -C or --no-color options will disable colorized output: >>> sys.argv = 'test --layer 122 -c -C'.split() >>> testrunner.run_internal(defaults) Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.007 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False >>> sys.argv = 'test --layer 122 -c --no-color'.split() >>> testrunner.run_internal(defaults) Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.007 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False Autodetecting colors -------------------- The --auto-color option will determine if stdout is a terminal that supports colors, and only enable colorized output if so. Our ``Terminal`` wrapper pretends it is a terminal, but the curses module will realize it isn't: >>> sys.argv = 'test --layer 122 --auto-color'.split() >>> testrunner.run_internal(defaults) Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.007 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False We can fake it >>> class FakeCurses(object): ... class error(Exception): ... pass ... def setupterm(self): ... pass ... def tigetnum(self, attr): ... return dict(colors=8).get(attr, -2) >>> sys.modules['curses'] = FakeCurses() >>> sys.argv = 'test --layer 122 --auto-color'.split() >>> testrunner.run_internal(defaults) {normal}Running samplelayers.Layer122 tests:{normal} Set up samplelayers.Layer1 in {green}0.000{normal} seconds. Set up samplelayers.Layer12 in {green}0.000{normal} seconds. Set up samplelayers.Layer122 in {green}0.000{normal} seconds. {normal} Ran {green}34{normal} tests with {green}0{normal} failures and {green}0{normal} errors in {green}0.007{normal} seconds.{normal} {normal}Tearing down left over layers:{normal} Tear down samplelayers.Layer122 in {green}0.000{normal} seconds. Tear down samplelayers.Layer12 in {green}0.000{normal} seconds. Tear down samplelayers.Layer1 in {green}0.000{normal} seconds. False >>> del sys.modules['curses'] The real stdout is not a terminal in a doctest: >>> sys.stdout = real_stdout >>> sys.argv = 'test --layer 122 --auto-color'.split() >>> testrunner.run_internal(defaults) Running samplelayers.Layer122 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Set up samplelayers.Layer122 in 0.000 seconds. Ran 34 tests with 0 failures and 0 errors in 0.007 seconds. Tearing down left over layers: Tear down samplelayers.Layer122 in 0.000 seconds. Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. False zope2.13-2.13.21/source/zope.testing/src/zope/testing/testrunner/testrunner-leaks.txt0000644000175000017500000002465212214017655027605 0ustar arnauarnauDebugging Memory Leaks ====================== The --report-refcounts (-r) option can be used with the --repeat (-N) option to detect and diagnose memory leaks. To use this option, you must configure Python with the --with-pydebug option. (On Unix, pass this option to configure and then build Python.) >>> import os.path, sys >>> directory_with_tests = os.path.join(this_directory, 'testrunner-ex') >>> defaults = [ ... '--path', directory_with_tests, ... '--tests-pattern', '^sampletestsf?$', ... ] >>> from zope.testing import testrunner >>> sys.argv = 'test --layer Layer11$ --layer Layer12$ -N4 -r'.split() >>> _ = testrunner.run_internal(defaults) Running samplelayers.Layer11 tests: Set up samplelayers.Layer1 in 0.000 seconds. Set up samplelayers.Layer11 in 0.000 seconds. Iteration 1 Ran 34 tests with 0 failures and 0 errors in 0.013 seconds. Iteration 2 Ran 34 tests with 0 failures and 0 errors in 0.012 seconds. sys refcount=100401 change=0 Iteration 3 Ran 34 tests with 0 failures and 0 errors in 0.012 seconds. sys refcount=100401 change=0 Iteration 4 Ran 34 tests with 0 failures and 0 errors in 0.013 seconds. sys refcount=100401 change=0 Running samplelayers.Layer12 tests: Tear down samplelayers.Layer11 in 0.000 seconds. Set up samplelayers.Layer12 in 0.000 seconds. Iteration 1 Ran 34 tests with 0 failures and 0 errors in 0.013 seconds. Iteration 2 Ran 34 tests with 0 failures and 0 errors in 0.012 seconds. sys refcount=100411 change=0 Iteration 3 Ran 34 tests with 0 failures and 0 errors in 0.012 seconds. sys refcount=100411 change=0 Iteration 4 Ran 34 tests with 0 failures and 0 errors in 0.012 seconds. sys refcount=100411 change=0 Tearing down left over layers: Tear down samplelayers.Layer12 in 0.000 seconds. Tear down samplelayers.Layer1 in 0.000 seconds. Total: 68 tests, 0 failures, 0 errors in N.NNN seconds. Each layer is repeated the requested number of times. For each iteration after the first, the system refcount and change in system refcount is shown. The system refcount is the total of all refcount in the system. When a refcount on any object is changed, the system refcount is changed by the same amount. Tests that don't leak show zero changes in systen refcount. Let's look at an example test that leaks: >>> sys.argv = 'test --tests-pattern leak -N4 -r'.split() >>> _ = testrunner.run_internal(defaults) Running zope.testing.testrunner.layer.UnitTests tests:... Iteration 1 Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. Iteration 2 Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sys refcount=92506 change=12 Iteration 3 Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sys refcount=92513 change=12 Iteration 4 Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sys refcount=92520 change=12 Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. Here we see that the system refcount is increating. If we specify a verbosity greater than one, we can get details broken out by object type (or class): >>> sys.argv = 'test --tests-pattern leak -N5 -r -v'.split() >>> _ = testrunner.run_internal(defaults) Running tests at level 1 Running zope.testing.testrunner.layer.UnitTests tests:... Iteration 1 Running: . Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. Iteration 2 Running: . Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sum detail refcount=95832 sys refcount=105668 change=16 Leak details, changes in instances and refcounts by type/class: type/class insts refs ------------------------------------------------------- ----- ---- classobj 0 1 dict 2 2 float 1 1 int 2 2 leak.ClassicLeakable 1 1 leak.Leakable 1 1 str 0 4 tuple 1 1 type 0 3 ------------------------------------------------------- ----- ---- total 8 16 Iteration 3 Running: . Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sum detail refcount=95844 sys refcount=105680 change=12 Leak details, changes in instances and refcounts by type/class: type/class insts refs ------------------------------------------------------- ----- ---- classobj 0 1 dict 2 2 float 1 1 int -1 0 leak.ClassicLeakable 1 1 leak.Leakable 1 1 str 0 4 tuple 1 1 type 0 1 ------------------------------------------------------- ----- ---- total 5 12 Iteration 4 Running: . Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sum detail refcount=95856 sys refcount=105692 change=12 Leak details, changes in instances and refcounts by type/class: type/class insts refs ------------------------------------------------------- ----- ---- classobj 0 1 dict 2 2 float 1 1 leak.ClassicLeakable 1 1 leak.Leakable 1 1 str 0 4 tuple 1 1 type 0 1 ------------------------------------------------------- ----- ---- total 6 12 Iteration 5 Running: . Ran 1 tests with 0 failures and 0 errors in 0.000 seconds. sum detail refcount=95868 sys refcount=105704 change=12 Leak details, changes in instances and refcounts by type/class: type/class insts refs ------------------------------------------------------- ----- ---- classobj 0 1 dict 2 2 float 1 1 leak.ClassicLeakable 1 1 leak.Leakable 1 1 str 0 4 tuple 1 1 type 0 1 ------------------------------------------------------- ----- ---- total 6 12 Tearing down left over layers: Tear down zope.testing.testrunner.layer.UnitTests in N.NNN seconds. It is instructive to analyze the results in some detail. The test being run was designed to intentionally leak: class ClassicLeakable: def __init__(self): self.x = 'x' class Leakable(object): def __init__(self): self.x = 'x' leaked = [] class TestSomething(unittest.TestCase): def testleak(self): leaked.append((ClassicLeakable(), Leakable(), time.time())) Let's go through this by type. float, leak.ClassicLeakable, leak.Leakable, and tuple We leak one of these every time. This is to be expected because we are adding one of these to the list every time. str We don't leak any instances, but we leak 4 references. These are due to the instance attributes avd values. dict We leak 2 of these, one for each ClassicLeakable and Leakable instance. classobj We increase the number of classobj instance references by one each time because each ClassicLeakable instance has a reference to its class. This instances increases the references in it's class, which increases the total number of references to classic classes (clasobj instances). type For most interations, we increase the number of type references by one for the same reason we increase the number of clasobj references by one. The increase of the number of type references by 3 in the second iteration is puzzling, but illustrates that this sort of data is often puzzling. int The change in the number of int instances and references in this example is a side effect of the statistics being gathered. Lots of integers are created to keep the memory statistics used here. The summary statistics include the sum of the detail refcounts. (Note that this sum is less than the system refcount. This is because the detailed analysis doesn't inspect every object. Not all objects in the system are returned by sys.getobjects.) zope2.13-2.13.21/source/zope.testing/src/zope/testing/cleanup.py0000644000175000017500000000346712214017655023327 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Provide a standard cleanup registry Unit tests that change global data should include the CleanUp base class, which provides simpler setUp and tearDown methods that call global-data cleanup routines:: class Test(CleanUp, unittest.TestCase): .... If custom setUp or tearDown are needed, then the base routines should be called, as in:: def tearDown(self): super(Test, self).tearDown() .... Cleanup routines for global data should be registered by passing them to addCleanup:: addCleanUp(pigRegistry._clear) $Id: cleanup.py 110538 2010-04-06 03:02:54Z tseaver $ """ _cleanups = [] def addCleanUp(func, args=(), kw={}): """Register a cleanup routines Pass a function to be called to cleanup global data. Optional argument tuple and keyword arguments may be passed. """ _cleanups.append((func, args, kw)) class CleanUp(object): """Mix-in class providing clean-up setUp and tearDown routines.""" def cleanUp(self): """Clean up global data.""" cleanUp() setUp = tearDown = cleanUp def cleanUp(): """Clean up global data.""" for func, args, kw in _cleanups: func(*args, **kw) setUp = tearDown = cleanUp zope2.13-2.13.21/source/zope.testing/src/zope/testing/module.py0000644000175000017500000000276412214017655023164 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Fake module support $Id: module.py 110538 2010-04-06 03:02:54Z tseaver $ """ import sys class FakeModule: def __init__(self, dict): self.__dict = dict def __getattr__(self, name): try: return self.__dict[name] except KeyError: raise AttributeError(name) def setUp(test, name='__main__'): dict = test.globs dict['__name__'] = name module = FakeModule(dict) sys.modules[name] = module if '.' in name: name = name.split('.') parent = sys.modules['.'.join(name[:-1])] setattr(parent, name[-1], module) def tearDown(test, name=None): if name is None: name = test.globs['__name__'] del test.globs['__name__'] del sys.modules[name] if '.' in name: name = name.split('.') parent = sys.modules['.'.join(name[:-1])] delattr(parent, name[-1]) zope2.13-2.13.21/source/zope.testing/src/zope/testing/tests.py0000644000175000017500000000361712214017655023037 0ustar arnauarnau############################################################################## # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for the testing framework. $Id: tests.py 110180 2010-03-25 16:08:52Z mgedmin $ """ import re import unittest import warnings from zope.testing import renormalizing # Yes, it is deprecated, but we want to run tests on it here. warnings.filterwarnings("ignore", "zope.testing.doctest is deprecated", DeprecationWarning, __name__, 0) from zope.testing import doctest def test_suite(): return unittest.TestSuite(( doctest.DocTestSuite('zope.testing.loggingsupport'), doctest.DocTestSuite('zope.testing.renormalizing'), doctest.DocTestSuite('zope.testing.server'), doctest.DocFileSuite('doctest.txt'), doctest.DocFileSuite('formparser.txt'), doctest.DocFileSuite( 'module.txt', # when this test is run in isolation, the error message shows the # module name as fully qualified; when it is run as part of the # full test suite, the error message shows the module name as # relative. checker=renormalizing.RENormalizing([ (re.compile('No module named zope.testing.unlikelymodulename'), 'No module named unlikelymodulename')])), doctest.DocFileSuite('setupstack.txt'), doctest.DocTestSuite(doctest, optionflags=doctest.INTERPRET_FOOTNOTES), )) zope2.13-2.13.21/source/zope.testing/src/zope/testing/formparser.txt0000644000175000017500000000754512214017655024250 0ustar arnauarnauParsing HTML Forms ================== Sometimes in functional tests, information from a generated form must be extracted in order to re-submit it as part of a subsequent request. The `zope.testing.formparser` module can be used for this purpose. The scanner is implemented using the `FormParser` class. The constructor arguments are the page data containing the form and (optionally) the URL from which the page was retrieved: >>> import zope.testing.formparser >>> page_text = '''\ ... ...
    ... ... ... ... ...
    ... ... Just for fun, a second form, after specifying a base: ... ...
    ... ... ... ... ...
    ... ... ''' >>> parser = zope.testing.formparser.FormParser(page_text) >>> forms = parser.parse() >>> len(forms) 2 >>> forms.form1 is forms[0] True >>> forms.form1 is forms[1] False More often, the `parse()` convenience function is all that's needed: >>> forms = zope.testing.formparser.parse( ... page_text, "http://cgi.example.com/somewhere/form.html") >>> len(forms) 2 >>> forms.form1 is forms[0] True >>> forms.form1 is forms[1] False Once we have the form we're interested in, we can check form attributes and individual field values: >>> form = forms.form1 >>> form.enctype 'application/x-www-form-urlencoded' >>> form.method 'post' >>> keys = form.keys() >>> keys.sort() >>> keys ['do-it-now', 'f1', 'not-really', 'pick-two'] >>> not_really = form["not-really"] >>> not_really.type 'image' >>> not_really.value "Don't." >>> not_really.readonly False >>> not_really.disabled False Note that relative URLs are converted to absolute URLs based on the ```` element (if present) or using the base passed in to the constructor. >>> form.action 'http://cgi.example.com/cgi-bin/foobar.py' >>> not_really.src 'http://cgi.example.com/somewhere/dont.png' >>> forms[1].action 'http://www.example.com/base/sproing/sprung.html' >>> forms[1]["action"].src 'http://www.example.com/base/else.png' Fields which are repeated are reported as lists of objects that represent each instance of the field:: >>> field = forms[1]["multi"] >>> type(field) >>> [o.value for o in field] ['', ''] >>> [o.size for o in field] [2, 3] The ``
    Locked by WebDAV   

    File  
    Locked by WebDAV
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/methodAdd.dtml0000644000175000017500000000357312214017421021371 0ustar arnauarnau

    A DTML Method is used to hold scripting tags and text content. It can contain HTML, XML, plain text, structured-text, etcetera. A DTML Method can contain DTML scripting tags which operate in the context of the DTML Method's containing object. A DTML Method differs from a DTML Document in that it does not have properties and calls methods on its container object.

    You may create a new DTML Method object using the form below. You may also choose to upload an existing html file from your local computer by clicking the Browse button.

    Id
    Title
    File
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/documentAdd.dtml0000644000175000017500000000343712214017421021726 0ustar arnauarnau

    A DTML Document is used to hold text content. It can contain HTML, XML, plain text, structured-text, etcetera. A DTML document can contain DTML scripting tags. A DTML Document differs from a DTML Method in that it has properties and does not call methods on its container object.

    You may create a new DTML Document using the form below. You may also choose to upload an existing html file from your local computer by clicking the Browse button.

    Id
    Title
    File
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/findFrame.dtml0000644000175000017500000000130412214017421021361 0ustar arnauarnau Find /manage_findAdv" NAME="findForm" /manage_findForm" NAME="findForm" MARGINWIDTH="2" MARGINHEIGHT="2" SCROLLING="auto"> /manage_findResult" NAME="findResult" MARGINWIDTH="2" MARGINHEIGHT="0" SCROLLING="auto"> Management interfaces require the use of a <B>frames-capable</B> web browser. zope2.13-2.13.21/source/Zope2/src/OFS/dtml/listLocalRoles.dtml0000644000175000017500000000547512214017421022436 0ustar arnauarnau
    &dtml-stat;

    Local roles allow you to give particular users extra roles in the context of this object, in addition to the roles they already have.

    The following users have been given local roles. To modify the local roles given to a particular user, click on the name of the user. To remove all local roles from a user, select the checkbox next to the name of the user and click the Remove button.

    &dtml-sequence-key; (&dtml-sequence-item;, )

    To give a user extra roles when accessing this object (and its children), select a user from the User list below, select the extra roles that should be given to that user from the Roles list.

    User
    Roles
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/userFolderProps.dtml0000644000175000017500000000204612214017421022630 0ustar arnauarnau
    Encrypt user passwords    
    Role assignment presents search dialog when more users than N (-1 is always, 0 is never).

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/addOrderedFolder.dtml0000644000175000017500000000404312214017421022662 0ustar arnauarnau

    An ordered Folder contains other objects. Use Folders to organize your web objects in to logical groups. The create public interface option creates an index document inside the Folder to give the Folder a default HTML representation. The create user folder option creates a User Folder inside the Folder to hold authorization information for the Folder.

    Id
    Title
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/properties.dtml0000644000175000017500000002254012214017421021667 0ustar arnauarnau A site-global encoding specification in a property. Note that this feature only works if there are no unicode objects around. This means that this feature is not likely to be supported in all future versions of zope. Thankfully no site-global encoding specification in a property. We can set UTF-8, and unicode properties will work.
    " method="post">

    Properties allow you to assign simple values to Zope objects. To change property values, edit the values and click "Save Changes".

     
    Name
    Value
    Type
    " /> " /> " /> " /> checked="checked" /> &dtml-sequence-item; " />
    No value for &dtml-select_variable;.
    No value for &dtml-select_variable;.
    Unknown property type
    &dtml-type;
    Warning: be aware that removing 'title' without re-adding it might be dangerous.
     
    This needs some community review before exposing it officially.  

    Properties allow you to assign simple values to Zope objects. There are currently no properties defined for this item. To add a property, enter a name, type and value and click the "Add" button.

    /manage_addProperty" method="post">

    To add a new property, enter a name, type and value for the new property and click the "Add" button.

    Name
    string" size="30" value="" /> Type
    Value
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/renameForm.dtml0000644000175000017500000000305612214017421021567 0ustar arnauarnau
    " method="post">
    &dtml-id;
    to: " value="&dtml-id;" /> may not be renamed.



    You must select an item to rename.

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/main.dtml0000644000175000017500000002347412214017421020426 0ustar arnauarnau
     
    /" method="get"> 1">
    /" name="objectItems" method="post">
    &dtml.missing-alt; &dtml-meta_type;  
    1 Kb 1048576"> Mb Kb    
    ...
    / by
    There are currently no items in &dtml-title_or_id;

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/imageAdd.dtml0000644000175000017500000000240112214017421021160 0ustar arnauarnau

    Select a file to upload from your local computer by clicking the Browse button.

    Id
    Title
    File
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/owner.dtml0000644000175000017500000000212612214017421020623 0ustar arnauarnau

    Almost all Zope objects can be owned. When you create an object you become its owner. Ownership matters for method objects since it determines what roles they have when they are executed. See the Proxy Roles view of method objects for more information.

    This object is owned by &dtml-id; (&dtml-path;).unowned.


    If you have the Take Ownership permission you can take ownership of an object. Usually when taking ownership you should also take ownership of sub-objects as well.

    Also take ownership of all sub-objects

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/importExport.dtml0000644000175000017500000000720112214017421022204 0ustar arnauarnau

    You can export Zope objects to a file in order to transfer them to a different Zope installation. You can either choose to download the export file to your local machine, or save it in the "var" directory of your Zope installation on the server.

    Note: Zope can export/import objects in two different formats: a binary format (called ZEXP) and as XML. The ZEXP format is the officially supported export/import format for moving data between identical Zope installations (it is not a migration tool).

    Export object id
    " class="form-element"/>
    Export to

     
    (unsupported, see above)

    You may import Zope objects which have been previously exported to a file, by placing the file in the "import" directory of your Zope installation on the server. You should create the "import" directory in the root of your Zope installation if it does not yet exist.

    Note that by default, you will become the owner of the objects that you are importing. If you wish the imported objects to retain their existing ownership information, select "retain existing ownership information".

    Import file name
    Ownership

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/editLocalRoles.dtml0000644000175000017500000000310312214017421022372 0ustar arnauarnau

    Local roles allow you to give particular users extra roles in the context of this object, in addition to the roles they already have.

    To change the local roles for this user, select the extra roles this user should have in the context of this object and click the Save Changes button.

    User
    Roles
    &dtml-userid;
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/imageEdit.dtml0000644000175000017500000000536212214017421021366 0ustar arnauarnau

    You can update the data for this &dtml-kind; using the form below. Select a data file from your local computer by clicking the browse button and click upload to update the contents of the &dtml-kind;.

    Title
    Content Type
    Preview
    250"> 250">
    Last Modified
    File Size
    bytes
    Locked by WebDAV

    File Data

    Locked by WebDAV
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/brokenEdit.dtml0000644000175000017500000000072212214017421021557 0ustar arnauarnau

    This object is broken because the &dtml-product_name; product that created it is no longer installed or is installed incorrectly. Please contact the product maintainer for assistance.

    Note that the data associated with this product has not been lost, and will be accessible again if the product is reinstalled.

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/imageView.dtml0000644000175000017500000000015712214017421021410 0ustar arnauarnau

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/cacheable.dtml0000644000175000017500000000170412214017421021361 0ustar arnauarnau
    Cache this object using:

    Cache Settings

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/mainUser.dtml0000644000175000017500000000260612214017421021257 0ustar arnauarnau

    The following users have been defined. Click on the name of a user to edit that user.

     

    There are no users defined.

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/propertyType.dtml0000644000175000017500000001034712214017421022223 0ustar arnauarnau Change Property Types
    " method="POST">

    To change property names and values, edit them and click "Save Changes". To edit properties using their new type, select the new types and click "Edit with new Types"

    Property Name Value New Type
    value="&dtml-sequence-item; " value="" value="&dtml-new_value;" > CHECKED> No value for &dtml-select_variable;. No value for &dtml-select_variable;. Unknown property type

    No properties were selected for this item.

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/history.dtml0000644000175000017500000000505612214017421021177 0ustar arnauarnau
    " method="POST">
    (&dtml-user_name;)

    revision: &dtml-revision;

    No change history is available for this object.

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/access.dtml0000644000175000017500000001075712214017421020743 0ustar arnauarnau

    The listing below shows the current security settings for this item. Permissions are rows and roles are columns. Checkboxes are used to indicate where roles are assigned permissions. You can also assign local roles to users, which give users extra roles in the context of this object and its subobjects.

    When a role is assigned to a permission, users with the given role will be able to perform tasks associated with the permission on this item. When the Acquire permission settings checkbox is selected then the containing objects's permission settings are used. Note: the acquired permission settings may be augmented by selecting Roles for a permission in addition to selecting to acquire permissions.

    Username:
     
    Permission
    ">
    Roles
    " align="left"> " align="left">

    You can define new roles by entering a role name and clicking the "Add Role" button.

    User defined roles
     
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/addUser.dtml0000644000175000017500000000374212214017421021065 0ustar arnauarnau

    To add a new user, enter the name ,password, confirmation and roles for the new user and click "Add". Domains is an optional list of domains from which the user is allowed to login.

    Name
    Password
    (Confirm)
    Domains
    Roles

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/historyCompare.dtml0000644000175000017500000000134112214017421022477 0ustar arnauarnau
    Changes to &dtml-id; as of
    to get to &dtml-id; as of

    This object does not provide comparison support.

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/cmassoc.dtml0000644000175000017500000000711612214017421021125 0ustar arnauarnau

    Select which objects should be cached using this cache manager. Only those objects for which you have the "Change cache settings" permission are shown.

    checked="checked">
      &dtml-path;(&dtml-title;)

    No objects matched your query.


    Locate cacheable objects:
    All Associated with this cache manager
    Of the type(s):
    Search subfolders

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/reportUserPermissions.dtml0000644000175000017500000000363312214017421024103 0ustar arnauarnau

    This listing shows the permissions and roles for particular user in the context of the current object.

    User account : &dtml-user;
    User account defined in:

    Roles Roles in context
    • &dtml-sequence-item;
    • &dtml-sequence-item;

    Allowed permissions Disallowed permissions
    • &dtml-sequence-item;
    • &dtml-sequence-item;
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/findAdv.dtml0000644000175000017500000000624112214017421021046 0ustar arnauarnau
    Find objects of type:
    with ids:
    containing:
    expr:
    modified:
    where the roles:
    have permission:
    Sort results by:
    Reverse?
    Search only in this folder
    Search all subfolders
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/cacheNamespaceKeys.dtml0000644000175000017500000000052712214017421023210 0ustar arnauarnau

    Names from the DTML namespace to use as cache keys:


    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/fileEdit.dtml0000644000175000017500000000644212214017421021223 0ustar arnauarnau

    You can update the data for this file object using the form below. Select a data file from your local computer by clicking the browse button and click upload to update the contents of the file. You may also edit the file content directly if the content is a text type and small enough to be edited in a text area.

    " method="post" enctype="multipart/form-data">
    Title
    Content Type
    Precondition
    Last Modified
    File Size
    bytes
    Locked by WebDAV

    File Data

    Locked by WebDAV
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/folderAdd.dtml0000644000175000017500000000401212214017421021351 0ustar arnauarnau

    A Folder contains other objects. Use Folders to organize your web objects in to logical groups. The create public interface option creates an index document inside the Folder to give the Folder a default HTML representation. The create user folder option creates a User Folder inside the Folder to hold authorization information for the Folder.

    Id
    Title
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/findForm.dtml0000644000175000017500000000437112214017421021241 0ustar arnauarnau

    Find allows you to locate Zope objects based on different criteria. For more find choices choose the Advanced find option.

    Find objects of type:
    with ids:
    containing:
    modified:
    Search only in this folder
    Search all subfolders
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/editUser.dtml0000644000175000017500000000371412214017421021261 0ustar arnauarnau
    Name
     
    New Password
    (Confirm)
    Domains
    &dtml-sequence-item; " />
    Roles
    " />

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/documentProxy.dtml0000644000175000017500000000232312214017421022350 0ustar arnauarnau

    Proxy roles allow you to control the access that a DTML document or method has. Proxy roles replace the roles of the user who is viewing the document or method. This can be used to both expand and limit access to resources. Select the proxy roles for this object from the list below.

    Proxy Roles
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/permissionEdit.dtml0000644000175000017500000000202512214017421022465 0ustar arnauarnau

    Roles assigned to the permission &dtml-permission_to_manage;

    > Also use roles acquired from folders containing this object

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/roleEdit.dtml0000644000175000017500000000127512214017421021244 0ustar arnauarnau

    Permissions assigned to the role &dtml-role_to_manage;

    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/editedDialog.dtml0000644000175000017500000000113112214017421022042 0ustar arnauarnau

    !


    &dtml-title_and_id; has been successfully edited.
    zope2.13-2.13.21/source/Zope2/src/OFS/dtml/findResult.dtml0000644000175000017500000001624712214017421021621 0ustar arnauarnau

    Displaying items &dtml-sequence-number;-&dtml-sequence-number; of items matching your query. You can revise your search terms below.

    No items were found matching your query. You can revise your search terms below.

    [&dtml-meta_type;]
    Find objects of type:
    with ids:
    ">
    containing:
    ">
    expr:
    modified:
    where the roles:
    have permission:
    Sort results by:
    Reverse?
    CHECKED> Search only in this folder
    CHECKED> Search all subfolders
    zope2.13-2.13.21/source/Zope2/src/OFS/Traversable.py0000644000175000017500000003265712214017421020507 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """This module implements a mix-in for traversable objects. """ from urllib import quote from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.SecurityManagement import getSecurityManager from AccessControl.unauthorized import Unauthorized from AccessControl.ZopeGuards import guarded_getattr from Acquisition import Acquired from Acquisition import aq_acquire from Acquisition import aq_base from Acquisition import aq_inner from Acquisition import aq_parent from Acquisition.interfaces import IAcquirer from OFS.interfaces import ITraversable from zExceptions import NotFound from ZPublisher.interfaces import UseTraversalDefault from ZODB.POSException import ConflictError from zope.interface import implements from zope.interface import Interface from zope.component import queryMultiAdapter from zope.location.interfaces import LocationError from zope.traversing.namespace import namespaceLookup from zope.traversing.namespace import nsParse _marker = object() class Traversable: implements(ITraversable) security = ClassSecurityInfo() security.declarePublic('absolute_url') def absolute_url(self, relative=0): """Return the absolute URL of the object. This a canonical URL based on the object's physical containment path. It is affected by the virtual host configuration, if any, and can be used by external agents, such as a browser, to address the object. If the relative argument is provided, with a true value, then the value of virtual_url_path() is returned. Some Products incorrectly use '/'+absolute_url(1) as an absolute-path reference. This breaks in certain virtual hosting situations, and should be changed to use absolute_url_path() instead. """ if relative: return self.virtual_url_path() spp = self.getPhysicalPath() try: toUrl = aq_acquire(self, 'REQUEST').physicalPathToURL except AttributeError: return path2url(spp[1:]) return toUrl(spp) security.declarePublic('absolute_url_path') def absolute_url_path(self): """Return the path portion of the absolute URL of the object. This includes the leading slash, and can be used as an 'absolute-path reference' as defined in RFC 2396. """ spp = self.getPhysicalPath() try: toUrl = aq_acquire(self, 'REQUEST').physicalPathToURL except AttributeError: return path2url(spp) or '/' return toUrl(spp, relative=1) or '/' security.declarePublic('virtual_url_path') def virtual_url_path(self): """Return a URL for the object, relative to the site root. If a virtual host is configured, the URL is a path relative to the virtual host's root object. Otherwise, it is the physical path. In either case, the URL does not begin with a slash. """ spp = self.getPhysicalPath() try: toVirt = aq_acquire(self, 'REQUEST').physicalPathToVirtualPath except AttributeError: return path2url(spp[1:]) return path2url(toVirt(spp)) security.declarePrivate('getPhysicalRoot') getPhysicalRoot=Acquired security.declarePublic('getPhysicalPath') def getPhysicalPath(self): """Get the physical path of the object. Returns a path (an immutable sequence of strings) that can be used to access this object again later, for example in a copy/paste operation. getPhysicalRoot() and getPhysicalPath() are designed to operate together. """ path = (self.getId(),) p = aq_parent(aq_inner(self)) if p is not None: path = p.getPhysicalPath() + path return path security.declarePrivate('unrestrictedTraverse') def unrestrictedTraverse(self, path, default=_marker, restricted=False): """Lookup an object by path. path -- The path to the object. May be a sequence of strings or a slash separated string. If the path begins with an empty path element (i.e., an empty string or a slash) then the lookup is performed from the application root. Otherwise, the lookup is relative to self. Two dots (..) as a path element indicates an upward traversal to the acquisition parent. default -- If provided, this is the value returned if the path cannot be traversed for any reason (i.e., no object exists at that path or the object is inaccessible). restricted -- If false (default) then no security checking is performed. If true, then all of the objects along the path are validated with the security machinery. Usually invoked using restrictedTraverse(). """ if not path: return self if isinstance(path, str): # Unicode paths are not allowed path = path.split('/') else: path = list(path) REQUEST = {'TraversalRequestNameStack': path} path.reverse() path_pop = path.pop if len(path) > 1 and not path[0]: # Remove trailing slash path_pop(0) if restricted: validate = getSecurityManager().validate if not path[-1]: # If the path starts with an empty string, go to the root first. path_pop() obj = self.getPhysicalRoot() if restricted: validate(None, None, None, obj) # may raise Unauthorized else: obj = self # import time ordering problem from webdav.NullResource import NullResource resource = _marker try: while path: name = path_pop() __traceback_info__ = path, name if name[0] == '_': # Never allowed in a URL. raise NotFound, name if name == '..': next = aq_parent(obj) if next is not None: if restricted and not validate(obj, obj, name, next): raise Unauthorized(name) obj = next continue bobo_traverse = getattr(obj, '__bobo_traverse__', None) try: if name and name[:1] in '@+' and name != '+' and nsParse(name)[1]: # Process URI segment parameters. ns, nm = nsParse(name) try: next = namespaceLookup( ns, nm, obj, aq_acquire(self, 'REQUEST')) if IAcquirer.providedBy(next): next = next.__of__(obj) if restricted and not validate( obj, obj, name, next): raise Unauthorized(name) except LocationError: raise AttributeError(name) else: next = UseTraversalDefault # indicator try: if bobo_traverse is not None: next = bobo_traverse(REQUEST, name) if restricted: if aq_base(next) is not next: # The object is wrapped, so the acquisition # context is the container. container = aq_parent(aq_inner(next)) elif getattr(next, 'im_self', None) is not None: # Bound method, the bound instance # is the container container = next.im_self elif getattr(aq_base(obj), name, _marker) is next: # Unwrapped direct attribute of the object so # object is the container container = obj else: # Can't determine container container = None # If next is a simple unwrapped property, its # parentage is indeterminate, but it may have # been acquired safely. In this case validate # will raise an error, and we can explicitly # check that our value was acquired safely. try: ok = validate(obj, container, name, next) except Unauthorized: ok = False if not ok: if (container is not None or guarded_getattr(obj, name, _marker) is not next): raise Unauthorized(name) except UseTraversalDefault: # behave as if there had been no '__bobo_traverse__' bobo_traverse = None if next is UseTraversalDefault: if getattr(aq_base(obj), name, _marker) is not _marker: if restricted: next = guarded_getattr(obj, name) else: next = getattr(obj, name) else: try: next = obj[name] # The item lookup may return a NullResource, # if this is the case we save it and return it # if all other lookups fail. if isinstance(next, NullResource): resource = next raise KeyError(name) except (AttributeError, TypeError): # Raise NotFound for easier debugging # instead of AttributeError: __getitem__ # or TypeError: not subscriptable raise NotFound(name) if restricted and not validate( obj, obj, None, next): raise Unauthorized(name) except (AttributeError, NotFound, KeyError), e: # Try to look for a view next = queryMultiAdapter((obj, aq_acquire(self, 'REQUEST')), Interface, name) if next is not None: if IAcquirer.providedBy(next): next = next.__of__(obj) if restricted and not validate(obj, obj, name, next): raise Unauthorized(name) elif bobo_traverse is not None: # Attribute lookup should not be done after # __bobo_traverse__: raise e else: # No view, try acquired attributes try: if restricted: next = guarded_getattr(obj, name, _marker) else: next = getattr(obj, name, _marker) except AttributeError: raise e if next is _marker: # If we have a NullResource from earlier use it. next = resource if next is _marker: # Nothing found re-raise error raise e obj = next return obj except ConflictError: raise except: if default is not _marker: return default else: raise security.declarePublic('restrictedTraverse') def restrictedTraverse(self, path, default=_marker): # Trusted code traversal code, always enforces securitys return self.unrestrictedTraverse(path, default, restricted=True) InitializeClass(Traversable) def path2url(path): return '/'.join(map(quote, path)) zope2.13-2.13.21/source/Zope2/src/OFS/metadirectives.py0000644000175000017500000000426412214017421021236 0ustar arnauarnaufrom zope.interface import Interface from zope.security.zcml import Permission from zope.configuration.fields import GlobalObject from zope.configuration.fields import Bool from zope.schema import ASCII class IDeprecatedManageAddDeleteDirective(Interface): """Call manage_afterAdd & co for these contained content classes. """ class_ = GlobalObject( title=u"Class", required=True, ) class IRegisterClassDirective(Interface): """registerClass directive schema. Register content with Zope 2. """ class_ = GlobalObject( title=u'Instance Class', description=u'Dotted name of the class that is registered.', required=True ) meta_type = ASCII( title=u'Meta Type', description=u'A human readable unique identifier for the class.', required=True ) permission = Permission( title=u'Add Permission', description=u'The permission for adding objects of this class.', required=True ) addview = ASCII( title=u'Add View ID', description=u'The ID of the add view used in the ZMI. Consider this ' u'required unless you know exactly what you do.', default=None, required=False ) icon = ASCII( title=u'Icon ID', description=u'The ID of the icon used in the ZMI.', default=None, required=False ) global_ = Bool( title=u'Global scope?', description=u'If "global" is False the class is only available in ' u'containers that explicitly allow one of its interfaces.', default=True, required=False ) class IRegisterPackageDirective(Interface): """Registers the given Python package which at a minimum fools zope2 into thinking of it as a zope2 product. """ package = GlobalObject( title=u'Target package', required=True ) initialize = GlobalObject( title=u'Initialization function to invoke', description=u'The dotted name of a function that will get invoked ' u'with a ProductContext instance', required=False ) zope2.13-2.13.21/source/Zope2/src/OFS/dtmlmethod.gif0000644000175000017500000000156112214017421020501 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,NH°`Á$¨0áB‡6Œhp"D 4@#E… t|¸Q¤I „ìˆÒ _¶,øæ˜$kÞÄ™sçÌ”').search, error_log_url=''): try: if error_type is None: error_type =sys.exc_info()[0] if error_value is None: error_value=sys.exc_info()[1] # allow for a few different traceback options if tb is None and error_tb is None: tb=sys.exc_info()[2] if type(tb) is not str and (error_tb is None): error_tb = pretty_tb(error_type, error_value, tb) elif type(tb) is str and not error_tb: error_tb = tb if hasattr(self, '_v_eek'): # Stop if there is recursion. raise error_type, error_value, tb self._v_eek = 1 if hasattr(error_type, '__name__'): error_name = error_type.__name__ else: error_name = 'Unknown' if not error_message: try: s = ustr(error_value) except: s = error_value try: match = tagSearch(s) except TypeError: match = None if match is not None: error_message=error_value if client is None: client = self if not REQUEST: REQUEST = aq_acquire(self, 'REQUEST') try: s = aq_acquire(client, 'standard_error_message') # For backward compatibility, we pass 'error_name' as # 'error_type' here as historically this has always # been a string. kwargs = {'error_type': error_name, 'error_value': error_value, 'error_tb': error_tb, 'error_traceback': error_tb, 'error_message': xml_escape(str(error_message)), 'error_log_url': error_log_url} if getattr(aq_base(s), 'isDocTemp', 0): v = s(client, REQUEST, **kwargs) elif callable(s): v = s(**kwargs) else: v = HTML.__call__(s, client, REQUEST, **kwargs) except: logger.error( 'Exception while rendering an error message', exc_info=True ) try: strv = repr(error_value) # quotes tainted strings except: strv = ('' % str(type(error_value).__name__)) v = strv + ( (" (Also, the following error occurred while attempting " "to render the standard error message, please see the " "event log for full details: %s)")%( html_quote(sys.exc_info()[1]), )) # If we've been asked to handle errors, just return the rendered # exception and let the ZPublisher Exception Hook deal with it. return error_type, v, tb finally: if hasattr(self, '_v_eek'): del self._v_eek tb = None def manage(self, URL1): """ """ raise Redirect, "%s/manage_main" % URL1 # This keeps simple items from acquiring their parents # objectValues, etc., when used in simple tree tags. def objectValues(self, spec=None): return () objectIds=objectItems=objectValues # FTP support methods def manage_FTPstat(self,REQUEST): """Psuedo stat, used by FTP for directory listings. """ from AccessControl.User import nobody mode=0100000 if (hasattr(aq_base(self),'manage_FTPget')): try: if getSecurityManager().validate( None, self, 'manage_FTPget', self.manage_FTPget): mode=mode | 0440 except Unauthorized: pass if nobody.allowed( self.manage_FTPget, getRoles(self, 'manage_FTPget', self.manage_FTPget, ()), ): mode=mode | 0004 # check write permissions if hasattr(aq_base(self),'PUT'): try: if getSecurityManager().validate(None, self, 'PUT', self.PUT): mode=mode | 0220 except Unauthorized: pass if nobody.allowed( self.PUT, getRoles(self, 'PUT', self.PUT, ()), ): mode=mode | 0002 # get size if hasattr(aq_base(self), 'get_size'): size=self.get_size() elif hasattr(aq_base(self),'manage_FTPget'): size=len(self.manage_FTPget()) else: size=0 # get modification time if hasattr(aq_base(self), 'bobobase_modification_time'): mtime=self.bobobase_modification_time().timeTime() else: mtime=time.time() # get owner and group owner=group='Zope' if hasattr(aq_base(self), 'get_local_roles'): for user, roles in self.get_local_roles(): if 'Owner' in roles: owner=user break return marshal.dumps((mode,0,0,1,owner,group,size,mtime,mtime,mtime)) def manage_FTPlist(self,REQUEST): """Directory listing for FTP. In the case of non-Foldoid objects, the listing should contain one object, the object itself. """ from App.Common import is_acquired # check to see if we are being acquiring or not ob=self while 1: if is_acquired(ob): raise ValueError('FTP List not supported on acquired objects') if not hasattr(ob,'aq_parent'): break ob = aq_parent(ob) stat=marshal.loads(self.manage_FTPstat(REQUEST)) id = self.getId() return marshal.dumps((id,stat)) def __len__(self): return 1 def __repr__(self): """Show the physical path of the object and its context if available. """ try: path = '/'.join(self.getPhysicalPath()) except: return Base.__repr__(self) context_path = None context = aq_parent(self) container = aq_parent(aq_inner(self)) if aq_base(context) is not aq_base(container): try: context_path = '/'.join(context.getPhysicalPath()) except: context_path = None res = '<%s' % self.__class__.__name__ res += ' at %s' % path if context_path: res += ' used for %s' % context_path res += '>' return res InitializeClass(Item) class Item_w__name__(Item): """Mixin class to support common name/id functions""" implements(IItemWithName) def getId(self): """Return the id of the object as a string. """ return self.__name__ def title_or_id(self): """Return the title if it is not blank and the id otherwise. """ return self.title or self.__name__ def title_and_id(self): """Return the title if it is not blank and the id otherwise. If the title is not blank, then the id is included in parens. """ t=self.title return t and ("%s (%s)" % (t,self.__name__)) or self.__name__ def _setId(self, id): self.__name__=id def getPhysicalPath(self): """Get the physical path of the object. Returns a path (an immutable sequence of strings) that can be used to access this object again later, for example in a copy/paste operation. getPhysicalRoot() and getPhysicalPath() are designed to operate together. """ path = (self.__name__,) p = aq_parent(aq_inner(self)) if p is not None: path = p.getPhysicalPath() + path return path def pretty_tb(t, v, tb, as_html=1): tb = format_exception(t, v, tb, as_html=as_html) tb = '\n'.join(tb) return tb class SimpleItem(Item, Persistent, Implicit, RoleManager, ): # Blue-plate special, Zope Masala """Mix-in class combining the most common set of basic mix-ins """ implements(ISimpleItem) security = ClassSecurityInfo() security.setPermissionDefault(View, ('Manager',)) manage_options=Item.manage_options+( {'label': 'Security', 'action': 'manage_access'}, ) InitializeClass(SimpleItem) zope2.13-2.13.21/source/Zope2/src/App/0000755000175000017500000000000012214017421015737 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/App/Common.py0000644000175000017500000001101112214017421017533 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Commonly used utility functions.""" import os import sys import time # Legacy API for this module; 3rd party code may use this. from os.path import realpath # These are needed because the various date formats below must # be in english per the RFCs. That means we can't use strftime, # which is affected by different locale settings. weekday_abbr = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] weekday_full = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] def iso8601_date(ts=None): # Return an ISO 8601 formatted date string, required # for certain DAV properties. # '2000-11-10T16:21:09-08:00 if ts is None: ts=time.time() return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(ts)) def rfc850_date(ts=None): # Return an HTTP-date formatted date string. # 'Friday, 10-Nov-00 16:21:09 GMT' if ts is None: ts=time.time() year, month, day, hh, mm, ss, wd, y, z = time.gmtime(ts) return "%s, %02d-%3s-%2s %02d:%02d:%02d GMT" % ( weekday_full[wd], day, monthname[month], str(year)[2:], hh, mm, ss) def rfc1123_date(ts=None): # Return an RFC 1123 format date string, required for # use in HTTP Date headers per the HTTP 1.1 spec. # 'Fri, 10 Nov 2000 16:21:09 GMT' if ts is None: ts=time.time() year, month, day, hh, mm, ss, wd, y, z = time.gmtime(ts) return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekday_abbr[wd], day, monthname[month], year, hh, mm, ss) def absattr(attr, callable=callable): # Return the absolute value of an attribute, # calling the attr if it is callable. if callable(attr): return attr() return attr def aq_base(ob, getattr=getattr): # Return the aq_base of an object. return getattr(ob, 'aq_base', ob) def is_acquired(ob, hasattr=hasattr, aq_base=aq_base, absattr=absattr): # Return true if this object is not considered to be # a direct subobject of its acquisition parent # Used to prevent acquisition side-affects in FTP traversal # Note that this method is misnamed since parents can (and do) # spoof it. It is not a true measure of whether something is # acquired, it relies on the parent's parent-ness exclusively if not hasattr(ob, 'aq_parent'): # We can't be acquired if we don't have an aq_parent return 0 parent = aq_base(ob.aq_parent) absId = absattr(ob.id) if hasattr(parent, absId): # Consider direct attributes not acquired return 0 if hasattr(parent, '__getitem__'): # Use __getitem__ as opposed to has_key to avoid TTW namespace # issues, and to support the most minimal mapping objects try: # We assume that getitem will not acquire which # is the standard behavior for Zope objects if aq_base(ob.aq_parent[absId]) is aq_base(ob): # This object is an item of the aq_parent, its not acquired return 0 except KeyError: pass if hasattr(aq_base(ob), 'isTopLevelPrincipiaApplicationObject') and \ ob.isTopLevelPrincipiaApplicationObject: # This object the top level return 0 return 1 # This object is acquired by our measure def package_home(globals_dict): __name__=globals_dict['__name__'] m=sys.modules[__name__] if hasattr(m,'__path__'): r=m.__path__[0] elif "." in __name__: r=sys.modules[__name__[:__name__.rfind('.')]].__path__[0] else: r=__name__ return os.path.abspath(r) # We really only want the 3-argument version of getattr: attrget = getattr def Dictionary(**kw): return kw # Sorry Guido zope2.13-2.13.21/source/Zope2/src/App/tar.py0000644000175000017500000000735612214017421017112 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Simple module for writing tar files """ import sys, time, zlib try: from newstruct import pack except: from struct import pack def oct8(i): i=oct(i) return '0'*(6-len(i))+i+' \0' def oct12(i): i=oct(i) v = '0'*(11-len(i))+i+' ' if len(v) > 12: left = v[:-12] for c in left: if c != '0': raise ValueError, 'value too large for oct12' return v[-12:] return v def pad(s,l): ls=len(s) if ls >= l: raise ValueError, 'value, %s, too wide for field (%d)' % (s,l) return s+'\0'*(l-ls) class TarEntry: def __init__(self, path, data, mode=0644, uid=0, gid=0, mtime=None, typeflag='0', linkname='', uname='jim', gname='system', prefix=''): "Initialize a Tar archive entry" self.data=data if mtime is None: mtime=int(time.time()) header=''.join([ pad(path, 100), oct8(mode), oct8(uid), oct8(gid), oct12(len(data)), oct12(mtime), ' ' * 8, typeflag, pad(linkname, 100), 'ustar\0', '00', pad(uname, 32), pad(gname, 32), '000000 \0', '000000 \0', pad(prefix, 155), '\0'*12, ]) if len(header) != 512: raise ValueError, 'Bad Header Length: %d' % len(header) header=(header[:148]+ oct8(reduce(lambda a,b: a+b, map(ord,header)))+ header[156:]) self.header=header def __str__(self): data=self.data l=len(data) if l%512: data=data+'\0'*(512-l%512) return self.header+data def tar(entries): r=[] ra=r.append for name, data in entries: ra(str(TarEntry(name,data))) ra('\0'*1024) return ''.join(r) def tgz(entries): c=zlib.compressobj() compress=c.compress r=[] ra=r.append for name, data in entries: ra(compress(str(TarEntry(name,data)))) ra(compress('\0'*1024)) ra(c.flush()) return ''.join(r) class tgzarchive: def __init__(self, name, time=None): self._f=gzFile('%s.tar' % name, time) def add(self, name, data): self._f.write(str(TarEntry(name,data))) def finish(self): self._f.write('\0'*1024) def __str__(self): return self._f.getdata() class gzFile: _l=0 _crc=zlib.crc32("") def __init__(self, name, t=None): self._c=zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0) if t is None: t=time.time() self._r=['\037\213\010\010', pack(" 0 and last_mod <= mod_since: RESPONSE.setStatus(304) return '' return filestream_iterator(self.path, mode='rb') security.declarePublic('HEAD') def HEAD(self, REQUEST, RESPONSE): """ """ RESPONSE.setHeader('Content-Type', self.content_type) RESPONSE.setHeader('Last-Modified', self.lmh) return '' def __len__(self): # This is bogus and needed because of the way Python tests truth. return 1 def __str__(self): return '' % self.__name__ InitializeClass(ImageFile) zope2.13-2.13.21/source/Zope2/src/App/ApplicationManager.py0000644000175000017500000003410712214017421022054 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """System management components""" from cgi import escape from cStringIO import StringIO from logging import getLogger import os import sys import time import urllib from AccessControl.class_init import InitializeClass from AccessControl.requestmethod import requestmethod from AccessControl.SecurityManagement import getSecurityManager from Acquisition import Implicit from App.CacheManager import CacheManager from App.config import getConfiguration from App.DavLockManager import DavLockManager from App.special_dtml import DTMLFile from App.Undo import UndoSupport from App.version_txt import version_txt from DateTime.DateTime import DateTime from Lifetime import shutdown from OFS.Folder import Folder from OFS.SimpleItem import Item from OFS.SimpleItem import SimpleItem from Product import ProductFolder from Products.PageTemplates.PageTemplateFile import PageTemplateFile from zExceptions import Redirect from ZPublisher import Publish LOG = getLogger('ApplicationManager') try: from thread import get_ident except ImportError: def get_ident(): return 0 class DatabaseManager(Item, Implicit): """Database management (legacy) """ manage = manage_main = DTMLFile('dtml/dbMain', globals()) manage_main._setName('manage_main') id = 'DatabaseManagement' name = title = 'Database Management' meta_type = 'Database Management' icon = 'p_/DatabaseManagement_icon' manage_options=(( {'label':'Database', 'action':'manage_main'}, {'label':'Activity', 'action':'manage_activity'}, {'label':'Cache Parameters', 'action':'manage_cacheParameters'}, {'label':'Flush Cache', 'action':'manage_cacheGC'}, )) # These need to be here rather to make tabs work correctly. This # needs to be revisited. manage_activity = DTMLFile('dtml/activity', globals()) manage_cacheParameters = DTMLFile('dtml/cacheParameters', globals()) manage_cacheGC = DTMLFile('dtml/cacheGC', globals()) InitializeClass(DatabaseManager) class FakeConnection: # Supports the methods of Connection that CacheManager needs def __init__(self, db, parent_jar): self._db = db def db(self): return self._db class DatabaseChooser(SimpleItem): """ Choose which database to view """ meta_type = 'Database Management' name = title = 'Database Management' icon = 'p_/DatabaseManagement_icon' isPrincipiaFolderish = 1 manage_options=( {'label':'Databases', 'action':'manage_main'}, ) manage_main = PageTemplateFile('www/chooseDatabase.pt', globals()) def __init__(self, id): self.id = id def getDatabaseNames(self, quote=False): configuration = getConfiguration() names = configuration.dbtab.listDatabaseNames() names.sort() if quote: return [(name, urllib.quote(name)) for name in names] return names def __getitem__(self, name): configuration = getConfiguration() db = configuration.dbtab.getDatabase(name=name) m = AltDatabaseManager() m.id = name m._p_jar = FakeConnection(db, self.getPhysicalRoot()._p_jar) return m.__of__(self) def __bobo_traverse__(self, request, name): configuration = getConfiguration() if configuration.dbtab.hasDatabase(name): return self[name] return getattr(self, name) def tpValues(self): names = self.getDatabaseNames() res = [] for name in names: m = AltDatabaseManager() m.id = name # Avoid opening the database just for the tree widget. m._p_jar = None res.append(m.__of__(self)) return res InitializeClass(DatabaseChooser) # refcount snapshot info _v_rcs = None _v_rst = None class DebugManager(Item, Implicit): """ Debug and profiling information """ manage = manage_main = DTMLFile('dtml/debug', globals()) manage_main._setName('manage_main') id ='DebugInfo' name = title = 'Debug Information' meta_type = name icon = 'p_/DebugManager_icon' manage_options=(( {'label':'Debugging Info', 'action':'manage_main'}, {'label':'Profiling', 'action':'manage_profile'}, )) manage_debug = DTMLFile('dtml/debug', globals()) def refcount(self, n=None, t=(type(Implicit), )): # return class reference info counts = {} for m in sys.modules.values(): for sym in dir(m): ob = getattr(m, sym) if type(ob) in t: counts[ob] = sys.getrefcount(ob) pairs = [] for ob, v in counts.items(): if hasattr(ob, '__module__'): name = '%s.%s' % (ob.__module__, ob.__name__) else: name = '%s' % ob.__name__ pairs.append((v, name)) pairs.sort() pairs.reverse() if n is not None: pairs = pairs[:n] return pairs def refdict(self): counts = {} for v, n in self.refcount(): counts[n] = v return counts def rcsnapshot(self): global _v_rcs global _v_rst _v_rcs = self.refdict() _v_rst = DateTime() def rcdate(self): return _v_rst def rcdeltas(self): if _v_rcs is None: self.rcsnapshot() nc = self.refdict() rc = _v_rcs rd = [] for n, c in nc.items(): try: prev = rc.get(n, 0) if c > prev: rd.append((c - prev, (c, prev, n))) except Exception: pass rd.sort() rd.reverse() return [{'name': n[1][2], 'delta': n[0], 'pc': n[1][1], 'rc': n[1][0], } for n in rd] def dbconnections(self): import Globals # for data return Globals.DB.connectionDebugInfo() # Profiling support manage_profile = DTMLFile('dtml/profile', globals()) def manage_profile_stats(self, sort='time', limit=200, stripDirs=1, mode='stats'): """Return profile data if available """ stats = getattr(sys, '_ps_', None) if stats is None: return None if stripDirs: from copy import copy stats = copy(stats) stats.strip_dirs() stats.sort_stats(sort) stats.stream = output = StringIO() getattr(stats, 'print_%s' % mode)(limit) return output.getvalue() def manage_profile_reset(self): """ Reset profile data """ Publish._pstat = sys._ps_ = None def manage_getSysPath(self): return list(sys.path) InitializeClass(DebugManager) class ApplicationManager(Folder,CacheManager): """System management """ __roles__ = ('Manager',) isPrincipiaFolderish = 1 Database = DatabaseChooser('Database') #DatabaseManager() DebugInfo = DebugManager() DavLocks = DavLockManager() manage = manage_main = DTMLFile('dtml/cpContents', globals()) manage_main._setName('manage_main') _objects=( {'id': 'Database', 'meta_type': Database.meta_type}, {'id': 'DavLocks', 'meta_type': DavLocks.meta_type}, {'id': 'Products', 'meta_type': 'Product Management'}, {'id': 'DebugInfo', 'meta_type': DebugInfo.meta_type}, ) manage_options=( ({'label':'Contents', 'action':'manage_main'}, ) + UndoSupport.manage_options ) id = 'Control_Panel' name = title = 'Control Panel' meta_type = 'Control Panel' icon = 'p_/ControlPanel_icon' process_id = os.getpid() process_start = int(time.time()) # Disable some inappropriate operations manage_addObject = None manage_delObjects = None manage_addProperty = None manage_editProperties = None manage_delProperties = None def __init__(self): self.Products = ProductFolder() def _canCopy(self, op=0): return 0 def _init(self): pass def version_txt(self): if not hasattr(self, '_v_version_txt'): self._v_version_txt = version_txt() return self._v_version_txt def sys_version(self): return sys.version def sys_platform(self): return sys.platform def manage_app(self, URL2): """Return to the main management screen""" raise Redirect, URL2+'/manage' def process_time(self, _when=None): if _when is None: _when = time.time() s = int(_when) - self.process_start d = int(s / 86400) s = s - (d * 86400) h = int(s / 3600) s = s -(h * 3600) m = int(s / 60) s = s - (m * 60) d = d and ('%d day%s' % (d, (d != 1 and 's' or ''))) or '' h = h and ('%d hour%s' % (h, (h != 1 and 's' or ''))) or '' m = m and ('%d min' % m) or '' s = '%d sec' % s return '%s %s %s %s' % (d, h, m, s) def thread_get_ident(self): return get_ident() def db_name(self): return self._p_jar.db().getName() def db_size(self): s = self._p_jar.db().getSize() if type(s) is str: return s if s >= 1048576.0: return '%.1fM' % (s/1048576.0) return '%.1fK' % (s/1024.0) if 'ZMANAGED' in os.environ: manage_restartable = 1 @requestmethod('POST') def manage_restart(self, URL1, REQUEST=None): """ Shut down the application for restart. """ try: user = '"%s"' % getSecurityManager().getUser().getUserName() except: user = 'unknown user' LOG.info("Restart requested by %s" % user) shutdown(1) return """ Zope is restarting """ % escape(URL1, 1) @requestmethod('POST') def manage_shutdown(self, REQUEST=None): """Shut down the application""" try: user = '"%s"' % getSecurityManager().getUser().getUserName() except: user = 'unknown user' LOG.info("Shutdown requested by %s" % user) shutdown(0) return """ Zope is shutting down """ @requestmethod('POST') def manage_pack(self, days=0, REQUEST=None, _when=None): """Pack the database""" if _when is None: _when = time.time() t = _when - (days * 86400) db = self._p_jar.db() t = db.pack(t) if REQUEST is not None: REQUEST['RESPONSE'].redirect( REQUEST['URL1'] + '/manage_workspace') return t def revert_points(self): return () def version_list(self): # Return a list of currently installed products/versions cfg = getConfiguration() product_dir = os.path.join(cfg.softwarehome,'Products') product_names = os.listdir(product_dir) product_names.sort() info = [] for product_name in product_names: package_dir = os.path.join(product_dir, product_name) if not os.path.isdir(package_dir): continue version_txt = None for name in ('VERSION.TXT', 'VERSION.txt', 'version.txt'): v = os.path.join(package_dir, name) if os.path.exists(v): version_txt = v break if version_txt is not None: file = open(version_txt, 'r') data = file.readline() file.close() info.append(data.strip()) return info def getSOFTWARE_HOME(self): cfg = getConfiguration() return getattr(cfg, 'softwarehome', None) def getZOPE_HOME(self): cfg = getConfiguration() return getattr(cfg, 'zopehome', None) def getINSTANCE_HOME(self): return getConfiguration().instancehome def getCLIENT_HOME(self): return getConfiguration().clienthome def getServers(self): # used only for display purposes # return a sequence of two-tuples. The first element of # each tuple is the service name, the second is a string repr. of # the port/socket/other on which it listens from asyncore import socket_map l = [] for k,v in socket_map.items(): # this is only an approximation if hasattr(v, 'port'): type = str(getattr(v, '__class__', 'unknown')) port = v.port l.append((str(type), 'Port: %s' % port)) return l def objectIds(self, spec=None): """ this is a patch for pre-2.4 Zope installations. Such installations don't have an entry for the WebDAV LockManager introduced in 2.4. """ meta_types = [x.get('meta_type', None) for x in self._objects] if not self.DavLocks.meta_type in meta_types: lst = list(self._objects) lst.append({'id': 'DavLocks', 'meta_type': self.DavLocks.meta_type}) self._objects = tuple(lst) return Folder.objectIds(self, spec) class AltDatabaseManager(DatabaseManager, CacheManager): """ Database management DBTab-style """ db_name = ApplicationManager.db_name.im_func db_size = ApplicationManager.db_size.im_func manage_pack = ApplicationManager.manage_pack.im_func zope2.13-2.13.21/source/Zope2/src/App/Permission.py0000644000175000017500000000261612214017421020446 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## '''Zope registerable permissions ''' from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import Implicit from OFS.role import RoleManager from OFS.SimpleItem import Item from Persistence import Persistent class Permission(RoleManager, Persistent, Implicit, Item ): """Model Permission meta-data """ meta_type = 'Zope Permission' icon = 'p_/Permission_icon' index_html = None security = ClassSecurityInfo() manage_options=( RoleManager.manage_options + Item.manage_options ) def __init__(self, id, title, name): self.id=id self.title=title self.name=name InitializeClass(Permission) zope2.13-2.13.21/source/Zope2/src/App/Management.py0000644000175000017500000001513212214017421020367 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Standard management interface support """ from cgi import escape import sys import urllib from zope.interface import implements from AccessControl import getSecurityManager, Unauthorized from AccessControl import ClassSecurityInfo from AccessControl.class_init import InitializeClass from AccessControl.Permissions import view_management_screens from App.interfaces import INavigation from App.special_dtml import HTMLFile from App.special_dtml import DTMLFile from ExtensionClass import Base from zExceptions import Redirect class Tabs(Base): """Mix-in provides management folder tab support.""" security = ClassSecurityInfo() security.declarePublic('manage_tabs') manage_tabs=DTMLFile('dtml/manage_tabs', globals()) manage_options =() security.declarePublic('filtered_manage_options') def filtered_manage_options(self, REQUEST=None): validate=getSecurityManager().validate result=[] try: options=tuple(self.manage_options) except TypeError: options=tuple(self.manage_options()) for d in options: filter=d.get('filter', None) if filter is not None and not filter(self): continue path=d.get('path', None) if path is None: path=d['action'] o=self.restrictedTraverse(path, None) if o is None: continue result.append(d) return result manage_workspace__roles__=('Authenticated',) def manage_workspace(self, REQUEST): """Dispatch to first interface in manage_options """ options=self.filtered_manage_options(REQUEST) try: m=options[0]['action'] if m=='manage_workspace': raise TypeError except (IndexError, KeyError): raise Unauthorized, ( 'You are not authorized to view this object.') if m.find('/'): raise Redirect, ( "%s/%s" % (REQUEST['URL1'], m)) return getattr(self, m)(self, REQUEST) def tabs_path_default(self, REQUEST, # Static var unquote=urllib.unquote, ): steps = REQUEST._steps[:-1] script = REQUEST['BASEPATH1'] linkpat = '
    %s' out = [] url = linkpat % (escape(script, 1), ' /') if not steps: return url last = steps.pop() for step in steps: script = '%s/%s' % (script, step) out.append(linkpat % (escape(script, 1), escape(unquote(step)))) script = '%s/%s' % (script, last) out.append('%s'% (escape(script, 1), escape(unquote(last)))) return '%s%s' % (url, '/'.join(out)) def tabs_path_info(self, script, path, # Static vars quote=urllib.quote, ): out=[] while path[:1]=='/': path = path[1:] while path[-1:]=='/': path = path[:-1] while script[:1]=='/': script = script[1:] while script[-1:]=='/': script = script[:-1] path=path.split('/')[:-1] if script: path = [script] + path if not path: return '' script='' last=path[-1] del path[-1] for p in path: script="%s/%s" % (script, quote(p)) out.append('%s' % (script, p)) out.append(last) return '/'.join(out) InitializeClass(Tabs) class Navigation(Base): """Basic navigation UI support""" implements(INavigation) security = ClassSecurityInfo() security.declareProtected(view_management_screens, 'manage') manage =DTMLFile('dtml/manage', globals()) security.declareProtected(view_management_screens, 'manage_menu') manage_menu =DTMLFile('dtml/menu', globals()) security.declareProtected(view_management_screens, 'manage_top_frame') manage_top_frame =DTMLFile('dtml/manage_top_frame', globals()) security.declareProtected(view_management_screens, 'manage_page_header') manage_page_header=DTMLFile('dtml/manage_page_header', globals()) security.declareProtected(view_management_screens, 'manage_page_footer') manage_page_footer=DTMLFile('dtml/manage_page_footer', globals()) security.declarePublic('manage_form_title') manage_form_title =DTMLFile('dtml/manage_form_title', globals(), form_title='Add Form', help_product=None, help_topic=None) manage_form_title._setFuncSignature( varnames=('form_title', 'help_product', 'help_topic') ) security.declarePublic('zope_quick_start') zope_quick_start=DTMLFile('dtml/zope_quick_start', globals()) security.declarePublic('manage_copyright') manage_copyright=DTMLFile('dtml/copyright', globals()) security.declarePublic('manage_zmi_logout') def manage_zmi_logout(self, REQUEST, RESPONSE): """Logout current user""" p = getattr(REQUEST, '_logout_path', None) if p is not None: return apply(self.restrictedTraverse(p)) realm=RESPONSE.realm RESPONSE.setStatus(401) RESPONSE.setHeader('WWW-Authenticate', 'basic realm="%s"' % realm, 1) RESPONSE.setBody(""" Logout

    You have been logged out.

    """) return security.declarePublic('manage_zmi_prefs') manage_zmi_prefs=DTMLFile('dtml/manage_zmi_prefs', globals()) # Navigation doesn't have an inherited __class_init__ so doesn't get # initialized automatically. file = DTMLFile('dtml/manage_page_style.css', globals()) Navigation.security.declarePublic('manage_page_style.css') setattr(Navigation, 'manage_page_style.css', file) InitializeClass(Navigation) zope2.13-2.13.21/source/Zope2/src/App/Extensions.py0000644000175000017500000001546312214017421020461 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Standard routines for handling extensions. Extensions currently include external methods and pluggable brains. """ import imp import os import Products from zExceptions import NotFound class FuncCode: def __init__(self, f, im=0): self.co_varnames = f.func_code.co_varnames[im:] self.co_argcount = f.func_code.co_argcount - im def __cmp__(self, other): if other is None: return 1 try: return cmp((self.co_argcount, self.co_varnames), (other.co_argcount, other.co_varnames)) except: return 1 def _getPath(home, prefix, name, suffixes): dir = os.path.join(home, prefix) if dir == prefix: raise ValueError('The prefix, %s, should be a relative path' % prefix) fn = os.path.join(dir, name) if fn == name: # Paranoia raise ValueError('The file name, %s, should be a simple file name' % name) for suffix in suffixes: if suffix: fqn = "%s.%s" % (fn, suffix) else: fqn = fn if os.path.exists(fqn): return fqn def getPath(prefix, name, checkProduct=1, suffixes=('',), cfg=None): """Find a file in one of several relative locations Arguments: prefix -- The location, relative to some home, to look for the file name -- The name of the file. This must not be a path. checkProduct -- a flag indicating whether product directories should be used as additional hope ares to be searched. This defaults to a true value. If this is true and the name contains a dot, then the text before the dot is treated as a product name and the product package directory is used as anothe rhome. suffixes -- a sequences of file suffixes to check. By default, the name is used without a suffix. cfg -- ease testing (not part of the API) The search takes on multiple homes which are the instance home, the directory containing the directory containing the software home, and possibly product areas. """ dir, ignored = os.path.split(name) if dir: raise ValueError('The file name, %s, should be a simple file name' % name) result = None if checkProduct: dot = name.find('.') if dot > 0: product = name[:dot] extname = name[dot + 1:] for product_dir in Products.__path__: found = _getPath(product_dir, os.path.join(product, prefix), extname, suffixes) if found is not None: return found if cfg is None: import App.config cfg = App.config.getConfiguration() if prefix == "Extensions" and getattr(cfg, 'extensions', None) is not None: found = _getPath(cfg.extensions, '', name, suffixes) if found is not None: return found locations = [cfg.instancehome] softwarehome = getattr(cfg, 'softwarehome', None) if softwarehome is not None: zopehome = os.path.dirname(softwarehome) locations.append(zopehome) for home in locations: found = _getPath(home, prefix, name, suffixes) if found is not None: return found try: dot = name.rfind('.') if dot > 0: realName = name[dot+1:] toplevel = name[:dot] rdot = toplevel.rfind('.') if rdot > -1: module = __import__(toplevel, globals(), {}, toplevel[rdot+1:]) else: module = __import__(toplevel) prefix = os.path.join(module.__path__[0], prefix, realName) for suffix in suffixes: if suffix: fn = "%s.%s" % (prefix, suffix) else: fn = prefix if os.path.exists(fn): return fn except: pass def getObject(module, name, reload=0, # The use of a mutable default is intentional here, # because modules is a module cache. modules={} ): # The use of modules here is not thread safe, however, there is # no real harm in a race condition here. If two threads # update the cache, then one will have simply worked a little # harder than need be. So, in this case, we won't incur # the expense of a lock. old = modules.get(module) if old is not None and name in old and not reload: return old[name] base, ext = os.path.splitext(module) if ext in ('py', 'pyc'): # XXX should never happen; splitext() keeps '.' with the extension prefix = base else: prefix = module path = getPath('Extensions', prefix, suffixes=('','py','pyc')) if path is None: raise NotFound("The specified module, '%s', couldn't be found." % module) __traceback_info__= path, module base, ext = os.path.splitext(path) if ext=='.pyc': file = open(path, 'rb') binmod = imp.load_compiled('Extension', path, file) file.close() module_dict = binmod.__dict__ else: try: execsrc = open(path) except: raise NotFound("The specified module, '%s', " "couldn't be opened." % module) module_dict = {} exec execsrc in module_dict if old is not None: # XXX Accretive?? old.update(module_dict) else: modules[module] = module_dict try: return module_dict[name] except KeyError: raise NotFound("The specified object, '%s', was not found " "in module, '%s'." % (name, module)) class NoBrains: pass def getBrain(module, class_name, reload=0, modules=None): """ Check/load a class from an extension. """ if not module and not class_name: return NoBrains if modules is None: c=getObject(module, class_name, reload) else: c=getObject(module, class_name, reload, modules=modules) if getattr(c, '__bases__', None) is None: raise ValueError('%s, is not a class' % class_name) return c zope2.13-2.13.21/source/Zope2/src/App/RefreshFuncs.py0000644000175000017500000002345012214017421020712 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## ''' Functions for refreshing products. ''' from logging import getLogger import os import sys from time import time from traceback import format_exception from ExtensionClass import Base from Persistence import PersistentMapping LOG = getLogger('RefreshFuncs') global_classes_timestamp = 0 products_mod_times = {} _marker = [] # create a new marker object. refresh_exc_info = {} class dummyClass: pass class dummyClass2(Base): pass def dummyFunc(): pass ClassTypes = (type(dummyClass), type(dummyClass2)) ModuleType = type(sys) FuncType = type(dummyFunc) next_auto_refresh_check = 0 AUTO_REFRESH_INTERVAL = 2 # 2 seconds. # Functions for storing and retrieving the auto-refresh state for # each product. def _getCentralRefreshData(jar, create=0): root = jar.root() if root.has_key('RefreshData'): rd = root['RefreshData'] else: rd = PersistentMapping() if create: root['RefreshData'] = rd return rd def isAutoRefreshEnabled(jar, productid): rd = _getCentralRefreshData(jar) ids = rd.get('auto', None) if ids: return ids.get(productid, 0) else: return 0 def enableAutoRefresh(jar, productid, enable): productid = str(productid) rd = _getCentralRefreshData(jar, 1) ids = rd.get('auto', None) if ids is None: if enable: rd['auto'] = ids = PersistentMapping() else: return if enable: ids[productid] = 1 else: if ids.has_key(productid): del ids[productid] def listAutoRefreshableProducts(jar): rd = _getCentralRefreshData(jar) auto = rd.get('auto', None) if auto: ids = [] for k, v in auto.items(): if v: ids.append(k) return ids else: return () def getDependentProducts(jar, productid): rd = _getCentralRefreshData(jar) products = rd.get('products', None) if products is None: return () product = products.get(productid, None) if product is None: return () return product.get('dependent_products', ()) def setDependentProducts(jar, productid, dep_ids): productid = str(productid) rd = _getCentralRefreshData(jar, 1) products = rd.get('products', None) if products is None: rd['products'] = products = PersistentMapping() product = products.get(productid, None) if product is None: products[productid] = product = PersistentMapping() product['dependent_products'] = tuple(map(str, dep_ids)) # Functions for performing refresh. def getReloadVar(module): reload_var = getattr(module, '__refresh_module__', _marker) if reload_var is _marker: reload_var = getattr(module, '__reload_module__', _marker) if reload_var is _marker: reload_var = 1 return reload_var def listRefreshableModules(productid): prefix = "Products.%s" % productid prefixdot = prefix + '.' lpdot = len(prefixdot) rval = [] for name, module in sys.modules.items(): if module and (name == prefix or name[:lpdot] == prefixdot): reload_var = getReloadVar(module) if reload_var: rval.append((name, module)) return rval def logBadRefresh(productid): exc = sys.exc_info() try: LOG.error('Exception while refreshing %s' % productid, exc_info=exc) if hasattr(exc[0], '__name__'): error_type = exc[0].__name__ else: error_type = str(exc[0]) error_value = str(exc[1]) info = ''.join(format_exception(exc[0], exc[1], exc[2], limit=200)) refresh_exc_info[productid] = (error_type, error_value, info) finally: exc = None def performRefresh(jar, productid): '''Attempts to perform a refresh operation. ''' refresh_exc_info[productid] = None setupModTimes(productid) # Refresh again only if changed again. modlist = listRefreshableModules(productid) former_modules = {} try: # Remove modules from sys.modules but keep a handle # on the old modules in case there's a problem. for name, module in modlist: m = sys.modules.get(name, None) if m is not None: former_modules[name] = m del sys.modules[name] # Reimport and reinstall the product. from OFS import Application Application.reimport_product(productid) app = jar.root()['Application'] Application.reinstall_product(app, productid) return 1 except: # Couldn't refresh. Reinstate removed modules. for name, module in former_modules.items(): sys.modules[name] = module raise def performSafeRefresh(jar, productid): try: LOG.info('Refreshing product %s' % productid) if not performRefresh(jar, productid): return 0 except: logBadRefresh(productid) return 0 else: return 1 def performFullRefresh(jar, productid): # Refresh dependent products also. if performSafeRefresh(jar, productid): dep_ids = getDependentProducts(jar, productid) for dep_id in dep_ids: if isAutoRefreshEnabled(jar, dep_id): if not performSafeRefresh(jar, dep_id): return 0 else: return 0 return 1 def getLastRefreshException(productid): return refresh_exc_info.get(productid, None) # Functions for quickly scanning the dates of product modules. def tryFindProductDirectory(productid): import Products path_join = os.path.join isdir = os.path.isdir exists = os.path.exists for products_dir in Products.__path__: product_dir = path_join(products_dir, productid) if not isdir(product_dir): continue if not exists(path_join(product_dir, '__init__.py')): if not exists(path_join(product_dir, '__init__.pyc')): continue return product_dir return None def tryFindModuleFilename(product_dir, filename): # Try different variations of the filename of a module. path_join = os.path.join isdir = os.path.isdir exists = os.path.exists found = None fn = path_join(product_dir, filename + '.py') if exists(fn): found = fn if not found: fn = fn + 'c' if exists(fn): found = fn if not found: fn = path_join(product_dir, filename) if isdir(fn): fn = path_join(fn, '__init__.py') if exists(fn): found = fn else: fn = fn + 'c' if exists(fn): found = fn return found def setupModTimes(productid): mod_times = [] product_dir = tryFindProductDirectory(productid) if product_dir is not None: modlist = listRefreshableModules(productid) path_join = os.path.join exists = os.path.exists for name, module in modlist: splitname = name.split( '.')[2:] if not splitname: filename = '__init__' else: filename = apply(path_join, splitname) found = tryFindModuleFilename(product_dir, filename) if found: try: mtime = os.stat(found)[8] except: mtime = 0 mod_times.append((found, mtime)) products_mod_times[productid] = mod_times def checkModTimes(productid): # Returns 1 if there were changes. mod_times = products_mod_times.get(productid, None) if mod_times is None: # Initialize the mod times. setupModTimes(productid) return 0 for filename, mod_time in mod_times: try: mtime = os.stat(filename)[8] except: mtime = 0 if mtime != mod_time: # Something changed! return 1 return 0 # Functions for performing auto-refresh. def checkAutoRefresh(jar): ''' Returns the IDs of products that need to be auto-refreshed. ''' # Note: this function is NOT allowed to change the database! global next_auto_refresh_check now = time() if next_auto_refresh_check and next_auto_refresh_check > now: # Not enough time has passed. return () next_auto_refresh_check = now + AUTO_REFRESH_INTERVAL rd = _getCentralRefreshData(jar) ids = rd.get('auto', None) if not ids: return () auto_refresh_ids = [] for productid in ids.keys(): if checkModTimes(productid): auto_refresh_ids.append(productid) return auto_refresh_ids def finishAutoRefresh(jar, productids): # This function is allowed to change the database. for productid in productids: performFullRefresh(jar, productid) def autoRefresh(jar): # Must be called before there are any changes made # by the connection to the database! import transaction auto_refresh_ids = checkAutoRefresh(jar) if auto_refresh_ids: finishAutoRefresh(jar, auto_refresh_ids) from ZODB import Connection Connection.resetCaches() transaction.commit() jar._resetCache() transaction.begin() def setupAutoRefresh(jar): # Install hook. from App.ZApplication import connection_open_hooks connection_open_hooks.append(autoRefresh) # Init mod times. checkAutoRefresh(jar) zope2.13-2.13.21/source/Zope2/src/App/class_init.py0000644000175000017500000000147112214017421020444 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Class initialization. """ # BBB from AccessControl.Permission import ApplicationDefaultPermissions from AccessControl.class_init import InitializeClass default__class_init__ = InitializeClass # BBB: old name zope2.13-2.13.21/source/Zope2/src/App/Dialogs.py0000644000175000017500000000411112214017421017670 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Common HTML dialog boxes MessageDialog(title, message, action, [target]) A very simple dialog used to display an HTML page titled title, displaying message message and an OK button. Clicking the OK button will take the browser to the URL specified in action. The *optional* target argument can be used to force a (frames capable) browser to load the URL specified in action into a specific frame. (Specifying '_new' will cause the browser to load the specified URL into a new window, for example). example usage:
        return MessageDialog(title='Just thought you should know...',
                             message='You have wiped out your data.',
                             action='/paid_tech_support/prices.html',
                             target='_top')
        
    """ from App.special_dtml import HTML MessageDialog = HTML(""" &dtml-title;
    TARGET="&dtml-target;">

    !


    """, target='', action='manage_main', title='Changed') zope2.13-2.13.21/source/Zope2/src/App/FactoryDispatcher.py0000644000175000017500000001215512214017421021733 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## # Implement the manage_addProduct method of object managers import sys import types from AccessControl.class_init import InitializeClass from AccessControl.owner import UnownableOwner from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.PermissionMapping import aqwrap from Acquisition import Acquired from Acquisition import aq_base from Acquisition import Implicit from ExtensionClass import Base from OFS.metaconfigure import get_registered_packages def _product_packages(): """Returns all product packages including the regularly defined zope2 packages and those without the Products namespace package. """ import Products packages = {} for x in dir(Products): m = getattr(Products, x) if isinstance(m, types.ModuleType): packages[x] = m for m in get_registered_packages(): packages[m.__name__] = m return packages class Product(Base): """Model a non-persistent product wrapper. """ security = ClassSecurityInfo() meta_type='Product' icon='p_/Product_icon' version='' configurable_objects_=() import_error_=None thisIsAnInstalledProduct = True title = 'This is a non-persistent product wrapper.' def __init__(self, id): self.id=id security.declarePublic('Destination') def Destination(self): "Return the destination for factory output" return self def getProductHelp(self): """Returns the ProductHelp object associated with the Product. """ from HelpSys.HelpSys import ProductHelp return ProductHelp('Help', self.id).__of__(self) InitializeClass(Product) class ProductDispatcher(Implicit): " " # Allow access to factory dispatchers __allow_access_to_unprotected_subobjects__=1 def __getitem__(self, name): return self.__bobo_traverse__(None, name) def __bobo_traverse__(self, REQUEST, name): # Try to get a custom dispatcher from a Python product dispatcher_class=getattr( _product_packages().get(name, None), '__FactoryDispatcher__', FactoryDispatcher) productfolder = self.aq_acquire('_getProducts')() try: product = productfolder._product(name) except AttributeError: # If we do not have a persistent product entry, return product = Product(name) dispatcher=dispatcher_class(product, self.aq_parent, REQUEST) return dispatcher.__of__(self) class FactoryDispatcher(Implicit): """Provide a namespace for product "methods" """ security = ClassSecurityInfo() _owner=UnownableOwner def __init__(self, product, dest, REQUEST=None): product = aq_base(product) self._product=product self._d=dest if REQUEST is not None: try: v=REQUEST['URL'] except KeyError: pass else: v=v[:v.rfind('/')] self._u=v[:v.rfind('/')] security.declarePublic('Destination') def Destination(self): "Return the destination for factory output" return self.__dict__['_d'] # we don't want to wrap the result! security.declarePublic('this') this=Destination security.declarePublic('DestinationURL') def DestinationURL(self): "Return the URL for the destination for factory output" url=getattr(self, '_u', None) if url is None: url=self.Destination().absolute_url() return url def __getattr__(self, name): p=self.__dict__['_product'] d=p.__dict__ if hasattr(p,name) and d.has_key(name): m=d[name] w=getattr(m, '_permissionMapper', None) if w is not None: m=aqwrap(m, aq_base(w), self) return m # Waaa m = 'Products.%s' % p.id if sys.modules.has_key(m) and sys.modules[m]._m.has_key(name): return sys.modules[m]._m[name] raise AttributeError, name # Provide acquired indicators for critical OM methods: _setObject = _getOb = Acquired # Make sure factory methods are unowned: _owner=UnownableOwner # Provide a replacement for manage_main that does a redirection: def manage_main(trueself, self, REQUEST, update_menu=0): """Implement a contents view by redirecting to the true view """ d = update_menu and '/manage_main?update_menu=1' or '/manage_main' REQUEST['RESPONSE'].redirect(self.DestinationURL()+d) InitializeClass(FactoryDispatcher) zope2.13-2.13.21/source/Zope2/src/App/www/0000755000175000017500000000000012214017421016563 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/App/www/store_bar.gif0000644000175000017500000000010412214017421021225 0ustar arnauarnauGIF89a€ÿÿÿÿ!þCreated with The GIMP!ù ,D;zope2.13-2.13.21/source/Zope2/src/App/www/dbManage.gif0000644000175000017500000000155512214017421020756 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,JH° A*Lx‡"(è0¢Å‰+Z„ˆQ`Â;"$°p!I‚AŠL¹q%HŽ(_JŒ)ÓeMš/EŽTÙ°$C;zope2.13-2.13.21/source/Zope2/src/App/www/arrow.jpg0000644000175000017500000000477712214017421020436 0ustar arnauarnauÿØÿàJFIFHHÿíÞPhotoshop 3.08BIMéxHHä@ÿ÷ÿ÷[ (ühht @h0 @C€N' ”dÌVe9CV+SR8BIMíHH8BIMó8BIM 8BIM' 8BIMõH/fflff/ff¡™š2Z5-8BIMøpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿè8BIM8BIM8BIM@@8BIM Ë€€€À¯ÿØÿàJFIFHHÿþ'File written by Adobe Photoshop¨ 4.0ÿîAdobed€ÿÛ„            ÿÀ€€"ÿÝÿÄ?   3!1AQa"q2‘¡±B#$RÁb34r‚ÑC%’Sðáñcs5¢²ƒ&D“TdE£t6ÒUâeò³„ÃÓuãóF'”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷5!1AQaq"2‘¡±B#ÁRÑð3$bár‚’CScs4ñ%¢²ƒ&5ÂÒD“T£dEU6teâò³„ÃÓuãóF”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö'7GWgw‡—§·ÇÿÚ ?îÕ$Uæª<™8+K¶Ç/ËûÜ^®éÅó> ®.!j' [ñðÖ·oZªªÊiÌiUU}RIMqVFIMõ”º$”×L¯* )šŠJÿÐôEâ‹Ú‡¨s~ջȟŸüû§Iu ‰]2‡féì­µMAM›èÒÍËUz»ôz…Ê.©J µ%k¬•´‚ŠÖº²³S¤§¨CUÒSARZJºJÿÑôEá ÝW„(²ôú¶ùOÓú~Ô‹Md£(HoDîú:Þ\íYÍT®ª'Û[ItK g)O rÛz¿¡F\òÖRµHtÓ)©¤†¢t”’SÿÒô5à«èeóÂ'FÏ+ú_EÑ•dunDîé-傆¢¶ÙtŒ¼íMYoz³—(’!iÕÚW>¯§DÕ°åTô«ª^N»U.<¼w¥SWšå=ž_ôáù^‘ZYªÊ•¨ÿÿÓôà«Þ—‚¨òôlò¿¥ôbˆ†’² yATI7…¹àØQCW¥‰ZL¬¥J2®e¼†Ž¤Ž?¾NfëÓø°]JÌZJP)«)ñVŽ’é—º¤V?ÿÔôÕóÒ÷…àê<½<¯é}&DN£lR4uyA$ì™A_WÄ/«óÕzš°’ÕRMiJØ)-%4Vµ–‚"´’‘­…U%?ÿÕôUá+Øž¨s—êÜä£|àÿÝ8ªòÔN‡Š§ŸoOâÐZˆèÊP)­)ßF’*¼­¢±¦¦¶U”ßEW$¥#¦VSMi*I)ÿÖéU5Ø*É$r–zÐI$8êÂ"¶’šÈëEXINZ’¼³SÐ* ú¨’šÊÚº’‘'BCIOÿÙ8BIMÿþ'File written by Adobe Photoshop¨ 4.0ÿîAdobed€ÿÛ„              ÿÀ"ÿÝÿÄ¢  s!1AQa"q2‘¡±B#ÁRÑá3bð$r‚ñ%C4S’¢²csÂ5D'“£³6TdtÃÒâ&ƒ „”EF¤´VÓU(òãóÄÔäôeu…•¥µÅÕåõfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø)9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúm!1AQa"q‘2¡±ðÁÑá#BRbrñ3$4C‚’S%¢c²ÂsÒ5âDƒT“ &6E'dtU7ò£³Ã()Óã󄔤´ÄÔäôeu…•¥µÅÕåõFVfv†–¦¶ÆÖæöGWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúÿÚ ?¢ùêK1ëúV©yÆÞÞúåmÒàžVg_KŸT*¿c&ú'™t}hKegsÌáÁ€ââãiKç(¿ü»Õ53jw“£G ÝåÄðÆ>ÑI%f^Cö~®ùÑ|§äè41 -·r­MÏj•™¨–œÄpƒÇBÌ~Ÿ‹‰_—›ÿÙzope2.13-2.13.21/source/Zope2/src/App/www/productFolder.gif0000644000175000017500000000163712214017421022075 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,|H° A,(€À‡ hø°b‹,xÀñÇL|ðð@ƒS. RÀ&œ<@%Ë‘%O"ØIseË—%kš´ù³bÌ“'}ŽLyt(›.Êœé*P˜BSZ­¨R%TŠV¤ÀhÓªE ;zope2.13-2.13.21/source/Zope2/src/App/www/ltab.gif0000644000175000017500000000014012214017421020167 0ustar arnauarnauGIF89a³™™™fff3f33ÿÿÿÿÿÌÿÿ™ÿÿÿ!ù, ¡Àç œCÎ\;zope2.13-2.13.21/source/Zope2/src/App/www/z_button.jpg0000644000175000017500000000461512214017421021137 0ustar arnauarnauÿØÿàJFIFHHÿÛC  !"$"$ÿÛCÿÀMN"ÿÄ ÿÄA !1AQ"aq‘¡ 2RÁ$3bcrƒ±Ñ#&BDdt‚ÂðÿÄÿÄ,!1ð2AQ‘¡aq±ÁÑáÿÚ ?2ô´µÞ}ζö²Ñr½_x­Å’Ü(MŸïe»²Ÿ :•@zÈ k=&3¥ÅƒÙ“¤³3)+uç–„$u%GÝRwÇj£¶žr4z¬ªü„ il÷ˆÈý¢ŠRG¬ ës7VãÝŠ·×¯‹tŠLjD$©C¤ +öŽŒã‡_6åÁjÒšJm;ŸRpª|¸£çöbq¤U@o1ïôçö‘{àBþÚpœpšvÛUä5÷—(ðJóÓ…¶šëÉj½iשY>’ÑÀòSóIùj™‹}ÞÎ6i8µáùœ…,k–}b¡SÝfÀžÈW\²â÷-J•·ì¹{D6¶ëw6òÿHMµsC‘$Œ˜Ž+º|ÑX'¯Q‘©Ö³¯hÓ–úfÑß‘EžƒÆ‚Q¯ pœhdzWVÂv•¯ZõxÖ~í8¹\!¸Õ•%7寡Éhééu>8=Ø­\­w‡†¦–¼ã>̘ÍÉŽêeÔ…¶´¥I# ƒå¯M^qW*p¨´iµŠ“áˆP˜\‰ˆBT£ðÍÛλX߭Ϩ\•IC¢ÅWu éw äð6yq‘é(ùž‰ß¤îr‡´1mØÎ”=_˜s™a¬-cÞ®ïç¡×ná ]µ Nq!×}kW3Ÿ`À÷iØt ~!²,(6’[NË Áu&)…:éÞHqÜùñ+¡öcSø´—ZHW×VßÂiÀŽ 3§½À¸íëN˜§jZïB2 æ|‡ÏÈu‹=*!’:‰Œj}l6®%p¥#*$à W[¸ÔJ7JxH‘Ó…<ÿûßðÕc¹;½R®<äJJŒh¹Ç'Ø?ÏU‹M˨MKL¶ô©// BVµ¨ø9“¡Ýœ©µ{™dÇ-»l$Žç½j5ªÚ¦ñwMÚhr HÉù’N½Áª\‡"3 Ù* I Ç¢ÙÏR£ÈrÏÄêsbìËÈm¬ð«9L×Ïø‹?u'Þ:jy*–Ôhé;l´²Ûi Hö¯SE¶l:k5±ÑèIa]Øš‰®m%Ðú‹ñÒ£IqÅ䀑é1ŸÊqáœrF²êõre|Q/ Z”̘Òà)ûè9‘îÖšÛ•XÕÛ~ŸZ† ¨ó£7!²}¤(=sò*ðØnÄ]OÔºÀÇéã·½›OQ=Ê!º¼xeN$’F¡Ô—Gzœt°~’jS¨—g\AîÒŠ¥xqe+HøjœZjÕ–¤ð) ·ÄyF}øÓ°­(\ N’Ưn,K6ˆ\~v´á´ÿ›>­W•ÙVºj “P²‚¢Rß zÏ™õ龿T•X¨¹2K«qJ<즛Y¶’î§›ŸSS°¨Ùý qüuχë^Yð×¾KôV6•®µ©zŸ˜Å·¶=~ù«ˆXÙm}bK™ 0ŠŸ'ËFÔlÍÏ„•ÃhÍ©­?9ä3æ9ð'Ô9žY'NV 6—E§±K¤Ãf6¾ËMŽY=I'™'Ì’Ož¯:$w ZÛÃë“2Û·¤ªjö²šl¯»#—– UšwváIOËEÓ1€¥@Õu°”É^Ž—‹l7TÛý)´þ0¦å£aCF‡dùnÍìíf¾òŠ–!)¼Ÿ$:´’FƒnÒò͹8[òøñêBN~j5»4Òœ¢ì5Ô”¬Sé¨ï sÿzÄ.~_Ì®0Ñ~±“µÕˆåù²õH°Úï*4ìNˆ2T¤Ä‘ë)ÈÖiKŸ.DF!¼â‹QKhðO2z{õ±jI)PD±6Vu‹t¿}[Jíùî—$!ȈêŽH#î(ôòÑ’4ïIr7ÖQ´Ç)5r鈚Ë8ïc)„©cï #ÇœGÞÙQR–Û¶¢ ¥?YP€9 0ÛZ\À" %8>ÒVÂá©Ý6´úA]–¬ÿµGô×NªÜ®µ0(&uGYË ´MR"‚›¶ ’<ä¯RšGkûžœY´iJÇœ‡5ùÛ´ªÞ¦à'Nñm+)xÿ Ò¶:u³âÞÞgI•º±Àž¬v°¼n#þIR›–D·?¦£—¶úM¦Ô]‰ @[ˆfJ‡už€’ž¸ç©X³ì3ù/HÏüTŸÃUFêVít;ù/eÛt™I ­èÐZ¢@h€IpžY<9ôÃ#c×È¿IÞ#Nzj›ó¼võ ¸*‹J y´/Œ2ÂOΓϽÉÓh̵;qØm-´Ò„Œ¤ =Ú¢»ì§öaj.³]e&é«¶“ g?TgªXïgšˆå0œ›ç\«¹Ôóªm¹ªpaÔéïÓêš•B o4âr•¤õk§KSâmÍôì‹6<·ëû\ð[d—Iu|+GŸt¿Ý?Òå]¶dóO¹¨s¡¼ÙÁL†‹jö‚FîÖ®kŠ­H¥UØ,Ui°ç4F $2—ÌjõÞPê6ïÚIë ±™•NܪkióRòáñÓ‹»ÁOa¼E§Ëç€Q §ãÌü´vÎØí£˜éuý¿¡ž¥1‚sðÆœhU¶Ô’õ&Ç Eu=L$yé?³N~ßìÅ^Ìíê&ôï‚¿D“”áÃ’T’Ä|~³ªæ¯ÝNså¢Ã³¿g+kkKuªƒˆ®\ü8úâц£g¨e'¡ðã<φ2F¯%- ! JR9×Ö‰eÌçReÕì"ÒÒÒÔ¦óÿÙzope2.13-2.13.21/source/Zope2/src/App/www/chooseDatabase.pt0000644000175000017500000000115612214017421022040 0ustar arnauarnau

    Databases

    Main

    zope2.13-2.13.21/source/Zope2/src/App/www/load_bar.gif0000644000175000017500000000010412214017421021010 0ustar arnauarnauGIF89a€€ÿÿÿ!þCreated with The GIMP!ù ,D;zope2.13-2.13.21/source/Zope2/src/App/www/installedProduct.gif0000644000175000017500000000156112214017421022575 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,NH° Áƒ*,xàÀ† Bœxp"‚‹¸Èñ@‡'Nlð‘!É“I‚èeÊ’!]¾\)°%Ê–4Ø<é1§N™5Š\;zope2.13-2.13.21/source/Zope2/src/App/www/permission.gif0000644000175000017500000000160612214017421021445 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,c àÀƒ Tˆ0á†2„(±àDŠ)Fx £ÇŽ @²$H‚|ôHà@H(Ù²åK È4i“ G3yºTÈp$ÍšC}â<Št¢B£4A:Źòd@;zope2.13-2.13.21/source/Zope2/src/App/www/border.gif0000644000175000017500000000163212214017421020531 0ustar arnauarnauGIF89a÷  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~€€€‚‚‚ƒƒƒ„„„………†††‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŒŒŒŽŽŽ‘‘‘’’’“““”””•••–––———˜˜˜™™™ššš›››œœœžžžŸŸŸ   ¡¡¡¢¢¢£££¤¤¤¥¥¥¦¦¦§§§¨¨¨©©©ªªª«««¬¬¬­­­®®®¯¯¯°°°±±±²²²³³³´´´µµµ¶¶¶···¸¸¸¹¹¹ººº»»»¼¼¼½½½¾¾¾¿¿¿ÀÀÀÁÁÁÂÂÂÃÃÃÄÄÄÅÅÅÆÆÆÇÇÇÈÈÈÉÉÉÊÊÊËËËÌÌÌÍÍÍÎÎÎÏÏÏÐÐÐÑÑÑÒÒÒÓÓÓÔÔÔÕÕÕÖÖÖ×××ØØØÙÙÙÚÚÚÛÛÛÜÜÜÝÝÝÞÞÞßßßàààáááâââãããäääåååæææçççèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüýýýþþþÿÿÿ!ù,wßHp Á‚*LÈp¡Ã†JŒHq¢Á{”(µêemà¿ CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳ§ÏŸ@ƒ J´¨Qž“ZTÊt©Ó¦PÏaÔÈÑãÑ«X³jÝʵ«×¯`ÊK¶¬Y£;zope2.13-2.13.21/source/Zope2/src/App/www/transparent_bar.gif0000644000175000017500000000010412214017421022432 0ustar arnauarnauGIF89a€ÿÿÿÿÿÿ!þCreated with The GIMP!ù ,L;zope2.13-2.13.21/source/Zope2/src/App/www/vManage.gif0000644000175000017500000000154312214017421020633 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,@H° Á‚DX a€…4œøð Ä‰^ÌqcE‚ ?†4è"€5bäHråG‹,;*4I³æÁ€;zope2.13-2.13.21/source/Zope2/src/App/www/factory.gif0000644000175000017500000000155312214017421020725 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,H@` Áƒ (,ˆÐ  6øð D„.th`"ÆŽ9†9’"È‘Rª\¹R Ë—*]Â|)sfK6YÖÌé@@;zope2.13-2.13.21/source/Zope2/src/App/www/PythonPoweredSmall.gif0000644000175000017500000000055112214017421023053 0ustar arnauarnauGIF89a7³ÿÿÿçççÞÞÞÖÖÖ½½½„„„BBB999111)))ÿÞc¥!ù ,7ÿ°ÉI+u8ë­ÿàÄ$žÍs¬Ç£>ÎË®oܪtý¢•s@@á×ú‚Àcp8&‰*&ÌÊ›B);„…\a´¥i®ÇôC9ŽÓÚ,Xº±p~ÁÂ:«oãóqpl#"w`7{}~`pKJNd„ #P.0•>qŽŒY-ƒ„$Pgt™K¦Ns¦.‘%/S¯±ˆ´«9µm­6,087d¯¼2´9¾¤%b¨CšË_Êy{°F‚a,|ØrÝißBÔÖHÌ›‚H|¶ëlŸåV‡naêØ]R«âùoÇv¥,Iƒ¦Õ R=!!cFÖ¬bE, »áJ˜¯(,iØK` Ž.6zÉCž®“­J†@ÉR¥K“-_6ˆ;zope2.13-2.13.21/source/Zope2/src/App/www/DebugManager_icon.gif0000644000175000017500000000156612214017421022613 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,SH°`Á *L¨°!Æðà¡A†3J$ø°bÆ,Ôˆ°¢Â$K9P$Ê” ²0à¥FŽ]Ú\ip&Íš[B,èsèĆ;zope2.13-2.13.21/source/Zope2/src/App/www/zope_button.jpg0000644000175000017500000002210712214017421021637 0ustar arnauarnauÿØÿàJFIFHHÿífPhotoshop 3.08BIM x8BIMéxHHØ(ÿáÿâùFG(üHHØ(d'`8BIMíHH8BIMó8BIM 8BIM' 8BIMõH/fflff/ff¡™š2Z5-8BIMøpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿè8BIM@@8BIM Z€7€R€>ÿØÿàJFIFHHÿþ'File written by Adobe Photoshop¨ 4.0ÿîAdobed€ÿÛ„            ÿÀ7€"ÿÝÿÄ?   3!1AQa"q2‘¡±B#$RÁb34r‚ÑC%’Sðáñcs5¢²ƒ&D“TdE£t6ÒUâeò³„ÃÓuãóF'”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷5!1AQaq"2‘¡±B#ÁRÑð3$bár‚’CScs4ñ%¢²ƒ&5ÂÒD“T£dEU6teâò³„ÃÓuãóF”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö'7GWgw‡—§·ÇÿÚ ?ë~¬}Xú·Õ¾“uÝ' ÛmÂÇ}–?§9ÎuU¹ï{Ý^ç=ÎUþ´¨?Vq[~wGÁ±ö+ǯÕpîñ]žŸèÚ«ÝõËêÇÔŽŒ\=^¡MÇ8xß¼áU —ÇÑc}MßË^~ΕÖ:®uOªenÍ¿WÐ÷4vc ÿGSû•±2sܯ„ ¶ ëÍêŽ'¤ýVé8xÿ˜ü¼P÷8~ó}:ª­5™=Sz–tNƒsG-û‹Z¥WÕŠÌ;+'*ïÖÇýHjŽ_KèØz¶]mq¨.´þEË.‡èÌ1 Ô}m¹Ñ~¸ýOÈ̯ ¬}WÁÁu‡gÚ4zAßËõê§c?¶õèLú«õMígHéîk„µÃ’=Áô×…gu¬;h­ŽºŽ&Ó3ýOÎjÛú¥õç©ý]Ê¢‹—tv½úAä{©°ÿ¢wø5,'/Ò,Rˆýo­ÿÍ?ª¿ùMÿ°´ÿé4¿æŸÕ_ü¦ÀÿØZôš?UË,èY™¸– ÍŶê-l"·Y]üÕÅt/¬ßYpñ±3úˆ³3« <\ä«ß›‘ü팳¶¬-ÿ OüR•ë¿æŸÕ_ü¦ÀÿØZôš_óOê¯þS`ì-?úMbdÿŒ!•n?RÁ7$bæÜËýFVl¥Ùx¯­Î¢—[êúoªÍÞ¥ÿ ªÑþ5(³¦ef¿§>»°=Oµâ›=Õ–œz)ÜïE¿Ïåeú?GÙö{íÿƒIOKÿ4þªÿå6þÂÓÿ¤ÒÿšUò›ÿaiÿÒk'#ë·P£ ›]Ñny@têqÞçÒ×îc¬«*›òñ¨ÝU›v~’šÂ£BgøÀÉvm”·¢äÛ”ü<¼Š–úO«ÒûE–ìÇô= ý[?íO«úþ‹=T”íÿÍ?ª¿ùMÿ°´ÿé4¿æŸÕ_ü¦ÀÿØZôšçþ®}bê=oë^>Eµ»'¤Y‘F(¼ÚÓ5ÔÌ‹kÙMUälÝþ—ôá”s>´gt_¬ýrÛkv_M¤ôêÞÃs‡¡ë‡×¿²Êìõ^í÷{ñÿ›IOEÿ4þªÿå6þÂÓÿ¤ÖoÖ«Vèú·Õ®§¤áUmXY®ÆcÔ×5ͪÇ1ì{kÜ×µÈ?ënFOYCÈÀýJ«êÖÛ½FŒwTr>ÚÛ=·í·f/§³ùëký*½õ³ÿ½gÿeç›SÿÐå32ÝÕúŸL?à:V ÐÛ]ÿ³zëºu¾\ó.¥»Ò½ìvŽutpj•¬õÛè­¸˜–»›ÿ©•R|RËÃÛýöÌŽ;z¿¬ÿ[úoMõŒ;h+ÉÌê{-•µž­®Ñ•ÖßÅ?Eè¹k05 šC‡¯q0?KÞC¿H½{ê¿ÕŽ™€ÓV o>ûeîþ³ÿ’¥1?½?ÉeÊbϦÓþ£>·g¿{€ŸE’?ʳ󿲬õ.›ëc[HÒ[â>Šôþ£Ò+eÁ£ï\v]è¡ËÇÄ >L¸Ä HÞÿÙ­ëPÛ‹ 4×nÍd†ífÏåú1Ì*,èØô¼ñ€jv5€×ê5ô1dîôÞæÿ.¿Meÿ‰ ïØYöæßœýŸæT»Ûm®šŸm® ®°\÷’¬‘",K‚¼ýÓZÅíÄðùا[Ô0s…yy5äÝ}Ž­¯}µÇ§üÞÊÚÝ•íýKK¥ýTèyxý[ ãdRξwå¶×êíÅÙ¥Ÿómßz gë'Ö=çô½3‰Ñ§þ««`ÿ¶WIÔú7 ù7˜ÚÆ%Î?AjƒIKŽrŸê¢H‰ z¸~iÿu|â @õü/£B¿ª9¸ø”:ˬ9uçT÷9»µ3ìõ 6VÆzm§ÛíbfýPé,êWuCò.nMµn™¹¼[«}VÿSÕô¿§…Öòªgå;¿Ü) V6ƒôC½Zm~ïÞý*_Sê!§¨8äôûHmw†´Ø×~í¾˜¡ŸCwø5!ÎD¥F'ôèÿ{÷VûdšØ+¡}Jèý4fàºãcqÝŠÆXàæ¶§Yö­†5ÿÎþ{Þ¥õ;¤gu'õ;­Ï¦Ë˜×Çœxû.ïo¨ßIÍÝú++ßþ.³Ô³›ŸÓ±°.k+Í%ðö–89Íï!u¾¿Òy—[^Fs[{và8ÆæmeõIK˜ˆ2ôȈT€ôíÄ‘ŒšÔz¶ Ü~”áõ“3¬ÚÀÒìz°ñÈt—1®~EÖ=¿›ú[[[âá}lÿįYÿÂ_ùæÄ£ÔsŸÕ°pú}íeYL±ÅЬwÒ{ûŠ¿Ö,~®Ï«½]Ù9-²‘•¹€7_Ð[·èÒÏÎþR#0”ŒDdxO –œ?½ÿtƒ ’‹®¯ÿÑæ>¶ô›úe¬ãG§Ó1\þ4²ºi¡Çoõ_RæéY¹÷µÖ>eÞè$åC×¾ô΃־¢ôÌ êÃê»§c69³UNc¿5Í{Xõä½êç[ú¡”kÉc²:y1F[´ŽÍú;?£˜ Ÿ±|H;aýr56š0ÊÙ h³ÿQ­|ñ±•†e½=®øÚôš£ÓúÎ=  Ât­z³q7?cG‰€ #ò|Ù¸%!óéäÙÿÇs;¨EØ5R×rw¹ß‘‹ê_‘Uô׊ëšË·ñ>ÝþžÏûúÔÉúÏÓ™â³í¹/;k¦–î.wa ú+wê¿øºê™ôuï¬ä7iõ*é€hݺãúß›í÷?Ñÿ·ÂV¤ˆã7(šéed¨ÈxÐzß¨Ý ô/«xO]Íõ¯#_ÒYúG7þ¶ÝµaUú×TêU}^Ä;ˆvKΜSoõXÏò×AŸfexäáR.¼ÈkKƒ@ÐÃÝ»émwæ®w£`u¾›mÙ6ôñ•—IuîÈcLs›·kþ“¾’g2Iáĸd[(ÆRô~ç§÷ÑŠ…ÎÅ”>nÿà»Ý'¦SÒð™‹V¤kcû¹çé=eýg÷gôf?Z]’7º­ŸõOW>ß׿ò¥¿ûÏý&£—ƒ“Öp]^m`È­áøïm‚ÒµûšÖ˜ŒÄeˆãÇ „Nô.)ÅQ±>)ÖõŒ¾g]f}b4Ž‘“ê‰ýöy?cö•*2ú½må`›^Ý ¸öWµßÊ ½ô=›•,þ™Ôºã˜Ì¸Á©۽ àûx÷–~Š¿fý¾ë²ÌËŒŒ¤(DÄÇüiKÒˆF¤ Ô }¤;êæÍ¦íŽÙ¼±#fí¾ï ·szWQê”gÛUXpu•Q¸¹ñÃ]e›v7û*9ý*÷õN•n-`caH~ mo´2~—Ñ[J zÖQÅ d3kÛ½¬2ðæ~õÕo¬™}NÏ«]—àz ­ÖzÌ|~‚Øö4núIØÀ2X˜&w÷8+‚»ú´H“Õ|ºü¼[ŸðŸÿÒë~¬}aÀ§êßI©õf—W…Ž×`f=²Ú«iÙmX¯ªÆÂVýWîúËÑý3ëÓ›é÷ßÓ³vÿÒÃ_7$’ŸLúÃOø§ê™O¸u[:}Óîe8¶5³üªþÆÇÓYô/ñ`×ÍßY¯{?u¸–´ÿœê-ÿ©\RHh_pú¯ÕÿÅÖ ôG¿+%­R¼ì¸2K±ðšõÒÎ^þ‡?ÿqÙßûƾkI!]o«ô§üåéßèsÿ÷ÿ¼iÎ^þ‡?ÿqÙßûƾkI?JÎ^þ‡?ÿqÙßûÆ—üåéßèsÿ÷ÿ¼kæ´’Sô§üåéßèsÿ÷ÿ¼iÎ^þ‡?ÿqÙßûƾkI%?JÎ^þ‡?ÿqÙßûÆ—üåéßèsÿ÷ÿ¼kæ´’Sô§üåéßèsÿ÷ÿ¼k7ë?Ö ¾­õj™Vhu˜Y i~c.ªÆöÛŠÊ«gü%ØÅóêI)ÿÙ8BIMÿþ'File written by Adobe Photoshop¨ 4.0ÿîAdobed@ÿÛ„       ÿÀ2sÿÝÿÄ¢  s!1AQa"q2‘¡±B#ÁRÑá3bð$r‚ñ%C4S’¢²csÂ5D'“£³6TdtÃÒâ&ƒ „”EF¤´VÓU(òãóÄÔäôeu…•¥µÅÕåõfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø)9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúm!1AQa"q‘2¡±ðÁÑá#BRbrñ3$4C‚’S%¢c²ÂsÒ5âDƒT“ &6E'dtU7ò£³Ã()Óã󄔤´ÄÔäôeu…•¥µÅÕåõFVfv†–¦¶ÆÖæöGWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúÿÚ ?ô7äÇäÇäö©ù=ù{©ê—¾Z½Ô¯|µ£\Þ^\èÖO4óXBòI$ fvbY™XîqW›~o~`Î*~ZjÒùCAüªòç<ÿ+.‰¥hš`ŠÙÇQurmÙc#ö•VG_ÛTË¡ˆÈÐæÕ<‚"Þ{çÍ[V•žËò·òËã³»Ñìî®@ðw B}Ô–l#¡—ZóÿàË[—ðñK,4î4 uÿÉ_Ë/5X)eÒ4k[[ŠlO)*V•ýŒÐHn(ÿTÿÅ":è]ñÿúGò_Í_ó‰~‰Ó.|³¢yÆ5-?–õ= LŽäñc 8Ê£üŸŽ›².kåŒÅØC {_ü¨ŸÉü¶žTÿ¸ÿT2¦ÇʉüÿËiåOûißõCwü¨ŸÉü¶žTÿ¸ÿT1WʉüÿËiåOûißõCwü¨ŸÉü¶žTÿ¸ÿT1WʉüÿËiåOûißõCwü¨ŸÉü¶žTÿ¸ÿT1WʉüÿËiåOûißõCwü¨ŸÉü¶žTÿ¸ÿT1Wˆʧü¬ÿ•¡ú?üåÿ¨ÊÊýõ_ÑV^Ôå[}{êü=.>—ÖÒ=:põÿ{OSâÅ_ÿÐ<üÍü÷Ö?/¿çÿ(¿-¼…p üÁów”´ZÞ©£éúgèèQçR7WrcoØ #S/ÃŒÎ@e£.AIä;yCòbÒñ^;Û»½Eç<îÕex!•Îä·¦C±ë»?Ý= =DŸ±ÒKW)€ûÞ‰oùIä]7º »*€Yç2=ŠT³¿ß¾Z4غxÙ2ò>y¯òËAy­<¿¥Áq¨(*¢ß’D®+J²v>F\ø°ŠÖ'œÝmÞó« cÌZÜñjZlS&¹§ºÏcª[ÐJ‡’‘0¥({øæµéõEÉðÆž_W¤óÔ_ùÅÏy¿9ü5·˜JÇçï,ºYkñ­ÖÒ¨éê`ÔØ:·ì•ÍfHQüsv¸æ$6xæ“ÿ+:ãÏÿ™˜Ú>©xú寛¼Á¨j¶§U½/}¦ÙX £Ò ° ð2Èâ¡Û§_€r¦TÚŸ?üå?æu–”ÚÄúv‡©ØÙù{Ë^wÕæ°µ¼Ak¤ëw+ å‘îZÜÂ’ cšª¼c“•³~ʪ/ÿ9{ù†|±çj/*Z.£äø¤Õ¤³– ¥¡õs§äýýD²Gy-ÅÈÛ÷VÎagøUdö_¿ž÷þmòo’£Ñü±o}æ}K^ŒjSÉõ‹y4"ÖÂö Õ4ÝJøC3Çs h^iy~æEd±Vü坿ýÏ“n¼ô¾VÑ-ô ¾Vºt÷3F¦_q4К„)©=Ì‘,#É'ÕìøÌÊ¿ÝÉ•Yåt·sþPÿÎIIyk||Íçs{§£Ci,«¤[x£ig(¤Ë!_³Íºâ¬GÉŸŸŸšIü¹Ó4?OѼÁ¥ù;É>Só5Ķ–×BdЦTµ»´ýq—ë‘Bâj$`E7+nœU}eùsæ-_ÍÞS´óN«V±ë=î‘orDë¤M+5ƒL$v>³ÛúrKö8»ðà¼qW•ÿå\ÿΫÿ¤³ÿÑóM‡˜.<Óæ‹]Vñ‰7—t ŤqYé¶ðž?6›ý™ÍçgÀqÜû-ÝhN¢y?ìv}å=ùcÈ^—Z×ÙJ"ŸJG9°µÏ°²h8X%],¾nüÒüõó?æ-ô–ö„éÚ+7làÙœAÈÎhskIôÃaö—q‹KüSÜ÷tý¬¯òÏþqƒÌZôë¾r†[ =À’ /uº”ë)ëŸåþóýL84w¾M‡s<Ú‚6ƒÓõO"ÛhV«¦ÙZ-¥¤B‰kÅG¿¹=Éë1*<Q"n\ÖÿÎ3jW>Fÿœ¦Òô¸X¦Ÿç :êÊò>ŠÏOÆ~fÇêu¡¬ÖhññÎp7Ê<'µë•š~³©ùHë>yÑôËMkÌGõ»KK$€µ„qúvðÝ.\ªDoîÕý>#âÍ^²8¡ÇÐûÝßgO<ð‰f¡)oC¤[&´òÏ“4#aõLÓÀÍú/ж··05ÐCН%ÔáN{rÌ0 äç™ͳä¿'Ô¤>^Ó šÈ㫱²·­âו.ÞŠïñòÀÉ_MòÇ–ô}>ãIÒ4k *ì»]ØÚ[C¼­"ÜÉj‹"…jÔqÅRMòß˺·•õß+é6¶þ_¶ó ôõ«.Úyn!‘ÇÖÊ*Õ¥ˆÉsñ/¨XoŠ²è¢ŽÒQc†5 hªªŠØ1Wå\ÿΫÿ¤³ÿÒòŒv²ùC^KmDú0Ýhš&®’7½2Þà^¿Þñÿ[7Z¢ß*äé5ø¥!9Ù6-¬jZ÷ž5xm­c’uvôìmPQ@Nû{³§1s垢t9tr´ø¡‚7.}_KþGþShT»¶Öu¥Qó-C,Œ9CjOûäÚïÿòñï³Á¢…äâäÕœ††Á÷ß‘´ëI"«9ƒAM³[©É1'a†1!âÿ›úV ëjOLÛi$Lwuº¨‹Ùàÿ“z[y‡þrÿÊÐZ/8<³aw¨_°è„ÛÊ‹Z•4_ðYƒÚ¼•ݽÈÑB¡}çî~Œêz•ާÝjºœëm§ÙDóÜÜHh©c“ô ÔÂr’ìrdŽ8™HК<‹¦ßÿÎ@~eOù•æ(]<‡åé}/鲂Y#<0èhi,Ýjü"ømÓjf48<}rúã囯hñË´õ'Q~îÑÇúi¥äõÎï<ë^TÑ´ÊÌ©æ¿5ßǤ铸ä õHW” Á`]j?k—ìÓ5}¦ŽYJSú`8‹»í}dðÂ0Çõä<òóLôOÉß"iÖ1Ǭi6þdÕÙG×µn%Ô.î%ý§/p¨'¢­Ê²kóHúI€éú@ù7á콂”Ýxg`­f9ÆÞDqF]eå.÷W©'³óBP?º™à”?†'ùÐþjWå¿.èÞlüþüDZó¨Ô--"´’Ú]øFí@• èËrå–-#DÛNŸ3v†qq_¡KI'Ëš>vòg“o..<“—®.µ¸’æÞÃQá@±<…ʱ®éËöš¿Ý’Ÿï4øòdŒQªâãñ»_ºÕåň“Œ@™ ⟗ãýÊÿÉ.~Yk–£æ•±›^›ë[’îë„ç…ä蜔°›tÁÚ9uÔHBøvä<‚{#–zXK%qovwúš]þò·ü­Ï÷Š/ü˜?¡¾ÛÇ/þUg÷?kì{õÿ+4~>Nÿââÿ?½é¿-‹¸}<æw?ÿÓ#ÿœ„üˆ¾óOüãßåWæ÷•íMƧ¡ù7Cµó ´"²I§®Ÿ $à Ï¢I þCrýŒ´z…uk;èùGËvfê$K j[GÃêÄ$PMJ’’-Esk§ÅÇ ŒëñïtÙóxy.Pü|™Ý—’|Õr­¼ý~î× OºàåßÉþ¨~ßÖ£]ùŒžÃò÷óÔ/ü­Q"þEžð¯Ýõ‘‘þOŸóÛ?=>”¯ÏúN·å-n/<ùr·Ë@b7òêJQPqUQZ±þÁ“Ô‰cÇõpû¹ËíjÓˆË'ÓÅïåÙÿó„¿’¿åÿ•/1<î’ÿŽ|䱸K²Ïsm¦/ÇÈ^¬$”ÒG¢ˆ•¨ÊÃ4s‘<÷=]ÔéÉ5üÞó«ù¹ç‹_É/%ÎF™m ŸÍZ„{Æž‹ÈÔØ¬$ŠŠüS”N©ƒt¸Ž£'3ôÇû—í<óÖçLGaýä½ßñ?îŸAysËÚW•4;/h }3OˆC}Må˜÷f$³7v5Î{.Ye™œ¹—­Á‚qˆ@P‹É¿ç!tÛë7ò_æ5¥»ÝÛy/U[½N—”‚ÊW…¤p:Pz û<¹t6ý•0|LD׉ÖßõºÜÇ(øYÀ±ŠW/êíî^ǦêVÅ…¾©¥ÜGw§]Ƴ[\ÂÁ‘Ñ…Afšp0&2CÑcÉ‘‰°^!ù×*yÿÎ>OüªÑHº¹·Ô#ÖüÂÑüIikn PŽŒÊòQNÿcù×7ž< SÏ-¶áô‹Ìv±üÎlzhnD¸çýÁû;Ò¯,ù_DóGüäæU¾·n÷Á›Ä©<öôfŠ j`t'o·6iãÑâ1=ýÞ}í}<3vŽq1u]Hîî{Ç•ü»åO'kv>\Ó-ôËW³ºyRÝE¾'n¬}ØœÓ ÓË–&d“a襧LJ Æ8ˆŠ<½Ï5ÿœ~ò‘õŸÊ/.êZÇ–tCQŸë¾µåÝ´ó¿ ë„^O$lÆŠ¡EOA›>ÔÔ冦B3r'ù¡Òö&M%(FDñnb ú¤€ú­·ü­ÏîcÿÉ£èý‘ý×üªÏ±ÓìûtÍ—§áÏÿÔõïäb$Ÿ‘¿–ÑÈ¡ã)èjÊ ƒ¦À â¯;ç4o4jwlü£Ô"òƹpÆ[­e?£%‘I‹€&Oì…hüåðÉFî{Dñ "Ãæë¿È¿ùÉß%ÎÐ]ù çWŠ3E¸ÒåŽéJE¾õ´Ç¯˜æøðýîª}Ÿ Ø‘ðâû™'—ÿ)?ç*üÎék¦ùyz(PÖfŽŒüƒšÆ6Ã>Йäøñ}Ìá ˆæIøpý錄%¿ç 4O&kPyëóGTÿyê$´IP2ÎA¸hâ}äe?e˜*/U‰XrÍVL¦FɳÞìáˆDPSe Ï6“òò‚YY|± Êä³»OtY˜š’I›rsf;OR?ì©ÓÅÑÎ1ó—ë[ÿBûù;ÿR¬ò:çþªãü©©þÝú‘ü‰¢ÿS9~¶Wå#ùSÉ–_–tÈì4û¹ ·6êÏ"»•IõYú¨™‰›S“1™²ý>ž&8ã@¥kùQäX^áì,n4´º%®-ôGPÓ-Ü·ZÃgq {ÿ«–þw1« ×ó„eöÈäÜøAÿ2S€ÿK D'^]ò—–¼¥o%·—4È4øçoRá¢ZË+ÔžRÈÕw;õv9N\óÊnfÜœlXcˆý¾þ÷XyK˺^½¨ùžÂÅa×uuEÔo9iV0‚ tgœ  O¦<–lpÉ,‘©}G½5¹·†îÞkK”õ-çFŠT5‘ÁVo¸9H$ òˆ ò,/ÈÏʸPE—Ö8×ì¢]^*ŠšìÙ°=¥¨?ÅöÔê‡cé(}²ýo"ÿy_þVçûÁÿMÿø{ûé¿ã—ÿ*³ûŸ·ÿýçùyùœÿÅÇþ{™ùL]ßÃáó?Góo7ÿÕyþP-ëJÿÇ*ÇþQÏøáÿ¼ÑÿÇ;þ]?åŸþ)ኲýʬUßû•X«¿÷*±WîUb®ÿܪÅ]ÿ¹UŠ»ÿr«wþåV*ïýʬUßû•X«¿÷*±WîUb®ÿܪÅ^ÿMwþ¼ü¥ö÷ÿ”Wþê¿÷cÅ_ÿÙzope2.13-2.13.21/source/Zope2/src/App/www/brokenProduct.gif0000644000175000017500000000156012214017421022075 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,MH°`Á(\h0a "*4x bD‚.d˜°¡E‰3Dø±AF‘!^Äxp%ÈŽ/ZDˆ2¥J‰5C2䨰§ÏŸ@ƒ ;zope2.13-2.13.21/source/Zope2/src/App/www/undo_icon.gif0000644000175000017500000000154012214017421021227 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,=H°àÀ Lh°¡C F”(PaÅ‹-Z„ˆ1ãÆŠŠéáÈ!)f|2åA•%_žœX0 ;zope2.13-2.13.21/source/Zope2/src/App/www/help.jpg0000644000175000017500000000535012214017421020220 0ustar arnauarnauÿØÿàJFIFHHÿízPhotoshop 3.08BIMéxHHä@ÿ÷ÿ÷[ (ühht @h0 @C€N' ”dÌVe9CV+SR8BIMíHH8BIMó8BIM 8BIM' 8BIMõH/fflff/ff¡™š2Z5-8BIMøpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿè8BIM8BIM8BIM@@8BIM M€€€À1ÿØÿàJFIFHHÿþ'File written by Adobe Photoshop¨ 4.0ÿîAdobed€ÿÛ„            ÿÀ€€"ÿÝÿÄ?   3!1AQa"q2‘¡±B#$RÁb34r‚ÑC%’Sðáñcs5¢²ƒ&D“TdE£t6ÒUâeò³„ÃÓuãóF'”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷5!1AQaq"2‘¡±B#ÁRÑð3$bár‚’CScs4ñ%¢²ƒ&5ÂÒD“T£dEU6teâò³„ÃÓuãóF”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö'7GWgw‡—§·ÇÿÚ ?ôÅI]URSMn®}i$¤ËtK‚NŒx¯Z¤oR°×• +ðä*ÿYÿ7ÿBg?­ø>´¢¼µ 8r_×ÿ›ÿ¡'Ùñü£Eóâôµ[7)ípúø¸¯¥lÇ<\5­ßƒÛ*ªJ²ªÄ…\T•””ÿÿÐôu%µRSES]"ñõ.,^åëU]Fž.+ë[-žNÒíåÖbõeɪøy»¿Gn¿ØÇ ·z~/*’dêû;ìÊúå—T°d*š$,³²I¨ÿÑîT¤t”¥ã«ØŸoo‹Kºë[/„øoK·Æ’]¢ÜWçÎp×¢ïúßú 9Í]?ʲ:åVn,\w­S^1â½[ˇI%¯‡·zñqWJÙ·pÞ·jN’îÓsçö¸}<\WÖ¶DòpÖ—nú¾ª¤±šnšÓAPIOÿÒôTTd”æ¡-…˜’šê²¬¼ÙOƒ»Åêáá®—ºøCŠõª]f¤™kÆ4Ü—RZK®Uóó~ߣ‹ŠúÖÌSËÃZ]µÒªº”MµZªÒÕAN‚¨ ©¤§ÿÓôDE4”™ \U’Så ‘[ má_Ñ»V¤’GR—¥Z+ƒ[ –^So_~Œ2Ŷ¿ƒÒ.µg­•š×i­E]^IL–z¾¨$§ÿÔôeI^PINjÒ@RIO¼ý{šãî_šáâôÝ×V|yjô|ù:ì•Å`óÔüWû¾‹À®áXZ*¦~oÜáôU_[ÝŠyx«J¯òº€„ª1=*2ÉGIM5aQZé)ÿÕïÓ¨¤¥Ô%q%5×.ºåàª×+ƒÝãõpð×KÝ—8¯Z§¶G\Jáå/ôÿífö¼Ó©'YDST‡¢DU’B¤ˆ¤’–U¢ÂIOÿÖìÒäL’“ªÊòÎINŠùÕ}¹uk”Ïíqúx¸«­lËŠ|7¥Ý>N’îÅtóuú‹7»]?Ml*ÊÚÉ&ڌՅž® ¤Š*Ò’JsÔµ’’ŸÿÙ8BIMÿþ'File written by Adobe Photoshop¨ 4.0ÿîAdobedÿÛ„         ÿÀÿÝÿÄ¢  s!1AQa"q2‘¡±B#ÁRÑá3bð$r‚ñ%C4S’¢²csÂ5D'“£³6TdtÃÒâ&ƒ „”EF¤´VÓU(òãóÄÔäôeu…•¥µÅÕåõfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø)9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúm!1AQa"q‘2¡±ðÁÑá#BRbrñ3$4C‚’S%¢c²ÂsÒ5âDƒT“ &6E'dtU7ò£³Ã()Óã󄔤´ÄÔäôeu…•¥µÅÕåõFVfv†–¦¶ÆÖæöGWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúÿÚ ?™~v_ù’Ú+Pº°Ñ¼¼ÊeÔ¦…ŠÝIqº„ƒ`ôu§Øoçõ9"å˜ë»wgÙÂý>&_àŒ¿»þ´’ßÉ[Ÿ2Þ.£úBæñô9œ>˜—Ò™e‰W“3òm•Z«ð}–ø'9)=¦qÜc8ÿyáýÿИþ}é6z—–´ËÙâ¼¹Ž  -ÖÅRFáp…‹2‘º~îœÛâæÜk-Äwv½“–Q™Ä\Ê2_"hCFòâÁ»CêI/Å)îK8Û÷Šø¾ÆVM—]—'Œ þoÒÿÿÙzope2.13-2.13.21/source/Zope2/src/App/www/rtab.gif0000644000175000017500000000014012214017421020175 0ustar arnauarnauGIF89a³™™™fff3f33ÿÿÿÿÿÌÿÿ™ÿÿÿ!ù, „Ð9€^<ö ;zope2.13-2.13.21/source/Zope2/src/App/www/sp.gif0000644000175000017500000000005312214017421017672 0ustar arnauarnauGIF89a€ÿÀÀÀ!ù,@D;zope2.13-2.13.21/source/Zope2/src/App/www/background.jpg0000644000175000017500000000641212214017421021407 0ustar arnauarnauÿØÿàJFIFÿÛC   (1#%(:3=<9387@H\N@DWE78PmQW_bghg>Mqypdx\egcÿÀ [°ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÚ?ìŽ~Q’=) ƒœTõÀÎ=¿JQ’ÇztaŠß`dgßüñK’ªüq‚xíÍ'ÊéŒgå=8¤RW$àŽr>Ÿýz@¬FF =G¯zƒ6rsÇÞê=½ûSˆ+ÈqÈô?ýzn6sŒ íÇCK´¨7mÿZh?1#‘ÐätíúTƒžCr=kуê÷tóýk¦î }QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEž§îã‘ÙÁÇõ¥o¿÷O^öÿëÐNXsŸ~‡šnN ƒŒCÓõ§¯ Ák¦î }QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEžr¸*ÀóœsÇ¯ÖÆ26úóÀÀæŽNÐzóÎ}¿Nh çr‚?ˆc¯ÐÒcÌ ‚Š{qšqs…~@=A¦gÁR§ô'µ=q¤cŒ~”Å8n-ížsÅ/9+ƒýïÓŸ­)W¶3Å $`àg“éNå[¯\R7=H«Ó?ÚÓîëæ˵uýÁO¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢³Ø`ü˜$ž¸¡ˆÆG¡Æx¿‡ò¥lq†ê­Qœvç=sMäfç'9>åŠq&0OnÏçÚ€Fp éØñši ¡€9F}=~´âǃóc=)uP0­ÎéÇQN8ô>†˜TÀ* G^¹ÿr±';qÎ~éTdåˆcc™®™~è§QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQYÊwÀs†ã§ùÏëA,Cí=GQOdp=3oî€'<`ƒØ÷ÅaNI\ò¼H•Øàõ=x©w<ÔÀ2Ä&ר¡”NÒNFAÇçéN(rØPÜ ç¨õü)®0qógëNã óÇ>¿ç4•=-úÐÜeqÎó¬M¸unªÄκ…û¢EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEgK9#*}IË0ÆÒG#=Ç @F2zŠE'yU#“ž¾ôìåþà«wúŠhR#J¹ïž3Š\•ÛÎ{öýaCm¶ÞW9ú“Jà´¹õéúÒ2€CÝïßõ¥ÁcƒÉ “BñÃ#'ñÿ&š@,ÈHÜ8Ãw=¿pe* mîzçšÅÒÎëù›ÖF?­té÷E:Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š£ŠÍÉ*Ù?•4ö€1íúPIìãïÊ…ä6†B£éšT$Æýù¥sÁ^E0ðHÀ Ž„duÅ’ÛÔž ~TãòÏÇ1¡€F!F#ëÖžŠxaœΘ ÂÿµœÒ¹""Ãïm?… óylzœïÛúÖF˜½—øÏó®‘~è§QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQ_ÿÙzope2.13-2.13.21/source/Zope2/src/App/www/zopelogo.jpg0000644000175000017500000000542612214017421021132 0ustar arnauarnauÿØÿàJFIF,,ÿþThe Gimp is The WayÿÛCÿÛCÿÀ Z"ÿÄ  ÿÄ(  !#$ÿÄÿÄ"!#"$ÿÚ ?áéOJsÏ/s¹o·Ù8ƒŽ‰:šŠ- µ^­Eé¾à׫Àí¾˜’y5ÓyÍ2m´ hQÈI2~_Ãè Gµ{‹¾«Pª=Óš¨?9™}KŒqÞF! @“þÈ"}Ò=i`S3¶Úé¶±™".SYU¦ñç ’l;ëõß=ƒæiF{|SÚÎì_Š—§PñZSç[ÍÄ読Ò<æä¤A=–HÚ",T§Á~ɱé¦ŸË „ùÇÒ<÷Ó4ymôy‰Šs°’íLo¸ät›°hFÊÚê%„X…&‡#|°¢Ó¾[&…€Vdб…©ô÷O¶´Ô¾óu~¯T˜cWužhwì/¸²]œmI4˜W%mbÑnÿ/(}fŒ ëûl#"eª¯'ÅžÅáY£)ޱAî’Ù8eå¹›02du¥Ç<3ö7ÞVU¶Bz\1 pËc1g1H§móœÞ˜r$š×ÛͪHþ\½åO,˜åJ¾GXã…`÷+Y3+ùì¸Æ„ø~¬3÷whñ†‚•3ÏËK3êA0O!äË'2V þkq1@Ï›…>âÿA&êýÇ«–ø|G=K\ó?¡^;§J¦é–äýÀL¶×uÕòôöl_©W–.}¤[·°´Þ1á„sC`¦2Á  “¼qwžkø¿‚°é½Ÿ[ þÍÜ®ÕüÿÆhÜçUVËÃ]×ó~ Ά°ºœ¢G–›Ý©LqgÑÓvîNRœB£î[Ͷ~Ka¢iE)‘×ÝZ®kAÒ N„” ϵÍ ç[ŒÈQAæ–(q’pê£ËE÷T¨£•‘­›Q`"†¬ÜTk0'½ŠlàX:ÝùÙ§Ãâñtég›Ršô{o5«¶¯VU‘`¹!¢]›:¶£®­ƒbݰ¯Äæ—^[w1H1Î|Éó=P’†˜”ÌÑ–B^v £.¾—o¢ñŠðš±Ëy'S ádêÎé‚Úvîd×aJ½g%¼cX´Ÿ¹;{u›F‚cňæÛ³™,ZZo¸€U>Çf½ŸEÐá%¤PL1ùÊý2ÜY;Œ Q|M­[±A¡BD×årâèÄ(°ÚNúRœ ÒÍfš½ÀL…¶ø|F9ß°,]¡ÿ>IÉùZBµ»ðé{9Et.’}?d¸ þg:eXŠ×7èq7˜g Ï8î¢,0O8ÖHó»°®F“-Wb½Ä‚»@U±!ºÐÏÚ-rPÀ5-Z2˜‰ ÙÞ1Í!2¹‰‹]f‘x›ï˜4,¤\˜k!i½Yƒ¿lCÖ’÷ZÚ>Œj€º¶x /¿¿›˜;xêøãÅØ±KwÛù}’mS5—ºÖÃ0ð`cä‡3t[¹¹çßðø|>?Ò=k=õCeÑüãé»Å¾žHFõEF•%zÖ¶"wŽ·d¤#ŸÝacܲ^‰[YUn>¢çLÅý÷÷¢›$üZG?NK¯’ü‰u²vîKß{7i¦X&h·od©QäÐŒÉƬ kLùû-¬í?a|›ê:A’2™´Ø¿Ñ¾ræž¡æ§ó>š¸‰WÈLMP¾U6ÙiÖPô—EÖjË-âŸ4 MØ–Ã89É^ÀR‚&x7ª>b÷ï˜Úåm}WÓ4a¥Î³SqKÍ®ñ¯?hõ~ŠòXUýMƸÿz§³´ŽMq®øÛï´QÅyÎÒºnã.L¾õñšê*ã¾-œØñ\óªìo‡-äÆTÃt÷iįÑG©$$öýÚ]éÆQÂ#Ž¥óÅÈÌ„Èñ¢°€nDªJÅVáÔùÄ­Ó“dÌ#1fç['Pÿ©#ÂVNk2Ï™½ž-‘˜™_—|.ª–µ é5 ÇŒzdQ‹›g?”’Ç®¸×?|ãý|£{¢iñö÷EÈ©ý KÍV;çcë&ߪy©î;9ëEÔ9ZõQîi›³ÆVCÝ™°6ŤZAÀÆL1“(ù…~Ãõ#½¬Ö°³œsï2\xEè—Ä]jÌ´9uþ)‹©óþ}9èt>3™BŠÙm^³;é÷,}´Æ±ÌÕp/?SüûWf¡ ®­«k‰m]+¥ÛËÕã¤Ü ×ñ)ý‘Ÿá¦¿Ç™ØTɃÒhÀÿÎ"sÌ5žÝík—Yi°øÊ9$òœµWñuqd󕩦h`–ê ¡RÚ›³Û.±ji7DG=ÛÞ|bxç¡ šŽEѶ)SI±s ÔºšhœIA&_cIºü5Šð‹¤$ÛYýo×t®ãË{òŽEç6¬ó­wjíöŠ®Cï%K+®Ãô¸ßº1 ö–$rLê•{)¨Q:Ôk?&G!g8­lnLF’‚uË*6¬8Á9…” #&–Û¬¡”³)“¨cÇÀ«=s©0µò»uc–Q:%¢^*Î*Õt¯ùÅò”SkÒDÚéb¸W#¯¡Aû›6mûîÅ!N%l,‰IמåXªÝjõH·z:‹Sò×’9:k*ë%þ‘[½•D[ÖÛ'j»Ôk¬Ø† TÌÉ3=šD¨g‘´@Ö] ²Ï‡Æhà•u’]{F–ɹ‹VN#.¯QR˜–ôµÇH±„L,ÏÆœV ðÒhÍ28ꊔë¨|±ºò*q˜ùZ³¬‚*‰kßú^L6!]sßyµ*¹ë ©Îù×NBƒÊ°Q+óNyÓÛÖѽ¦þàUˆ`äiþ×kÑm0àáD&"Ìzc} Æl¿0"M:¨ ²U`n¸fËÛ©f89/Õ…‚ËÁ½^lP~Gf<ØÞ#¡Œd×_áñ˜8Àö1Zo'úŒØ·/HV8­x³ÔÒ¿ˆhyñí¹Ÿµ­äNäȶ}½åH§ÉQ¬Iàén³KÍ—–ŸÔ½ügù·ÃáóiÖ»¯ÿÙzope2.13-2.13.21/source/Zope2/src/App/www/CacheManager_icon.gif0000644000175000017500000000157712214017421022572 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿªªÿÿ!ù,@\@°`Á@à‡!0°aÄ‹+:¼øp@Æ… Dø‘áFŽJZD™’âBzœé1áK–mšÄ©ò$Çž<]îd ”¨Ð•k*€°©S—;zope2.13-2.13.21/source/Zope2/src/App/www/properties.jpg0000644000175000017500000000536012214017421021465 0ustar arnauarnauÿØÿàJFIFHHÿí^Photoshop 3.08BIMéxHHä@ÿ÷ÿ÷[ (ühht @h0 @C€N' ”dÌVe9CV+SR8BIMíHH8BIMó8BIM 8BIM' 8BIMõH/fflff/ff¡™š2Z5-8BIMøpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿè8BIM 8BIM8BIM@@8BIM 2€€€ÀÿØÿàJFIFHHÿþ'File written by Adobe Photoshop¨ 4.0ÿîAdobed€ÿÛ„            ÿÀ€€"ÿÝÿÄ?   3!1AQa"q2‘¡±B#$RÁb34r‚ÑC%’Sðáñcs5¢²ƒ&D“TdE£t6ÒUâeò³„ÃÓuãóF'”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷5!1AQaq"2‘¡±B#ÁRÑð3$bár‚’CScs4ñ%¢²ƒ&5ÂÒD“T£dEU6teâò³„ÃÓuãóF”¤…´•ÄÔäô¥µÅÕåõVfv†–¦¶ÆÖæö'7GWgw‡—§·ÇÿÚ ?ôÅI]^d¤ÇŽõª]ÛÛ­Õójôc'%Á^»¿êÿèL’Ã]Ò– éZ¦Â×N¼)$”ûb°¼-$”ûòª¼-$”ûªðõ ’ŸWW–r¸’ŸÿÐôuóÊ÷Ç+œ–N=.øî™pˆôìùúI.éhdÉÁZ]³ÊUÑômc«Ë ¦øöEãkeUæçÃÁ¥Ýº¿Ãî{ºÕpt½øßKIy¢J¯»áøº¿sþ¿üßý ô´—š$—»áø«î×ÿ›ÿ¡8ËAg­¨ò¯­*jÚ°’ŸÿÑîTU•e%)$ê²JNˆ©£¤§ç•ì‹ÆÖÊ«ÍχƒK»u~‡Ü÷uªàé{ñ¾–’óD•_wÃñu~çýù¿úéi/4I/wÃñWÜÿ¯ÿ7ÿBq–‚ÏZ Qå_R[K5XIOÿÒôU®áxªŸx¸µª¦×).=j«ö©v IX”8«VöHqÖµO´#,uuPqß^ȼm%L|u­Sc—æ=ž/Ouáù_dIxÚIŸwþ·àØÿHÿ«ÿÿ ¾È’ñ´’û¿õ¿¤ÕÿÎÿÐT´zÐS¹ï®¢,ÄD”ÿÿÓôEÂ.áM:á½.Ù1äà½.ß]ºôD,¹Ž*ôÕx³O›â¯EW‹šŒ„¦ j¾T’I$¥$’I)I$’JRÆ[+%>¤¤†Š’ŸÿÔôeI^\Jl¥ÃZ2bÅÇzÕ=Ö^.»sñ_§ñlääx+×wý_ý ì•uaLÒ|±$’IJI$’R’I$”¥Œ¶V2J} k¬eѤ§ÿÕd\R‡4x¸uîÝärpqéwÃÿtò $ºõ^1¾®ŽLœ¥ÛÙªêÚÃWœÉÒ^–´ÒSä)/FY))ãÒ]Ò°’Ÿ=D^‚­$¤¨ªÊ¢’ŸÿÖìÒäL’“ +Š‚JuVZ´ ’žQi-d$”ÐCVÔÒS”%5TÖŠ2JsQ¥š’ŸÿÙ8BIMÿþ'File written by Adobe Photoshop¨ 4.0ÿîAdobedÿÛ„         ÿÀÿÝÿÄ¢  s!1AQa"q2‘¡±B#ÁRÑá3bð$r‚ñ%C4S’¢²csÂ5D'“£³6TdtÃÒâ&ƒ „”EF¤´VÓU(òãóÄÔäôeu…•¥µÅÕåõfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø)9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúm!1AQa"q‘2¡±ðÁÑá#BRbrñ3$4C‚’S%¢c²ÂsÒ5âDƒT“ &6E'dtU7ò£³Ã()Óã󄔤´ÄÔäôeu…•¥µÅÕåõFVfv†–¦¶ÆÖæöGWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúÿÚ ?“ù·Pk_.ùÖko6Mtº•“IiW€iÌ÷±Å$QKêÛü Œñ»$°§àÒ+ú’æ^,CŽc†1ÿI~§'TO‡§êÿTa>Nó§qçíÐëÍg ºõáúëOá$,†ÿQO„üIËÒø¿Ÿö3{[š>êã៉üÞ‘þ“¯Ã)›âÿÐèZÖæ­_Có5¯è›Hï. }&îÝ€k¨ ¡Ì·&5y øš>±öždfàá ”zø¿ƒú¨ã‘ØòK ò¿å÷š¬üÅg{y1[Y;¼Å$gb¦'JݪŽ\ú³f:_ÿÙzope2.13-2.13.21/source/Zope2/src/App/www/cpSystem.gif0000644000175000017500000000162012214017421021060 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿªªÿÿ!ù,@m@° Á‚ 0€€ÃuaÕý¯zó_ŽÞ&¼ÔüŸiFIÁCz±rf,FËuû¬G¡Z¸e<Óäh] õý•üÎ|c¨ÈÇ‘É1ü¯— þb¸_Ùú`ŸšÖmjP2HÏîaã^ƒ|ÿÙ¬#$(a»òé_o“/ö4’Ýþ§ m$®fÒ§»±”•…ÿ{·<+©Ãcê0à+U¼'ŸÅWZåàÌ£t‹»³±8#è3ùŠ»4bÞÚ;ÆùL¥Gý÷M½_ìÍ¿ÃæÿA‘úW¦ìÛ¶ïúfIhuM©åHÝú׈XiVÚ×ÄSKº2åñ.¾Q‘¶´r,*QÔö;€ü«½þÒÉnþµæ—·kâïÜ[ëZ~—%¯ˆõƒq$ò)•!™=ÑÅÒ; áB‚7uÚ9¯£B¼“Rü¬ŽšNé›öú÷ˆ-înw:­ž‹§Ot£,¡§–ÞIÑ™U¹é‡`9§¬wö¶qk·¶1Ø äM2ÒÌ64ýš}ÔËo\3âhÙŸæÜKÕR]oÀÒèžlº¼^•¨iúu•£Åp“Ýi÷0,­çI gÍ”UfÚ7yƒr: jz,>°ÖüIâ LGŠÛR¹Œ½—öpMÉ`êŶƒPêXå+Ï©^ŒýèËåò5Q’Üç5‹=A¼?âç¶·™lŸUÕèÆ¥bcöí;ËVÇÃ8о8Íw:ž€m>!x’&U… ñ/…‘”d Œç€'½rž(ñ&‘©hÞ$ðÞŸâ {[O]Ô5‹F·×c†à’â#LCyeŠùŽÈ`Pq’V—^ÓÿV¼±ñd³]ê:Uþžú®´­=×ÙéY³a|ÐþÖ‘­JQ“çÕÿö¿ä&|ï:´°¥É33ÉåŽs»œãðªÕÕüI³Ò,µØ—G ‰%¬r\Æ—ñÞ,sîhþVè¸í\·ç_?(8»3k‰Š)zuŠ=»à|¬<-¦G¸üþ#uÆy?º€ ö¯xrïSñŽ›iR%œpI-ÔØá@p6Vn˜ì9¯ø;q¶áÏ:T‰[ÅM¹Ý‚¨Q¾I' ë^íñ/ã‡ô×¹Ó¼y«|ÌA¿\›kpzÏúæ§ðƒ×=+ëòú󎚊þoÌä©ÉÜÂø«©A¡‹öù’ëoð9Uö*3‘Ø·µkëÒx‹áÄ:Ý‚ïžó]e·/Ë2`wãpÃÖ¼N]TK+Ë-Á–W%žGl–'Ö»¿ƒ_,|-«Ic­\°Ðï>iT¹µ”¾rYXaX¼ƽIÕP§]Z2Qw³1á½/,a_ïºÏ^kN=Û@ñ/ˆuy'’Ò÷TÕµ›Û»û}²\YiVs8“Ê €“Ï2F[§Ë\iÞGƒ®¦ndHt¢?Ý“F»“ú ¤¹bÒÙvïóüIÕž“&ˆfñÒ@,ŒbгIöO&XüÆÊíÜd.s»vO¥/kzTæFžÎÎK.ÏW½Ô-&„ywrÚÇÙ$# ùXlq¹dlz|¹Ï½aø^ÊÒóPð”“Ø}¹ïtß[ϸV¸íRŸ/qõß Ïl“ÓãNœÓ§ÜÇwsÈ~< =üG¡Ú[ë}wo¡Aý¥uá‰4ÏŠÞ:ӬᴵÕ,ÄPùE<Í*ÒFÝ…‰Ë8£fÓm˜H±Áä ”yx˜,e•|ÍÄ×<ÑEJ”“ºaÐsüFñT“–¼¶7ij ãeÚ×''mÞ=:ð>^œUÁñWÆj"òîôäXÈUt»p#WÎõQ³åWÉ,£†î U{I½ +œÏ‹õíOXûV£$M?‘í†%Š(Р؈Šª…ÀÀœýIEoV6ÿÙzope2.13-2.13.21/source/Zope2/src/App/www/product.gif0000644000175000017500000000157012214017421020735 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,UH°`Á8С 4|¸P¢E‡ #^<À±#FˆÉ‘ Ã#Gž<è±£DŒ5^|yÐẩ&m¾¤™SgD˜}âìi³¤Á–;zope2.13-2.13.21/source/Zope2/src/App/config.py0000644000175000017500000000667312214017421017572 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Simple access to configuration values. The configuration values are represented as a single object with attributes for each bit of information. """ import sys _config = None def getConfiguration(): """Return the global Zope configuration object. If a configuration hasn't been set yet, generates a simple configuration object and uses that. Once generated, it may not be overridden by calling ``setConfiguration()``. """ if _config is None: setConfiguration(DefaultConfiguration()) return _config def setConfiguration(cfg): """Set the global configuration object. Legacy sources of common configuration values are updated to reflect the new configuration; this may be removed in some future version. """ global _config _config = cfg if cfg is None: return from App import FindHomes import __builtin__ import os import Globals # to set data __builtin__.CLIENT_HOME = FindHomes.CLIENT_HOME = cfg.clienthome os.environ["CLIENT_HOME"] = cfg.clienthome # Globals does not export CLIENT_HOME Globals.data_dir = cfg.clienthome __builtin__.INSTANCE_HOME = FindHomes.INSTANCE_HOME = cfg.instancehome os.environ["INSTANCE_HOME"] = cfg.instancehome Globals.INSTANCE_HOME = cfg.instancehome if hasattr(cfg, 'softwarehome') and cfg.softwarehome is not None: __builtin__.SOFTWARE_HOME = FindHomes.SOFTWARE_HOME = cfg.softwarehome os.environ["SOFTWARE_HOME"] = cfg.softwarehome Globals.SOFTWARE_HOME = cfg.softwarehome if hasattr(cfg, 'zopehome') and cfg.zopehome is not None: __builtin__.ZOPE_HOME = FindHomes.ZOPE_HOME = cfg.zopehome os.environ["ZOPE_HOME"] = cfg.zopehome Globals.ZOPE_HOME = cfg.zopehome Globals.DevelopmentMode = cfg.debug_mode class DefaultConfiguration: """ This configuration should be used effectively only during unit tests """ def __init__(self): from App import FindHomes self.clienthome = FindHomes.CLIENT_HOME self.instancehome = FindHomes.INSTANCE_HOME if hasattr(FindHomes, 'SOFTWARE_HOME'): self.softwarehome = FindHomes.SOFTWARE_HOME if hasattr(FindHomes, 'ZOPE_HOME'): self.zopehome = FindHomes.ZOPE_HOME self.dbtab = None self.debug_mode = True self.enable_product_installation = False self.locale = None # restructured text default_enc = sys.getdefaultencoding() self.rest_input_encoding = default_enc self.rest_output_encoding = default_enc self.rest_header_level = 3 self.rest_language_code = 'en' # ZServer.HTTPServer self.http_header_max_length = 8196 # VerboseSecurity self.skip_ownership_checking = False self.skip_authentication_checking = False zope2.13-2.13.21/source/Zope2/src/App/interfaces.py0000644000175000017500000000427712214017421020446 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """App interfaces. """ from zope.interface import Attribute from zope.interface import Interface # XXX: might contain non-API methods and outdated comments; # not synced with ZopeBook API Reference; # based on App.Management.Navigation class INavigation(Interface): """Basic navigation UI support""" manage = Attribute(""" """) manage_menu = Attribute(""" """) manage_top_frame = Attribute(""" """) manage_page_header = Attribute(""" """) manage_page_footer = Attribute(""" """) manage_form_title = Attribute("""Add Form""") zope_quick_start = Attribute(""" """) manage_copyright = Attribute(""" """) manage_zmi_prefs = Attribute(""" """) def manage_zmi_logout(REQUEST, RESPONSE): """Logout current user""" INavigation.setTaggedValue('manage_page_style.css', Attribute(""" """)) # XXX: might contain non-API methods and outdated comments; # not synced with ZopeBook API Reference; # based on App.PersistentExtra.PersistentUtil class IPersistentExtra(Interface): def bobobase_modification_time(): """ """ # XXX: might contain non-API methods and outdated comments; # not synced with ZopeBook API Reference; # based on App.Undo.UndoSupport class IUndoSupport(Interface): manage_UndoForm = Attribute("""Manage Undo form""") def undoable_transactions(first_transaction=None, last_transaction=None, PrincipiaUndoBatchSize=None): """ """ def manage_undo_transactions(transaction_info=(), REQUEST=None): """ """ zope2.13-2.13.21/source/Zope2/src/App/PersistentExtra.py0000644000175000017500000000262612214017421021463 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Patch for Persistent to support IPersistentExtra. """ from DateTime.DateTime import DateTime class PersistentUtil: def bobobase_modification_time(self): jar = self._p_jar oid = self._p_oid if jar is None or oid is None: return DateTime() try: t = self._p_mtime except AttributeError: t = 0 return DateTime(t) _patched = False def patchPersistent(): global _patched if _patched: return _patched = True from zope.interface import classImplements from Persistence import Persistent from App.interfaces import IPersistentExtra for k, v in PersistentUtil.__dict__.items(): if k[0] != '_': setattr(Persistent, k, v) classImplements(Persistent, IPersistentExtra) zope2.13-2.13.21/source/Zope2/src/App/__init__.py0000644000175000017500000000114412214017421020050 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## zope2.13-2.13.21/source/Zope2/src/App/Undo.py0000644000175000017500000001260112214017421017216 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Undo support. """ from Acquisition import aq_inner from Acquisition import aq_parent from AccessControl import getSecurityManager from AccessControl import ClassSecurityInfo from AccessControl.class_init import InitializeClass from AccessControl.Permissions import undo_changes from App.interfaces import IUndoSupport from App.special_dtml import DTMLFile from DateTime.DateTime import DateTime import ExtensionClass import transaction from ZopeUndo.Prefix import Prefix from zope.interface import implements class UndoSupport(ExtensionClass.Base): implements(IUndoSupport) security = ClassSecurityInfo() manage_options=( {'label': 'Undo', 'action': 'manage_UndoForm'}, ) security.declareProtected(undo_changes, 'manage_UndoForm') manage_UndoForm = DTMLFile( 'dtml/undo', globals(), PrincipiaUndoBatchSize=20, first_transaction=0, last_transaction=20, ) def _get_request_var_or_attr(self, name, default): if hasattr(self, 'REQUEST'): REQUEST=self.REQUEST if REQUEST.has_key(name): return REQUEST[name] if hasattr(self, name): v = getattr(self, name) else: v = default REQUEST[name] = v return v else: if hasattr(self, name): v = getattr(self, name) else: v = default return v security.declareProtected(undo_changes, 'undoable_transactions') def undoable_transactions(self, first_transaction=None, last_transaction=None, PrincipiaUndoBatchSize=None): if first_transaction is None: first_transaction = self._get_request_var_or_attr( 'first_transaction', 0) if PrincipiaUndoBatchSize is None: PrincipiaUndoBatchSize = self._get_request_var_or_attr( 'PrincipiaUndoBatchSize', 20) if last_transaction is None: last_transaction = self._get_request_var_or_attr( 'last_transaction', first_transaction+PrincipiaUndoBatchSize) spec = {} # A user is allowed to undo transactions that were initiated # by any member of a user folder in the place where the user # is defined. user = getSecurityManager().getUser() user_parent = aq_parent(user) if user_parent is not None: path = '/'.join(user_parent.getPhysicalPath()[1:-1]) else: path = '' if path: spec['user_name'] = Prefix(path) if getattr(aq_parent(aq_inner(self)), '_p_jar', None) == self._p_jar: # We only want to undo things done here (and not in mounted # databases) opath = '/'.join(self.getPhysicalPath()) else: # Special case: at the root of a database, # allow undo of any path. opath = None if opath: spec['description'] = Prefix(opath) r = self._p_jar.db().undoInfo( first_transaction, last_transaction, spec) for d in r: d['time'] = t = DateTime(d['time']) desc = d['description'] tid = d['id'] if desc: desc = desc.split() d1 = desc[0] desc = ' '.join(desc[1:]) if len(desc) > 60: desc = desc[:56] + ' ...' tid = "%s %s %s %s" % (encode64(tid), t, d1, desc) else: tid = "%s %s" % (encode64(tid), t) d['id'] = tid return r security.declareProtected(undo_changes, 'manage_undo_transactions') def manage_undo_transactions(self, transaction_info=(), REQUEST=None): """ """ tids = [] descriptions = [] for tid in transaction_info: tid = tid.split() if tid: tids.append(decode64(tid[0])) descriptions.append(tid[-1]) if tids: transaction.get().note("Undo %s" % ' '.join(descriptions)) self._p_jar.db().undoMultiple(tids) if REQUEST is None: return REQUEST['RESPONSE'].redirect("%s/manage_UndoForm" % REQUEST['URL1']) return '' InitializeClass(UndoSupport) ######################################################################## # Blech, need this cause binascii.b2a_base64 is too pickly import binascii def encode64(s, b2a=binascii.b2a_base64): if len(s) < 58: return b2a(s) r = [] a = r.append for i in range(0, len(s), 57): a(b2a(s[i:i+57])[:-1]) return ''.join(r) def decode64(s, a2b=binascii.a2b_base64): __traceback_info__=len(s), `s` return a2b(s+'\n') del binascii zope2.13-2.13.21/source/Zope2/src/App/special_dtml.py0000644000175000017500000001631312214017421020755 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import DocumentTemplate, Common, Persistence, MethodObject, Globals, os, sys from types import InstanceType from logging import getLogger from App.config import getConfiguration LOG = getLogger('special_dtml') import Zope2 PREFIX = os.path.realpath( os.path.join(os.path.dirname(Zope2.__file__), os.path.pardir) ) class HTML(DocumentTemplate.HTML,Persistence.Persistent,): "Persistent HTML Document Templates" class ClassicHTMLFile(DocumentTemplate.HTMLFile,MethodObject.Method,): "Persistent HTML Document Templates read from files" class func_code: pass func_code=func_code() func_code.co_varnames='trueself', 'self', 'REQUEST' func_code.co_argcount=3 _need__name__=1 _v_last_read=0 def __init__(self, name, _prefix=None, **kw): if _prefix is None: _prefix = getattr(getConfiguration(), 'softwarehome', PREFIX) elif type(_prefix) is not str: _prefix = Common.package_home(_prefix) args=(self, os.path.join(_prefix, name + '.dtml')) if '__name__' not in kw: kw['__name__'] = os.path.split(name)[-1] apply(ClassicHTMLFile.inheritedAttribute('__init__'), args, kw) def _cook_check(self): if Globals.DevelopmentMode: __traceback_info__=self.raw try: mtime=os.stat(self.raw)[8] except: mtime=0 if mtime != self._v_last_read: self.cook() self._v_last_read=mtime elif not hasattr(self,'_v_cooked'): try: changed=self.__changed__() except: changed=1 self.cook() if not changed: self.__changed__(0) def _setName(self, name): self.__name__ = name self._need__name__ = 0 def __call__(self, *args, **kw): self._cook_check() return apply(HTMLFile.inheritedAttribute('__call__'), (self,)+args[1:],kw) defaultBindings = {'name_context': 'context', 'name_container': 'container', 'name_m_self': 'self', 'name_ns': 'caller_namespace', 'name_subpath': 'traverse_subpath'} from Shared.DC.Scripts.Bindings import Bindings from Acquisition import Explicit, aq_inner, aq_parent from DocumentTemplate.DT_String import _marker, DTReturn, render_blocks from DocumentTemplate.DT_Util import TemplateDict, InstanceDict from AccessControl import getSecurityManager from ComputedAttribute import ComputedAttribute class DTMLFile(Bindings, Explicit, ClassicHTMLFile): "HTMLFile with bindings and support for __render_with_namespace__" func_code = None func_defaults = None _need__name__=1 _Bindings_ns_class = TemplateDict def _get__roles__(self): imp = getattr(aq_parent(aq_inner(self)), '%s__roles__' % self.__name__) if hasattr(imp, '__of__'): return imp.__of__(self) return imp __roles__ = ComputedAttribute(_get__roles__, 1) # By default, we want to look up names in our container. _Bindings_client = 'container' def __init__(self, name, _prefix=None, **kw): self.ZBindings_edit(defaultBindings) self._setFuncSignature() apply(DTMLFile.inheritedAttribute('__init__'), (self, name, _prefix), kw) def getOwner(self, info=0): ''' This method is required of all objects that go into the security context stack. ''' return None def _exec(self, bound_data, args, kw): # Cook if we haven't already self._cook_check() # Get our caller's namespace, and set up our own. cns = bound_data['caller_namespace'] ns = self._Bindings_ns_class() push = ns._push ns.guarded_getattr = None ns.guarded_getitem = None req = None kw_bind = kw if cns: # Someone called us. push(cns) ns.level = cns.level + 1 ns.this = getattr(cns, 'this', None) # Get their bindings. Copy the request reference # forward, and include older keyword arguments in the # current 'keyword_args' binding. try: last_bound = ns[('current bindings',)] last_req = last_bound.get('REQUEST', None) if last_req: bound_data['REQUEST'] = last_req old_kw = last_bound['keyword_args'] if old_kw: kw_bind = old_kw.copy() kw_bind.update(kw) except: pass else: # We're first, so get the REQUEST. try: req = self.aq_acquire('REQUEST') if hasattr(req, 'taintWrapper'): req = req.taintWrapper() except: pass bound_data['REQUEST'] = req ns.this = bound_data['context'] # Bind 'keyword_args' to the complete set of keyword arguments. bound_data['keyword_args'] = kw_bind # Push globals, initialized variables, REQUEST (if any), # and keyword arguments onto the namespace stack for nsitem in (self.globals, self._vars, req, kw): if nsitem: push(nsitem) # Push the 'container' (default), 'context', or nothing. bind_to = self._Bindings_client if bind_to in ('container', 'client'): push(InstanceDict(bound_data[bind_to], ns)) # Push the name bindings, and a reference to grab later. push(bound_data) push({('current bindings',): bound_data}) security = getSecurityManager() security.addContext(self) try: value = self.ZDocumentTemplate_beforeRender(ns, _marker) if value is _marker: try: result = render_blocks(self._v_blocks, ns) except DTReturn, v: result = v.v except AttributeError: if type(sys.exc_value)==InstanceType and sys.exc_value.args[0]=="_v_blocks": LOG.warn("DTML file '%s' could not be read" % self.raw) raise ValueError, ("DTML file error: " "Check logfile for details") else: raise self.ZDocumentTemplate_afterRender(ns, result) return result else: return value finally: security.removeContext(self) # Clear the namespace, breaking circular references. while len(ns): ns._pop() from Shared.DC.Scripts.Signature import _setFuncSignature HTMLFile = ClassicHTMLFile zope2.13-2.13.21/source/Zope2/src/App/DavLockManager.py0000644000175000017500000000764212214017421021140 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import aq_base from Acquisition import Implicit from App.special_dtml import DTMLFile from OFS.SimpleItem import Item from webdav.Lockable import wl_isLocked class DavLockManager(Item, Implicit): id = 'DavLockManager' name = title = 'WebDAV Lock Manager' meta_type = 'WebDAV Lock Manager' icon = 'p_/davlocked' security = ClassSecurityInfo() security.declareProtected('Manage WebDAV Locks', 'findLockedObjects', 'manage_davlocks', 'manage_unlockObjects') security.declarePrivate('unlockObjects') manage_davlocks = manage_main = manage = DTMLFile( 'dtml/davLockManager', globals()) manage_davlocks._setName('manage_davlocks') manage_options = ({'label': 'Write Locks', 'action': 'manage_main'}, ) def findLockedObjects(self, frompath=''): app = self.getPhysicalRoot() if frompath: if frompath[0] == '/': frompath = frompath[1:] # since the above will turn '/' into an empty string, check # for truth before chopping a final slash if frompath and frompath[-1] == '/': frompath= frompath[:-1] # Now we traverse to the node specified in the 'frompath' if # the user chose to filter the search, and run a ZopeFind with # the expression 'wl_isLocked()' to find locked objects. obj = app.unrestrictedTraverse(frompath) lockedobjs = self._findapply(obj, path=frompath) return lockedobjs def unlockObjects(self, paths=[]): app = self.getPhysicalRoot() for path in paths: ob = app.unrestrictedTraverse(path) ob.wl_clearLocks() def manage_unlockObjects(self, paths=[], REQUEST=None): " Management screen action to unlock objects. " if paths: self.unlockObjects(paths) if REQUEST is not None: m = '%s objects unlocked.' % len(paths) return self.manage_davlocks(self, REQUEST, manage_tabs_message=m) def _findapply(self, obj, result=None, path=''): # recursive function to actually dig through and find the locked # objects. if result is None: result = [] base = aq_base(obj) if not hasattr(base, 'objectItems'): return result try: items = obj.objectItems() except Exception: return result addresult = result.append for id, ob in items: if path: p = '%s/%s' % (path, id) else: p = id dflag = hasattr(ob, '_p_changed') and (ob._p_changed == None) bs = aq_base(ob) if wl_isLocked(ob): li = [] addlockinfo = li.append for token, lock in ob.wl_lockItems(): addlockinfo({'owner': lock.getCreatorPath(), 'token': token}) addresult((p, li)) dflag = 0 if hasattr(bs, 'objectItems'): self._findapply(ob, result, p) if dflag: ob._p_deactivate() return result InitializeClass(DavLockManager) zope2.13-2.13.21/source/Zope2/src/App/ZApplication.py0000644000175000017500000000470512214017421020714 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Implement an bobo_application object that is BoboPOS3 aware This module provides a wrapper that causes a database connection to be created and used when bobo publishes a bobo_application object. """ import transaction connection_open_hooks = [] class ZApplicationWrapper: def __init__(self, db, name, klass= None, klass_args=()): self._stuff = db, name if klass is not None: conn=db.open() root=conn.root() if not root.has_key(name): root[name]=klass() transaction.commit() conn.close() self._klass=klass # This hack is to overcome a bug in Bobo! def __getattr__(self, name): return getattr(self._klass, name) def __bobo_traverse__(self, REQUEST=None, name=None): db, aname = self._stuff conn = db.open() if connection_open_hooks: for hook in connection_open_hooks: hook(conn) # arrange for the connection to be closed when the request goes away cleanup = Cleanup(conn) REQUEST._hold(cleanup) conn.setDebugInfo(REQUEST.environ, REQUEST.other) v=conn.root()[aname] if name is not None: if hasattr(v, '__bobo_traverse__'): return v.__bobo_traverse__(REQUEST, name) if hasattr(v,name): return getattr(v,name) return v[name] return v def __call__(self, connection=None): db, aname = self._stuff if connection is None: connection=db.open() elif isinstance(connection, basestring): connection=db.open(connection) return connection.root()[aname] class Cleanup: def __init__(self, jar): self._jar = jar def __del__(self): transaction.abort() self._jar.close() zope2.13-2.13.21/source/Zope2/src/App/ProductRegistry.py0000644000175000017500000001116512214017421021466 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## # Product registry and new product factory model. There will be a new # mechanism for defining actions for meta types. If an action is of # the form: # # manage_addProduct-name-factoryid # # Then the machinery that invokes an add-product form # will return: # ....what? class ProductRegistryMixin: # This class implements a protocol for registering products that # are defined through the web. # This class is a mix-in class for the top-level application object. def _manage_remove_product_meta_type(self, product, id=None, meta_type=None): r=[] pid=product.id for mt in self._getProductRegistryMetaTypes(): if 'product' in mt: if mt['product']==pid and ( meta_type is None or meta_type==mt['name']): continue elif meta_type==mt['name']: continue r.append(mt) self._setProductRegistryMetaTypes(tuple(r)) def _constructor_prefix_string(self, pid): return 'manage_addProduct/%s/' % pid def _manage_add_product_meta_type(self, product, id, meta_type, permission=''): pid=product.id meta_types=self._getProductRegistryMetaTypes() for mt in meta_types: if mt['name']==meta_type: if 'product' not in mt: mt['product']=pid if mt['product'] != pid: raise ValueError, ( 'The type %s is already defined.' % meta_type) mt['action']='%s%s' % ( self._constructor_prefix_string(pid), id) if permission: mt['permission']=permission return mt={ 'name': meta_type, 'action': ('%s%s' % ( self._constructor_prefix_string(pid), id)), 'product': pid } if permission: mt['permission']=permission self._setProductRegistryMetaTypes(meta_types+(mt,)) # HACK - sometimes an unwrapped App object seems to be passed as # self to these methods, which means that they dont have an aq_aquire # method. Until Jim has time to look into this, this aq_maybe method # appears to be an effective work-around... def aq_maybe(self, name): if hasattr(self, name): return getattr(self, name) return self.aq_acquire(name) def _manage_add_product_data(self, type, product, id, **data): values=filter( lambda d, product=product, id=id: not (d['product']==product and d['id']==id), list(self.aq_maybe('_getProductRegistryData')(type)) ) data['product']=product data['id']=id values.append(data) self.aq_maybe('_setProductRegistryData')(type, tuple(values)) def _manage_remove_product_data(self, type, product, id): values=filter( lambda d, product=product, id=id: not (d['product']==product and d['id']==id), self.aq_maybe('_getProductRegistryData')(type) ) self.aq_maybe('_setProductRegistryData')(type, tuple(values)) class ProductRegistry(ProductRegistryMixin): # This class implements a protocol for registering products that # are defined through the web. It also provides methods for # getting hold of the Product Registry, Control_Panel.Products. # This class is a mix-in class for the top-level application object. def _getProducts(self): return self.Control_Panel.Products _product_meta_types=() def _getProductRegistryMetaTypes(self): return self._product_meta_types def _setProductRegistryMetaTypes(self, v): self._product_meta_types=v def _getProductRegistryData(self, name): return getattr(self, '_product_%s' % name) def _setProductRegistryData(self, name, v): name='_product_%s' % name if hasattr(self, name): return setattr(self, name, v) else: raise AttributeError, name zope2.13-2.13.21/source/Zope2/src/App/ProductContext.py0000644000175000017500000003025112214017421021277 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Objects providing context for product initialization """ from logging import getLogger import os import re import stat from AccessControl.Permission import registerPermissions from AccessControl.PermissionRole import PermissionRole from App.Common import package_home from App.ImageFile import ImageFile from DateTime.DateTime import DateTime from HelpSys import APIHelpTopic from HelpSys import HelpTopic from OFS.misc_ import Misc_ from OFS.misc_ import misc_ from OFS.ObjectManager import ObjectManager from zope.interface import implementedBy from App.FactoryDispatcher import FactoryDispatcher # Waaaa import Products if not hasattr(Products, 'meta_types'): Products.meta_types=() if not hasattr(Products, 'meta_classes'): Products.meta_classes={} Products.meta_class_info={} _marker = [] # Create a new marker object LOG = getLogger('ProductContext') class ProductContext: def __init__(self, product, app, package): self.__prod = product # app is None by default which signals disabled product installation self.__app = app self.__pack = package def registerClass(self, instance_class=None, meta_type='', permission=None, constructors=(), icon=None, permissions=None, legacy=(), visibility="Global", interfaces=_marker, container_filter=None ): """Register a constructor Keyword arguments are used to provide meta data: instance_class -- The class of the object that will be created. This is not currently used, but may be used in the future to increase object mobility. meta_type -- The kind of object being created This appears in add lists. If not specified, then the class meta_type will be used. permission -- The permission name for the constructors. If not specified, then a permission name based on the meta type will be used. constructors -- A list of constructor methods A method can me a callable object with a __name__ attribute giving the name the method should have in the product, or the method may be a tuple consisting of a name and a callable object. The method must be picklable. The first method will be used as the initial method called when creating an object. icon -- The name of an image file in the package to be used for instances. Note that the class icon attribute will be set automagically if an icon is provided. permissions -- Additional permissions to be registered If not provided, then permissions defined in the class will be registered. legacy -- A list of legacy methods to be added to ObjectManager for backward compatibility visibility -- "Global" if the object is globally visible, None else interfaces -- a list of the interfaces the object supports container_filter -- function that is called with an ObjectManager object as the only parameter, which should return a true object if the object is happy to be created in that container. The filter is called before showing ObjectManager's Add list, and before pasting (after object copy or cut), but not before calling an object's constructor. """ pack=self.__pack initial=constructors[0] productObject=self.__prod pid=productObject.id if icon and instance_class is not None: setattr(instance_class, 'icon', 'misc_/%s/%s' % (pid, os.path.split(icon)[1])) if permissions: if isinstance(permissions, basestring): # You goofed it! raise TypeError, ('Product context permissions should be a ' 'list of permissions not a string', permissions) for p in permissions: if isinstance(p, tuple): p, default= p registerPermissions(((p, (), default),)) else: registerPermissions(((p, ()),)) ############################################################ # Constructor permission setup if permission is None: permission="Add %ss" % (meta_type or instance_class.meta_type) if isinstance(permission, tuple): permission, default = permission else: default = ('Manager',) pr = PermissionRole(permission,default) registerPermissions(((permission, (), default),)) ############################################################ OM = ObjectManager for method in legacy: if isinstance(method, tuple): name, method = method aliased = 1 else: name=method.__name__ aliased = 0 if name not in OM.__dict__: setattr(OM, name, method) setattr(OM, name+'__roles__', pr) if aliased: # Set the unaliased method name and its roles # to avoid security holes. XXX: All "legacy" # methods need to be eliminated. setattr(OM, method.__name__, method) setattr(OM, method.__name__+'__roles__', pr) if isinstance(initial, tuple): name, initial = initial else: name = initial.__name__ fd = getattr(pack, '__FactoryDispatcher__', None) if fd is None: class __FactoryDispatcher__(FactoryDispatcher): "Factory Dispatcher for a Specific Product" fd = pack.__FactoryDispatcher__ = __FactoryDispatcher__ if not hasattr(pack, '_m'): pack._m = AttrDict(fd) m = pack._m if interfaces is _marker: if instance_class is None: interfaces = () else: interfaces = tuple(implementedBy(instance_class)) Products.meta_types = Products.meta_types + ( { 'name': meta_type or instance_class.meta_type, # 'action': The action in the add drop down in the ZMI. This is # currently also required by the _verifyObjectPaste # method of CopyContainers like Folders. 'action': ('manage_addProduct/%s/%s' % (pid, name)), # 'product': Used by ProductRegistry for TTW products and by # OFS.Application for refreshing products. # This key might not be available. 'product': pid, # 'permission': Guards the add action. 'permission': permission, # 'visibility': A silly name. Doesn't have much to do with # visibility. Allowed values: 'Global', None 'visibility': visibility, # 'interfaces': A tuple of oldstyle and/or newstyle interfaces. 'interfaces': interfaces, 'instance': instance_class, 'container_filter': container_filter },) m[name]=initial m[name+'__roles__']=pr for method in constructors[1:]: if isinstance(method, tuple): name, method = method else: name=os.path.split(method.__name__)[-1] if name not in productObject.__dict__: m[name]=method m[name+'__roles__']=pr if icon: name = os.path.split(icon)[1] icon = ImageFile(icon, self.__pack.__dict__) icon.__roles__=None if not hasattr(misc_, pid): setattr(misc_, pid, Misc_(pid, {})) getattr(misc_, pid)[name]=icon def getProductHelp(self): """ Returns the ProductHelp associated with the current Product. """ if self.__app is None: return self.__prod.getProductHelp() return self.__prod.__of__(self.__app.Control_Panel.Products).getProductHelp() def registerHelpTopic(self, id, topic): """ Register a Help Topic for a product. """ self.getProductHelp()._setObject(id, topic) def registerHelpTitle(self, title): """ Sets the title of the Product's Product Help """ h = self.getProductHelp() if getattr(h, 'title', None) != title: h.title = title def registerHelp(self, directory='help', clear=1, title_re=re.compile(r'(.+?)', re.I)): """ Registers Help Topics for all objects in a directory. Nothing will be done if the files in the directory haven't changed since the last registerHelp call. 'clear' indicates whether or not to delete all existing Topics from the Product. HelpTopics are created for these kind of files .dtml -- DTMLHelpTopic .html .htm -- TextHelpTopic .stx .txt -- STXHelpTopic .jpg .png .gif -- ImageHelpTopic .py -- APIHelpTopic """ if not self.__app: return help=self.getProductHelp() path=os.path.join(package_home(self.__pack.__dict__), directory) # If help directory does not exist, log a warning and return. try: dir_mod_time=DateTime(os.stat(path)[stat.ST_MTIME]) except OSError, (errno, text): LOG.warn('%s: %s' % (text, path)) return # test to see if nothing has changed since last registration if help.lastRegistered is not None and \ help.lastRegistered >= dir_mod_time: return help.lastRegistered=DateTime() if clear: for id in help.objectIds(['Help Topic','Help Image']): help._delObject(id) for file in os.listdir(path): ext=os.path.splitext(file)[1] ext=ext.lower() if ext in ('.dtml',): contents = open(os.path.join(path,file),'rb').read() m = title_re.search(contents) if m: title = m.group(1) else: title = '' ht=HelpTopic.DTMLTopic(file, '', os.path.join(path,file)) self.registerHelpTopic(file, ht) elif ext in ('.html', '.htm'): contents = open(os.path.join(path,file),'rb').read() m = title_re.search(contents) if m: title = m.group(1) else: title = '' ht=HelpTopic.TextTopic(file, title, os.path.join(path,file)) self.registerHelpTopic(file, ht) elif ext in ('.stx', '.txt'): title=(open(os.path.join(path,file),'rb').readline()).split(':')[0] ht=HelpTopic.STXTopic(file, title, os.path.join(path, file)) self.registerHelpTopic(file, ht) elif ext in ('.jpg', '.gif', '.png'): ht=HelpTopic.ImageTopic(file, '', os.path.join(path, file)) self.registerHelpTopic(file, ht) elif ext in ('.py',): if file[0] == '_': # ignore __init__.py continue ht=APIHelpTopic.APIHelpTopic(file, '', os.path.join(path, file)) self.registerHelpTopic(file, ht) class AttrDict: def __init__(self, ob): self.ob = ob def __setitem__(self, name, v): setattr(self.ob, name, v) zope2.13-2.13.21/source/Zope2/src/App/tests/0000755000175000017500000000000012214017421017101 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/App/tests/testImageFile.py0000644000175000017500000000314212214017421022175 0ustar arnauarnauimport unittest import os.path import App from Testing.ZopeTestCase.warnhook import WarningsHook class TestImageFile(unittest.TestCase): def setUp(self): # ugly: need to save the old App.config configuration value since # ImageFile might read it and trigger setting it to the default value self.oldcfg = App.config._config self.warningshook = WarningsHook() self.warningshook.install() def tearDown(self): self.warningshook.uninstall() # ugly: need to restore configuration, or lack thereof App.config._config = self.oldcfg def test_warn_on_software_home_default(self): App.ImageFile.ImageFile('App/www/zopelogo.jpg') self.assertEquals(self.warningshook.warnings.pop()[0], App.ImageFile.NON_PREFIX_WARNING) def test_no_warn_on_absolute_path(self): path = os.path.join(os.path.dirname(App.__file__), 'www','zopelogo.jpg') App.ImageFile.ImageFile(path) self.assertFalse(self.warningshook.warnings) def test_no_warn_on_path_as_prefix(self): prefix = os.path.dirname(App.__file__) App.ImageFile.ImageFile('www/zopelogo.jpg', prefix) self.assertFalse(self.warningshook.warnings) def test_no_warn_on_namespace_as_prefix(self): prefix = App.__dict__ # same as calling globals() inside the App module App.ImageFile.ImageFile('www/zopelogo.jpg', prefix) self.assertFalse(self.warningshook.warnings) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestImageFile), )) zope2.13-2.13.21/source/Zope2/src/App/tests/test_setConfiguration.py0000644000175000017500000000755212214017421024046 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for App.config.setConfiguration() """ import unittest from Testing.ZopeTestCase.layer import ZopeLite class SetConfigTests(unittest.TestCase): layer = ZopeLite def setUp(self): # Save away everything as we need to restore it later on self.clienthome = self.getconfig('clienthome') self.instancehome = self.getconfig('instancehome') self.softwarehome = self.getconfig('softwarehome') self.zopehome = self.getconfig('zopehome') self.debug_mode = self.getconfig('debug_mode') def tearDown(self): self.setconfig(clienthome=self.clienthome, instancehome=self.instancehome, softwarehome=self.softwarehome, zopehome=self.zopehome, debug_mode=self.debug_mode) def getconfig(self, key): import App.config config = App.config.getConfiguration() return getattr(config, key, None) def setconfig(self, **kw): import App.config config = App.config.getConfiguration() for key, value in kw.items(): setattr(config, key, value) App.config.setConfiguration(config) def testClientHomeLegacySources(self): import os import App.FindHomes import Globals # for data import __builtin__ self.setconfig(clienthome='foo') self.assertEqual(os.environ.get('CLIENT_HOME'), 'foo') self.assertEqual(App.FindHomes.CLIENT_HOME, 'foo') self.assertEqual(__builtin__.CLIENT_HOME, 'foo') self.assertEqual(Globals.data_dir, 'foo') def testInstanceHomeLegacySources(self): import os import App.FindHomes import Globals # for data import __builtin__ self.setconfig(instancehome='foo') self.assertEqual(os.environ.get('INSTANCE_HOME'), 'foo') self.assertEqual(App.FindHomes.INSTANCE_HOME, 'foo') self.assertEqual(__builtin__.INSTANCE_HOME, 'foo') self.assertEqual(Globals.INSTANCE_HOME, 'foo') def testSoftwareHomeLegacySources(self): import os import App.FindHomes import Globals # for data import __builtin__ self.setconfig(softwarehome='foo') self.assertEqual(os.environ.get('SOFTWARE_HOME'), 'foo') self.assertEqual(App.FindHomes.SOFTWARE_HOME, 'foo') self.assertEqual(__builtin__.SOFTWARE_HOME, 'foo') self.assertEqual(Globals.SOFTWARE_HOME, 'foo') def testZopeHomeLegacySources(self): import os import App.FindHomes import Globals # for data import __builtin__ self.setconfig(zopehome='foo') self.assertEqual(os.environ.get('ZOPE_HOME'), 'foo') self.assertEqual(App.FindHomes.ZOPE_HOME, 'foo') self.assertEqual(__builtin__.ZOPE_HOME, 'foo') self.assertEqual(Globals.ZOPE_HOME, 'foo') def testDebugModeLegacySources(self): import Globals # for data self.setconfig(debug_mode=True) self.assertEqual(Globals.DevelopmentMode, True) self.setconfig(debug_mode=False) self.assertEqual(Globals.DevelopmentMode, False) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(SetConfigTests)) return suite zope2.13-2.13.21/source/Zope2/src/App/tests/test_ApplicationManager.py0000644000175000017500000004435112214017421024257 0ustar arnauarnauimport unittest class ConfigTestBase: def setUp(self): import App.config self._old_config = App.config._config def tearDown(self): import App.config App.config._config = self._old_config def _makeConfig(self, **kw): import App.config class DummyConfig: pass App.config._config = config = DummyConfig() config.dbtab = DummyDBTab(kw) return config class FakeConnectionTests(unittest.TestCase): def _getTargetClass(self): from App.ApplicationManager import FakeConnection return FakeConnection def _makeOne(self, db, parent_jar): return self._getTargetClass()(db, parent_jar) def test_holds_db(self): db = object() parent_jar = object() fc = self._makeOne(db, parent_jar) self.assertTrue(fc.db() is db) class DatabaseChooserTests(ConfigTestBase, unittest.TestCase): def _getTargetClass(self): from App.ApplicationManager import DatabaseChooser return DatabaseChooser def _makeOne(self, id): return self._getTargetClass()(id) def _makeRoot(self): from ExtensionClass import Base class Root(Base): _p_jar = None def getPhysicalRoot(self): return self return Root() def test_getDatabaseNames_sorted(self): self._makeConfig(foo=object(), bar=object(), qux=object()) dc = self._makeOne('test') self.assertEqual(list(dc.getDatabaseNames()), ['bar', 'foo', 'qux']) def test___getitem___miss(self): self._makeConfig(foo=object(), bar=object(), qux=object()) dc = self._makeOne('test') self.assertRaises(KeyError, dc.__getitem__, 'nonesuch') def test___getitem___hit(self): from App.ApplicationManager import AltDatabaseManager from App.ApplicationManager import FakeConnection foo=object() bar=object() qux=object() self._makeConfig(foo=foo, bar=bar, qux=qux) root = self._makeRoot() dc = self._makeOne('test').__of__(root) found = dc['foo'] self.assertTrue(isinstance(found, AltDatabaseManager)) self.assertEqual(found.id, 'foo') self.assertTrue(found.aq_parent is dc) conn = found._p_jar self.assertTrue(isinstance(conn, FakeConnection)) self.assertTrue(conn.db() is foo) def test___bobo_traverse___miss(self): self._makeConfig(foo=object(), bar=object(), qux=object()) dc = self._makeOne('test') self.assertRaises(AttributeError, dc.__bobo_traverse__, None, 'nonesuch') def test___bobo_traverse___hit_db(self): from App.ApplicationManager import AltDatabaseManager from App.ApplicationManager import FakeConnection foo=object() bar=object() qux=object() self._makeConfig(foo=foo, bar=bar, qux=qux) root = self._makeRoot() dc = self._makeOne('test').__of__(root) found = dc.__bobo_traverse__(None, 'foo') self.assertTrue(isinstance(found, AltDatabaseManager)) self.assertEqual(found.id, 'foo') self.assertTrue(found.aq_parent is dc) conn = found._p_jar self.assertTrue(isinstance(conn, FakeConnection)) self.assertTrue(conn.db() is foo) def test___bobo_traverse___miss_db_hit_attr(self): foo=object() bar=object() qux=object() self._makeConfig(foo=foo, bar=bar, qux=qux) root = self._makeRoot() dc = self._makeOne('test').__of__(root) dc.spam = spam = object() found = dc.__bobo_traverse__(None, 'spam') self.assertTrue(found is spam) def test_tpValues(self): from App.ApplicationManager import AltDatabaseManager foo=object() bar=object() qux=object() self._makeConfig(foo=foo, bar=bar, qux=qux) root = self._makeRoot() dc = self._makeOne('test').__of__(root) values = dc.tpValues() self.assertEqual(len(values), 3) self.assertTrue(isinstance(values[0], AltDatabaseManager)) self.assertEqual(values[0].id, 'bar') self.assertEqual(values[0]._p_jar, None) self.assertTrue(isinstance(values[1], AltDatabaseManager)) self.assertEqual(values[1].id, 'foo') self.assertEqual(values[1]._p_jar, None) self.assertTrue(isinstance(values[2], AltDatabaseManager)) self.assertEqual(values[2].id, 'qux') self.assertEqual(values[2]._p_jar, None) class DebugManagerTests(unittest.TestCase): def setUp(self): import sys self._sys = sys self._old_sys_modules = sys.modules.copy() def tearDown(self): self._sys.modules.clear() self._sys.modules.update(self._old_sys_modules) def _getTargetClass(self): from App.ApplicationManager import DebugManager return DebugManager def _makeOne(self, id): return self._getTargetClass()(id) def _makeModuleClasses(self): import sys import types from ExtensionClass import Base class Foo(Base): pass class Bar(Base): pass class Baz(Base): pass foo = sys.modules['foo'] = types.ModuleType('foo') foo.Foo = Foo Foo.__module__ = 'foo' foo.Bar = Bar Bar.__module__ = 'foo' qux = sys.modules['qux'] = types.ModuleType('qux') qux.Baz = Baz Baz.__module__ = 'qux' return Foo, Bar, Baz def test_refcount_no_limit(self): import sys dm = self._makeOne('test') Foo, Bar, Baz = self._makeModuleClasses() pairs = dm.refcount() # XXX : Ugly empiricism here: I don't know why the count is up 1. foo_count = sys.getrefcount(Foo) self.assertTrue((foo_count+1, 'foo.Foo') in pairs) bar_count = sys.getrefcount(Bar) self.assertTrue((bar_count+1, 'foo.Bar') in pairs) baz_count = sys.getrefcount(Baz) self.assertTrue((baz_count+1, 'qux.Baz') in pairs) def test_refdict(self): import sys dm = self._makeOne('test') Foo, Bar, Baz = self._makeModuleClasses() mapping = dm.refdict() # XXX : Ugly empiricism here: I don't know why the count is up 1. foo_count = sys.getrefcount(Foo) self.assertEqual(mapping['foo.Foo'], foo_count+1) bar_count = sys.getrefcount(Bar) self.assertEqual(mapping['foo.Bar'], bar_count+1) baz_count = sys.getrefcount(Baz) self.assertEqual(mapping['qux.Baz'], baz_count+1) def test_rcsnapshot(self): import sys import App.ApplicationManager from DateTime.DateTime import DateTime dm = self._makeOne('test') Foo, Bar, Baz = self._makeModuleClasses() before = DateTime() dm.rcsnapshot() after = DateTime() # XXX : Ugly empiricism here: I don't know why the count is up 1. self.assertTrue(before <= App.ApplicationManager._v_rst <= after) mapping = App.ApplicationManager._v_rcs foo_count = sys.getrefcount(Foo) self.assertEqual(mapping['foo.Foo'], foo_count+1) bar_count = sys.getrefcount(Bar) self.assertEqual(mapping['foo.Bar'], bar_count+1) baz_count = sys.getrefcount(Baz) self.assertEqual(mapping['qux.Baz'], baz_count+1) def test_rcdate(self): import App.ApplicationManager dummy = object() App.ApplicationManager._v_rst = dummy dm = self._makeOne('test') found = dm.rcdate() App.ApplicationManager._v_rst = None self.assertTrue(found is dummy) def test_rcdeltas(self): dm = self._makeOne('test') dm.rcsnapshot() Foo, Bar, Baz = self._makeModuleClasses() mappings = dm.rcdeltas() self.assertTrue(len(mappings)) mapping = mappings[0] self.assertTrue('rc' in mapping) self.assertTrue('pc' in mapping) self.assertEqual(mapping['delta'], mapping['rc'] - mapping['pc']) #def test_dbconnections(self): XXX -- TOO UGLY TO TEST #def test_manage_profile_stats(self): XXX -- TOO UGLY TO TEST def test_manage_profile_reset(self): import sys from ZPublisher import Publish _old_sys__ps_ = getattr(sys, '_ps_', self) _old_Publish_pstat = getattr(Publish, '_pstat', self) sys._ps_ = Publish._pstat = object() try: dm = self._makeOne('test') dm.manage_profile_reset() finally: if _old_sys__ps_ is not self: sys._ps_ = _old_sys__ps_ if _old_Publish_pstat is not self: Publish._pstat = _old_Publish_pstat self.assertTrue(sys._ps_ is None) self.assertTrue(Publish._pstat is None) def test_manage_getSysPath(self): import sys dm = self._makeOne('test') self.assertEqual(dm.manage_getSysPath(), list(sys.path)) class DBProxyTestsBase: def _makeOne(self): return self._getTargetClass()() def _makeJar(self, dbname, dbsize): class Jar: def db(self): return self._db jar = Jar() jar._db = DummyDB(dbname, dbsize) return jar def test_db_name(self): am = self._makeOne() am._p_jar = self._makeJar('foo', '') self.assertEqual(am.db_name(), 'foo') def test_db_size_string(self): am = self._makeOne() am._p_jar = self._makeJar('foo', 'super') self.assertEqual(am.db_size(), 'super') def test_db_size_lt_1_meg(self): am = self._makeOne() am._p_jar = self._makeJar('foo', 4497) self.assertEqual(am.db_size(), '4.4K') def test_db_size_gt_1_meg(self): am = self._makeOne() am._p_jar = self._makeJar('foo', (2048 * 1024) + 123240) self.assertEqual(am.db_size(), '2.1M') def test_manage_pack(self): am = self._makeOne() jar = am._p_jar = self._makeJar('foo', '') am.manage_pack(1, _when=86400*2) self.assertEqual(jar._db._packed, 86400) class ApplicationManagerTests(ConfigTestBase, DBProxyTestsBase, unittest.TestCase, ): def setUp(self): ConfigTestBase.setUp(self) self._tempdirs = () def tearDown(self): import shutil for tempdir in self._tempdirs: shutil.rmtree(tempdir) ConfigTestBase.tearDown(self) def _getTargetClass(self): from App.ApplicationManager import ApplicationManager return ApplicationManager def _makeTempdir(self): import tempfile tmp = tempfile.mkdtemp() self._tempdirs += (tmp,) return tmp def _makeFile(self, dir, name, text): import os os.makedirs(dir) fqn = os.path.join(dir, name) f = open(fqn, 'w') f.write(text) f.flush() f.close() return fqn def test_version_txt(self): from App.version_txt import version_txt am = self._makeOne() self.assertEqual(am.version_txt(), version_txt()) def test_sys_version(self): import sys am = self._makeOne() self.assertEqual(am.sys_version(), sys.version) def test_sys_platform(self): import sys am = self._makeOne() self.assertEqual(am.sys_platform(), sys.platform) def test_ctor_initializes_Products(self): from App.Product import ProductFolder am = self._makeOne() self.assertTrue(isinstance(am.Products, ProductFolder)) def test__canCopy(self): am = self._makeOne() self.assertFalse(am._canCopy()) def test_manage_app(self): from zExceptions import Redirect am = self._makeOne() try: am.manage_app('http://example.com/foo') except Redirect, v: self.assertEqual(v.args, ('http://example.com/foo/manage',)) else: self.fail('Redirect not raised') def test_process_time_seconds(self): am = self._makeOne() am.process_start = 0 self.assertEqual(am.process_time(0).strip(), '0 sec') self.assertEqual(am.process_time(1).strip(), '1 sec') self.assertEqual(am.process_time(2).strip(), '2 sec') def test_process_time_minutes(self): am = self._makeOne() am.process_start = 0 self.assertEqual(am.process_time(60).strip(), '1 min 0 sec') self.assertEqual(am.process_time(61).strip(), '1 min 1 sec') self.assertEqual(am.process_time(62).strip(), '1 min 2 sec') self.assertEqual(am.process_time(120).strip(), '2 min 0 sec') self.assertEqual(am.process_time(121).strip(), '2 min 1 sec') self.assertEqual(am.process_time(122).strip(), '2 min 2 sec') def test_process_time_hours(self): am = self._makeOne() am.process_start = 0 n1 = 60 * 60 n2 = n1 * 2 self.assertEqual(am.process_time(n1).strip(), '1 hour 0 sec') self.assertEqual(am.process_time(n1 + 61).strip(), '1 hour 1 min 1 sec') self.assertEqual(am.process_time(n2 + 1).strip(), '2 hours 1 sec') self.assertEqual(am.process_time(n2 + 122).strip(), '2 hours 2 min 2 sec') def test_process_time_days(self): am = self._makeOne() am.process_start = 0 n1 = 60 * 60 * 24 n2 = n1 * 2 self.assertEqual(am.process_time(n1).strip(), '1 day 0 sec') self.assertEqual(am.process_time(n1 + 3661).strip(), '1 day 1 hour 1 min 1 sec') self.assertEqual(am.process_time(n2 + 1).strip(), '2 days 1 sec') self.assertEqual(am.process_time(n2 + 7322).strip(), '2 days 2 hours 2 min 2 sec') def test_thread_get_ident(self): import thread am = self._makeOne() self.assertEqual(am.thread_get_ident(), thread.get_ident()) #def test_manage_restart(self): XXX -- TOO UGLY TO TEST #def test_manage_restart(self): XXX -- TOO UGLY TO TEST def test_revert_points(self): am = self._makeOne() self.assertEqual(list(am.revert_points()), []) def test_version_list(self): # XXX this method is too stupid to live: returning a bare list # of versions without even tying them to the products? # and what about products living outside SOFTWARE_HOME? # Nobody calls it, either import os am = self._makeOne() config = self._makeConfig() swdir = config.softwarehome = self._makeTempdir() foodir = os.path.join(swdir, 'Products', 'foo') self._makeFile(foodir, 'VERSION.TXT', '1.2') bardir = os.path.join(swdir, 'Products', 'bar') self._makeFile(bardir, 'VERSION.txt', '3.4') bazdir = os.path.join(swdir, 'Products', 'baz') self._makeFile(bazdir, 'version.txt', '5.6') versions = am.version_list() self.assertEqual(versions, ['3.4', '5.6', '1.2']) def test_getSOFTWARE_HOME_missing(self): am = self._makeOne() config = self._makeConfig() self.assertEqual(am.getSOFTWARE_HOME(), None) def test_getSOFTWARE_HOME_present(self): am = self._makeOne() config = self._makeConfig() swdir = config.softwarehome = self._makeTempdir() self.assertEqual(am.getSOFTWARE_HOME(), swdir) def test_getZOPE_HOME_missing(self): am = self._makeOne() config = self._makeConfig() self.assertEqual(am.getZOPE_HOME(), None) def test_getZOPE_HOME_present(self): am = self._makeOne() config = self._makeConfig() zopedir = config.zopehome = self._makeTempdir() self.assertEqual(am.getZOPE_HOME(), zopedir) def test_getINSTANCE_HOME(self): am = self._makeOne() config = self._makeConfig() instdir = config.instancehome = self._makeTempdir() self.assertEqual(am.getINSTANCE_HOME(), instdir) def test_getCLIENT_HOME(self): am = self._makeOne() config = self._makeConfig() cldir = config.clienthome = self._makeTempdir() self.assertEqual(am.getCLIENT_HOME(), cldir) def test_getServers(self): from asyncore import socket_map class DummySocketServer: def __init__(self, port): self.port = port class AnotherSocketServer(DummySocketServer): pass class NotAServer: pass am = self._makeOne() _old_socket_map = socket_map.copy() socket_map.clear() socket_map['foo'] = DummySocketServer(45) socket_map['bar'] = AnotherSocketServer(57) socket_map['qux'] = NotAServer() try: pairs = am.getServers() finally: socket_map.clear() socket_map.update(_old_socket_map) self.assertEqual(len(pairs), 2) self.assertTrue((str(DummySocketServer), 'Port: 45') in pairs) self.assertTrue((str(AnotherSocketServer), 'Port: 57') in pairs) #def test_objectIds(self): XXX -- TOO UGLY TO TEST (BBB for Zope 2.3!!) class AltDatabaseManagerTests(DBProxyTestsBase, unittest.TestCase, ): def _getTargetClass(self): from App.ApplicationManager import AltDatabaseManager return AltDatabaseManager class DummyDBTab: def __init__(self, databases=None): self._databases = databases or {} def listDatabaseNames(self): return self._databases.keys() def hasDatabase(self, name): return name in self._databases def getDatabase(self, name): return self._databases[name] class DummyDB: _packed = None def __init__(self, name, size): self._name = name self._size = size def getName(self): return self._name def getSize(self): return self._size def pack(self, when): self._packed = when def test_suite(): return unittest.TestSuite(( unittest.makeSuite(FakeConnectionTests), unittest.makeSuite(DatabaseChooserTests), unittest.makeSuite(DebugManagerTests), unittest.makeSuite(ApplicationManagerTests), unittest.makeSuite(AltDatabaseManagerTests), )) zope2.13-2.13.21/source/Zope2/src/App/tests/testUndo.py0000644000175000017500000000060712214017421021263 0ustar arnauarnauimport unittest class TestUndoSupport(unittest.TestCase): def test_interfaces(self): from App.interfaces import IUndoSupport from App.Undo import UndoSupport from zope.interface.verify import verifyClass verifyClass(IUndoSupport, UndoSupport) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestUndoSupport), )) zope2.13-2.13.21/source/Zope2/src/App/tests/test_Extensions.py0000644000175000017500000003637512214017421022667 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Unit tests for App.Extensions module """ import unittest class FuncCodeTests(unittest.TestCase): def _getTargetClass(self): from App.Extensions import FuncCode return FuncCode def _makeOne(self, f, im=0): return self._getTargetClass()(f, im) def test_ctor_not_method_no_args(self): def f(): pass fc = self._makeOne(f) self.assertEqual(fc.co_varnames, ()) self.assertEqual(fc.co_argcount, 0) def test_ctor_not_method_w_args(self): def f(a, b): pass fc = self._makeOne(f) self.assertEqual(fc.co_varnames, ('a', 'b')) self.assertEqual(fc.co_argcount, 2) def test_ctor_w_method_no_args(self): def f(self): pass fc = self._makeOne(f, im=1) self.assertEqual(fc.co_varnames, ()) self.assertEqual(fc.co_argcount, 0) def test_ctor_w_method_w_args(self): def f(self, a, b): pass fc = self._makeOne(f, im=1) self.assertEqual(fc.co_varnames, ('a', 'b')) self.assertEqual(fc.co_argcount, 2) def test___cmp___None(self): def f(self): pass fc = self._makeOne(f, im=1) self.assertTrue(cmp(fc, None) > 0) def test___cmp___non_FuncCode(self): def f(self): pass fc = self._makeOne(f, im=1) self.assertTrue(cmp(fc, object()) > 0) def test___cmp___w_FuncCode_same_args(self): def f(self, a, b): pass def g(self, a, b): pass fc = self._makeOne(f, im=1) fc2 = self._makeOne(g, im=1) self.assertTrue(cmp(fc, fc2) == 0) def test___cmp___w_FuncCode_different_args(self): def f(self): pass def g(self, a, b): pass fc = self._makeOne(f, im=1) fc2 = self._makeOne(g, im=1) self.assertTrue(cmp(fc, fc2) < 0) class _TempdirBase: _old_Products___path__ = None _tmpdirs = () _old_sys_path = None _added_path = None def tearDown(self): import shutil if self._old_Products___path__ is not None: import Products Products.__path__ = self._old_Products___path__ for tmpdir in self._tmpdirs: shutil.rmtree(tmpdir) if self._old_sys_path is not None: import sys sys.path[:] = self._old_sys_path for k, v in sys.modules.items(): if getattr(v, '__file__', '').startswith(self._added_path): del sys.modules[k] def _makeTempdir(self): import tempfile tmp = tempfile.mkdtemp() self._tmpdirs += (tmp,) return tmp def _makePathDir(self): import sys dir = self._makeTempdir() self._old_sys_path = sys.path[:] sys.path.insert(0, dir) self._added_path = dir return dir def _makeTempExtension(self, name='foo', extname='Extensions', dir=None): import os if dir is None: dir = self._makeTempdir() if name is None: extdir = os.path.join(dir, extname) else: extdir = os.path.join(dir, name, extname) os.makedirs(extdir) return extdir def _makeTempProduct(self, name='foo', extname='Extensions'): import Products self._old_Products___path__ = Products.__path__[:] root = self._makeTempdir() pdir = self._makeTempExtension(name=name, extname=extname, dir=root) Products.__path__ = (root,) return pdir def _makeFile(self, dir, name, text='#extension'): import os fqn = os.path.join(dir, name) f = open(fqn, 'w') f.write(text) f.flush() f.close() return fqn class Test_getPath(_TempdirBase, unittest.TestCase): def _callFUT(self, prefix, name, checkProduct=1, suffixes=('',), cfg=None): from App.Extensions import getPath return getPath(prefix, name, checkProduct, suffixes, cfg) def _makeConfig(self, **kw): class DummyConfig: def __init__(self, **kw): self.__dict__.update(kw) return DummyConfig(**kw) def test_name_as_path_raises(self): self.assertRaises(ValueError, self._callFUT, 'Extensions', 'foo/bar') def test_found_in_product(self): instdir = self._makeTempdir() swdir = self._makeTempdir() cfg = self._makeConfig(instancehome=instdir, softwarehome=swdir, ) extdir = self._makeTempProduct() ext = self._makeFile(extdir, 'extension.py') path = self._callFUT('Extensions', 'foo.extension', suffixes=('py',), cfg=cfg) self.assertEqual(path, ext) def test_not_found_in_product(self): instdir = self._makeTempdir() swdir = self._makeTempdir() cfg = self._makeConfig(instancehome=instdir, softwarehome=swdir, ) extdir = self._makeTempProduct() ext = self._makeFile(extdir, 'extension.py') path = self._callFUT('Extensions', 'foo.other', suffixes=('py',), cfg=cfg) self.assertEqual(path, None) def test_wo_checkProduct_skips_product(self): import Products self._old_Products___path__ = Products.__path__ del Products.__path__ # so any iteration will raise instdir = self._makeTempdir() instext = self._makeTempExtension(name=None, dir=instdir) instfqn = self._makeFile(instext, 'extension.py') swdir = self._makeTempdir() swext = self._makeTempExtension(name=None, dir=swdir) swfqn = self._makeFile(swext, 'extension.py') cfg = self._makeConfig(instancehome=instdir, softwarehome=swdir, ) path = self._callFUT('Extensions', 'extension', checkProduct=0, suffixes=('py',), cfg=cfg) self.assertEqual(path, instfqn) def test_w_cfg_extensions(self): cfgdir = self._makeTempdir() cfgfqn = self._makeFile(cfgdir, 'extension.py') instdir = self._makeTempdir() instext = self._makeTempExtension(name=None, dir=instdir) instfqn = self._makeFile(instext, 'extension.py') swdir = self._makeTempdir() swext = self._makeTempExtension(name=None, dir=swdir) swfqn = self._makeFile(swext, 'extension.py') cfg = self._makeConfig(extensions=cfgdir, instancehome=instdir, softwarehome=swdir, ) path = self._callFUT('Extensions', 'extension', checkProduct=0, suffixes=('py',), cfg=cfg) self.assertEqual(path, cfgfqn) def test_not_found_in_instancehome(self): import os instdir = self._makeTempdir() zopedir = self._makeTempdir() swdir = os.path.join(zopedir, 'src') os.mkdir(swdir) zopeext = self._makeTempExtension(name=None, dir=zopedir) zopefqn = self._makeFile(zopeext, 'extension.py') cfg = self._makeConfig(instancehome=instdir, softwarehome=swdir, ) path = self._callFUT('Extensions', 'extension', suffixes=('py',), cfg=cfg) self.assertEqual(path, zopefqn) def test_no_swhome(self): instdir = self._makeTempdir() cfg = self._makeConfig(instancehome=instdir, ) extdir = self._makeTempProduct() path = self._callFUT('Extensions', 'extension', suffixes=('py',), cfg=cfg) self.assertEqual(path, None) def test_search_via_import_one_dot(self): import os instdir = self._makeTempdir() cfg = self._makeConfig(instancehome=instdir, ) pathdir = self._makePathDir() pkgdir = os.path.join(pathdir, 'somepkg') os.mkdir(pkgdir) self._makeFile(pkgdir, '__init__.py', '#package') pkgext = self._makeTempExtension(name=None, dir=pkgdir) pkgfqn = self._makeFile(pkgext, 'extension.py') path = self._callFUT('Extensions', 'somepkg.extension', suffixes=('py',), cfg=cfg) self.assertEqual(path, pkgfqn) def test_search_via_import_multiple_dots(self): import os instdir = self._makeTempdir() cfg = self._makeConfig(instancehome=instdir, ) pathdir = self._makePathDir() pkgdir = os.path.join(pathdir, 'somepkg') os.mkdir(pkgdir) self._makeFile(pkgdir, '__init__.py', '#package') subpkgdir = os.path.join(pkgdir, 'subpkg') os.mkdir(subpkgdir) self._makeFile(subpkgdir, '__init__.py', '#subpackage') subpkgext = self._makeTempExtension(name=None, dir=subpkgdir) subpkgfqn = self._makeFile(subpkgext, 'extension.py') path = self._callFUT('Extensions', 'somepkg.subpkg.extension', suffixes=('py',), cfg=cfg) self.assertEqual(path, subpkgfqn) """ Index: lib/python/App/Extensions.py =================================================================== --- lib/python/App/Extensions.py (revision 28473) +++ lib/python/App/Extensions.py (working copy) @@ -87,8 +87,14 @@ r = _getPath(product_dir, os.path.join(p, prefix), n, suffixes) if r is not None: return r + import App.config cfg = App.config.getConfiguration() + + if (prefix=="Extensions") and (cfg.extensions is not None): + r=_getPath(cfg.extensions, '', name, suffixes) + if r is not None: return r + sw=os.path.dirname(os.path.dirname(cfg.softwarehome)) for home in (cfg.instancehome, sw): r=_getPath(home, prefix, name, suffixes) """ class Test_getObject(_TempdirBase, unittest.TestCase): def _callFUT(self, module, name, reload=0, modules=None): from App.Extensions import getObject if modules is not None: return getObject(module, name, reload, modules) return getObject(module, name, reload) def test_cache_miss(self): from zExceptions import NotFound MODULES = {'somemodule': {}} self.assertRaises(NotFound, self._callFUT, 'somemodule', 'name', modules=MODULES) def test_cache_hit(self): obj = object() MODULES = {'somemodule': {'name': obj}} found = self._callFUT('somemodule', 'name', modules=MODULES) self.assertTrue(found is obj) def test_no_such_module(self): from zExceptions import NotFound MODULES = {} self.assertRaises(NotFound, self._callFUT, 'nonesuch', 'name', modules=MODULES) self.assertFalse('nonesuch' in MODULES) def test_not_found_in_module(self): from zExceptions import NotFound MODULES = {} extdir = self._makeTempProduct() ext = self._makeFile(extdir, 'extension.py') self.assertRaises(NotFound, self._callFUT, 'foo.extension', 'name', modules=MODULES) self.assertTrue('foo.extension' in MODULES) self.assertFalse('named' in MODULES['foo.extension']) def test_found_in_module(self): MODULES = {} extdir = self._makeTempProduct() ext = self._makeFile(extdir, 'extension.py', EXTENSION_PY) found = self._callFUT('foo.extension', 'named', modules=MODULES) self.assertEqual(found, 'NAMED') self.assertTrue('foo.extension' in MODULES) self.assertEqual(MODULES['foo.extension']['named'], 'NAMED') def test_found_in_module_pyc(self): from compileall import compile_dir import os MODULES = {} extdir = self._makeTempProduct() ext = self._makeFile(extdir, 'extension.py', EXTENSION_PY) compile_dir(extdir, quiet=1) os.remove(ext) found = self._callFUT('foo.extension', 'named', modules=MODULES) self.assertEqual(found, 'NAMED') self.assertTrue('foo.extension' in MODULES) self.assertEqual(MODULES['foo.extension']['named'], 'NAMED') def test_found_in_module_after_cache_miss(self): cached = {} MODULES = {'foo.extension': cached} extdir = self._makeTempProduct() ext = self._makeFile(extdir, 'extension.py', EXTENSION_PY) found = self._callFUT('foo.extension', 'named', modules=MODULES) self.assertEqual(found, 'NAMED') self.assertEqual(cached['named'], 'NAMED') def test_found_in_module_after_cache_hit_but_reload(self): cached = {'named': 'BEFORE'} MODULES = {'foo.extension': cached} extdir = self._makeTempProduct() ext = self._makeFile(extdir, 'extension.py', EXTENSION_PY) found = self._callFUT('foo.extension', 'named', reload=1, modules=MODULES) self.assertEqual(found, 'NAMED') self.assertEqual(cached['named'], 'NAMED') class Test_getBrain(_TempdirBase, unittest.TestCase): def _callFUT(self, module, name, reload=0, modules=None): from App.Extensions import getBrain if modules is not None: return getBrain(module, name, reload, modules) return getBrain(module, name, reload) def test_no_module_no_class_yields_NoBrains(self): from App.Extensions import NoBrains self.assertTrue(self._callFUT('', '') is NoBrains) def test_missing_name(self): from App.Extensions import NoBrains from zExceptions import NotFound self.assertTrue(self._callFUT('', '') is NoBrains) MODULES = {'somemodule': {}} self.assertRaises(NotFound, self._callFUT, 'somemodule', 'name', modules=MODULES) def test_not_a_class(self): from App.Extensions import NoBrains self.assertTrue(self._callFUT('', '') is NoBrains) MODULES = {'somemodule': {'name': object()}} self.assertRaises(ValueError, self._callFUT, 'somemodule', 'name', modules=MODULES) def test_found_class(self): from App.Extensions import NoBrains self.assertTrue(self._callFUT('', '') is NoBrains) MODULES = {'somemodule': {'name': self.__class__}} self.assertEqual(self._callFUT('somemodule', 'name', modules=MODULES), self.__class__) EXTENSION_PY = """\ named = 'NAMED' """ def test_suite(): return unittest.TestSuite(( unittest.makeSuite(FuncCodeTests), unittest.makeSuite(Test_getPath), unittest.makeSuite(Test_getObject), unittest.makeSuite(Test_getBrain), )) zope2.13-2.13.21/source/Zope2/src/App/tests/test_class_init.py0000644000175000017500000000233712214017421022647 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests class initialization. """ def test_InitializeClass(): """Test that InitializeClass (default__class_init__) works in specific corner cases. Check when the class has an ExtensionClass as attribute. >>> import ExtensionClass >>> from AccessControl.class_init import InitializeClass >>> class AnotherClass(ExtensionClass.Base): ... _need__name__ = 1 >>> class C: ... foo = AnotherClass >>> InitializeClass(C) """ from doctest import DocTestSuite import unittest def test_suite(): return unittest.TestSuite(( DocTestSuite(), )) zope2.13-2.13.21/source/Zope2/src/App/tests/__init__.py0000644000175000017500000000005012214017421021205 0ustar arnauarnau# Needed to make this a Python package. zope2.13-2.13.21/source/Zope2/src/App/tests/test_cachemanager.py0000644000175000017500000000346512214017421023120 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for the CacheManager. """ import unittest class DummyConnection: def __init__(self, db): self.__db = db def db(self): return self.__db class DummyDB: def __init__(self, cache_size): self._set_sizes(cache_size) def _set_sizes(self, cache_size): self.__cache_size = cache_size def getCacheSize(self): return self.__cache_size class CacheManagerTestCase(unittest.TestCase): def _getManagerClass(self): from App.CacheManager import CacheManager class TestCacheManager(CacheManager): # Derived CacheManager that fakes enough of the DatabaseManager to # make it possible to test at least some parts of the CacheManager. def __init__(self, connection): self._p_jar = connection return TestCacheManager def test_cache_size(self): db = DummyDB(42) connection = DummyConnection(db) manager = self._getManagerClass()(connection) self.assertEqual(manager.cache_size(), 42) db._set_sizes(12) self.assertEqual(manager.cache_size(), 12) def test_suite(): return unittest.makeSuite(CacheManagerTestCase) zope2.13-2.13.21/source/Zope2/src/App/tests/testPersistentExtra.py0000644000175000017500000000074012214017421023520 0ustar arnauarnauimport unittest class TestPersistent(unittest.TestCase): def test_interfaces(self): from App.interfaces import IPersistentExtra from Persistence import Persistent from zope.interface.verify import verifyClass from App.PersistentExtra import patchPersistent patchPersistent() verifyClass(IPersistentExtra, Persistent) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestPersistent), )) zope2.13-2.13.21/source/Zope2/src/App/tests/testManagement.py0000644000175000017500000000060712214017421022432 0ustar arnauarnauimport unittest class TestNavigation(unittest.TestCase): def test_interfaces(self): from App.interfaces import INavigation from App.Management import Navigation from zope.interface.verify import verifyClass verifyClass(INavigation, Navigation) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestNavigation), )) zope2.13-2.13.21/source/Zope2/src/App/dtml/0000755000175000017500000000000012214017421016677 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/App/dtml/manage_tabs.dtml0000644000175000017500000001237712214017421022034 0ustar arnauarnau
       ">
     href="&dtml-action;"href="" target="&dtml-target;">   href="&dtml-action;"href="" target="&dtml-target;"> 

    &dtml-meta_type; &dtml-meta_type; Object at This item has been locked by WebDAV
    ()
    zope2.13-2.13.21/source/Zope2/src/App/dtml/readme.dtml0000644000175000017500000000022012214017421021010 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/App/dtml/manage_page_style.css.dtml0000644000175000017500000000557412214017421024027 0ustar arnauarnau h1 { font-family: Verdana, Helvetica, sans-serif; font-size: 24pt; font-weight: bold; } h2 { font-family: Verdana, Helvetica, sans-serif; font-size: 18pt; font-weight: bold; } h3 { font-family: Verdana, Helvetica, sans-serif; font-size: 14pt; font-weight: bold; } a:hover { font-family: Verdana, Helvetica, sans-serif; text-decoration: underline; color: #333333; } a:link { font-family: Verdana, Helvetica, sans-serif; text-decoration: none; color: #000099; } a { font-family: Verdana, Helvetica, sans-serif; text-decoration: none; color: #000099; } a.strong-link { font-family: Verdana, Helvetica, sans-serif; text-decoration: underline; color: #000099; } a img { border: 0; } p { font-family: Verdana, Helvetica, sans-serif; font-size: 10pt; color: #333333; } th { font-family: Verdana, Helvetica, sans-serif; font-weight: bold; font-size: 10pt; color: #333333; } .form-help { font-family: Verdana, Helvetica, sans-serif; font-size: 10pt; color: #333333; } .std-text { font-family: Verdana, Helvetica, sans-serif; font-size: 10pt; color: #333333; } .tab-small { font-family: Verdana, Helvetica, sans-serif; font-size: 8pt; color: #333333; } .location-bar { background-color: #efefef; border: none; } .strong-header { font-family: Verdana, Helvetica, sans-serif; font-size: 12pt; font-weight: bold; background-color: #000000; color: #ffffff; } .list-header { background-color: #c0c0c0; border: none; } .list-item { font-family: Verdana, Helvetica, sans-serif; font-size: 10pt; } .list-nav { font-family: Verdana, Helvetica, sans-serif; font-size: 10pt; font-weight: bold; } .row-normal { background-color: #ffffff; border: none; } .row-hilite { background-color: #efefef; border: none; } .section-bar { background-color: #c0c0c0; border: none; } .system-msg { font-family: Verdana, Helvetica, sans-serif; font-size: 10pt; background-color: #ffffff; border: 1px solid #000000; margin-bottom: 6px; margin-top: 6px; padding: 4px; color: #660033; } .form-title { font-family: Verdana, Helvetica, sans-serif; font-weight: bold; font-size: 12pt; color: #333333; } .form-label { font-family: Verdana, Helvetica, sans-serif; font-weight: bold; font-size: 10pt; color: #333333; } .form-optional { font-family: Verdana, Helvetica, sans-serif; font-weight: bold; font-style: italic; font-size: 10pt; color: #333333; } .form-element { font-family: Verdana, Helvetica, sans-serif; font-size: 10pt; } .form-text { font-family: Verdana, Helvetica, sans-serif; font-size: 10pt; color: #333333; } .form-mono { font-family: monospace; font-size: 12px; text-decoration: none; } zope2.13-2.13.21/source/Zope2/src/App/dtml/traceback.dtml0000644000175000017500000000022012214017421021472 0ustar arnauarnau

    Import Traceback

    &dtml-import_error_;
    zope2.13-2.13.21/source/Zope2/src/App/dtml/activity.dtml0000644000175000017500000000502112214017421021413 0ustar arnauarnau

    Recent Database Activity

    Keep History (seconds)
    Displayed Range &dtml-start_time; to
    &dtml-end_time;



    Object stores &dtml-store_count;   Total: &dtml-total_store_count;
    Object loads &dtml-load_count;   Total: &dtml-total_load_count;
    Connections &dtml-connections;   Total: &dtml-total_connections;
    &dtml-time_offset;
    zope2.13-2.13.21/source/Zope2/src/App/dtml/undo.dtml0000644000175000017500000000602512214017421020531 0ustar arnauarnau

    This application's transactional feature allows you to easily undo changes made to the application's settings or data. You can revert the application to a "snapshot" of its state at a previous point in time.

    Select one or more transactions below and then click on the "Undo" button to undo those transactions. Note that even though a transaction is shown below, you may not be able to undo it if later transactions modified objects that were modified by a selected transaction.

    by &dtml-user_name;Zope

    There are no transactions that can be undone.

    zope2.13-2.13.21/source/Zope2/src/App/dtml/cacheGC.dtml0000644000175000017500000000242712214017421021043 0ustar arnauarnau
    Minimize
    Remove all objects from all ZODB in-memory caches

    Cache Details

    Object ClassCount
    &dtml-sequence-key;&dtml-sequence-item;

    Objects in the cache

    Object ID Object Class Reference Count References
    &dtml-oid; &dtml-klass; &dtml-rc; &dtml-references;
    zope2.13-2.13.21/source/Zope2/src/App/dtml/profile.dtml0000644000175000017500000000472612214017421021232 0ustar arnauarnau

    Profiling data was reset.

    Profiling information is generated using the standard Python profiler. To learn how to interpret the profiler statistics, see the Python profiler documentation.


    Sort: Limit: strip Dirs: checked> Mode:  

    &dtml-stats;
    

    Profiling is not currently enabled or there is not yet any profiling data to report. To enable profiling, restart the Zope process with the configuration setting 'publisher-profile-file' defined. The value of this variable should be the full system path to a file that will be used to dump a profile report when the process restarts or exits.
    zope2.13-2.13.21/source/Zope2/src/App/dtml/zope_quick_start.dtml0000644000175000017500000000413012214017421023145 0ustar arnauarnau Zope QuickStart

    You have not created any users in this Zope instance. In order to log in and manage this Zope instance, you'll need to add an administrative user account.

    You can create an administrative user account via the "zopectl adduser" command from a shell. Note: You'll need to shut Zope itself down before "zopectl adduser" will work. Restart Zope after executing this command in order to log in.

    Welcome to Zope, a high-performance object-oriented platform for building dynamic Web applications. Here are some quick pointers to get you started:

    • Read The Fine Manual. This document guides you through the whole process of learning Zope, from logging in for the first time to creating your own web applications.

    • Go to the main Documentation Overview on Zope.org. Here you will find pointers to official and community contributed documentation.

    • Look at the various Mailing Lists about Zope. The Mailing Lists are where you can get quick, accurate, friendly help from a large community of Zope users from around the world.

    • Go directly to the Zope Management Interface if you'd like to start working with Zope right away.

    zope2.13-2.13.21/source/Zope2/src/App/dtml/manage.dtml0000644000175000017500000000227512214017421021017 0ustar arnauarnau Zope on &dtml-BASE0; /manage_top_frame" name="manage_top_frame" marginheight="0" scrolling="no"/> /manage_menu" name="manage_menu" marginwidth="2" marginheight="2" scrolling="auto"/> /manage_workspace" name="manage_main" marginwidth="2" marginheight="2" scrolling="auto"/> Management interfaces require the use of a <B>frames-capable</B> web browser. zope2.13-2.13.21/source/Zope2/src/App/dtml/davLockManager.dtml0000644000175000017500000000565712214017421022454 0ustar arnauarnau

    All locked objects from path &dtml-frompath; are listed below.

    No locked objects from path &dtml-frompath; were found.

    Search for locked objects starting from path:

    Locked Item Lock Info
    [&dtml-meta_type;] Owner: &dtml-owner;, Token: &dtml-token;
     
    zope2.13-2.13.21/source/Zope2/src/App/dtml/manage_page_footer.dtml0000644000175000017500000000002012214017421023353 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/App/dtml/cacheParameters.dtml0000644000175000017500000000463412214017421022657 0ustar arnauarnau
    Total number of objects in the database
    &dtml-database_size;
    Total number of objects in memory from all caches
    &dtml-cache_length;
    Target number of objects in memory per cache
    &dtml-cache_size;
    Target memory size per cache in bytes
    &dtml-cache_length_bytes;
    Total number of objects in each cache:
    Cache Name
    Number of active objects
    Total active and non-active objects
    &dtml-connection;
    &dtml-ngsize;
    &dtml-size;
    Total
    &dtml-cache_length;

    Cache Details

    Object ClassCount
    &dtml-sequence-key;&dtml-sequence-item;

    Objects in the cache

    Object ID Object Class Reference Count References
    &dtml-oid; &dtml-klass; &dtml-rc; &dtml-references;
    zope2.13-2.13.21/source/Zope2/src/App/dtml/menu.dtml0000644000175000017500000000477112214017421020536 0ustar arnauarnau
    &dtml-meta_type; Root Folder &dtml-id;
    &dtml-meta_type; &dtml-id;
    © Zope Foundation
    Refresh
    Save layout
    Logged in as &dtml-AUTHENTICATED_USER;   
     
    zope2.13-2.13.21/source/Zope2/src/App/dtml/manage_style_test.dtml0000644000175000017500000000710612214017421023274 0ustar arnauarnau System Stylesheet Test &dtml-HTTP_USER_AGENT;

    Text Styles

    This is small-st. This is small-st. This is small-st. This is small-st.

    This is small-un. This is small-un. This is small-un. This is small-un.

    This is small-em. This is small-em. This is small-em. This is small-em.

    This is small-tx. This is small-tx. This is small-tx. This is small-tx.

    This is normal-st. This is normal-st. This is normal-st. This is normal-st.

    This is normal-un. This is normal-un. This is normal-un. This is normal-un.

    This is normal-em. This is normal-em. This is normal-em. This is normal-em.

    This is normal-tx. This is normal-tx. This is normal-tx. This is normal-tx.

    This is large-st. This is large-st. This is large-st. This is large-st.

    This is large-un. This is large-un. This is large-un. This is large-un.

    This is large-em. This is large-em. This is large-em. This is large-em.

    This is large-tx. This is large-tx. This is large-tx. This is large-tx.

    This is some text. This is some text. This is some text. This is some text. This is some text. This is some text. This is some text. This is some text. This is some text. This is some text. This is some text. This is some text. This is some text. This is some text. This is some text. This is some text. This is some text. This is some text.

    Form Elements



    This is option 1
    This is option 2
    This is option 3

    This is option 1
    This is option 2
    This is option 3


    zope2.13-2.13.21/source/Zope2/src/App/dtml/manage_zmi_prefs.dtml0000644000175000017500000000620712214017421023074 0ustar arnauarnau

    This form lets you tweak the Zope management interface to fit your browser. Note that you need to press the browser reload button in order for some changes to take effect.

    Show Top Frame
    checked="checked" /> Yes checked="checked" /> No
    Use Style Sheets
    checked="checked" /> Yes checked="checked" /> No
    Text Area Width
    Text Area Height
    zope2.13-2.13.21/source/Zope2/src/App/dtml/dbMain.dtml0000644000175000017500000000240112214017421020750 0ustar arnauarnau

    The Database Manager allows you to view database status information. It also allows you to perform maintenance tasks such as database packing and cache management.

    Database Location
    &dtml-db_name;
    Database Size
    &dtml-db_size;

    Click pack to pack the Zope database, removing previous revisions of objects that are older than days.
    zope2.13-2.13.21/source/Zope2/src/App/dtml/refresh.dtml0000644000175000017500000000706312214017421021225 0ustar arnauarnau Arguments for this method: id, refresh_txt, error_type, error_value, error_tb, devel_mode, auto_refresh_enabled, auto_refresh_other, dependent_products, loaded_modules &dtml-form_title;

    &dtml-form_title;

    The refresh function, designed to ease the development of Zope products, is not currently enabled for this product. To make it available, put a file named "refresh.txt" in the &dtml-id; product directory. Please note that not all products are compatible with the refresh function.

    An exception occurred during the last refresh.
    Exception type: &dtml-error_type;
    Exception value: &dtml-error_value;

    &dtml-error_tb;

    Important information about refreshing this product:

    Auto refresh is enabled. Zope will repeatedly scan for changes to the Python modules that make up this product and execute a refresh when needed. Although auto refresh is enabled, Zope is not in development mode so auto refresh is not available. Use the "-D" argument when starting Zope to enable development mode. Auto refresh is disabled. Enable auto refresh to cause Zope to frequently scan this product for changes. Note that auto refresh can slow down Zope considerably if enabled for more than a few products.
    Auto refresh mode  

    Select dependent auto-refreshable products to be refreshed simultaneously.

    Refreshable product modules:

    • &dtml-sequence-item;
    zope2.13-2.13.21/source/Zope2/src/App/dtml/editFactory.dtml0000644000175000017500000000355612214017421022047 0ustar arnauarnau

    The initial method is the method that will be invoked when a user adds a new object. This must be one of the objects in the product, typically a Document.

    Id
    &dtml-id;
    Title
    Add list name
    Initial method
    Permission
    zope2.13-2.13.21/source/Zope2/src/App/dtml/manage_form_title.dtml0000644000175000017500000000107012214017421023233 0ustar arnauarnau
    &dtml-form_title;
     
    zope2.13-2.13.21/source/Zope2/src/App/dtml/cpContents.dtml0000644000175000017500000000545412214017421021711 0ustar arnauarnau

    The Control Panel provides access to system information and management functions such as database and product management.

    Zope Version
    &dtml-version_txt;
    Python Version
    &dtml-sys_version;
    System Platform
    &dtml-sys_platform;
    INSTANCE_HOME
    &dtml-getINSTANCE_HOME;
    CLIENT_HOME
    &dtml-getCLIENT_HOME;
    Network Services
    &dtml-sequence-key; (&dtml-sequence-item;)
    Process Id
    &dtml-process_id; (&dtml-thread_get_ident;)
    Running For
    &dtml-process_time;
     

    zope2.13-2.13.21/source/Zope2/src/App/dtml/debug.dtml0000644000175000017500000000536512214017421020660 0ustar arnauarnau <dtml-if title>&dtml-title;</dtml-if>

    Debug Information

    • Zope version: &dtml-version_txt;
    • Python version: &dtml-sys_version;
    • System Platform: &dtml-sys_platform;
    • INSTANCE_HOME: &dtml-getINSTANCE_HOME;
    • CLIENT_HOME: &dtml-getCLIENT_HOME;
    • Process ID: &dtml-process_id; (&dtml-thread_get_ident;)
    • Running for: &dtml-process_time;
    • sys.path:
        &dtml-sequence-item;
    • Top Refcounts:


      Class Delta
      &dtml-name; &dtml-pc; &dtml-rc; +&dtml-delta;

      Cache detail | Cache extreme detail

      Update Snapshot | Stop auto refresh Refresh | Auto refresh interval (seconds):

    • Connections:
      openedinfo
      &dtml-opened;&dtml-info;

    zope2.13-2.13.21/source/Zope2/src/App/dtml/manage_page_header.dtml0000644000175000017500000000202312214017421023312 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/App/dtml/manage_top_frame.dtml0000644000175000017500000000242412214017421023047 0ustar arnauarnau
    Logged in as &dtml-AUTHENTICATED_USER;     
    zope2.13-2.13.21/source/Zope2/src/App/dtml/copyright.dtml0000644000175000017500000001502312214017421021572 0ustar arnauarnau Zope Copyright

    Zope License | Python License | Zope Credits

    Zope Public License (ZPL) Version 2.1

    This software is Copyright © Zope Foundation and Contributors. All rights reserved.

    This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF).

    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

    1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer.
    2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution.
    3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders.
    4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders.
    5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.

    Disclaimer

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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.

    This software consists of contributions made by Zope Corporation and many individuals on behalf of Zope Corporation. Specific attributions are listed in the accompanying credits file.


    This software is powered by Python!

    Copyright © 1991-1995 by Stichting Mathematisch Centrum, Amsterdam, The Netherlands.

    All Rights Reserved

    Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the names of Stichting Mathematisch Centrum or CWI or Corporation for National Research Initiatives or CNRI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission.

    While CWI is the initial source for this software, a modified version is made available by the Corporation for National Research Initiatives (CNRI) at the Internet address ftp://ftp.python.org.

    STICHTING MATHEMATISCH CENTRUM AND CNRI DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM OR CNRI BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.


    Zope Credits

    The Zope software receives contributions from far and wide. Here's the Zope Hall of Fame:

    Paul Everitt, paul@zope.com.

    zope2.13-2.13.21/source/Zope2/src/App/Product.py0000644000175000017500000002156712214017421017744 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Product objects """ # The new Product model: # # Products may be defined in the Products folder or by placing directories # in lib/python/Products. # # Products in lib/python/Products may have up to three sources of information: # # - Static information defined via Python. This information is # described and made available via __init__.py. # # - Dynamic object data that gets copied into the Bobobase. # This is contained in product.dat (which is obfuscated). # # - Static extensions supporting the dynamic data. These too # are obfuscated. # # Products may be copied and pasted only within the products folder. # # If a product is deleted (or cut), it is automatically recreated # on restart if there is still a product directory. import os from AccessControl.class_init import InitializeClass from AccessControl.owner import UnownableOwner from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.unauthorized import Unauthorized from App.special_dtml import DTMLFile from OFS.Folder import Folder class ProductFolder(Folder): "Manage a collection of Products" id = 'Products' name = title = 'Product Management' meta_type = 'Product Management' icon = 'p_/ProductFolder_icon' all_meta_types=() meta_types=() # This prevents subobjects from being owned! _owner = UnownableOwner def _product(self, name): return getattr(self, name) def _canCopy(self, op=0): return 0 InitializeClass(ProductFolder) class Product(Folder): """Model a product that can be created through the web. """ security = ClassSecurityInfo() meta_type='Product' icon='p_/Product_icon' version='' configurable_objects_=() import_error_=None manage_options = ( (Folder.manage_options[0],) + tuple(Folder.manage_options[2:]) ) _properties = Folder._properties+( {'id':'version', 'type': 'string'}, ) _reserved_names=('Help',) def __init__(self, id, title): from HelpSys.HelpSys import ProductHelp self.id = id self.title = title self._setObject('Help', ProductHelp('Help', id)) security.declarePublic('Destination') def Destination(self): "Return the destination for factory output" return self security.declarePublic('DestinationURL') def DestinationURL(self): "Return the URL for the destination for factory output" return self.REQUEST['BASE4'] manage_traceback = DTMLFile('dtml/traceback', globals()) manage_readme = DTMLFile('dtml/readme', globals()) def manage_get_product_readme__(self): for name in ('README.txt', 'README.TXT', 'readme.txt'): path = os.path.join(self.home, name) if os.path.isfile(path): return open(path).read() return '' def permissionMappingPossibleValues(self): return self.possible_permissions() def getProductHelp(self): """Returns the ProductHelp object associated with the Product. """ from HelpSys.HelpSys import ProductHelp if not hasattr(self, 'Help'): self._setObject('Help', ProductHelp('Help', self.id)) return self.Help # # Product refresh # _refresh_dtml = DTMLFile('dtml/refresh', globals()) def _readRefreshTxt(self, pid=None): import Products refresh_txt = None if pid is None: pid = self.id for productDir in Products.__path__: found = 0 for name in ('refresh.txt', 'REFRESH.txt', 'REFRESH.TXT'): p = os.path.join(productDir, pid, name) if os.path.exists(p): found = 1 break if found: try: file = open(p) text = file.read() file.close() refresh_txt = text break except: # Not found here. pass return refresh_txt def manage_performRefresh(self, REQUEST=None): """ Attempts to perform a refresh operation. """ from App.RefreshFuncs import performFullRefresh if self._readRefreshTxt() is None: raise Unauthorized, 'refresh.txt not found' message = None if performFullRefresh(self._p_jar, self.id): from ZODB import Connection Connection.resetCaches() # Clears cache in future connections. message = 'Product refreshed.' else: message = 'An exception occurred.' if REQUEST is not None: return self.manage_refresh(REQUEST, manage_tabs_message=message) def manage_enableAutoRefresh(self, enable=0, REQUEST=None): """ Changes the auto refresh flag for this product. """ from App.RefreshFuncs import enableAutoRefresh if self._readRefreshTxt() is None: raise Unauthorized, 'refresh.txt not created' enableAutoRefresh(self._p_jar, self.id, enable) if enable: message = 'Enabled auto refresh.' else: message = 'Disabled auto refresh.' if REQUEST is not None: return self.manage_refresh(REQUEST, manage_tabs_message=message) def manage_selectDependentProducts(self, selections=(), REQUEST=None): """ Selects which products to refresh simultaneously. """ from App.RefreshFuncs import setDependentProducts if self._readRefreshTxt() is None: raise Unauthorized, 'refresh.txt not created' setDependentProducts(self._p_jar, self.id, selections) if REQUEST is not None: return self.manage_refresh(REQUEST) InitializeClass(Product) def initializeProduct(productp, name, home, app): # Initialize a persistent product assert doInstall() import Globals # to set data fver = '' if hasattr(productp, '__import_error__'): ie = productp.__import_error__ else: ie = None # Retrieve version number from any suitable version.txt for fname in ('version.txt', 'VERSION.txt', 'VERSION.TXT'): try: fpath = os.path.join(home, fname) fhandle = open(fpath, 'r') fver = fhandle.read().strip() fhandle.close() break except IOError: continue old = None products = app.Control_Panel.Products try: if ihasattr(products, name): old=getattr(products, name) if ihasattr(old,'version') and old.version==fver: if hasattr(old, 'import_error_') and \ old.import_error_==ie: # Version hasn't changed. Don't reinitialize. return old except: pass f = fver and (" (%s)" % fver) product=Product(name, 'Installed product %s%s' % (name, f)) if old is not None: app._manage_remove_product_meta_type(product) products._delObject(name) for id, v in old.objectItems(): try: product._setObject(id, v) except: pass products._setObject(name, product) product.home = home if ie: product.import_error_=ie product.title='Broken product %s' % name product.icon='p_/BrokenProduct_icon' product.manage_options=( {'label':'Traceback', 'action':'manage_traceback'}, ) for name in ('README.txt', 'README.TXT', 'readme.txt'): path = os.path.join(home, name) if os.path.isfile(path): product.manage_options=product.manage_options+( {'label':'README', 'action':'manage_readme'}, ) break # Ensure this product has a refresh tab. found = 0 for option in product.manage_options: if option.get('label') == 'Refresh': found = 1 break if not found: product.manage_options = product.manage_options + ( {'label':'Refresh', 'action':'manage_refresh'}, ) return product def ihasattr(o, name): return hasattr(o, name) and o.__dict__.has_key(name) def doInstall(): from App.config import getConfiguration return getConfiguration().enable_product_installation zope2.13-2.13.21/source/Zope2/src/App/CacheManager.py0000644000175000017500000002132412214017421020611 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## '''Cache management support. This class is mixed into the database manager in App.ApplicationManager. ''' from AccessControl.class_init import InitializeClass from App.special_dtml import DTMLFile from App.ImageFile import ImageFile from DateTime.DateTime import DateTime class CacheManager: """Cache management mix-in """ _cache_age = 60 _vcache_age = 60 _history_length = 3600 # Seconds manage_cacheParameters = DTMLFile('dtml/cacheParameters', globals()) manage_cacheGC = DTMLFile('dtml/cacheGC', globals()) transparent_bar = ImageFile('www/transparent_bar.gif', globals()) store_bar = ImageFile('www/store_bar.gif', globals()) load_bar = ImageFile('www/load_bar.gif', globals()) def _getDB(self): return self._p_jar.db() def cache_length(self): return self._getDB().cacheSize() def cache_length_bytes(self): return self._getDB().getCacheSizeBytes() def cache_detail_length(self): return self._getDB().cacheDetailSize() def database_size(self): return self._getDB().objectCount() def cache_age(self): return self._cache_age def manage_cache_age(self,value,REQUEST): "set cache age" db = self._getDB() self._cache_age = value db.setCacheDeactivateAfter(value) if REQUEST is not None: response=REQUEST['RESPONSE'] response.redirect(REQUEST['URL1']+'/manage_cacheParameters') def cache_size(self): db = self._getDB() return db.getCacheSize() def manage_cache_size(self,value,REQUEST): "set cache size" db = self._getDB() db.setCacheSize(value) if REQUEST is not None: response=REQUEST['RESPONSE'] response.redirect(REQUEST['URL1']+'/manage_cacheParameters') def manage_full_sweep(self,value,REQUEST): "Perform a full sweep through the cache" db = self._getDB() db.cacheFullSweep(value) if REQUEST is not None: response=REQUEST['RESPONSE'] response.redirect(REQUEST['URL1']+'/manage_cacheGC') def manage_minimize(self,value=1,REQUEST=None): "Perform a full sweep through the cache" # XXX Add a deprecation warning about value? self._getDB().cacheMinimize() if REQUEST is not None: response=REQUEST['RESPONSE'] response.redirect(REQUEST['URL1']+'/manage_cacheGC') def cache_detail(self, REQUEST=None): """ Returns the name of the classes of the objects in the cache and the number of objects in the cache for each class. """ detail = self._getDB().cacheDetail() if REQUEST is not None: # format as text REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain') return '\n'.join( ['%6d %s' % (count, name) for name, count in detail]) # raw return detail def cache_extreme_detail(self, REQUEST=None): """ Returns information about each object in the cache. """ detail = self._getDB().cacheExtremeDetail() if REQUEST is not None: # sort the list. lst = [((dict['conn_no'], dict['oid']), dict) for dict in detail] # format as text. res = [ '# Table shows connection number, oid, refcount, state, ' 'and class.', '# States: L = loaded, G = ghost, C = changed'] for sortkey, dict in lst: id = dict.get('id', None) if id: idinfo = ' (%s)' % id else: idinfo = '' s = dict['state'] if s == 0: state = 'L' # loaded elif s == 1: state = 'C' # changed else: state = 'G' # ghost res.append('%d %-34s %6d %s %s%s' % ( dict['conn_no'], `dict['oid']`, dict['rc'], state, dict['klass'], idinfo)) REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain') return '\n'.join(res) else: # raw return detail def _getActivityMonitor(self): db = self._getDB() if not hasattr(db, 'getActivityMonitor'): return None am = db.getActivityMonitor() if am is None: return None return am def getHistoryLength(self): am = self._getActivityMonitor() if am is None: return 0 return am.getHistoryLength() def manage_setHistoryLength(self, length, REQUEST=None): """Change the length of the activity monitor history. """ am = self._getActivityMonitor() length = int(length) if length < 0: raise ValueError, 'length can not be negative' if am is not None: am.setHistoryLength(length) self._history_length = length # Restore on startup if REQUEST is not None: response = REQUEST['RESPONSE'] response.redirect(REQUEST['URL1'] + '/manage_activity') def getActivityChartData(self, segment_height, REQUEST=None): """Returns information for generating an activity chart. """ am = self._getActivityMonitor() if am is None: return None if REQUEST is not None: start = float(REQUEST.get('chart_start', 0)) end = float(REQUEST.get('chart_end', 0)) divisions = int(REQUEST.get('chart_divisions', 10)) analysis = am.getActivityAnalysis(start, end, divisions) else: analysis = am.getActivityAnalysis() total_load_count = 0 total_store_count = 0 total_connections = 0 limit = 0 divs = [] for div in analysis: total_store_count = total_store_count + div['stores'] total_load_count = total_load_count + div['loads'] total_connections = total_connections + div['connections'] sum = div['stores'] + div['loads'] if sum > limit: limit = sum if analysis: segment_time = analysis[0]['end'] - analysis[0]['start'] else: segment_time = 0 for div in analysis: stores = div['stores'] if stores > 0: store_len = max(int(segment_height * stores / limit), 1) else: store_len = 0 loads = div['loads'] if loads > 0: load_len = max(int(segment_height * loads / limit), 1) else: load_len = 0 t = div['end'] - analysis[-1]['end'] # Show negative numbers. if segment_time >= 3600: # Show hours. time_offset = '%dh' % (t / 3600) elif segment_time >= 60: # Show minutes. time_offset = '%dm' % (t / 60) elif segment_time >= 1: # Show seconds. time_offset = '%ds' % t else: # Show fractions. time_offset = '%.2fs' % t divs.append({ 'store_len': store_len, 'load_len': load_len, 'trans_len': max(segment_height - store_len - load_len, 0), 'store_count': div['stores'], 'load_count': div['loads'], 'connections': div['connections'], 'start': div['start'], 'end': div['end'], 'time_offset': time_offset, }) if analysis: start_time = DateTime(divs[0]['start']).aCommonZ() end_time = DateTime(divs[-1]['end']).aCommonZ() else: start_time = '' end_time = '' res = {'start_time': start_time, 'end_time': end_time, 'divs': divs, 'total_store_count': total_store_count, 'total_load_count': total_load_count, 'total_connections': total_connections, } return res InitializeClass(CacheManager) zope2.13-2.13.21/source/Zope2/src/App/version_txt.py0000644000175000017500000000346412214017421020704 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Extract Zope 2 version information $id$ """ import re import sys import pkg_resources _version_string = None _zope_version = None def _prep_version_data(): global _version_string, _zope_version if _version_string is None: v = sys.version_info pyver = "python %d.%d.%d, %s" % (v[0], v[1], v[2], sys.platform) dist = pkg_resources.get_distribution('Zope2') expr = re.compile( r'(?P[0-9]+)\.(?P[0-9]+)\.(?P[0-9]+)' '(?P[A-Za-z]+)?(?P[0-9]+)?') dict = expr.match(dist.version).groupdict() _zope_version = ( int(dict.get('major') or -1), int(dict.get('minor') or -1), int(dict.get('micro') or -1), dict.get('status') or '', int(dict.get('release') or -1), ) _version_string = "%s, %s" % (dist.version, pyver) def version_txt(): _prep_version_data() return '(%s)' % _version_string def getZopeVersion(): """ Format of zope_version tuple: (major , minor , micro , status , release ) If unreleased, integers may be -1. """ _prep_version_data() return _zope_version zope2.13-2.13.21/source/Zope2/src/App/FindHomes.py0000644000175000017500000000536712214017421020200 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Stick directory information in the built-in namespace.""" import __builtin__ import os import sys import Products try: home = os.environ['SOFTWARE_HOME'] except KeyError: pass else: home = os.path.realpath(home) __builtin__.SOFTWARE_HOME = SOFTWARE_HOME = home try: zhome = os.environ['ZOPE_HOME'] except KeyError: pass else: zhome = os.path.realpath(zhome) __builtin__.ZOPE_HOME = ZOPE_HOME = zhome try: chome = os.environ['INSTANCE_HOME'] except KeyError: import Zope2 base = os.path.dirname(Zope2.__file__) base = os.path.join(base, os.path.pardir, os.path.pardir) chome = os.path.realpath(base) else: chome = os.path.realpath(chome) inst_ppath = os.path.join(chome, 'lib', 'python') if os.path.isdir(inst_ppath): sys.path.insert(0, inst_ppath) __builtin__.INSTANCE_HOME = INSTANCE_HOME = chome # CLIENT_HOME allows ZEO clients to easily keep distinct pid and # log files. This is currently an *experimental* feature, as I expect # that increasing ZEO deployment will cause bigger changes to the # way that z2.py works fairly soon. try: CLIENT_HOME = os.environ['CLIENT_HOME'] except KeyError: CLIENT_HOME = os.path.join(INSTANCE_HOME, 'var') __builtin__.CLIENT_HOME = CLIENT_HOME # If there is a Products package in INSTANCE_HOME, add it to the # Products package path ip = os.path.join(INSTANCE_HOME, 'Products') ippart = 0 ppath = Products.__path__ if os.path.isdir(ip) and ip not in ppath: disallow = os.environ.get('DISALLOW_LOCAL_PRODUCTS', '').lower() if disallow in ('no', 'off', '0', ''): ppath.insert(0, ip) ippart = 1 ppathpat = os.environ.get('PRODUCTS_PATH', None) if ppathpat is not None: psep = os.pathsep if ppathpat.find('%(') >= 0: newppath = (ppathpat % { 'PRODUCTS_PATH': psep.join(ppath ), 'SOFTWARE_PRODUCTS': psep.join(ppath[ippart:] ), 'INSTANCE_PRODUCTS': ip, }).split(psep) else: newppath = ppathpat.split(psep) del ppath[:] for p in filter(None, newppath): p = os.path.abspath(p) if os.path.isdir(p) and p not in ppath: ppath.append(p) zope2.13-2.13.21/source/Zope2/src/webdav/0000755000175000017500000000000012214017422016470 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/webdav/NullResource.py0000644000175000017500000004547212214017422021500 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """WebDAV support - null resource objects. """ import sys from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.SecurityManagement import getSecurityManager from AccessControl.Permissions import view as View from AccessControl.Permissions import add_folders from AccessControl.Permissions import webdav_lock_items from AccessControl.Permissions import webdav_unlock_items from Acquisition import aq_base from Acquisition import aq_parent from Acquisition import Implicit from App.special_dtml import DTMLFile from Persistence import Persistent from OFS.CopySupport import CopyError from OFS.SimpleItem import Item_w__name__ from zExceptions import BadRequest from zExceptions import Forbidden from zExceptions import MethodNotAllowed from zExceptions import NotFound from zExceptions import Unauthorized from webdav.common import Conflict from webdav.common import IfParser from webdav.common import isDavCollection from webdav.common import Locked from webdav.common import PreconditionFailed from webdav.common import tokenFinder from webdav.common import UnsupportedMediaType from webdav.davcmds import Lock from webdav.davcmds import Unlock from webdav.interfaces import IWriteLock from webdav.Resource import Resource from zope.contenttype import guess_content_type class NullResource(Persistent, Implicit, Resource): """Null resources are used to handle HTTP method calls on objects which do not yet exist in the url namespace.""" __null_resource__=1 security = ClassSecurityInfo() def __init__(self, parent, name, request=None): self.__name__=name self.__parent__=parent def __bobo_traverse__(self, REQUEST, name=None): # We must handle traversal so that we can recognize situations # where a 409 Conflict must be returned instead of the normal # 404 Not Found, per [WebDAV 8.3.1]. try: return getattr(self, name) except: pass method=REQUEST.get('REQUEST_METHOD', 'GET') if method in ('PUT', 'MKCOL', 'LOCK'): raise Conflict, 'Collection ancestors must already exist.' raise NotFound, 'The requested resource was not found.' security.declareProtected(View, 'HEAD') def HEAD(self, REQUEST, RESPONSE): """Retrieve resource information without a response message body.""" self.dav__init(REQUEST, RESPONSE) RESPONSE.setBody('', lock=True) raise NotFound, 'The requested resource does not exist.' # Most methods return 404 (Not Found) for null resources. DELETE=TRACE=PROPFIND=PROPPATCH=COPY=MOVE=HEAD index_html = HEAD def _default_PUT_factory( self, name, typ, body ): # Return DTMLDoc/PageTemplate/Image/File, based on sniffing. if name and name.endswith('.pt'): from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate ob = ZopePageTemplate(name, body, content_type=typ) elif typ in ('text/html', 'text/xml', 'text/plain'): from OFS.DTMLDocument import DTMLDocument ob = DTMLDocument( '', __name__=name ) elif typ[:6]=='image/': from OFS.Image import Image ob=Image(name, '', body, content_type=typ) else: from OFS.Image import File ob=File(name, '', body, content_type=typ) return ob security.declarePublic('PUT') def PUT(self, REQUEST, RESPONSE): """Create a new non-collection resource. """ from ZServer import LARGE_FILE_THRESHOLD self.dav__init(REQUEST, RESPONSE) name = self.__name__ parent = self.__parent__ ifhdr = REQUEST.get_header('If', '') if IWriteLock.providedBy(parent) and parent.wl_isLocked(): if ifhdr: parent.dav__simpleifhandler(REQUEST, RESPONSE, col=1) else: # There was no If header at all, and our parent is locked, # so we fail here raise Locked elif ifhdr: # There was an If header, but the parent is not locked raise PreconditionFailed # SDS: Only use BODY if the file size is smaller than # LARGE_FILE_THRESHOLD, otherwise read LARGE_FILE_THRESHOLD # bytes from the file which should be enough to trigger # content_type detection, and possibly enough for CMF's # content_type_registry too. # # Note that body here is really just used for detecting the # content type and figuring out the correct factory. The correct # file content will be uploaded on ob.PUT(REQUEST, RESPONSE) after # the object has been created. # # A problem I could see is content_type_registry predicates # that do depend on the whole file being passed here as an # argument. There's none by default that does this though. If # they really do want to look at the file, they should use # REQUEST['BODYFILE'] directly and try as much as possible not # to read the whole file into memory. if int(REQUEST.get('CONTENT_LENGTH') or 0) > LARGE_FILE_THRESHOLD: file = REQUEST['BODYFILE'] body = file.read(LARGE_FILE_THRESHOLD) file.seek(0) else: body = REQUEST.get('BODY', '') typ=REQUEST.get_header('content-type', None) if typ is None: typ, enc=guess_content_type(name, body) factory = getattr(parent, 'PUT_factory', self._default_PUT_factory ) ob = factory(name, typ, body) if ob is None: ob = self._default_PUT_factory(name, typ, body) # We call _verifyObjectPaste with verify_src=0, to see if the # user can create this type of object (and we don't need to # check the clipboard. try: parent._verifyObjectPaste(ob.__of__(parent), 0) except CopyError: sMsg = 'Unable to create object of class %s in %s: %s' % \ (ob.__class__, repr(parent), sys.exc_info()[1],) raise Unauthorized, sMsg # Delegate actual PUT handling to the new object, # SDS: But just *after* it has been stored. self.__parent__._setObject(name, ob) ob = self.__parent__._getOb(name) ob.PUT(REQUEST, RESPONSE) RESPONSE.setStatus(201) RESPONSE.setBody('') return RESPONSE security.declareProtected(add_folders, 'MKCOL') def MKCOL(self, REQUEST, RESPONSE): """Create a new collection resource.""" self.dav__init(REQUEST, RESPONSE) if REQUEST.get('BODY', ''): raise UnsupportedMediaType, 'Unknown request body.' name=self.__name__ parent = self.__parent__ if hasattr(aq_base(parent), name): raise MethodNotAllowed, 'The name %s is in use.' % name if not isDavCollection(parent): raise Forbidden, 'Cannot create collection at this location.' ifhdr = REQUEST.get_header('If', '') if IWriteLock.providedBy(parent) and parent.wl_isLocked(): if ifhdr: parent.dav__simpleifhandler(REQUEST, RESPONSE, col=1) else: raise Locked elif ifhdr: # There was an If header, but the parent is not locked raise PreconditionFailed # Add hook for webdav/FTP MKCOL (Collector #2254) (needed for CMF) # parent.manage_addFolder(name) mkcol_handler = getattr(parent,'MKCOL_handler' ,parent.manage_addFolder) mkcol_handler(name) RESPONSE.setStatus(201) RESPONSE.setBody('') return RESPONSE security.declareProtected(webdav_lock_items, 'LOCK') def LOCK(self, REQUEST, RESPONSE): """ LOCK on a Null Resource makes a LockNullResource instance """ self.dav__init(REQUEST, RESPONSE) security = getSecurityManager() creator = security.getUser() body = REQUEST.get('BODY', '') ifhdr = REQUEST.get_header('If', '') depth = REQUEST.get_header('Depth', 'infinity') name = self.__name__ parent = self.__parent__ if IWriteLock.providedBy(parent) and parent.wl_isLocked(): if ifhdr: parent.dav__simpleifhandler(REQUEST, RESPONSE, col=1) else: raise Locked elif ifhdr: # There was an If header, but the parent is not locked. raise PreconditionFailed # The logic involved in locking a null resource is simpler than # a regular resource, since we know we're not already locked, # and the lock isn't being refreshed. if not body: raise BadRequest, 'No body was in the request' locknull = LockNullResource(name) parent._setObject(name, locknull) locknull = parent._getOb(name) cmd = Lock(REQUEST) token, result = cmd.apply(locknull, creator, depth=depth) if result: # Return the multistatus result (there were multiple errors) # This *shouldn't* happen for locking a NullResource, but it's # inexpensive to handle and is good coverage for any future # changes in davcmds.Lock RESPONSE.setStatus(207) RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setBody(result) else: # The command was succesful lock = locknull.wl_getLock(token) RESPONSE.setStatus(200) RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setHeader('Lock-Token', 'opaquelocktoken:' + token) RESPONSE.setBody(lock.asXML()) InitializeClass(NullResource) class LockNullResource(NullResource, Item_w__name__): """ A Lock-Null Resource is created when a LOCK command is succesfully executed on a NullResource, essentially locking the Name. A PUT or MKCOL deletes the LockNull resource from its container and replaces it with the target object. An UNLOCK deletes it. """ __locknull_resource__ = 1 meta_type = 'WebDAV LockNull Resource' security = ClassSecurityInfo() manage_options = ({'label': 'Info', 'action': 'manage_main'},) security.declareProtected(View, 'manage') security.declareProtected(View, 'manage_main') manage = manage_main = DTMLFile('dtml/locknullmain', globals()) security.declareProtected(View, 'manage_workspace') manage_workspace = manage manage_main._setName('manage_main') # explicit def __no_valid_write_locks__(self): # A special hook (for better or worse) called when there are no # valid locks left. We have to delete ourselves from our container # now. parent = aq_parent(self) if parent: parent._delObject(self.id) def __init__(self, name): self.id = self.__name__ = name self.title = "LockNull Resource '%s'" % name security.declarePublic('title_or_id') def title_or_id(self): return 'Foo' def PROPFIND(self, REQUEST, RESPONSE): """Retrieve properties defined on the resource.""" return Resource.PROPFIND(self, REQUEST, RESPONSE) security.declareProtected(webdav_lock_items, 'LOCK') def LOCK(self, REQUEST, RESPONSE): """ A Lock command on a LockNull resource should only be a refresh request (one without a body) """ self.dav__init(REQUEST, RESPONSE) body = REQUEST.get('BODY', '') ifhdr = REQUEST.get_header('If', '') if body: # If there's a body, then this is a full lock request # which conflicts with the fact that we're already locked RESPONSE.setStatus(423) else: # There's no body, so this is likely to be a refresh request if not ifhdr: raise PreconditionFailed taglist = IfParser(ifhdr) found = 0 for tag in taglist: for listitem in tag.list: token = tokenFinder(listitem) if token and self.wl_hasLock(token): lock = self.wl_getLock(token) timeout = REQUEST.get_header('Timeout', 'infinite') lock.setTimeout(timeout) # Automatically refreshes found = 1 RESPONSE.setStatus(200) RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setBody(lock.asXML()) if found: break if not found: RESPONSE.setStatus(412) # Precondition failed return RESPONSE security.declareProtected(webdav_unlock_items, 'UNLOCK') def UNLOCK(self, REQUEST, RESPONSE): """ Unlocking a Null Resource removes it from its parent """ self.dav__init(REQUEST, RESPONSE) security = getSecurityManager() user = security.getUser() token = REQUEST.get_header('Lock-Token', '') url = REQUEST['URL'] if token: token = tokenFinder(token) else: raise BadRequest, 'No lock token was submitted in the request' cmd = Unlock() result = cmd.apply(self, token, url) parent = aq_parent(self) parent._delObject(self.id) if result: RESPONSE.setStatus(207) RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setBody(result) else: RESPONSE.setStatus(204) return RESPONSE security.declarePublic('PUT') def PUT(self, REQUEST, RESPONSE): """ Create a new non-collection resource, deleting the LockNull object from the container before putting the new object in. """ self.dav__init(REQUEST, RESPONSE) name = self.__name__ parent = self.aq_parent parenturl = parent.absolute_url() ifhdr = REQUEST.get_header('If', '') # Since a Lock null resource is always locked by definition, all # operations done by an owner of the lock that affect the resource # MUST have the If header in the request if not ifhdr: raise PreconditionFailed, 'No If-header' # First we need to see if the parent of the locknull is locked, and # if the user owns that lock (checked by handling the information in # the If header). if IWriteLock.providedBy(parent) and parent.wl_isLocked(): itrue = parent.dav__simpleifhandler(REQUEST, RESPONSE, 'PUT', col=1, url=parenturl, refresh=1) if not itrue: raise PreconditionFailed, ( 'Condition failed against resources parent') # Now we need to check the If header against our own lock state itrue = self.dav__simpleifhandler(REQUEST, RESPONSE, 'PUT', refresh=1) if not itrue: raise PreconditionFailed, ( 'Condition failed against locknull resource') # All of the If header tests succeeded, now we need to remove ourselves # from our parent. We need to transfer lock state to the new object. locks = self.wl_lockItems() parent._delObject(name) # Now we need to go through the regular operations of PUT body = REQUEST.get('BODY', '') typ = REQUEST.get_header('content-type', None) if typ is None: typ, enc = guess_content_type(name, body) factory = getattr(parent, 'PUT_factory', self._default_PUT_factory) ob = (factory(name, typ, body) or self._default_PUT_factory(name, typ, body)) # Verify that the user can create this type of object try: parent._verifyObjectPaste(ob.__of__(parent), 0) except Unauthorized: raise except: raise Forbidden, sys.exc_info()[1] # Put the locks on the new object if not IWriteLock.providedBy(ob): raise MethodNotAllowed, ( 'The target object type cannot be locked') for token, lock in locks: ob.wl_setLock(token, lock) # Delegate actual PUT handling to the new object. ob.PUT(REQUEST, RESPONSE) parent._setObject(name, ob) RESPONSE.setStatus(201) RESPONSE.setBody('') return RESPONSE security.declareProtected(add_folders, 'MKCOL') def MKCOL(self, REQUEST, RESPONSE): """ Create a new Collection (folder) resource. Since this is being done on a LockNull resource, this also involves removing the LockNull object and transferring its locks to the newly created Folder """ self.dav__init(REQUEST, RESPONSE) if REQUEST.get('BODY', ''): raise UnsupportedMediaType, 'Unknown request body.' name = self.__name__ parent = self.aq_parent parenturl = parent.absolute_url() ifhdr = REQUEST.get_header('If', '') if not ifhdr: raise PreconditionFailed, 'No If-header' # If the parent object is locked, that information should be in the # if-header if the user owns a lock on the parent if IWriteLock.providedBy(parent) and parent.wl_isLocked(): itrue = parent.dav__simpleifhandler(REQUEST, RESPONSE, 'MKCOL', col=1, url=parenturl, refresh=1) if not itrue: raise PreconditionFailed, ( 'Condition failed against resources parent') # Now we need to check the If header against our own lock state itrue = self.dav__simpleifhandler(REQUEST,RESPONSE,'MKCOL',refresh=1) if not itrue: raise PreconditionFailed, ( 'Condition failed against locknull resource') # All of the If header tests succeeded, now we need to remove ourselves # from our parent. We need to transfer lock state to the new folder. locks = self.wl_lockItems() parent._delObject(name) parent.manage_addFolder(name) folder = parent._getOb(name) for token, lock in locks: folder.wl_setLock(token, lock) RESPONSE.setStatus(201) RESPONSE.setBody('') return RESPONSE InitializeClass(LockNullResource) zope2.13-2.13.21/source/Zope2/src/webdav/Resource.py0000644000175000017500000006435012214017422020641 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """WebDAV support - resource objects. """ import mimetypes import sys import warnings import re from urllib import unquote from AccessControl import getSecurityManager from AccessControl import ClassSecurityInfo from AccessControl.class_init import InitializeClass from AccessControl.Permissions import delete_objects from AccessControl.Permissions import manage_properties from AccessControl.Permissions import view as View from AccessControl.Permissions import webdav_lock_items from AccessControl.Permissions import webdav_unlock_items from AccessControl.Permissions import webdav_access from Acquisition import aq_base from Acquisition import aq_inner from Acquisition import aq_parent from ExtensionClass import Base from OFS.event import ObjectClonedEvent from OFS.event import ObjectWillBeMovedEvent from OFS.subscribers import compatibilityCall from zExceptions import BadRequest from zExceptions import Forbidden from zExceptions import MethodNotAllowed from zExceptions import NotFound from zExceptions import Unauthorized from ZPublisher.HTTPRangeSupport import HTTPRangeInterface from zope.interface import implements from zope.event import notify from zope.lifecycleevent import ObjectCopiedEvent from zope.lifecycleevent import ObjectMovedEvent from zope.container.contained import notifyContainerModified from webdav.Lockable import LockableItem from webdav.Lockable import wl_isLockable from webdav.Lockable import wl_isLocked from webdav.common import absattr from webdav.common import Conflict from webdav.common import IfParser from webdav.common import isDavCollection from webdav.common import Locked from webdav.common import PreconditionFailed from webdav.common import rfc1123_date from webdav.common import tokenFinder from webdav.common import urlbase from webdav.common import urlfix from webdav.interfaces import IDAVResource from webdav.interfaces import IWriteLock ms_dav_agent = re.compile("Microsoft.*Internet Publishing.*") class Resource(Base, LockableItem): """The Resource mixin class provides basic WebDAV support for non-collection objects. It provides default implementations for most supported WebDAV HTTP methods, however certain methods such as PUT should be overridden to ensure correct behavior in the context of the object type.""" implements(IDAVResource) __dav_resource__=1 __http_methods__=('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK', ) security = ClassSecurityInfo() security.setPermissionDefault(webdav_access, ('Authenticated', 'Manager')) def dav__init(self, request, response): # Init expected HTTP 1.1 / WebDAV headers which are not # currently set by the base response object automagically. # # We sniff for a ZServer response object, because we don't # want to write duplicate headers (since ZS writes Date # and Connection itself). if not hasattr(response, '_server_version'): response.setHeader('Connection', 'close') response.setHeader('Date', rfc1123_date(), 1) # HTTP Range support if HTTPRangeInterface.providedBy(self): response.setHeader('Accept-Ranges', 'bytes') else: response.setHeader('Accept-Ranges', 'none') def dav__validate(self, object, methodname, REQUEST): msg='You are not authorized to access this resource.' method=None if hasattr(object, methodname): method=getattr(object, methodname) else: try: method=object.aq_acquire(methodname) except: method=None if method is not None: try: return getSecurityManager().validate(None, object, methodname, method) except: pass raise Unauthorized, msg def dav__simpleifhandler(self, request, response, method='PUT', col=0, url=None, refresh=0): ifhdr = request.get_header('If', None) lockable = wl_isLockable(self) if not lockable: # degenerate case, we shouldnt have even called this method. return None locked = self.wl_isLocked() if locked and (not ifhdr): raise Locked('Resource is locked.') if not ifhdr: return None # Since we're a simple if handler, and since some clients don't # pass in the port information in the resource part of an If # header, we're only going to worry about if the paths compare if url is None: url = urlfix(request['URL'], method) url = urlbase(url) # Gets just the path information # if 'col' is passed in, an operation is happening on a submember # of a collection, while the Lock may be on the parent. Lob off # the final part of the URL (ie '/a/b/foo.html' becomes '/a/b/') if col: url = url[:url.rfind('/')+1] found = 0; resourcetagged = 0 taglist = IfParser(ifhdr) for tag in taglist: if not tag.resource: # There's no resource (url) with this tag tag_list = map(tokenFinder, tag.list) wehave = [t for t in tag_list if self.wl_hasLock(t)] if not wehave: continue if tag.NOTTED: continue if refresh: for token in wehave: self.wl_getLock(token).refresh() resourcetagged = 1 found = 1; break elif urlbase(tag.resource) == url: resourcetagged = 1 tag_list = map(tokenFinder, tag.list) wehave = [t for t in tag_list if self.wl_hasLock(t)] if not wehave: continue if tag.NOTTED: continue if refresh: for token in wehave: self.wl_getLock(token).refresh() found = 1; break if resourcetagged and (not found): raise PreconditionFailed, 'Condition failed.' elif resourcetagged and found: return 1 else: return 0 # WebDAV class 1 support security.declareProtected(View, 'HEAD') def HEAD(self, REQUEST, RESPONSE): """Retrieve resource information without a response body.""" self.dav__init(REQUEST, RESPONSE) content_type=None if hasattr(self, 'content_type'): content_type=absattr(self.content_type) if content_type is None: url=urlfix(REQUEST['URL'], 'HEAD') name=unquote(filter(None, url.split( '/')[-1])) content_type, encoding=mimetypes.guess_type(name) if content_type is None: if hasattr(self, 'default_content_type'): content_type=absattr(self.default_content_type) if content_type is None: content_type = 'application/octet-stream' RESPONSE.setHeader('Content-Type', content_type.lower()) if hasattr(aq_base(self), 'get_size'): RESPONSE.setHeader('Content-Length', absattr(self.get_size)) if hasattr(self, '_p_mtime'): mtime=rfc1123_date(self._p_mtime) RESPONSE.setHeader('Last-Modified', mtime) if hasattr(aq_base(self), 'http__etag'): etag = self.http__etag(readonly=1) if etag: RESPONSE.setHeader('Etag', etag) RESPONSE.setStatus(200) return RESPONSE def PUT(self, REQUEST, RESPONSE): """Replace the GET response entity of an existing resource. Because this is often object-dependent, objects which handle PUT should override the default PUT implementation with an object-specific implementation. By default, PUT requests fail with a 405 (Method Not Allowed).""" self.dav__init(REQUEST, RESPONSE) raise MethodNotAllowed, 'Method not supported for this resource.' security.declarePublic('OPTIONS') def OPTIONS(self, REQUEST, RESPONSE): """Retrieve communication options.""" import webdav self.dav__init(REQUEST, RESPONSE) RESPONSE.setHeader('Allow', ', '.join(self.__http_methods__)) RESPONSE.setHeader('Content-Length', 0) RESPONSE.setHeader('DAV', '1,2', 1) # Microsoft Web Folders compatibility, only enabled if # User-Agent matches. if ms_dav_agent.match(REQUEST.get_header('User-Agent', '')): if webdav.enable_ms_public_header: RESPONSE.setHeader('Public', ', '.join(self.__http_methods__)) if webdav.enable_ms_author_via: RESPONSE.setHeader('MS-Author-Via', 'DAV') RESPONSE.setStatus(200) return RESPONSE security.declarePublic('TRACE') def TRACE(self, REQUEST, RESPONSE): """Return the HTTP message received back to the client as the entity-body of a 200 (OK) response. This will often usually be intercepted by the web server in use. If not, the TRACE request will fail with a 405 (Method Not Allowed), since it is not often possible to reproduce the HTTP request verbatim from within the Zope environment.""" self.dav__init(REQUEST, RESPONSE) raise MethodNotAllowed, 'Method not supported for this resource.' security.declareProtected(delete_objects, 'DELETE') def DELETE(self, REQUEST, RESPONSE): """Delete a resource. For non-collection resources, DELETE may return either 200 or 204 (No Content) to indicate success.""" self.dav__init(REQUEST, RESPONSE) ifhdr = REQUEST.get_header('If', '') url = urlfix(REQUEST['URL'], 'DELETE') name = unquote(filter(None, url.split( '/')[-1])) parent = aq_parent(aq_inner(self)) # Lock checking if wl_isLocked(self): if ifhdr: self.dav__simpleifhandler(REQUEST, RESPONSE, 'DELETE') else: # We're locked, and no if header was passed in, so # the client doesn't own a lock. raise Locked, 'Resource is locked.' elif IWriteLock.providedBy(parent) and parent.wl_isLocked(): if ifhdr: parent.dav__simpleifhandler(REQUEST, RESPONSE, 'DELETE', col=1) else: # Our parent is locked, and no If header was passed in. # When a parent is locked, members cannot be removed raise PreconditionFailed, 'Resource is locked, and no '\ 'condition was passed in.' # Either we're not locked, or a succesful lock token was submitted # so we can delete the lock now. # ajung: Fix for Collector # 2196 if parent.manage_delObjects([name],REQUEST=None) is None: RESPONSE.setStatus(204) else: RESPONSE.setStatus(403) return RESPONSE security.declareProtected(webdav_access, 'PROPFIND') def PROPFIND(self, REQUEST, RESPONSE): """Retrieve properties defined on the resource.""" from webdav.davcmds import PropFind self.dav__init(REQUEST, RESPONSE) cmd = PropFind(REQUEST) result = cmd.apply(self) # work around MSIE DAV bug for creation and modified date if (REQUEST.get_header('User-Agent') == 'Microsoft Data Access Internet Publishing Provider DAV 1.1'): result = result.replace('', '') result = result.replace('', '') RESPONSE.setStatus(207) RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setBody(result) return RESPONSE security.declareProtected(manage_properties, 'PROPPATCH') def PROPPATCH(self, REQUEST, RESPONSE): """Set and/or remove properties defined on the resource.""" from webdav.davcmds import PropPatch self.dav__init(REQUEST, RESPONSE) if not hasattr(aq_base(self), 'propertysheets'): raise MethodNotAllowed, ( 'Method not supported for this resource.') # Lock checking ifhdr = REQUEST.get_header('If', '') if wl_isLocked(self): if ifhdr: self.dav__simpleifhandler(REQUEST, RESPONSE, 'PROPPATCH') else: raise Locked, 'Resource is locked.' cmd = PropPatch(REQUEST) result = cmd.apply(self) RESPONSE.setStatus(207) RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setBody(result) return RESPONSE def MKCOL(self, REQUEST, RESPONSE): """Create a new collection resource. If called on an existing resource, MKCOL must fail with 405 (Method Not Allowed).""" self.dav__init(REQUEST, RESPONSE) raise MethodNotAllowed, 'The resource already exists.' security.declarePublic('COPY') def COPY(self, REQUEST, RESPONSE): """Create a duplicate of the source resource whose state and behavior match that of the source resource as closely as possible. Though we may later try to make a copy appear seamless across namespaces (e.g. from Zope to Apache), COPY is currently only supported within the Zope namespace.""" self.dav__init(REQUEST, RESPONSE) if not hasattr(aq_base(self), 'cb_isCopyable') or \ not self.cb_isCopyable(): raise MethodNotAllowed, 'This object may not be copied.' depth=REQUEST.get_header('Depth', 'infinity') if not depth in ('0', 'infinity'): raise BadRequest, 'Invalid Depth header.' dest=REQUEST.get_header('Destination', '') while dest and dest[-1]=='/': dest=dest[:-1] if not dest: raise BadRequest, 'Invalid Destination header.' try: path = REQUEST.physicalPathFromURL(dest) except ValueError: raise BadRequest, 'Invalid Destination header' name = path.pop() oflag=REQUEST.get_header('Overwrite', 'F').upper() if not oflag in ('T', 'F'): raise BadRequest, 'Invalid Overwrite header.' try: parent=self.restrictedTraverse(path) except ValueError: raise Conflict, 'Attempt to copy to an unknown namespace.' except NotFound: raise Conflict, 'Object ancestors must already exist.' except: t, v, tb=sys.exc_info() raise t, v if hasattr(parent, '__null_resource__'): raise Conflict, 'Object ancestors must already exist.' existing=hasattr(aq_base(parent), name) if existing and oflag=='F': raise PreconditionFailed, 'Destination resource exists.' try: parent._checkId(name, allow_dup=1) except: raise Forbidden, sys.exc_info()[1] try: parent._verifyObjectPaste(self) except Unauthorized: raise except: raise Forbidden, sys.exc_info()[1] # Now check locks. The If header on a copy only cares about the # lock on the destination, so we need to check out the destinations # lock status. ifhdr = REQUEST.get_header('If', '') if existing: # The destination itself exists, so we need to check its locks destob = aq_base(parent)._getOb(name) if IWriteLock.providedBy(destob) and destob.wl_isLocked(): if ifhdr: itrue = destob.dav__simpleifhandler( REQUEST, RESPONSE, 'COPY', refresh=1) if not itrue: raise PreconditonFailed else: raise Locked, 'Destination is locked.' elif IWriteLock.providedBy(parent) and parent.wl_isLocked(): if ifhdr: parent.dav__simpleifhandler(REQUEST, RESPONSE, 'COPY', refresh=1) else: raise Locked, 'Destination is locked.' self._notifyOfCopyTo(parent, op=0) ob = self._getCopy(parent) ob._setId(name) if depth=='0' and isDavCollection(ob): for id in ob.objectIds(): ob._delObject(id) notify(ObjectCopiedEvent(ob, self)) if existing: object=getattr(parent, name) self.dav__validate(object, 'DELETE', REQUEST) parent._delObject(name) parent._setObject(name, ob) ob = parent._getOb(name) ob._postCopy(parent, op=0) compatibilityCall('manage_afterClone', ob, ob) notify(ObjectClonedEvent(ob)) # We remove any locks from the copied object because webdav clients # don't track the lock status and the lock token for copied resources ob.wl_clearLocks() RESPONSE.setStatus(existing and 204 or 201) if not existing: RESPONSE.setHeader('Location', dest) RESPONSE.setBody('') return RESPONSE security.declarePublic('MOVE') def MOVE(self, REQUEST, RESPONSE): """Move a resource to a new location. Though we may later try to make a move appear seamless across namespaces (e.g. from Zope to Apache), MOVE is currently only supported within the Zope namespace.""" self.dav__init(REQUEST, RESPONSE) self.dav__validate(self, 'DELETE', REQUEST) if not hasattr(aq_base(self), 'cb_isMoveable') or \ not self.cb_isMoveable(): raise MethodNotAllowed, 'This object may not be moved.' dest=REQUEST.get_header('Destination', '') try: path = REQUEST.physicalPathFromURL(dest) except ValueError: raise BadRequest, 'No destination given' flag=REQUEST.get_header('Overwrite', 'F') flag=flag.upper() name = path.pop() parent_path = '/'.join(path) try: parent = self.restrictedTraverse(path) except ValueError: raise Conflict, 'Attempt to move to an unknown namespace.' except 'Not Found': raise Conflict, 'The resource %s must exist.' % parent_path except: raise if hasattr(parent, '__null_resource__'): raise Conflict, 'The resource %s must exist.' % parent_path existing=hasattr(aq_base(parent), name) if existing and flag=='F': raise PreconditionFailed, 'Resource %s exists.' % dest try: parent._checkId(name, allow_dup=1) except: raise Forbidden, sys.exc_info()[1] try: parent._verifyObjectPaste(self) except Unauthorized: raise except: raise Forbidden, sys.exc_info()[1] # Now check locks. Since we're affecting the resource that we're # moving as well as the destination, we have to check both. ifhdr = REQUEST.get_header('If', '') if existing: # The destination itself exists, so we need to check its locks destob = aq_base(parent)._getOb(name) if IWriteLock.providedBy(destob) and destob.wl_isLocked(): if ifhdr: itrue = destob.dav__simpleifhandler( REQUEST, RESPONSE, 'MOVE', url=dest, refresh=1) if not itrue: raise PreconditionFailed else: raise Locked, 'Destination is locked.' elif IWriteLock.providedBy(parent) and parent.wl_isLocked(): # There's no existing object in the destination folder, so # we need to check the folders locks since we're changing its # member list if ifhdr: itrue = parent.dav__simpleifhandler(REQUEST, RESPONSE, 'MOVE', col=1, url=dest, refresh=1) if not itrue: raise PreconditionFailed, 'Condition failed.' else: raise Locked, 'Destination is locked.' if wl_isLocked(self): # Lastly, we check ourselves if ifhdr: itrue = self.dav__simpleifhandler(REQUEST, RESPONSE, 'MOVE', refresh=1) if not itrue: raise PreconditionFailed, 'Condition failed.' else: raise Locked('Source is locked and no condition was passed in') orig_container = aq_parent(aq_inner(self)) orig_id = self.getId() self._notifyOfCopyTo(parent, op=1) notify(ObjectWillBeMovedEvent(self, orig_container, orig_id, parent, name)) # try to make ownership explicit so that it gets carried # along to the new location if needed. self.manage_changeOwnershipType(explicit=1) ob = self._getCopy(parent) ob._setId(name) orig_container._delObject(orig_id, suppress_events=True) if existing: object=getattr(parent, name) self.dav__validate(object, 'DELETE', REQUEST) parent._delObject(name) parent._setObject(name, ob, set_owner=0, suppress_events=True) ob = parent._getOb(name) notify(ObjectMovedEvent(ob, orig_container, orig_id, parent, name)) notifyContainerModified(orig_container) if aq_base(orig_container) is not aq_base(parent): notifyContainerModified(parent) ob._postCopy(parent, op=1) # try to make ownership implicit if possible ob.manage_changeOwnershipType(explicit=0) RESPONSE.setStatus(existing and 204 or 201) if not existing: RESPONSE.setHeader('Location', dest) RESPONSE.setBody('') return RESPONSE # WebDAV Class 2, Lock and Unlock security.declareProtected(webdav_lock_items, 'LOCK') def LOCK(self, REQUEST, RESPONSE): """Lock a resource""" from webdav.davcmds import Lock self.dav__init(REQUEST, RESPONSE) security = getSecurityManager() creator = security.getUser() body = REQUEST.get('BODY', '') ifhdr = REQUEST.get_header('If', None) depth = REQUEST.get_header('Depth', 'infinity') alreadylocked = wl_isLocked(self) if body and alreadylocked: # This is a full LOCK request, and the Resource is # already locked, so we need to raise the alreadylocked # exception. RESPONSE.setStatus(423) elif body: # This is a normal lock request with an XML payload cmd = Lock(REQUEST) token, result = cmd.apply(self, creator, depth=depth) if result: # Return the multistatus result (there were multiple # errors. Note that davcmds.Lock.apply aborted the # transaction already. RESPONSE.setStatus(207) RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setBody(result) else: # Success lock = self.wl_getLock(token) RESPONSE.setStatus(200) RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setHeader('Lock-Token', 'opaquelocktoken:' + token) RESPONSE.setBody(lock.asXML()) else: # There's no body, so this likely to be a refresh request if not ifhdr: raise PreconditionFailed, 'If Header Missing' taglist = IfParser(ifhdr) found = 0 for tag in taglist: for listitem in tag.list: token = tokenFinder(listitem) if token and self.wl_hasLock(token): lock = self.wl_getLock(token) timeout = REQUEST.get_header('Timeout', 'Infinite') lock.setTimeout(timeout) # automatically refreshes found = 1 RESPONSE.setStatus(200) RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setBody(lock.asXML()) break if found: break if not found: RESPONSE.setStatus(412) # Precondition failed return RESPONSE security.declareProtected(webdav_unlock_items, 'UNLOCK') def UNLOCK(self, REQUEST, RESPONSE): """Remove an existing lock on a resource.""" from webdav.davcmds import Unlock self.dav__init(REQUEST, RESPONSE) security = getSecurityManager() token = REQUEST.get_header('Lock-Token', '') url = REQUEST['URL'] token = tokenFinder(token) cmd = Unlock() result = cmd.apply(self, token, url) if result: RESPONSE.setStatus(207) RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setBody(result) else: RESPONSE.setStatus(204) # No Content response code return RESPONSE security.declareProtected(webdav_access, 'manage_DAVget') def manage_DAVget(self): """Gets the document source""" # The default implementation calls manage_FTPget return self.manage_FTPget() security.declareProtected(webdav_access, 'listDAVObjects') def listDAVObjects(self): return [] InitializeClass(Resource) zope2.13-2.13.21/source/Zope2/src/webdav/common.py0000644000175000017500000000767312214017422020347 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Commonly used functions for WebDAV support modules.""" import random import re import time import urllib from Acquisition import aq_base from App.Common import iso8601_date from App.Common import rfc1123_date from App.Common import rfc850_date _randGen = random.Random(time.time()) class WebDAVException(Exception): pass class Locked(WebDAVException): pass class PreconditionFailed(WebDAVException): pass class Conflict(WebDAVException): pass class UnsupportedMediaType(WebDAVException): pass def absattr(attr): if callable(attr): return attr() return attr def urljoin(url, s): url = url.rstrip('/') s = s.lstrip('/') return '/'.join((url, s)) def urlfix(url, s): n=len(s) if url[-n:]==s: url=url[:-n] if len(url) > 1 and url[-1]=='/': url=url[:-1] return url def is_acquired(ob): # Return true if this object is not a direct # subobject of its aq_parent object. if not hasattr(ob, 'aq_parent'): return 0 if hasattr(aq_base(ob.aq_parent), absattr(ob.id)): return 0 if hasattr(aq_base(ob), 'isTopLevelPrincipiaApplicationObject') and \ ob.isTopLevelPrincipiaApplicationObject: return 0 return 1 def urlbase(url, ftype=urllib.splittype, fhost=urllib.splithost): # Return a '/' based url such as '/foo/bar', removing # type, host and port information if necessary. if url[0]=='/': return url type, uri=ftype(url) host, uri=fhost(uri) return uri or '/' def generateLockToken(): # Generate a lock token return '%s-%s-00105A989226:%.03f' % \ (_randGen.random(),_randGen.random(),time.time()) def isDavCollection(object): """Return true if object is a DAV collection.""" return getattr(object, '__dav_collection__', 0) def tokenFinder(token): # takes a string like ' and returns the token # part. if not token: return None # An empty string was passed in if token[0] == '[': return None # An Etag was passed in if token[0] == '<': token = token[1:-1] return token[token.find(':')+1:] ### If: header handling support. IfParser returns a sequence of ### TagList objects in the order they were parsed which can then ### be used in WebDAV methods to decide whether an operation can ### proceed or to raise HTTP Error 412 (Precondition failed) IfHdr = re.compile( r"(?P<.+?>)?\s*\((?P[^)]+)\)" ) ListItem = re.compile( r"(?Pnot)?\s*(?P<[a-zA-Z]+:[^>]*>|\[.*?\])", re.I) class TagList: def __init__(self): self.resource = None self.list = [] self.NOTTED = 0 def IfParser(hdr): out = [] i = 0 while 1: m = IfHdr.search(hdr[i:]) if not m: break i = i + m.end() tag = TagList() tag.resource = m.group('resource') if tag.resource: # We need to delete < > tag.resource = tag.resource[1:-1] listitem = m.group('listitem') tag.NOTTED, tag.list = ListParser(listitem) out.append(tag) return out def ListParser(listitem): out = [] NOTTED = 0 i = 0 while 1: m = ListItem.search(listitem[i:]) if not m: break i = i + m.end() out.append(m.group('listitem')) if m.group('not'): NOTTED = 1 return NOTTED, out zope2.13-2.13.21/source/Zope2/src/webdav/litmus-results.txt0000644000175000017500000002056112214017422022251 0ustar arnauarnauZope "litmus" (http://www.webdav.org/neon/litmus/) 0.10.5 test warnings/skips/failures as of 6/17/2007. Command line used: litmus -k http://localhost:8080/ admin admin 'basic' tests: 4. put_get_utf8_segment.. FAIL (PUT of `/litmus/res-%e2%82%ac' failed: 400 Bad Request) Zope considers the id "res-%e2%82%ac" invalid due to the "bad_id" regex in OFS.ObjectManager, which is consulted whenever a new object is added to a container through the OFS objectmanager interface. It's likely possible to replace this regex with a more permissive one via a monkepatch as necessary. 8. delete_fragment....... WARNING: DELETE removed collection resource with Request-URI including fragment; unsafe ZServer strips off the fragment portion of the URL and throws it away, so we never get a chance to detect if a fragment was sent in the URL within appserver code. 'props' tests: 17. prophighunicode....... FAIL (PROPPATCH of property with high unicode value) The exception raised by Zope here is: 2007-06-17 15:27:02 ERROR Zope.SiteErrorLog http://localhost:8080/litmus/prop2/PROPPATCH Traceback (innermost last): Module ZPublisher.Publish, line 119, in publish Module ZPublisher.mapply, line 88, in mapply Module ZPublisher.Publish, line 42, in call_object Module webdav.Resource, line 315, in PROPPATCH Module webdav.davcmds, line 190, in __init__ Module webdav.davcmds, line 226, in parse Module webdav.xmltools, line 98, in strval UnicodeEncodeError: 'latin-1' codec can't encode characters in position 0-1: ordinal not in range(256) This is because the webdav.xmltools.Node.strval method attempts to encode the string representation of the property node to the 'default' propertysheet encoding, which is assumed to be 'latin-1'. The value of the received property cannot be encoded using this encoding. 18. propget............... FAIL (No value given for property {http://webdav.org/neon/litmus/}high-unicode) This is because test 17 fails to set a value. 'locks' tests: 15. cond_put.............. SKIPPED Zope does not appear to send an Etag in normal responses, which this test seems to require as a precondition for execution. See http://www.mnot.net/cache_docs/ for more information about Etags. These tests appear to be skipped for the same reason: 16. fail_cond_put......... SKIPPED 19. complex_cond_put...... SKIPPED 20. fail_complex_cond_put. SKIPPED Zope's webdav package has an webdav.EtagSupport.EtagSupport class which is inherited by the webdav.Lockable.LockableItem class, which is in turn inherited by the webdav.Resource.Resource class, which is in turn inherited by OFS.SimpleItem.SimpleItem (upon which almost all Zope content is based), so potentially all Zope content can reasonably easily generate meaningful ETags in responses. Finding out why it's not generating them appears to be an archaeology exercise. 18. cond_put_corrupt_token FAIL (conditional PUT with invalid lock-token should fail: 204 No Content) I (chrism) haven't been able to fix this without breaking 32. lock_collection, which is a more important interaction. See webdav.tests.testResource.TestResource.donttest_dav__simpleifhandler_cond_put_corrupt_token. 22. fail_cond_put_unlocked FAIL (conditional PUT with invalid lock-token should fail: 204 No Content) I (chrism) haven't been able to fix this without breaking 32. lock_collection, which is a more important interaction. See webdav.tests.testResource.TestResource.donttest_dav__simpleifhandler_fail_cond_put_unlocked. 23. lock_shared........... FAIL (LOCK on `/litmus/lockme': 403 Forbidden) Zope does not support locking resources with lockscope 'shared' (only exclusive locks are supported for any kind of Zope resource). Litmus could probably do a PROPFIND on the /litmus/lockme resource and check the lockscope in the response before declaring this a failure (class 2 DAV servers are not required to support shared locks). The dependent tests below are skipped due to this failure: 24. notowner_modify....... SKIPPED 25. notowner_lock......... SKIPPED 26. owner_modify.......... SKIPPED 27. double_sharedlock..... SKIPPED 28. notowner_modify....... SKIPPED 29. notowner_lock......... SKIPPED 30. unlock................ SKIPPED 34. notowner_modify....... WARNING: DELETE failed with 412 not 423 FAIL (MOVE of locked resource should fail) Unknown reasons (not yet investigated). 36. indirect_refresh...... FAIL (indirect refresh LOCK on /litmus/lockcoll/ via /litmus/lockcoll/lockme.txt: 400 Bad Request) Unknown reason (not yet investigated). 'http' tests: 2. expect100............. FAIL (timeout waiting for interim response) Unknown reason (not yet investigated). additional notes: litmus 0.11 times out on several of the lock tests due to some HTTP-level miscommunication between neon 0.26 and Zope (perhaps, as I've gathered on the litmus maillist, having to do with neon 0.26's expectation to use persistent connections, and perhaps due to some bug in Zope's implementation of same), and this is why litmus 0.11/neon 0.25 was used to do the testing even though litmus 11.0 was available. litmus 0.10.5 times out in a similar fashion on the "http.expect100" test but on none of the lock tests. analyses: Analysis of what happens during locks 32. lock_collection: The first request in this test set is a successful LOCK request with "Depth: infinity" to /litmus/lockcoll (an existing newly-created collection): LOCK /litmus/lockcoll/ HTTP/1.1 Depth: infinity litmus test suite Zope responds to this with a success response like this: infinity litmus test suite Second-3600 opaquelocktoken:{olt} ({olt} in the above quoted response represents an actual valid lock token, not a literal) The next request sent during this test is a conditional PUT request to /litmus/lockcoll/lockme.txt (which doesn't yet exist at the time of the request): PUT /litmus/lockcoll/lockme.txt HTTP/1.1 If: ( now) # there's time remaining def getLockType(): """ returns the lock type ('write') """ def getLockScope(): """ returns the lock scope ('exclusive') """ def asLockDiscoveryProperty(ns='d'): """ Return the lock rendered as an XML representation of a WebDAV 'lockdiscovery' property. 'ns' is the namespace identifier used on the XML elements.""" def asXML(): """ Render a full XML representation of a lock for WebDAV, used when returning the value of a newly created lock. """ class IWriteLock(Interface): """Basic protocol needed to support the write lock machinery. It must be able to answer the questions: o Is the object locked? o Is the lock owned by the current user? o What lock tokens are associated with the current object? o What is their state (how long until they're supposed to time out?, what is their depth? what type are they? And it must be able to do the following: o Grant a write lock on the object to a specified user. - *If lock depth is infinite, this must also grant locks on **all** subobjects, or fail altogether* o Revoke a lock on the object. - *If lock depth is infinite, this must also revoke locks on all subobjects* **All methods in the WriteLock interface that deal with checking valid locks MUST check the timeout values on the lockitem (ie, by calling 'lockitem.isValid()'), and DELETE the lock if it is no longer valid** """ def wl_lockItems(killinvalids=0): """ Returns (key, value) pairs of locktoken, lock. if 'killinvalids' is true, invalid locks (locks whose timeout has been exceeded) will be deleted""" def wl_lockValues(killinvalids=0): """ Returns a sequence of locks. if 'killinvalids' is true, invalid locks will be deleted""" def wl_lockTokens(killinvalids=0): """ Returns a sequence of lock tokens. if 'killinvalids' is true, invalid locks will be deleted""" def wl_hasLock(token, killinvalids=0): """ Returns true if the lock identified by the token is attached to the object. """ def wl_isLocked(): """ Returns true if 'self' is locked at all. If invalid locks still exist, they should be deleted.""" def wl_setLock(locktoken, lock): """ Store the LockItem, 'lock'. The locktoken will be used to fetch and delete the lock. If the lock exists, this MUST overwrite it if all of the values except for the 'timeout' on the old and new lock are the same. """ def wl_getLock(locktoken): """ Returns the locktoken identified by the locktokenuri """ def wl_delLock(locktoken): """ Deletes the locktoken identified by the locktokenuri """ def wl_clearLocks(): """ Deletes ALL DAV locks on the object - should only be called by lock management machinery. """ # XXX: might contain non-API methods and outdated comments; # not synced with ZopeBook API Reference; # based on webdav.Resource.Resource class IDAVResource(IWriteLock): """Provide basic WebDAV support for non-collection objects.""" __dav_resource__ = Bool( title=u"Is DAV resource" ) __http_methods__ = Tuple( title=u"HTTP methods", description=u"Sequence of valid HTTP methods" ) def dav__init(request, response): """Init expected HTTP 1.1 / WebDAV headers which are not currently set by the base response object automagically. Also, we sniff for a ZServer response object, because we don't want to write duplicate headers (since ZS writes Date and Connection itself). """ def dav__validate(object, methodname, REQUEST): """ """ def dav__simpleifhandler(request, response, method='PUT', col=0, url=None, refresh=0): """ """ def HEAD(REQUEST, RESPONSE): """Retrieve resource information without a response body.""" def PUT(REQUEST, RESPONSE): """Replace the GET response entity of an existing resource. Because this is often object-dependent, objects which handle PUT should override the default PUT implementation with an object-specific implementation. By default, PUT requests fail with a 405 (Method Not Allowed).""" def OPTIONS(REQUEST, RESPONSE): """Retrieve communication options.""" def TRACE(REQUEST, RESPONSE): """Return the HTTP message received back to the client as the entity-body of a 200 (OK) response. This will often usually be intercepted by the web server in use. If not, the TRACE request will fail with a 405 (Method Not Allowed), since it is not often possible to reproduce the HTTP request verbatim from within the Zope environment.""" def DELETE(REQUEST, RESPONSE): """Delete a resource. For non-collection resources, DELETE may return either 200 or 204 (No Content) to indicate success.""" def PROPFIND(REQUEST, RESPONSE): """Retrieve properties defined on the resource.""" def PROPPATCH(REQUEST, RESPONSE): """Set and/or remove properties defined on the resource.""" def MKCOL(REQUEST, RESPONSE): """Create a new collection resource. If called on an existing resource, MKCOL must fail with 405 (Method Not Allowed).""" def COPY(REQUEST, RESPONSE): """Create a duplicate of the source resource whose state and behavior match that of the source resource as closely as possible. Though we may later try to make a copy appear seamless across namespaces (e.g. from Zope to Apache), COPY is currently only supported within the Zope namespace.""" def MOVE(REQUEST, RESPONSE): """Move a resource to a new location. Though we may later try to make a move appear seamless across namespaces (e.g. from Zope to Apache), MOVE is currently only supported within the Zope namespace.""" def LOCK(REQUEST, RESPONSE): """Lock a resource""" def UNLOCK(REQUEST, RESPONSE): """Remove an existing lock on a resource.""" def manage_DAVget(): """Gets the document source""" def listDAVObjects(): """ """ # XXX: might contain non-API methods and outdated comments; # not synced with ZopeBook API Reference; # based on webdav.Collection.Collection class IDAVCollection(IDAVResource): """The Collection class provides basic WebDAV support for collection objects. It provides default implementations for all supported WebDAV HTTP methods. The behaviors of some WebDAV HTTP methods for collections are slightly different than those for non-collection resources.""" __dav_collection__ = Bool( title=u"Is a DAV collection", description=u"Should be true", ) def PUT(REQUEST, RESPONSE): """The PUT method has no inherent meaning for collection resources, though collections are not specifically forbidden to handle PUT requests. The default response to a PUT request for collections is 405 (Method Not Allowed).""" def DELETE(REQUEST, RESPONSE): """Delete a collection resource. For collection resources, DELETE may return either 200 (OK) or 204 (No Content) to indicate total success, or may return 207 (Multistatus) to indicate partial success. Note that in Zope a DELETE currently never returns 207.""" zope2.13-2.13.21/source/Zope2/src/webdav/Lockable.py0000644000175000017500000001254712214017422020567 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """WebDAV support - lockable item. """ from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import aq_base from Persistence import PersistentMapping from zope.interface import implements from webdav.EtagSupport import EtagSupport from webdav.interfaces import ILockItem from webdav.interfaces import IWriteLock class ResourceLockedError(Exception): pass class LockableItem(EtagSupport): """Implements the WriteLock interface. This class is inherited by Resource which is then inherited by the majority of Zope objects. """ implements(IWriteLock) # Protect methods using declarative security security = ClassSecurityInfo() security.declarePrivate('wl_lockmapping') security.declarePublic('wl_isLocked', 'wl_getLock', 'wl_isLockedByUser', 'wl_lockItems', 'wl_lockValues', 'wl_lockTokens',) security.declareProtected('WebDAV Lock items', 'wl_setLock') security.declareProtected('WebDAV Unlock items', 'wl_delLock') security.declareProtected('Manage WebDAV Locks', 'wl_clearLocks') # Setting default roles for permissions - we want owners of conent # to be able to lock. security.setPermissionDefault('WebDAV Lock items', ('Manager', 'Owner',)) security.setPermissionDefault('WebDAV Unlock items',('Manager','Owner',)) def wl_lockmapping(self, killinvalids=0, create=0): """ if 'killinvalids' is 1, locks who are no longer valid will be deleted """ try: locks = getattr(self, '_dav_writelocks', None) except: locks = None if locks is None: if create: locks = self._dav_writelocks = PersistentMapping() else: # Don't generate a side effect transaction. locks = {} return locks elif killinvalids: # Delete invalid locks for token, lock in locks.items(): if not lock.isValid(): del locks[token] if (not locks) and hasattr(aq_base(self), '__no_valid_write_locks__'): self.__no_valid_write_locks__() return locks else: return locks def wl_lockItems(self, killinvalids=0): return self.wl_lockmapping(killinvalids).items() def wl_lockValues(self, killinvalids=0): return self.wl_lockmapping(killinvalids).values() def wl_lockTokens(self, killinvalids=0): return self.wl_lockmapping(killinvalids).keys() def wl_hasLock(self, token, killinvalids=0): if not token: return 0 return token in self.wl_lockmapping(killinvalids).keys() def wl_isLocked(self): # returns true if 'self' is locked at all # We set 'killinvalids' to 1 to delete all locks who are no longer # valid (timeout has been exceeded) locks = self.wl_lockmapping(killinvalids=1) if locks.keys(): return 1 else: return 0 def wl_setLock(self, locktoken, lock): locks = self.wl_lockmapping(create=1) if ILockItem.providedBy(lock): if locktoken == lock.getLockToken(): locks[locktoken] = lock else: raise ValueError, 'Lock tokens do not match' else: raise ValueError, 'Lock does not implement the LockItem Interface' def wl_getLock(self, locktoken): locks = self.wl_lockmapping(killinvalids=1) return locks.get(locktoken, None) def wl_delLock(self, locktoken): locks = self.wl_lockmapping() if locks.has_key(locktoken): del locks[locktoken] def wl_clearLocks(self): # Called by lock management machinery to quickly and effectively # destroy all locks. try: locks = self.wl_lockmapping() locks.clear() except: # The locks may be totally messed up, so we'll just delete # and replace. if hasattr(self, '_dav_writelocks'): del self._dav_writelocks if IWriteLock.providedBy(self): self._dav_writelocks = PersistentMapping() # Call into a special hook used by LockNullResources to delete # themselves. Could be used by other objects who want to deal # with the state of empty locks. if hasattr(aq_base(self), '__no_valid_write_locks__'): self.__no_valid_write_locks__() InitializeClass(LockableItem) ### Utility functions def wl_isLocked(ob): """ Returns true if the object is locked, returns 0 if the object is not locked or does not implement the WriteLockInterface """ return wl_isLockable(ob) and ob.wl_isLocked() def wl_isLockable(ob): return IWriteLock.providedBy(ob) zope2.13-2.13.21/source/Zope2/src/webdav/__init__.py0000644000175000017500000000356512214017422020612 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """The webdav package provides WebDAV capability for common Zope objects. Current WebDAV support in Zope provides for the correct handling of HTTP GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY and MOVE methods, as appropriate for the object that is the target of the operation. Objects which do not support a given operation should respond appropriately with a "405 Method Not Allowed" response. Note that the ability of a Zope installation to support WebDAV HTTP methods depends on the willingness of the web server to defer handling of those methods to the Zope process. In most cases, servers will allow the process to handle any request, so the Zope portion of your url namespace may well be able to handle WebDAV operations even though your web server software is not WebDAV-aware itself. Zope installations which use bundled server implementations such as ZopeHTTPServer or ZServer should fully support WebDAV functions. References: [WebDAV] Y. Y. Goland, E. J. Whitehead, Jr., A. Faizi, S. R. Carter, D. Jensen, "HTTP Extensions for Distributed Authoring - WebDAV." RFC 2518. Microsoft, U.C. Irvine, Netscape, Novell. February, 1999.""" enable_ms_author_via = False enable_ms_public_header = False zope2.13-2.13.21/source/Zope2/src/webdav/client.py0000644000175000017500000004650212214017422020327 0ustar arnauarnau"""HTTP 1.1 / WebDAV client library.""" import sys, os, time, types,re import httplib, mimetools from types import FileType from mimetypes import guess_type from base64 import encodestring from common import rfc1123_date from cStringIO import StringIO from random import random from urllib import quote class NotAvailable(Exception): pass class HTTP(httplib.HTTP): # A revised version of the HTTP class that can do basic # HTTP 1.1 connections, and also compensates for a bug # that occurs on some platforms in 1.5 and 1.5.1 with # socket.makefile().read() read_bug=sys.version[:5] in ('1.5 (','1.5.1') def putrequest(self, request, selector, ver='1.1'): selector=selector or '/' str = '%s %s HTTP/%s\r\n' % (request, selector, ver) self.send(str) def getreply(self): file=self.sock.makefile('rb') data=''.join(file.readlines()) file.close() self.file=StringIO(data) line = self.file.readline() try: [ver, code, msg] = line.split( None, 2) except ValueError: try: [ver, code] = line.split( None, 1) msg = "" except ValueError: return -1, line, None if ver[:5] != 'HTTP/': return -1, line, None code=int(code) msg =msg.strip() headers =mimetools.Message(self.file, 0) return ver, code, msg, headers class Resource: """An object representing a web resource.""" def __init__(self, url, username=None, password=None): self.username=username self.password=password self.url=url mo = urlreg.match(url) if mo: host,port,uri=mo.group(1,2,3) self.host=host self.port=port and int(port[1:]) or 80 self.uri=uri or '/' else: raise ValueError, url def __getattr__(self, name): url=os.path.join(self.url, name) return self.__class__(url, username=self.username, password=self.password) def __get_headers(self, kw={}): headers={} headers=self.__set_authtoken(headers) headers['User-Agent']='WebDAV.client' headers['Host']=self.host headers['Connection']='close' headers['Accept']='*/*' if kw.has_key('headers'): for name, val in kw['headers'].items(): headers[name]=val del kw['headers'] return headers def __set_authtoken(self, headers, atype='Basic'): if not (self.username and self.password): return headers if headers.has_key('Authorization'): return headers if atype=='Basic': headers['Authorization']=( "Basic %s" % (encodestring('%s:%s' % (self.username,self.password))).replace( '\012','')) return headers raise ValueError, 'Unknown authentication scheme: %s' % atype def __enc_formdata(self, args={}): formdata=[] for key, val in args.items(): n=key.rfind( '__') if n > 0: tag=key[n+2:] key=key[:n] else: tag='string' func=varfuncs.get(tag, marshal_string) formdata.append(func(key, val)) return '&'.join(formdata) def __enc_multipart(self, args={}): return MultiPart(args).render() def __snd_request(self, method, uri, headers={}, body='', eh=1): try: h=HTTP() h.connect(self.host, self.port) h.putrequest(method, uri) for n, v in headers.items(): h.putheader(n, v) if eh: h.endheaders() if body: h.send(body) ver, code, msg, hdrs=h.getreply() data=h.getfile().read() h.close() except: raise NotAvailable, sys.exc_value return http_response(ver, code, msg, hdrs, data) # HTTP methods def get(self, **kw): headers=self.__get_headers(kw) query=self.__enc_formdata(kw) uri=query and '%s?%s' % (self.uri, query) or self.uri return self.__snd_request('GET', uri, headers) def head(self, **kw): headers=self.__get_headers(kw) query=self.__enc_formdata(kw) uri=query and '%s?%s' % (self.uri, query) or self.uri return self.__snd_request('HEAD', uri, headers) def post(self, **kw): headers=self.__get_headers(kw) content_type=None for key, val in kw.items(): if (key[-6:]=='__file') or hasattr(val, 'read'): content_type='multipart/form-data' break if content_type=='multipart/form-data': body=self.__enc_multipart(kw) return self.__snd_request('POST', self.uri, headers, body, eh=0) else: body=self.__enc_formdata(kw) headers['Content-Type']='application/x-www-form-urlencoded' headers['Content-Length']=str(len(body)) return self.__snd_request('POST', self.uri, headers, body) def put(self, file='', content_type='', content_enc='', isbin=re.compile(r'[\000-\006\177-\277]').search, **kw): headers=self.__get_headers(kw) filetype=type(file) if filetype is type('') and (isbin(file) is None) and \ os.path.exists(file): ob=open(file, 'rb') body=ob.read() ob.close() c_type, c_enc=guess_type(file) elif filetype is FileType: body=file.read() c_type, c_enc=guess_type(file.name) elif filetype is type(''): body=file c_type, c_enc=guess_type(self.url) else: raise ValueError, 'File must be a filename, file or string.' content_type=content_type or c_type content_enc =content_enc or c_enc if content_type: headers['Content-Type']=content_type if content_enc: headers['Content-Encoding']=content_enc headers['Content-Length']=str(len(body)) return self.__snd_request('PUT', self.uri, headers, body) def options(self, **kw): headers=self.__get_headers(kw) return self.__snd_request('OPTIONS', self.uri, headers) def trace(self, **kw): headers=self.__get_headers(kw) return self.__snd_request('TRACE', self.uri, headers) def delete(self, **kw): headers=self.__get_headers(kw) return self.__snd_request('DELETE', self.uri, headers) def propfind(self, body='', depth=0, **kw): headers=self.__get_headers(kw) headers['Depth']=str(depth) headers['Content-Type']='text/xml; charset="utf-8"' headers['Content-Length']=str(len(body)) return self.__snd_request('PROPFIND', self.uri, headers, body) def proppatch(self, body, **kw): headers=self.__get_headers(kw) if body: headers['Content-Type']='text/xml; charset="utf-8"' headers['Content-Length']=str(len(body)) return self.__snd_request('PROPPATCH', self.uri, headers, body) def copy(self, dest, depth='infinity', overwrite=0, **kw): """Copy a resource to the specified destination.""" headers=self.__get_headers(kw) headers['Overwrite']=overwrite and 'T' or 'F' headers['Destination']=dest headers['Depth']=depth return self.__snd_request('COPY', self.uri, headers) def move(self, dest, depth='infinity', overwrite=0, **kw): """Move a resource to the specified destination.""" headers=self.__get_headers(kw) headers['Overwrite']=overwrite and 'T' or 'F' headers['Destination']=dest headers['Depth']=depth return self.__snd_request('MOVE', self.uri, headers) def mkcol(self, **kw): headers=self.__get_headers(kw) return self.__snd_request('MKCOL', self.uri, headers) # class 2 support def lock(self, scope='exclusive', type='write', owner='', depth='infinity', timeout='Infinite', **kw): """Create a lock with the specified scope, type, owner, depth and timeout on the resource. A locked resource prevents a principal without the lock from executing a PUT, POST, PROPPATCH, LOCK, UNLOCK, MOVE, DELETE, or MKCOL on the locked resource.""" if not scope in ('shared', 'exclusive'): raise ValueError, 'Invalid lock scope.' if not type in ('write',): raise ValueError, 'Invalid lock type.' if not depth in ('0', 'infinity'): raise ValueError, 'Invalid depth.' headers=self.__get_headers(kw) body='\n' \ '\n' \ ' \n' \ ' \n' \ ' %s\n' \ ' \n' \ ' %s\n' \ ' \n' \ '' % (scope, type, depth, owner) headers['Content-Type']='text/xml; charset="utf-8"' headers['Content-Length']=str(len(body)) headers['Timeout']=timeout headers['Depth']=depth return self.__snd_request('LOCK', self.uri, headers, body) def unlock(self, token, **kw): """Remove the lock identified by token from the resource and all other resources included in the lock. If all resources which have been locked under the submitted lock token can not be unlocked the unlock method will fail.""" headers=self.__get_headers(kw) token='' % str(token) headers['Lock-Token']=token return self.__snd_request('UNLOCK', self.uri, headers) def allprops(self, depth=0): return self.propfind('', depth) def propnames(self, depth=0): body='\n' \ '\n' \ ' \n' \ '' return self.propfind(body, depth) def getprops(self, *names): if not names: return self.propfind() tags='/>\n <'.join(names ) body='\n' \ '\n' \ ' \n' \ ' <%s>\n' \ ' \n' \ '' % tags return self.propfind(body, 0) def setprops(self, **props): if not props: raise ValueError, 'No properties specified.' tags=[] for key, val in props.items(): tags.append(' <%s>%s' % (key, val, key)) tags='\n'.join(tags ) body='\n' \ '\n' \ '\n' \ ' \n' \ ' %s\n' \ ' \n' \ '\n' \ '' % tags return self.proppatch(body) def delprops(self, *names): if not names: raise ValueError, 'No property names specified.' tags='/>\n <'.join(names) body='\n' \ '\n' \ '\n' \ ' \n' \ ' <%s>\n' \ ' \n' \ '\n' \ '' % tags return self.proppatch(body) def __str__(self): return '' % self.url __repr__=__str__ class http_response: def __init__(self, ver, code, msg, headers, body): self.version=ver self.code=code self.msg=msg self.headers=headers self.body=body def get_status(self): return '%s %s' % (self.code, self.msg) def get_header(self, name, val=None): return self.headers.dict.get(name.lower(), val) def get_headers(self): return self.headers.dict def get_body(self): return self.body def __str__(self): data=[] data.append('%s %s %s\r\n' % (self.version, self.code, self.msg)) map(data.append, self.headers.headers) data.append('\r\n') data.append(self.body) return ''.join(data) set_xml=""" Brian Lloyd My New Title """ funny=""" Brian Lloyd blue 72 Brian Lloyd red """ rem_xml=""" """ find_xml=""" """ ############################################################################## # Implementation details below here urlreg=re.compile(r'http://([^:/]+)(:[0-9]+)?(/.+)?', re.I) def marshal_string(name, val): return '%s=%s' % (name, quote(str(val))) def marshal_float(name, val): return '%s:float=%s' % (name, val) def marshal_int(name, val): return '%s:int=%s' % (name, val) def marshal_long(name, val): value = '%s:long=%s' % (name, val) if value[-1] == 'L': value = value[:-1] return value def marshal_list(name, seq, tname='list', lt=type([]), tt=type(())): result=[] for v in seq: tp=type(v) if tp in (lt, tt): raise TypeError, 'Invalid recursion in data to be marshaled.' result.append(marshal_var("%s:%s" % (name, tname), v)) return '&'.join(result) def marshal_tuple(name, seq): return marshal_list(name, seq, 'tuple') varfuncs={} vartypes=(('int', type(1), marshal_int), ('float', type(1.0), marshal_float), ('long', type(1L), marshal_long), ('list', type([]), marshal_list), ('tuple', type(()), marshal_tuple), ('string', type(''), marshal_string), ('file', types.FileType, None), ) for name, tp, func in vartypes: varfuncs[name]=func varfuncs[tp]=func def marshal_var(name, val): return varfuncs.get(type(val), marshal_string)(name, val) class MultiPart: def __init__(self,*args): c=len(args) if c==1: name,val=None,args[0] elif c==2: name,val=args[0],args[1] else: raise ValueError, 'Invalid arguments' h={'Content-Type': {'_v':''}, 'Content-Transfer-Encoding': {'_v':''}, 'Content-Disposition': {'_v':''}, } dt=type(val) b=t=None if dt==types.DictType: t=1 b=self.boundary() d=[] h['Content-Type']['_v']='multipart/form-data; boundary=%s' % b for n,v in val.items(): d.append(MultiPart(n,v)) elif (dt==types.ListType) or (dt==types.TupleType): raise ValueError, 'Sorry, nested multipart is not done yet!' elif dt==types.FileType or hasattr(val,'read'): if hasattr(val,'name'): ct, enc=guess_type(val.name) if not ct: ct='application/octet-stream' fn=val.name.replace('\\','/') fn=fn[(fn.rfind('/')+1):] else: ct='application/octet-stream' enc='' fn='' enc=enc or (ct[:6] in ('image/', 'applic') and 'binary' or '') h['Content-Disposition']['_v'] ='form-data' h['Content-Disposition']['name'] ='"%s"' % name h['Content-Disposition']['filename']='"%s"' % fn h['Content-Transfer-Encoding']['_v']=enc h['Content-Type']['_v'] =ct d=[] l=val.read(8192) while l: d.append(l) l=val.read(8192) else: n=name.rfind( '__') if n > 0: name='%s:%s' % (name[:n], name[n+2:]) h['Content-Disposition']['_v']='form-data' h['Content-Disposition']['name']='"%s"' % name d=[str(val)] self._headers =h self._data =d self._boundary=b self._top =t def boundary(self): return '%s_%s_%s' % (int(time.time()), os.getpid(), random()) def render(self): h=self._headers s=[] if self._top: for n,v in h.items(): if v['_v']: s.append('%s: %s' % (n,v['_v'])) for k in v.keys(): if k != '_v': s.append('; %s=%s' % (k, v[k])) s.append('\n') p=[] t=[] b=self._boundary for d in self._data: p.append(d.render()) t.append('--%s\n' % b) t.append(('\n--%s\n' % b).join(p)) t.append('\n--%s--\n' % b) t=''.join(t) s.append('Content-Length: %s\n\n' % len(t)) s.append(t) return ''.join(s) else: for n,v in h.items(): if v['_v']: s.append('%s: %s' % (n,v['_v'])) for k in v.keys(): if k != '_v': s.append('; %s=%s' % (k, v[k])) s.append('\n') s.append('\n') if self._boundary: p=[] b=self._boundary for d in self._data: p.append(d.render()) s.append('--%s\n' % b) s.append(('\n--%s\n' % b).join(p)) s.append('\n--%s--\n' % b) return ''.join(s) else: return ''.join(s+self._data) _extmap={'': 'text/plain', 'rdb': 'text/plain', 'html': 'text/html', 'dtml': 'text/html', 'htm': 'text/html', 'dtm': 'text/html', 'gif': 'image/gif', 'jpg': 'image/jpeg', 'exe': 'application/octet-stream', None : 'application/octet-stream', } _encmap={'image/gif': 'binary', 'image/jpg': 'binary', 'application/octet-stream': 'binary', } bri =Resource('http://tarzan.digicool.com/dev/brian3/', username='brian', password='123') abri=Resource('http://tarzan.digicool.com/dev/brian3/') dav =Resource('http://tarzan.digicool.com/dev/dav/', username='brian', password='123') adav=Resource('http://tarzan.digicool.com/dev/dav/') zope2.13-2.13.21/source/Zope2/src/webdav/EtagSupport.py0000644000175000017500000001330712214017422021323 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Etag support. """ import time from zope.interface import implements from zope.interface import Interface from webdav.common import PreconditionFailed class EtagBaseInterface(Interface): """\ Basic Etag support interface, meaning the object supports generating an Etag that can be used by certain HTTP and WebDAV Requests. """ def http__etag(): """\ Entity tags are used for comparing two or more entities from the same requested resource. Predominantly used for Caching, Etags can also be used to deal with the 'Lost Updates Problem'. An HTTP Client such as Amaya that supports PUT for editing can use the Etag value returned in the head of a GET response in the 'if-match' header submitted with a PUT request. If the Etag for the requested resource in the PUT request's 'if-match' header is different from the current Etag value returned by this method, the PUT will fail (it means that the state of the resource has changed since the last copy the Client recieved) because the precondition (the 'if-match') fails (the submitted Etag does not match the current Etag). """ def http__refreshEtag(): """\ While it may make sense to use the ZODB Object Id or bobobase_modification_time to generate an Etag, this could fail on certain REQUESTS because: o The object is not stored in the ZODB, or o A Request such as PUT changes the oid or bobobase_modification_time *AFTER* the Response has been written out, but the Etag needs to be updated and returned with the Response of the PUT request. Thus, Etags need to be refreshed manually when an object changes. """ class EtagSupport: """\ This class is the basis for supporting Etags in Zope. It's main function right now is to support the *Lost Updates Problem* by allowing Etags and If-Match headers to be checked on PUT calls to provide a *Seatbelt* style functionality. The Etags is based on the bobobase_modification_time, and thus is updated whenever the object is updated. If a PUT request, or other HTTP or Dav request comes in with an Etag different than the current one, that request can be rejected according to the type of header (If-Match, If-None-Match). """ implements(EtagBaseInterface) def http__etag(self, readonly=0): try: etag = self.__etag except AttributeError: if readonly: # Don't refresh the etag on reads return self.http__refreshEtag() etag = self.__etag return etag def http__refreshEtag(self): self.__etag = 'ts%s' % str(time.time())[2:] def http__parseMatchList(self, REQUEST, header="if-match"): # Return a sequence of strings found in the header specified # (should be one of {'if-match' or 'if-none-match'}). If the # header is not in the request, returns None. Otherwise, # returns a tuple of Etags. matchlist = REQUEST.get_header(header) if matchlist is None: matchlist = REQUEST.get_header(header.title()) if matchlist is None: return None matchlist = [ x.strip() for x in matchlist.split(',')] r = [] for match in matchlist: if match == '*': r.insert(0, match) elif (match[0] + match[-1] == '""') and (len(match) > 2): r.append(match[1:-1]) return tuple(r) def http__processMatchHeaders(self, REQUEST=None): # Process if-match and if-none-match headers if REQUEST is None: REQUEST = self.aq_acquire('REQUEST') matchlist = self.http__parseMatchList(REQUEST, 'if-match') nonematch = self.http__parseMatchList(REQUEST, 'if-none-match') if matchlist is None: # There's no Matchlist, but 'if-none-match' might need processing pass elif ('*' in matchlist): return 1 # * matches everything elif self.http__etag() not in matchlist: # The resource etag is not in the list of etags required # to match, as specified in the 'if-match' header. The # condition fails and the HTTP Method may *not* execute. raise PreconditionFailed elif self.http__etag() in matchlist: return 1 if nonematch is None: # There's no 'if-none-match' header either, so there's no # problem continuing with the request return 1 elif ('*' in nonelist): # if-none-match: * means that the operation should not # be performed if the specified resource exists # (webdav.NullResource will want to do special behavior # here) raise PreconditionFailed elif self.http__etag() in nonelist: # The opposite of if-match, the condition fails # IF the resources Etag is in the if-none-match list raise PreconditionFailed elif self.http__etag() not in nonelist: return 1 zope2.13-2.13.21/source/Zope2/src/webdav/xmltools.py0000644000175000017500000001640612214017422020732 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ WebDAV XML request parsing tool using xml.minidom as xml parser. Code contributed by Simon Eisenmann, struktur AG, Stuttgart, Germany """ """ TODO: - Check the methods Node.addNode and find out if some code uses/requires this method. => If yes implement them, else forget them. NOTE: So far i didn't have any problems. If you have problems please report them. - We are using a hardcoded default of latin-1 for encoding unicode strings. While this is suboptimal, it does match the expected encoding from OFS.PropertySheet. We need to find a the encoding somehow, maybe use the same encoding as the ZMI is using? """ from StringIO import StringIO from xml.dom import minidom from xml.sax.expatreader import ExpatParser from xml.sax.saxutils import escape as _escape from xml.sax.saxutils import unescape as _unescape escape_entities = {'"': '"', "'": ''', } unescape_entities = {'"': '"', ''': "'", } def escape(value, entities=None): _ent = escape_entities if entities is not None: _ent = _ent.copy() _ent.update(entities) return _escape(value, entities) def unescape(value, entities=None): _ent = unescape_entities if entities is not None: _ent = _ent.copy() _ent.update(entities) return _unescape(value, entities) # XXX latin-1 is hardcoded on OFS.PropertySheets as the expected # encoding properties will be stored in. Optimally, we should use the # same encoding as the 'default_encoding' property that is used for # the ZMI. zope_encoding = 'latin-1' class Node: """ Our nodes no matter what type """ node = None def __init__(self, node): self.node = node def elements(self, name=None, ns=None): nodes = [] for n in self.node.childNodes: if (n.nodeType == n.ELEMENT_NODE and ((name is None) or ((n.localName.lower())==name)) and ((ns is None) or (n.namespaceURI==ns))): nodes.append(Element(n)) return nodes def qname(self): return '%s%s' % (self.namespace(), self.name()) def addNode(self, node): # XXX: no support for adding nodes here raise NotImplementedError, 'addNode not implemented' def toxml(self): return self.node.toxml() def strval(self): return self.toxml().encode(zope_encoding) def name(self): return self.node.localName def value(self): return self.node.nodeValue def nodes(self): return self.node.childNodes def nskey(self): return self.node.namespaceURI def namespace(self): return self.nskey() def attrs(self): return [Node(n) for n in self.node.attributes.values()] def remove_namespace_attrs(self): # remove all attributes which start with "xmlns:" or # are equal to "xmlns" if self.node.hasAttributes(): toremove = [] for name, value in self.node.attributes.items(): if name.startswith('xmlns:'): toremove.append(name) if name == 'xmlns': toremove.append(name) for name in toremove: self.node.removeAttribute(name) def del_attr(self, name): # NOTE: zope calls this after remapping to remove namespace # zope passes attributes like xmlns:n # but the :n isnt part of the attribute name .. gash! attr = name.split(':')[0] if (self.node.hasAttributes() and self.node.attributes.has_key(attr)): # Only remove attributes if they exist return self.node.removeAttribute(attr) def remap(self, dict, n=0, top=1): # XXX: this method is used to do some strange remapping of elements # and namespaces .. someone wants to explain that code? # XXX: i also dont understand why this method returns anything # as the return value is never used # NOTE: zope calls this to change namespaces in PropPatch and Lock # we dont need any fancy remapping here and simply remove # the attributes in del_attr return {},0 def __repr__(self): if self.namespace(): return "" % (self.name(), self.namespace()) else: return "" % self.name() class Element(Node): def toxml(self): # When dealing with Elements, we only want the Element's content. writer = StringIO(u'') for n in self.node.childNodes: if n.nodeType == n.CDATA_SECTION_NODE: # CDATA sections should not be unescaped. writer.write(n.data) elif n.nodeType == n.ELEMENT_NODE: writer.write(n.toxml()) else: # TEXT_NODE and what else? value = n.toxml() # Unescape possibly escaped values. We do this # because the value is *always* escaped in it's XML # representation, and if we store it escaped it will come # out *double escaped* when doing a PROPFIND. value = unescape(value, entities=unescape_entities) writer.write(value) return writer.getvalue() class ProtectedExpatParser(ExpatParser): """ See https://bugs.launchpad.net/zope2/+bug/1114688 """ def __init__(self, forbid_dtd=True, forbid_entities=True, *args, **kwargs): # Python 2.x old style class ExpatParser.__init__(self, *args, **kwargs) self.forbid_dtd = forbid_dtd self.forbid_entities = forbid_entities def start_doctype_decl(self, name, sysid, pubid, has_internal_subset): raise ValueError("Inline DTD forbidden") def entity_decl(self, entityName, is_parameter_entity, value, base, systemId, publicId, notationName): raise ValueError(" forbidden") def unparsed_entity_decl(self, name, base, sysid, pubid, notation_name): # expat 1.2 raise ValueError(" forbidden") def reset(self): ExpatParser.reset(self) if self.forbid_dtd: self._parser.StartDoctypeDeclHandler = self.start_doctype_decl if self.forbid_entities: self._parser.EntityDeclHandler = self.entity_decl self._parser.UnparsedEntityDeclHandler = self.unparsed_entity_decl class XmlParser: """ Simple wrapper around minidom to support the required interfaces for zope.webdav """ dom = None def __init__(self): pass def parse(self, data): self.dom = minidom.parseString(data, parser=ProtectedExpatParser()) return Node(self.dom) zope2.13-2.13.21/source/Zope2/src/webdav/Collection.py0000644000175000017500000001350412214017422021140 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """WebDAV support - collection objects. """ from urllib import unquote from AccessControl.class_init import InitializeClass from AccessControl.SecurityManagement import getSecurityManager from zExceptions import MethodNotAllowed from zExceptions import NotFound from zope.interface import implements from webdav.common import Locked from webdav.common import PreconditionFailed from webdav.common import rfc1123_date from webdav.common import urlfix from webdav.Lockable import wl_isLocked from webdav.interfaces import IDAVCollection from webdav.Resource import Resource class Collection(Resource): """The Collection class provides basic WebDAV support for collection objects. It provides default implementations for all supported WebDAV HTTP methods. The behaviors of some WebDAV HTTP methods for collections are slightly different than those for non-collection resources.""" implements(IDAVCollection) __dav_collection__=1 def dav__init(self, request, response): # We are allowed to accept a url w/o a trailing slash # for a collection, but are supposed to provide a # hint to the client that it should be using one. # [WebDAV, 5.2] pathinfo=request.get('PATH_INFO','') if pathinfo and pathinfo[-1] != '/': location='%s/' % request['URL1'] response.setHeader('Content-Location', location) response.setHeader('Connection', 'close', 1) response.setHeader('Date', rfc1123_date(), 1) def HEAD(self, REQUEST, RESPONSE): """Retrieve resource information without a response body.""" self.dav__init(REQUEST, RESPONSE) # Note that we are willing to acquire the default document # here because what we really care about is whether doing # a GET on this collection / would yield a 200 response. if hasattr(self, 'index_html'): if hasattr(self.index_html, 'HEAD'): return self.index_html.HEAD(REQUEST, RESPONSE) raise MethodNotAllowed, ( 'Method not supported for this resource.' ) raise NotFound, 'The requested resource does not exist.' def PUT(self, REQUEST, RESPONSE): """The PUT method has no inherent meaning for collection resources, though collections are not specifically forbidden to handle PUT requests. The default response to a PUT request for collections is 405 (Method Not Allowed).""" self.dav__init(REQUEST, RESPONSE) raise MethodNotAllowed, 'Method not supported for collections.' def DELETE(self, REQUEST, RESPONSE): """Delete a collection resource. For collection resources, DELETE may return either 200 (OK) or 204 (No Content) to indicate total success, or may return 207 (Multistatus) to indicate partial success. Note that in Zope a DELETE currently never returns 207.""" from webdav.davcmds import DeleteCollection self.dav__init(REQUEST, RESPONSE) ifhdr = REQUEST.get_header('If', '') url = urlfix(REQUEST['URL'], 'DELETE') name = unquote(filter(None, url.split( '/'))[-1]) parent = self.aq_parent sm = getSecurityManager() token = None # if re.match("/Control_Panel",REQUEST['PATH_INFO']): # RESPONSE.setStatus(403) # RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') # return RESPONSE # Level 1 of lock checking (is the collection or its parent locked?) if wl_isLocked(self): if ifhdr: self.dav__simpleifhandler(REQUEST, RESPONSE, 'DELETE', col=1) else: raise Locked elif wl_isLocked(parent): if ifhdr: parent.dav__simpleifhandler(REQUEST, RESPONSE, 'DELETE', col=1) else: raise PreconditionFailed # Second level of lock\conflict checking (are any descendants locked, # or is the user not permitted to delete?). This results in a # multistatus response if ifhdr: tokens = self.wl_lockTokens() for tok in tokens: # We already know that the simple if handler succeeded, # we just want to get the right token out of the header now if ifhdr.find(tok) > -1: token = tok cmd = DeleteCollection() result = cmd.apply(self, token, sm, REQUEST['URL']) if result: # There were conflicts, so we need to report them RESPONSE.setStatus(207) RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setBody(result) else: # There were no conflicts, so we can go ahead and delete # ajung: additional check if we really could delete the collection # (Collector #2196) if parent.manage_delObjects([name],REQUEST=None) is None: RESPONSE.setStatus(204) else: RESPONSE.setStatus(403) return RESPONSE def listDAVObjects(self): objectValues = getattr(self, 'objectValues', None) if objectValues is not None: return objectValues() return [] InitializeClass(Collection) zope2.13-2.13.21/source/Zope2/src/webdav/tests/0000755000175000017500000000000012214017422017632 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/webdav/tests/testResource.py0000644000175000017500000002255612214017422022705 0ustar arnauarnauimport unittest from AccessControl.SecurityManagement import newSecurityManager from AccessControl.SecurityManagement import noSecurityManager from AccessControl.SecurityManager import setSecurityPolicy from Acquisition import Implicit MS_DAV_AGENT = "Microsoft Data Access Internet Publishing Provider DAV" def make_request_response(environ=None): from StringIO import StringIO from ZPublisher.HTTPRequest import HTTPRequest from ZPublisher.HTTPResponse import HTTPResponse if environ is None: environ = {} stdout = StringIO() stdin = StringIO() resp = HTTPResponse(stdout=stdout) environ.setdefault('SERVER_NAME', 'foo') environ.setdefault('SERVER_PORT', '80') environ.setdefault('REQUEST_METHOD', 'GET') req = HTTPRequest(stdin, environ, resp) return req, resp class TestResource(unittest.TestCase): def setUp(self): self.app = DummyContent() self.app.acl_users = DummyUserFolder() self._policy = PermissiveSecurityPolicy() self._oldPolicy = setSecurityPolicy(self._policy) newSecurityManager(None, OmnipotentUser().__of__(self.app.acl_users)) def tearDown(self): noSecurityManager() setSecurityPolicy(self._oldPolicy) def _getTargetClass(self): from webdav.Resource import Resource return Resource def _makeOne(self): klass = self._getTargetClass() inst = klass() return inst def test_interfaces(self): from webdav.interfaces import IDAVResource from webdav.interfaces import IWriteLock Resource = self._getTargetClass() from zope.interface.verify import verifyClass verifyClass(IDAVResource, Resource) verifyClass(IWriteLock, Resource) def test_ms_author_via(self): import webdav from webdav.Resource import Resource default_settings = webdav.enable_ms_author_via try: req, resp = make_request_response() resource = Resource() resource.OPTIONS(req, resp) self.assert_(not resp.headers.has_key('ms-author-via')) webdav.enable_ms_author_via = True req, resp = make_request_response() resource = Resource() resource.OPTIONS(req, resp) self.assert_(not resp.headers.has_key('ms-author-via')) req, resp = make_request_response( environ={'USER_AGENT': MS_DAV_AGENT}) resource = Resource() resource.OPTIONS(req, resp) self.assert_(resp.headers.has_key('ms-author-via')) self.assert_(resp.headers['ms-author-via'] == 'DAV') finally: webdav.enable_ms_author_via = default_settings def test_ms_public_header(self): import webdav from webdav.Resource import Resource default_settings = webdav.enable_ms_public_header try: req, resp = make_request_response() resource = Resource() resource.OPTIONS(req, resp) self.assert_(not resp.headers.has_key('public')) webdav.enable_ms_public_header = True req, resp = make_request_response() resource = Resource() resource.OPTIONS(req, resp) self.assert_(not resp.headers.has_key('public')) self.assert_(resp.headers.has_key('allow')) req, resp = make_request_response( environ={'USER_AGENT': MS_DAV_AGENT}) resource = Resource() resource.OPTIONS(req, resp) self.assert_(resp.headers.has_key('public')) self.assert_(resp.headers.has_key('allow')) self.assert_(resp.headers['public'] == resp.headers['allow']) finally: webdav.enable_ms_public_header = default_settings def test_MOVE_self_locked(self): """ DAV: litmus"notowner_modify" tests warn during a MOVE request because we returned "412 Precondition Failed" instead of "423 Locked" when the resource attempting to be moved was itself locked. Fixed by changing Resource.Resource.MOVE to raise the correct error. """ app = self.app request = DummyRequest({}, {}) response = DummyResponse() inst = self._makeOne() inst.cb_isMoveable = lambda *arg: True inst.restrictedTraverse = lambda *arg: app inst.getId = lambda *arg: '123' inst._dav_writelocks = {'a':DummyLock()} from zope.interface import directlyProvides from webdav.interfaces import IWriteLock directlyProvides(inst, IWriteLock) from webdav.common import Locked self.assertRaises(Locked, inst.MOVE, request, response) def dont_test_dav__simpleifhandler_fail_cond_put_unlocked(self): """ DAV: litmus' cond_put_unlocked test (#22) exposed a bug in webdav.Resource.dav__simpleifhandler. If the resource is not locked, and a DAV request contains an If header, no token can possibly match and we must return a 412 Precondition Failed instead of 204 No Content. I (chrism) haven't been able to make this work properly without breaking other litmus tests (32. lock_collection being the most important), so this test is not currently running. """ ifhdr = 'If: ()' request = DummyRequest({'URL':'http://example.com/foo/PUT'}, {'If':ifhdr}) response = DummyResponse() inst = self._makeOne() from zope.interface import directlyProvides from webdav.interfaces import IWriteLock directlyProvides(inst, IWriteLock) from webdav.common import PreconditionFailed self.assertRaises(PreconditionFailed, inst.dav__simpleifhandler, request, response) def dont_test_dav__simpleifhandler_cond_put_corrupt_token(self): """ DAV: litmus' cond_put_corrupt_token test (#18) exposed a bug in webdav.Resource.dav__simpleifhandler. If the resource is locked at all, and a DAV request contains an If header, and none of the lock tokens present in the header match a lock on the resource, we need to return a 423 Locked instead of 204 No Content. I (chrism) haven't been able to make this work properly without breaking other litmus tests (32. lock_collection being the most important), so this test is not currently running. """ ifhdr = 'If: () (Not )' request = DummyRequest({'URL':'http://example.com/foo/PUT'}, {'If':ifhdr}) response = DummyResponse() inst = self._makeOne() inst._dav_writelocks = {'a':DummyLock()} from zope.interface import directlyProvides from webdav.interfaces import IWriteLock directlyProvides(inst, IWriteLock) from webdav.common import Locked self.assertRaises(Locked, inst.dav__simpleifhandler, request, response) class DummyLock: def isValid(self): return True class DummyContent(Implicit): def cb_isMoveable(self): return True def _checkId(self, *arg, **kw): return True def _verifyObjectPaste(self, *arg): return True class DummyUserFolder(Implicit): pass class DummyRequest: def __init__(self, form, headers): self.form = form self.headers = headers def get_header(self, name, default): return self.headers.get(name, default) def get(self, name, default): return self.form.get(name, default) def __getitem__(self, name): return self.form[name] def physicalPathFromURL(self, *arg): return [''] class DummyResponse: def __init__(self): self.headers = {} def setHeader(self, name, value, *arg): self.headers[name] = value from AccessControl.PermissionRole import rolesForPermissionOn from Acquisition import Implicit class PermissiveSecurityPolicy: """ Very permissive security policy for unit testing purposes. """ # # Standard SecurityPolicy interface # def validate( self , accessed=None , container=None , name=None , value=None , context=None , roles=None , *args , **kw): if name and name.startswith('hidden'): return False else: return True def checkPermission(self, permission, object, context): if permission == 'forbidden permission': return 0 if permission == 'addFoo': return context.user.allowed(object, ['FooAdder']) roles = rolesForPermissionOn(permission, object) if isinstance(roles, basestring): roles=[roles] return context.user.allowed(object, roles) class OmnipotentUser( Implicit ): """ Omnipotent User for unit testing purposes. """ def getId( self ): return 'all_powerful_Oz' getUserName = getId def getRoles(self): return ('Manager',) def allowed( self, object, object_roles=None ): return 1 def getRolesInContext(self, object): return ('Manager',) def _check_context(self, object): return True def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestResource), )) zope2.13-2.13.21/source/Zope2/src/webdav/tests/testLockable.py0000644000175000017500000000215112214017422022617 0ustar arnauarnauimport unittest class TestUtilFunctions(unittest.TestCase): def test_wl_isLocked(self): from webdav.Lockable import wl_isLocked unlockable = UnlockableResource() self.assertFalse(wl_isLocked(unlockable)) lockable_unlocked = LockableResource(locked=False) self.assertFalse(wl_isLocked(lockable_unlocked)) lockable_locked = LockableResource(locked=True) self.assertTrue(wl_isLocked(lockable_locked)) def test_wl_isLockable(self): from webdav.Lockable import wl_isLockable unlockable = UnlockableResource() self.assertFalse(wl_isLockable(unlockable)) lockable = LockableResource(locked=False) self.assertTrue(wl_isLockable(lockable)) from webdav.interfaces import IWriteLock from zope.interface import implements class LockableResource: implements(IWriteLock) def __init__(self, locked): self.locked = locked def wl_isLocked(self): return self.locked class UnlockableResource: pass def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestUtilFunctions), )) zope2.13-2.13.21/source/Zope2/src/webdav/tests/__init__.py0000644000175000017500000000007512214017422021745 0ustar arnauarnau# # This file is necessary to make this directory a package. zope2.13-2.13.21/source/Zope2/src/webdav/tests/testLockItem.py0000644000175000017500000000057612214017422022623 0ustar arnauarnauimport unittest class TestLockItem(unittest.TestCase): def test_interfaces(self): from webdav.interfaces import ILockItem from webdav.LockItem import LockItem from zope.interface.verify import verifyClass verifyClass(ILockItem, LockItem) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestLockItem), )) zope2.13-2.13.21/source/Zope2/src/webdav/tests/test_davcmds.py0000644000175000017500000001044212214017422022665 0ustar arnauarnauimport unittest from AccessControl.SecurityManagement import getSecurityManager from AccessControl.SecurityManagement import noSecurityManager from AccessControl.SecurityManager import setSecurityPolicy from zExceptions import Forbidden from zope.interface import implements class _DummySecurityPolicy(object): def checkPermission(self, permission, object, context): return False class _DummyContent(object): from webdav.interfaces import IWriteLock implements(IWriteLock) def __init__(self, token=None): self.token = token def wl_hasLock(self, token): return self.token == token def wl_isLocked(self): return bool(self.token) class TestUnlock(unittest.TestCase): def _getTargetClass(self): from webdav.davcmds import Unlock return Unlock def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def test_apply_bogus_lock(self): """ When attempting to unlock a resource with a token that the resource hasn't been locked with, we should return an error instead of a 20X response. See http://lists.w3.org/Archives/Public/w3c-dist-auth/2001JanMar/0099.html for rationale. Prior to Zope 2.11, we returned a 204 under this circumstance. We choose do what mod_dav does, which is return a '400 Bad Request' error. This was caught by litmus locks.notowner_lock test #10. """ inst = self._makeOne() lockable = _DummyContent() result = inst.apply(lockable, 'bogus', url='http://example.com/foo/UNLOCK', top=0) result = result.getvalue() self.assertNotEqual( result.find('HTTP/1.1 400 Bad Request'), -1) class TestPropPatch(unittest.TestCase): def _getTargetClass(self): from webdav.davcmds import PropPatch return PropPatch def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def test_parse_xml_property_values_with_namespaces(self): """ Before Zope 2.11, litmus props tests 19: propvalnspace and 20: propwformed were failing because Zope did not strip off the xmlns: attribute attached to XML property values. We now strip off all attributes that look like xmlns declarations. """ reqbody = """ """ request = {'BODY':reqbody} inst = self._makeOne(request) self.assertEqual(len(inst.values), 1) self.assertEqual(inst.values[0][3]['__xml_attrs__'], {}) class TestDeleteCollection(unittest.TestCase): def _getTargetClass(self): from webdav.davcmds import DeleteCollection return DeleteCollection def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def setUp(self): self._oldPolicy = setSecurityPolicy(_DummySecurityPolicy()) def tearDown(self): noSecurityManager() setSecurityPolicy(self._oldPolicy) def test_apply_no_parent(self): cmd = self._makeOne() obj = _DummyContent() sm = getSecurityManager() self.assertEqual(cmd.apply(obj, None, sm, '/foo/DELETE'), '') def test_apply_no_col_Forbidden(self): cmd = self._makeOne() obj = _DummyContent() obj.__parent__ = _DummyContent() sm = getSecurityManager() self.assertRaises(Forbidden, cmd.apply, obj, None, sm, '/foo/DELETE') def test_apply_no_col_Locked(self): from webdav.common import Locked cmd = self._makeOne() obj = _DummyContent('LOCKED') sm = getSecurityManager() self.assertRaises(Locked, cmd.apply, obj, None, sm, '/foo/DELETE') def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestUnlock), unittest.makeSuite(TestPropPatch), unittest.makeSuite(TestDeleteCollection), )) zope2.13-2.13.21/source/Zope2/src/webdav/tests/test_xmltools.py0000644000175000017500000000315312214017422023126 0ustar arnauarnauimport unittest class NodeTests(unittest.TestCase): def _getTargetClass(self): from webdav.xmltools import Node return Node def _makeOne(self, wrapped): return self._getTargetClass()(wrapped) def test_remove_namespace_attrs(self): class DummyMinidomNode(object): def __init__(self): self.attributes = {'xmlns:foo':'foo', 'xmlns':'bar', 'a':'b'} def hasAttributes(self): return True def removeAttribute(self, name): del self.attributes[name] wrapped = DummyMinidomNode() node = self._makeOne(wrapped) node.remove_namespace_attrs() self.assertEqual(wrapped.attributes, {'a':'b'}) class XmlParserTests(unittest.TestCase): def _getTargetClass(self): from webdav.xmltools import XmlParser return XmlParser def _makeOne(self): return self._getTargetClass()() def test_parse_rejects_entities(self): XML = '\n'.join([ '', ']>', '&entity;' ]) parser = self._makeOne() self.assertRaises(ValueError, parser.parse, XML) def test_parse_rejects_doctype_wo_entities(self): XML = '\n'.join([ '', '' ]) parser = self._makeOne() self.assertRaises(ValueError, parser.parse, XML) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(NodeTests), unittest.makeSuite(XmlParserTests), )) zope2.13-2.13.21/source/Zope2/src/webdav/tests/testPUT_factory.py0000644000175000017500000000627712214017422023317 0ustar arnauarnauimport unittest import Testing import Zope2 Zope2.startup() from Testing.makerequest import makerequest import transaction import base64 auth_info = 'Basic %s' % base64.encodestring('manager:secret').rstrip() class TestPUTFactory(unittest.TestCase): def setUp(self): self.app = makerequest(Zope2.app()) try: # Make a manager user uf = self.app.acl_users uf._doAddUser('manager', 'secret', ['Manager'], []) # Make a folder to put stuff into self.app.manage_addFolder('folder', '') self.folder = self.app.folder # Fake a WebDAV PUT request request = self.app.REQUEST request['PARENTS'] = [self.app] request['BODY'] = 'bar' request.environ['CONTENT_TYPE'] = 'text/plain' request.environ['REQUEST_METHOD'] = 'PUT' request.environ['WEBDAV_SOURCE_PORT'] = 1 request._auth = auth_info except: self.tearDown() raise def tearDown(self): transaction.abort() self.app.REQUEST.close() self.app._p_jar.close() def testNoVirtualHosting(self): request = self.app.REQUEST put = request.traverse('/folder/doc') put(request, request.RESPONSE) self.assertTrue('doc' in self.folder.objectIds()) def testSimpleVirtualHosting(self): request = self.app.REQUEST put = request.traverse('/VirtualHostBase/http/foo.com:80/VirtualHostRoot/folder/doc') put(request, request.RESPONSE) self.assertTrue('doc' in self.folder.objectIds()) def testSubfolderVirtualHosting(self): request = self.app.REQUEST put = request.traverse('/VirtualHostBase/http/foo.com:80/folder/VirtualHostRoot/doc') put(request, request.RESPONSE) self.assertTrue('doc' in self.folder.objectIds()) def testInsideOutVirtualHosting(self): request = self.app.REQUEST put = request.traverse('/VirtualHostBase/http/foo.com:80/VirtualHostRoot/_vh_foo/folder/doc') put(request, request.RESPONSE) self.assertTrue('doc' in self.folder.objectIds()) def testSubfolderInsideOutVirtualHosting(self): request = self.app.REQUEST put = request.traverse('/VirtualHostBase/http/foo.com:80/folder/VirtualHostRoot/_vh_foo/doc') put(request, request.RESPONSE) self.assertTrue('doc' in self.folder.objectIds()) def testCollector2261(self): from OFS.Folder import manage_addFolder from OFS.DTMLMethod import addDTMLMethod self.app.manage_addFolder('A', '') addDTMLMethod(self.app, 'a', file='I am file a') self.app.A.manage_addFolder('B', '') request = self.app.REQUEST # this should create 'a' within /A/B containing 'bar' put = request.traverse('/A/B/a') put(request, request.RESPONSE) # PUT should no acquire A.a self.assertEqual(str(self.app.A.a), 'I am file a', 'PUT factory should not acquire content') # check for the newly created file self.assertEqual(str(self.app.A.B.a), 'bar') def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestPUTFactory), )) zope2.13-2.13.21/source/Zope2/src/webdav/tests/testCollection.py0000644000175000017500000000062312214017422023200 0ustar arnauarnauimport unittest class TestCollection(unittest.TestCase): def test_interfaces(self): from webdav.Collection import Collection from webdav.interfaces import IDAVCollection from zope.interface.verify import verifyClass verifyClass(IDAVCollection, Collection) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestCollection), )) zope2.13-2.13.21/source/Zope2/src/webdav/tests/testEtagSupport.py0000644000175000017500000000063712214017422023367 0ustar arnauarnauimport unittest class TestEtagSupport(unittest.TestCase): def test_interfaces(self): from zope.interface.verify import verifyClass from webdav.EtagSupport import EtagBaseInterface from webdav.EtagSupport import EtagSupport verifyClass(EtagBaseInterface, EtagSupport) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestEtagSupport), )) zope2.13-2.13.21/source/Zope2/src/webdav/tests/testNullResource.py0000644000175000017500000000521312214017422023527 0ustar arnauarnauimport unittest class TestLockNullResource(unittest.TestCase): def test_interfaces(self): from webdav.interfaces import IWriteLock from webdav.NullResource import LockNullResource from zope.interface.verify import verifyClass verifyClass(IWriteLock, LockNullResource) class TestNullResource(unittest.TestCase): def _getTargetClass(self): from webdav.NullResource import NullResource return NullResource def _makeOne(self, parent=None, name='nonesuch', **kw): return self._getTargetClass()(parent, name, **kw) def test_interfaces(self): from webdav.interfaces import IWriteLock from zope.interface.verify import verifyClass verifyClass(IWriteLock, self._getTargetClass()) def test_HEAD_locks_empty_body_before_raising_NotFound(self): from zExceptions import NotFound # See https://bugs.launchpad.net/bugs/239636 class DummyResponse: _server_version = 'Dummy' # emulate ZServer response locked = False body = None def setHeader(self, *args): pass def setBody(self, body, lock=False): self.body = body self.locked = bool(lock) nonesuch = self._makeOne() request = {} response = DummyResponse() self.assertRaises(NotFound, nonesuch.HEAD, request, response) self.assertEqual(response.body, '') self.assertTrue(response.locked) def test_PUT_unauthorized_message(self): # See https://bugs.launchpad.net/bugs/143946 import ExtensionClass from OFS.CopySupport import CopyError from zExceptions import Unauthorized class DummyRequest: def get_header(self, header, default=''): return default def get(self, name, default=None): return default class DummyResponse: _server_version = 'Dummy' # emulate ZServer response def setHeader(self, *args): pass class DummyParent(ExtensionClass.Base): def _verifyObjectPaste(self, *args, **kw): raise CopyError('Bad Boy!') nonesuch = self._makeOne() nonesuch.__parent__ = DummyParent() request = DummyRequest() response = DummyResponse() try: nonesuch.PUT(request, response) except Unauthorized, e: self.assertTrue(str(e).startswith('Unable to create object')) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestLockNullResource), unittest.makeSuite(TestNullResource), )) zope2.13-2.13.21/source/Zope2/src/webdav/dtml/0000755000175000017500000000000012214017422017430 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/webdav/dtml/locknullmain.dtml0000644000175000017500000000076312214017422023010 0ustar arnauarnau

    This item is locked by WebDAV as a Lock-Null Resource. A lock-null resource is created when a resource is locked before it is fully created, basically reserving its name. When the owner of this resource issues a command to either fill it with content, or turn it into a collection (folder), that object will replace this one.

    zope2.13-2.13.21/source/Zope2/src/webdav/LockItem.py0000644000175000017500000001404312214017422020553 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """WebDAV support - lock item. """ import time from AccessControl.class_init import InitializeClass from AccessControl.owner import ownerInfo from AccessControl.SecurityInfo import ClassSecurityInfo from Persistence import Persistent from zope.interface import implements from webdav.common import generateLockToken from webdav.interfaces import ILockItem MAXTIMEOUT = (2L**32)-1 # Maximum timeout time DEFAULTTIMEOUT = 12 * 60L # Default timeout def validateTimeout(timeout): # Timeout *should* be in the form "Seconds-XXX" or "Infinite" errors = [] try: t =str(timeout).split('-')[-1] if t.lower() == 'infinite': timeout = DEFAULTTIMEOUT # Default to 1800 secods for infinite else: # requests timeout = long(t) except ValueError: errors.append("Bad timeout value") if timeout > MAXTIMEOUT: errors.append("Timeout request is greater than %s" % MAXTIMEOUT) return timeout, errors class LockItem(Persistent): implements(ILockItem) # Use the Zope 2.3 declarative security to manage access security = ClassSecurityInfo() security.declarePublic('getOwner', 'getLockToken', 'getDepth', 'getTimeout', 'getTimeoutString', 'getModifiedTime', 'isValid', 'getLockScope', 'getLockType') security.declareProtected('Change Lock Information', 'setTimeout', 'refresh') security.declareProtected('Access contents information', 'getCreator', 'getCreatorPath') def __init__(self, creator, owner='', depth=0, timeout='Infinite', locktype='write', lockscope='exclusive', token=None): errors = [] # First check the values and raise value errors if outside of contract if not getattr(creator, 'getUserName', None): errors.append("Creator not a user object") if str(depth).lower() not in ('0', 'infinity'): errors.append("Depth must be 0 or infinity") if locktype.lower() != 'write': errors.append("Lock type '%s' not supported" % locktype) if lockscope.lower() != 'exclusive': errors.append("Lock scope '%s' not supported" % lockscope) timeout, e = validateTimeout(timeout) errors = errors + e # Finally, if there were errors, report them ALL to on high if errors: raise ValueError, errors # AccessControl.owner.ownerInfo returns the id of the creator # and the path to the UserFolder they're defined in self._creator = ownerInfo(creator) self._owner = owner self._depth = depth self._timeout = timeout self._locktype = locktype self._lockscope = lockscope self._modifiedtime = time.time() if token is None: self._token = generateLockToken() else: self._token = token def getCreator(self): return self._creator def getCreatorPath(self): db, name = self._creator path = '/'.join(db) return "/%s/%s" % (path, name) def getOwner(self): return self._owner def getLockToken(self): return self._token def getDepth(self): return self._depth def getTimeout(self): return self._timeout def getTimeoutString(self): t = str(self._timeout) if t[-1] == 'L': t = t[:-1] # lob off Long signifier return "Second-%s" % t def setTimeout(self, newtimeout): timeout, errors = validateTimeout(newtimeout) if errors: raise ValueError, errors else: self._timeout = timeout self._modifiedtime = time.time() # reset modified def getModifiedTime(self): return self._modifiedtime def refresh(self): self._modifiedtime = time.time() def isValid(self): now = time.time() modified = self._modifiedtime timeout = self._timeout return (modified + timeout) > now def getLockType(self): return self._locktype def getLockScope(self): return self._lockscope def asLockDiscoveryProperty(self, ns='d',fake=0): if fake: token = 'this-is-a-faked-no-permission-token' else: token = self._token s = (' <%(ns)s:activelock>\n' ' <%(ns)s:locktype><%(ns)s:%(locktype)s/>\n' ' <%(ns)s:lockscope><%(ns)s:%(lockscope)s/>\n' ' <%(ns)s:depth>%(depth)s\n' ' <%(ns)s:owner>%(owner)s\n' ' <%(ns)s:timeout>%(timeout)s\n' ' <%(ns)s:locktoken>\n' ' <%(ns)s:href>opaquelocktoken:%(locktoken)s\n' ' \n' ' \n' ) % { 'ns': ns, 'locktype': self._locktype, 'lockscope': self._lockscope, 'depth': self._depth, 'owner': self._owner, 'timeout': self.getTimeoutString(), 'locktoken': token, } return s def asXML(self): s = """ %s """ % self.asLockDiscoveryProperty(ns="d") return s InitializeClass(LockItem) zope2.13-2.13.21/source/Zope2/src/webdav/davcmds.py0000644000175000017500000005146112214017422020472 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """WebDAV xml request objects. """ import sys from cStringIO import StringIO from urllib import quote import transaction from AccessControl.Permissions import delete_objects from AccessControl.SecurityManagement import getSecurityManager from Acquisition import aq_base from Acquisition import aq_parent from OFS.PropertySheets import DAVProperties from zExceptions import BadRequest from zExceptions import Forbidden from webdav.common import absattr from webdav.common import isDavCollection from webdav.common import Locked from webdav.common import PreconditionFailed from webdav.common import urlbase from webdav.common import urlfix from webdav.common import urljoin from webdav.interfaces import IWriteLock from webdav.LockItem import LockItem from webdav.xmltools import XmlParser def safe_quote(url, mark=r'%'): if url.find(mark) > -1: return url return quote(url) class DAVProps(DAVProperties): """Emulate required DAV properties for objects which do not themselves support properties. This is mainly so that non-PropertyManagers can appear to support DAV PROPFIND requests.""" def __init__(self, obj): self.__obj__=obj def v_self(self): return self.__obj__ p_self=v_self class PropFind: """Model a PROPFIND request.""" def __init__(self, request): self.request=request self.depth='infinity' self.allprop=0 self.propname=0 self.propnames=[] self.parse(request) def parse(self, request, dav='DAV:'): self.depth=request.get_header('Depth', 'infinity') if not (self.depth in ('0','1','infinity')): raise BadRequest, 'Invalid Depth header.' body=request.get('BODY', '') self.allprop=(not len(body)) if not body: return try: root=XmlParser().parse(body) except: raise BadRequest, sys.exc_info()[1] e=root.elements('propfind', ns=dav) if not e: raise BadRequest, 'Invalid xml request.' e=e[0] if e.elements('allprop', ns=dav): self.allprop=1 return if e.elements('propname', ns=dav): self.propname=1 return prop=e.elements('prop', ns=dav) if not prop: raise BadRequest, 'Invalid xml request.' prop=prop[0] for val in prop.elements(): self.propnames.append((val.name(), val.namespace())) if (not self.allprop) and (not self.propname) and \ (not self.propnames): raise BadRequest, 'Invalid xml request.' return def apply(self, obj, url=None, depth=0, result=None, top=1): if result is None: result=StringIO() depth=self.depth url=urlfix(self.request['URL'], 'PROPFIND') url=urlbase(url) result.write('\n' \ '\n') iscol=isDavCollection(obj) if iscol and url[-1] != '/': url=url+'/' result.write('\n%s\n' % safe_quote(url)) if hasattr(aq_base(obj), 'propertysheets'): propsets=obj.propertysheets.values() obsheets=obj.propertysheets else: davprops=DAVProps(obj) propsets=(davprops,) obsheets={'DAV:': davprops} if self.allprop: stats=[] for ps in propsets: if hasattr(aq_base(ps), 'dav__allprop'): stats.append(ps.dav__allprop()) stats=''.join(stats) or '200 OK\n' result.write(stats) elif self.propname: stats=[] for ps in propsets: if hasattr(aq_base(ps), 'dav__propnames'): stats.append(ps.dav__propnames()) stats=''.join(stats) or '200 OK\n' result.write(stats) elif self.propnames: rdict={} for name, ns in self.propnames: ps=obsheets.get(ns, None) if ps is not None and hasattr(aq_base(ps), 'dav__propstat'): stat=ps.dav__propstat(name, rdict) else: prop='' % (name, ns) code='404 Not Found' if not rdict.has_key(code): rdict[code]=[prop] else: rdict[code].append(prop) keys=rdict.keys() keys.sort() for key in keys: result.write('\n' \ ' \n' \ ) map(result.write, rdict[key]) result.write(' \n' \ ' HTTP/1.1 %s\n' \ '\n' % key ) else: raise BadRequest, 'Invalid request' result.write('\n') if depth in ('1', 'infinity') and iscol: for ob in obj.listDAVObjects(): if hasattr(ob,"meta_type"): if ob.meta_type=="Broken Because Product is Gone": continue dflag=hasattr(ob, '_p_changed') and (ob._p_changed == None) if hasattr(ob, '__locknull_resource__'): # Do nothing, a null resource shouldn't show up to DAV if dflag: ob._p_deactivate() elif hasattr(ob, '__dav_resource__'): uri = urljoin(url, absattr(ob.getId())) depth = depth=='infinity' and depth or 0 self.apply(ob, uri, depth, result, top=0) if dflag: ob._p_deactivate() if not top: return result result.write('') return result.getvalue() class PropPatch: """Model a PROPPATCH request.""" def __init__(self, request): self.request=request self.values=[] self.parse(request) def parse(self, request, dav='DAV:'): body=request.get('BODY', '') try: root=XmlParser().parse(body) except: raise BadRequest, sys.exc_info()[1] vals=self.values e=root.elements('propertyupdate', ns=dav) if not e: raise BadRequest, 'Invalid xml request.' e=e[0] for ob in e.elements(): if ob.name()=='set' and ob.namespace()==dav: proptag=ob.elements('prop', ns=dav) if not proptag: raise BadRequest, 'Invalid xml request.' proptag=proptag[0] for prop in proptag.elements(): # We have to ensure that all tag attrs (including # an xmlns attr for all xml namespaces used by the # element and its children) are saved, per rfc2518. name, ns=prop.name(), prop.namespace() e, attrs=prop.elements(), prop.attrs() if (not e) and (not attrs): # simple property item=(name, ns, prop.strval(), {}) vals.append(item) else: # xml property attrs={} prop.remove_namespace_attrs() for attr in prop.attrs(): attrs[attr.qname()]=attr.value() md={'__xml_attrs__':attrs} item=(name, ns, prop.strval(), md) vals.append(item) if ob.name()=='remove' and ob.namespace()==dav: proptag=ob.elements('prop', ns=dav) if not proptag: raise BadRequest, 'Invalid xml request.' proptag=proptag[0] for prop in proptag.elements(): item=(prop.name(), prop.namespace()) vals.append(item) def apply(self, obj): url=urlfix(self.request['URL'], 'PROPPATCH') if isDavCollection(obj): url=url+'/' result=StringIO() errors=[] result.write('\n' \ '\n' \ '\n' \ '%s\n' % quote(url)) propsets=obj.propertysheets for value in self.values: status='200 OK' if len(value) > 2: name, ns, val, md=value propset=propsets.get(ns, None) if propset is None: propsets.manage_addPropertySheet('', ns) propset=propsets.get(ns) if propset.hasProperty(name): try: propset._updateProperty(name, val, meta=md) except: errors.append(str(sys.exc_info()[1])) status='409 Conflict' else: try: propset._setProperty(name, val, meta=md) except: errors.append(str(sys.exc_info()[1])) status='409 Conflict' else: name, ns=value propset=propsets.get(ns, None) if propset is None or not propset.hasProperty(name): # removing a non-existing property is not an error! # according to RFC 2518 status='200 OK' else: try: propset._delProperty(name) except: errors.append('%s cannot be deleted.' % name) status='409 Conflict' result.write('\n' \ ' \n' \ ' \n' \ ' \n' \ ' HTTP/1.1 %s\n' \ '\n' % (ns, name, status)) errmsg='\n'.join(errors) or 'The operation succeded.' result.write('\n' \ '%s\n' \ '\n' \ '\n' \ '' % errmsg) result=result.getvalue() if not errors: return result # This is lame, but I cant find a way to keep ZPublisher # from sticking a traceback into my xml response :( transaction.abort() result=result.replace( '200 OK', '424 Failed Dependency') return result class Lock: """Model a LOCK request.""" def __init__(self, request): self.request = request data = request.get('BODY', '') self.scope = 'exclusive' self.type = 'write' self.owner = '' timeout = request.get_header('Timeout', 'infinite') self.timeout = timeout.split(',')[-1].strip() self.parse(data) def parse(self, data, dav='DAV:'): root = XmlParser().parse(data) info = root.elements('lockinfo', ns=dav)[0] ls = info.elements('lockscope', ns=dav)[0] self.scope = ls.elements()[0].name() lt = info.elements('locktype', ns=dav)[0] self.type = lt.elements()[0].name() lockowner = info.elements('owner', ns=dav) if lockowner: # Since the Owner element may contain children in different # namespaces (or none at all), we have to find them for potential # remapping. Note that Cadaver doesn't use namespaces in the # XML it sends. lockowner = lockowner[0] for el in lockowner.elements(): name, elns = el.name(), el.namespace() if not elns: # There's no namespace, so we have to add one lockowner.remap({dav:'ot'}) el.__nskey__ = 'ot' for subel in el.elements(): if not subel.namespace(): el.__nskey__ = 'ot' else: el.remap({dav:'o'}) self.owner = lockowner.strval() def apply(self, obj, creator=None, depth='infinity', token=None, result=None, url=None, top=1): """ Apply, built for recursion (so that we may lock subitems of a collection if requested """ if result is None: result = StringIO() url = urlfix(self.request['URL'], 'LOCK') url = urlbase(url) iscol = isDavCollection(obj) if iscol and url[-1] != '/': url = url + '/' errmsg = None lock = None try: lock = LockItem(creator, self.owner, depth, self.timeout, self.type, self.scope, token) if token is None: token = lock.getLockToken() except ValueError: errmsg = "412 Precondition Failed" except: errmsg = "403 Forbidden" try: if not IWriteLock.providedBy(obj): if top: # This is the top level object in the apply, so we # do want an error errmsg = "405 Method Not Allowed" else: # We're in an infinity request and a subobject does # not support locking, so we'll just pass pass elif obj.wl_isLocked(): errmsg = "423 Locked" else: method = getattr(obj, 'wl_setLock') vld = getSecurityManager().validate(None, obj, 'wl_setLock', method) if vld and token and (lock is not None): obj.wl_setLock(token, lock) else: errmsg = "403 Forbidden" except: errmsg = "403 Forbidden" if errmsg: if top and ((depth in (0, '0')) or (not iscol)): # We don't need to raise multistatus errors raise errmsg[4:] elif not result.getvalue(): # We haven't had any errors yet, so our result is empty # and we need to set up the XML header result.write('\n' \ '\n') result.write('\n %s\n' % url) result.write(' HTTP/1.1 %s\n' % errmsg) result.write('\n') if depth == 'infinity' and iscol: for ob in obj.objectValues(): if hasattr(obj, '__dav_resource__'): uri = urljoin(url, absattr(ob.getId())) self.apply(ob, creator, depth, token, result, uri, top=0) if not top: return token, result if result.getvalue(): # One or more subitems probably failed, so close the multistatus # element and clear out all succesful locks result.write('') transaction.abort() # This *SHOULD* clear all succesful locks return token, result.getvalue() class Unlock: """ Model an Unlock request """ def apply(self, obj, token, url=None, result=None, top=1): if result is None: result = StringIO() url = urlfix(url, 'UNLOCK') url = urlbase(url) iscol = isDavCollection(obj) if iscol and url[-1] != '/': url = url + '/' errmsg = None islockable = IWriteLock.providedBy(obj) if islockable: if obj.wl_hasLock(token): method = getattr(obj, 'wl_delLock') vld = getSecurityManager().validate(None,obj, 'wl_delLock',method) if vld: obj.wl_delLock(token) else: errmsg = "403 Forbidden" else: errmsg = '400 Bad Request' else: # Only set an error message if the command is being applied # to a top level object. Otherwise, we're descending a tree # which may contain many objects that don't implement locking, # so we just want to avoid them if top: errmsg = "405 Method Not Allowed" if errmsg: if top and (not iscol): # We don't need to raise multistatus errors if errmsg[:3] == '403': raise Forbidden else: raise PreconditionFailed elif not result.getvalue(): # We haven't had any errors yet, so our result is empty # and we need to set up the XML header result.write('\n' \ '\n') result.write('\n %s\n' % url) result.write(' HTTP/1.1 %s\n' % errmsg) result.write('\n') if iscol: for ob in obj.objectValues(): if hasattr(ob, '__dav_resource__') and \ IWriteLock.providedBy(ob): uri = urljoin(url, absattr(ob.getId())) self.apply(ob, token, uri, result, top=0) if not top: return result if result.getvalue(): # One or more subitems probably failed, so close the multistatus # element and clear out all succesful unlocks result.write('') transaction.abort() return result.getvalue() class DeleteCollection: """ With WriteLocks in the picture, deleting a collection involves checking *all* descendents (deletes on collections are always of depth infinite) for locks and if the locks match. """ def apply(self, obj, token, sm, url=None, result=None, top=1): if result is None: result = StringIO() url = urlfix(url, 'DELETE') url = urlbase(url) iscol = isDavCollection(obj) errmsg = None parent = aq_parent(obj) islockable = IWriteLock.providedBy(obj) if parent and (not sm.checkPermission(delete_objects, parent)): # User doesn't have permission to delete this object errmsg = "403 Forbidden" elif islockable and obj.wl_isLocked(): if token and obj.wl_hasLock(token): # Object is locked, and the token matches (no error) errmsg = "" else: errmsg = "423 Locked" if errmsg: if top and (not iscol): if errmsg == "403 Forbidden": raise Forbidden() if errmsg == "423 Locked": raise Locked() elif not result.getvalue(): # We haven't had any errors yet, so our result is empty # and we need to set up the XML header result.write('\n' \ '\n') result.write('\n %s\n' % url) result.write(' HTTP/1.1 %s\n' % errmsg) result.write('\n') if iscol: for ob in obj.objectValues(): dflag = hasattr(ob,'_p_changed') and (ob._p_changed == None) if hasattr(ob, '__dav_resource__'): uri = urljoin(url, absattr(ob.getId())) self.apply(ob, token, sm, uri, result, top=0) if dflag: ob._p_deactivate() if not top: return result if result.getvalue(): # One or more subitems can't be delted, so close the multistatus # element result.write('\n') return result.getvalue() zope2.13-2.13.21/source/Zope2/src/Lifetime/0000755000175000017500000000000012214017421016755 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Lifetime/__init__.py0000644000175000017500000000561112214017421021071 0ustar arnauarnauimport sys, asyncore, time _shutdown_phase = 0 _shutdown_timeout = 30 # seconds per phase # The shutdown phase counts up from 0 to 4. # # 0 Not yet terminating. running in main loop # # 1 Loss of service is imminent. Prepare any front-end proxies for this # happening by stopping any ICP servers, so that they can choose to send # requests to other Zope servers in the cluster. # # 2 Stop accepting any new requests. # # 3 Wait for all old requests to have been processed # # 4 Already terminated # # It is up to individual socket handlers to implement these actions, by # providing the 'clean_shutdown_control' method. This is called intermittantly # during shutdown with two parameters; the current phase number, and the amount # of time that it has currently been in that phase. This method should return # true if it does not yet want shutdown to proceed to the next phase. def shutdown(exit_code,fast = 0): global _shutdown_phase global _shutdown_timeout if _shutdown_phase == 0: # Thread safety? proably no need to care import ZServer ZServer.exit_code = exit_code _shutdown_phase = 1 if fast: # Someone wants us to shutdown fast. This is hooked into SIGTERM - so # possibly the system is going down and we can expect a SIGKILL within # a few seconds. Limit each shutdown phase to one second. This is fast # enough, but still clean. _shutdown_timeout = 1.0 def loop(): # Run the main loop until someone calls shutdown() lifetime_loop() # Gradually close sockets in the right order, while running a select # loop to allow remaining requests to trickle away. graceful_shutdown_loop() def lifetime_loop(): # The main loop. Stay in here until we need to shutdown map = asyncore.socket_map timeout = 30.0 while map and _shutdown_phase == 0: asyncore.poll(timeout, map) def graceful_shutdown_loop(): # The shutdown loop. Allow various services to shutdown gradually. global _shutdown_phase timestamp = time.time() timeout = 1.0 map = asyncore.socket_map while map and _shutdown_phase < 4: time_in_this_phase = time.time()-timestamp veto = 0 for fd,obj in map.items(): try: fn = getattr(obj,'clean_shutdown_control') except AttributeError: pass else: try: veto = veto or fn(_shutdown_phase,time_in_this_phase) except: obj.handle_error() if veto and time_in_this_phase<_shutdown_timeout: # Any open socket handler can veto moving on to the next shutdown # phase. (but not forever) asyncore.poll(timeout, map) else: # No vetos? That is one step closer to shutting down _shutdown_phase += 1 timestamp = time.time() zope2.13-2.13.21/source/Zope2/src/HelpSys/0000755000175000017500000000000012214017421016606 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/HelpSys/HelpSys.py0000644000175000017500000002623012214017421020552 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from cgi import escape from AccessControl.class_init import InitializeClass from AccessControl.Permissions import access_contents_information from AccessControl.Permissions import add_documents_images_and_files from AccessControl.Permissions import view as View from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.SecurityManagement import getSecurityManager from Acquisition import Implicit from App.special_dtml import DTMLFile from App.special_dtml import HTML from OFS.ObjectManager import ObjectManager from OFS.SimpleItem import Item from Persistence import Persistent from Products.PluginIndexes.KeywordIndex.KeywordIndex import KeywordIndex from Products.ZCatalog.Lazy import LazyCat from Products.ZCatalog.ZCatalog import ZCatalog from Products.ZCTextIndex.HTMLSplitter import HTMLWordSplitter from Products.ZCTextIndex.Lexicon import CaseNormalizer from Products.ZCTextIndex.Lexicon import StopWordRemover from Products.ZCTextIndex.OkapiIndex import OkapiIndex from Products.ZCTextIndex.ZCTextIndex import PLexicon from Products.ZCTextIndex.ZCTextIndex import ZCTextIndex class HelpSys(Implicit, ObjectManager, Item, Persistent): """ Zope Help System Provides browsing and searching of Zope Product Help. """ meta_type='Help System' security = ClassSecurityInfo() security.declareObjectProtected(View) manage_options=( {'label' : 'Contents', 'action' : 'menu'}, {'label' : 'Search', 'action' : 'search'}, ) def __init__(self, id='HelpSys'): self.id=id security.declareProtected(access_contents_information, 'helpValues') def helpValues(self, spec=None): "ProductHelp objects of all Products that have help" hv=[] for product in self.Control_Panel.Products.objectValues(): productHelp=product.getProductHelp() # only list products that actually have help if productHelp.helpValues(): hv.append(productHelp) return hv # Seaching does an aggregated search of all ProductHelp # objects. Only Help Topics for which the user has permissions # are returned. security.declareProtected(View, '__call__') def __call__(self, REQUEST=None, **kw): "Searchable interface" if REQUEST is not None: perms = [] sm = getSecurityManager() for p in self.ac_inherited_permissions(all=True): if sm.checkPermission(p[0], self): perms.append(p[0]) REQUEST.set('permissions', perms) results = [] for ph in self.helpValues(): results.append(apply(getattr(ph, '__call__'), (REQUEST,) , kw)) return LazyCat(results) security.declareProtected(View, 'searchResults') searchResults=__call__ security.declareProtected(View, 'index_html') index_html=DTMLFile('dtml/frame', globals()) security.declareProtected(View, 'menu') menu=DTMLFile('dtml/menu', globals()) security.declareProtected(View, 'search') search=DTMLFile('dtml/search', globals()) security.declareProtected(View, 'results') results=DTMLFile('dtml/results', globals()) security.declareProtected(View, 'main') main=HTML("""""") standard_html_header=DTMLFile('dtml/menu_header', globals()) standard_html_footer=DTMLFile('dtml/menu_footer', globals()) button=DTMLFile('dtml/button', globals()) security.declareProtected(View, 'HelpButton') def HelpButton(self, topic, product): """ Insert a help button linked to a help topic. """ return self.button(self, self.REQUEST, product=product, topic=topic) helpURL=DTMLFile('dtml/helpURL',globals()) security.declareProtected(View, 'helpLink') def helpLink(self, product='OFSP', topic='ObjectManager_Contents.stx'): # Generate an tag linking to a help topic. This # is a little lighter weight than the help button approach. basepath=self.REQUEST['BASEPATH1'] products = self.Control_Panel.Products.objectIds() if product not in products: return None help_url='%s/Control_Panel/Products/%s/Help/%s' % ( basepath, product, topic ) help_url='%s?help_url=%s' % (self.absolute_url(), help_url) script="window.open('%s','zope_help','width=600,height=500," \ "menubar=yes,toolbar=yes,scrollbars=yes,resizable=yes');" \ "return false;" % escape(help_url, 1).replace("'", "\\'") h_link='Help!' % ( escape(help_url, 1), script ) return h_link def tpValues(self): """ Tree protocol - returns child nodes Aggregates Product Helps with the same title. """ helps={} for help in self.helpValues(): if helps.has_key(help.title): helps[help.title].append(help) else: helps[help.title]=[help] cols=[] for k,v in helps.items(): cols.append(TreeCollection(k,v,0)) return cols InitializeClass(HelpSys) class TreeCollection: """ A temporary wrapper for a collection of objects objects, used for help topic browsing to make a collection of objects appear as a single object. """ def __init__(self, id, objs, simple=1): self.id=self.title=id self.objs=objs self.simple=simple def tpValues(self): values=[] if self.simple: values=self.objs else: for obj in self.objs: values=values + list(obj.tpValues()) # resolve overlap ids={} for value in values: if ids.has_key(value.id): ids[value.id].append(value) else: ids[value.id]=[value] results=[] for k,v in ids.items(): if len(v)==1: results.append(v[0]) else: values=[] for topic in v: values=values + list(topic.tpValues()) results.append(TreeCollection(k, values)) results.sort(lambda x, y: cmp(x.id, y.id)) return results def tpId(self): return self.id class ProductHelp(Implicit, ObjectManager, Item, Persistent): """ Manages a collection of Help Topics for a given Product. Provides searching services to HelpSystem. """ meta_type='Product Help' icon='p_/ProductHelp_icon' security = ClassSecurityInfo() lastRegistered=None meta_types=({'name':'Help Topic', 'action':'addTopicForm', 'permission':'Add Documents, Images, and Files'}, ) manage_options=( ObjectManager.manage_options + Item.manage_options ) def __init__(self, id='Help', title=''): self.id = id self.title = title c = self.catalog = ZCatalog('catalog') l = PLexicon('lexicon', '', HTMLWordSplitter(), CaseNormalizer(), StopWordRemover()) c._setObject('lexicon', l) i = ZCTextIndex('SearchableText', caller=c, index_factory=OkapiIndex, lexicon_id=l.id) # not using c.addIndex because it depends on Product initialization c._catalog.addIndex('SearchableText', i) c._catalog.addIndex('categories', KeywordIndex('categories')) c._catalog.addIndex('permissions', KeywordIndex('permissions')) c.addColumn('categories') c.addColumn('permissions') c.addColumn('title_or_id') c.addColumn('url') c.addColumn('id') security.declareProtected(add_documents_images_and_files, 'addTopicForm') addTopicForm=DTMLFile('dtml/addTopic', globals()) security.declareProtected(add_documents_images_and_files, 'addTopic') def addTopic(self, id, title, REQUEST=None): "Add a Help Topic" from HelpSys.HelpTopic import DTMLDocumentTopic from HelpSys.HelpTopic import default_topic_content topic = DTMLDocumentTopic(default_topic_content, __name__=id) topic.title=title self._setObject(id, topic) if REQUEST is not None: return self.manage_main(self, REQUEST, manage_tabs_message='Help Topic added.') def helpValues(self, REQUEST=None): """ Lists contained Help Topics. Help Topics for which the user is not authorized are not listed. """ topics = self.objectValues('Help Topic') sm = getSecurityManager() return [ t for t in topics if t.authorized(sm) ] def tpValues(self): """ Tree protocol - child nodes """ topics=[] apitopics=[] dtmltopics=[] zpttopics=[] for topic in self.objectValues('Help Topic'): if hasattr(topic,'isAPIHelpTopic') and topic.isAPIHelpTopic: apitopics.append(topic) else: try: if callable(topic.id): id=topic.id() else: id=topic.id if id[:5]=='dtml-': dtmltopics.append(topic) if (id[:5] in ('metal', 'tales') and id[5] in ('.', '-')) or \ (id[:3]=='tal' and id[3] in ('.', '-')): zpttopics.append(topic) else: topics.append(topic) except ImportError: # Don't blow up if we have references to non-existant # products laying around pass if dtmltopics: topics = topics + [TreeCollection(' DTML Reference', dtmltopics)] if apitopics: topics = topics + [TreeCollection(' API Reference', apitopics)] if zpttopics: topics = topics + [TreeCollection(' ZPT Reference', zpttopics)] return topics def all_meta_types(self): import Products f=lambda x: x['name'] in ('Image', 'File') return filter(f, Products.meta_types) + self.meta_types def __call__(self, *args, **kw): """ Searchable interface """ return apply(self.catalog.__call__, args, kw) standard_html_header=DTMLFile('dtml/topic_header', globals()) standard_html_footer=DTMLFile('dtml/topic_footer', globals()) InitializeClass(ProductHelp) zope2.13-2.13.21/source/Zope2/src/HelpSys/images/0000755000175000017500000000000012214017421020053 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/HelpSys/images/hs_cbook.gif0000644000175000017500000000160412214017421022332 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,aH° Á,˜°A… :t‘ Ä‰+0àÆvüˆb€‡#IRÜH1%I…'[z,€€À˜(g&|ðÀæÍŒvò| 1¦Pž ž 04iD¦NÞÔ80 ;zope2.13-2.13.21/source/Zope2/src/HelpSys/images/hs_obook.gif0000644000175000017500000000162412214017421022350 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,qH°àÀ *Dø ¡B‚<0"Bˆ dØÐ!€ˆBrIà"H’!I6¨x²!”.$làeÊš+?ft çÌkN€@N„@]@ é™"œ:5çF™j•U*Uª= ;zope2.13-2.13.21/source/Zope2/src/HelpSys/images/hs_dnode.gif0000644000175000017500000000160412214017421022326 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,a@° AxÀ°áƒ…*tØpáÄ)fŒ8"à A"\ˆ dI†R鱡J–&c"x@`eÇ–(mN”i’¦ÎŒ-kÂä9SèMœF'âô92¥Ó§/†œJ5 ;zope2.13-2.13.21/source/Zope2/src/HelpSys/images/hs_uarrow.gif0000644000175000017500000000150412214017421022553 0ustar arnauarnauGIF89a ÷111p€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù, ! $` A‚X˜paAƒŒø Ň f\;zope2.13-2.13.21/source/Zope2/src/HelpSys/images/hs_rarrow.gif0000644000175000017500000000150312214017421022547 0ustar arnauarnauGIF89a ÷111p€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù,  Hp €ƒ DHp¡C…#F„ÈbB/;zope2.13-2.13.21/source/Zope2/src/HelpSys/images/hs_larrow.gif0000644000175000017500000000150312214017421022541 0ustar arnauarnauGIF89a ÷111p€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù,  (Á‚ LÈ!Ç:|HâA /b ;zope2.13-2.13.21/source/Zope2/src/HelpSys/images/hs_darrow.gif0000644000175000017500000000150312214017421022531 0ustar arnauarnauGIF89a ÷111p€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù,  8Á‚ Àð ÆB\(1!ÅŠ1J ;zope2.13-2.13.21/source/Zope2/src/HelpSys/images/productHelp.gif0000644000175000017500000000021112214017421023025 0ustar arnauarnauGIF89aÂÿÿÿÿÿÀÀÀ€€€PPPÿÿÿÿ!ù,@NxºÜJ@YêœÃ€¯•'jD&¦m)á,ePf¶EWZ“Äàÿ@ßjC(¾`º@qi¼M,hÀÉ= * '•h¹ØÔ7)Îèôù;zope2.13-2.13.21/source/Zope2/src/HelpSys/images/helpTopic.gif0000644000175000017500000000017512214017421022474 0ustar arnauarnauGIF89a¢ÿÿÿÿÀÀÀPPPÿÀÀÀ!ù,@BX*Ü®°@Á§Å!rKfj¤tžyÙ¥m] Rôì|Ÿ«§‰ëU¹\`C ìÃKÁ>·ßó$µeª+\ª0êz;zope2.13-2.13.21/source/Zope2/src/HelpSys/ObjectRef.py0000644000175000017500000001062612214017421021030 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Object Reference implementation""" import sys from urllib import quote from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from App.special_dtml import DTMLFile from HelpSys.HelpUtil import classobject from HelpSys.HelpUtil import HelpBase from HelpSys.HelpUtil import is_class from HelpSys.HelpUtil import is_module class ObjectItem(HelpBase, classobject): """ """ security = ClassSecurityInfo() security.declareObjectPublic() hs_main=DTMLFile('dtml/objectitem', globals()) hs_cicon='HelpSys/hs_dnode' hs_eicon='HelpSys/hs_dnode' def hs_id(self): return self._obj_.meta_type def hs_url(self): return quote(self._obj_.meta_type) hs_title=hs_id def __getattr__(self, name): if name in ('isDocTemp', '__call__'): raise AttributeError, name return getattr(self.__dict__['_obj_'], name) def get_method_list(self): rdict=classobject.get_method_dict(self) perms=self.__ac_permissions__ mdict={} mlist=[] for p in self.__ac_permissions__: pname=p[0] fnames=p[1] for fname in fnames: if rdict.has_key(fname): fn=rdict[fname] fn.permission=pname mdict[fname]=fn keys=mdict.keys() keys.sort() for key in keys: fn=mdict[key] if not hasattr(fn._obj_, '__doc__'): continue doc=fn._obj_.__doc__ if hasattr(fn._obj_, '__class__') and \ fn._obj_.__class__.__doc__ is doc: continue mlist.append(mdict[key]) del rdict del mdict return mlist security.declarePublic('hs_objectvalues') def hs_objectvalues(self): return [] InitializeClass(ObjectItem) class ObjectRef(HelpBase): """ """ security = ClassSecurityInfo() security.declareObjectPublic() __names__=None hs_main=DTMLFile('dtml/objectref', globals()) hs_cicon='HelpSys/hs_cbook' hs_eicon='HelpSys/hs_obook' hs_id ='ObjectRef' hs_title='Object Reference' hs_url =hs_id def hs_deferred__init__(self): # This is necessary because we want to wait until all # products have been installed (imported). dict={} for k, v in sys.modules.items(): if v is not None and k != '__builtins__': dict=self.hs_search_mod(v, dict) keys=dict.keys() keys.sort() __traceback_info__=(`dict`,) for key in keys: setattr(self, key, dict[key]) self.__names__=keys def hs_search_mod(self, mod, dict): # Root through a module for things that look like # createable object classes. hidden=('Control Panel', 'simple item', 'Broken Because Product is Gone') for k, v in mod.__dict__.items(): if is_class(v) and hasattr(v, 'meta_type') and \ hasattr(v, '__ac_permissions__'): if callable(v.meta_type): meta_type=v.meta_type() else: meta_type=v.meta_type if (meta_type is not None) and (meta_type not in hidden): dict[meta_type]=ObjectItem(k, v) if is_module(v) and hasattr(v, '__path__'): dict=self.hs_search_mod(v, dict) return dict security.declarePublic('hs_objectvalues') def hs_objectvalues(self): if self.__names__ is None: self.hs_deferred__init__() items=[] for id in self.__names__: items.append(getattr(self, id)) return items def __getitem__(self, key): return self.__dict__[key].__of__(self) InitializeClass(ObjectRef) zope2.13-2.13.21/source/Zope2/src/HelpSys/APIHelpTopic.py0000644000175000017500000002027312214017421021405 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ API documentation help topics. """ import types from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from App.special_dtml import DTMLFile from Persistence import Persistent from HelpTopic import HelpTopic # XXX relative to avoid cycle _ignore_objects = {} class APIHelpTopic(HelpTopic): """ Provides API documentation. """ isAPIHelpTopic=1 funcs=() # for backward compatibility def __init__(self, id, title, file): self.id=id self.title=title dict={} execfile(file, dict) self.doc=dict.get('__doc__','') self.apis=[] self.funcs=[] for k, v in dict.items(): if (not _ignore_objects.has_key(k) or _ignore_objects[k] is not v): if type(v)==types.ClassType: # A class. self.apis.append(APIDoc(v, 0)) elif (hasattr(v, 'implementedBy')): # A zope.interface.Interface. self.apis.append(APIDoc(v, 1)) elif type(v)==types.FunctionType: # A function self.funcs.append(MethodDoc(v, 0)) # try to get title from first non-blank line # of module docstring if not self.title: lines=self.doc.split('\n') while 1: line=lines[0].strip() if line: # get rid of anything after a colon in the line self.title=line.split(':')[0] break lines.pop(0) if not lines: break # otherwise get title from first class name if not self.title: self.title=self.apis[0].name index_html=DTMLFile('dtml/APIHelpView', globals()) def SearchableText(self): "The full text of the Help Topic, for indexing purposes" text="%s %s" % (self.title, self.doc) for api in self.apis + self.funcs: try: # not all api's provide SearchableText() text="%s %s" % (text, api.SearchableText()) except AttributeError: pass return text class APIDoc(Persistent): """ Describes an API. """ security = ClassSecurityInfo() security.setDefaultAccess( {'attributes': True, 'constructor': True, 'doc': True, 'extends': True, 'name': True, 'methods': True} ) extends=() def __init__(self, klass, isInterface=0): if isInterface: self._createFromInterface(klass) else: self._createFromClass(klass) def _createFromInterface(self, klass): # Creates an APIDoc instance given an interface object. self.name=klass.__name__ self.doc=trim_doc_string(klass.__doc__) # inheritence information self.extends=[] # Get info on methods and attributes, ignore special items self.attributes=[] self.methods=[] for k,v in klass.namesAndDescriptions(): if hasattr(v, 'getSignatureInfo'): self.methods.append(MethodDoc(v, 1)) else: self.attributes.append(AttributeDoc(k, v.__doc__)) def _createFromClass(self, klass): # Creates an APIDoc instance given a python class. # the class describes the API; it contains # methods, arguments and doc strings. # # The name of the API is deduced from the name # of the class. self.name=klass.__name__ self.doc=trim_doc_string(klass.__doc__) # Get info on methods and attributes, ignore special items self.attributes=[] self.methods=[] for k,v in klass.__dict__.items(): if k not in ('__extends__', '__doc__', '__constructor__'): if type(v)==types.FunctionType: self.methods.append(MethodDoc(v, 0)) else: self.attributes.append(AttributeDoc(k, v)) def SearchableText(self): """ The full text of the API, for indexing purposes. """ text="%s %s" % (self.name, self.doc) for attribute in self.attributes: text="%s %s" % (text, attribute.name) for method in self.methods: text="%s %s %s" % (text, method.name, method.doc) return text view=DTMLFile('dtml/APIView', globals()) InitializeClass(APIDoc) class AttributeDoc(Persistent): """ Describes an attribute of an API. """ security = ClassSecurityInfo() security.setDefaultAccess( {'name': True, 'value': True} ) def __init__(self, name, value): self.name=name self.value=value view=DTMLFile('dtml/attributeView', globals()) InitializeClass(AttributeDoc) class MethodDoc(Persistent): """ Describes a method of an API. required - a sequence of required arguments optional - a sequence of tuples (name, default value) varargs - the name of the variable argument or None kwargs - the name of the kw argument or None """ security = ClassSecurityInfo() security.setDefaultAccess( {'doc': True, 'kwargs': True, 'name': True, 'optional': True, 'required': True, 'varargs': True} ) varargs=None kwargs=None def __init__(self, func, isInterface=0): if isInterface: self._createFromInterfaceMethod(func) else: self._createFromFunc(func) def _createFromInterfaceMethod(self, func): self.name = func.__name__ self.doc = trim_doc_string(func.__doc__) self.required = func.required opt = [] for p in func.positional[len(func.required):]: opt.append((p, func.optional[p])) self.optional = tuple(opt) if func.varargs: self.varargs = func.varargs if func.kwargs: self.kwargs = func.kwargs def _createFromFunc(self, func): if hasattr(func, 'im_func'): func=func.im_func self.name=func.__name__ self.doc=trim_doc_string(func.__doc__) # figure out the method arguments # mostly stolen from pythondoc CO_VARARGS = 4 CO_VARKEYWORDS = 8 names = func.func_code.co_varnames nrargs = func.func_code.co_argcount if func.func_defaults: nrdefaults = len(func.func_defaults) else: nrdefaults = 0 self.required = names[:nrargs-nrdefaults] if func.func_defaults: self.optional = tuple(map(None, names[nrargs-nrdefaults:nrargs], func.func_defaults)) else: self.optional = () varargs = [] ix = nrargs if func.func_code.co_flags & CO_VARARGS: self.varargs=names[ix] ix = ix+1 if func.func_code.co_flags & CO_VARKEYWORDS: self.kwargs=names[ix] view=DTMLFile('dtml/methodView', globals()) InitializeClass(MethodDoc) def trim_doc_string(text): """ Trims a doc string to make it format correctly with structured text. """ text=text.strip() text=text.replace( '\r\n', '\n') lines=text.split('\n') nlines=[lines[0]] if len(lines) > 1: min_indent=None for line in lines[1:]: if not line: continue indent=len(line) - len(line.lstrip()) if indent < min_indent or min_indent is None: min_indent=indent for line in lines[1:]: nlines.append(line[min_indent:]) return '\n'.join(nlines) zope2.13-2.13.21/source/Zope2/src/HelpSys/__init__.py0000644000175000017500000000130212214017421020713 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## # backward compatibility import HelpSys import sys sys.modules['HelpSys.HelpSystem']=HelpSys zope2.13-2.13.21/source/Zope2/src/HelpSys/HelpUtil.py0000644000175000017500000002723212214017421020714 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Help system support module""" import re import sys from AccessControl.class_init import InitializeClass from Acquisition import Implicit from App.Dialogs import MessageDialog from App.special_dtml import HTML from Persistence import Persistent from zope.structuredtext.html import HTMLWithImages from zope.structuredtext.document import DocumentWithImages class HelpBase(Implicit): """ """ def __bobo_traverse__(self, REQUEST, name=None): # A sneaky trick - we cant really _have_ an index_html # because that would often hide the index_html of a # wrapped object ;( if name=='index_html': return self.hs_index return getattr(self, name) def __len__(self): return 1 class object(Implicit): def __init__(self, name, ob, op=None): self._name=name self._obj_=ob self._obp_=op def __getattr__(self, name): return getattr(self.__dict__['_obj_'], name) def __len__(self): return 1 def get_id(self): return id(self._obj_) def get_name(self): return self._name def get_type(self): return type(self._obj_).__name__ def get_value(self): return self._obj_ def get_docstring(self): if hasattr(self._obj_, '__doc__'): doc=self._obj_.__doc__ if not doc: doc='' return doc return '' def get_docstring_html(self): doc=self.get_docstring() if doc.find('\n\n') > -1: doc=doc.split('\n\n') if len(doc) > 1: doc[1]=doc[1].strip() doc='\n\n'.join(doc) doc = DocumentWithImages()(doc) return HTMLWithImages()(doc) def version(self): return None tpId =get_name tpURL =get_name __str__=get_name class moduleobject(object): def get_file(self): if hasattr(self._obj_, '__file__'): return self._obj_.__file__ def get_modules(self): data=[] for name, ob in self._obj_.__dict__.items(): if is_module(ob) and _chModule(name, ob): data.append(moduleobject(name, ob, self)) return data def get_classes(self): data=[] for name, ob in self._obj_.__dict__.items(): if is_class(ob) and _chClass(name, ob): data.append(classobject(name, ob, self)) return data class classobject(object): def get_metatype(self): try: return self._obj_.meta_type except: t, v = sys.exc_info()[:2] return '%s %s' % (t, v) def get_module(self): if hasattr(self._obj_, '__module__'): module=sys.modules[self._obj_.__module__] return moduleobject(module.__name__, module) def get_file(self): return self.get_module().get_file() def get_bases(self): bases=[] if hasattr(self._obj_, '__bases__'): for base in self._obj_.__bases__: bases.append(classobject(base.__name__, base)) return bases def get_base_list(self, list=None): if list is None: list=[] list.append(self) for base in self.get_bases(): list=base.get_base_list(list) return list def get_methods(self): keys=self._obj_.__dict__.keys() dict=self._obj_.__dict__ keys.sort() methods=[] for name in keys: ob=dict[name] if is_method(ob) and _chMethod(name, ob): methods.append(methodobject(name, ob, self)) return methods def get_method_dict(self, dict=None): if dict is None: dict={} dup=dict.has_key for method in self.get_methods(): name=method.get_name() if not dup(name): dict[name]=method for base in self.get_bases(): dict=base.get_method_dict(dict) return dict def get_method_list(self): dict=self.get_method_dict() keys=dict.keys() keys.sort() list=[] for key in keys: list.append(dict[key]) del dict return list ## def obAttributes(self): ## # Return list of class attributes ## keys=self._obj_.__dict__.keys() ## dict=self._obj_.__dict__ ## keys.sort() ## attrs=[] ## for name in keys: ## ob=dict[name] ## if _isAttribute(ob) and _chAttribute(name, ob): ## attrs.append(AttributeObject(name, ob, self)) ## return attrs ## def obAttributeDict(self, dict=None): ## # Return dict of attrs in class and superclasses ## if dict is None: ## dict={} ## root=1 ## else: root=0 ## dup=dict.has_key ## for attr in self._obj_Attributes(): ## name=attr.obName() ## if not dup(name): ## dict[name]=attr ## for base in self._obj_Bases(): ## dict=base.obAttributeDict(dict) ## return dict ## def obAttributeList(self): ## # Return list of attrs in class and superclasses ## dict=self._obj_AttributeDict() ## keys=dict.keys() ## keys.sort() ## list=[] ## append=list.append ## for name in keys: ## append(dict[name]) ## del dict ## return list # needs to be tested !!! The conversion of reconvert.convert looks suspicious sig_match=re.compile(r'[\w]*\([^)]*\)').match # matches "f(arg1, arg2)" pre_match=re.compile(r'[\w]*\([^)]*\)[ -]*').match # with ' ' or '-' included class methodobject(object): def get_class(self): return self._obp_ def get_module(self): return self.get_class().get_module() def get_file(self): return self.get_module().get_file() def get_docstring(self): func=self._obj_ doc='' if hasattr(func, 'im_func'): func=func.im_func if hasattr(func, '__doc__'): doc=func.__doc__ if not doc: doc='' doc=doc.strip() if hasattr(func, 'func_code'): if hasattr(func.func_code, 'co_varnames'): return doc mo=pre_match(doc) if mo is not None: return doc[mo.end(0):] return doc def get_signaturex(self): name=self._name func=self._obj_ method=None if hasattr(func, 'im_func'): method=1 func=func.im_func # Normal functions if hasattr(func, 'func_code'): if hasattr(func.func_code, 'co_varnames'): args=map(lambda x: x, func.func_code.co_varnames[:func.func_code.co_argcount]) ndefaults=func.func_defaults ndefaults=ndefaults and len(ndefaults) or 0 if '__ick__' in args: nick=len(args)-args.index('__ick__') args=args[:-nick] ndefaults=ndefaults-nick if ndefaults > 0: args[-ndefaults]='['+args[-ndefaults] args[-1]=args[-1]+']' if method: args=args[1:] if name=='__call__': name='Call Operation' return '%s(%s)' % (name, ', '.join(args)) # Other functions - look for something that smells like # a signature at the beginning of the docstring. if hasattr(func, '__doc__'): doc=func.__doc__ if not doc: doc='' doc=doc.strip() mo=sig_match(doc) if mo is not None: return doc[:mo.end(0)] return '%s()' % name def get_signature(self): try: return self.get_signaturex() except: t, v = sys.exc_info()[:2] return '%s %s' % (t, v) ## class AttributeObject(_ob_): ## def obClass(self): ## return self.op ## def obModule(self): ## return self.obClass().obModule() ## def obFile(self): ## return self.obModule().obFile() ## class InstanceObject(_ob_): ## def obClass(self): ## # Return the class for this instance ## c=self._obj_.__class__ ## return ClassObject(c.__name__, c) ## def obClassList(self): ## # Return list of all superclasses ## return self._obj_Class().obClassList() ## def obMethods(self): ## # Return list of instance methods ## keys=self._obj_.__dict__.keys() ## dict=self._obj_.__dict__ ## keys.sort() ## methods=[] ## for name in keys: ## ob=dict[name] ## if _isMethod(ob) and _chMethod(name, ob): ## methods.append(MethodObject(name, ob)) ## return methods ## def obMethodDict(self): ## # Return dict of instance and superclass methods ## dict=self._obj_Class().obMethodDict() ## for method in self._obj_Methods(): ## dict[method.obName()]=method ## return dict ## def obMethodList(self): ## # Return list of instance and superclass methods ## dict=self._obj_MethodDict() ## keys=dict.keys() ## keys.sort() ## list=[] ## append=list.append ## for name in keys: ## append(dict[name]) ## return list ## def obAttributes(self): ## # Return list of instance attributes ## keys=self._obj_.__dict__.keys() ## dict=self._obj_.__dict__ ## keys.sort() ## attrs=[] ## for name in keys: ## ob=dict[name] ## if _isAttribute(ob) and _chAttribute(name, ob): ## attrs.append(AttributeObject(name, ob, self)) ## return attrs ## def obAttributeDict(self): ## # Return dict of instance and superclass attributes ## dict=self._obj_Class().obAttributeDict() ## for attr in self._obj_Attributes(): ## dict[attr.obName()]=attr ## return dict ## def obAttributeList(self): ## # Return list of instance and superclass attributes ## dict=self._obj_AttributeDict() ## keys=dict.keys() ## keys.sort() ## list=[] ## append=list.append ## for name in keys: ## append(dict[name]) ## return list _classtypes=(type(HTML), type(Persistent), ) _methodtypes=(type([].sort), type(default__class_init__), type(HTML.manage_edit), type(HTML.__changed__), type(MessageDialog.manage_edit), ) def is_module(ob): return type(ob)==type(sys) def is_class(ob): return type(ob) in _classtypes def is_method(ob): if type(ob) in _methodtypes or hasattr(ob, 'func_code'): return 1 return 0 def is_attribute(ob): return not is_method(ob) def _chModule(name, ob): if name[0]=='_': return 0 return 1 def _chClass(name, ob): if name[0]=='_': return 0 return 1 def _chMethod(name, ob): if name[0]=='_': return 0 return 1 def _chAttribute(name, ob): if name[0]=='_': return 0 return 1 zope2.13-2.13.21/source/Zope2/src/HelpSys/HelpTopic.py0000644000175000017500000002075512214017421021060 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import os from AccessControl.class_init import InitializeClass from AccessControl.Permissions import access_contents_information from AccessControl.Permissions import view as View from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import Implicit from App.ImageFile import ImageFile from App.special_dtml import DTMLFile from App.special_dtml import HTML from ComputedAttribute import ComputedAttribute from OFS.DTMLDocument import DTMLDocument from OFS.PropertyManager import PropertyManager from OFS.SimpleItem import Item from Persistence import Persistent class HelpTopicBase: "Mix-in Help Topic support class" _properties=( {'id':'title', 'type':'string', 'mode':'w'}, {'id':'categories', 'type':'multiple selection', 'select_variable':'categories_values', 'mode':'w'}, {'id':'permissions', 'type':'multiple selection', 'select_variable':'permissions_values', 'mode':'w'}, ) # default values categories=('Content Manager Information',) permissions=('View',) def _permissions_values(self): perms=[] for m in self.permission_settings(): perms.append(m['name']) return perms permissions_values=ComputedAttribute(_permissions_values, 1) categories_values=( 'Content Manager Information', 'DTML Programmer Information', 'Python Programmer Information', ) def helpValues(self, REQUEST=None): return () def authorized(self, sm): "Is a given user authorized to view this Help Topic?" if not self.permissions: return True return any( sm.checkPermission(p, self) for p in self.permissions ) # Indexable methods # ----------------- def SearchableText(self): "The full text of the Help Topic, for indexing purposes" raise NotImplementedError def url(self): "URL for indexing purposes" return '/'.join(self.getPhysicalPath()) # Private indexing methods # ------------------------ def manage_afterAdd(self, item, container): self.index_object() def manage_afterClone(self, item): self.index_object() def manage_beforeDelete(self, item, container): self.unindex_object() def _setPropValue(self, id, value): setattr(self,id,value) self.reindex_object() def index_object(self, prefix=''): self.get_catalog().catalog_object(self, prefix + self.url()) def unindex_object(self, prefix=''): self.get_catalog().uncatalog_object(prefix + self.url()) def reindex_object(self): self.unindex_object() self.index_object() def get_catalog(self): return self.catalog class HelpTopic(Implicit, HelpTopicBase, Item, PropertyManager, Persistent): """ Abstract base class for Help Topics """ meta_type='Help Topic' icon='p_/HelpTopic_icon' _v_last_read = 0 security = ClassSecurityInfo() manage_options=( {'label':'Properties', 'action':'manage_propertiesForm'}, {'label':'View', 'action':'index_html'}, ) security.declareProtected(View, 'SearchableText') security.declareProtected(View, 'url') security.declareProtected(access_contents_information, 'helpValues') def _set_last_read(self, filepath): try: mtime = os.stat(filepath)[8] except: mtime = 0 self._v_last_read = mtime def _check_for_update(self): import Globals if Globals.DevelopmentMode: try: mtime=os.stat(self.file)[8] except: mtime=0 if mtime != self._v_last_read: fileob = open(self.file) self.obj = fileob.read() fileob.close() self._v_last_read=mtime self.reindex_object() security.declareProtected(View, 'index_html') def index_html(self, REQUEST, RESPONSE): "View the Help Topic" raise NotImplementedError InitializeClass(HelpTopic) class DTMLDocumentTopic(HelpTopicBase, DTMLDocument): """ A user addable Help Topic based on DTML Document. """ meta_type='Help Topic' icon='p_/HelpTopic_icon' def munge(self,*args, **kw): apply(DTMLDocument.munge, (self,) + args, kw) self.reindex_object() def SearchableText(self): return '%s %s' % (self.title, self.read()) default_topic_content="""\ <dtml-var title_or_id>

    This is the Help Topic.

    """ class DTMLTopic(HelpTopic): """ A basic Help Topic. Holds a HTMLFile object. """ def __init__(self, id, title, file, permissions=None, categories=None): self.id=id self.title=title file,ext=os.path.splitext(file) prefix,file=os.path.split(file) self.index_html=DTMLFile(file,prefix) if permissions is not None: self.permissions=permissions if categories is not None: self.categories=categories def SearchableText(self): "The full text of the Help Topic, for indexing purposes" return '%s %s' % (self.title, self.index_html.read()) class TextTopic(HelpTopic): """ A basic Help Topic. Holds a text file. """ index_html = None def __init__(self, id, title, file, permissions=None, categories=None): self.id=id self.title=title self.file = file self.obj=open(file).read() self._set_last_read(file) if permissions is not None: self.permissions=permissions if categories is not None: self.categories=categories def __call__(self, REQUEST=None): "View the Help Topic" self._check_for_update() return self.obj def SearchableText(self): "The full text of the Help Topic, for indexing purposes" return '%s %s' % (self.title, self.obj) class STXTopic(TextTopic): """ A structured-text topic. Holds a HTMLFile object. """ index_html = None def __call__(self, REQUEST=None): """ View the STX Help Topic """ self._check_for_update() return self.htmlfile(self, REQUEST) htmlfile = HTML("""\ <dtml-var title_or_id> """) class ReSTTopic(TextTopic): """ A reStructuredText [1]_ topic. Similar to STXTopic, it uses a simle DTML construct to render its contents - this time using the *reStructuredText* language. .. [1] reStructuredText (http://docutils.sourceforge.net/rst.html) """ index_html = None def __call__(self, REQUEST=None): """ Renders the ReST Help Topic """ self._check_for_update() return self.htmlfile(self, REQUEST) htmlfile = HTML("""\ <dtml-var title_or_id> """) class ImageTopic(HelpTopic): """ A image Help Topic. Holds an ImageFile object. """ meta_type='Help Image' def __init__(self, id, title, file, categories=None, permissions=None): self.id=id self.title=title self.file = file self.obj=open(file).read() self._set_last_read(file) dir, file=os.path.split(file) self.image=ImageFile(file, dir) if permissions is not None: self.permissions=permissions if categories is not None: self.categories=categories def index_html(self, REQUEST, RESPONSE): "View the Help Topic" self._check_for_update() return self.image.index_html(REQUEST, RESPONSE) def SearchableText(self): "The full text of the Help Topic, for indexing purposes" return '' zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/0000755000175000017500000000000012214017421017546 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/button.dtml0000644000175000017500000000116112214017421021742 0ustar arnauarnau
    zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/helpsys_menu.dtml0000644000175000017500000000176112214017421023150 0ustar arnauarnau Z Object Publishing Environment Help
    Z Online Help Z Online Help
    click to view this item click to view this item
    &dtml-hs_title; zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/attributeView.dtml0000644000175000017500000000012212214017421023261 0ustar arnauarnau

    &dtml-name; = &dtml-value;

    zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/menu_footer.dtml0000644000175000017500000000002012214017421022742 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/APIView.dtml0000644000175000017500000000117412214017421021677 0ustar arnauarnau

    class &dtml-name; ( &dtml-sequence-key;, )


    Product Constructor


    Methods

    zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/search.dtml0000644000175000017500000000045312214017421021677 0ustar arnauarnau

    Search terms

    zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/addTopic.dtml0000644000175000017500000000110312214017421022152 0ustar arnauarnau Add Help Topic

    Add Help Topic

    Id
    Title
    zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/objectref.dtml0000644000175000017500000000152612214017421022377 0ustar arnauarnau Object Reference

    Object Reference

    The object reference documents the interfaces of objects which are built in to the system or have been installed as add-on products. This reference focuses on those object services useful in DTML scripting.

    Back to Help

    zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/frame.dtml0000644000175000017500000000071312214017421021523 0ustar arnauarnau Zope Help System SRC="&dtml.url_quote-help_url;" SRC="main" NAME="help_main" MARGINWIDTH="2" MARGINHEIGHT="0" SCROLLING="auto"> Management interfaces require the use of a <B>frames-capable</B> web browser. zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/helpURL.dtml0000644000175000017500000000011012214017421021733 0ustar arnauarnau&dtml-BASEPATH1;/Control_Panel/Products/&dtml-product;/Help/&dtml-topic;zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/helpsys.dtml0000644000175000017500000000106512214017421022121 0ustar arnauarnau Z Object Publishing Environment Help This item requires the use of a <em>frames-capable</em> web browser. zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/topic_footer.dtml0000644000175000017500000000001712214017421023122 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/topic_header.dtml0000644000175000017500000000067412214017421023065 0ustar arnauarnau &dtml-title; zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/menu_header.dtml0000644000175000017500000000011512214017421022701 0ustar arnauarnau &dtml-title; zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/objectitem.dtml0000644000175000017500000000346312214017421022563 0ustar arnauarnau Object Reference

    Object Reference

    &dtml-meta_type;

    &dtml-meta_type; methods

    &dtml-get_name;
    &dtml-get_name;
    &dtml-get_signature;
    Permission: &dtml-permission;

    No documentation for this method

    top

    Back to Object Reference

    zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/menu.dtml0000644000175000017500000000050612214017421021375 0ustar arnauarnau &dtml-title_or_id; &dtml-title; zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/helpsys_main.dtml0000644000175000017500000000347512214017421023134 0ustar arnauarnau Z Object Publishing Environment Help

    Z Object Publishing Environment Help

    The Z online help system provides links to in-depth product guides, programming references and other resources.

    General Documentation

    General documentation for the use and management of built-in and add-on products, as well as an extensive DTML manual are available on the Zope website at http://www.zope.org.

    The Zope Guides are a great place to start learning Zope.

    Programming References

    There is currently an online object reference that documents the services provided by high-level objects such as Documents and Folders. Other references will be added soon.

    Other Resources

    One of the best resources available is the Zope mailing list. You can subscribe to the mailing list by sending an email to zope-request@zope.org and including the word "subscribe" in the subject line.

    There is also a Zope documentation project working on a Zope FAQ

    zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/APIHelpView.dtml0000644000175000017500000000204512214017421022506 0ustar arnauarnau

    API Documentation

    1 or (apis and funcs)">

    Classes

    &dtml-name; ,

    1 or (apis and funcs)">

    Functions

    &dtml-name; ,


    Classes


    Functions


    zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/methodView.dtml0000644000175000017500000000070512214017421022545 0ustar arnauarnau

    &dtml-name;(&dtml-sequence-item; , , &dtml-sequence-key;=&dtml-sequence-item;, , *&dtml-varargs; , **&dtml-kwargs;):

    zope2.13-2.13.21/source/Zope2/src/HelpSys/dtml/results.dtml0000644000175000017500000000065212214017421022134 0ustar arnauarnau

    Help topics matching &dtml-SearchableText;:

    &dtml-title_or_id;
    No matches.

    zope2.13-2.13.21/source/Zope2/src/Signals/0000755000175000017500000000000012214017422016620 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Signals/Signals.py0000644000175000017500000001011012214017422020563 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Zope signal handlers for clean shutdown, restart and log rotation. """ import logging import os import sys import Lifetime from .threads import dump_threads logger = logging.getLogger("Z2") if os.name == 'nt': try: from WinSignalHandler import SignalHandler except ImportError: msg = ('Can not install signal handlers. Please install ' '(or upgrade) your pywin32 installation ' '(https://sf.net/projects/pywin32)') logger.warning(msg) SignalHandler = None else: from SignalHandler import SignalHandler def shutdownFastHandler(): """Shutdown cleanly on SIGTERM. This is registered first, so it should be called after all other handlers.""" logger.info("Shutting down fast") Lifetime.shutdown(0,fast=1) def shutdownHandler(): """Shutdown cleanly on SIGINT. This is registered first, so it should be called after all other handlers.""" logger.info("Shutting down") sys.exit(0) def restartHandler(): """Restart cleanly on SIGHUP. This is registered first, so it should be called after all other SIGHUP handlers.""" logger.info("Restarting") Lifetime.shutdown(1) def showStacks(): """Dump a stracktrace of all threads on the console.""" print dump_threads() sys.stdout.flush() class LogfileReopenHandler: """Reopen log files on SIGUSR2. This is registered first, so it should be called after all other SIGUSR2 handlers. """ def __init__(self, loggers): self.loggers = [log for log in loggers if log is not None] def __call__(self): for log in self.loggers: log.reopen() logger.info("Log files reopened successfully") # On Windows, a 'reopen' is useless - the file can not be renamed # while open, so we perform a trivial 'rotate'. class LogfileRotateHandler: """Rotate log files on SIGUSR2. Only called on Windows. This is registered first, so it should be called after all other SIGUSR2 handlers.""" def __init__(self, loggers): self.loggers = [log for log in loggers if log is not None] def __call__(self): logger.debug("Log files rotation starting...") for log in self.loggers: for f in log.handler_factories: handler = f() if hasattr(handler, 'rotate') and callable(handler.rotate): handler.rotate() logger.info("Log files rotation complete") def registerZopeSignals(loggers): from signal import SIGTERM, SIGINT try: from signal import SIGHUP, SIGUSR1, SIGUSR2 except ImportError: # Windows doesn't have these (but also doesn't care what the exact # numbers are) SIGHUP = 1 SIGUSR1 = 10 SIGUSR2 = 12 if not SignalHandler: return mod_wsgi = True try: from mod_wsgi import version except ImportError: mod_wsgi = False if not mod_wsgi: SignalHandler.registerHandler(SIGTERM, shutdownFastHandler) SignalHandler.registerHandler(SIGINT, shutdownHandler) if os.name != 'nt': if not mod_wsgi: SignalHandler.registerHandler(SIGHUP, restartHandler) SignalHandler.registerHandler(SIGUSR1, showStacks) SignalHandler.registerHandler(SIGUSR2, LogfileReopenHandler(loggers)) else: # no restart handler on windows. # Log files get 'rotated', not 'reopened' SignalHandler.registerHandler(SIGUSR2, LogfileRotateHandler(loggers)) zope2.13-2.13.21/source/Zope2/src/Signals/WinSignalHandler.py0000644000175000017500000002723612214017422022375 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Signal handling dispatcher for Windows.""" # This code "simulates" Unix signals via Windows events. When a signal is # registered, we simply create a global named event for that signal. The # signal can be set by any user with the correct permission opening and # setting the event. # # One event is used per signal, and the event name is based on both the # Zope process ID and the signal number. For example, assuming a process # ID of 123, a SIGINT handler would create an event called "Zope-123-2" # (as signal.SIGINT==2). The logfile reopen handler uses an event named # "Zope-123-12" (as the logfile handler uses SIGUSR2, which == 12) # The following program will send such an event: # import sys, win32event # hev = win32event.OpenEvent(win32event.EVENT_MODIFY_STATE, 0, sys.argv[1]) # win32event.SetEvent(hev) # A good way to get the PID is to read the var/*.pid file for the app. # This code is only the generic signal mechanism for Windows. # The signal handlers are still external, just like for other platforms. # NOTE: There is one huge semantic difference between these "signals" # and signals on Unix. On Windows, the signals are delivered asynchronously # to a thread inside this module. This thread calls the event handler # directly - there is no magic to switch the call back to the main thread. # If this is a problem (not currently, but likely later), one option may be # to add yet another asyncore handler - the thread in this module could # then "post" the request to the main thread via this asyncore handler. import sys, os import signal import threading import asyncore import atexit import Lifetime # As at pywin32-204, we must ensure pywintypes is the first win32 module # imported in our process, otherwise we can end up with 2 pywintypesxx.dll # instances in our process resulting in: # TypeError: The object is not a PySECURITY_ATTRIBUTES object import pywintypes # SetConsoleCtrlHandler not in early pywin32 versions - Signals.py will # catch the import error. from win32api import SetConsoleCtrlHandler import win32con import win32event import ntsecuritycon import logging logger=logging.getLogger("WinSignalHandler") # We simulate signals via win32 named events. This is the event name # prefix we use - the "signal number" is appended to this name. event_name_prefix = "Zope-%d-" % os.getpid() # For Windows 2000 and later, we prefix "Global\" to the name, so that # it works correctly in a Terminal Services environment. winver = sys.getwindowsversion() # sys.getwindowsversion() -> major, minor, build, platform_id, ver_string # for platform_id, 2==VER_PLATFORM_WIN32_NT if winver[0] >= 5 and winver[3] == 2: event_name_prefix = "Global\\" + event_name_prefix def createEventSecurityObject(): # Create a security object giving World read/write access, # but only "Owner" modify access. sa = pywintypes.SECURITY_ATTRIBUTES() sidEveryone = pywintypes.SID() sidEveryone.Initialize(ntsecuritycon.SECURITY_WORLD_SID_AUTHORITY,1) sidEveryone.SetSubAuthority(0, ntsecuritycon.SECURITY_WORLD_RID) sidCreator = pywintypes.SID() sidCreator.Initialize(ntsecuritycon.SECURITY_CREATOR_SID_AUTHORITY,1) sidCreator.SetSubAuthority(0, ntsecuritycon.SECURITY_CREATOR_OWNER_RID) acl = pywintypes.ACL() acl.AddAccessAllowedAce(win32event.EVENT_MODIFY_STATE, sidEveryone) acl.AddAccessAllowedAce(ntsecuritycon.FILE_ALL_ACCESS, sidCreator) sa.SetSecurityDescriptorDacl(1, acl, 0) return sa def wakeSelect(): """Interrupt a sleeping asyncore 'select' call""" # What is the right thing to do here? # asyncore.close_all() works, but I fear that would # prevent the poll based graceful cleanup code from working. # This seems to work :) for fd, obj in asyncore.socket_map.items(): if hasattr(obj, "pull_trigger"): obj.pull_trigger() class SignalHandler: def __init__(self): self.registry = {} self.event_handles = {} self.admin_event_handle = win32event.CreateEvent(None, 0, 0, None) self.shutdown_requested = False # Register a "console control handler" for Ctrl+C/Break notification. SetConsoleCtrlHandler(consoleCtrlHandler) # Start the thread that is watching for events. thread = threading.Thread(target=self.signalCheckerThread) # If something goes terribly wrong, don't wait for this thread! thread.setDaemon(True) thread.start() self.signal_thread = thread def shutdown(self): # Shutdown our signal watcher thread. logger.debug("signal handler shutdown starting.") self.shutdown_requested = 1 win32event.SetEvent(self.admin_event_handle) # sadly, this can deadlock at shutdown when Ctrl+C is used # (although not then the event is used to trigger shutdown) # at least in build 204. Further updates as they come to hand... # Remove the Windows control handler #SetConsoleCtrlHandler(consoleCtrlHandler, 0) self.signal_thread.join(5) # should never block for long! self.registry = None self.event_handles = None self.admin_event_handle = None logger.debug("signal handler shutdown complete.") def consoleCtrlHandler(self, ctrlType): """Called by Windows on a new thread whenever a console control event is raised.""" logger.debug("Windows control event %d" % ctrlType) sig = None if ctrlType == win32con.CTRL_C_EVENT: # user pressed Ctrl+C or someone did GenerateConsoleCtrlEvent sig = signal.SIGINT elif ctrlType == win32con.CTRL_BREAK_EVENT: sig = signal.SIGTERM elif ctrlType == win32con.CTRL_CLOSE_EVENT: # Console is about to die. # CTRL_CLOSE_EVENT gives us 5 seconds before displaying # the "End process" dialog - so treat as 'fast' sig = signal.SIGTERM elif ctrlType in (win32con.CTRL_LOGOFF_EVENT, win32con.CTRL_SHUTDOWN_EVENT): # MSDN says: # "Note that this signal is received only by services. # Interactive applications are terminated at logoff, so # they are not present when the system sends this signal." # We can therefore ignore it (our service framework # manages shutdown in this case) pass else: logger.info("Unexpected windows control event %d" % ctrlType) # Call the signal handler - we could also do it asynchronously # by setting the relevant event, but we need it synchronous so # that we don't wake the select loop until after the shutdown # flags have been set. result = 0 if sig is not None and self.registry.has_key(sig): self.signalHandler(sig, None) result = 1 # don't call other handlers. return result def signalCheckerThread(self): while not self.shutdown_requested: handles = [self.admin_event_handle] signums = [None] for signum, handle in self.event_handles.items(): signums.append(signum) handles.append(handle) rc = win32event.WaitForMultipleObjects(handles, False, win32event.INFINITE) logger.debug("signalCheckerThread awake with %s" % rc) signum = signums[rc - win32event.WAIT_OBJECT_0] if signum is None: # Admin event - either shutdown, or new event object created. pass else: logger.debug("signalCheckerThread calling %s" % signum) self.signalHandler(signum, None) logger.debug("signalCheckerThread back") logger.debug("signalCheckerThread stopped") def registerHandler(self, signum, handler): """Register a handler function that will be called when the process recieves the signal signum. The signum argument must be a signal constant such as SIGTERM. The handler argument must be a function or method that takes no arguments.""" items = self.registry.get(signum) if items is None: items = self.registry[signum] = [] # Create an event for this signal. event_name = event_name_prefix + str(signum) sa = createEventSecurityObject() hevent = win32event.CreateEvent(sa, 0, 0, event_name) self.event_handles[signum] = hevent # Let the worker thread know there is a new handle. win32event.SetEvent(self.admin_event_handle) signame = get_signal_name(signum) logger.debug("Installed sighandler for %s (%s)" % (signame, event_name)) items.insert(0, handler) def getRegisteredSignals(self): """Return a list of the signals that have handlers registered. This is used to pass the signals through to the ZDaemon code.""" return self.registry.keys() def signalHandler(self, signum, frame): """Meta signal handler that dispatches to registered handlers.""" signame = get_signal_name(signum) logger.info("Caught signal %s" % signame) for handler in self.registry.get(signum, []): # Never let a bad handler prevent the standard signal # handlers from running. try: handler() except SystemExit, rc: # On Unix, signals are delivered to the main thread, so a # SystemExit does the right thing. On Windows, we are on # our own thread, so throwing SystemExit there isn't a great # idea. Just shutdown the main loop. logger.debug("Trapped SystemExit(%s) - doing Lifetime shutdown" % (rc,)) Lifetime.shutdown(rc) except: logger.exception("A handler for %s failed!'" % signame) wakeSelect() # trigger a walk around the Lifetime loop. _signals = None def get_signal_name(n): """Return the symbolic name for signal n. Returns 'signal n' if there is no SIG name bound to n in the signal module. """ global _signals if _signals is None: _signals = {} for k, v in signal.__dict__.items(): startswith = getattr(k, 'startswith', None) if startswith is None: continue if startswith('SIG') and not startswith('SIG_'): _signals[v] = k # extra ones that aren't (weren't?) in Windows. for name, val in ("SIGHUP", 1), ("SIGUSR1", 10), ("SIGUSR2", 12): if not _signals.has_key(name): _signals[val] = name return _signals.get(n, 'signal %d' % n) # The win32 ConsoleCtrlHandler def consoleCtrlHandler(ctrlType): return SignalHandler.consoleCtrlHandler(ctrlType) # The SignalHandler is actually a singleton. SignalHandler = SignalHandler() # Need to be careful at shutdown - the 'signal watcher' thread which triggers # the shutdown may still be running when the main thread terminates and # Python starts cleaning up. atexit.register(SignalHandler.shutdown) zope2.13-2.13.21/source/Zope2/src/Signals/__init__.py0000644000175000017500000000004412214017422020727 0ustar arnauarnau""" Signals package __init__.py """ zope2.13-2.13.21/source/Zope2/src/Signals/threads.py0000644000175000017500000000262612214017422020632 0ustar arnauarnauimport sys import thread import traceback import time from cStringIO import StringIO def dump_threads(): """Dump running threads. Returns a string with the tracebacks.""" frames = sys._current_frames() this_thread_id = thread.get_ident() now = time.strftime("%Y-%m-%d %H:%M:%S") res = ["Threads traceback dump at %s\n" % now] for thread_id, frame in frames.iteritems(): if thread_id == this_thread_id: continue # Find request in frame reqinfo = '' f = frame while f is not None: co = f.f_code if co.co_name == 'publish': if co.co_filename.endswith('/ZPublisher/Publish.py'): request = f.f_locals.get('request') if request is not None: reqinfo += (request.get('REQUEST_METHOD', '') + ' ' + request.get('PATH_INFO', '')) qs = request.get('QUERY_STRING') if qs: reqinfo += '?'+qs break f = f.f_back if reqinfo: reqinfo = " (%s)" % reqinfo output = StringIO() traceback.print_stack(frame, file=output) res.append("Thread %s%s:\n%s" % (thread_id, reqinfo, output.getvalue())) frames = None res.append("End of dump") return '\n'.join(res) zope2.13-2.13.21/source/Zope2/src/Signals/SignalHandler.py0000644000175000017500000000576012214017422021715 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Signal handling dispatcher.""" import sys, os import signal from logging import getLogger LOG = getLogger('SignalHandler') class SignalHandler: def __init__(self): self.registry = {} def registerHandler(self, signum, handler): """Register a handler function that will be called when the process recieves the signal signum. The signum argument must be a signal constant such as SIGTERM. The handler argument must be a function or method that takes no arguments. Note that handlers will not be called on non-posix platforms.""" if os.name != 'posix': return items = self.registry.get(signum) if items is None: items = self.registry[signum] = [] signal.signal(signum, self.signalHandler) signame = get_signal_name(signum) LOG.debug("Installed sighandler for %s" % signame) items.insert(0, handler) def getRegisteredSignals(self): """Return a list of the signals that have handlers registered. This is used to pass the signals through to the ZDaemon code.""" return self.registry.keys() def signalHandler(self, signum, frame): """Meta signal handler that dispatches to registered handlers.""" signame = get_signal_name(signum) LOG.info("Caught signal %s" % signame) for handler in self.registry.get(signum, []): # Never let a bad handler prevent the standard signal # handlers from running. try: handler() except SystemExit: # if we trap SystemExit, we can't restart raise except: LOG.warn('A handler for %s failed!' % signame, exc_info=sys.exc_info()) _signals = None def get_signal_name(n): """Return the symbolic name for signal n. Returns 'unknown' if there is no SIG name bound to n in the signal module. """ global _signals if _signals is None: _signals = {} for k, v in signal.__dict__.items(): startswith = getattr(k, 'startswith', None) if startswith is None: continue if startswith('SIG') and not startswith('SIG_'): _signals[v] = k return _signals.get(n, 'signal %d' % n) # The SignalHandler is actually a singleton. SignalHandler = SignalHandler() zope2.13-2.13.21/source/Zope2/src/Products/0000755000175000017500000000000012214017422017023 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/0000755000175000017500000000000012214017422017714 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/fivedirectives.py0000644000175000017500000000455212214017421023306 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Five ZCML directive schemas """ from zope.interface import Interface from zope.browserresource.metadirectives import IBasicResourceInformation from zope.configuration.fields import GlobalObject, Tokens from zope.schema import TextLine # Deprecated, the class directive from zope.security allows the same class IImplementsDirective(Interface): """State that a class implements something. """ class_ = GlobalObject( title=u"Class", required=True ) interface = Tokens( title=u"One or more interfaces", required=True, value_type=GlobalObject() ) class ISizableDirective(Interface): """Attach sizable adapters to classes. """ class_ = GlobalObject( title=u"Class", required=True ) class IPagesFromDirectoryDirective(IBasicResourceInformation): """Register each file in a skin directory as a page resource """ for_ = GlobalObject( title=u"The interface this view is for.", required=False ) module = GlobalObject( title=u"Module", required=True ) directory = TextLine( title=u"Directory", description=u"The directory containing the resource data.", required=True ) from zope.deferredimport import deprecated deprecated("Please import from OFS.metadirectives", IRegisterPackageDirective = 'OFS.metadirectives:IRegisterPackageDirective', IRegisterClassDirective = 'OFS.metadirectives:IRegisterClassDirective', IDeprecatedManageAddDeleteDirective = \ 'OFS.metadirectives:IDeprecatedManageAddDeleteDirective', ) deprecated("Please import from zope.configuration.xmlconfig", IInclude = 'zope.configuration.xmlconfig:IInclude', ) zope2.13-2.13.21/source/Zope2/src/Products/Five/schema.py0000644000175000017500000000026112214017421021524 0ustar arnauarnau# BBB from zope.deferredimport import deprecated deprecated("Please import from Zope2.App.schema", Zope2VocabularyRegistry = 'Zope2.App.schema:Zope2VocabularyRegistry', ) zope2.13-2.13.21/source/Zope2/src/Products/Five/testbrowser.py0000644000175000017500000000056412214017421022655 0ustar arnauarnau# BBB from zope.deferredimport import deprecated deprecated("Please import from Testing.testbrowser", PublisherConnection = 'Testing.testbrowser:PublisherConnection', PublisherHTTPHandler = 'Testing.testbrowser:PublisherHTTPHandler', PublisherMechanizeBrowser = 'Testing.testbrowser:PublisherMechanizeBrowser', Browser = 'Testing.testbrowser:Browser', ) zope2.13-2.13.21/source/Zope2/src/Products/Five/configure.zcml0000644000175000017500000000064012214017421022563 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/security.py0000644000175000017500000000276412214017421022145 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Five security handling """ from zope.deferredimport import deprecated deprecated("Please import InitializeClass from App.class_init", initializeClass = 'App.class_init:InitializeClass', ) deprecated("Please import from AccessControl.security", CheckerPublicId = 'AccessControl.security:CheckerPublicId', CheckerPrivateId = 'AccessControl.security:CheckerPrivateId', getSecurityInfo = 'AccessControl.security:getSecurityInfo', clearSecurityInfo = 'AccessControl.security:clearSecurityInfo', checkPermission = 'AccessControl.security:checkPermission', FiveSecurityPolicy = 'AccessControl.security:SecurityPolicy', newInteraction = 'AccessControl.security:newInteraction', _getSecurity = 'AccessControl.security:_getSecurity', protectName = 'AccessControl.security:protectName', protectClass = 'AccessControl.security:protectClass', ) zope2.13-2.13.21/source/Zope2/src/Products/Five/TODO.txt0000644000175000017500000000112612214017421021221 0ustar arnauarnau==== TODO ==== - container events - get rid of deprecation warnings - make rendering of resource urls support sites? - HTTP/WebDAV: support dispatching of all HTTP/WebDAV methods to HTTP views. If lookup fails, fall back to methods on the object (Zope 2 style). Security is implied by HTTP views. - FTP: allow manage_FTPstat, manage_FTPget, manage_FTPlist to dispatch to filerepresentation adapters. Make sure to handle security correctly. - Grant security stuff through ZCML ()? - top-level , directives as a ZCML spelling of allowModule, allowClass zope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/0000755000175000017500000000000012214017422021727 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/marker.py0000644000175000017500000001051612214017422023565 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Marker interfaces adapter. Allows for arbitrary application of marker interfaces to objects. """ from sets import Set from zope.interface import implements, implementedBy, providedBy from zope.interface import directlyProvides, directlyProvidedBy from zope.interface.interfaces import IInterface from zope.component.interface import getInterface, interfaceToName from zope.component.interface import searchInterface from interfaces import IMarkerInterfaces def interfaceStringCheck(f): def wrapper(ob, interface): if isinstance(interface, str): interface = getInterface(ob, interface) return f(ob, interface) return wrapper def mark(ob, interface): directlyProvides(ob, directlyProvidedBy(ob), interface) def erase(ob, interface): directlyProvides(ob, directlyProvidedBy(ob)-interface) mark = interfaceStringCheck(mark) erase = interfaceStringCheck(erase) class MarkerInterfacesAdapter(object): implements(IMarkerInterfaces) mark = staticmethod(mark) erase = staticmethod(erase) def __init__(self, context): self.context = context def dottedToInterfaces(self, seq): return [getInterface(self.context, dotted) for dotted in seq] def getDirectlyProvided(self): return directlyProvidedBy(self.context) def getDirectlyProvidedNames(self): return self._getInterfaceNames(self.getDirectlyProvided()) def getAvailableInterfaces(self): results = [] todo = list(providedBy(self.context)) done = [] while todo: interface = todo.pop() done.append(interface) for base in interface.__bases__: if base not in todo and base not in done: todo.append(base) markers = self._getDirectMarkersOf(interface) for interface in markers: if (interface not in results and not interface.providedBy(self.context)): results.append(interface) todo += markers return tuple(results) def getAvailableInterfaceNames(self): names = self._getInterfaceNames(self.getAvailableInterfaces()) names.sort() return names def getInterfaces(self): return tuple(implementedBy(self.context.__class__)) def getInterfaceNames(self): return self._getInterfaceNames(self.getInterfaces()) def getProvided(self): return providedBy(self.context) def getProvidedNames(self): return self._getInterfaceNames(self.getProvided()) def update(self, add=(), remove=()): """Currently update adds and then removes, rendering duplicate null. """ marker_ifaces = self.getAvailableInterfaces() if len(add): [mark(self.context, interface) for interface in Set(marker_ifaces) & Set(add)] direct_ifaces = self.getDirectlyProvided() if len(remove): [erase(self.context, interface) for interface in Set(direct_ifaces) & Set(remove)] def _getInterfaceNames(self, interfaces): return [interfaceToName(self, iface) for iface in interfaces] def _getDirectMarkersOf(self, base): """Get empty interfaces directly inheriting from the given one. """ results = [] interfaces = searchInterface(None, base=base) for interface in interfaces: # There are things registered with the interface service # that are not interfaces. Yay! if not IInterface.providedBy(interface): continue if base in interface.__bases__ and not interface.names(): results.append(interface) results.sort() return tuple(results) zope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/configure.zcml0000644000175000017500000000041712214017422024601 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/interfaces.py0000644000175000017500000000414112214017422024424 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Utility Interface Definitions. """ from zope.interface import Interface class IReadInterface(Interface): def getDirectlyProvided(): """List the interfaces directly implemented by the object. """ def getDirectlyProvidedNames(): """List the names of interfaces directly implemented by the object. """ def getAvailableInterfaces(): """List the marker interfaces available for the object. """ def getAvailableInterfaceNames(): """List the names of marker interfaces available for the object. """ def getInterfaces(): """List interfaces provided by the class of the object. """ def getInterfaceNames(): """List the names of interfaces provided by the class of the object. """ def getProvided(): """List interfaces provided by the object. """ def getProvidedNames(): """List the names of interfaces provided by the object. """ class IWriteInterface(Interface): def update(add=(), remove=()): """Update directly provided interfaces of the object. """ def mark(interface): """Add interface to interfaces the object directly provides. """ def erase(interface): """Remove interfaces from interfaces the object directly provides. """ class IMarkerInterfaces(IReadInterface, IWriteInterface): """Provides methods for inspecting and assigning marker interfaces. """ zope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/__init__.py0000644000175000017500000000004012214017422024032 0ustar arnauarnau# make this directory a package zope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/browser/0000755000175000017500000000000012214017422023412 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/browser/marker.py0000644000175000017500000000431012214017422025243 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Marker interfaces adapter views. """ from Products.Five.utilities.interfaces import IMarkerInterfaces class EditView: """Marker interface edit view. """ def __init__(self, context, request): self.context = context self.request = request self.adapted = IMarkerInterfaces(context) self.context_url = self.context.absolute_url() def __call__(self, SAVE=None, add=(), remove=()): if SAVE: self.update(add, remove) self.request.response.redirect(self.request.ACTUAL_URL) return '' return self.index() def _getLinkToInterfaceDetailsView(self, interfaceName): return (self.context_url + '/views-details.html?iface=%s&type=zope.publisher.interfaces.browser.IBrowserRequest' % interfaceName) def _getNameLinkDicts(self, interfaceNames): return [dict(name=name, link=self._getLinkToInterfaceDetailsView(name)) for name in interfaceNames] def getAvailableInterfaceNames(self): return self._getNameLinkDicts( self.adapted.getAvailableInterfaceNames()) def getDirectlyProvidedNames(self): return self._getNameLinkDicts(self.adapted.getDirectlyProvidedNames()) def getInterfaceNames(self): return self._getNameLinkDicts(self.adapted.getInterfaceNames()) def update(self, add, remove): # this could return errors add = self.adapted.dottedToInterfaces(add) remove = self.adapted.dottedToInterfaces(remove) self.adapted.update(add=add, remove=remove) zope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/browser/configure.zcml0000644000175000017500000000103612214017422026262 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/browser/manage_interfaces.pt0000644000175000017500000000052112214017422027410 0ustar arnauarnau

    PAGE HEADER

    TABS

    PAGE FOOTER

    zope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/browser/__init__.py0000644000175000017500000000004012214017422025515 0ustar arnauarnau# make this directory a package zope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/browser/tests/0000755000175000017500000000000012214017422024554 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/browser/tests/__init__.py0000644000175000017500000000004012214017422026657 0ustar arnauarnau# make this directory a package zope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/browser/tests/test_marker.py0000644000175000017500000000541312214017422027451 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unit tests for marker interface views. """ def test_editview(): """ Set everything up: >>> from zope.component.testing import setUp, tearDown >>> setUp() >>> import AccessControl >>> import Products.Five >>> import Products.Five.utilities >>> from Zope2.App import zcml >>> zcml.load_config('meta.zcml', Products.Five) >>> zcml.load_config('permissions.zcml', AccessControl) >>> zcml.load_config('configure.zcml', Products.Five.utilities) >>> from Products.Five.utilities.browser.marker import EditView >>> from Products.Five.tests.testing.simplecontent import SimpleContent >>> obj = SimpleContent('foo', 'Foo').__of__(self.folder) Create an EditView: >>> view = EditView(obj, {}) >>> view.context.aq_inner is obj True >>> view.request {} >>> view.getAvailableInterfaceNames() [] >>> view.getDirectlyProvidedNames() [] >>> view.getInterfaceNames() [...ISimpleContent...] Try to add a marker interface that doesn't exist: >>> view.update(('__main__.IFooMarker',), ()) Traceback (most recent call last): ... ComponentLookupError... Now create the marker interface: >>> from Products.Five.tests.testing.simplecontent import ISimpleContent >>> class IFooMarker(ISimpleContent): pass >>> from zope.component.interface import provideInterface >>> provideInterface('', IFooMarker) >>> view.getAvailableInterfaceNames() [...IFooMarker...] >>> view.getDirectlyProvidedNames() [] And try again to add it to the object: >>> view.update(('__main__.IFooMarker',), ()) >>> view.getAvailableInterfaceNames() [] >>> view.getDirectlyProvidedNames() [...IFooMarker...] And remove it again: >>> view.update((), ('__main__.IFooMarker',)) >>> view.getAvailableInterfaceNames() [...IFooMarker...] >>> view.getDirectlyProvidedNames() [] Finally tear down: >>> tearDown() """ def test_suite(): from Testing.ZopeTestCase import ZopeDocTestSuite return ZopeDocTestSuite() zope2.13-2.13.21/source/Zope2/src/Products/Five/utilities/browser/edit_markers.pt0000644000175000017500000000441212214017422026431 0ustar arnauarnau

    Assign Marker Interfaces

    Change the behavior of this object by adding or removing marker interfaces. You can choose one or more interfaces to be added to the list of provided interfaces for this object.

    A marker interface is used to identify an instance of a piece of content. When in conjunction with Five, this allows you to enable and disable views based on marker interfaces for example.

    Provided Interfaces      

    Available Marker Interfaces
    zope2.13-2.13.21/source/Zope2/src/Products/Five/COPYING.txt0000644000175000017500000000105012214017421021560 0ustar arnauarnauFive is distributed under the provisions of the Zope Public License (ZPL) v2.1. See doc/ZopePublicLicense.txt for the license text. Copyright (C) 2005 Five Contributors. See CREDITS.txt for a list of Five contributors. Five contains source code derived from: - Zope, copyright (C) 2001-2005 by Zope Corporation. - metaclass.py is derived from PEAK, copyright (C) 1996-2004 by Phillip J. Eby and Tyler C. Sarna. PEAK may be used under the same terms as Zope. - TrustedExecutables. Dieter Mauer kindly allow licensing this under the ZPL 2.1. zope2.13-2.13.21/source/Zope2/src/Products/Five/skin/0000755000175000017500000000000012214017421020657 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/skin/configure.zcml0000644000175000017500000000073112214017421023530 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/skin/__init__.py0000644000175000017500000000001612214017421022765 0ustar arnauarnau# import this zope2.13-2.13.21/source/Zope2/src/Products/Five/skin/five_template.pt0000644000175000017500000000025512214017421024052 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/skin/standardmacros.py0000644000175000017500000000321512214017421024237 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Mimick the zope.app.basicskin skinning system. """ import zope.interface import zope.component from Products.Five.browser import BrowserView # this is a verbatim copy of zope.app.basicskin except that it doesn't # derive from ``object`` class Macros: zope.interface.implements(zope.interface.common.mapping.IItemMapping) macro_pages = () aliases = { 'view': 'page', 'dialog': 'page', 'addingdialog': 'page' } def __getitem__(self, key): key = self.aliases.get(key, key) context = self.context request = self.request for name in self.macro_pages: page = zope.component.getMultiAdapter((context, request), name=name) try: v = page[key] except KeyError: pass else: return v raise KeyError, key class StandardMacros(BrowserView, Macros): macro_pages = ('five_template', 'widget_macros', 'form_macros',) zope2.13-2.13.21/source/Zope2/src/Products/Five/skin/tests/0000755000175000017500000000000012214017422022022 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/skin/tests/demomacros.py0000644000175000017500000000156312214017422024532 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Demo StandardMacros """ from Products.Five import StandardMacros as BaseMacros class StandardMacros(BaseMacros): macro_pages = ('bird_macros', 'dog_macros') aliases = {'flying':'birdmacro', 'walking':'dogmacro'} zope2.13-2.13.21/source/Zope2/src/Products/Five/skin/tests/configure.zcml0000644000175000017500000000153512214017422024676 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/skin/tests/dog.pt0000644000175000017500000000021012214017422023131 0ustar arnauarnaudog macroBreed: zope2.13-2.13.21/source/Zope2/src/Products/Five/skin/tests/__init__.py0000644000175000017500000000001612214017422024130 0ustar arnauarnau# import this zope2.13-2.13.21/source/Zope2/src/Products/Five/skin/tests/bird.pt0000644000175000017500000000026012214017422023305 0ustar arnauarnaubird macroColor: zope2.13-2.13.21/source/Zope2/src/Products/Five/skin/tests/test_standardmacros.py0000644000175000017500000000470012214017422026441 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test standard macros """ def test_standard_macros(): """Test standard macros >>> uf = self.folder.acl_users >>> _ignored = uf._doAddUser('manager', 'r00t', ['Manager'], []) >>> self.login('manager') >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> import Products.Five.skin.tests >>> from Zope2.App import zcml >>> zcml.load_config('configure.zcml', package=Products.Five) >>> zcml.load_config('configure.zcml', package=Products.Five.skin.tests) Test macro access through our flavour of StandardMacros. First, when looking up a non-existing macro, we get a KeyError: >>> view = self.folder.unrestrictedTraverse('testoid/@@fivetest_macros') >>> view['non-existing-macro'] Traceback (most recent call last): ... KeyError: 'non-existing-macro' Existing macros are accessible through index notation: >>> for macroname in ('birdmacro', 'dogmacro', 'flying', 'walking'): ... view[macroname] is not None True True True True Aliases are resolve correctly: >>> view['flying'] is view['birdmacro'] True >>> view['walking'] is view['dogmacro'] True One can also access the macros through regular traversal: >>> base = 'testoid/@@fivetest_macros/%s' >>> for macro in ('birdmacro', 'dogmacro', 'flying', 'walking'): ... view = self.folder.unrestrictedTraverse(base % macro) ... view is not None True True True True Clean up: >>> from zope.component.testing import tearDown >>> tearDown() """ def test_suite(): from Testing.ZopeTestCase import ZopeDocTestSuite return ZopeDocTestSuite() zope2.13-2.13.21/source/Zope2/src/Products/Five/component/0000755000175000017500000000000012214017421021715 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/component/configure.zcml0000644000175000017500000000126712214017421024573 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/component/browser.py0000644000175000017500000000434612214017421023761 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Component browser views """ from Products.Five.browser import BrowserView from Products.Five.component import enableSite, disableSite from Products.Five.component.interfaces import IObjectManagerSite from zope.component.globalregistry import base from zope.component.persistentregistry import PersistentComponents from zope.site.hooks import setSite class ObjectManagerSiteView(BrowserView): def update(self): form = self.request.form if form.has_key('MAKESITE'): self.makeSite() elif form.has_key('UNMAKESITE'): self.unmakeSite() def isSite(self): return IObjectManagerSite.providedBy(self.context) def makeSite(self): if IObjectManagerSite.providedBy(self.context): raise ValueError('This is already a site') enableSite(self.context, iface=IObjectManagerSite) #TODO in the future we'll have to walk up to other site # managers and put them in the bases components = PersistentComponents() components.__bases__ = (base,) self.context.setSiteManager(components) def unmakeSite(self): if not self.isSite(): raise ValueError('This is not a site') disableSite(self.context) # disableLocalSiteHook circumcised our context so that it's # not an ISite anymore. That can mean that certain things for # it can't be found anymore. So, for the rest of this request # (which will be over in about 20 CPU cycles), already clear # the local site from the thread local. setSite() self.context.setSiteManager(None) zope2.13-2.13.21/source/Zope2/src/Products/Five/component/interfaces.py0000644000175000017500000000151612214017421024415 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Component interfaces """ from zope.location.interfaces import ISite from OFS.interfaces import IObjectManager class IObjectManagerSite(IObjectManager, ISite): """Object manager that is also a site.""" zope2.13-2.13.21/source/Zope2/src/Products/Five/component/components.pt0000644000175000017500000000135312214017421024451 0ustar arnauarnau

    Sites support local configuration of components.

    zope2.13-2.13.21/source/Zope2/src/Products/Five/component/__init__.py0000644000175000017500000000640112214017421024027 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Five local component look-up support """ import zope.interface import zope.component import zope.event from zope.component.interfaces import IComponentLookup from zope.traversing.interfaces import BeforeTraverseEvent import ExtensionClass from Acquisition import aq_base, aq_inner, aq_parent from Products.SiteAccess.AccessRule import AccessRule from ZPublisher.BeforeTraverse import registerBeforeTraverse from ZPublisher.BeforeTraverse import unregisterBeforeTraverse # BBB try: from zope.component.interfaces import IPossibleSite from zope.component.interfaces import ISite except ImportError: from zope.location.interfaces import IPossibleSite from zope.location.interfaces import ISite # Hook up custom component architecture calls from zope.site.hooks import setHooks setHooks() def findSite(obj, iface=ISite): """Find a site by walking up the object hierarchy, supporting both the ``ILocation`` API and Zope 2 Acquisition.""" while obj is not None and not iface.providedBy(obj): obj = aq_parent(aq_inner(obj)) return obj @zope.component.adapter(zope.interface.Interface) @zope.interface.implementer(IComponentLookup) def siteManagerAdapter(ob): """Look-up a site manager/component registry for local component lookup. This is registered in place of the one in zope.site so that we lookup using acquisition in addition to the ``ILocation`` API. """ site = findSite(ob) if site is None: return zope.component.getGlobalSiteManager() return site.getSiteManager() class LocalSiteHook(ExtensionClass.Base): def __call__(self, container, request): zope.event.notify(BeforeTraverseEvent(container, request)) HOOK_NAME = '__local_site_hook__' def enableSite(obj, iface=ISite): """Install __before_traverse__ hook for Local Site """ # We want the original object, not stuff in between, and no acquisition obj = aq_base(obj) if not IPossibleSite.providedBy(obj): raise TypeError, 'Must provide IPossibleSite' hook = AccessRule(HOOK_NAME) registerBeforeTraverse(obj, hook, HOOK_NAME, 1) if not hasattr(obj, HOOK_NAME): setattr(obj, HOOK_NAME, LocalSiteHook()) zope.interface.alsoProvides(obj, iface) def disableSite(obj, iface=ISite): """Remove __before_traverse__ hook for Local Site """ # We want the original object, not stuff in between, and no acquisition obj = aq_base(obj) if not iface.providedBy(obj): raise TypeError('Object must be a site.') unregisterBeforeTraverse(obj, HOOK_NAME) if hasattr(obj, HOOK_NAME): delattr(obj, HOOK_NAME) zope.interface.noLongerProvides(obj, iface) zope2.13-2.13.21/source/Zope2/src/Products/Five/component/tests.py0000644000175000017500000000177012214017421023436 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Component tests """ import unittest from doctest import DocFileSuite from Testing.ZopeTestCase import FunctionalDocFileSuite def test_suite(): return unittest.TestSuite([ DocFileSuite('component.txt', package="Products.Five.component"), FunctionalDocFileSuite('makesite.txt', package="Products.Five.component"), ]) zope2.13-2.13.21/source/Zope2/src/Products/Five/component/component.txt0000644000175000017500000000400512214017421024457 0ustar arnauarnauLocal component look-up ======================= ``IComponentLookup`` adapter ----------------------------- In order to do context-based component look-up, the Component Architecture adapts the context to ``IComponentLookup``. zope.site's default adapter uses the ``ILocation`` API to walk up the object tree and find a site that way. Five provides its own adapter that also supports acquisitional parents. First, we register Five's adapter: >>> import zope.component >>> from Products.Five.component import siteManagerAdapter >>> zope.component.provideAdapter(siteManagerAdapter) Now we create a site object with a stub component registry: >>> from OFS.ObjectManager import ObjectManager >>> from zope.interface import alsoProvides >>> from zope.location.interfaces import ISite >>> components = object() >>> site = ObjectManager() >>> site.setSiteManager(components) >>> alsoProvides(site, ISite) When we adapt the site itself, we obviously get its component registry: >>> from zope.component.interfaces import IComponentLookup >>> IComponentLookup(site) is components True In case the adapted object has an acquisition context, acquisition is used to retrieve the closest site: >>> ob = ObjectManager() >>> ob2 = ObjectManager() >>> ob = ob.__of__(site) >>> ob2 = ob2.__of__(ob) >>> IComponentLookup(ob2) is components True The adapter also works using the ``ILocation`` API by inspecting the ``__parent__`` object instead of using the acquisition parent: >>> from zope.location import Location >>> ob = Location() >>> ob2 = Location() >>> ob.__parent__ = site >>> ob2.__parent__ = ob >>> IComponentLookup(ob2) is components True If it is unable to find a site and therefore a component registry, the global component registry is returned: >>> from zope.component import getGlobalSiteManager >>> orphan = ObjectManager() >>> IComponentLookup(orphan) is getGlobalSiteManager() True Clean up: --------- >>> from zope.component.testing import tearDown >>> tearDown() zope2.13-2.13.21/source/Zope2/src/Products/Five/component/makesite.txt0000644000175000017500000000621412214017421024263 0ustar arnauarnauTurning folders into sites ========================== Set up ------ Make this test available as a module so that stuff defined in here can be pickled properly: >>> from zope.testing.module import setUp, tearDown >>> setUp(test, name='Products.Five.component.makesite') Load all of Five's configuration (this is a functional test): >>> import Products.Five >>> from Zope2.App.zcml import load_config >>> load_config('configure.zcml', package=Products.Five) Enable local component lookup hooks: >>> from zope.site.hooks import setHooks >>> setHooks() Making a site ------------- >>> uf = app.acl_users >>> _ignored = uf._doAddUser('manager', 'r00t', ['Manager'], []) Create the test browser we'll be using: >>> from Testing.testbrowser import Browser >>> browser = Browser() >>> browser.addHeader('Authorization', 'Basic manager:r00t') Let's add a folder: >>> browser.open('http://localhost/manage_addProduct/OFSP/folderAdd') >>> browser.getControl(name='id').value = 'folder' >>> browser.getControl('Add').click() >>> browser.getLink('folder').click() We can turn it into a site by using the ``components.html`` view: >>> browser.open('http://localhost/folder/components.html') >>> browser.getControl('Make site').click() Now we ensure that the folder has been turned into a site: >>> from zope.location.interfaces import ISite >>> ISite.providedBy(app.folder) True We get the site manager for the folder and assert that it is indeed a component registry: >>> sm = app.folder.getSiteManager() >>> from zope.component.interfaces import IComponents >>> IComponents.providedBy(sm) True Now we register a simple view locally that we will look up via traversal: >>> from Products.Five.browser import BrowserView >>> class TestView(BrowserView): ... """A silly docstring""" ... __name__ = 'testview.html' ... def __call__(self): ... return "Hello World!" >>> from OFS.interfaces import IObjectManager >>> from zope.interface import Interface >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer >>> from zope.component import getGlobalSiteManager >>> sm.registerAdapter(TestView, (IObjectManager, IDefaultBrowserLayer), ... Interface, name='testview.html') Browse to our local view and check that it works: >>> browser.handleErrors = False >>> browser.open('http://localhost/folder/testview.html') >>> print browser.contents Hello World! Unmaking a site --------------- Folderish sites can be reverted back to standard folders also using the ``components.html`` view: >>> browser.open('http://localhost/folder/components.html') >>> browser.getControl('Unmake site').click() Ensure that its local components are no longer available: >>> ISite.providedBy(app.folder) False >>> browser.open('http://localhost/folder/@@testview.html') Traceback (most recent call last): ... NotFound: ... Clean up: --------- >>> from zope.testing.cleanup import cleanUp >>> cleanUp() >>> tearDown(test, name='Products.Five.component.makesite') zope2.13-2.13.21/source/Zope2/src/Products/Five/interfaces.py0000644000175000017500000000150612214017421022412 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Five interfaces """ from zope.deferredimport import deprecated deprecated("Please import from zope.browsermenu.interfaces", IMenuItemType = 'zope.browsermenu.interfaces:IMenuItemType', ) zope2.13-2.13.21/source/Zope2/src/Products/Five/metaconfigure.py0000644000175000017500000000007512214017421023117 0ustar arnauarnau# BBB from AccessControl.metaconfigure import ClassDirective zope2.13-2.13.21/source/Zope2/src/Products/Five/fiveconfigure.py0000644000175000017500000000702712214017421023126 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Five-specific directive handlers These directives are specific to Five and have no equivalents outside of it. """ import logging import os import glob import warnings from App.config import getConfiguration from zope.interface import classImplements from zope.component.interface import provideInterface from zope.configuration.exceptions import ConfigurationError from zope.publisher.interfaces.browser import IDefaultBrowserLayer from Products.Five.browser.metaconfigure import page logger = logging.getLogger('Products.Five') def implements(_context, class_, interface): warnings.warn('Using in %s is deprecated. Please use ' 'the ' ' ' 'directive instead.' % _context.info, DeprecationWarning, stacklevel=2) for interface in interface: _context.action( discriminator = None, callable = classImplements, args = (class_, interface) ) _context.action( discriminator = None, callable = provideInterface, args = (interface.__module__ + '.' + interface.getName(), interface) ) def pagesFromDirectory(_context, directory, module, for_=None, layer=IDefaultBrowserLayer, permission='zope.Public'): if isinstance(module, basestring): module = _context.resolve(module) _prefix = os.path.dirname(module.__file__) directory = os.path.join(_prefix, directory) if not os.path.isdir(directory): raise ConfigurationError( "Directory %s does not exist" % directory ) for fname in glob.glob(os.path.join(directory, '*.pt')): name = os.path.splitext(os.path.basename(fname))[0] page(_context, name=name, permission=permission, layer=layer, for_=for_, template=fname) def handleBrokenProduct(product): if getConfiguration().debug_mode: # Just reraise the error and let Zope handle it. raise # Not debug mode. Zope should continue to load. Print a log message: logger.exception('Could not import Product %s' % product.__name__) from zope.deferredimport import deprecated deprecated("Please import from OFS.metaconfigure", findProducts = 'OFS.metaconfigure:findProducts', loadProducts = 'OFS.metaconfigure:loadProducts', loadProductsOverrides = 'OFS.metaconfigure:loadProductsOverrides', _register_monkies = 'OFS.metaconfigure:_register_monkies', _meta_type_regs = 'OFS.metaconfigure:_meta_type_regs', _registerClass = 'OFS.metaconfigure:_registerClass', registerClass = 'OFS.metaconfigure:registerClass', _registerPackage = 'OFS.metaconfigure:_registerPackage', registerPackage = 'OFS.metaconfigure:registerPackage', unregisterClass = 'OFS.metaconfigure:unregisterClass', ) zope2.13-2.13.21/source/Zope2/src/Products/Five/bbb.py0000644000175000017500000000271012214017421021012 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Things needed for backward compatibility """ import Acquisition class AcquisitionBBB(object): """Emulate a class implementing Acquisition.interfaces.IAcquirer and IAcquisitionWrapper. """ def __of__(self, context): # Technically this isn't in line with the way Acquisition's # __of__ works. With Acquisition, you get a wrapper around # the original object and only that wrapper's parent is the # new context. return self aq_self = aq_inner = aq_base = property(lambda self: self) aq_chain = property(Acquisition.aq_chain) aq_parent = property(Acquisition.aq_parent) def aq_acquire(self, *args, **kw): return Acquisition.aq_acquire(self, *args, **kw) def aq_inContextOf(self, *args, **kw): return Acquisition.aq_inContextOf(self, *args, **kw) zope2.13-2.13.21/source/Zope2/src/Products/Five/__init__.py0000644000175000017500000000201312214017421022020 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Initialize the Five product """ # public API provided by Five # usage: from Products.Five import from Products.Five.browser import BrowserView from Products.Five.skin.standardmacros import StandardMacros # some convenience methods/decorators def fivemethod(func): func.__five_method__ = True return func def isFiveMethod(m): return hasattr(m, '__five_method__') zope2.13-2.13.21/source/Zope2/src/Products/Five/README.txt0000644000175000017500000000275112214017421021416 0ustar arnauarnauIntroduction ------------ "It was the dawn of the third age of Zope. The Five project was a dream given form. Its goal: to use Zope 3 technologies in Zope 2.7 by creating a Zope 2 product where Zope 3 and Zope 2 could work out their differences peacefully." -- Babylon 5, creatively quoted "The Law of Fives states simply that: ALL THINGS HAPPEN IN FIVES, OR ARE DIVISIBLE BY OR ARE MULTIPLES OF FIVE, OR ARE SOMEHOW DIRECTLY OR INDIRECTLY RELATED TO FIVE. THE LAW OF FIVES IS NEVER WRONG." -- Principia Discordia What is Five? ------------- A couple of years back an attempt was made to rewrite Zope 2 from scratch and provide an upgrade path from current Zope 2 to the new version. This project used the name Zope 3. The attempt of writing a newer version of a full blown application server similar to Zope 2 failed. Instead the project generated a whole lot of underlying technologies and new concepts packaged up in reusable libraries. Five is the project that integrates those technologies and packages into Zope 2. It's name is a pun on the original naming of Zope 2 + Zope 3 = Zope 5. Among others, it allows you to use zope.interface, ZCML-based configuration, adapters, browser pages (including layers, and resources), zope.schemas, object events, as well as zope.i18n message catalogs. We've tried to keep the Five experience as close to that of the integrated Zope packages as possible, so this means that what you learn while using Five should also be applicable to the Zope packages directly. zope2.13-2.13.21/source/Zope2/src/Products/Five/doc/0000755000175000017500000000000012214017421020460 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/doc/manual.txt0000644000175000017500000001560212214017421022502 0ustar arnauarnau=========== Five Manual =========== Zope interfaces --------------- Interfaces? =========== An interface is simply a description of what an object provides to the world, i.e. its public attribute and methods. It looks very much like a class, but contains no implementation:: from zope.interface import Interface # by convention, all interfaces are prefixed with ``I`` class IElephant(Interface): """An elephant is a big object that barely fits in the cupboard. """ def getAngerLevel(): """Anger level, maximum of 100. The longer the elephant has been in the cupboard, the angrier. """ def isInCupboard(): """Returns true if the elephant is indeed in cupboard. """ def trunkSmash(target): """Smash the target with trunk. The anger level determines the force of the hit. """ def trample(target): """Trample the target. The anger level determines the rate of flattening of the target. """ A concrete class somewhere can now claim that it implements the interface (i.e. its instance will provide the interface):: class PinkElephant: # this says all instances of this class provide IElephant implements(IElephant) def getAngerLevel(self): return 0 # this elephant is peaceful def isInCupboard(self): return False # it's never in a cupboard but can be found in bottles def trunkSmash(self, target): target.tickle() def trample(self, target): target.patOnHead() Interfaces themselves are good for a number of reasons: * They provide API documentation. * They help you make explicit the design of your application, hopefully improving it. * If an object provides an interface, that object is considered to be a *component*. This means you can use Zope's component architecture with these objects. In order to use the component architecture, you'll have to make your objects provide interfaces. Sometimes, you cannot change the code of class (as you are not the maintainer), but you still want to make it implement an interface. Zope provides a ZCML directive to do this:: Adapters -------- From a Python programmer's perspective, the immediate thing that Five brings to do the table are adapters. This section goes through some demo code to explain how everything is tied together. Zope adapters depend on Zope interfaces. To create a Zope interface you need to subclass it from ``zope.interface.Interface``. Here is an example:: from zope.interface import Interface class IMyInterface(Interface): """This is an interface. """ def someMethod(): """This method does amazing stuff. """ Now to make some class declare that it implements this interface, you need to use the ``implements()`` function in the class:: from zope.interface import implements from interfaces import IMyInterface class MyClass: implements(IMyInterface) def someMethod(self): return "I am alive! Alive!" Now let's set up the interface that we are adapting to:: class INewInterface(Interface): """The interface we adapt to. """ def anotherMethod(): """This method does more stuff. """ Next we'll work on the class that implements the adapter. The requirement to make a class that is an adapter is very simple; you only need to take a context object as the constructor. The context object is the object being adapted. An example:: from zope.interface import implements from interfaces import INewInterface class MyAdapter: implements(INewInterface) def __init__(self, context): self.context = context def anotherMethod(self): return "We have adapted: %s" % self.context.someMethod() Next, we hook it all up using zcml. If the classes are in a module called ``classes.py`` and the interfaces in a module called ``interfaces.py``, we can declare ``MyAdapter`` to be an adapter for ``IMyInterface`` to ``INewInterface`` like this (in a file called ``configure.zcml``):: Zope will automatically pickup ``configure.zcml`` when it's placed in the product's directory. Any object that provides ``IMyInterface`` can now be adapted to ``INewInterface``, like this:: from classes import MyClass from interfaces import INewInterface object = MyClass() adapted = INewInterface(object) print adapted.anotherMethod() Views ----- This section will give a brief introduction on how to use the Zope view system. Zope enables you to create views for your own objects, or even built-in Zope objects, as long as two things are the case: * The object provides a Zope interface, typically through its class. * The object (typically its class) is made traversable. This allows Zope views, resources and other things to be attached to a Zope object. Typically you give your classes an interface using the ``implements`` directive in the class body:: class MyClass: implements(ISomeInterface) For existing objects that you cannot modify this is not possible. Instead, we provide a ZCML directive to accomplish this. As an example, to make Zope's ``Folder`` (and all its subclasses) implement ``IFolder`` (an interface you defined), you can do the following in ZCML:: Views in Zope are simple classes. The only requirements for a view class are: * They need an ``__init__()`` that take a context and a request attribute. Typically this comes from a base class, such as ``BrowserView``. An example of a simple view:: from zope.publisher.browser import BrowserView class SimpleFolderView(BrowserView): def eagle(self): """Test """ return "The eagle has landed: %s" % self.context.keys() Note that it is not a good idea to give a view class its own ``index_html``, as this confuses Zope's view lookup machinery. This view uses methods in Python, but you can also use other mechanisms such as ``ViewPageTemplateFile``. Finally, we need to hook up the pages through ZCML:: ``browser`` in this refers to the XML namespace for browser related things; it's ``http://namespace.zope.org/browser``. ``permission`` declares the Zope 2 permission needs in order to access this view. The file ``permissions.zcml`` in AccessControl contains a mapping of Zope 2 permissions to their zope.security names. zope2.13-2.13.21/source/Zope2/src/Products/Five/doc/features.txt0000644000175000017500000000344412214017421023044 0ustar arnauarnau============= Zope features ============= Zope features are mostly features from zope.* packages, though it has some extras and some limitations. ZCML ==== ZCML is the Zope Configuration Markup Language, an XML application. Zope code consists of a lot of components that can be plugged together using ZCML. If you put a ``site.zcml`` in the etc directory of your Zope instance, this is the root of the ZCML tree. An example of ``site.zcml`` is in ``site.zcml.in``. If you don't place a ``site.zcml``, Five falls back on ``fallback.zcml``. ZCML in Zope 2 has a special directive, ``five:loadProducts``, to load the ZCML (``meta.zcml``, ``configure.zcml``) of all installed Zope 2 products, if available. Another special directive, ``five:loadProductsOverrides`` is available to load any overriding ZCML (``overrides.zcml``) in these products. In the ``overrides.zcml`` you can override existing views or adapters, in this or in other products. Security declarations ===================== Zope 2 aims to eradicate ``declareProtected``, ``ClassSecurityInfo`` and ``initializeClass`` from your code. In order to do this, Zope 2 provides a way of declaring permissions from ZCML. To declare permissions for methods and templates on views you use the ``permission`` attribute on the ``browser:page`` directive, and specify a Zope 2 permission (given a dotted name). You can find a list of these permissions in ``permissions.zcml`` in AccessControl's permissions.zcml. The permission check takes place before the view is executed. The ``class`` directive can also be used to declare permissions on Zope 2 content classes. Note however that these permissions will be ignored by views anyway, as they are trusted -- it only serves to protect directly exposed methods on content classes (the Python scripts and the ZPublisher). zope2.13-2.13.21/source/Zope2/src/Products/Five/doc/directives.txt0000644000175000017500000000445512214017421023372 0ustar arnauarnau=================================== ZCML Directives supported by Zope 2 =================================== Zope 2 tries to use the directives from zope.* packages where possible, though does sometimes subset the possible attributes. It also introduces a few directives of its own under the ``five`` namespace. Directives are listed per namespace, in alphabetic order. zope ``http://namespaces.zope.org/zope`` ======================================== adapter ------- Hook an adapter factory to an interface. class ----- Declare interface and permissions on classes. Declares Zope 2 permissions. permission ---------- Way to make Zope 2 permissions available, ``title`` is permission name. redefinePermission ------------------ Redefine a permission in included ZCML as another one. utility ------- Declare a global utility. interface --------- Register an interface in ZCML. hook ---- Install a hook on a hookable object. browser ``http://namespaces.zope.org/browser`` ============================================== page ---- Declare a page view for an interface. Permission is a Zope 2 permission. pages ----- Declare multiple page views for an interface. Permissions are Zope 2 permissions. defaultView ----------- Declare the name of the view that should be used for the default when viewing the object; i.e. when the object is traversed to without a view. menu ---- Declare a menu menuItem, menuItems ------------------- Declare menuItems five ``http://namespaces.zope.org/five`` ======================================== loadProducts ------------ Loads ZCML in all Zope 2 products. First processes all ``meta.zcml`` files, then processes all ``configure.zcml`` files. loadProductsOverrides --------------------- Loads overriding ZCML in all products (``overrides.zcml``). sizable ------- Retrieve size information for a Zope 2 content class via a zope.size style ``ISized`` adapter. deprecatedManageAddDelete ------------------------- Specify a class that needs its old deprecated methods like ``manage_afterAdd``, ``manage_beforeDelete`` and ``manage_afterClone`` to be called. Modern classes should use event subscribers instead. pagesFromDirectory ------------------ Loads all files with .pt extension in a directory as pages. registerClass ------------- Registers Zope 2 content classes with Zope 2. zope2.13-2.13.21/source/Zope2/src/Products/Five/doc/i18n.txt0000644000175000017500000000073712214017421022007 0ustar arnauarnauInternationalization ==================== Translation ----------- To register translation domains, use the following ZCML statement:: where the 'i18n' prefix is bound to the http://namespaces.zope.org/i18n namespace identifier. The directory (in this case 'locales') should conform to the `standard gettext locale directory layout`__. .. __: http://www.gnu.org/software/gettext/manual/html_chapter/gettext_10.html#SEC148 zope2.13-2.13.21/source/Zope2/src/Products/Five/sizeconfigure.py0000644000175000017500000000517712214017421023153 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Use structured monkey-patching to enable ``ISized`` adapters for Zope 2 objects. """ from zope.size.interfaces import ISized from Products.Five import fivemethod, isFiveMethod # holds classes that were monkeyed with; for clean up _monkied = [] @fivemethod def get_size(self): size = ISized(self, None) if size is not None: unit, amount = size.sizeForSorting() if unit == 'byte': return amount method = getattr(self, '__five_original_get_size', None) if method is not None: return self.__five_original_get_size() def classSizable(class_): """Monkey the class to be sizable through Five""" # tuck away the original method if necessary if hasattr(class_, "get_size") and not isFiveMethod(class_.get_size): class_.__five_original_get_size = class_.get_size class_.get_size = get_size # remember class for clean up _monkied.append(class_) def sizable(_context, class_): _context.action( discriminator = ('five:sizable', class_), callable = classSizable, args=(class_,) ) # clean up code def killMonkey(class_, name, fallback, attr=None): """Die monkey, die!""" method = getattr(class_, name, None) if isFiveMethod(method): original = getattr(class_, fallback, None) if original is not None: delattr(class_, fallback) if original is None or isFiveMethod(original): try: delattr(class_, name) except AttributeError: pass else: setattr(class_, name, original) if attr is not None: try: delattr(class_, attr) except (AttributeError, KeyError): pass def unsizable(class_): """Restore class's initial state with respect to being sizable""" killMonkey(class_, 'get_size', '__five_original_get_size') def cleanUp(): for class_ in _monkied: unsizable(class_) from zope.testing.cleanup import addCleanUp addCleanUp(cleanUp) del addCleanUp zope2.13-2.13.21/source/Zope2/src/Products/Five/viewlet/0000755000175000017500000000000012214017422021373 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/viewlet/configure.zcml0000644000175000017500000000050112214017422024237 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/viewlet/directives.txt0000644000175000017500000004000112214017422024270 0ustar arnauarnau================================ The ``viewletManager`` Directive ================================ Setup traversal stuff >>> import Products.Five >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) The ``viewletManager`` directive allows you to quickly register a new viewlet manager without worrying about the details of the ``adapter`` directive. Before we can use the directives, we have to register their handlers by executing the package's meta configuration: >>> context = zcml.load_string(''' ... ... ... ... ''') Now we can register a viewlet manager: >>> context = zcml.load_string(''' ... ... ... ... ''') Let's make sure the directive has really issued a sensible adapter registration; to do that, we create some dummy content, request and view objects: >>> from Products.Five.viewlet.tests import Content >>> content = Content() >>> obj_id = self.folder._setObject('content1', Content()) >>> content = self.folder[obj_id] >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> from Products.Five.browser import BrowserView as View >>> view = View(content, request) Now let's lookup the manager. This particular registration is pretty boring: >>> import zope.component >>> from zope.viewlet import interfaces >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ... interfaces.IViewletManager, name='defaultmanager') >>> manager object ...> >>> interfaces.IViewletManager.providedBy(manager) True >>> manager.template is None True >>> manager.update() >>> manager.render() u'' However, this registration is not very useful, since we did specify a specific viewlet manager interface, a specific content interface, specific view or specific layer. This means that all viewlets registered will be found. The first step to effectively using the viewlet manager directive is to define a special viewlet manager interface: >>> from Products.Five.viewlet.tests import ILeftColumn Now we can register register a manager providing this interface: >>> context = zcml.load_string(''' ... ... ... ... ''') >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> ILeftColumn.providedBy(manager) True >>> manager.template is None True >>> manager.update() >>> manager.render() u'' Next let's see what happens, if we specify a template for the viewlet manager: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> leftColumnTemplate = os.path.join(temp_dir, 'leftcolumn.pt') >>> open(leftColumnTemplate, 'w').write(''' ...
    ...
    ...
    ... ''') >>> context = zcml.load_string(''' ... ... ... ... ''' %leftColumnTemplate) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> ILeftColumn.providedBy(manager) True >>> manager.update() >>> print manager.render().strip()
    Additionally you can specify a class that will serve as a base to the default viewlet manager or be a viewlet manager in its own right. In our case we will provide a custom implementation of the ``sort()`` method, which will sort by a weight attribute in the viewlet: >>> context = zcml.load_string(''' ... ... ... ... ''' %leftColumnTemplate) >>> manager = zope.component.getMultiAdapter( ... (content, request, view), ILeftColumn, name='leftcolumn') >>> manager object ...> >>> manager.__class__.__bases__ (, ) >>> ILeftColumn.providedBy(manager) True >>> manager.update() >>> print manager.render().strip()
    Finally, if a non-existent template is specified, an error is raised: >>> context = zcml.load_string(''' ... ... ... ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-7.8 ConfigurationError: ('No such file', '...foo.pt') ========================= The ``viewlet`` Directive ========================= Now that we have a viewlet manager, we have to register some viewlets for it. The ``viewlet`` directive is similar to the ``viewletManager`` directive, except that the viewlet is also registered for a particular manager interface, as seen below: >>> weatherTemplate = os.path.join(temp_dir, 'weather.pt') >>> open(weatherTemplate, 'w').write(''' ...
    sunny
    ... ''') >>> context = zcml.load_string(''' ... ... ... ... ''' % weatherTemplate) If we look into the adapter registry, we will find the viewlet: >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='weather') >>> viewlet.render().strip() u'
    sunny
    ' >>> viewlet.extra_string_attributes u'can be specified' The manager now also gives us the output of the one and only viewlet: >>> manager.update() >>> print manager.render().strip()
    sunny
    Let's now ensure that we can also specify a viewlet class: >>> context = zcml.load_string(''' ... ... ... ... ''' % weatherTemplate) >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='weather2') >>> viewlet().strip() u'
    sunny
    ' Okay, so the template-driven cases work. But just specifying a class should also work: >>> context = zcml.load_string(''' ... ... ... ... ''') >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, name='sport') >>> viewlet() u'Red Sox vs. White Sox' It should also be possible to specify an alternative attribute of the class to be rendered upon calling the viewlet: >>> context = zcml.load_string(''' ... ... ... ... ''') >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='stock') >>> viewlet.render() u'SRC $5.19' A final feature the ``viewlet`` directive supports is the additional specification of any amount keyword arguments: >>> context = zcml.load_string(''' ... ... ... ... ''') >>> viewlet = zope.component.getMultiAdapter( ... (content, request, view, manager), interfaces.IViewlet, ... name='stock2') >>> viewlet.weight u'8' Error Scenarios --------------- Neither the class or template have been specified: >>> context = zcml.load_string(''' ... ... ... ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-7.8 ConfigurationError: Must specify a class or template The specified attribute is not ``__call__``, but also a template has been specified: >>> context = zcml.load_string(''' ... ... ... ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-9.8 ConfigurationError: Attribute and template cannot be used together. Now, we are not specifying a template, but a class that does not have the specified attribute: >>> context = zcml.load_string(''' ... ... ... ... ''') Traceback (most recent call last): ... ZopeXMLConfigurationError: File "", line 3.2-9.8 ConfigurationError: The provided class doesn't have the specified attribute ================================ Viewlet Directive Security ================================ Before we can begin, we need to set up a few things. We need a manager account: >>> uf = self.folder.acl_users >>> _ignored = uf._doAddUser('manager', 'r00t', ['Manager'], []) Finally, we need to setup a traversable folder. Otherwise, Five won't get do its view lookup magic: >>> from OFS.Folder import manage_addFolder >>> manage_addFolder(self.folder, 'ftf') Now we can register another simple viewlet manager: >>> from Products.Five.viewlet.tests import INewColumn >>> context = zcml.load_string(''' ... ... ... ... ''') And a view to call our new content provider: >>> testTemplate = os.path.join(temp_dir, 'test.pt') >>> open(testTemplate, 'w').write(''' ... ... ...

    Weather

    ...
    ... ... ... ''') >>> context = zcml.load_string(''' ... ... ... ... ''' % testTemplate) We now register some viewlets with different permissions: >>> weatherTemplate = os.path.join(temp_dir, 'weather2.pt') >>> open(weatherTemplate, 'w').write(''' ...
    sunny
    ... ''') >>> context = zcml.load_string(''' ... ... ... ... ''' % weatherTemplate) >>> context = zcml.load_string(''' ... ... ... ... ''' % weatherTemplate) If we make the request as a manager, we should see both viewlets: >>> print http(r""" ... GET /test_folder_1_/ftf/@@securitytest_view HTTP/1.1 ... Authorization: Basic manager:r00t ... """, handle_errors=False) HTTP/1.1 200 OK ...

    Weather

    sunny
    sunny
    ... But when we make an anonymous request, we will only see the public viewlet: >>> print http(r""" ... GET /test_folder_1_/ftf/@@securitytest_view HTTP/1.1 ... """, handle_errors=False) HTTP/1.1 200 OK ...

    Weather

    sunny
    ... A Dynamic Viewlet ----------------- A viewlet template can of course contain some dynamic code, let's see how that works: >>> dynWeatherTemplate = os.path.join(temp_dir, 'dynamic_weather.pt') >>> open(dynWeatherTemplate, 'w').write(u''' ...
    ''' ... ) >>> context = zcml.load_string(''' ... ... ... ... ''' % dynWeatherTemplate) Now we request the view to ensure that we can see the dynamic template rendered as expected: >>> print http(r""" ... GET /test_folder_1_/ftf/@@securitytest_view HTTP/1.1 ... """, handle_errors=False) HTTP/1.1 200 OK ...

    Weather

    Los Angeles, CA: 78 F
    sunny
    ... Cleanup ------- >>> import shutil >>> shutil.rmtree(temp_dir) Clear registries: >>> from zope.testing.cleanup import cleanUp >>> cleanUp() zope2.13-2.13.21/source/Zope2/src/Products/Five/viewlet/metaconfigure.py0000644000175000017500000001601312214017422024576 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Viewlet ZCML directives. """ import os from zope.browserpage.metaconfigure import _handle_for from zope.component import zcml from zope.configuration.exceptions import ConfigurationError from zope.interface import Interface from zope.browser.interfaces import IBrowserView from zope.publisher.interfaces.browser import IDefaultBrowserLayer from zope.viewlet import interfaces from AccessControl.class_init import InitializeClass from AccessControl.security import protectClass from AccessControl.security import protectName from Products.Five.viewlet import manager from Products.Five.viewlet import viewlet def viewletManagerDirective( _context, name, permission, for_=Interface, layer=IDefaultBrowserLayer, view=IBrowserView, provides=interfaces.IViewletManager, class_=None, template=None, allowed_interface=None, allowed_attributes=None): # If class is not given we use the basic viewlet manager. if class_ is None: class_ = manager.ViewletManagerBase # Iterate over permissions if allowed_attributes is None: allowed_attributes = ['render', 'update'] if allowed_interface is not None: for interface in allowed_interface: allowed_attributes.extend(interface.names()) # Make sure that the template exists and that all low-level API methods # have the right permission. if template: template = os.path.abspath(str(_context.path(template))) if not os.path.isfile(template): raise ConfigurationError("No such file", template) allowed_attributes.append('__getitem__') # Create a new class based on the template and class. new_class = manager.ViewletManager( name, provides, template=template, bases=(class_, )) else: # Create a new class based on the class. new_class = manager.ViewletManager(name, provides, bases=(class_, )) # Register interfaces _handle_for(_context, for_) zcml.interface(_context, view) # register a viewlet manager _context.action( discriminator = ('viewletManager', for_, layer, view, name), callable = zcml.handler, args = ('registerAdapter', new_class, (for_, layer, view), provides, name, _context.info),) _context.action( discriminator = ('five:protectClass', new_class), callable = protectClass, args = (new_class, permission) ) if allowed_attributes: for attr in allowed_attributes: _context.action( discriminator = ('five:protectName', new_class, attr), callable = protectName, args = (new_class, attr, permission) ) _context.action( discriminator = ('five:initialize:class', new_class), callable = InitializeClass, args = (new_class,) ) def viewletDirective( _context, name, permission, for_=Interface, layer=IDefaultBrowserLayer, view=IBrowserView, manager=interfaces.IViewletManager, class_=None, template=None, attribute='render', allowed_interface=None, allowed_attributes=None, **kwargs): # Either the class or template must be specified. if not (class_ or template): raise ConfigurationError("Must specify a class or template") # Make sure that all the non-default attribute specifications are correct. if attribute != 'render': if template: raise ConfigurationError( "Attribute and template cannot be used together.") # Note: The previous logic forbids this condition to evere occur. if not class_: raise ConfigurationError( "A class must be provided if attribute is used") # Iterate over permissions if allowed_attributes is None: allowed_attributes = ['render', 'update'] if allowed_interface is not None: for interface in allowed_interface: allowed_attributes.extend(interface.names()) # Make sure that the template exists and that all low-level API methods # have the right permission. if template: template = os.path.abspath(str(_context.path(template))) if not os.path.isfile(template): raise ConfigurationError("No such file", template) allowed_attributes.append('__getitem__') # Make sure the has the right form, if specified. if class_: if attribute != 'render': if not hasattr(class_, attribute): raise ConfigurationError( "The provided class doesn't have the specified attribute " ) if template: # Create a new class for the viewlet template and class. new_class = viewlet.SimpleViewletClass( template, bases=(class_, ), attributes=kwargs) else: if not hasattr(class_, 'browserDefault'): cdict = {'browserDefault': lambda self, request: (getattr(self, attribute), ())} else: cdict = {} cdict['__name__'] = name cdict['__page_attribute__'] = attribute cdict.update(kwargs) new_class = type(class_.__name__, (class_, viewlet.SimpleAttributeViewlet), cdict) else: # Create a new class for the viewlet template alone. new_class = viewlet.SimpleViewletClass(template, name=name, attributes=kwargs) # Register the interfaces. _handle_for(_context, for_) zcml.interface(_context, view) # register viewlet _context.action( discriminator = ('viewlet', for_, layer, view, manager, name), callable = zcml.handler, args = ('registerAdapter', new_class, (for_, layer, view, manager), interfaces.IViewlet, name, _context.info),) _context.action( discriminator = ('five:protectClass', new_class), callable = protectClass, args = (new_class, permission) ) if allowed_attributes: for attr in allowed_attributes: _context.action( discriminator = ('five:protectName', new_class, attr), callable = protectName, args = (new_class, attr, permission) ) _context.action( discriminator = ('five:initialize:class', new_class), callable = InitializeClass, args = (new_class,) ) zope2.13-2.13.21/source/Zope2/src/Products/Five/viewlet/__init__.py0000644000175000017500000000003712214017422023504 0ustar arnauarnau# A package for viewlet supportzope2.13-2.13.21/source/Zope2/src/Products/Five/viewlet/README.txt0000644000175000017500000006634712214017422023111 0ustar arnauarnau============================= Viewlets and Viewlet Managers ============================= Let's start with some motivation. Using content providers allows us to insert one piece of HTML content. In most Web development, however, you are often interested in defining some sort of region and then allow developers to register content for those regions. >>> from zope.viewlet import interfaces Setup traversal stuff >>> import Products.Five >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) Set a loose security policy because these are unit tests, security will be tested in another file: >>> from AccessControl import SecurityManager >>> from Products.Five.viewlet.tests import UnitTestSecurityPolicy >>> from AccessControl.SecurityManagement import newSecurityManager >>> from AccessControl.SecurityManagement import noSecurityManager >>> noSecurityManager() >>> oldPolicy = SecurityManager.setSecurityPolicy(UnitTestSecurityPolicy()) Design Notes ------------ As mentioned above, besides inserting snippets of HTML at places, we more frequently want to define a region in our page and allow specialized content providers to be inserted based on configuration. Those specialized content providers are known viewlets and are only available inside viewlet managers, which are just a more complex example of content providers. Unfortunately, the Java world does not implement this layer separately. The viewlet manager is most similar to a Java "channel", but we decided against using this name, since it is very generic and not very meaningful. The viewlet has no Java counterpart, since Java does not implement content providers using a component architecture and thus does not register content providers specifically for viewlet managers, which I believe makes the Java implementation less useful as a generic concept. In fact, the main design goal in the Java world is the implementation of reusable and sharable portlets. The scope for Zope is larger, since we want to provide a generic framework for building pluggable user interfaces. The Viewlet Manager ------------------- In this implementation of viewlets, those regions are just content providers called viewlet managers that manage a special type of content providers known as viewlets. Every viewlet manager handles the viewlets registered for it: >>> from Products.Five.viewlet.tests import ILeftColumn You can then create a viewlet manager using this interface now: >>> from Products.Five.viewlet import manager >>> LeftColumn = manager.ViewletManager('left', ILeftColumn) Now we have to instantiate it in the context of an actual zope object: >>> import zope.interface >>> from OFS import SimpleItem, Folder >>> class Content(SimpleItem.SimpleItem): ... zope.interface.implements(zope.interface.Interface) >>> obj_id = self.folder._setObject('content1', Content()) >>> content = self.folder[obj_id] >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> from Products.Five.browser import BrowserView as View >>> view = View(content, request) >>> leftColumn = LeftColumn(content, request, view) So initially nothing gets rendered: >>> leftColumn.update() >>> leftColumn.render() u'' But now we register some viewlets for the manager >>> import zope.component >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer >>> from zope.browser.interfaces import IBrowserView >>> class WeatherBox(object): ... zope.interface.implements(interfaces.IViewlet) ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... self.context = context ... ... def update(self): ... pass ... ... def render(self): ... return u'
    It is sunny today!
    ' >>> zope.component.provideAdapter( ... WeatherBox, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, ILeftColumn), ... interfaces.IViewlet, name='weather') >>> class SportBox(object): ... zope.interface.implements(interfaces.IViewlet) ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... self.context = context ... ... def update(self): ... pass ... ... def render(self): ... return u'
    Patriots (23) : Steelers (7)
    ' >>> zope.component.provideAdapter( ... SportBox, ... (zope.interface.Interface, IDefaultBrowserLayer, ... IBrowserView, ILeftColumn), ... interfaces.IViewlet, name='sport') and thus the left column is filled: >>> leftColumn.update() >>> print leftColumn.render()
    Patriots (23) : Steelers (7)
    It is sunny today!
    But this is of course pretty lame, since there is no way of specifying how the viewlets are put together. But we have a solution. The second argument of the ``ViewletManager()`` function is a template in which we can specify how the viewlets are put together: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> leftColTemplate = os.path.join(temp_dir, 'leftCol.pt') >>> open(leftColTemplate, 'w').write(''' ...
    ... ...
    ... ''') >>> LeftColumn = manager.ViewletManager('left', ILeftColumn, ... template=leftColTemplate) >>> leftColumn = LeftColumn(content, request, view) TODO: Fix this silly thing; viewlets should be directly available. As you can see, the viewlet manager provides a global ``options/viewlets`` variable that is an iterable of all the avialable viewlets in the correct order: >>> leftColumn.update() >>> print leftColumn.render().strip()
    Patriots (23) : Steelers (7)
    It is sunny today!
    You can also lookup the viewlets directly for management purposes: >>> leftColumn['weather'] >>> leftColumn.get('weather') If the viewlet is not found, then the expected behavior is provided: >>> leftColumn['stock'] Traceback (most recent call last): ... ComponentLookupError: No provider with name `stock` found. >>> leftColumn.get('stock') is None True Customizing the default Viewlet Manager --------------------------------------- One important feature of any viewlet manager is to be able to filter and sort the viewlets it is displaying. The default viewlet manager that we have been using in the tests above, supports filtering by access availability and sorting via the viewlet's ``__cmp__()`` method (default). You can easily override this default policy by providing a base viewlet manager class. In our case we will manage the viewlets using a global list: >>> shown = ['weather', 'sport'] The viewlet manager base class now uses this list: >>> class ListViewletManager(object): ... ... def filter(self, viewlets): ... viewlets = super(ListViewletManager, self).filter(viewlets) ... return [(name, viewlet) ... for name, viewlet in viewlets ... if name in shown] ... ... def sort(self, viewlets): ... viewlets = dict(viewlets) ... return [(name, viewlets[name]) for name in shown] Let's now create a new viewlet manager: >>> LeftColumn = manager.ViewletManager( ... 'left', ILeftColumn, bases=(ListViewletManager,), ... template=leftColTemplate) >>> leftColumn = LeftColumn(content, request, view) So we get the weather box first and the sport box second: >>> leftColumn.update() >>> print leftColumn.render().strip()
    It is sunny today!
    Patriots (23) : Steelers (7)
    Now let's change the order... >>> shown.reverse() and the order should switch as well: >>> leftColumn.update() >>> print leftColumn.render().strip()
    Patriots (23) : Steelers (7)
    It is sunny today!
    Of course, we also can remove a shown viewlet: >>> weather = shown.pop() >>> leftColumn.update() >>> print leftColumn.render().strip()
    Patriots (23) : Steelers (7)
    Viewlet Base Classes -------------------- To make the creation of viewlets simpler, a set of useful base classes and helper functions are provided: >>> from Products.Five.viewlet import viewlet The first class is a base class that simply defines the constructor: >>> base = viewlet.ViewletBase('context', 'request', 'view', 'manager') >>> base.context 'context' >>> base.request 'request' >>> base.__parent__ 'view' >>> base.manager 'manager' But a default ``render()`` method implementation is not provided: >>> base.render() Traceback (most recent call last): ... NotImplementedError: `render` method must be implemented by subclass. If you have already an existing class that produces the HTML content in some method, then the ``SimpleAttributeViewlet`` might be for you, since it can be used to convert any class quickly into a viewlet: >>> class FooViewlet(viewlet.SimpleAttributeViewlet): ... __page_attribute__ = 'foo' ... ... def foo(self): ... return 'output' The `__page_attribute__` attribute provides the name of the function to call for rendering. >>> foo = FooViewlet('context', 'request', 'view', 'manager') >>> foo.foo() 'output' >>> foo.render() 'output' If you specify `render` as the attribute an error is raised to prevent infinite recursion: >>> foo.__page_attribute__ = 'render' >>> foo.render() Traceback (most recent call last): ... AttributeError: render The same is true if the specified attribute does not exist: >>> foo.__page_attribute__ = 'bar' >>> foo.render() Traceback (most recent call last): ... AttributeError: 'FooViewlet' object has no attribute 'bar' To create simple template-based viewlets you can use the ``SimpleViewletClass()`` function. This function is very similar to its view equivalent and is used by the ZCML directives to create viewlets. The result of this function call will be a fully functional viewlet class. Let's start by simply specifying a template only: >>> template = os.path.join(temp_dir, 'demoTemplate.pt') >>> open(template, 'w').write('''
    contents
    ''') >>> Demo = viewlet.SimpleViewletClass(template) >>> print Demo(content, request, view, manager).render()
    contents
    Now let's additionally specify a class that can provide additional features: >>> class MyViewlet(object): ... myAttribute = 8 >>> Demo = viewlet.SimpleViewletClass(template, bases=(MyViewlet,)) >>> MyViewlet in Demo.__bases__ True >>> Demo(content, request, view, manager).myAttribute 8 The final important feature is the ability to pass in further attributes to the class: >>> Demo = viewlet.SimpleViewletClass( ... template, attributes={'here': 'now', 'lucky': 3}) >>> demo = Demo(content, request, view, manager) >>> demo.here 'now' >>> demo.lucky 3 As for all views, they must provide a name that can also be passed to the function: >>> Demo = viewlet.SimpleViewletClass(template, name='demoViewlet') >>> demo = Demo(content, request, view, manager) >>> demo.__name__ 'demoViewlet' In addition to the the generic viewlet code above, the package comes with two viewlet base classes and helper functions for inserting CSS and Javascript links into HTML headers, since those two are so very common. I am only going to demonstrate the helper functions here, since those demonstrations will fully demonstrate the functionality of the base classes as well. >>> from zope.interface import Interface >>> from zope.component import getGlobalSiteManager >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer >>> def registerResource(name, factory): ... gsm = getGlobalSiteManager() ... gsm.registerAdapter( ... factory, ... required=(IDefaultBrowserLayer, ), ... provided=Interface, ... name=name, ... ) The viewlet will look up the resource it was given and tries to produce the absolute URL for it: >>> class JSResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/resource.js' >>> registerResource('resource.js', JSResource) >>> JSViewlet = viewlet.JavaScriptViewlet('resource.js') >>> print JSViewlet(content, request, view, manager).render().strip() The same works for the CSS resource viewlet: >>> class CSSResource(object): ... def __init__(self, request): ... self.request = request ... ... def __call__(self): ... return '/@@/resource.css' >>> registerResource('resource.css', CSSResource) >>> CSSViewlet = viewlet.CSSViewlet('resource.css') >>> print CSSViewlet(content, request, view, manager).render().strip() You can also change the media type and the rel attribute: >>> CSSViewlet = viewlet.CSSViewlet('resource.css', media='print', rel='css') >>> print CSSViewlet(content, request, view, manager).render().strip() A Complex Example ----------------- The Data ~~~~~~~~ So far we have only demonstrated simple (maybe overly trivial) use cases of the viewlet system. In the following example, we are going to develop a generic contents view for files. The step is to create a file component: >>> class IFile(zope.interface.Interface): ... data = zope.interface.Attribute('Data of file.') >>> class File(SimpleItem.SimpleItem): ... zope.interface.implements(IFile) ... def __init__(self, data=''): ... self.__name__ = '' ... self.data = data Since we want to also provide the size of a file, here a simple implementation of the ``ISized`` interface: >>> from zope import size >>> class FileSized(object): ... zope.interface.implements(size.interfaces.ISized) ... zope.component.adapts(IFile) ... ... def __init__(self, file): ... self.file = file ... ... def sizeForSorting(self): ... return 'byte', len(self.file.data) ... ... def sizeForDisplay(self): ... return '%i bytes' %len(self.file.data) >>> zope.component.provideAdapter(FileSized) We also need a container to which we can add files: >>> class Container(Folder.Folder): ... def __setitem__(self, name, value): ... self._setObject(name, value) ... value.__name__ = name Here is some sample data: >>> container = Container() >>> obj_id = self.folder._setObject('container', container) >>> container = self.folder[obj_id] >>> container['mypage.html'] = File('Hello World!') >>> container['data.xml'] = File('Hello World!') >>> container['test.txt'] = File('Hello World!') The View ~~~~~~~~ The contents view of the container should iterate through the container and represent the files in a table: >>> contentsTemplate = os.path.join(temp_dir, 'contents.pt') >>> open(contentsTemplate, 'w').write(''' ... ... ...

    Cotnents

    ...
    ... ... ... ''') >>> from Products.Five.browser.metaconfigure import makeClassForTemplate >>> Contents = makeClassForTemplate(contentsTemplate, name='contents.html') The Viewlet Manager ~~~~~~~~~~~~~~~~~~~ Now we have to write our own viewlet manager. In this case we cannot use the default implementation, since the viewlets will be looked up for each different item: >>> shownColumns = [] >>> class ContentsViewletManager(object): ... zope.interface.implements(interfaces.IViewletManager) ... index = None ... ... def __init__(self, context, request, view): ... self.context = context ... self.request = request ... self.__parent__ = view ... ... def update(self): ... rows = [] ... for name, value in self.context.objectItems(): ... rows.append( ... [zope.component.getMultiAdapter( ... (value, self.request, self.__parent__, self), ... interfaces.IViewlet, name=colname) ... for colname in shownColumns]) ... [entry.update() for entry in rows[-1]] ... self.rows = rows ... ... def render(self, *args, **kw): ... return self.index(*args, **kw) Now we need a template to produce the contents table: >>> tableTemplate = os.path.join(temp_dir, 'table.pt') >>> open(tableTemplate, 'w').write(''' ... ... ... ... ...
    ... ...
    ... ''') From the two pieces above, we can generate the final viewlet manager class and register it (it's a bit tedious, I know): >>> from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile >>> ContentsViewletManager = type( ... 'ContentsViewletManager', (ContentsViewletManager,), ... {'index': ZopeTwoPageTemplateFile('table.pt', temp_dir)}) >>> zope.component.provideAdapter( ... ContentsViewletManager, ... (Container, IDefaultBrowserLayer, zope.interface.Interface), ... interfaces.IViewletManager, name='contents') Since we have not defined any viewlets yet, the table is totally empty: >>> contents = Contents(container, request) >>> print contents().strip()

    Cotnents

    The Viewlets and the Final Result ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now let's create a first viewlet for the manager... >>> class NameViewlet(object): ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... self.context = context ... ... def update(self): ... pass ... ... def render(self): ... return self.context.__name__ and register it: >>> zope.component.provideAdapter( ... NameViewlet, ... (IFile, IDefaultBrowserLayer, ... zope.interface.Interface, interfaces.IViewletManager), ... interfaces.IViewlet, name='name') Note how you register the viewlet on ``IFile`` and not on the container. Now we should be able to see the name for each file in the container: >>> print contents().strip()

    Cotnents

    Waaa, nothing there! What happened? Well, we have to tell our user preferences that we want to see the name as a column in the table: >>> shownColumns = ['name'] >>> print contents().strip()

    Cotnents

    mypage.html
    data.xml
    test.txt
    Let's now write a second viewlet that will display the size of the object for us: >>> class SizeViewlet(object): ... ... def __init__(self, context, request, view, manager): ... self.__parent__ = view ... self.context = context ... ... def update(self): ... pass ... ... def render(self): ... return size.interfaces.ISized(self.context).sizeForDisplay() >>> zope.component.provideAdapter( ... SizeViewlet, ... (IFile, IDefaultBrowserLayer, ... zope.interface.Interface, interfaces.IViewletManager), ... interfaces.IViewlet, name='size') After we added it to the list of shown columns, >>> shownColumns = ['name', 'size'] we can see an entry for it: >>> print contents().strip()

    Cotnents

    mypage.html 38 bytes
    data.xml 31 bytes
    test.txt 12 bytes
    If we switch the two columns around, >>> shownColumns = ['size', 'name'] the result will be >>> print contents().strip()

    Cotnents

    38 bytes mypage.html
    31 bytes data.xml
    12 bytes test.txt
    Supporting Sorting ~~~~~~~~~~~~~~~~~~ Oftentimes you also want to batch and sort the entries in a table. Since those two features are not part of the view logic, they should be treated with independent components. In this example, we are going to only implement sorting using a simple utility: >>> class ISorter(zope.interface.Interface): ... ... def sort(values): ... """Sort the values.""" >>> class SortByName(object): ... zope.interface.implements(ISorter) ... ... def sort(self, values): ... return sorted(values, lambda x, y: cmp(x.__name__, y.__name__)) >>> zope.component.provideUtility(SortByName(), name='name') >>> class SortBySize(object): ... zope.interface.implements(ISorter) ... ... def sort(self, values): ... return sorted( ... values, ... lambda x, y: cmp(size.interfaces.ISized(x).sizeForSorting(), ... size.interfaces.ISized(y).sizeForSorting())) >>> zope.component.provideUtility(SortBySize(), name='size') Note that we decided to give the sorter utilities the same name as the corresponding viewlet. This convention will make our implementation of the viewlet manager much simpler: >>> sortByColumn = '' >>> class SortedContentsViewletManager(manager.ViewletManagerBase): ... zope.interface.implements(interfaces.IViewletManager) ... index = None ... ... def __init__(self, context, request, view): ... self.context = context ... self.request = request ... self.__parent__ = view ... ... def update(self): ... values = self.context.objectValues() ... ... if sortByColumn: ... sorter = zope.component.queryUtility(ISorter, sortByColumn) ... if sorter: ... values = sorter.sort(values) ... ... rows = [] ... for value in values: ... rows.append( ... [zope.component.getMultiAdapter( ... (value, self.request, self.__parent__, self), ... interfaces.IViewlet, name=colname) ... for colname in shownColumns]) ... [entry.update() for entry in rows[-1]] ... self.rows = rows ... ... def render(self, *args, **kw): ... return self.index(*args, **kw) As you can see, the concern of sorting is cleanly separated from generating the view code. In MVC terms that means that the controller (sort) is logically separated from the view (viewlets). Let's now do the registration dance for the new viewlet manager. We simply override the existing registration: >>> SortedContentsViewletManager = type( ... 'SortedContentsViewletManager', (SortedContentsViewletManager,), ... {'index': ZopeTwoPageTemplateFile('table.pt', temp_dir)}) >>> zope.component.provideAdapter( ... SortedContentsViewletManager, ... (Container, IDefaultBrowserLayer, zope.interface.Interface), ... interfaces.IViewletManager, name='contents') Finally we sort the contents by name: >>> shownColumns = ['name', 'size'] >>> sortByColumn = 'name' >>> print contents().strip()

    Cotnents

    data.xml 31 bytes
    mypage.html 38 bytes
    test.txt 12 bytes
    Now let's sort by size: >>> sortByColumn = 'size' >>> print contents().strip()

    Cotnents

    test.txt 12 bytes
    data.xml 31 bytes
    mypage.html 38 bytes
    That's it! As you can see, in a few steps we have built a pretty flexible contents view with selectable columns and sorting. However, there is a lot of room for extending this example: - Table Header: The table header cell for each column should be a different type of viewlet, but registered under the same name. The column header viewlet also adapts the container not the item. The header column should also be able to control the sorting. - Batching: A simple implementation of batching should work very similar to the sorting feature. Of course, efficient implementations should somehow combine batching and sorting more effectively. - Sorting in ascending and descending order: Currently, you can only sort from the smallest to the highest value; however, this limitation is almost superficial and can easily be removed by making the sorters a bit more flexible. - Further Columns: For a real application, you would want to implement other columns, of course. You would also probably want some sort of fallback for the case that a viewlet is not found for a particular container item and column. Cleanup ------- >>> ignore = SecurityManager.setSecurityPolicy(oldPolicy) >>> import shutil >>> shutil.rmtree(temp_dir) zope2.13-2.13.21/source/Zope2/src/Products/Five/viewlet/viewlet.py0000644000175000017500000000520512214017422023426 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Viewlet. """ import os import zope.viewlet.viewlet from Products.Five.bbb import AcquisitionBBB from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile class ViewletBase(zope.viewlet.viewlet.ViewletBase, AcquisitionBBB): pass class SimpleAttributeViewlet(zope.viewlet.viewlet.SimpleAttributeViewlet, AcquisitionBBB): pass class simple(zope.viewlet.viewlet.simple): # We need to ensure that the proper __init__ is called. __init__ = ViewletBase.__init__.im_func def SimpleViewletClass(template, bases=(), attributes=None, name=u''): """A function that can be used to generate a viewlet from a set of information. """ # Create the base class hierarchy bases += (simple, ViewletBase) attrs = {'index' : ViewPageTemplateFile(template), '__name__' : name} if attributes: attrs.update(attributes) # Generate a derived view class. class_ = type("SimpleViewletClass from %s" % template, bases, attrs) return class_ class ResourceViewletBase(zope.viewlet.viewlet.ResourceViewletBase): pass def JavaScriptViewlet(path): """Create a viewlet that can simply insert a javascript link.""" src = os.path.join(os.path.dirname(__file__), 'javascript_viewlet.pt') klass = type('JavaScriptViewlet', (ResourceViewletBase, ViewletBase), {'index': ViewPageTemplateFile(src), '_path': path}) return klass class CSSResourceViewletBase(zope.viewlet.viewlet.CSSResourceViewletBase): pass def CSSViewlet(path, media="all", rel="stylesheet"): """Create a viewlet that can simply insert a javascript link.""" src = os.path.join(os.path.dirname(__file__), 'css_viewlet.pt') klass = type('CSSViewlet', (CSSResourceViewletBase, ViewletBase), {'index': ViewPageTemplateFile(src), '_path': path, '_media':media, '_rel':rel}) return klass zope2.13-2.13.21/source/Zope2/src/Products/Five/viewlet/tests.py0000644000175000017500000000434312214017422023113 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Viewlet tests """ import unittest from Testing.ZopeTestCase import FunctionalDocFileSuite from zope.interface import Interface from zope.interface import implements from zope.viewlet import interfaces from OFS.SimpleItem import SimpleItem class Content(SimpleItem): implements(Interface) class UnitTestSecurityPolicy: """ Stub out the existing security policy for unit testing purposes. """ # # Standard SecurityPolicy interface # def validate( self , accessed=None , container=None , name=None , value=None , context=None , roles=None , *args , **kw): return 1 def checkPermission( self, permission, object, context) : return 1 class ILeftColumn(interfaces.IViewletManager): """Left column of my page.""" class INewColumn(interfaces.IViewletManager): """Left column of my page.""" class WeightBasedSorting(object): def sort(self, viewlets): return sorted(viewlets, lambda x, y: cmp(x[1].weight, y[1].weight)) class Weather(object): weight = 0 class Stock(object): weight = 0 def getStockTicker(self): return u'SRC $5.19' class Sport(object): weight = 0 def __call__(self): return u'Red Sox vs. White Sox' class DynamicTempBox(object): weight = 0 city = {'name': 'Los Angeles, CA', 'temp': 78} def test_suite(): return unittest.TestSuite([ FunctionalDocFileSuite('README.txt'), FunctionalDocFileSuite('directives.txt'), ]) zope2.13-2.13.21/source/Zope2/src/Products/Five/viewlet/css_viewlet.pt0000644000175000017500000000030512214017422024265 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/viewlet/manager.py0000644000175000017500000000717112214017422023365 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Viewlet manager. """ from Acquisition import aq_base from AccessControl.ZopeGuards import guarded_hasattr import zope.interface import zope.security from zope.viewlet import interfaces from zope.viewlet.manager import ViewletManagerBase as origManagerBase from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile class ViewletManagerBase(origManagerBase): """A base class for Viewlet managers to work in Zope2""" template = None def __getitem__(self, name): """See zope.interface.common.mapping.IReadMapping""" # Find the viewlet viewlet = zope.component.queryMultiAdapter( (self.context, self.request, self.__parent__, self), interfaces.IViewlet, name=name) # If the viewlet was not found, then raise a lookup error if viewlet is None: raise zope.component.interfaces.ComponentLookupError( 'No provider with name `%s` found.' %name) # If the viewlet cannot be accessed, then raise an # unauthorized error if not guarded_hasattr(viewlet, 'render'): raise zope.security.interfaces.Unauthorized( 'You are not authorized to access the provider ' 'called `%s`.' %name) # Return the viewlet. return viewlet def filter(self, viewlets): """Sort out all content providers ``viewlets`` is a list of tuples of the form (name, viewlet). """ results = [] # Only return viewlets accessible to the principal # We need to wrap each viewlet in its context to make sure that # the object has a real context from which to determine owner # security. for name, viewlet in viewlets: if guarded_hasattr(viewlet, 'render'): results.append((name, viewlet)) return results def sort(self, viewlets): """Sort the viewlets. ``viewlets`` is a list of tuples of the form (name, viewlet). """ # By default, use the standard Python way of doing sorting. Unwrap the # objects first so that they are sorted as expected. This is dumb # but it allows the tests to have deterministic results. return sorted(viewlets, lambda x, y: cmp(aq_base(x[1]), aq_base(y[1]))) def ViewletManager(name, interface, template=None, bases=()): attrDict = {'__name__': name} if template is not None: attrDict['template'] = ZopeTwoPageTemplateFile(template) if ViewletManagerBase not in bases: # Make sure that we do not get a default viewlet manager mixin, if the # provided base is already a full viewlet manager implementation. if not (len(bases) == 1 and interfaces.IViewletManager.implementedBy(bases[0])): bases = bases + (ViewletManagerBase,) ViewletManager = type( '' % interface.getName(), bases, attrDict) zope.interface.classImplements(ViewletManager, interface) return ViewletManager zope2.13-2.13.21/source/Zope2/src/Products/Five/viewlet/meta.zcml0000644000175000017500000000106512214017422023212 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/viewlet/javascript_viewlet.pt0000644000175000017500000000015112214017422025642 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/CREDITS.txt0000644000175000017500000000235612214017421021557 0ustar arnauarnauFive contributors ----------------- - Martijn Faassen (faassen@infrae.com) - Sidnei da Silva (sidnei@awkly.org) - Philipp von Weitershausen (philikon@philikon.de) - Lennart Regebro (regebro@nuxeo.com) - Tres Seaver (tseaver@palladion.com) - Jan-Wijbrand Kolman (jw@infrae.com) - Stefan Holek (ssh@epy.co.at) - Florent Guillaume (fg@nuxeo.com) - Godefroid Chapelle (gotcha@bubblenet.be) - Andy Adiwidjaja (mail@adiwidjaja.com) - Stuart Bishop (stuart@stuartbishop.net) - Simon Eisenmann (simon@struktur.de) - Dieter Maurer (dieter@handshake.de) - Yvo Schubbe (y.2007-@wcm-solutions.de) - Malcolm Cleaton (malcolm@jamkit.com) - Tarek Ziadé (tziade@nuxeo.com) - Whit Morriss (whit@longnow.org) - Brian Sutherland (jinty@web.de) - Rocky Burt (rocky@serverzen.com) Thank you --------- Infrae for the initial development and continuing support. Martijn Faassen would like to thank ETH Zurich for their support and encouragement during the initial development of Five. Nuxeo for significant contributions to making Five usable in the real world. Dieter Maurer for use of code from TrustedExecutables within Five under the ZPL. The Five developers would like to thank the Zope developers, in particular Jim Fulton, for the mountain to stand on. zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/0000755000175000017500000000000012214017421021376 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/browser/configure.zcml0000644000175000017500000000172212214017421024250 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/menu.py0000644000175000017500000000160412214017421022715 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Some menu code """ import zope.deferredimport zope.deferredimport.deprecated( "The Five specific view has been made obsolete. Please use the " "view from zope.browsermenu directly.", MenuAccessView = 'zope.browsermenu.menu.MenuAccessView', ) zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/resource.py0000644000175000017500000001261012214017421023577 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Provide basic resource functionality """ import os import urllib import zope.browserresource.directory import zope.browserresource.file from zope.browserresource.file import File from zope.interface import implements from zope.traversing.browser import absoluteURL from zope.publisher.interfaces import NotFound from zope.publisher.interfaces.browser import IBrowserPublisher from zope.ptresource.ptresource import PageTemplate from Acquisition import aq_base from Products.Five.browser import BrowserView _marker = object() class Resource(object): """A mixin that changes the URL-rendering of resources (__call__). In zope.browserresource, resource URLs are of the form nearest_site/@@/resource_name. Since Zope 2 didn't have support for sites from the beginning of the Five integration, resource URLs in Zope 2 are of the form context/++resource++resource_name. TODO It would be good if that could be changed in the long term, thus making this mixin (and probably the other classes in this module) obsolete. """ def __call__(self): name = self.__name__ container = self.__parent__ url = urllib.unquote(absoluteURL(container, self.request)) if not isinstance(container, DirectoryResource): name = '++resource++%s' % name return "%s/%s" % (url, name) class PageTemplateResource(Resource, BrowserView): implements(IBrowserPublisher) def browserDefault(self, request): return self.render, () def publishTraverse(self, request, name): raise NotFound(self, name, request) def render(self): """Rendered content""" # ZPublisher might have called setBody with an incorrect URL # we definitely don't want that if we are plain html self.request.response.setBase(None) pt = self.context return pt(self.request) class FileResource(Resource, zope.browserresource.file.FileResource): pass class ResourceFactory: factory = None resource = None def __init__(self, name, path, resource_factory=None): self.__name = name self.__rsrc = self.factory(path, name) if resource_factory is not None: self.resource = resource_factory def __call__(self, request): resource = self.resource(self.__rsrc, request) return resource def _PageTemplate(self, path, name): # PageTemplate doesn't take a name parameter, # which makes it different from FileResource. # This is probably an error. template = PageTemplate(path) template.__name__ = name return template class PageTemplateResourceFactory(ResourceFactory): """A factory for Page Template resources""" factory = _PageTemplate resource = PageTemplateResource class FileResourceFactory(ResourceFactory): """A factory for File resources""" factory = File resource = FileResource class ImageResourceFactory(ResourceFactory): """A factory for Image resources""" factory = File resource = FileResource # we only need this class a context for DirectoryResource class Directory: def __init__(self, path, name): self.path = path self.__name__ = name class DirectoryResource(Resource, zope.browserresource.directory.DirectoryResource): resource_factories = { 'gif': ImageResourceFactory, 'png': ImageResourceFactory, 'jpg': ImageResourceFactory, 'pt': PageTemplateResourceFactory, 'zpt': PageTemplateResourceFactory, 'html': PageTemplateResourceFactory, 'htm': PageTemplateResourceFactory, } default_factory = FileResourceFactory def getId(self): name = self.__name__ if not name.startswith('++resource++'): name = '++resource++%s' % self.__name__ return name def get(self, name, default=_marker): path = self.context.path filename = os.path.join(path, name) isfile = os.path.isfile(filename) isdir = os.path.isdir(filename) if not (isfile or isdir): if default is _marker: raise KeyError(name) return default if isfile: ext = name.split('.')[-1] factory = self.resource_factories.get(ext, self.default_factory) else: factory = DirectoryResourceFactory resource = factory(name, filename)(self.request) resource.__name__ = name resource.__parent__ = self # We need to propagate security so that restrictedTraverse() will # work if hasattr(aq_base(self), '__roles__'): resource.__roles__ = self.__roles__ return resource class DirectoryResourceFactory(ResourceFactory): factory = Directory resource = DirectoryResource zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/metaconfigure.py0000644000175000017500000004434312214017421024610 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Browser directives Directives to emulate the 'http://namespaces.zope.org/browser' namespace in ZCML known from zope.app. """ import os from inspect import ismethod from zope import component from zope.interface import implements from zope.interface import Interface from zope.component.zcml import handler from zope.component.interface import provideInterface from zope.configuration.exceptions import ConfigurationError from zope.publisher.interfaces import NotFound from zope.publisher.interfaces.browser import IDefaultBrowserLayer from zope.publisher.interfaces.browser import IBrowserPublisher from zope.publisher.interfaces.browser import IBrowserRequest from zope.security.zcml import Permission import zope.browserpage.metaconfigure from zope.browserpage.metaconfigure import providesCallable from zope.browserpage.metaconfigure import _handle_menu from zope.browserpage.metaconfigure import _handle_for from zope.browserpage.metadirectives import IViewDirective from AccessControl.class_init import InitializeClass from AccessControl.security import getSecurityInfo from AccessControl.security import protectClass from AccessControl.security import protectName from AccessControl.security import CheckerPrivateId from Products.Five.browser import BrowserView from Products.Five.browser.resource import FileResourceFactory from Products.Five.browser.resource import ImageResourceFactory from Products.Five.browser.resource import PageTemplateResourceFactory from Products.Five.browser.resource import DirectoryResourceFactory from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from Products.Five.metaclass import makeClass def page(_context, name, permission, for_, layer=IDefaultBrowserLayer, template=None, class_=None, allowed_interface=None, allowed_attributes=None, attribute='__call__', menu=None, title=None, ): name = str(name) # De-unicode _handle_menu(_context, menu, title, [for_], name, permission) if not (class_ or template): raise ConfigurationError("Must specify a class or template") if allowed_attributes is None: allowed_attributes = [] if allowed_interface is not None: for interface in allowed_interface: allowed_attributes.extend(interface.names(all=True)) if attribute != '__call__': if template: raise ConfigurationError( "Attribute and template cannot be used together.") if not class_: raise ConfigurationError( "A class must be provided if attribute is used") if template: template = os.path.abspath(str(_context.path(template))) if not os.path.isfile(template): raise ConfigurationError("No such file", template) if class_: if attribute != '__call__': if not hasattr(class_, attribute): raise ConfigurationError( "The provided class doesn't have the specified attribute " ) cdict = getSecurityInfo(class_) cdict['__name__'] = name if template: new_class = makeClassForTemplate(template, bases=(class_, ), cdict=cdict, name=name) elif attribute != "__call__": # we're supposed to make a page for an attribute (read: # method) and it's not __call__. We thus need to create a # new class using our mixin for attributes. cdict.update({'__page_attribute__': attribute}) new_class = makeClass(class_.__name__, (class_, ViewMixinForAttributes), cdict) # in case the attribute does not provide a docstring, # ZPublisher refuses to publish it. So, as a workaround, # we provide a stub docstring func = getattr(new_class, attribute) if not func.__doc__: # cannot test for MethodType/UnboundMethod here # because of ExtensionClass if hasattr(func, 'im_func'): # you can only set a docstring on functions, not # on method objects func = func.im_func func.__doc__ = "Stub docstring to make ZPublisher work" else: # we could use the class verbatim here, but we'll execute # some security declarations on it so we really shouldn't # modify the original. So, instead we make a new class # with just one base class -- the original new_class = makeClass(class_.__name__, (class_, BrowserView), cdict) else: # template new_class = makeClassForTemplate(template, name=name) _handle_for(_context, for_) _context.action( discriminator = ('view', for_, name, IBrowserRequest, layer), callable = handler, args = ('registerAdapter', new_class, (for_, layer), Interface, name, _context.info), ) _context.action( discriminator = ('five:protectClass', new_class), callable = protectClass, args = (new_class, permission) ) if allowed_attributes: for attr in allowed_attributes: _context.action( discriminator = ('five:protectName', new_class, attr), callable = protectName, args = (new_class, attr, permission) ) # Make everything else private allowed = [attribute] + (allowed_attributes or []) private_attrs = [name for name in dir(new_class) if (not name.startswith('_')) and (name not in allowed) and ismethod(getattr(new_class, name))] for attr in private_attrs: _context.action( discriminator = ('five:protectName', new_class, attr), callable = protectName, args = (new_class, attr, CheckerPrivateId) ) # Protect the class _context.action( discriminator = ('five:initialize:class', new_class), callable = InitializeClass, args = (new_class,) ) class pages(zope.browserpage.metaconfigure.pages): def page(self, _context, name, attribute='__call__', template=None, menu=None, title=None): return page(_context, name=name, attribute=attribute, template=template, menu=menu, title=title, **(self.opts)) # view (named view with pages) class IFiveViewDirective(IViewDirective): permission = Permission( title=u"Permission", description=u"The permission needed to use the view.", required=False, ) class view(zope.browserpage.metaconfigure.view): def __call__(self): (_context, name, for_, permission, layer, class_, allowed_interface, allowed_attributes) = self.args name = str(name) # De-unicode required = {} cdict = {} pages = {} for pname, attribute, template in self.pages: if template: cdict[pname] = ViewPageTemplateFile(template) if attribute and attribute != name: cdict[attribute] = cdict[pname] else: if not hasattr(class_, attribute): raise ConfigurationError("Undefined attribute", attribute) attribute = attribute or pname required[pname] = permission pages[pname] = attribute # This should go away, but noone seems to remember what to do. :-( if hasattr(class_, 'publishTraverse'): def publishTraverse(self, request, name, pages=pages, getattr=getattr): if name in pages: return getattr(self, pages[name]) view = component.queryMultiAdapter((self, request), name=name, default=None) if view is not None: return view m = class_.publishTraverse.__get__(self) return m(request, name) else: def publishTraverse(self, request, name, pages=pages, getattr=getattr): if name in pages: return getattr(self, pages[name]) view = component.queryMultiAdapter((self, request), name=name, default=None) if view is not None: return view raise NotFound(self, name, request) cdict['publishTraverse'] = publishTraverse if not hasattr(class_, 'browserDefault'): if self.default or self.pages: default = self.default or self.pages[0][0] cdict['browserDefault'] = ( lambda self, request, default=default: (self, (default, )) ) elif providesCallable(class_): cdict['browserDefault'] = ( lambda self, request: (self, ()) ) if class_ is not None: bases = (class_, ViewMixinForTemplates) else: bases = (ViewMixinForTemplates,) try: cname = str(name) except: cname = "GeneratedClass" cdict['__name__'] = name newclass = makeClass(cname, bases, cdict) _handle_for(_context, for_) if self.provides is not None: _context.action( discriminator = None, callable = provideInterface, args = ('', self.provides) ) _context.action( discriminator = ('view', for_, name, IBrowserRequest, layer, self.provides), callable = handler, args = ('registerAdapter', newclass, (for_, layer), self.provides, name, _context.info), ) # Security _context.action( discriminator = ('five:protectClass', newclass), callable = protectClass, args = (newclass, permission) ) if allowed_attributes: for attr in allowed_attributes: _context.action( discriminator = ('five:protectName', newclass, attr), callable = protectName, args = (newclass, attr, permission) ) # Make everything else private allowed = allowed_attributes or [] private_attrs = [name for name in dir(newclass) if (not name.startswith('_')) and (name not in allowed) and ismethod(getattr(newclass, name))] for attr in private_attrs: _context.action( discriminator = ('five:protectName', newclass, attr), callable = protectName, args = (newclass, attr, CheckerPrivateId, False) ) # Protect the class _context.action( discriminator = ('five:initialize:class', newclass), callable = InitializeClass, args = (newclass,) ) _factory_map = {'image':{'prefix':'ImageResource', 'count':0, 'factory':ImageResourceFactory}, 'file':{'prefix':'FileResource', 'count':0, 'factory':FileResourceFactory}, 'template':{'prefix':'PageTemplateResource', 'count':0, 'factory':PageTemplateResourceFactory} } def resource(_context, name, layer=IDefaultBrowserLayer, permission='zope.Public', file=None, image=None, template=None): if ((file and image) or (file and template) or (image and template) or not (file or image or template)): raise ConfigurationError( "Must use exactly one of file or image or template" "attributes for resource directives" ) res = file or image or template res_type = ((file and 'file') or (image and 'image') or (template and 'template')) factory_info = _factory_map.get(res_type) factory_info['count'] += 1 res_factory = factory_info['factory'] class_name = '%s%s' % (factory_info['prefix'], factory_info['count']) new_class = makeClass(class_name, (res_factory.resource,), {}) factory = res_factory(name, res, resource_factory=new_class) _context.action( discriminator = ('resource', name, IBrowserRequest, layer), callable = handler, args = ('registerAdapter', factory, (layer,), Interface, name, _context.info), ) _context.action( discriminator = ('five:protectClass', new_class), callable = protectClass, args = (new_class, permission) ) _context.action( discriminator = ('five:initialize:class', new_class), callable = InitializeClass, args = (new_class,) ) _rd_map = {ImageResourceFactory:{'prefix':'DirContainedImageResource', 'count':0}, FileResourceFactory:{'prefix':'DirContainedFileResource', 'count':0}, PageTemplateResourceFactory:{'prefix':'DirContainedPTResource', 'count':0}, DirectoryResourceFactory:{'prefix':'DirectoryResource', 'count':0} } def resourceDirectory(_context, name, directory, layer=IDefaultBrowserLayer, permission='zope.Public'): if not os.path.isdir(directory): raise ConfigurationError( "Directory %s does not exist" % directory ) resource = DirectoryResourceFactory.resource f_cache = {} resource_factories = dict(resource.resource_factories) resource_factories['default'] = resource.default_factory for ext, factory in resource_factories.items(): if f_cache.get(factory) is not None: continue factory_info = _rd_map.get(factory) factory_info['count'] += 1 class_name = '%s%s' % (factory_info['prefix'], factory_info['count']) factory_name = '%s%s' % (factory.__name__, factory_info['count']) f_resource = makeClass(class_name, (factory.resource,), {}) f_cache[factory] = makeClass(factory_name, (factory,), {'resource':f_resource}) for ext, factory in resource_factories.items(): resource_factories[ext] = f_cache[factory] default_factory = resource_factories['default'] del resource_factories['default'] cdict = {'resource_factories':resource_factories, 'default_factory':default_factory} factory_info = _rd_map.get(DirectoryResourceFactory) factory_info['count'] += 1 class_name = '%s%s' % (factory_info['prefix'], factory_info['count']) dir_factory = makeClass(class_name, (resource,), cdict) factory = DirectoryResourceFactory(name, directory, resource_factory=dir_factory) new_classes = [dir_factory, ] + [f.resource for f in f_cache.values()] _context.action( discriminator = ('resource', name, IBrowserRequest, layer), callable = handler, args = ('registerAdapter', factory, (layer,), Interface, name, _context.info), ) for new_class in new_classes: _context.action( discriminator = ('five:protectClass', new_class), callable = protectClass, args = (new_class, permission) ) _context.action( discriminator = ('five:initialize:class', new_class), callable = InitializeClass, args = (new_class,) ) class ViewMixinForAttributes(BrowserView, zope.browserpage.metaconfigure.simple): # For some reason, the 'simple' baseclass doesn't implement this # mandatory method (see https://bugs.launchpad.net/zope3/+bug/129296) def browserDefault(self, request): return getattr(self, self.__page_attribute__), () # __call__ should have the same signature as the original method @property def __call__(self): return getattr(self, self.__page_attribute__) class ViewMixinForTemplates(BrowserView): # Cloned from zope.app.pagetemplate.simpleviewclass.simple implements(IBrowserPublisher) def browserDefault(self, request): return self, () def publishTraverse(self, request, name): if name == 'index.html': return self.index raise NotFound(self, name, request) def __getitem__(self, name): if name == 'macros': return self.index.macros return self.index.macros[name] def __call__(self, *args, **kw): return self.index(*args, **kw) def makeClassForTemplate(filename, globals=None, used_for=None, bases=(), cdict=None, name=u''): # XXX needs to deal with security from the bases? if cdict is None: cdict = {} cdict.update({'index': ViewPageTemplateFile(filename, globals), '__name__': name}) bases += (ViewMixinForTemplates,) class_ = makeClass("SimpleViewClass from %s" % filename, bases, cdict) if used_for is not None: class_.__used_for__ = used_for return class_ zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/__init__.py0000644000175000017500000000300112214017421023501 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Provide basic browser functionality """ import Acquisition import zope.publisher.browser from Products.Five.bbb import AcquisitionBBB class BrowserView(zope.publisher.browser.BrowserView, AcquisitionBBB): # Use an explicit __init__ to work around problems with magically inserted # super classes when using BrowserView as a base for viewlets. def __init__(self, context, request): self.context = context self.request = request # Classes which are still based on Acquisition and access # self.context in a method need to call aq_inner on it, or get a # funky aq_chain. We do this here for BBB friendly purposes. def __getParent(self): return getattr(self, '_parent', Acquisition.aq_inner(self.context)) def __setParent(self, parent): self._parent = parent aq_parent = __parent__ = property(__getParent, __setParent) zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/adding.py0000644000175000017500000002041312214017421023176 0ustar arnauarnau############################################################################## # # Copyright (c) 2002-2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Adding View The Adding View is used to add new objects to a container. It is sort of a factory screen. (original: zope.app.container.browser.adding) """ __docformat__ = 'restructuredtext' from zope.browser.interfaces import IAdding from zope.browsermenu.menu import getMenu from zope.component import getMultiAdapter from zope.component import getUtility from zope.component import queryMultiAdapter from zope.component import queryUtility from zope.component.interfaces import IFactory from zope.container.constraints import checkFactory from zope.container.constraints import checkObject from zope.container.i18n import ZopeMessageFactory as _ from zope.container.interfaces import IContainerNamesContainer from zope.container.interfaces import INameChooser from zope.event import notify from zope.exceptions.interfaces import UserError from zope.interface import implements from zope.lifecycleevent import ObjectCreatedEvent from zope.publisher.interfaces import IPublishTraverse from zope.traversing.browser.absoluteurl import absoluteURL from zExceptions import BadRequest from OFS.SimpleItem import SimpleItem from Products.Five import BrowserView from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile class Adding(BrowserView): implements(IAdding, IPublishTraverse) def add(self, content): """See zope.browser.interfaces.IAdding """ container = self.context name = self.contentName chooser = INameChooser(container) # check precondition checkObject(container, name, content) if IContainerNamesContainer.providedBy(container): # The container picks its own names. # We need to ask it to pick one. name = chooser.chooseName(self.contentName or '', content) else: request = self.request name = request.get('add_input_name', name) if name is None: name = chooser.chooseName(self.contentName or '', content) elif name == '': name = chooser.chooseName('', content) else: # Invoke the name chooser even when we have a # name. It'll do useful things with it like converting # the incoming unicode to an ASCII string. name = chooser.chooseName(name, content) content.id = name container._setObject(name, content) self.contentName = name # Set the added object Name return container._getOb(name) contentName = None # usually set by Adding traverser def nextURL(self): """See zope.browser.interfaces.IAdding""" # XXX this is definitely not right for all or even most uses # of Five, but can be overridden by an AddView subclass return absoluteURL(self.context, self.request) + '/manage_main' # set in BrowserView.__init__ request = None context = None def publishTraverse(self, request, name): """See zope.publisher.interfaces.IPublishTraverse""" if '=' in name: view_name, content_name = name.split("=", 1) self.contentName = content_name if view_name.startswith('@@'): view_name = view_name[2:] return getMultiAdapter((self, request), name=view_name) if name.startswith('@@'): view_name = name[2:] else: view_name = name view = queryMultiAdapter((self, request), name=view_name) if view is not None: return view factory = queryUtility(IFactory, name) if factory is None: return super(Adding, self).publishTraverse(request, name) return factory def action(self, type_name='', id=''): if not type_name: raise UserError(_(u"You must select the type of object to add.")) if type_name.startswith('@@'): type_name = type_name[2:] if '/' in type_name: view_name = type_name.split('/', 1)[0] else: view_name = type_name if queryMultiAdapter((self, self.request), name=view_name) is not None: url = "%s/%s=%s" % ( absoluteURL(self, self.request), type_name, id) self.request.response.redirect(url) return if not self.contentName: self.contentName = id factory = getUtility(IFactory, type_name) content = factory() notify(ObjectCreatedEvent(content)) self.add(content) self.request.response.redirect(self.nextURL()) def nameAllowed(self): """Return whether names can be input by the user.""" return not IContainerNamesContainer.providedBy(self.context) menu_id = None index = ViewPageTemplateFile("adding.pt") def addingInfo(self): """Return menu data. This is sorted by title. """ container = self.context result = [] for menu_id in (self.menu_id, 'zope.app.container.add'): if not menu_id: continue for item in getMenu(menu_id, self, self.request): extra = item.get('extra') if extra: factory = extra.get('factory') if factory: factory = getUtility(IFactory, factory) if not checkFactory(container, None, factory): continue elif item['extra']['factory'] != item['action']: item['has_custom_add_view']=True result.append(item) result.sort(lambda a, b: cmp(a['title'], b['title'])) return result def isSingleMenuItem(self): "Return whether there is single menu item or not." return len(self.addingInfo()) == 1 def hasCustomAddView(self): "This should be called only if there is `singleMenuItem` else return 0" if self.isSingleMenuItem(): menu_item = self.addingInfo()[0] if 'has_custom_add_view' in menu_item: return True return False class ContentAdding(Adding, SimpleItem): menu_id = "add_content" class ObjectManagerNameChooser: """A name chooser for a Zope object manager. """ implements(INameChooser) def __init__(self, context): self.context = context def checkName(self, name, object): # ObjectManager can only deal with ASCII names. Specially # ObjectManager._checkId can only deal with strings. try: name = name.encode('ascii') except UnicodeDecodeError: raise UserError, "Id must contain only ASCII characters." try: self.context._checkId(name, allow_dup=False) except BadRequest, e: msg = ' '.join(e.args) or "Id is in use or invalid" raise UserError, msg def chooseName(self, name, object): if not name: name = object.__class__.__name__ else: try: name = name.encode('ascii') except UnicodeDecodeError: raise UserError, "Id must contain only ASCII characters." dot = name.rfind('.') if dot >= 0: suffix = name[dot:] name = name[:dot] else: suffix = '' n = name + suffix i = 0 while True: i += 1 try: self.context._getOb(n) except (AttributeError, KeyError): break n = name + '-' + str(i) + suffix # Make sure the name is valid. We may have started with # something bad. self.checkName(n, object) return n zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/meta.zcml0000644000175000017500000000556412214017421023225 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/decode.py0000644000175000017500000000547512214017421023206 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ Utility functions for decoding browser input and setting the output encoding. """ from ZPublisher.HTTPRequest import isCGI_NAMEs from zope.i18n.interfaces import IUserPreferredCharsets # taken and adapted from zope.publisher.browser.BrowserRequest def _decode(text, charsets): """Try to decode the text using one of the available charsets. """ for charset in charsets: try: text = unicode(text, charset) break except UnicodeError: pass return text def processInputValue(value, charsets): """Recursively look for values (e.g. elements of lists, tuples or dicts) and attempt to decode. """ if isinstance(value, list): return [processInputValue(v, charsets) for v in value] elif isinstance(value, tuple): return tuple([processInputValue(v, charsets) for v in value]) elif isinstance(value, dict): for k, v in value.items(): value[k] = processInputValue(v, charsets) return value elif isinstance(value, str): return _decode(value, charsets) else: return value def processInputs(request, charsets=None): """Process the values in request.form to decode strings to unicode, using the passed-in list of charsets. If none are passed in, look up the user's preferred charsets. The default is to use utf-8. """ if charsets is None: envadapter = IUserPreferredCharsets(request, None) if envadapter is None: charsets = ['utf-8'] else: charsets = envadapter.getPreferredCharsets() or ['utf-8'] for name, value in request.form.items(): if not (name in isCGI_NAMEs or name.startswith('HTTP_')): request.form[name] = processInputValue(value, charsets) def setPageEncoding(request): """Set the encoding of the form page via the Content-Type header. ZPublisher uses the value of this header to determine how to encode unicode data for the browser. """ envadapter = IUserPreferredCharsets(request) charsets = envadapter.getPreferredCharsets() or ['utf-8'] request.RESPONSE.setHeader( 'Content-Type', 'text/html; charset=%s' % charsets[0]) zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/adding.pt0000644000175000017500000000023712214017421023173 0ustar arnauarnau

    + screen not yet supported by Five

    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/0000755000175000017500000000000012214017421022540 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/flamingo.pt0000644000175000017500000000014712214017421024703 0ustar arnauarnau

    Replaced

    Replaced

    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/falcon.pt0000644000175000017500000000004212214017421024343 0ustar arnauarnau

    The falcon has taken flight

    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/provider_namespace.pt0000644000175000017500000000065512214017421026761 0ustar arnauarnau

    My Web Page

    Content here
    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/provider_error.pt0000644000175000017500000000015112214017421026145 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_zope3security.py0000644000175000017500000001164712214017421027012 0ustar arnauarnau def test_check_permission(): """Code (in Zope packages) often uses zope.security.management.checkPermission to determine whether the current user has a certain permission in a given context. Five inserts its own interaction that assures that such calls still work. >>> configure_zcml = ''' ... ... ... ... ... ... ''' >>> import Products.Five >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_string(configure_zcml) In order to be able to traverse to the PageTemplate view, we need a traversable object: >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') Now we access a page that uses zope.security.management.checkPermission(). We see it works as expected: >>> from Testing.testbrowser import Browser >>> browser = Browser() >>> browser.open('http://localhost/test_folder_1_/testoid/@@zope3security.html?permission=zope2.View') >>> print browser.contents Yes, you have the 'zope2.View' permission. >>> browser.open('http://localhost/test_folder_1_/testoid/@@zope3security.html?permission=zope2.DeleteObjects') >>> print browser.contents No, you don't have the 'zope2.DeleteObjects' permission. Clean up: >>> from zope.component.testing import tearDown >>> tearDown() """ def test_allowed_interface(): """This test demonstrates that allowed_interface security declarations work as expected. >>> from zope.component.testing import setUp, tearDown >>> setUp() Before we can make security declarations through ZCML, we need to register the directive and the permission: >>> import AccessControl >>> from zope.configuration.xmlconfig import XMLConfig >>> XMLConfig('meta.zcml', AccessControl)() >>> import Products.Five.browser >>> XMLConfig('meta.zcml', Products.Five.browser)() >>> XMLConfig('permissions.zcml', AccessControl)() Now we provide some ZCML declarations for ``Dummy1``: >>> from StringIO import StringIO >>> configure_zcml = StringIO(''' ... ... ... ... ''') >>> from zope.configuration.xmlconfig import xmlconfig >>> xmlconfig(configure_zcml) We are going to check that roles are correctly setup, so we need getRoles. >>> from AccessControl.ZopeSecurityPolicy import getRoles >>> from AccessControl import ACCESS_PRIVATE Due to the nasty voodoo involved in Five's handling of view classes, browser:page doesn't apply security to Dummy1, but rather to the "magic" view class that is created at ZCML parse time. That means we can't just instanciate with Dummy1() directly and expect a security-aware instance :(. Instead, we'll have to actually lookup the view. The view was declared for "*", so we just use an instance of Dummy1 ;-). Instanciate a Dummy1 object to test with. >>> from AccessControl.tests.testZCML import Dummy1 >>> dummy1 = Dummy1() >>> from zope.component import getMultiAdapter >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> view = getMultiAdapter((dummy1, request), name="testview") As 'foo' is defined in IDummy, it should have the 'Manager' role. >>> getRoles(view, 'foo', view.foo, ('Def',)) ('Manager',) As 'wot' is not defined in IDummy, it should be private. >>> getRoles(view, 'wot', view.wot, ('Def',)) is ACCESS_PRIVATE True But 'superMethod' is defined on IDummy by inheritance from ISuperDummy, and so should have the 'Manager' role setup. >>> getRoles(view, 'superMethod', view.superMethod, ('Def',)) ('Manager',) >>> tearDown() """ def test_suite(): from Testing.ZopeTestCase import FunctionalDocTestSuite from doctest import ELLIPSIS return FunctionalDocTestSuite(optionflags=ELLIPSIS) zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/tales_traversal.pt0000644000175000017500000000022512214017421026277 0ustar arnauarnau

    dunno

    dunno

    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_recurse.py0000644000175000017500000000367412214017421025633 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test default view recursion """ def test_recursion(): """ Test recursion This test makes sure that recursion is avoided for view lookup. First, we need to set up a stub interface... >>> from zope.interface import Interface, implements >>> class IRecurse(Interface): ... pass ... and a class that is callable and has a view method: >>> from OFS.Traversable import Traversable >>> class Recurse(Traversable): ... implements(IRecurse) ... def view(self): ... return self() ... def __call__(self): ... return 'foo' ... Now we register a default view name for the class: >>> from zope.component import provideAdapter >>> from zope.publisher.interfaces.browser import IBrowserRequest >>> from zope.publisher.interfaces import IDefaultViewName >>> provideAdapter(u'view', (IRecurse, IBrowserRequest), IDefaultViewName) Here comes the actual test: >>> ob = Recurse() >>> ob.view() 'foo' >>> ob() 'foo' Clean up adapter registry and monkey patches to classes: >>> from zope.testing.cleanup import cleanUp >>> cleanUp() """ def test_suite(): from Testing.ZopeTestCase import ZopeDocTestSuite return ZopeDocTestSuite() zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/template_variables.pt0000644000175000017500000000247512214017421026760 0ustar arnauarnauView is a view: Context is testoid: Context.aq_parent is test_folder_1_: Container is context: Here is context: Nothing is None: Default works: True Root is the application: Template is a template: Traverse_subpath exists and is empty: Request is a request: User is manager: Options exist: Attrs exist: Repeat exists: Loop exists: Modules exists: zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_resource.py0000644000175000017500000000213212214017421025776 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test browser resources """ import unittest def test_suite(): from Testing.ZopeTestCase import FunctionalDocFileSuite from Testing.ZopeTestCase import ZopeDocFileSuite return unittest.TestSuite(( ZopeDocFileSuite('resource.txt', package='Products.Five.browser.tests'), FunctionalDocFileSuite('resource_ftest.txt', package='Products.Five.browser.tests'), )) zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/aqlegacy_ftest.txt0000644000175000017500000001362112214017421026277 0ustar arnauarnauTesting legacy browser views ============================ This test tests publishing aspects of browser pages. Let's register some: >>> import Products.Five.browser.tests >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config('aqlegacy.zcml', package=Products.Five.browser.tests) >>> from Testing.testbrowser import Browser >>> browser = Browser() >>> browser.handleErrors = False Acquisition API legacy on BrowserView ------------------------------------- Let's make sure that accessing those old aq_* properties on browser views still works (the printed output is the aq_chain of the view): >>> browser.open('http://localhost/test_folder_1_/attributes') >>> print browser.contents [, , , ] The same goes for browser views that just mix in Acquisition.Explicit: >>> browser.open('http://localhost/test_folder_1_/explicitattributes') >>> print browser.contents [, , , ] Let's do some more manual tests with the view object. But first we must get it: >>> from zope.component import getMultiAdapter >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> view = getMultiAdapter((self.folder, request), name='attributes') Let's check for the various aq_* attributes: >>> view.aq_parent == self.folder True >>> view.aq_inner == view True >>> view.aq_base == view True >>> view.aq_self == view True Let's try to acquire something from the root folder: >>> button = view.aq_acquire('ZopeAttributionButton') >>> print button() Powered by Zope Let's check that we're in the right context: >>> view.aq_inContextOf(self.folder) 1 >>> view.aq_inContextOf(self.app) 1 >>> view.aq_inContextOf(object()) 0 Views also still support the __of__ protocol, at least pro forma: >>> view == view.__of__(self.app) True Acquisition API legacy on a browser view's template --------------------------------------------------- A view's template will also support the various aq_* attributes: >>> view = getMultiAdapter((self.folder, request), name='template') >>> template = view.template >>> template.aq_parent == view True >>> template.aq_inner == template True >>> template.aq_base == template True >>> template.aq_self == template True >>> button = template.aq_acquire('ZopeAttributionButton') >>> print button() Powered by Zope Mixing in Acquisition.{Ex|Im}plicit ----------------------------------- Let's make sure that mixing in Acquisition.Explicit or Implicit won't mess up your views (even though you should never have done it in the first place...): >>> browser.open('http://localhost/test_folder_1_/explicit') >>> print browser.contents Explicit >>> browser.open('http://localhost/test_folder_1_/explicit_zcmltemplate') >>> print browser.contents

    The falcon has taken flight

    >>> browser.open('http://localhost/test_folder_1_/explicit_template') >>> print browser.contents

    The falcon has taken flight

    >>> browser.open('http://localhost/test_folder_1_/implicit') >>> print browser.contents Implicit >>> browser.open('http://localhost/test_folder_1_/implicit_template') >>> print browser.contents

    The falcon has taken flight

    >>> browser.open('http://localhost/test_folder_1_/implicit_zcmltemplate') >>> print browser.contents

    The falcon has taken flight

    Testing legacy content providers and viewlets ============================================= >>> browser.open('http://localhost/test_folder_1_/aqlegacyprovider') >>> print browser.contents

    Content provider inheriting from Explicit

    >>> browser.open('http://localhost/test_folder_1_/aqlegacymanager') >>> print browser.contents

    BrowserView viewlet Viewlet inheriting from Explicit

    Testing namespace traversal =========================== Namespace traversal can turn up objects during traversal without attribute access. That means they might not be wrapped by default. Here we make sure that they are wrapped and that things like the request can be acquired. First let's try ``restrictedTraverse()``: >>> foo = self.folder.restrictedTraverse('++aqlegacy++foo') >>> import Acquisition >>> Acquisition.aq_acquire(foo, 'REQUEST') Now let's try URL traversal: >>> browser.open('http://localhost/test_folder_1_/++aqlegacy++foo/index.html') >>> print browser.contents

    The falcon has taken flight

    Testing keyword arguments ========================= ViewPageTemplateFile's take arbitrary keyword arguments: >>> view = getMultiAdapter((self.folder, request), name='template') >>> template = view.template >>> print template(foo=1, bar=2)

    The falcon has taken flight

    Passing in an argument called instance was supported by the old Five version of ViewPageTemplateFile, so we still need to support it. >>> print template(instance='allowed')

    The falcon has taken flight

    No arguments required ===================== ViewPageTemplateFile's require no arguments, but you can only use them as class variables: >>> view = getMultiAdapter((self.folder, request), name='template_two') >>> print view() Traceback (most recent call last): ... TypeError: __call__() takes at least 2 arguments (1 given) Clean up -------- >>> from zope.component.testing import tearDown >>> tearDown() zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_i18n.py0000644000175000017500000000564012214017421024735 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unit tests for the i18n framework """ def test_zpt_i18n(): """ Test i18n functionality in ZPTs >>> configure_zcml = ''' ... ... ... ... ... ... ... ... ''' >>> import Products.Five >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_string(configure_zcml) In order to be able to traverse to the PageTemplate view, we need a traversable object: >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') We tell Zope to translate the messages by passing the ``Accept-Language`` header which is processed by the ``IUserPreferredLangauges`` adapter: >>> print http(r''' ... GET /test_folder_1_/testoid/@@i18n.html HTTP/1.1 ... Accept-Language: de ... ''') HTTP/1.1 200 OK ...

    Dies ist eine Nachricht

    Dies ist eine explizite Nachricht

    Dies sind 4 Nachrichten

    Dies sind 4 explizite Nachrichten

    Dies ist eine Nachricht

    Dies ist eine Nachricht

    ... Clean up: >>> from zope.component.testing import tearDown >>> tearDown() """ def test_suite(): from Testing.ZopeTestCase import FunctionalDocTestSuite from doctest import ELLIPSIS return FunctionalDocTestSuite(optionflags=ELLIPSIS) zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/i18n.py0000644000175000017500000000152012214017421023667 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test i18n. """ from zope.i18nmessageid import MessageFactory _ = MessageFactory('fivetest') from Products.Five import BrowserView class I18nView(BrowserView): this_is_a_message = _(u'This is a message') zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/security.pt0000644000175000017500000000042012214017421024750 0ustar arnauarnau
    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/provider.zcml0000644000175000017500000000234512214017421025265 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/adding.txt0000644000175000017500000000333012214017421024526 0ustar arnauarnau============ Adding tests ============ ObjectManagerNameChooser ------------------------ First we need to import and setup some prerequisites: >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder >>> from Products.Five.browser.adding import ObjectManagerNameChooser >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> chooser = ObjectManagerNameChooser(self.folder) Now we can start. ``INameChooser`` defines a ``checkName()`` method that checks whether a given name is valid in the container or not. Under the hood, ``ObjectManagerNameChooser`` calls ``_checkId()`` of the object manager. Valid names/ids are those that aren't in use yet and don't contain invalid characters. >>> chooser.checkName('abc', object()) >>> chooser.checkName('testoid', object()) Traceback (most recent call last): ... UserError: The id "testoid" is invalid - it is already in use. >>> chooser.checkName('slash/slash', object()) Traceback (most recent call last): ... UserError: The id "slash/slash" contains characters illegal in URLs. ``INameChooser`` also promises us a ``chooseName()`` method that chooses a name for us in case we don't have one or that chooses a different name in case the one we chose was invalid. >>> chooser.chooseName('', self.folder.testoid) 'FiveTraversableFolder' >>> chooser.chooseName('abc', self.folder.testoid) 'abc' >>> chooser.chooseName('testoid', self.folder.testoid) 'testoid-1' Of course, if we start out with something bad, it isn't going to become good automagically: >>> chooser.chooseName('slash/slash', object()) Traceback (most recent call last): ... UserError: The id "slash/slash" contains characters illegal in URLs. zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/pattern.png0000644000175000017500000000025212214017421024722 0ustar arnauarnau‰PNG  IHDRlæ'üPLTEÌÌÌÿÿÿÓv pHYs  šœtIMEÓ \.ÔtEXtCommentCreated with The GIMPïd%nIDATxÚcp`XÀàf!¹ ¤IEND®B`‚zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_traversable.py0000644000175000017500000002327712214017421026476 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test Five-traversable classes """ class SimpleClass(object): """Class with no __bobo_traverse__.""" def test_traversable(): """ Test the behaviour of Five-traversable classes. >>> import Products.Five >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) ``SimpleContent`` is a traversable class by default. Its fallback traverser should raise NotFound when traversal fails. (Note: If we return None in __fallback_traverse__, this test passes but for the wrong reason: None doesn't have a docstring so BaseRequest raises NotFoundError.) >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> print http(r''' ... GET /test_folder_1_/testoid/doesntexist HTTP/1.1 ... ''') HTTP/1.1 404 Not Found ... Now let's take class which already has a __bobo_traverse__ method. Five should correctly use that as a fallback. >>> configure_zcml = ''' ... ... ... ... ... ... ... ... ... ... ... ''' >>> zcml.load_string(configure_zcml) >>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent >>> info = manage_addFancyContent(self.folder, 'fancy', '') In the following test we let the original __bobo_traverse__ method kick in: >>> print http(r''' ... GET /test_folder_1_/fancy/something-else HTTP/1.1 ... ''') HTTP/1.1 200 OK ... something-else Once we have a custom __bobo_traverse__ method, though, it always takes over. Therefore, unless it raises AttributeError or KeyError, it will be the only way traversal is done. >>> print http(r''' ... GET /test_folder_1_/fancy/fancyview HTTP/1.1 ... ''') HTTP/1.1 200 OK ... fancyview As said, if the original __bobo_traverse__ method *does* raise AttributeError or KeyError, we can get normal view look-up. Other exceptions are passed through just fine: >>> print http(r''' ... GET /test_folder_1_/fancy/raise-attributeerror HTTP/1.1 ... ''') HTTP/1.1 200 OK ... Fancy, fancy >>> print http(r''' ... GET /test_folder_1_/fancy/raise-keyerror HTTP/1.1 ... ''') HTTP/1.1 200 OK ... Fancy, fancy >>> print http(r''' ... GET /test_folder_1_/fancy/raise-valueerror HTTP/1.1 ... ''', handle_errors=False) Traceback (most recent call last): ... ValueError: ... Five's traversable monkeypatches the __bobo_traverse__ method to do view lookup and then delegates back to the original __bobo_traverse__ or direct attribute/item lookup to do normal lookup. In the Zope 2 ZPublisher, an object with a __bobo_traverse__ will not do attribute lookup unless the __bobo_traverse__ method itself does it (i.e. the __bobo_traverse__ is the only element used for traversal lookup). Let's demonstrate: >>> from Products.Five.tests.testing.fancycontent import manage_addNonTraversableFancyContent >>> info = manage_addNonTraversableFancyContent(self.folder, 'fancy_zope2', '') >>> self.folder.fancy_zope2.an_attribute = 'This is an attribute' >>> print http(r''' ... GET /test_folder_1_/fancy_zope2/an_attribute HTTP/1.1 ... ''') HTTP/1.1 200 OK ... an_attribute Without a __bobo_traverse__ method this would have returned the attribute value 'This is an attribute'. Let's make sure the same thing happens for an object that has been marked traversable by Five: >>> self.folder.fancy.an_attribute = 'This is an attribute' >>> print http(r''' ... GET /test_folder_1_/fancy/an_attribute HTTP/1.1 ... ''') HTTP/1.1 200 OK ... an_attribute Clean up: >>> from zope.component.testing import tearDown >>> tearDown() Verify that after cleanup, there's no cruft left from five:traversable:: >>> from Products.Five.browser.tests.test_traversable import SimpleClass >>> hasattr(SimpleClass, '__bobo_traverse__') False >>> hasattr(SimpleClass, '__fallback_traverse__') False >>> from Products.Five.tests.testing.fancycontent import FancyContent >>> hasattr(FancyContent, '__bobo_traverse__') True >>> hasattr(FancyContent.__bobo_traverse__, '__five_method__') False >>> hasattr(FancyContent, '__fallback_traverse__') False """ def test_view_doesnt_shadow_attribute(): """ Test that views don't shadow attributes, e.g. items in a folder. Let's first define a browser page for object managers called ``eagle``: >>> configure_zcml = ''' ... ... ... ... ... ... ''' >>> import Products.Five >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_string(configure_zcml) Then we create a traversable folder... >>> from Products.Five.tests.testing.folder import manage_addFiveTraversableFolder >>> manage_addFiveTraversableFolder(self.folder, 'ftf') and add an object called ``eagle`` to it: >>> from Products.Five.tests.testing.simplecontent import manage_addIndexSimpleContent >>> manage_addIndexSimpleContent(self.folder.ftf, 'eagle', 'Eagle') When we publish the ``ftf/eagle`` now, we expect the attribute to take precedence over the view during traversal: >>> print http(r''' ... GET /test_folder_1_/ftf/eagle HTTP/1.1 ... ''') HTTP/1.1 200 OK ... Default index_html called Of course, unless we explicitly want to lookup the view using @@: >>> print http(r''' ... GET /test_folder_1_/ftf/@@eagle HTTP/1.1 ... ''') HTTP/1.1 200 OK ... The eagle has landed Some weird implementations of __bobo_traverse__, like the one found in OFS.Application, raise NotFound. Five still knows how to deal with this, hence views work there too: >>> print http(r''' ... GET /eagle HTTP/1.1 ... ... ''') HTTP/1.1 200 OK ... The eagle has landed >>> print http(r''' ... GET /@@eagle HTTP/1.1 ... ... ''') HTTP/1.1 200 OK ... The eagle has landed However, acquired attributes *should* be shadowed. See discussion on http://codespeak.net/pipermail/z3-five/2006q2/001474.html >>> manage_addIndexSimpleContent(self.folder, 'mouse', 'Mouse') >>> print http(r''' ... GET /test_folder_1_/ftf/mouse HTTP/1.1 ... ''') HTTP/1.1 200 OK ... The mouse has been eaten by the eagle Clean up: >>> from zope.component.testing import tearDown >>> tearDown() """ def test_suite(): from Testing.ZopeTestCase import FunctionalDocTestSuite return FunctionalDocTestSuite() zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/skin.zcml0000644000175000017500000000125212214017421024373 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/overrides.zcml0000644000175000017500000000055212214017421025433 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/provider.txt0000644000175000017500000001705412214017421025142 0ustar arnauarnau================= Content Providers ================= We need some tests for the Zope2 versions of the TAL directives for provider. To this end we have copied the tests from zope.contentprovider and made them work with Five. We have defined a muber of relevant views which use the new tal expression in providers.zcml: >>> from zope.contentprovider import interfaces >>> import Products.Five.browser.tests >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config('provider.zcml', package=Products.Five.browser.tests) Content Providers ----------------- Content Provider is a term from the Java world that refers to components that can provide HTML content. It means nothing more! How the content is found and returned is totally up to the implementation. The Zope touch to the concept is that content providers are multi-adapters that are looked up by the context, request (and thus the layer/skin), and view they are displayed in. So let's create a simple content provider: >>> import zope.interface >>> import zope.component >>> from zope.publisher.interfaces import browser >>> class MessageBox(object): ... zope.interface.implements(interfaces.IContentProvider) ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... message = u'My Message' ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... ... def update(self): ... pass ... ... def render(self): ... return u'
    %s
    ' %self.message The ``update()`` method is executed during phase one. Since no state needs to be calculated and no data is modified by this simple content provider, it is an empty implementation. The ``render()`` method implements phase 2 of the process. We can now instantiate the content provider (manually) and render it: >>> box = MessageBox(None, None, None) >>> box.render() u'
    My Message
    ' Since our content provider did not require the context, request or view to create its HTML content, we were able to pass trivial dummy values into the constructor. Also note that the provider must have a parent (using the ``__parent__`` attribute) specified at all times. The parent must be the view the provider appears in. The TALES ``provider`` Expression --------------------------------- The ``provider`` expression will look up the name of the content provider, call it and return the HTML content. The first step, however, will be to register our content provider with the component architecture: >>> zope.component.provideAdapter(MessageBox, name='mypage.MessageBox') The content provider must be registered by name, since the TALES expression uses the name to look up the provider at run time. >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent >>> manage_addSimpleContent(self.folder, 'content_obj', 'ContentObj') >>> content = self.folder.content_obj Finally we publish the view: >>> print http(r''' ... GET /test_folder_1_/content_obj/main.html HTTP/1.1 ... ''') HTTP/1.1 200 OK ...

    My Web Page

    My Message
    Content here
    Failure to lookup a Content Provider ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >>> print http(r''' ... GET /test_folder_1_/content_obj/error.html HTTP/1.1 ... ''', handle_errors=False) Traceback (most recent call last): ... ContentProviderLookupError: ...mypage.UnknownName... Additional Data from TAL ~~~~~~~~~~~~~~~~~~~~~~~~ The ``provider`` expression allows also for transferring data from the TAL context into the content provider. This is accomplished by having the content provider implement an interface that specifies the attributes and provides ``ITALNamespaceData``: >>> import zope.schema >>> class IMessageText(zope.interface.Interface): ... message = zope.schema.Text(title=u'Text of the message box') >>> zope.interface.directlyProvides(IMessageText, ... interfaces.ITALNamespaceData) Now the message box can receive its text from the TAL environment: >>> class DynamicMessageBox(MessageBox): ... zope.interface.implements(IMessageText) >>> zope.component.provideAdapter( ... DynamicMessageBox, provides=interfaces.IContentProvider, ... name='mypage.DynamicMessageBox') Now we should get two message boxes with different text: >>> print http(r''' ... GET /test_folder_1_/content_obj/namespace.html HTTP/1.1 ... ''') HTTP/1.1 200 OK ...

    My Web Page

    Hello World!
    Hello World again!
    Content here
    Finally, a content provider can also implement several ``ITALNamespaceData``: >>> class IMessageType(zope.interface.Interface): ... type = zope.schema.TextLine(title=u'The type of the message box') >>> zope.interface.directlyProvides(IMessageType, ... interfaces.ITALNamespaceData) We'll change our message box content provider implementation a bit, so the new information is used: >>> class BetterDynamicMessageBox(DynamicMessageBox): ... zope.interface.implements(IMessageType) ... type = None ... ... def render(self): ... return u'
    %s
    ' %(self.type, self.message) >>> zope.component.provideAdapter( ... BetterDynamicMessageBox, provides=interfaces.IContentProvider, ... name='mypage.MessageBox') >>> print http(r''' ... GET /test_folder_1_/content_obj/namespace2.html HTTP/1.1 ... ''') HTTP/1.1 200 OK ...

    My Web Page

    Hello World!
    Hello World again!
    Content here
    Now we test a provider using a PageTemplateFile to render itself: >>> import os, tempfile >>> temp_dir = tempfile.mkdtemp() >>> dynTemplate = os.path.join(temp_dir, 'dynamic_template.pt') >>> open(dynTemplate, 'w').write( ... 'A simple template: ') >>> from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile >>> class TemplateProvider(object): ... zope.component.adapts(zope.interface.Interface, ... browser.IDefaultBrowserLayer, ... zope.interface.Interface) ... ... def __init__(self, context, request, view): ... self.__parent__ = view ... self.context = context ... self.request = request ... self.view = view ... ... def update(self): ... pass ... # Is there a better way to tell it to look in the current dir? ... render = ZopeTwoPageTemplateFile(dynTemplate, temp_dir) ... my_property = 'A string for you' >>> zope.component.provideAdapter(TemplateProvider, name='mypage.TemplateProvider', provides=interfaces.IContentProvider) >>> print http(r''' ... GET /test_folder_1_/content_obj/template_based.html HTTP/1.1 ... ''') HTTP/1.1 200 OK ... A simple template: A string for you Cleanup ------- >>> import shutil >>> shutil.rmtree(temp_dir) zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/seagull.pt0000644000175000017500000000015712214017421024544 0ustar arnauarnaugray zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/legacyprovider.pt0000644000175000017500000000005612214017421026125 0ustar arnauarnau

    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_absoluteurl.py0000644000175000017500000000616512214017421026522 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test AbsoluteURL """ def test_absoluteurl(): """This tests the absolute url view (IAbsoluteURL or @@absolute_url), in particular the breadcrumb functionality. First we make some preparations: >>> import Products.Five >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') A simple traversal will yield us the @@absolute_url view: >>> view = self.folder.unrestrictedTraverse('testoid/@@absolute_url') >>> view() 'http://nohost/test_folder_1_/testoid' IAbsoluteURL also defines a breadcrumbs() method that returns a simple Python structure: >>> for crumb in view.breadcrumbs(): ... info = crumb.items() ... info.sort() ... info [('name', ''), ('url', 'http://nohost')] [('name', 'test_folder_1_'), ('url', 'http://nohost/test_folder_1_')] [('name', 'testoid'), ('url', 'http://nohost/test_folder_1_/testoid')] This test assures and demonstrates that the absolute url stops traversing through an object's parents when it has reached the root object. >>> from zope.interface import alsoProvides, noLongerProvides >>> from OFS.interfaces import IApplication >>> alsoProvides(self.folder, IApplication) >>> for crumb in view.breadcrumbs(): ... info = crumb.items() ... info.sort() ... info [('name', 'test_folder_1_'), ('url', 'http://nohost/test_folder_1_')] [('name', 'testoid'), ('url', 'http://nohost/test_folder_1_/testoid')] >>> noLongerProvides(self.folder, IApplication) The absolute url view is obviously not affected by virtual hosting: >>> request = self.app.REQUEST >>> request['PARENTS'] = [self.folder.test_folder_1_] >>> url = request.setServerURL( ... protocol='http', hostname='foo.bar.com', port='80') >>> request.setVirtualRoot('') >>> for crumb in view.breadcrumbs(): ... info = crumb.items() ... info.sort() ... info [('name', 'test_folder_1_'), ('url', 'http://foo.bar.com')] [('name', 'testoid'), ('url', 'http://foo.bar.com/testoid')] Clean up: >>> from zope.component.testing import tearDown >>> tearDown() """ def test_suite(): from Testing.ZopeTestCase import ZopeDocTestSuite return ZopeDocTestSuite() zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_menu.py0000644000175000017500000001163112214017421025117 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test browser menus """ def test_menu(): """ Test menus Before we can start we need to set up a few things. For menu configuration, we have to start a new interaction: >>> import AccessControl >>> import Products.Five.browser.tests >>> from Zope2.App import zcml >>> zcml.load_config("meta.zcml", Products.Five) >>> zcml.load_config("permissions.zcml", AccessControl) >>> zcml.load_config('menu.zcml', package=Products.Five.browser.tests) >>> from AccessControl.security import newInteraction >>> newInteraction() Now for some actual testing... Let's look up the menu we registered: >>> from zope.publisher.browser import TestRequest >>> from zope.browsermenu.menu import getMenu >>> request = TestRequest() >>> menu = getMenu('testmenu', self.folder, request) It should have >>> len(menu) 4 Sort menu items by title so we get a stable testable result: >>> menu.sort(lambda x, y: cmp(x['title'], y['title'])) >>> from pprint import pprint >>> pprint(menu[0]) {'action': '@@cockatiel_menu_public.html', 'description': u'', 'extra': None, 'icon': None, 'selected': u'', 'submenu': None, 'title': u'Page in a menu (public)'} >>> pprint(menu[1]) {'action': u'seagull.html', 'description': u'This is a test menu item', 'extra': None, 'icon': None, 'selected': u'', 'submenu': None, 'title': u'Test Menu Item'} >>> pprint(menu[2]) {'action': u'parakeet.html', 'description': u'This is a test menu item', 'extra': None, 'icon': None, 'selected': u'', 'submenu': None, 'title': u'Test Menu Item 2'} >>> pprint(menu[3]) {'action': u'falcon.html', 'description': u'This is a test menu item', 'extra': None, 'icon': None, 'selected': u'', 'submenu': None, 'title': u'Test Menu Item 3'} Let's create a manager user account and log in. >>> uf = self.folder.acl_users >>> _ignored = uf._doAddUser('manager', 'r00t', ['Manager'], []) >>> self.login('manager') >>> newInteraction() >>> menu = getMenu('testmenu', self.folder, request) We should get the protected menu items now: >>> len(menu) 7 >>> menu.sort(lambda x, y: cmp(x['title'], y['title'])) >>> pprint(menu[0]) {'action': '@@cockatiel_menu_protected.html', 'description': u'', 'extra': None, 'icon': None, 'selected': u'', 'submenu': None, 'title': u'Page in a menu (protected)'} >>> pprint(menu[1]) {'action': '@@cockatiel_menu_public.html', 'description': u'', 'extra': None, 'icon': None, 'selected': u'', 'submenu': None, 'title': u'Page in a menu (public)'} >>> pprint(menu[2]) {'action': u'seagull.html', 'description': u'This is a protected test menu item', 'extra': None, 'icon': None, 'selected': u'', 'submenu': None, 'title': u'Protected Test Menu Item'} >>> pprint(menu[3]) {'action': u'falcon.html', 'description': u'This is a protected test menu item', 'extra': None, 'icon': None, 'selected': u'', 'submenu': None, 'title': u'Protected Test Menu Item 2'} >>> pprint(menu[4]) {'action': u'seagull.html', 'description': u'This is a test menu item', 'extra': None, 'icon': None, 'selected': u'', 'submenu': None, 'title': u'Test Menu Item'} >>> pprint(menu[5]) {'action': u'parakeet.html', 'description': u'This is a test menu item', 'extra': None, 'icon': None, 'selected': u'', 'submenu': None, 'title': u'Test Menu Item 2'} >>> pprint(menu[6]) {'action': u'falcon.html', 'description': u'This is a test menu item', 'extra': None, 'icon': None, 'selected': u'', 'submenu': None, 'title': u'Test Menu Item 3'} Clean up: >>> from zope.component.testing import tearDown >>> tearDown() """ def test_suite(): from Testing.ZopeTestCase import ZopeDocTestSuite return ZopeDocTestSuite() zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/classes.py0000644000175000017500000000201512214017421024545 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test fixtures """ from zope.interface import Interface, implements from Products.Five import BrowserView class IOne(Interface): """This is an interface. """ class One(object): 'A class' implements(IOne) class ViewOne(BrowserView): 'Yet another class' def my_method(self, arg1, arg2, kw1=None, kw2='D'): print "CALLED %s %s %s %s" % (arg1, arg2, kw1, kw2) zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_provider.py0000644000175000017500000000154012214017421026003 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test browser pages """ def test_suite(): from Testing.ZopeTestCase import FunctionalDocFileSuite return FunctionalDocFileSuite('provider.txt', package='Products.Five.browser.tests') zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/parakeet.pt0000644000175000017500000000014512214017421024701 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/legacymanager.pt0000644000175000017500000000005512214017421025704 0ustar arnauarnau

    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/aqlegacy.py0000644000175000017500000001041512214017421024701 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Legacy browser view tests. Here we nake sure that legacy implementations of views (e.g. those which mix-in one of the Acquisition base classes without knowing better) still work. """ import Acquisition import OFS.SimpleItem from zope.interface import implements from zope.traversing.interfaces import ITraversable from zope.contentprovider.interfaces import IContentProvider from Products.Five import BrowserView from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile class LegacyAttributes(BrowserView): """Make sure that those old aq_* attributes on Five BrowserViews still work, in particular aq_chain, even though BrowserView may not be an Acquisition-decendant class anymore... """ def __call__(self): return repr([obj for obj in self.aq_chain]) class ExplicitLegacyAttributes(Acquisition.Explicit): """Make sure that those old aq_* attributes work on browser views that only inherit from Explicit as well.""" def __call__(self): return repr([obj for obj in self.aq_chain]) class LegacyTemplate(BrowserView): template = ViewPageTemplateFile('falcon.pt') def __call__(self): return self.template() class LegacyTemplateTwo(BrowserView): def __init__(self, context, request): self.__parent__ = context self.context = context self.request = request self.template = ViewPageTemplateFile('falcon.pt') def __call__(self): return self.template() class Explicit(Acquisition.Explicit): def render(self): return 'Explicit' class ExplicitWithTemplate(Acquisition.Explicit): template = ViewPageTemplateFile('falcon.pt') class Implicit(Acquisition.Implicit): index_html = None # we don't want to acquire this! def render(self): return 'Implicit' class ImplicitWithTemplate(Acquisition.Implicit): template = ViewPageTemplateFile('falcon.pt') class ExplicitContentProvider(Acquisition.Explicit): implements(IContentProvider) def __init__(self, context, request, view): self.context = context self.request = request self.view = view # A content provider must set __parent__ to view or context. self.__parent__ = context def update(self): pass def render(self): return 'Content provider inheriting from Explicit' class ExplicitViewlet(Acquisition.Explicit): def __init__(self, context, request, view, manager): self.context = context self.request = request def update(self): # Make sure that the viewlet has the legacy attributes and # they point to the right objects. assert self.aq_parent == self.context assert self.aq_base == self def render(self): return 'Viewlet inheriting from Explicit' class BrowserViewViewlet(BrowserView): def __init__(self, context, request, view, manager): # This is the tricky bit. super(...).__init__ wouldn't # necessarily have to resolve to BrowserView.__init__ because # generates classes on the fly with a # mix-in base class... super(BrowserViewViewlet, self).__init__(context, request) self.view = view self.manager = manager def update(self): pass def render(self): return 'BrowserView viewlet' class LegacyNamespace(object): implements(ITraversable) def __init__(self, context, request): self.context = context self.request = request def traverse(self, name, ignored): return LegacyNamespaceObject(name) class LegacyNamespaceObject(OFS.SimpleItem.SimpleItem): def __init__(self, name): self.id = name zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_adding.py0000644000175000017500000000152112214017421025376 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test adding views """ def test_suite(): from Testing.ZopeTestCase import ZopeDocFileSuite return ZopeDocFileSuite('adding.txt', package="Products.Five.browser.tests") zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/pages.txt0000644000175000017500000002126112214017421024402 0ustar arnauarnauTest browser pages ================== Let's register a quite large amount of test pages: >>> import Products.Five.browser.tests >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests) Let's add a test object that we view most of the pages off of: >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') We also need to create a stub user account and login; otherwise we wouldn't have all the rights to do traversal etc.: >>> uf = self.folder.acl_users >>> _ignored = uf._doAddUser('manager', 'r00t', ['Manager'], []) >>> self.login('manager') Now for some actual testing... Simple pages ------------ A browser page that is a view class's attribute (method): >>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt') >>> view is not None True >>> from Products.Five.browser.tests.pages import SimpleView >>> isinstance(view, SimpleView) True >>> view() u'The eagle has landed' A browser page that is a Page Template. >>> view = self.folder.unrestrictedTraverse('testoid/owl.html') >>> view() u'

    2

    ' A browser page that is a PageTemplate plus a view class: >>> view = self.folder.unrestrictedTraverse('testoid/falcon.html') >>> isinstance(view, SimpleView) True >>> view() u'

    The falcon has taken flight

    ' Test pages that have been registered through the cumulative directive: >>> view = self.folder.unrestrictedTraverse('testoid/eagle-page.txt') >>> isinstance(view, SimpleView) True >>> view() u'The eagle has landed' >>> view = self.folder.unrestrictedTraverse('testoid/mouse-page.txt') >>> isinstance(view, SimpleView) True >>> view() u'The mouse has been eaten by the eagle' Zope 2 objects always need a docstring in order to be published. Five adds a docstring automatically if a view method doesn't have it, but it shouldn't modify existing ones: >>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt') >>> view.eagle.__doc__ == SimpleView.eagle.__doc__ True Make sure new-style classes work fine as view classes: >>> self.folder.unrestrictedTraverse('testoid/@@new_style_class') At one point browser classes with no attribute and no template values specified wasn't getting BrowserView mixed in. Lets make sure it is now: >>> self.folder.unrestrictedTraverse('testoid/@@new_style_class2') Both browser:view and browser:page are ILocation providers, so make sure they have a __name__ attribute with a str instance: >>> page = self.folder.unrestrictedTraverse('testoid/eagle.txt') >>> page.__name__ 'eagle.txt' >>> view = self.folder.unrestrictedTraverse('testoid/named_view') >>> view.__name__ 'named_view' ZPT-based browser pages ----------------------- Test access to ``context`` from ZPTs: >>> view = self.folder.unrestrictedTraverse('testoid/flamingo.html') >>> print view()

    Hello world

    Hello world

    Test macro access from ZPT pages: >>> view = self.folder.unrestrictedTraverse('testoid/seagull.html') >>> view() u'bird macroColor: gray\n' test_zpt_things: >>> view = self.folder.unrestrictedTraverse('testoid/condor.html') >>> print view()

    Hello world

    The eagle has landed

    Hello world

    Hello world

    Make sure that tal:repeat works in ZPT browser pages: >>> view = self.folder.unrestrictedTraverse('testoid/ostrich.html') >>> print view()
    • Alpha
    • Beta
    • Gamma
    • 0
    • 1
    • 2
    Test TALES traversal in ZPT pages: >>> view = self.folder.unrestrictedTraverse('testoid/tales_traversal.html') >>> print view()

    testoid

    test_folder_1_

    Make sure that global template variables in ZPT pages are correct: >>> view = self.folder.unrestrictedTraverse('testoid/template_variables.html') >>> print view() View is a view: True Context is testoid: True Context.aq_parent is test_folder_1_: True Container is context: True Here is context: True Nothing is None: True Default works: True Root is the application: True Template is a template: True Traverse_subpath exists and is empty: True Request is a request: True User is manager: True Options exist: True Attrs exist: True Repeat exists: True Loop exists: True Modules exists: True Make sure that ZPT's aren't a security-less zone. Let's logout and try to access some protected stuff. Let's not forgot to login again, of course: >>> from AccessControl import allow_module >>> allow_module('smtpd') >>> self.logout() >>> view = self.folder.unrestrictedTraverse('testoid/security.html') >>> print view()
    NoneType
    smtpd
    >>> self.login('manager') Test pages registered through the directive: >>> view = self.folder.unrestrictedTraverse('testoid/dirpage1') >>> print view()

    This is page 1

    >>> view = self.folder.unrestrictedTraverse('testoid/dirpage2') >>> print view()

    This is page 2

    Low-level security ------------------ This tests security on a low level (functional pages test has high-level security tests). Let's manually look up a protected view: >>> from zope.component import getMultiAdapter >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> view = getMultiAdapter((self.folder.testoid, request), name=u'eagle.txt') It's protecting the object with the permission, and not the attribute, so we get ('',) instead of ('eagle',): >>> view.__ac_permissions__ (('View management screens', ('',)),) The view's __roles__ attribute can be evaluated correctly: (We have to use aq_acquire here instead of a simple getattr. The reason is that __roles__ actually is an object that expects being called through the __of__ protocol upon which it renders the roles tuple. aq_acquire will trigger this for us. This isn't a problem, really, because AccessControl ends up using aq_acquire anyway, so it Just Works.) >>> from Acquisition import aq_acquire >>> aq_acquire(view, '__roles__') ('Manager',) Check to see if view's context properly acquires its true parent >>> from Acquisition import aq_parent, aq_base, aq_inner >>> context = view.context Check the wrapper type >>> from Acquisition import ImplicitAcquisitionWrapper >>> type(context) == ImplicitAcquisitionWrapper True The parent of the view is the view's context: >>> view.__parent__ == view.context True >>> aq_parent(view) == view.context True The direct parent of the context is >>> context.aq_inner.aq_parent C methods work the same >>> aq_parent(aq_inner(context)) The same applies to a view registered with instead of >>> request = TestRequest() >>> view = getMultiAdapter((self.folder.testoid, request), name=u'permission_view') >>> view.__ac_permissions__ (('View management screens', ('',)),) >>> aq_acquire(view, '__roles__') ('Manager',) >>> context = view.context >>> from Acquisition import ImplicitAcquisitionWrapper >>> type(context) == ImplicitAcquisitionWrapper True >>> view.__parent__ == view.context True >>> aq_parent(view) == view.context True >>> context.aq_inner.aq_parent >>> aq_parent(aq_inner(context)) Make sure that methods which are not included in the allowed interface or attributes, but which already had security declarations from a base class, don't get those declarations overridden to be private. (The roles for restrictedTraverse should be None, indicating it is public.) >>> view.restrictedTraverse__roles__ Other ----- Make sure that browser pages can be overridden: >>> zcml.load_string(''' ... ... ''') >>> view = self.folder.unrestrictedTraverse('testoid/overridden_view') >>> view() u'The mouse has been eaten by the eagle' Test traversal to resources from within ZPT pages: >>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests) >>> view = self.folder.unrestrictedTraverse('testoid/parakeet.html') >>> print view() Clean up -------- >>> from zope.component.testing import tearDown >>> tearDown() zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/__init__.py0000644000175000017500000000001612214017421024646 0ustar arnauarnau# import this zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/resource_ftest.txt0000644000175000017500000000754012214017421026343 0ustar arnauarnauFunctional Resource Test ======================== Set up the test fixtures: >>> import Products.Five.browser.tests >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests) >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> import os, glob >>> _prefix = os.path.dirname(Products.Five.browser.tests.__file__) >>> dir_resource_names = [os.path.basename(r) for r in ( ... glob.glob('%s/*.png' % _prefix) + ... glob.glob('%s/*.pt' % _prefix) + ... glob.glob('%s/[a-z]*.py' % _prefix) + ... glob.glob('%s/*.css' % _prefix))] >>> uf = self.folder.acl_users >>> _ignored = uf._doAddUser('manager', 'r00t', ['Manager'], []) Image resource ~~~~~~~~~~~~~~ >>> print http(r''' ... GET /test_folder_1_/testoid/++resource++pattern.png HTTP/1.1 ... Authorization: Basic manager:r00t ... ''') HTTP/1.1 200 OK ... Image resources can't be traversed further: >>> print http(r''' ... GET /test_folder_1_/testoid/++resource++pattern.png/more HTTP/1.1 ... Authorization: Basic manager:r00t ... ''') HTTP/1.1 404 Not Found ... File resource ~~~~~~~~~~~~~ >>> print http(r''' ... GET /test_folder_1_/testoid/++resource++style.css HTTP/1.1 ... Authorization: Basic manager:r00t ... ''') HTTP/1.1 200 OK ... File resources can't be traversed further: >>> print http(r''' ... GET /test_folder_1_/testoid/++resource++style.css/more HTTP/1.1 ... Authorization: Basic manager:r00t ... ''') HTTP/1.1 404 Not Found ... Template resource ~~~~~~~~~~~~~~~~~ >>> print http(r''' ... GET /test_folder_1_/testoid/++resource++cockatiel.html HTTP/1.1 ... Authorization: Basic manager:r00t ... ''') HTTP/1.1 200 OK ... Template resources can't be traversed further: >>> print http(r''' ... GET /test_folder_1_/testoid/++resource++cockatiel.html/more HTTP/1.1 ... Authorization: Basic manager:r00t ... ''') HTTP/1.1 404 Not Found ... Resource directory ~~~~~~~~~~~~~~~~~~ Page templates aren't guaranteed to render, so exclude them from the test: >>> base_url = '/test_folder_1_/testoid/++resource++fivetest_resources/%s' >>> for r in dir_resource_names: ... if r.endswith('.pt'): ... continue ... response = self.publish(base_url % r, basic='manager:r00t') ... self.assertEquals(200, response.getStatus()) >>> print http(r''' ... GET /test_folder_1_/testoid/++resource++fivetest_resources/resource_subdir/resource.htm HTTP/1.1 ... Authorization: Basic manager:r00t ... ''') HTTP/1.1 200 OK ... This .html should not have a base tag automatically added to the header. We also can traverse into sub-directories: >>> print http(r''' ... GET /test_folder_1_/testoid/++resource++fivetest_resources/resource_subdir/resource.txt HTTP/1.1 ... Authorization: Basic manager:r00t ... ''') HTTP/1.1 200 OK ... This is a resource in a subdirectory of a normal resource to test traversal. >>> print http(r''' ... GET /test_folder_1_/testoid/++resource++fivetest_resources/resource_subdir/resource.html HTTP/1.1 ... Authorization: Basic manager:r00t ... ''') HTTP/1.1 200 OK ... This .html should not have a base tag automatically added to the header. >>> print http(r''' ... GET /test_folder_1_/testoid/++resource++fivetest_resources/resource_subdir/not-existant HTTP/1.1 ... Authorization: Basic manager:r00t ... ''') HTTP/1.1 404 Not Found ... Clean up -------- >>> from zope.component.testing import tearDown >>> tearDown() zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/condor.pt0000644000175000017500000000022112214017421024364 0ustar arnauarnau

    Alpha

    Beta

    Gamma
    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_defaultview.py0000644000175000017500000001516012214017421026473 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test Default View functionality """ def test_default_view(): """ Test default view functionality Let's register a couple of default views and make our stub classes default viewable: >>> import Products.Five.browser.tests >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config('defaultview.zcml', Products.Five.browser.tests) Now let's add a couple of stub objects: >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addCallableSimpleContent >>> from Products.Five.tests.testing.simplecontent import manage_addIndexSimpleContent >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> manage_addCallableSimpleContent(self.folder, 'testcall', 'TestCall') >>> manage_addIndexSimpleContent(self.folder, 'testindex', 'TestIndex') As a last act of preparation, we create a manager login: >>> uf = self.folder.acl_users >>> _ignored = uf._doAddUser('manager', 'r00t', ['Manager'], []) We can specify another default view with browser:defaultView: >>> zcml.load_string(''' ... ... ... ''') >>> print http(r''' ... GET /test_folder_1_/testoid HTTP/1.1 ... Authorization: Basic manager:r00t ... ''') HTTP/1.1 200 OK ... The mouse has been eaten by the eagle In Five 1.5 ``index_html`` you can no longer set default views to anything else than views: >>> print http(r''' ... GET /test_folder_1_/testindex HTTP/1.1 ... ''') HTTP/1.1 404 Not Found ... Disabled __call__ overriding for now. Causes more trouble than it fixes. Thus, no test here: #>>> print http(r''' #... GET /test_folder_1_/testcall HTTP/1.1 #... ''') #HTTP/1.1 200 OK #... #Default __call__ called Clean up: >>> from zope.component.testing import tearDown >>> tearDown() """ def test_default_method_args_marshalling(): """\ Test the default call method of a view, with respect to possible breakage of argument marshalling from other components This is not directly a bug in Five, just a change that enables components have simpler code to imitate ZPublisher's arguments marshalling strategy on default view methods. The ZPublisher marshalls arguments to called methods from the request based on the method's signature. This however assumes that it finds the real callable method. However in case of the autogenerated __call__ method of a view, the real method is wrapped. Although the publisher correctly handles this by looking at the __browser_default__ and applying the request on the real method, Plone's portal factory does not do this correctly, thus causing these method calls fail with TypeError, since no parameters will be marshalled to the browser default methods if within the portal factory. The applied fix changes the __call__ in such a way that it is not wrapper any more, but yields the original callable instead. This test simply checks that this is so, in other words this is a check that would have failed with the original version. First, we load the configuration file: >>> import AccessControl >>> import Products.Five.tests >>> from Zope2.App import zcml >>> zcml.load_config('meta.zcml', Products.Five) >>> zcml.load_config("permissions.zcml", AccessControl) >>> zcml.load_config('directives.zcml', Products.Five.tests) Define a view, with a single attribute and the name of the view is the same as the attribute. Important is that we will use the default browser view. >>> zcml.load_string(''' ... ... ... ... ''') Create a context object and a request. Provide parameters on the request. >>> from Products.Five.browser.tests.classes import One >>> context = One() >>> from zope.publisher.browser import TestRequest >>> request = TestRequest(form={'arg1': 'A', 'arg2': 'B', 'kw1': 'C'}) Create the view. >>> from zope.component import getMultiAdapter >>> from zope.interface import Interface >>> view = getMultiAdapter((context, request), Interface, 'my_method') Check that the __call__ method's signature equals to the real method's signature. They both should yield the four parameters. >>> def args(method): ... f = method.im_func ... c = f.func_code ... defaults = f.func_defaults ... names = c.co_varnames[1:c.co_argcount] ... return names >>> args(view.my_method) ('arg1', 'arg2', 'kw1', 'kw2') >>> args(view.__call__) ('arg1', 'arg2', 'kw1', 'kw2') Finally, call the view's default method. Important is, if this gives a TypeError then the portal factory will fail. This is in effect the same as the previous argument check was. >>> from ZPublisher.mapply import mapply >>> mapply(view.__call__, (), request) CALLED A B C D Clean up adapter registry and others: >>> from zope.testing.cleanup import cleanUp >>> cleanUp() """ def test_suite(): from Testing.ZopeTestCase import FunctionalDocTestSuite return FunctionalDocTestSuite() zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/style.css0000644000175000017500000000003512214017421024410 0ustar arnauarnaua { text-decoration: none; } zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/templates/0000755000175000017500000000000012214017421024536 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/templates/dirpage1.pt0000644000175000017500000000004512214017421026576 0ustar arnauarnau

    This is page 1

    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/templates/dirpage2.pt0000644000175000017500000000004512214017421026577 0ustar arnauarnau

    This is page 2

    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_scriptsecurity.py0000644000175000017500000001512612214017421027252 0ustar arnauarnauimport unittest from AccessControl import Unauthorized def addPythonScript(folder, id, params='', body=''): """Add a PythonScript to folder.""" from Products.PythonScripts.PythonScript import manage_addPythonScript # clean up any 'ps' that's already here.. if id in folder: del folder[id] manage_addPythonScript(folder, id) folder[id].ZPythonScript_edit(params, body) def checkRestricted(folder, psbody): """Perform a check by running restricted Python code.""" addPythonScript(folder, 'ps', body=psbody) try: folder.ps() except Unauthorized, e: raise AssertionError(e) def checkUnauthorized(folder, psbody): """Perform a check by running restricted Python code. Expect to encounter an Unauthorized exception.""" addPythonScript(folder, 'ps', body=psbody) try: folder.ps() except Unauthorized: pass else: raise AssertionError("Authorized but shouldn't be") def test_resource_restricted_code(): """ Set up the test fixtures: >>> import Products.Five.browser.tests >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests) >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> import os, glob >>> _prefix = os.path.dirname(Products.Five.browser.tests.__file__) >>> dir_resource_names = [os.path.basename(r) for r in ( ... glob.glob('%s/*.png' % _prefix) + ... glob.glob('%s/*.pt' % _prefix) + ... glob.glob('%s/[a-z]*.py' % _prefix) + ... glob.glob('%s/*.css' % _prefix))] >>> from Products.Five.browser.tests.test_scriptsecurity import checkRestricted >>> from Products.Five.browser.tests.test_scriptsecurity import checkUnauthorized >>> resource_names = ['cockatiel.html', 'style.css', 'pattern.png'] We should get Unauthorized as long as we're unauthenticated: >>> for resource in resource_names: ... checkUnauthorized( ... self.folder, ... 'context.restrictedTraverse("testoid/++resource++%s")()' % resource) >>> base = 'testoid/++resource++fivetest_resources/%s' >>> for resource in dir_resource_names: ... path = base % resource ... checkUnauthorized(self.folder, 'context.restrictedTraverse("%s")' % path) Now let's create a manager user account and log in: >>> uf = self.folder.acl_users >>> _ignored = uf._doAddUser('manager', 'r00t', ['Manager'], []) >>> self.login('manager') We can now view them all: >>> for resource in resource_names: ... checkRestricted( ... self.folder, ... 'context.restrictedTraverse("testoid/++resource++%s")()' % resource) >>> base = 'testoid/++resource++fivetest_resources/%s' >>> for resource in dir_resource_names: ... path = base % resource ... checkRestricted(self.folder, 'context.restrictedTraverse("%s")' % path) Let's make sure restrictedTraverse() works directly, too. It used to get tripped up on subdirectories due to missing security declarations. >>> self.folder.restrictedTraverse('++resource++fivetest_resources/resource.txt') is not None True >>> self.folder.restrictedTraverse('++resource++fivetest_resources/resource_subdir/resource.txt') is not None True Clean up >>> from zope.component.testing import tearDown >>> tearDown() """ def test_view_restricted_code(): """ Let's register a quite large amount of test pages: >>> import Products.Five.browser.tests >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests) Let's add a test object that we view most of the pages off of: >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') We also need to create a stub user account and login; otherwise we wouldn't have all the rights to do traversal etc.: >>> uf = self.folder.acl_users >>> _ignored = uf._doAddUser('manager', 'r00t', ['Manager'], []) >>> self.login('manager') >>> protected_view_names = [ ... 'eagle.txt', 'falcon.html', 'owl.html', 'flamingo.html', ... 'condor.html', 'permission_view'] >>> >>> public_view_names = [ ... 'public_attribute_page', ... 'public_template_page', ... 'public_template_class_page', ... 'nodoc-method', 'nodoc-function', 'nodoc-object', ... 'dirpage1', 'dirpage2'] >>> from Products.Five.browser.tests.test_scriptsecurity import checkRestricted >>> from Products.Five.browser.tests.test_scriptsecurity import checkUnauthorized As long as we're not authenticated, we should get Unauthorized for protected views, but we should be able to view the public ones: >>> self.logout() >>> for view_name in protected_view_names: ... checkUnauthorized( ... self.folder, ... 'context.restrictedTraverse("testoid/%s")()' % view_name) >>> for view_name in public_view_names: ... checkRestricted( ... self.folder, ... 'context.restrictedTraverse("testoid/%s")()' % view_name) >>> self.login('manager') Being logged in as a manager again, we find that the protected pages are accessible to us: >>> for view_name in protected_view_names: ... checkRestricted( ... self.folder, ... 'context.restrictedTraverse("testoid/%s")()' % view_name) >>> checkRestricted( ... self.folder, ... 'context.restrictedTraverse("testoid/eagle.method").eagle()') Even when logged in though the private methods should not be accessible: >>> checkUnauthorized( self.folder, ... 'context.restrictedTraverse("testoid/eagle.method").mouse()') Cleanup: >>> from zope.component.testing import tearDown >>> tearDown() """ def test_suite(): suite = unittest.TestSuite() try: import Products.PythonScripts except ImportError: pass else: from Testing.ZopeTestCase import ZopeDocTestSuite from Testing.ZopeTestCase import installProduct installProduct('PythonScripts') suite.addTest(ZopeDocTestSuite()) return suite zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_pages.py0000644000175000017500000000551012214017421025251 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test browser pages """ import unittest def test_view_with_unwrapped_context(): """ It may be desirable when writing tests for views themselves to provide dummy contexts which are not wrapped. >>> import Products.Five.browser.tests >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests) >>> from Products.Five.tests.testing import simplecontent as sc >>> from zope.interface import Interface >>> from zope.interface import implements >>> from zope.component import queryMultiAdapter >>> class Unwrapped: ... implements(sc.ISimpleContent) >>> unwrapped = Unwrapped() Simple views should work fine without having their contexts wrapped: >>> eagle = queryMultiAdapter((unwrapped, self.app.REQUEST), ... Interface, 'eagle.txt') >>> eagle is not None True >>> from Products.Five.browser.tests.pages import SimpleView >>> isinstance(eagle, SimpleView) True >>> eagle() u'The eagle has landed' We also want to be able to render the file-based ZPT without requiring that the context be wrapped: >>> falcon = queryMultiAdapter((unwrapped, self.app.REQUEST), ... Interface, 'falcon.html') >>> falcon is not None True >>> from Products.Five.browser.tests.pages import SimpleView >>> isinstance(falcon, SimpleView) True >>> print falcon()

    The falcon has taken flight

    Clean up: >>> from zope.component.testing import tearDown >>> tearDown() """ def test_suite(): from Testing.ZopeTestCase import FunctionalDocFileSuite from Testing.ZopeTestCase import ZopeDocFileSuite from Testing.ZopeTestCase import ZopeDocTestSuite return unittest.TestSuite(( ZopeDocTestSuite(), ZopeDocFileSuite('pages.txt', package='Products.Five.browser.tests'), FunctionalDocFileSuite('pages_ftest.txt', package='Products.Five.browser.tests'), FunctionalDocFileSuite('aqlegacy_ftest.txt', package='Products.Five.browser.tests'), )) zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/pages.py0000644000175000017500000000427412214017421024220 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test browser pages """ from Products.Five import BrowserView from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from OFS.SimpleItem import SimpleItem class SimpleView(BrowserView): """More docstring. Please Zope""" def eagle(self): """Docstring""" return u"The eagle has landed" def eagle2(self): """Docstring""" return u"The eagle has landed:\n%s" % self.context.absolute_url() def mouse(self): """Docstring""" return u"The mouse has been eaten by the eagle" class FancyView(BrowserView): """Fancy, fancy stuff""" def view(self): return u"Fancy, fancy" class CallView(BrowserView): def __call__(self): return u"I was __call__()'ed" class PermissionView(BrowserView, SimpleItem): def __call__(self): return u"I was __call__()'ed" class CallTemplate(BrowserView): __call__ = ViewPageTemplateFile('falcon.pt') class CallableNoDocstring: def __call__(self): return u"No docstring" def function_no_docstring(self): return u"No docstring" class NoDocstringView(BrowserView): def method(self): return u"No docstring" function = function_no_docstring object = CallableNoDocstring() class NewStyleClass(object): """ This is a testclass to verify that new style classes work in browser:page """ def __init__(self, context, request): """Docstring""" self.context = context self.request = request def method(self): """Docstring""" return zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/cockatiel.pt0000644000175000017500000000011712214017421025042 0ustar arnauarnau

    Have you ever seen a cockatiel?

    dunno

    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/provider_messagebox.pt0000644000175000017500000000034612214017421027157 0ustar arnauarnau

    My Web Page

    Content here
    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/provider_template_based.pt0000644000175000017500000000010312214017421027762 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/i18n.pt0000644000175000017500000000142412214017421023665 0ustar arnauarnau

    This is a message

    This is an explicit message

    These are messages

    These are explicit messages

    Text should be inserted here and translated automatically

    Text should be inserted here and translated

    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_decode.py0000644000175000017500000000532512214017421025401 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unit tests for decode module. """ def test_processInputs(): """ Testing processInputs >>> from Products.Five.browser.decode import processInputs >>> charsets = ['iso-8859-1'] >>> class DummyRequest: ... form = {} >>> request = DummyRequest() Strings are converted to unicode:: >>> request.form['foo'] = u'f\xf6\xf6'.encode('iso-8859-1') >>> processInputs(request, charsets) >>> request.form['foo'] == u'f\xf6\xf6' True Strings in lists are converted to unicode:: >>> request.form['foo'] = [u'f\xf6\xf6'.encode('iso-8859-1')] >>> processInputs(request, charsets) >>> request.form['foo'] == [u'f\xf6\xf6'] True Strings in tuples are converted to unicode:: >>> request.form['foo'] = (u'f\xf6\xf6'.encode('iso-8859-1'),) >>> processInputs(request, charsets) >>> request.form['foo'] == (u'f\xf6\xf6',) True Ints in lists are not lost:: >>> request.form['foo'] = [1, 2, 3] >>> processInputs(request, charsets) >>> request.form['foo'] == [1, 2, 3] True Ints in tuples are not lost:: >>> request.form['foo'] = (1, 2, 3,) >>> processInputs(request, charsets) >>> request.form['foo'] == (1, 2, 3) True Mixed lists work: >>> request.form['foo'] = [u'f\xf6\xf6'.encode('iso-8859-1'), 2, 3] >>> processInputs(request, charsets) >>> request.form['foo'] == [u'f\xf6\xf6', 2, 3] True Mixed dicts work: >>> request.form['foo'] = {'foo': u'f\xf6\xf6'.encode('iso-8859-1'), 'bar': 2} >>> processInputs(request, charsets) >>> request.form['foo'] == {'foo': u'f\xf6\xf6', 'bar': 2} True Deep recursion works: >>> request.form['foo'] = [{'foo': u'f\xf6\xf6'.encode('iso-8859-1'), 'bar': 2}, {'foo': u"one", 'bar': 3}] >>> processInputs(request, charsets) >>> request.form['foo'] == [{'foo': u'f\xf6\xf6', 'bar': 2}, {'foo': u"one", 'bar': 3}] True """ def test_suite(): from doctest import DocTestSuite return DocTestSuite() zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/pages.zcml0000644000175000017500000001631412214017421024533 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/resource.zcml0000644000175000017500000000126012214017421025255 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_metaconfigure.py0000644000175000017500000000620212214017421027001 0ustar arnauarnauimport unittest class ViewMixinForTemplatesTests(unittest.TestCase): def _getTargetClass(self): from Products.Five.browser.metaconfigure import ViewMixinForTemplates return ViewMixinForTemplates def _makeOne(self, context=None, request=None): if context is None: context = DummyContext() if request is None: request = DummyRequest() return self._getTargetClass()(context, request) def test_class_conforms_to_IBrowserPublisher(self): from zope.interface.verify import verifyClass from zope.publisher.interfaces.browser import IBrowserPublisher verifyClass(IBrowserPublisher, self._getTargetClass()) def test_browserDefault(self): request = DummyRequest() view = self._makeOne(request=request) self.assertEqual(view.browserDefault(request), (view, ())) def test_publishTraverse_not_index_raises_NotFound(self): from zope.publisher.interfaces import NotFound request = DummyRequest() view = self._makeOne(request=request) self.assertRaises(NotFound, view.publishTraverse, request, 'nonesuch') def test_publishTraverse_w_index_returns_index(self): request = DummyRequest() view = self._makeOne(request=request) index = view.index = DummyTemplate() self.assertTrue(view.publishTraverse(request, 'index.html') is index) def test___getitem___uses_index_macros(self): view = self._makeOne() view.index = index = DummyTemplate() index.macros = {} index.macros['aaa'] = aaa = object() self.assertTrue(view['aaa'] is aaa) def test__getitem__gives_shortcut_to_index_macros(self): view = self._makeOne() view.index = index = DummyTemplate() index.macros = {} self.assertTrue(view['macros'] is index.macros) def test___call___no_args_no_kw(self): view = self._makeOne() view.index = index = DummyTemplate() result = view() self.assertTrue(result is index) self.assertEqual(index._called_with, ((), {})) def test___call___w_args_no_kw(self): view = self._makeOne() view.index = index = DummyTemplate() result = view('abc') self.assertTrue(result is index) self.assertEqual(index._called_with, (('abc',), {})) def test___call___no_args_w_kw(self): view = self._makeOne() view.index = index = DummyTemplate() result = view(foo='bar') self.assertTrue(result is index) self.assertEqual(index._called_with, ((), {'foo': 'bar'})) def test___call___w_args_w_kw(self): view = self._makeOne() view.index = index = DummyTemplate() result = view('abc', foo='bar') self.assertTrue(result is index) self.assertEqual(index._called_with, (('abc',), {'foo': 'bar'})) class DummyContext: pass class DummyRequest: pass class DummyTemplate: def __call__(self, *args, **kw): self._called_with = (args, kw) return self def test_suite(): return unittest.TestSuite(( unittest.makeSuite(ViewMixinForTemplatesTests), )) zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/provider_namespace2.pt0000644000175000017500000000077312214017421027044 0ustar arnauarnau

    My Web Page

    Content here
    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/ostrich.pt0000644000175000017500000000030712214017421024560 0ustar arnauarnau
    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/menu.zcml0000644000175000017500000000405212214017421024374 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/defaultview.zcml0000644000175000017500000000161512214017421025751 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/resource.txt0000644000175000017500000000434412214017421025135 0ustar arnauarnauTesting resources ================= Set up the test fixtures: >>> import Products.Five.browser.tests >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests) >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') >>> import os, glob >>> _prefix = os.path.dirname(Products.Five.browser.tests.__file__) >>> dir_resource_names = [os.path.basename(r) for r in ( ... glob.glob('%s/*.png' % _prefix) + ... glob.glob('%s/*.pt' % _prefix) + ... glob.glob('%s/[a-z]*.py' % _prefix) + ... glob.glob('%s/*.css' % _prefix))] Resource types -------------- >>> from Products.Five.browser.resource import Resource, PageTemplateResource Template resource ~~~~~~~~~~~~~~~~~ >>> resource = self.folder.unrestrictedTraverse('testoid/++resource++cockatiel.html') >>> isinstance(resource, Resource) True >>> resource() 'http://nohost/test_folder_1_/testoid/++resource++cockatiel.html' File resource ~~~~~~~~~~~~~ >>> resource = self.folder.unrestrictedTraverse('testoid/++resource++style.css') >>> isinstance(resource, Resource) True >>> resource() 'http://nohost/test_folder_1_/testoid/++resource++style.css' Image resource ~~~~~~~~~~~~~~ >>> resource = self.folder.unrestrictedTraverse('testoid/++resource++pattern.png') >>> isinstance(resource, Resource) True >>> resource() 'http://nohost/test_folder_1_/testoid/++resource++pattern.png' Resource directory ~~~~~~~~~~~~~~~~~~ >>> base = 'testoid/++resource++fivetest_resources/%s' >>> base_url = 'http://nohost/test_folder_1_/' + base >>> abs_url = self.folder.unrestrictedTraverse(base % '')() >>> abs_url + '/' == base_url % '' True PageTemplateResource's __call__ renders the template >>> for r in dir_resource_names: ... resource = self.folder.unrestrictedTraverse(base % r) ... self.assert_(isinstance(resource, Resource)) ... if not isinstance(resource, PageTemplateResource): ... self.assertEquals(resource(), base_url % r) Clean up -------- >>> from zope.component.testing import tearDown >>> tearDown() zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/skin.py0000644000175000017500000000140312214017421024054 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test skins """ from zope.publisher.interfaces.browser import IDefaultBrowserLayer class ITestSkin(IDefaultBrowserLayer): pass zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/pages_ftest.txt0000644000175000017500000001175412214017421025615 0ustar arnauarnauFunctional Browser Pages Test ============================= This test tests publishing aspects of browser pages. Let's register some: >>> import Products.Five.browser.tests >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests) Let's also add one of our stub objects to play with: >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') Docstrings ---------- In Zope 2, objects normally have to have a docstring in order to be published. This crazy requirement luckily isn't true for zope.publisher, so it should be possible to write docstring-less view classes that are still published through ZPublisher. We see that even though the callables have no docstring, they are published nevertheless: >>> print http(r""" ... GET /test_folder_1_/testoid/nodoc-function HTTP/1.1 ... """) HTTP/1.1 200 OK ... No docstring >>> print http(r""" ... GET /test_folder_1_/testoid/nodoc-method HTTP/1.1 ... """) HTTP/1.1 200 OK ... No docstring >>> print http(r""" ... GET /test_folder_1_/testoid/nodoc-object HTTP/1.1 ... """) HTTP/1.1 200 OK ... No docstring Security -------- Browser pages need to be protected with a permission. Let's test those; we start by adding two users: >>> uf = self.folder.acl_users >>> _ignored = uf._doAddUser('viewer', 'secret', [], []) >>> _ignored = uf._doAddUser('manager', 'r00t', ['Manager'], []) >>> protected_view_names = [ ... 'eagle.txt', 'falcon.html', 'owl.html', 'flamingo.html', ... 'condor.html'] >>> >>> public_view_names = [ ... 'public_attribute_page', ... 'public_template_page', ... 'public_template_class_page', ... 'nodoc-method', 'nodoc-function', 'nodoc-object', ... 'dirpage1', 'dirpage2'] >>> >>> ViewManagementScreens = 'View management screens' As a normal user we shouldn't get to see those pages protected with the 'View management screens' permission. Thus we expect a 401 Unauthorized: >>> for view_name in protected_view_names: ... response = self.publish('/test_folder_1_/testoid/%s' % view_name, ... basic='viewer:secret') ... status = response.getStatus() ... self.assertTrue(status == 401, (status, 401, view_name)) Methods of views which were not explicitly declared as allowed should not be accessible TTW, even if we have the permission to render the view: >>> response = self.publish('/test_folder_1_/testoid/eagle.method/mouse', ... basic='viewer:secret') >>> self.assertEqual(response.getStatus(), 401) The same should apply for the user if he has all other permissions except 'View management screens': >>> permissions = self.folder.possible_permissions() >>> permissions.remove(ViewManagementScreens) >>> self.folder._addRole('Viewer') >>> self.folder.manage_role('Viewer', permissions) >>> self.folder.manage_addLocalRoles('viewer', ['Viewer']) >>> for view_name in protected_view_names: ... response = self.publish('/test_folder_1_/testoid/%s' % view_name, ... basic='viewer:secret') ... status = response.getStatus() ... self.assertTrue(status == 401, (status, 401, view_name)) If we grant 'View management screens' now, the protected views should become viewable: >>> self.folder.manage_role('Viewer', [ViewManagementScreens]) >>> for view_name in protected_view_names: ... response = self.publish('/test_folder_1_/testoid/%s' % view_name, ... basic='viewer:secret') ... status = response.getStatus() ... self.assertTrue(status == 200, (status, 200, view_name)) Managers should always be able to view anything, including proctected stuff: >>> for view_name in protected_view_names: ... response = self.publish('/test_folder_1_/testoid/%s' % view_name, ... basic='manager:r00t') ... self.assertEqual(response.getStatus(), 200) All public views should always be accessible by anyone: >>> for view_name in public_view_names: ... response = self.publish('/test_folder_1_/testoid/%s' % view_name) ... status = response.getStatus() ... self.assertTrue(status == 200, (status, 200, view_name)) Miscellaneous ------------- Zope 2 always wants objects in the traversal graph to have a __name__. That is also true for views, e.g. a view constructed from a simple class bearing only a __call__ method: >>> print http(r''' ... GET /test_folder_1_/testoid/callview.html HTTP/1.1 ... ''') HTTP/1.1 200 OK ... I was __call__()'ed or a __call__ object that's callable, such as a ViewPageTemplateFile: >>> print http(r''' ... GET /test_folder_1_/testoid/calltemplate.html HTTP/1.1 ... ''') HTTP/1.1 200 OK ...

    The falcon has taken flight

    Clean up -------- >>> from zope.component.testing import tearDown >>> tearDown() zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/zope3security.py0000644000175000017500000000057712214017421025753 0ustar arnauarnaufrom zope.publisher.browser import BrowserView from zope.security.management import checkPermission class Zope3SecurityView(BrowserView): def __call__(self, permission): if checkPermission(permission, self.context): return "Yes, you have the %r permission." % permission else: return "No, you don't have the %r permission." % permission zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_skin.py0000644000175000017500000000153412214017421025120 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test browser pages """ def test_suite(): from Testing.ZopeTestCase import FunctionalDocFileSuite return FunctionalDocFileSuite('skin.txt', package='Products.Five.browser.tests') zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/test_pagetemplatefile.py0000644000175000017500000002744712214017421027477 0ustar arnauarnauimport unittest class ViewPageTemplateFileTests(unittest.TestCase): def setUp(self): from AccessControl.SecurityManagement import noSecurityManager noSecurityManager() def tearDown(self): from AccessControl.SecurityManagement import noSecurityManager noSecurityManager() def _getTargetClass(self): from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile return ViewPageTemplateFile def _makeOne(self, filename, _prefix=None, content_type=None): return self._getTargetClass()(filename, _prefix, content_type) def _makeView(self, context=None, request=None): if context is None: context = DummyContext() if request is None: request = DummyRequest() return DummyView(context, request) def test_getId_simple_name(self): vptf = self._makeOne('seagull.pt') self.assertEqual(vptf.getId(), 'seagull.pt') self.assertEqual(vptf.id, 'seagull.pt') def test_getId_with_path(self): vptf = self._makeOne('templates/dirpage1.pt') self.assertEqual(vptf.id, 'dirpage1.pt') def test_pt_getEngine(self): from zope.tales.expressions import DeferExpr from zope.tales.expressions import NotExpr from zope.tales.expressions import PathExpr from zope.tales.expressions import Undefs from zope.tales.pythonexpr import PythonExpr from zope.contentprovider.tales import TALESProviderExpression from Products.PageTemplates.DeferExpr import LazyExpr from Products.PageTemplates.Expressions import TrustedZopePathExpr from Products.PageTemplates.Expressions import SecureModuleImporter from Products.PageTemplates.Expressions import UnicodeAwareStringExpr vptf = self._makeOne('seagull.pt') engine = vptf.pt_getEngine() self.assertEqual(engine.types['standard'], TrustedZopePathExpr) self.assertEqual(engine.types['path'], TrustedZopePathExpr) self.assertEqual(engine.types['exists'], TrustedZopePathExpr) self.assertEqual(engine.types['nocall'], TrustedZopePathExpr) self.assertEqual(engine.types['string'], UnicodeAwareStringExpr) self.assertEqual(engine.types['python'], PythonExpr) self.assertEqual(engine.types['not'], NotExpr) self.assertEqual(engine.types['defer'], DeferExpr) self.assertEqual(engine.types['lazy'], LazyExpr) self.assertEqual(engine.types['provider'], TALESProviderExpression) self.assertEqual(engine.base_names['modules'], SecureModuleImporter) def test_pt_getContext_no_kw_no_physicalRoot(self): from Products.Five.browser.pagetemplatefile import ViewMapper from Products.PageTemplates.Expressions import SecureModuleImporter from AccessControl.SecurityManagement import newSecurityManager newSecurityManager(None, DummyUser('a_user')) context = DummyContext() request = DummyRequest() view = self._makeView(context, request) vptf = self._makeOne('seagull.pt') namespace = vptf.pt_getContext(view, request) self.assertTrue(namespace['context'] is context) self.assertTrue(namespace['request'] is request) views = namespace['views'] self.assertTrue(isinstance(views, ViewMapper)) self.assertEqual(views.ob, context) self.assertEqual(views.request, request) self.assertTrue(namespace['here'] is context) self.assertTrue(namespace['container'] is context) self.assertTrue(namespace['root'] is None) modules = namespace['modules'] self.assertTrue(modules is SecureModuleImporter) self.assertEqual(namespace['traverse_subpath'], []) self.assertEqual(namespace['user'].getId(), 'a_user') def test_pt_getContext_w_physicalRoot(self): from Products.Five.browser.pagetemplatefile import ViewMapper from Products.PageTemplates.Expressions import SecureModuleImporter from AccessControl.SecurityManagement import newSecurityManager newSecurityManager(None, DummyUser('a_user')) context = DummyContext() root = DummyContext() context.getPhysicalRoot = lambda: root request = DummyRequest() view = self._makeView(context, request) vptf = self._makeOne('seagull.pt') namespace = vptf.pt_getContext(view, request) self.assertTrue(namespace['root'] is root) def test_pt_getContext_w_ignored_kw(self): from Products.Five.browser.pagetemplatefile import ViewMapper from Products.PageTemplates.Expressions import SecureModuleImporter from AccessControl.SecurityManagement import newSecurityManager newSecurityManager(None, DummyUser('a_user')) context = DummyContext() request = DummyRequest() view = self._makeView(context, request) vptf = self._makeOne('seagull.pt') namespace = vptf.pt_getContext(view, request, foo='bar') self.assertFalse('foo' in namespace) self.assertFalse('foo' in namespace['options']) def test_pt_getContext_w_args_kw(self): from Products.Five.browser.pagetemplatefile import ViewMapper from Products.PageTemplates.Expressions import SecureModuleImporter from AccessControl.SecurityManagement import newSecurityManager newSecurityManager(None, DummyUser('a_user')) context = DummyContext() request = DummyRequest() view = self._makeView(context, request) vptf = self._makeOne('seagull.pt') namespace = vptf.pt_getContext(view, request, args=('bar', 'baz')) self.assertEqual(namespace['args'], ('bar', 'baz')) def test_pt_getContext_w_options_kw(self): from Products.Five.browser.pagetemplatefile import ViewMapper from Products.PageTemplates.Expressions import SecureModuleImporter from AccessControl.SecurityManagement import newSecurityManager newSecurityManager(None, DummyUser('a_user')) context = DummyContext() request = DummyRequest() view = self._makeView(context, request) vptf = self._makeOne('seagull.pt') namespace = vptf.pt_getContext(view, request, options={'bar': 'baz'}) self.assertEqual(namespace['options'], {'bar': 'baz'}) def test___call___no_previous_content_type(self): context = DummyContext() request = DummyRequest() response = request.response = DummyResponse() view = self._makeView(context, request) vptf = self._makeOne('templates/dirpage1.pt') body = vptf(view) self.assertEqual(body, DIRPAGE1) self.assertEqual(response._headers['Content-Type'], 'text/html') def test___call___w_previous_content_type(self): context = DummyContext() request = DummyRequest() response = request.response = DummyResponse( {'Content-Type': 'text/xhtml'}) view = self._makeView(context, request) vptf = self._makeOne('templates/dirpage1.pt') body = vptf(view) self.assertEqual(response._headers['Content-Type'], 'text/xhtml') def test___get___(self): from Products.Five.browser.pagetemplatefile import BoundPageTemplate template = self._makeOne('templates/dirpage1.pt') class Foo: def __init__(self, context, request): self.context = context self.request = request bar = template context = DummyContext() request = DummyRequest() foo = Foo(context, request) bound = foo.bar self.assertTrue(isinstance(bound, BoundPageTemplate)) self.assertTrue(bound.im_func is template) self.assertTrue(bound.im_self is foo) class ViewMapperTests(unittest.TestCase): def setUp(self): from zope.component.testing import setUp setUp() def tearDown(self): from zope.component.testing import tearDown tearDown() def _getTargetClass(self): from Products.Five.browser.pagetemplatefile import ViewMapper return ViewMapper def _makeOne(self, ob=None, request=None): if ob is None: ob = DummyContext() if request is None: request = DummyRequest() return self._getTargetClass()(ob, request) def test___getitem___miss(self): from zope.component import ComponentLookupError mapper = self._makeOne() self.assertRaises(ComponentLookupError, mapper.__getitem__, 'nonesuch') def test___getitem___hit(self): from zope.interface import Interface from zope.component import provideAdapter def _adapt(context, request): return self provideAdapter(_adapt, (None, None), Interface, name='test') mapper = self._makeOne() self.assertTrue(mapper['test'] is self) _marker = object() class BoundPageTemplateTests(unittest.TestCase): def _getTargetClass(self): from Products.Five.browser.pagetemplatefile import BoundPageTemplate return BoundPageTemplate def _makeOne(self, pt=_marker, ob=_marker): if pt is _marker: pt = DummyTemplate() if ob is _marker: ob = DummyContext() return self._getTargetClass()(pt, ob) def test___init__(self): pt = DummyTemplate({'foo': 'bar'}) ob = DummyContext() bpt = self._makeOne(pt, ob) self.assertTrue(bpt.im_func is pt) self.assertTrue(bpt.im_self is ob) self.assertTrue(bpt.__parent__ is ob) self.assertEqual(bpt.macros['foo'], 'bar') self.assertEqual(bpt.filename, 'dummy.pt') def test___setattr___raises(self): bpt = self._makeOne() try: bpt.foo = 'bar' except AttributeError: pass else: self.fail('Attribute assigned') def test___call___w_real_im_self_no_args_no_kw(self): pt = DummyTemplate() ob = DummyContext() bpt = self._makeOne(pt, ob) rendered = bpt() self.assertEqual(rendered, '

    Dummy

    ') self.assertEqual(pt._called_with, (ob, (), {})) def test___call___w_real_im_self_w_args_w_kw(self): pt = DummyTemplate() ob = DummyContext() bpt = self._makeOne(pt, ob) rendered = bpt('abc', foo='bar') self.assertEqual(rendered, '

    Dummy

    ') self.assertEqual(pt._called_with, (ob, ('abc',), {'foo': 'bar'})) def test___call___wo_real_im_self_w_args_w_kw(self): pt = DummyTemplate() bpt = self._makeOne(pt, None) rendered = bpt('abc', 'def', foo='bar') self.assertEqual(rendered, '

    Dummy

    ') self.assertEqual(pt._called_with, ('abc', ('def',), {'foo': 'bar'})) DIRPAGE1 = """\

    This is page 1

    """ class DummyContext: pass class DummyRequest: debug = object() class DummyResponse: def __init__(self, headers=None): if headers is None: headers = {} self._headers = headers def getHeader(self, name): return self._headers.get(name) def setHeader(self, name, value): self._headers[name] = value class DummyTemplate: filename = 'dummy.pt' def __init__(self, macros=None): if macros is None: macros = {} self.macros = macros def __call__(self, im_self, *args, **kw): self._called_with = (im_self, args, kw) return '

    Dummy

    ' class DummyView: def __init__(self, context, request): self.context = context self.request = request class DummyUser: def __init__(self, name): self._name = name def getId(self): return self._name def test_suite(): return unittest.TestSuite(( unittest.makeSuite(ViewPageTemplateFileTests), unittest.makeSuite(ViewMapperTests), unittest.makeSuite(BoundPageTemplateTests), )) zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/birdmacro.pt0000644000175000017500000000026012214017421025045 0ustar arnauarnaubird macroColor: zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/resource_subdir/0000755000175000017500000000000012214017421025737 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/resource_subdir/resource.htm0000644000175000017500000000022612214017421030300 0ustar arnauarnau This .html should not have a base tag automatically added to the header. zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/resource_subdir/resource.html0000644000175000017500000000022612214017421030454 0ustar arnauarnau This .html should not have a base tag automatically added to the header. zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/resource_subdir/resource.txt0000644000175000017500000000011512214017421030324 0ustar arnauarnauThis is a resource in a subdirectory of a normal resource to test traversal. zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/aqlegacy.zcml0000644000175000017500000000555312214017421025225 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/skin.txt0000644000175000017500000000273012214017421024247 0ustar arnauarnauTest layer and skin support =========================== Let's register a test layer and test skin: >>> import Products.Five.browser.tests >>> from Zope2.App import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_config("skin.zcml", package=Products.Five.browser.tests) Let's add a test object that we'll access the test page from: >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') The view was registered on a different layer than 'default', that's why we can't access it straight away: >>> print http(r""" ... GET /test_folder_1_/testoid/eagle.html HTTP/1.1 ... """) HTTP/1.1 404 Not Found ... It works when we explicitly use the skin that includes that layer: >>> print http(r""" ... GET /++skin++TestSkin/test_folder_1_/testoid/eagle.html HTTP/1.1 ... """) HTTP/1.1 200 OK ... The eagle has landed: http://localhost/++skin++TestSkin/test_folder_1_/testoid Or when we make that skin the default skin: >>> zcml.load_string(''' ... ... ''') >>> print http(r""" ... GET /test_folder_1_/testoid/eagle.html HTTP/1.1 ... """) HTTP/1.1 200 OK ... The eagle has landed: http://localhost/test_folder_1_/testoid Clean up -------- >>> from zope.component.testing import tearDown >>> tearDown() zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/tests/owl.pt0000644000175000017500000000005412214017421023705 0ustar arnauarnau

    Some content

    zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/pagetemplatefile.py0000644000175000017500000001160612214017421025264 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """A 'PageTemplateFile' without security restrictions. """ from os.path import basename from zope.component import getMultiAdapter from zope.pagetemplate.pagetemplatefile import PageTemplateFile from zope.pagetemplate.engine import TrustedAppPT from Acquisition import aq_get from AccessControl import getSecurityManager from Products.PageTemplates.Expressions import SecureModuleImporter from Products.PageTemplates.Expressions import createTrustedZopeEngine from Products.Five.bbb import AcquisitionBBB _engine = createTrustedZopeEngine() def getEngine(): return _engine class ViewPageTemplateFile(TrustedAppPT, PageTemplateFile): """Page Template used as class variable of views defined as Python classes. """ def __init__(self, filename, _prefix=None, content_type=None): _prefix = self.get_path_from_prefix(_prefix) super(ViewPageTemplateFile, self).__init__(filename, _prefix) if content_type is not None: self.content_type = content_type def getId(self): return basename(self.filename) id = property(getId) def __call__(self, __instance, *args, **keywords): # Work around BBB foul. Before Zope 2.12 there was no first argument # but the zope.pagetemplate version has one called instance. Some # people used instance as an additional keyword argument. instance = __instance namespace = self.pt_getContext( request=instance.request, instance=instance, args=args, options=keywords) debug_flags = instance.request.debug s = self.pt_render( namespace, showtal=getattr(debug_flags, 'showTAL', 0), sourceAnnotations=getattr(debug_flags, 'sourceAnnotations', 0), ) response = instance.request.response if not response.getHeader("Content-Type"): response.setHeader("Content-Type", self.content_type) return s def pt_getEngine(self): return getEngine() def pt_getContext(self, instance, request, **kw): namespace = super(ViewPageTemplateFile, self).pt_getContext(**kw) namespace['request'] = request namespace['view'] = instance namespace['context'] = context = instance.context namespace['views'] = ViewMapper(context, request) # get the root obj = context root = None meth = aq_get(obj, 'getPhysicalRoot', None) if meth is not None: root = meth() namespace.update(here=obj, # philiKON thinks container should be the view, # but BBB is more important than aesthetics. container=obj, root=root, modules=SecureModuleImporter, traverse_subpath=[], # BBB, never really worked user = getSecurityManager().getUser() ) return namespace def __get__(self, instance, type): return BoundPageTemplate(self, instance) class ViewMapper(object): def __init__(self, ob, request): self.ob = ob self.request = request def __getitem__(self, name): return getMultiAdapter((self.ob, self.request), name=name) # When a view's template is accessed e.g. as template.view, a # BoundPageTemplate object is retured. For BBB reasons, it needs to # support the aq_* methods and attributes known from Acquisition. For # that it also needs to be locatable thru __parent__. class BoundPageTemplate(AcquisitionBBB): def __init__(self, pt, ob): object.__setattr__(self, 'im_func', pt) object.__setattr__(self, 'im_self', ob) macros = property(lambda self: self.im_func.macros) filename = property(lambda self: self.im_func.filename) __parent__ = property(lambda self: self.im_self) def __call__(self, *args, **kw): if self.im_self is None: im_self, args = args[0], args[1:] else: im_self = self.im_self return self.im_func(im_self, *args, **kw) def __setattr__(self, name, v): raise AttributeError("Can't set attribute", name) def __repr__(self): return "" % self.im_self # BBB ZopeTwoPageTemplateFile = ViewPageTemplateFile zope2.13-2.13.21/source/Zope2/src/Products/Five/browser/absoluteurl.py0000644000175000017500000000163112214017421024312 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.deferredimport import deprecated deprecated("Please import from OFS.absoluteurl", AbsoluteURL = 'OFS.absoluteurl:AbsoluteURL', OFSTraversableAbsoluteURL = 'OFS.absoluteurl:OFSTraversableAbsoluteURL', RootAbsoluteURL = 'OFS.absoluteurl:RootAbsoluteURL', ) zope2.13-2.13.21/source/Zope2/src/Products/Five/permissions.zcml0000644000175000017500000000027112214017421023155 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/zcml.py0000644000175000017500000000056512214017421021240 0ustar arnauarnau# BBB from zope.deferredimport import deprecated deprecated("Please import from Zope2.App.zcml", _context = 'Zope2.App.zcml:_context', _initialized = 'Zope2.App.zcml:_initialized', cleanUp = 'Zope2.App.zcml:cleanUp', load_config = 'Zope2.App.zcml:load_config', load_site = 'Zope2.App.zcml:load_site', load_string = 'Zope2.App.zcml:load_string', ) zope2.13-2.13.21/source/Zope2/src/Products/Five/meta.zcml0000644000175000017500000000155012214017421021531 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/event.zcml0000644000175000017500000000020412214017421021717 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/0000755000175000017500000000000012214017422021056 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/tests/directives.zcml0000644000175000017500000000163112214017422024107 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/adapters.py0000644000175000017500000000354112214017422023236 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Adapter test fixtures """ from zope.interface import implements, Interface from zope.component import adapts class IAdaptable(Interface): """This is a Zope interface. """ def method(): """This method will be adapted """ class IAdapted(Interface): """The interface we adapt to. """ def adaptedMethod(): """A method to adapt. """ class IOrigin(Interface): """Something we'll adapt""" class IDestination(Interface): """The result of an adaption""" def method(): """Do something""" class Adaptable: implements(IAdaptable) def method(self): return "The method" class Adapter: implements(IAdapted) adapts(IAdaptable) def __init__(self, context): self.context = context def adaptedMethod(self): return "Adapted: %s" % self.context.method() class Origin: implements(IOrigin) class OriginalAdapter: implements(IDestination) def __init__(self, context): self.context = context def method(self): return "Original" class OverrideAdapter: implements(IDestination) def __init__(self, context): self.context = context def method(self): return "Overridden" zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/test_directives.py0000644000175000017500000000454612214017422024641 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test the basic ZCML directives """ def test_directives(): """ Test ZCML directives There isn't much to test here since the actual directive handlers are either tested in other, more specific tests, or they're already tested in the original Zope packages. We'll just do a symbolic test of adapters and overrides of adapters here as well as registering meta directives. But first, we load the configuration file: >>> import Products.Five.tests >>> from Zope2.App import zcml >>> zcml.load_config('meta.zcml', Products.Five) >>> zcml.load_config('directives.zcml', Products.Five.tests) Now for some testing. Here we check whether the registered adapter works: >>> from Products.Five.tests.adapters import IAdapted, IDestination >>> from Products.Five.tests.adapters import Adaptable, Origin >>> obj = Adaptable() >>> adapted = IAdapted(obj) >>> adapted.adaptedMethod() 'Adapted: The method' Now let's load some overriding ZCML statements: >>> zcml.load_string( ... '''''') >>> origin = Origin() >>> dest = IDestination(origin) >>> dest.method() 'Overridden' Check the result of the directives >>> from Products.Five.tests.classes import One, Two, IOne, ITwo >>> IOne.implementedBy(One) True >>> ITwo.implementedBy(One) True Clean up adapter registry and others: >>> from zope.testing.cleanup import cleanUp >>> cleanUp() """ def test_suite(): from Testing.ZopeTestCase import ZopeDocTestSuite return ZopeDocTestSuite() zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/test_i18n.py0000644000175000017500000000371512214017422023254 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unit tests for the i18n framework """ from zope.component.testing import setUp, tearDown def test_directive(): """ Test the i18n directive. First, we need to register the ZCML directive: >>> import zope.i18n >>> from Zope2.App import zcml >>> zcml.load_config('meta.zcml', zope.i18n) Let's register the gettext locales using the ZCML directive: >>> configure_zcml = ''' ... ... ... ''' >>> zcml.load_string(configure_zcml) Now, take an arbitrary message id from that domain: >>> from zope.i18nmessageid import MessageFactory >>> from zope.i18n import translate >>> _ = MessageFactory('fivetest') >>> msg = _(u'explicit-msg', u'This is an explicit message') As you can see, both the default functionality and translation to German work: >>> translate(msg) u'This is an explicit message' >>> translate(msg, target_language='de') u'Dies ist eine explizite Nachricht' """ def test_suite(): from doctest import DocTestSuite return DocTestSuite(setUp=setUp, tearDown=tearDown) zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/overrides.zcml0000644000175000017500000000036512214017422023753 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/classes.py0000644000175000017500000000163212214017422023067 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interfaces test fixtures """ from zope.interface import Interface class One(object): 'A class' class Two(object): 'Another class' class IOne(Interface): """This is a Zope interface. """ class ITwo(Interface): """This is another Zope interface. """ zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/metaconfigure.py0000644000175000017500000000244312214017422024263 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Parrot directive and support classes """ from zope.interface import Interface from zope.configuration.fields import GlobalObject from zope.schema import TextLine class IParrotDirective(Interface): """State that a class implements something. """ class_ = GlobalObject( title=u"Class", required=True ) name = TextLine( title=u"Name", description=u"The parrots name.", required=True ) def parrot(_context, class_, name): parrot = class_() parrot.pineForFjords() class NorwegianBlue(object): def pineForFjords(self): return "This parrot is no more!" zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/testing/0000755000175000017500000000000012214017422022533 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/tests/testing/fancycontent.py0000644000175000017500000000603412214017422025603 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test content objects. """ from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import Explicit from OFS.SimpleItem import SimpleItem from zope.interface import implements from zope.interface import Interface class IFancyContent(Interface): pass class FancyAttribute(Explicit): """Doc test fanatics""" def __init__(self, name): self.name = name security = ClassSecurityInfo() security.declarePublic('index_html') def index_html(self, REQUEST): """Doc test fanatics""" return self.name InitializeClass(FancyAttribute) class FancyContent(SimpleItem): """A class that already comes with its own __bobo_traverse__ handler. Quite fancy indeed. It also comes with its own get_size method. """ implements(IFancyContent) meta_type = "Fancy Content" security = ClassSecurityInfo() def __bobo_traverse__(self, REQUEST, name): if name == 'raise-attributeerror': raise AttributeError(name) elif name == 'raise-keyerror': raise KeyError(name) elif name == 'raise-valueerror': raise ValueError(name) return FancyAttribute(name).__of__(self) def get_size(self): return 43 InitializeClass(FancyContent) # A copy of the above class used to demonstrate some baseline behavior class NonTraversableFancyContent(SimpleItem): """A class that already comes with its own __bobo_traverse__ handler. Quite fancy indeed. It also comes with its own get_size method. """ implements(IFancyContent) meta_type = "Fancy Content" security = ClassSecurityInfo() def __bobo_traverse__(self, REQUEST, name): if name == 'raise-attributeerror': raise AttributeError(name) elif name == 'raise-keyerror': raise KeyError(name) elif name == 'raise-valueerror': raise ValueError(name) return FancyAttribute(name).__of__(self) def get_size(self): return 43 InitializeClass(NonTraversableFancyContent) def manage_addFancyContent(self, id, REQUEST=None): """Add the fancy fancy content.""" id = self._setObject(id, FancyContent(id)) return '' def manage_addNonTraversableFancyContent(self, id, REQUEST=None): """Add the fancy fancy content.""" id = self._setObject(id, NonTraversableFancyContent(id)) return '' zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/testing/simplecontent.py0000644000175000017500000000516412214017422025777 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Simple content class(es) for browser tests """ from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from OFS.SimpleItem import SimpleItem from zope.interface import implements from zope.interface import Interface class ISimpleContent(Interface): pass class ICallableSimpleContent(ISimpleContent): pass class IIndexSimpleContent(ISimpleContent): pass class SimpleContent(SimpleItem): implements(ISimpleContent) meta_type = 'Five SimpleContent' security = ClassSecurityInfo() def __init__(self, id, title): self.id = id self.title = title security.declarePublic('mymethod') def mymethod(self): return "Hello world" security.declarePublic('direct') def direct(self): """Should be able to traverse directly to this as there is no view. """ return "Direct traversal worked" InitializeClass(SimpleContent) class CallableSimpleContent(SimpleItem): """A Viewable piece of content""" implements(ICallableSimpleContent) meta_type = "Five CallableSimpleContent" def __call__(self, *args, **kw): """ """ return "Default __call__ called" InitializeClass(CallableSimpleContent) class IndexSimpleContent(SimpleItem): """A Viewable piece of content""" implements(IIndexSimpleContent) meta_type = 'Five IndexSimpleContent' def index_html(self, *args, **kw): """ """ return "Default index_html called" InitializeClass(IndexSimpleContent) def manage_addSimpleContent(self, id, title, REQUEST=None): """Add the simple content.""" self._setObject(id, SimpleContent(id, title)) def manage_addCallableSimpleContent(self, id, title, REQUEST=None): """Add the viewable simple content.""" self._setObject(id, CallableSimpleContent(id, title)) def manage_addIndexSimpleContent(self, id, title, REQUEST=None): """Add the viewable simple content.""" self._setObject(id, IndexSimpleContent(id, title)) zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/testing/folder.py0000644000175000017500000000264212214017422024364 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test folders """ from OFS.Folder import Folder from OFS.interfaces import IFolder from zope.interface import implements class NoVerifyPasteFolder(Folder): """Folder that does not perform paste verification. Used by test_events """ def _verifyObjectPaste(self, object, validate_src=1): pass def manage_addNoVerifyPasteFolder(container, id, title=''): container._setObject(id, NoVerifyPasteFolder()) folder = container[id] folder.id = id folder.title = title class FiveTraversableFolder(Folder): """Folder that is five-traversable """ implements(IFolder) def manage_addFiveTraversableFolder(container, id, title=''): container._setObject(id, FiveTraversableFolder()) folder = container[id] folder.id = id folder.title = title zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/testing/__init__.py0000644000175000017500000000167312214017422024653 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test helpers """ from Products.Five.tests.testing.folder import FiveTraversableFolder from Products.Five.tests.testing.folder import manage_addFiveTraversableFolder from Products.Five.tests.testing.folder import manage_addNoVerifyPasteFolder from Products.Five.tests.testing.folder import NoVerifyPasteFolder zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/testing/pythonproduct1.py0000644000175000017500000000120212214017422026103 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/testing/pythonproduct2/0000755000175000017500000000000012214017422025537 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/tests/testing/pythonproduct2/__init__.py0000644000175000017500000000010212214017422027641 0ustar arnauarnau def initialize(context): print "pythonproduct2 initialized" zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/testing/pythonproduct2/Extensions/0000755000175000017500000000000012214017422027676 5ustar arnauarnau././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Zope2/src/Products/Five/tests/testing/pythonproduct2/Extensions/somemodule.pyzope2.13-2.13.21/source/Zope2/src/Products/Five/tests/testing/pythonproduct2/Extensions/somemodule.p0000644000175000017500000000007012214017422032225 0ustar arnauarnau def somemethod(self): print "Executed somemethod" zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/testing/pythonproduct2/Extensions/__init__.py0000644000175000017500000000000012214017422031775 0ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/tests/__init__.py0000644000175000017500000000001612214017422023164 0ustar arnauarnau# import this zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/README.txt0000644000175000017500000000016312214017422022554 0ustar arnauarnauFive tests ========== All you have to do is type:: $ bin/zopectl test -s Products.Five to run the Five tests. zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/boilerplate.py0000644000175000017500000000244112214017422023733 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Boiler plate test module """ def test_boilerplate(): """ >>> from zope.component.testing import setUp, tearDown >>> setUp() >>> import Products.Five.tests >>> from Zope2.App import zcml >>> zcml.load_config('boilerplate.zcml', Products.Five.tests) >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent >>> tearDown() """ def test_suite(): from Testing.ZopeTestCase import ZopeDocTestSuite return ZopeDocTestSuite() zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/meta.zcml0000644000175000017500000000053112214017422022672 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/locales/0000755000175000017500000000000012214017422022500 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/tests/locales/de/0000755000175000017500000000000012214017422023070 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/tests/locales/de/LC_MESSAGES/0000755000175000017500000000000012214017422024655 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/tests/locales/de/LC_MESSAGES/fivetest.po0000644000175000017500000000311312214017422027044 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## msgid "" msgstr "" "Project-Id-Version: Five 1.1\n" "POT-Creation-Date: Sun Jun 12 17:22:49 2005\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Philipp von Weitershausen \n" "Language-Team: Five Developers \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "This is a message" msgstr "Dies ist eine Nachricht" # Default: "This is an explicit message" msgid "explicit-msg" msgstr "Dies ist eine explizite Nachricht" msgid "These are ${number} messages" msgstr "Dies sind ${number} Nachrichten" # Default: "These are ${number} explicit messages" msgid "explicit-msgs" msgstr "Dies sind ${number} explizite Nachrichten" msgid "This is an attribute" msgstr "Dies ist ein Attribut" # Default: "Explicit title" msgid "explicit-title" msgstr "Expliziter Titel" # Default: "Explicit summary" msgid "explicit-summary" msgstr "Explizite Zusammenfassung" zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/locales/de/LC_MESSAGES/fivetest.mo0000644000175000017500000000151512214017422027045 0ustar arnauarnau•Þ\ œÈÉæø  (9?Hˆ¨À!Ö)ø"<These are ${number} messagesThis is a messageThis is an attributeexplicit-msgexplicit-msgsexplicit-summaryexplicit-titleProject-Id-Version: Five 1.1 POT-Creation-Date: Sun Jun 12 17:22:49 2005 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: Philipp von Weitershausen Language-Team: Five Developers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dies sind ${number} NachrichtenDies ist eine NachrichtDies ist ein AttributDies ist eine explizite NachrichtDies sind ${number} explizite NachrichtenExplizite ZusammenfassungExpliziter Titelzope2.13-2.13.21/source/Zope2/src/Products/Five/tests/locales/fivetest.pot0000644000175000017500000000261512214017422025061 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## msgid "" msgstr "" "Project-Id-Version: Five 1.1\n" "POT-Creation-Date: Sun Jun 12 17:22:49 2005\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Philipp von Weitershausen \n" "Language-Team: Five Developers \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "This is a message" msgstr "" # Default: "This is an explicit message" msgid "explicit-msg" msgstr "" msgid "These are ${number} messages" msgstr "" # Default: "These are ${number} explicit messages" msgid "explicit-msgs" msgstr "" msgid "This is an attribute" msgstr "" # Default: "Explicit title" msgid "explicit-title" msgstr "" # Default: "Explicit summary" msgid "explicit-summary" msgstr "" zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/locales/en/0000755000175000017500000000000012214017422023102 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/tests/locales/en/LC_MESSAGES/0000755000175000017500000000000012214017422024667 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Five/tests/locales/en/LC_MESSAGES/fivetest.po0000644000175000017500000000210412214017422027055 0ustar arnauarnau############################################################################## # # Copyright (c) 2004-2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This file contains no message ids because Zope's default language # is English msgid "" msgstr "" "Project-Id-Version: Five 1.1\n" "POT-Creation-Date: Sun Jun 12 17:22:49 2005\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Philipp von Weitershausen \n" "Language-Team: Five Developers \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/locales/en/LC_MESSAGES/fivetest.mo0000644000175000017500000000057112214017422027060 0ustar arnauarnau•Þ$,8?9Project-Id-Version: Five 1.1 POT-Creation-Date: Sun Jun 12 17:22:49 2005 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: Philipp von Weitershausen Language-Team: Five Developers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zope2.13-2.13.21/source/Zope2/src/Products/Five/tests/test_size.py0000644000175000017500000000622012214017422023441 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Size adapters for testing """ from zope.interface import implements from zope.size.interfaces import ISized class SimpleContentSize(object): """Size for ``SimpleContent`` objects.""" implements(ISized) def __init__(self, context): self.context = context def sizeForSorting(self): return ('byte', 42) def sizeForDisplay(self): return "What is the meaning of life?" class FancyContentSize(object): """Size for ``SimpleContent`` objects.""" implements(ISized) def __init__(self, context): self.context = context def sizeForSorting(self): return ('line', 143) def sizeForDisplay(self): return "That's not the meaning of life!" def test_size(): """ Test size adapters Set up: >>> from zope.component.testing import setUp, tearDown >>> setUp() >>> configure_zcml = ''' ... ... ... ... ... ... ''' >>> import Products.Five >>> from Zope2.App import zcml >>> zcml.load_config('meta.zcml', Products.Five) >>> zcml.load_string(configure_zcml) >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent >>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent We have registered an ``ISized`` adapter for SimpleContent: >>> n = manage_addSimpleContent(self.folder, 'simple', 'Simple') >>> self.folder.simple.get_size() 42 Fancy content already has a ``get_size`` method >>> n = manage_addFancyContent(self.folder, 'fancy', 'Fancy') >>> self.folder.fancy.get_size() 43 Clean up: >>> tearDown() """ def test_suite(): from Testing.ZopeTestCase import ZopeDocTestSuite return ZopeDocTestSuite() zope2.13-2.13.21/source/Zope2/src/Products/Five/metaclass.py0000644000175000017500000000660312214017421022246 0ustar arnauarnau############################################################################## # # Copyright (c) 2004, 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # metaclass taken from PEAK. # Martijn doesn't pretend to understand this. from weakref import WeakValueDictionary from types import ClassType def makeClass(name,bases,dict): """makeClass(name, bases, dict) - enhanced class creation """ # Create either a "classic" Python class, an ExtensionClass, or a new-style # class with autogenerated metaclass, based on the nature of the base # classes involved name = str(name) # De-unicode metaclasses = [getattr(b,'__class__',type(b)) for b in bases] if dict.has_key('__metaclass__'): metaclasses.insert(0,dict['__metaclass__']) if dict.has_key('__metaclasses__'): metaclasses[0:0] = list(dict['__metaclasses__']) metaclasses = normalizeBases(metaclasses) if metaclasses: # If we have metaclasses, it's not a classic class, so derive a # single metaclass, and ask it to create the class. if len(metaclasses)==1: metaclass = metaclasses[0] else: metaclass = derivedMeta(metaclasses) return metaclass(name,bases,dict) # No metaclasses, it's a classic class, so use 'new.classobj' from new import classobj; return classobj(name,bases,dict) def normalizeBases(allBases): return minimalBases([b for b in allBases if b is not makeClass]) def minimalBases(classes): """Reduce a list of base classes to its ordered minimum equivalent""" classes = [c for c in classes if c is not ClassType] candidates = [] for m in classes: for n in classes: if issubclass(n,m) and m is not n: break else: # m has no subclasses in 'classes' if m in candidates: candidates.remove(m) # ensure that we're later in the list candidates.append(m) return candidates metaReg = WeakValueDictionary() def derivedMeta(metaclasses): metaclasses = tuple(metaclasses) derived = metaReg.get(metaclasses) if derived is None: normalized = tuple(normalizeBases(metaclasses)) derived = metaReg.get(normalized) if derived is None: if len(normalized)==1: derived = normalized[0] else: derived = metaFromBases(normalized)( '_'.join([n.__name__ for n in normalized]), metaclasses, {} ) try: metaReg[normalized] = derived except TypeError: pass # Some metatypes can't be weakref'd try: metaReg[metaclasses] = derived except TypeError: pass return derived def metaFromBases(bases): meta = tuple([getattr(b,'__class__',type(b)) for b in bases]) if meta==bases: raise TypeError("Incompatible root metatypes",bases) return derivedMeta(meta) zope2.13-2.13.21/source/Zope2/src/Products/Five/eventconfigure.py0000644000175000017500000000161412214017421023312 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from zope.deferredimport import deprecated deprecated("Please import from OFS.metaconfigure", deprecatedManageAddDelete = 'OFS.metaconfigure:deprecatedManageAddDelete', setDeprecatedManageAddDelete = \ 'OFS.metaconfigure:setDeprecatedManageAddDelete', ) zope2.13-2.13.21/source/Zope2/src/Products/SiteErrorLog/0000755000175000017500000000000012214017422021403 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/SiteErrorLog/www/0000755000175000017500000000000012214017422022227 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/SiteErrorLog/www/showEntry.pt0000644000175000017500000000355512214017422024606 0ustar arnauarnau

    Header

    Tabs

    Exception traceback

    The specified log entry was not found. It may have expired.
    Time
    User Name (User Id) joe (joe)
    Request URL http://example.com
    Exception Type AttributeError
    Exception Value zzope
    Traceback (HTML)
    Traceback (text)
    

    Display traceback as text

    REQUEST

    Footer

    zope2.13-2.13.21/source/Zope2/src/Products/SiteErrorLog/www/ok.gif0000644000175000017500000000051712214017422023332 0ustar arnauarnauGIF89a¥%4‡2I“GP˜NP˜OWœVe¤dl¨jl¨kl©kµ€¸Õ·¸Õ¸¹Õ·¹Õ¸¸Ö¸¿Ù¿ÀÙ¿ÀÚ¿ÆÝÆÆÞÆÇÞÆÔåÓÔåÔÔæÓÔæÔÕæÔÚéÚÛéÚÛêÚÛêÛÜêÛâîâ÷úö÷ú÷øûøþþþþÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,tÀ’pH,ŽŠHá(ÔI—FÀ)dB5$9 Lit$†(‰H'Dy:¢á9A•Kas Mè!E! kH\w‚ $[‹T “TJ ¢N$œ¨!”BA;zope2.13-2.13.21/source/Zope2/src/Products/SiteErrorLog/www/error.gif0000644000175000017500000000017112214017422024046 0ustar arnauarnauGIF89a¡ÿÿÿ!þCreated with The GIMP!ù ,1”©À­×"xR&BÌ bdZ'l"HJ§UšhÚœ¯ƒ¢C‡ãGzUøM­ñˆ(;zope2.13-2.13.21/source/Zope2/src/Products/SiteErrorLog/www/main.pt0000644000175000017500000000604412214017422023524 0ustar arnauarnau

    Header

    Tabs

    This page lists the exceptions that have occurred in this site recently. You can configure how many exceptions should be kept and whether the exceptions should be copied to Zope's event log file(s).

    Number of exceptions to keep
    Copy exceptions to the event log
    Ignored exception types

    Exception Log (most recent first)

    No exceptions logged.
    Time Username (User Id) Exception
    13:04:41 joe (joe) AttributeError: Application object has no attribute "zzope"

    Footer

    zope2.13-2.13.21/source/Zope2/src/Products/SiteErrorLog/__init__.py0000644000175000017500000000204612214017422023516 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Site error log product. """ import SiteErrorLog from App.ImageFile import ImageFile misc_={ 'ok.gif': ImageFile('www/ok.gif', globals()), } def initialize(context): context.registerClass(SiteErrorLog.SiteErrorLog, constructors=(SiteErrorLog.manage_addErrorLog,), permission=SiteErrorLog.use_error_logging, icon='www/error.gif') zope2.13-2.13.21/source/Zope2/src/Products/SiteErrorLog/SiteErrorLog.py0000644000175000017500000002563712214017422024352 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Site error log module. """ import os import sys import time import logging from random import random from thread import allocate_lock from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.SecurityManagement import getSecurityManager from AccessControl.unauthorized import Unauthorized from Acquisition import aq_base from App.Dialogs import MessageDialog from OFS.SimpleItem import SimpleItem from Products.PageTemplates.PageTemplateFile import PageTemplateFile from zExceptions.ExceptionFormatter import format_exception LOG = logging.getLogger('Zope.SiteErrorLog') # Permission names use_error_logging = 'Log Site Errors' log_to_event_log = 'Log to the Event Log' # We want to restrict the rate at which errors are sent to the Event Log # because we know that these errors can be generated quick enough to # flood some zLOG backends. zLOG is used to notify someone of a problem, # not to record every instance. # This dictionary maps exception name to a value which encodes when we # can next send the error with that name into the event log. This dictionary # is shared between threads and instances. Concurrent access will not # do much harm. _rate_restrict_pool = {} # The number of seconds that must elapse on average between sending two # exceptions of the same name into the the Event Log. one per minute. _rate_restrict_period = 60 # The number of exceptions to allow in a burst before the above limit # kicks in. We allow five exceptions, before limiting them to one per # minute. _rate_restrict_burst = 5 _www = os.path.join(os.path.dirname(__file__), 'www') # temp_logs holds the logs. temp_logs = {} # { oid -> [ traceback string ] } cleanup_lock = allocate_lock() class SiteErrorLog (SimpleItem): """Site error log class. You can put an error log anywhere in the tree and exceptions in that area will be posted to the site error log. """ meta_type = 'Site Error Log' id = 'error_log' keep_entries = 20 copy_to_zlog = True security = ClassSecurityInfo() manage_options = ( {'label': 'Log', 'action': 'manage_main'}, ) + SimpleItem.manage_options security.declareProtected(use_error_logging, 'manage_main') manage_main = PageTemplateFile('main.pt', _www) security.declareProtected(use_error_logging, 'showEntry') showEntry = PageTemplateFile('showEntry.pt', _www) security.declarePrivate('manage_beforeDelete') def manage_beforeDelete(self, item, container): if item is self: try: del container.__error_log__ except AttributeError: pass security.declarePrivate('manage_afterAdd') def manage_afterAdd(self, item, container): if item is self: container.__error_log__ = aq_base(self) def _setId(self, id): if id != self.id: raise ValueError(MessageDialog( title='Invalid Id', message='Cannot change the id of a SiteErrorLog', action='./manage_main')) def _getLog(self): """Returns the log for this object. Careful, the log is shared between threads. """ log = temp_logs.get(self._p_oid, None) if log is None: log = [] temp_logs[self._p_oid] = log return log security.declareProtected(use_error_logging, 'forgetEntry') def forgetEntry(self, id, REQUEST=None): """Removes an entry from the error log.""" log = self._getLog() cleanup_lock.acquire() i=0 for entry in log: if entry['id'] == id: del log[i] i += 1 cleanup_lock.release() if REQUEST is not None: REQUEST.RESPONSE.redirect( '%s/manage_main?manage_tabs_message=Error+log+entry+was+removed.' % self.absolute_url()) # Exceptions that happen all the time, so we dont need # to log them. Eventually this should be configured # through-the-web. _ignored_exceptions = ( 'Unauthorized', 'NotFound', 'Redirect' ) security.declarePrivate('raising') def raising(self, info): """Log an exception. Called by SimpleItem's exception handler. Returns the url to view the error log entry """ try: now = time.time() try: tb_text = None tb_html = None strtype = str(getattr(info[0], '__name__', info[0])) if strtype in self._ignored_exceptions: return if not isinstance(info[2], basestring): tb_text = ''.join( format_exception(*info, **{'as_html': 0})) tb_html = ''.join( format_exception(*info, **{'as_html': 1})) else: tb_text = info[2] request = getattr(self, 'REQUEST', None) url = None username = None userid = None req_html = None try: strv = str(info[1]) except: strv = '' % type(info[1]).__name__ if request: url = request.get('URL', '?') usr = getSecurityManager().getUser() username = usr.getUserName() userid = usr.getId() try: req_html = str(request) except: pass if strtype == 'NotFound': strv = url next = request['TraversalRequestNameStack'] if next: next = list(next) next.reverse() strv = '%s [ /%s ]' % (strv, '/'.join(next)) log = self._getLog() entry_id = str(now) + str(random()) # Low chance of collision log.append({ 'type': strtype, 'value': strv, 'time': now, 'id': entry_id, 'tb_text': tb_text, 'tb_html': tb_html, 'username': username, 'userid': userid, 'url': url, 'req_html': req_html, }) cleanup_lock.acquire() try: if len(log) >= self.keep_entries: del log[:-self.keep_entries] finally: cleanup_lock.release() except: LOG.error('Error while logging', exc_info=sys.exc_info()) else: if self.copy_to_zlog: self._do_copy_to_zlog(now,strtype,entry_id,str(url),tb_text) return '%s/showEntry?id=%s' % (self.absolute_url(), entry_id) finally: info = None def _do_copy_to_zlog(self,now,strtype,entry_id,url,tb_text): when = _rate_restrict_pool.get(strtype,0) if now>when: next_when = max(when, now-_rate_restrict_burst*_rate_restrict_period) next_when += _rate_restrict_period _rate_restrict_pool[strtype] = next_when LOG.error('%s %s\n%s' % (entry_id, url, tb_text.rstrip())) security.declareProtected(use_error_logging, 'getProperties') def getProperties(self): return { 'keep_entries': self.keep_entries, 'copy_to_zlog': self.copy_to_zlog, 'ignored_exceptions': self._ignored_exceptions, } security.declareProtected(log_to_event_log, 'checkEventLogPermission') def checkEventLogPermission(self): if not getSecurityManager().checkPermission(log_to_event_log, self): raise Unauthorized, ('You do not have the "%s" permission.' % log_to_event_log) return 1 security.declareProtected(use_error_logging, 'setProperties') def setProperties(self, keep_entries, copy_to_zlog=0, ignored_exceptions=(), RESPONSE=None): """Sets the properties of this site error log. """ copy_to_zlog = not not copy_to_zlog if copy_to_zlog and not self.copy_to_zlog: # Before turning on event logging, check the permission. self.checkEventLogPermission() self.keep_entries = int(keep_entries) self.copy_to_zlog = copy_to_zlog self._ignored_exceptions = tuple( filter(None, map(str, ignored_exceptions))) if RESPONSE is not None: RESPONSE.redirect( '%s/manage_main?manage_tabs_message=Changed+properties.' % self.absolute_url()) security.declareProtected(use_error_logging, 'getLogEntries') def getLogEntries(self): """Returns the entries in the log, most recent first. Makes a copy to prevent changes. """ # List incomprehension ;-) res = [entry.copy() for entry in self._getLog()] res.reverse() return res security.declareProtected(use_error_logging, 'getLogEntryById') def getLogEntryById(self, id): """Returns the specified log entry. Makes a copy to prevent changes. Returns None if not found. """ for entry in self._getLog(): if entry['id'] == id: return entry.copy() return None security.declareProtected(use_error_logging, 'getLogEntryAsText') def getLogEntryAsText(self, id, RESPONSE=None): """Returns the specified log entry. Makes a copy to prevent changes. Returns None if not found. """ entry = self.getLogEntryById(id) if entry is None: return 'Log entry not found or expired' if RESPONSE is not None: RESPONSE.setHeader('Content-Type', 'text/plain') return entry['tb_text'] InitializeClass(SiteErrorLog) def manage_addErrorLog(dispatcher, RESPONSE=None): """Add a site error log to a container.""" log = SiteErrorLog() dispatcher._setObject(log.id, log) if RESPONSE is not None: RESPONSE.redirect( dispatcher.DestinationURL() + '/manage_main?manage_tabs_message=Error+Log+Added.' ) zope2.13-2.13.21/source/Zope2/src/Products/SiteErrorLog/tests/0000755000175000017500000000000012214017422022545 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/SiteErrorLog/tests/testSiteErrorLog.py0000644000175000017500000001213612214017422026402 0ustar arnauarnau"""SiteErrorLog tests """ from Testing.makerequest import makerequest import Zope2 Zope2.startup() import transaction import sys import unittest import logging class SiteErrorLogTests(unittest.TestCase): def setUp(self): transaction.begin() self.app = makerequest(Zope2.app()) try: if not hasattr(self.app, 'error_log'): # If ZopeLite was imported, we have no default error_log from Products.SiteErrorLog.SiteErrorLog import SiteErrorLog self.app._setObject('error_log', SiteErrorLog()) self.app.manage_addDTMLMethod('doc', '') self.logger = logging.getLogger('Zope.SiteErrorLog') self.log = logging.handlers.BufferingHandler(sys.maxint) self.logger.addHandler(self.log) self.old_level = self.logger.level self.logger.setLevel(logging.ERROR) except: self.tearDown() def tearDown(self): self.logger.removeHandler(self.log) self.logger.setLevel(self.old_level) transaction.abort() self.app._p_jar.close() def testInstantiation(self): # Retrieve the error_log by ID sel_ob = getattr(self.app, 'error_log', None) # Does the error log exist? self.assert_(sel_ob is not None) # Is the __error_log__ hook in place? self.assert_(self.app.__error_log__ == sel_ob) # Right now there should not be any entries in the log # but if another test fails and leaves something in the # log (which belongs to app , we get a spurious error here. # There's no real point in testing this anyway. #self.assertEquals(len(sel_ob.getLogEntries()), 0) def testSimpleException(self): # Grab the Site Error Log and make sure it's empty sel_ob = self.app.error_log previous_log_length = len(sel_ob.getLogEntries()) # Fill the DTML method at self.root.doc with bogus code dmeth = self.app.doc dmeth.manage_upload(file="""""") # "Faking out" the automatic involvement of the Site Error Log # by manually calling the method "raising" that gets invoked # automatically in a normal web request environment. try: dmeth.__call__() except ZeroDivisionError: sel_ob.raising(sys.exc_info()) # Now look at the SiteErrorLog, it has one more log entry self.assertEquals(len(sel_ob.getLogEntries()), previous_log_length+1) def testForgetException(self): elog = self.app.error_log # Create a predictable error try: raise AttributeError, "DummyAttribute" except AttributeError: info = sys.exc_info() elog.raising(info) previous_log_length = len(elog.getLogEntries()) entries = elog.getLogEntries() self.assertEquals(entries[0]['value'], "DummyAttribute") # Kick it elog.forgetEntry(entries[0]['id']) # Really gone? self.assertEquals(len(elog.getLogEntries()), previous_log_length-1) def testIgnoredException(self): # Grab the Site Error Log sel_ob = self.app.error_log previous_log_length = len(sel_ob.getLogEntries()) # Tell the SiteErrorLog to ignore ZeroDivisionErrors current_props = sel_ob.getProperties() ignored = list(current_props['ignored_exceptions']) ignored.append('ZeroDivisionError') sel_ob.setProperties( current_props['keep_entries'] , copy_to_zlog = current_props['copy_to_zlog'] , ignored_exceptions = ignored ) # Fill the DTML method at self.root.doc with bogus code dmeth = self.app.doc dmeth.manage_upload(file="""""") # "Faking out" the automatic involvement of the Site Error Log # by manually calling the method "raising" that gets invoked # automatically in a normal web request environment. try: dmeth.__call__() except ZeroDivisionError: sel_ob.raising(sys.exc_info()) # Now look at the SiteErrorLog, it must have the same number of # log entries self.assertEquals(len(sel_ob.getLogEntries()), previous_log_length) def testEntryID(self): elog = self.app.error_log # Create a predictable error try: raise AttributeError, "DummyAttribute" except AttributeError: info = sys.exc_info() elog.raising(info) entries = elog.getLogEntries() entry_id = entries[0]['id'] self.assertTrue(entry_id in self.log.buffer[-1].msg, (entry_id, self.log.buffer[-1].msg)) def testCleanup(self): # Need to make sure that the __error_log__ hook gets cleaned up self.app._delObject('error_log') self.assertEquals(getattr(self.app, '__error_log__', None), None) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(SiteErrorLogTests)) return suite zope2.13-2.13.21/source/Zope2/src/Products/SiteErrorLog/tests/__init__.py0000644000175000017500000000003412214017422024653 0ustar arnauarnau# SiteErrorLog test package zope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/0000755000175000017500000000000012214017422022141 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/mount.py0000644000175000017500000002323112214017422023656 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Mounted database support """ import time import thread import logging import persistent from Acquisition import Implicit from Acquisition import ImplicitAcquisitionWrapper from Acquisition import aq_base from ZODB.POSException import StorageError class MountedStorageError(StorageError): """Unable to access mounted storage.""" logger = logging.getLogger('ZODB.Mount') # dbs is a holder for all DB objects, needed to overcome # threading issues. It maps connection params to a DB object # and a mapping of mount points. dbs = {} # dblock is locked every time dbs is accessed. dblock = thread.allocate_lock() def parentClassFactory(jar, module, name): # Use the class factory from the parent database. parent_conn = getattr(jar, '_mount_parent_jar', None) parent_db = getattr(parent_conn, '_db', None) if parent_db is None: _globals = {} _silly = ('__doc__',) return getattr(__import__( module, _globals, _globals, _silly), name) else: return parent_db.classFactory(parent_conn, module, name) class MountPoint(persistent.Persistent, Implicit): '''The base class for a Zope object which, when traversed, accesses a different database. ''' # Default values for non-persistent variables. _v_db = None _v_data = None _v_connect_error = None def __init__(self, path, params=None, classDefsFromRoot=None): ''' @arg path The path within the mounted database from which to derive the root. @arg params The parameters used to connect to the database. No particular format required. If there is more than one mount point referring to a database, MountPoint will detect the matching params and use the existing database. Include the class name of the storage. For example, ZEO params might be "ZODB.ZEOClient localhost 1081". ''' # The only reason we need a __mountpoint_id is to # be sure we don't close a database prematurely when # it is mounted more than once and one of the points # is unmounted. self.__mountpoint_id = '%s_%f' % (id(self), time.time()) if params is None: # We still need something to use as a hash in # the "dbs" dictionary. params = self.__mountpoint_id self._params = repr(params) self._path = path def _createDB(self): '''Gets the database object, usually by creating a Storage object and returning ZODB.DB(storage). ''' raise NotImplementedError def _getDB(self): '''Creates or opens a DB object. ''' newMount = 0 dblock.acquire() try: params = self._params dbInfo = dbs.get(params, None) if dbInfo is None: logger.info('Opening database for mounting: %s', params) db = self._createDB() newMount = 1 dbs[params] = (db, {self.__mountpoint_id:1}) else: db, mounts = dbInfo # Be sure this object is in the list of mount points. if not mounts.has_key(self.__mountpoint_id): newMount = 1 mounts[self.__mountpoint_id] = 1 self._v_db = db finally: dblock.release() return db, newMount def _getMountpointId(self): return self.__mountpoint_id def _getMountParams(self): return self._params def __repr__(self): return "%s(%s, %s)" % (self.__class__.__name__, repr(self._path), self._params) def _openMountableConnection(self, parent): # Opens a new connection to the database. db = self._v_db if db is None: self._v_close_db = 0 db, newMount = self._getDB() else: newMount = 0 jar = getattr(self, '_p_jar', None) if jar is None: # Get _p_jar from parent. self._p_jar = jar = parent._p_jar conn = db.open() # Add an attribute to the connection which # makes it possible for us to find the primary # database connection. See ClassFactoryForMount(). conn._mount_parent_jar = jar mcc = MountedConnectionCloser(self, conn) jar.onCloseCallback(mcc) return conn, newMount, mcc def _getObjectFromConnection(self, conn): obj = self._getMountRoot(conn.root()) data = aq_base(obj) # Store the data object in a tuple to hide from acquisition. self._v_data = (data,) return data def _getOrOpenObject(self, parent): t = self._v_data if t is None: self._v_connect_error = None conn = None newMount = 0 mcc = None try: conn, newMount, mcc = self._openMountableConnection(parent) data = self._getObjectFromConnection(conn) except: # Possibly broken database. if mcc is not None: # Note that the next line may be a little rash-- # if, for example, a working database throws an # exception rather than wait for a new connection, # this will likely cause the database to be closed # prematurely. Perhaps DB.py needs a # countActiveConnections() method. mcc.setCloseDb() logger.warning('Failed to mount database. %s (%s)', exc_info=True) raise if newMount: try: id = data.getId() except: id = '???' # data has no getId() method. Bad. p = '/'.join(parent.getPhysicalPath() + (id,)) logger.info('Mounted database %s at %s', self._getMountParams(), p) else: data = t[0] return data.__of__(parent) def __of__(self, parent): # Accesses the database, returning an acquisition # wrapper around the connected object rather than around self. try: return self._getOrOpenObject(parent) except: return ImplicitAcquisitionWrapper(self, parent) def _test(self, parent): '''Tests the database connection. ''' self._getOrOpenObject(parent) return 1 def _getMountRoot(self, root): '''Gets the object to be mounted. Can be overridden to provide different behavior. ''' try: app = root['Application'] except: raise MountedStorageError( "No 'Application' object exists in the mountable database.") try: return app.unrestrictedTraverse(self._path) except: raise MountedStorageError( "The path '%s' was not found in the mountable database." % self._path) class MountedConnectionCloser: '''Closes the connection used by the mounted database while performing other cleanup. ''' close_db = 0 def __init__(self, mountpoint, conn): # conn is the child connection. self.mp = mountpoint self.conn = conn def setCloseDb(self): self.close_db = 1 def __call__(self): # The onCloseCallback handler. # Closes a single connection to the database # and possibly the database itself. conn = self.conn close_db = 0 if conn is not None: mp = self.mp # Remove potential circular references. self.conn = None self.mp = None # Detect whether we should close the database. close_db = self.close_db t = mp.__dict__.get('_v_data', None) if t is not None: del mp.__dict__['_v_data'] data = t[0] if not close_db and data.__dict__.get( '_v__object_deleted__', 0): # This mount point has been deleted. del data.__dict__['_v__object_deleted__'] close_db = 1 # Close the child connection. try: del conn._mount_parent_jar except: pass conn.close() if close_db: # Stop using this database. Close it if no other # MountPoint is using it. dblock.acquire() try: params = mp._getMountParams() mp._v_db = None if dbs.has_key(params): dbInfo = dbs[params] db, mounts = dbInfo try: del mounts[mp._getMountpointId()] except: pass if len(mounts) < 1: # No more mount points are using this database. del dbs[params] db.close() logger.info('Closed database: %s', params) finally: dblock.release() zope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/www/0000755000175000017500000000000012214017422022765 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/www/tempfolder.gif0000644000175000017500000000032412214017422025614 0ustar arnauarnauGIF89a„ÿÿÿïïïßßßÏÏÏ¿¿¿¯¯¯ŸŸŸooo___OOO???///PPPÀÀÀÿÿªª€€€99@@@UUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù,Qà'ŽdIFhd–‘4¹êúµoÞb4|ïóàE©®J0pAžPÈK)$V,‹1¹lÖ.KMBeŽ1Œ‹»‚®Ìk´ºF5¹Mð±¾.ëûG!;zope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/__init__.py0000644000175000017500000000261412214017422024255 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Temporary Folder initialization routines """ import ZODB # for testrunner to be happy # we import this so that config files can use the shorter name, # it's not used directly from TemporaryFolder import SimpleTemporaryContainer as TemporaryContainer def initialize(context): import TemporaryFolder context.registerClass( TemporaryFolder.MountedTemporaryFolder, permission=TemporaryFolder.ADD_TEMPORARY_FOLDER_PERM, icon='www/tempfolder.gif', meta_type='Temporary Folder', constructors=(TemporaryFolder.constructTemporaryFolderForm, TemporaryFolder.constructTemporaryFolder), visibility=0 # dont show this in the add list for 2.7+ (use dbtab) ) context.registerHelp() context.registerHelpTitle('Zope Help') zope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/help/0000755000175000017500000000000012214017422023071 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/help/TemporaryFolder.stx0000644000175000017500000000302512214017422026747 0ustar arnauarnauTemporary Folders Overview Temporary Folders are Folders which store their contents "in-memory", in much the same way as a RAM disk. The contents of a Temporary Folder are lost upon shutdown. Creating By default, Zope will create a Temporary Folder named "temp_folder" in the root of every Zope installation. This Temporary Folder will be used by the Zope Sessions machinery, but it may be used for other purposes as well. You may create additional Temporary Folders. Creating a Temporary Folder is fairly straightfoward; they are created in the same way as a "regular" Folder through the Zope management interface: - Specify an id (a name) for the folder - Specify an optional title for the folder Usage Once created, a Temporary Folder acts just like regular Folder object with the exception that the items which it contains will be lost upon Zope shutdown and restart. Since Temporary Folders use RAM to store data, it is advised to add items to a Temporary Folder sparingly. The capacity of a Temporary Folder is limited by available RAM. Interaction with ZEO Temporary Folders exist local to the Zope server. Thus, each server in a ZEO cluster would have their own private copy of data in a Temporary Folder. Only temporary data that should be local to a specific Zope instance should go into a Temporary Folder. Items which need to be shared between Zope servers in a ZEO cluster should not be placed in Temporary Folders. zope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/TemporaryFolder.py0000644000175000017500000000641112214017422025633 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Mounted database support A MountedTemporaryFolder is an object that is a mount point. It mounts a TemporaryStorage-backed database and masquerades as its root object. When you traverse one of these things, the __of__ method of the mount point object is called, and that returns a Folder object that actually lives in another ZODB. To understand this fully, you'll need to read the source of Products.TemporaryFolder.mount.MountPoint. """ from App.special_dtml import DTMLFile from App.special_dtml import HTMLFile from OFS.Folder import Folder from OFS.SimpleItem import Item from tempstorage.TemporaryStorage import TemporaryStorage from ZODB.DB import DB from Products.TemporaryFolder.mount import MountPoint ADD_TEMPORARY_FOLDER_PERM="Add Temporary Folder" def constructTemporaryFolder(self, id, title=None, REQUEST=None): """ """ ms = MountedTemporaryFolder(id, title) self._setObject(id, ms) if REQUEST is not None: return self.manage_main(self, REQUEST, update_menu=1) constructTemporaryFolderForm=HTMLFile('dtml/addTemporaryFolder', globals()) class SimpleTemporaryContainer(Folder): # dbtab-style container class meta_type = 'Temporary Folder' icon = 'misc_/TemporaryFolder/tempfolder.gif' class MountedTemporaryFolder(MountPoint, Item): """ A mounted RAM database with a basic interface for displaying the reason the database did not connect. XXX this is only here for backwards compatibility purposes: DBTab uses the SimpleTemporaryContainer class instead. """ icon = 'p_/broken' manage_options = ({'label':'Traceback', 'action':'manage_traceback'},) meta_type = 'Broken Temporary Folder' def __init__(self, id, title='', params=None): self.id = str(id) self.title = title MountPoint.__init__(self, path='/') # Eep manage_traceback = DTMLFile('dtml/mountfail', globals()) def _createDB(self): """ Create a mounted RAM database """ return DB(TemporaryStorage()) def _getMountRoot(self, root): sdc = root.get('folder', None) if sdc is None: sdc = root['folder'] = Folder() self._populate(sdc, root) return sdc def mount_error_(self): return self._v_connect_error def _populate(self, folder, root): # Set up our folder object folder.id = self.id # be a chameleon folder.title = self.title folder.icon = "misc_/TemporaryFolder/tempfolder.gif" s=folder.manage_options[1:] folder.manage_options = ( {'label':'Contents', 'action':'manage_main', 'help':('TemporaryFolder','TemporaryFolder.stx')}, )+s zope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/LowConflictConnection.py0000644000175000017500000000340412214017422026757 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from ZODB.Connection import Connection from ZODB.POSException import ConflictError from cPickle import Unpickler from cStringIO import StringIO class LowConflictConnection(Connection): def setstate(self, object): """ Unlike the 'stock' Connection class' setstate, this method doesn't raise ConflictErrors. This is potentially dangerous for applications that need absolute consistency, but sessioning is not one of those. """ oid=object._p_oid invalid = self._invalid if invalid(None): # only raise a conflict if there was # a mass invalidation, but not if we see this # object's oid as invalid raise ConflictError, `oid` p, serial = self._storage.load(oid, self._version) file=StringIO(p) unpickler=Unpickler(file) unpickler.persistent_load=self._persistent_load unpickler.load() state = unpickler.load() if hasattr(object, '__setstate__'): object.__setstate__(state) else: d=object.__dict__ for k,v in state.items(): d[k]=v object._p_serial=serial zope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/patchfs.py0000644000175000017500000000331112214017422024141 0ustar arnauarnau# Utility program to patch Data.fs.in to include a temporary folder, browser # id manager, and session data manager ############################################################################ # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################ import os import sys import Globals # for data from ZODB import DB from ZODB import FileStorage import transaction from Products.Sessions.BrowserIdManager import BrowserIdManager from Products.Sessions.SessionDataManager import SessionDataManager from Products.TemporaryFolder.TemporaryFolder import MountedTemporaryFolder fs = FileStorage.FileStorage(os.path.join(Globals.data_dir,'Data.fs.in')) db = DB(fs) conn = db.open() root = conn.root() app = root['Application'] print "Patching Data.fs.in" tf = MountedTemporaryFolder('temp_folder','Temporary Folder') app._setObject('temp_folder', tf) bid = BrowserIdManager('browser_id_manager', 'Browser Id Manager') app._setObject('browser_id_manager', bid) sdm = r.SessionDataManager('session_data_manager', title='Session Data Manager', path='/temp_folder/transient_container', automatic=0) app._setObject('session_data_manager', sdm) app._p_changed = 1 transaction.commit() zope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/dtml/0000755000175000017500000000000012214017422023101 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/dtml/mountfail.dtml0000644000175000017500000000071612214017422025765 0ustar arnauarnauMount Failure Traceback

    Mount Failure Traceback

    Error type:
    Error value:
    
    
    Database not mounted.
    zope2.13-2.13.21/source/Zope2/src/Products/TemporaryFolder/dtml/addTemporaryFolder.dtml0000644000175000017500000000205212214017422027551 0ustar arnauarnau

    Temporary Folders are folderish objects which keep their contents entirely in volatile memory (RAM). Their contents are flushed on each Zope restart. They are useful for storing limited numbers of temporary objects.

    Id
    Title

    zope2.13-2.13.21/source/Zope2/src/Products/ZODBMountPoint/0000755000175000017500000000000012214017422021616 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/ZODBMountPoint/www/0000755000175000017500000000000012214017422022442 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/ZODBMountPoint/www/addMountsForm.pt0000644000175000017500000000276612214017422025604 0ustar arnauarnau

    Header

    Form Title

    Use this form to finalize the mount points configured in zope.conf. To make more mount points available, edit zope.conf.

    Path Database Status
    /virtual_hosts Virtual Hosts Ok
     
    Create new folders if the mounted objects don't yet exist
    No mount points configured.

    Footer

    zope2.13-2.13.21/source/Zope2/src/Products/ZODBMountPoint/www/mountfail.pt0000644000175000017500000000073212214017422025007 0ustar arnauarnau

    Mount Failure Traceback

    Error type: Error
    Error value: An error occurred.
    Traceback
    

    zope2.13-2.13.21/source/Zope2/src/Products/ZODBMountPoint/__init__.py0000644000175000017500000000177612214017422023742 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ZODBMountPoint product. """ def initialize(context): # Configure and load databases if not already done. import MountedObject context.registerClass( MountedObject.MountedObject, constructors=(MountedObject.manage_addMountsForm, MountedObject.manage_getMountStatus, MountedObject.manage_addMounts,), ) zope2.13-2.13.21/source/Zope2/src/Products/ZODBMountPoint/MountedObject.py0000644000175000017500000003232312214017422024735 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Mount point (stored in ZODB). """ import os import sys import traceback from cStringIO import StringIO from logging import getLogger import transaction from AccessControl.class_init import InitializeClass from Acquisition import ImplicitAcquisitionWrapper from Acquisition import aq_base from Acquisition import aq_inner from Acquisition import aq_parent from OFS.SimpleItem import SimpleItem from OFS.Folder import Folder from OFS.Folder import manage_addFolder from Products.PageTemplates.PageTemplateFile import PageTemplateFile LOG = getLogger('Zope.ZODBMountPoint') _www = os.path.join(os.path.dirname(__file__), 'www') def getConfiguration(): from App.config import getConfiguration return getConfiguration().dbtab class SimpleTrailblazer: """Follows Zope paths. If a path is not found, creates a Folder. Respects Zope security. """ restricted = 1 def __init__(self, base): self.base = base def _construct(self, context, id): """Creates and returns the named folder.""" manage_addFolder(context, id) o = context.restrictedTraverse(id) context._p_jar.add(aq_base(o)) return o def traverseOrConstruct(self, path, omit_final=0): """Traverses a path, constructing it if necessary.""" container = self.base parts = filter(None, path.split('/')) if omit_final: if len(parts) < 1: raise ValueError, 'Path %s is not a valid mount path' % path parts = parts[:-1] for part in parts: try: if self.restricted: container = container.restrictedTraverse(part) else: container = container.unrestrictedTraverse(part) except (KeyError, AttributeError): # Try to create a container in this place. container = self._construct(container, part) return container class CustomTrailblazer (SimpleTrailblazer): """Like SimpleTrailblazer but creates custom objects. Does not respect Zope security because this may be invoked before security and products get initialized. """ restricted = 0 def __init__(self, base, container_class=None): self.base = base if not container_class: container_class = 'OFS.Folder.Folder' pos = container_class.rfind('.') if pos < 0: raise ValueError("Not a valid container_class: %s" % repr( container_class)) self.module_name = container_class[:pos] self.class_name = container_class[pos + 1:] def _construct(self, context, id): """Creates and returns the named object.""" jar = self.base._p_jar klass = jar.db().classFactory(jar, self.module_name, self.class_name) obj = klass(id) obj._setId(id) context._setObject(id, obj) obj = context.unrestrictedTraverse(id) # Commit a subtransaction to assign the new object to # the correct database. transaction.savepoint(optimistic=True) return obj class MountedObject(SimpleItem): '''A database mount point with a basic interface for displaying the reason the database did not connect. ''' meta_type = 'ZODB Mount Point' _isMountedObject = 1 # DM 2005-05-17: default value change necessary after fix of # '_create_mount_point' handling #_create_mount_points = 0 _create_mount_points = True icon = 'p_/broken' manage_options = ({'label':'Traceback', 'action':'manage_traceback'},) _v_mount_params = None _v_data = None _v_connect_error = None manage_traceback = PageTemplateFile('mountfail.pt', _www) def __init__(self, path): path = str(path) self._path = path id = path.split('/')[-1] self.id = id def _getMountedConnection(self, anyjar): # This creates the DB if it doesn't exist yet and adds it # to the multidatabase self._getDB() # Return a new or existing connection linked to the multidatabase set return anyjar.get_connection(self._getDBName()) def mount_error_(self): return self._v_connect_error def _getDB(self): """Hook for getting the DB object for this mount point. """ return getConfiguration().getDatabase(self._path) def _getDBName(self): """Hook for getting the name of the database for this mount point. """ return getConfiguration().getDatabaseFactory(self._path).getName() def _getRootDBName(self): """Hook for getting the name of the root database. """ return getConfiguration().getDatabaseFactory('/').getName() def _loadMountParams(self): factory = getConfiguration().getDatabaseFactory(self._path) params = factory.getMountParams(self._path) self._v_mount_params = params return params def _traverseToMountedRoot(self, root, mount_parent): """Hook for getting the object to be mounted. """ params = self._v_mount_params if params is None: params = self._loadMountParams() real_root, real_path, container_class = params if real_root is None: real_root = 'Application' try: obj = root[real_root] except KeyError: # DM 2005-05-17: why should we require 'container_class'? #if container_class or self._create_mount_points: if self._create_mount_points: # Create a database automatically. from OFS.Application import Application obj = Application() root[real_root] = obj # Get it into the database transaction.savepoint(optimistic=True) else: raise if real_path is None: real_path = self._path if real_path and real_path != '/': try: obj = obj.unrestrictedTraverse(real_path) except (KeyError, AttributeError): # DM 2005-05-13: obviously, we do not want automatic # construction when "_create_mount_points" is false #if container_class or self._create_mount_points: if container_class and self._create_mount_points: blazer = CustomTrailblazer(obj, container_class) obj = blazer.traverseOrConstruct(real_path) else: raise return obj def _logConnectException(self): '''Records info about the exception that just occurred. ''' exc = sys.exc_info() LOG.error('Failed to mount database. %s (%s)' % exc[:2], exc_info=exc) f=StringIO() traceback.print_tb(exc[2], 100, f) self._v_connect_error = (exc[0], exc[1], f.getvalue()) exc = None def __of__(self, parent): # Accesses the database, returning an acquisition # wrapper around the connected object rather than around self. try: return self._getOrOpenObject(parent) except: return ImplicitAcquisitionWrapper(self, parent) def _test(self, parent): '''Tests the database connection. ''' self._getOrOpenObject(parent) return 1 def _getOrOpenObject(self, parent): t = self._v_data if t is not None: data = t[0] else: self._v_connect_error = None conn = None try: anyjar = self._p_jar if anyjar is None: anyjar = parent._p_jar conn = self._getMountedConnection(anyjar) root = conn.root() obj = self._traverseToMountedRoot(root, parent) data = aq_base(obj) # Store the data object in a tuple to hide from acquisition. self._v_data = (data,) except: # Possibly broken database. self._logConnectException() raise try: # XXX This method of finding the mount point is deprecated. # Do not use the _v_mount_point_ attribute. data._v_mount_point_ = (aq_base(self),) except: # Might be a read-only object. pass return data.__of__(parent) def __repr__(self): return "%s(id=%s)" % (self.__class__.__name__, repr(self.id)) InitializeClass(MountedObject) def getMountPoint(ob): """Gets the mount point for a mounted object. Returns None if the object is not a mounted object. """ container = aq_parent(aq_inner(ob)) mps = getattr(container, '_mount_points', None) if mps: mp = mps.get(ob.getId()) if mp is not None and (mp._p_jar is ob._p_jar or ob._p_jar is None): # Since the mount point and the mounted object are from # the same connection, the mount point must have been # replaced. The object is not mounted after all. return None # else the object is mounted. return mp return None def setMountPoint(container, id, mp): mps = getattr(container, '_mount_points', None) if mps is None: container._mount_points = {id: aq_base(mp)} else: container._p_changed = 1 mps[id] = aq_base(mp) manage_addMountsForm = PageTemplateFile('addMountsForm.pt', _www) def manage_getMountStatus(dispatcher): """Returns the status of each mount point specified by zope.conf """ res = [] conf = getConfiguration() items = conf.listMountPaths() items.sort() root = dispatcher.getPhysicalRoot() for path, name in items: if not path or path == '/': # Ignore the root mount. continue o = root.unrestrictedTraverse(path, None) # Examine the _v_mount_point_ attribute to verify traversal # to the correct mount point. if o is None: exists = 0 status = 'Ready to create' elif getattr(o, '_isMountedObject', 0): # Oops, didn't actually mount! exists = 1 t, v = o._v_connect_error[:2] status = '%s: %s' % (t, v) else: exists = 1 mp = getMountPoint(o) if mp is None: mp_old = getattr(o, '_v_mount_point_', None) if mp_old is not None: # Use the old method of accessing mount points # to update to the new method. # Update the container right now. setMountPoint(dispatcher.this(), o.getId(), mp_old[0]) status = 'Ok (updated)' else: status = '** Something is in the way **' else: mp_path = getattr(mp, '_path', None) if mp_path != path: status = '** Set to wrong path: %s **' % repr(mp_path) else: status = 'Ok' res.append({ 'path': path, 'name': name, 'exists': exists, 'status': status, }) return res # DM 2005-05-17: change default for 'create_mount_points' as # otherwise (after our fix) 'temp_folder' can no longer be mounted #def manage_addMounts(dispatcher, paths=(), create_mount_points=0, def manage_addMounts(dispatcher, paths=(), create_mount_points=True, REQUEST=None): """Adds MountedObjects at the requested paths. """ count = 0 app = dispatcher.getPhysicalRoot() for path in paths: mo = MountedObject(path) mo._create_mount_points = not not create_mount_points # Raise an error now if there is any problem. mo._test(app) blazer = SimpleTrailblazer(app) container = blazer.traverseOrConstruct(path, omit_final=1) container._p_jar.add(mo) loaded = mo.__of__(container) # Add a faux object to avoid generating manage_afterAdd() events # while appeasing OFS.ObjectManager._setObject(), then discreetly # replace the faux object with a MountedObject. faux = Folder() faux.id = mo.id faux.meta_type = loaded.meta_type container._setObject(faux.id, faux) # DM 2005-05-17: we want to keep our decision about automatic # mount point creation #del mo._create_mount_points container._setOb(faux.id, mo) setMountPoint(container, faux.id, mo) count += 1 if REQUEST is not None: REQUEST['RESPONSE'].redirect( REQUEST['URL1'] + ('/manage_main?manage_tabs_message=' 'Added %d mount points.' % count)) zope2.13-2.13.21/source/Zope2/src/Products/ZODBMountPoint/tests/0000755000175000017500000000000012214017422022760 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/ZODBMountPoint/tests/testMountPoint.py0000644000175000017500000002044412214017422026352 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Tests of ZODBMountPoint """ import os import os.path import sys import unittest import transaction from OFS.Application import Application from OFS.Folder import Folder import App.config from Products.ZODBMountPoint.MountedObject import manage_addMounts from Products.ZODBMountPoint.MountedObject import getMountPoint from Products.ZODBMountPoint.MountedObject import manage_getMountStatus from Zope2.Startup.datatypes import DBTab try: __file__ except NameError: __file__ = os.path.abspath(sys.argv[0]) class TestDBConfig: def __init__(self, fname, mpoints): self.fname = fname self.mpoints = mpoints def getDB(self): from ZODB.config import DemoStorage from ZODB.Connection import Connection from Zope2.Startup.datatypes import ZopeDatabase self.name = self.fname self.base = None self.path = os.path.join(os.path.dirname(__file__), self.fname) self.cache_size = 5000 self.cache_size_bytes = 0 self.class_factory = None self.connection_class = Connection self.container_class = None self.create = None self.factories = () self.historical_pool_size = 3 self.historical_cache_size = 1000 self.historical_cache_size_bytes = 0 self.historical_timeout = 300 self.mount_points = self.mpoints self.pool_size = 7 self.pool_timeout = 1<<31 self.quota = None self.read_only = None self.storage = DemoStorage(self) self.version_cache_size = 100 self.version_pool_size = 3 self.allow_implicit_cross_references = False self.large_record_size = 1<<24 return ZopeDatabase(self) def getSectionName(self): return self.name original_config = None class MountingTests(unittest.TestCase): def setUp(self): global original_config if original_config is None: # stow away original config so we can reset it original_config = App.config.getConfiguration() databases = [TestDBConfig('test_main.fs', ['/']).getDB(), TestDBConfig('test_mount1.fs', ['/mount1']).getDB(), TestDBConfig('test_mount2.fs', ['/mount2']).getDB(), TestDBConfig('test_mount3.fs', ['/i/mount3']).getDB(), ] mount_points = {} mount_factories = {} for database in databases: points = database.getVirtualMountPaths() name = database.config.getSectionName() mount_factories[name] = database for point in points: mount_points[point] = name conf = DBTab(mount_factories, mount_points) d = App.config.DefaultConfiguration() d.dbtab = conf App.config.setConfiguration(d) self.conf = conf db = conf.getDatabase('/') conn = db.open() root = conn.root() root['Application'] = app = Application() self.app = app # login from AccessControl.User import system from AccessControl.SecurityManagement import newSecurityManager newSecurityManager(None, system) transaction.commit() # Get app._p_jar set manage_addMounts(app, ('/mount1', '/mount2', '/i/mount3')) transaction.commit() # Get the mount points ready def tearDown(self): # logout from AccessControl.SecurityManagement import noSecurityManager noSecurityManager() App.config.setConfiguration(original_config) transaction.abort() self.app._p_jar.close() del self.app for db in self.conf.databases.values(): db.close() del self.conf def testRead(self): self.assertEqual(self.app.mount1.id, 'mount1') self.assertEqual(self.app.mount2.id, 'mount2') self.assertEqual(self.app.i.mount3.id, 'mount3') def testWrite(self): app = self.app app.mount1.a1 = '1' app.mount2.a2 = '2' app.a3 = '3' self.assertEqual(app.mount1._p_changed, 1) self.assertEqual(app.mount2._p_changed, 1) self.assertEqual(app._p_changed, 1) transaction.commit() self.assertEqual(app.mount1._p_changed, 0) self.assertEqual(app.mount2._p_changed, 0) self.assertEqual(app._p_changed, 0) self.assertEqual(app.mount1.a1, '1') self.assertEqual(app.mount2.a2, '2') self.assertEqual(app.a3, '3') def testGetMountPoint(self): self.assert_(getMountPoint(self.app) is None) self.assert_(getMountPoint(self.app.mount1) is not None) self.assertEqual(getMountPoint(self.app.mount1)._path, '/mount1') self.assert_(getMountPoint(self.app.mount2) is not None) self.assertEqual(getMountPoint(self.app.mount2)._path, '/mount2') self.assertEqual(getMountPoint(self.app.i.mount3)._path, '/i/mount3') del self.app.mount2 self.app.mount2 = Folder() self.app.mount2.id = 'mount2' self.assert_(getMountPoint(self.app.mount2) is None) transaction.commit() self.assert_(getMountPoint(self.app.mount2) is None) def test_manage_getMountStatus(self): status = manage_getMountStatus(self.app) expected = [{'status': 'Ok', 'path': '/mount1', 'name': 'test_mount1.fs', 'exists': 1}, {'status': 'Ok', 'path': '/mount2', 'name': 'test_mount2.fs', 'exists': 1}, {'status': 'Ok', 'path': '/i/mount3', 'name': 'test_mount3.fs', 'exists': 1}, ] self.assertEqual(sorted(expected), sorted(status)) del self.app.mount2 status = manage_getMountStatus(self.app) expected = [{'status': 'Ok', 'path': '/mount1', 'name': 'test_mount1.fs', 'exists': 1}, {'status': 'Ready to create', 'path': '/mount2', 'name': 'test_mount2.fs', 'exists': 0}, {'status': 'Ok', 'path': '/i/mount3', 'name': 'test_mount3.fs', 'exists': 1}, ] self.assertEqual(sorted(expected), sorted(status)) self.app.mount2 = Folder('mount2') status = manage_getMountStatus(self.app) expected = [{'status': 'Ok', 'path': '/mount1', 'name': 'test_mount1.fs', 'exists': 1}, {'status': '** Something is in the way **', 'path': '/mount2', 'name': 'test_mount2.fs', 'exists': 1}, {'status': 'Ok', 'path': '/i/mount3', 'name': 'test_mount3.fs', 'exists': 1}, ] self.assertEqual(sorted(expected), sorted(status)) def test_close(self): app = self.app app.mount1.a1 = '1' app.mount2.a2 = '2' app.a3 = '3' conn1 = app.mount1._p_jar conn2 = app.mount2._p_jar conn3 = app.i.mount3._p_jar transaction.abort() # Close the main connection app._p_jar.close() self.assertEqual(app._p_jar.opened, None) # Check that secondary connections have been closed too self.assertEqual(conn1.opened, None) self.assertEqual(conn2.opened, None) self.assertEqual(conn3.opened, None) def test_suite(): return unittest.makeSuite(MountingTests, 'test') zope2.13-2.13.21/source/Zope2/src/Products/ZODBMountPoint/tests/__init__.py0000644000175000017500000000003212214017422025064 0ustar arnauarnau"""DBTab tests package""" zope2.13-2.13.21/source/Zope2/src/Products/__init__.py0000644000175000017500000000135012214017421021132 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages __import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/Zope2/src/Products/Sessions/0000755000175000017500000000000012214017422020631 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Sessions/common.py0000644000175000017500000000122312214017422022471 0ustar arnauarnau############################################################################ # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################ import os DEBUG = os.environ.get('CST_DEBUG', '') zope2.13-2.13.21/source/Zope2/src/Products/Sessions/stresstests/0000755000175000017500000000000012214017422023237 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Sessions/stresstests/stresstestMultiThread.py0000644000175000017500000001707212214017422030206 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import time from Testing import makerequest import ZODB # in order to get Persistence.Persistent working import transaction import Acquisition from Products.Sessions.BrowserIdManager import BrowserIdManager, \ getNewBrowserId from Products.Sessions.SessionDataManager import \ SessionDataManager from Products.Transience.Transience import \ TransientObjectContainer from Products.TemporaryFolder.TemporaryFolder import MountedTemporaryFolder from ZODB.POSException import ConflictError, \ ReadConflictError, BTreesConflictError from unittest import TestCase, TestSuite, makeSuite import threading, random from ZODB.DemoStorage import DemoStorage from OFS.Application import Application import traceback from Products.Transience.tests import fauxtime import Products.Transience.Transience import Products.Transience.TransientObject Products.Transience.Transience.time = fauxtime Products.Transience.TransientObject.time = fauxtime tf_name = 'temp_folder' idmgr_name = 'browser_id_manager' toc_name = 'temp_transient_container' sdm_name = 'session_data_manager' stuff = {} def log_time(): """Return a simple time string without spaces suitable for logging.""" return ("%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d" % time.localtime()[:6]) def _getDB(): db = stuff.get('db') if not db: ds = DemoStorage() db = ZODB.DB(ds) conn = db.open() root = conn.root() app = Application() root['Application']= app _populate(app) transaction.commit() stuff['db'] = db conn.close() return db def _delDB(): transaction.abort() del stuff['db'] class Foo(Acquisition.Implicit): pass def _populate(app): bidmgr = BrowserIdManager(idmgr_name) tf = MountedTemporaryFolder(tf_name, 'Temporary Folder') toc = TransientObjectContainer(toc_name, title='Temporary ' 'Transient Object Container', timeout_mins=1) session_data_manager=SessionDataManager(id=sdm_name, path='/'+tf_name+'/'+toc_name, title='Session Data Manager') try: app._delObject(idmgr_name) except AttributeError: pass try: app._delObject(tf_name) except AttributeError: pass try: app._delObject(sdm_name) except AttributeError: pass app._setObject(idmgr_name, bidmgr) app._setObject(sdm_name, session_data_manager) app._setObject(tf_name, tf) transaction.commit() app.temp_folder._setObject(toc_name, toc) transaction.commit() class TestMultiThread(TestCase): def testOverlappingBrowserIds(self): token = getNewBrowserId() self.go(token) def testNonOverlappingBrowserIds(self): token = None self.go(token) def go(self, token): readers = [] writers = [] valuers = [] readiters = 3 writeiters = 3 valueiters = 3 numreaders = 2 numwriters = 4 numvaluers = 1 db = _getDB() for i in range(numreaders): thread = ReaderThread(db, readiters, token) readers.append(thread) for i in range(numvaluers): thread = ValuesGetterThread(db, valueiters, token) valuers.append(thread) for i in range(numwriters): thread = WriterThread(db, writeiters, token) writers.append(thread) for thread in readers: thread.start() time.sleep(0.1) for thread in writers: thread.start() time.sleep(0.1) for thread in valuers: thread.start() time.sleep(0.1) active = threading.activeCount() while active > 0: active = threading.activeCount()-1 print 'waiting for %s threads' % active print "readers: ", numActive(readers), print "writers: ", numActive(writers), print "valuers: ", numActive(valuers) time.sleep(5) def numActive(threads): i = 0 for thread in threads: if not thread.isFinished(): i+=1 return i class BaseReaderWriter(threading.Thread): def __init__(self, db, iters, token=None): self.iters = iters self.sdm_name = sdm_name self.finished = 0 self.db = db self.token = token threading.Thread.__init__(self) def run(self): i = 0 try: while 1: self.conn = self.db.open() self.app = self.conn.root()['Application'] self.app = makerequest.makerequest(self.app) if self.token is None: token = getNewBrowserId() else: token = self.token self.app.REQUEST.browser_id_ = token try: self.run1() return except ReadConflictError: #traceback.print_exc() print "R", except BTreesConflictError: print "B", except ConflictError: print "W", except: transaction.abort() print log_time() traceback.print_exc() raise i = i + 1 transaction.abort() self.conn.close() time.sleep(random.randrange(10) * .1) finally: transaction.abort() self.conn.close() del self.app self.finished = 1 print '%s finished' % self.__class__ def isFinished(self): return self.finished class ReaderThread(BaseReaderWriter): def run1(self): session_data_manager = getattr(self.app, self.sdm_name) data = session_data_manager.getSessionData(create=1) t = time.time() data[t] = 1 transaction.commit() for i in range(self.iters): data = session_data_manager.getSessionData() time.sleep(random.choice(range(3))) transaction.commit() class WriterThread(BaseReaderWriter): def run1(self): session_data_manager = getattr(self.app, self.sdm_name) for i in range(self.iters): data = session_data_manager.getSessionData() data[time.time()] = 1 n = random.choice(range(3)) time.sleep(n) if n % 2 == 0: transaction.commit() else: transaction.abort() class ValuesGetterThread(BaseReaderWriter): def run1(self): tf = getattr(self.app, tf_name) toc = getattr(tf, toc_name) for i in range(self.iters): print '%s values in toc' % len(toc.values()) n = random.choice(range(3)) time.sleep(n) if n % 2 == 0: transaction.commit() else: transaction.abort() def test_suite(): test_multithread = makeSuite(TestMultiThread, 'test') suite = TestSuite((test_multithread,)) return suite zope2.13-2.13.21/source/Zope2/src/Products/Sessions/www/0000755000175000017500000000000012214017422021455 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Sessions/www/datamgr.gif0000644000175000017500000000102212214017422023556 0ustar arnauarnauGIF89aæÿÿÿ^^TTIIFFEE<<66332200**%%k kl ltt‡7‡E‘G‘˜S˜šVš›X›§m§¬t¬¯z¯É¿º¥¢™’‘ŽŠ~un^][ZXV;4ÍÐÒÓ""Ô&&Ö33×77ÛIIÜOOÜPPÝUUß^^áhhãqqävväyyå||怀煅燇爈牉ÿÿÿ!ùF,o€F‚ƒ„…†‡ˆƒ>‰†BA‰ƒCE?ˆƒ@D@;6†ƒ<>=92ƒF8:73"#F F540&+-(ƒ ƒ1!)/Ò„ ƒ *Ó… ƒ'./Œ‚$,äƒ%èëìF;zope2.13-2.13.21/source/Zope2/src/Products/Sessions/www/idmgr.gif0000644000175000017500000000103012214017422023240 0ustar arnauarnauGIF89aæÿÿÿ]]ZZYYRRKKIIAA886655,,(("" nnppyy=‹‹@ŒŒN••T˜˜U™™k¦¦x®®|°°…¶¶‹¹¹–ÀÀÉ¿º¥¢™’‘ŽŠ~un^][ZXV;4ÍÐÒÓ""Ô&&Ö33×77ÛIIÜOOÜPPÝUUß^^áhhãqqävväyyå||怀煅燇爈牉ÿÿÿ!ùK,u€K‚ƒ„…†‡ˆƒC‰†GFŒ„HJD‡‚EIE@;„KACB>7ƒK=?<8!$'(K K:95"+02-K K6 &.4Úƒ ‚ %/ÛŒ#,34K)1ë‚*ïòï;zope2.13-2.13.21/source/Zope2/src/Products/Sessions/interfaces.py0000644000175000017500000001733612214017422023340 0ustar arnauarnau############################################################################ # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################ """ Session APIs o See Also - "Transient Object API":../../Transience/Help/TransienceInterfaces.py """ from zope.interface import Interface class IBrowserIdManager(Interface): """ Zope Browser Id Manager interface. A Zope Browser Id Manager is responsible for assigning ids to site visitors, and for servicing requests from Session Data Managers related to the browser id. """ def hasBrowserId(): """ Return true if there is a browser id for the current request. o Permission required: Access contents information o Does *not* raise an error if the request contains a broken browser id. """ def getBrowserId(create=1): """ Return a browser id for the current request. o If create is false, return None if there is no browser id associated with the current request. o If create is true, return a newly-created browser id if there is no browser id associated with the current request. o This method is useful in conjunction with 'getBrowserIdName' if you wish to embed the browser-id-name/browser-id combination as a hidden value in a POST-based form. o The browser id is opaque, has no business meaning, and its length, type, and composition are subject to change. o Permission required: Access contents information o Raises BrowserIdManagerErr if an ill-formed browser id is found in REQUEST. """ def getBrowserIdName(): """ Returns a string with the name of the cookie/form variable which is used by the current browser id manager as the name to look up when attempting to obtain the browser id value. For example, '_ZopeId'. Permission required: Access contents information """ def isBrowserIdNew(): """ Returns true if browser id is 'new'. A browser id is 'new' when it is first created and the client has therefore not sent it back to the server in any request. Permission required: Access contents information Raises: BrowserIdManagerErr. If there is no current browser id. """ def isBrowserIdFromCookie(): """ Return true if browser id comes from a cookie. o Permission required: Access contents information o Raise BrowserIdManagerErr if there is no current browser id. """ def isBrowserIdFromForm(): """ Return true if browser id comes from a form variable. o Variable may come from either the query string or a post. o Permission required: Access contents information o Raise BrowserIdManagerErr if there is no current browser id. """ def isBrowserIdFromUrl(): """ Return true if browser id comes from a cookie. o Permission required: Access contents information o Raise BrowserIdManagerErr if there is no current browser id. """ def flushBrowserIdCookie(): """ Deletes the browser id cookie from the client browser. o Permission required: Access contents information o Raise BrowserIdManagerErr if the 'cookies' namespace isn't a browser id namespace. """ def setBrowserIdCookieByForce(bid): """ Sets the browser id cookie to browser id 'bid' by force. o Useful when you need to 'chain' browser id cookies across domains for the same user (perhaps temporarily using query strings). o Permission required: Access contents information o Raise BrowserIdManagerErr if the 'cookies' namespace isn't a browser id namespace. """ def getHiddenFormField(): """ Return a string usable as a hidden form field for the browser id. o String is of the form:: o name and the value represent the current browser id name and current browser id. """ def encodeUrl(url, style='querystring'): """ Encode a given URL with the current browser id. o Two forms of URL-encoding are supported: 'querystring' and 'inline'. o 'querystring' is the default. o If the 'querystring' form is used, the browser id name/value pair are postfixed onto the URL as a query string. o If the 'inline' form is used, the browser id name/value pair are prefixed onto the URL as the first two path segment elements. o For example: - The call encodeUrl('http://foo.com/amethod', style='querystring') might return 'http://foo.com/amethod?_ZopeId=as9dfu0adfu0ad'. - The call encodeUrl('http://foo.com/amethod, style='inline') might return 'http://foo.com/_ZopeId/as9dfu0adfu0ad/amethod'. o Permission required: Access contents information o Raise BrowserIdManagerErr if there is no current browser id. """ class BrowserIdManagerErr(ValueError): """ Error raised during some browser id manager operations o See IBrowserIdManager methods. o This exception may be caught in PythonScripts. A successful import of the exception for PythonScript use would need to be:: from Products.Sessions.interfaces import BrowserIdManagerErr """ class ISessionDataManager(Interface): """ Zope Session Data Manager interface. A Zope Session Data Manager is responsible for maintaining Session Data Objects, and for servicing requests from application code related to Session Data Objects. It also communicates with a Browser Id Manager to provide information about browser ids. """ def getBrowserIdManager(): """ Return the nearest acquirable browser id manager. o Raise SessionDataManagerErr if no browser id manager can be found. o Permission required: Access session data """ def getSessionData(create=1): """ Return a Session Data Object for the current browser id. o If there is no current browser id, and create is true, return a new Session Data Object. o If there is no current browser id and create is false, returns None. o Permission required: Access session data """ def hasSessionData(): """ Does a Session Data Object exist for the current browser id? o Do not create a Session Data Object if one does not exist. o Permission required: Access session data """ def getSessionDataByKey(key): """ Return a Session Data Object associated with 'key'. o If there is no Session Data Object associated with 'key', return None. o Permission required: Access arbitrary user session data """ class SessionDataManagerErr(ValueError): """ Error raised during some session data manager operations o See ISesseionDataManager. o This exception may be caught in PythonScripts. A successful import of the exception for PythonScript use would need to be:: from Products.Sessions.interfaces import SessionDataManagerErr """ zope2.13-2.13.21/source/Zope2/src/Products/Sessions/__init__.py0000644000175000017500000000477212214017422022754 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Session managemnt product initialization """ from Products.Sessions.interfaces import BrowserIdManagerErr #BBB from Products.Sessions.interfaces import SessionDataManagerErr #BBB def initialize(context): import BrowserIdManager import SessionDataManager context.registerClass( BrowserIdManager.BrowserIdManager, icon="www/idmgr.gif", permission=BrowserIdManager.ADD_BROWSER_ID_MANAGER_PERM, constructors=(BrowserIdManager.constructBrowserIdManagerForm, BrowserIdManager.constructBrowserIdManager) ) context.registerClass( SessionDataManager.SessionDataManager, icon='www/datamgr.gif', permission=SessionDataManager.ADD_SESSION_DATAMANAGER_PERM, constructors=(SessionDataManager.constructSessionDataManagerForm, SessionDataManager.constructSessionDataManager) ) context.registerHelp() context.registerHelpTitle("Zope Help") # do module security declarations so folks can use some of the # module-level stuff in PythonScripts # # declare on behalf of Transience too, since ModuleSecurityInfo is too # stupid for me to declare in two places without overwriting one set # with the other. :-( from AccessControl import ModuleSecurityInfo security = ModuleSecurityInfo('Products') security.declarePublic('Sessions') security.declarePublic('Transience') security = ModuleSecurityInfo('Products.Sessions.interfaces') security.declareObjectPublic() security.setDefaultAccess('allow') security = ModuleSecurityInfo('Products.Transience') security.declarePublic('MaxTransientObjectsExceeded') #BBB for names which should be imported from Products.Sessions.interfaces security = ModuleSecurityInfo('Products.Sessions') security.declarePublic('BrowserIdManagerErr') security.declarePublic('SessionDataManagerErr') zope2.13-2.13.21/source/Zope2/src/Products/Sessions/help/0000755000175000017500000000000012214017422021561 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Sessions/help/browser-add.stx0000644000175000017500000000757212214017422024545 0ustar arnauarnauBrowser Id Manager - Add A browser id manager is an object which identifies visitors to your site, even if they don't log in. Browser id managers are part of the Zope sessioning machinery. Form options available are: Id -- you cannot choose an 'id' for your browser id manager. It must always be "browser_id_manager". Additionally, you cannot rename a browser id manager. This is required in the current implementation so that session data managers can find browser id managers via Zope acquisition. This may be changed in a later release. Title -- the browser id manager title. Browser Id Name -- the cookie name and/or form variable name used for this browser id manager instance. This will be the name looked up in the 'cookies' or 'form' REQUEST namespaces when the browser id manager attempts to find a cookie or form variable with a browser id in it. Look for Browser Id In -- choose any of 'Forms and Query Strings', 'URLs', or 'Cookies'. The browser id name/value will be looked for within these places. Automatically Encode Zope-Generated URLs With A Browser Id -- if this is selected, URLs generated by Zope (such as URLs which come as a result of calling an object's 'absolute_url' method) will be encoded with a browser name and browser id as the first two elements of the URL path. Cookie path -- this is the 'path' element which should be sent in the session token cookie. For more information, see the Netscape Cookie specification at http://home.netscape.com/newsref/std/cookie_spec.html. Cookie domain -- this is the "domain" element which should be sent in the browser id cookie. For more information, see the Netscape Cookie specification at http://home.netscape.com/newsref/std/cookie_spec.html. Leaving this form element blank results in no domain element in the cookie. If you change the cookie domain here, the value you enter must have at least two dots (as per the cookie spec). Cookie lifetime in days -- browser id cookies sent to browsers will last this many days on a remote system before expiring if this value is set. If this value is 0, cookies will persist on client browsers for only as long as the browser is open. Only send cookie over https -- if this flag is set, only send cookies to remote browsers if they're communicating with us over https. The browser id cookie sent under this circumstance will also have the 'secure' flag set in it, which the remote browser should interpret as a request to refrain from sending the cookie back to the server over an insecure (non-https) connection. NOTE: In the case you wish to share browser id cookies between https and non-https connections from the same browser, do not set this flag. After reviewing and changing these options, click the "Add" button to instantiate a browser id manager. You can manage a browser id manager by visiting it in the management interface. Instantiating Multiple Browser Id Managers (Optional) If you've got special needs, you may want to instantiate more than one browser id manager. In its default configuration, Zope will not allow you to create a browser id manager if one is installed in the root or in a place where the new browser id manager can acquire the original browser id manager via its containment path (for programmers: the session id manager's class' Zope __replaceable__ property is set to UNIQUE). This means, practically, that if you wish to have multiple browser id managers, you need to carefully delete the root browser id manager, then you need to place additional browser id managers in the most deeply-nested containers first, working your way out towards the root, finally replacing the root browser id manager if desired. See Also - "Session API":SessionInterfaces.py zope2.13-2.13.21/source/Zope2/src/Products/Sessions/help/session-change.stx0000644000175000017500000000127312214017422025232 0ustar arnauarnauSession Data Manager - Change The session data manager add form displays these options: title -- choose a title for the session data manager transient object container path -- the path in Zope to a transient object container which will store the actual session data. This path is /temp_folder/transient_container in a default Zope installation. place SESSION in REQUEST as -- If set, the REQUEST variable will be populated with the session object, stored as the given name (default is 'SESSION') After reviewing and changing these options, click the "Change" button to change the session data manager. See Also - "Session API":SessionInterfaces.py zope2.13-2.13.21/source/Zope2/src/Products/Sessions/help/session-add.stx0000644000175000017500000000314012214017422024530 0ustar arnauarnauSession Data Manager - Add A Zope Session Data Manager is responsible for maintaining a relationship between session data objects and Zope browser ids. It is part of the Zope sessioning machinery. Programmers will sometimes interact with a session data manager in order to obtain information about session data. You can place a session data manager in any Zope container,as long as a browser id manager object can be acquired from that container. The session data manager will use the first acquired object named "browser_id_manager" as a browser id manager. Choose "Session Data Manager" within the container you wish to house the session data manager from the "Add" dropdown box in the Zope management interface. The session data manager add form displays these options: id -- choose an id for the session data manager title -- choose a title for the session data manager transient object container path -- the path in Zope to a transient object container which will store the actual session data. This path is /temp_folder/transient_container in a default Zope installation. place SESSION in REQUEST as -- If set, the REQUEST variable will be populated with the session object, stored as the given name (default is 'SESSION') After reviewing and changing these options, click the "Add" button to instantiate a session data manager. You can manage a session data manager by visiting it in the management interface. You may change all options available during the add process by doing this. See Also - "Session API":SessionInterfaces.py zope2.13-2.13.21/source/Zope2/src/Products/Sessions/help/browser-change.stx0000644000175000017500000000460312214017422025232 0ustar arnauarnauBrowser Id Manager - Change Form options available are: Title -- the browser id manager title. Browser id name -- the cookie or forms variable name used for this browser id manager instance. This will be the name looked up in the namespaces specified by "Look for browser id name in" (below). Look for Browser Id In -- choose any of 'Forms and Query Strings', 'URLs', or 'Cookies'. The browser id name/value will be looked for within these places. Automatically Encode Zope-Generated URLs With A Browser Id -- if this is selected, URLs generated by Zope (such as URLs which come as a result of calling an object's 'absolute_url' method) will be encoded with a browser name and browser id as the first two elements of the URL path. Cookie path -- this is the 'path' element which should be sent in the session token cookie. For more information, see the Netscape Cookie specification at http://home.netscape.com/newsref/std/cookie_spec.html. Cookie domain -- this is the "domain" element which should be sent in the browser id cookie. For more information, see the Netscape Cookie specification at http://home.netscape.com/newsref/std/cookie_spec.html. Leaving this form element blank results in no domain element in the cookie. If you change the cookie domain here, the value you enter must have at least two dots (as per the cookie spec). Cookie lifetime in days -- browser id cookies sent to browsers will last this many days on a remote system before expiring if this value is set. If this value is 0, cookies will persist on client browsers for only as long as the browser is open. Only send cookie over https -- if this flag is set, only send cookies to remote browsers if they're communicating with us over https. The browser id cookie sent under this circumstance will also have the 'secure' flag set in it, which the remote browser should interpret as a request to refrain from sending the cookie back to the server over an insecure (non-https) connection. NOTE: In the case you wish to share browser id cookies between https and non-https connections from the same browser, do not set this flag. After reviewing and changing these options, click the "Change" button to change the browser id manager. See Also - "Session API":SessionInterfaces.py zope2.13-2.13.21/source/Zope2/src/Products/Sessions/SessionDataManager.py0000644000175000017500000002744112214017422024723 0ustar arnauarnau############################################################################ # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################ from logging import getLogger import re import sys from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import Implicit from App.special_dtml import DTMLFile from App.Management import Tabs from OFS.owner import Owned from OFS.role import RoleManager from OFS.SimpleItem import Item from Persistence import Persistent from ZPublisher.BeforeTraverse import registerBeforeTraverse from ZPublisher.BeforeTraverse import unregisterBeforeTraverse from ZODB.POSException import ConflictError from zope.interface import implements from Products.Sessions.interfaces import ISessionDataManager from Products.Sessions.interfaces import SessionDataManagerErr from Products.Sessions.SessionPermissions import ACCESS_CONTENTS_PERM from Products.Sessions.SessionPermissions import ACCESS_SESSIONDATA_PERM from Products.Sessions.SessionPermissions import ARBITRARY_SESSIONDATA_PERM from Products.Sessions.SessionPermissions import CHANGE_DATAMGR_PERM from Products.Sessions.SessionPermissions import MGMT_SCREEN_PERM from Products.Sessions.common import DEBUG from Products.Sessions.BrowserIdManager import BROWSERID_MANAGER_NAME bad_path_chars_in=re.compile('[^a-zA-Z0-9-_~\,\. \/]').search LOG = getLogger('SessionDataManager') constructSessionDataManagerForm = DTMLFile('dtml/addDataManager', globals()) ADD_SESSION_DATAMANAGER_PERM="Add Session Data Manager" def constructSessionDataManager(self, id, title='', path=None, requestName=None, REQUEST=None): """ """ ob = SessionDataManager(id, path, title, requestName) self._setObject(id, ob) if REQUEST is not None: return self.manage_main(self, REQUEST, update_menu=1) class SessionDataManager(Item, Implicit, Persistent, RoleManager, Owned, Tabs): """ The Zope default session data manager implementation """ meta_type = 'Session Data Manager' manage_options=( {'label': 'Settings', 'action':'manage_sessiondatamgr', }, {'label': 'Security', 'action':'manage_access', }, {'label': 'Ownership', 'action':'manage_owner' }, ) security = ClassSecurityInfo() security.declareObjectPublic() ok = {'meta_type':1, 'id':1, 'title': 1, 'icon':1, 'bobobase_modification_time':1, 'title_or_id':1 } security.setDefaultAccess(ok) security.setPermissionDefault(CHANGE_DATAMGR_PERM, ['Manager']) security.setPermissionDefault(MGMT_SCREEN_PERM, ['Manager']) security.setPermissionDefault(ACCESS_CONTENTS_PERM,['Manager','Anonymous']) security.setPermissionDefault(ARBITRARY_SESSIONDATA_PERM,['Manager']) security.setPermissionDefault(ACCESS_SESSIONDATA_PERM, ['Manager','Anonymous']) icon='misc_/CoreSessionTracking/datamgr.gif' implements(ISessionDataManager) manage_sessiondatamgr = DTMLFile('dtml/manageDataManager', globals()) # INTERFACE METHODS FOLLOW security.declareProtected(ACCESS_SESSIONDATA_PERM, 'getSessionData') def getSessionData(self, create=1): """ """ key = self.getBrowserIdManager().getBrowserId(create=create) if key is not None: return self._getSessionDataObject(key) security.declareProtected(ACCESS_SESSIONDATA_PERM, 'hasSessionData') def hasSessionData(self): """ """ key = self.getBrowserIdManager().getBrowserId(create=0) if key is None: return 0 return self._hasSessionDataObject(key) security.declareProtected(ARBITRARY_SESSIONDATA_PERM,'getSessionDataByKey') def getSessionDataByKey(self, key): return self._getSessionDataObjectByKey(key) security.declareProtected(ACCESS_CONTENTS_PERM, 'getBrowserIdManager') def getBrowserIdManager(self): """ """ mgr = getattr(self, BROWSERID_MANAGER_NAME, None) if mgr is None: raise SessionDataManagerErr( 'No browser id manager named %s could be found.' % BROWSERID_MANAGER_NAME ) return mgr # END INTERFACE METHODS def __init__(self, id, path=None, title='', requestName=None): self.id = id self.setContainerPath(path) self.setTitle(title) self._requestSessionName = requestName security.declareProtected(CHANGE_DATAMGR_PERM, 'manage_changeSDM') def manage_changeSDM(self, title, path=None, requestName=None, REQUEST=None): """ """ self.setContainerPath(path) self.setTitle(title) if requestName: if requestName != self._requestSessionName: self.updateTraversalData(requestName) else: self.updateTraversalData(None) if REQUEST is not None: return self.manage_sessiondatamgr( self, REQUEST, manage_tabs_message = 'Changes saved.' ) security.declareProtected(CHANGE_DATAMGR_PERM, 'setTitle') def setTitle(self, title): """ """ if not title: self.title = '' else: self.title = str(title) security.declareProtected(CHANGE_DATAMGR_PERM, 'setContainerPath') def setContainerPath(self, path=None): """ """ if not path: self.obpath = None # undefined state elif type(path) is str: if bad_path_chars_in(path): raise SessionDataManagerErr( 'Container path contains characters invalid in a Zope ' 'object path' ) self.obpath = path.split('/') elif type(path) in (type([]), type(())): self.obpath = list(path) # sequence else: raise SessionDataManagerErr('Bad path value %s' % path) security.declareProtected(MGMT_SCREEN_PERM, 'getContainerPath') def getContainerPath(self): """ """ if self.obpath is not None: return '/'.join(self.obpath) return '' # blank string represents undefined state def _hasSessionDataObject(self, key): """ """ c = self._getSessionDataContainer() return c.has_key(key) def _getSessionDataObject(self, key): """ returns new or existing session data object """ container = self._getSessionDataContainer() ob = container.new_or_existing(key) # hasattr hides conflicts; be explicit by comparing to None # because otherwise __len__ of the requested object might be called! if ( getattr(ob, '__of__', None) is not None and getattr(ob, 'aq_parent', None) is not None ): # splice ourselves into the acquisition chain return ob.__of__(self.__of__(ob.aq_parent)) return ob.__of__(self) def _getSessionDataObjectByKey(self, key): """ returns new or existing session data object """ container = self._getSessionDataContainer() ob = container.get(key) if ob is not None: # hasattr hides conflicts; be explicit by comparing to None # because otherwise __len__ of the requested object might be # called! if ( getattr(ob, '__of__', None) is not None and getattr(ob, 'aq_parent', None) is not None ): # splice ourselves into the acquisition chain return ob.__of__(self.__of__(ob.aq_parent)) return ob.__of__(self) def _getSessionDataContainer(self): """ Do not cache the results of this call. Doing so breaks the transactions for mounted storages. """ if self.obpath is None: err = 'Session data container is unspecified in %s' % self.getId() LOG.warn(err) raise SessionIdManagerErr, err try: # This should arguably use restrictedTraverse, but it # currently fails for mounted storages. This might # be construed as a security hole, albeit a minor one. # unrestrictedTraverse is also much faster. # hasattr hides conflicts if DEBUG and not getattr(self, '_v_wrote_dc_type', None): args = '/'.join(self.obpath) LOG.debug('External data container at %s in use' % args) self._v_wrote_dc_type = 1 return self.unrestrictedTraverse(self.obpath) except ConflictError: raise except: raise SessionDataManagerErr( "External session data container '%s' not found." % '/'.join(self.obpath) ) security.declareProtected(MGMT_SCREEN_PERM, 'getRequestName') def getRequestName(self): """ """ return self._requestSessionName or '' def manage_afterAdd(self, item, container): """ Add our traversal hook """ self.updateTraversalData(self._requestSessionName) def manage_beforeDelete(self, item, container): """ Clean up on delete """ self.updateTraversalData(None) def updateTraversalData(self, requestSessionName=None): # Note this cant be called directly at add -- manage_afterAdd will # work though. parent = self.aq_inner.aq_parent if getattr(self,'_hasTraversalHook', None): unregisterBeforeTraverse(parent, 'SessionDataManager') del self._hasTraversalHook self._requestSessionName = None if requestSessionName: hook = SessionDataManagerTraverser(requestSessionName, self.id) registerBeforeTraverse(parent, hook, 'SessionDataManager', 50) self._hasTraversalHook = 1 self._requestSessionName = requestSessionName InitializeClass(SessionDataManager) class SessionDataManagerTraverser(Persistent): def __init__(self, requestSessionName, sessionDataManagerName): self._requestSessionName = requestSessionName self._sessionDataManager = sessionDataManagerName def __call__(self, container, request): """ This method places a session data object reference in the request. It is called on each and every request to Zope in Zopes after 2.5.0 when there is a session data manager installed in the root. """ try: sdmName = self._sessionDataManager if not isinstance(sdmName, str): # Zopes v2.5.0 - 2.5.1b1 stuck the actual session data # manager object in _sessionDataManager in order to use # its getSessionData method. We don't actually want to # do this, because it's safer to use getattr to get the # data manager object by name. Using getattr also puts # the sdm in the right context automatically. Here we # pay the penance for backwards compatibility: sdmName = sdmName.id sdm = getattr(container, sdmName) getSessionData = sdm.getSessionData except: msg = 'Session automatic traversal failed to get session data' LOG.warn(msg, exc_info=sys.exc_info()) return # set the getSessionData method in the "lazy" namespace if self._requestSessionName is not None: request.set_lazy(self._requestSessionName, getSessionData) zope2.13-2.13.21/source/Zope2/src/Products/Sessions/BrowserIdManager.py0000644000175000017500000005622512214017422024410 0ustar arnauarnau############################################################################ # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################ import binascii from cgi import escape from hashlib import sha256 import logging import os import re import string import sys import time from urllib import quote from urlparse import urlparse from urlparse import urlunparse from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import Implicit from Acquisition import aq_parent from Acquisition import aq_inner from App.Management import Tabs from App.special_dtml import DTMLFile from Persistence import Persistent from persistent import TimeStamp from OFS.owner import Owned from OFS.role import RoleManager from OFS.SimpleItem import Item from ZPublisher.BeforeTraverse import registerBeforeTraverse from ZPublisher.BeforeTraverse import unregisterBeforeTraverse from ZPublisher.BeforeTraverse import queryBeforeTraverse from zope.interface import implements from Products.Sessions.interfaces import IBrowserIdManager from Products.Sessions.interfaces import BrowserIdManagerErr from Products.Sessions.SessionPermissions import ACCESS_CONTENTS_PERM from Products.Sessions.SessionPermissions import CHANGE_IDMGR_PERM from Products.Sessions.SessionPermissions import MGMT_SCREEN_PERM b64_trans = string.maketrans('+/', '-.') b64_untrans = string.maketrans('-.', '+/') badidnamecharsin = re.compile('[\?&;,<> ]').search badcookiecharsin = re.compile('[;,<>& ]').search twodotsin = re.compile('(\w*\.){2,}').search _marker = [] constructBrowserIdManagerForm = DTMLFile('dtml/addIdManager', globals()) BROWSERID_MANAGER_NAME = 'browser_id_manager'# imported by SessionDataManager ALLOWED_BID_NAMESPACES = ('form', 'cookies', 'url') ADD_BROWSER_ID_MANAGER_PERM="Add Browser Id Manager" TRAVERSAL_APPHANDLE = 'BrowserIdManager' LOG = logging.getLogger('Zope.BrowserIdManager') # Use the system PRNG if possible import random try: random = random.SystemRandom() using_sysrandom = True except NotImplementedError: using_sysrandom = False def _randint(start, end): if not using_sysrandom: # This is ugly, and a hack, but it makes things better than # the alternative of predictability. This re-seeds the PRNG # using a value that is hard for an attacker to predict, every # time a random string is required. This may change the # properties of the chosen random sequence slightly, but this # is better than absolute predictability. random.seed(sha256( "%s%s%s" % (random.getstate(), time.time(), os.getpid()) ).digest()) return random.randint(start, end) def constructBrowserIdManager( self, id=BROWSERID_MANAGER_NAME, title='', idname='_ZopeId', location=('cookies', 'form'), cookiepath='/', cookiedomain='', cookielifedays=0, cookiesecure=0, cookiehttponly=0, auto_url_encoding=0, REQUEST=None ): """ """ ob = BrowserIdManager(id, title, idname, location, cookiepath, cookiedomain, cookielifedays, cookiesecure, cookiehttponly, auto_url_encoding) self._setObject(id, ob) ob = self._getOb(id) if REQUEST is not None: return self.manage_main(self, REQUEST, update_menu=1) class BrowserIdManager(Item, Persistent, Implicit, RoleManager, Owned, Tabs): """ browser id management class """ implements(IBrowserIdManager) meta_type = 'Browser Id Manager' security = ClassSecurityInfo() security.declareObjectPublic() ok = {'meta_type':1, 'id':1, 'title': 1, 'icon':1, 'bobobase_modification_time':1, 'title_or_id':1 } security.setDefaultAccess(ok) security.setPermissionDefault(MGMT_SCREEN_PERM, ['Manager']) security.setPermissionDefault(ACCESS_CONTENTS_PERM,['Manager','Anonymous']) security.setPermissionDefault(CHANGE_IDMGR_PERM, ['Manager']) # BBB auto_url_encoding = 0 cookie_http_only = 0 def __init__(self, id, title='', idname='_ZopeId', location=('cookies', 'form'), cookiepath=('/'), cookiedomain='', cookielifedays=0, cookiesecure=0, cookiehttponly=0, auto_url_encoding=0): self.id = str(id) self.title = str(title) self.setBrowserIdName(idname) self.setBrowserIdNamespaces(location) self.setCookiePath(cookiepath) self.setCookieDomain(cookiedomain) self.setCookieLifeDays(cookielifedays) self.setCookieSecure(cookiesecure) self.setCookieHTTPOnly(cookiehttponly) self.setAutoUrlEncoding(auto_url_encoding) # IBrowserIdManager security.declareProtected(ACCESS_CONTENTS_PERM, 'hasBrowserId') def hasBrowserId(self): """ See IBrowserIdManager. """ try: return self.getBrowserId(create=0) is not None except BrowserIdManagerErr: return False security.declareProtected(ACCESS_CONTENTS_PERM, 'getBrowserId') def getBrowserId(self, create=1): """ See IBrowserIdManager. """ REQUEST = self.REQUEST # let's see if bid has already been attached to request bid = getattr(REQUEST, 'browser_id_', None) if bid is not None: # it's already set in this request so we can just return it # if it's well-formed if not isAWellFormedBrowserId(bid): # somebody screwed with the REQUEST instance during # this request. raise BrowserIdManagerErr( 'Ill-formed browserid in ' 'REQUEST.browser_id_: %s' % escape(bid)) return bid # fall through & ck form/cookie namespaces if bid is not in request. tk = self.browserid_name ns = self.browserid_namespaces for name in ns: if name == 'url': continue # browser ids in url are checked by Traverser class current_ns = getattr(REQUEST, name, None) if current_ns is None: continue bid = current_ns.get(tk, None) if bid is not None: # hey, we got a browser id! if isAWellFormedBrowserId(bid): # bid is not "plain old broken" REQUEST.browser_id_ = bid REQUEST.browser_id_ns_ = name return bid # fall through if bid is invalid or not in namespaces if create: # create a brand new bid bid = getNewBrowserId() if 'cookies' in ns: self._setCookie(bid, REQUEST) REQUEST.browser_id_ = bid REQUEST.browser_id_ns_ = None return bid # implies a return of None if: # (not create=1) and (invalid or ((not in req) and (not in ns))) security.declareProtected(ACCESS_CONTENTS_PERM, 'getBrowserIdName') def getBrowserIdName(self): """ See IBrowserIdManager. """ return self.browserid_name security.declareProtected(ACCESS_CONTENTS_PERM, 'isBrowserIdNew') def isBrowserIdNew(self): """ See IBrowserIdManager. """ if not self.getBrowserId(create=False): raise BrowserIdManagerErr('There is no current browser id.') # ns will be None if new return getattr(self.REQUEST, 'browser_id_ns_', None) is None security.declareProtected(ACCESS_CONTENTS_PERM, 'isBrowserIdFromCookie') def isBrowserIdFromCookie(self): """ See IBrowserIdManager. """ if not self.getBrowserId(create=False): raise BrowserIdManagerErr('There is no current browser id.') if getattr(self.REQUEST, 'browser_id_ns_') == 'cookies': return 1 security.declareProtected(ACCESS_CONTENTS_PERM, 'isBrowserIdFromForm') def isBrowserIdFromForm(self): """ See IBrowserIdManager. """ if not self.getBrowserId(create=False): raise BrowserIdManagerErr('There is no current browser id.') if getattr(self.REQUEST, 'browser_id_ns_') == 'form': return 1 security.declareProtected(ACCESS_CONTENTS_PERM, 'isBrowserIdFromUrl') def isBrowserIdFromUrl(self): """ See IBrowserIdManager. """ if not self.getBrowserId(create=False): raise BrowserIdManagerErr('There is no current browser id.') if getattr(self.REQUEST, 'browser_id_ns_') == 'url': return 1 security.declareProtected(ACCESS_CONTENTS_PERM, 'flushBrowserIdCookie') def flushBrowserIdCookie(self): """ See IBrowserIdManager. """ if 'cookies' not in self.browserid_namespaces: raise BrowserIdManagerErr( 'Cookies are not now being used as a ' 'browser id namespace, thus the ' 'browserid cookie cannot be flushed.') self._setCookie('deleted', self.REQUEST, remove=1) security.declareProtected(ACCESS_CONTENTS_PERM,'setBrowserIdCookieByForce') def setBrowserIdCookieByForce(self, bid): """ See IBrowserIdManager. """ if 'cookies' not in self.browserid_namespaces: raise BrowserIdManagerErr( 'Cookies are not now being used as a ' 'browser id namespace, thus the ' 'browserid cookie cannot be forced.') self._setCookie(bid, self.REQUEST) security.declareProtected(ACCESS_CONTENTS_PERM, 'getHiddenFormField') def getHiddenFormField(self): """ See IBrowserIdManager. """ s = '' return s % (self.getBrowserIdName(), self.getBrowserId()) security.declareProtected(ACCESS_CONTENTS_PERM, 'encodeUrl') def encodeUrl(self, url, style='querystring', create=1): """ See IBrowserIdManager. """ bid = self.getBrowserId(create) if bid is None: raise BrowserIdManagerErr('There is no current browser id.') name = self.getBrowserIdName() if style == 'querystring': # encode bid in querystring if '?' in url: return '%s&%s=%s' % (url, name, bid) else: return '%s?%s=%s' % (url, name, bid) else: # encode bid as first two URL path segments proto, host, path, params, query, frag = urlparse(url) path = '/%s/%s%s' % (name, bid, path) return urlunparse((proto, host, path, params, query, frag)) # Non-IBrowserIdManager accessors / mutators. security.declareProtected(CHANGE_IDMGR_PERM, 'setBrowserIdName') def setBrowserIdName(self, k): """ Set browser id name string o Enforce "valid" values. """ if not (type(k) is str and k and not badidnamecharsin(k)): raise BrowserIdManagerErr( 'Bad id name string %s' % escape(repr(k))) self.browserid_name = k security.declareProtected(CHANGE_IDMGR_PERM, 'setBrowserIdNamespaces') def setBrowserIdNamespaces(self, ns): """ accepts list of allowable browser id namespaces """ for name in ns: if name not in ALLOWED_BID_NAMESPACES: raise BrowserIdManagerErr( 'Bad browser id namespace %s' % repr(name)) self.browserid_namespaces = tuple(ns) security.declareProtected(ACCESS_CONTENTS_PERM, 'getBrowserIdNamespaces') def getBrowserIdNamespaces(self): """ """ return self.browserid_namespaces security.declareProtected(CHANGE_IDMGR_PERM, 'setCookiePath') def setCookiePath(self, path=''): """ sets cookie 'path' element for id cookie """ if not (type(path) is str and not badcookiecharsin(path)): raise BrowserIdManagerErr( 'Bad cookie path %s' % escape(repr(path))) self.cookie_path = path security.declareProtected(ACCESS_CONTENTS_PERM, 'getCookiePath') def getCookiePath(self): """ """ return self.cookie_path security.declareProtected(CHANGE_IDMGR_PERM, 'setCookieLifeDays') def setCookieLifeDays(self, days): """ offset for id cookie 'expires' element """ if type(days) not in (type(1), type(1.0)): raise BrowserIdManagerErr( 'Bad cookie lifetime in days %s ' '(requires integer value)' % escape(repr(days))) self.cookie_life_days = int(days) security.declareProtected(ACCESS_CONTENTS_PERM, 'getCookieLifeDays') def getCookieLifeDays(self): """ """ return self.cookie_life_days security.declareProtected(CHANGE_IDMGR_PERM, 'setCookieDomain') def setCookieDomain(self, domain): """ sets cookie 'domain' element for id cookie """ if type(domain) is not str: raise BrowserIdManagerErr( 'Cookie domain must be string: %s' % escape(repr(domain))) if not domain: self.cookie_domain = '' return if not twodotsin(domain): raise BrowserIdManagerErr( 'Cookie domain must contain at least two dots ' '(e.g. ".zope.org" or "www.zope.org") or it must ' 'be left blank. : ' '%s' % escape(`domain`)) if badcookiecharsin(domain): raise BrowserIdManagerErr( 'Bad characters in cookie domain %s' % escape(`domain`)) self.cookie_domain = domain security.declareProtected(ACCESS_CONTENTS_PERM, 'getCookieDomain') def getCookieDomain(self): """ """ return self.cookie_domain security.declareProtected(CHANGE_IDMGR_PERM, 'setCookieHTTPOnly') def setCookieHTTPOnly(self, http_only): """ sets cookie 'HTTPOnly' on or off """ self.cookie_http_only = bool(http_only) security.declareProtected(ACCESS_CONTENTS_PERM, 'getCookieHTTPOnly') def getCookieHTTPOnly(self): """ retrieve the 'HTTPOnly' flag """ return self.cookie_http_only security.declareProtected(CHANGE_IDMGR_PERM, 'setCookieSecure') def setCookieSecure(self, secure): """ sets cookie 'secure' element for id cookie """ self.cookie_secure = not not secure security.declareProtected(ACCESS_CONTENTS_PERM, 'getCookieSecure') def getCookieSecure(self): """ """ return self.cookie_secure security.declareProtected(CHANGE_IDMGR_PERM, 'setAutoUrlEncoding') def setAutoUrlEncoding(self, auto_url_encoding): """ sets 'auto url encoding' on or off """ self.auto_url_encoding = not not auto_url_encoding security.declareProtected(ACCESS_CONTENTS_PERM, 'getAutoUrlEncoding') def getAutoUrlEncoding(self): """ """ return self.auto_url_encoding security.declareProtected(ACCESS_CONTENTS_PERM, 'isUrlInBidNamespaces') def isUrlInBidNamespaces(self): """ Returns true if 'url' is in the browser id namespaces for this browser id """ return 'url' in self.browserid_namespaces def _setCookie( self, bid, REQUEST, remove=0, now=time.time, strftime=time.strftime, gmtime=time.gmtime ): """ """ expires = None if remove: expires = "Sun, 10-May-1971 11:59:00 GMT" elif self.cookie_life_days: expires = now() + self.cookie_life_days * 86400 # Wdy, DD-Mon-YYYY HH:MM:SS GMT expires = strftime('%a %d-%b-%Y %H:%M:%S GMT',gmtime(expires)) # cookie attributes managed by BrowserIdManager d = {'domain':self.cookie_domain,'path':self.cookie_path, 'secure':self.cookie_secure,'http_only': self.cookie_http_only, 'expires':expires} if self.cookie_secure: URL1 = REQUEST.get('URL1', None) if URL1 is None: return # should we raise an exception? if string.split(URL1,':')[0] != 'https': return # should we raise an exception? cookies = REQUEST.RESPONSE.cookies cookie = cookies[self.browserid_name]= {} for k,v in d.items(): if v: cookie[k] = v #only stuff things with true values cookie['value'] = bid def _setId(self, id): if id != self.id: raise ValueError('Cannot rename a browser id manager') # Jukes for handling URI-munged browser IDS def hasTraversalHook(self, parent): name = TRAVERSAL_APPHANDLE return not not queryBeforeTraverse(parent, name) def updateTraversalData(self): if 'url' in self.browserid_namespaces: self.registerTraversalHook() else: self.unregisterTraversalHook() def unregisterTraversalHook(self): parent = aq_parent(aq_inner(self)) name = TRAVERSAL_APPHANDLE if self.hasTraversalHook(parent): unregisterBeforeTraverse(parent, name) def registerTraversalHook(self): parent = aq_parent(aq_inner(self)) if not self.hasTraversalHook(parent): hook = BrowserIdManagerTraverser() name = TRAVERSAL_APPHANDLE priority = 40 # "higher" priority than session data traverser registerBeforeTraverse(parent, hook, name, priority) # ZMI icon = 'misc_/Sessions/idmgr.gif' manage_options=({'label': 'Settings', 'action':'manage_browseridmgr'}, {'label': 'Security', 'action':'manage_access'}, {'label': 'Ownership', 'action':'manage_owner'}, ) def manage_afterAdd(self, item, container): """ Maybe add our traversal hook """ self.updateTraversalData() def manage_beforeDelete(self, item, container): """ Remove our traversal hook if it exists """ self.unregisterTraversalHook() security.declareProtected(MGMT_SCREEN_PERM, 'manage_browseridmgr') manage_browseridmgr = DTMLFile('dtml/manageIdManager', globals()) security.declareProtected(CHANGE_IDMGR_PERM, 'manage_changeBrowserIdManager') def manage_changeBrowserIdManager( self, title='', idname='_ZopeId', location=('cookies', 'form'), cookiepath='/', cookiedomain='', cookielifedays=0, cookiesecure=0, cookiehttponly=0, auto_url_encoding=0, REQUEST=None ): """ """ self.title = str(title) self.setBrowserIdName(idname) self.setCookiePath(cookiepath) self.setCookieDomain(cookiedomain) self.setCookieLifeDays(cookielifedays) self.setCookieSecure(cookiesecure) self.setCookieHTTPOnly(cookiehttponly) self.setBrowserIdNamespaces(location) self.setAutoUrlEncoding(auto_url_encoding) self.updateTraversalData() if REQUEST is not None: msg = '/manage_browseridmgr?manage_tabs_message=Changes saved' REQUEST.RESPONSE.redirect(self.absolute_url()+msg) InitializeClass(BrowserIdManager) class BrowserIdManagerTraverser(Persistent): def __call__(self, container, request, browser_id=None, browser_id_ns=None, BROWSERID_MANAGER_NAME=BROWSERID_MANAGER_NAME): """ Registered hook to set and get a browser id in the URL. If a browser id is found in the URL of an incoming request, we put it into a place where it can be found later by the browser id manager. If our browser id manager's auto-url-encoding feature is on, cause Zope-generated URLs to contain the browser id by rewriting the request._script list. """ browser_id_manager = getattr(container, BROWSERID_MANAGER_NAME, None) # fail if we cannot find a browser id manager (that means this # instance has been "orphaned" somehow) if browser_id_manager is None: LOG.error('Could not locate browser id manager!') return try: stack = request['TraversalRequestNameStack'] request.browser_id_ns_ = browser_id_ns bid_name = browser_id_manager.getBrowserIdName() # stuff the browser id and id namespace into the request # if the URL has a browser id name and browser id as its first # two elements. Only remove these elements from the # traversal stack if they are a "well-formed pair". if len(stack) >= 2 and stack[-1] == bid_name: if isAWellFormedBrowserId(stack[-2]): name = stack.pop() # pop the name off the stack browser_id = stack.pop() # pop id off the stack request.browser_id_ = browser_id request.browser_id_ns_ = 'url' # if the browser id manager is set up with 'auto url encoding', # cause generated URLs to be encoded with the browser id name/value # pair by munging request._script. if browser_id_manager.getAutoUrlEncoding(): if browser_id is None: request.browser_id_ = browser_id = getNewBrowserId() request._script.append(quote(bid_name)) request._script.append(quote(browser_id)) except: LOG.error('indeterminate error', exc_info=sys.exc_info()) def getB64TStamp( b2a=binascii.b2a_base64,gmtime=time.gmtime, time=time.time, b64_trans=b64_trans, split=string.split, TimeStamp=TimeStamp.TimeStamp, translate=string.translate ): t=time() ts=split(b2a(`TimeStamp(*gmtime(t)[:5]+(t%60,))`)[:-1],'=')[0] return translate(ts, b64_trans) def getB64TStampToInt( ts, TimeStamp=TimeStamp.TimeStamp, b64_untrans=b64_untrans, a2b=binascii.a2b_base64, translate=string.translate ): return TimeStamp(a2b(translate(ts+'=',b64_untrans))).timeTime() def getBrowserIdPieces(bid): """ returns browser id parts in a tuple consisting of rand_id, timestamp """ return (bid[:8], bid[8:19]) def isAWellFormedBrowserId(bid, binerr=binascii.Error): try: rnd, ts = getBrowserIdPieces(bid) int(rnd) getB64TStampToInt(ts) return bid except (TypeError, ValueError, AttributeError, IndexError, binerr): return None def getNewBrowserId(randint=_randint, maxint=99999999): """ Returns 19-character string browser id 'AAAAAAAABBBBBBBB' where: A == leading-0-padded 8-char string-rep'd random integer B == modified base64-encoded 11-char timestamp To be URL-compatible, base64 encoding is modified as follows: '=' end-padding is stripped off '+' is translated to '-' '/' is translated to '.' An example is: 89972317A0C3EHnUi90w """ return '%08i%s' % (randint(0, maxint - 1), getB64TStamp()) zope2.13-2.13.21/source/Zope2/src/Products/Sessions/SessionPermissions.py0000644000175000017500000000172112214017422025063 0ustar arnauarnau############################################################################ # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################ CHANGE_DATAMGR_PERM = 'Change Session Data Manager' MGMT_SCREEN_PERM = 'View management screens' ACCESS_CONTENTS_PERM = 'Access contents information' ACCESS_SESSIONDATA_PERM = 'Access session data' ARBITRARY_SESSIONDATA_PERM = 'Access arbitrary user session data' CHANGE_IDMGR_PERM = 'Change Browser Id Manager' MANAGE_CONTAINER_PERM = 'Manage Session Data Container' zope2.13-2.13.21/source/Zope2/src/Products/Sessions/SessionInterfaces.py0000644000175000017500000000200112214017422024623 0ustar arnauarnau############################################################################ # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################ # BBB location for APIs now defined in Products.Sessions.interfaces from Products.Sessions.interfaces import IBrowserIdManager BrowserIdManagerInterface = IBrowserIdManager # BBB from Products.Sessions.interfaces import ISessionDataManager SessionDataManagerInterface = ISessionDataManager from Products.Sessions.interfaces import SessionDataManagerErr from Products.Sessions.interfaces import BrowserIdManagerErr zope2.13-2.13.21/source/Zope2/src/Products/Sessions/tests/0000755000175000017500000000000012214017422021773 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Sessions/tests/testBrowserIdManager.py0000644000175000017500000006547312214017422026457 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Test suite for session id manager. """ import unittest class TestBrowserIdManager(unittest.TestCase): def _getTargetClass(self): from Products.Sessions.BrowserIdManager import BrowserIdManager return BrowserIdManager def _makeOne(self, request=None, name='browser_id_manager'): bid = self._getTargetClass()(name) if request is not None: bid.REQUEST = request return bid def test_hasBrowserId_already_set_on_request_invalid(self): request = DummyRequest(browser_id_='xxx') mgr = self._makeOne(request) self.assertFalse(mgr.hasBrowserId()) def test_hasBrowserId_already_set_on_request(self): from Products.Sessions.BrowserIdManager import getNewBrowserId request = DummyRequest(browser_id_=getNewBrowserId()) mgr = self._makeOne(request) self.assertTrue(mgr.hasBrowserId()) def test_hasBrowserId_namespace_hit(self): from Products.Sessions.BrowserIdManager import getNewBrowserId request = DummyRequest(cookies={'bid': getNewBrowserId()}) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') self.assertTrue(mgr.hasBrowserId()) def test_hasBrowserId_namespace_miss(self): request = DummyRequest() mgr = self._makeOne(request) self.assertFalse(mgr.hasBrowserId()) self.assertRaises(AttributeError, getattr, request, 'browser_id_') self.assertRaises(AttributeError, getattr, request, 'browser_id_ns_') def test_getBrowserId_already_set_on_request_invalid_raises(self): request = DummyRequest(browser_id_='xxx') mgr = self._makeOne(request) self.assertRaises(ValueError, mgr.getBrowserId) def test_getBrowserId_already_set_on_request(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() request = DummyRequest(browser_id_=bid) mgr = self._makeOne(request) self.assertEqual(mgr.getBrowserId(), bid) def test_getBrowserId_namespace_hit(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() request = DummyRequest(cookies={'bid': bid}) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') self.assertTrue(mgr.hasBrowserId()) self.assertEqual(request.browser_id_, bid) self.assertEqual(request.browser_id_ns_, 'cookies') def test_getBrowserId_namespace_miss_no_create(self): request = DummyRequest() mgr = self._makeOne(request) mgr.setBrowserIdName('bid') self.assertEqual(mgr.getBrowserId(create=False), None) self.assertRaises(AttributeError, getattr, request, 'browser_id_') self.assertRaises(AttributeError, getattr, request, 'browser_id_ns_') def test_getBrowserId_namespace_miss_w_create_no_cookies(self): from Products.Sessions.BrowserIdManager import isAWellFormedBrowserId request = DummyRequest() mgr = self._makeOne(request) mgr.setBrowserIdName('bid') mgr.setBrowserIdNamespaces(()) bid = mgr.getBrowserId() self.assertTrue(isAWellFormedBrowserId(bid)) self.assertEqual(request.browser_id_, bid) self.assertEqual(request.browser_id_ns_, None) def test_getBrowserId_namespace_miss_w_create_w_cookies(self): from Products.Sessions.BrowserIdManager import isAWellFormedBrowserId response = DummyResponse(cookies={}) request = DummyRequest(RESPONSE=response) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') mgr.setBrowserIdNamespaces(('cookies',)) bid = mgr.getBrowserId() self.assertTrue(isAWellFormedBrowserId(bid)) self.assertEqual(request.browser_id_, bid) self.assertEqual(request.browser_id_ns_, None) self.assertEqual(response.cookies['bid'], {'path': '/', 'value': bid}) def test_isBrowserIdNew_nonesuch_raises(self): request = DummyRequest() mgr = self._makeOne(request) self.assertRaises(ValueError, mgr.isBrowserIdNew) def test_isBrowserIdNew_no_ns(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() request = DummyRequest(browser_id_=bid, browser_id_ns_=None) mgr = self._makeOne(request) self.assertTrue(mgr.isBrowserIdNew()) def test_isBrowserIdNew_w_ns(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() request = DummyRequest(browser_id_=bid, browser_id_ns_='url') mgr = self._makeOne(request) self.assertFalse(mgr.isBrowserIdNew()) def test_isBrowserIdFromCookie_nonesuch_raises(self): request = DummyRequest() mgr = self._makeOne(request) self.assertRaises(ValueError, mgr.isBrowserIdFromCookie) def test_isBrowserIdFromCookie_wrong_ns(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() request = DummyRequest(browser_id_=bid, browser_id_ns_='url') mgr = self._makeOne(request) self.assertFalse(mgr.isBrowserIdFromCookie()) def test_isBrowserIdFromCookie_right_ns(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() request = DummyRequest(browser_id_=bid, browser_id_ns_='cookies') mgr = self._makeOne(request) self.assertTrue(mgr.isBrowserIdFromCookie()) def test_isBrowserIdFromForm_nonesuch_raises(self): request = DummyRequest() mgr = self._makeOne(request) self.assertRaises(ValueError, mgr.isBrowserIdFromForm) def test_isBrowserIdFromForm_wrong_ns(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() request = DummyRequest(browser_id_=bid, browser_id_ns_='url') mgr = self._makeOne(request) self.assertFalse(mgr.isBrowserIdFromForm()) def test_isBrowserIdFromForm_right_ns(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() request = DummyRequest(browser_id_=bid, browser_id_ns_='form') mgr = self._makeOne(request) self.assertTrue(mgr.isBrowserIdFromForm()) def test_isBrowserIdFromUrl_nonesuch_raises(self): request = DummyRequest() mgr = self._makeOne(request) self.assertRaises(ValueError, mgr.isBrowserIdFromUrl) def test_isBrowserIdFromUrl_wrong_ns(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() request = DummyRequest(browser_id_=bid, browser_id_ns_='form') mgr = self._makeOne(request) self.assertFalse(mgr.isBrowserIdFromUrl()) def test_isBrowserIdFromUrl_right_ns(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() request = DummyRequest(browser_id_=bid, browser_id_ns_='url') mgr = self._makeOne(request) self.assertTrue(mgr.isBrowserIdFromUrl()) def test_flushBrowserIdCookie_wrong_ns_raises(self): mgr = self._makeOne() mgr.setBrowserIdNamespaces(('url', 'form')) self.assertRaises(ValueError, mgr.flushBrowserIdCookie) def test_flushBrowserIdCookie_ok(self): response = DummyResponse(cookies={}) request = DummyRequest(RESPONSE=response) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') mgr.setBrowserIdNamespaces(('cookies',)) mgr.flushBrowserIdCookie() self.assertEqual(response.cookies['bid'], {'path': '/', 'expires': 'Sun, 10-May-1971 11:59:00 GMT', 'value': 'deleted'}) def test_setBrowserIdCookieByForce_wrong_ns_raises(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() mgr = self._makeOne() mgr.setBrowserIdNamespaces(('url', 'form')) self.assertRaises(ValueError, mgr.setBrowserIdCookieByForce, bid) def test_setBrowserIdCookieByForce_ok(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() response = DummyResponse(cookies={}) request = DummyRequest(RESPONSE=response) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') mgr.setBrowserIdNamespaces(('cookies',)) mgr.setBrowserIdCookieByForce(bid) self.assertEqual(response.cookies['bid'], {'path': '/', 'value': bid}) def test_getHiddenFormField(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() request = DummyRequest(browser_id_=bid) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') self.assertEqual(mgr.getHiddenFormField(), '' % bid) def test_encodeUrl_no_create_no_bid_raises(self): URL = 'http://example.com/' request = DummyRequest() mgr = self._makeOne(request) self.assertRaises(ValueError, mgr.encodeUrl, URL, create=False) def test_encodeUrl_no_create_w_bid_querystring_style(self): from Products.Sessions.BrowserIdManager import getNewBrowserId URL = 'http://example.com/' bid = getNewBrowserId() request = DummyRequest(browser_id_=bid) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') munged = mgr.encodeUrl(URL, create=False) self.assertEqual(munged, '%s?bid=%s' % (URL, bid)) def test_encodeUrl_no_create_w_bid_querystring_style_existing_qs(self): from Products.Sessions.BrowserIdManager import getNewBrowserId URL = 'http://example.com/' QS = 'foo=bar' bid = getNewBrowserId() request = DummyRequest(browser_id_=bid) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') munged = mgr.encodeUrl('%s?%s' % (URL, QS), create=False) self.assertEqual(munged, '%s?%s&bid=%s' % (URL, QS, bid)) def test_encodeUrl_no_create_w_bid_inline_style(self): from Products.Sessions.BrowserIdManager import getNewBrowserId NETHOST = 'http://example.com' PATH_INFO = 'path/to/page' URL = '%s/%s' % (NETHOST, PATH_INFO) bid = getNewBrowserId() request = DummyRequest(browser_id_=bid) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') munged = mgr.encodeUrl(URL, style='inline', create=False) self.assertEqual(munged, '%s/bid/%s/%s' % (NETHOST, bid, PATH_INFO)) def test_setBrowserIdName_empty_string_raises(self): mgr = self._makeOne() self.assertRaises(ValueError, mgr.setBrowserIdName, '') def test_setBrowserIdName_non_string_raises(self): mgr = self._makeOne() self.assertRaises(ValueError, mgr.setBrowserIdName, 1) def test_setBrowserIdName_normal(self): mgr = self._makeOne() mgr.setBrowserIdName('foo') self.assertEqual(mgr.getBrowserIdName(), 'foo') def test_setBrowserIdNamespaces_invalid_raises(self): mgr = self._makeOne() self.assertRaises(ValueError, mgr.setBrowserIdNamespaces, ('gummy', 'froopy')) def test_setBrowserIdNamespaces_normal(self): NAMESPACES = ('cookies', 'url', 'form') mgr = self._makeOne() mgr.setBrowserIdNamespaces(NAMESPACES) self.assertEqual(mgr.getBrowserIdNamespaces(), NAMESPACES) def test_setCookiePath_invalid_raises(self): mgr = self._makeOne() self.assertRaises(ValueError, mgr.setCookiePath, '/;') def test_setCookiePath_normal(self): mgr = self._makeOne() mgr.setCookiePath('/foo') self.assertEqual(mgr.getCookiePath(), '/foo') def test_setCookieLifeDays_invalid_raises(self): mgr = self._makeOne() self.assertRaises(ValueError, mgr.setCookieLifeDays, '') def test_setCookieLifeDays_normal(self): mgr = self._makeOne() mgr.setCookieLifeDays(1) self.assertEqual(mgr.getCookieLifeDays(), 1) def test_setCookieDomain_non_string_raises(self): mgr = self._makeOne() self.assertRaises(ValueError, mgr.setCookieDomain, {1:1}) def test_setCookieDomain_no_dots_raises(self): mgr = self._makeOne() self.assertRaises(ValueError, mgr.setCookieDomain, 'gubble') def test_setCookieDomain_one_dot_raises(self): mgr = self._makeOne() self.assertRaises(ValueError, mgr.setCookieDomain, 'zope.org') def test_setCookieDomain_trailing_semicolon_raises(self): mgr = self._makeOne() self.assertRaises(ValueError, mgr.setCookieDomain, '.zope.org;') def test_setCookieDomain_empty_OK(self): mgr = self._makeOne() mgr.setCookieDomain('') self.assertEqual(mgr.getCookieDomain(), '') def test_setCookieDomain_two_dots(self): mgr = self._makeOne() mgr.setCookieDomain('.zope.org') self.assertEqual(mgr.getCookieDomain(), '.zope.org') def test_setCookieDomain_three_dots(self): mgr = self._makeOne() mgr.setCookieDomain('.dev.zope.org') self.assertEqual(mgr.getCookieDomain(), '.dev.zope.org') def test_setCookieSecure_int(self): mgr = self._makeOne() mgr.setCookieSecure(1) self.assertTrue(mgr.getCookieSecure()) mgr.setCookieSecure(0) self.assertFalse(mgr.getCookieSecure()) def test_setCookieSecure_bool(self): mgr = self._makeOne() mgr.setCookieSecure(True) self.assertTrue(mgr.getCookieSecure()) mgr.setCookieSecure(False) self.assertFalse(mgr.getCookieSecure()) def test_setCookieHTTPOnly_bool(self): mgr = self._makeOne() mgr.setCookieHTTPOnly(True) self.assertTrue(mgr.getCookieHTTPOnly()) mgr.setCookieHTTPOnly(False) self.assertFalse(mgr.getCookieHTTPOnly()) def test_setAutoUrlEncoding_bool(self): mgr = self._makeOne() mgr.setAutoUrlEncoding(True) self.assertTrue(mgr.getAutoUrlEncoding()) mgr.setAutoUrlEncoding(False) self.assertFalse(mgr.getAutoUrlEncoding()) def test_isUrlInBidNamespaces(self): mgr = self._makeOne() mgr.setBrowserIdNamespaces(('cookies', 'url', 'form')) self.assertTrue(mgr.isUrlInBidNamespaces()) mgr.setBrowserIdNamespaces(('cookies', 'form')) self.assertFalse(mgr.isUrlInBidNamespaces()) def test__setCookie_remove(self): response = DummyResponse(cookies={}) request = DummyRequest(RESPONSE=response) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') mgr._setCookie('xxx', request, remove=True) self.assertEqual(response.cookies['bid'], {'path': '/', 'value': 'xxx', 'expires': 'Sun, 10-May-1971 11:59:00 GMT'}) def test__setCookie_cookie_life_days(self): response = DummyResponse(cookies={}) request = DummyRequest(RESPONSE=response) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') mgr.setCookieLifeDays(1) mgr._setCookie('xxx', request, now=lambda: 1, strftime=lambda x, y: 'Seconds: %d' % y, gmtime=lambda x: x) self.assertEqual(response.cookies['bid'], {'path': '/', 'value': 'xxx', 'expires': 'Seconds: 86401'}) def test__setCookie_cookie_secure_no_URL1_sets_no_cookie(self): request = DummyRequest() mgr = self._makeOne(request) mgr.setBrowserIdName('bid') mgr.setCookieSecure(True) mgr._setCookie('xxx', request) # no response, doesn't blow up def test__setCookie_cookie_secure_not_https_sets_no_cookie(self): request = DummyRequest(URL1='http://example.com/') mgr = self._makeOne(request) mgr.setBrowserIdName('bid') mgr.setCookieSecure(True) mgr._setCookie('xxx', request) # no response, doesn't blow up def test__setCookie_cookie_secure_is_https(self): response = DummyResponse(cookies={}) request = DummyRequest(RESPONSE=response, URL1='https://example.com/') mgr = self._makeOne(request) mgr.setBrowserIdName('bid') mgr.setCookieSecure(True) mgr._setCookie('xxx', request) self.assertEqual(response.cookies['bid'], {'path': '/', 'value': 'xxx', 'secure': True}) def test__setCookie_domain(self): response = DummyResponse(cookies={}) request = DummyRequest(RESPONSE=response) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') mgr.setCookieDomain('.zope.org') mgr._setCookie('xxx', request) self.assertEqual(response.cookies['bid'], {'path': '/', 'value': 'xxx', 'domain': '.zope.org'}) def test__setCookie_path(self): response = DummyResponse(cookies={}) request = DummyRequest(RESPONSE=response) mgr = self._makeOne(request) mgr.setBrowserIdName('bid') mgr.setCookiePath('/path/') mgr._setCookie('xxx', request) self.assertEqual(response.cookies['bid'], {'path': '/path/', 'value': 'xxx'}) def test__setCookie_http_only(self): response = DummyResponse(cookies={}) request = DummyRequest(RESPONSE=response, URL1='https://example.com/') mgr = self._makeOne(request) mgr.setBrowserIdName('bid') mgr.setCookieHTTPOnly(True) mgr._setCookie('xxx', request) self.assertEqual(response.cookies['bid'], {'path': '/', 'value': 'xxx', 'http_only': True}) def test__setCookie_http_only_missing_attr(self): # See https://bugs.launchpad.net/bugs/374816 response = DummyResponse(cookies={}) request = DummyRequest(RESPONSE=response, URL1='https://example.com/') mgr = self._makeOne(request) del mgr.cookie_http_only # pre-2.12 instances didn't have this mgr.setBrowserIdName('bid') mgr._setCookie('xxx', request) self.assertEqual(response.cookies['bid'], {'path': '/', 'value': 'xxx'}) def test__setId_same_id_noop(self): mgr = self._makeOne(name='foo') mgr._setId('foo') def test__setId_different_id_raises(self): mgr = self._makeOne(name='foo') self.assertRaises(ValueError, mgr._setId, 'bar') def test_setCookieSecure_non_HTTPS_doesnt_set_cookie(self): # Document the "feature" that 'setCookieSecure' allows returning # a browser ID even where the URL is not HTTPS, and therefor no # cookie is set. response = DummyResponse(cookies={}) request = DummyRequest(RESPONSE=response, URL1='http://example.com/') mgr = self._makeOne(request) mgr.setCookieSecure(1) bid = mgr.getBrowserId() # doesn't raise self.assertEqual(len(response.cookies), 0) def test_hasTraversalHook_missing(self): mgr = self._makeOne() parent = DummyObject() self.assertFalse(mgr.hasTraversalHook(parent)) def test_hasTraversalHook_present(self): mgr = self._makeOne() parent = DummyObject() parent.__before_traverse__ = {(0, 'BrowserIdManager'): object()} self.assertTrue(mgr.hasTraversalHook(parent)) def test_updateTraversalData_w_url_ns(self): from Acquisition import Implicit from ZPublisher.BeforeTraverse import queryBeforeTraverse from Products.Sessions.BrowserIdManager import BrowserIdManagerTraverser class Parent(Implicit): pass mgr = self._makeOne() mgr.setBrowserIdNamespaces(('url',)) parent = Parent() parent.browser_id_manager = mgr parent.browser_id_manager.updateTraversalData() # needs wrapper hooks = queryBeforeTraverse(parent, 'BrowserIdManager') self.assertEqual(len(hooks), 1) self.assertEqual(hooks[0][0], 40) self.assertTrue(isinstance(hooks[0][1], BrowserIdManagerTraverser)) def test_updateTraversalData_not_url_ns(self): from Acquisition import Implicit from ZPublisher.BeforeTraverse import queryBeforeTraverse class Parent(Implicit): pass mgr = self._makeOne() mgr.setBrowserIdNamespaces(('cookies', 'form')) parent = Parent() parent.__before_traverse__ = {(0, 'BrowserIdManager'): object()} parent.browser_id_manager = mgr parent.browser_id_manager.updateTraversalData() # needs wrapper self.assertFalse(queryBeforeTraverse(mgr, 'BrowserIdManager')) def test_registerTraversalHook_doesnt_replace_existing(self): from Acquisition import Implicit from ZPublisher.BeforeTraverse import queryBeforeTraverse class Parent(Implicit): pass mgr = self._makeOne() parent = Parent() hook = object() parent.__before_traverse__ = {(0, 'BrowserIdManager'): hook} parent.browser_id_manager = mgr parent.browser_id_manager.registerTraversalHook() # needs wrapper hooks = queryBeforeTraverse(parent, 'BrowserIdManager') self.assertEqual(len(hooks), 1) self.assertEqual(hooks[0][0], 0) self.assertTrue(hooks[0][1] is hook) def test_registerTraversalHook_normal(self): from Acquisition import Implicit from ZPublisher.BeforeTraverse import queryBeforeTraverse from Products.Sessions.BrowserIdManager import BrowserIdManagerTraverser class Parent(Implicit): pass mgr = self._makeOne() parent = Parent() parent.browser_id_manager = mgr parent.browser_id_manager.registerTraversalHook() # needs wrapper hooks = queryBeforeTraverse(parent, 'BrowserIdManager') self.assertEqual(len(hooks), 1) self.assertEqual(hooks[0][0], 40) self.assertTrue(isinstance(hooks[0][1], BrowserIdManagerTraverser)) def test_unregisterTraversalHook_nonesuch_doesnt_raise(self): from Acquisition import Implicit class Parent(Implicit): pass mgr = self._makeOne() parent = Parent() parent.browser_id_manager = mgr parent.browser_id_manager.unregisterTraversalHook() # needs wrapper def test_unregisterTraversalHook_normal(self): from Acquisition import Implicit from ZPublisher.BeforeTraverse import queryBeforeTraverse class Parent(Implicit): pass mgr = self._makeOne() parent = Parent() parent.__before_traverse__ = {(0, 'BrowserIdManager'): object()} parent.browser_id_manager = mgr parent.browser_id_manager.unregisterTraversalHook() # needs wrapper self.assertFalse(queryBeforeTraverse(mgr, 'BrowserIdManager')) class TestBrowserIdManagerTraverser(unittest.TestCase): def _getTargetClass(self): from Products.Sessions.BrowserIdManager \ import BrowserIdManagerTraverser return BrowserIdManagerTraverser def _makeOne(self): return self._getTargetClass()() def test___call___no_mgr(self): traverser = self._makeOne() container = DummyObject() request = DummyRequest() traverser(container, request) # doesn't raise def test___call___w_mgr_request_has_no_stack(self): traverser = self._makeOne() mgr = DummyBrowserIdManager() container = DummyObject(browser_id_manager=mgr) request = DummyRequest() traverser(container, request) # doesn't raise def test___call___w_mgr_request_has_stack_no_auto_encode(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() traverser = self._makeOne() mgr = DummyBrowserIdManager() container = DummyObject(browser_id_manager=mgr) request = DummyRequest( TraversalRequestNameStack=[bid, 'bid']) traverser(container, request) self.assertEqual(request.browser_id_, bid) self.assertEqual(request.browser_id_ns_, 'url') self.assertEqual(len(request.TraversalRequestNameStack), 0) def test___call___w_mgr_request_has_stack_w_auto_encode(self): from Products.Sessions.BrowserIdManager import getNewBrowserId bid = getNewBrowserId() traverser = self._makeOne() mgr = DummyBrowserIdManager(True) container = DummyObject(browser_id_manager=mgr) request = DummyRequest( TraversalRequestNameStack=[bid, 'bid'], _script=[]) traverser(container, request) self.assertEqual(request.browser_id_, bid) self.assertEqual(request.browser_id_ns_, 'url') self.assertEqual(len(request.TraversalRequestNameStack), 0) self.assertEqual(len(request._script), 2) self.assertEqual(request._script[0], 'bid') self.assertEqual(request._script[1], bid) def test___call___w_mgr_request_empty_stack_w_auto_encode(self): from Products.Sessions.BrowserIdManager import isAWellFormedBrowserId traverser = self._makeOne() mgr = DummyBrowserIdManager(True) container = DummyObject(browser_id_manager=mgr) request = DummyRequest( TraversalRequestNameStack=[], _script=[]) traverser(container, request) bid = request.browser_id_ self.assertTrue(isAWellFormedBrowserId(bid)) self.assertEqual(request.browser_id_ns_, None) self.assertEqual(len(request.TraversalRequestNameStack), 0) self.assertEqual(len(request._script), 2) self.assertEqual(request._script[0], 'bid') self.assertEqual(request._script[1], bid) class DummyObject: def __init__(self, **kw): self.__dict__.update(kw) class DummyResponse(DummyObject): pass class DummyRequest(DummyObject): def __getitem__(self, key): return getattr(self, key) def get(self, key, default=None): return getattr(self, key, default) class DummyBrowserIdManager: def __init__(self, auto=False): self._auto = auto def getBrowserIdName(self): return 'bid' def getAutoUrlEncoding(self): return self._auto def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestBrowserIdManager), unittest.makeSuite(TestBrowserIdManagerTraverser), )) zope2.13-2.13.21/source/Zope2/src/Products/Sessions/tests/testSessionDataManager.py0000644000175000017500000002061312214017422026757 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import unittest tf_name = 'temp_folder' idmgr_name = 'browser_id_manager' toc_name = 'temp_transient_container' sdm_name = 'session_data_manager' stuff = {} def _getDB(): from OFS.Application import Application import transaction db = stuff.get('db') if not db: from ZODB import DB from ZODB.DemoStorage import DemoStorage ds = DemoStorage() db = DB(ds, pool_size=60) conn = db.open() root = conn.root() app = Application() root['Application']= app transaction.savepoint(optimistic=True) _populate(app) stuff['db'] = db conn.close() return db def _delDB(): import transaction transaction.abort() del stuff['db'] def _populate(app): from OFS.DTMLMethod import DTMLMethod from Products.Sessions.BrowserIdManager import BrowserIdManager from Products.Sessions.SessionDataManager import SessionDataManager from Products.TemporaryFolder.TemporaryFolder import MountedTemporaryFolder from Products.Transience.Transience import TransientObjectContainer import transaction bidmgr = BrowserIdManager(idmgr_name) tf = MountedTemporaryFolder(tf_name, title="Temporary Folder") toc = TransientObjectContainer(toc_name, title='Temporary ' 'Transient Object Container', timeout_mins=20) session_data_manager=SessionDataManager(id=sdm_name, path='/'+tf_name+'/'+toc_name, title='Session Data Manager', requestName='TESTOFSESSION') try: app._delObject(idmgr_name) except (AttributeError, KeyError): pass try: app._delObject(tf_name) except (AttributeError, KeyError): pass try: app._delObject(sdm_name) except (AttributeError, KeyError): pass try: app._delObject('index_html') except (AttributeError, KeyError): pass app._setObject(idmgr_name, bidmgr) app._setObject(sdm_name, session_data_manager) app._setObject(tf_name, tf) transaction.commit() app.temp_folder._setObject(toc_name, toc) transaction.commit() # index_html necessary for publishing emulation for testAutoReqPopulate app._setObject('index_html', DTMLMethod('', __name__='foo')) transaction.commit() class TestSessionManager(unittest.TestCase): def setUp(self): from Testing import makerequest db = _getDB() conn = db.open() root = conn.root() self.app = makerequest.makerequest(root['Application']) timeout = self.timeout = 1 def tearDown(self): _delDB() del self.app def testHasId(self): self.assertTrue(self.app.session_data_manager.id == \ sdm_name) def testHasTitle(self): self.assertTrue(self.app.session_data_manager.title \ == 'Session Data Manager') def testGetSessionDataNoCreate(self): sd = self.app.session_data_manager.getSessionData(0) self.assertTrue(sd is None) def testGetSessionDataCreate(self): from Products.Transience.Transience import TransientObject sd = self.app.session_data_manager.getSessionData(1) self.assertTrue(sd.__class__ is TransientObject) def testHasSessionData(self): sd = self.app.session_data_manager.getSessionData() self.assertTrue(self.app.session_data_manager.hasSessionData()) def testNotHasSessionData(self): self.assertTrue(not self.app.session_data_manager.hasSessionData()) def testSessionDataWrappedInSDMandTOC(self): from Acquisition import aq_base sd = self.app.session_data_manager.getSessionData(1) sdm = aq_base(getattr(self.app, sdm_name)) toc = aq_base(getattr(self.app.temp_folder, toc_name)) self.assertTrue(aq_base(sd.aq_parent) is sdm) self.assertTrue(aq_base(sd.aq_parent.aq_parent) is toc) def testNewSessionDataObjectIsValid(self): from Acquisition import aq_base from Products.Transience.Transience import TransientObject sdType = type(TransientObject(1)) sd = self.app.session_data_manager.getSessionData() self.assertTrue(type(aq_base(sd)) is sdType) self.assertTrue(not hasattr(sd, '_invalid')) def testBrowserIdIsSet(self): sd = self.app.session_data_manager.getSessionData() mgr = getattr(self.app, idmgr_name) self.assertTrue(mgr.hasBrowserId()) def testGetSessionDataByKey(self): sd = self.app.session_data_manager.getSessionData() mgr = getattr(self.app, idmgr_name) token = mgr.getBrowserId() bykeysd = self.app.session_data_manager.getSessionDataByKey(token) self.assertTrue(sd == bykeysd) def testBadExternalSDCPath(self): from Products.Sessions.SessionDataManager import SessionDataManagerErr sdm = self.app.session_data_manager # fake out webdav sdm.REQUEST['REQUEST_METHOD'] = 'GET' sdm.setContainerPath('/fudgeffoloo') self.assertRaises(SessionDataManagerErr, self._testbadsdcpath) def _testbadsdcpath(self): self.app.session_data_manager.getSessionData() def testInvalidateSessionDataObject(self): sdm = self.app.session_data_manager sd = sdm.getSessionData() sd['test'] = 'Its alive! Alive!' sd.invalidate() self.assertTrue(not sdm.getSessionData().has_key('test')) def testGhostUnghostSessionManager(self): import transaction sdm = self.app.session_data_manager transaction.commit() sd = sdm.getSessionData() sd.set('foo', 'bar') sdm._p_changed = None transaction.commit() self.assertTrue(sdm.getSessionData().get('foo') == 'bar') def testSubcommitAssignsPJar(self): global DummyPersistent # so pickle can find it from Persistence import Persistent import transaction class DummyPersistent(Persistent): pass sd = self.app.session_data_manager.getSessionData() dummy = DummyPersistent() sd.set('dp', dummy) self.assertTrue(sd['dp']._p_jar is None) transaction.savepoint(optimistic=True) self.assertFalse(sd['dp']._p_jar is None) def testForeignObject(self): from ZODB.POSException import InvalidObjectReference self.assertRaises(InvalidObjectReference, self._foreignAdd) def _foreignAdd(self): import transaction ob = self.app.session_data_manager # we don't want to fail due to an acquisition wrapper ob = ob.aq_base # we want to fail for some other reason: sd = self.app.session_data_manager.getSessionData() sd.set('foo', ob) transaction.commit() def testAqWrappedObjectsFail(self): from Acquisition import Implicit import transaction class DummyAqImplicit(Implicit): pass a = DummyAqImplicit() b = DummyAqImplicit() aq_wrapped = a.__of__(b) sd = self.app.session_data_manager.getSessionData() sd.set('foo', aq_wrapped) self.assertRaises(TypeError, transaction.commit) def testAutoReqPopulate(self): self.app.REQUEST['PARENTS'] = [self.app] self.app.REQUEST['URL'] = 'a' self.app.REQUEST.traverse('/') self.assertTrue(self.app.REQUEST.has_key('TESTOFSESSION')) def testUnlazifyAutoPopulated(self): from Acquisition import aq_base from Products.Transience.Transience import TransientObject self.app.REQUEST['PARENTS'] = [self.app] self.app.REQUEST['URL'] = 'a' self.app.REQUEST.traverse('/') sess = self.app.REQUEST['TESTOFSESSION'] sdType = type(TransientObject(1)) self.assertTrue(type(aq_base(sess)) is sdType) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TestSessionManager), )) zope2.13-2.13.21/source/Zope2/src/Products/Sessions/tests/__init__.py0000644000175000017500000000125312214017422024105 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This file is needed to make this a package. zope2.13-2.13.21/source/Zope2/src/Products/Sessions/dtml/0000755000175000017500000000000012214017422021571 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Sessions/dtml/manageDataManager.dtml0000644000175000017500000000324112214017422025770 0ustar arnauarnau

    A Session Data Manager object is responsible for maintaining a relationship between session data objects and Zope browser ids. It is part of the Zope sessioning machinery. Programmers may interact with a session data manager in order to obtain information about session data, but will more often use the REQUEST.SESSION object to do sessioning-related tasks.

    Title
    Transient Object Container Path
    e.g. '/temp_folder/session_data'
    Place SESSION in REQUEST object as
    zope2.13-2.13.21/source/Zope2/src/Products/Sessions/dtml/manageIdManager.dtml0000644000175000017500000000754712214017422025470 0ustar arnauarnau

    Zope Browser Id Manager objects allow Zope to differentiate between site visitors by "tagging" each of their browsers with a unique identifier. This is useful if you need to tell visitors apart from one another even if they do not "log in" to your site. Browser Id Managers are generally used by interacting with the Zope sessioning machinery.

    Title
    Browser Id Name
    Look for Browser Id in
    CHECKED> Cookies
    CHECKED> Forms and Query Strings
    CHECKED> URLs
    Automatically Encode Zope-Generated
    URLs With A Browser Id
    CHECKED>
    Cookie Path
    leave blank to provide no path info in the browser cookie
    Cookie Domain
    leave blank to send cookies without domain
    info -- however, if cookie domain is not blank,
    it must contain at least two dots
    Cookie Lifetime In Days
    0 means send cookies which last only for the
    lifetime of the browser
    Only Send Cookie Over HTTPS
    CHECKED>
    Make cookie not aviable from JavaScript
    CHECKED>

    zope2.13-2.13.21/source/Zope2/src/Products/Sessions/dtml/addDataManager.dtml0000644000175000017500000000341412214017422025272 0ustar arnauarnau
    Zope Session Data Managers objects keep track of your users' session data objects. Developers interact with a Session Data Manager in order to store and retrieve information during a user session. A Session Data Manager communicates with a Browser Id Manager to determine the session information for the current user, and hands out Session Data Objects related to that user.
    Id
    Title
    Transient Object Container Path
    e.g. '/temp_folder/session_data'.
    Place SESSION in REQUEST object as

    zope2.13-2.13.21/source/Zope2/src/Products/Sessions/dtml/addIdManager.dtml0000644000175000017500000000737412214017422024766 0ustar arnauarnau
    Zope Browser Id Manager objects allow Zope to differentiate between site visitors by "tagging" each of their browsers with a unique identifier. This is useful if you need to tell visitors apart from one another even if they do not "log in" to your site. Browser Id Managers are generally used by interacting with the Zope sessioning machinery.
     
     
    Id
    This object's Zope id must be
    "browser_id_manager"
    Title
    Browser Id Name
     
    Look for Browser Id in
    Cookies
    Forms and Query Strings
    URLs
     
    Automatically Encode Zope-Generated
    URLs With A Browser Id
    Cookie Path
    leave blank to provide no path info in the browser cookie
    Cookie Domain
    leave blank to send cookies without domain
    info -- however, if cookie domain is not blank,
    it must contain at least two dots
    Cookie Lifetime In Days
    0 means send cookies which last only for the
    lifetime of the browser
    Only Send Cookie Over HTTPS
    Make cookie not aviable from JavaScript
    zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/0000755000175000017500000000000012214017422021051 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/VirtualHostMonster.py0000644000175000017500000002447612214017422025274 0ustar arnauarnau"""VirtualHostMonster module Defines the VirtualHostMonster class """ from AccessControl.class_init import InitializeClass from AccessControl.Permissions import view as View from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import Implicit from App.Dialogs import MessageDialog from App.special_dtml import DTMLFile from OFS.SimpleItem import Item from Persistence import Persistent from ZPublisher.BeforeTraverse import NameCaller from ZPublisher.BeforeTraverse import queryBeforeTraverse from ZPublisher.BeforeTraverse import registerBeforeTraverse from ZPublisher.BeforeTraverse import unregisterBeforeTraverse from ZPublisher.BaseRequest import quote from zExceptions import BadRequest class VirtualHostMonster(Persistent, Item, Implicit): """Provide a simple drop-in solution for virtual hosting. """ meta_type='Virtual Host Monster' priority = 25 id = 'VHM' title = '' lines = () have_map = 0 security = ClassSecurityInfo() manage_options=({'label':'About', 'action':'manage_main'}, {'label':'Mappings', 'action':'manage_edit'}) security.declareProtected(View, 'manage_main') manage_main = DTMLFile('www/VirtualHostMonster', globals(), __name__='manage_main') security.declareProtected('Add Site Roots', 'manage_edit') manage_edit = DTMLFile('www/manage_edit', globals()) security.declareProtected('Add Site Roots', 'set_map') def set_map(self, map_text, RESPONSE=None): "Set domain to path mappings." lines = map_text.split('\n') self.fixed_map = fixed_map = {} self.sub_map = sub_map = {} new_lines = [] for line in lines: line = line.split('#!')[0].strip() if not line: continue try: # Drop the protocol, if any line = line.split('://')[-1] try: host, path = [x.strip() for x in line.split('/', 1)] except: raise ValueError( 'Line needs a slash between host and path: %s' % line ) pp = filter(None, path.split( '/')) if pp: obpath = pp[:] if obpath[0] == 'VirtualHostBase': obpath = obpath[3:] if 'VirtualHostRoot' in obpath: i1 = obpath.index('VirtualHostRoot') i2 = i1 + 1 while i2 < len(obpath) and obpath[i2][:4] == '_vh_': i2 = i2 + 1 del obpath[i1:i2] if obpath: try: ob = self.unrestrictedTraverse(obpath) except: raise ValueError, ( 'Path not found: %s' % obpath ) if not getattr(ob.aq_base, 'isAnObjectManager', 0): raise ValueError, ( 'Path must lead to an Object Manager: %s' % obpath) if 'VirtualHostRoot' not in pp: pp.append('/') pp.reverse() try: int(host.replace('.','')) raise ValueError, ( 'IP addresses are not mappable: %s' % host) except ValueError: pass if host[:2] == '*.': host_map = sub_map host = host[2:] else: host_map = fixed_map hostname, port = (host.split( ':', 1) + [None])[:2] if hostname not in host_map: host_map[hostname] = {} host_map[hostname][port] = pp except 'LineError', msg: line = '%s #! %s' % (line, msg) new_lines.append(line) self.lines = tuple(new_lines) self.have_map = not not (fixed_map or sub_map) # booleanize if RESPONSE is not None: RESPONSE.redirect( 'manage_edit?manage_tabs_message=Changes%20Saved.') def addToContainer(self, container): container._setObject(self.id, self) def manage_addToContainer(self, container, nextURL=''): self.addToContainer(container) if nextURL: return MessageDialog(title='Item Added', message='This object now has a %s' % self.meta_type, action=nextURL) def manage_beforeDelete(self, item, container): if item is self: unregisterBeforeTraverse(container, self.meta_type) def manage_afterAdd(self, item, container): if item is self: if queryBeforeTraverse(container, self.meta_type): raise BadRequest, ('This container already has a %s' % self.meta_type) id = self.id if callable(id): id = id() # We want the original object, not stuff in between container = container.this() hook = NameCaller(id) registerBeforeTraverse(container, hook, self.meta_type, self.priority) def __call__(self, client, request, response=None): '''Traversing at home''' vh_used = 0 stack = request['TraversalRequestNameStack'] path = None while 1: if stack and stack[-1] == 'VirtualHostBase': vh_used = 1 stack.pop() protocol = stack.pop() host = stack.pop() if ':' in host: host, port = host.split(':') request.setServerURL(protocol, host, port) else: request.setServerURL(protocol, host) path = list(stack) # Find and convert VirtualHostRoot directive # If it is followed by one or more path elements that each # start with '_vh_', use them to construct the path to the # virtual root. vh = -1 for ii in range(len(stack)): if stack[ii] == 'VirtualHostRoot': vh_used = 1 pp = [''] at_end = (ii == len(stack) - 1) if vh >= 0: for jj in range(vh, ii): pp.insert(1, stack[jj][4:]) stack[vh:ii + 1] = ['/'.join(pp), self.id] ii = vh + 1 elif ii > 0 and stack[ii - 1][:1] == '/': pp = stack[ii - 1].split('/') stack[ii] = self.id else: stack[ii] = self.id stack.insert(ii, '/') ii += 1 path = stack[:ii] # If the directive is on top of the stack, go ahead # and process it right away. if at_end: request.setVirtualRoot(pp) del stack[-2:] break elif vh < 0 and stack[ii][:4] == '_vh_': vh = ii if vh_used or not self.have_map: if path is not None: path.reverse() vh_part = '' if path and path[0].startswith('/'): vh_part = path.pop(0)[1:] if vh_part: request['VIRTUAL_URL_PARTS'] = vup = ( request['SERVER_URL'], vh_part, quote('/'.join(path))) else: request['VIRTUAL_URL_PARTS'] = vup = ( request['SERVER_URL'], quote('/'.join(path))) request['VIRTUAL_URL'] = '/'.join(vup) # new ACTUAL_URL add = (path and request['ACTUAL_URL'].endswith('/')) and '/' or '' request['ACTUAL_URL'] = request['VIRTUAL_URL'] + add return vh_used = 1 # Only retry once. # Try to apply the host map if one exists, and if no # VirtualHost directives were found. host = request['SERVER_URL'].split('://')[1].lower() hostname, port = (host.split( ':', 1) + [None])[:2] ports = self.fixed_map.get(hostname, 0) if not ports and self.sub_map: get = self.sub_map.get while hostname: ports = get(hostname, 0) if ports: break if '.' not in hostname: return hostname = hostname.split('.', 1)[1] if ports: pp = ports.get(port, 0) if pp == 0 and port is not None: # Try default port pp = ports.get(None, 0) if not pp: return # If there was no explicit VirtualHostRoot, add one at the end if pp[0] == '/': pp = pp[:] pp.insert(1, self.id) stack.extend(pp) def __bobo_traverse__(self, request, name): '''Traversing away''' if name[:1] != '/': return getattr(self, name) parents = request.PARENTS parents.pop() # I don't belong there if len(name) > 1: request.setVirtualRoot(name) else: request.setVirtualRoot([]) return parents.pop() # He'll get put back on InitializeClass(VirtualHostMonster) def manage_addVirtualHostMonster(self, id=None, REQUEST=None, **ignored): """ """ container = self.this() vhm = VirtualHostMonster() container._setObject(vhm.getId(), vhm) if REQUEST is not None: goto = '%s/manage_main' % self.absolute_url() qs = 'manage_tabs_message=Virtual+Host+Monster+added.' REQUEST['RESPONSE'].redirect('%s?%s' % (goto, qs)) constructors = ( ('manage_addVirtualHostMonster', manage_addVirtualHostMonster), ) zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/AccessRule.py0000644000175000017500000000560412214017422023461 0ustar arnauarnau"""AccessRule module Provide a simple method to set up Access Rules """ from cgi import escape import os from App.Dialogs import MessageDialog from App.special_dtml import DTMLFile from ZPublisher.BeforeTraverse import NameCaller from ZPublisher.BeforeTraverse import queryBeforeTraverse from ZPublisher.BeforeTraverse import registerBeforeTraverse from ZPublisher.BeforeTraverse import unregisterBeforeTraverse SUPPRESS_ACCESSRULE = os.environ.has_key('SUPPRESS_ACCESSRULE') class AccessRule(NameCaller): meta_type = 'Set Access Rule' def __call__(self, container, request): if SUPPRESS_ACCESSRULE: return NameCaller.__call__(self, container, request) def manage_addAccessRule(self, method_id=None, REQUEST=None, **ignored): """Point a __before_traverse__ entry at the specified method""" # We want the original object, not stuff in between, and no acquisition self = self.this() self = getattr(self, 'aq_base', self) priority = (1, 'AccessRule') if method_id is None or (REQUEST and REQUEST.form.has_key('none')): rules = unregisterBeforeTraverse(self, 'AccessRule') if rules: try: del getattr(self, rules[0].name).icon except: pass if REQUEST: return MessageDialog(title='No Access Rule', message='This object now has no Access Rule', action='%s/manage_main' % REQUEST['URL1']) elif method_id and hasattr(self, method_id): rules = unregisterBeforeTraverse(self, 'AccessRule') if rules: try: del getattr(self, rules[0].name).icon except: pass hook = AccessRule(method_id) registerBeforeTraverse(self, hook, 'AccessRule', 1) try: getattr(self, method_id).icon = 'misc_/SiteAccess/AccessRule.gif' except: pass if REQUEST: return MessageDialog(title='Access Rule Set', message='"%s" is now the Access Rule for this object' % escape(method_id), action='%s/manage_main' % REQUEST['URL1']) else: if REQUEST: return MessageDialog(title='Invalid Method Id', message='"%s" is not the Id of a method of this object' % escape(method_id), action='%s/manage_main' % REQUEST['URL1']) def getAccessRule(self, REQUEST=None): "Return the name of the current AccessRule, if any" self = self.this() rules = queryBeforeTraverse(self, 'AccessRule') if rules: try: return rules[0][1].name except: return 'Invalid BeforeTraverse data: ' + `rules` return '' constructors = ( ('manage_addAccessRuleForm', DTMLFile('www/AccessRuleAdd', globals())), ('manage_addAccessRule', manage_addAccessRule), ('manage_getAccessRule', getAccessRule), ) zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/www/0000755000175000017500000000000012214017422021675 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/www/SiteRoot.gif0000644000175000017500000000036612214017422024141 0ustar arnauarnauGIF89a„ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!þMade with GIMP!ù,a #ŽdI ¨`–±¸êê´oÞ¢°<|ïóÙcÑ ‹ÅAPðp5žÐ"A)

    This will change URLs generated by all objects within the same container as the SiteRoot. If a Base is specified (or a SiteRootBASE value can be found) then it will replace the host:port/script portion of generated URLs. If a Path is specified (or a SiteRootPATH value can be found) then it will replace the remainder of each URL.

    Values affected include DTML variables starting with URL or BASE, and the absolute_url() methods of objects.

    If Base is not set, the SiteRoot will first attempt to acquire SiteRootBASE and then search the REQUEST for it. The same holds for Path and SiteRootPATH.

    Base (if specified) should always start with "http://"

    Title
    Base
    Path
    zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/www/SiteRootAdd.dtml0000644000175000017500000000402412214017422024740 0ustar arnauarnau

    This will change URLs generated by all objects within the same container as the SiteRoot. If a Base is specified (or a SiteRootBASE value can be found) then it will replace the host:port/script portion of generated URLs. If a Path is specified (or a SiteRootPATH value can be found) then it will replace the remainder of each URL.

    Values affected include DTML variables starting with URL or BASE, and the absolute_url() methods of objects.

    If Base is not set, the SiteRoot will first attempt to acquire SiteRootBASE and then search the REQUEST for it. The same holds for Path and SiteRootPATH.

    Base (if specified) should always start with "http://"

    Id
    SiteRoot
    Title
    Base
    Path
    zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/www/VirtualHostMonster.gif0000644000175000017500000000034212214017422026217 0ustar arnauarnauGIF89a„ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!þMade with GIMP!ù,M #:i–cz®êX¾štüš4áEѨ¡Ñö¿òˆl&wNäcê¤9§5zÍ‘ßjS— s-³®%2ƒÙÖ(Må–‹lø<>;zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/www/VirtualHostMonster.dtml0000644000175000017500000001121312214017422026411 0ustar arnauarnau

    The Virtual Host Monster supports virtual hosting

    It changes the protocol, host, and/or path of URLs generated by Zope. Values affected include REQUEST variables starting with URL or BASE (such as URL1, BASE2, URLPATH0), and the absolute_url() methods of objects. By changing these, the Virtual Host Monster allows you to host several different domains in a single Zope.

    The most common sort of virtual hosting setup is one in which you create a Folder in your Zope root for each domain that you want to serve. For instance http://www.buystuff.com is served from Folder /buystuff.com while http://www.mycause.org is served from /mycause.org.

    One is enough

    A single Virtual Host Monster in your Zope root can handle all of your virtual hosting needs. It doesn't matter what Id you give it, as long as nothing else in your site has the same Id.

    You must add special names in the path

    The VHM doesn't do anything unless it sees one of the following special path elements in a URL: VirtualHostBase sets the protocol and host, while VirtualHostRoot sets the path root.

    If the URL path of a request begins with "/VirtualHostBase/http/www.buystuff.com", for instance, then URLs generated by Zope will start with http://www.buystuff.com. Since the port number was not specified, it is left unchanged. If your Zope is running on port 8080, and you want generated URLs not to include this port number, you must use "/VirtualHostBase/http/www.buystuff.com:80".

    If the URL contains VirtualHostRoot, then all path elements up to that point are removed from generated URLs. For instance, a request with path "/a/b/c/VirtualHostRoot/d" will traverse "a/b/c/d" and then generate a URL with path /d.

    You add these names by rewriting incoming URLs

    Visitors to your site don't see these special names, of course. You insert them into the path using either an external rewriter, such as an Apache RewriteRule or ProxyPass directive, or by setting up a mapping on the "Mappings" tab.

    For example, suppose Zope is running on port 8080 behind an Apache running on port 80. You place a Virtual Host Monster in the Zope root Folder, and use Apache to rewrite "/(.*)" to http://localhost:8080/VirtualHostBase/http/www.buystuff.com:80/buystuff.com/VirtualHostRoot/$1.

    You could get the same effect in a standalone Zope by adding the line www.buystuff.com/buystuff.com to the "Mappings" tab. In either case, requests for http://www.buystuff.com/anything will look for Zope object /buystuff.com/anything.

    You should only use the "Mappings" tab for simple virtual hosting, in a Zope that is serving requests directly. Each mapping line is a host name followed by a path to a Folder. The VHM checks the host specified in each incoming request to see if it is in the list. If it is, then the corresponding path is inserted at the start of the path, followed by "VirtualHostRoot".

    You can match multiple subdomains by putting "*." in front of the host name, as in "*.buystuff.com". If an exact match exists, it is used instead of a wildcard match.

    Inside-out hosting

    Another use for virtual hosting is to make Zope appear to be part of a site controlled by another server. For example, Zope might only serve the contents of http://www.mycause.org/dynamic_stuff. To accomplish this, you want to add "dynamic_stuff" to the start of all Zope-generated URLs.

    If you insert VirtualHostRoot, followed by one or more path elements that start with '_vh_', then these elements will be ignored during traversal and then added (without the '_vh_') to the start of generated URLs. For instance, a request for "/a/VirtualHostRoot/_vh_z/" will traverse "a" and then generate URLs that start with /z.

    In our example, you would have the main server send requests for http://www.mycause.org/dynamic_stuff/anything to Zope, rewritten as /VirtualHostRoot/_vh_dynamic_stuff/anything.

    zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/www/manage_edit.dtml0000644000175000017500000000422612214017422025020 0ustar arnauarnau

    You may edit the mappings for this Virtual Host Monster using the form below. You don't need to use this tab if you are using Apache or some other front-end server to rewrite requests. This is only for simple virtual hosting in a bare Zope server. If you place the hostname that you use to manage your Zope in this list you are likely to regret it, and will probably need to manage Zope using its raw IP address to fix things.

    Last Modified
    Each line represents a path mapping for a single host (host/path), or a set of hosts (*.host/path).
    Locked by WebDAV   
    zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/www/AccessRuleAdd.dtml0000644000175000017500000000273712214017422025232 0ustar arnauarnau

    WARNING: Access Rules are powerful, and can temporarily disable Zope access! Don't use them unless you have read all about them and know how to recover from mistakes!

    In the form below rule id is the id of an object in this Zope Folder which will be called whenever the Folder is published. It can implement rewrite rules, preload request variables, etc.

    The current Access Rule is "&dtml-manage_getAccessRule;".

    No Access Rule is currently set.

    Rule Id
    zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/__init__.py0000644000175000017500000000135212214017422023163 0ustar arnauarnau"""SiteAccess product """ def initialize(context): import SiteRoot import AccessRule import VirtualHostMonster context.registerClass(instance_class=SiteRoot.SiteRoot, permission='Add Site Roots', constructors=SiteRoot.constructors, legacy=SiteRoot.constructors, icon='www/SiteRoot.gif') context.registerClass(instance_class=AccessRule.AccessRule, permission='Manage Access Rules', constructors=AccessRule.constructors, icon='www/AccessRule.gif') context.registerClass(instance_class=VirtualHostMonster.VirtualHostMonster, permission='Add Virtual Host Monsters', constructors=VirtualHostMonster.constructors, icon='www/VirtualHostMonster.gif') context.registerHelp() zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/help/0000755000175000017500000000000012214017422022001 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/help/SiteRoot_Edit.stx0000644000175000017500000000100012214017422025245 0ustar arnauarnauSiteRoot - Edit: Edit SiteRoot parameters Description This view displays the SiteRoot parameters and allows you to change them. Information 'Title' -- The optional title. 'Base' -- Replacement for base of URLs. If you do not want to replace this portion of URLs, leave it blank. 'Path' -- Replacement for the URL path to the SiteRoot's container. If you do not want to replace this portion of URLs, leave it blank. Controls 'Change' -- Changes the parameters. zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/README.txt0000644000175000017500000000056212214017422022552 0ustar arnauarnauSiteAccess The SiteAccess product provides support for pluggable pre- traversal methods, and supplies objects and methods which can be used to rewrite access URLs at the start of resolution and as folders are traversed. The main intent is to provide for multi-site hosting in Zope. More information is available at http://zope.org/Members/4am/SiteAccess zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/doc/0000755000175000017500000000000012214017422021616 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/doc/info.html0000644000175000017500000000267512214017422023451 0ustar arnauarnau All about SiteAccess

    Basics

    SiteAccess started as a Product to enable Zope Virtual Hosting, then became more generalized. Virtual Hosting is the ability to publish sub-objects of a Zope hierarchy as though they were the root of their own site. For example, it allows 'http:/www.silly-walks.org/current' to publish the Zope object 'silly-walks/current' instead of just 'current'.

    Usage

    See:

    What does SiteAccess do?

    SiteAccess provides the ability to force ZPublisher to call objects of your choice as it enters any folder. With this capability, you can designate a method in the Zope root to examine the request parameters and alter or replace the URL.

    If an Access Rule is broken, and is preventing normal access, it can be disabled by restarting Zope with environment variable SUPPRESS_ACCESSRULE set.

    zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/doc/vhosting.html0000644000175000017500000001663312214017422024356 0ustar arnauarnau Virtual Site Hosting

    Getting Started

    Before you can set up virtual hosting in Zope, you need to answer two questions:
    • What is the URI of each site's logical root?
    • What is the corresponding path to the physical root?
    Suppose, for example, that you want to use Zope to host the domain www.hotsite.com, and you want 'http://www.hotsite.com' to publish the Zope object '/hotsite/index_html'. Then 'http://www.hotsite.com' is the URI of your logical root, and '/hotsite' is the path to your physical root.

    Example 1: One Site

    Put a SiteRoot in '/hotsite', your site's physical root, and accept the default Path. Create a DTML Method in the Zope root folder containing
    Is the first-level domain 'hotsite.com'? Ignore sub-domains and port number.
    <dtml-if "_.string.split(_.string.split(HTTP_HOST, ':')[0], '.')[-2:]==['hotsite', 'com']">
      Add physical root: 
      <dtml-call "REQUEST['TraversalRequestNameStack'].append('hotsite')">
    </dtml-if >
    
    Use "Set Access Rule" with the DTML Method's Id. Want to understand this? Read on.

    Getting Physical

    The first half of virtual hosting is rewriting incoming URIs into physical paths. Many people run ZServer behind Apache, or another HTTP server with rewriting capabilities, or a proxy. In these cases, you can tell the front-end HTTP server to rewrite 'http://www.hotsite.com/(.*)' to '/blah/cgi/Zope.cgi/hotsite/$1', for example.

    This works perfectly well, but if your clients are connecting directly to ZServer, or if you would like to keep all of the virtual hosting logic in Zope, you will need to do your rewriting in an Access Rule.

    An Access Rule is just a regular method (DTML Method or Document, External Method, Python Method, etc.) on which you have used SiteAccess' "Set Access Rule" method. In this case, the method lives in the root, so it will examine every incoming request and decide how to deal with it.

    The example DTML Method above is the simplest kind of rewrite rule, forcing all requests to traverse the 'hotsite' object before any others in the URI.

    Getting Logical

    The second, and more difficult, half of virtual hosting is getting your Zope objects to generate correct logical URIs for links and images. For example, if you are rewriting hotsite as described above, then a standard DTML snippet such as
    <a href="&dtml-URL;/hottopics">
    
    in object '/hotsite/forum' will generate
    <a href="http://www.hotsite.com/hotsite/forum/hottopics">
    rather than
    <a href="http://www.hotsite.com/forum/hottopics">
    
    To prevent this, all of the URLn and BASEn request variables and the absolute_url() method need to be told to strip off '/hotsite'. That's what SiteRoot objects do.

    A SiteRoot object should be placed in the physical root folder ('/hotsite', in this case) and told the logical URL at which to base all requests passing through this folder. You tell it by setting its Path property, which in this case should have the value '/'.

    For flexibility's sake, Path can also be set as a property 'SiteRootPATH' of the '/hotsite' folder or of the root folder, or it can be set in the rewriting Access Rule with a call to "REQUEST.set('SiteRootPATH', '/')", or it can be passed in from the mediating web server as an environment variable. You can also provide a Base ('SiteRootBASE') value, which will then replace the host:port/script portion of URIs.

    Example 2: Multiple Sites

    Suppose we are hosting 'hotsite.net', 'fooflowers.com', and 'openmouths.org' from '/hotsite', '/foof', and '/openm' respectively. We are distinguishing requests via HTTP_HOST, and we don't care what subdomain or port was specified.

    Put a SiteRoot in each of '/hotsite', '/foof', and '/openm'. In each one, erase the default Path and leave Base blank. Make a DTML Method in the root folder containing
    Extract the part of HTTP_HOST we care about, and declare our rewrite dictionary.
    <dtml-let hostname="_.string.join(_.string.split(_.string.split(HTTP_HOST, ':')[0], '.')[-2:], '.')"
              sitemap="{'hotsite.net': 'hotsite',
                        'fooflowers.com': 'foof',
                        'openmouths.org': 'openm'}">
        Do we have a match?
        <dtml-if expr="sitemap.has_key(hostname)">
    Set the logical root: <dtml-call "REQUEST.set('SiteRootPATH', '/')">
    Add physical root: <dtml-call "REQUEST['TraversalRequestNameStack'].append(sitemap[hostname])">
        </dtml-if>
    </dtml-let>
    
    Use "Set Access Rule" with the DTML Method's Id. An almost identical method can be used to distinguish sites by SERVER_ADDRESS and SERVER_PORT instead of HTTP_HOST. In that case, though, you would probably add a line to set the appropriate SiteRootBASE.

    If you wanted all of these virtual hosts' root folders to live in the folder 'vhosts', you could add the line:
    Add vhost root: <dtml-call "REQUEST['TraversalRequestNameStack'].append('vhosts')"> 
    
    after the 'Add physical root' line. If you wanted to add multiple path elements for each site, you could use path.extend instead of path.append and map 'fooflowers.org', for example, to ['foof', 'f', 'comsites']. This would place the root of fooflowers in folder '/comsites/f/foof/'.

    Minor Notes

    • The return value of an Access Rule is ignored and discarded. This allows embedded string comments such as in the examples above, and the use of <dtml-return " 'ignored' "> to exit the Rule. It also means that if you want to redirect within an Access Rule, you must use <dtml-raise type="Redirect"> instead of "RESPONSE.redirect()"
    • A SiteRoot object is essentially an Access Rule which calls
      REQUEST.setServerURL(SiteRootBASE) and REQUEST.setVirtualRoot(SiteRootPATH).
    • You might want to exempt management access from being affected by the virtual hosting. One way to do this is to have a 'magic folder' start all management interactions. I use 'Z', and wrap the rest of the Access Rule code in something like:
      Is there a path, and does it start with 'Z'?
      <dtml-let stack="REQUEST['TraversalRequestNameStack']">
      <dtml-if "stack and stack[-1]=='Z'">
      Get rid of 'Z':     <dtml-call "stack.pop()">
      Put it back logically: <dtml-call "REQUEST.setVirtualRoot('Z')">
      <dtml-else>
      ...
      </dtml-if>
      </dtml-let>
      
    • If a SiteRooted folder is ever accessed through URLs with a base or path that does not get rewritten to match the Base and Path of the SiteRoot, you should make the SiteRoot's Base and Path blank and dynamically create SiteRootPATH/SiteRootBASE variables. For example, if you made a 'Zope' global-access prefix as described above, then the 'else' part should contain something like <dtml-call "REQUEST.set('SiteRootPATH', '/')">.
    zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/doc/upgrading.html0000644000175000017500000000163412214017422024470 0ustar arnauarnau Upgrading from SiteAccess 1

    Must Upgrade

    If your site has SiteAccess 1 objects in it, you must upgrade them. Until you do, they will be inert

    From the command line

    Copy 'updata.py' from the 'Extensions' directory of the SiteAccess Product to the 'lib/python' directory. Run it there, then delete it.

    From an External Method

    In the Zope root folder, add an External Method with method "updata" and python file "SiteAccess.updata". Click on the "Try It" tab, then delete it.

    zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/doc/otheruse.html0000644000175000017500000000452612214017422024351 0ustar arnauarnau Other Uses of Access Rules

    Embedded Session Values

    Sometimes it would be nice to be able to embed a variable/value in the middle of a URL, rather than having to tack a query string onto the end. It can be essential, such as when you want parameterized pages to be spiderable (spiders don't like query strings).

    One way to accomplish this is with an Access Rule. For example, suppose we created a Zope folder called 'Session', containing the following DTML Method Access Rule:
    <dtml-let stack="REQUEST['TraversalRequestNameStack']">
    Don't intercept management requests
    <dtml-unless "stack[0][:6]=='manage'">
      Is the next path segment a positive integer?
      <dtml-if "_.int(stack[-1])>0">
        Save it and remove it from the path
        <dtml-call "REQUEST.set('SessionID', stack.pop())">
        Add it back into the logical path
        <dtml-call "REQUEST.setVirtualRoot(REQUEST.steps+[SessionID])">
      <dtml-else>
        <dtml-raise type="Invalid">Invalid Session ID!</dtml-raise>
      </dtml-if>
    </dtml-unless>
    </dtml-let>
    
    Then the request URI 'http://www.mysite.com/foo/Session/84076/step3' will publish the Zope object at '/foo/Session/step3', with variable 'SessionID' set to '84076'. Thanks to acquisition, 'step3' doesn't need to be in 'Session'. 'Session' may be empty except for the Access Rule, or it may contain session-management objects.

    When writing this kind of Access Rule, it is useful to remember the following:
    • REQUEST['TraversalRequestNameStack'] is the stack of Ids yet to be traversed.
    • REQUEST.steps is the list of Ids already traversed.
    • You can manipulate the path stack with append, insert, pop, and similar list operations.
    • You should not manipulate 'steps', instead using REQUEST.setVirtualRoot('path') to alter the apparent traversal history and URL generation.
    zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/doc/installing.html0000644000175000017500000000135212214017422024651 0ustar arnauarnau Installing SiteAccess

    Installing SiteAccess

    Please let me know what you think.

    zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/SiteRoot.py0000644000175000017500000001113512214017422023174 0ustar arnauarnau"""SiteRoot module Defines the Traverser base class and SiteRoot class """ from cgi import escape import os from Acquisition import Implicit from App.Dialogs import MessageDialog from App.special_dtml import DTMLFile from OFS.SimpleItem import Item from Persistence import Persistent from ZPublisher.BeforeTraverse import NameCaller from ZPublisher.BeforeTraverse import registerBeforeTraverse from ZPublisher.BeforeTraverse import unregisterBeforeTraverse SUPPRESS_SITEROOT = os.environ.has_key('SUPPRESS_SITEROOT') class Traverser(Persistent, Item): """ Class for overriding container's __before_traverse__ Containers are expected to have at most one instance of any particular subclass, with Id equal to the meta_type of the subclass. """ meta_type = 'Traverser' priority = 100 __ac_permissions__=() def addToContainer(self, container): container._setObject(self.id, self) self.manage_afterAdd(self, container) def manage_addToContainer(self, container, nextURL=''): if nextURL: if hasattr(getattr(container, 'aq_base', container), self.id): return MessageDialog(title='Item Exists', message='This object already contains a %s' % self.meta_type, action=nextURL) self.addToContainer(container) if nextURL: return MessageDialog(title='Item Added', message='This object now has a %s' % escape(self.meta_type), action=nextURL) def manage_beforeDelete(self, item, container): if item is self: unregisterBeforeTraverse(container, self.meta_type) def manage_afterAdd(self, item, container): if item is self: id = self.id if callable(id): id = id() # We want the original object, not stuff in between container = container.this() hook = NameCaller(id) registerBeforeTraverse(container, hook, self.meta_type, self.priority) def _setId(self, id): if id != self.id: raise ValueError('Cannot change the id of a %s' % escape(self.meta_type)) class SiteRoot(Traverser, Implicit): """ SiteAccess.SiteRoot object A SiteRoot causes traversal of its container to replace the part of the Request path traversed so far with the request's SiteRootURL. """ id = meta_type = 'SiteRoot' title = '' priority = 50 manage_options=({'label':'Edit', 'action':'manage_main', 'help': ('SiteAccess', 'SiteRoot_Edit.stx'), },) manage = manage_main = DTMLFile('www/SiteRootEdit', globals()) manage_main._setName('manage_main') def __init__(self, title, base, path): self.title = title.strip() self.base = base = base.strip() self.path = path = path.strip() def manage_edit(self, title, base, path, REQUEST=None): """ Set the title, base, and path. """ self.__init__(title, base, path) if REQUEST: return MessageDialog(title='SiteRoot changed.', message='SiteRoot changed.', action='%s/manage_main' % REQUEST['URL1']) def __call__(self, client, request, response=None): """ Traversing. """ rq = request if SUPPRESS_SITEROOT: return base = (self.base or rq.get('SiteRootBASE') or rq.environ.get('SiteRootBASE')) path = (self.path or rq.get('SiteRootPATH') or rq.environ.get('SiteRootPATH')) if base is not None: rq['ACTUAL_URL'] = rq['ACTUAL_URL'].replace(rq['SERVER_URL'], base) rq['SERVER_URL'] = base rq._resetURLS() if path is not None: old = rq['URL'] rq.setVirtualRoot(path) rq['ACTUAL_URL'] = rq['ACTUAL_URL'].replace(old, rq['URL']) def get_size(self): """ Make FTP happy """ return 0 def manage_addSiteRoot(self, title='', base='', path='', REQUEST=None, **ignored): """ Add a SiteRoot to a container. """ sr = SiteRoot(title, base, path) if REQUEST: return sr.manage_addToContainer(self.this(), '%s/manage_main' % REQUEST['URL1']) else: sr.manage_addToContainer(self.this()) constructors = ( ('manage_addSiteRootForm', DTMLFile('www/SiteRootAdd', globals())), ('manage_addSiteRoot', manage_addSiteRoot), ) zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/Extensions/0000755000175000017500000000000012214017422023210 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/Extensions/updata.py0000644000175000017500000000253312214017422025043 0ustar arnauarnaudef updata(self): """Convert SiteAccess objects from 1.x to 2.x""" _cvt_btr(self.REQUEST['PARENTS'][-1]) from App.Dialogs import MessageDialog return MessageDialog(title='Update Complete', message='Update Complete!', action='./manage_main') def _cvt_btr(app): from ZPublisher.BeforeTraverse import NameCaller from ZPublisher.BeforeTraverse import rewriteBeforeTraverse from Products.SiteAccess.AccessRule import AccessRule stack = [app] while stack: o = stack.pop() ov = getattr(o, 'objectValues', None) if ov is not None: stack.extend(list(ov())) btr = getattr(o, '__before_traverse__', None) if btr and type(btr) == type({}): touched = 0 for k, v in btr.items(): if type(v) is type(''): touched = 1 if k[1] == 'AccessRule': btr[k] = AccessRule(v) else: btr[k] = NameCaller(v) if touched: rewriteBeforeTraverse(o, btr) if __name__ == '__main__': import Zope2 import transaction print "Converting SiteAccess objects from 1.x to 2.x ..." app = Zope2.app() _cvt_btr(app) transaction.commit() print "Done." zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/tests/0000755000175000017500000000000012214017422022213 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/tests/testAccessRule.py0000644000175000017500000002233712214017422025525 0ustar arnauarnauimport unittest class AccessRuleTests(unittest.TestCase): _old_SAR = None def setUp(self): from Testing.ZopeTestCase import ZopeLite ZopeLite.startup() def tearDown(self): if self._old_SAR is not None: self._set_SUPPRESS_ACCESSRULE(self._old_SAR) def _set_SUPPRESS_ACCESSRULE(self, value): from Products.SiteAccess import AccessRule as AR (self._old_SAR, AR.SUPPRESS_ACCESSRULE) = (AR.SUPPRESS_ACCESSRULE, value) def _getTargetClass(self): from Products.SiteAccess.AccessRule import AccessRule return AccessRule def _makeOne(self, method_id='testing'): return self._getTargetClass()(method_id) def test___call___w_SUPPRESS_ACCESSRULE_set(self): self._set_SUPPRESS_ACCESSRULE(1) _called = [] def _func(*args): _called.append(args) rule = self._makeOne() request = DummyRequest(TraversalRequestNameStack=[]) container = DummyContainer(testing=_func) rule(container, request) self.assertFalse(_called) def test___call___w_SUPPRESS_ACCESSRULE_in_URL(self): # This behavior changed in landing lp:142878. _called = [] def _func(*args): _called.append(args) rule = self._makeOne() request = DummyRequest(TraversalRequestNameStack= ['_SUPPRESS_ACCESSRULE']) request.steps = [] container = DummyContainer(testing=_func) rule(container, request) self.assertTrue(_called) self.assertEqual(request._virtual_root, None) def test___call___wo_SUPPRESS_ACCESSRULE(self): _called = [] def _func(*args): _called.append(args) rule = self._makeOne() request = DummyRequest(TraversalRequestNameStack=[]) request.steps = [] container = DummyContainer(testing=_func) rule(container, request) self.assertTrue(_called) self.assertEqual(request._virtual_root, None) class Test_manage_addAccessRule(unittest.TestCase): def _callFUT(self, container, method_id, REQUEST): from Products.SiteAccess.AccessRule import manage_addAccessRule return manage_addAccessRule(container, method_id, REQUEST) def test_no_method_id_no_existing_rules_no_request(self): container = DummyContainer() result = self._callFUT(container, None, None) self.assertTrue(result is None) self.assertFalse(container.__dict__) def test_no_method_id_no_existing_rules_w_request(self): container = DummyContainer() result = self._callFUT(container, None, {'URL1': 'http://example.com/'}) self.assertTrue(isinstance(result, str)) self.assertTrue('No Access Rule' in result) self.assertFalse(container.__dict__) def test_no_method_id_w_existing_rules_no_request(self): from ZPublisher.BeforeTraverse import registerBeforeTraverse container = DummyContainer() old_rule = container.old_rule = DummyObject(name='old_rule', icon='rule_icon.jpg') registerBeforeTraverse(container, old_rule, 'AccessRule') result = self._callFUT(container, None, None) self.assertTrue(result is None) self.assertFalse(container.__before_traverse__) self.assertFalse('icon' in old_rule.__dict__) def test_w_method_id_w_existing_rules_w_request_none(self): from ZPublisher.BeforeTraverse import registerBeforeTraverse container = DummyContainer() old_rule = container.old_rule = DummyObject(name='old_rule', icon='rule_icon.jpg') registerBeforeTraverse(container, old_rule, 'AccessRule') request = DummyRequest(URL1 = 'http://example.com/') request.form = {'none': '1'} result = self._callFUT(container, None, request) self.assertTrue(isinstance(result, str)) self.assertTrue('No Access Rule' in result) self.assertFalse(container.__before_traverse__) self.assertFalse('icon' in old_rule.__dict__) def test_w_invalid_method_id_w_existing_rules_no_request(self): from ZPublisher.BeforeTraverse import registerBeforeTraverse container = DummyContainer() old_rule = container.old_rule = DummyObject(name='old_rule', icon='rule_icon.jpg') registerBeforeTraverse(container, old_rule, 'AccessRule') result = self._callFUT(container, 'nonesuch', None) self.assertTrue(result is None) self.assertTrue((99, 'AccessRule') in container.__before_traverse__) rule = container.__before_traverse__[(99, 'AccessRule')] self.assertEqual(rule.name, 'old_rule') self.assertEqual(old_rule.icon, 'rule_icon.jpg') def test_w_invalid_method_id_w_existing_rules_w_request(self): from ZPublisher.BeforeTraverse import registerBeforeTraverse container = DummyContainer() old_rule = container.old_rule = DummyObject(name='old_rule', icon='rule_icon.jpg') registerBeforeTraverse(container, old_rule, 'AccessRule') request = DummyRequest(URL1 = 'http://example.com/') request.form = {} result = self._callFUT(container, 'nonesuch', request) self.assertTrue(isinstance(result, str)) self.assertTrue('Invalid Method Id' in result) self.assertTrue((99, 'AccessRule') in container.__before_traverse__) rule = container.__before_traverse__[(99, 'AccessRule')] self.assertEqual(rule.name, 'old_rule') self.assertEqual(old_rule.icon, 'rule_icon.jpg') def test_w_valid_method_id_w_existing_rules_no_request(self): from ZPublisher.BeforeTraverse import registerBeforeTraverse container = DummyContainer() old_rule = container.old_rule = DummyObject(name='old_rule', icon='rule_icon.jpg') new_rule = container.new_rule = DummyObject(name='new_rule') registerBeforeTraverse(container, old_rule, 'AccessRule') result = self._callFUT(container, 'new_rule', None) self.assertTrue(result is None) self.assertFalse((99, 'AccessRule') in container.__before_traverse__) self.assertTrue((1, 'AccessRule') in container.__before_traverse__) rule = container.__before_traverse__[(1, 'AccessRule')] self.assertEqual(rule.name, 'new_rule') self.assertFalse('icon' in old_rule.__dict__) self.assertEqual(new_rule.icon, 'misc_/SiteAccess/AccessRule.gif') def test_w_valid_method_id_w_existing_rules_w_request(self): from ZPublisher.BeforeTraverse import registerBeforeTraverse container = DummyContainer() old_rule = container.old_rule = DummyObject(name='old_rule', icon='rule_icon.jpg') new_rule = container.new_rule = DummyObject(name='new_rule') registerBeforeTraverse(container, old_rule, 'AccessRule') request = DummyRequest(URL1 = 'http://example.com/') request.form = {} result = self._callFUT(container, 'new_rule', request) self.assertTrue(isinstance(result, str)) self.assertTrue('Access Rule Set' in result) self.assertFalse((99, 'AccessRule') in container.__before_traverse__) self.assertTrue((1, 'AccessRule') in container.__before_traverse__) rule = container.__before_traverse__[(1, 'AccessRule')] self.assertEqual(rule.name, 'new_rule') self.assertFalse('icon' in old_rule.__dict__) self.assertEqual(new_rule.icon, 'misc_/SiteAccess/AccessRule.gif') class Test_getAccessRule(unittest.TestCase): def _callFUT(self, container, REQUEST=None): from Products.SiteAccess.AccessRule import getAccessRule return getAccessRule(container, REQUEST) def test_no_rules(self): container = DummyContainer() self.assertEqual(self._callFUT(container), '') def test_w_rule_invalid(self): from ZPublisher.BeforeTraverse import registerBeforeTraverse container = DummyContainer() registerBeforeTraverse(container, DummyObject(), 'AccessRule') self.assertTrue(self._callFUT(container).startswith( 'Invalid BeforeTraverse data: ')) def test_w_rule_valid(self): from ZPublisher.BeforeTraverse import registerBeforeTraverse container = DummyContainer() registerBeforeTraverse(container, DummyObject(name='foo'), 'AccessRule') self.assertEqual(self._callFUT(container), 'foo') class DummyRequest(dict): _virtual_root = None def setVirtualRoot(self, root): self._virtual_root = root class DummyObject(object): def __init__(self, **kw): self.__dict__.update(kw) class DummyContainer(object): def __init__(self, **kw): self.__dict__.update(kw) def this(self): return self def test_suite(): return unittest.TestSuite(( unittest.makeSuite(AccessRuleTests), unittest.makeSuite(Test_manage_addAccessRule), unittest.makeSuite(Test_getAccessRule), )) zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/tests/__init__.py0000644000175000017500000000003212214017422024317 0ustar arnauarnau# SiteAccess test package zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/tests/testVirtualHostMonster.py0000644000175000017500000001775412214017422027337 0ustar arnauarnau"""Virtual Host Monster regression tests. These tests mainly verify that OFS.Traversable.absolute_url() works correctly in a VHM environment. Also see http://zope.org/Collectors/Zope/809 Note: Tests require Zope >= 2.7 """ import unittest class VHMRegressions(unittest.TestCase): def setUp(self): import transaction from Testing.makerequest import makerequest from Testing.ZopeTestCase.ZopeLite import app transaction.begin() self.app = makerequest(app()) if 'virtual_hosting' not in self.app.objectIds(): # If ZopeLite was imported, we have no default virtual # host monster from Products.SiteAccess.VirtualHostMonster \ import manage_addVirtualHostMonster manage_addVirtualHostMonster(self.app, 'virtual_hosting') self.app.manage_addFolder('folder') self.app.folder.manage_addDTMLMethod('doc', '') self.app.REQUEST.set('PARENTS', [self.app]) self.traverse = self.app.REQUEST.traverse def tearDown(self): import transaction transaction.abort() self.app._p_jar.close() def testAbsoluteUrl(self): m = self.app.folder.doc.absolute_url self.assertEqual(m(), 'http://foo/folder/doc') def testAbsoluteUrlPath(self): m = self.app.folder.doc.absolute_url_path self.assertEqual(m(), '/folder/doc') def testVirtualUrlPath(self): m = self.app.folder.doc.absolute_url self.assertEqual(m(relative=1), 'folder/doc') m = self.app.folder.doc.virtual_url_path self.assertEqual(m(), 'folder/doc') def testPhysicalPath(self): m = self.app.folder.doc.getPhysicalPath self.assertEqual(m(), ('', 'folder', 'doc')) def test_actual_url_no_VHR_no_doc_w_trailing_slash(self): ob = self.traverse('/VirtualHostBase/http/www.mysite.com:80' '/folder/') self.assertEqual(self.app.REQUEST['ACTUAL_URL'], 'http://www.mysite.com/folder/') def test_actual_url_no_VHR_no_doc_no_trailing_slash(self): ob = self.traverse('/VirtualHostBase/http/www.mysite.com:80' '/folder') self.assertEqual(self.app.REQUEST['ACTUAL_URL'], 'http://www.mysite.com/folder') def test_actual_url_no_VHR_w_doc_w_trailing_slash(self): ob = self.traverse('/VirtualHostBase/http/www.mysite.com:80' '/folder/doc/') self.assertEqual(self.app.REQUEST['ACTUAL_URL'], 'http://www.mysite.com/folder/doc/') def test_actual_url_no_VHR_w_doc_no_trailing_slash(self): ob = self.traverse('/VirtualHostBase/http/www.mysite.com:80' '/folder/doc') self.assertEqual(self.app.REQUEST['ACTUAL_URL'], 'http://www.mysite.com/folder/doc') def test_actual_url_w_VHR_w_doc_w_trailing_slash(self): ob = self.traverse('/VirtualHostBase/http/www.mysite.com:80' '/folder/VirtualHostRoot/doc/') self.assertEqual(self.app.REQUEST['ACTUAL_URL'], 'http://www.mysite.com/doc/') def test_actual_url_w_VHR_w_doc_no_trailing_slash(self): ob = self.traverse('/VirtualHostBase/http/www.mysite.com:80' '/folder/VirtualHostRoot/doc') self.assertEqual(self.app.REQUEST['ACTUAL_URL'], 'http://www.mysite.com/doc') def test_actual_url_w_VHR_no_doc_w_trailing_slash(self): ob = self.traverse('/VirtualHostBase/http/www.mysite.com:80' '/folder/VirtualHostRoot/') self.assertEqual(self.app.REQUEST['ACTUAL_URL'], 'http://www.mysite.com/') def test_actual_url_w_VHR_no_doc_no_trailing_slash(self): ob = self.traverse('/VirtualHostBase/http/www.mysite.com:80' '/folder/VirtualHostRoot') self.assertEqual(self.app.REQUEST['ACTUAL_URL'], 'http://www.mysite.com/') def gen_cases(): for vbase, ubase in ( ('', 'http://foo'), ('/VirtualHostBase/http/example.com:80', 'http://example.com'), ): yield vbase, '', '', 'folder/doc', ubase for vr, _vh, p in ( ('folder', '', 'doc'), ('folder', 'foo', 'doc'), ('', 'foo', 'folder/doc'), ): vparts = [vbase, vr, 'VirtualHostRoot'] if not vr: del vparts[1] if _vh: vparts.append('_vh_' + _vh) yield '/'.join(vparts), vr, _vh, p, ubase for i, (vaddr, vr, _vh, p, ubase) in enumerate(gen_cases()): def test(self, vaddr=vaddr, vr=vr, _vh=_vh, p=p, ubase=ubase): ob = self.traverse('%s/%s/' % (vaddr, p)) sl_vh = (_vh and ('/' + _vh)) aup = sl_vh + (p and ('/' + p)) self.assertEqual(ob.absolute_url_path(), aup) self.assertEqual(self.app.REQUEST['BASEPATH1'] + '/' + p, aup) self.assertEqual(ob.absolute_url(), ubase + aup) self.assertEqual(ob.absolute_url(relative=1), p) self.assertEqual(ob.virtual_url_path(), p) self.assertEqual(ob.getPhysicalPath(), ('', 'folder', 'doc')) app = ob.aq_parent.aq_parent # The absolute URL doesn't end with a slash self.assertEqual(app.absolute_url(), ubase + sl_vh) # The absolute URL path always begins with a slash self.assertEqual(app.absolute_url_path(), '/' + _vh) self.assertEqual(app.absolute_url(relative=1), '') self.assertEqual(app.virtual_url_path(), '') setattr(VHMRegressions, 'testTraverse%s' % i, test) class VHMAddingTests(unittest.TestCase): def setUp(self): from OFS.Folder import Folder super(VHMAddingTests, self).setUp() self.root = Folder('root') def _makeOne(self): from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster return VirtualHostMonster() def test_add_with_existing_vhm(self): from Products.SiteAccess.VirtualHostMonster import \ manage_addVirtualHostMonster from zExceptions import BadRequest vhm1 = self._makeOne() vhm1.manage_addToContainer(self.root) vhm2 = self._makeOne() self.assertRaises(BadRequest, vhm2.manage_addToContainer, self.root) self.assertRaises( BadRequest , manage_addVirtualHostMonster , self.root ) def test_add_id_collision(self): from OFS.Folder import Folder from Products.SiteAccess.VirtualHostMonster import \ manage_addVirtualHostMonster from zExceptions import BadRequest self.root._setObject('VHM', Folder('VHM')) vhm1 = self._makeOne() self.assertRaises(BadRequest, vhm1.manage_addToContainer, self.root) self.assertRaises( BadRequest , manage_addVirtualHostMonster , self.root ) def test_add_addToContainer(self): from ZPublisher.BeforeTraverse import queryBeforeTraverse vhm1 = self._makeOne() vhm1.manage_addToContainer(self.root) self.assertTrue(vhm1.getId() in self.root.objectIds()) self.assertTrue(queryBeforeTraverse(self.root, vhm1.meta_type)) def test_add_manage_addVirtualHostMonster(self): from Products.SiteAccess.VirtualHostMonster import \ manage_addVirtualHostMonster from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster from ZPublisher.BeforeTraverse import queryBeforeTraverse manage_addVirtualHostMonster(self.root) self.assertTrue(VirtualHostMonster.id in self.root.objectIds()) hook = queryBeforeTraverse(self.root, VirtualHostMonster.meta_type) self.assertTrue(hook) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(VHMRegressions)) suite.addTest(unittest.makeSuite(VHMAddingTests)) return suite zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/tests/testSiteRoot.py0000644000175000017500000002763312214017422025250 0ustar arnauarnau"""SiteRoot regression tests. These tests verify that the request URL headers, in particular ACTUAL_URL, are set correctly when a SiteRoot is used. See http://www.zope.org/Collectors/Zope/2077 """ import unittest class TraverserTests(unittest.TestCase): def _getTargetClass(self): from Products.SiteAccess.SiteRoot import Traverser return Traverser def _makeOne(self): traverser = self._getTargetClass()() traverser.id = 'testing' return traverser def test_addToContainer(self): traverser = self._makeOne() container = DummyContainer() traverser.addToContainer(container) self.assertTrue(container.testing is traverser) hook = container.__before_traverse__[(100, 'Traverser')] self.assertEqual(hook.name, 'testing') def test_manage_addToContainer_no_nextUrl(self): traverser = self._makeOne() container = DummyContainer() result = traverser.manage_addToContainer(container) self.assertTrue(result is None) self.assertTrue(container.testing is traverser) hook = container.__before_traverse__[(100, 'Traverser')] self.assertEqual(hook.name, 'testing') def test_manage_addToContainer_w_nextUrl_w_name_collision(self): NEXTURL='http://example.com/manage_main' traverser = self._makeOne() container = DummyContainer() container.testing = object() result = traverser.manage_addToContainer(container, nextURL=NEXTURL) self.assertTrue('Item Exists' in result) self.assertFalse(container.testing is traverser) def test_manage_addToContainer_w_nextUrl_wo_name_collision(self): NEXTURL='http://example.com/manage_main' traverser = self._makeOne() container = DummyContainer() result = traverser.manage_addToContainer(container, nextURL=NEXTURL) self.assertTrue('Item Added' in result) self.assertTrue(container.testing is traverser) hook = container.__before_traverse__[(100, 'Traverser')] self.assertEqual(hook.name, 'testing') def test_manage_beforeDelete_item_is_not_self(self): from ZPublisher.BeforeTraverse import registerBeforeTraverse traverser = self._makeOne() container = DummyContainer() other = container.other = DummyObject(name='other') registerBeforeTraverse(container, other, 'Traverser', 100) item = object() traverser.manage_beforeDelete(item, container) hook = container.__before_traverse__[(100, 'Traverser')] self.assertEqual(hook.name, 'other') def test_manage_beforeDelete_item_is_self(self): from ZPublisher.BeforeTraverse import registerBeforeTraverse traverser = self._makeOne() container = DummyContainer() other = container.other = DummyObject(name='other') registerBeforeTraverse(container, other, 'Traverser', 100) traverser.manage_beforeDelete(traverser, container) self.assertFalse(container.__before_traverse__) def test_manage_afterAdd_item_not_self(self): traverser = self._makeOne() container = DummyContainer() item = object() traverser.manage_afterAdd(item, container) self.assertFalse('__before_traverse__' in container.__dict__) def test_manage_afterAdd_item_is_self(self): traverser = self._makeOne() container = DummyContainer() traverser.manage_afterAdd(traverser, container) hook = container.__before_traverse__[(100, 'Traverser')] self.assertEqual(hook.name, 'testing') def test__setId_same(self): traverser = self._makeOne() traverser._setId('testing') # doesn't raise def test__setId_different(self): traverser = self._makeOne() self.assertRaises(ValueError, traverser._setId, 'other') class SiteRootTests(unittest.TestCase): _old_SSR = None def setUp(self): from Testing.ZopeTestCase import ZopeLite ZopeLite.startup() def tearDown(self): if self._old_SSR is not None: self._set_SUPPRESS_SITEROOT(self._old_SSR) def _set_SUPPRESS_SITEROOT(self, value): from Products.SiteAccess import SiteRoot as SR (self._old_SSR, SR.SUPPRESS_SITEROOT) = (SR.SUPPRESS_SITEROOT, value) def _getTargetClass(self): from Products.SiteAccess.SiteRoot import SiteRoot return SiteRoot def _makeOne(self, title='TITLE', base='', path=''): return self._getTargetClass()(title, base, path) def test___init___strips_base_and_path(self): siteroot = self._makeOne(base=' ', path=' ') self.assertEqual(siteroot.title, 'TITLE') self.assertEqual(siteroot.base, '') self.assertEqual(siteroot.path, '') def test___init___w_base_and_path(self): siteroot = self._makeOne(base='http://example.com', path='/path') self.assertEqual(siteroot.title, 'TITLE') self.assertEqual(siteroot.base, 'http://example.com') self.assertEqual(siteroot.path, '/path') def test_manage_edit_no_REQUEST(self): siteroot = self._makeOne(title='Before', base='http://before.example.com', path='/before') result = siteroot.manage_edit('After', 'http://after.example.com ', '/after ') self.assertTrue(result is None) self.assertEqual(siteroot.title, 'After') self.assertEqual(siteroot.base, 'http://after.example.com') self.assertEqual(siteroot.path, '/after') def test_manage_edit_w_REQUEST(self): siteroot = self._makeOne(title='Before', base='http://before.example.com', path='/before') result = siteroot.manage_edit('After', 'http://after.example.com ', '/after ', REQUEST = {'URL1': 'http://localhost:8080/manage_main'}) self.assertTrue('SiteRoot changed.' in result) self.assertEqual(siteroot.title, 'After') self.assertEqual(siteroot.base, 'http://after.example.com') self.assertEqual(siteroot.path, '/after') def test___call___w_SUPPRESS_SITEROOT_set(self): self._set_SUPPRESS_SITEROOT(1) siteroot = self._makeOne(base='http://example.com', path='/path') request = {} siteroot(None, request) self.assertEqual(request, {}) def test___call___w_SUPPRESS_SITEROOT_in_URL(self): # This behavior changed in landing lp:142878. URL='http://localhost:8080/example/folder/' siteroot = self._makeOne(base='http://example.com', path='/example') request = DummyRequest(TraversalRequestNameStack= ['_SUPPRESS_SITEROOT'], URL=URL, ACTUAL_URL=URL, SERVER_URL='http://localhost:8080', ) request.steps = [] request.environ = {} siteroot(None, request) self.assertEqual(request['URL'], URL) self.assertEqual(request['SERVER_URL'], 'http://example.com') self.assertEqual(request['ACTUAL_URL'], 'http://example.com/example/folder/') self.assertEqual(request._virtual_root, '/example') self.assertTrue(request._urls_reset) def test___call___wo_SUPPRESS_SITEROOT_w_base_wo_path(self): URL='http://localhost:8080/example/folder/' siteroot = self._makeOne(base='http://example.com', path='') request = DummyRequest(TraversalRequestNameStack=[], URL=URL, ACTUAL_URL=URL, SERVER_URL='http://localhost:8080', ) request.steps = [] request.environ = {} siteroot(None, request) self.assertEqual(request['URL'], URL) self.assertEqual(request['SERVER_URL'], 'http://example.com') self.assertEqual(request['ACTUAL_URL'], 'http://example.com/example/folder/') self.assertEqual(request._virtual_root, None) self.assertTrue(request._urls_reset) def test___call___wo_SUPPRESS_SITEROOT_wo_base_w_path(self): URL='http://localhost:8080/example/folder/' siteroot = self._makeOne(base='', path='/example') request = DummyRequest(TraversalRequestNameStack=[], URL=URL, ACTUAL_URL=URL, SERVER_URL='http://localhost:8080', ) request.steps = [] request.environ = {} siteroot(None, request) self.assertEqual(request['URL'], URL) self.assertEqual(request['SERVER_URL'], 'http://localhost:8080') self.assertEqual(request['ACTUAL_URL'], URL) self.assertEqual(request._virtual_root, '/example') self.assertFalse(request._urls_reset) def test___call___wo_SUPPRESS_SITEROOT_w_base_w_path(self): URL='http://localhost:8080/example/folder/' siteroot = self._makeOne(base='http://example.com', path='/example') request = DummyRequest(TraversalRequestNameStack=[], URL=URL, ACTUAL_URL=URL, SERVER_URL='http://localhost:8080', ) request.steps = [] request.environ = {} siteroot(None, request) self.assertEqual(request['URL'], URL) self.assertEqual(request['SERVER_URL'], 'http://example.com') self.assertEqual(request['ACTUAL_URL'], 'http://example.com/example/folder/') self.assertEqual(request._virtual_root, '/example') self.assertTrue(request._urls_reset) def test_get_size(self): siteroot = self._makeOne() self.assertEqual(siteroot.get_size(), 0) class DummyObject(object): def __init__(self, **kw): self.__dict__.update(kw) class DummyContainer(object): def __init__(self, **kw): self.__dict__.update(kw) def _setObject(self, name, object): setattr(self, name, object) def this(self): return self class DummyRequest(dict): _virtual_root = None _urls_reset = False def setVirtualRoot(self, root): self._virtual_root = root def _resetURLS(self): self._urls_reset = True class SiteRootRegressions(unittest.TestCase): def setUp(self): import transaction from Testing.makerequest import makerequest from Testing.ZopeTestCase.ZopeLite import app transaction.begin() self.app = makerequest(app()) self.app.manage_addFolder('folder') p_disp = self.app.folder.manage_addProduct['SiteAccess'] p_disp.manage_addSiteRoot(title='SiteRoot', base='http://test_base', path='/test_path') self.app.REQUEST.set('PARENTS', [self.app]) self.app.REQUEST.traverse('/folder') def tearDown(self): import transaction transaction.abort() self.app._p_jar.close() def testRequest(self): self.assertEqual(self.app.REQUEST['SERVER_URL'], 'http://test_base') self.assertEqual(self.app.REQUEST['URL'], 'http://test_base/test_path/index_html') self.assertEqual(self.app.REQUEST['ACTUAL_URL'], 'http://test_base/test_path') def testAbsoluteUrl(self): self.assertEqual(self.app.folder.absolute_url(), 'http://test_base/test_path') def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TraverserTests), unittest.makeSuite(SiteRootTests), unittest.makeSuite(SiteRootRegressions), )) zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/COPYRIGHT.txt0000644000175000017500000000303112214017422023157 0ustar arnauarnauThis software is released under the following Wide-Open Source licence: Copyright (c) 1999 Zope Foundation and Contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of the Author nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/CHANGES.txt0000644000175000017500000000720112214017422022662 0ustar arnauarnau2000-10-03 Evan Simpson * Final release * Added VirtualHostMonster, to make common virtual hosting cases easy. 2000-07-20 Evan Simpson * Beta 3 * Fixed bug with SiteRoot deletion * Added ability to turn off SiteRoots and AccessRules on a case-by-case basis, by adding _SUPPRESS_SITEROOT or _SUPPRESS_ACCESSRULE to a URL just after the container name. * Added crude beginnings of Help 2000-06-19 Evan Simpson * Beta 2 * Removed a chunk of code left from 1.0 which messed up SiteRoots 2000-06-09 Evan Simpson * Version 2.0.0 * Changed to use the new virtual hosting support in Zope 2.2 * INCOMPATIBLE with Zope versions prior to 2.2b1 * The tarball no longer includes 'lib/python/Products' in file paths. It must be unpacked in the Products directory, for better compatibility with INSTANCE_HOME installations. 2000-03-21 Evan Simpson * Version 1.0.1 * Fix for FTP incompatibility * Match changes in Zope 2.1.5/6 2000-01-18 Evan Simpson * Version 1.0.0 * Decided that it's been long enough to call it stable * Eliminated stale _v_absolute_url attributes (thanks to Wade Simmons) * Killed peculiar and obscure interaction with ZCatalog 1999-12-15 Evan Simpson * Version 0.2.0 * Got absolute_url to do the right thing under both Zope 2.0 and 2.1. Note that this will change 2.0's behavior to match 2.1 1999-11-19 Evan Simpson * Added COPYRIGHT.txt, making Wide Open Source licence (BSD-style) explicit. (Mike Goldman provided the text, I provided the silly name). 1999-11-18 Evan Simpson * Version 0.1.4 * BASE tags generated for default pages should play nicely with setURL(base=...) * Setting the base but not the path works now. * Added ./doc/ directory with copies of documentation from Zope.org * SiteRoot* variables set in the environment are now seen. 1999-10-29 Evan Simpson * Version 0.1.3 * Using DTML Docs/Methods as Access Rules should work better, and allow normal acquisition. You won't have permissions for anything unless you give the rule a Proxy Role, though. * __no_before_traverse__ at the start of a URL path now persists in generated URLs, making debugging easier. 1999-10-27 Evan Simpson * Version 0.1.2 * Fixed absolute_url() of objects acquired from above the point at which setURL was called (usually by a SiteRoot). (thanks again to Bruce Perens and technocrat.net) * Added Base and Path properties to SiteRoot, with Path defaulting to '/', the most commonly used value. If these are blank, it will search for SiteRootBASE and SiteRootPATH. * REQUEST.setURL now accepts either a string or sequence of strings for its path argument. 1999-10-24 Evan Simpson * Version 0.1.1 * Made Access Rules work on unadorned root path (thanks to Bruce Perens) * Reorganized REQUEST.traverse to better handle changes to the path within the loop. * Experimental change: Overrides icon of methods designated as Access Rules to provide visual feedback. This is a fragile hack, but relatively harmless if it fails. (suggested by Evan Gibson) * Fixed Acquisition of SiteRoot* values (thanks to Oleg Broytmann) * Rationalized file permissions (thanks to Joshua Brauer) 1999-10-19 Evan Simpson * Version 0.1.0 * First (apparently) working version * Implemented REQUEST.setURL(base, path) and made SiteRoot use it. * Put a link to existing AccessRule on "Add AccessRule" page. zope2.13-2.13.21/source/Zope2/src/Products/SiteAccess/version.txt0000644000175000017500000000002112214017422023270 0ustar arnauarnauSiteAccess-2-0-0 zope2.13-2.13.21/source/Zope2/src/Products/ZReST/0000755000175000017500000000000012214017422017772 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/ZReST/TODO.txt0000644000175000017500000000031012214017422021272 0ustar arnauarnau. charset configuration, defaulting to 'latin-1' . add "level" and "header" args to render / as_html - level indicates the header level to start at - header boolean turns page header tags on/off zope2.13-2.13.21/source/Zope2/src/Products/ZReST/ZReST.py0000644000175000017500000002441512214017422021321 0ustar arnauarnau''' ReStructuredText Product for Zope This Product stores two texts - a "source" text in ReStructureText format, and a HTML "formatted" version of that text. ''' import docutils.core import docutils.io from docutils.writers.html4css1 import HTMLTranslator from docutils.writers.html4css1 import Writer import sys from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.SecurityInfo import ModuleSecurityInfo from Acquisition import Implicit from App.config import getConfiguration from App.special_dtml import DTMLFile from DateTime.DateTime import DateTime from OFS.SimpleItem import Item from OFS.PropertyManager import PropertyManager from OFS.History import Historical from OFS.History import html_diff from Persistence import Persistent modulesecurity = ModuleSecurityInfo() modulesecurity.declareProtected('View management screens', 'manage_addZReSTForm') manage_addZReSTForm = DTMLFile('dtml/manage_addZReSTForm', globals()) modulesecurity.declareProtected('Add RestructuredText Document', 'manage_addZReST') def manage_addZReST(self, id, file='', REQUEST=None): """Add a ZReST product """ # validate the instance_home self._setObject(id, ZReST(id)) self._getOb(id).manage_upload(file) if REQUEST is not None: return self.manage_main(self, REQUEST) class Warnings: def __init__(self): self.messages = [] def write(self, message): self.messages.append(message) class ZReST(Item, PropertyManager, Historical, Implicit, Persistent): '''An instance of this class provides an interface between Zope and ReStructuredText for one text. ''' meta_type = 'ReStructuredText Document' security = ClassSecurityInfo() _v_formatted = _v_warnings = None def __init__(self, id,output_encoding=None, input_encoding=None): self.id = id self.title = id self.stylesheet = '' self.report_level = '2' self.source = '' from reStructuredText import default_output_encoding, \ default_input_encoding self.input_encoding = input_encoding or \ default_input_encoding self.output_encoding = output_encoding or \ default_output_encoding # define the properties that define this object _properties = ( {'id':'stylesheet', 'type': 'string', 'mode': 'w', 'default': ''}, {'id':'report_level', 'type': 'string', 'mode': 'w', 'default': '2'}, {'id':'input_encoding', 'type': 'string', 'mode': 'w', 'default': 'iso-8859-15'}, {'id':'output_encoding', 'type': 'string', 'mode': 'w', 'default': 'iso-8859-15'}, ) property_extensible_schema__ = 0 # define the tabs for the management interface manage_options= ( {'label': 'Edit', 'action':'manage_main'}, {'label': 'View', 'action':'index_html'}, {'label': 'Source', 'action':'source_txt'}, ) \ + PropertyManager.manage_options \ + Historical.manage_options \ + Item.manage_options # access to the source text and formatted text security.declareProtected('View', 'index_html') def index_html(self, REQUEST=None): ''' Getting the formatted text ''' if REQUEST is not None: REQUEST.RESPONSE.setHeader('content-type', 'text/html; charset=%s' % self.output_encoding) return self.render() security.declareProtected('View', 'source_txt') def source_txt(self, REQUEST=None): ''' Getting the source text ''' if REQUEST is not None: REQUEST.RESPONSE.setHeader('content-type', 'text/plain; charset=%s' % self.input_encoding) return self.source # edit form, which is also the primary interface security.declareProtected('Edit ReStructuredText', 'manage_main') manage_main = DTMLFile('dtml/manage_editForm', globals()) # edit action security.declareProtected('Edit ReStructuredText', 'manage_edit') def manage_edit(self, data, SUBMIT='Change',dtpref_cols='50', dtpref_rows='20', REQUEST=None): '''Alias index_html to roundup's index ''' if self._size_changes.has_key(SUBMIT): return self._er(data, SUBMIT, dtpref_cols, dtpref_rows, REQUEST) if data != self.source: self.source = data self._clear_cache() if REQUEST is not None: message="Saved changes." return self.manage_main(self, REQUEST, manage_tabs_message=message) # handle edit window size changes _size_changes = { 'Bigger': (5,5), 'Smaller': (-5,-5), 'Narrower': (0,-5), 'Wider': (0,5), 'Taller': (5,0), 'Shorter': (-5,0), } def _er(self, data, SUBMIT, dtpref_cols, dtpref_rows, REQUEST): dr,dc = self._size_changes[SUBMIT] rows = str(max(1, int(dtpref_rows) + dr)) cols = str(dtpref_cols) if cols.endswith('%'): cols = str(min(100, max(25, int(cols[:-1]) + dc))) + '%' else: cols = str(max(35, int(cols) + dc)) e = (DateTime("GMT") + 365).rfc822() setCookie = REQUEST["RESPONSE"].setCookie setCookie("dtpref_rows", rows, path='/', expires=e) setCookie("dtpref_cols", cols, path='/', expires=e) REQUEST.other.update({"dtpref_cols":cols, "dtpref_rows":rows}) return self.manage_main(self, REQUEST, __str__=self.quotedHTML(data)) security.declarePrivate('quotedHTML') def quotedHTML(self, text=None, character_entities=( (('&'), '&'), (("<"), '<' ), ((">"), '>' ), (('"'), '"'))): #" if text is None: text=self.read_raw() for re,name in character_entities: if text.find(re) >= 0: text=name.join(text.split(re)) return text security.declarePrivate('_clear_cache') def _clear_cache(self): """ Forget results of rendering. """ try: del self._v_formatted except AttributeError: pass try: del self._v_warnings except AttributeError: pass # handle uploads too security.declareProtected('Edit ReStructuredText', 'manage_upload') def manage_upload(self, file='', REQUEST=None): ''' Replaces the current source with the upload. ''' if isinstance(file, type('')): self.source = file else: self.source = file.read() self._clear_cache() if REQUEST is not None: message="Saved changes." return self.manage_main(self, REQUEST, manage_tabs_message=message) security.declarePrivate('render') def render(self): ''' Render the source to HTML ''' if self._v_formatted is None: warnings = self._v_warnings = Warnings() settings = { 'halt_level': 6, 'report_level' : int(self.report_level), 'input_encoding': self.input_encoding, 'output_encoding': self.output_encoding, 'initial_header_level' : 1, 'stylesheet' : self.stylesheet, 'stylesheet_path' : None, 'warning_stream' : warnings, 'raw_enabled' : 0, 'file_insertion_enabled' : 0, } self._v_formatted = docutils.core.publish_string( self.source, writer=Writer(), settings_overrides=settings, ) return self._v_formatted security.declareProtected('Edit ReStructuredText', 'PUT', 'manage_FTPput') def PUT(self, REQUEST, RESPONSE): ''' Handle HTTP PUT requests ''' data = REQUEST.get('BODY', '') if data != self.source: if data.startswith('.. '): data = data.splitlines() new = [] for i in range(len(data)): line = data[i] if not line.startswith('.. '): break if line.startswith('.. stylesheet='): self.stylesheet = line.split('=')[1] elif line.startswith('.. report_level='): self.report_level = line.split('=')[1] else: pass # ignore data = '\n'.join(new) + '\n'.join(data[i:]) self.source = data RESPONSE.setStatus(204) return RESPONSE manage_FTPput = PUT def manage_FTPget(self): ''' Get source for FTP download ''' self.REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain') s = [ '.. This is a ReStructuredText Document. Initial comment lines ' '(".. ") will be stripped.', '.. stylesheet='+self.stylesheet, '.. report_level='+self.report_level ] if self._v_warnings: s.append('.. ') s.append('.. ' + '\n.. '.join(self._v_warnings.splitlines())) s.append('.. ') return '\n'.join(s) + '\n' + self.source def __str__(self): ''' Stringfy .. return the source ''' return self.quotedHTML(self.source) __call__ = __str__ def PrincipiaSearchSource(self): ''' Support for searching - the document's contents are searched. ''' return self.source def manage_historyCompare(self, rev1, rev2, REQUEST, historyComparisonResults=''): return ZReST.inheritedAttribute('manage_historyCompare')( self, rev1, rev2, REQUEST, historyComparisonResults=html_diff(rev1.source, rev2.source)) def manage_editProperties(self, REQUEST): """ re-render the page after changing the properties (encodings!!!) """ result = PropertyManager.manage_editProperties(self, REQUEST) self._clear_cache() return result InitializeClass(ZReST) modulesecurity.apply(globals()) # vim: set filetype=python ts=4 sw=4 et si zope2.13-2.13.21/source/Zope2/src/Products/ZReST/www/0000755000175000017500000000000012214017422020616 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/ZReST/www/zrest.gif0000644000175000017500000000032112214017422022450 0ustar arnauarnauGIF89aòQQQ2ýeýªªªÀÀÀÿÿÿ!ù,–xwww‡ffhfawwhDD„fqxwa„DDhfq‡ffhf€wh&‚"bxwa†fffhVp‡fh33†whff†ffxwa†aVp‡ffhff†whf†axwa†fffhFp‡a(""†wXUE…UUxw€;zope2.13-2.13.21/source/Zope2/src/Products/ZReST/__init__.py0000644000175000017500000000044512214017422022106 0ustar arnauarnau# product initialisation import ZReST def initialize(context): context.registerClass( ZReST.ZReST, meta_type = 'ReStructuredText Document', icon='www/zrest.gif', constructors = ( ZReST.manage_addZReSTForm, ZReST.manage_addZReST ) ) zope2.13-2.13.21/source/Zope2/src/Products/ZReST/README.txt0000644000175000017500000000164312214017422021474 0ustar arnauarnau---------------------------------- ReStructuredText Document for Zope ---------------------------------- Usage ===== See the docutils user documentation section: http://docutils.sourceforge.net/#user-documentation I recommend starting with the ReST "primer": http://docutils.sourceforge.net/docs/rst/quickstart.html Character set encoding issues ============================= ZReST instances have a property "input_encoding" to specify the encoding of your reST document. "output_encoding" is used the encoding of the output. You can specify any valid encoding that is known to Python (e.g. "iso-8859-1" or "utf-8"). Footnotes ========= This product was written by Richard Jones, rjones@ekit-inc.com. Minor extensions for the Zope 2.7 integration by Andreas Jung, andreas@andreas-jung.com. Please direct questions about ReStructureText to the docutils mailing lists. This package is placed in the Public Domain. zope2.13-2.13.21/source/Zope2/src/Products/ZReST/tests/0000755000175000017500000000000012214017422021134 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/ZReST/tests/test_ZReST.py0000644000175000017500000001101012214017422023505 0ustar arnauarnau# -*- coding: iso-8859-15 -*- """ Unit tests for ZReST objects """ import unittest import cgi import tempfile txt = """Hello World ============ text text Von Vögeln und Öfen =================== - some - more - text """ csv_text = """bin:x:1:1:bin:/bin:/bin/bash daemon:x:2:2:Daemon:/sbin:/bin/bash """ docutils_include_warning = '(WARNING/2) "include" directive disabled.' docutils_raw_warning = '(WARNING/2) "raw" directive disabled.' class TestZReST(unittest.TestCase): def _getTargetClass(self): from Products.ZReST.ZReST import ZReST return ZReST def _makeOne(self, id='test', *args, **kw): return self._getTargetClass()(id=id, *args, **kw) def _csvfile(self): fn = tempfile.mktemp() open(fn, 'w').write(csv_text) return fn def test_empty(self): empty = self._makeOne() # New instances should not have non-volatile cache attributes self.assertRaises(AttributeError, lambda: empty.formatted) self.assertRaises(AttributeError, lambda: empty.warnings) self.assertEqual(empty._v_formatted, None) self.assertEqual(empty._v_warnings, None) def test_formatted_ignored(self): resty = self._makeOne() resty.formatted = 'IGNORE ME' self.assertFalse('IGNORE ME' in resty.index_html()) def testConversion(self): resty = self._makeOne() resty.source = txt resty.input_encoding = 'iso-8859-15' resty.output_encoding = 'iso-8859-15' resty.render() html = resty.index_html() s = '

    Hello World

    ' self.assertEqual(s in html, True) s = '

    Von Vögeln und Öfen

    ' self.assertEqual(s in html, True) # ZReST should render a complete HTML document self.assertEqual('' in html, True) def test_include_directive_raises(self): resty = self._makeOne() resty.source = 'hello world\n .. include:: /etc/passwd' result = resty.render() warnings = ''.join(resty._v_warnings.messages) # The include: directive hasn't been rendered, it remains # verbatimly in the rendered output. Instead a warning # message is presented: self.assert_(docutils_include_warning in warnings) def test_raw_directive_disabled(self): resty = self._makeOne() EXPECTED = '

    HELLO WORLD

    ' resty.source = '.. raw:: html\n\n %s\n' % EXPECTED result = resty.render() warnings = ''.join(resty._v_warnings.messages) # The raw: directive hasn't been rendered, it remains # verbatimly in the rendered output. Instead a warning # message is presented: self.assert_(EXPECTED not in result) self.assert_(cgi.escape(EXPECTED) in result) self.assert_(docutils_raw_warning in warnings) def test_raw_directive_file_directive_raises(self): resty = self._makeOne() resty.source = '.. raw:: html\n :file: inclusion.txt' result = resty.render() warnings = ''.join(resty._v_warnings.messages) # The raw: directive hasn't been rendered, it remains # verbatimly in the rendered output. Instead a warning # message is presented: self.assert_(docutils_raw_warning in warnings) def test_raw_directive_url_directive_raises(self): resty = self._makeOne() resty.source = '.. raw:: html\n :url: http://www.zope.org/' result = resty.render() warnings = ''.join(resty._v_warnings.messages) # The raw: directive hasn't been rendered, it remains # verbatimly in the rendered output. Instead a warning # message is presented: self.assert_(docutils_raw_warning in warnings) def test_csv_table_file_option_raise(self): resty = self._makeOne() csv_file = self._csvfile() resty.source = '.. csv-table:: \n :file: %s' % csv_file result = resty.render() self.assertTrue('daemon' not in result, 'csv-table/file directive is not disabled!') def test_csv_table_url_option_raise(self): resty = self._makeOne() csv_file = self._csvfile() resty.source = '.. csv-table:: \n :url: file://%s' % csv_file result = resty.render() self.assertTrue('daemon' not in result, 'csv-table/url directive is not disabled!') def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestZReST)) return suite zope2.13-2.13.21/source/Zope2/src/Products/ZReST/tests/__init__.py0000644000175000017500000000004612214017422023245 0ustar arnauarnau""" Unit tests for ZReST product. """ zope2.13-2.13.21/source/Zope2/src/Products/ZReST/dtml/0000755000175000017500000000000012214017422020732 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/ZReST/dtml/manage_editForm.dtml0000644000175000017500000000477612214017422024713 0ustar arnauarnau

    You may edit the source for this document using the form below. You may also upload the source for this document from a local file. Click the browse button to select a local file to upload.

    &dtml-warnings;
    

    Help: ReStructuredText primer for beginners, quick reference for the more advanced and home page for all the details.

    Locked by WebDAV   
    File  
    Locked by WebDAV
    zope2.13-2.13.21/source/Zope2/src/Products/ZReST/dtml/manage_addZReSTForm.dtml0000644000175000017500000000205712214017422025374 0ustar arnauarnau

    You may optionally select a file to upload from your local computer by clicking the Browse button.

    Id
    File
    zope2.13-2.13.21/source/Zope2/src/Products/ZReST/version.txt0000644000175000017500000000000412214017422022212 0ustar arnauarnau1.1 zope2.13-2.13.21/source/Zope2/src/Products/Transience/0000755000175000017500000000000012214017422021116 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Transience/Transience.py0000644000175000017500000012450212214017422023567 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Transient Object Container Class ('timeslice'-based design, no index). """ from cgi import escape from logging import getLogger import math import os import random import sys import thread import time from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.SecurityManagement import getSecurityManager from AccessControl.SecurityManagement import newSecurityManager from AccessControl.SecurityManagement import setSecurityManager from AccessControl.SpecialUsers import nobody from App.special_dtml import HTMLFile from BTrees.Length import Length as BTreesLength from BTrees.OOBTree import OOBTree from BTrees.IOBTree import IOBTree from OFS.SimpleItem import SimpleItem from Persistence import Persistent from zope.interface import implements from Products.Transience.TransienceInterfaces import DictionaryLike from Products.Transience.TransienceInterfaces \ import ImmutablyValuedMappingOfPickleableObjects from Products.Transience.TransienceInterfaces import ItemWithId from Products.Transience.TransienceInterfaces \ import StringKeyedHomogeneousItemContainer from Products.Transience.TransienceInterfaces import Transient from Products.Transience.TransienceInterfaces import TransientItemContainer from Products.Transience.TransienceInterfaces import TTWDictionary from Products.Transience.TransientObject import TransientObject from Products.Transience.Fake import FakeIOBTree ADD_CONTAINER_PERM = 'Add Transient Object Container' MGMT_SCREEN_PERM = 'View management screens' ACCESS_CONTENTS_PERM = 'Access contents information' CREATE_TRANSIENTS_PERM = 'Create Transient Objects' ACCESS_TRANSIENTS_PERM = 'Access Transient Objects' MANAGE_CONTAINER_PERM = 'Manage Transient Object Container' SPARE_BUCKETS = 15 # minimum number of buckets to keep "spare" BUCKET_CLASS = OOBTree # constructor for buckets DATA_CLASS = IOBTree # const for main data structure (timeslice->"bucket") STRICT = os.environ.get('Z_TOC_STRICT', '') DEBUG = int(os.environ.get('Z_TOC_DEBUG', 0)) _marker = [] LOG = getLogger('Transience') def setStrict(on=''): """ Turn on assertions (which may cause conflicts) """ global STRICT STRICT = on def TLOG(*args): sargs = [] sargs.append(str(thread.get_ident())) sargs.append(str(time.time())) for arg in args: sargs.append(str(arg)) msg = ' '.join(sargs) LOG.info(msg) constructTransientObjectContainerForm = HTMLFile( 'dtml/addTransientObjectContainer', globals()) def constructTransientObjectContainer(self, id, title='', timeout_mins=20, addNotification=None, delNotification=None, limit=0, period_secs=20, REQUEST=None): """ """ ob = TransientObjectContainer(id, title, timeout_mins, addNotification, delNotification, limit=limit, period_secs=period_secs) self._setObject(id, ob) if REQUEST is not None: return self.manage_main(self, REQUEST, update_menu=1) class MaxTransientObjectsExceeded(Exception): pass class TransientObjectContainer(SimpleItem): """ Object which contains items that are automatically flushed after a period of inactivity """ meta_type = "Transient Object Container" icon = "misc_/Transience/datacontainer.gif" implements(ItemWithId, StringKeyedHomogeneousItemContainer, TransientItemContainer, ) manage_options = ( { 'label': 'Manage', 'action': 'manage_container', 'help': ('Transience', 'Transience.stx') }, { 'label': 'Security', 'action': 'manage_access' }, ) security = ClassSecurityInfo() security.setPermissionDefault(MANAGE_CONTAINER_PERM, ['Manager',]) security.setPermissionDefault(MGMT_SCREEN_PERM, ['Manager',]) security.setPermissionDefault(ACCESS_CONTENTS_PERM, ['Manager','Anonymous']) security.setPermissionDefault(ACCESS_TRANSIENTS_PERM, ['Manager','Anonymous','Sessions']) security.setPermissionDefault(CREATE_TRANSIENTS_PERM, ['Manager',]) security.declareProtected(MGMT_SCREEN_PERM, 'manage_container') manage_container = HTMLFile('dtml/manageTransientObjectContainer', globals()) _limit = 0 _data = None _inband_housekeeping = True security.setDefaultAccess('deny') # intitialize locks used for finalization, replentishing, and # garbage collection (used in _finalize, _replentish, and _gc # respectively) finalize_lock = thread.allocate_lock() replentish_lock = thread.allocate_lock() gc_lock = thread.allocate_lock() def __init__(self, id, title='', timeout_mins=20, addNotification=None, delNotification=None, limit=0, period_secs=20): self.id = id self.title=title self._setTimeout(timeout_mins, period_secs) self._setLimit(limit) self.setDelNotificationTarget(delNotification) self.setAddNotificationTarget(addNotification) self._reset() # helpers def _setTimeout(self, timeout_mins, period_secs): if type(timeout_mins) is not type(1): raise TypeError, (escape(`timeout_mins`), "Must be integer") if type(period_secs) is not type(1): raise TypeError, (escape(`period_secs`), "Must be integer") timeout_secs = timeout_mins * 60 # special-case 0-minute timeout value by ignoring period if timeout_secs != 0: if period_secs == 0: raise ValueError('resolution cannot be 0') if period_secs > timeout_secs: raise ValueError( 'resolution cannot be greater than timeout ' 'minutes * 60 ( %s > %s )' % (period_secs, timeout_secs)) # we need the timeout to be evenly divisible by the period if timeout_secs % period_secs != 0: raise ValueError( 'timeout seconds (%s) must be evenly divisible ' 'by resolution (%s)' % (timeout_secs, period_secs) ) # our timeout secs is the number of seconds that an item should # remain unexpired self._timeout_secs = timeout_secs # our _period is the number of seconds that constitutes a timeslice self._period = period_secs # timeout_slices == fewest number of timeslices that's >= timeout_secs self._timeout_slices=int(math.ceil(float(timeout_secs)/period_secs)) def _setLimit(self, limit): if type(limit) is not type(1): raise TypeError, (escape(`limit`), "Must be integer") self._limit = limit def _reset(self): """ Reset ourselves to a sane state (deletes all content) """ # _data contains a mapping of f-of-time(int) (aka "slice") to # "bucket". Each bucket will contain a set of transient items. # Transient items move automatically from bucket-to-bucket inside # of the _data structure based on last access time (e.g. # "get" calls), escaping expiration and eventual destruction only if # they move quickly enough. # # We make enough buckets initially to last us a while, and # we subsequently extend _data with fresh buckets and remove old # buckets as necessary during normal operations (see # _replentish() and _gc()). self._data = DATA_CLASS() # populate _data with some number of buckets, each of which # is "current" for its timeslice key if self._timeout_slices: new_slices = getTimeslices( getCurrentTimeslice(self._period), SPARE_BUCKETS*2, self._period) for i in new_slices: self._data[i] = BUCKET_CLASS() # max_timeslice is at any time during operations the highest # key value in _data. Its existence is an optimization; getting # the maxKey of a BTree directly is read-conflict-prone. self._max_timeslice = Increaser(max(new_slices)) else: self._data[0] = BUCKET_CLASS() # sentinel value for non-expiring self._max_timeslice = Increaser(0) # '_last_finalized_timeslice' is a value that indicates which # timeslice had its items last run through the finalization # process. The finalization process calls the delete notifier for # each expired item. self._last_finalized_timeslice = Increaser(-self._period) # '_last_gc_timeslice' is a value that indicates in which # timeslice the garbage collection process was last run. self._last_gc_timeslice = Increaser(-self._period) # our "_length" is the number of "active" data objects in _data. # it does not include items that are still kept in _data but need to # be garbage collected. # # we need to maintain the length of the index structure separately # because getting the length of a BTree is very expensive, and it # doesn't really tell us which ones are "active" anyway. try: self._length.set(0) except AttributeError: self._length = self.getLen = Length2() def _getCurrentSlices(self, now): if self._timeout_slices: begin = now - (self._period * self._timeout_slices) # add add one to _timeout_slices below to account for the fact that # a call to this method may happen any time within the current # timeslice; calling it in the beginning of the timeslice can lead # to sessions becoming invalid a maximum of self._period seconds # earlier than the requested timeout value. Adding one here can # lead to sessions becoming invalid *later* than the timeout value # (also by a max of self._period), but in the common sessioning # case, that seems preferable. num_slices = self._timeout_slices + 1 else: return [0] # sentinel for timeout value 0 (don't expire) DEBUG and TLOG('_getCurrentSlices, now = %s ' % now) DEBUG and TLOG('_getCurrentSlices, begin = %s' % begin) DEBUG and TLOG('_getCurrentSlices, num_slices = %s' % num_slices) result = getTimeslices(begin, num_slices, self._period) DEBUG and TLOG('_getCurrentSlices, result = %s' % result) return result def _move_item(self, k, current_ts, default=None): if not self._timeout_slices: # special case for no timeout value bucket = self._data.get(0) return bucket.get(k, default) if self._inband_housekeeping: self._housekeep(current_ts) else: # dont allow the TOC to stop working in an emergency bucket # shortage if self._in_emergency_bucket_shortage(current_ts): self._replentish(current_ts) # SUBTLETY ALERTY TO SELF: do not "improve" the code below # unnecessarily, as it will end only in tears. The lack of aliases # and the ordering is intentional. STRICT and _assert(self._data.has_key(current_ts)) current_slices = self._getCurrentSlices(current_ts) found_ts = None for ts in current_slices: abucket = self._data.get(ts, None) # XXX ReadConflictError hotspot if abucket is None: DEBUG and TLOG('_move_item: no bucket for ts %s' % ts) continue DEBUG and TLOG( '_move_item: bucket for ts %s is %s' % (ts, id(abucket))) DEBUG and TLOG( '_move_item: keys for ts %s (bucket %s)-- %s' % (ts, id(abucket), str(list(abucket.keys()))) ) # uhghost? if abucket.get(k, None) is not None: found_ts = ts break DEBUG and TLOG('_move_item: found_ts is %s' % found_ts) if found_ts is None: DEBUG and TLOG('_move_item: returning default of %s' % default) return default if found_ts != current_ts: DEBUG and TLOG('_move_item: current_ts (%s) != found_ts (%s), ' 'moving to current' % (current_ts, found_ts)) DEBUG and TLOG( '_move_item: keys for found_ts %s (bucket %s): %s' % ( found_ts, id(self._data[found_ts]), `list(self._data[found_ts].keys())`) ) self._data[current_ts][k] = self._data[found_ts][k] if not issubclass(BUCKET_CLASS, Persistent): # tickle persistence machinery self._data[current_ts] = self._data[current_ts] DEBUG and TLOG( '_move_item: copied item %s from %s to %s (bucket %s)' % ( k, found_ts, current_ts, id(self._data[current_ts]))) del self._data[found_ts][k] if not issubclass(BUCKET_CLASS, Persistent): # tickle persistence machinery self._data[found_ts] = self._data[found_ts] DEBUG and TLOG( '_move_item: deleted item %s from ts %s (bucket %s)' % ( k, found_ts, id(self._data[found_ts])) ) STRICT and _assert(self._data[found_ts].get(k, None) is None) STRICT and _assert(not self._data[found_ts].has_key(k)) if getattr(self._data[current_ts][k], 'setLastAccessed', None): self._data[current_ts][k].setLastAccessed() DEBUG and TLOG('_move_item: returning %s from current_ts %s ' % (k, current_ts)) return self._data[current_ts][k] def _all(self): if self._timeout_slices: current_ts = getCurrentTimeslice(self._period) else: current_ts = 0 if self._inband_housekeeping: self._housekeep(current_ts) elif self._in_emergency_bucket_shortage(current_ts): # if our scheduler fails, dont allow the TOC to stop working self._replentish(current_ts, force=True) STRICT and _assert(self._data.has_key(current_ts)) current = self._getCurrentSlices(current_ts) current.reverse() # overwrite older with newer d = {} for ts in current: bucket = self._data.get(ts) if bucket is None: continue for k,v in bucket.items(): d[k] = self._wrap(v) return d def keys(self): return self._all().keys() def raw(self, current_ts): # for debugging and unit testing current = self._getCurrentSlices(current_ts) current.reverse() # overwrite older with newer d = {} for ts in current: bucket = self._data.get(ts, None) if bucket is None: continue for k,v in bucket.items(): d[k] = self._wrap(v) return d def items(self): return self._all().items() def values(self): return self._all().values() def _wrap(self, item): # dont use hasattr here (it hides conflict errors) if getattr(item, '__of__', None): item = item.__of__(self) return item def __getitem__(self, k): if self._timeout_slices: current_ts = getCurrentTimeslice(self._period) else: current_ts = 0 item = self._move_item(k, current_ts, _marker) STRICT and _assert(self._data.has_key(current_ts)) if item is _marker: raise KeyError, k return self._wrap(item) def __setitem__(self, k, v): DEBUG and TLOG('__setitem__: called with key %s, value %s' % (k,v)) if self._timeout_slices: current_ts = getCurrentTimeslice(self._period) else: current_ts = 0 item = self._move_item(k, current_ts, _marker) STRICT and _assert(self._data.has_key(current_ts)) if item is _marker: # the key didnt already exist, this is a new item length = self._length() # XXX ReadConflictError hotspot if self._limit and length >= self._limit: LOG.warn('Transient object container %s max subobjects ' 'reached' % self.getId()) raise MaxTransientObjectsExceeded, ( "%s exceeds maximum number of subobjects %s" % (length, self._limit)) self._length.increment(1) DEBUG and TLOG('__setitem__: placing value for key %s in bucket %s' % (k, current_ts)) current_bucket = self._data[current_ts] current_bucket[k] = v if not issubclass(BUCKET_CLASS, Persistent): # tickle persistence machinery self._data[current_ts] = current_bucket self.notifyAdd(v) # change the TO's last accessed time # dont use hasattr here (it hides conflict errors) if getattr(v, 'setLastAccessed', None): v.setLastAccessed() def __delitem__(self, k): DEBUG and TLOG('__delitem__ called with key %s' % k) if self._timeout_slices: current_ts = getCurrentTimeslice(self._period) else: current_ts = 0 item = self._move_item(k, current_ts) STRICT and _assert(self._data.has_key(current_ts)) bucket = self._data[current_ts] del bucket[k] if not issubclass(BUCKET_CLASS, Persistent): # tickle persistence machinery self._data[current_ts] = bucket # XXX does increment(-1) make any sense here? # rationale from dunny: we are removing an item rather than simply # declaring it to be unused? self._length.increment(-1) return current_ts, item def __len__(self): return self._length() security.declareProtected(ACCESS_TRANSIENTS_PERM, 'get') def get(self, k, default=None): DEBUG and TLOG('get: called with key %s, default %s' % (k, default)) if self._timeout_slices: current_ts = getCurrentTimeslice(self._period) else: current_ts = 0 item = self._move_item(k, current_ts, default) STRICT and _assert(self._data.has_key(current_ts)) if item is default: DEBUG and TLOG('get: returning default') return default return self._wrap(item) security.declareProtected(ACCESS_TRANSIENTS_PERM, 'has_key') def has_key(self, k): if self._timeout_slices: current_ts = getCurrentTimeslice(self._period) else: current_ts = 0 DEBUG and TLOG('has_key: calling _move_item with %s' % str(k)) item = self._move_item(k, current_ts, _marker) DEBUG and TLOG('has_key: _move_item returned %s%s' % (item, item is _marker and ' (marker)' or '')) STRICT and _assert(self._data.has_key(current_ts)) if item is not _marker: return True DEBUG and TLOG('has_key: returning false from for %s' % k) return False def _get_max_expired_ts(self, now): return now - (self._period * (self._timeout_slices + 1)) def _in_emergency_bucket_shortage(self, now): max_ts = self._max_timeslice() low = now/self._period high = max_ts/self._period required = high <= low return required def _finalize(self, now): """ Call finalization handlers for the data in each stale bucket """ if not self._timeout_slices: DEBUG and TLOG('_finalize: doing nothing (no timeout)') return # don't do any finalization if there is no timeout # The nature of sessioning is that when the timeslice rolls # over, all active threads will try to do a lot of work during # finalization if inband housekeeping is enabled, all but one # unnecessarily. We really don't want more than one thread at # a time to try to finalize buckets at the same time so we try # to lock. We give up if we can't lock immediately because it # doesn't matter if we skip a couple of opportunities for # finalization, as long as it gets done by some thread # eventually. A similar pattern exists for _gc and # _replentish. if not self.finalize_lock.acquire(0): DEBUG and TLOG('_finalize: could not acquire lock, returning') return try: DEBUG and TLOG('_finalize: lock acquired successfully') last_finalized = self._last_finalized_timeslice() # we want to start finalizing from one timeslice after the # timeslice which we last finalized. start_finalize = last_finalized + self._period # we want to finalize only up to the maximum expired timeslice max_ts = self._get_max_expired_ts(now) if start_finalize >= max_ts: DEBUG and TLOG( '_finalize: start_finalize (%s) >= max_ts (%s), ' 'doing nothing' % (start_finalize, max_ts)) return else: DEBUG and TLOG( '_finalize: start_finalize (%s) <= max_ts (%s), ' 'finalization possible' % (start_finalize, max_ts)) # we don't try to avoid conflicts here by doing a "random" # dance (ala _replentish and _gc) because it's important that # buckets are finalized as soon as possible after they've # expired in order to call the delete notifier "on time". self._do_finalize_work(now, max_ts, start_finalize) finally: self.finalize_lock.release() def _do_finalize_work(self, now, max_ts, start_finalize): # this is only separated from _finalize for readability; it # should generally not be called by anything but _finalize DEBUG and TLOG('_do_finalize_work: entering') DEBUG and TLOG('_do_finalize_work: now is %s' % now) DEBUG and TLOG('_do_finalize_work: max_ts is %s' % max_ts) DEBUG and TLOG('_do_finalize_work: start_finalize is %s' % start_finalize) to_finalize = list(self._data.keys(start_finalize, max_ts)) DEBUG and TLOG('_do_finalize_work: to_finalize is %s' % `to_finalize`) delta = 0 for key in to_finalize: _assert(start_finalize <= key) _assert(key <= max_ts) STRICT and _assert(self._data.has_key(key)) values = list(self._data[key].values()) DEBUG and TLOG('_do_finalize_work: values to notify from ts %s ' 'are %s' % (key, `list(values)`)) delta += len(values) for v in values: self.notifyDel(v) if delta: self._length.decrement(delta) DEBUG and TLOG('_do_finalize_work: setting _last_finalized_timeslice ' 'to max_ts of %s' % max_ts) self._last_finalized_timeslice.set(max_ts) def _invoke_finalize_and_gc(self): # for unit testing purposes only! last_finalized = self._last_finalized_timeslice() now = getCurrentTimeslice(self._period) # for unit tests start_finalize = last_finalized + self._period max_ts = self._get_max_expired_ts(now) self._do_finalize_work(now, max_ts, start_finalize) self._do_gc_work(now) def _replentish(self, now): """ Add 'fresh' future or current buckets """ if not self._timeout_slices: DEBUG and TLOG('_replentish: no timeout, doing nothing') return # the difference between high and low naturally diminishes to # zero as now approaches self._max_timeslice() during normal # operations. If high <= low, it means we have no current bucket, # so we *really* need to replentish (having a current bucket is # an invariant for continued operation). required = self._in_emergency_bucket_shortage(now) lock_acquired = self.replentish_lock.acquire(0) try: if required: # we're in an emergency bucket shortage, we need to # replentish regardless of whether we got the lock or # not. (if we didn't get the lock, this transaction # will likely result in a conflict error, that's ok) if lock_acquired: DEBUG and TLOG('_replentish: required, lock acquired)') else: DEBUG and TLOG('_replentish: required, lock NOT acquired)') max_ts = self._max_timeslice() self._do_replentish_work(now, max_ts) elif lock_acquired: # If replentish is optional, minimize the chance that # two threads will attempt to do replentish work at # the same time (which causes conflicts) by # introducing a random element. DEBUG and TLOG('_replentish: attempting optional replentish ' '(lock acquired)') max_ts = self._max_timeslice() low = now/self._period high = max_ts/self._period if roll(low, high, 'optional replentish'): self._do_replentish_work(now, max_ts) else: # This is an optional replentish and we can't acquire # the lock, bail. DEBUG and TLOG('_optional replentish attempt aborted, could ' 'not acquire lock.') return finally: if lock_acquired: self.replentish_lock.release() def _do_replentish_work(self, now, max_ts): DEBUG and TLOG('_do_replentish_work: entering') # this is only separated from _replentish for readability; it # should generally not be called by anything but _replentish # available_spares == the number of "spare" buckets that exist # in "_data" available_spares = (max_ts - now) / self._period DEBUG and TLOG('_do_replentish_work: now = %s' % now) DEBUG and TLOG('_do_replentish_work: max_ts = %s' % max_ts) DEBUG and TLOG('_do_replentish_work: available_spares = %s' % available_spares) if available_spares >= SPARE_BUCKETS: DEBUG and TLOG('_do_replentish_work: available_spares (%s) >= ' 'SPARE_BUCKETS (%s), doing ' 'nothing'% (available_spares, SPARE_BUCKETS)) return if max_ts < now: # the newest bucket in self._data is older than now! replentish_start = now replentish_end = now + (self._period * SPARE_BUCKETS) else: replentish_start = max_ts + self._period replentish_end = max_ts + (self._period * (SPARE_BUCKETS +1)) DEBUG and TLOG('_do_replentish_work: replentish_start = %s' % replentish_start) DEBUG and TLOG('_do_replentish_work: replentish_end = %s' % replentish_end) # n is the number of buckets to create n = (replentish_end - replentish_start) / self._period new_buckets = getTimeslices(replentish_start, n, self._period) new_buckets.reverse() STRICT and _assert(new_buckets) DEBUG and TLOG('_do_replentish_work: adding %s new buckets' % n) DEBUG and TLOG('_do_replentish_work: buckets to add = %s' % new_buckets) for k in new_buckets: STRICT and _assert(not self._data.has_key(k)) self._data[k] = BUCKET_CLASS() # XXX ReadConflictError hotspot self._max_timeslice.set(max(new_buckets)) def _gc(self, now=None): """ Remove stale buckets """ if not self._timeout_slices: return # dont do gc if there is no timeout # give callers a good chance to do nothing (gc isn't as important # as replentishment or finalization) if not roll(0, 5, 'gc'): DEBUG and TLOG('_gc: lost roll, doing nothing') return if not self.gc_lock.acquire(0): DEBUG and TLOG('_gc: couldnt acquire lock') return try: if now is None: now = getCurrentTimeslice(self._period) # for unit tests last_gc = self._last_gc_timeslice() gc_every = self._period * round(SPARE_BUCKETS / 2.0) if (now - last_gc) < gc_every: DEBUG and TLOG('_gc: gc attempt not yet required ' '( (%s - %s) < %s )' % (now, last_gc, gc_every)) return else: DEBUG and TLOG( '_gc: (%s -%s) > %s, gc invoked' % (now, last_gc, gc_every)) self._do_gc_work(now) finally: self.gc_lock.release() def _do_gc_work(self, now): # this is only separated from _gc for readability; it should # generally not be called by anything but _gc # we garbage collect any buckets that have already been run # through finalization DEBUG and TLOG('_do_gc_work: entering') max_ts = self._last_finalized_timeslice() DEBUG and TLOG('_do_gc_work: max_ts is %s' % max_ts) to_gc = list(self._data.keys(None, max_ts)) DEBUG and TLOG('_do_gc_work: to_gc is: %s' % str(to_gc)) for key in to_gc: _assert(key <= max_ts) STRICT and _assert(self._data.has_key(key)) DEBUG and TLOG('_do_gc_work: deleting %s from _data' % key) del self._data[key] DEBUG and TLOG('_do_gc_work: setting last_gc_timeslice to %s' % now) self._last_gc_timeslice.set(now) def notifyAdd(self, item): DEBUG and TLOG('notifyAdd with %s' % item) callback = self._getCallback(self._addCallback) if callback is None: return self._notify(item, callback, 'notifyAdd') def notifyDel(self, item): DEBUG and TLOG('notifyDel with %s' % item) callback = self._getCallback(self._delCallback) if callback is None: return self._notify(item, callback, 'notifyDel' ) def _getCallback(self, callback): if not callback: return None if type(callback) is type(''): try: method = self.unrestrictedTraverse(callback) except (KeyError, AttributeError): path = self.getPhysicalPath() err = 'No such onAdd/onDelete method %s referenced via %s' LOG.warn(err % (callback, '/'.join(path)), exc_info=sys.exc_info()) return else: method = callback return method def _notify(self, item, callback, name): if callable(callback): sm = getSecurityManager() try: user = sm.getUser() try: newSecurityManager(None, nobody) callback(item, self) except: # dont raise, just log path = self.getPhysicalPath() LOG.warn('%s failed when calling %s in %s' % (name,callback, '/'.join(path)), exc_info=sys.exc_info()) finally: setSecurityManager(sm) else: err = '%s in %s attempted to call non-callable %s' path = self.getPhysicalPath() LOG.warn(err % (name, '/'.join(path), callback), exc_info=sys.exc_info()) def getId(self): return self.id security.declareProtected(CREATE_TRANSIENTS_PERM, 'new_or_existing') def new_or_existing(self, key): DEBUG and TLOG('new_or_existing called with %s' % key) item = self.get(key, _marker) if item is _marker: item = TransientObject(key) self[key] = item item = self._wrap(item) return item security.declareProtected(CREATE_TRANSIENTS_PERM, 'new') def new(self, key): DEBUG and TLOG('new called with %s' % key) if type(key) is not type(''): raise TypeError, (key, "key is not a string type") if self.has_key(key): raise KeyError, "cannot duplicate key %s" % key item = TransientObject(key) self[key] = item return self._wrap(item) # TransientItemContainer methods security.declareProtected(MANAGE_CONTAINER_PERM, 'setTimeoutMinutes') def setTimeoutMinutes(self, timeout_mins, period_secs=20): """ The period_secs parameter is defaulted to preserve backwards API compatibility. In older versions of this code, period was hardcoded to 20. """ timeout_secs = timeout_mins * 60 if (timeout_mins != self.getTimeoutMinutes() or period_secs != self.getPeriodSeconds()): # do nothing unless something has changed self._setTimeout(timeout_mins, period_secs) self._reset() def getTimeoutMinutes(self): """ """ return self._timeout_secs / 60 def getPeriodSeconds(self): """ """ return self._period security.declareProtected(MGMT_SCREEN_PERM, 'getSubobjectLimit') def getSubobjectLimit(self): """ """ return self._limit security.declareProtected(MANAGE_CONTAINER_PERM, 'setSubobjectLimit') def setSubobjectLimit(self, limit): """ """ if limit != self.getSubobjectLimit(): self._setLimit(limit) security.declareProtected(MGMT_SCREEN_PERM, 'getAddNotificationTarget') def getAddNotificationTarget(self): return self._addCallback or '' security.declareProtected(MANAGE_CONTAINER_PERM,'setAddNotificationTarget') def setAddNotificationTarget(self, f): self._addCallback = f security.declareProtected(MGMT_SCREEN_PERM, 'getDelNotificationTarget') def getDelNotificationTarget(self): return self._delCallback or '' security.declareProtected(MANAGE_CONTAINER_PERM,'setDelNotificationTarget') def setDelNotificationTarget(self, f): self._delCallback = f security.declareProtected(MGMT_SCREEN_PERM, 'disableInbandHousekeeping') def disableInbandHousekeeping(self): """ No longer perform inband housekeeping """ self._inband_housekeeping = False security.declareProtected(MGMT_SCREEN_PERM, 'enableInbandHousekeeping') def enableInbandHousekeeping(self): """ (Re)enable inband housekeeping """ self._inband_housekeeping = True security.declareProtected(MGMT_SCREEN_PERM, 'isInbandHousekeepingEnabled') def isInbandHousekeepingEnabled(self): """ Report if inband housekeeping is enabled """ return self._inband_housekeeping security.declareProtected('View', 'housekeep') def housekeep(self): """ Call this from a scheduler at least every self._period * (SPARE_BUCKETS - 1) seconds to perform out of band housekeeping """ # we can protect this method from being called too often by # anonymous users as necessary in the future; we already have a lot # of protection as-is though so no need to make it more complicated # than necessary at the moment self._housekeep(getCurrentTimeslice(self._period)) def _housekeep(self, now): self._finalize(now) self._replentish(now) self._gc(now) security.declareProtected(MANAGE_CONTAINER_PERM, 'manage_changeTransientObjectContainer') def manage_changeTransientObjectContainer( self, title='', timeout_mins=20, addNotification=None, delNotification=None, limit=0, period_secs=20, REQUEST=None ): """ Change an existing transient object container. """ self.title = title self.setTimeoutMinutes(timeout_mins, period_secs) self.setSubobjectLimit(limit) if not addNotification: addNotification = None if not delNotification: delNotification = None self.setAddNotificationTarget(addNotification) self.setDelNotificationTarget(delNotification) if REQUEST is not None: return self.manage_container( self, REQUEST, manage_tabs_message='Changes saved.' ) def __setstate__(self, state): # upgrade versions of Transience in Zope versions less # than 2.7.1, which used a different transience mechanism. Note: # this will not work for upgrading versions older than 2.6.0, # all of which used a very different transience implementation # can't make __len__ an instance variable in new-style classes # f/w compat: 2.8 cannot use __len__ as an instance variable if not state.has_key('_length'): length = state.get('__len__', Length2()) self._length = self.getLen = length oldlength = state['_length'] if isinstance(oldlength, BTreesLength): # TOCS prior to 2.7.3 had a BTrees.Length.Length object as # the TOC length object, replace it with our own Length2 # that does our conflict resolution correctly: sz = oldlength() self._length = self.getLen = Length2(sz) # TOCs prior to 2.7.1 took their period from a global if not state.has_key('_period'): self._period = 20 # this was the default for all prior releases # TOCs prior to 2.7.1 used a different set of data structures # for efficiently keeping tabs on the maximum slice if not state.has_key('_max_timeslice'): new_slices = getTimeslices( getCurrentTimeslice(self._period), SPARE_BUCKETS*2, self._period) for i in new_slices: if not self._data.has_key(i): self._data[i] = BUCKET_CLASS() # create an Increaser for max timeslice self._max_timeslice = Increaser(max(new_slices)) if not state.has_key('_last_finalized_timeslice'): self._last_finalized_timeslice = Increaser(-self._period) # TOCs prior to 2.7.3 didn't have a _last_gc_timeslice if not state.has_key('_last_gc_timeslice'): self._last_gc_timeslice = Increaser(-self._period) # we should probably delete older attributes from state such as # '_last_timeslice', '_deindex_next',and '__len__' here but we leave # them in order to allow people to switch between 2.6.0->2.7.0 and # 2.7.1+ as necessary (although that has not been tested) self.__dict__.update(state) def getCurrentTimeslice(period): """ Return an integer representing the 'current' timeslice. The current timeslice is guaranteed to be the same integer within a 'slice' of time based on a divisor of 'self._period'. 'self._period' is the number of seconds in a slice. """ now = time.time() low = int(math.floor(now)) - period + 1 high = int(math.ceil(now)) + 1 for x in range(low, high): if x % period == 0: return x def getTimeslices(begin, n, period): """ Get a list of future timeslice integers of 'n' size in descending order """ l = [] for x in range(n): l.insert(0, begin + (x * period)) return l def roll(low, high, reason): try: result = random.randrange(low, high) except ValueError: # empty range, must win this roll result = low if result == low: DEBUG and TLOG('roll: low: %s, high: %s: won with %s (%s)' % (low, high, result, reason)) return True else: DEBUG and TLOG('roll: low: %s, high: %s: lost with %s (%s)' % (low, high, result, reason)) return False def _assert(case): if not case: raise AssertionError class Increaser(Persistent): """ A persistent object representing a typically increasing integer that has conflict resolution which uses the greatest integer out of the three available states. """ def __init__(self, v): self.value = v def set(self, v): self.value = v def __getstate__(self): return self.value def __setstate__(self, v): self.value = v def __call__(self): return self.value def _p_resolveConflict(self, old, state1, state2): return max(old, state1, state2) class Length2(Persistent): """ A persistent object responsible for maintaining a repesention of the number of current transient objects. Conflict resolution is sensitive to which methods are used to change the length. """ def __init__(self, value=0): self.set(value) def set(self, value): self.value = value self.floor = 0 self.ceiling = value def increment(self, delta): """Increase the length by delta. Conflict resolution will take the sum of all the increments.""" self.ceiling += delta self.value += delta def decrement(self, delta): """Decrease the length by delta. Conflict resolution will take the highest decrement.""" self.floor += delta self.value -= delta def __getstate__(self): return self.__dict__ def __setstate__(self, state): self.__dict__.update(state) def __call__(self): return self.value def _p_resolveConflict(self, old, saved, new): new['ceiling'] = saved['ceiling'] + new['ceiling'] - old['ceiling'] new['floor'] = max(old['floor'], saved['floor'], new['floor']) new['value'] = new['ceiling'] - new['floor'] return new InitializeClass(TransientObjectContainer) zope2.13-2.13.21/source/Zope2/src/Products/Transience/Fake.py0000644000175000017500000000203012214017422022331 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Module used for testing transience (BTree-API-conforming data structure) """ from Persistence.mapping import PersistentMapping import sys class FakeIOBTree(PersistentMapping): def keys(self, min, max): L = [] if min is None: min = 0 if max is None: max = sys.maxint for k in self.data: if min <= k <= max: L.append(k) return L zope2.13-2.13.21/source/Zope2/src/Products/Transience/HowTransienceWorks.stx0000644000175000017500000001211312214017422025453 0ustar arnauarnauHow Transience Works The object responsible for managing the expiration of "transient" objects is the TransientObjectContainer, the class definition for which is located in Products.Transience.Transience.TransientObjectContainer. An instance of this class is found in the default Zope installation at /temp_folder/session_data. The TransientObjectContainer (TOC) holds Transient Objects (TOs). A TO is obtained via its container via a call to TOC.new_or_existing(key), where "key" is usually the "browser id" associated with a visitor (See Products.Session.BrowserIdManager). If the TOC has a "current" TO corresponding to "key", it is returned. If the TOC does not have a "current" TO corresponding to "key", (due to the expiration of the TO or because it never existed in the first place) a "new" TO is manufactured and returned. Timeslices Transience defines the notion of a "timeslice". A "timeslice" is an integer that represents some "slice" of time, defined by a "period". For example, if a period is 20 seconds long, three ordered time slices might be expressed as 0, 20, and 40. The next timeslice would be 60, and so on. For an absolute time to "belong" to a timeslice, it would need to be equal to or greater than one timeslice integer, but less than the subsequent timeslice integer. Data Structures Maintained by a Transient Object Container The TOC maintains three important kinds of data structures: - a "_data" structure, which is an IOBTree mapping a "timeslice" integer to a "bucket" (see next bullet for definition of bucket). - One or more "buckets", which are OOBTree objects which map a "key" (usually browser id) to a TransientObject. Buckets are stored inside of the "_data" structure. There is a concept of a "current" bucket, which is the bucket that is contained within the _data structured with a key equal to the "current" timeslice. A current bucket must always exist (this is an invariant). - A "max_timeslice" integer, which is equal to the "largest" timeslice for which there exists a bucket in the _data structure. This is an optimization given that key operations against BTrees can be slow and could cause conflicts (the same could be achieved via _data.maxKey() otherwise). When a Transient Object is created via new_or_existing, it is added to the "current" bucket. As time goes by, the bucket to which the TO was added ceases to be the "current" bucket. If the transient object is "accessed" (it is called up out of the TOC via the TOC's 'get' method), it is again moved to the "current" bucket defined by the current time's timeslice. During the course of normal operations, a TransientObject will move from an "old" bucket to the "current" bucket many times, as long as it continues to be accessed. It is possible for a TransientObject to never expire, as long as it is called up out of its TOC often enough. If a TransientObject is not accessed in the period of time defined by the TOC's "timeout", it is eventually garbage collected. How the TransientObjectContainer Determines if a TransientObject is "Current" All "current" timeslice buckets (as specified by the timeout) are searched for the transient object, most recent bucket first. Housekeeping: Finalization, Garbage Collection, and Bucket Replentishing The TOC performs "finalization", "garbage collection", and "bucket replentishing". It typically performs these tasks "in-band" (although it is possible to do the housekeeping tasks "out of band" as well: see the methods of the Transient Object Container with "housekeep" in their names). "In band" housekeeping implies that the TOC does not maintain a separate thread or process that wakes up every so often to clean up. Instead, during the course of normal operations, the TOC opportunistically performs housekeeping functions. Finalization is defined as optionally calling a function at bucket expiration time against all transient objects contained within that bucket. The optional function call is user-defined, but it is managed by the "notifyDel" method of the TOC. Garbage collection is defined as deleting "expired" buckets in the _data structure (the _data structure maps a timeslice to a bucket). Typically this is done by throwing away one or more buckets in the _data structure after they expire. Bucket replentishing is defined as the action of (opportunistically) creating more buckets to insert into the the _data structure, replacing ones that are deleted during garbage collection. The act of deleting a bucket does not necessarily imply that a new bucket will be immediately created thereafter. We create new buckets in batches to reduce the possibility of conflicts. Finalization is attempted on every call to the transience machinery to make TOs appear to expire "on time". Garbage collection and replentishment is performed on a somewhat random basis to avoid unnecessary conflicts. Goals - A low number of ZODB conflict errors (which reduce performance). - Stability. To Do - Testing under ZEO. zope2.13-2.13.21/source/Zope2/src/Products/Transience/TransientObject.py0000644000175000017500000002276512214017422024602 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Simple ZODB-based transient object implementation. """ import logging import os import random import sys import thread import time from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import Implicit from Persistence import Persistent from ZODB.POSException import ConflictError from zope.interface import implements from Products.Transience.TransienceInterfaces import DictionaryLike from Products.Transience.TransienceInterfaces import \ ImmutablyValuedMappingOfPickleableObjects from Products.Transience.TransienceInterfaces import ItemWithId from Products.Transience.TransienceInterfaces import Transient from Products.Transience.TransienceInterfaces import TransientItemContainer from Products.Transience.TransienceInterfaces import TTWDictionary DEBUG = int(os.environ.get('Z_TOC_DEBUG', 0)) LOG = logging.getLogger('Zope.TransientObject') def TLOG(*args): sargs = [] sargs.append(str(thread.get_ident())) sargs.append(str(time.time())) for arg in args: sargs.append(str(arg)) msg = ' '.join(sargs) LOG.info(msg) _notfound = [] WRITEGRANULARITY=30 # Timing granularity for access write clustering, seconds class TransientObject(Persistent, Implicit): """ Dictionary-like object that supports additional methods concerning expiration and containment in a transient object container """ implements(ItemWithId, # randomly generate an id Transient, DictionaryLike, TTWDictionary, ImmutablyValuedMappingOfPickleableObjects ) security = ClassSecurityInfo() security.setDefaultAccess('allow') security.declareObjectPublic() _last_modified = None # _last modified indicates the last time that __setitem__, __delitem__, # update or clear was called on us. def __init__(self, containerkey): self.token = containerkey self.id = self._generateUniqueId() self._container = {} self._created = self._last_accessed = time.time() # _last_accessed indicates the last time that *our container # was asked about us* (NOT the last time __getitem__ or get # or any of our other invariant data access methods are called). # Our container manages our last accessed time, we don't much # concern ourselves with it other than exposing an interface # to set it on ourselves. # ----------------------------------------------------------------- # ItemWithId # def getId(self): return self.id # ----------------------------------------------------------------- # Transient # def invalidate(self): # hasattr hides conflicts if getattr(self, '_invalid', _notfound) is not _notfound: # we dont want to invalidate twice return trans_ob_container = None # search our acquisition chain for a transient object container # and delete ourselves from it. for ob in getattr(self, 'aq_chain', []): if TransientItemContainer.providedBy(ob): trans_ob_container = ob break if trans_ob_container is not None: if trans_ob_container.has_key(self.token): del trans_ob_container[self.token] self._invalid = None def isValid(self): # hasattr hides conflicts if getattr(self, '_invalid', _notfound) is _notfound: return 1 def getLastAccessed(self): return self._last_accessed def setLastAccessed(self): # check to see if the last_accessed time is too recent, and avoid # setting if so, to cut down on heavy writes t = time.time() if (self._last_accessed + WRITEGRANULARITY) < t: self._last_accessed = t def getLastModified(self): return self._last_modified def setLastModified(self): self._last_modified = time.time() def getCreated(self): return self._created def getContainerKey(self): return self.token # ----------------------------------------------------------------- # DictionaryLike # def keys(self): return self._container.keys() def values(self): return self._container.values() def items(self): return self._container.items() def get(self, k, default=_notfound): v = self._container.get(k, default) if v is _notfound: return None return v def has_key(self, k): if self._container.get(k, _notfound) is not _notfound: return 1 return 0 def clear(self): self._p_changed = 1 self._container.clear() self.setLastModified() def update(self, d): self._p_changed = 1 for k in d.keys(): self[k] = d[k] # ----------------------------------------------------------------- # ImmutablyValuedMappingOfPickleableObjects (what a mouthful!) # def __setitem__(self, k, v): self._p_changed = 1 self._container[k] = v self.setLastModified() def __getitem__(self, k): return self._container[k] def __delitem__(self, k): self._p_changed = 1 del self._container[k] self.setLastModified() # ----------------------------------------------------------------- # TTWDictionary # set = __setitem__ __guarded_setitem__ = __setitem__ __guarded_delitem__ = __delitem__ delete = __delitem__ # ----------------------------------------------------------------- # Other non interface code # def _p_resolveConflict(self, saved, state1, state2): DEBUG and TLOG('entering TO _p_rc') DEBUG and TLOG('states: sv: %s, s1: %s, s2: %s' % ( saved, state1, state2)) states = [saved, state1, state2] # We can clearly resolve the conflict if one state is invalid, # because it's a terminal state. for state in states: if state.has_key('_invalid'): DEBUG and TLOG('TO _p_rc: a state was invalid') return state # The only other times we can clearly resolve the conflict is if # the token, the id, or the creation time don't differ between # the three states, so we check that here. If any differ, we punt # by raising ConflictError. attrs = ['token', 'id', '_created'] for attr in attrs: svattr = saved.get(attr) s1attr = state1.get(attr) s2attr = state2.get(attr) DEBUG and TLOG('TO _p_rc: attr %s: sv: %s s1: %s s2: %s' % (attr, svattr, s1attr, s2attr)) if not svattr==s1attr==s2attr: DEBUG and TLOG('TO _p_rc: cant resolve conflict') raise ConflictError # Now we need to do real work. # # Data in our _container dictionaries might conflict. To make # things simple, we intentionally create a race condition where the # state which was last modified "wins". It would be preferable to # somehow merge our _containers together, but as there's no # generally acceptable way to union their states, there's not much # we can do about it if we want to be able to resolve this kind of # conflict. # We return the state which was most recently modified, if # possible. states.sort(lastmodified_sort) if states[0].get('_last_modified'): DEBUG and TLOG('TO _p_rc: returning last mod state') return states[0] # If we can't determine which object to return on the basis # of last modification time (no state has been modified), we return # the object that was most recently accessed (last pulled out of # our parent). This will return an essentially arbitrary state if # all last_accessed values are equal. states.sort(lastaccessed_sort) DEBUG and TLOG('TO _p_rc: returning last_accessed state') return states[0] getName = getId # this is for SQLSession compatibility def _generateUniqueId(self): t = str(int(time.time())) d = "%010d" % random.randint(0, sys.maxint-1) return "%s%s" % (t, d) def __repr__(self): return "id: %s, token: %s, content keys: %s" % ( self.id, self.token, `self.keys()` ) def lastmodified_sort(d1, d2): """ sort dictionaries in descending order based on last mod time """ m1 = d1.get('_last_modified', 0) m2 = d2.get('_last_modified', 0) if m1 == m2: return 0 if m1 > m2: return -1 # d1 is "less than" d2 return 1 def lastaccessed_sort(d1, d2): """ sort dictionaries in descending order based on last access time """ m1 = d1.get('_last_accessed', 0) m2 = d2.get('_last_accessed', 0) if m1 == m2: return 0 if m1 > m2: return -1 # d1 is "less than" d2 return 1 InitializeClass(TransientObject) zope2.13-2.13.21/source/Zope2/src/Products/Transience/TransienceInterfaces.py0000644000175000017500000002425312214017422025575 0ustar arnauarnau########################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ########################################################################## """ Transient Objects TransientObjectContainers are objects which contain zero or more TransientObjects. They implement the following interfaces: - ItemWithId - StringKeyedHomogenousItemContainer - TransientItemContainer In particular, one uses the 'new_or_existing' method on TransientObjectContainers to retrieve or create a TransientObject based on a given string key. If add or delete notifications are registered with the container, they will be called back when items in the container are added or deleted, with the item and the container as arguments. The callbacks may be registered either as bound methods, functions, or physical paths to Zope Script (Python Script or External Method) objects (e.g. '/some/resolvable/script/name'). In any of these cases, the delete and add notifications will be called with arguments allowing the callbacks to operate on data representing the state of the transient object at the moment of addition or deletion (see setAddNotificationTarget and setDelNotificationTarget below). TransientObjects are containerish items held within TransientObjectContainers and they implement the following interfaces: - ItemWithId - Transient - DictionaryLike - TTWDictionary - ImmutablyValuedMappingOfPickleableObjects Of particular importance is the idea that TransientObjects do not offer the contract of "normal" ZODB container objects; mutations made to items which are contained within a TransientObject cannot be expected to persist. Developers need explicitly resave the state of a subobject of a TransientObject by placing it back into the TransientObject via the TransientObject.__setitem__ or .set methods. This requirement is due to the desire to allow people to create alternate TransientObject implementations that are *not* based on the ZODB. Practically, this means that when working with a TransientObject which contains mutable subobjects (even if they inherit from Persistence.Persistent), you *must* resave them back into the TransientObject. For example:: class Foo(Persistence.Persistent): pass transient_object = transient_data_container.new('t') foo = transient_object['foo'] = Foo() foo.bar = 1 # the following is *necessary* to repersist the data transient_object['foo'] = foo """ from zope.interface import Interface class Transient(Interface): def invalidate(): """ Invalidate (expire) the transient object. Causes the transient object container's "before destruct" method related to this object to be called as a side effect. """ def isValid(): """ Return true if transient object is still valid, false if not. A transient object is valid if its invalidate method has not been called. """ def getLastAccessed(): """ Return the time the transient object was last accessed in integer seconds-since-the-epoch form. Last accessed time is defined as the last time the transient object's container "asked about" this transient object. """ def setLastAccessed(): """ Cause the last accessed time to be set to now. """ def getLastModified(): """ Return the time the transient object was last modified in integer seconds-since-the-epoch form. Modification generally implies a call to one of the transient object's __setitem__ or __delitem__ methods, directly or indirectly as a result of a call to update, clear, or other mutating data access methods. """ def setLastModified(): """ Cause the last modified time to be set to now. """ def getCreated(): """ Return the time the transient object was created in integer seconds-since-the-epoch form. """ def getContainerKey(): """ Return the key under which the object was placed in its container. """ class DictionaryLike(Interface): def keys(): """ Return sequence of key elements. """ def values(): """ Return sequence of value elements. """ def items(): """ Return sequence of (key, value) elements. """ def get(k, default='marker'): """ Return value associated with key k. Return None or default if k does not exist. """ def has_key(k): """ Return true if item referenced by key k exists. """ def clear(): """ Remove all key/value pairs. """ def update(d): """ Merge dictionary d into ourselves. """ # DictionaryLike does NOT support copy() class ItemWithId(Interface): def getId(): """ Returns a meaningful unique id for the object. Note that this id need not the key under which the object is stored in its container. """ class TTWDictionary(DictionaryLike, ItemWithId): def set(k, v): """ Call __setitem__ with key k, value v. """ def delete(k): """ Call __delitem__ with key k. """ def __guarded_setitem__(k, v): """ Call __setitem__ with key k, value v. """ class ImmutablyValuedMappingOfPickleableObjects(Interface): def __setitem__(k, v): """ Sets key k to value v, if k is both hashable and pickleable and v is pickleable, else raise TypeError. """ def __getitem__(k): """ Returns the value associated with key k. Note that no guarantee is made to persist changes made to mutable objects obtained via __getitem__, even if they support the ZODB Persistence interface. In order to ensure that changes to mutable values are persisted, you need to explicitly put the value back in to the mapping via __setitem__. """ def __delitem__(k): """ Remove the key/value pair related to key k. """ class HomogeneousItemContainer(Interface): """ An object which: 1. Contains zero or more subobjects, all of the same type. 2. Is responsible for the creation of its subobjects. 3. Allows for the access of a subobject by key. """ def get(k, default=None): """ Return value associated with key k via __getitem__. If value associated with k does not exist, return default. Returned item is acquisition-wrapped in self unless a default is passed in and returned. """ def has_key(k): """ Return true if container has value associated with key k, else return false. """ class StringKeyedHomogeneousItemContainer(HomogeneousItemContainer): def new(k): """ Creates a new subobject of the type supported by this container with key "k" and returns it. If an object already exists in the container with key "k", a KeyError is raised. "k" must be a string, else a TypeError is raised. If the container is 'full', a MaxTransientObjectsExceeded exception will be raised. Returned object is acquisition-wrapped in self. """ def new_or_existing(k): """ If an object already exists in the container with key "k", it is returned. Otherwise, create a new subobject of the type supported by this container with key "k" and return it. "k" must be a string, else a TypeError is raised. If a new object needs to be created and the container is 'full', a MaxTransientObjectsExceeded exception will be raised. Returned object is acquisition-wrapped in self. """ class TransientItemContainer(Interface): def setTimeoutMinutes(timeout_mins): """ Set the number of minutes of inactivity allowable for subobjects before they expire. """ def getTimeoutMinutes(): """ Return the number of minutes allowed for subobject inactivity before expiration. """ def getAddNotificationTarget(): """ Returns the currently registered 'add notification' value, or None. """ def setAddNotificationTarget(f): """ Cause the 'add notification' function to be 'f'. If 'f' is not callable and is a string, treat it as a physical path to a Zope Script object (Python Script, External Method, et. al). 'add notify' functions need accept two arguments: 'item', which is the transient object being destroyed, and 'container', which is the transient object container which is performing the destruction. For example:: def addNotify(item, container): print "id of 'item' arg was %s" % item.getId() """ def getDelNotificationTarget(): """ Returns the currently registered 'delete notification' value, or None. """ def setDelNotificationTarget(f): """ Cause the 'delete notification' function to be 'f'. If 'f' is not callable and is a string, treat it as a physical path to a Zope Script object (Python Script, External Method, et. al). 'Before destruction' functions need accept two arguments: 'item', which is the transient object being destroyed, and 'container', which is the transient object container which is performing the destruction. For example:: def delNotify(item, container): print "id of 'item' arg was %s" % item.getId() """ zope2.13-2.13.21/source/Zope2/src/Products/Transience/www/0000755000175000017500000000000012214017422021742 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Transience/www/datacontainer.gif0000644000175000017500000000103212214017422025241 0ustar arnauarnauGIF89aæÿÿÿ‹‹††xxmmkkddYYOOLLDD9955''žž ŸŸŸŸ¦¦ ´´CººR»»V¼¼XÇÇsËË}ÍÍ‚ÑÑ‹ÓÓ’ÖÖ™ÙÙ É¿º¥¢™’‘ŽŠ~un^][ZXV;4ÍÐÒÓ""Ô&&Ö33×77ÛIIÜOOÜPPÝUUß^^áhhãqqävväyyå||怀煅燇爈牉ÿÿÿ!ùK,w€K‚ƒ„…†‡ˆƒC‰†GFŒ„HJD†KEIE@;„KACB>7ƒ K=?<8!$'(K K:95"+02-K K6 &.4Ü‚ ÕÕK %/ÝŒ#,34K)1í‚*ñôñ;zope2.13-2.13.21/source/Zope2/src/Products/Transience/__init__.py0000644000175000017500000000250212214017422023226 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ Transience initialization routines """ import ZODB # this is to help out testrunner, don't remove. import Transience # import of MaxTransientObjectsExceeded for easy import from scripts, # this is protected by a module security info declaration in the # Sessions package. from Transience import MaxTransientObjectsExceeded def initialize(context): context.registerClass( Transience.TransientObjectContainer, permission=Transience.ADD_CONTAINER_PERM, icon='www/datacontainer.gif', constructors=(Transience.constructTransientObjectContainerForm, Transience.constructTransientObjectContainer) ) context.registerHelp() context.registerHelpTitle('Zope Help') zope2.13-2.13.21/source/Zope2/src/Products/Transience/help/0000755000175000017500000000000012214017422022046 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Transience/help/Transience-add.stx0000644000175000017500000000670412214017422025436 0ustar arnauarnauTransientObjectContainer - Add Transient Object Containers A Transient Object Container contains objects which expire after a user-settable period of time. Items placed into transient object must have string keys, but may have any type of value. Common Usages A Transient Object Container is used by Session Data Managers to store session data. To create a Transient Object Container, specify the following: - **Id** The Zope id of the Transient Object Container. - **Title** *Optional* The title of the object. - **Data object timeout (in minutes)** The minimum number of minutes that objects in the container will persist for. Objects in the container are passively deleted, so they may not be deleted exactly after this number of minutes elapses. A setting of "0" indicates that objects should not expire. - **Timeout resolution (in seconds)** Defines what the "resolution" of item timeout is. Setting this higher allows the transience machinery to do fewer "writes" at the expense of causing items to time out later than the "Data object timeout value" by a factor of (at most) this many seconds. This number must divide evenly into the number of timeout seconds ("Data object timeout value" * 60) and cannot be set higher than the timeout value in seconds. - **Maximum number of subobjects ** The maximum number of subobjects that this container may simultaneously hold. Since transient objects normally hold session data, this number is this is the effective limit for the number of simultaneous sessions. If the value is "0", the number of objects addable to the container will be not be artificially limited. Note: This setting is useful to prevent accidental or deliberate denial of service due to RAM shortage if the transient object container is instantiated in a storage which is backed solely by RAM, such as a Temporary Folder. - **Script to call when objects are added** *Optional* The physical path of of a Zope script which will receive notifications when objects are added to the Transient Object Container. Ex: '/path/to/add/script'. For more information, see "Add and Delete Scripts" below. - **Script to call when objects are deleted** *Optional* The physical path of a Zope script which will receive notifications when objects are deleted from the Transient Object Container, either explicitly or through timeout-related expiration. Ex: '/path/to/delete/script' For more information, see "Add and Delete Scripts" below. Add and Delete Scripts Add and Delete scripts are Zope scripts which are called, respectively, when an object is added or removed from a Transient Object Container. An add or delete script is specified by naming it by its full Zope object path with slash separators, e.g. "/path/to/method". Add and delete scripts are called with two arguments. The first argument is the item being added or removed from the container; the second argument is the Transient Object Container itself. The container will be acquisition wrapped, allowing the it to be used as a context to reference other Zope objects. See Also - "Transience API":TransienceInterfaces.py zope2.13-2.13.21/source/Zope2/src/Products/Transience/help/TransienceInterfaces.py0000644000175000017500000002012012214017422026512 0ustar arnauarnau########################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ########################################################################## """ Transient Objects """ class TransientObjectContainer: """ TransientObjectContainers hold transient objects, most often, session data. You will rarely have to script a transient object container. You'll almost always deal with a TransientObject itself which you'll usually get as 'REQUEST.SESSION'. """ def getId(self): """ Returns a meaningful unique id for the object. Permission -- Always available """ def get(self, k, default=None): """ Return value associated with key k. If value associated with k does not exist, return default. Permission -- 'Access Transient Objects' """ def has_key(self, k): """ Return true if container has value associated with key k, else return false. Permission -- 'Access Transient Objects' """ def new(self, k): """ Creates a new subobject of the type supported by this container with key "k" and returns it. If an object already exists in the container with key "k", a KeyError is raised. "k" must be a string, else a TypeError is raised. If the container is 'full', a MaxTransientObjectsExceeded will be raised. Permission -- 'Create Transient Objects' """ def new_or_existing(self, k): """ If an object already exists in the container with key "k", it is returned. Otherwiser, create a new subobject of the type supported by this container with key "k" and return it. "k" must be a string, else a TypeError is raised. If the container is 'full', a MaxTransientObjectsExceeded exception be raised. Permission -- 'Create Transient Objects' """ def setTimeoutMinutes(self, timeout_mins, period=20): """ Set the number of minutes of inactivity allowable for subobjects before they expire (timeout_mins) as well as the 'timeout resolution' in seconds (period). 'timeout_mins' * 60 must be evenly divisible by the period. Period must be less than 'timeout_mins' * 60. Permission -- 'Manage Transient Object Container' """ def getTimeoutMinutes(self): """ Return the number of minutes allowed for subobject inactivity before expiration. Permission -- 'View management screens' """ def getPeriodSeconds(self): """ Return the 'timeout resolution' in seconds. Permission -- 'View management screens' """ def getAddNotificationTarget(self): """ Returns the current 'after add' function, or None. Permission -- 'View management screens' """ def setAddNotificationTarget(self, f): """ Cause the 'after add' function to be 'f'. If 'f' is not callable and is a string, treat it as a Zope path to a callable function. 'after add' functions need accept a single argument: 'item', which is the item being added to the container. Permission -- 'Manage Transient Object Container' """ def getDelNotificationTarget(self): """ Returns the current 'before destruction' function, or None. Permission -- 'View management screens' """ def setDelNotificationTarget(self, f): """ Cause the 'before destruction' function to be 'f'. If 'f' is not callable and is a string, treat it as a Zope path to a callable function. 'before destruction' functions need accept a single argument: 'item', which is the item being destroyed. Permission -- 'Manage Transient Object Container' """ class TransientObject: """ A transient object is a temporary object contained in a transient object container. Most of the time you'll simply treat a transient object as a dictionary. You can use Python sub-item notation:: SESSION['foo']=1 foo=SESSION['foo'] del SESSION['foo'] When using a transient object from Python-based Scripts or DTML you can use the 'get', 'set', and 'delete' methods instead. Methods of transient objects are not protected by security assertions. It's necessary to reassign mutuable sub-items when you change them. For example:: l=SESSION['myList'] l.append('spam') SESSION['myList']=l This is necessary in order to save your changes. Note that this caveat is true even for mutable subitems which inherit from the Persistence.Persistent class. """ def getId(self): """ Returns a meaningful unique id for the object. Permission -- Always available """ def getContainerKey(self): """ Returns the key under which the object is "filed" in its container. getContainerKey will often return a differnt value than the value returned by getId. Permission -- Always available """ def invalidate(self): """ Invalidate (expire) the transient object. Causes the transient object container's "before destruct" method related to this object to be called as a side effect. Permission -- Always available """ def getLastAccessed(self): """ Return the time the transient object was last accessed in integer seconds-since-the-epoch form. Permission -- Always available """ def setLastAccessed(self): """ Cause the last accessed time to be set to now. Permission -- Always available """ def getCreated(self): """ Return the time the transient object was created in integer seconds-since-the-epoch form. Permission -- Always available """ def keys(self): """ Return sequence of key elements. Permission -- Always available """ def values(self): """ Return sequence of value elements. Permission -- Always available """ def items(self): """ Return sequence of (key, value) elements. Permission -- Always available """ def get(self, k, default='marker'): """ Return value associated with key k. If k does not exist and default is not marker, return default, else raise KeyError. Permission -- Always available """ def has_key(self, k): """ Return true if item referenced by key k exists. Permission -- Always available """ def clear(self): """ Remove all key/value pairs. Permission -- Always available """ def update(self, d): """ Merge dictionary d into ourselves. Permission -- Always available """ def set(self, k, v): """ Call __setitem__ with key k, value v. Permission -- Always available """ def delete(self, k): """ Call __delitem__ with key k. Permission -- Always available """ class MaxTransientObjectsExceeded: """ An exception importable from the Products.Transience.Transience module which is raised when an attempt is made to add an item to a TransientObjectContainer that is 'full'. This exception may be caught in PythonScripts through a normal import. A successful import of the exception can be achieved via:: from Products.Transience import MaxTransientObjectsExceeded """ zope2.13-2.13.21/source/Zope2/src/Products/Transience/help/Transience-change.stx0000644000175000017500000000735612214017422026137 0ustar arnauarnauTransientObjectContainer - Manage Transient Object Containers A Transient Object Container contains objects which expire after a user-settable period of time. Items placed into transient object must have string keys, but may have any type of value. Common Usages A Transient Object Container is used by Session Data Mangers to store session data. Editing Form - **Id** The Zope id of the Transient Object Container. - **Title** *Optional* The title of the object. - **Data object timeout (in minutes)** The minimum number of minutes that objects in the container will persist for. Objects in the container are passively deleted, so they may not be deleted exactly after this number of minutes elapses. If you change the timeout value, all objects in the transient container will be flushed. If the timeout value is "0", objects will not time out. - **Timeout resolution (in seconds)** Defines what the "resolution" of item timeout is. Setting this higher allows the transience machinery to do fewer "writes" at the expense of causing items to time out later than the "Data object timeout value" by a factor of (at most) this many seconds. This number must divide evenly into the number of timeout seconds ("Data object timeout value" * 60) and cannot be set higher than the timeout value in seconds. - **Maximum number of subobjects ** The maximum number of subobjects that this container may simultaneously hold. Since transient objects normally hold session data, this number is this is the effective limit for the number of simultaneous sessions. If the value is "0", the number of objects addable to the container will be not be artificially limited. This setting is useful to prevent accidental or deliberate denial of service due to RAM shortage if the transient object container is instantiated in a storage which is backed solely by RAM, such as a Temporary Folder. - **Script to call when objects are added** *Optional* The physical path of of a Zope script which will receive notifications when objects are added to the Transient Object Container. Ex: '/path/to/add/script'. For more information, see "Add and Delete Scripts" below. - **Script to call when objects are deleted** *Optional* The physical path of a Zope script which will receive notifications when objects are deleted from the Transient Object Container, either explicitly or through timeout-related expiration. Ex: '/path/to/delete/script' For more information, see "Add and Delete Scripts" below. Add and Delete Scripts Add and Delete scripts are Zope scripts which are called, respectively, when an object is added or removed from a Transient Object Container. An add or delete script is specified by naming it by its full Zope object path with slash separators, e.g. "/path/to/method". Add and delete scripts are called with two arguments. The first argument is the item being added or removed from the container; the second argument is the Transient Object Container itself. The container will be acquisition wrapped, allowing the it to be used as a context to reference other Zope objects. An example of an External Method used as a delete script:: def deleteScript(item, container): from logging import getLogger LOG = getLogger('test') LOG.info('id: %s' % item.getId()) See Also - "Transience API":TransienceInterfaces.py zope2.13-2.13.21/source/Zope2/src/Products/Transience/TransactionHelper.py0000644000175000017500000000171012214017422025114 0ustar arnauarnauimport time class PreventTransactionCommit(Exception): def __init__(self, reason): self. reason = reason def __str__(self): return "Uncommittable transaction: " % self.reason class UncommittableJar: """ A jar that cannot be committed """ def __init__(self, reason): self.reason = reason self.time = time.time() def sortKey(self): return str(id(self)) def tpc_begin(self, *arg, **kw): pass def commit(self, obj, transaction): pass def tpc_vote(self, transaction): raise PreventTransactionCommit(self.reason) def abort(*args): pass class makeTransactionUncommittable: """ - register an uncommittable object with the provided transaction which prevents the commit of that transaction """ def __init__(self, transaction, reason): self._p_jar = UncommittableJar(reason) transaction.register(self) zope2.13-2.13.21/source/Zope2/src/Products/Transience/tests/0000755000175000017500000000000012214017422022260 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Transience/tests/testTransactionHelper.py0000644000175000017500000000227212214017422027162 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import transaction from unittest import TestCase, makeSuite from Products.Transience.TransactionHelper import PreventTransactionCommit, \ makeTransactionUncommittable class TestTransactionHelper(TestCase): def setUp(self): self.t = transaction.get() def tearDown(self): self.t = None def testUncommittable(self): makeTransactionUncommittable(self.t, "test") self.assertRaises(PreventTransactionCommit, transaction.commit) transaction.abort() def test_suite(): suite = makeSuite(TestTransactionHelper, 'test') return suite zope2.13-2.13.21/source/Zope2/src/Products/Transience/tests/testTimeoutRelated.py0000644000175000017500000001041112214017422026456 0ustar arnauarnauimport ZODB # in order to get Persistence.Persistent working import transaction from Testing import makerequest from Products.Transience.Transience import TransientObjectContainer import Products.Transience.Transience import Products.Transience.TransientObject from unittest import TestCase, TestSuite, makeSuite from ZODB.DemoStorage import DemoStorage from OFS.Application import Application import fauxtime import time as oldtime WRITEGRANULARITY = 30 stuff = {} def _getApp(): app = stuff.get('app', None) if not app: ds = DemoStorage() db = ZODB.DB(ds) conn = db.open() root = conn.root() app = Application() root['Application']= app transaction.commit() stuff['app'] = app stuff['conn'] = conn stuff['db'] = db return app def _openApp(): conn = stuff['db'].open() root = conn.root() app = root['Application'] return conn, app def _delApp(): transaction.abort() stuff['conn'].close() del stuff['conn'] del stuff['app'] del stuff['db'] class TestBase(TestCase): def setUp(self): Products.Transience.Transience.time = fauxtime Products.Transience.TransientObject.time = fauxtime Products.Transience.Transience.setStrict(1) self.app = makerequest.makerequest(_getApp()) timeout = self.timeout = 1 sm=TransientObjectContainer( id='sm', timeout_mins=timeout, title='SessionThing', addNotification=addNotificationTarget, delNotification=delNotificationTarget) self.app._setObject('sm', sm) def tearDown(self): transaction.abort() _delApp() del self.app Products.Transience.Transience.time = oldtime Products.Transience.TransientObject.time = oldtime Products.Transience.Transience.setStrict(0) class TestLastAccessed(TestBase): def testLastAccessed(self): sdo = self.app.sm.new_or_existing('TempObject') la1 = sdo.getLastAccessed() # time.time() on Windows has coarse granularity (updates at # 18.2 Hz -- about once each 0.055 seconds). We have to sleep # long enough so that "the next" call to time.time() actually # delivers a larger value. _last_accessed isn't actually updated # unless current time.time() is greater than the last value + # WRITEGRANULARITY. The time() and sleep() are fudged by a # factor of 60, though. The code here used to do # fauxtime.sleep(WRITEGRANULARITY + 1) # and that wasn't enough on Windows. The "+1" only added 1/60th # of a second sleep time in real time, much less than the Windows # time.time() resolution. Rounding up 0.055 to 1 digit and # multiplying by 60 ensures that we'll actually sleep long enough # to get to the next Windows time.time() tick. fauxtime.sleep(WRITEGRANULARITY + 0.06 * 60) sdo = self.app.sm.get('TempObject') self.assert_(sdo.getLastAccessed() > la1) class TestNotifications(TestBase): def testAddNotification(self): self.app.sm.setAddNotificationTarget(addNotificationTarget) sdo = self.app.sm.new_or_existing('TempObject') now = fauxtime.time() k = sdo.get('starttime') self.assertEqual(type(k), type(now)) self.assert_(k <= now) def testDelNotification(self): self.app.sm.setDelNotificationTarget(delNotificationTarget) sdo = self.app.sm.new_or_existing('TempObject') # sleep longer than timeout fauxtime.sleep(self.timeout * 100.0) self.app.sm.get('TempObject') now = fauxtime.time() k = sdo.get('endtime') self.assertEqual(type(k), type(now)) self.assert_(k <= now) def testMissingCallbackGetCallbackReturnsNone(self): # in response to http://zope.org/Collectors/Zope/1403 self.assertEqual(None, self.app.sm._getCallback('/foo/bar/baz')) def addNotificationTarget(item, context): item['starttime'] = fauxtime.time() def delNotificationTarget(item, context): item['endtime'] = fauxtime.time() def test_suite(): last_accessed = makeSuite(TestLastAccessed, 'test') start_end = makeSuite(TestNotifications, 'test') suite = TestSuite((start_end, last_accessed)) return suite zope2.13-2.13.21/source/Zope2/src/Products/Transience/tests/__init__.py0000644000175000017500000000125312214017422024372 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This file is needed to make this a package. zope2.13-2.13.21/source/Zope2/src/Products/Transience/tests/testTransientObjectContainer.py0000644000175000017500000003503612214017422030502 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import random from Products.Transience.Transience import TransientObjectContainer,\ MaxTransientObjectsExceeded, SPARE_BUCKETS from Products.Transience.TransientObject import TransientObject import Products.Transience.Transience import Products.Transience.TransientObject from unittest import TestCase, TestSuite, makeSuite import time as oldtime import fauxtime import slowfauxtime class TestTransientObjectContainer(TestCase): def setUp(self): Products.Transience.Transience.time = fauxtime Products.Transience.TransientObject.time = fauxtime Products.Transience.Transience.setStrict(1) self.errmargin = .20 self.timeout = fauxtime.timeout self.period = 20 self.t = TransientObjectContainer('sdc', timeout_mins=self.timeout/60, period_secs=self.period) def tearDown(self): self.t = None Products.Transience.Transience.time = oldtime Products.Transience.TransientObject.time = oldtime Products.Transience.Transience.setStrict(0) def testGetItemFails(self): self.assertRaises(KeyError, self._getitemfail) def _getitemfail(self): return self.t[10] def testGetReturnsDefault(self): self.assertEqual(self.t.get(10), None) self.assertEqual(self.t.get(10, 'foo'), 'foo') def testSetItemGetItemWorks(self): self.t[10] = 1 a = self.t[10] self.assertEqual(a, 1) def testReplaceWorks(self): self.t[10] = 1 self.assertEqual(self.t[10], 1) self.t[10] = 2 self.assertEqual(self.t[10], 2) def testHasKeyWorks(self): self.t[10] = 1 self.assertTrue(self.t.has_key(10)) def testValuesWorks(self): for x in range(10, 110): self.t[x] = x v = self.t.values() v.sort() self.assertEqual(len(v), 100) i = 10 for x in v: assert x == i i = i + 1 def testKeysWorks(self): for x in range(10, 110): self.t[x] = x v = self.t.keys() v.sort() self.assertEqual(len(v), 100) i = 10 for x in v: self.assertEqual(x, i) i = i + 1 def testItemsWorks(self): for x in range(10, 110): self.t[x] = x v = self.t.items() v.sort() self.assertEquals(len(v), 100) i = 10 for x in v: self.assertEqual(x[0], i) self.assertEqual(x[1], i) i = i + 1 def testDeleteInvalidKeyRaisesKeyError(self): self.assertRaises(KeyError, self._deletefail) def _deletefail(self): del self.t[10] def testRandomNonOverlappingInserts(self): added = {} r = range(10, 110) for x in r: k = random.choice(r) if not added.has_key(k): self.t[k] = x added[k] = 1 addl = added.keys() addl.sort() self.assertEqual(lsubtract(self.t.keys(),addl), []) def testRandomOverlappingInserts(self): added = {} r = range(10, 110) for x in r: k = random.choice(r) self.t[k] = x added[k] = 1 addl = added.keys() addl.sort() self.assertEqual(lsubtract(self.t.keys(), addl), []) def testRandomDeletes(self): r = range(10, 1010) added = [] for x in r: k = random.choice(r) self.t[k] = x added.append(k) deleted = [] for x in r: k = random.choice(r) if self.t.has_key(k): del self.t[k] deleted.append(k) if self.t.has_key(k): print "had problems deleting %s" % k badones = [] for x in deleted: if self.t.has_key(x): badones.append(x) self.assertEqual(badones, []) def testTargetedDeletes(self): r = range(10, 1010) seen = {} for x in r: k = random.choice(r) vals = seen.setdefault(k, []) vals.append(x) self.t[k] = x couldntdelete = {} weird = [] results = {} for x in r: try: ts, item = self.t.__delitem__(x) results[x] = ts, item except KeyError, v: if v.args[0] != x: weird.append(x) couldntdelete[x] = v.args[0] self.assertEqual(self.t.keys(), []) def testPathologicalRightBranching(self): r = range(10, 1010) for x in r: self.t[x] = 1 assert list(self.t.keys()) == r, (self.t.keys(), r) map(self.t.__delitem__, r) self.assertEqual(list(self.t.keys()), []) def testPathologicalLeftBranching(self): r = range(10, 1010) revr = r[:] revr.reverse() for x in revr: self.t[x] = 1 self.assertEqual(list(self.t.keys()), r) map(self.t.__delitem__, revr) self.assertEqual(list(self.t.keys()), []) def testSuccessorChildParentRewriteExerciseCase(self): add_order = [ 85, 73, 165, 273, 215, 142, 233, 67, 86, 166, 235, 225, 255, 73, 175, 171, 285, 162, 108, 28, 283, 258, 232, 199, 260, 298, 275, 44, 261, 291, 4, 181, 285, 289, 216, 212, 129, 243, 97, 48, 48, 159, 22, 285, 92, 110, 27, 55, 202, 294, 113, 251, 193, 290, 55, 58, 239, 71, 4, 75, 129, 91, 111, 271, 101, 289, 194, 218, 77, 142, 94, 100, 115, 101, 226, 17, 94, 56, 18, 163, 93, 199, 286, 213, 126, 240, 245, 190, 195, 204, 100, 199, 161, 292, 202, 48, 165, 6, 173, 40, 218, 271, 228, 7, 166, 173, 138, 93, 22, 140, 41, 234, 17, 249, 215, 12, 292, 246, 272, 260, 140, 58, 2, 91, 246, 189, 116, 72, 259, 34, 120, 263, 168, 298, 118, 18, 28, 299, 192, 252, 112, 60, 277, 273, 286, 15, 263, 141, 241, 172, 255, 52, 89, 127, 119, 255, 184, 213, 44, 116, 231, 173, 298, 178, 196, 89, 184, 289, 98, 216, 115, 35, 132, 278, 238, 20, 241, 128, 179, 159, 107, 206, 194, 31, 260, 122, 56, 144, 118, 283, 183, 215, 214, 87, 33, 205, 183, 212, 221, 216, 296, 40, 108, 45, 188, 139, 38, 256, 276, 114, 270, 112, 214, 191, 147, 111, 299, 107, 101, 43, 84, 127, 67, 205, 251, 38, 91, 297, 26, 165, 187, 19, 6, 73, 4, 176, 195, 90, 71, 30, 82, 139, 210, 8, 41, 253, 127, 190, 102, 280, 26, 233, 32, 257, 194, 263, 203, 190, 111, 218, 199, 29, 81, 207, 18, 180, 157, 172, 192, 135, 163, 275, 74, 296, 298, 265, 105, 191, 282, 277, 83, 188, 144, 259, 6, 173, 81, 107, 292, 231, 129, 65, 161, 113, 103, 136, 255, 285, 289, 1 ] delete_order = [ 276, 273, 12, 275, 2, 286, 127, 83, 92, 33, 101, 195, 299, 191, 22, 232, 291, 226, 110, 94, 257, 233, 215, 184, 35, 178, 18, 74, 296, 210, 298, 81, 265, 175, 116, 261, 212, 277, 260, 234, 6, 129, 31, 4, 235, 249, 34, 289, 105, 259, 91, 93, 119, 7, 183, 240, 41, 253, 290, 136, 75, 292, 67, 112, 111, 256, 163, 38, 126, 139, 98, 56, 282, 60, 26, 55, 245, 225, 32, 52, 40, 271, 29, 252, 239, 89, 87, 205, 213, 180, 97, 108, 120, 218, 44, 187, 196, 251, 202, 203, 172, 28, 188, 77, 90, 199, 297, 282, 141, 100, 161, 216, 73, 19, 17, 189, 30, 258 ] for x in add_order: self.t[x] = 1 for x in delete_order: try: del self.t[x] except KeyError: self.assertFalse(self.t.has_key(x)) def testGetDelaysTimeout(self): for x in range(10, 110): self.t[x] = x # current bucket will become old after we sleep for a while. fauxtime.sleep(self.timeout/2) # these items will be added to the new current bucket by getitem for x in range(10, 110): self.t.get(x) fauxtime.sleep(self.timeout/2) self.assertEqual(len(self.t.keys()), 100) for x in range(10, 110): self.assertEqual(self.t[x], x) def testSetItemDelaysTimeout(self): for x in range(10, 110): self.t[x] = x # current bucket will become old after we sleep for a while. fauxtime.sleep(self.timeout/2) # these items will be added to the new current bucket by setitem for x in range(10, 110): self.t[x] = x + 1 fauxtime.sleep(self.timeout/2) assert len(self.t.keys()) == 100, len(self.t.keys()) for x in range(10, 110): assert self.t[x] == x + 1 def testLen(self): # This test must not time out else it will fail. # make timeout extremely unlikely by setting it very high self.t._setTimeout(self.timeout, self.period) added = {} r = range(10, 1010) for x in r: k = random.choice(r) self.t[k] = x added[k] = x self.assertEqual(len(self.t), len(added)) for k in added.keys(): del self.t[k] self.assertEqual(len(self.t), 0) def testResetWorks(self): self.t[10] = 1 self.t._reset() self.assertFalse(self.t.get(10)) def testGetTimeoutMinutesWorks(self): self.assertEqual(self.t.getTimeoutMinutes(), self.timeout / 60) self.t._setTimeout(10, 30) self.assertEqual(self.t.getTimeoutMinutes(), 10) self.assertEqual(self.t.getPeriodSeconds(), 30) def test_new(self): t = self.t.new('foobieblech') self.assertTrue(issubclass(t.__class__, TransientObject)) def _dupNewItem(self): self.t.new('foobieblech') def test_newDupFails(self): self.t.new('foobieblech') self.assertRaises(KeyError, self._dupNewItem) def test_new_or_existing(self): t = self.t.new('foobieblech') t['hello'] = "Here I am!" t2 = self.t.new_or_existing('foobieblech') self.assertEqual(t2['hello'], "Here I am!") def test_getId(self): self.assertEqual(self.t.getId(), 'sdc') def testSubobjectLimitWorks(self): self.t = TransientObjectContainer('a', timeout_mins=self.timeout/60, limit=10) self.assertRaises(MaxTransientObjectsExceeded, self._maxOut) def testZeroTimeoutMeansPersistForever(self): self.t._setTimeout(0, self.period) self.t._reset() for x in range(10, 110): self.t[x] = x fauxtime.sleep(180) self.assertEqual(len(self.t.keys()), 100) def testGarbageCollection(self): # this is pretty implementation-dependent :-( for x in range(0, 100): self.t[x] = x sleeptime = self.period * SPARE_BUCKETS fauxtime.sleep(sleeptime) self.t._invoke_finalize_and_gc() max_ts = self.t._last_finalized_timeslice() keys = list(self.t._data.keys()) for k in keys: self.assert_(k > max_ts, "k %s < max_ts %s" % (k, max_ts)) def _maxOut(self): for x in range(11): self.t.new(str(x)) class TestSlowTransientObjectContainer(TestCase): def setUp(self): Products.Transience.Transience.time = slowfauxtime Products.Transience.TransientObject.time = slowfauxtime Products.Transience.Transience.setStrict(1) self.errmargin = .20 self.timeout = 120 self.period = 20 self.t = TransientObjectContainer('sdc', timeout_mins=self.timeout/60, period_secs=self.period) def tearDown(self): self.t = None Products.Transience.Transience.time = oldtime Products.Transience.TransientObject.time = oldtime Products.Transience.Transience.setStrict(0) def testChangingTimeoutWorks(self): # TODO: This test is slooooow # 1 minute for x in range(10, 110): self.t[x] = x slowfauxtime.sleep(self.timeout * (self.errmargin + 1)) self.assertEqual(len(self.t.keys()), 0) # 2 minutes self.t._setTimeout(self.timeout/60*2, self.period) self.t._reset() for x in range(10, 110): self.t[x] = x slowfauxtime.sleep(self.timeout) self.assertEqual(len(self.t.keys()), 100) slowfauxtime.sleep(self.timeout * (self.errmargin+1)) self.assertEqual(len(self.t.keys()), 0) # 3 minutes self.t._setTimeout(self.timeout/60*3, self.period) self.t._reset() for x in range(10, 110): self.t[x] = x slowfauxtime.sleep(self.timeout) self.assertEqual(len(self.t.keys()), 100) slowfauxtime.sleep(self.timeout) self.assertEqual(len(self.t.keys()), 100) slowfauxtime.sleep(self.timeout * (self.errmargin+1)) self.assertEqual(len(self.t.keys()), 0) def testItemsGetExpired(self): for x in range(10, 110): self.t[x] = x # these items will time out while we sleep slowfauxtime.sleep(self.timeout * (self.errmargin+1)) for x in range(110, 210): self.t[x] = x self.assertEqual(len(self.t.keys()), 100) # call _gc just to make sure __len__ gets changed after a gc #self.t._gc() self.assertEqual(len(self.t), 100) # we should still have 100 - 199 for x in range(110, 210): self.assertEqual(self.t[x], x) # but we shouldn't have 0 - 100 for x in range(10, 110): try: self.t[x] except KeyError: pass else: assert 1 == 2, x def lsubtract(l1, l2): l1=list(l1) l2=list(l2) l = filter(lambda x, l1=l1: x not in l1, l2) l = l + filter(lambda x, l2=l2: x not in l2, l1) return l def test_suite(): suite = TestSuite() suite.addTest(makeSuite(TestTransientObjectContainer)) suite.addTest(makeSuite(TestSlowTransientObjectContainer)) return suite zope2.13-2.13.21/source/Zope2/src/Products/Transience/tests/testTransientObject.py0000644000175000017500000001102712214017422026631 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from Products.Transience.Transience import TransientObjectContainer import Products.Transience.TransientObject import Products.Transience.Transience from unittest import TestCase, TestSuite, makeSuite import time as oldtime import fauxtime class TestTransientObject(TestCase): def setUp(self): Products.Transience.Transience.time = fauxtime Products.Transience.TransientObject.time = fauxtime Products.Transience.Transience.setStrict(1) self.errmargin = .20 self.timeout = fauxtime.timeout self.t = TransientObjectContainer('sdc', timeout_mins=self.timeout/60) def tearDown(self): Products.Transience.Transience.time = oldtime Products.Transience.TransientObject.time = oldtime Products.Transience.Transience.setStrict(0) self.t = None del self.t def test_id(self): t = self.t.new('xyzzy') self.assertNotEqual(t.getId(), 'xyzzy') # dont acquire self.assertEqual(t.getContainerKey(), 'xyzzy') def test_validate(self): t = self.t.new('xyzzy') self.assert_(t.isValid()) t.invalidate() self.assertFalse(t.isValid()) def test_getLastAccessed(self): t = self.t.new('xyzzy') ft = fauxtime.time() self.assert_(t.getLastAccessed() <= ft) def test_getCreated(self): t = self.t.new('xyzzy') ft = fauxtime.time() self.assert_(t.getCreated() <= ft) def test_getLastModifiedUnset(self): t = self.t.new('xyzzy') self.assertEqual(t.getLastModified(), None) def test_getLastModifiedSet(self): t = self.t.new('xyzzy') t['a'] = 1 self.assertNotEqual(t.getLastModified(), None) def testSetLastModified(self): t = self.t.new('xyzzy') t.setLastModified() self.assertNotEqual(t.getLastModified(), None) def test_setLastAccessed(self): t = self.t.new('xyzzy') ft = fauxtime.time() self.assert_(t.getLastAccessed() <= ft) fauxtime.sleep(self.timeout * 2) # go to sleep past the granularity ft2 = fauxtime.time() t.setLastAccessed() ft3 = fauxtime.time() self.assert_(t.getLastAccessed() <= ft3) self.assert_(t.getLastAccessed() >= ft2) def _genKeyError(self, t): return t.get('foobie') def _genLenError(self, t): return t.len() def test_dictionaryLike(self): t = self.t.new('keytest') t.update(data) self.assertEqual(t.keys(), data.keys()) self.assertEqual(t.values(), data.values()) self.assertEqual(t.items(), data.items()) for k in data.keys(): self.assertEqual(t.get(k), data.get(k)) self.assertEqual(t.get('foobie'), None) self.assertRaises(AttributeError, self._genLenError, t) self.assertEqual(t.get('foobie',None), None) self.assert_(t.has_key('a')) self.assertFalse(t.has_key('foobie')) t.clear() self.assertEqual(len(t.keys()), 0) def test_TTWDictionary(self): t = self.t.new('mouthfultest') t.set('foo', 'bar') self.assertEqual(t['foo'], 'bar') self.assertEqual(t.get('foo'), 'bar') t.set('foobie', 'blech') t.delete('foobie') self.assertEqual(t.get('foobie'), None) def test_repr_leaking_information(self): # __repr__ used to show all contents, which could lead to sensitive # information being visible in e.g. the ErrorLog object. t = self.t.new('password-storing-session') t.set('__ac_password__', 'secret') self.assertFalse( repr(t).find('secret') != -1 , '__repr__ leaks: %s' % repr(t) ) def test_suite(): testsuite = makeSuite(TestTransientObject, 'test') alltests = TestSuite((testsuite,)) return alltests data = { 'a': 'a', 1: 1, 'Mary': 'no little lamb for you today!', 'epoch': 999999999, 'fauxtime': fauxtime } zope2.13-2.13.21/source/Zope2/src/Products/Transience/tests/slowfauxtime.py0000644000175000017500000000044212214017422025361 0ustar arnauarnauimport time as origtime epoch = origtime.time() def time(): """ False timer -- returns time 60 x faster than normal time """ return (origtime.time() - epoch) * 60 def sleep(duration): """ False sleep -- sleep for 1/60 the time specifed """ origtime.sleep(duration / 60) zope2.13-2.13.21/source/Zope2/src/Products/Transience/tests/fauxtime.py0000644000175000017500000000065212214017422024457 0ustar arnauarnauimport sys import time as origtime epoch = origtime.time() resolution = 120.0 timeout = 30 if sys.platform[:3].lower() == "win": resolution = 60.0 timeout = 60 def time(): """ False timer -- returns time R x faster than normal time """ return (origtime.time() - epoch) * resolution def sleep(duration): """ False sleep -- sleep for 1/R the time specifed """ origtime.sleep(duration / resolution) zope2.13-2.13.21/source/Zope2/src/Products/Transience/dtml/0000755000175000017500000000000012214017422022056 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/Transience/dtml/manageTransientObjectContainer.dtml0000644000175000017500000000721312214017422031055 0ustar arnauarnau

    Transient Object Containers are used to store transient data. Transient data will persist, but only for a user-specified period of time (the "data object timeout") after which it will be flushed.

    1 item is in this transient object container. &dtml-l; items are in this transient object container. There are no items in this transient object container.

    Title
    Data object timeout value (in minutes)
    ("0" means no expiration)
    Timeout resolution (in seconds)
    Defines what the "resolution" of item timeout is. Setting this higher allows the transience machinery to do fewer "writes" at the expense of causing items to time out later than the "Data object timeout value" by a factor of (at most) this many seconds. This number must divide evenly into the number of timeout seconds ("Data object timeout value" * 60) and cannot be set higher than the timeout value in seconds.
    Maximum number of subobjects
    ("0" means infinite)
    Script to call when objects are added
    (e.g. "/somefolder/addScript")
    Script to call when objects are deleted
    (e.g. "/somefolder/delScript")

    WARNING! All data objects existing in this transient object container will be deleted when the data object timeout or expiration resolution is changed.

    zope2.13-2.13.21/source/Zope2/src/Products/Transience/dtml/addTransientObjectContainer.dtml0000644000175000017500000000642512214017422030361 0ustar arnauarnau

    Transient Object Containers are used to store transient data. Transient data will persist, but only for a user-specified period of time, (the "data object timeout") after which it will be flushed.

    It is recommended that Transient Object Containers be added to storages which do not support undo operations; transient objects are write-intensive; their use may cause many undoable transactions, potentially bloating undoing ZODB databases.

    Transient Object Containers support Add and Delete Scripts which are methods which are invoked when transient objects are added or deleted from the container. A add/delete script is invoked with the item being operated upon and the transient object container as arguments. Specify the Zope physical path to the method to be invoked to receive the notification (e.g. '/folder/add_notifier').

    Id
    Title (optional)
    Data object timeout (in minutes)
    ("0" means no expiration)
    Timeout resolution (in seconds)
    (accept the default if you're not sure)
    Maximum number of subobjects
    ("0" means infinite)
    Script to call upon object add (optional)
    (e.g. "/somefolder/addScript")
    Script to call upon object delete (optional)
    (e.g. "/somefolder/delScript")

    zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/0000755000175000017500000000000012214017422021556 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/PageTemplate.py0000644000175000017500000001076112214017422024505 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Page Template module """ import sys import ExtensionClass import zope.pagetemplate.pagetemplate from zope.pagetemplate.pagetemplate import _error_start, PTRuntimeError from zope.pagetemplate.pagetemplate import PageTemplateTracebackSupplement from zope.tales.expressions import SimpleModuleImporter from Products.PageTemplates.Expressions import getEngine class PageTemplate(ExtensionClass.Base, zope.pagetemplate.pagetemplate.PageTemplate): def pt_getEngine(self): return getEngine() def pt_getContext(self): c = {'template': self, 'options': {}, 'nothing': None, 'request': None, 'modules': SimpleModuleImporter(), } parent = getattr(self, 'aq_parent', None) if parent is not None: c['here'] = parent c['context'] = parent c['container'] = self.aq_inner.aq_parent while parent is not None: self = parent parent = getattr(self, 'aq_parent', None) c['root'] = self return c @property def macros(self): return self.pt_macros() # sub classes may override this to do additional stuff for macro access def pt_macros(self): self._cook_check() if self._v_errors: __traceback_supplement__ = (PageTemplateTracebackSupplement, self, {}) raise PTRuntimeError, ( 'Page Template %s has errors: %s' % ( self.id, self._v_errors )) return self._v_macros # these methods are reimplemented or duplicated here because of # different call signatures in the Zope 2 world def pt_render(self, source=False, extra_context={}): c = self.pt_getContext() c.update(extra_context) debug = getattr(c['request'], 'debug', None) if debug is not None: showtal = getattr(debug, 'showTAL', False) sourceAnnotations = getattr(debug, 'sourceAnnotations', False) else: showtal = sourceAnnotations = False if source: showtal = True return super(PageTemplate, self).pt_render(c, source=source, sourceAnnotations=sourceAnnotations, showtal=showtal) def pt_errors(self, namespace={}, check_macro_expansion=None): # The check_macro_expansion argument is added for # compatibility with zope.pagetemplate 4.0.0. The argument is # ignored. See LP #732972. self._cook_check() err = self._v_errors if err: return err try: self.pt_render(source=True, extra_context=namespace) except: return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2]) def __call__(self, *args, **kwargs): if not kwargs.has_key('args'): kwargs['args'] = args return self.pt_render(extra_context={'options': kwargs}) def read(self): self._cook_check() if not self._v_errors: if not self.expand: return self._text try: return self.pt_render(source=True) except: return ('%s\n Macro expansion failed\n %s\n-->\n%s' % (_error_start, "%s: %s" % sys.exc_info()[:2], self._text) ) return ('%s\n %s\n-->\n%s' % (_error_start, '\n '.join(self._v_errors), self._text)) # convenience method for the ZMI which allows to explicitly # specify the HTMLness of a template. The old Zope 2 # implementation had this as well, but arguably on the wrong class # (this should be a ZopePageTemplate thing if at all) def html(self): if not hasattr(getattr(self, 'aq_base', self), 'is_html'): return self.content_type == 'text/html' return self.is_html zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/configure.zcml0000644000175000017500000000040512214017422024425 0ustar arnauarnau zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/examples/0000755000175000017500000000000012214017422023374 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/examples/index.xml0000644000175000017500000000241712214017422025231 0ustar arnauarnau This is a set of examples of the use of page templates. It includes examples of batching, macros, and trees. zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/examples/zpt_examples.zexp0000644000175000017500000007420212214017422027024 0ustar arnauarnauZEXPìˆ((U OFS.FolderqUFolderqtqNt.}q(U_objectsq(}q(U meta_typeqUFolderqUidq Udataq u}q (U meta_typeq UScript (Python)q UidqU simple_treequ}q(U meta_typeqU Page TemplateqUidqU master_pagequ}q(U meta_typeqUScript (Python)qUidqUimage_batches_errorqu}q(U meta_typeqU Page TemplateqUidqU index_htmlqu}q(hhhUimage_batches.ptq u}q!(hUScript (Python)q"hU image_batchesq#u}q$(U meta_typeq%U Page Templateq&Uidq'U use_master.ptq(u}q)(h%h&h'U tree_menu.ptq*u}q+(h%h&h'U show_text.ptq,uth U zpt_examplesq-Udataq.(Uïq/(hUFolderq0ttQU master_pageq1(Uíq2(U'Products.PageTemplates.ZopePageTemplateq3UZopePageTemplateq4ttQU__ac_local_roles__q5}q6Uevanq7]q8UOwnerq9ash((Uñq:(h3UZopePageTemplateq;ttQU_ownerq<(]q=U acl_usersq>ah7th(Uðq?(U#Products.PythonScripts.PythonScriptq@U PythonScriptqAttQh*(UóqB(h3UZopePageTemplateqCttQU image_batchesqD(UqE(h@U PythonScriptqFttQh (UòqG(h3UZopePageTemplateqHttQh,(UîqI(h3UZopePageTemplateqJttQUtitleqKUPage Template ExamplesqLU index_htmlqM(UqN(h3UZopePageTemplateqOttQh(UôqP(h@U PythonScriptqQttQu.ïH((U OFS.FolderqUFolderqtqNt.}q(UidqUdataqU__ac_local_roles__q}qUevanq ]q UOwnerq asU_objectsq (}q (U meta_typeqU DTML MethodqUidqUmethod 1qu}q(hhhUmethod 2qu}q(U meta_typeqUImageqUidqUimage 1qu}q(hhhUimage 2qu}q(hhhUimage 3qu}q(hhhUimage 4qu}q(hhhUimage 5q u}q!(hhhUimage 6q"u}q#(hhhUimage 7q$u}q%(hhhUimage 8q&u}q'(hUFolderq(hU subfolderq)uth&(Uõq*(U OFS.Imageq+UImageq,tq-tq.Qh$(Uöq/(h+UImageq0tq1tq2Qh"(U÷q3(h+UImageq4tq5tq6Qh (Uøq7(h+UImageq8tq9tq:Qh(Uùq;(h+UImageqQh(Uúq?(h+UImageq@tqAtqBQh(UûqC(h+UImageqDtqEtqFQh(UüqG(h+UImageqHtqItqJQh(UýqK(UOFS.DTMLMethodqLU DTMLMethodqMtqNtqOQh(UþqP(hLU DTMLMethodqQtqRtqSQh)(UÿqT(hUFolderqUtqVtqWQUtitleqXUu.í°((U'Products.PageTemplates.ZopePageTemplateqUZopePageTemplateqtqNt.}q(UexpandqKUidqU master_pageqU__ac_local_roles__q}q Uevanq ]q UOwnerq asU _bind_namesq (cShared.DC.Scripts.Bindings NameAssignments qoq}qU_asgnsq}qU name_subpathqUtraverse_subpathqssbU_textqTV The title

    Click here to return to the index page, or here to view the template source.

    This is Page Template .

    Plug some text in here.

    qU content_typeqU text/htmlqUtitleqU Master Pagequ.ñÒ((U'Products.PageTemplates.ZopePageTemplateqUZopePageTemplateqtqNt.}q(UexpandqKUidqU use_master.ptqU__ac_local_roles__q}q Uevanq ]q UOwnerq asU _bind_namesq (cShared.DC.Scripts.Bindings NameAssignments qoq}qU_asgnsq}qU name_subpathqUtraverse_subpathqssbU_textqTq The title

    Click here to return to the index page.

    Totally Different Text.

    Plug some text in this slot.

    Plug some text in here too.

    qU content_typeqU text/htmlqUtitleqUUses Master Pagequ.ð((U#Products.PythonScripts.PythonScriptqU PythonScriptqtqNt.}q(U func_codeq(cShared.DC.Scripts.Signature FuncCode qoq}q(U co_varnamesq (Uerrorq U_getitemq U_getattrq U_printq tqU co_argcountqKubU_paramsqUerrorqUidqUimage_batches_errorqU__ac_local_roles__q}qUevanq]qUOwnerqasU _bind_namesq(cShared.DC.Scripts.Bindings NameAssignments qoq}qU_asgnsq}q(Uname_containerqU containerq U name_m_selfq!Uscriptq"Uname_nsq#Unsq$U name_contextq%Ucontextq&usbU func_defaultsq'NU_codeq(T¥csd„ZdS(Ncst}t}tƒ}do>|dIJ|d||tdƒdƒIJ|ƒSn||dƒ||dƒ‚dS(NisC

    You are not authorized to view one or more of these images

    s/Click here to skip them.sREQUESTsURLs error_types error_value(s _getitem_s_getitems _getattr_s_getattrs_print_s_prints containerserror(serrors_getitems_getattrs_print((sScript (Python)simage_batches_errors #(simage_batches_error(((sScript (Python)ssq)U_bodyq*T if 1: #error.error_type == 'Unauthorized': print '

    You are not authorized to view one or more of these images

    ' print 'Click here to skip them.' % container.REQUEST['URL'] return printed raise error.error_type, error.error_value q+U Script_magicq,KUwarningsq-)Uerrorsq.)U Python_magicq/U*ë q0u.ó ð((U'Products.PageTemplates.ZopePageTemplateqUZopePageTemplateqtqNt.}q(UexpandqKUidqU tree_menu.ptqU__ac_local_roles__q}q Uevanq ]q UOwnerq asU _bind_namesq (cShared.DC.Scripts.Bindings NameAssignments qoq}qU_asgnsq}qU name_subpathqUtraverse_subpathqssbU_textqT¢ The title

    Click here to return to the index page, or here to view the template source.

    qU content_typeqU text/htmlqUtitleqUu.Š((U#Products.PythonScripts.PythonScriptqU PythonScriptqtqNt.}q(U func_codeq(cShared.DC.Scripts.Signature FuncCode qoq}q(U co_varnamesq (U_getattrq U url_queryq UBatchq Ureqq Ub_startqUimagesqUbatchqUb_urlqtU co_argcountqKubU_paramsqUUidqU image_batchesqU__ac_local_roles__q}qUevanq]qUOwnerqasU _bind_namesq(cShared.DC.Scripts.Bindings NameAssignments qoq}qU_asgnsq}q (Uname_containerq!U containerq"U name_subpathq#Utraverse_subpathq$U name_m_selfq%Uscriptq&U name_contextq'Ucontextq(usbU func_defaultsq)NU_codeq*T?csd„ZdS(Nc4sÒt}dkl}l}|tdƒ}t||dƒddƒƒ}||tdƒdƒdƒ} ||d |d d d ||dƒd ƒƒ} ||ddƒ}h|d<|dsq+U_bodyq,TÂfrom ZTUtils import url_query, Batch req = container.REQUEST # Get the starting offset b_start = int(req.get('b_start', 0)) # Make a batch of image objects, 3 at a time images = container.data.objectValues('Image') batch = Batch(images, 3, b_start, orphan=1, skip_unauthorized=req.get('perm')) # Make a batch URL that is the same as the REQUEST URL without 'b_start' b_url = url_query(req, omit='b_start') return {'batch': batch, 'b_url': b_url} q-U Script_magicq.KUwarningsq/)Uerrorsq0)U Python_magicq1U*ë q2u.ò¶((U'Products.PageTemplates.ZopePageTemplateqUZopePageTemplateqtqNt.}q(UexpandqKUidqUimage_batches.ptqU__ac_local_roles__q}q Uevanq ]q UOwnerq asU _bind_namesq (cShared.DC.Scripts.Bindings NameAssignments qoq}qU_asgnsq}qU name_subpathqUtraverse_subpathqssbU_textqTQ The title
    Previous n

    A) Image Id:

    Next n
    qU content_typeqU text/htmlqUtitleqUBatches of Imagesqu.îZ((U'Products.PageTemplates.ZopePageTemplateqUZopePageTemplateqtqNt.}q(UexpandqKUidqU show_text.ptqU__ac_local_roles__q}q Uevanq ]q UOwnerq asU _bind_namesq (cShared.DC.Scripts.Bindings NameAssignments qoq}qU_asgnsq}qU name_subpathqUtraverse_subpathqssbU_textqT 

    Here is some DTML

    DTML Method 1 of n: "Id"
    Source
    Source
    Rendered
    Rendered
    qU content_typeqU text/htmlqUtitleqUu.d((U'Products.PageTemplates.ZopePageTemplateqUZopePageTemplateqtqNt.}q(UexpandqKUidqU index_htmlqU__ac_local_roles__q}q Uevanq ]q UOwnerq asU _bind_namesq (cShared.DC.Scripts.Bindings NameAssignments qoq}qU_asgnsq}qU name_subpathqUtraverse_subpathqssbU_textqT The title

    content title or id optional template id

    Here are some examples of Zope Page Template use:
    Image Batches This example displays images in batches of three. One of the images has View permission turned off, so this also demonstrates error handling and skip_unauthorized.
    Macro Usage This example uses a full-page macro, and uses a paragraph macro twice.
    Object Tree This example uses the SimpleTreeMaker to simulate the ZMI left panel tree.
    Object Access and HTML This example accesses several objects and shows the difference between structure and text insertion.
    qU content_typeqU text/htmlqUtitleqUIndexqu.ô ((U#Products.PythonScripts.PythonScriptqU PythonScriptqtqNt.}q(U func_codeq(cShared.DC.Scripts.Signature FuncCode qoq}q(U co_varnamesq (U tree_rootq Utree_preq U_getattrq USimpleTreeMakerq UtmqUtreeqUrowsqtU co_argcountqKubU_paramsqUtree_root, tree_preqUidqU simple_treeqU__ac_local_roles__q}qUevanq]qUOwnerqasU _bind_namesq(cShared.DC.Scripts.Bindings NameAssignments qoq}qU_asgnsq}q (Uname_containerq!U containerq"U name_subpathq#Utraverse_subpathq$U name_m_selfq%Uscriptq&U name_contextq'Ucontextq(usbU func_defaultsq)NU_codeq*Txcsd„ZdS(NcsŒt}dkl}||ƒ}||dƒdƒ||dƒ|ƒ\}}||dƒdƒh|d<|dsq+U_tq,(hN(KKKKTht }|ƒt!}t"}t#}klllklld|}d|}||ƒ}  |t ƒi }  || ƒi|ƒ}  | oA ||| ƒiƒi|ƒ}  | o|| dƒ\} }}|| t|ƒƒ\} \}}|tjonª| djo[||ƒ|tjo ht|<||ƒ|Ureqq?Ugetq@UstateqAUformqBUsetstqCUstqDUpnqEUexpidqFUintqGUmUobidqHUNoneqIUtreeqJU tree_rootqKUflatqLh1URESPONSEqMU setCookieqNUpopqOU $loop_watcherqPU $read_guardqQU $write_guardqRU$guardqSt(hKh9h3h4h5h7h8h:h;h

    This is the Document in the Folder. The request argument is &dtml-reqarg;!

    qU_varsq}qu.þ]((UOFS.DTMLMethodqU DTMLMethodqtqNt.}q(U__ac_local_roles__q}qUevanq]qUOwnerq asU__name__q Umethod 2q Utitleq U With Titleq U#_View_management_screens_Permissionq]qU AnonymousqaUglobalsq}qUrawqUt

    I was called by .

    qU_varsq}qu.ÿ((U OFS.FolderqUFolderqtqNt.}q(UidqU subfolderqU__ac_local_roles__q}qUevanq ]q UOwnerq asU_objectsq (}q (U meta_typeqUFolderqhUsubsub 1qu}q(hhhUsubsub 2quth(Uq(hUFolderqtqtqQh(Uq(hUFolderqtqtqQUtitleqUu.Ý((U OFS.FolderqUFolderqtqNt.}q(UtitleqUUsubsub 1q(Uq(hUFolderqtq tq QUidq Usubsub 2q U__ac_local_roles__q }qUevanq]qUOwnerqasU_objectsq(}q(U meta_typeqUFolderqUidqUsubsub 1qutqu.((U OFS.FolderqUFolderqtqNt.}q(Usubsub 1q(Uq(hUFolderqttqQUidq hU__ac_local_roles__q }q Uevanq ]q UOwnerqasUsubsub 2q(Uq(hUFolderqtqtqQU_objectsq(}q(U meta_typeqUFolderqh Usubsub 1qu}q(hhh Usubsub 2qutUtitleqUu.((U OFS.FolderqUFolderqtqNt.}q(Usubsub 1q(Uq(hUFolderqtqtq QUidq hU__ac_local_roles__q }q Uevanq ]qUOwnerqasUsubsub 2q(Uq(hUFolderqtqtqQU_objectsq(}q(U meta_typeqUFolderqh Usubsub 1qu}q(hhh Usubsub 2qutUtitleqUu.r((U OFS.FolderqUFolderqtqNt.}q(UtitleqUUidqUsubsub 1qU__ac_local_roles__q}q Uevanq ]q UOwnerq asu.((U OFS.FolderqUFolderqtqNt.}q(Usubsub 1q(Uq(hUFolderqtqtq QUidq Usubsub 2q U__ac_local_roles__q }q Uevanq]qUOwnerqasUsubsub 2q(Uq(hUFolderqtqtqQU_objectsq(}q(U meta_typeqUFolderqUidqUsubsub 1qu}q(hhhUsubsub 2qutUtitleqUu.r((U OFS.FolderqUFolderqtqNt.}q(UtitleqUUidqUsubsub 1qU__ac_local_roles__q}q Uevanq ]q UOwnerq asu.((U OFS.FolderqUFolderqtqNt.}q(UidqUsubsub 2qU__ac_local_roles__q}qUevanq ]q UOwnerq asU_objectsq (}q (U meta_typeqUFolderqUidqUsubsub 1qu}q(hhhUsubsub 2qutUsubsub 2q(U q(hUFolderqtqtqQUsubsub 1q(U q(hUFolderqtqtqQUtitleqUu.((U OFS.FolderqUFolderqtqNt.}q(Usubsub 1q(U q(hUFolderqtqtq QUidq hU__ac_local_roles__q }q Uevanq ]qUOwnerqasUsubsub 2q(U q(hUFolderqtqtqQU_objectsq(}q(U meta_typeqUFolderqh Usubsub 1qu}q(hhh Usubsub 2qutUtitleqUu.r((U OFS.FolderqUFolderqtqNt.}q(UtitleqUUidqUsubsub 2qU__ac_local_roles__q}q Uevanq ]q UOwnerq asu. r((U OFS.FolderqUFolderqtqNt.}q(UtitleqUUidqUsubsub 2qU__ac_local_roles__q}q Uevanq ]q UOwnerq asu. ((U OFS.FolderqUFolderqtqNt.}q(UidqUsubsub 1qU__ac_local_roles__q}qUevanq ]q UOwnerq asU_objectsq (}q (U meta_typeqUFolderqhUsubsub 1qu}q(hhhUsubsub 2qutUsubsub 2q(U q(hUFolderqtqtqQh(Uq(hUFolderqtqtqQUtitleqUu. r((U OFS.FolderqUFolderqtqNt.}q(UtitleqUUidqUsubsub 1qU__ac_local_roles__q}q Uevanq ]q UOwnerq asu. r((U OFS.FolderqUFolderqtqNt.}q(UtitleqUUidqUsubsub 2qU__ac_local_roles__q}q Uevanq ]q UOwnerq asu. r((U OFS.FolderqUFolderqtqNt.}q(UtitleqUUidqUsubsub 2qU__ac_local_roles__q}q Uevanq ]q UOwnerq asu.r((U OFS.FolderqUFolderqtqNt.}q(UtitleqUUidqUsubsub 1qU__ac_local_roles__q}q Uevanq ]q UOwnerq asu.ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/unicodeconflictresolver.py0000644000175000017500000001000212214017422027053 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Unicode conflict resolution. """ import sys from Acquisition import aq_get from Products.PageTemplates.interfaces import IUnicodeEncodingConflictResolver from zope.interface import implements from zope.i18n.interfaces import IUserPreferredCharsets default_encoding = sys.getdefaultencoding() class DefaultUnicodeEncodingConflictResolver: """ This resolver implements the old-style behavior and will raise an exception in case of the string 'text' can't be converted propertly to unicode. """ implements(IUnicodeEncodingConflictResolver) def resolve(self, context, text, expression): return unicode(text) DefaultUnicodeEncodingConflictResolver = DefaultUnicodeEncodingConflictResolver() class Z2UnicodeEncodingConflictResolver: """ This resolver tries to lookup the encoding from the 'management_page_charset' property and defaults to sys.getdefaultencoding(). """ implements(IUnicodeEncodingConflictResolver) def __init__(self, mode='strict'): self.mode = mode def resolve(self, context, text, expression): try: return unicode(text) except UnicodeDecodeError: encoding = getattr(context, 'management_page_charset', default_encoding) return unicode(text, encoding, self.mode) class PreferredCharsetResolver: """ A resolver that tries use the encoding information from the HTTP_ACCEPT_CHARSET header. """ implements(IUnicodeEncodingConflictResolver) def resolve(self, context, text, expression): request = aq_get(context, 'REQUEST', None) # Deal with the fact that a REQUEST is not always available. # In this case fall back to the encoding of the ZMI and the # Python default encoding. if request is None: charsets = [default_encoding] management_charset = getattr(context, 'management_page_charset', None) if management_charset: charsets.insert(0, management_charset) else: # charsets might by cached within the request charsets = getattr(request, '__zpt_available_charsets', None) # No uncached charsets found: investigate the HTTP_ACCEPT_CHARSET # header. This code is only called if 'context' has a request # object. The condition is true because otherwise 'charsets' contains # at least the default encoding of Python. if charsets is None: charsets = list() # add Python's default encoding as last fallback charsets.append(default_encoding) # include the charsets based on the HTTP_ACCEPT_CHARSET # header charsets = IUserPreferredCharsets(request).getPreferredCharsets() +\ charsets # cache list of charsets request.__zpt_available_charsets = charsets for enc in charsets: if enc == '*': continue try: return unicode(text, enc) except (LookupError, UnicodeDecodeError): pass return text StrictUnicodeEncodingConflictResolver = Z2UnicodeEncodingConflictResolver('strict') ReplacingUnicodeEncodingConflictResolver = Z2UnicodeEncodingConflictResolver('replace') IgnoringUnicodeEncodingConflictResolver = Z2UnicodeEncodingConflictResolver('ignore') PreferredCharsetResolver = PreferredCharsetResolver() zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/PageTemplateFile.py0000644000175000017500000001665412214017422025314 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import os from logging import getLogger from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.SecurityManagement import getSecurityManager from App.Common import package_home from App.special_dtml import DTMLFile from App.config import getConfiguration from Acquisition import aq_parent, aq_inner, aq_get from ComputedAttribute import ComputedAttribute from OFS.SimpleItem import SimpleItem from OFS.Traversable import Traversable from Shared.DC.Scripts.Script import Script from Shared.DC.Scripts.Signature import FuncCode from Products.PageTemplates.Expressions import SecureModuleImporter from Products.PageTemplates.PageTemplate import PageTemplate from zope.contenttype import guess_content_type from zope.pagetemplate.pagetemplatefile import sniff_type LOG = getLogger('PageTemplateFile') def guess_type(filename, text): # check for XML ourself since guess_content_type can't # detect text/xml if 'filename' won't end with .xml # XXX: fix this in zope.contenttype if text.startswith('Header

    Tabs

    Title
    Content-Type
    Last Modified
    1/1/2000
    Browse HTML source Browse XML source
    Expand macros when editing
    Errors
    errors
    Locked by WebDAV   

    You can upload the text for using the following form. Choose an existing HTML or XML file from your local computer by clicking browse. You can also click context to view or download the current text.

    File  
    Encoding  
    (only used for non-XML and non-XHTML content)
    Locked by WebDAV

    Footer

    zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/www/ptAdd.zpt0000644000175000017500000000321712214017422024200 0ustar arnauarnau

    Header

    Form Title

    Page Templates allow you to use simple HTML or XML attributes to create dynamic templates. You may choose to upload the template text from a local file by typing the file name or using the browse button.

    Id
    File
    Encoding
    (only used for non-XML and non-HTML content)

    Footer

    zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/www/exclamation.gif0000644000175000017500000000032212214017422025372 0ustar arnauarnauGIF89a „þþþQQþyyþƒƒþ¨¨þµµþÊÊþÒÒþÛÛþââþææþêêþ99þîîþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù , Oà#Š’ ʨBK,ªX@ À1ì 3‚]`àˆ!ÂBQe KÕ€ç‹9‚eì±@ ¢±Ã ±-o†<2 åé®Pf fÎÌ;zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/www/zpt.gif0000644000175000017500000000020212214017422023700 0ustar arnauarnauGIF89a¢ÿÿÿÌÿÌÀÀÀPPPÿÿÿ!ù,GX:Ü®° @+˜#J[ç-†yuD ®A…pÁ® „³êZwšï•^®Å¹Ér5ᯃ•€ã¢ÆM©Q«DÀíz¥ ˜xœ;zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/www/default.html0000644000175000017500000000071212214017422024714 0ustar arnauarnau The title

    content title or id optional template title

    This is Page Template template id. zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/interfaces.py0000644000175000017500000000230512214017422024253 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from zope.interface import Interface class IUnicodeEncodingConflictResolver(Interface): """ A utility that tries to convert a non-unicode string into a Python unicode by implementing some policy in order to figure out a possible encoding - either through the calling context, the location or the system environment """ def resolve(context, text, expression): """ Returns 'text' as unicode string. 'context' is the current context object. 'expression' is the original expression (can be used for logging purposes) """ zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/utils.py0000644000175000017500000000517712214017422023302 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Some helper methods """ import re xml_preamble_reg = re.compile(r'^<\?xml.*?encoding="(.*?)".*?\?>', re.M) http_equiv_reg = re.compile(r'(]*?http\-equiv[^>]*?content-type.*?>)', re.I|re.M|re.S) http_equiv_reg2 = re.compile(r'charset.*?=.*?(?P[\w\-]*)', re.I|re.M|re.S) def encodingFromXMLPreamble(xml): """ Extract the encoding from a xml preamble. Return 'utf-8' if not available """ mo = xml_preamble_reg.match(xml) if not mo: return 'utf-8' else: return mo.group(1).lower() def charsetFromMetaEquiv(html): """ Return the value of the 'charset' from a html document containing tag mo = http_equiv_reg.search(html) if mo: # extract the meta tag meta = mo.group(1) # search for the charset value mo = http_equiv_reg2.search(meta) if mo: # return charset return mo.group(1).lower() return None def convertToUnicode(source, content_type, preferred_encodings): """ Convert 'source' to unicode. Returns (unicode_str, source_encoding). """ if content_type.startswith('text/xml'): encoding = encodingFromXMLPreamble(source) return unicode(source, encoding), encoding elif content_type.startswith('text/html'): encoding = charsetFromMetaEquiv(source) if encoding: return unicode(source, encoding), encoding # Try to detect the encoding by converting it unicode without raising # exceptions. There are some smarter Python-based sniffer methods # available however we have to check their licenses first before # including them into the Zope 2 core for enc in preferred_encodings: try: return unicode(source, enc), enc except UnicodeDecodeError: continue return unicode(source), None zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/__init__.py0000644000175000017500000000212412214017422023666 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Package wrapper for Page Templates This wrapper allows the Page Template modules to be segregated in a separate package. """ # Placeholder for Zope Product data misc_ = {} # import ZTUtils in order to make i importable through # ZopeGuards.load_module() where an importable modules must be # available in sys.modules import ZTUtils def initialize(context): # Import lazily, and defer initialization to the module import ZopePageTemplate ZopePageTemplate.initialize(context) zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/0000755000175000017500000000000012214017422022506 5ustar arnauarnauzope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tal-define.stx0000644000175000017500000000315312214017422025260 0ustar arnauarnaudefine: Define variables Syntax 'tal:define' syntax:: argument ::= define_scope [';' define_scope]* define_scope ::= (['local'] | 'global') define_var define_var ::= variable_name expression variable_name ::= Name *Note: If you want to include a semi-colon (;) in an 'expression', it must be escaped by doubling it (;;).* Description The 'tal:define' statement defines variables. You can define two different kinds of TAL variables: local and global. When you define a local variable in a statement element, you can only use that variable in that element and the elements it contains. If you redefine a local variable in a contained element, the new definition hides the outer element's definition within the inner element. When you define a global variables, you can use it in any element processed after the defining element. If you redefine a global variable, you replace its definition for the rest of the template. *Note: local variables are the default* If the expression associated with a variable evaluates to *nothing*, then that variable has the value *nothing*, and may be used as such in further expressions. Likewise, if the expression evaluates to *default*, then the variable has the value *default*, and may be used as such in further expressions. Examples Defining a global variable:: tal:define="global company_name string:Zope Corp, Inc." Defining two variables, where the second depends on the first:: tal:define="mytitle template/title; tlen python:len(mytitle)" zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tal-replace.stx0000644000175000017500000000231712214017422025442 0ustar arnauarnaureplace: Replace an element Syntax 'tal:replace' syntax:: argument ::= (['text'] | 'structure') expression Description The 'tal:replace' statement replaces an element with dynamic content. It replaces the statement element with either text or a structure (unescaped markup). The body of the statement is an expression with an optional type prefix. The value of the expression is converted into an escaped string if you prefix the expression with 'text' or omit the prefix, and is inserted unchanged if you prefix it with 'structure'. Escaping consists of converting "&" to "&amp;", "<" to "&lt;", and ">" to "&gt;". If the value is *nothing*, then the element is simply removed. If the value is *default*, then the element is left unchanged. Examples The two ways to insert the title of a template:: Title Title Inserting HTML/XML::
    Inserting nothing::
    This element is a comment.
    See Also "'tal:content'":tal-content.stx zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tal-condition.stx0000644000175000017500000000236212214017422026015 0ustar arnauarnaucondition: Conditionally insert or remove an element Syntax 'tal:condition' syntax:: argument ::= expression Description The 'tal:condition' statement includes the statement element in the template only if the condition is met, and omits it otherwise. If its expression evaluates to a *true* value, then normal processing of the element continues, otherwise the statement element is immediately removed from the template. For these purposes, the value *nothing* is false, and *default* has the same effect as returning a true value. *Note: Zope considers missing variables, None, zero, empty strings, and empty sequences false; all other values are true.* Examples Test a variable before inserting it (the first example tests for existence and truth, while the second only tests for existence)::

    message goes here

    message goes here

    Test for alternate conditions::

    Even

    Odd

    zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tales-string.stx0000644000175000017500000000214112214017422025660 0ustar arnauarnauTALES String expressions Syntax String expression syntax:: string_expression ::= ( plain_string | [ varsub ] )* varsub ::= ( '$' Path ) | ( '${' Path '}' ) plain_string ::= ( '$$' | non_dollar )* non_dollar ::= any character except '$' Description String expressions interpret the expression string as text. If no expression string is supplied the resulting string is *empty*. The string can contain variable substitutions of the form '$name' or '${path}', where 'name' is a variable name, and 'path' is a "path expression":tales-path.stx. The escaped string value of the path expression is inserted into the string. To prevent a '$' from being interpreted this way, it must be escaped as '$$'. Examples Basic string formatting:: Spam and Eggs Using paths::

    total: 12

    Including a dollar sign::

    cost: $42.00

    zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/PageTemplate_Edit.stx0000644000175000017500000000343212214017422026565 0ustar arnauarnauEdit View: Edit A Page Template Description This view allows you to edit the template's text, upload new text, or change the content type and title of the template. Template Text The text in a template must be well-formed HTML if the content type is 'text/html'. Otherwise, it must be well-formed XML. Either way, it may include "TAL":tal.stx or "METAL":metal.stx markup containing "TALES":tales.stx expressions. Controls 'Title' -- Allows you to specify the Zope title of the template. 'Content-Type' -- Allows you to specify the content-type that will be given to renderings of this template. Also, if the content type is 'text/html' (the default) then the template is assumed to contain HTML, not XML. This affects both parsing and rendering of the template, and can be overridden by giving the template an 'is_html' property. In HTML mode you don't need to explicitly declare 'tal' and 'metal' XML namespaces. In XML mode you must explicitly declare 'tal' and 'metal' XML namespaces, and Zope assumes that your template contains well-formed XML. 'Expand macros when editing' -- Allows you to turn the expansion of METAL macros on or off. This only affects viewing of the source code, not rendering. Buttons and Other Form Elements 'Save Changes' -- saves changes you make to the body, title, or content type. 'Taller'/'Shorter'/'Wider'/'Narrower' -- make the body textarea taller, shorter, wider, or narrower. 'File' -- upload a file into this template. File Upload Details Files uploaded into a template must be valid HTML or XML text. zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tales-not.stx0000644000175000017500000000205412214017422025155 0ustar arnauarnauTALES Not expressions Syntax Not expression syntax:: not_expression ::= 'not:' expression Description Not expression evaluate the expression string (recursively) as a full expression, and returns the boolean negation of its value. If the expression supplied does not evaluate to a boolean value, *not* will issue a warning and *coerce* the expression's value into a boolean type based on the following rules: 1. the number 0 is *false* 2. numbers > 0 are *true* 3. an empty string or other sequence is *false* 4. a non-empty string or other sequence is *true* 5. a *non-value* (e.g. void, None, Nil, NULL, etc) is *false* 6. all other values are implementation-dependent. If no expression string is supplied, an error should be generated. Zope considers all objects not specifically listed above as *false* (including negative numbers) to be *true*. Examples Testing a sequence::

    There are no contained objects.

    zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tales.stx0000644000175000017500000000716312214017422024365 0ustar arnauarnauTALES Overview The *Template Attribute Language Expression Syntax* (TALES) standard describes expressions that supply "TAL":tal.stx and "METAL":metal.stx with data. TALES is *one* possible expression syntax for these languages, but they are not bound to this definition. Similarly, TALES could be used in a context having nothing to do with TAL or METAL. TALES expressions are described below with any delimiter or quote markup from higher language layers removed. Here is the basic definition of TALES syntax:: Expression ::= [type_prefix ':'] String type_prefix ::= Name Here are some simple examples:: a/b/c path:a/b/c nothing path:nothing python: 1 + 2 string:Hello, ${user/getUserName} The optional *type prefix* determines the semantics and syntax of the *expression string* that follows it. A given implementation of TALES can define any number of expression types, with whatever syntax you like. It also determines which expression type is indicated by omitting the prefix. If you do not specify a prefix, Zope assumes that the expression is a *path* expression. TALES Expression Types These are the TALES expression types supported by Zope: * "path":tales-path.stx expressions - locate a value by its path. * "exists":tales-exists.stx expressions - test whether a path is valid. * "nocall":tales-nocall.stx expressions - locate an object by its path. * "not":tales-not.stx expressions - negate an expression * "string":tales-string.stx expressions - format a string * "python":tales-python.stx expressions - execute a Python expression Built-in Names These are the names that always available to TALES expressions in Zope: - *nothing* - special value used by to represent a *non-value* (e.g. void, None, Nil, NULL). - *default* - special value used to specify that existing text should not be replaced. See the documentation for individual TAL statements for details on how they interpret *default*. - *options* - the *keyword* arguments passed to the template. These are generally available when a template is called from Methods and Scripts, rather than from the web. - *repeat* - the 'repeat' variables; see the "tal:repeat":tal-repeat.stx documentation. - *attrs* - a dictionary containing the initial values of the attributes of the current statement tag. - *CONTEXTS* - the list of standard names (this list). This can be used to access a built-in variable that has been hidden by a local or global variable with the same name. - *root* - the system's top-most object: the Zope root folder. - *here* - the object to which the template is being applied. - *container* - The folder in which the template is located. - *template* - the template itself. - *request* - the publishing request object. - *user* - the authenticated user object. - *modules* - a collection through which Python modules and packages can be accessed. Only modules which are approved by the Zope security policy can be accessed. Note the names 'root', 'here', 'container', 'template', 'request', 'user', and 'modules' are optional names supported by Zope, but are not required by the TALES standard. See Also "TAL Overview":tal.stx "METAL Overview":metal.stx "exists":tales-exists.stx expressions "nocall":tales-nocall.stx expressions "not":tales-not.stx expressions "string":tales-string.stx expressions "path":tales-path.stx expressions "python":tales-python.stx expressions zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/metal-use-macro.stx0000644000175000017500000000326112214017422026243 0ustar arnauarnauuse-macro: Use a macro Syntax 'metal:use-macro' syntax:: argument ::= expression Description The 'metal:use-macro' statement replaces the statement element with a macro. The statement expression describes a macro definition. In Zope the expression will generally be a path expression referring to a macro defined in another template. See "metal:define-macro" for more information. The effect of expanding a macro is to graft a subtree from another document (or from elsewhere in the current document) in place of the statement element, replacing the existing sub-tree. Parts of the original subtree may remain, grafted onto the new subtree, if the macro has *slots*. See "metal:define-slot":metal-define-slot.stx for more information. If the macro body uses any macros, they are expanded first. When a macro is expanded, its 'metal:define-macro' attribute is replaced with the 'metal:use-macro' attribute from the statement element. This makes the root of the expanded macro a valid 'use-macro' statement element. Examples Basic macro usage::

    header macro from defined in other.html template

    This example refers to the 'header' macro defined in the 'other.html' template which is in the same folder as the current template. When the macro is expanded, the 'p' element and its contents will be replaced by the macro. Note: there will still be a 'metal:use-macro' attribute on the replacement element. See Also "metal:define-macro":metal-define-macro.stx "metal:fill-slot":metal-fill-slot.stx zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tal-on-error.stx0000644000175000017500000000374612214017422025601 0ustar arnauarnauon-error: Handle errors Syntax 'tal:on-error' syntax:: argument ::= (['text'] | 'structure') expression Description The 'tal:on-error' statement provides error handling for your template. When a TAL statement produces an error, the TAL interpreter searches for a 'tal:on-error' statement on the same element, then on the enclosing element, and so forth. The first 'tal:on-error' found is invoked. It is treated as a 'tal:content' statement. A local variable 'error' is set. This variable has these attributes: 'type' -- the exception type 'value' -- the exception instance 'traceback' -- the traceback object The simplest sort of 'tal:on-error' statement has a literal error string or *nothing* for an expression. A more complex handler may call a script that examines the error and either emits error text or raises an exception to propagate the error outwards. Examples Simple error message:: Ishmael Removing elements with errors:: Ishmael Calling an error-handling script::
    ...
    Here's what the error-handling script might look like:: ## Script (Python) "errHandler" ##bind namespace=_ ## error=_['error'] if error.type==ZeroDivisionError: return "

    Can't divide by zero.

    " else return """

    An error ocurred.

    Error type: %s

    Error value: %s

    """ % (error.type, error.value) See Also "Python Tutorial: Errors and Exceptions":http://www.python.org/doc/current/tut/node10.html "Python Built-in Exceptions":http://www.python.org/doc/current/lib/module-exceptions.html zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/ZTUtils.py0000644000175000017500000000437212214017422024444 0ustar arnauarnau""" ZTUtils: Page Template Utilities The classes in this module are available from Page Templates. """ class Batch: """ Batch - a section of a large sequence. You can use batches to break up large sequences (such as search results) over several pages. Batches provide Page Templates with similar functions as those built-in to ''. You can access elements of a batch just as you access elements of a list. For example:: >>> b=Batch(range(100), 10) >>> b[5] 4 >>> b[10] IndexError: list index out of range Batches have these public attributes: start -- The first element number (counting from 1). first -- The first element index (counting from 0). Note that this is that same as start - 1. end -- The last element number (counting from 1). orphan -- The desired minimum batch size. This controls how sequences are split into batches. If a batch smaller than the orphan size would occur, then no split is performed, and a batch larger than the batch size results. overlap -- The number of elements that overlap between batches. length -- The actual length of the batch. Note that this can be different than size due to orphan settings. size -- The desired size. Note that this can be different than the actual length of the batch due to orphan settings. previous -- The previous batch or None if this is the first batch. next -- The next batch or None if this is the last batch. """ def __init__(self, sequence, size, start=0, end=0, orphan=0, overlap=0): """ Creates a new batch given a sequence and a desired batch size. sequence -- The full sequence. size -- The desired batch size. start -- The index of the start of the batch (counting from 0). end -- The index of the end of the batch (counting from 0). orphan -- The desired minimum batch size. This controls how sequences are split into batches. If a batch smaller than the orphan size would occur, then no split is performed, and a batch larger than the batch size results. overlap -- The number of elements that overlap between batches. """ zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tal-repeat.stx0000644000175000017500000001065712214017422025315 0ustar arnauarnaurepeat: Repeat an element Syntax 'tal:repeat' syntax:: argument ::= variable_name expression variable_name ::= Name Description The 'tal:repeat' statement replicates a sub-tree of your document once for each item in a sequence. The expression should evaluate to a sequence. If the sequence is empty, then the statement element is deleted, otherwise it is repeated for each value in the sequence. If the expression is *default*, then the element is left unchanged, and no new variables are defined. The 'variable_name' is used to define a local variable and a repeat variable. For each repetition, the local variable is set to the current sequence element, and the repeat variable is set to an iteration object. Repeat Variables You use repeat variables to access information about the current repetition (such as the repeat index). The repeat variable has the same name as the local variable, but is only accessible through the built-in variable named 'repeat'. The following information is available from the repeat variable: o *index* - repetition number, starting from zero. o *number* - repetition number, starting from one. o *even* - true for even-indexed repetitions (0, 2, 4, ...). o *odd* - true for odd-indexed repetitions (1, 3, 5, ...). o *start* - true for the starting repetition (index 0). o *end* - true for the ending, or final, repetition. o *first* - true for the first item in a group - see note below o *last* - true for the last item in a group - see note below o *length* - length of the sequence, which will be the total number of repetitions. o *letter* - repetition number as a lower-case letter: "a" - "z", "aa" - "az", "ba" - "bz", ..., "za" - "zz", "aaa" - "aaz", and so forth. o *Letter* - upper-case version of *letter*. o *roman* - repetition number as a lower-case roman numeral: "i", "ii", "iii", "iv", "v", etc. o *Roman* - upper-case version of *roman*. You can access the contents of the repeat variable using path expressions or Python expressions. In path expressions, you write a three-part path consisting of the name 'repeat', the statement variable's name, and the name of the information you want, for example, 'repeat/item/start'. In Python expressions, you use normal dictionary notation to get the repeat variable, then attribute access to get the information, for example, "python:repeat['item'].start". Note that 'first' and 'last' are intended for use with sorted sequences. They try to divide the sequence into group of items with the same value. If you provide a path, then the value obtained by following that path from a sequence item is used for grouping, otherwise the value of the item is used. You can provide the path by passing it as a parameter, as in "python:repeat['item'].first('color')", or by appending it to the path from the repeat variable, as in "repeat/item/first/color". Examples Iterating over a sequence of strings::

    Inserting a sequence of table rows, and using the repeat variable to number the rows::
    1 Widget $1.50
    Nested repeats::
    1 * 1 = 1
    Insert objects. Seperates groups of objects by meta-type by drawing a rule between them::

    Meta Type

    Object ID


    Note, the objects in the above example should already be sorted by meta-type. zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tales-path.stx0000644000175000017500000000536412214017422025320 0ustar arnauarnauTALES Path expressions Syntax Path expression syntax:: PathExpr ::= Path [ '|' Path ]* Path ::= variable [ '/' URL_Segment ]* variable ::= Name Description A path expression consists of one or more *paths* separated by vertical bars (|). A path consists of one or more non-empty strings separated by slashes. The first string must be a variable name (built-in variable or a user defined variable), and the remaining strings, the *path segments*, may contain letters, digits, spaces, and the punctuation characters underscore, dash, period, comma, and tilde. For example:: request/cookies/oatmeal nothing here/some-file 2001_02.html.tar.gz/foo root/to/branch | default request/name | string:Anonymous Coward When a path expression is evaluated, Zope attempts to traverse the path, from left to right, until it succeeds or runs out of paths segments. To traverse a path, it first fetches the object stored in the variable. For each path segment, it traverses from the current object to the subobject named by the path segment. Subobjects are located according to standard Zope traversal rules (via getattr, getitem, or traversal hooks). Once a path has been successfully traversed, the resulting object is the value of the expression. If it is a callable object, such as a method or template, it is called. If a traversal step fails, evaluation immediately proceeds to the next path. If there are no further paths, an error results. The expression in a series of paths seperated by vertical bars can be any TALES expression. For example, 'request/name | string:Anonymous Coward'. This is useful chiefly for providing default values such as strings and numbers which are not expressable as path expressions. If no path is given the result is *nothing*. Since every path must start with a variable name, you need a set of starting variables that you can use to find other objects and values. See the "TALES overview":tales.stx for a list of built-in variables. Since variable names are looked up first in locals, then in globals, then in this list, these names act just like built-ins in Python; They are always available, but they can be shadowed by a global or local variable declaration. You can always access the built-in names explicitly by prefixing them with *CONTEXTS*. (e.g. CONTEXTS/root, CONTEXTS/nothing, etc). Examples Inserting a cookie variable or a property:: preference Inserting the user name::

    User name

    zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/metal-define-slot.stx0000644000175000017500000000171312214017422026561 0ustar arnauarnaudefine-slot: Define a macro customization point Syntax 'metal:define-slot' syntax:: argument ::= Name Description The 'metal:define-slot' statement defines a macro customization point or *slot*. When a macro is used, its slots can be replaced, in order to customize the macro. Slot definitions provide default content for the slot. You will get the default slot contents if you decide not to customize the macro when using it. The 'metal:define-slot' statement must be used inside a 'metal:define-macro' statement. Slot names must be unique within a macro. Examples Simple macro with slot::

    Hello World

    This example defines a macro with one slot named 'name'. When you use this macro you can customize the 'b' element by filling the 'name' slot. See Also "metal:fill-slot":metal-fill-slot.stx zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/metal-define-macro.stx0000644000175000017500000000140112214017422026673 0ustar arnauarnaudefine-macro: Define a macro Syntax 'metal:define-macro' syntax:: argument ::= Name Description The 'metal:define-macro' statement defines a macro. The macro is named by the statement expression, and is defined as the element and its sub-tree. In Zope, a macro definition is available as a sub-object of a template's 'macros' object. For example, to access a macro named 'header' in a template named 'master.html', you could use the path expression 'master.html/macros/header'. Examples Simple macro definition::

    Copyright 2001, Foobar Inc.

    See Also "metal:use-macro":metal-use-macro.stx "metal:define-slot":metal-define-slot.stx zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tal-omit-tag.stx0000644000175000017500000000225712214017422025553 0ustar arnauarnauomit-tag: Remove an element, leaving its contents Syntax 'tal:omit-tag' syntax:: argument ::= [ expression ] Description The 'tal:omit-tag' statement leaves the contents of a tag in place while omitting the surrounding start and end tag. If its expression evaluates to a *false* value, then normal processing of the element continues and the tag is not omitted. If the expression evaluates to a *true* value, or there is no expression, the statement tag is replaced with its contents. Zope treats empty strings, empty sequences, zero, None, *nothing*, and *default* at false. All other values are considered true. Examples Unconditionally omitting a tag::
    ...but this text will remain.
    Conditionally omitting a tag:: I may be bold. The above example will omit the 'b' tag if the variable 'bold' is false. Creating ten paragraph tags, with no enclosing tag::

    1

    zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/metal.stx0000644000175000017500000000355412214017422024357 0ustar arnauarnauMETAL Overview The *Macro Expansion Template Attribute Language* (METAL) standard is a facility for HTML/XML macro preprocessing. It can be used in conjunction with or independently of "TAL":tal.stx and "TALES":tales.stx. Macros provide a way to define a chunk of presentation in one template, and share it in others, so that changes to the macro are immediately reflected in all of the places that share it. Additionally, macros are always fully expanded, even in a template's source text, so that the template appears very similar to its final rendering. METAL Namespace The METAL namespace URI and recommended alias are currently defined as:: xmlns:metal="http://xml.zope.org/namespaces/metal" Just like the TAL namespace URI, this URI is not attached to a web page; it's just a unique identifier. Zope does not require an XML namespace declaration when creating templates with a content-type of 'text/html'. However, it does require an XML namespace declaration for all other content-types. METAL Statements METAL defines a number of statements: * "metal:define-macro":metal-define-macro.stx - Define a macro. * "metal:use-macro":metal-use-macro.stx - Use a macro. * "metal:define-slot":metal-define-slot.stx - Define a macro customization point. * "metal:fill-slot":metal-fill-slot.stx - Customize a macro. Although METAL does not define the syntax of expression non-terminals, leaving that up to the implementation, a canonical expression syntax for use in METAL arguments is described in "TALES Specification":tales.stx. See Also "TAL Overview":tal.stx "TALES Overview":tales.stx "metal:define-macro":metal-define-macro.stx "metal:use-macro":metal-use-macro.stx "metal:define-slot":metal-define-slot.stx "metal:fill-slot":metal-fill-slot.stx zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tales-exists.stx0000644000175000017500000000132712214017422025676 0ustar arnauarnauTALES Exists expressions Syntax Exists expression syntax:: exists_expressions ::= 'exists:' path_expression Description Exists expressions test for the existence of paths. An exists expression returns true when the path expressions following it expression returns a value. It is false when the path expression cannot locate an object. Examples Testing for the existence of a form variable::

    Please enter a number between 0 and 5

    Note that in this case you can't use the expression, 'not:request/form/number', since that expression will be true if the 'number' variable exists and is zero. zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tales-python.stx0000644000175000017500000000702012214017422025674 0ustar arnauarnauTALES Python expressions Syntax Python expression syntax:: Any valid Python language expression Description Python expressions evaluate Python code in a security-restricted environment. Python expressions offer the same facilities as those available in Python-based Scripts and DTML variable expressions. Security Restrictions Python expressions are subject to the same security restrictions as Python-based scripts. These restrictions include: access limits -- Python expressions are subject to Zope permission and role security restrictions. In addition, expressions cannot access objects whose names begin with underscore. write limits -- Python expressions cannot change attributes of Zope objects. Despite these limits malicious Python expressions can cause problems. See The Zope Book for more information. Built-in Functions Python expressions have the same built-ins as Python-based Scripts with a few additions. These standard Python built-ins are available: 'None', 'abs', 'apply', 'callable', 'chr', 'cmp', 'complex', 'delattr', 'divmod', 'filter', 'float', 'getattr', 'hash', 'hex', 'int', 'isinstance', 'issubclass', 'list', 'len', 'long', 'map', 'max', 'min', 'oct', 'ord', 'repr', 'round', 'setattr', 'str', 'tuple'. The 'range' and 'pow' functions are available and work the same way they do in standard Python; however, they are limited to keep them from generating very large numbers and sequences. This limitation helps protect against denial of service attacks. Finally, these functions are available in Python expressions, but not in Python-based scripts: 'path(string)' -- Evaluate a TALES "path":tales-path.stx expression. 'string(string)' -- Evaluate a TALES "string":tales-string.stx expression. 'exists(string)' -- Evaluates a TALES "exists":tales-exists.stx expression. 'nocall(string)' -- Evaluates a TALES "nocall":tales-nocall.stx expression. Python Modules A number of Python modules are available by default. You can make more modules available. You can access modules either via path expressions (for example 'modules/string/join') or in Python with the 'modules' mapping object (for example 'modules["string"].join'). Here are the default modules: 'string' -- The standard "Python string module":http://www.python.org/doc/current/lib/module-string.html. Note: most of the functions in the module are also available as methods on string objects. 'random' -- The standard "Python random module":http://www.python.org/doc/current/lib/module-random.html. 'math' -- The standard "Python math module":http://www.python.org/doc/current/lib/module-math.html. Examples Using a module usage (pick a random choice from a list):: a random number between one and five String processing (capitalize the user name)::

    User Name

    Basic math (convert an image size to megabytes)::

    12.2323

    String formatting (format a float to two decimal places)::

    13.56

    zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/metal-fill-slot.stx0000644000175000017500000000155212214017422026256 0ustar arnauarnaufill-slot: Customize a macro Syntax 'metal:fill-slot' syntax:: argument ::= Name Description The 'metal:fill-slot' statement customizes a macro by replacing a *slot* in the macro with the statement element (and its content). The 'metal:fill-slot' statement must be used inside a 'metal:use-macro' statement. Slot names must be unique within a macro. If the named slot does not exist within the macro, the slot contents will be silently dropped. Examples Given this macro::

    Hello World

    You can fill the 'name' slot like so::

    Hello Kevin Bacon

    See Also "metal:define-slot":metal-define-slot.stx zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tal-content.stx0000644000175000017500000000162712214017422025504 0ustar arnauarnaucontent: Replace the content of an element Syntax 'tal:content' syntax:: argument ::= (['text'] | 'structure') expression Description Rather than replacing an entire element, you can insert text or structure in place of its children with the 'tal:content' statement. The statement argument is exactly like that of 'tal:replace', and is interpreted in the same fashion. If the expression evaluates to *nothing*, the statement element is left childless. If the expression evaluates to *default*, then the element's contents are unchanged. *Note: The default replacement behavior is 'text'.* Examples Inserting the user name::

    Fred Farkas

    Inserting HTML/XML::

    marked up content goes here.

    See Also "'tal:replace'":tal-replace.stx zope2.13-2.13.21/source/Zope2/src/Products/PageTemplates/help/tal-attributes.stx0000644000175000017500000000340412214017422026213 0ustar arnauarnauattributes: Replace element attributes Syntax 'tal:attributes' syntax:: argument ::= attribute_statement [';' attribute_statement]* attribute_statement ::= attribute_name expression attribute_name ::= [namespace ':'] Name namespace ::= Name *Note: If you want to include a semi-colon (;) in an 'expression', it must be escaped by doubling it (;;).* Description The 'tal:attributes' statement replaces the value of an attribute (or creates an attribute) with a dynamic value. You can qualify an attribute name with a namespace prefix, for example 'html:table', if you are generating an XML document with multiple namespaces. The value of each expression is converted to a string, if necessary. If the expression associated with an attribute assignment evaluates to *nothing*, then that attribute is deleted from the statement element. If the expression evaluates to *default*, then that attribute is left unchanged. Each attribute assignment is independent, so attributes may be assigned in the same statement in which some attributes are deleted and others are left alone. If you use 'tal:attributes' on an element with an active 'tal:replace' command, the 'tal:attributes' statement is ignored. If you use 'tal:attributes' on an element with a 'tal:repeat' statement, the replacement is made on each repetition of the element, and the replacement expression is evaluated fresh for each repetition. Examples Replacing a link:: Replacing two attributes::
    Locked by WebDAV   

    You may upload the source for &dtml-title_and_id; using the form below. Choose an existing file from your local computer by clicking browse The contents of the file should be a valid script with an optional "##data" block at the start. You may click the following link to view or download the current source.

    File  
    Locked by WebDAV
    zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/www/pyScriptProxy.dtml0000644000175000017500000000220012214017673031677 0ustar arnauarnau

    Proxy roles allow you to control the access that a script has. Proxy roles replace the roles of the user who is executing the script. This can be used to both expand and limit access to resources. Select the proxy roles for this object from the list below.

    Proxy Roles
    zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/www/default_py0000644000175000017500000000065412214017673030230 0ustar arnauarnau# Example code: # Import a standard function, and get the HTML request and response objects. from Products.PythonScripts.standard import html_quote request = container.REQUEST response = request.response # Return a string identifying this script. print "This is the", script.meta_type, '"%s"' % script.getId(), if script.title: print "(%s)" % html_quote(script.title), print "in", container.absolute_url() return printed zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/www/pyScriptAdd.dtml0000644000175000017500000000247012214017673031257 0ustar arnauarnau

    Python Scripts allow you to add functionality to Zope by writing scripts in the Python programming language that are exposed as callable Zope objects. You may choose to upload the script from a local file by typing the file name or using the browse button.

    Id
    File
    zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/__init__.py0000644000175000017500000000402312214017673027430 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## __doc__='''Python Scripts Product Initialization $Id: __init__.py 110402 2010-04-01 16:04:02Z tseaver $''' import PythonScript import standard # Temporary from Shared.DC import Scripts __module_aliases__ = ( ('Products.PythonScripts.Script', Scripts.Script), ('Products.PythonScripts.Bindings', Scripts.Bindings), ('Products.PythonScripts.BindingsUI', Scripts.BindingsUI),) __roles__ = None __allow_access_to_unprotected_subobjects__ = 1 def initialize(context): context.registerClass( PythonScript.PythonScript, permission='Add Python Scripts', constructors=(PythonScript.manage_addPythonScriptForm, PythonScript.manage_addPythonScript), icon='www/pyscript.gif' ) context.registerHelp() context.registerHelpTitle('Script (Python)') global _m _m['recompile'] = recompile _m['recompile__roles__'] = ('Manager',) # utility stuff def recompile(self): '''Recompile all Python Scripts''' base = self.this() scripts = base.ZopeFind(base, obj_metatypes=('Script (Python)',), search_sub=1) names = [] for name, ob in scripts: if ob._v_change: names.append(name) ob._compile() ob._p_changed = 1 if names: return 'The following Scripts were recompiled:\n' + '\n'.join(names) return 'No Scripts were found that required recompilation.' import patches zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/help/0000755000175000017500000000000012214017673026250 5ustar arnauarnauzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/help/ModuleAccess.stx0000644000175000017500000000426612214017673031367 0ustar arnauarnauAllowing Import of Modules Scripts are able to import a small number of Python modules for which there are security declarations. These include 'string', 'math', and 'random'. The only way to make other Python modules available for import is to add security declarations to them in the filesystem. MyScriptModules The simplest way to allow import of a module is to create your own simple custom Product. To make this Product: 1. Create a subdirectory of your Zope installation's "Products" directory. The name of the directory doesn't really matter; Let's call it 'MyScriptModules'. 2. Create a file in this subdirectory called '__init__.py'. 3. Add the following lines to your '__init__.py':: from Products.PythonScripts.Utility import allow_module, allow_class from AccessControl import ModuleSecurityInfo, ClassSecurityInfo from AccessControl.class_init import InitializeClass 4. For each module to which you want to allow access, add security declarations in '__init__.py'. Security Declarations You will need to write different security declarations depending on how much of a module you want to expose. You should import the module at the Python command line, and use 'dir()' to examine its contents. Names starting with underscore ('_') may be safely ignored. Be wary of dangerous modules, such as 'sys' and 'os', which may be exposed by the module. You can handle a module, such as 'base64', that contains only safe functions by writing 'allow_module("module_name")'. To allow access to only some names, in a module with dangerous contents, you can write:: ModuleSecurityInfo('module_name').declarePublic('name1', 'name2', ...) If the module contains a class that you want to use, you will need to add the following:: from import allow_class() Certain modules, such as 'sha', provide extension types instead of classes. Security declarations typically cannot be added to extension types, so the only way to use this sort of module is to write a Python wrapper class, or use External Methods. zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/help/PythonScript_edit.stx0000644000175000017500000001132012214017673032460 0ustar arnauarnauEdit View: Edit A Script (Python) Description This view allows you to edit the logic which composes a script in Python. Script instances execute in a restricted context, bounded by your user's privilege level in Zope, and certain global restrictions of all through-the-web code. For information about what you "can" and "cannot" do in a Script instance as opposed to non-through-the-web Python, see the API Reference documentation for "Script (Python)" in this help system. Controls 'Title' -- Allows you to specify the Zope title of the script. 'Id' -- Allows you to specify the id of the script. 'Parameter List' -- Enter function parameters for this script separated by commas. For example: *foo, bar, baz* Status Elements 'Bound Names' -- the names used by this script for bindings. You may use these names in the body of the script to refer to bound elements. The defaults are:: context -- the script's "parent" respective to acquisition. container -- the script's "parent" respective to containment. script -- the script object itself. traverse_subpath -- if the script is called directly from a URL, this is the portion of the URL path after the script's name, split at slash separators, into a list of strings. If the script was not called directly from a URL, this will be an empty list. Another possible name binding, to the "namespace" object, is not set by default. If this was set, if the Script was called from DTML, it would represent the namespace of the calling DTML object. More information about bindings can be found by visiting the help screens of the "Bindings" tab of a Script (Python) instance. Buttons and Other Form Elements 'Save Changes' -- saves changes you make to the body, title, or parameter list. 'Taller'/'Shorter'/'Wider'/'Narrower' -- make the body textarea taller, shorter, wider, or narrower. 'File' -- upload a file into this Script (Python) instance. File Upload Details Files uploaded into a Script (Python) instance may either consist only of the actual body of the function, or the file containing the function body may contain at its head a set of lines starting with "##" which describe bindings, parameters, and the title. For example, a file uploaded into a Script (Python) instance might be simply:: return "Hello" If you upload this file into a Script (Python) instance, the existing settings (or default settings) for bindings, parameters, and title will remain. However, if you wished to, you could develop a Script (Python) on disk which looked like:: ## Script (Python) "foo" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters=goop, fudge ##title= ## return "Fudge was %s, goop was %s" % (fudge, goop) The lines preceded by "##" are metadata about the Script (Python) instance which can survive a round trip via FTP or through the web interface. When these lines are encountered by the parser after an upload (or webform save), they serve to *modify* the settings of the Script (Python) instance with the metadata contained within the blocked area. Lines beginning with "##" without any spaces after the "##" are contextually meaningful to the file upload parser. There are three keywords which can directly follow a "##": "bind", "parameters", and "title". The "bind" keyword following a "##" binds a name to a object in the context this Script (Python) instance's body. For example, the line "##bind container=goober" binds the name "goober" to the acquisition parent of the script, allowing you to refer to "goober" in the script body. Legal objects to which to bind are: container, context, namespace, script, and subpath. See the help available from the "bindings" tab of Script (Python) instances for more details about what bindings mean. The "title" keyword following a "##" provides a title to the script. E.g. "title=A Really Neat Script" The "parameters" keyword following a "##" provides parameters to the Script (Python) instance. E.g. "parameters=foo,bar,baz". zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/help/standard.py0000644000175000017500000001052012214017673030420 0ustar arnauarnau""" Products.PythonScripts.standard: Utility functions and classes The functions and classes in this module are available from Python-based scripts, DTML, and Page Templates. """ def whole_dollars(number): """ Show a numeric value with a dollar symbol. """ def dollars_and_cents(number): """ Show a numeric value with a dollar symbol and two decimal places. """ def structured_text(s): """ Convert a string in structured-text format to HTML. See Also "Structured-Text Rules":http://dev.zope.org/Members/jim/StructuredTextWiki/StructuredTextNGRules """ def sql_quote(s): """ Convert single quotes to pairs of single quotes. This is needed to safely include values in Standard Query Language (SQL) strings. """ def html_quote(s): """ Convert characters that have special meaning in HTML to HTML character entities. See Also "Python 'cgi' module":http://www.python.org/doc/current/lib/Functions_in_cgi_module.html 'escape' function. """ def url_quote(s): """ Convert characters that have special meaning in URLS to HTML character entities using decimal values. See Also "Python 'urllib' module":http://www.python.org/doc/current/lib/module-urllib.html 'quote' function. """ def url_quote_plus(s): """ Like url_quote but also replace blank space characters with '+'. This is needed for building query strings in some cases. See Also "Python 'urllib' module":http://www.python.org/doc/current/lib/module-urllib.html 'quote_plus' function. """ def url_unquote(s): """ Convert HTML %xx character entities into the characters they represent. (Undoes the affects of url_quote). See Also "Python 'urllib' module":http://www.python.org/doc/current/lib/module-urllib.html 'unquote' function. """ def url_unquote_plus(s): """ Like url_unquote but also replace '+' characters with blank spaces. See Also "Python 'urllib' module":http://www.python.org/doc/current/lib/module-urllib.html 'unquote_plus' function. """ def urlencode(query, doseq=0): """ Convert a mapping object (such as a dictionary) or a sequence of two-element tuples to a URL encoded query string. Useful for generating query strings programmatically. See Also "Python 'urllib' module":http://www.python.org/doc/current/lib/module-urllib.html 'urlencode' function. """ def newline_to_br(s): """ Convert newlines and carriage-return and newline combinations to break tags. """ def thousand_commas(number): """ Insert commas every three digits to the left of a decimal point in values containing numbers. For example, the value, "12000 widgets" becomes "12,000 widgets". """ class DTML: """ DTML - temporary, security-restricted DTML objects """ def __init__(source, **kw): """ Create a DTML object with source text and keyword variables. The source text defines the DTML source content. The optinal keyword arguments define variables. """ def call(client=None, REQUEST={}, **kw): """ Render the DTML. To accomplish its task, DTML often needs to resolve various names into objects. For example, when the code <dtml-var spam> is executed, the DTML engine tries to resolve the name 'spam'. In order to resolve names, you must be pass a namespace to the DTML. This can be done several ways: * By passing a 'client' object - If the argument 'client' is passed, then names are looked up as attributes on the argument. * By passing a 'REQUEST' mapping - If the argument 'REQUEST' is passed, then names are looked up as items on the argument. If the object is not a mapping, an TypeError will be raised when a name lookup is attempted. * By passing keyword arguments -- names and their values can be passed as keyword arguments to the Method. The namespace given to a DTML object is the composite of these three methods. You can pass any number of them or none at all. Names will be looked up first in the keyword argument, next in the client and finally in the mapping. """ zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/help/PythonScript_test.stx0000644000175000017500000000125312214017673032516 0ustar arnauarnauTest View: Test a Script (Python) Description This view allows you to test a Script (Python) instance. Controls If a Script has no parameters, when the "Test" tab is visited the return value of the script will be presented in the manage_main frame. However, if a Script instance has parameters, a form will be presented with fields for "Parameter" and "Value", changeable on a per-parameter basis. These accept string values. The 'Run Script' button runs the script after the 'Parameter' and 'Value' fields have been filled in, and returns the results in the manage_main frame. zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/help/Script.py0000644000175000017500000000164312214017673030072 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class Script: """ Web-callable script base interface. """ def ZScriptHTML_tryAction(REQUEST, argvars): """ Apply the test parameters provided by the dictionary 'argvars'. This will call the current script with the given arguments and return the result. """ zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/help/PythonScript.py0000644000175000017500000001563112214017673031276 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## def manage_addPythonScript(id, REQUEST=None): """Add a Python script to a folder. """ class PythonScript: """ Python Scripts contain python code that gets executed when you call the script by: o Calling the script through the web by going to its location with a web browser. o Calling the script from another script object. o Calling the script from a method object, such as a DTML Method. Python Scripts can contain a "safe" subset of the python language. Python Scripts must be safe because they can be potentially edited by many different users through an insecure medium like the web. The following safety issues drive the need for secure Python Scripts: o Because many users can use Zope, a Python Script must make sure it does not allow a user to do something they are not allowed to do, like deleting an object they do not have permission to delete. Because of this requirement, Python Scripts do many security checks in the course of their execution. o Because Python Scripts can be edited through the insecure medium of the web, they are not allowed access to the Zope server's file-system. Normal Python builtins like 'open' are, therefore, not allowed. o Because many standard Python modules break the above two security restrictions, only a small subset of Python modules may be imported into a Python Scripts with the "import" statement unless they have been validated by Zope's security policy. Currently, the following standard python modules have been validated: o string o math o random o Products.PythonScripts.standard o Because it allows you to execute arbitrary python code, the python "exec" statement is not allowed in Python methods. o Because they may represent or cause security violations, some Python builtin functions are not allowed. The following Python builtins are not allowed: o open o input o raw_input o eval o execfile o compile o type o coerce o intern o dir o globals o locals o vars o buffer o reduce o Other builtins are restricted in nature. The following builtins are restricted: range -- Due to possible memory denial of service attacks, the range builtin is restricted to creating ranges less than 10,000 elements long. filter, map, tuple, list -- For the same reason, builtins that construct lists from sequences do not operate on strings. getattr, setattr, delattr -- Because these may enable Python code to circumvent Zope's security system, they are replaced with custom, security constrained versions. o In order to be consistent with the Python expressions available to DTML, the builtin functions are augmented with a small number of functions and a class: o test o namespace o render o same_type o DateTime o Because the "print" statement cannot operate normally in Zope, its effect has been changed. Rather than sending text to stdout, "print" appends to an internal variable. The special builtin name "printed" evaluates to the concatenation of all text printed so far during the current execution of the script. """ __constructor__ = manage_addPythonScript __extends__=( 'PythonScripts.Script.Script', ) def ZPythonScriptHTML_editAction(REQUEST, title, params, body): """ Change the script's main parameters. This method accepts the following arguments: REQUEST -- The current request. title -- The new value of the Python Script's title. This must be a string. params -- The new value of the Python Script's parameters. Must be a comma seperated list of values in valid python function signature syntax. If it does not contain a valid signature string, a SyntaxError is raised. body -- The new value of the Python Script's body. Must contain valid Python syntax. If it does not contain valid Python syntax, a SyntaxError is raised. """ def ZPythonScript_setTitle(title): """ Change the script's title. This method accepts one argument, 'title' which is the new value for the script's title and must be a string. """ def ZPythonScript_edit(params, body): """ Change the parameters and body of the script. This method accepts two arguments: params -- The new value of the Python Script's parameters. Must be a comma seperated list of values in valid python function signature syntax. If it does not contain a valid signature string, a SyntaxError is raised. body -- The new value of the Python Script's body. Must contain valid Python syntax. If it does not contain valid Python syntax, a SyntaxError is raised. """ def ZPythonScriptHTML_upload(REQUEST, file=''): """ Pass the text in file to the 'write' method. """ def ZScriptHTML_tryParams(): """ Return a list of the required parameters with which to test the script. """ def read(): """ Return the body of the Python Script, with a special comment block prepended. This block contains meta-data in the form of comment lines as expected by the 'write' method. """ def write(text): """ Change the script by parsing the text argument into parts. Leading lines that begin with '##' are stripped off, and if they are of the form '##name=value', they are used to set meta-data such as the title and parameters. The remainder of the text is set as the body of the Python Script. """ def document_src(REQUEST=None, RESPONSE=None): """ Return the text of the 'read' method, with content type 'text/plain' set on the RESPONSE. """ zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/module_access_examples.py0000644000175000017500000000523612214017673032404 0ustar arnauarnau'''Examples for enabling Script import This file contains example code that can be used to make various standard Python modules available to Scripts. In order to use the example code, create a directory called "MyScriptModules", or something equally descriptive, in your Zope's "Products" directory. Copy this file to a file called "__init__.py" in the new directory. Edit the new file, uncommenting the block of code for each module that you want to make available for import by Scripts. You can, of course, add your own code to your "__init__.py" for modules that are not listed below. The list is not comprehensive, but is provided as a decent cross-section of modules. NB: Placing security assestions within the package/module you are trying to import will not work unless that package/module is located in your Products directory. This is because that package/module would have to be imported for its included security assertions to take effect, but to do that would require importing a module without any security declarations, which defeats the point of the restricted python environment. Products work differently as they are imported at Zope startup. By placing a package/module in your Products directory, you are asserting, among other things, that it is safe for Zope to check that package/module for security assertions. As a result, please be careful when place packages or modules that are not Zope Products in the Products directory. ''' from AccessControl import allow_module, allow_class, allow_type from AccessControl import ModuleSecurityInfo # These modules are pretty safe # allow_module('base64') # allow_module('binascii') # allow_module('bisect') # allow_module('colorsys') # allow_module('crypt') # Only parts of these modules should be exposed # ModuleSecurityInfo('fnmatch').declarePublic('fnmatch', 'fnmatchcase') # ModuleSecurityInfo('re').declarePublic('compile', 'findall', # 'match', 'search', 'split', 'sub', 'subn', 'error', # 'I', 'L', 'M', 'S', 'X') # import re # allow_type(type(re.compile(''))) # allow_type(type(re.match('x','x'))) # ModuleSecurityInfo('StringIO').declarePublic('StringIO') # These modules allow access to other servers # ModuleSecurityInfo('ftplib').declarePublic('FTP', 'all_errors', # 'error_reply', 'error_temp', 'error_perm', 'error_proto') # from ftplib import FTP # allow_class(FTP) # ModuleSecurityInfo('httplib').declarePublic('HTTP') # from httplib import HTTP # allow_class(HTTP) # ModuleSecurityInfo('nntplib').declarePublic('NNTP', # 'error_reply', 'error_temp', 'error_perm', 'error_proto') # from httplib import NNTP # allow_class(NNTP) zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/PythonScript.py0000644000175000017500000004704112214017673030346 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Python Scripts Product This product provides support for Script objects containing restricted Python code. $Id: PythonScript.py 113172 2010-06-05 20:23:38Z hannosch $ """ from logging import getLogger import marshal import new import os import re import sys import traceback from urllib import quote from AccessControl.class_init import InitializeClass from AccessControl.requestmethod import requestmethod from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.SecurityManagement import getSecurityManager from AccessControl.ZopeGuards import get_safe_globals, guarded_getattr from AccessControl.ZopeGuards import get_safe_globals, guarded_getattr from Acquisition import aq_parent from App.Common import package_home from App.Dialogs import MessageDialog from App.special_dtml import DTMLFile from DateTime.DateTime import DateTime from OFS.Cache import Cacheable from OFS.History import Historical from OFS.History import html_diff from OFS.SimpleItem import SimpleItem from RestrictedPython import compile_restricted_function from Shared.DC.Scripts.Script import BindingsUI from Shared.DC.Scripts.Script import defaultBindings from Shared.DC.Scripts.Script import Script from webdav.Lockable import ResourceLockedError from zExceptions import Forbidden LOG = getLogger('PythonScripts') # Track the Python bytecode version import imp Python_magic = imp.get_magic() del imp # This should only be incremented to force recompilation. Script_magic = 3 _log_complaint = ( 'Some of your Scripts have stale code cached. Since Zope cannot' ' use this code, startup will be slightly slower until these Scripts' ' are edited. You can automatically recompile all Scripts that have' ' this problem by visiting /manage_addProduct/PythonScripts/recompile' ' of your server in a browser.') manage_addPythonScriptForm = DTMLFile('www/pyScriptAdd', globals()) _default_file = os.path.join(package_home(globals()), 'www', 'default_py') _marker = [] # Create a new marker object def manage_addPythonScript(self, id, REQUEST=None, submit=None): """Add a Python script to a folder. """ id = str(id) id = self._setObject(id, PythonScript(id)) if REQUEST is not None: file = REQUEST.form.get('file', '') if type(file) is not type(''): file = file.read() if not file: file = open(_default_file).read() self._getOb(id).write(file) try: u = self.DestinationURL() except: u = REQUEST['URL1'] if submit==" Add and Edit ": u="%s/%s" % (u,quote(id)) REQUEST.RESPONSE.redirect(u+'/manage_main') return '' class PythonScript(Script, Historical, Cacheable): """Web-callable scripts written in a safe subset of Python. The function may include standard python code, so long as it does not attempt to use the "exec" statement or certain restricted builtins. """ meta_type='Script (Python)' _proxy_roles = () _params = _body = '' errors = warnings = () _v_change = 0 manage_options = ( {'label':'Edit', 'action':'ZPythonScriptHTML_editForm', 'help': ('PythonScripts', 'PythonScript_edit.stx')}, ) + BindingsUI.manage_options + ( {'label':'Test', 'action':'ZScriptHTML_tryForm', 'help': ('PythonScripts', 'PythonScript_test.stx')}, {'label':'Proxy', 'action':'manage_proxyForm', 'help': ('OFSP','DTML-DocumentOrMethod_Proxy.stx')}, ) + Historical.manage_options + SimpleItem.manage_options + \ Cacheable.manage_options def __init__(self, id): self.id = id self.ZBindings_edit(defaultBindings) self._makeFunction() security = ClassSecurityInfo() security.declareObjectProtected('View') security.declareProtected('View', '__call__') security.declareProtected('View management screens', 'ZPythonScriptHTML_editForm', 'manage_main', 'read', 'ZScriptHTML_tryForm', 'PrincipiaSearchSource', 'document_src', 'params', 'body', 'get_filepath') ZPythonScriptHTML_editForm = DTMLFile('www/pyScriptEdit', globals()) manage = manage_main = ZPythonScriptHTML_editForm ZPythonScriptHTML_editForm._setName('ZPythonScriptHTML_editForm') security.declareProtected('Change Python Scripts', 'ZPythonScriptHTML_editAction', 'ZPythonScript_setTitle', 'ZPythonScript_edit', 'ZPythonScriptHTML_upload', 'ZPythonScriptHTML_changePrefs') def ZPythonScriptHTML_editAction(self, REQUEST, title, params, body): """Change the script's main parameters.""" self.ZPythonScript_setTitle(title) self.ZPythonScript_edit(params, body) message = "Saved changes." return self.ZPythonScriptHTML_editForm(self, REQUEST, manage_tabs_message=message) def ZPythonScript_setTitle(self, title): title = str(title) if self.title != title: self.title = title self.ZCacheable_invalidate() def ZPythonScript_edit(self, params, body): self._validateProxy() if self.wl_isLocked(): raise ResourceLockedError, "The script is locked via WebDAV." if type(body) is not type(''): body = body.read() if self._params <> params or self._body <> body or self._v_change: self._params = str(params) self.write(body) def ZPythonScriptHTML_upload(self, REQUEST, file=''): """Replace the body of the script with the text in file.""" if self.wl_isLocked(): raise ResourceLockedError, "The script is locked via WebDAV." if type(file) is not type(''): if not file: raise ValueError, 'File not specified' file = file.read() self.write(file) message = 'Saved changes.' return self.ZPythonScriptHTML_editForm(self, REQUEST, manage_tabs_message=message) def ZPythonScriptHTML_changePrefs(self, REQUEST, height=None, width=None, dtpref_cols="100%", dtpref_rows="20"): """Change editing preferences.""" dr = {"Taller":5, "Shorter":-5}.get(height, 0) dc = {"Wider":5, "Narrower":-5}.get(width, 0) if isinstance(height, int): dtpref_rows = height if isinstance(width, int) or \ isinstance(width, str) and width.endswith('%'): dtpref_cols = width rows = str(max(1, int(dtpref_rows) + dr)) cols = str(dtpref_cols) if cols.endswith('%'): cols = str(min(100, max(25, int(cols[:-1]) + dc))) + '%' else: cols = str(max(35, int(cols) + dc)) e = (DateTime("GMT") + 365).rfc822() setCookie = REQUEST["RESPONSE"].setCookie setCookie("dtpref_rows", rows, path='/', expires=e) setCookie("dtpref_cols", cols, path='/', expires=e) REQUEST.other.update({"dtpref_cols":cols, "dtpref_rows":rows}) return self.manage_main(self, REQUEST) def ZScriptHTML_tryParams(self): """Parameters to test the script with.""" param_names = [] for name in self._params.split(','): name = name.strip() if name and name[0] != '*' and re.match('\w',name): param_names.append(name.split('=', 1)[0].strip()) return param_names def manage_historyCompare(self, rev1, rev2, REQUEST, historyComparisonResults=''): return PythonScript.inheritedAttribute('manage_historyCompare')( self, rev1, rev2, REQUEST, historyComparisonResults=html_diff(rev1.read(), rev2.read()) ) def __setstate__(self, state): Script.__setstate__(self, state) if (getattr(self, 'Python_magic', None) != Python_magic or getattr(self, 'Script_magic', None) != Script_magic): global _log_complaint if _log_complaint: LOG.info(_log_complaint) _log_complaint = 0 # Changes here won't get saved, unless this Script is edited. body = self._body.rstrip() if body: self._body = body + '\n' self._compile() self._v_change = 1 elif self._code is None: self._v_ft = None else: self._newfun(marshal.loads(self._code)) def _compiler(self, *args, **kw): return compile_restricted_function(*args, **kw) def _compile(self): bind_names = self.getBindingAssignments().getAssignedNamesInOrder() r = self._compiler(self._params, self._body or 'pass', self.id, self.meta_type, globalize=bind_names) code = r[0] errors = r[1] self.warnings = tuple(r[2]) if errors: self._code = None self._v_ft = None self._setFuncSignature((), (), 0) # Fix up syntax errors. filestring = ' File "",' for i in range(len(errors)): line = errors[i] if line.startswith(filestring): errors[i] = line.replace(filestring, ' Script', 1) self.errors = errors return self._code = marshal.dumps(code) self.errors = () f = self._newfun(code) fc = f.func_code self._setFuncSignature(f.func_defaults, fc.co_varnames, fc.co_argcount) self.Python_magic = Python_magic self.Script_magic = Script_magic self._v_change = 0 def _newfun(self, code): g = get_safe_globals() g['_getattr_'] = guarded_getattr g['__debug__'] = __debug__ # it doesn't really matter what __name__ is, *but* # - we need a __name__ # (see testPythonScript.TestPythonScriptGlobals.test__name__) # - it should not contain a period, so we can't use the id # (see https://bugs.launchpad.net/zope2/+bug/142731/comments/4) # - with Python 2.6 it should not be None # (see testPythonScript.TestPythonScriptGlobals.test_filepath) g['__name__'] = 'script' l = {} exec code in g, l f = l.values()[0] self._v_ft = (f.func_code, g, f.func_defaults or ()) return f def _makeFunction(self): self.ZCacheable_invalidate() self._compile() if not (aq_parent(self) is None or hasattr(self, '_filepath')): # It needs a _filepath, and has an acquisition wrapper. self._filepath = self.get_filepath() def _editedBindings(self): if getattr(self, '_v_ft', None) is not None: self._makeFunction() def _exec(self, bound_names, args, kw): """Call a Python Script Calling a Python Script is an actual function invocation. """ # Retrieve the value from the cache. keyset = None if self.ZCacheable_isCachingEnabled(): # Prepare a cache key. keyset = kw.copy() asgns = self.getBindingAssignments() name_context = asgns.getAssignedName('name_context', None) if name_context: keyset[name_context] = aq_parent(self).getPhysicalPath() name_subpath = asgns.getAssignedName('name_subpath', None) if name_subpath: keyset[name_subpath] = self._getTraverseSubpath() # Note: perhaps we should cache based on name_ns also. keyset['*'] = args result = self.ZCacheable_get(keywords=keyset, default=_marker) if result is not _marker: # Got a cached value. return result #__traceback_info__ = bound_names, args, kw, self.func_defaults ft = self._v_ft if ft is None: __traceback_supplement__ = ( PythonScriptTracebackSupplement, self) raise RuntimeError, '%s %s has errors.' % (self.meta_type, self.id) fcode, g, fadefs = ft g = g.copy() if bound_names is not None: g.update(bound_names) g['__traceback_supplement__'] = ( PythonScriptTracebackSupplement, self, -1) g['__file__'] = getattr(self, '_filepath', None) or self.get_filepath() f = new.function(fcode, g, None, fadefs) try: result = f(*args, **kw) except SystemExit: raise ValueError('SystemExit cannot be raised within a PythonScript') if keyset is not None: # Store the result in the cache. self.ZCacheable_set(result, keywords=keyset) return result def manage_afterAdd(self, item, container): if item is self: self._filepath = self.get_filepath() def manage_beforeDelete(self, item, container): # shut up deprecation warnings pass def manage_afterClone(self, item): # shut up deprecation warnings pass def get_filepath(self): return self.meta_type + ':' + '/'.join(self.getPhysicalPath()) def manage_haveProxy(self,r): return r in self._proxy_roles def _validateProxy(self, roles=None): if roles is None: roles = self._proxy_roles if not roles: return user = getSecurityManager().getUser() if user is not None and user.allowed(self, roles): return raise Forbidden, ('You are not authorized to change %s ' 'because you do not have proxy roles.\n' % (self.id, user, roles)) security.declareProtected('Change proxy roles', 'manage_proxyForm', 'manage_proxy') manage_proxyForm = DTMLFile('www/pyScriptProxy', globals()) @requestmethod('POST') def manage_proxy(self, roles=(), REQUEST=None): "Change Proxy Roles" self._validateProxy(roles) self._validateProxy() self.ZCacheable_invalidate() self._proxy_roles=tuple(roles) if REQUEST: return MessageDialog( title ='Success!', message='Your changes have been saved', action ='manage_main') security.declareProtected('Change Python Scripts', 'PUT', 'manage_FTPput', 'write', 'manage_historyCopy', 'manage_beforeHistoryCopy', 'manage_afterHistoryCopy') def PUT(self, REQUEST, RESPONSE): """ Handle HTTP PUT requests """ self.dav__init(REQUEST, RESPONSE) self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1) self.write(REQUEST.get('BODY', '')) RESPONSE.setStatus(204) return RESPONSE manage_FTPput = PUT def write(self, text): """ Change the Script by parsing a read()-style source text. """ self._validateProxy() mdata = self._metadata_map() bindmap = self.getBindingAssignments().getAssignedNames() bup = 0 st = 0 try: while 1: # Find the next non-empty line m = _nonempty_line.search(text, st) if not m: # There were no non-empty body lines body = '' break line = m.group(0).strip() if line[:2] != '##': # We have found the first line of the body body = text[m.start(0):] break st = m.end(0) # Parse this header line if len(line) == 2 or line[2] == ' ' or '=' not in line: # Null header line continue k, v = line[2:].split('=', 1) k = k.strip().lower() v = v.strip() if not mdata.has_key(k): raise SyntaxError, 'Unrecognized header line "%s"' % line if v == mdata[k]: # Unchanged value continue # Set metadata value if k == 'title': self.title = v elif k == 'parameters': self._params = v elif k[:5] == 'bind ': bindmap[_nice_bind_names[k[5:]]] = v bup = 1 body = body.rstrip() if body: body = body + '\n' if body != self._body: self._body = body if bup: self.ZBindings_edit(bindmap) else: self._makeFunction() except: LOG.error('write failed', exc_info=sys.exc_info()) raise def manage_FTPget(self): "Get source for FTP download" self.REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain') return self.read() def _metadata_map(self): m = { 'title': self.title, 'parameters': self._params, } bindmap = self.getBindingAssignments().getAssignedNames() for k, v in _nice_bind_names.items(): m['bind '+k] = bindmap.get(v, '') return m def read(self): """ Generate a text representation of the Script source. Includes specially formatted comment lines for parameters, bindings, and the title. """ # Construct metadata header lines, indented the same as the body. m = _first_indent.search(self._body) if m: prefix = m.group(0) + '##' else: prefix = '##' hlines = ['%s %s "%s"' % (prefix, self.meta_type, self.id)] mm = self._metadata_map().items() mm.sort() for kv in mm: hlines.append('%s=%s' % kv) if self.errors: hlines.append('') hlines.append(' Errors:') for line in self.errors: hlines.append(' ' + line) if self.warnings: hlines.append('') hlines.append(' Warnings:') for line in self.warnings: hlines.append(' ' + line) hlines.append('') return ('\n' + prefix).join(hlines) + '\n' + self._body def params(self): return self._params def body(self): return self._body def get_size(self): return len(self.read()) getSize = get_size def PrincipiaSearchSource(self): "Support for searching - the document's contents are searched." return "%s\n%s" % (self._params, self._body) def document_src(self, REQUEST=None, RESPONSE=None): """Return unprocessed document source.""" if RESPONSE is not None: RESPONSE.setHeader('Content-Type', 'text/plain') return self.read() InitializeClass(PythonScript) class PythonScriptTracebackSupplement: """Implementation of ITracebackSupplement""" def __init__(self, script, line=0): self.object = script # If line is set to -1, it means to use tb_lineno. self.line = line _first_indent = re.compile('(?m)^ *(?! |$)') _nonempty_line = re.compile('(?m)^(.*\S.*)$') _nice_bind_names = {'context': 'name_context', 'container': 'name_container', 'script': 'name_m_self', 'namespace': 'name_ns', 'subpath': 'name_subpath'} zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/Extensions/0000755000175000017500000000000012214017673027457 5ustar arnauarnauzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/Extensions/RemotePS.py0000644000175000017500000000115112214017673031525 0ustar arnauarnau''' RemotePS.py External Method that allows you to remotely (via XML-RPC, for instance) execute restricted Python code. For example, create an External Method 'restricted_exec' in your Zope root, and you can remotely call: foobarsize = s.foo.bar.restricted_exec('len(context.objectIds())') ''' from Products.PythonScripts.PythonScript import PythonScript from string import join def restricted_exec(self, body, varmap=None): ps = PythonScript('temp') if varmap is None: varmap = {} ps.ZPythonScript_edit(join(varmap.keys(), ','), body) return apply(ps.__of__(self), varmap.values()) zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/0000755000175000017500000000000012214017673026462 5ustar arnauarnauzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/testBindings.py0000644000175000017500000002511512214017673031475 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test Bindings $Id: testBindings.py 113162 2010-06-05 17:35:00Z hannosch $ """ import unittest import ZODB import transaction from Acquisition import Implicit from AccessControl import ClassSecurityInfo from AccessControl.class_init import InitializeClass from OFS.ObjectManager import ObjectManager from OFS.Folder import Folder class SecurityManager: def __init__(self, reject=0): self.calls = [] self.reject = reject def validate(self, *args): from AccessControl import Unauthorized self.calls.append(('validate', args)) if self.reject: raise Unauthorized return 1 def validateValue(self, *args): from AccessControl import Unauthorized self.calls.append(('validateValue', args)) if self.reject: raise Unauthorized return 1 def checkPermission(self, *args): self.calls.append(('checkPermission', args)) return not self.reject def addContext(self, *args): self.calls.append(('addContext', args)) return 1 def removeContext(self, *args): self.calls.append(('removeContext', args)) return 1 class UnderprivilegedUser: def getId(self): return 'underprivileged' def allowed(self, object, object_roles=None): return 0 class RivilegedUser: def getId(self): return 'privileged' def allowed(self, object, object_roles=None): return 1 class FauxRoot(ObjectManager): def getPhysicalPath(self): return ('',) def __repr__(self): return '' class FauxFolder(Folder): security = ClassSecurityInfo() security.declareObjectPrivate() security.declarePrivate('__repr__') def __repr__(self): return '' % self.getId() security.declarePublic('methodWithRoles') def methodWithRoles(self): return 'method called' InitializeClass(FauxFolder) class TestBindings(unittest.TestCase): def setUp(self): from Testing.ZODButil import makeDB transaction.begin() self.db = makeDB() self.connection = self.db.open() def tearDown(self): from Testing.ZODButil import cleanDB from AccessControl.SecurityManagement import noSecurityManager noSecurityManager() transaction.abort() self.connection.close() self.db.close() cleanDB() def _getRoot(self): from Testing.makerequest import makerequest #true_root = self.connection.root()[ 'Application' ] #true_root = self.connection.root() #return makerequest(true_root) return makerequest(FauxRoot()) def _makeTree(self): root = self._getRoot() guarded = FauxFolder() guarded._setId('guarded') guarded.__roles__ = ( 'Manager', ) root._setOb('guarded', guarded) guarded = root._getOb('guarded') open = FauxFolder() open._setId('open') open.__roles__ = ( 'Anonymous', ) guarded._setOb('open', open) bound_unused_container_ps = self._newPS('return 1') guarded._setOb('bound_unused_container_ps', bound_unused_container_ps) bound_used_container_ps = self._newPS('return container.id') guarded._setOb('bound_used_container_ps', bound_used_container_ps) bound_used_container_ok_ps = self._newPS('return container.id') open._setOb('bound_used_container_ok_ps', bound_used_container_ok_ps) bound_unused_context_ps = self._newPS('return 1') guarded._setOb('bound_unused_context_ps', bound_unused_context_ps) bound_used_context_ps = self._newPS('return context.id') guarded._setOb('bound_used_context_ps', bound_used_context_ps) bound_used_context_methodWithRoles_ps = self._newPS( 'return context.methodWithRoles()') guarded._setOb('bound_used_context_methodWithRoles_ps', bound_used_context_methodWithRoles_ps) container_ps = self._newPS('return container') guarded._setOb('container_ps', container_ps) container_str_ps = self._newPS('return str(container)') guarded._setOb('container_str_ps', container_str_ps) context_ps = self._newPS('return context') guarded._setOb('context_ps', context_ps) context_str_ps = self._newPS('return str(context)') guarded._setOb('context_str_ps', context_str_ps) return root def _newPS(self, txt, bind=None): from Products.PythonScripts.PythonScript import PythonScript ps = PythonScript('ps') #ps.ZBindings_edit(bind or {}) ps.write(txt) ps._makeFunction() return ps # These test that the mere binding of context or container, when the # user doesn't have access to them, doesn't raise an unauthorized. An # exception *will* be raised if the script attempts to use them. This # is a b/w compatibility hack: see Bindings.py for details. def test_bound_unused_container(self): from AccessControl.SecurityManagement import newSecurityManager newSecurityManager(None, UnderprivilegedUser()) root = self._makeTree() guarded = root._getOb('guarded') ps = guarded._getOb('bound_unused_container_ps') self.assertEqual(ps(), 1) def test_bound_used_container(self): from AccessControl.SecurityManagement import newSecurityManager from AccessControl import Unauthorized newSecurityManager(None, UnderprivilegedUser()) root = self._makeTree() guarded = root._getOb('guarded') ps = guarded._getOb('bound_used_container_ps') self.assertRaises(Unauthorized, ps) ps = guarded._getOb('container_str_ps') self.assertRaises(Unauthorized, ps) ps = guarded._getOb('container_ps') container = ps() self.assertRaises(Unauthorized, container) self.assertRaises(Unauthorized, container.index_html) try: str(container) except Unauthorized: pass else: self.fail("str(container) didn't raise Unauthorized!") ps = guarded._getOb('bound_used_container_ps') ps._proxy_roles = ( 'Manager', ) ps() ps = guarded._getOb('container_str_ps') ps._proxy_roles = ( 'Manager', ) ps() def test_bound_used_container_allowed(self): from AccessControl.SecurityManagement import newSecurityManager newSecurityManager(None, UnderprivilegedUser()) root = self._makeTree() guarded = root._getOb('guarded') open = guarded._getOb('open') ps = open.unrestrictedTraverse('bound_used_container_ok_ps') self.assertEqual(ps(), 'open') def test_bound_unused_context(self): from AccessControl.SecurityManagement import newSecurityManager newSecurityManager(None, UnderprivilegedUser()) root = self._makeTree() guarded = root._getOb('guarded') ps = guarded._getOb('bound_unused_context_ps') self.assertEqual(ps(), 1) def test_bound_used_context(self): from AccessControl.SecurityManagement import newSecurityManager from AccessControl import Unauthorized newSecurityManager(None, UnderprivilegedUser()) root = self._makeTree() guarded = root._getOb('guarded') ps = guarded._getOb('bound_used_context_ps') self.assertRaises(Unauthorized, ps) ps = guarded._getOb('context_str_ps') self.assertRaises(Unauthorized, ps) ps = guarded._getOb('context_ps') context = ps() self.assertRaises(Unauthorized, context) self.assertRaises(Unauthorized, context.index_html) try: str(context) except Unauthorized: pass else: self.fail("str(context) didn't raise Unauthorized!") ps = guarded._getOb('bound_used_context_ps') ps._proxy_roles = ( 'Manager', ) ps() ps = guarded._getOb('context_str_ps') ps._proxy_roles = ( 'Manager', ) ps() def test_bound_used_context_allowed(self): from AccessControl.SecurityManagement import newSecurityManager newSecurityManager(None, UnderprivilegedUser()) root = self._makeTree() guarded = root._getOb('guarded') open = guarded._getOb('open') ps = open.unrestrictedTraverse('bound_used_context_ps') self.assertEqual(ps(), 'open') def test_ok_no_bindings(self): from AccessControl.SecurityManagement import newSecurityManager newSecurityManager(None, UnderprivilegedUser()) root = self._makeTree() guarded = root._getOb('guarded') boundless_ps = self._newPS('return 42') guarded._setOb('boundless_ps', boundless_ps) boundless_ps = guarded._getOb('boundless_ps') # # Clear the bindings, so that the script may execute. # boundless_ps.ZBindings_edit( {'name_context': '', 'name_container': '', 'name_m_self': '', 'name_ns': '', 'name_subpath': ''}) self.assertEqual(boundless_ps(), 42) def test_bound_used_context_method_w_roles(self): from AccessControl.SecurityManagement import newSecurityManager from AccessControl import Unauthorized newSecurityManager(None, UnderprivilegedUser()) root = self._makeTree() guarded = root._getOb('guarded') # Assert that we can call a protected method, even though we have # no access to the context directly. ps = guarded._getOb('bound_used_context_ps') self.assertRaises(Unauthorized, ps) ps = guarded._getOb('bound_used_context_methodWithRoles_ps') self.assertEqual(ps(), 'method called') def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestBindings)) return suite if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/__init__.py0000644000175000017500000000003012214017673030564 0ustar arnauarnau""" Python package. """ zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/0000755000175000017500000000000012214017673030335 5ustar arnauarnau././@LongLink0000000000000000000000000000016200000000000011564 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/global_is_declaration.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/global_is_d0000644000175000017500000000005312214017673032514 0ustar arnauarnaucontainer = container + 1 return container ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/mutate_literals.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/mutate_lite0000644000175000017500000000023712214017673032576 0ustar arnauarnaul1 = [1, 2, 3] l2 = [l1[0], l1[1]] l2.extend(l1) l2.append(4) del l2[:2] del l2[0] l2[-2:] = [] d = {'a': 1, 'b': l2[0]} d['a'] = 0 del d['a'] return l2, d zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/for_loop.ps0000644000175000017500000000006112214017673032515 0ustar arnauarnaua = 0 for x in range(10): a = a + 1 return a zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/filepath.ps0000644000175000017500000000011512214017673032472 0ustar arnauarnaureturn container('foo') # This test is meant to raise a deprecation warning. ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/class.__name__.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/class.__nam0000644000175000017500000000012712214017673032435 0ustar arnauarnauimport string class foo: pass return repr(foo).split()[1], repr(string).split()[1] ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/complex_print.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/complex_pri0000644000175000017500000000017612214017673032605 0ustar arnauarnaux = {'x': 1} y = range(3) print 'double' print printed, print 'x:', x['x'] print 'y:', y[0], y[1], y[2] print return printed ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/fibonacci.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/fibonacci.p0000644000175000017500000000012312214017673032427 0ustar arnauarnaul = [] a, b = 0, 1 while b < 100000000: l.append(b) a, b = b, a+b return l ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/ns_bind_invalid.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/ns_bind_inv0000644000175000017500000000005612214017673032551 0ustar arnauarnau##parameters=yes ##unknownheader='foo' return ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/while_loop.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/while_loop.0000644000175000017500000000005312214017673032475 0ustar arnauarnaua = 0 while a < 10: a = a + 1 return 1 ././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/subversive_except.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/subversive_0000644000175000017500000000021012214017673032605 0ustar arnauarnau# -*- python -*- # An attempt to bind an illegal name in an except clause try: 1/0 except ZeroDivisionError, __getattr__: pass zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/ns_bind.ps0000644000175000017500000000004312214017673032312 0ustar arnauarnau##parameters=yes no = 0 return yes ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/try_except.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/try_except.0000644000175000017500000000015612214017673032526 0ustar arnauarnaua = 0 b = 1 try: int('$') except ValueError: a = 1 try: int('1') except: b = 0 return a, b ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/boolean_map.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/boolean_map0000644000175000017500000000045012214017673032533 0ustar arnauarnauresult = [] for c in 'abcdef': result.append({'a-c': (c <= 'c') or None, 'd-f': (c >= 'd') and 2}) return result == [ {'a-c': 1, 'd-f': 0}, {'a-c': 1, 'd-f': 0}, {'a-c': 1, 'd-f': 0}, {'a-c': None, 'd-f': 2}, {'a-c': None, 'd-f': 2}, {'a-c': None, 'd-f': 2}, ] ././@LongLink0000000000000000000000000000016400000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/tuple_unpack_assignment.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/tuple_unpac0000644000175000017500000000007212214017673032576 0ustar arnauarnaud = {} (d['a'], d['b'], d['c'], x) = range(4) return d, x ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/simple_print.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/simple_prin0000644000175000017500000000004112214017673032574 0ustar arnauarnauprint 'a', 1, [] return printed ././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/big_boolean.pszope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/tscripts/big_boolean0000644000175000017500000000016712214017673032524 0ustar arnauarnau'ab'[1] mab = {'a': 1, 'b': 2} r10 = range(10) return r10[3:5][1] == 4 and r10[mab['b']] and 1 < mab['ab'[r10[1]]] < 3 zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/tests/testPythonScript.py0000644000175000017500000002572112214017673032411 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import os, unittest, warnings from Products.PythonScripts.PythonScript import PythonScript from AccessControl.SecurityManagement import newSecurityManager from AccessControl.SecurityManagement import noSecurityManager from RestrictedPython.tests.verify import verify if __name__=='__main__': here = os.getcwd() else: here = os.path.dirname(__file__) if not here: here = os.getcwd() class WarningInterceptor: _old_stderr = None _our_stderr_stream = None def _trap_warning_output( self ): if self._old_stderr is not None: return import sys from StringIO import StringIO self._old_stderr = sys.stderr self._our_stderr_stream = sys.stderr = StringIO() def _free_warning_output( self ): if self._old_stderr is None: return import sys sys.stderr = self._old_stderr # Test Classes def readf(name): path = os.path.join(here, 'tscripts', '%s.ps' % name) return open(path, 'r').read() class VerifiedPythonScript(PythonScript): def _newfun(self, code): verify(code) return PythonScript._newfun(self, code) class PythonScriptTestBase(unittest.TestCase): def setUp(self): from AccessControl import ModuleSecurityInfo as MSI from AccessControl.SecurityInfo import _moduleSecurity from AccessControl.SecurityInfo import _appliedModuleSecurity self._ms_before = _moduleSecurity.copy() self._ams_before = _appliedModuleSecurity.copy() MSI('string').declarePublic('split') MSI('sets').declarePublic('Set') newSecurityManager(None, None) def tearDown(self): from AccessControl.SecurityInfo import _moduleSecurity from AccessControl.SecurityInfo import _appliedModuleSecurity noSecurityManager() _moduleSecurity.clear() _moduleSecurity.update(self._ms_before) _appliedModuleSecurity.clear() _appliedModuleSecurity.update(self._ams_before) def _newPS(self, txt, bind=None): ps = VerifiedPythonScript('ps') ps.ZBindings_edit(bind or {}) ps.write(txt) ps._makeFunction() if ps.errors: raise SyntaxError, ps.errors[0] return ps def _filePS(self, fname, bind=None): ps = VerifiedPythonScript(fname) ps.ZBindings_edit(bind or {}) ps.write(readf(fname)) ps._makeFunction() if ps.errors: raise SyntaxError, ps.errors[0] return ps class TestPythonScriptNoAq(PythonScriptTestBase): def testEmpty(self): empty = self._newPS('')() self.failUnless(empty is None) def testIndented(self): # This failed to compile in Zope 2.4.0b2. res = self._newPS('if 1:\n return 2')() self.assertEqual(res, 2) def testReturn(self): res = self._newPS('return 1')() self.assertEqual(res, 1) def testReturnNone(self): res = self._newPS('return')() self.failUnless(res is None) def testParam1(self): res = self._newPS('##parameters=x\nreturn x')('txt') self.assertEqual(res, 'txt') def testParam2(self): eq = self.assertEqual one, two = self._newPS('##parameters=x,y\nreturn x,y')('one','two') eq(one, 'one') eq(two, 'two') def testParam26(self): import string params = string.letters[:26] sparams = ','.join(params) ps = self._newPS('##parameters=%s\nreturn %s' % (sparams, sparams)) res = ps(*params) self.assertEqual(res, tuple(params)) def testArithmetic(self): res = self._newPS('return 1 * 5 + 4 / 2 - 6')() self.assertEqual(res, 1) def testReduce(self): res = self._newPS('return reduce(lambda x, y: x + y, [1,3,5,7])')() self.assertEqual(res, 16) res = self._newPS('return reduce(lambda x, y: x + y, [1,3,5,7], 1)')() self.assertEqual(res, 17) def testImport(self): eq = self.assertEqual a, b, c = self._newPS('import string; return string.split("a b c")')() eq(a, 'a') eq(b, 'b') eq(c, 'c') def testWhileLoop(self): res = self._filePS('while_loop')() self.assertEqual(res, 1) def testForLoop(self): res = self._filePS('for_loop')() self.assertEqual(res, 10) def testMutateLiterals(self): eq = self.assertEqual l, d = self._filePS('mutate_literals')() eq(l, [2]) eq(d, {'b': 2}) def testTupleUnpackAssignment(self): eq = self.assertEqual d, x = self._filePS('tuple_unpack_assignment')() eq(d, {'a': 0, 'b': 1, 'c': 2}) eq(x, 3) def testDoubleNegation(self): res = self._newPS('return not not "this"')() self.assertEqual(res, 1) def testTryExcept(self): eq = self.assertEqual a, b = self._filePS('try_except')() eq(a, 1) eq(b, 1) def testBigBoolean(self): res = self._filePS('big_boolean')() self.failUnless(res) def testFibonacci(self): res = self._filePS('fibonacci')() self.assertEqual( res, [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986]) def testSimplePrint(self): res = self._filePS('simple_print')() self.assertEqual(res, 'a 1 []\n') def testComplexPrint(self): res = self._filePS('complex_print')() self.assertEqual(res, 'double\ndouble\nx: 1\ny: 0 1 2\n\n') def testNSBind(self): f = self._filePS('ns_bind', bind={'name_ns': '_'}) bound = f.__render_with_namespace__({'yes': 1, 'no': self.fail}) self.assertEqual(bound, 1) def testNSBindInvalidHeader(self): self.assertRaises(SyntaxError, self._filePS, 'ns_bind_invalid') def testBooleanMap(self): res = self._filePS('boolean_map')() self.failUnless(res) def testGetSize(self): f = self._filePS('complex_print') self.assertEqual(f.get_size(), len(f.read())) def testSet(self): res = self._newPS('from sets import Set; return len(Set([1,2,3]))')() self.assertEqual(res, 3) def testDateTime(self): res = self._newPS("return DateTime('2007/12/10').strftime('%d.%m.%Y')")() self.assertEqual(res, '10.12.2007') def testRaiseSystemExitLaunchpad257269(self): ps = self._newPS("raise SystemExit") self.assertRaises(ValueError, ps) def testEncodingTestDotTestAllLaunchpad257276(self): ps = self._newPS("return 'foo'.encode('test.testall')") self.assertRaises(LookupError, ps) class TestPythonScriptErrors(PythonScriptTestBase): def assertPSRaises(self, error, path=None, body=None): assert not (path and body) and (path or body) if body is None: body = readf(path) if error is SyntaxError: self.assertRaises(SyntaxError, self._newPS, body) else: ps = self._newPS(body) self.assertRaises(error, ps) def testSubversiveExcept(self): self.assertPSRaises(SyntaxError, path='subversive_except') def testBadImports(self): from zExceptions import Unauthorized self.assertPSRaises(Unauthorized, body="from string import *") self.assertPSRaises(Unauthorized, body="from datetime import datetime") self.assertPSRaises(Unauthorized, body="import mmap") def testAttributeAssignment(self): # It's illegal to assign to attributes of anything that # doesn't has enabling security declared. # Classes (and their instances) defined by restricted code # are an exception -- they are fully readable and writable. cases = [("import string", "string"), ("def f(): pass", "f"), ] assigns = ["%s.splat = 'spam'", "setattr(%s, '_getattr_', lambda x, y: True)", "del %s.splat", ] for defn, name in cases: for asn in assigns: f = self._newPS(defn + "\n" + asn % name) self.assertRaises(TypeError, f) class TestPythonScriptGlobals(PythonScriptTestBase, WarningInterceptor): def setUp(self): PythonScriptTestBase.setUp(self) def tearDown(self): self._free_warning_output() PythonScriptTestBase.tearDown(self) def _exec(self, script, bound_names=None, args=None, kws=None): if args is None: args = () if kws is None: kws = {} bindings = {'name_container': 'container'} f = self._filePS(script, bindings) return f._exec(bound_names, args, kws) def testGlobalIsDeclaration(self): bindings = {'container': 7} results = self._exec('global_is_declaration', bindings) self.assertEqual(results, 8) def test__name__(self): f = self._filePS('class.__name__') self.assertEqual(f(), ("'script.foo'>", "'string'")) def test_filepath(self): # This test is meant to raise a deprecation warning. # It used to fail mysteriously instead. def warnMe(message): warnings.warn(message, stacklevel=2) try: f = self._filePS('filepath') self._trap_warning_output() results = f._exec({'container': warnMe}, (), {}) self._free_warning_output() warning = self._our_stderr_stream.getvalue() self.failUnless('UserWarning: foo' in warning) except TypeError, e: self.fail(e) class PythonScriptInterfaceConformanceTests(unittest.TestCase): def test_class_conforms_to_IWriteLock(self): from zope.interface.verify import verifyClass from webdav.interfaces import IWriteLock verifyClass(IWriteLock, PythonScript) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestPythonScriptNoAq)) suite.addTest(unittest.makeSuite(TestPythonScriptErrors)) suite.addTest(unittest.makeSuite(TestPythonScriptGlobals)) suite.addTest(unittest.makeSuite(PythonScriptInterfaceConformanceTests)) return suite def main(): unittest.TextTestRunner().run(test_suite()) if __name__ == '__main__': main() zope2.13-2.13.21/source/Products.PythonScripts/src/Products/PythonScripts/Utility.py0000644000175000017500000000153212214017673027336 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Utility module for making simple security assertions for Python scripts.""" __version__='$Revision: 1.6 $'[11:-2] # These have been relocated, and should be imported from AccessControl from AccessControl import allow_module, allow_class zope2.13-2.13.21/source/Products.PythonScripts/src/Products/__init__.py0000644000175000017500000000007012214017673024575 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.dottedname/0000755000175000017500000000000012214017676016545 5ustar arnauarnauzope2.13-2.13.21/source/zope.dottedname/setup.py0000644000175000017500000000447212214017676020266 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """ $Id: setup.py 104061 2009-09-15 13:04:15Z janjaapdriessen $ """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name="zope.dottedname", version = '3.4.6', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', description='Resolver for Python dotted names.', long_description='\n\n'.join(( read('src', 'zope', 'dottedname', 'README.txt'), read('CHANGES.txt'), )), keywords = 'resolve dotted name', classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.dottedname', license='ZPL 2.1', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope'], extras_require = dict( test=['zope.testing'], ), install_requires = ['setuptools'], include_package_data=True, zip_safe = False ) zope2.13-2.13.21/source/zope.dottedname/PKG-INFO0000644000175000017500000000706112214017676017646 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.dottedname Version: 3.4.6 Summary: Resolver for Python dotted names. Home-page: http://pypi.python.org/pypi/zope.dottedname Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ====================== Dotted Name Resolution ====================== The ``zope.dottedname`` module provides one function, ``resolve`` that resolves strings containing dotted names into the appropriate python object. Dotted names are resolved by importing modules and by getting attributes from imported modules. Names may be relative, provided the module they are relative to is supplied. Here are some examples of importing absolute names:: >>> from zope.dottedname.resolve import resolve >>> resolve('unittest') >>> resolve('datetime.datetime') >>> resolve('datetime.datetime.now') >>> resolve('non existent module') Traceback (most recent call last): ... ImportError: No module named non existent module >>> resolve('__doc__') Traceback (most recent call last): ... ImportError: No module named __doc__ >>> resolve('datetime.foo') Traceback (most recent call last): ... ImportError: No module named foo >>> resolve('os.path.split').__name__ 'split' Here are some examples of importing relative names:: >>> resolve('.split', 'os.path') >>> resolve('..system', 'os.path') >>> resolve('...datetime', 'os.path') NB: When relative names are imported, a module the name is relative to **must** be supplied:: >>> resolve('.split').__name__ Traceback (most recent call last): ... ValueError: relative name without base module CHANGES ======= 3.4.6 (2009-09-15) ------------------ - Make tests pass on python26. 3.4.5 (2009-01-27) ------------------ - Move README.txt in the egg, so tests works with the released egg as well. 3.4.4 (2009-01-27) ------------------ - Fix ReST in README.txt, fix broken tests with recent zope.testing. 3.4.3 (2008-12-02) ------------------ - More documentation and tests. 3.4.2 (2007-10-02) ------------------ - Fix broken release. 3.4.1 (2007-10-02) ------------------ - Updated package meta-data. 3.4.0 (2007-07-19) ------------------ - Initial Zope-independent release. Keywords: resolve dotted name Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.dottedname/pip-egg-info/0000755000175000017500000000000012214017676021026 5ustar arnauarnauzope2.13-2.13.21/source/zope.dottedname/pip-egg-info/zope.dottedname.egg-info/0000755000175000017500000000000012214017676025620 5ustar arnauarnauzope2.13-2.13.21/source/zope.dottedname/pip-egg-info/zope.dottedname.egg-info/PKG-INFO0000644000175000017500000000715712214017676026727 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.dottedname Version: 3.4.6 Summary: Resolver for Python dotted names. Home-page: http://pypi.python.org/pypi/zope.dottedname Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ====================== Dotted Name Resolution ====================== The ``zope.dottedname`` module provides one function, ``resolve`` that resolves strings containing dotted names into the appropriate python object. Dotted names are resolved by importing modules and by getting attributes from imported modules. Names may be relative, provided the module they are relative to is supplied. Here are some examples of importing absolute names:: >>> from zope.dottedname.resolve import resolve >>> resolve('unittest') >>> resolve('datetime.datetime') >>> resolve('datetime.datetime.now') >>> resolve('non existent module') Traceback (most recent call last): ... ImportError: No module named non existent module >>> resolve('__doc__') Traceback (most recent call last): ... ImportError: No module named __doc__ >>> resolve('datetime.foo') Traceback (most recent call last): ... ImportError: No module named foo >>> resolve('os.path.split').__name__ 'split' Here are some examples of importing relative names:: >>> resolve('.split', 'os.path') >>> resolve('..system', 'os.path') >>> resolve('...datetime', 'os.path') NB: When relative names are imported, a module the name is relative to **must** be supplied:: >>> resolve('.split').__name__ Traceback (most recent call last): ... ValueError: relative name without base module CHANGES ======= 3.4.6 (2009-09-15) ------------------ - Make tests pass on python26. 3.4.5 (2009-01-27) ------------------ - Move README.txt in the egg, so tests works with the released egg as well. 3.4.4 (2009-01-27) ------------------ - Fix ReST in README.txt, fix broken tests with recent zope.testing. 3.4.3 (2008-12-02) ------------------ - More documentation and tests. 3.4.2 (2007-10-02) ------------------ - Fix broken release. 3.4.1 (2007-10-02) ------------------ - Updated package meta-data. 3.4.0 (2007-07-19) ------------------ - Initial Zope-independent release. Keywords: resolve dotted name Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.dottedname/pip-egg-info/zope.dottedname.egg-info/dependency_links.txt0000644000175000017500000000000112214017676031666 0ustar arnauarnau zope2.13-2.13.21/source/zope.dottedname/pip-egg-info/zope.dottedname.egg-info/requires.txt0000644000175000017500000000003712214017676030220 0ustar arnauarnausetuptools [test] zope.testingzope2.13-2.13.21/source/zope.dottedname/pip-egg-info/zope.dottedname.egg-info/namespace_packages.txt0000644000175000017500000000000512214017676032146 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.dottedname/pip-egg-info/zope.dottedname.egg-info/top_level.txt0000644000175000017500000000000512214017676030345 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.dottedname/pip-egg-info/zope.dottedname.egg-info/SOURCES.txt0000644000175000017500000000075512214017676027513 0ustar arnauarnausetup.cfg pip-egg-info/zope.dottedname.egg-info/PKG-INFO pip-egg-info/zope.dottedname.egg-info/SOURCES.txt pip-egg-info/zope.dottedname.egg-info/dependency_links.txt pip-egg-info/zope.dottedname.egg-info/namespace_packages.txt pip-egg-info/zope.dottedname.egg-info/not-zip-safe pip-egg-info/zope.dottedname.egg-info/requires.txt pip-egg-info/zope.dottedname.egg-info/top_level.txt src/zope/__init__.py src/zope/dottedname/__init__.py src/zope/dottedname/resolve.py src/zope/dottedname/tests.pyzope2.13-2.13.21/source/zope.dottedname/pip-egg-info/zope.dottedname.egg-info/not-zip-safe0000644000175000017500000000000112214017676030046 0ustar arnauarnau zope2.13-2.13.21/source/zope.dottedname/setup.cfg0000644000175000017500000000007312214017676020366 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.dottedname/buildout.cfg0000644000175000017500000000014112214017676021051 0ustar arnauarnau[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = zope.dottedname zope2.13-2.13.21/source/zope.dottedname/bootstrap.py0000644000175000017500000000337212214017676021141 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 73113 2007-03-09 11:49:38Z baijum $ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.dottedname/CHANGES.txt0000644000175000017500000000112012214017676020350 0ustar arnauarnauCHANGES ======= 3.4.6 (2009-09-15) ------------------ - Make tests pass on python26. 3.4.5 (2009-01-27) ------------------ - Move README.txt in the egg, so tests works with the released egg as well. 3.4.4 (2009-01-27) ------------------ - Fix ReST in README.txt, fix broken tests with recent zope.testing. 3.4.3 (2008-12-02) ------------------ - More documentation and tests. 3.4.2 (2007-10-02) ------------------ - Fix broken release. 3.4.1 (2007-10-02) ------------------ - Updated package meta-data. 3.4.0 (2007-07-19) ------------------ - Initial Zope-independent release. zope2.13-2.13.21/source/zope.dottedname/src/0000755000175000017500000000000012214017676017334 5ustar arnauarnauzope2.13-2.13.21/source/zope.dottedname/src/zope/0000755000175000017500000000000012214017676020311 5ustar arnauarnauzope2.13-2.13.21/source/zope.dottedname/src/zope/__init__.py0000644000175000017500000000007012214017676022417 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/zope.dottedname/src/zope/dottedname/0000755000175000017500000000000012214017676022435 5ustar arnauarnauzope2.13-2.13.21/source/zope.dottedname/src/zope/dottedname/resolve.py0000644000175000017500000000245112214017676024470 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Dotted name support $Id: resolve.py 38682 2005-09-29 09:12:45Z jim $ """ def resolve(name, module=None): name = name.split('.') if not name[0]: if module is None: raise ValueError("relative name without base module") module = module.split('.') name.pop(0) while not name[0]: module.pop() name.pop(0) name = module + name used = name.pop(0) found = __import__(used) for n in name: used += '.' + n try: found = getattr(found, n) except AttributeError: __import__(used) found = getattr(found, n) return found zope2.13-2.13.21/source/zope.dottedname/src/zope/dottedname/__init__.py0000644000175000017500000000000212214017676024536 0ustar arnauarnau# zope2.13-2.13.21/source/zope.dottedname/src/zope/dottedname/README.txt0000644000175000017500000000305212214017676024133 0ustar arnauarnau====================== Dotted Name Resolution ====================== The ``zope.dottedname`` module provides one function, ``resolve`` that resolves strings containing dotted names into the appropriate python object. Dotted names are resolved by importing modules and by getting attributes from imported modules. Names may be relative, provided the module they are relative to is supplied. Here are some examples of importing absolute names:: >>> from zope.dottedname.resolve import resolve >>> resolve('unittest') >>> resolve('datetime.datetime') >>> resolve('datetime.datetime.now') >>> resolve('non existent module') Traceback (most recent call last): ... ImportError: No module named non existent module >>> resolve('__doc__') Traceback (most recent call last): ... ImportError: No module named __doc__ >>> resolve('datetime.foo') Traceback (most recent call last): ... ImportError: No module named foo >>> resolve('os.path.split').__name__ 'split' Here are some examples of importing relative names:: >>> resolve('.split', 'os.path') >>> resolve('..system', 'os.path') >>> resolve('...datetime', 'os.path') NB: When relative names are imported, a module the name is relative to **must** be supplied:: >>> resolve('.split').__name__ Traceback (most recent call last): ... ValueError: relative name without base module zope2.13-2.13.21/source/zope.dottedname/src/zope/dottedname/tests.py0000644000175000017500000000220412214017676024147 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """test resolution of dotted names $Id: tests.py 95202 2009-01-27 14:15:28Z thefunny42 $ """ import os,unittest from zope.testing.doctest import DocFileSuite,REPORT_NDIFF,ELLIPSIS def test_suite(): return unittest.TestSuite(( DocFileSuite( os.path.abspath(os.path.join(os.path.dirname(__file__), 'README.txt')), optionflags=REPORT_NDIFF|ELLIPSIS, module_relative=False, ), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/zope.dottedname/src/zope.dottedname.egg-info/0000755000175000017500000000000012214017676024126 5ustar arnauarnauzope2.13-2.13.21/source/zope.dottedname/src/zope.dottedname.egg-info/PKG-INFO0000644000175000017500000000706112214017676025227 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.dottedname Version: 3.4.6 Summary: Resolver for Python dotted names. Home-page: http://pypi.python.org/pypi/zope.dottedname Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ====================== Dotted Name Resolution ====================== The ``zope.dottedname`` module provides one function, ``resolve`` that resolves strings containing dotted names into the appropriate python object. Dotted names are resolved by importing modules and by getting attributes from imported modules. Names may be relative, provided the module they are relative to is supplied. Here are some examples of importing absolute names:: >>> from zope.dottedname.resolve import resolve >>> resolve('unittest') >>> resolve('datetime.datetime') >>> resolve('datetime.datetime.now') >>> resolve('non existent module') Traceback (most recent call last): ... ImportError: No module named non existent module >>> resolve('__doc__') Traceback (most recent call last): ... ImportError: No module named __doc__ >>> resolve('datetime.foo') Traceback (most recent call last): ... ImportError: No module named foo >>> resolve('os.path.split').__name__ 'split' Here are some examples of importing relative names:: >>> resolve('.split', 'os.path') >>> resolve('..system', 'os.path') >>> resolve('...datetime', 'os.path') NB: When relative names are imported, a module the name is relative to **must** be supplied:: >>> resolve('.split').__name__ Traceback (most recent call last): ... ValueError: relative name without base module CHANGES ======= 3.4.6 (2009-09-15) ------------------ - Make tests pass on python26. 3.4.5 (2009-01-27) ------------------ - Move README.txt in the egg, so tests works with the released egg as well. 3.4.4 (2009-01-27) ------------------ - Fix ReST in README.txt, fix broken tests with recent zope.testing. 3.4.3 (2008-12-02) ------------------ - More documentation and tests. 3.4.2 (2007-10-02) ------------------ - Fix broken release. 3.4.1 (2007-10-02) ------------------ - Updated package meta-data. 3.4.0 (2007-07-19) ------------------ - Initial Zope-independent release. Keywords: resolve dotted name Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.dottedname/src/zope.dottedname.egg-info/dependency_links.txt0000644000175000017500000000000112214017676030174 0ustar arnauarnau zope2.13-2.13.21/source/zope.dottedname/src/zope.dottedname.egg-info/requires.txt0000644000175000017500000000003712214017676026526 0ustar arnauarnausetuptools [test] zope.testingzope2.13-2.13.21/source/zope.dottedname/src/zope.dottedname.egg-info/namespace_packages.txt0000644000175000017500000000000512214017676030454 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.dottedname/src/zope.dottedname.egg-info/top_level.txt0000644000175000017500000000000512214017676026653 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.dottedname/src/zope.dottedname.egg-info/SOURCES.txt0000644000175000017500000000076212214017676026017 0ustar arnauarnauCHANGES.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.dottedname.egg-info/PKG-INFO src/zope.dottedname.egg-info/SOURCES.txt src/zope.dottedname.egg-info/dependency_links.txt src/zope.dottedname.egg-info/namespace_packages.txt src/zope.dottedname.egg-info/not-zip-safe src/zope.dottedname.egg-info/requires.txt src/zope.dottedname.egg-info/top_level.txt src/zope/dottedname/README.txt src/zope/dottedname/__init__.py src/zope/dottedname/resolve.py src/zope/dottedname/tests.pyzope2.13-2.13.21/source/zope.dottedname/src/zope.dottedname.egg-info/not-zip-safe0000644000175000017500000000000112214017676026354 0ustar arnauarnau zope2.13-2.13.21/source/tempstorage/0000755000175000017500000000000012214017510015762 5ustar arnauarnauzope2.13-2.13.21/source/tempstorage/setup.py0000644000175000017500000000254312214017510017500 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Setup for the tempstorage package """ from setuptools import setup, find_packages long_description = file("README.txt").read() + "\n" + \ file("CHANGES.txt").read() setup(name='tempstorage', version = '2.12.2', url='http://pypi.python.org/pypi/tempstorage', license='ZPL 2.1', description='A RAM-based storage for ZODB', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=long_description, packages=find_packages('src'), package_dir={'': 'src'}, install_requires=[ 'setuptools', 'ZODB3 >= 3.9.0', 'zope.testing', ], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/tempstorage/PKG-INFO0000644000175000017500000000405112214017510017057 0ustar arnauarnauMetadata-Version: 1.0 Name: tempstorage Version: 2.12.2 Summary: A RAM-based storage for ZODB Home-page: http://pypi.python.org/pypi/tempstorage Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== A storage implementation which uses RAM to persist objects, much like MappingStorage. Unlike MappingStorage, it needs not be packed to get rid of non-cyclic garbage and it does rudimentary conflict resolution. This is a ripoff of Jim's Packless bsddb3 storage. Changelog ========= 2.12.2 - 2012-10-14 ------------------- - Explicitly state distribution dependencies instead of re-using the ZODB test requirements. 2.12.1 - 2010-09-29 ------------------- - Disabled ``check_tid_ordering_w_commit`` test from BasicStorage, as it uses invalid test data. 2.12.0 - 2010-09-25 ------------------- - Require at least ZODB 3.9 and adjusted method signatures to disuse versions. - Expanded dependency on ZODB3 to include the test extra. 2.11.3 - 2010-06-05 ------------------- - Approximate PEP8 compliance. - Split out the ZODB protocol tests from the tests specific to the module. Make the local tests use "normal" unittest conventions. - Comply with repository policy. - Clean imports, docstrings; add an instance-level hook for GC parms. - Fix a test failure due to never-unghostified root in second connection. 2.11.2 - 2009-08-03 ------------------- - Added change log and readme. - Lauchpad #143736, #271395: fixed AttributeError' on _ltid in TempStorage 2.11.1 - 2008-08-05 ------------------- - Initial release as a stand-alone package. Platform: UNKNOWN zope2.13-2.13.21/source/tempstorage/pip-egg-info/0000755000175000017500000000000012214017510020243 5ustar arnauarnauzope2.13-2.13.21/source/tempstorage/pip-egg-info/tempstorage.egg-info/0000755000175000017500000000000012214017510024267 5ustar arnauarnauzope2.13-2.13.21/source/tempstorage/pip-egg-info/tempstorage.egg-info/PKG-INFO0000644000175000017500000000405112214017510025364 0ustar arnauarnauMetadata-Version: 1.0 Name: tempstorage Version: 2.12.2 Summary: A RAM-based storage for ZODB Home-page: http://pypi.python.org/pypi/tempstorage Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== A storage implementation which uses RAM to persist objects, much like MappingStorage. Unlike MappingStorage, it needs not be packed to get rid of non-cyclic garbage and it does rudimentary conflict resolution. This is a ripoff of Jim's Packless bsddb3 storage. Changelog ========= 2.12.2 - 2012-10-14 ------------------- - Explicitly state distribution dependencies instead of re-using the ZODB test requirements. 2.12.1 - 2010-09-29 ------------------- - Disabled ``check_tid_ordering_w_commit`` test from BasicStorage, as it uses invalid test data. 2.12.0 - 2010-09-25 ------------------- - Require at least ZODB 3.9 and adjusted method signatures to disuse versions. - Expanded dependency on ZODB3 to include the test extra. 2.11.3 - 2010-06-05 ------------------- - Approximate PEP8 compliance. - Split out the ZODB protocol tests from the tests specific to the module. Make the local tests use "normal" unittest conventions. - Comply with repository policy. - Clean imports, docstrings; add an instance-level hook for GC parms. - Fix a test failure due to never-unghostified root in second connection. 2.11.2 - 2009-08-03 ------------------- - Added change log and readme. - Lauchpad #143736, #271395: fixed AttributeError' on _ltid in TempStorage 2.11.1 - 2008-08-05 ------------------- - Initial release as a stand-alone package. Platform: UNKNOWN zope2.13-2.13.21/source/tempstorage/pip-egg-info/tempstorage.egg-info/dependency_links.txt0000644000175000017500000000000112214017510030335 0ustar arnauarnau zope2.13-2.13.21/source/tempstorage/pip-egg-info/tempstorage.egg-info/requires.txt0000644000175000017500000000004612214017510026667 0ustar arnauarnausetuptools ZODB3 >= 3.9.0 zope.testingzope2.13-2.13.21/source/tempstorage/pip-egg-info/tempstorage.egg-info/top_level.txt0000644000175000017500000000001412214017510027014 0ustar arnauarnautempstorage zope2.13-2.13.21/source/tempstorage/pip-egg-info/tempstorage.egg-info/SOURCES.txt0000644000175000017500000000142412214017510026154 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt MANIFEST.in README.txt setup.cfg pip-egg-info/tempstorage.egg-info/PKG-INFO pip-egg-info/tempstorage.egg-info/SOURCES.txt pip-egg-info/tempstorage.egg-info/dependency_links.txt pip-egg-info/tempstorage.egg-info/not-zip-safe pip-egg-info/tempstorage.egg-info/requires.txt pip-egg-info/tempstorage.egg-info/top_level.txt src/tempstorage/TemporaryStorage.py src/tempstorage/__init__.py src/tempstorage/component.xml src/tempstorage/config.py src/tempstorage.egg-info/PKG-INFO src/tempstorage.egg-info/SOURCES.txt src/tempstorage.egg-info/dependency_links.txt src/tempstorage.egg-info/not-zip-safe src/tempstorage.egg-info/requires.txt src/tempstorage.egg-info/top_level.txt src/tempstorage/tests/__init__.py src/tempstorage/tests/testTemporaryStorage.pyzope2.13-2.13.21/source/tempstorage/pip-egg-info/tempstorage.egg-info/not-zip-safe0000644000175000017500000000000112214017510026515 0ustar arnauarnau zope2.13-2.13.21/source/tempstorage/LICENSE.txt0000644000175000017500000000402612214017510017607 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/tempstorage/README.txt0000644000175000017500000000043012214017510017455 0ustar arnauarnauOverview ======== A storage implementation which uses RAM to persist objects, much like MappingStorage. Unlike MappingStorage, it needs not be packed to get rid of non-cyclic garbage and it does rudimentary conflict resolution. This is a ripoff of Jim's Packless bsddb3 storage. zope2.13-2.13.21/source/tempstorage/setup.cfg0000644000175000017500000000007312214017510017603 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/tempstorage/MANIFEST.in0000644000175000017500000000012212214017510017513 0ustar arnauarnauinclude *.txt recursive-include src * global-exclude *.pyc global-exclude *.pyo zope2.13-2.13.21/source/tempstorage/COPYRIGHT.txt0000644000175000017500000000004012214017510020065 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/tempstorage/buildout.cfg0000644000175000017500000000013512214017510020271 0ustar arnauarnau[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = tempstorage zope2.13-2.13.21/source/tempstorage/bootstrap.py0000644000175000017500000000733612214017510020362 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id$ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/tempstorage/CHANGES.txt0000644000175000017500000000211512214017510017572 0ustar arnauarnauChangelog ========= 2.12.2 - 2012-10-14 ------------------- - Explicitly state distribution dependencies instead of re-using the ZODB test requirements. 2.12.1 - 2010-09-29 ------------------- - Disabled ``check_tid_ordering_w_commit`` test from BasicStorage, as it uses invalid test data. 2.12.0 - 2010-09-25 ------------------- - Require at least ZODB 3.9 and adjusted method signatures to disuse versions. - Expanded dependency on ZODB3 to include the test extra. 2.11.3 - 2010-06-05 ------------------- - Approximate PEP8 compliance. - Split out the ZODB protocol tests from the tests specific to the module. Make the local tests use "normal" unittest conventions. - Comply with repository policy. - Clean imports, docstrings; add an instance-level hook for GC parms. - Fix a test failure due to never-unghostified root in second connection. 2.11.2 - 2009-08-03 ------------------- - Added change log and readme. - Lauchpad #143736, #271395: fixed AttributeError' on _ltid in TempStorage 2.11.1 - 2008-08-05 ------------------- - Initial release as a stand-alone package. zope2.13-2.13.21/source/tempstorage/src/0000755000175000017500000000000012214017510016551 5ustar arnauarnauzope2.13-2.13.21/source/tempstorage/src/tempstorage.egg-info/0000755000175000017500000000000012214017510022575 5ustar arnauarnauzope2.13-2.13.21/source/tempstorage/src/tempstorage.egg-info/PKG-INFO0000644000175000017500000000405112214017510023672 0ustar arnauarnauMetadata-Version: 1.0 Name: tempstorage Version: 2.12.2 Summary: A RAM-based storage for ZODB Home-page: http://pypi.python.org/pypi/tempstorage Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== A storage implementation which uses RAM to persist objects, much like MappingStorage. Unlike MappingStorage, it needs not be packed to get rid of non-cyclic garbage and it does rudimentary conflict resolution. This is a ripoff of Jim's Packless bsddb3 storage. Changelog ========= 2.12.2 - 2012-10-14 ------------------- - Explicitly state distribution dependencies instead of re-using the ZODB test requirements. 2.12.1 - 2010-09-29 ------------------- - Disabled ``check_tid_ordering_w_commit`` test from BasicStorage, as it uses invalid test data. 2.12.0 - 2010-09-25 ------------------- - Require at least ZODB 3.9 and adjusted method signatures to disuse versions. - Expanded dependency on ZODB3 to include the test extra. 2.11.3 - 2010-06-05 ------------------- - Approximate PEP8 compliance. - Split out the ZODB protocol tests from the tests specific to the module. Make the local tests use "normal" unittest conventions. - Comply with repository policy. - Clean imports, docstrings; add an instance-level hook for GC parms. - Fix a test failure due to never-unghostified root in second connection. 2.11.2 - 2009-08-03 ------------------- - Added change log and readme. - Lauchpad #143736, #271395: fixed AttributeError' on _ltid in TempStorage 2.11.1 - 2008-08-05 ------------------- - Initial release as a stand-alone package. Platform: UNKNOWN zope2.13-2.13.21/source/tempstorage/src/tempstorage.egg-info/dependency_links.txt0000644000175000017500000000000112214017510026643 0ustar arnauarnau zope2.13-2.13.21/source/tempstorage/src/tempstorage.egg-info/requires.txt0000644000175000017500000000004612214017510025175 0ustar arnauarnausetuptools ZODB3 >= 3.9.0 zope.testingzope2.13-2.13.21/source/tempstorage/src/tempstorage.egg-info/top_level.txt0000644000175000017500000000001412214017510025322 0ustar arnauarnautempstorage zope2.13-2.13.21/source/tempstorage/src/tempstorage.egg-info/SOURCES.txt0000644000175000017500000000101712214017510024460 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt MANIFEST.in README.txt bootstrap.py buildout.cfg setup.py src/tempstorage/TemporaryStorage.py src/tempstorage/__init__.py src/tempstorage/component.xml src/tempstorage/config.py src/tempstorage.egg-info/PKG-INFO src/tempstorage.egg-info/SOURCES.txt src/tempstorage.egg-info/dependency_links.txt src/tempstorage.egg-info/not-zip-safe src/tempstorage.egg-info/requires.txt src/tempstorage.egg-info/top_level.txt src/tempstorage/tests/__init__.py src/tempstorage/tests/testTemporaryStorage.pyzope2.13-2.13.21/source/tempstorage/src/tempstorage.egg-info/not-zip-safe0000644000175000017500000000000112214017510025023 0ustar arnauarnau zope2.13-2.13.21/source/tempstorage/src/tempstorage/0000755000175000017500000000000012214017510021103 5ustar arnauarnauzope2.13-2.13.21/source/tempstorage/src/tempstorage/TemporaryStorage.py0000644000175000017500000003221312214017510024765 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """ A storage implementation which uses RAM to persist objects Although this storage is much like MappingStorage, it does not need to be packed to get rid of non-cyclic garbage and it does rudimentary conflict resolution. This is a ripoff of Jim's Packless bsddb3 storage. """ import bisect from logging import getLogger import time from ZODB import POSException from ZODB.BaseStorage import BaseStorage from ZODB.ConflictResolution import ConflictResolvingStorage from ZODB.ConflictResolution import ResolvedSerial from ZODB.serialize import referencesf from ZODB.utils import z64 # keep old object revisions for CONFLICT_CACHE_MAXAGE seconds CONFLICT_CACHE_MAXAGE = 60 # garbage collect conflict cache every CONFLICT_CACHE_GCEVERY seconds CONFLICT_CACHE_GCEVERY = 60 # keep history of recently gc'ed oids of length RECENTLY_GC_OIDS_LEN RECENTLY_GC_OIDS_LEN = 200 LOG = getLogger('TemporaryStorage') class ReferenceCountError(POSException.POSError): """ Error while decrementing a reference to an object in the commit phase. The object's reference count was below zero. """ class TemporaryStorageError(POSException.POSError): """ A Temporary Storage exception occurred. This probably indicates that there is a low memory condition or a tempfile space shortage. Check available tempfile space and RAM consumption and restart the server process. """ class TemporaryStorage(BaseStorage, ConflictResolvingStorage): def __init__(self, name='TemporaryStorage'): """ _index -- mapping, oid => current serial _referenceCount -- mapping, oid => count _oreferences -- mapping, oid => sequence of referenced oids _opickle -- mapping, oid => pickle _tmp -- used by 'store' to collect changes before finalization _conflict_cache -- cache of recently-written object revisions _last_cache_gc -- last time that conflict cache was garbage collected _recently_gc_oids -- a queue of recently GC'ed oids _oid -- ??? _ltid -- serial of last committed transaction (required by ZEO) _conflict_cache_gcevery -- interval for doing GC on conflict cache _conflict_cache_maxage -- age at whic conflict cache items are GC'ed """ BaseStorage.__init__(self, name) self._index = {} self._referenceCount = {} self._oreferences = {} self._opickle = {} self._tmp = [] self._conflict_cache = {} self._last_cache_gc = 0 self._recently_gc_oids = [None for x in range(RECENTLY_GC_OIDS_LEN)] self._oid = z64 self._ltid = z64 # Alow overrides for testing. self._conflict_cache_gcevery = CONFLICT_CACHE_GCEVERY self._conflict_cache_maxage = CONFLICT_CACHE_MAXAGE def lastTransaction(self): """ Return tid for last committed transaction (for ZEO) """ return self._ltid def __len__(self): return len(self._index) def getSize(self): return 0 def _clear_temp(self): now = time.time() if now > (self._last_cache_gc + self._conflict_cache_gcevery): for k, v in self._conflict_cache.items(): data, t = v if now > (t + self._conflict_cache_maxage): del self._conflict_cache[k] self._last_cache_gc = now self._tmp = [] def close(self): """ Close the storage """ def load(self, oid, version=''): self._lock_acquire() try: try: s = self._index[oid] p = self._opickle[oid] return p, s # pickle, serial except KeyError: # this oid was probably garbage collected while a thread held # on to an object that had a reference to it; we can probably # force the loader to sync their connection by raising a # ConflictError (at least if Zope is the loader, because it # will resync its connection on a retry). This isn't # perfect because the length of the recently gc'ed oids list # is finite and could be overrun through a mass gc, but it # should be adequate in common-case usage. if oid in self._recently_gc_oids: raise POSException.ConflictError(oid=oid) else: raise finally: self._lock_release() # Apparently loadEx is required to use this as a ZEO storage for # ZODB 3.3. The tests don't make it totally clear what it's meant # to do. There is a comment in FileStorage about its loadEx # method implementation that says "a variant of load that also # returns a transaction id. ZEO wants this for managing its # cache". But 'load' appears to do that too, so uh, who knows. # - CM def loadEx(self, oid, version=''): data = self.load(oid) # pickle, serial, version return (data[0], data[1], "") def loadSerial(self, oid, serial, marker=[]): """ This is only useful to make conflict resolution work. It does not actually implement all the semantics that a revisioning storage needs! """ self._lock_acquire() try: data = self._conflict_cache.get((oid, serial), marker) if data is marker: # XXX Need 2 serialnos to pass them to ConflictError-- # the old and the new raise POSException.ConflictError(oid=oid) else: return data[0] # data here is actually (data, t) finally: self._lock_release() def loadBefore(self, oid, tid): """ Return most recent revision of oid before tid committed. Needed for MVCC. """ # implementation stolen from ZODB.test_storage.MinimalMemoryStorage self._lock_acquire() try: tids = [stid for soid, stid in self._conflict_cache if soid == oid] if not tids: raise KeyError(oid) tids.sort() i = bisect.bisect_left(tids, tid) -1 if i == -1: return None start_tid = tids[i] j = i + 1 if j == len(tids): return None # the caller can't deal with current data else: end_tid = tids[j] data = self.loadSerial(oid, start_tid) return data, start_tid, end_tid finally: self._lock_release() def store(self, oid, serial, data, version, transaction): if transaction is not self._transaction: raise POSException.StorageTransactionError(self, transaction) assert not version self._lock_acquire() try: if oid in self._index: oserial = self._index[oid] if serial != oserial: newdata = self.tryToResolveConflict( oid, oserial, serial, data) if not newdata: raise POSException.ConflictError( oid=oid, serials=(oserial, serial), data=data) else: data = newdata else: oserial = serial newserial = self._tid self._tmp.append((oid, data)) return serial == oserial and newserial or ResolvedSerial finally: self._lock_release() def _finish(self, tid, u, d, e): zeros = {} referenceCount = self._referenceCount referenceCount_get = referenceCount.get oreferences = self._oreferences serial = self._tid index = self._index opickle = self._opickle self._ltid = tid # iterate over all the objects touched by/created within this # transaction for entry in self._tmp: oid, data = entry[:] referencesl = [] referencesf(data, referencesl) references = {} for roid in referencesl: references[roid] = 1 referenced = references.has_key # Create a reference count for this object if one # doesn't already exist if referenceCount_get(oid) is None: referenceCount[oid] = 0 #zeros[oid]=1 # update references that are already associated with this # object roids = oreferences.get(oid, []) for roid in roids: if referenced(roid): # still referenced, so no need to update # remove it from the references dict so it doesn't # get "added" in the next clause del references[roid] else: # Delete the stored ref, since we no longer # have it oreferences[oid].remove(roid) # decrement refcnt: rc = referenceCount_get(roid, 1) rc = rc-1 if rc < 0: # This should never happen raise ReferenceCountError( "%s (Oid %r had refcount %s)" % (ReferenceCountError.__doc__, roid, rc)) referenceCount[roid] = rc if rc == 0: zeros[roid] = 1 # Create a reference list for this object if one # doesn't already exist if oreferences.get(oid) is None: oreferences[oid] = [] # Now add any references that weren't already stored for roid in references.keys(): oreferences[oid].append(roid) # Create/update refcnt rc = referenceCount_get(roid, 0) if rc == 0 and zeros.get(roid) is not None: del zeros[roid] referenceCount[roid] = rc + 1 index[oid] = serial opickle[oid] = data now = time.time() self._conflict_cache[(oid, serial)] = data, now if zeros: for oid in zeros.keys(): if oid == '\0\0\0\0\0\0\0\0': continue self._takeOutGarbage(oid) self._tmp = [] def _takeOutGarbage(self, oid): # take out the garbage. referenceCount = self._referenceCount referenceCount_get = referenceCount.get self._recently_gc_oids.pop() self._recently_gc_oids.insert(0, oid) try: del referenceCount[oid] except: pass try: del self._opickle[oid] except: pass try: del self._index[oid] except: pass # remove this object from the conflict cache if it exists there for k in self._conflict_cache.keys(): if k[0] == oid: del self._conflict_cache[k] # Remove/decref references roids = self._oreferences.get(oid, []) while roids: roid = roids.pop(0) # decrement refcnt: # DM 2005-01-07: decrement *before* you make the test! # rc=referenceCount_get(roid, 0) rc = referenceCount_get(roid, 0) - 1 if rc == 0: self._takeOutGarbage(roid) elif rc < 0: raise ReferenceCountError( "%s (Oid %r had refcount %s)" % (ReferenceCountError.__doc__, roid, rc)) else: # DM 2005-01-07: decremented *before* the test! see above #referenceCount[roid] = rc - 1 referenceCount[roid] = rc try: del self._oreferences[oid] except: pass def pack(self, t, referencesf): self._lock_acquire() try: rindex = {} referenced = rindex.has_key rootl = ['\0\0\0\0\0\0\0\0'] # mark referenced objects while rootl: oid = rootl.pop() if referenced(oid): continue p = self._opickle[oid] referencesf(p, rootl) rindex[oid] = None # sweep unreferenced objects for oid in self._index.keys(): if not referenced(oid): self._takeOutGarbage(oid) finally: self._lock_release() zope2.13-2.13.21/source/tempstorage/src/tempstorage/component.xml0000644000175000017500000000062312214017510023630 0ustar arnauarnau A nonundoing storage which keeps data in RAM and which does not need to be packed unless cyclic references are kept. zope2.13-2.13.21/source/tempstorage/src/tempstorage/config.py0000644000175000017500000000151712214017510022726 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from ZODB.config import BaseConfig class TemporaryStorage(BaseConfig): def open(self): from tempstorage.TemporaryStorage import TemporaryStorage return TemporaryStorage(self.config.name) zope2.13-2.13.21/source/tempstorage/src/tempstorage/__init__.py0000644000175000017500000000117412214017510023217 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## zope2.13-2.13.21/source/tempstorage/src/tempstorage/tests/0000755000175000017500000000000012214017510022245 5ustar arnauarnauzope2.13-2.13.21/source/tempstorage/src/tempstorage/tests/testTemporaryStorage.py0000644000175000017500000001417012214017510027031 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import unittest from ZODB.tests import StorageTestBase from ZODB.tests import BasicStorage from ZODB.tests import Synchronization from ZODB.tests import ConflictResolution from ZODB.tests import MTStorage class ZODBProtocolTests(StorageTestBase.StorageTestBase, BasicStorage.BasicStorage, Synchronization.SynchronizedStorage, ConflictResolution.ConflictResolvingStorage, MTStorage.MTStorage, ): def setUp(self): StorageTestBase.StorageTestBase.setUp(self) self.open() def open(self, **kwargs): from tempstorage.TemporaryStorage import TemporaryStorage self._storage = TemporaryStorage('foo') def check_tid_ordering_w_commit(self): # The test uses invalid test data of 'x'. The normal storages # don't load the actual data and thus pass, but the tempstorage # will always try to load the data and fail pass class TemporaryStorageTests(unittest.TestCase): def _getTargetClass(self): from tempstorage.TemporaryStorage import TemporaryStorage return TemporaryStorage def _makeOne(self, name='foo'): return self._getTargetClass()(name) def _dostore(self, storage, oid=None, revid=None, data=None, already_pickled=0, user=None, description=None): # Borrowed from StorageTestBase, to allow passing storage. """Do a complete storage transaction. The defaults are: - oid=None, ask the storage for a new oid - revid=None, use a revid of ZERO - data=None, pickle up some arbitrary data (the integer 7) Returns the object's new revision id. """ import transaction from ZODB.tests.MinPO import MinPO if oid is None: oid = storage.new_oid() if revid is None: revid = StorageTestBase.ZERO if data is None: data = MinPO(7) if type(data) == int: data = MinPO(data) if not already_pickled: data = StorageTestBase.zodb_pickle(data) # Begin the transaction t = transaction.Transaction() if user is not None: t.user = user if description is not None: t.description = description try: storage.tpc_begin(t) # Store an object r1 = storage.store(oid, revid, data, '', t) # Finish the transaction r2 = storage.tpc_vote(t) revid = StorageTestBase.handle_serials(oid, r1, r2) storage.tpc_finish(t) except: storage.tpc_abort(t) raise return revid def _do_read_conflict(self, db, mvcc): import transaction from ZODB.tests.MinPO import MinPO tm1 = transaction.TransactionManager() conn = db.open(transaction_manager=tm1) r1 = conn.root() obj = MinPO('root') r1["p"] = obj obj = r1["p"] obj.child1 = MinPO('child1') tm1.get().commit() # start a new transaction with a new connection tm2 = transaction.TransactionManager() cn2 = db.open(transaction_manager=tm2) r2 = cn2.root() r2["p"]._p_activate() self.assertEqual(r1._p_serial, r2._p_serial) obj.child2 = MinPO('child2') tm1.get().commit() # resume the transaction using cn2 obj = r2["p"] # An attempt to access obj.child1 should fail with an RCE # below if conn isn't using mvcc, because r2 was read earlier # in the transaction and obj was modified by the other # transaction. obj.child1 return obj def test_conflict_cache_clears_over_time(self): import time from ZODB.tests.MinPO import MinPO storage = self._makeOne() storage._conflict_cache_gcevery = 1 # second storage._conflict_cache_maxage = 1 # second oid = storage.new_oid() self._dostore(storage, oid, data=MinPO(5)) time.sleep(2) oid2 = storage.new_oid() self._dostore(storage, oid2, data=MinPO(10)) oid3 = storage.new_oid() self._dostore(storage, oid3, data=MinPO(9)) self.assertEqual(len(storage._conflict_cache), 2) time.sleep(2) oid4 = storage.new_oid() self._dostore(storage, oid4, data=MinPO(11)) self.assertEqual(len(storage._conflict_cache), 1) def test_have_MVCC_ergo_no_ReadConflict(self): from ZODB.DB import DB from ZODB.tests.MinPO import MinPO storage = self._makeOne() db = DB(storage) ob = self._do_read_conflict(db, True) self.assertEquals(ob.__class__, MinPO) self.assertEquals(getattr(ob, 'child1', MinPO()).value, 'child1') self.failIf(getattr(ob, 'child2', None)) def test_load_ex_matches_load(self): from ZODB.tests.MinPO import MinPO storage = self._makeOne() oid = storage.new_oid() self._dostore(storage, oid, data=MinPO(1)) loadp, loads = storage.load(oid, 'whatever') exp, exs, exv = storage.loadEx(oid, 'whatever') self.assertEqual(loadp, exp) self.assertEqual(loads, exs) self.assertEqual(exv, '') def test_suite(): return unittest.TestSuite(( unittest.makeSuite(TemporaryStorageTests), # Note: we follow the ZODB 'check' pattern here so that the base # class tests are picked up. unittest.makeSuite(ZODBProtocolTests, 'check'), )) zope2.13-2.13.21/source/tempstorage/src/tempstorage/tests/__init__.py0000644000175000017500000000117412214017510024361 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## zope2.13-2.13.21/source/zope.browserresource/0000755000175000017500000000000012214017540017642 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserresource/setup.py0000644000175000017500000000432212214017540021355 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """zope.browserresource setup """ from setuptools import setup, find_packages long_description = (open('README.txt').read() + '\n\n' + open('CHANGES.txt').read()) setup(name='zope.browserresource', version = '3.10.3', url='http://pypi.python.org/pypi/zope.browserresource/', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', classifiers = ['Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3', ], description='Browser resources implementation for Zope.', long_description=long_description, packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope'], include_package_data=True, install_requires=['setuptools', 'zope.component>=3.8.0', 'zope.configuration', 'zope.contenttype', 'zope.i18n', 'zope.interface', 'zope.location', 'zope.publisher>=3.8', 'zope.schema', 'zope.traversing>3.7', ], extras_require={ 'test': ['zope.testing'], }, zip_safe = False, ) zope2.13-2.13.21/source/zope.browserresource/PKG-INFO0000644000175000017500000001565612214017540020754 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.browserresource Version: 3.10.3 Summary: Browser resources implementation for Zope. Home-page: http://pypi.python.org/pypi/zope.browserresource/ Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: UNKNOWN Description: ======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides an implementation of browser resources. It also provides directives for defining those resources using ZCML. Resources are static files and directories that are served to the browser directly from the filesystem. The most common example are images, CSS style sheets, or JavaScript files. Resources are be registered under a symbolic name and can later be referred to by that name, so their usage is independent from their physical location. You can register a single file with the `` directive, and a whole directory with the `` directive, for example This causes a named adapter to be registered that adapts the request to zope.interface.Interface (XXX why do we not use an explicit interface?), so to later retrieve a resource, use `zope.component.getAdapter(request, name='myfile')`. There are two ways to traverse to a resource, 1. with the 'empty' view on a site, e. g. `http://localhost/@@/myfile` (This is declared by zope.browserresource) 2. with the `++resource++` namespace, e. g. `http://localhost/++resource++myfile` (This is declared by zope.traversing.namespace) In case of resource-directories traversal simply continues through its contents, e. g. `http://localhost/@@/main-images/subdir/sample.jpg` Rather than putting together the URL to a resource manually, you should use zope.traversing.browser.interfaces.IAbsoluteURL to get the URL, or for a shorthand, call the resource object. This has an additional benefit: If you want to serve resources from a different URL, for example because you want to use a web server specialized in serving static files instead of the appserver, you can register an IAbsoluteURL adapter for the site under the name 'resource' that will be used to compute the base URLs for resources. For example, if you register 'http://static.example.com/' as the base 'resource' URL, the resources from the above example would yield the following absolute URLs: http://static.example.com/@@/myfile and http://static.example.com/@@/main-images ======= CHANGES ======= 3.10.3 (2010-04-30) =================== - Prefer the standard libraries doctest module to the one from zope.testing. 3.10.2 (2009-11-25) =================== - The previous release had a broken egg, sorry. 3.10.1 (2009-11-24) =================== - Import hooks functionality from zope.component after it was moved there from zope.site. This lifts the dependency on zope.site and thereby, ZODB. - Import ISite and IPossibleSite from zope.component after they were moved there from zope.location. 3.10.0 (2009-09-25) =================== - Add an ability to forbid publishing of some files in the resource directory, this is done by fnmatch'ing the wildcards in the ``forbidden_names``class attribute of ``DirectoryResource``. By default, the ``.svn`` is in that attribute, so directories won't publish subversion system directory that can contain private information. 3.9.0 (2009-08-27) ================== Initial release. This package was splitted off zope.app.publisher as a part of refactoring process. Additional changes that are made during refactoring: * Resource class for file resources are now selected the pluggable way. The resource directory publisher and browser:resource ZCML directive now creating file resources using factory utility lookup based on the file extension, so it's now possible to add new resource types without introducing new ZCML directives and they will work inside resource directories as well. NOTE: the "resource_factories" attribute from the DirectoryResource was removed, so if you were using this attribute for changing resource classes for some file extensions, you need to migrate your code to new utility-based mechanism. See zope.browserresource.interfaces.IResourceFactoryFactory interface. * The Image resource class was removed, as they are actually simple files. To migrate, simply rename the "image" argument in browser:resource and browser:i18n-resource directives to "file", if you don't do this, resouces will work, but you'll get deprecation warnings. If you need custom behaviour for images, you can register a resource factory utility for needed file extensions. * The PageTemplateResource was moved into a separate package, "zope.ptresource", which is a plugin for this package now. Because of that, the "template" argument of browser:resource directive was deprecated and you should rename it to "file" to migrate. The PageTemplateResource will be created for "pt", "zpt" and "html" files automatically, if zope.ptresource package is included in your configuration. * Fix stripping the "I" from an interface name for icon title, if no title is specified. * When publishing a resource via Resources view, set resource parent to an ISite object, not to current site manager. * Clean up code and improve test coverage. Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.browserresource/pip-egg-info/0000755000175000017500000000000012214017540022123 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserresource/pip-egg-info/zope.browserresource.egg-info/0000755000175000017500000000000012214017540030024 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserresource/pip-egg-info/zope.browserresource.egg-info/PKG-INFO0000644000175000017500000001565612214017540031136 0ustar arnauarnauMetadata-Version: 1.1 Name: zope.browserresource Version: 3.10.3 Summary: Browser resources implementation for Zope. Home-page: http://pypi.python.org/pypi/zope.browserresource/ Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: UNKNOWN Description: ======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides an implementation of browser resources. It also provides directives for defining those resources using ZCML. Resources are static files and directories that are served to the browser directly from the filesystem. The most common example are images, CSS style sheets, or JavaScript files. Resources are be registered under a symbolic name and can later be referred to by that name, so their usage is independent from their physical location. You can register a single file with the `` directive, and a whole directory with the `` directive, for example This causes a named adapter to be registered that adapts the request to zope.interface.Interface (XXX why do we not use an explicit interface?), so to later retrieve a resource, use `zope.component.getAdapter(request, name='myfile')`. There are two ways to traverse to a resource, 1. with the 'empty' view on a site, e. g. `http://localhost/@@/myfile` (This is declared by zope.browserresource) 2. with the `++resource++` namespace, e. g. `http://localhost/++resource++myfile` (This is declared by zope.traversing.namespace) In case of resource-directories traversal simply continues through its contents, e. g. `http://localhost/@@/main-images/subdir/sample.jpg` Rather than putting together the URL to a resource manually, you should use zope.traversing.browser.interfaces.IAbsoluteURL to get the URL, or for a shorthand, call the resource object. This has an additional benefit: If you want to serve resources from a different URL, for example because you want to use a web server specialized in serving static files instead of the appserver, you can register an IAbsoluteURL adapter for the site under the name 'resource' that will be used to compute the base URLs for resources. For example, if you register 'http://static.example.com/' as the base 'resource' URL, the resources from the above example would yield the following absolute URLs: http://static.example.com/@@/myfile and http://static.example.com/@@/main-images ======= CHANGES ======= 3.10.3 (2010-04-30) =================== - Prefer the standard libraries doctest module to the one from zope.testing. 3.10.2 (2009-11-25) =================== - The previous release had a broken egg, sorry. 3.10.1 (2009-11-24) =================== - Import hooks functionality from zope.component after it was moved there from zope.site. This lifts the dependency on zope.site and thereby, ZODB. - Import ISite and IPossibleSite from zope.component after they were moved there from zope.location. 3.10.0 (2009-09-25) =================== - Add an ability to forbid publishing of some files in the resource directory, this is done by fnmatch'ing the wildcards in the ``forbidden_names``class attribute of ``DirectoryResource``. By default, the ``.svn`` is in that attribute, so directories won't publish subversion system directory that can contain private information. 3.9.0 (2009-08-27) ================== Initial release. This package was splitted off zope.app.publisher as a part of refactoring process. Additional changes that are made during refactoring: * Resource class for file resources are now selected the pluggable way. The resource directory publisher and browser:resource ZCML directive now creating file resources using factory utility lookup based on the file extension, so it's now possible to add new resource types without introducing new ZCML directives and they will work inside resource directories as well. NOTE: the "resource_factories" attribute from the DirectoryResource was removed, so if you were using this attribute for changing resource classes for some file extensions, you need to migrate your code to new utility-based mechanism. See zope.browserresource.interfaces.IResourceFactoryFactory interface. * The Image resource class was removed, as they are actually simple files. To migrate, simply rename the "image" argument in browser:resource and browser:i18n-resource directives to "file", if you don't do this, resouces will work, but you'll get deprecation warnings. If you need custom behaviour for images, you can register a resource factory utility for needed file extensions. * The PageTemplateResource was moved into a separate package, "zope.ptresource", which is a plugin for this package now. Because of that, the "template" argument of browser:resource directive was deprecated and you should rename it to "file" to migrate. The PageTemplateResource will be created for "pt", "zpt" and "html" files automatically, if zope.ptresource package is included in your configuration. * Fix stripping the "I" from an interface name for icon title, if no title is specified. * When publishing a resource via Resources view, set resource parent to an ISite object, not to current site manager. * Clean up code and improve test coverage. Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.browserresource/pip-egg-info/zope.browserresource.egg-info/dependency_links.txtzope2.13-2.13.21/source/zope.browserresource/pip-egg-info/zope.browserresource.egg-info/dependency_l0000644000175000017500000000000112214017540032367 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserresource/pip-egg-info/zope.browserresource.egg-info/requires.txt0000644000175000017500000000026412214017540032426 0ustar arnauarnausetuptools zope.component>=3.8.0 zope.configuration zope.contenttype zope.i18n zope.interface zope.location zope.publisher>=3.8 zope.schema zope.traversing>3.7 [test] zope.testing././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/zope.browserresource/pip-egg-info/zope.browserresource.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.browserresource/pip-egg-info/zope.browserresource.egg-info/namespace_pa0000644000175000017500000000000512214017540032356 0ustar arnauarnauzope ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.browserresource/pip-egg-info/zope.browserresource.egg-info/top_level.txtzope2.13-2.13.21/source/zope.browserresource/pip-egg-info/zope.browserresource.egg-info/top_level.tx0000644000175000017500000000000512214017540032365 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.browserresource/pip-egg-info/zope.browserresource.egg-info/SOURCES.txt0000644000175000017500000000234212214017540031711 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/zope.browserresource.egg-info/PKG-INFO pip-egg-info/zope.browserresource.egg-info/SOURCES.txt pip-egg-info/zope.browserresource.egg-info/dependency_links.txt pip-egg-info/zope.browserresource.egg-info/namespace_packages.txt pip-egg-info/zope.browserresource.egg-info/not-zip-safe pip-egg-info/zope.browserresource.egg-info/requires.txt pip-egg-info/zope.browserresource.egg-info/top_level.txt src/zope/__init__.py src/zope/browserresource/__init__.py src/zope/browserresource/directory.py src/zope/browserresource/file.py src/zope/browserresource/i18nfile.py src/zope/browserresource/icon.py src/zope/browserresource/interfaces.py src/zope/browserresource/metaconfigure.py src/zope/browserresource/metadirectives.py src/zope/browserresource/resource.py src/zope/browserresource/resources.py src/zope/browserresource/tests/__init__.py src/zope/browserresource/tests/support.py src/zope/browserresource/tests/test_directives.py src/zope/browserresource/tests/test_directory.py src/zope/browserresource/tests/test_file.py src/zope/browserresource/tests/test_i18nfile.py src/zope/browserresource/tests/test_icondirective.py src/zope/browserresource/tests/test_resource.py src/zope/browserresource/tests/test_resources.pyzope2.13-2.13.21/source/zope.browserresource/pip-egg-info/zope.browserresource.egg-info/not-zip-safe0000644000175000017500000000000112214017540032252 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserresource/README.txt0000644000175000017500000000464312214017540021347 0ustar arnauarnau======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides an implementation of browser resources. It also provides directives for defining those resources using ZCML. Resources are static files and directories that are served to the browser directly from the filesystem. The most common example are images, CSS style sheets, or JavaScript files. Resources are be registered under a symbolic name and can later be referred to by that name, so their usage is independent from their physical location. You can register a single file with the `` directive, and a whole directory with the `` directive, for example This causes a named adapter to be registered that adapts the request to zope.interface.Interface (XXX why do we not use an explicit interface?), so to later retrieve a resource, use `zope.component.getAdapter(request, name='myfile')`. There are two ways to traverse to a resource, 1. with the 'empty' view on a site, e. g. `http://localhost/@@/myfile` (This is declared by zope.browserresource) 2. with the `++resource++` namespace, e. g. `http://localhost/++resource++myfile` (This is declared by zope.traversing.namespace) In case of resource-directories traversal simply continues through its contents, e. g. `http://localhost/@@/main-images/subdir/sample.jpg` Rather than putting together the URL to a resource manually, you should use zope.traversing.browser.interfaces.IAbsoluteURL to get the URL, or for a shorthand, call the resource object. This has an additional benefit: If you want to serve resources from a different URL, for example because you want to use a web server specialized in serving static files instead of the appserver, you can register an IAbsoluteURL adapter for the site under the name 'resource' that will be used to compute the base URLs for resources. For example, if you register 'http://static.example.com/' as the base 'resource' URL, the resources from the above example would yield the following absolute URLs: http://static.example.com/@@/myfile and http://static.example.com/@@/main-images zope2.13-2.13.21/source/zope.browserresource/setup.cfg0000644000175000017500000000007312214017540021463 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/zope.browserresource/buildout.cfg0000644000175000017500000000062612214017540022156 0ustar arnauarnau[buildout] develop = . parts = test coverage-test coverage-report [test] recipe = zc.recipe.testrunner eggs = zope.browserresource [test] [coverage-test] recipe = zc.recipe.testrunner eggs = zope.browserresource [test] defaults = ['--coverage', '../../coverage'] [coverage-report] recipe = zc.recipe.egg eggs = z3c.coverage scripts = coverage=coverage-report arguments = ('coverage', 'coverage/report') zope2.13-2.13.21/source/zope.browserresource/bootstrap.py0000644000175000017500000000742212214017540022236 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 111697 2010-04-30 20:21:32Z hannosch $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/zope.browserresource/CHANGES.txt0000644000175000017500000000552712214017540021464 0ustar arnauarnau======= CHANGES ======= 3.10.3 (2010-04-30) =================== - Prefer the standard libraries doctest module to the one from zope.testing. 3.10.2 (2009-11-25) =================== - The previous release had a broken egg, sorry. 3.10.1 (2009-11-24) =================== - Import hooks functionality from zope.component after it was moved there from zope.site. This lifts the dependency on zope.site and thereby, ZODB. - Import ISite and IPossibleSite from zope.component after they were moved there from zope.location. 3.10.0 (2009-09-25) =================== - Add an ability to forbid publishing of some files in the resource directory, this is done by fnmatch'ing the wildcards in the ``forbidden_names``class attribute of ``DirectoryResource``. By default, the ``.svn`` is in that attribute, so directories won't publish subversion system directory that can contain private information. 3.9.0 (2009-08-27) ================== Initial release. This package was splitted off zope.app.publisher as a part of refactoring process. Additional changes that are made during refactoring: * Resource class for file resources are now selected the pluggable way. The resource directory publisher and browser:resource ZCML directive now creating file resources using factory utility lookup based on the file extension, so it's now possible to add new resource types without introducing new ZCML directives and they will work inside resource directories as well. NOTE: the "resource_factories" attribute from the DirectoryResource was removed, so if you were using this attribute for changing resource classes for some file extensions, you need to migrate your code to new utility-based mechanism. See zope.browserresource.interfaces.IResourceFactoryFactory interface. * The Image resource class was removed, as they are actually simple files. To migrate, simply rename the "image" argument in browser:resource and browser:i18n-resource directives to "file", if you don't do this, resouces will work, but you'll get deprecation warnings. If you need custom behaviour for images, you can register a resource factory utility for needed file extensions. * The PageTemplateResource was moved into a separate package, "zope.ptresource", which is a plugin for this package now. Because of that, the "template" argument of browser:resource directive was deprecated and you should rename it to "file" to migrate. The PageTemplateResource will be created for "pt", "zpt" and "html" files automatically, if zope.ptresource package is included in your configuration. * Fix stripping the "I" from an interface name for icon title, if no title is specified. * When publishing a resource via Resources view, set resource parent to an ISite object, not to current site manager. * Clean up code and improve test coverage. zope2.13-2.13.21/source/zope.browserresource/src/0000755000175000017500000000000012214017540020431 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserresource/src/zope.browserresource.egg-info/0000755000175000017500000000000012214017540026332 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserresource/src/zope.browserresource.egg-info/PKG-INFO0000644000175000017500000001565612214017540027444 0ustar arnauarnauMetadata-Version: 1.0 Name: zope.browserresource Version: 3.10.3 Summary: Browser resources implementation for Zope. Home-page: http://pypi.python.org/pypi/zope.browserresource/ Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: UNKNOWN Description: ======== Overview ======== *This package is at present not reusable without depending on a large chunk of the Zope Toolkit and its assumptions. It is maintained by the* `Zope Toolkit project `_. This package provides an implementation of browser resources. It also provides directives for defining those resources using ZCML. Resources are static files and directories that are served to the browser directly from the filesystem. The most common example are images, CSS style sheets, or JavaScript files. Resources are be registered under a symbolic name and can later be referred to by that name, so their usage is independent from their physical location. You can register a single file with the `` directive, and a whole directory with the `` directive, for example This causes a named adapter to be registered that adapts the request to zope.interface.Interface (XXX why do we not use an explicit interface?), so to later retrieve a resource, use `zope.component.getAdapter(request, name='myfile')`. There are two ways to traverse to a resource, 1. with the 'empty' view on a site, e. g. `http://localhost/@@/myfile` (This is declared by zope.browserresource) 2. with the `++resource++` namespace, e. g. `http://localhost/++resource++myfile` (This is declared by zope.traversing.namespace) In case of resource-directories traversal simply continues through its contents, e. g. `http://localhost/@@/main-images/subdir/sample.jpg` Rather than putting together the URL to a resource manually, you should use zope.traversing.browser.interfaces.IAbsoluteURL to get the URL, or for a shorthand, call the resource object. This has an additional benefit: If you want to serve resources from a different URL, for example because you want to use a web server specialized in serving static files instead of the appserver, you can register an IAbsoluteURL adapter for the site under the name 'resource' that will be used to compute the base URLs for resources. For example, if you register 'http://static.example.com/' as the base 'resource' URL, the resources from the above example would yield the following absolute URLs: http://static.example.com/@@/myfile and http://static.example.com/@@/main-images ======= CHANGES ======= 3.10.3 (2010-04-30) =================== - Prefer the standard libraries doctest module to the one from zope.testing. 3.10.2 (2009-11-25) =================== - The previous release had a broken egg, sorry. 3.10.1 (2009-11-24) =================== - Import hooks functionality from zope.component after it was moved there from zope.site. This lifts the dependency on zope.site and thereby, ZODB. - Import ISite and IPossibleSite from zope.component after they were moved there from zope.location. 3.10.0 (2009-09-25) =================== - Add an ability to forbid publishing of some files in the resource directory, this is done by fnmatch'ing the wildcards in the ``forbidden_names``class attribute of ``DirectoryResource``. By default, the ``.svn`` is in that attribute, so directories won't publish subversion system directory that can contain private information. 3.9.0 (2009-08-27) ================== Initial release. This package was splitted off zope.app.publisher as a part of refactoring process. Additional changes that are made during refactoring: * Resource class for file resources are now selected the pluggable way. The resource directory publisher and browser:resource ZCML directive now creating file resources using factory utility lookup based on the file extension, so it's now possible to add new resource types without introducing new ZCML directives and they will work inside resource directories as well. NOTE: the "resource_factories" attribute from the DirectoryResource was removed, so if you were using this attribute for changing resource classes for some file extensions, you need to migrate your code to new utility-based mechanism. See zope.browserresource.interfaces.IResourceFactoryFactory interface. * The Image resource class was removed, as they are actually simple files. To migrate, simply rename the "image" argument in browser:resource and browser:i18n-resource directives to "file", if you don't do this, resouces will work, but you'll get deprecation warnings. If you need custom behaviour for images, you can register a resource factory utility for needed file extensions. * The PageTemplateResource was moved into a separate package, "zope.ptresource", which is a plugin for this package now. Because of that, the "template" argument of browser:resource directive was deprecated and you should rename it to "file" to migrate. The PageTemplateResource will be created for "pt", "zpt" and "html" files automatically, if zope.ptresource package is included in your configuration. * Fix stripping the "I" from an interface name for icon title, if no title is specified. * When publishing a resource via Resources view, set resource parent to an ISite object, not to current site manager. * Clean up code and improve test coverage. Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope2.13-2.13.21/source/zope.browserresource/src/zope.browserresource.egg-info/dependency_links.txt0000644000175000017500000000000112214017540032400 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserresource/src/zope.browserresource.egg-info/requires.txt0000644000175000017500000000026412214017540030734 0ustar arnauarnausetuptools zope.component>=3.8.0 zope.configuration zope.contenttype zope.i18n zope.interface zope.location zope.publisher>=3.8 zope.schema zope.traversing>3.7 [test] zope.testing././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.browserresource/src/zope.browserresource.egg-info/namespace_packages.txtzope2.13-2.13.21/source/zope.browserresource/src/zope.browserresource.egg-info/namespace_packages.tx0000644000175000017500000000000512214017540032474 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.browserresource/src/zope.browserresource.egg-info/top_level.txt0000644000175000017500000000000512214017540031057 0ustar arnauarnauzope zope2.13-2.13.21/source/zope.browserresource/src/zope.browserresource.egg-info/SOURCES.txt0000644000175000017500000000302312214017540030214 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.browserresource.egg-info/PKG-INFO src/zope.browserresource.egg-info/SOURCES.txt src/zope.browserresource.egg-info/dependency_links.txt src/zope.browserresource.egg-info/namespace_packages.txt src/zope.browserresource.egg-info/not-zip-safe src/zope.browserresource.egg-info/requires.txt src/zope.browserresource.egg-info/top_level.txt src/zope/browserresource/__init__.py src/zope/browserresource/configure.zcml src/zope/browserresource/directory.py src/zope/browserresource/file.py src/zope/browserresource/i18nfile.py src/zope/browserresource/icon.py src/zope/browserresource/interfaces.py src/zope/browserresource/meta.zcml src/zope/browserresource/metaconfigure.py src/zope/browserresource/metadirectives.py src/zope/browserresource/resource.py src/zope/browserresource/resources.py src/zope/browserresource/tests/__init__.py src/zope/browserresource/tests/support.py src/zope/browserresource/tests/test_directives.py src/zope/browserresource/tests/test_directory.py src/zope/browserresource/tests/test_file.py src/zope/browserresource/tests/test_i18nfile.py src/zope/browserresource/tests/test_icondirective.py src/zope/browserresource/tests/test_resource.py src/zope/browserresource/tests/test_resources.py src/zope/browserresource/tests/testfiles/test.gif src/zope/browserresource/tests/testfiles/test.pt src/zope/browserresource/tests/testfiles/test.txt src/zope/browserresource/tests/testfiles/test2.pt src/zope/browserresource/tests/testfiles/subdir/test.gifzope2.13-2.13.21/source/zope.browserresource/src/zope.browserresource.egg-info/not-zip-safe0000644000175000017500000000000112214017540030560 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserresource/src/zope/0000755000175000017500000000000012214017540021406 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/0000755000175000017500000000000012214017540024641 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/configure.zcml0000644000175000017500000000171312214017540027513 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/resource.py0000644000175000017500000000416512214017540027050 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Resource base class and AbsoluteURL adapter $Id: resource.py 105850 2009-11-19 07:05:29Z tlotze $ """ import zope.component.hooks from zope.component import adapts, getMultiAdapter, queryMultiAdapter from zope.interface import implements, implementsOnly from zope.location import Location from zope.publisher.interfaces.browser import IBrowserRequest from zope.traversing.browser.interfaces import IAbsoluteURL import zope.traversing.browser.absoluteurl from zope.browserresource.interfaces import IResource class Resource(Location): implements(IResource) def __init__(self, request): self.request = request def __call__(self): return str(getMultiAdapter((self, self.request), IAbsoluteURL)) class AbsoluteURL(zope.traversing.browser.absoluteurl.AbsoluteURL): implementsOnly(IAbsoluteURL) adapts(IResource, IBrowserRequest) def __init__(self, context, request): self.context = context self.request = request def _createUrl(self, baseUrl, name): return "%s/@@/%s" % (baseUrl, name) def __str__(self): name = self.context.__name__ if name.startswith('++resource++'): name = name[12:] site = zope.component.hooks.getSite() base = queryMultiAdapter((site, self.request), IAbsoluteURL, name="resource") if base is None: url = str(getMultiAdapter((site, self.request), IAbsoluteURL)) else: url = str(base) return self._createUrl(url, name) zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/file.py0000644000175000017500000001746512214017540026147 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """File-based browser resources. $Id: file.py 103197 2009-08-25 11:54:46Z nadako $ """ import os import time try: from email.utils import formatdate, parsedate_tz, mktime_tz except ImportError: # python 2.4 from email.Utils import formatdate, parsedate_tz, mktime_tz from zope.contenttype import guess_content_type from zope.interface import implements, classProvides from zope.publisher.browser import BrowserView from zope.publisher.interfaces import NotFound from zope.publisher.interfaces.browser import IBrowserPublisher from zope.browserresource.resource import Resource from zope.browserresource.interfaces import IResourceFactory from zope.browserresource.interfaces import IResourceFactoryFactory class File(object): def __init__(self, path, name): self.path = path self.__name__ = name f = open(path, 'rb') data = f.read() f.close() self.content_type = guess_content_type(path, data)[0] self.lmt = float(os.path.getmtime(path)) or time.time() self.lmh = formatdate(self.lmt, usegmt=True) class FileResource(BrowserView, Resource): implements(IBrowserPublisher) cacheTimeout = 86400 def publishTraverse(self, request, name): '''File resources can't be traversed further, so raise NotFound if someone tries to traverse it. >>> factory = FileResourceFactory(testFilePath, nullChecker, 'test.txt') >>> request = TestRequest() >>> resource = factory(request) >>> resource.publishTraverse(request, '_testData') Traceback (most recent call last): ... NotFound: Object: None, name: '_testData' ''' raise NotFound(None, name) def browserDefault(self, request): '''Return a callable for processing browser requests. >>> factory = FileResourceFactory(testFilePath, nullChecker, 'test.txt') >>> request = TestRequest(REQUEST_METHOD='GET') >>> resource = factory(request) >>> view, next = resource.browserDefault(request) >>> view() == open(testFilePath, 'rb').read() True >>> next == () True >>> request = TestRequest(REQUEST_METHOD='HEAD') >>> resource = factory(request) >>> view, next = resource.browserDefault(request) >>> view() == '' True >>> next == () True ''' return getattr(self, request.method), () def chooseContext(self): '''Choose the appropriate context. This method can be overriden in subclasses, that need to choose appropriate file, based on current request or other condition, like, for example, i18n files. ''' return self.context def GET(self): '''Return a file data for downloading with GET requests >>> factory = FileResourceFactory(testFilePath, nullChecker, 'test.txt') >>> request = TestRequest() >>> resource = factory(request) >>> resource.GET() == open(testFilePath, 'rb').read() True >>> request.response.getHeader('Content-Type') == 'text/plain' True Let's test If-Modified-Since header support. >>> timestamp = time.time() >>> file = factory._FileResourceFactory__file # get mangled file >>> file.lmt = timestamp >>> file.lmh = formatdate(timestamp, usegmt=True) >>> before = timestamp - 1000 >>> request = TestRequest(HTTP_IF_MODIFIED_SINCE=formatdate(before, usegmt=True)) >>> resource = factory(request) >>> bool(resource.GET()) True >>> after = timestamp + 1000 >>> request = TestRequest(HTTP_IF_MODIFIED_SINCE=formatdate(after, usegmt=True)) >>> resource = factory(request) >>> bool(resource.GET()) False >>> request.response.getStatus() 304 It won't fail on bad If-Modified-Since headers. >>> request = TestRequest(HTTP_IF_MODIFIED_SINCE='bad header') >>> resource = factory(request) >>> bool(resource.GET()) True ''' file = self.chooseContext() request = self.request response = request.response setCacheControl(response, self.cacheTimeout) # HTTP If-Modified-Since header handling. This is duplicated # from OFS.Image.Image - it really should be consolidated # somewhere... header = request.getHeader('If-Modified-Since', None) if header is not None: header = header.split(';')[0] # Some proxies seem to send invalid date strings for this # header. If the date string is not valid, we ignore it # rather than raise an error to be generally consistent # with common servers such as Apache (which can usually # understand the screwy date string as a lucky side effect # of the way they parse it). try: mod_since = long(mktime_tz(parsedate_tz(header))) except: mod_since = None if mod_since is not None: if getattr(file, 'lmt', None): last_mod = long(file.lmt) else: last_mod = 0L if last_mod > 0 and last_mod <= mod_since: response.setStatus(304) return '' response.setHeader('Content-Type', file.content_type) response.setHeader('Last-Modified', file.lmh) f = open(file.path,'rb') data = f.read() f.close() return data def HEAD(self): '''Return proper headers and no content for HEAD requests >>> factory = FileResourceFactory(testFilePath, nullChecker, 'test.txt') >>> request = TestRequest() >>> resource = factory(request) >>> resource.HEAD() == '' True >>> request.response.getHeader('Content-Type') == 'text/plain' True ''' file = self.chooseContext() response = self.request.response response.setHeader('Content-Type', file.content_type) response.setHeader('Last-Modified', file.lmh) setCacheControl(response, self.cacheTimeout) return '' # for unit tests def _testData(self): f = open(self.context.path, 'rb') data = f.read() f.close() return data def setCacheControl(response, secs=86400): # Cache for one day by default response.setHeader('Cache-Control', 'public,max-age=%s' % secs) t = time.time() + secs response.setHeader('Expires', formatdate(t, usegmt=True)) class FileResourceFactory(object): resourceClass = FileResource implements(IResourceFactory) classProvides(IResourceFactoryFactory) def __init__(self, path, checker, name): self.__file = File(path, name) self.__checker = checker self.__name = name def __call__(self, request): resource = self.resourceClass(self.__file, request) resource.__Security_checker__ = self.__checker resource.__name__ = self.__name return resource zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/resources.py0000644000175000017500000001005212214017540027223 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Resource URL access $Id: resources.py 105850 2009-11-19 07:05:29Z tlotze $ """ from zope.component import queryAdapter from zope.interface import implements from zope.location import locate from zope.publisher.browser import BrowserView from zope.publisher.interfaces import NotFound from zope.publisher.interfaces.browser import IBrowserPublisher class Resources(BrowserView): """A view that can be traversed further to access browser resources This view is usually registered for zope.component.interfaces.ISite objects with no name, so resources will be available at /@@/. Let's test how it's traversed to get registered resources. Let's create a sample resource class and register it. >>> from zope.component import provideAdapter >>> from zope.interface import Interface >>> from zope.publisher.interfaces import NotFound >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer >>> from zope.publisher.browser import TestRequest >>> class Resource(object): ... def __init__(self,request): ... self.request = request ... def __call__(self): ... return 'http://localhost/testresource' >>> provideAdapter(Resource, (IDefaultBrowserLayer,), Interface, 'test') Now, create a site and request objects and get the Resources object to work with. >>> site = object() >>> request = TestRequest() >>> resources = Resources(site, request) Okay, let's test the publishTraverse method. It should traverse to our registered resource. >>> resource = resources.publishTraverse(request, 'test') >>> resource.__parent__ is site True >>> resource.__name__ == 'test' True >>> resource() 'http://localhost/testresource' However, it will raise NotFound exception if we try to traverse to an unregistered resource. >>> resources.publishTraverse(request, 'does-not-exist') Traceback (most recent call last): ... NotFound: Object: , name: 'does-not-exist' When accessed without further traversing, it returns an empty page and no futher traversing steps. >>> view, path = resources.browserDefault(request) >>> view() == '' True >>> path == () True The Resources view also provides __getitem__ method for use in templates. >>> resource = resources['test'] >>> resource.__parent__ is site True >>> resource.__name__ == 'test' True >>> resource() 'http://localhost/testresource' """ implements(IBrowserPublisher) def publishTraverse(self, request, name): '''See zope.publisher.interfaces.browser.IBrowserPublisher interface''' resource = queryAdapter(request, name=name) if resource is None: raise NotFound(self, name) locate(resource, self.context, name) return resource def browserDefault(self, request): '''See zope.publisher.interfaces.browser.IBrowserPublisher interface''' return empty, () def __getitem__(self, name): '''A helper method to make this view usable from templates, so resources can be acessed in template like context/@@/. ''' return self.publishTraverse(self.request, name) def empty(): return '' zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/interfaces.py0000644000175000017500000000311412214017540027335 0ustar arnauarnau############################################################################## # # Copyright (c) 2009 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Resource interfaces $Id: interfaces.py 103276 2009-08-27 14:31:59Z nadako $ """ from zope.interface import Interface, Attribute class IResource(Interface): request = Attribute('Request object that is requesting the resource') def __call__(): """return the absolute URL of this resource.""" class IResourceFactory(Interface): def __call__(request): """Return an IResource object""" class IResourceFactoryFactory(Interface): """A factory for IResourceFactory objects These factories are registered as named utilities that can be selected for creating resource factories in a pluggable way. Resource directories and browser:resource directive use these utilities to choose what resource to create, depending on the file extension, so third-party packages could easily plug-in additional resource types. """ def __call__(path, checker, name): """Return an IResourceFactory""" zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/metaconfigure.py0000644000175000017500000002225312214017540030047 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZCML directive handlers for browser resources $Id: metaconfigure.py 103276 2009-08-27 14:31:59Z nadako $ """ import os from zope.component import queryUtility from zope.component.interface import provideInterface from zope.component.zcml import handler from zope.configuration.exceptions import ConfigurationError from zope.interface import Interface, implements, classProvides from zope.publisher.interfaces.browser import IBrowserRequest from zope.publisher.interfaces.browser import IDefaultBrowserLayer from zope.security.checker import CheckerPublic, NamesChecker, Checker from zope.security.proxy import Proxy from zope.browserresource.directory import DirectoryResourceFactory from zope.browserresource.file import File, FileResourceFactory from zope.browserresource.i18nfile import I18nFileResourceFactory from zope.browserresource.icon import IconViewFactory from zope.browserresource.interfaces import IResourceFactory from zope.browserresource.interfaces import IResourceFactoryFactory allowed_names = ('GET', 'HEAD', 'publishTraverse', 'browserDefault', 'request', '__call__') class ResourceFactoryWrapper(object): implements(IResourceFactory) classProvides(IResourceFactoryFactory) def __init__(self, factory, checker, name): self.__factory = factory self.__checker = checker self.__name = name def __call__(self, request): resource = self.__factory(request) resource.__Security_checker__ = self.__checker resource.__name__ = self.__name return resource def resource(_context, name, layer=IDefaultBrowserLayer, permission='zope.Public', factory=None, file=None, image=None, template=None): if permission == 'zope.Public': permission = CheckerPublic checker = NamesChecker(allowed_names, permission) if (factory and (file or image or template)) or \ (file and (factory or image or template)) or \ (image and (factory or file or template)) or \ (template and (factory or file or image)): raise ConfigurationError( "Must use exactly one of factory or file or image or template" " attributes for resource directives" ) if image or template: import warnings warnings.warn_explicit( 'The "template" and "image" attributes of resource ' 'directive are deprecated in favor of pluggable ' 'file resource factories based on file extensions. ' 'Use the "file" attribute instead.', DeprecationWarning, _context.info.file, _context.info.line) if image: file = image elif template: file = template _context.action( discriminator = ('resource', name, IBrowserRequest, layer), callable = resourceHandler, args = (name, layer, checker, factory, file, _context.info), ) def resourceHandler(name, layer, checker, factory, file, context_info): if factory is not None: factory = ResourceFactoryWrapper(factory, checker, name) else: ext = os.path.splitext(os.path.normcase(file))[1][1:] factory_factory = queryUtility(IResourceFactoryFactory, ext, FileResourceFactory) factory = factory_factory(file, checker, name) handler('registerAdapter', factory, (layer,), Interface, name, context_info) def resourceDirectory(_context, name, directory, layer=IDefaultBrowserLayer, permission='zope.Public'): if permission == 'zope.Public': permission = CheckerPublic checker = NamesChecker(allowed_names + ('__getitem__', 'get'), permission) if not os.path.isdir(directory): raise ConfigurationError( "Directory %s does not exist" % directory ) factory = DirectoryResourceFactory(directory, checker, name) _context.action( discriminator = ('resource', name, IBrowserRequest, layer), callable = handler, args = ('registerAdapter', factory, (layer,), Interface, name, _context.info), ) def icon(_context, name, for_, file=None, resource=None, layer=IDefaultBrowserLayer, title=None, width=16, height=16): iname = for_.getName() if title is None: title = iname if title.startswith('I'): title = title[1:] # Remove leading 'I' if file is not None and resource is not None: raise ConfigurationError( "Can't use more than one of file, and resource " "attributes for icon directives" ) elif file is not None: resource = '-'.join(for_.__module__.split('.')) resource = "%s-%s-%s" % (resource, iname, name) ext = os.path.splitext(file)[1] if ext: resource += ext # give this module another name, so we can use the "resource" directive # in it that won't conflict with our local variable with the same name. from zope.browserresource import metaconfigure metaconfigure.resource(_context, file=file, name=resource, layer=layer) elif resource is None: raise ConfigurationError( "At least one of the file, and resource " "attributes for resource directives must be specified" ) vfactory = IconViewFactory(resource, title, width, height) _context.action( discriminator = ('view', name, vfactory, layer), callable = handler, args = ('registerAdapter', vfactory, (for_, layer), Interface, name, _context.info) ) _context.action( discriminator = None, callable = provideInterface, args = (for_.__module__+'.'+for_.getName(), for_) ) class I18nResource(object): type = IBrowserRequest default_allowed_attributes = '__call__' def __init__(self, _context, name=None, defaultLanguage='en', layer=IDefaultBrowserLayer, permission=None): self._context = _context self.name = name self.defaultLanguage = defaultLanguage self.layer = layer self.permission = permission self.__data = {} def translation(self, _context, language, file=None, image=None): if file is not None and image is not None: raise ConfigurationError( "Can't use more than one of file, and image " "attributes for resource directives" ) elif file is None and image is None: raise ConfigurationError( "At least one of the file, and image " "attributes for resource directives must be specified" ) if image is not None: import warnings warnings.warn_explicit( 'The "image" attribute of i18n-resource directive is ' 'deprecated in favor of simple files. Use the "file" ' 'attribute instead.', DeprecationWarning, _context.info.file, _context.info.line) file = image self.__data[language] = File(_context.path(file), self.name) def __call__(self, require = None): if self.name is None: return if self.defaultLanguage not in self.__data: raise ConfigurationError( "A translation for the default language (%s) " "must be specified" % self.defaultLanguage ) permission = self.permission factory = I18nFileResourceFactory(self.__data, self.defaultLanguage) if permission: if require is None: require = {} if permission == 'zope.Public': permission = CheckerPublic if require: checker = Checker(require) factory = self._proxyFactory(factory, checker) self._context.action( discriminator = ('i18n-resource', self.name, self.type, self.layer), callable = handler, args = ('registerAdapter', factory, (self.layer,), Interface, self.name, self._context.info) ) def _proxyFactory(self, factory, checker): def proxyView(request, factory=factory, checker=checker): resource = factory(request) # We need this in case the resource gets unwrapped and # needs to be rewrapped resource.__Security_checker__ = checker return Proxy(resource, checker) return proxyView zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/__init__.py0000644000175000017500000000000012214017540026740 0ustar arnauarnauzope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/directory.py0000644000175000017500000001015112214017540027215 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Resource Directory A 'resource directory' is an on-disk directory which is registered as a resource using the ZCML directive. The directory is treated as a source for individual resources; it can be traversed to retrieve resources represented by contained files, which can in turn be treated as resources. The contained files have __name__ values which include a '/' separating the __name__ of the resource directory from the name of the file within the directory. $Id: directory.py 104477 2009-09-24 09:41:25Z nadako $ """ import fnmatch import os from zope.component import queryUtility from zope.interface import implements, classProvides from zope.publisher.browser import BrowserView from zope.publisher.interfaces import NotFound from zope.publisher.interfaces.browser import IBrowserPublisher from zope.browserresource.file import FileResourceFactory from zope.browserresource.resource import Resource from zope.browserresource.interfaces import IResourceFactory from zope.browserresource.interfaces import IResourceFactoryFactory _marker = object() def empty(): return '' # we only need this class as a context for DirectoryResource class Directory(object): def __init__(self, path, checker, name): self.path = path self.checker = checker self.__name__ = name class DirectoryResource(BrowserView, Resource): implements(IBrowserPublisher) default_factory = FileResourceFactory directory_factory = None # this will be assigned later in the module forbidden_names = ('.svn', ) def publishTraverse(self, request, name): '''See interface IBrowserPublisher''' return self.get(name) def browserDefault(self, request): '''See interface IBrowserPublisher''' return empty, () def __getitem__(self, name): res = self.get(name, None) if res is None: raise KeyError(name) return res def get(self, name, default=_marker): for pat in self.forbidden_names: if fnmatch.fnmatch(name, pat): if default is _marker: raise NotFound(None, name) else: return default path = self.context.path filename = os.path.join(path, name) isfile = os.path.isfile(filename) isdir = os.path.isdir(filename) if not (isfile or isdir): if default is _marker: raise NotFound(None, name) return default if isfile: ext = os.path.splitext(os.path.normcase(name))[1][1:] factory = queryUtility(IResourceFactoryFactory, ext, self.default_factory) else: factory = self.directory_factory rname = self.__name__ + '/' + name resource = factory(filename, self.context.checker, rname)(self.request) resource.__parent__ = self return resource class DirectoryResourceFactory(object): implements(IResourceFactory) classProvides(IResourceFactoryFactory) factoryClass = DirectoryResource def __init__(self, path, checker, name): self.__dir = Directory(path, checker, name) self.__checker = checker self.__name = name def __call__(self, request): resource = self.factoryClass(self.__dir, request) resource.__Security_checker__ = self.__checker resource.__name__ = self.__name return resource DirectoryResource.directory_factory = DirectoryResourceFactory zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/icon.py0000644000175000017500000000334412214017540026147 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Icon support $Id: icon.py 105850 2009-11-19 07:05:29Z tlotze $ """ import zope.component.hooks from zope.component import getAdapter from zope.location import locate class IconView(object): def __init__(self, context, request, rname, alt, width, height): self.context = context self.request = request self.rname = rname self.alt = alt self.width = width self.height = height def __call__(self): return ('%s' % (self.url(), self.alt, self.width, self.height)) def url(self): resource = getAdapter(self.request, name=self.rname) locate(resource, zope.component.hooks.getSite(), self.rname) return resource() class IconViewFactory(object): def __init__(self, rname, alt, width, height): self.rname = rname self.alt = alt self.width = width self.height = height def __call__(self, context, request): return IconView(context, request, self.rname, self.alt, self.width, self.height) zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/meta.zcml0000644000175000017500000000170512214017540026461 0ustar arnauarnau zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/0000755000175000017500000000000012214017540026003 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/test_resources.py0000644000175000017500000000216312214017540031430 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test Browser Resources $Id: test_resources.py 111698 2010-04-30 20:23:50Z hannosch $ """ import doctest import unittest from zope.testing import cleanup def setUp(test): cleanup.setUp() def tearDown(test): cleanup.tearDown() def test_suite(): return unittest.TestSuite(( doctest.DocTestSuite( 'zope.browserresource.resources', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE), )) zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/test_resource.py0000644000175000017500000000476412214017540031256 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unit tests for Resource $Id: test_resource.py 105850 2009-11-19 07:05:29Z tlotze $ """ import unittest from zope import component from zope.publisher.browser import TestRequest import zope.component.interfaces from zope.browserresource.resource import Resource from zope.browserresource.tests import support from zope.traversing.browser.interfaces import IAbsoluteURL from zope.traversing.browser.absoluteurl import AbsoluteURL from zope.testing import cleanup class TestResource(support.SiteHandler, cleanup.CleanUp, unittest.TestCase): def setUp(self): super(TestResource, self).setUp() component.provideAdapter(AbsoluteURL, (None, None), IAbsoluteURL) def testGlobal(self): req = TestRequest() r = Resource(req) req._vh_root = support.site r.__parent__ = support.site r.__name__ = 'foo' self.assertEquals(r(), 'http://127.0.0.1/@@/foo') r.__name__ = '++resource++foo' self.assertEquals(r(), 'http://127.0.0.1/@@/foo') def testGlobalInVirtualHost(self): req = TestRequest() req.setVirtualHostRoot(['x', 'y']) r = Resource(req) req._vh_root = support.site r.__parent__ = support.site r.__name__ = 'foo' self.assertEquals(r(), 'http://127.0.0.1/x/y/@@/foo') def testResourceUrl(self): # fake IAbsoluteURL adapter def resourceBase(site, request): return 'http://cdn.example.com' component.provideAdapter( resourceBase, (zope.component.interfaces.ISite, TestRequest), IAbsoluteURL, 'resource') req = TestRequest() r = Resource(req) req._vh_root = support.site r.__parent__ = support.site r.__name__ = 'foo' self.assertEquals(r(), 'http://cdn.example.com/@@/foo') def test_suite(): return unittest.makeSuite(TestResource) zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/test_i18nfile.py0000644000175000017500000001260012214017540031032 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """I18n File-Resource Tests $Id: test_i18nfile.py 103196 2009-08-25 11:48:42Z nadako $ """ from unittest import main, makeSuite import os from zope.publisher.interfaces import NotFound from zope.component import provideAdapter, provideUtility from zope.testing import cleanup from zope.i18n.interfaces import IUserPreferredCharsets, IUserPreferredLanguages from zope.publisher.http import IHTTPRequest, HTTPCharsets from zope.publisher.browser import BrowserLanguages, TestRequest from zope.browserresource.i18nfile import I18nFileResource from zope.browserresource.i18nfile import I18nFileResourceFactory from zope.browserresource.file import File import zope.browserresource.tests as p from zope.i18n.interfaces import INegotiator from zope.i18n.negotiator import negotiator from zope.i18n.tests.testii18naware import TestII18nAware test_directory = os.path.dirname(p.__file__) class Test(cleanup.CleanUp, TestII18nAware): def setUp(self): super(Test, self).setUp() TestII18nAware.setUp(self) provideAdapter(HTTPCharsets, (IHTTPRequest,), IUserPreferredCharsets) provideAdapter(BrowserLanguages, (IHTTPRequest,), IUserPreferredLanguages) # Setup the negotiator utility provideUtility(negotiator, INegotiator) def _createObject(self): obj = I18nFileResource({'en':None, 'lt':None, 'fr':None}, TestRequest(), 'fr') return obj def test_setDefaultLanguage(self): ob = self._createObject() self.assertRaises(ValueError, ob.setDefaultLanguage, 'ru') def _createDict(self, filename1='test.pt', filename2='test2.pt'): path1 = os.path.join(test_directory, 'testfiles', filename1) path2 = os.path.join(test_directory, 'testfiles', filename2) return { 'en': File(path1, filename1), 'fr': File(path2, filename2) } def testNoTraversal(self): resource = I18nFileResourceFactory(self._createDict(), 'en')\ (TestRequest()) self.assertRaises(NotFound, resource.publishTraverse, resource.request, '_testData') def testFileGET(self): # case 1: no language preference, should get en path = os.path.join(test_directory, 'testfiles', 'test.txt') resource = I18nFileResourceFactory(self._createDict('test.txt'), 'en')\ (TestRequest()) self.assertEqual(resource.GET(), open(path, 'rb').read()) response = resource.request.response self.assertEqual(response.getHeader('Content-Type'), 'text/plain') # case 2: prefer lt, have only en and fr, should get en resource = I18nFileResourceFactory( self._createDict('test.txt'), 'en')\ (TestRequest(HTTP_ACCEPT_LANGUAGE='lt')) self.assertEqual(resource.GET(), open(path, 'rb').read()) response = resource.request.response self.assertEqual(response.getHeader('Content-Type'), 'text/plain') # case 3: prefer fr, have it, should get fr path = os.path.join(test_directory, 'testfiles', 'test2.pt') resource = I18nFileResourceFactory( self._createDict('test.pt', 'test2.pt'), 'en')\ (TestRequest(HTTP_ACCEPT_LANGUAGE='fr')) self.assertEqual(resource.GET(), open(path, 'rb').read()) response = resource.request.response self.assertEqual(response.getHeader('Content-Type'), 'text/html') def testFileHEAD(self): # case 1: no language preference, should get en resource = I18nFileResourceFactory(self._createDict('test.txt'), 'en')\ (TestRequest()) self.assertEqual(resource.HEAD(), '') response = resource.request.response self.assertEqual(response.getHeader('Content-Type'), 'text/plain') # case 2: prefer lt, have only en and fr, should get en resource = I18nFileResourceFactory( self._createDict('test.txt'), 'en')\ (TestRequest(HTTP_ACCEPT_LANGUAGE='lt')) self.assertEqual(resource.HEAD(), '') response = resource.request.response self.assertEqual(response.getHeader('Content-Type'), 'text/plain') # case 3: prefer fr, have it, should get fr resource = I18nFileResourceFactory( self._createDict('test.pt', 'test2.pt'), 'en')\ (TestRequest(HTTP_ACCEPT_LANGUAGE='fr')) self.assertEqual(resource.HEAD(), '') response = resource.request.response self.assertEqual(response.getHeader('Content-Type'), 'text/html') def test_suite(): return makeSuite(Test) zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/test_directives.py0000644000175000017500000002036012214017540031556 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """'browser' namespace directive tests $Id: test_directives.py 103138 2009-08-24 11:37:52Z nadako $ """ import os import unittest from cStringIO import StringIO from zope import component from zope.interface import Interface, implements, directlyProvides, providedBy import zope.security.management from zope.configuration.xmlconfig import xmlconfig, XMLConfig from zope.configuration.exceptions import ConfigurationError from zope.publisher.browser import TestRequest from zope.publisher.interfaces import IDefaultViewName from zope.publisher.interfaces.browser import IBrowserPublisher from zope.publisher.interfaces.browser import IBrowserRequest from zope.publisher.interfaces.browser import IBrowserSkinType, IDefaultSkin from zope.security.proxy import removeSecurityProxy, ProxyFactory from zope.security.permission import Permission from zope.security.interfaces import IPermission from zope.traversing.adapters import DefaultTraversable from zope.traversing.interfaces import ITraversable import zope.publisher.defaultview import zope.browserresource from zope.component import provideAdapter, provideUtility from zope.component.testfiles.views import R1, IV from zope.browserresource.file import FileResource from zope.browserresource.i18nfile import I18nFileResource from zope.browserresource.directory import DirectoryResource from zope.testing import cleanup tests_path = os.path.join( os.path.dirname(zope.browserresource.__file__), 'tests') template = """ %s """ request = TestRequest() class ITestLayer(IBrowserRequest): """Test Layer.""" class ITestSkin(ITestLayer): """Test Skin.""" class MyResource(object): def __init__(self, request): self.request = request class Test(cleanup.CleanUp, unittest.TestCase): def setUp(self): super(Test, self).setUp() XMLConfig('meta.zcml', zope.browserresource)() provideAdapter(DefaultTraversable, (None,), ITraversable) def tearDown(self): super(Test, self).tearDown() def testI18nResource(self): self.assertEqual(component.queryAdapter(request, name='test'), None) path1 = os.path.join(tests_path, 'testfiles', 'test.pt') path2 = os.path.join(tests_path, 'testfiles', 'test2.pt') xmlconfig(StringIO(template % ( ''' ''' % (path1, path2) ))) v = component.getAdapter(request, name='test') self.assertEqual( component.queryAdapter(request, name='test').__class__, I18nFileResource) self.assertEqual(v._testData('en'), open(path1, 'rb').read()) self.assertEqual(v._testData('fr'), open(path2, 'rb').read()) # translation must be provided for the default language config = StringIO(template % ( ''' ''' % (path1, path2) )) self.assertRaises(ConfigurationError, xmlconfig, config) def testFactory(self): self.assertEqual( component.queryAdapter(request, name='index.html'), None) xmlconfig(StringIO(template % ''' ''' )) r = component.getAdapter(request, name='index.html') self.assertEquals(r.__class__, MyResource) r = ProxyFactory(r) self.assertEqual(r.__name__, "index.html") def testFile(self): path = os.path.join(tests_path, 'testfiles', 'test.pt') self.assertEqual(component.queryAdapter(request, name='test'), None) xmlconfig(StringIO(template % ''' ''' % path )) r = component.getAdapter(request, name='index.html') self.assertTrue(isinstance(r, FileResource)) r = ProxyFactory(r) self.assertEqual(r.__name__, "index.html") # Make sure we can access available attrs and not others for n in ('GET', 'HEAD', 'publishTraverse', 'request', '__call__'): getattr(r, n) self.assertRaises(Exception, getattr, r, '_testData') r = removeSecurityProxy(r) self.assertEqual(r._testData(), open(path, 'rb').read()) def testPluggableFactory(self): class ImageResource(object): def __init__(self, image, request): pass class ImageResourceFactory(object): def __init__(self, path, checker, name): pass def __call__(self, request): return ImageResource(None, request) from zope.browserresource.interfaces import IResourceFactoryFactory component.provideUtility(ImageResourceFactory, IResourceFactoryFactory, name='gif') xmlconfig(StringIO(template % ''' ''' % os.path.join(tests_path, 'testfiles', 'test.gif') )) r = component.getAdapter(request, name='test.gif') self.assertTrue(isinstance(r, ImageResource)) def testDirectory(self): path = os.path.join(tests_path, 'testfiles', 'subdir') self.assertEqual(component.queryAdapter(request, name='dir'), None) xmlconfig(StringIO(template % ''' ''' % path )) r = component.getAdapter(request, name='dir') self.assertTrue(isinstance(r, DirectoryResource)) r = ProxyFactory(r) self.assertEqual(r.__name__, "dir") # Make sure we can access available attrs and not others for n in ('publishTraverse', 'browserDefault', 'request', '__call__', 'get', '__getitem__'): getattr(r, n) self.assertRaises(Exception, getattr, r, 'directory_factory') inexistent_dir = StringIO(template % ''' ''') self.assertRaises(ConfigurationError, xmlconfig, inexistent_dir) def test_SkinResource(self): self.assertEqual(component.queryAdapter(request, name='test'), None) path = os.path.join(tests_path, 'testfiles', 'test.pt') xmlconfig(StringIO(template % ( ''' ''' % path ))) self.assertEqual(component.queryAdapter(request, name='test'), None) r = component.getAdapter(TestRequest(skin=ITestSkin), name='test') self.assertEqual(r._testData(), open(path, 'rb').read()) def test_suite(): return unittest.makeSuite(Test) zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/support.py0000644000175000017500000000272012214017540030072 0ustar arnauarnau############################################################################## # # Copyright (c) 2004 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Support for tests that need a simple site to be provided. $Id: support.py 105850 2009-11-19 07:05:29Z tlotze $ """ import zope.component import zope.component.hooks import zope.component.interfaces from zope.interface import implements from zope.traversing.interfaces import IContainmentRoot import zope.browserresource.resource class Site: implements(zope.component.interfaces.ISite, IContainmentRoot) def getSiteManager(self): return zope.component.getGlobalSiteManager() site = Site() class SiteHandler(object): def setUp(self): super(SiteHandler, self).setUp() zope.component.hooks.setSite(site) zope.component.provideAdapter( zope.browserresource.resource.AbsoluteURL) def tearDown(self): zope.component.hooks.setSite() super(SiteHandler, self).tearDown() zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/test_icondirective.py0000644000175000017500000001633012214017540032246 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test Icon-Directive $Id: test_icondirective.py 103136 2009-08-24 11:20:44Z nadako $ """ import os from StringIO import StringIO from unittest import TestCase, main, makeSuite from zope import component from zope.configuration.exceptions import ConfigurationError from zope.configuration.xmlconfig import xmlconfig, XMLConfig from zope.interface import implements from zope.publisher.browser import TestRequest from zope.security.checker import ProxyFactory, CheckerPublic from zope.security.interfaces import Forbidden from zope.security.proxy import removeSecurityProxy from zope.traversing.interfaces import IContainmentRoot from zope.traversing.browser.absoluteurl import AbsoluteURL from zope.traversing.browser.interfaces import IAbsoluteURL import zope.location.interfaces import zope.browserresource from zope.component.testfiles.views import IC from zope.browserresource.tests import support from zope.testing import cleanup template = """ %s """ request = TestRequest() class Ob(object): implements(IC) ob = Ob() request._vh_root = support.site def defineCheckers(): # define the appropriate checker for a FileResource for these tests from zope.security.protectclass import protectName from zope.browserresource.file import FileResource protectName(FileResource, '__call__', 'zope.Public') class Test(support.SiteHandler, cleanup.CleanUp, TestCase): def setUp(self): super(Test, self).setUp() XMLConfig('meta.zcml', zope.browserresource)() defineCheckers() component.provideAdapter(AbsoluteURL, (None, None), IAbsoluteURL) def test(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='zmi_icon'), None) import zope.browserresource.tests as p path = os.path.dirname(p.__file__) path = os.path.join(path, 'testfiles', 'test.gif') # Configure the icon and make sure we can render the resulting view: xmlconfig(StringIO(template % ( ''' ''' % path ))) view = component.getMultiAdapter((ob, request), name='zmi_icon') rname = 'zope-component-testfiles-views-IC-zmi_icon.gif' self.assertEqual( view(), 'C' % rname) self.assertEqual(view.url(), 'http://127.0.0.1/@@/' + rname) # Make sure that the title attribute works xmlconfig(StringIO(template % ( ''' ''' % path ))) view = component.getMultiAdapter( (ob, request), name='zmi_icon_w_title') rname = 'zope-component-testfiles-views-IC-zmi_icon_w_title.gif' self.assertEqual( view(), 'click this!' % rname) # Make sure that the width and height attributes work xmlconfig(StringIO(template % ( ''' ''' % path ))) view = component.getMultiAdapter((ob, request), name='zmi_icon_w_width_and_height') rname = ('zope-component-testfiles-views-IC-' 'zmi_icon_w_width_and_height.gif') self.assertEqual( view(), 'C' % rname) # Make sure that the image was installed as a resource: resource = ProxyFactory(component.getAdapter(request, name=rname)) self.assertRaises(Forbidden, getattr, resource, '_testData') resource = removeSecurityProxy(resource) self.assertEqual(resource._testData(), open(path, 'rb').read()) def testResource(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='zmi_icon'), None) import zope.browserresource.tests as p path = os.path.dirname(p.__file__) path = os.path.join(path, 'testfiles', 'test.gif') xmlconfig(StringIO(template % ( ''' ''' % path ))) view = component.getMultiAdapter((ob, request), name='zmi_icon') rname = "zmi_icon_res" self.assertEqual( view(), 'C' % rname) resource = ProxyFactory(component.getAdapter(request, name=rname)) self.assertRaises(Forbidden, getattr, resource, '_testData') resource = removeSecurityProxy(resource) self.assertEqual(resource._testData(), open(path, 'rb').read()) def testResourceErrors(self): self.assertEqual( component.queryMultiAdapter((ob, request), name='zmi_icon'), None) import zope.browserresource.tests as p path = os.path.dirname(p.__file__) path = os.path.join(path, 'testfiles', 'test.gif') config = StringIO(template % ( ''' ''' % (path, path) )) self.assertRaises(ConfigurationError, xmlconfig, config) config = StringIO(template % ( """ """ )) self.assertRaises(ConfigurationError, xmlconfig, config) def test_suite(): return makeSuite(Test) zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/test_file.py0000644000175000017500000000270412214017540030336 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """File-based browser resource tests. $Id: test_file.py 111698 2010-04-30 20:23:50Z hannosch $ """ import doctest import os import unittest from zope.testing import cleanup from zope.publisher.browser import TestRequest from zope.security.checker import NamesChecker def setUp(test): cleanup.setUp() data_dir = os.path.join(os.path.dirname(__file__), 'testfiles') test.globs['testFilePath'] = os.path.join(data_dir, 'test.txt') test.globs['nullChecker'] = NamesChecker() test.globs['TestRequest'] = TestRequest def tearDown(test): cleanup.tearDown() def test_suite(): return unittest.TestSuite(( doctest.DocTestSuite( 'zope.browserresource.file', setUp=setUp, tearDown=tearDown, optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE), )) zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/__init__.py0000644000175000017500000000000012214017540030102 0ustar arnauarnauzope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/test_directory.py0000644000175000017500000001503112214017540031420 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Directory-based resources test $Id: test_directory.py 104477 2009-09-24 09:41:25Z nadako $ """ import os import tempfile import shutil from unittest import TestCase, main, makeSuite from zope.publisher.interfaces import NotFound from zope.proxy import isProxy from zope.publisher.browser import TestRequest from zope.security import proxy from zope.security.checker import NamesChecker, ProxyFactory from zope.interface import implements from zope.location.interfaces import IContained from zope.traversing.browser.absoluteurl import AbsoluteURL from zope.traversing.browser.interfaces import IAbsoluteURL from zope.component import provideAdapter, provideUtility from zope.testing import cleanup from zope.browserresource.directory import \ DirectoryResourceFactory, DirectoryResource from zope.browserresource.file import FileResource import zope.browserresource.tests as p from zope.browserresource.tests import support test_directory = os.path.dirname(p.__file__) checker = NamesChecker( ('get', '__getitem__', 'request', 'publishTraverse') ) class Ob(object): implements(IContained) __parent__ = __name__ = None ob = Ob() class Test(support.SiteHandler, cleanup.CleanUp, TestCase): def setUp(self): super(Test, self).setUp() provideAdapter(AbsoluteURL, (None, None), IAbsoluteURL) def testNotFound(self): path = os.path.join(test_directory, 'testfiles') request = TestRequest() factory = DirectoryResourceFactory(path, checker, 'testfiles') resource = factory(request) self.assertRaises(NotFound, resource.publishTraverse, resource.request, 'doesnotexist') self.assertRaises(NotFound, resource.get, 'doesnotexist') def testBrowserDefault(self): path = os.path.join(test_directory, 'testfiles') request = TestRequest() factory = DirectoryResourceFactory(path, checker, 'testfiles') resource = factory(request) view, next = resource.browserDefault(request) self.assertEquals(view(), '') self.assertEquals(next, ()) def testGetitem(self): path = os.path.join(test_directory, 'testfiles') request = TestRequest() factory = DirectoryResourceFactory(path, checker, 'testfiles') resource = factory(request) self.assertRaises(KeyError, resource.__getitem__, 'doesnotexist') file = resource['test.txt'] def testForbiddenNames(self): request = TestRequest() old_forbidden_names = DirectoryResource.forbidden_names path = tempfile.mkdtemp() try: os.mkdir(os.path.join(path, '.svn')) open(os.path.join(path, 'test.txt'), 'w').write('') factory = DirectoryResourceFactory(path, checker, 'testfiles') resource = factory(request) self.assertEquals(resource.get('.svn', None), None) self.assertNotEquals(resource.get('test.txt', None), None) DirectoryResource.forbidden_names = ('*.txt', ) self.assertEquals(resource.get('test.txt', None), None) self.assertNotEquals(resource.get('.svn', None), None) finally: shutil.rmtree(path) DirectoryResource.forbidden_names = old_forbidden_names def testProxy(self): path = os.path.join(test_directory, 'testfiles') request = TestRequest() factory = DirectoryResourceFactory(path, checker, 'testfiles') resource = factory(request) file = ProxyFactory(resource['test.txt']) self.assert_(isProxy(file)) def testURL(self): request = TestRequest() request._vh_root = support.site path = os.path.join(test_directory, 'testfiles') files = DirectoryResourceFactory(path, checker, 'test_files')(request) files.__parent__ = support.site file = files['test.gif'] self.assertEquals(file(), 'http://127.0.0.1/@@/test_files/test.gif') def testURL2Level(self): request = TestRequest() request._vh_root = support.site ob.__parent__ = support.site ob.__name__ = 'ob' path = os.path.join(test_directory, 'testfiles') files = DirectoryResourceFactory(path, checker, 'test_files')(request) files.__parent__ = ob file = files['test.gif'] self.assertEquals(file(), 'http://127.0.0.1/@@/test_files/test.gif') def testURL3Level(self): request = TestRequest() request._vh_root = support.site ob.__parent__ = support.site ob.__name__ = 'ob' path = os.path.join(test_directory, 'testfiles') files = DirectoryResourceFactory(path, checker, 'test_files')(request) files.__parent__ = ob file = files['test.gif'] self.assertEquals(file(), 'http://127.0.0.1/@@/test_files/test.gif') subdir = files['subdir'] self.assert_(proxy.isinstance(subdir, DirectoryResource)) file = subdir['test.gif'] self.assertEquals(file(), 'http://127.0.0.1/@@/test_files/subdir/test.gif') def testPluggableFactories(self): path = os.path.join(test_directory, 'testfiles') request = TestRequest() resource = DirectoryResourceFactory(path, checker, 'files')(request) class ImageResource(object): def __init__(self, image, request): pass class ImageResourceFactory(object): def __init__(self, path, checker, name): pass def __call__(self, request): return ImageResource(None, request) from zope.browserresource.interfaces import IResourceFactoryFactory provideUtility(ImageResourceFactory, IResourceFactoryFactory, 'gif') image = resource['test.gif'] self.assert_(proxy.isinstance(image, ImageResource)) file = resource['test.txt'] self.assert_(proxy.isinstance(file, FileResource)) def test_suite(): return makeSuite(Test) zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/testfiles/0000755000175000017500000000000012214017540030005 5ustar arnauarnauzope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/testfiles/test.pt0000644000175000017500000000004612214017540031331 0ustar arnauarnau

    test

    zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/testfiles/subdir/0000755000175000017500000000000012214017540031275 5ustar arnauarnau././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/testfiles/subdir/test.gifzope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/testfiles/subdir/test.gi0000644000175000017500000000161512214017540032600 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,j@° AxÀ°áƒ…*tØpáÄ)fŒ8ðA€=‚ €pa€†< À’dÇ“ A²$àr"L”*i–ôH1åÊš&{æÊÓ¡O/G~ºsåÌ™?›RlˆTáÓ«-*Ý ;zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/testfiles/test.txt0000644000175000017500000000001212214017540031516 0ustar arnauarnautest data zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/testfiles/test.gif0000644000175000017500000000161512214017540031456 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,j@° AxÀ°áƒ…*tØpáÄ)fŒ8ðA€=‚ €pa€†< À’dÇ“ A²$àr"L”*i–ôH1åÊš&{æÊÓ¡O/G~ºsåÌ™?›RlˆTáÓ«-*Ý ;zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/tests/testfiles/test2.pt0000644000175000017500000000007612214017540031416 0ustar arnauarnau

    test

    zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/metadirectives.py0000644000175000017500000001657312214017540030237 0ustar arnauarnau############################################################################# # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ZCML directives for defining browser resources $Id: metadirectives.py 103276 2009-08-27 14:31:59Z nadako $ """ from zope.configuration.fields import GlobalObject, GlobalInterface from zope.configuration.fields import Path, MessageID from zope.interface import Interface from zope.schema import TextLine, Int from zope.security.zcml import Permission class IBasicResourceInformation(Interface): """ This is the basic information for all browser resources. """ layer = GlobalInterface( title=u"The layer the resource should be found in", description=u""" For information on layers, see the documentation for the skin directive. Defaults to "default".""", required=False ) permission = Permission( title=u"The permission needed to access the resource.", description=u""" If a permission isn't specified, the resource will always be accessible.""", required=False ) class IResourceDirective(IBasicResourceInformation): """ Defines a browser resource """ name = TextLine( title=u"The name of the resource", description=u""" This is the name used in resource urls. Resource urls are of the form site/@@/resourcename, where site is the url of "site", a folder with a site manager. We make resource urls site-relative (as opposed to content-relative) so as not to defeat caches.""", required=True ) factory = GlobalObject( title=u"Resource Factory", description=u"The factory used to create the resource. The factory " u"should only expect to get the request passed when " u"called.", required=False ) file = Path( title=u"File", description=u"The file containing the resource data. The resource " u"type that will be created depends on file extension. " u"The named IResourceFactoryFactory utilities are " u"registered per extension. If no factory is registered " u"for given file extension, the default FileResource " u"factory will be used.", required=False ) image = Path( title=u"Image", description=u""" If the image attribute is used, then an image resource, rather than a file resource will be created. This attribute is deprecated in favor of pluggable resource types, registered per extension. Use the "file" attribute instead. """, required=False ) template = Path( title=u"Template", description=u""" If the template attribute is used, then a page template resource, rather than a file resource will be created. This attribute is deprecated in favor of pluggable resource types, registered per extension. Use the "file" attribute instead. To use page template resources, you need to instal zope.ptresource package. """, required=False ) class II18nResourceDirective(IBasicResourceInformation): """ Defines an i18n'd resource. """ name = TextLine( title=u"The name of the resource", description=u""" This is the name used in resource urls. Resource urls are of the form site/@@/resourcename, where site is the url of "site", a folder with a site manager. We make resource urls site-relative (as opposed to content-relative) so as not to defeat caches.""", required=True ) defaultLanguage = TextLine( title=u"Default language", description=u"Defines the default language", required=False ) class II18nResourceTranslationSubdirective(IBasicResourceInformation): """ Subdirective to II18nResourceDirective. """ language = TextLine( title=u"Language", description=u"Language of this translation of the resource", required=True ) file = Path( title=u"File", description=u"The file containing the resource data.", required=False ) image = Path( title=u"Image", description=u""" If the image attribute is used, then an image resource, rather than a file resource will be created. This attribute is deprecated, as images are now simply files. Use the "file" attribute instead. """, required=False ) class IResourceDirectoryDirective(IBasicResourceInformation): """ Defines a directory containing browser resource """ name = TextLine( title=u"The name of the resource", description=u""" This is the name used in resource urls. Resource urls are of the form site/@@/resourcename, where site is the url of "site", a folder with a site manager. We make resource urls site-relative (as opposed to content-relative) so as not to defeat caches.""", required=True ) directory = Path( title=u"Directory", description=u"The directory containing the resource data.", required=True ) class IIconDirective(Interface): """ Define an icon for an interface """ name = TextLine( title=u"The name of the icon.", description=u"The name shows up in URLs/paths. For example 'foo'.", required=True ) for_ = GlobalInterface( title=u"The interface this icon is for.", description=u""" The icon will be for all objects that implement this interface.""", required=True ) file = Path( title=u"File", description=u"The file containing the icon.", required=False ) resource = TextLine( title=u"Resource", description=u"A resource containing the icon.", required=False ) title = MessageID( title=u"Title", description=u"Descriptive title", required=False ) layer = GlobalInterface( title=u"The layer the icon should be found in", description=u""" For information on layers, see the documentation for the skin directive. Defaults to "default".""", required=False ) width = Int( title=u"The width of the icon.", description=u""" The width will be used for the attribute. Defaults to 16.""", required=False, default=16 ) height = Int( title=u"The height of the icon.", description=u""" The height will be used for the attribute. Defaults to 16.""", required=False, default=16 ) zope2.13-2.13.21/source/zope.browserresource/src/zope/browserresource/i18nfile.py0000644000175000017500000000527112214017540026637 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Internationalized file resource. $Id: i18nfile.py 103196 2009-08-25 11:48:42Z nadako $ """ from zope.i18n.interfaces import II18nAware from zope.i18n.negotiator import negotiator from zope.interface import implements, classProvides from zope.browserresource.file import FileResource from zope.browserresource.interfaces import IResourceFactory from zope.browserresource.interfaces import IResourceFactoryFactory class I18nFileResource(FileResource): implements(II18nAware) def __init__(self, data, request, defaultLanguage='en'): """Creates an internationalized file resource. data should be a mapping from languages to File objects. """ self._data = data self.request = request self.defaultLanguage = defaultLanguage def chooseContext(self): """Choose the appropriate context according to language""" langs = self.getAvailableLanguages() language = negotiator.getLanguage(langs, self.request) try: return self._data[language] except KeyError: return self._data[self.defaultLanguage] def getDefaultLanguage(self): 'See II18nAware' return self.defaultLanguage def setDefaultLanguage(self, language): 'See II18nAware' if language not in self._data: raise ValueError( 'cannot set nonexistent language (%s) as default' % language) self.defaultLanguage = language def getAvailableLanguages(self): 'See II18nAware' return self._data.keys() # for unit tests def _testData(self, language): file = self._data[language] f=open(file.path,'rb') data=f.read() f.close() return data class I18nFileResourceFactory(object): implements(IResourceFactory) classProvides(IResourceFactoryFactory) def __init__(self, data, defaultLanguage): self.__data = data self.__defaultLanguage = defaultLanguage def __call__(self, request): return I18nFileResource(self.__data, request, self.__defaultLanguage) zope2.13-2.13.21/source/zope.browserresource/src/zope/__init__.py0000644000175000017500000000007012214017540023514 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/RestrictedPython/0000755000175000017500000000000012214017460016746 5ustar arnauarnauzope2.13-2.13.21/source/RestrictedPython/setup.py0000644000175000017500000000275612214017460020472 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Setup for RestrictedPython package """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='RestrictedPython', version='3.6.0', url='http://pypi.python.org/pypi/RestrictedPython', license='ZPL 2.1', description='RestrictedPython provides a restricted execution ' 'environment for Python, e.g. for running untrusted code.', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=(read('src', 'RestrictedPython', 'README.txt') + '\n' + read('CHANGES.txt')), packages = find_packages('src'), package_dir = {'': 'src'}, install_requires = ['setuptools'], include_package_data = True, zip_safe = False, ) zope2.13-2.13.21/source/RestrictedPython/PKG-INFO0000644000175000017500000002466412214017460020057 0ustar arnauarnauMetadata-Version: 1.0 Name: RestrictedPython Version: 3.6.0 Summary: RestrictedPython provides a restricted execution environment for Python, e.g. for running untrusted code. Home-page: http://pypi.python.org/pypi/RestrictedPython Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: .. contents:: Overview ======== RestrictedPython provides a ``restricted_compile`` function that works like the built-in ``compile`` function, except that it allows the controlled and restricted execution of code: >>> src = ''' ... def hello_world(): ... return "Hello World!" ... ''' >>> from RestrictedPython import compile_restricted >>> code = compile_restricted(src, '', 'exec') The resulting code can be executed using the ``exec`` built-in: >>> exec(code) As a result, the ``hello_world`` function is now available in the global namespace: >>> hello_world() 'Hello World!' Compatibility ============= This release of RestrictedPython is compatible with Python 2.3, 2.4, 2.5, 2.6, and 2.7. Implementing a policy ===================== RestrictedPython only provides the raw material for restricted execution. To actually enforce any restrictions, you need to supply a policy implementation by providing restricted versions of ``print``, ``getattr``, ``setattr``, ``import``, etc. These restricted implementations are hooked up by providing a set of specially named objects in the global dict that you use for execution of code. Specifically: 1. ``_print_`` is a callable object that returns a handler for print statements. This handler must have a ``write()`` method that accepts a single string argument, and must return a string when called. ``RestrictedPython.PrintCollector.PrintCollector`` is a suitable implementation. 2. ``_write_`` is a guard function taking a single argument. If the object passed to it may be written to, it should be returned, otherwise the guard function should raise an exception. ``_write`` is typically called on an object before a ``setattr`` operation. 3. ``_getattr_`` and ``_getitem_`` are guard functions, each of which takes two arguments. The first is the base object to be accessed, while the second is the attribute name or item index that will be read. The guard function should return the attribute or subitem, or raise an exception. 4. ``__import__`` is the normal Python import hook, and should be used to control access to Python packages and modules. 5. ``__builtins__`` is the normal Python builtins dictionary, which should be weeded down to a set that cannot be used to get around your restrictions. A usable "safe" set is ``RestrictedPython.Guards.safe_builtins``. To help illustrate how this works under the covers, here's an example function:: def f(x): x.foo = x.foo + x[0] print x return printed and (sort of) how it looks after restricted compilation:: def f(x): # Make local variables from globals. _print = _print_() _write = _write_ _getattr = _getattr_ _getitem = _getitem_ # Translation of f(x) above _write(x).foo = _getattr(x, 'foo') + _getitem(x, 0) print >>_print, x return _print() Examples ======== ``print`` --------- To support the ``print`` statement in restricted code, we supply a ``_print_`` object (note that it's a *factory*, e.g. a class or a callable, from which the restricted machinery will create the object): >>> from RestrictedPython.PrintCollector import PrintCollector >>> _print_ = PrintCollector >>> src = ''' ... print "Hello World!" ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) As you can see, the text doesn't appear on stdout. The print collector collects it. We can have access to the text using the ``printed`` variable, though: >>> src = ''' ... print "Hello World!" ... result = printed ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) >>> result 'Hello World!\n' Built-ins --------- By supplying a different ``__builtins__`` dictionary, we can rule out unsafe operations, such as opening files: >>> from RestrictedPython.Guards import safe_builtins >>> restricted_globals = dict(__builtins__ = safe_builtins) >>> src = ''' ... open('/etc/passwd') ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) in restricted_globals Traceback (most recent call last): ... NameError: name 'open' is not defined Guards ------ Here's an example of a write guard that never lets restricted code modify (assign, delete an attribute or item) except dictionaries and lists: >>> from RestrictedPython.Guards import full_write_guard >>> _write_ = full_write_guard >>> _getattr_ = getattr >>> class BikeShed(object): ... colour = 'green' ... >>> shed = BikeShed() Normally accessing attriutes works as expected, because we're using the standard ``getattr`` function for the ``_getattr_`` guard: >>> src = ''' ... print shed.colour ... result = printed ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) >>> result 'green\n' However, changing an attribute doesn't work: >>> src = ''' ... shed.colour = 'red' ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) Traceback (most recent call last): ... TypeError: attribute-less object (assign or del) As said, this particular write guard (``full_write_guard``) will allow restricted code to modify lists and dictionaries: >>> fibonacci = [1, 1, 2, 3, 4] >>> transl = dict(one=1, two=2, tres=3) >>> src = ''' ... # correct mistake in list ... fibonacci[-1] = 5 ... # one item doesn't belong ... del transl['tres'] ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) >>> fibonacci [1, 1, 2, 3, 5] >>> sorted(transl.keys()) ['one', 'two'] Changes ======= 3.6.0 (2010-07-09) ------------------ - Added name check for names assigned during imports using the "from x import y" format. - Added test for name check when assigning an alias using multiple-context with statements in Python 2.7. - Added tests for protection of the iterators for dict and set comprehensions in Python 2.7. 3.6.0a1 (2010-06-05) -------------------- - Removed support for DocumentTemplate.sequence - this is handled in the DocumentTemplate package itself. 3.5.2 (2010-04-30) ------------------ - Removed a testing dependency on zope.testing. 3.5.1 (2009-03-17) ------------------ - Added tests for ``Utilities`` module. - Filtered DeprecationWarnings when importing Python's ``sets`` module. 3.5.0 (2009-02-09) ------------------ - Dropped legacy support for Python 2.1 / 2.2 (``__future__`` imports of ``nested_scopes`` / ``generators``.). 3.4.3 (2008-10-26) ------------------ - Fixed deprecation warning: ``with`` is now a reserved keyword on Python 2.6. That means RestrictedPython should run on Python 2.6 now. Thanks to Ranjith Kannikara, GSoC Student for the patch. - Added tests for ternary if expression and for 'with' keyword and context managers. 3.4.2 (2007-07-28) ------------------ - Changed homepage URL to the CheeseShop site - Greatly improved README.txt 3.4.1 (2007-06-23) ------------------ - Fixed http://www.zope.org/Collectors/Zope/2295: Bare conditional in a Zope 2 PythonScript followed by a comment causes SyntaxError. 3.4.0 (2007-06-04) ------------------ - RestrictedPython now has its own release cycle as a separate egg. - Synchronized with RestrictedPython from Zope 2 tree. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the RestrictedPython package shipped as part of the Zope 3.2.0 release. - No changes from 3.1.0. 3.1.0 (2005-10-03) ------------------ - Corresponds to the verison of the RestrictedPython package shipped as part of the Zope 3.1.0 release. - Removed unused fossil module, ``SafeMapping``. - Replaced use of deprecated 'whrandom' module with 'random' (aliased to 'whrandom' for backward compatibility). 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the RestrictedPython package shipped as part of the Zope X3.0.0 release. Platform: UNKNOWN zope2.13-2.13.21/source/RestrictedPython/pip-egg-info/0000755000175000017500000000000012214017460021227 5ustar arnauarnauzope2.13-2.13.21/source/RestrictedPython/pip-egg-info/RestrictedPython.egg-info/0000755000175000017500000000000012214017460026233 5ustar arnauarnauzope2.13-2.13.21/source/RestrictedPython/pip-egg-info/RestrictedPython.egg-info/PKG-INFO0000644000175000017500000002466412214017460027344 0ustar arnauarnauMetadata-Version: 1.0 Name: RestrictedPython Version: 3.6.0 Summary: RestrictedPython provides a restricted execution environment for Python, e.g. for running untrusted code. Home-page: http://pypi.python.org/pypi/RestrictedPython Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: .. contents:: Overview ======== RestrictedPython provides a ``restricted_compile`` function that works like the built-in ``compile`` function, except that it allows the controlled and restricted execution of code: >>> src = ''' ... def hello_world(): ... return "Hello World!" ... ''' >>> from RestrictedPython import compile_restricted >>> code = compile_restricted(src, '', 'exec') The resulting code can be executed using the ``exec`` built-in: >>> exec(code) As a result, the ``hello_world`` function is now available in the global namespace: >>> hello_world() 'Hello World!' Compatibility ============= This release of RestrictedPython is compatible with Python 2.3, 2.4, 2.5, 2.6, and 2.7. Implementing a policy ===================== RestrictedPython only provides the raw material for restricted execution. To actually enforce any restrictions, you need to supply a policy implementation by providing restricted versions of ``print``, ``getattr``, ``setattr``, ``import``, etc. These restricted implementations are hooked up by providing a set of specially named objects in the global dict that you use for execution of code. Specifically: 1. ``_print_`` is a callable object that returns a handler for print statements. This handler must have a ``write()`` method that accepts a single string argument, and must return a string when called. ``RestrictedPython.PrintCollector.PrintCollector`` is a suitable implementation. 2. ``_write_`` is a guard function taking a single argument. If the object passed to it may be written to, it should be returned, otherwise the guard function should raise an exception. ``_write`` is typically called on an object before a ``setattr`` operation. 3. ``_getattr_`` and ``_getitem_`` are guard functions, each of which takes two arguments. The first is the base object to be accessed, while the second is the attribute name or item index that will be read. The guard function should return the attribute or subitem, or raise an exception. 4. ``__import__`` is the normal Python import hook, and should be used to control access to Python packages and modules. 5. ``__builtins__`` is the normal Python builtins dictionary, which should be weeded down to a set that cannot be used to get around your restrictions. A usable "safe" set is ``RestrictedPython.Guards.safe_builtins``. To help illustrate how this works under the covers, here's an example function:: def f(x): x.foo = x.foo + x[0] print x return printed and (sort of) how it looks after restricted compilation:: def f(x): # Make local variables from globals. _print = _print_() _write = _write_ _getattr = _getattr_ _getitem = _getitem_ # Translation of f(x) above _write(x).foo = _getattr(x, 'foo') + _getitem(x, 0) print >>_print, x return _print() Examples ======== ``print`` --------- To support the ``print`` statement in restricted code, we supply a ``_print_`` object (note that it's a *factory*, e.g. a class or a callable, from which the restricted machinery will create the object): >>> from RestrictedPython.PrintCollector import PrintCollector >>> _print_ = PrintCollector >>> src = ''' ... print "Hello World!" ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) As you can see, the text doesn't appear on stdout. The print collector collects it. We can have access to the text using the ``printed`` variable, though: >>> src = ''' ... print "Hello World!" ... result = printed ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) >>> result 'Hello World!\n' Built-ins --------- By supplying a different ``__builtins__`` dictionary, we can rule out unsafe operations, such as opening files: >>> from RestrictedPython.Guards import safe_builtins >>> restricted_globals = dict(__builtins__ = safe_builtins) >>> src = ''' ... open('/etc/passwd') ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) in restricted_globals Traceback (most recent call last): ... NameError: name 'open' is not defined Guards ------ Here's an example of a write guard that never lets restricted code modify (assign, delete an attribute or item) except dictionaries and lists: >>> from RestrictedPython.Guards import full_write_guard >>> _write_ = full_write_guard >>> _getattr_ = getattr >>> class BikeShed(object): ... colour = 'green' ... >>> shed = BikeShed() Normally accessing attriutes works as expected, because we're using the standard ``getattr`` function for the ``_getattr_`` guard: >>> src = ''' ... print shed.colour ... result = printed ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) >>> result 'green\n' However, changing an attribute doesn't work: >>> src = ''' ... shed.colour = 'red' ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) Traceback (most recent call last): ... TypeError: attribute-less object (assign or del) As said, this particular write guard (``full_write_guard``) will allow restricted code to modify lists and dictionaries: >>> fibonacci = [1, 1, 2, 3, 4] >>> transl = dict(one=1, two=2, tres=3) >>> src = ''' ... # correct mistake in list ... fibonacci[-1] = 5 ... # one item doesn't belong ... del transl['tres'] ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) >>> fibonacci [1, 1, 2, 3, 5] >>> sorted(transl.keys()) ['one', 'two'] Changes ======= 3.6.0 (2010-07-09) ------------------ - Added name check for names assigned during imports using the "from x import y" format. - Added test for name check when assigning an alias using multiple-context with statements in Python 2.7. - Added tests for protection of the iterators for dict and set comprehensions in Python 2.7. 3.6.0a1 (2010-06-05) -------------------- - Removed support for DocumentTemplate.sequence - this is handled in the DocumentTemplate package itself. 3.5.2 (2010-04-30) ------------------ - Removed a testing dependency on zope.testing. 3.5.1 (2009-03-17) ------------------ - Added tests for ``Utilities`` module. - Filtered DeprecationWarnings when importing Python's ``sets`` module. 3.5.0 (2009-02-09) ------------------ - Dropped legacy support for Python 2.1 / 2.2 (``__future__`` imports of ``nested_scopes`` / ``generators``.). 3.4.3 (2008-10-26) ------------------ - Fixed deprecation warning: ``with`` is now a reserved keyword on Python 2.6. That means RestrictedPython should run on Python 2.6 now. Thanks to Ranjith Kannikara, GSoC Student for the patch. - Added tests for ternary if expression and for 'with' keyword and context managers. 3.4.2 (2007-07-28) ------------------ - Changed homepage URL to the CheeseShop site - Greatly improved README.txt 3.4.1 (2007-06-23) ------------------ - Fixed http://www.zope.org/Collectors/Zope/2295: Bare conditional in a Zope 2 PythonScript followed by a comment causes SyntaxError. 3.4.0 (2007-06-04) ------------------ - RestrictedPython now has its own release cycle as a separate egg. - Synchronized with RestrictedPython from Zope 2 tree. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the RestrictedPython package shipped as part of the Zope 3.2.0 release. - No changes from 3.1.0. 3.1.0 (2005-10-03) ------------------ - Corresponds to the verison of the RestrictedPython package shipped as part of the Zope 3.1.0 release. - Removed unused fossil module, ``SafeMapping``. - Replaced use of deprecated 'whrandom' module with 'random' (aliased to 'whrandom' for backward compatibility). 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the RestrictedPython package shipped as part of the Zope X3.0.0 release. Platform: UNKNOWN zope2.13-2.13.21/source/RestrictedPython/pip-egg-info/RestrictedPython.egg-info/dependency_links.txt0000644000175000017500000000000112214017460032301 0ustar arnauarnau zope2.13-2.13.21/source/RestrictedPython/pip-egg-info/RestrictedPython.egg-info/requires.txt0000644000175000017500000000001212214017460030624 0ustar arnauarnausetuptoolszope2.13-2.13.21/source/RestrictedPython/pip-egg-info/RestrictedPython.egg-info/top_level.txt0000644000175000017500000000002112214017460030756 0ustar arnauarnauRestrictedPython zope2.13-2.13.21/source/RestrictedPython/pip-egg-info/RestrictedPython.egg-info/SOURCES.txt0000644000175000017500000000272212214017460030122 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/RestrictedPython.egg-info/PKG-INFO pip-egg-info/RestrictedPython.egg-info/SOURCES.txt pip-egg-info/RestrictedPython.egg-info/dependency_links.txt pip-egg-info/RestrictedPython.egg-info/not-zip-safe pip-egg-info/RestrictedPython.egg-info/requires.txt pip-egg-info/RestrictedPython.egg-info/top_level.txt src/RestrictedPython/Eval.py src/RestrictedPython/Guards.py src/RestrictedPython/Limits.py src/RestrictedPython/MutatingWalker.py src/RestrictedPython/PrintCollector.py src/RestrictedPython/RCompile.py src/RestrictedPython/RestrictionMutator.py src/RestrictedPython/SelectCompiler.py src/RestrictedPython/Utilities.py src/RestrictedPython/__init__.py src/RestrictedPython/tests/__init__.py src/RestrictedPython/tests/before_and_after.py src/RestrictedPython/tests/before_and_after24.py src/RestrictedPython/tests/before_and_after25.py src/RestrictedPython/tests/before_and_after26.py src/RestrictedPython/tests/before_and_after27.py src/RestrictedPython/tests/class.py src/RestrictedPython/tests/lambda.py src/RestrictedPython/tests/restricted_module.py src/RestrictedPython/tests/security_in_syntax.py src/RestrictedPython/tests/security_in_syntax26.py src/RestrictedPython/tests/security_in_syntax27.py src/RestrictedPython/tests/testCompile.py src/RestrictedPython/tests/testREADME.py src/RestrictedPython/tests/testRestrictions.py src/RestrictedPython/tests/testUtiliities.py src/RestrictedPython/tests/unpack.py src/RestrictedPython/tests/verify.pyzope2.13-2.13.21/source/RestrictedPython/pip-egg-info/RestrictedPython.egg-info/not-zip-safe0000644000175000017500000000000112214017460030461 0ustar arnauarnau zope2.13-2.13.21/source/RestrictedPython/LICENSE.txt0000644000175000017500000000402612214017460020573 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/RestrictedPython/README.txt0000644000175000017500000000006112214017460020441 0ustar arnauarnauPlease refer to src/RestrictedPython/README.txt. zope2.13-2.13.21/source/RestrictedPython/setup.cfg0000644000175000017500000000007312214017460020567 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/RestrictedPython/COPYRIGHT.txt0000644000175000017500000000004012214017460021051 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/RestrictedPython/buildout.cfg0000644000175000017500000000030112214017460021250 0ustar arnauarnau[buildout] develop = . parts = interpreter test [interpreter] recipe = zc.recipe.egg interpreter = python eggs = RestrictedPython [test] recipe = zc.recipe.testrunner eggs = RestrictedPython zope2.13-2.13.21/source/RestrictedPython/bootstrap.py0000644000175000017500000000733012214017460021340 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/RestrictedPython/CHANGES.txt0000644000175000017500000000441012214017460020556 0ustar arnauarnauChanges ======= 3.6.0 (2010-07-09) ------------------ - Added name check for names assigned during imports using the "from x import y" format. - Added test for name check when assigning an alias using multiple-context with statements in Python 2.7. - Added tests for protection of the iterators for dict and set comprehensions in Python 2.7. 3.6.0a1 (2010-06-05) -------------------- - Removed support for DocumentTemplate.sequence - this is handled in the DocumentTemplate package itself. 3.5.2 (2010-04-30) ------------------ - Removed a testing dependency on zope.testing. 3.5.1 (2009-03-17) ------------------ - Added tests for ``Utilities`` module. - Filtered DeprecationWarnings when importing Python's ``sets`` module. 3.5.0 (2009-02-09) ------------------ - Dropped legacy support for Python 2.1 / 2.2 (``__future__`` imports of ``nested_scopes`` / ``generators``.). 3.4.3 (2008-10-26) ------------------ - Fixed deprecation warning: ``with`` is now a reserved keyword on Python 2.6. That means RestrictedPython should run on Python 2.6 now. Thanks to Ranjith Kannikara, GSoC Student for the patch. - Added tests for ternary if expression and for 'with' keyword and context managers. 3.4.2 (2007-07-28) ------------------ - Changed homepage URL to the CheeseShop site - Greatly improved README.txt 3.4.1 (2007-06-23) ------------------ - Fixed http://www.zope.org/Collectors/Zope/2295: Bare conditional in a Zope 2 PythonScript followed by a comment causes SyntaxError. 3.4.0 (2007-06-04) ------------------ - RestrictedPython now has its own release cycle as a separate egg. - Synchronized with RestrictedPython from Zope 2 tree. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the RestrictedPython package shipped as part of the Zope 3.2.0 release. - No changes from 3.1.0. 3.1.0 (2005-10-03) ------------------ - Corresponds to the verison of the RestrictedPython package shipped as part of the Zope 3.1.0 release. - Removed unused fossil module, ``SafeMapping``. - Replaced use of deprecated 'whrandom' module with 'random' (aliased to 'whrandom' for backward compatibility). 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the RestrictedPython package shipped as part of the Zope X3.0.0 release. zope2.13-2.13.21/source/RestrictedPython/src/0000755000175000017500000000000012214017460017535 5ustar arnauarnauzope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython.egg-info/0000755000175000017500000000000012214017460024541 5ustar arnauarnauzope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython.egg-info/PKG-INFO0000644000175000017500000002466412214017460025652 0ustar arnauarnauMetadata-Version: 1.0 Name: RestrictedPython Version: 3.6.0 Summary: RestrictedPython provides a restricted execution environment for Python, e.g. for running untrusted code. Home-page: http://pypi.python.org/pypi/RestrictedPython Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: .. contents:: Overview ======== RestrictedPython provides a ``restricted_compile`` function that works like the built-in ``compile`` function, except that it allows the controlled and restricted execution of code: >>> src = ''' ... def hello_world(): ... return "Hello World!" ... ''' >>> from RestrictedPython import compile_restricted >>> code = compile_restricted(src, '', 'exec') The resulting code can be executed using the ``exec`` built-in: >>> exec(code) As a result, the ``hello_world`` function is now available in the global namespace: >>> hello_world() 'Hello World!' Compatibility ============= This release of RestrictedPython is compatible with Python 2.3, 2.4, 2.5, 2.6, and 2.7. Implementing a policy ===================== RestrictedPython only provides the raw material for restricted execution. To actually enforce any restrictions, you need to supply a policy implementation by providing restricted versions of ``print``, ``getattr``, ``setattr``, ``import``, etc. These restricted implementations are hooked up by providing a set of specially named objects in the global dict that you use for execution of code. Specifically: 1. ``_print_`` is a callable object that returns a handler for print statements. This handler must have a ``write()`` method that accepts a single string argument, and must return a string when called. ``RestrictedPython.PrintCollector.PrintCollector`` is a suitable implementation. 2. ``_write_`` is a guard function taking a single argument. If the object passed to it may be written to, it should be returned, otherwise the guard function should raise an exception. ``_write`` is typically called on an object before a ``setattr`` operation. 3. ``_getattr_`` and ``_getitem_`` are guard functions, each of which takes two arguments. The first is the base object to be accessed, while the second is the attribute name or item index that will be read. The guard function should return the attribute or subitem, or raise an exception. 4. ``__import__`` is the normal Python import hook, and should be used to control access to Python packages and modules. 5. ``__builtins__`` is the normal Python builtins dictionary, which should be weeded down to a set that cannot be used to get around your restrictions. A usable "safe" set is ``RestrictedPython.Guards.safe_builtins``. To help illustrate how this works under the covers, here's an example function:: def f(x): x.foo = x.foo + x[0] print x return printed and (sort of) how it looks after restricted compilation:: def f(x): # Make local variables from globals. _print = _print_() _write = _write_ _getattr = _getattr_ _getitem = _getitem_ # Translation of f(x) above _write(x).foo = _getattr(x, 'foo') + _getitem(x, 0) print >>_print, x return _print() Examples ======== ``print`` --------- To support the ``print`` statement in restricted code, we supply a ``_print_`` object (note that it's a *factory*, e.g. a class or a callable, from which the restricted machinery will create the object): >>> from RestrictedPython.PrintCollector import PrintCollector >>> _print_ = PrintCollector >>> src = ''' ... print "Hello World!" ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) As you can see, the text doesn't appear on stdout. The print collector collects it. We can have access to the text using the ``printed`` variable, though: >>> src = ''' ... print "Hello World!" ... result = printed ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) >>> result 'Hello World!\n' Built-ins --------- By supplying a different ``__builtins__`` dictionary, we can rule out unsafe operations, such as opening files: >>> from RestrictedPython.Guards import safe_builtins >>> restricted_globals = dict(__builtins__ = safe_builtins) >>> src = ''' ... open('/etc/passwd') ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) in restricted_globals Traceback (most recent call last): ... NameError: name 'open' is not defined Guards ------ Here's an example of a write guard that never lets restricted code modify (assign, delete an attribute or item) except dictionaries and lists: >>> from RestrictedPython.Guards import full_write_guard >>> _write_ = full_write_guard >>> _getattr_ = getattr >>> class BikeShed(object): ... colour = 'green' ... >>> shed = BikeShed() Normally accessing attriutes works as expected, because we're using the standard ``getattr`` function for the ``_getattr_`` guard: >>> src = ''' ... print shed.colour ... result = printed ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) >>> result 'green\n' However, changing an attribute doesn't work: >>> src = ''' ... shed.colour = 'red' ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) Traceback (most recent call last): ... TypeError: attribute-less object (assign or del) As said, this particular write guard (``full_write_guard``) will allow restricted code to modify lists and dictionaries: >>> fibonacci = [1, 1, 2, 3, 4] >>> transl = dict(one=1, two=2, tres=3) >>> src = ''' ... # correct mistake in list ... fibonacci[-1] = 5 ... # one item doesn't belong ... del transl['tres'] ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) >>> fibonacci [1, 1, 2, 3, 5] >>> sorted(transl.keys()) ['one', 'two'] Changes ======= 3.6.0 (2010-07-09) ------------------ - Added name check for names assigned during imports using the "from x import y" format. - Added test for name check when assigning an alias using multiple-context with statements in Python 2.7. - Added tests for protection of the iterators for dict and set comprehensions in Python 2.7. 3.6.0a1 (2010-06-05) -------------------- - Removed support for DocumentTemplate.sequence - this is handled in the DocumentTemplate package itself. 3.5.2 (2010-04-30) ------------------ - Removed a testing dependency on zope.testing. 3.5.1 (2009-03-17) ------------------ - Added tests for ``Utilities`` module. - Filtered DeprecationWarnings when importing Python's ``sets`` module. 3.5.0 (2009-02-09) ------------------ - Dropped legacy support for Python 2.1 / 2.2 (``__future__`` imports of ``nested_scopes`` / ``generators``.). 3.4.3 (2008-10-26) ------------------ - Fixed deprecation warning: ``with`` is now a reserved keyword on Python 2.6. That means RestrictedPython should run on Python 2.6 now. Thanks to Ranjith Kannikara, GSoC Student for the patch. - Added tests for ternary if expression and for 'with' keyword and context managers. 3.4.2 (2007-07-28) ------------------ - Changed homepage URL to the CheeseShop site - Greatly improved README.txt 3.4.1 (2007-06-23) ------------------ - Fixed http://www.zope.org/Collectors/Zope/2295: Bare conditional in a Zope 2 PythonScript followed by a comment causes SyntaxError. 3.4.0 (2007-06-04) ------------------ - RestrictedPython now has its own release cycle as a separate egg. - Synchronized with RestrictedPython from Zope 2 tree. 3.2.0 (2006-01-05) ------------------ - Corresponds to the verison of the RestrictedPython package shipped as part of the Zope 3.2.0 release. - No changes from 3.1.0. 3.1.0 (2005-10-03) ------------------ - Corresponds to the verison of the RestrictedPython package shipped as part of the Zope 3.1.0 release. - Removed unused fossil module, ``SafeMapping``. - Replaced use of deprecated 'whrandom' module with 'random' (aliased to 'whrandom' for backward compatibility). 3.0.0 (2004-11-07) ------------------ - Corresponds to the verison of the RestrictedPython package shipped as part of the Zope X3.0.0 release. Platform: UNKNOWN zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython.egg-info/dependency_links.txt0000644000175000017500000000000112214017460030607 0ustar arnauarnau zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython.egg-info/requires.txt0000644000175000017500000000001212214017460027132 0ustar arnauarnausetuptoolszope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython.egg-info/top_level.txt0000644000175000017500000000002112214017460027264 0ustar arnauarnauRestrictedPython zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython.egg-info/SOURCES.txt0000644000175000017500000000303212214017460026423 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/RestrictedPython/Eval.py src/RestrictedPython/Guards.py src/RestrictedPython/Limits.py src/RestrictedPython/MutatingWalker.py src/RestrictedPython/PrintCollector.py src/RestrictedPython/RCompile.py src/RestrictedPython/README.txt src/RestrictedPython/RestrictionMutator.py src/RestrictedPython/SelectCompiler.py src/RestrictedPython/Utilities.py src/RestrictedPython/__init__.py src/RestrictedPython/notes.txt src/RestrictedPython.egg-info/PKG-INFO src/RestrictedPython.egg-info/SOURCES.txt src/RestrictedPython.egg-info/dependency_links.txt src/RestrictedPython.egg-info/not-zip-safe src/RestrictedPython.egg-info/requires.txt src/RestrictedPython.egg-info/top_level.txt src/RestrictedPython/tests/__init__.py src/RestrictedPython/tests/before_and_after.py src/RestrictedPython/tests/before_and_after24.py src/RestrictedPython/tests/before_and_after25.py src/RestrictedPython/tests/before_and_after26.py src/RestrictedPython/tests/before_and_after27.py src/RestrictedPython/tests/class.py src/RestrictedPython/tests/lambda.py src/RestrictedPython/tests/restricted_module.py src/RestrictedPython/tests/security_in_syntax.py src/RestrictedPython/tests/security_in_syntax26.py src/RestrictedPython/tests/security_in_syntax27.py src/RestrictedPython/tests/testCompile.py src/RestrictedPython/tests/testREADME.py src/RestrictedPython/tests/testRestrictions.py src/RestrictedPython/tests/testUtiliities.py src/RestrictedPython/tests/unpack.py src/RestrictedPython/tests/verify.pyzope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython.egg-info/not-zip-safe0000644000175000017500000000000112214017460026767 0ustar arnauarnau zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/0000755000175000017500000000000012214017460023047 5ustar arnauarnauzope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/PrintCollector.py0000644000175000017500000000157512214017460026374 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## __version__='$Revision: 1.4 $'[11:-2] class PrintCollector: '''Collect written text, and return it when called.''' def __init__(self): self.txt = [] def write(self, text): self.txt.append(text) def __call__(self): return ''.join(self.txt) zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/Limits.py0000644000175000017500000000324012214017460024661 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## __version__='$Revision: 1.5 $'[11:-2] limited_builtins = {} def limited_range(iFirst, *args): # limited range function from Martijn Pieters RANGELIMIT = 1000 if not len(args): iStart, iEnd, iStep = 0, iFirst, 1 elif len(args) == 1: iStart, iEnd, iStep = iFirst, args[0], 1 elif len(args) == 2: iStart, iEnd, iStep = iFirst, args[0], args[1] else: raise AttributeError, 'range() requires 1-3 int arguments' if iStep == 0: raise ValueError, 'zero step for range()' iLen = int((iEnd - iStart) / iStep) if iLen < 0: iLen = 0 if iLen >= RANGELIMIT: raise ValueError, 'range() too large' return range(iStart, iEnd, iStep) limited_builtins['range'] = limited_range def limited_list(seq): if isinstance(seq, str): raise TypeError, 'cannot convert string to list' return list(seq) limited_builtins['list'] = limited_list def limited_tuple(seq): if isinstance(seq, str): raise TypeError, 'cannot convert string to tuple' return tuple(seq) limited_builtins['tuple'] = limited_tuple zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/Eval.py0000644000175000017500000000734512214017460024321 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Restricted Python Expressions """ __version__='$Revision: 1.6 $'[11:-2] from RestrictedPython import compile_restricted_eval from string import translate, strip import string nltosp = string.maketrans('\r\n',' ') default_guarded_getattr = getattr # No restrictions. def default_guarded_getitem(ob, index): # No restrictions. return ob[index] PROFILE = 0 class RestrictionCapableEval: """A base class for restricted code.""" globals = {'__builtins__': None} rcode = None # restricted ucode = None # unrestricted used = None def __init__(self, expr): """Create a restricted expression where: expr -- a string containing the expression to be evaluated. """ expr = strip(expr) self.__name__ = expr expr = translate(expr, nltosp) self.expr = expr self.prepUnrestrictedCode() # Catch syntax errors. def prepRestrictedCode(self): if self.rcode is None: if PROFILE: from time import clock start = clock() co, err, warn, used = compile_restricted_eval( self.expr, '') if PROFILE: end = clock() print 'prepRestrictedCode: %d ms for %s' % ( (end - start) * 1000, `self.expr`) if err: raise SyntaxError, err[0] self.used = tuple(used.keys()) self.rcode = co def prepUnrestrictedCode(self): if self.ucode is None: # Use the standard compiler. co = compile(self.expr, '', 'eval') if self.used is None: # Examine the code object, discovering which names # the expression needs. names=list(co.co_names) used={} i=0 code=co.co_code l=len(code) LOAD_NAME=101 HAVE_ARGUMENT=90 while(i < l): c=ord(code[i]) if c==LOAD_NAME: name=names[ord(code[i+1])+256*ord(code[i+2])] used[name]=1 i=i+3 elif c >= HAVE_ARGUMENT: i=i+3 else: i=i+1 self.used=tuple(used.keys()) self.ucode=co def eval(self, mapping): # This default implementation is probably not very useful. :-( # This is meant to be overridden. self.prepRestrictedCode() code = self.rcode d = {'_getattr_': default_guarded_getattr, '_getitem_': default_guarded_getitem} d.update(self.globals) has_key = d.has_key for name in self.used: try: if not has_key(name): d[name] = mapping[name] except KeyError: # Swallow KeyErrors since the expression # might not actually need the name. If it # does need the name, a NameError will occur. pass return eval(code, d) def __call__(self, **kw): return self.eval(kw) zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/MutatingWalker.py0000644000175000017500000000460412214017460026363 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## __version__='$Revision: 1.6 $'[11:-2] from SelectCompiler import ast ListType = type([]) TupleType = type(()) SequenceTypes = (ListType, TupleType) class MutatingWalker: def __init__(self, visitor): self.visitor = visitor self._cache = {} def defaultVisitNode(self, node, walker=None, exclude=None): for name, child in node.__dict__.items(): if exclude is not None and name in exclude: continue v = self.dispatchObject(child) if v is not child: # Replace the node. node.__dict__[name] = v return node def visitSequence(self, seq): res = seq for idx in range(len(seq)): child = seq[idx] v = self.dispatchObject(child) if v is not child: # Change the sequence. if type(res) is ListType: res[idx : idx + 1] = [v] else: res = res[:idx] + (v,) + res[idx + 1:] return res def dispatchObject(self, ob): ''' Expected to return either ob or something that will take its place. ''' if isinstance(ob, ast.Node): return self.dispatchNode(ob) elif type(ob) in SequenceTypes: return self.visitSequence(ob) else: return ob def dispatchNode(self, node): klass = node.__class__ meth = self._cache.get(klass, None) if meth is None: className = klass.__name__ meth = getattr(self.visitor, 'visit' + className, self.defaultVisitNode) self._cache[klass] = meth return meth(node, self) def walk(tree, visitor): return MutatingWalker(visitor).dispatchNode(tree) zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/Utilities.py0000644000175000017500000000522412214017460025377 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## __version__='$Revision: 1.7 $'[11:-2] import math import random import string import warnings _old_filters = warnings.filters[:] warnings.filterwarnings('ignore', category=DeprecationWarning) try: try: import sets except ImportError: sets = None finally: warnings.filters[:] = _old_filters utility_builtins = {} utility_builtins['string'] = string utility_builtins['math'] = math utility_builtins['random'] = random utility_builtins['whrandom'] = random utility_builtins['sets'] = sets try: import DateTime utility_builtins['DateTime']= DateTime.DateTime except ImportError: pass def same_type(arg1, *args): '''Compares the class or type of two or more objects.''' t = getattr(arg1, '__class__', type(arg1)) for arg in args: if getattr(arg, '__class__', type(arg)) is not t: return 0 return 1 utility_builtins['same_type'] = same_type def test(*args): length = len(args) for i in range(1, length, 2): if args[i-1]: return args[i] if length % 2: return args[-1] utility_builtins['test'] = test def reorder(s, with_=None, without=()): # s, with_, and without are sequences treated as sets. # The result is subtract(intersect(s, with_), without), # unless with_ is None, in which case it is subtract(s, without). if with_ is None: with_ = s orig = {} for item in s: if isinstance(item, tuple) and len(item) == 2: key, value = item else: key = value = item orig[key] = value result = [] for item in without: if isinstance(item, tuple) and len(item) == 2: key, ignored = item else: key = item if key in orig: del orig[key] for item in with_: if isinstance(item, tuple) and len(item) == 2: key, ignored = item else: key = item if key in orig: result.append((key, orig[key])) del orig[key] return result utility_builtins['reorder'] = reorder zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/SelectCompiler.py0000644000175000017500000000171412214017460026336 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Compiler selector. """ # Use the compiler from the standard library. import compiler from compiler import ast from compiler.transformer import parse from compiler.consts import OP_ASSIGN, OP_DELETE, OP_APPLY from RCompile import \ compile_restricted, \ compile_restricted_function, \ compile_restricted_exec, \ compile_restricted_eval zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/__init__.py0000644000175000017500000000131612214017460025161 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## ''' RestrictedPython package. ''' from SelectCompiler import * from PrintCollector import PrintCollector zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/README.txt0000644000175000017500000001304212214017460024545 0ustar arnauarnau.. contents:: Overview ======== RestrictedPython provides a ``restricted_compile`` function that works like the built-in ``compile`` function, except that it allows the controlled and restricted execution of code: >>> src = ''' ... def hello_world(): ... return "Hello World!" ... ''' >>> from RestrictedPython import compile_restricted >>> code = compile_restricted(src, '', 'exec') The resulting code can be executed using the ``exec`` built-in: >>> exec(code) As a result, the ``hello_world`` function is now available in the global namespace: >>> hello_world() 'Hello World!' Compatibility ============= This release of RestrictedPython is compatible with Python 2.3, 2.4, 2.5, 2.6, and 2.7. Implementing a policy ===================== RestrictedPython only provides the raw material for restricted execution. To actually enforce any restrictions, you need to supply a policy implementation by providing restricted versions of ``print``, ``getattr``, ``setattr``, ``import``, etc. These restricted implementations are hooked up by providing a set of specially named objects in the global dict that you use for execution of code. Specifically: 1. ``_print_`` is a callable object that returns a handler for print statements. This handler must have a ``write()`` method that accepts a single string argument, and must return a string when called. ``RestrictedPython.PrintCollector.PrintCollector`` is a suitable implementation. 2. ``_write_`` is a guard function taking a single argument. If the object passed to it may be written to, it should be returned, otherwise the guard function should raise an exception. ``_write`` is typically called on an object before a ``setattr`` operation. 3. ``_getattr_`` and ``_getitem_`` are guard functions, each of which takes two arguments. The first is the base object to be accessed, while the second is the attribute name or item index that will be read. The guard function should return the attribute or subitem, or raise an exception. 4. ``__import__`` is the normal Python import hook, and should be used to control access to Python packages and modules. 5. ``__builtins__`` is the normal Python builtins dictionary, which should be weeded down to a set that cannot be used to get around your restrictions. A usable "safe" set is ``RestrictedPython.Guards.safe_builtins``. To help illustrate how this works under the covers, here's an example function:: def f(x): x.foo = x.foo + x[0] print x return printed and (sort of) how it looks after restricted compilation:: def f(x): # Make local variables from globals. _print = _print_() _write = _write_ _getattr = _getattr_ _getitem = _getitem_ # Translation of f(x) above _write(x).foo = _getattr(x, 'foo') + _getitem(x, 0) print >>_print, x return _print() Examples ======== ``print`` --------- To support the ``print`` statement in restricted code, we supply a ``_print_`` object (note that it's a *factory*, e.g. a class or a callable, from which the restricted machinery will create the object): >>> from RestrictedPython.PrintCollector import PrintCollector >>> _print_ = PrintCollector >>> src = ''' ... print "Hello World!" ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) As you can see, the text doesn't appear on stdout. The print collector collects it. We can have access to the text using the ``printed`` variable, though: >>> src = ''' ... print "Hello World!" ... result = printed ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) >>> result 'Hello World!\n' Built-ins --------- By supplying a different ``__builtins__`` dictionary, we can rule out unsafe operations, such as opening files: >>> from RestrictedPython.Guards import safe_builtins >>> restricted_globals = dict(__builtins__ = safe_builtins) >>> src = ''' ... open('/etc/passwd') ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) in restricted_globals Traceback (most recent call last): ... NameError: name 'open' is not defined Guards ------ Here's an example of a write guard that never lets restricted code modify (assign, delete an attribute or item) except dictionaries and lists: >>> from RestrictedPython.Guards import full_write_guard >>> _write_ = full_write_guard >>> _getattr_ = getattr >>> class BikeShed(object): ... colour = 'green' ... >>> shed = BikeShed() Normally accessing attriutes works as expected, because we're using the standard ``getattr`` function for the ``_getattr_`` guard: >>> src = ''' ... print shed.colour ... result = printed ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) >>> result 'green\n' However, changing an attribute doesn't work: >>> src = ''' ... shed.colour = 'red' ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) Traceback (most recent call last): ... TypeError: attribute-less object (assign or del) As said, this particular write guard (``full_write_guard``) will allow restricted code to modify lists and dictionaries: >>> fibonacci = [1, 1, 2, 3, 4] >>> transl = dict(one=1, two=2, tres=3) >>> src = ''' ... # correct mistake in list ... fibonacci[-1] = 5 ... # one item doesn't belong ... del transl['tres'] ... ''' >>> code = compile_restricted(src, '', 'exec') >>> exec(code) >>> fibonacci [1, 1, 2, 3, 5] >>> sorted(transl.keys()) ['one', 'two'] zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/RestrictionMutator.py0000644000175000017500000003566312214017460027317 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Modify AST to include security checks. RestrictionMutator modifies a tree produced by compiler.transformer.Transformer, restricting and enhancing the code in various ways before sending it to pycodegen. $Revision: 1.13 $ """ from SelectCompiler import ast, parse, OP_ASSIGN, OP_DELETE, OP_APPLY # These utility functions allow us to generate AST subtrees without # line number attributes. These trees can then be inserted into other # trees without affecting line numbers shown in tracebacks, etc. def rmLineno(node): """Strip lineno attributes from a code tree.""" if node.__dict__.has_key('lineno'): del node.lineno for child in node.getChildren(): if isinstance(child, ast.Node): rmLineno(child) def stmtNode(txt): """Make a "clean" statement node.""" node = parse(txt).node.nodes[0] rmLineno(node) return node # The security checks are performed by a set of six functions that # must be provided by the restricted environment. _apply_name = ast.Name("_apply_") _getattr_name = ast.Name("_getattr_") _getitem_name = ast.Name("_getitem_") _getiter_name = ast.Name("_getiter_") _print_target_name = ast.Name("_print") _write_name = ast.Name("_write_") _inplacevar_name = ast.Name("_inplacevar_") # Constants. _None_const = ast.Const(None) _write_const = ast.Const("write") _printed_expr = stmtNode("_print()").expr _print_target_node = stmtNode("_print = _print_()") class FuncInfo: print_used = False printed_used = False class RestrictionMutator: def __init__(self): self.warnings = [] self.errors = [] self.used_names = {} self.funcinfo = FuncInfo() def error(self, node, info): """Records a security error discovered during compilation.""" lineno = getattr(node, 'lineno', None) if lineno is not None and lineno > 0: self.errors.append('Line %d: %s' % (lineno, info)) else: self.errors.append(info) def checkName(self, node, name): """Verifies that a name being assigned is safe. This is to prevent people from doing things like: __metatype__ = mytype (opens up metaclasses, a big unknown in terms of security) __path__ = foo (could this confuse the import machinery?) _getattr = somefunc (not very useful, but could open a hole) Note that assigning a variable is not the only way to assign a name. def _badname, class _badname, import foo as _badname, and perhaps other statements assign names. Special case: '_' is allowed. """ if name.startswith("_") and name != "_": # Note: "_" *is* allowed. self.error(node, '"%s" is an invalid variable name because' ' it starts with "_"' % name) if name.endswith('__roles__'): self.error(node, '"%s" is an invalid variable name because ' 'it ends with "__roles__".' % name) if name == "printed": self.error(node, '"printed" is a reserved name.') def checkAttrName(self, node): """Verifies that an attribute name does not start with _. As long as guards (security proxies) have underscored names, this underscore protection is important regardless of the security policy. Special case: '_' is allowed. """ name = node.attrname if name.startswith("_") and name != "_": # Note: "_" *is* allowed. self.error(node, '"%s" is an invalid attribute name ' 'because it starts with "_".' % name) if name.endswith('__roles__'): self.error(node, '"%s" is an invalid attribute name ' 'because it ends with "__roles__".' % name) def prepBody(self, body): """Insert code for print at the beginning of the code suite.""" if self.funcinfo.print_used or self.funcinfo.printed_used: # Add code at top for creating _print_target body.insert(0, _print_target_node) if not self.funcinfo.printed_used: self.warnings.append( "Prints, but never reads 'printed' variable.") elif not self.funcinfo.print_used: self.warnings.append( "Doesn't print, but reads 'printed' variable.") def visitFunction(self, node, walker): """Checks and mutates a function definition. Checks the name of the function and the argument names using checkName(). It also calls prepBody() to prepend code to the beginning of the code suite. """ self.checkName(node, node.name) for argname in node.argnames: if isinstance(argname, str): self.checkName(node, argname) else: for name in argname: self.checkName(node, name) walker.visitSequence(node.defaults) former_funcinfo = self.funcinfo self.funcinfo = FuncInfo() node = walker.defaultVisitNode(node, exclude=('defaults',)) self.prepBody(node.code.nodes) self.funcinfo = former_funcinfo return node def visitLambda(self, node, walker): """Checks and mutates an anonymous function definition. Checks the argument names using checkName(). It also calls prepBody() to prepend code to the beginning of the code suite. """ for argname in node.argnames: self.checkName(node, argname) return walker.defaultVisitNode(node) def visitPrint(self, node, walker): """Checks and mutates a print statement. Adds a target to all print statements. 'print foo' becomes 'print >> _print, foo', where _print is the default print target defined for this scope. Alternatively, if the untrusted code provides its own target, we have to check the 'write' method of the target. 'print >> ob, foo' becomes 'print >> (_getattr(ob, 'write') and ob), foo'. Otherwise, it would be possible to call the write method of templates and scripts; 'write' happens to be the name of the method that changes them. """ node = walker.defaultVisitNode(node) self.funcinfo.print_used = True if node.dest is None: node.dest = _print_target_name else: # Pre-validate access to the "write" attribute. # "print >> ob, x" becomes # "print >> (_getattr(ob, 'write') and ob), x" node.dest = ast.And([ ast.CallFunc(_getattr_name, [node.dest, _write_const]), node.dest]) return node visitPrintnl = visitPrint def visitName(self, node, walker): """Prevents access to protected names as defined by checkName(). Also converts use of the name 'printed' to an expression. """ if node.name == 'printed': # Replace name lookup with an expression. self.funcinfo.printed_used = True return _printed_expr self.checkName(node, node.name) self.used_names[node.name] = True return node def visitCallFunc(self, node, walker): """Checks calls with *-args and **-args. That's a way of spelling apply(), and needs to use our safe _apply_ instead. """ walked = walker.defaultVisitNode(node) if node.star_args is None and node.dstar_args is None: # This is not an extended function call return walked # Otherwise transform foo(a, b, c, d=e, f=g, *args, **kws) into a call # of _apply_(foo, a, b, c, d=e, f=g, *args, **kws). The interesting # thing here is that _apply_() is defined with just *args and **kws, # so it gets Python to collapse all the myriad ways to call functions # into one manageable form. # # From there, _apply_() digs out the first argument of *args (it's the # function to call), wraps args and kws in guarded accessors, then # calls the function, returning the value. # Transform foo(...) to _apply(foo, ...) walked.args.insert(0, walked.node) walked.node = _apply_name return walked def visitAssName(self, node, walker): """Checks a name assignment using checkName().""" self.checkName(node, node.name) return node def visitFor(self, node, walker): # convert # for x in expr: # to # for x in _getiter(expr): # # Note that visitListCompFor is the same thing. # # Also for list comprehensions: # [... for x in expr ...] # to # [... for x in _getiter(expr) ...] node = walker.defaultVisitNode(node) node.list = ast.CallFunc(_getiter_name, [node.list]) return node visitListCompFor = visitFor def visitGenExprFor(self, node, walker): # convert # (... for x in expr ...) # to # (... for x in _getiter(expr) ...) node = walker.defaultVisitNode(node) node.iter = ast.CallFunc(_getiter_name, [node.iter]) return node def visitGetattr(self, node, walker): """Converts attribute access to a function call. 'foo.bar' becomes '_getattr(foo, "bar")'. Also prevents augmented assignment of attributes, which would be difficult to support correctly. """ self.checkAttrName(node) node = walker.defaultVisitNode(node) if getattr(node, 'in_aug_assign', False): # We're in an augmented assignment # We might support this later... self.error(node, 'Augmented assignment of ' 'attributes is not allowed.') return ast.CallFunc(_getattr_name, [node.expr, ast.Const(node.attrname)]) def visitSubscript(self, node, walker): """Checks all kinds of subscripts. 'foo[bar] += baz' is disallowed. 'a = foo[bar, baz]' becomes 'a = _getitem(foo, (bar, baz))'. 'a = foo[bar]' becomes 'a = _getitem(foo, bar)'. 'a = foo[bar:baz]' becomes 'a = _getitem(foo, slice(bar, baz))'. 'a = foo[:baz]' becomes 'a = _getitem(foo, slice(None, baz))'. 'a = foo[bar:]' becomes 'a = _getitem(foo, slice(bar, None))'. 'del foo[bar]' becomes 'del _write(foo)[bar]'. 'foo[bar] = a' becomes '_write(foo)[bar] = a'. The _write function returns a security proxy. """ node = walker.defaultVisitNode(node) if node.flags == OP_APPLY: # Set 'subs' to the node that represents the subscript or slice. if getattr(node, 'in_aug_assign', False): # We're in an augmented assignment # We might support this later... self.error(node, 'Augmented assignment of ' 'object items and slices is not allowed.') if hasattr(node, 'subs'): # Subscript. subs = node.subs if len(subs) > 1: # example: ob[1,2] subs = ast.Tuple(subs) else: # example: ob[1] subs = subs[0] else: # Slice. # example: obj[0:2] lower = node.lower if lower is None: lower = _None_const upper = node.upper if upper is None: upper = _None_const subs = ast.Sliceobj([lower, upper]) return ast.CallFunc(_getitem_name, [node.expr, subs]) elif node.flags in (OP_DELETE, OP_ASSIGN): # set or remove subscript or slice node.expr = ast.CallFunc(_write_name, [node.expr]) return node visitSlice = visitSubscript def visitAssAttr(self, node, walker): """Checks and mutates attribute assignment. 'a.b = c' becomes '_write(a).b = c'. The _write function returns a security proxy. """ self.checkAttrName(node) node = walker.defaultVisitNode(node) node.expr = ast.CallFunc(_write_name, [node.expr]) return node def visitExec(self, node, walker): self.error(node, 'Exec statements are not allowed.') def visitYield(self, node, walker): self.error(node, 'Yield statements are not allowed.') def visitClass(self, node, walker): """Checks the name of a class using checkName(). Should classes be allowed at all? They don't cause security issues, but they aren't very useful either since untrusted code can't assign instance attributes. """ self.checkName(node, node.name) return walker.defaultVisitNode(node) def visitModule(self, node, walker): """Adds prep code at module scope. Zope doesn't make use of this. The body of Python scripts is always at function scope. """ node = walker.defaultVisitNode(node) self.prepBody(node.node.nodes) return node def visitAugAssign(self, node, walker): """Makes a note that augmented assignment is in use. Note that although augmented assignment of attributes and subscripts is disallowed, augmented assignment of names (such as 'n += 1') is allowed. This could be a problem if untrusted code got access to a mutable database object that supports augmented assignment. """ if node.node.__class__.__name__ == 'Name': node = walker.defaultVisitNode(node) newnode = ast.Assign( [ast.AssName(node.node.name, OP_ASSIGN)], ast.CallFunc( _inplacevar_name, [ast.Const(node.op), ast.Name(node.node.name), node.expr, ] ), ) newnode.lineno = node.lineno return newnode else: node.node.in_aug_assign = True return walker.defaultVisitNode(node) def visitImport(self, node, walker): """Checks names imported using checkName().""" for name, asname in node.names: self.checkName(node, name) if asname: self.checkName(node, asname) return node visitFrom = visitImport zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/RCompile.py0000644000175000017500000002156512214017460025144 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Compiles restricted code using the compiler module from the Python standard library. """ __version__='$Revision: 1.6 $'[11:-2] from compiler import ast, parse, misc, syntax, pycodegen from compiler.pycodegen import AbstractCompileMode, Expression, \ Interactive, Module, ModuleCodeGenerator, FunctionCodeGenerator, findOp import MutatingWalker from RestrictionMutator import RestrictionMutator def niceParse(source, filename, mode): if isinstance(source, unicode): # Use the utf-8-sig BOM so the compiler # detects this as a UTF-8 encoded string. source = '\xef\xbb\xbf' + source.encode('utf-8') try: return parse(source, mode) except: # Try to make a clean error message using # the builtin Python compiler. try: compile(source, filename, mode) except SyntaxError: raise # Some other error occurred. raise class RestrictedCompileMode(AbstractCompileMode): """Abstract base class for hooking up custom CodeGenerator.""" # See concrete subclasses below. def __init__(self, source, filename): if source: source = '\n'.join(source.splitlines()) + '\n' self.rm = RestrictionMutator() AbstractCompileMode.__init__(self, source, filename) def parse(self): return niceParse(self.source, self.filename, self.mode) def _get_tree(self): tree = self.parse() MutatingWalker.walk(tree, self.rm) if self.rm.errors: raise SyntaxError, self.rm.errors[0] misc.set_filename(self.filename, tree) syntax.check(tree) return tree def compile(self): tree = self._get_tree() gen = self.CodeGeneratorClass(tree) self.code = gen.getCode() def compileAndTuplize(gen): try: gen.compile() except SyntaxError, v: return None, (str(v),), gen.rm.warnings, gen.rm.used_names return gen.getCode(), (), gen.rm.warnings, gen.rm.used_names def compile_restricted_function(p, body, name, filename, globalize=None): """Compiles a restricted code object for a function. The function can be reconstituted using the 'new' module: new.function(, ) The globalize argument, if specified, is a list of variable names to be treated as globals (code is generated as if each name in the list appeared in a global statement at the top of the function). """ gen = RFunction(p, body, name, filename, globalize) return compileAndTuplize(gen) def compile_restricted_exec(s, filename=''): """Compiles a restricted code suite.""" gen = RModule(s, filename) return compileAndTuplize(gen) def compile_restricted_eval(s, filename=''): """Compiles a restricted expression.""" gen = RExpression(s, filename) return compileAndTuplize(gen) def compile_restricted(source, filename, mode): """Replacement for the builtin compile() function.""" if mode == "single": gen = RInteractive(source, filename) elif mode == "exec": gen = RModule(source, filename) elif mode == "eval": gen = RExpression(source, filename) else: raise ValueError("compile_restricted() 3rd arg must be 'exec' or " "'eval' or 'single'") gen.compile() return gen.getCode() class RestrictedCodeGenerator: """Mixin for CodeGenerator to replace UNPACK_SEQUENCE bytecodes. The UNPACK_SEQUENCE opcode is not safe because it extracts elements from a sequence without using a safe iterator or making __getitem__ checks. This code generator replaces use of UNPACK_SEQUENCE with calls to a function that unpacks the sequence, performes the appropriate security checks, and returns a simple list. """ # Replace the standard code generator for assignments to tuples # and lists. def _gen_safe_unpack_sequence(self, num): # We're at a place where UNPACK_SEQUENCE should be generated, to # unpack num items. That's a security hole, since it exposes # individual items from an arbitrary iterable. We don't remove # the UNPACK_SEQUENCE, but instead insert a call to our _getiter_() # wrapper first. That applies security checks to each item as # it's delivered. codegen is (just) a bit messy because the # iterable is already on the stack, so we have to do a stack swap # to get things in the right order. self.emit('LOAD_GLOBAL', '_getiter_') self.emit('ROT_TWO') self.emit('CALL_FUNCTION', 1) self.emit('UNPACK_SEQUENCE', num) def _visitAssSequence(self, node): if findOp(node) != 'OP_DELETE': self._gen_safe_unpack_sequence(len(node.nodes)) for child in node.nodes: self.visit(child) visitAssTuple = _visitAssSequence visitAssList = _visitAssSequence # Call to generate code for unpacking nested tuple arguments # in function calls. def unpackSequence(self, tup): self._gen_safe_unpack_sequence(len(tup)) for elt in tup: if isinstance(elt, tuple): self.unpackSequence(elt) else: self._nameOp('STORE', elt) # A collection of code generators that adds the restricted mixin to # handle unpacking for all the different compilation modes. They # are defined here (at the end) so that can refer to RestrictedCodeGenerator. class RestrictedFunctionCodeGenerator(RestrictedCodeGenerator, pycodegen.FunctionCodeGenerator): pass class RestrictedExpressionCodeGenerator(RestrictedCodeGenerator, pycodegen.ExpressionCodeGenerator): pass class RestrictedInteractiveCodeGenerator(RestrictedCodeGenerator, pycodegen.InteractiveCodeGenerator): pass class RestrictedModuleCodeGenerator(RestrictedCodeGenerator, pycodegen.ModuleCodeGenerator): def initClass(self): ModuleCodeGenerator.initClass(self) self.__class__.FunctionGen = RestrictedFunctionCodeGenerator # These subclasses work around the definition of stub compile and mode # attributes in the common base class AbstractCompileMode. If it # didn't define new attributes, then the stub code inherited via # RestrictedCompileMode would override the real definitions in # Expression. class RExpression(RestrictedCompileMode, Expression): mode = "eval" CodeGeneratorClass = RestrictedExpressionCodeGenerator class RInteractive(RestrictedCompileMode, Interactive): mode = "single" CodeGeneratorClass = RestrictedInteractiveCodeGenerator class RModule(RestrictedCompileMode, Module): mode = "exec" CodeGeneratorClass = RestrictedModuleCodeGenerator class RFunction(RModule): """A restricted Python function built from parts.""" CodeGeneratorClass = RestrictedModuleCodeGenerator def __init__(self, p, body, name, filename, globals): self.params = p if body: body = '\n'.join(body.splitlines()) + '\n' self.body = body self.name = name self.globals = globals or [] RModule.__init__(self, None, filename) def parse(self): # Parse the parameters and body, then combine them. firstline = 'def f(%s): pass' % self.params tree = niceParse(firstline, '', 'exec') f = tree.node.nodes[0] body_code = niceParse(self.body, self.filename, 'exec') # Stitch the body code into the function. f.code.nodes = body_code.node.nodes f.name = self.name # Look for a docstring, if there are any nodes at all if len(f.code.nodes) > 0: stmt1 = f.code.nodes[0] if (isinstance(stmt1, ast.Discard) and isinstance(stmt1.expr, ast.Const) and isinstance(stmt1.expr.value, str)): f.doc = stmt1.expr.value # The caller may specify that certain variables are globals # so that they can be referenced before a local assignment. # The only known example is the variables context, container, # script, traverse_subpath in PythonScripts. if self.globals: f.code.nodes.insert(0, ast.Global(self.globals)) return tree zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/notes.txt0000644000175000017500000000664412214017460024752 0ustar arnauarnauHow it works ============ Every time I see this code, I have to relearn it. These notes will hopefully make this a little easier. :) - The important module is RCompile. The entry points are the compile_restricted_* functions. + compile_restricted_function is used by Python scripts. + compile_restricted_eval is used by ZPT and by DTML indirectly through Eval.RestrictionCapableEval. - OK, so lets see how this works by following the logic of compile_restricted_eval. - First, we create an RExpression, passing the source and a "file name", to be used in tracebacks. Now, an RExpression is just: + a subclass of RestrictedCompileMode and Expression. Expression is a subclass of AbstractCompileMode that sets it's mode to 'eval' and everided compile. Sigh. + RestrictedCompileMode is a subclass of AbstractCompileMode that changes a bunch of things. :) These include compile, so we can ignore the compile we got from Expression. It would have been simpler to just set the dang mode in RExpression. Sigh. RestrictedCompileMode seem to be the interestng base class. I assume it implements the interesting functionality. We'll see below... - Next, we call compileAndTuplize. + This calls compile on the RExpression. It has an error handler that does something that I hope I don't care about. :) + It then calls the genCode method on the RExpression. This is boring, so we'll not worry about it. - The compile method provided by RestrictedCompileMode is interesting. + First it calls _get_tree. * It uses compiler.parse to parse the source * it uses MutatingWalker.walk to mutate the tree using the RestrictedCompileMode's 'rm' attr, which is a RestrictionMutator. The RestrictionMutator has the recipies for mutating the parse tree. (Note, for comparison, that Zope3's zope.security.untrustedpython.rcompile module an alternative RestrictionMutator that provides a much smaller set of changes.) A mutator has visit method for different kinds of AST nodes. These visit methods may mutate nodes or return new nodes that replace the originally visited nodes. There is a default visitor that visits a node's children and replaces the children whose visitors returned new nodes. The walk function just calls the visitor for the root node of the given tree. Note _get_tree ignores the walk return value, thus assuming that the visitor for the root node doesn't return a new node. This is a theoretical bug that we can ignore. + Second, it generates the code. This too is boring. - So this seems simple enough. ;) When we want to add a check, we need to update or add a visit function in RestrictionMutator. How does a visit function work. - First, we usually call walker.defaultVisitNode(node). This transforms the node's child nodes. - Then we hack the node, or possibly return the node. To do this, we have to know how the node works. - The hack often involved changing the code to call some checker function. These have names like _name_. These are names that would be illegal in the input source. If this is a new function, we have to provide it in AccessControl.ZopeGuards._safe_globals. - Don't forget to add a test case to tests.before_and_after. zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/Guards.py0000644000175000017500000001050712214017460024651 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## __version__ = '$Revision: 1.14 $'[11:-2] import exceptions # This tiny set of safe builtins is extended by users of the module. # AccessControl.ZopeGuards contains a large set of wrappers for builtins. # DocumentTemplate.DT_UTil contains a few. safe_builtins = {} for name in ['False', 'None', 'True', 'abs', 'basestring', 'bool', 'callable', 'chr', 'cmp', 'complex', 'divmod', 'float', 'hash', 'hex', 'id', 'int', 'isinstance', 'issubclass', 'len', 'long', 'oct', 'ord', 'pow', 'range', 'repr', 'round', 'str', 'tuple', 'unichr', 'unicode', 'xrange', 'zip']: safe_builtins[name] = __builtins__[name] # Wrappers provided by this module: # delattr # setattr # Wrappers provided by ZopeGuards: # __import__ # apply # dict # enumerate # filter # getattr # hasattr # iter # list # map # max # min # sum # all # any # Builtins that are intentionally disabled # compile - don't let them produce new code # dir - a general purpose introspector, probably hard to wrap # execfile - no direct I/O # file - no direct I/O # globals - uncontrolled namespace access # input - no direct I/O # locals - uncontrolled namespace access # open - no direct I/O # raw_input - no direct I/O # vars - uncontrolled namespace access # There are several strings that describe Python. I think there's no # point to including these, although they are obviously safe: # copyright, credits, exit, help, license, quit # Not provided anywhere. Do something about these? Several are # related to new-style classes, which we are too scared of to support # <0.3 wink>. coerce, buffer, and reload are esoteric enough that no # one should care. # buffer # bytes # bytearray # classmethod # coerce # eval # intern # memoryview # object # property # reload # slice # staticmethod # super # type for name in dir(exceptions): if name[0] != "_": safe_builtins[name] = getattr(exceptions, name) def _write_wrapper(): # Construct the write wrapper class def _handler(secattr, error_msg): # Make a class method. def handler(self, *args): try: f = getattr(self.ob, secattr) except AttributeError: raise TypeError, error_msg f(*args) return handler class Wrapper: def __len__(self): # Required for slices with negative bounds. return len(self.ob) def __init__(self, ob): self.__dict__['ob'] = ob __setitem__ = _handler('__guarded_setitem__', 'object does not support item or slice assignment') __delitem__ = _handler('__guarded_delitem__', 'object does not support item or slice assignment') __setattr__ = _handler('__guarded_setattr__', 'attribute-less object (assign or del)') __delattr__ = _handler('__guarded_delattr__', 'attribute-less object (assign or del)') return Wrapper def _full_write_guard(): # Nested scope abuse! # safetype and Wrapper variables are used by guard() safetype = {dict: True, list: True}.has_key Wrapper = _write_wrapper() def guard(ob): # Don't bother wrapping simple types, or objects that claim to # handle their own write security. if safetype(type(ob)) or hasattr(ob, '_guarded_writes'): return ob # Hand the object to the Wrapper instance, then return the instance. return Wrapper(ob) return guard full_write_guard = _full_write_guard() def guarded_setattr(object, name, value): setattr(full_write_guard(object), name, value) safe_builtins['setattr'] = guarded_setattr def guarded_delattr(object, name): delattr(full_write_guard(object), name) safe_builtins['delattr'] = guarded_delattr zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/0000755000175000017500000000000012214017460024211 5ustar arnauarnauzope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/security_in_syntax27.py0000644000175000017500000000060712214017460030702 0ustar arnauarnau# These are all supposed to raise a SyntaxError when using # compile_restricted() but not when using compile(). # Each function in this module is compiled using compile_restricted(). def dict_comp_bad_name(): {y: y for _restricted_name in x} def set_comp_bad_name(): {y for _restricted_name in x} def compound_with_bad_name(): with a as b, c as _restricted_name: pass zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/before_and_after24.py0000644000175000017500000000314012214017460030174 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Restricted Python transformation examples This module contains pairs of functions. Each pair has a before and an after function. The after function shows the source code equivalent of the before function after it has been modified by the restricted compiler. These examples are actually used in the testRestrictions.py checkBeforeAndAfter() unit tests, which verifies that the restricted compiler actually produces the same output as would be output by the normal compiler for the after function. """ def simple_generator_expression_before(): x = (y**2 for y in whatever if y > 3) def simple_generator_expression_after(): x = (y**2 for y in _getiter_(whatever) if y > 3) def nested_generator_expression_before(): x = (x**2 + y**2 for x in whatever1 if x >= 0 for y in whatever2 if y >= x) def nested_generator_expression_after(): x = (x**2 + y**2 for x in _getiter_(whatever1) if x >= 0 for y in _getiter_(whatever2) if y >= x) zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/before_and_after25.py0000644000175000017500000000245012214017460030200 0ustar arnauarnau############################################################################## # # Copyright (c) 2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Restricted Python transformation examples This module contains pairs of functions. Each pair has a before and an after function. The after function shows the source code equivalent of the before function after it has been modified by the restricted compiler. These examples are actually used in the testRestrictions.py checkBeforeAndAfter() unit tests, which verifies that the restricted compiler actually produces the same output as would be output by the normal compiler for the after function. """ def simple_ternary_if_before(): x.y = y.z if y.z else y.x def simple_ternary_if_after(): _write_(x).y = _getattr_(y, 'z') if _getattr_(y, 'z') else _getattr_(y, 'x') zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/before_and_after27.py0000644000175000017500000000330012214017460030175 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Restricted Python transformation examples This module contains pairs of functions. Each pair has a before and an after function. The after function shows the source code equivalent of the before function after it has been modified by the restricted compiler. These examples are actually used in the testRestrictions.py checkBeforeAndAfter() unit tests, which verifies that the restricted compiler actually produces the same output as would be output by the normal compiler for the after function. """ # dictionary and set comprehensions def simple_dict_comprehension_before(): x = {y: y for y in whatever if y} def simple_dict_comprehension_after(): x = {y: y for y in _getiter_(whatever) if y} def dict_comprehension_attrs_before(): x = {y: y.q for y in whatever.z if y.q} def dict_comprehension_attrs_after(): x = {y: _getattr_(y, 'q') for y in _getiter_(_getattr_(whatever, 'z')) if _getattr_(y, 'q')} def simple_set_comprehension_before(): x = {y for y in whatever if y} def simple_set_comprehension_after(): x = {y for y in _getiter_(whatever) if y} zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/before_and_after.py0000644000175000017500000001405512214017460030035 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Restricted Python transformation examples This module contains pairs of functions. Each pair has a before and an after function. The after function shows the source code equivalent of the before function after it has been modified by the restricted compiler. These examples are actually used in the testRestrictions.py checkBeforeAndAfter() unit tests, which verifies that the restricted compiler actually produces the same output as would be output by the normal compiler for the after function. """ # getattr def simple_getattr_before(x): return x.y def simple_getattr_after(x): return _getattr_(x, 'y') # set attr def simple_setattr_before(): x.y = "bar" def simple_setattr_after(): _write_(x).y = "bar" # for loop and list comprehensions def simple_forloop_before(x): for x in [1, 2, 3]: pass def simple_forloop_after(x): for x in _getiter_([1, 2, 3]): pass def nested_forloop_before(x): for x in [1, 2, 3]: for y in "abc": pass def nested_forloop_after(x): for x in _getiter_([1, 2, 3]): for y in _getiter_("abc"): pass def simple_list_comprehension_before(): x = [y**2 for y in whatever if y > 3] def simple_list_comprehension_after(): x = [y**2 for y in _getiter_(whatever) if y > 3] def nested_list_comprehension_before(): x = [x**2 + y**2 for x in whatever1 if x >= 0 for y in whatever2 if y >= x] def nested_list_comprehension_after(): x = [x**2 + y**2 for x in _getiter_(whatever1) if x >= 0 for y in _getiter_(whatever2) if y >= x] # print def simple_print_before(): print "foo" def simple_print_after(): _print = _print_() print >> _print, "foo" # getitem def simple_getitem_before(): return x[0] def simple_getitem_after(): return _getitem_(x, 0) def simple_get_tuple_key_before(): x = y[1,2] def simple_get_tuple_key_after(): x = _getitem_(y, (1,2)) # set item def simple_setitem_before(): x[0] = "bar" def simple_setitem_after(): _write_(x)[0] = "bar" # delitem def simple_delitem_before(): del x[0] def simple_delitem_after(): del _write_(x)[0] # a collection of function parallels to many of the above def function_with_print_before(): def foo(): print "foo" return printed def function_with_print_after(): def foo(): _print = _print_() print >> _print, "foo" return _print() def function_with_getattr_before(): def foo(): return x.y def function_with_getattr_after(): def foo(): return _getattr_(x, 'y') def function_with_setattr_before(): def foo(x): x.y = "bar" def function_with_setattr_after(): def foo(x): _write_(x).y = "bar" def function_with_getitem_before(): def foo(x): return x[0] def function_with_getitem_after(): def foo(x): return _getitem_(x, 0) def function_with_forloop_before(): def foo(): for x in [1, 2, 3]: pass def function_with_forloop_after(): def foo(): for x in _getiter_([1, 2, 3]): pass # this, and all slices, won't work in these tests because the before code # parses the slice as a slice object, while the after code can't generate a # slice object in this way. The after code as written below # is parsed as a call to the 'slice' name, not as a slice object. # XXX solutions? #def simple_slice_before(): # x = y[:4] #def simple_slice_after(): # _getitem = _getitem_ # x = _getitem(y, slice(None, 4)) # Assignment stmts in Python can be very complicated. The "no_unpack" # test makes sure we're not doing unnecessary rewriting. def no_unpack_before(): x = y x = [y] x = y, x = (y, (y, y), [y, (y,)], x, (x, y)) x = y = z = (x, y, z) no_unpack_after = no_unpack_before # that is, should be untouched # apply() variations. Native apply() is unsafe because, e.g., # # def f(a, b, c): # whatever # # apply(f, two_element_sequence, dict_with_key_c) # # or (different spelling of the same thing) # # f(*two_element_sequence, **dict_with_key_c) # # makes the elements of two_element_sequence visible to f via its 'a' and # 'b' arguments, and the dict_with_key_c['c'] value visible via its 'c' # argument. That is, it's a devious way to extract values without going # thru security checks. def star_call_before(): foo(*a) def star_call_after(): _apply_(foo, *a) def star_call_2_before(): foo(0, *a) def star_call_2_after(): _apply_(foo, 0, *a) def starstar_call_before(): foo(**d) def starstar_call_after(): _apply_(foo, **d) def star_and_starstar_call_before(): foo(*a, **d) def star_and_starstar_call_after(): _apply_(foo, *a, **d) def positional_and_star_and_starstar_call_before(): foo(b, *a, **d) def positional_and_star_and_starstar_call_after(): _apply_(foo, b, *a, **d) def positional_and_defaults_and_star_and_starstar_call_before(): foo(b, x=y, w=z, *a, **d) def positional_and_defaults_and_star_and_starstar_call_after(): _apply_(foo, b, x=y, w=z, *a, **d) def lambda_with_getattr_in_defaults_before(): f = lambda x=y.z: x def lambda_with_getattr_in_defaults_after(): f = lambda x=_getattr_(y, "z"): x # augmented operators # Note that we don't have to worry about item, attr, or slice assignment, # as they are disallowed. Yay! ## def inplace_id_add_before(): ## x += y+z ## def inplace_id_add_after(): ## x = _inplacevar_('+=', x, y+z) zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/testREADME.py0000644000175000017500000000156612214017460026430 0ustar arnauarnau############################################################################## # # Copyright (c) 2007 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Run tests in README.txt """ import unittest from doctest import DocFileSuite __docformat__ = "reStructuredText" def test_suite(): return unittest.TestSuite([ DocFileSuite('README.txt', package='RestrictedPython'), ]) zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/class.py0000644000175000017500000000034712214017460025674 0ustar arnauarnauclass MyClass: def set(self, val): self.state = val def get(self): return self.state x = MyClass() x.set(12) x.set(x.get() + 1) if x.get() != 13: raise AssertionError, "expected 13, got %d" % x.get() zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/lambda.py0000644000175000017500000000014212214017460026000 0ustar arnauarnauf = lambda x, y=1: x + y if f(2) != 3: raise ValueError if f(2, 2) != 4: raise ValueError zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/__init__.py0000644000175000017500000000002612214017460026320 0ustar arnauarnau'''Python package.''' zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/before_and_after26.py0000644000175000017500000000317612214017460030207 0ustar arnauarnau############################################################################## # # Copyright (c) 2008 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Restricted Python transformation examples This module contains pairs of functions. Each pair has a before and an after function. The after function shows the source code equivalent of the before function after it has been modified by the restricted compiler. These examples are actually used in the testRestrictions.py checkBeforeAndAfter() unit tests, which verifies that the restricted compiler actually produces the same output as would be output by the normal compiler for the after function. """ def simple_context_before(): with whatever as x: x.y = z def simple_context_after(): with whatever as x: _write_(x).y = z def simple_context_assign_attr_before(): with whatever as x.y: x.y = z def simple_context_assign_attr_after(): with whatever as _write_(x).y: _write_(x).y = z def simple_context_load_attr_before(): with whatever.w as z: x.y = z def simple_context_load_attr_after(): with _getattr_(whatever, 'w') as z: _write_(x).y = z zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/restricted_module.py0000644000175000017500000000652512214017460030310 0ustar arnauarnauimport sys def print0(): print 'Hello, world!', return printed def print1(): print 'Hello,', print 'world!', return printed def printStuff(): print 'a', 'b', 'c', return printed def printToNone(): x = None print >>x, 'Hello, world!', return printed def printLines(): # This failed before Zope 2.4.0a2 r = range(3) for n in r: for m in r: print m + n * len(r), print return printed def try_map(): inc = lambda i: i+1 x = [1, 2, 3] print map(inc, x), return printed def try_apply(): def f(x, y, z): return x + y + z print f(*(300, 20), **{'z': 1}), return printed def try_inplace(): x = 1 x += 3 def primes(): # Somewhat obfuscated code on purpose print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0, map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,20))), return printed def allowed_read(ob): print ob.allowed print ob.s print ob[0] print ob[2] print ob[3:-1] print len(ob) return printed def allowed_default_args(ob): def f(a=ob.allowed, s=ob.s): return a, s def allowed_simple(): q = {'x':'a'} q['y'] = 'b' q.update({'z': 'c'}) r = ['a'] r.append('b') r[2:2] = ['c'] s = 'a' s = s[:100] + 'b' s += 'c' if sys.version_info >= (2, 3): t = ['l', 'm', 'n', 'o', 'p', 'q'] t[1:5:2] = ['n', 'p'] _ = q return q['x'] + q['y'] + q['z'] + r[0] + r[1] + r[2] + s def allowed_write(ob): ob.writeable = 1 #ob.writeable += 1 [1 for ob.writeable in 1,2] ob['safe'] = 2 #ob['safe'] += 2 [1 for ob['safe'] in 1,2] def denied_print(ob): print >> ob, 'Hello, world!', def denied_getattr(ob): #ob.disallowed += 1 ob.disallowed = 1 return ob.disallowed def denied_default_args(ob): def f(d=ob.disallowed): return d def denied_setattr(ob): ob.allowed = -1 def denied_setattr2(ob): #ob.allowed += -1 ob.allowed = -1 def denied_setattr3(ob): [1 for ob.allowed in 1,2] def denied_getitem(ob): ob[1] def denied_getitem2(ob): #ob[1] += 1 ob[1] def denied_setitem(ob): ob['x'] = 2 def denied_setitem2(ob): #ob[0] += 2 ob['x'] = 2 def denied_setitem3(ob): [1 for ob['x'] in 1,2] def denied_setslice(ob): ob[0:1] = 'a' def denied_setslice2(ob): #ob[0:1] += 'a' ob[0:1] = 'a' def denied_setslice3(ob): [1 for ob[0:1] in 1,2] ##def strange_attribute(): ## # If a guard has attributes with names that don't start with an ## # underscore, those attributes appear to be an attribute of ## # anything. ## return [].attribute_of_anything def order_of_operations(): return 3 * 4 * -2 + 2 * 12 def rot13(ss): mapping = {} orda = ord('a') ordA = ord('A') for n in range(13): c1 = chr(orda + n) c2 = chr(orda + n + 13) c3 = chr(ordA + n) c4 = chr(ordA + n + 13) mapping[c1] = c2 mapping[c2] = c1 mapping[c3] = c4 mapping[c4] = c3 del c1, c2, c3, c4, orda, ordA res = '' for c in ss: res = res + mapping.get(c, c) return res def nested_scopes_1(): # Fails if 'a' is consumed by the first function. a = 1 def f1(): return a def f2(): return a return f1() + f2() class Classic: pass zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/verify.py0000644000175000017500000001711712214017460026076 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Verify simple properties of bytecode. Some of the transformations performed by the RestrictionMutator are tricky. This module checks the generated bytecode as a way to verify the correctness of the transformations. Violations of some restrictions are obvious from inspection of the bytecode. For example, the bytecode should never contain a LOAD_ATTR call, because all attribute access is performed via the _getattr_() checker function. """ import dis import types def verify(code): """Verify all code objects reachable from code. In particular, traverse into contained code objects in the co_consts table. """ verifycode(code) for ob in code.co_consts: if isinstance(ob, types.CodeType): verify(ob) def verifycode(code): try: _verifycode(code) except: dis.dis(code) raise def _verifycode(code): line = code.co_firstlineno # keep a window of the last three opcodes, with the most recent first window = (None, None, None) with_context = (None, None) for op in disassemble(code): if op.line is not None: line = op.line if op.opname.endswith("LOAD_ATTR"): # All the user code that generates LOAD_ATTR should be # rewritten, but the code generated for a list comp # includes a LOAD_ATTR to extract the append method. # Another exception is the new-in-Python 2.6 'context # managers', which do a LOAD_ATTR for __exit__ and # __enter__. if op.arg == "__exit__": with_context = (op, with_context[1]) elif op.arg == "__enter__": with_context = (with_context[0], op) elif not ((op.arg == "__enter__" and window[0].opname == "ROT_TWO" and window[1].opname == "DUP_TOP") or (op.arg == "append" and window[0].opname == "DUP_TOP" and window[1].opname == "BUILD_LIST")): raise ValueError("direct attribute access %s: %s, %s:%d" % (op.opname, op.arg, code.co_filename, line)) if op.opname in ("WITH_CLEANUP"): # Here we check if the LOAD_ATTR for __exit__ and # __enter__ were part of a 'with' statement by checking # for the 'WITH_CLEANUP' bytecode. If one is seen, we # clear the with_context variable and let it go. The # access was safe. with_context = (None, None) if op.opname in ("STORE_ATTR", "DEL_ATTR"): if not (window[0].opname == "CALL_FUNCTION" and window[2].opname == "LOAD_GLOBAL" and window[2].arg == "_write_"): # check that arg is appropriately wrapped for i, op in enumerate(window): print i, op.opname, op.arg raise ValueError("unguard attribute set/del at %s:%d" % (code.co_filename, line)) if op.opname.startswith("UNPACK"): # An UNPACK opcode extracts items from iterables, and that's # unsafe. The restricted compiler doesn't remove UNPACK opcodes, # but rather *inserts* a call to _getiter_() before each, and # that's the pattern we need to see. if not (window[0].opname == "CALL_FUNCTION" and window[1].opname == "ROT_TWO" and window[2].opname == "LOAD_GLOBAL" and window[2].arg == "_getiter_"): raise ValueError("unguarded unpack sequence at %s:%d" % (code.co_filename, line)) # should check CALL_FUNCTION_{VAR,KW,VAR_KW} but that would # require a potentially unlimited history. need to refactor # the "window" before I can do that. if op.opname == "LOAD_SUBSCR": raise ValueError("unguarded index of sequence at %s:%d" % (code.co_filename, line)) window = (op,) + window[:2] if not with_context == (None, None): # An access to __enter__ and __exit__ was performed but not as # part of a 'with' statement. This is not allowed. for op in with_context: if op is not None: if op.line is not None: line = op.line raise ValueError("direct attribute access %s: %s, %s:%d" % (op.opname, op.arg, code.co_filename, line)) class Op(object): __slots__ = ( "opname", # string, name of the opcode "argcode", # int, the number of the argument "arg", # any, the object, name, or value of argcode "line", # int, line number or None "target", # boolean, is this op the target of a jump "pos", # int, offset in the bytecode ) def __init__(self, opcode, pos): self.opname = dis.opname[opcode] self.arg = None self.line = None self.target = False self.pos = pos def disassemble(co, lasti=-1): code = co.co_code labels = dis.findlabels(code) linestarts = dict(findlinestarts(co)) n = len(code) i = 0 extended_arg = 0 free = co.co_cellvars + co.co_freevars while i < n: op = ord(code[i]) o = Op(op, i) i += 1 if i in linestarts and i > 0: o.line = linestarts[i] if i in labels: o.target = True if op > dis.HAVE_ARGUMENT: arg = ord(code[i]) + ord(code[i+1]) * 256 + extended_arg extended_arg = 0 i += 2 if op == dis.EXTENDED_ARG: extended_arg = arg << 16 o.argcode = arg if op in dis.hasconst: o.arg = co.co_consts[arg] elif op in dis.hasname: o.arg = co.co_names[arg] elif op in dis.hasjrel: o.arg = i + arg elif op in dis.haslocal: o.arg = co.co_varnames[arg] elif op in dis.hascompare: o.arg = dis.cmp_op[arg] elif op in dis.hasfree: o.arg = free[arg] yield o # findlinestarts is copied from Python 2.4's dis module. The code # didn't exist in 2.3, but it would be painful to code disassemble() # without it. def findlinestarts(code): """Find the offsets in a byte code which are start of lines in the source. Generate pairs (offset, lineno) as described in Python/compile.c. """ byte_increments = [ord(c) for c in code.co_lnotab[0::2]] line_increments = [ord(c) for c in code.co_lnotab[1::2]] lastlineno = None lineno = code.co_firstlineno addr = 0 for byte_incr, line_incr in zip(byte_increments, line_increments): if byte_incr: if lineno != lastlineno: yield (addr, lineno) lastlineno = lineno addr += byte_incr lineno += line_incr if lineno != lastlineno: yield (addr, lineno) zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/testRestrictions.py0000644000175000017500000004535312214017460030165 0ustar arnauarnauimport os import re import sys import unittest # Note that nothing should be imported from AccessControl, and in particular # nothing from ZopeGuards.py. Transformed code may need several wrappers # in order to run at all, and most of the production wrappers are defined # in ZopeGuards. But RestrictedPython isn't supposed to depend on # AccessControl, so we need to define throwaway wrapper implementations # here instead. from RestrictedPython import compile_restricted, PrintCollector from RestrictedPython.Eval import RestrictionCapableEval from RestrictedPython.tests import restricted_module, verify from RestrictedPython.RCompile import RModule, RFunction try: __file__ except NameError: __file__ = os.path.abspath(sys.argv[1]) _FILEPATH = os.path.abspath( __file__ ) _HERE = os.path.dirname( _FILEPATH ) def _getindent(line): """Returns the indentation level of the given line.""" indent = 0 for c in line: if c == ' ': indent = indent + 1 elif c == '\t': indent = indent + 8 else: break return indent def find_source(fn, func): """Given a func_code object, this function tries to find and return the python source code of the function. Originally written by Harm van der Heijden (H.v.d.Heijden@phys.tue.nl)""" f = open(fn,"r") for i in range(func.co_firstlineno): line = f.readline() ind = _getindent(line) msg = "" while line: msg = msg + line line = f.readline() # the following should be <= ind, but then we get # confused by multiline docstrings. Using == works most of # the time... but not always! if _getindent(line) == ind: break f.close() return fn, msg def get_source(func): """Less silly interface to find_source""" file = func.func_globals['__file__'] if file.endswith('.pyc'): file = file[:-1] source = find_source(file, func.func_code)[1] assert source.strip(), "Source should not be empty!" return source def create_rmodule(): global rmodule fn = os.path.join(_HERE, 'restricted_module.py') f = open(fn, 'r') source = f.read() f.close() # Sanity check compile(source, fn, 'exec') # Now compile it for real code = compile_restricted(source, fn, 'exec') rmodule = {'__builtins__':{'__import__':__import__, 'None':None, '__name__': 'restricted_module'}} builtins = getattr(__builtins__, '__dict__', __builtins__) for name in ('map', 'reduce', 'int', 'pow', 'range', 'filter', 'len', 'chr', 'ord', ): rmodule[name] = builtins[name] exec code in rmodule class AccessDenied (Exception): pass DisallowedObject = [] class RestrictedObject: disallowed = DisallowedObject allowed = 1 _ = 2 __ = 3 _some_attr = 4 __some_other_attr__ = 5 s = 'Another day, another test...' __writeable_attrs__ = ('writeable',) def __getitem__(self, idx): if idx == 'protected': raise AccessDenied elif idx == 0 or idx == 'safe': return 1 elif idx == 1: return DisallowedObject else: return self.s[idx] def __getslice__(self, lo, hi): return self.s[lo:hi] def __len__(self): return len(self.s) def __setitem__(self, idx, v): if idx == 'safe': self.safe = v else: raise AccessDenied def __setslice__(self, lo, hi, value): raise AccessDenied write = DisallowedObject def guarded_getattr(ob, name): v = getattr(ob, name) if v is DisallowedObject: raise AccessDenied return v SliceType = type(slice(0)) def guarded_getitem(ob, index): if type(index) is SliceType and index.step is None: start = index.start stop = index.stop if start is None: start = 0 if stop is None: v = ob[start:] else: v = ob[start:stop] else: v = ob[index] if v is DisallowedObject: raise AccessDenied return v def minimal_import(name, _globals, _locals, names): if name != "__future__": raise ValueError, "Only future imports are allowed" import __future__ return __future__ class TestGuard: '''A guard class''' def __init__(self, _ob, write=None): self.__dict__['_ob'] = _ob # Write guard methods def __setattr__(self, name, value): _ob = self.__dict__['_ob'] writeable = getattr(_ob, '__writeable_attrs__', ()) if name not in writeable: raise AccessDenied if name[:5] == 'func_': raise AccessDenied setattr(_ob, name, value) def __setitem__(self, index, value): _ob = self.__dict__['_ob'] _ob[index] = value def __setslice__(self, lo, hi, value): _ob = self.__dict__['_ob'] _ob[lo:hi] = value # A wrapper for _apply_. apply_wrapper_called = [] def apply_wrapper(func, *args, **kws): apply_wrapper_called.append('yes') return func(*args, **kws) inplacevar_wrapper_called = {} def inplacevar_wrapper(op, x, y): inplacevar_wrapper_called[op] = x, y # This is really lame. But it's just a test. :) globs = {'x': x, 'y': y} exec 'x'+op+'y' in globs return globs['x'] class RestrictionTests(unittest.TestCase): def execFunc(self, name, *args, **kw): func = rmodule[name] verify.verify(func.func_code) func.func_globals.update({'_getattr_': guarded_getattr, '_getitem_': guarded_getitem, '_write_': TestGuard, '_print_': PrintCollector, # I don't want to write something as involved as ZopeGuard's # SafeIter just for these tests. Using the builtin list() function # worked OK for everything the tests did at the time this was added, # but may fail in the future. If Python 2.1 is no longer an # interesting platform then, using 2.2's builtin iter() here should # work for everything. '_getiter_': list, '_apply_': apply_wrapper, '_inplacevar_': inplacevar_wrapper, }) return func(*args, **kw) def checkPrint(self): for i in range(2): res = self.execFunc('print%s' % i) self.assertEqual(res, 'Hello, world!') def checkPrintToNone(self): try: res = self.execFunc('printToNone') except AttributeError: # Passed. "None" has no "write" attribute. pass else: self.fail(0, res) def checkPrintStuff(self): res = self.execFunc('printStuff') self.assertEqual(res, 'a b c') def checkPrintLines(self): res = self.execFunc('printLines') self.assertEqual(res, '0 1 2\n3 4 5\n6 7 8\n') def checkPrimes(self): res = self.execFunc('primes') self.assertEqual(res, '[2, 3, 5, 7, 11, 13, 17, 19]') def checkAllowedSimple(self): res = self.execFunc('allowed_simple') self.assertEqual(res, 'abcabcabc') def checkAllowedRead(self): self.execFunc('allowed_read', RestrictedObject()) def checkAllowedWrite(self): self.execFunc('allowed_write', RestrictedObject()) def checkAllowedArgs(self): self.execFunc('allowed_default_args', RestrictedObject()) def checkTryMap(self): res = self.execFunc('try_map') self.assertEqual(res, "[2, 3, 4]") def checkApply(self): del apply_wrapper_called[:] res = self.execFunc('try_apply') self.assertEqual(apply_wrapper_called, ["yes"]) self.assertEqual(res, "321") def checkInplace(self): inplacevar_wrapper_called.clear() res = self.execFunc('try_inplace') self.assertEqual(inplacevar_wrapper_called['+='], (1, 3)) def checkDenied(self): for k in rmodule.keys(): if k[:6] == 'denied': try: self.execFunc(k, RestrictedObject()) except AccessDenied: # Passed the test pass else: self.fail('%s() did not trip security' % k) def checkSyntaxSecurity(self): self._checkSyntaxSecurity('security_in_syntax.py') if sys.version_info >= (2, 6): self._checkSyntaxSecurity('security_in_syntax26.py') if sys.version_info >= (2, 7): self._checkSyntaxSecurity('security_in_syntax27.py') def _checkSyntaxSecurity(self, mod_name): # Ensures that each of the functions in security_in_syntax.py # throws a SyntaxError when using compile_restricted. fn = os.path.join(_HERE, mod_name) f = open(fn, 'r') source = f.read() f.close() # Unrestricted compile. code = compile(source, fn, 'exec') m = {'__builtins__': {'__import__':minimal_import}} exec code in m for k, v in m.items(): if hasattr(v, 'func_code'): filename, source = find_source(fn, v.func_code) # Now compile it with restrictions try: code = compile_restricted(source, filename, 'exec') except SyntaxError: # Passed the test. pass else: self.fail('%s should not have compiled' % k) def checkOrderOfOperations(self): res = self.execFunc('order_of_operations') self.assertEqual(res, 0) def checkRot13(self): res = self.execFunc('rot13', 'Zope is k00l') self.assertEqual(res, 'Mbcr vf x00y') def checkNestedScopes1(self): res = self.execFunc('nested_scopes_1') self.assertEqual(res, 2) def checkUnrestrictedEval(self): expr = RestrictionCapableEval("{'a':[m.pop()]}['a'] + [m[0]]") v = [12, 34] expect = v[:] expect.reverse() res = expr.eval({'m':v}) self.assertEqual(res, expect) v = [12, 34] res = expr(m=v) self.assertEqual(res, expect) def checkStackSize(self): for k, rfunc in rmodule.items(): if not k.startswith('_') and hasattr(rfunc, 'func_code'): rss = rfunc.func_code.co_stacksize ss = getattr(restricted_module, k).func_code.co_stacksize self.failUnless( rss >= ss, 'The stack size estimate for %s() ' 'should have been at least %d, but was only %d' % (k, ss, rss)) def checkBeforeAndAfter(self): from RestrictedPython.RCompile import RModule from RestrictedPython.tests import before_and_after from compiler import parse defre = re.compile(r'def ([_A-Za-z0-9]+)_(after|before)\(') beforel = [name for name in before_and_after.__dict__ if name.endswith("_before")] for name in beforel: before = getattr(before_and_after, name) before_src = get_source(before) before_src = re.sub(defre, r'def \1(', before_src) rm = RModule(before_src, '') tree_before = rm._get_tree() after = getattr(before_and_after, name[:-6]+'after') after_src = get_source(after) after_src = re.sub(defre, r'def \1(', after_src) tree_after = parse(after_src) self.assertEqual(str(tree_before), str(tree_after)) rm.compile() verify.verify(rm.getCode()) def _checkBeforeAndAfter(self, mod): from RestrictedPython.RCompile import RModule from compiler import parse defre = re.compile(r'def ([_A-Za-z0-9]+)_(after|before)\(') beforel = [name for name in mod.__dict__ if name.endswith("_before")] for name in beforel: before = getattr(mod, name) before_src = get_source(before) before_src = re.sub(defre, r'def \1(', before_src) rm = RModule(before_src, '') tree_before = rm._get_tree() after = getattr(mod, name[:-6]+'after') after_src = get_source(after) after_src = re.sub(defre, r'def \1(', after_src) tree_after = parse(after_src) self.assertEqual(str(tree_before), str(tree_after)) rm.compile() verify.verify(rm.getCode()) if sys.version_info[:2] >= (2, 4): def checkBeforeAndAfter24(self): from RestrictedPython.tests import before_and_after24 self._checkBeforeAndAfter(before_and_after24) if sys.version_info[:2] >= (2, 5): def checkBeforeAndAfter25(self): from RestrictedPython.tests import before_and_after25 self._checkBeforeAndAfter(before_and_after25) if sys.version_info[:2] >= (2, 6): def checkBeforeAndAfter26(self): from RestrictedPython.tests import before_and_after26 self._checkBeforeAndAfter(before_and_after26) if sys.version_info[:2] >= (2, 7): def checkBeforeAndAfter27(self): from RestrictedPython.tests import before_and_after27 self._checkBeforeAndAfter(before_and_after27) def _compile_file(self, name): path = os.path.join(_HERE, name) f = open(path, "r") source = f.read() f.close() co = compile_restricted(source, path, "exec") verify.verify(co) return co def checkUnpackSequence(self): co = self._compile_file("unpack.py") calls = [] def getiter(seq): calls.append(seq) return list(seq) globals = {"_getiter_": getiter, '_inplacevar_': inplacevar_wrapper} exec co in globals, {} # The comparison here depends on the exact code that is # contained in unpack.py. # The test doing implicit unpacking in an "except:" clause is # a pain, because there are two levels of unpacking, and the top # level is unpacking the specific TypeError instance constructed # by the test. We have to worm around that one. ineffable = "a TypeError instance" expected = [[1, 2], (1, 2), "12", [1], [1, [2, 3], 4], [2, 3], (1, (2, 3), 4), (2, 3), [1, 2, 3], 2, ('a', 'b'), ((1, 2), (3, 4)), (1, 2), ((1, 2), (3, 4)), (3, 4), ineffable, [42, 666], [[0, 1], [2, 3], [4, 5]], [0, 1], [2, 3], [4, 5], ([[[1, 2]]], [[[3, 4]]]), [[[1, 2]]], [[1, 2]], [1, 2], [[[3, 4]]], [[3, 4]], [3, 4], ] i = expected.index(ineffable) self.assert_(isinstance(calls[i], TypeError)) expected[i] = calls[i] self.assertEqual(calls, expected) def checkUnpackSequenceExpression(self): co = compile_restricted("[x for x, y in [(1, 2)]]", "", "eval") verify.verify(co) calls = [] def getiter(s): calls.append(s) return list(s) globals = {"_getiter_": getiter} exec co in globals, {} self.assertEqual(calls, [[(1,2)], (1, 2)]) def checkUnpackSequenceSingle(self): co = compile_restricted("x, y = 1, 2", "", "single") verify.verify(co) calls = [] def getiter(s): calls.append(s) return list(s) globals = {"_getiter_": getiter} exec co in globals, {} self.assertEqual(calls, [(1, 2)]) def checkClass(self): getattr_calls = [] setattr_calls = [] def test_getattr(obj, attr): getattr_calls.append(attr) return getattr(obj, attr) def test_setattr(obj): setattr_calls.append(obj.__class__.__name__) return obj co = self._compile_file("class.py") globals = {"_getattr_": test_getattr, "_write_": test_setattr, } exec co in globals, {} # Note that the getattr calls don't correspond to the method call # order, because the x.set method is fetched before its arguments # are evaluated. self.assertEqual(getattr_calls, ["set", "set", "get", "state", "get", "state"]) self.assertEqual(setattr_calls, ["MyClass", "MyClass"]) def checkLambda(self): co = self._compile_file("lambda.py") exec co in {}, {} def checkEmpty(self): rf = RFunction("", "", "issue945", "empty.py", {}) rf.parse() rf2 = RFunction("", "# still empty\n\n# by", "issue945", "empty.py", {}) rf2.parse() def checkSyntaxError(self): err = ("def f(x, y):\n" " if x, y < 2 + 1:\n" " return x + y\n" " else:\n" " return x - y\n") self.assertRaises(SyntaxError, compile_restricted, err, "", "exec") # these two tests check that source code with Windows line # endings still works. def checkLineEndingsRFunction(self): from RestrictedPython.RCompile import RFunction gen = RFunction( p='', body='# testing\r\nprint "testing"\r\nreturn printed\n', name='test', filename='', globals=(), ) gen.mode = 'exec' # if the source has any line ending other than \n by the time # parse() is called, then you'll get a syntax error. gen.parse() def checkLineEndingsRestrictedCompileMode(self): from RestrictedPython.RCompile import RestrictedCompileMode gen = RestrictedCompileMode( '# testing\r\nprint "testing"\r\nreturn printed\n', '' ) gen.mode='exec' # if the source has any line ending other than \n by the time # parse() is called, then you'll get a syntax error. gen.parse() def checkCollector2295(self): from RestrictedPython.RCompile import RestrictedCompileMode gen = RestrictedCompileMode( 'if False:\n pass\n# Me Grok, Say Hi', '' ) gen.mode='exec' # if the source has any line ending other than \n by the time # parse() is called, then you'll get a syntax error. gen.parse() create_rmodule() def test_suite(): return unittest.makeSuite(RestrictionTests, 'check') if __name__=='__main__': unittest.main(defaultTest="test_suite") zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/security_in_syntax26.py0000644000175000017500000000067212214017460030703 0ustar arnauarnau# These are all supposed to raise a SyntaxError when using # compile_restricted() but not when using compile(). # Each function in this module is compiled using compile_restricted(). def with_as_bad_name(): with x as _leading_underscore: pass def relative_import_as_bad_name(): from .x import y as _leading_underscore def except_as_bad_name(): try: 1/0 except Exception as _leading_underscore: pass zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/unpack.py0000644000175000017500000000263512214017460026052 0ustar arnauarnau# A series of short tests for unpacking sequences. def u1(L): x, y = L assert x == 1 assert y == 2 u1([1,2]) u1((1, 2)) def u1a(L): x, y = L assert x == '1' assert y == '2' u1a("12") try: u1([1]) except ValueError: pass else: raise AssertionError, "expected 'unpack list of wrong size'" def u2(L): x, (a, b), y = L assert x == 1 assert a == 2 assert b == 3 assert y == 4 u2([1, [2, 3], 4]) u2((1, (2, 3), 4)) try: u2([1, 2, 3]) except TypeError: pass else: raise AssertionError, "expected 'iteration over non-sequence'" def u3((x, y)): assert x == 'a' assert y == 'b' return x, y u3(('a', 'b')) def u4(x): (a, b), c = d, (e, f) = x assert a == 1 and b == 2 and c == (3, 4) assert d == (1, 2) and e == 3 and f == 4 u4( ((1, 2), (3, 4)) ) def u5(x): try: raise TypeError(x) # This one is tricky to test, because the first level of unpacking # has a TypeError instance. That's a headache for the test driver. except TypeError, [(a, b)]: assert a == 42 assert b == 666 u5([42, 666]) def u6(x): expected = 0 for i, j in x: assert i == expected expected += 1 assert j == expected expected += 1 u6([[0, 1], [2, 3], [4, 5]]) def u7(x): stuff = [i + j for toplevel, in x for i, j in toplevel] assert stuff == [3, 7] u7( ([[[1, 2]]], [[[3, 4]]]) ) zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/testCompile.py0000644000175000017500000000253212214017460027055 0ustar arnauarnau# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## __version__ = '$Revision: 110600 $'[11:-2] import unittest from RestrictedPython.RCompile import niceParse import compiler.ast class CompileTests(unittest.TestCase): def testUnicodeSource(self): # We support unicode sourcecode. source = u"u'Ä väry nice säntänce with umlauts.'" parsed = niceParse(source, "test.py", "exec") self.failUnless(isinstance(parsed, compiler.ast.Module)) parsed = niceParse(source, "test.py", "single") self.failUnless(isinstance(parsed, compiler.ast.Module)) parsed = niceParse(source, "test.py", "eval") self.failUnless(isinstance(parsed, compiler.ast.Expression)) def test_suite(): return unittest.makeSuite(CompileTests) zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/security_in_syntax.py0000644000175000017500000000272012214017460030527 0ustar arnauarnau# These are all supposed to raise a SyntaxError when using # compile_restricted() but not when using compile(). # Each function in this module is compiled using compile_restricted(). def overrideGuardWithFunction(): def _getattr(o): return o def overrideGuardWithLambda(): lambda o, _getattr=None: o def overrideGuardWithClass(): class _getattr: pass def overrideGuardWithName(): _getattr = None def overrideGuardWithArgument(): def f(_getattr=None): pass def reserved_names(): printed = '' def bad_name(): __ = 12 def bad_attr(): some_ob._some_attr = 15 def no_exec(): exec 'q = 1' def no_yield(): yield 42 def check_getattr_in_lambda(arg=lambda _getattr=(lambda ob, name: name): _getattr): 42 def import_as_bad_name(): import os as _leading_underscore def from_import_as_bad_name(): from x import y as _leading_underscore def except_using_bad_name(): try: foo except NameError, _leading_underscore: # The name of choice (say, _write) is now assigned to an exception # object. Hard to exploit, but conceivable. pass def keyword_arg_with_bad_name(): def f(okname=1, __badname=2): pass def no_augmeneted_assignment_to_sub(): a[b] += c def no_augmeneted_assignment_to_attr(): a.b += c def no_augmeneted_assignment_to_slice(): a[x:y] += c def no_augmeneted_assignment_to_slice2(): a[x:y:z] += c zope2.13-2.13.21/source/RestrictedPython/src/RestrictedPython/tests/testUtiliities.py0000644000175000017500000001362212214017460027613 0ustar arnauarnau############################################################################## # # Copyright (c) 2009 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Run tests in README.txt """ import unittest class UtilitiesTests(unittest.TestCase): def test_string_in_utility_builtins(self): import string from RestrictedPython.Utilities import utility_builtins self.failUnless(utility_builtins['string'] is string) def test_math_in_utility_builtins(self): import math from RestrictedPython.Utilities import utility_builtins self.failUnless(utility_builtins['math'] is math) def test_whrandom_in_utility_builtins(self): import random from RestrictedPython.Utilities import utility_builtins self.failUnless(utility_builtins['whrandom'] is random) def test_random_in_utility_builtins(self): import random from RestrictedPython.Utilities import utility_builtins self.failUnless(utility_builtins['random'] is random) def test_sets_in_utility_builtins_if_importable(self): import warnings from RestrictedPython.Utilities import utility_builtins _old_filters = warnings.filters[:] warnings.filterwarnings('ignore', category=DeprecationWarning) try: try: import sets except ImportError: sets = None finally: warnings.filters[:] = _old_filters self.failUnless(utility_builtins['sets'] is sets) def test_DateTime_in_utility_builtins_if_importable(self): try: import DateTime except ImportError: pass else: from RestrictedPython.Utilities import utility_builtins self.failUnless('DateTime' in utility_builtins) def test_same_type_in_utility_builtins(self): from RestrictedPython.Utilities import same_type from RestrictedPython.Utilities import utility_builtins self.failUnless(utility_builtins['same_type'] is same_type) def test_test_in_utility_builtins(self): from RestrictedPython.Utilities import test from RestrictedPython.Utilities import utility_builtins self.failUnless(utility_builtins['test'] is test) def test_reorder_in_utility_builtins(self): from RestrictedPython.Utilities import reorder from RestrictedPython.Utilities import utility_builtins self.failUnless(utility_builtins['reorder'] is reorder) def test_sametype_only_one_arg(self): from RestrictedPython.Utilities import same_type self.failUnless(same_type(object())) def test_sametype_only_two_args_same(self): from RestrictedPython.Utilities import same_type self.failUnless(same_type(object(), object())) def test_sametype_only_two_args_different(self): from RestrictedPython.Utilities import same_type class Foo(object): pass self.failIf(same_type(object(), Foo())) def test_sametype_only_multiple_args_same(self): from RestrictedPython.Utilities import same_type self.failUnless(same_type(object(), object(), object(), object())) def test_sametype_only_multipe_args_one_different(self): from RestrictedPython.Utilities import same_type class Foo(object): pass self.failIf(same_type(object(), object(), Foo())) def test_test_single_value_true(self): from RestrictedPython.Utilities import test self.failUnless(test(True)) def test_test_single_value_False(self): from RestrictedPython.Utilities import test self.failIf(test(False)) def test_test_even_values_first_true(self): from RestrictedPython.Utilities import test self.assertEqual(test(True, 'first', True, 'second'), 'first') def test_test_even_values_not_first_true(self): from RestrictedPython.Utilities import test self.assertEqual(test(False, 'first', True, 'second'), 'second') def test_test_odd_values_first_true(self): from RestrictedPython.Utilities import test self.assertEqual(test(True, 'first', True, 'second', False), 'first') def test_test_odd_values_not_first_true(self): from RestrictedPython.Utilities import test self.assertEqual(test(False, 'first', True, 'second', False), 'second') def test_test_odd_values_last_true(self): from RestrictedPython.Utilities import test self.assertEqual(test(False, 'first', False, 'second', 'third'), 'third') def test_test_odd_values_last_false(self): from RestrictedPython.Utilities import test self.assertEqual(test(False, 'first', False, 'second', False), False) def test_reorder_with__None(self): from RestrictedPython.Utilities import reorder before = ['a', 'b', 'c', 'd', 'e'] without = ['a', 'c', 'e'] after = reorder(before, without=without) self.assertEqual(after, [('b', 'b'), ('d', 'd')]) def test_reorder_with__not_None(self): from RestrictedPython.Utilities import reorder before = ['a', 'b', 'c', 'd', 'e'] with_ = ['a', 'd'] without = ['a', 'c', 'e'] after = reorder(before, with_=with_, without=without) self.assertEqual(after, [('d', 'd')]) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(UtilitiesTests), )) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope2.13-2.13.21/source/Record/0000755000175000017500000000000012214017456014657 5ustar arnauarnauzope2.13-2.13.21/source/Record/setup.py0000644000175000017500000000276712214017456016405 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from os.path import join from setuptools import setup, find_packages, Extension setup(name='Record', version = '2.13.0', url='http://pypi.python.org/pypi/Record', license='ZPL 2.1', description="Special Record objects used in Zope2.", author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', long_description=open('README.txt').read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), package_dir={'': 'src'}, ext_modules=[Extension( name='Record._Record', include_dirs=['include', 'src'], sources=[join('src', 'Record', '_Record.c')], depends=[join('include', 'ExtensionClass', 'ExtensionClass.h')]), ], install_requires=['ExtensionClass'], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/Record/PKG-INFO0000644000175000017500000000104512214017456015754 0ustar arnauarnauMetadata-Version: 1.0 Name: Record Version: 2.13.0 Summary: Special Record objects used in Zope2. Home-page: http://pypi.python.org/pypi/Record Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== Record provides special objects used in some Zope2 internals like ZRDB. Changelog ========= 2.13.0 (2010-03-30) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Record/pip-egg-info/0000755000175000017500000000000012214017456017140 5ustar arnauarnauzope2.13-2.13.21/source/Record/pip-egg-info/Record.egg-info/0000755000175000017500000000000012214017456022050 5ustar arnauarnauzope2.13-2.13.21/source/Record/pip-egg-info/Record.egg-info/PKG-INFO0000644000175000017500000000104512214017456023145 0ustar arnauarnauMetadata-Version: 1.0 Name: Record Version: 2.13.0 Summary: Special Record objects used in Zope2. Home-page: http://pypi.python.org/pypi/Record Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== Record provides special objects used in some Zope2 internals like ZRDB. Changelog ========= 2.13.0 (2010-03-30) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Record/pip-egg-info/Record.egg-info/dependency_links.txt0000644000175000017500000000000112214017456026116 0ustar arnauarnau zope2.13-2.13.21/source/Record/pip-egg-info/Record.egg-info/requires.txt0000644000175000017500000000001612214017456024445 0ustar arnauarnauExtensionClasszope2.13-2.13.21/source/Record/pip-egg-info/Record.egg-info/top_level.txt0000644000175000017500000000000712214017456024577 0ustar arnauarnauRecord zope2.13-2.13.21/source/Record/pip-egg-info/Record.egg-info/SOURCES.txt0000644000175000017500000000052412214017456023735 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/Record.egg-info/PKG-INFO pip-egg-info/Record.egg-info/SOURCES.txt pip-egg-info/Record.egg-info/dependency_links.txt pip-egg-info/Record.egg-info/not-zip-safe pip-egg-info/Record.egg-info/requires.txt pip-egg-info/Record.egg-info/top_level.txt src/Record/_Record.c src/Record/__init__.py src/Record/tests.pyzope2.13-2.13.21/source/Record/pip-egg-info/Record.egg-info/not-zip-safe0000644000175000017500000000000112214017456024276 0ustar arnauarnau zope2.13-2.13.21/source/Record/README.txt0000644000175000017500000000013312214017456016352 0ustar arnauarnauOverview ======== Record provides special objects used in some Zope2 internals like ZRDB. zope2.13-2.13.21/source/Record/setup.cfg0000644000175000017500000000007312214017456016500 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/Record/include/0000755000175000017500000000000012214017456016302 5ustar arnauarnauzope2.13-2.13.21/source/Record/include/ExtensionClass/0000755000175000017500000000000012214017456021244 5ustar arnauarnauzope2.13-2.13.21/source/Record/include/ExtensionClass/pickle/0000755000175000017500000000000012214017456022513 5ustar arnauarnauzope2.13-2.13.21/source/Record/include/ExtensionClass/pickle/pickle.c0000644000175000017500000002202312214017456024125 0ustar arnauarnau/* Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. */ /* Reusable pickle support code This is "includeware", meant to be used through a C include */ /* It's a dang shame we can't inherit __get/setstate__ from object :( */ static PyObject *str__slotnames__, *copy_reg_slotnames, *__newobj__; static PyObject *str__getnewargs__, *str__getstate__; #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) typedef int Py_ssize_t; #define PY_SSIZE_T_MAX INT_MAX #define PY_SSIZE_T_MIN INT_MIN #endif static int pickle_setup(void) { PyObject *copy_reg; int r = -1; #define DEFINE_STRING(S) \ if(! (str ## S = PyString_FromString(# S))) return -1 DEFINE_STRING(__slotnames__); DEFINE_STRING(__getnewargs__); DEFINE_STRING(__getstate__); #undef DEFINE_STRING copy_reg = PyImport_ImportModule("copy_reg"); if (copy_reg == NULL) return -1; copy_reg_slotnames = PyObject_GetAttrString(copy_reg, "_slotnames"); if (copy_reg_slotnames == NULL) goto end; __newobj__ = PyObject_GetAttrString(copy_reg, "__newobj__"); if (__newobj__ == NULL) goto end; r = 0; end: Py_DECREF(copy_reg); return r; } static PyObject * pickle_slotnames(PyTypeObject *cls) { PyObject *slotnames; slotnames = PyDict_GetItem(cls->tp_dict, str__slotnames__); if (slotnames != NULL) { Py_INCREF(slotnames); return slotnames; } slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames, (PyObject*)cls, NULL); if (slotnames != NULL && slotnames != Py_None && ! PyList_Check(slotnames)) { PyErr_SetString(PyExc_TypeError, "copy_reg._slotnames didn't return a list or None"); Py_DECREF(slotnames); slotnames = NULL; } return slotnames; } static PyObject * pickle_copy_dict(PyObject *state) { PyObject *copy, *key, *value; char *ckey; Py_ssize_t pos = 0; Py_ssize_t nr; copy = PyDict_New(); if (copy == NULL) return NULL; if (state == NULL) return copy; while ((nr = PyDict_Next(state, &pos, &key, &value))) { if (nr < 0) goto err; if (key && PyString_Check(key)) { ckey = PyString_AS_STRING(key); if (*ckey == '_' && (ckey[1] == 'v' || ckey[1] == 'p') && ckey[2] == '_') /* skip volatile and persistent */ continue; } if (key != NULL && value != NULL && (PyObject_SetItem(copy, key, value) < 0) ) goto err; } return copy; err: Py_DECREF(copy); return NULL; } static char pickle___getstate__doc[] = "Get the object serialization state\n" "\n" "If the object has no assigned slots and has no instance dictionary, then \n" "None is returned.\n" "\n" "If the object has no assigned slots and has an instance dictionary, then \n" "the a copy of the instance dictionary is returned. The copy has any items \n" "with names starting with '_v_' or '_p_' ommitted.\n" "\n" "If the object has assigned slots, then a two-element tuple is returned. \n" "The first element is either None or a copy of the instance dictionary, \n" "as described above. The second element is a dictionary with items \n" "for each of the assigned slots.\n" ; static PyObject * pickle___getstate__(PyObject *self) { PyObject *slotnames=NULL, *slots=NULL, *state=NULL; PyObject **dictp; int n=0; slotnames = pickle_slotnames(self->ob_type); if (slotnames == NULL) return NULL; dictp = _PyObject_GetDictPtr(self); if (dictp) state = pickle_copy_dict(*dictp); else { state = Py_None; Py_INCREF(state); } if (slotnames != Py_None) { int i; slots = PyDict_New(); if (slots == NULL) goto end; for (i = 0; i < PyList_GET_SIZE(slotnames); i++) { PyObject *name, *value; char *cname; name = PyList_GET_ITEM(slotnames, i); if (PyString_Check(name)) { cname = PyString_AS_STRING(name); if (*cname == '_' && (cname[1] == 'v' || cname[1] == 'p') && cname[2] == '_') /* skip volatile and persistent */ continue; } value = PyObject_GetAttr(self, name); if (value == NULL) PyErr_Clear(); else { int err = PyDict_SetItem(slots, name, value); Py_DECREF(value); if (err) goto end; n++; } } } if (n) state = Py_BuildValue("(NO)", state, slots); end: Py_XDECREF(slotnames); Py_XDECREF(slots); return state; } static int pickle_setattrs_from_dict(PyObject *self, PyObject *dict) { PyObject *key, *value; Py_ssize_t pos = 0; if (! PyDict_Check(dict)) { PyErr_SetString(PyExc_TypeError, "Expected dictionary"); return -1; } while (PyDict_Next(dict, &pos, &key, &value)) { if (key != NULL && value != NULL && (PyObject_SetAttr(self, key, value) < 0) ) return -1; } return 0; } static char pickle___setstate__doc[] = "Set the object serialization state\n" "\n" "The state should be in one of 3 forms:\n" "\n" "- None\n" "\n" " Ignored\n" "\n" "- A dictionary\n" "\n" " In this case, the object's instance dictionary will be cleared and \n" " updated with the new state.\n" "\n" "- A two-tuple with a string as the first element. \n" "\n" " In this case, the method named by the string in the first element will be\n" " called with the second element.\n" "\n" " This form supports migration of data formats.\n" "\n" "- A two-tuple with None or a Dictionary as the first element and\n" " with a dictionary as the second element.\n" "\n" " If the first element is not None, then the object's instance dictionary \n" " will be cleared and updated with the value.\n" "\n" " The items in the second element will be assigned as attributes.\n" ; static PyObject * pickle___setstate__(PyObject *self, PyObject *state) { PyObject *slots=NULL; if (PyTuple_Check(state)) { if (! PyArg_ParseTuple(state, "OO", &state, &slots)) return NULL; } if (state != Py_None) { PyObject **dict; dict = _PyObject_GetDictPtr(self); if (dict) { if (*dict == NULL) { *dict = PyDict_New(); if (*dict == NULL) return NULL; } } if (*dict != NULL) { PyDict_Clear(*dict); if (PyDict_Update(*dict, state) < 0) return NULL; } else if (pickle_setattrs_from_dict(self, state) < 0) return NULL; } if (slots != NULL && pickle_setattrs_from_dict(self, slots) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } static char pickle___getnewargs__doc[] = "Get arguments to be passed to __new__\n" ; static PyObject * pickle___getnewargs__(PyObject *self) { return PyTuple_New(0); } static char pickle___reduce__doc[] = "Reduce an object to contituent parts for serialization\n" ; static PyObject * pickle___reduce__(PyObject *self) { PyObject *args=NULL, *bargs=0, *state=NULL; int l, i; /* we no longer require '__getnewargs__' to be defined but use it if it is */ PyObject *getnewargs=NULL; getnewargs = PyObject_GetAttr(self, str__getnewargs__); if (getnewargs) bargs = PyEval_CallObject(getnewargs, (PyObject *)NULL); else { PyErr_Clear(); bargs = PyTuple_New(0); } l = PyTuple_Size(bargs); if (l < 0) goto end; args = PyTuple_New(l+1); if (args == NULL) goto end; Py_INCREF(self->ob_type); PyTuple_SET_ITEM(args, 0, (PyObject*)(self->ob_type)); for (i = 0; i < l; i++) { Py_INCREF(PyTuple_GET_ITEM(bargs, i)); PyTuple_SET_ITEM(args, i+1, PyTuple_GET_ITEM(bargs, i)); } state = PyObject_CallMethodObjArgs(self, str__getstate__, NULL); if (state == NULL) goto end; state = Py_BuildValue("(OON)", __newobj__, args, state); end: Py_XDECREF(bargs); Py_XDECREF(args); Py_XDECREF(getnewargs); return state; } #define PICKLE_GETSTATE_DEF \ {"__getstate__", (PyCFunction)pickle___getstate__, METH_NOARGS, \ pickle___getstate__doc}, #define PICKLE_SETSTATE_DEF \ {"__setstate__", (PyCFunction)pickle___setstate__, METH_O, \ pickle___setstate__doc}, #define PICKLE_GETNEWARGS_DEF #define PICKLE_REDUCE_DEF \ {"__reduce__", (PyCFunction)pickle___reduce__, METH_NOARGS, \ pickle___reduce__doc}, #define PICKLE_METHODS PICKLE_GETSTATE_DEF PICKLE_SETSTATE_DEF \ PICKLE_GETNEWARGS_DEF PICKLE_REDUCE_DEF zope2.13-2.13.21/source/Record/include/ExtensionClass/_ExtensionClass.c0000644000175000017500000006076512214017456024527 0ustar arnauarnau/* Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE. */ static char _extensionclass_module_documentation[] = "ExtensionClass\n" "\n" "$Id: _ExtensionClass.c 109054 2010-02-14 21:35:34Z hannosch $\n" ; #include "ExtensionClass/ExtensionClass.h" #define EC PyTypeObject static PyObject *str__of__, *str__get__, *str__class_init__, *str__init__; static PyObject *str__bases__, *str__mro__, *str__new__; #define OBJECT(O) ((PyObject *)(O)) #define TYPE(O) ((PyTypeObject *)(O)) static PyTypeObject ExtensionClassType; static PyTypeObject BaseType; static PyObject * of_get(PyObject *self, PyObject *inst, PyObject *cls) { /* Descriptor slot function that calls __of__ */ if (inst && PyExtensionInstance_Check(inst)) return PyObject_CallMethodObjArgs(self, str__of__, inst, NULL); Py_INCREF(self); return self; } PyObject * Base_getattro(PyObject *obj, PyObject *name) { /* This is a modified copy of PyObject_GenericGetAttr. See the change note below. */ PyTypeObject *tp = obj->ob_type; PyObject *descr = NULL; PyObject *res = NULL; descrgetfunc f; long dictoffset; PyObject **dictptr; if (!PyString_Check(name)){ #ifdef Py_USING_UNICODE /* The Unicode to string conversion is done here because the existing tp_setattro slots expect a string object as name and we wouldn't want to break those. */ if (PyUnicode_Check(name)) { name = PyUnicode_AsEncodedString(name, NULL, NULL); if (name == NULL) return NULL; } else #endif { PyErr_SetString(PyExc_TypeError, "attribute name must be string"); return NULL; } } else Py_INCREF(name); if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) goto done; } #if !defined(Py_TPFLAGS_HAVE_VERSION_TAG) /* Inline _PyType_Lookup */ /* this is not quite _PyType_Lookup anymore */ { int i, n; PyObject *mro, *base, *dict; /* Look in tp_dict of types in MRO */ mro = tp->tp_mro; assert(mro != NULL); assert(PyTuple_Check(mro)); n = PyTuple_GET_SIZE(mro); for (i = 0; i < n; i++) { base = PyTuple_GET_ITEM(mro, i); if (PyClass_Check(base)) dict = ((PyClassObject *)base)->cl_dict; else { assert(PyType_Check(base)); dict = ((PyTypeObject *)base)->tp_dict; } assert(dict && PyDict_Check(dict)); descr = PyDict_GetItem(dict, name); if (descr != NULL) break; } } #else descr = _PyType_Lookup(tp, name); #endif Py_XINCREF(descr); f = NULL; if (descr != NULL && PyType_HasFeature(descr->ob_type, Py_TPFLAGS_HAVE_CLASS)) { f = descr->ob_type->tp_descr_get; if (f != NULL && PyDescr_IsData(descr)) { res = f(descr, obj, (PyObject *)obj->ob_type); Py_DECREF(descr); goto done; } } /* Inline _PyObject_GetDictPtr */ dictoffset = tp->tp_dictoffset; if (dictoffset != 0) { PyObject *dict; if (dictoffset < 0) { int tsize; size_t size; tsize = ((PyVarObject *)obj)->ob_size; if (tsize < 0) tsize = -tsize; size = _PyObject_VAR_SIZE(tp, tsize); dictoffset += (long)size; assert(dictoffset > 0); assert(dictoffset % SIZEOF_VOID_P == 0); } dictptr = (PyObject **) ((char *)obj + dictoffset); dict = *dictptr; if (dict != NULL) { Py_INCREF(dict); res = PyDict_GetItem(dict, name); if (res != NULL) { Py_INCREF(res); Py_XDECREF(descr); Py_DECREF(dict); /* CHANGED! If the tp_descr_get of res is of_get, then call it. */ if (PyObject_TypeCheck(res->ob_type, &ExtensionClassType) && res->ob_type->tp_descr_get != NULL) { PyObject *tres; tres = res->ob_type->tp_descr_get( res, obj, OBJECT(obj->ob_type)); Py_DECREF(res); res = tres; } goto done; } Py_DECREF(dict); } } if (f != NULL) { res = f(descr, obj, (PyObject *)obj->ob_type); Py_DECREF(descr); goto done; } if (descr != NULL) { res = descr; /* descr was already increfed above */ goto done; } /* CHANGED: Just use the name. Don't format. */ PyErr_SetObject(PyExc_AttributeError, name); done: Py_DECREF(name); return res; } #include "pickle/pickle.c" static struct PyMethodDef Base_methods[] = { PICKLE_METHODS {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static EC BaseType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "Base", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* tp_getattro */ (getattrofunc)Base_getattro, 0, 0, (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif ), "Standard ExtensionClass base type", 0, 0, 0, 0, 0, 0, Base_methods, }; static EC NoInstanceDictionaryBaseType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "NoInstanceDictionaryBase", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif ), "Base types for subclasses without instance dictionaries", }; static PyObject * EC_new(PyTypeObject *self, PyObject *args, PyObject *kw) { PyObject *name, *bases=NULL, *dict=NULL; PyObject *new_bases=NULL, *new_args, *result; int have_base = 0, i; if (kw && PyObject_IsTrue(kw)) { PyErr_SetString(PyExc_TypeError, "Keyword arguments are not supported"); return NULL; } if (!PyArg_ParseTuple(args, "O|O!O!", &name, &PyTuple_Type, &bases, &PyDict_Type, &dict)) return NULL; /* Make sure Base is in bases */ if (bases) { for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { if (PyObject_TypeCheck(PyTuple_GET_ITEM(bases, i), &ExtensionClassType)) { have_base = 1; break; } } if (! have_base) { new_bases = PyTuple_New(PyTuple_GET_SIZE(bases) + 1); if (new_bases == NULL) return NULL; for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { Py_XINCREF(PyTuple_GET_ITEM(bases, i)); PyTuple_SET_ITEM(new_bases, i, PyTuple_GET_ITEM(bases, i)); } Py_INCREF(OBJECT(&BaseType)); PyTuple_SET_ITEM(new_bases, PyTuple_GET_SIZE(bases), OBJECT(&BaseType)); } } else { new_bases = Py_BuildValue("(O)", &BaseType); if (new_bases == NULL) return NULL; } if (new_bases) { if (dict) new_args = Py_BuildValue("OOO", name, new_bases, dict); else new_args = Py_BuildValue("OO", name, new_bases); Py_DECREF(new_bases); if (new_args == NULL) return NULL; result = PyType_Type.tp_new(self, new_args, kw); Py_DECREF(new_args); } else { result = PyType_Type.tp_new(self, args, kw); /* We didn't have to add Base, so maybe NoInstanceDictionaryBase is in the bases. We need to check if it was. If it was, we need to suppress instance dictionary support. */ for (i = 0; i < PyTuple_GET_SIZE(bases); i++) { if ( PyObject_TypeCheck(PyTuple_GET_ITEM(bases, i), &ExtensionClassType) && PyType_IsSubtype(TYPE(PyTuple_GET_ITEM(bases, i)), &NoInstanceDictionaryBaseType) ) { TYPE(result)->tp_dictoffset = 0; break; } } } return result; } /* set up __get__, if necessary */ static int EC_init_of(PyTypeObject *self) { PyObject *__of__; __of__ = PyObject_GetAttr(OBJECT(self), str__of__); if (__of__) { Py_DECREF(__of__); if (self->tp_descr_get) { if (self->tp_descr_get != of_get) { PyErr_SetString(PyExc_TypeError, "Can't mix __of__ and descriptors"); return -1; } } else self->tp_descr_get = of_get; } else { PyErr_Clear(); if (self->tp_descr_get == of_get) self->tp_descr_get = NULL; } return 0; } static int EC_init(PyTypeObject *self, PyObject *args, PyObject *kw) { PyObject *__class_init__, *r; if (PyType_Type.tp_init(OBJECT(self), args, kw) < 0) return -1; if (self->tp_dict != NULL) { r = PyDict_GetItemString(self->tp_dict, "__doc__"); if ((r == Py_None) && (PyDict_DelItemString(self->tp_dict, "__doc__") < 0) ) return -1; } if (EC_init_of(self) < 0) return -1; /* Call __class_init__ */ __class_init__ = PyObject_GetAttr(OBJECT(self), str__class_init__); if (__class_init__ == NULL) { PyErr_Clear(); return 0; } if (! (PyMethod_Check(__class_init__) && PyMethod_GET_FUNCTION(__class_init__) ) ) { Py_DECREF(__class_init__); PyErr_SetString(PyExc_TypeError, "Invalid type for __class_init__"); return -1; } r = PyObject_CallFunctionObjArgs(PyMethod_GET_FUNCTION(__class_init__), OBJECT(self), NULL); Py_DECREF(__class_init__); if (! r) return -1; Py_DECREF(r); return 0; } static int EC_setattro(PyTypeObject *type, PyObject *name, PyObject *value) { /* We want to allow setting attributes of builti-in types, because EC did in the past and there's code that relies on it. We can't really set slots though, but I don't think we need to. There's no good way to spot slots. We could use a lame rule like names that begin and end with __s and have just 4 _s smell too much like slots. */ if (! (type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { char *cname; int l; cname = PyString_AsString(name); if (cname == NULL) return -1; l = PyString_GET_SIZE(name); if (l > 4 && cname[0] == '_' && cname[1] == '_' && cname[l-1] == '_' && cname[l-2] == '_' ) { char *c; c = strchr(cname+2, '_'); if (c != NULL && (c - cname) >= (l-2)) { PyErr_Format (PyExc_TypeError, "can't set attributes of built-in/extension type '%s' if the " "attribute name begins and ends with __ and contains only " "4 _ characters", type->tp_name ); return -1; } } if (PyObject_GenericSetAttr(OBJECT(type), name, value) < 0) return -1; } else if (PyType_Type.tp_setattro(OBJECT(type), name, value) < 0) return -1; #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(type); #endif return 0; } static PyObject * inheritedAttribute(PyTypeObject *self, PyObject *name) { int i; PyObject *d, *cls; for (i = 1; i < PyTuple_GET_SIZE(self->tp_mro); i++) { cls = PyTuple_GET_ITEM(self->tp_mro, i); if (PyType_Check(cls)) d = ((PyTypeObject *)cls)->tp_dict; else if (PyClass_Check(cls)) d = ((PyClassObject *)cls)->cl_dict; else /* Unrecognized thing, punt */ d = NULL; if ((d == NULL) || (PyDict_GetItem(d, name) == NULL)) continue; return PyObject_GetAttr(cls, name); } PyErr_SetObject(PyExc_AttributeError, name); return NULL; } static PyObject * __basicnew__(PyObject *self) { return PyObject_CallMethodObjArgs(self, str__new__, self, NULL); } static int append_new(PyObject *result, PyObject *v) { int contains; if (v == OBJECT(&BaseType) || v == OBJECT(&PyBaseObject_Type)) return 0; /* Don't add these until end */ contains = PySequence_Contains(result, v); if (contains != 0) return contains; return PyList_Append(result, v); } static int copy_mro(PyObject *mro, PyObject *result) { PyObject *base; int i, l; l = PyTuple_Size(mro); if (l < 0) return -1; for (i=0; i < l; i++) { base = PyTuple_GET_ITEM(mro, i); if (append_new(result, base) < 0) return -1; } return 0; } static int copy_classic(PyObject *base, PyObject *result) { PyObject *bases, *basebase; int i, l, err=-1; if (append_new(result, base) < 0) return -1; bases = PyObject_GetAttr(base, str__bases__); if (bases == NULL) return -1; l = PyTuple_Size(bases); if (l < 0) goto end; for (i=0; i < l; i++) { basebase = PyTuple_GET_ITEM(bases, i); if (copy_classic(basebase, result) < 0) goto end; } err = 0; end: Py_DECREF(bases); return err; } static PyObject * mro(PyTypeObject *self) { /* Compute an MRO for a class */ PyObject *result, *base, *basemro, *mro=NULL; int i, l, err; result = PyList_New(0); if (result == NULL) return NULL; if (PyList_Append(result, OBJECT(self)) < 0) goto end; l = PyTuple_Size(self->tp_bases); if (l < 0) goto end; for (i=0; i < l; i++) { base = PyTuple_GET_ITEM(self->tp_bases, i); if (base == NULL) continue; basemro = PyObject_GetAttr(base, str__mro__); if (basemro != NULL) { /* Type */ err = copy_mro(basemro, result); Py_DECREF(basemro); if (err < 0) goto end; } else { PyErr_Clear(); if (copy_classic(base, result) < 0) goto end; } } if (self != &BaseType && PyList_Append(result, OBJECT(&BaseType)) < 0) goto end; if (PyList_Append(result, OBJECT(&PyBaseObject_Type)) < 0) goto end; l = PyList_GET_SIZE(result); mro = PyTuple_New(l); if (mro == NULL) goto end; for (i=0; i < l; i++) { Py_INCREF(PyList_GET_ITEM(result, i)); PyTuple_SET_ITEM(mro, i, PyList_GET_ITEM(result, i)); } end: Py_DECREF(result); return mro; } static struct PyMethodDef EC_methods[] = { {"__basicnew__", (PyCFunction)__basicnew__, METH_NOARGS, "Create a new empty object"}, {"inheritedAttribute", (PyCFunction)inheritedAttribute, METH_O, "Look up an inherited attribute"}, {"mro", (PyCFunction)mro, METH_NOARGS, "Compute an mro using the 'encalsulated base' scheme"}, {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static PyTypeObject ExtensionClassType = { PyObject_HEAD_INIT(NULL) /* ob_size */ 0, /* tp_name */ "ExtensionClass." "ExtensionClass", /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ (destructor)0, /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, /* tp_compare */ (cmpfunc)0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ (hashfunc)0, /* tp_call */ (ternaryfunc)0, /* tp_str */ (reprfunc)0, /* tp_getattro */ (getattrofunc)0, /* tp_setattro */ (setattrofunc)EC_setattro, /* tp_as_buffer */ 0, /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE #ifdef Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_HAVE_VERSION_TAG #endif , /* tp_doc */ "Meta-class for extension classes", /* tp_traverse */ (traverseproc)0, /* tp_clear */ (inquiry)0, /* tp_richcompare */ (richcmpfunc)0, /* tp_weaklistoffset */ (long)0, /* tp_iter */ (getiterfunc)0, /* tp_iternext */ (iternextfunc)0, /* tp_methods */ EC_methods, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* internal use */ /* tp_descr_get */ (descrgetfunc)0, /* tp_descr_set */ (descrsetfunc)0, /* tp_dictoffset */ 0, /* tp_init */ (initproc)EC_init, /* tp_alloc */ (allocfunc)0, /* tp_new */ (newfunc)EC_new, /* tp_free */ 0, /* Low-level free-mem routine */ /* tp_is_gc */ (inquiry)0, /* For PyObject_IS_GC */ }; static PyObject * debug(PyObject *self, PyObject *o) { Py_INCREF(Py_None); return Py_None; } static PyObject * pmc_init_of(PyObject *self, PyObject *args) { PyObject *o; if (! PyArg_ParseTuple(args, "O!", (PyObject *)&ExtensionClassType, &o)) return NULL; if (EC_init_of((PyTypeObject *)o) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } /* List of methods defined in the module */ static struct PyMethodDef ec_methods[] = { {"debug", (PyCFunction)debug, METH_O, ""}, {"pmc_init_of", (PyCFunction)pmc_init_of, METH_VARARGS, "Initialize __get__ for classes that define __of__"}, {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; static PyObject * EC_findiattrs_(PyObject *self, char *cname) { PyObject *name, *r; name = PyString_FromString(cname); if (name == NULL) return NULL; r = ECBaseType->tp_getattro(self, name); Py_DECREF(name); return r; } static PyObject * ec_new_for_custom_dealloc(PyTypeObject *type, PyObject *args, PyObject *kw) { /* This is for EC's that have deallocs. For these, we need to incref the type when we create an instance, because the deallocs will decref the type. */ PyObject *r; r = PyType_GenericNew(type, args, kw); if (r) { Py_INCREF(type); } return r; } static int ec_init(PyObject *self, PyObject *args, PyObject *kw) { PyObject *r, *__init__; __init__ = PyObject_GetAttr(self, str__init__); if (__init__ == NULL) return -1; r = PyObject_Call(__init__, args, kw); Py_DECREF(__init__); if (r == NULL) return -1; Py_DECREF(r); return 0; } static int PyExtensionClass_Export_(PyObject *dict, char *name, PyTypeObject *typ) { long ecflags = 0; PyMethodDef *pure_methods = NULL, *mdef = NULL; PyObject *m; if (typ->tp_flags == 0) { /* Old-style EC */ if (typ->tp_traverse) { /* ExtensionClasses stick there methods in the tp_traverse slot */ mdef = (PyMethodDef *)typ->tp_traverse; if (typ->tp_basicsize <= sizeof(_emptyobject)) /* Pure mixin. We want rebindable methods */ pure_methods = mdef; else typ->tp_methods = mdef; typ->tp_traverse = NULL; /* Look for __init__ method */ for (; mdef->ml_name; mdef++) { if (strcmp(mdef->ml_name, "__init__") == 0) { /* we have an old-style __init__, install a special slot */ typ->tp_init = ec_init; break; } } } if (typ->tp_clear) { /* ExtensionClasses stick there flags in the tp_clear slot */ ecflags = (long)(typ->tp_clear); /* Some old-style flags were set */ if ((ecflags & EXTENSIONCLASS_BINDABLE_FLAG) && typ->tp_descr_get == NULL) /* We have __of__-style binding */ typ->tp_descr_get = of_get; } typ->tp_clear = NULL; typ->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; if (typ->tp_dealloc != NULL) typ->tp_new = ec_new_for_custom_dealloc; } typ->ob_type = ECExtensionClassType; if (ecflags & EXTENSIONCLASS_NOINSTDICT_FLAG) typ->tp_base = &NoInstanceDictionaryBaseType; else typ->tp_base = &BaseType; if (typ->tp_new == NULL) typ->tp_new = PyType_GenericNew; if (PyType_Ready(typ) < 0) return -1; if (pure_methods) { /* We had pure methods. We want to be able to rebind these, so we'll make them ordinary method wrappers around method descrs */ for (; pure_methods->ml_name; pure_methods++) { m = PyDescr_NewMethod(ECBaseType, pure_methods); if (! m) return -1; m = PyMethod_New((PyObject *)m, NULL, (PyObject *)ECBaseType); if (! m) return -1; if (PyDict_SetItemString(typ->tp_dict, pure_methods->ml_name, m) < 0) return -1; } #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(typ); #endif } else if (mdef && mdef->ml_name) { /* Blast, we have to stick __init__ in the dict ourselves because PyType_Ready probably stuck a wrapper for ec_init in instead. */ m = PyDescr_NewMethod(typ, mdef); if (! m) return -1; if (PyDict_SetItemString(typ->tp_dict, mdef->ml_name, m) < 0) return -1; #ifdef Py_TPFLAGS_HAVE_VERSION_TAG PyType_Modified(typ); #endif } if (PyMapping_SetItemString(dict, name, (PyObject*)typ) < 0) return -1; return 0; } PyObject * PyECMethod_New_(PyObject *callable, PyObject *inst) { if (! PyExtensionInstance_Check(inst)) { PyErr_SetString(PyExc_TypeError, "Can't bind non-ExtensionClass instance."); return NULL; } if (PyMethod_Check(callable)) { if (callable->ob_refcnt == 1) { Py_XDECREF(((PyMethodObject*)callable)->im_self); Py_INCREF(inst); ((PyMethodObject*)callable)->im_self = inst; Py_INCREF(callable); return callable; } else return callable->ob_type->tp_descr_get( callable, inst, ((PyMethodObject*)callable)->im_class); } else return PyMethod_New(callable, inst, (PyObject*)(ECBaseType)); } static struct ExtensionClassCAPIstruct TrueExtensionClassCAPI = { EC_findiattrs_, PyExtensionClass_Export_, PyECMethod_New_, &BaseType, &ExtensionClassType, }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC init_ExtensionClass(void) { PyObject *m, *s; if (pickle_setup() < 0) return; #define DEFINE_STRING(S) \ if(! (str ## S = PyString_FromString(# S))) return DEFINE_STRING(__of__); DEFINE_STRING(__get__); DEFINE_STRING(__class_init__); DEFINE_STRING(__init__); DEFINE_STRING(__bases__); DEFINE_STRING(__mro__); DEFINE_STRING(__new__); #undef DEFINE_STRING PyExtensionClassCAPI = &TrueExtensionClassCAPI; ExtensionClassType.ob_type = &PyType_Type; ExtensionClassType.tp_base = &PyType_Type; ExtensionClassType.tp_traverse = PyType_Type.tp_traverse; ExtensionClassType.tp_clear = PyType_Type.tp_clear; /* Initialize types: */ if (PyType_Ready(&ExtensionClassType) < 0) return; BaseType.ob_type = &ExtensionClassType; BaseType.tp_base = &PyBaseObject_Type; BaseType.tp_new = PyType_GenericNew; if (PyType_Ready(&BaseType) < 0) return; NoInstanceDictionaryBaseType.ob_type = &ExtensionClassType; NoInstanceDictionaryBaseType.tp_base = &BaseType; NoInstanceDictionaryBaseType.tp_new = PyType_GenericNew; if (PyType_Ready(&NoInstanceDictionaryBaseType) < 0) return; /* Create the module and add the functions */ m = Py_InitModule3("_ExtensionClass", ec_methods, _extensionclass_module_documentation); if (m == NULL) return; s = PyCObject_FromVoidPtr(PyExtensionClassCAPI, NULL); if (PyModule_AddObject(m, "CAPI2", s) < 0) return; /* Add types: */ if (PyModule_AddObject(m, "ExtensionClass", (PyObject *)&ExtensionClassType) < 0) return; if (PyModule_AddObject(m, "Base", (PyObject *)&BaseType) < 0) return; if (PyModule_AddObject(m, "NoInstanceDictionaryBase", (PyObject *)&NoInstanceDictionaryBaseType) < 0) return; } zope2.13-2.13.21/source/Record/include/ExtensionClass/__init__.py0000644000175000017500000000523712214017456023364 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ExtensionClass Extension Class exists to support types derived from the old ExtensionType meta-class that preceeded Python 2.2 and new-style classes. As a meta-class, ExtensionClass provides the following features: - Support for a class initialiser: >>> from ExtensionClass import ExtensionClass, Base >>> class C(Base): ... def __class_init__(self): ... print 'class init called' ... print self.__name__ ... def bar(self): ... return 'bar called' class init called C >>> c = C() >>> int(c.__class__ is C) 1 >>> int(c.__class__ is type(c)) 1 - Making sure that every instance of the meta-class has Base as a base class: >>> class X: ... __metaclass__ = ExtensionClass >>> Base in X.__mro__ 1 - Provide an inheritedAttribute method for looking up attributes in base classes: >>> class C2(C): ... def bar(*a): ... return C2.inheritedAttribute('bar')(*a), 42 class init called C2 >>> o = C2() >>> o.bar() ('bar called', 42) This is for compatability with old code. New code should use super instead. The base class, Base, exists mainly to support the __of__ protocol. The __of__ protocol is similar to __get__ except that __of__ is called when an implementor is retrieved from an instance as well as from a class: >>> class O(Base): ... def __of__(*a): ... return a >>> o1 = O() >>> o2 = O() >>> C.o1 = o1 >>> c.o2 = o2 >>> c.o1 == (o1, c) 1 >>> C.o1 == o1 1 >>> int(c.o2 == (o2, c)) 1 We accomplish this by making a class that implements __of__ a descriptor and treating all descriptor ExtensionClasses this way. That is, if an extension class is a descriptor, it's __get__ method will be called even when it is retrieved from an instance. >>> class O(Base): ... def __get__(*a): ... return a ... >>> o1 = O() >>> o2 = O() >>> C.o1 = o1 >>> c.o2 = o2 >>> int(c.o1 == (o1, c, type(c))) 1 >>> int(C.o1 == (o1, None, type(c))) 1 >>> int(c.o2 == (o2, c, type(c))) 1 $Id: __init__.py 40218 2005-11-18 14:39:19Z andreasjung $ """ from _ExtensionClass import * zope2.13-2.13.21/source/Record/include/ExtensionClass/tests.py0000644000175000017500000004744512214017456022776 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ $Id: tests.py 109298 2010-02-22 17:30:41Z hannosch $ """ from ExtensionClass import * import pickle def print_dict(d): d = d.items() d.sort() print '{%s}' % (', '.join( [('%r: %r' % (k, v)) for (k, v) in d] )) def test_mixing(): """Test working with a classic class >>> class Classic: ... def x(self): ... return 42 >>> class O(Base): ... def __of__(*a): ... return a >>> class O2(Classic, O): ... def __of__(*a): ... return (O2.inheritedAttribute('__of__')(*a), ... O2.inheritedAttribute('x')(a[0])) >>> class C(Base): ... def __class_init__(self): ... print 'class init called' ... print self.__name__ ... def bar(self): ... return 'bar called' class init called C >>> c = C() >>> o2 = O2() >>> c.o2 = o2 >>> int(c.o2 == ((o2, c), 42)) 1 Test working with a new style >>> class Modern(object): ... def x(self): ... return 42 >>> class O2(Modern, O): ... def __of__(*a): ... return (O2.inheritedAttribute('__of__')(*a), ... O2.inheritedAttribute('x')(a[0])) >>> o2 = O2() >>> c.o2 = o2 >>> int(c.o2 == ((o2, c), 42)) 1 """ def test_class_creation_under_stress(): """ >>> for i in range(100): ... class B(Base): ... print i, ... if i and i%20 == 0: ... print 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 >>> import gc >>> x = gc.collect() """ def old_test_add(): """test_add.py from old EC >>> class foo(Base): ... def __add__(self,other): print 'add called' >>> foo()+foo() add called """ def proper_error_on_deleattr(): """ Florent Guillaume wrote: ... Excellent. Will it also fix this particularity of ExtensionClass: >>> class A(Base): ... def foo(self): ... self.gee ... def bar(self): ... del self.gee >>> a=A() >>> a.foo() Traceback (most recent call last): ... AttributeError: gee >>> a.bar() Traceback (most recent call last): ... AttributeError: 'A' object has no attribute 'gee' I.e., the fact that KeyError is raised whereas a normal class would raise AttributeError. """ def test_NoInstanceDictionaryBase(): """ >>> class B(NoInstanceDictionaryBase): pass ... >>> B().__dict__ Traceback (most recent call last): ... AttributeError: This object has no __dict__ >>> class B(NoInstanceDictionaryBase): ... __slots__ = ('a', 'b') ... >>> class BB(B): pass ... >>> b = BB() >>> b.__dict__ Traceback (most recent call last): ... AttributeError: This object has no __dict__ >>> b.a = 1 >>> b.b = 2 >>> b.a 1 >>> b.b 2 """ def test__basicnew__(): """ >>> x = Simple.__basicnew__() >>> x.__dict__ {} """ def cmpattrs(self, other, *attrs): for attr in attrs: if attr[:3] in ('_v_', '_p_'): continue c = cmp(getattr(self, attr, None), getattr(other, attr, None)) if c: return c return 0 class Simple(Base): def __init__(self, name, **kw): self.__name__ = name self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', *(self.__dict__.keys())) def test_basic_pickling(): """ >>> x = Simple('x', aaa=1, bbb='foo') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> print_dict(x.__getstate__()) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> f, (c,), state = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Simple' >>> print_dict(state) {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.__setstate__({'z': 1}) >>> x.__dict__ {'z': 1} """ class Custom(Simple): def __new__(cls, x, y): r = Base.__new__(cls) r.x, r.y = x, y return r def __init__(self, x, y): self.a = 42 def __getnewargs__(self): return self.x, self.y def __getstate__(self): return self.a def __setstate__(self, a): self.a = a def test_pickling_w_overrides(): """ >>> x = Custom('x', 'y') >>> x.a = 99 >>> (f, (c, ax, ay), a) = x.__reduce__() >>> f.__name__ '__newobj__' >>> f.__module__ 'copy_reg' >>> c.__name__ 'Custom' >>> ax, ay, a ('x', 'y', 99) >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class Slotted(Base): __slots__ = 's1', 's2', '_p_splat', '_v_eek' def __init__(self, s1, s2): self.s1, self.s2 = s1, s2 self._v_eek = 1 self._p_splat = 2 class SubSlotted(Slotted): __slots__ = 's3', 's4' def __init__(self, s1, s2, s3): Slotted.__init__(self, s1, s2) self.s3 = s3 def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4') def test_pickling_w_slots_only(): """ >>> x = SubSlotted('x', 'y', 'z') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> d >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ class SubSubSlotted(SubSlotted): def __init__(self, s1, s2, s3, **kw): SubSlotted.__init__(self, s1, s2, s3) self.__dict__.update(kw) self._v_favorite_color = 'blue' self._p_foo = 'bar' def __cmp__(self, other): return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4', *(self.__dict__.keys())) def test_pickling_w_slots(): """ >>> x = SubSubSlotted('x', 'y', 'z', aaa=1, bbb='foo') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {'aaa': 1, 'bbb': 'foo'} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ def test_pickling_w_slots_w_empty_dict(): """ >>> x = SubSubSlotted('x', 'y', 'z') >>> x.__getnewargs__() Traceback (most recent call last): ... AttributeError: __getnewargs__ >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 >>> x.s4 = 'spam' >>> d, s = x.__getstate__() >>> print_dict(d) {} >>> print_dict(s) {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'} >>> pickle.loads(pickle.dumps(x)) == x 1 >>> pickle.loads(pickle.dumps(x, 0)) == x 1 >>> pickle.loads(pickle.dumps(x, 1)) == x 1 >>> pickle.loads(pickle.dumps(x, 2)) == x 1 """ def test_setattr_on_extension_type(): """ >>> for name in 'x', '_x', 'x_', '__x_y__', '___x__', '__x___', '_x_': ... setattr(Base, name, 1) ... print getattr(Base, name) ... delattr(Base, name) ... print getattr(Base, name, 0) 1 0 1 0 1 0 1 0 1 0 1 0 1 0 >>> Base.__foo__ = 1 Traceback (most recent call last): ... TypeError: can't set attributes of built-in/extension type """ \ """'ExtensionClass.Base' if the attribute name begins """ \ """and ends with __ and contains only 4 _ characters >>> Base.__foo__ Traceback (most recent call last): ... AttributeError: type object 'ExtensionClass.Base' """ \ """has no attribute '__foo__' >>> del Base.__foo__ Traceback (most recent call last): ... TypeError: can't set attributes of built-in/extension type """ \ """'ExtensionClass.Base' if the attribute name begins """ \ """and ends with __ and contains only 4 _ characters """ def test_mro(): """ExtensionClass method-resolution order The EC MRO is chosen to maximize backward compatibility and provide a model that is easy to reason about. The basic idea is: I'll call this the "encapsulated base" scheme. Consider: >>> class X(Base): ... pass >>> class Y(Base): ... pass >>> class Z(Base): ... pass >>> class C(X, Y, Z): ... def foo(self): ... return 42 When we look up an attribute, we do the following: - Look in C's dictionary first. - Look up the attribute in X. We don't care how we get the attribute from X. If X is a new-style-class, we use the new algorithm. If X is a classic class, we use left-to-right depth-first. If X is an nsEC, use the "encapsulated base" algorithm. If we don't find the attribute in X, look in Y and then in Z, using the same approach. This algorithm will produce backward compatible results, providing the equivalent of left-to-right depth-first for nsECs and classic classes. We'll actually do something less abstract. We'll use a simple algorthm to merge the __mro__ of the base classes, computing an __mro__ for classic classes using the left-to-right depth-first algorithm. We'll basically lay the mros end-to-end left-to-right and remove repeats, keeping the first occurence of each class. >>> [c.__name__ for c in C.__mro__] ['C', 'X', 'Y', 'Z', 'Base', 'object'] For backward-compatability's sake, we actually depart from the above description a bit. We always put Base and object last in the mro, as shown in the example above. The primary reason for this is that object provides a do-nothing __init__ method. It is common practice to mix a C-implemented base class that implements a few methods with a Python class that implements those methods and others. The idea is that the C implementation overrides selected methods in C, so the C subclass is listed first. Unfortunately, because all extension classes are required to subclass Base, and thus, object, the C subclass brings along the __init__ object from objects, which would hide any __init__ method provided by the Python mix-in. Base and object are special in that they are implied by their meta classes. For example, a new-style class always has object as an ancestor, even if it isn't listed as a base: >>> class O: ... __metaclass__ = type >>> [c.__name__ for c in O.__bases__] ['object'] >>> [c.__name__ for c in O.__mro__] ['O', 'object'] Similarly, Base is always an ancestor of an extension class: >>> class E: ... __metaclass__ = ExtensionClass >>> [c.__name__ for c in E.__bases__] ['Base'] >>> [c.__name__ for c in E.__mro__] ['E', 'Base', 'object'] Base and object are generally added soley to get a particular meta class. They aren't used to provide application functionality and really shouldn't be considered when reasoning about where attributes come from. They do provide some useful default functionality and should be included at the end of the mro. Here are more examples: >>> from ExtensionClass import Base >>> class NA(object): ... pass >>> class NB(NA): ... pass >>> class NC(NA): ... pass >>> class ND(NB, NC): ... pass >>> [c.__name__ for c in ND.__mro__] ['ND', 'NB', 'NC', 'NA', 'object'] >>> class EA(Base): ... pass >>> class EB(EA): ... pass >>> class EC(EA): ... pass >>> class ED(EB, EC): ... pass >>> [c.__name__ for c in ED.__mro__] ['ED', 'EB', 'EA', 'EC', 'Base', 'object'] >>> class EE(ED, ND): ... pass >>> [c.__name__ for c in EE.__mro__] ['EE', 'ED', 'EB', 'EA', 'EC', 'ND', 'NB', 'NC', 'NA', 'Base', 'object'] >>> class EF(ND, ED): ... pass >>> [c.__name__ for c in EF.__mro__] ['EF', 'ND', 'NB', 'NC', 'NA', 'ED', 'EB', 'EA', 'EC', 'Base', 'object'] >>> class CA: ... pass >>> class CB(CA): ... pass >>> class CC(CA): ... pass >>> class CD(CB, CC): ... pass >>> class ECD(Base, CD): ... pass >>> [c.__name__ for c in ECD.__mro__] ['ECD', 'CD', 'CB', 'CA', 'CC', 'Base', 'object'] >>> class CDE(CD, Base): ... pass >>> [c.__name__ for c in CDE.__mro__] ['CDE', 'CD', 'CB', 'CA', 'CC', 'Base', 'object'] >>> class CEND(CD, ED, ND): ... pass >>> [c.__name__ for c in CEND.__mro__] ['CEND', 'CD', 'CB', 'CA', 'CC', """ \ """'ED', 'EB', 'EA', 'EC', 'ND', 'NB', 'NC', 'NA', 'Base', 'object'] """ def test_avoiding___init__decoy_w_inheritedAttribute(): """ >>> class Decoy(Base): ... pass >>> class B(Base): ... def __init__(self, a, b): ... print '__init__', a, b >>> class C(Decoy, B): ... def __init__(self): ... print 'C init' ... C.inheritedAttribute('__init__')(self, 1, 2) >>> x = C() C init __init__ 1 2 """ def test_of_not_called_when_not_accessed_through_EC_instance(): """ >>> class Eek(Base): ... def __of__(self, parent): ... return self, parent If I define an EC instance as an attr of an ordinary class: >>> class O(object): ... eek = Eek() >>> class C: ... eek = Eek() I get the instance, without calling __of__, when I get it from either tha class: >>> O.eek is O.__dict__['eek'] True >>> C.eek is C.__dict__['eek'] True or an instance of the class: >>> O().eek is O.__dict__['eek'] True >>> C().eek is C.__dict__['eek'] True If I define an EC instance as an attr of an extension class: >>> class E(Base): ... eek = Eek() I get the instance, without calling __of__, when I get it from tha class: >>> E.eek is E.__dict__['eek'] True But __of__ is called if I go through the instance: >>> e = E() >>> e.eek == (E.__dict__['eek'], e) True """ def test_inheriting___doc__(): """Old-style ExtensionClass inherited __doc__ from base classes. >>> class E(Base): ... "eek" >>> class EE(E): ... pass >>> EE.__doc__ 'eek' >>> EE().__doc__ 'eek' """ def test___of___w_metaclass_instance(): """When looking for extension class instances, need to handle meta classes >>> class C(Base): ... pass >>> class O(Base): ... def __of__(self, parent): ... print '__of__ called on an O' >>> class M(ExtensionClass): ... pass >>> class X: ... __metaclass__ = M ... >>> class S(X, O): ... pass >>> c = C() >>> c.s = S() >>> c.s __of__ called on an O """ def test___of__set_after_creation(): """We may need to set __of__ after a class is created. Normally, in a class's __init__, the initialization code checks for an __of__ method and, if it isn't already set, sets __get__. If a class is persistent and loaded from the database, we want this to happen in __setstate__. The pmc_init_of function allws us to do that. We'll create an extension class without a __of__. We'll also give it a special meta class, just to make sure that this works with funny metaclasses too: >>> import ExtensionClass >>> class M(ExtensionClass.ExtensionClass): ... "A meta class" >>> class B(ExtensionClass.Base): ... __metaclass__ = M ... def __init__(self, name): ... self.name = name ... def __repr__(self): ... return self.name >>> B.__class__ is M True >>> x = B('x') >>> x.y = B('y') >>> x.y y We define a __of__ method for B after the fact: >>> def __of__(self, other): ... print '__of__(%r, %r)' % (self, other) ... return self >>> B.__of__ = __of__ We see that this has no effect: >>> x.y y Until we use pmc_init_of: >>> ExtensionClass.pmc_init_of(B) >>> x.y __of__(y, x) y Note that there is no harm in calling pmc_init_of multiple times: >>> ExtensionClass.pmc_init_of(B) >>> ExtensionClass.pmc_init_of(B) >>> ExtensionClass.pmc_init_of(B) >>> x.y __of__(y, x) y If we remove __of__, we'll go back to the behavior we had before: >>> del B.__of__ >>> ExtensionClass.pmc_init_of(B) >>> x.y y """ def test_Basic_gc(): """Test to make sure that EC instances participate in GC >>> from ExtensionClass import Base >>> import gc >>> class C1(Base): ... pass ... >>> class C2(Base): ... def __del__(self): ... print 'removed' ... >>> a=C1() >>> a.b = C1() >>> a.b.a = a >>> a.b.c = C2() >>> thresholds = gc.get_threshold() >>> gc.set_threshold(0) >>> ignore = gc.collect() >>> del a >>> ignored = gc.collect() removed >>> ignored > 0 True >>> gc.set_threshold(*thresholds) """ from zope.testing.doctest import DocTestSuite import unittest def test_suite(): return unittest.TestSuite(( DocTestSuite('ExtensionClass'), DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/Record/include/ExtensionClass/ExtensionClass.h0000644000175000017500000002370012214017456024361 0ustar arnauarnau/***************************************************************************** Copyright (c) 1996-2002 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ /* $Id: ExtensionClass.h 40218 2005-11-18 14:39:19Z andreasjung $ Extension Class Definitions Implementing base extension classes A base extension class is implemented in much the same way that an extension type is implemented, except: - The include file, 'ExtensionClass.h', must be included. - The type structure is declared to be of type 'PyExtensionClass', rather than of type 'PyTypeObject'. - The type structure has an additional member that must be defined after the documentation string. This extra member is a method chain ('PyMethodChain') containing a linked list of method definition ('PyMethodDef') lists. Method chains can be used to implement method inheritance in C. Most extensions don't use method chains, but simply define method lists, which are null-terminated arrays of method definitions. A macro, 'METHOD_CHAIN' is defined in 'ExtensionClass.h' that converts a method list to a method chain. (See the example below.) - Module functions that create new instances must be replaced by an '__init__' method that initializes, but does not create storage for instances. - The extension class must be initialized and exported to the module with:: PyExtensionClass_Export(d,"name",type); where 'name' is the module name and 'type' is the extension class type object. Attribute lookup Attribute lookup is performed by calling the base extension class 'getattr' operation for the base extension class that includes C data, or for the first base extension class, if none of the base extension classes include C data. 'ExtensionClass.h' defines a macro 'Py_FindAttrString' that can be used to find an object's attributes that are stored in the object's instance dictionary or in the object's class or base classes:: v = Py_FindAttrString(self,name); In addition, a macro is provided that replaces 'Py_FindMethod' calls with logic to perform the same sort of lookup that is provided by 'Py_FindAttrString'. Linking The extension class mechanism was designed to be useful with dynamically linked extension modules. Modules that implement extension classes do not have to be linked against an extension class library. The macro 'PyExtensionClass_Export' imports the 'ExtensionClass' module and uses objects imported from this module to initialize an extension class with necessary behavior. */ #ifndef EXTENSIONCLASS_H #define EXTENSIONCLASS_H #include "Python.h" #include "import.h" /* Declarations for objects of type ExtensionClass */ #define EC PyTypeObject #define PyExtensionClass PyTypeObject #define EXTENSIONCLASS_BINDABLE_FLAG 1 << 2 #define EXTENSIONCLASS_NOINSTDICT_FLAG 1 << 5 typedef struct { PyObject_HEAD } _emptyobject; static struct ExtensionClassCAPIstruct { /***************************************************************************** WARNING: THIS STRUCT IS PRIVATE TO THE EXTENSION CLASS INTERFACE IMPLEMENTATION AND IS SUBJECT TO CHANGE !!! *****************************************************************************/ PyObject *(*EC_findiattrs_)(PyObject *self, char *cname); int (*PyExtensionClass_Export_)(PyObject *dict, char *name, PyTypeObject *typ); PyObject *(*PyECMethod_New_)(PyObject *callable, PyObject *inst); PyExtensionClass *ECBaseType_; PyExtensionClass *ECExtensionClassType_; } *PyExtensionClassCAPI = NULL; #define ECBaseType (PyExtensionClassCAPI->ECBaseType_) #define ECExtensionClassType (PyExtensionClassCAPI->ECExtensionClassType_) /* Following are macros that are needed or useful for defining extension classes: */ /* This macro redefines Py_FindMethod to do attribute for an attribute name given by a C string lookup using extension class meta-data. This is used by older getattr implementations. This macro is used in base class implementations of tp_getattr to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. Note that in Python 1.4, a getattr operation may be provided that uses an object argument. Classes that support this new operation should use Py_FindAttr. */ #define EC_findiattrs (PyExtensionClassCAPI->EC_findiattrs_) #define Py_FindMethod(M,SELF,NAME) (EC_findiattrs((SELF),(NAME))) /* Do method or attribute lookup for an attribute name given by a C string using extension class meta-data. This macro is used in base class implementations of tp_getattro to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. Note that in Python 1.4, a getattr operation may be provided that uses an object argument. Classes that support this new operation should use Py_FindAttr. */ #define Py_FindAttrString(SELF,NAME) (EC_findiattrs((SELF),(NAME))) /* Do method or attribute lookup using extension class meta-data. This macro is used in base class implementations of tp_getattr to lookup methods or attributes that are not managed by the base type directly. The macro is generally used to search for attributes after other attribute searches have failed. */ #define Py_FindAttr (ECBaseType->tp_getattro) /* Do attribute assignment for an attribute. This macro is used in base class implementations of tp_setattro to set attributes that are not managed by the base type directly. The macro is generally used to assign attributes after other attribute attempts to assign attributes have failed. */ #define PyEC_SetAttr(SELF,NAME,V) (ECBaseType->tp_setattro(SELF, NAME, V)) /* Convert a method list to a method chain. */ #define METHOD_CHAIN(DEF) (traverseproc)(DEF) /* The following macro checks whether a type is an extension class: */ #define PyExtensionClass_Check(TYPE) \ PyObject_TypeCheck((PyObject*)(TYPE), ECExtensionClassType) /* The following macro checks whether an instance is an extension instance: */ #define PyExtensionInstance_Check(INST) \ PyObject_TypeCheck(((PyObject*)(INST))->ob_type, ECExtensionClassType) #define CHECK_FOR_ERRORS(MESS) /* The following macro can be used to define an extension base class that only provides method and that is used as a pure mix-in class. */ #define PURE_MIXIN_CLASS(NAME,DOC,METHODS) \ static PyExtensionClass NAME ## Type = { PyObject_HEAD_INIT(NULL) 0, # NAME, \ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \ 0 , DOC, (traverseproc)METHODS, } /* The following macros provide limited access to extension-class method facilities. */ /* Test for an ExtensionClass method: */ #define PyECMethod_Check(O) PyMethod_Check((O)) /* Create a method object that wraps a callable object and an instance. Note that if the callable object is an extension class method, then the new method will wrap the callable object that is wrapped by the extension class method. Also note that if the callable object is an extension class method with a reference count of 1, then the callable object will be rebound to the instance and returned with an incremented reference count. */ #define PyECMethod_New(CALLABLE, INST) \ PyExtensionClassCAPI->PyECMethod_New_((CALLABLE),(INST)) /* Return the instance that is bound by an extension class method. */ #define PyECMethod_Self(M) \ (PyMethod_Check((M)) ? ((PyMethodObject*)(M))->im_self : NULL) /* Check whether an object has an __of__ method for returning itself in the context of it's container. */ #define has__of__(O) (PyObject_TypeCheck((O)->ob_type, ECExtensionClassType) \ && (O)->ob_type->tp_descr_get != NULL) /* The following macros are used to check whether an instance or a class' instanses have instance dictionaries: */ #define HasInstDict(O) (_PyObject_GetDictPtr(O) != NULL) #define ClassHasInstDict(C) ((C)->tp_dictoffset > 0)) /* Get an object's instance dictionary. Use with caution */ #define INSTANCE_DICT(inst) (_PyObject_GetDictPtr(O)) /* Test whether an ExtensionClass, S, is a subclass of ExtensionClass C. */ #define ExtensionClassSubclass_Check(S,C) PyType_IsSubtype((S), (C)) /* Test whether an ExtensionClass instance , I, is a subclass of ExtensionClass C. */ #define ExtensionClassSubclassInstance_Check(I,C) PyObject_TypeCheck((I), (C)) /* Export an Extension Base class in a given module dictionary with a given name and ExtensionClass structure. */ #define PyExtensionClass_Export(D,N,T) \ if (! ExtensionClassImported || \ PyExtensionClassCAPI->PyExtensionClass_Export_((D),(N),&(T)) < 0) return; #define ExtensionClassImported \ ((PyExtensionClassCAPI != NULL) || \ (PyExtensionClassCAPI = PyCObject_Import("ExtensionClass","CAPI2"))) /* These are being overridded to use tp_free when used with new-style classes. This is to allow old extention-class code to work. */ #undef PyMem_DEL #undef PyObject_DEL #define PyMem_DEL(O) \ if (((O)->ob_type->tp_flags & Py_TPFLAGS_HAVE_CLASS) \ && ((O)->ob_type->tp_free != NULL)) \ (O)->ob_type->tp_free((PyObject*)(O)); \ else \ PyObject_FREE((O)); #define PyObject_DEL(O) PyMem_DEL(O) #endif /* EXTENSIONCLASS_H */ zope2.13-2.13.21/source/Record/buildout.cfg0000644000175000017500000000025512214017456017171 0ustar arnauarnau[buildout] develop = . parts = interpreter test [interpreter] recipe = zc.recipe.egg interpreter = python eggs = Record [test] recipe = zc.recipe.testrunner eggs = Record zope2.13-2.13.21/source/Record/bootstrap.py0000644000175000017500000000742112214017456017252 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 108946 2010-02-12 02:40:18Z yusei $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/Record/CHANGES.txt0000644000175000017500000000013612214017456016470 0ustar arnauarnauChangelog ========= 2.13.0 (2010-03-30) ------------------- - Released as separate package. zope2.13-2.13.21/source/Record/src/0000755000175000017500000000000012214017456015446 5ustar arnauarnauzope2.13-2.13.21/source/Record/src/Record.egg-info/0000755000175000017500000000000012214017456020356 5ustar arnauarnauzope2.13-2.13.21/source/Record/src/Record.egg-info/PKG-INFO0000644000175000017500000000104512214017456021453 0ustar arnauarnauMetadata-Version: 1.0 Name: Record Version: 2.13.0 Summary: Special Record objects used in Zope2. Home-page: http://pypi.python.org/pypi/Record Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== Record provides special objects used in some Zope2 internals like ZRDB. Changelog ========= 2.13.0 (2010-03-30) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Record/src/Record.egg-info/dependency_links.txt0000644000175000017500000000000112214017456024424 0ustar arnauarnau zope2.13-2.13.21/source/Record/src/Record.egg-info/requires.txt0000644000175000017500000000001612214017456022753 0ustar arnauarnauExtensionClasszope2.13-2.13.21/source/Record/src/Record.egg-info/top_level.txt0000644000175000017500000000000712214017456023105 0ustar arnauarnauRecord zope2.13-2.13.21/source/Record/src/Record.egg-info/SOURCES.txt0000644000175000017500000000077612214017456022254 0ustar arnauarnauCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py include/ExtensionClass/ExtensionClass.h include/ExtensionClass/_ExtensionClass.c include/ExtensionClass/__init__.py include/ExtensionClass/tests.py include/ExtensionClass/pickle/pickle.c src/Record/_Record.c src/Record/__init__.py src/Record/tests.py src/Record.egg-info/PKG-INFO src/Record.egg-info/SOURCES.txt src/Record.egg-info/dependency_links.txt src/Record.egg-info/not-zip-safe src/Record.egg-info/requires.txt src/Record.egg-info/top_level.txtzope2.13-2.13.21/source/Record/src/Record.egg-info/not-zip-safe0000644000175000017500000000000112214017456022604 0ustar arnauarnau zope2.13-2.13.21/source/Record/src/Record/0000755000175000017500000000000012214017456016664 5ustar arnauarnauzope2.13-2.13.21/source/Record/src/Record/_Record.c0000644000175000017500000002601712214017456020413 0ustar arnauarnau/***************************************************************************** Copyright (c) 1996-2002 Zope Corporation and Contributors. All Rights Reserved. This software is subject to the provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ****************************************************************************/ static char Record_module_documentation[] = "" "\n$Id: _Record.c,v 1.2 2003/11/28 16:46:36 jim Exp $" ; #include "ExtensionClass/ExtensionClass.h" /* ----------------------------------------------------- */ #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) typedef int Py_ssize_t; typedef Py_ssize_t (*lenfunc)(PyObject *); typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t); typedef PyObject *(*ssizessizeargfunc)(PyObject *, Py_ssize_t, Py_ssize_t); typedef int(*ssizeobjargproc)(PyObject *, Py_ssize_t, PyObject *); typedef int(*ssizessizeobjargproc)(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); #define PY_SSIZE_T_MAX INT_MAX #define PY_SSIZE_T_MIN INT_MIN #endif static void PyVar_Assign(PyObject **v, PyObject *e) { Py_XDECREF(*v); *v=e;} #define ASSIGN(V,E) PyVar_Assign(&(V),(E)) #define UNLESS(E) if(!(E)) #define UNLESS_ASSIGN(V,E) ASSIGN(V,E); UNLESS(V) #define OBJECT(O) ((PyObject*)(O)) static PyObject *py___record_schema__; /* Declarations for objects of type Record */ typedef struct { PyObject_HEAD PyObject **data; PyObject *schema; } Record; staticforward PyExtensionClass RecordType; /* ---------------------------------------------------------------- */ static Py_ssize_t Record_init(Record *self) { Py_ssize_t l; UNLESS(self->schema) UNLESS(self->schema=PyObject_GetAttr(OBJECT(self->ob_type), py___record_schema__)) return -1; if((l=PyObject_Length(self->schema)) < 0) return -1; UNLESS(self->data) { UNLESS(self->data=malloc(sizeof(PyObject*)*(l+1))) { PyErr_NoMemory(); return -1; } memset(self->data, 0, sizeof(PyObject*)*(l+1)); } return l; } static PyObject * Record___setstate__(Record *self, PyObject *args) { PyObject *state=0, *parent, **d; Py_ssize_t l, ls, i; if((l=Record_init(self)) < 0) return NULL; UNLESS(PyArg_ParseTuple(args, "|OO", &state, &parent)) return NULL; if(state) { if(PyDict_Check(state)) { PyObject *k, *v; for(i=0; PyDict_Next(state, &i, &k, &v);) if(k && v && PyObject_SetAttr(OBJECT(self),k,v) < 0) PyErr_Clear(); } else { if((ls=PyObject_Length(state)) < 0) return NULL; for(i=0, d=self->data; i < l && i < ls; i++, d++) { ASSIGN(*d, PySequence_GetItem(state, i)); UNLESS(*d) return NULL; } } } Py_INCREF(Py_None); return Py_None; } static PyObject * Record___getstate__( Record *self, PyObject *args) { PyObject *r, **d, *v; Py_ssize_t i, l; UNLESS(self->data) return PyTuple_New(0); if((l=Record_init(self)) < 0) return NULL; UNLESS(r=PyTuple_New(l)) return NULL; for(d=self->data, i=0; i < l; i++, d++) { v= *d; if(!v) v=Py_None; Py_INCREF(v); PyTuple_SET_ITEM(r,i,v); } return r; } static void Record_deal(Record *self) { int l; PyObject **d; if(self->schema) { l=PyObject_Length(self->schema); for(d=self->data; --l >= 0; d++) { Py_XDECREF(*d); } Py_DECREF(self->schema); free(self->data); } } static struct PyMethodDef Record_methods[] = { {"__getstate__", (PyCFunction)Record___getstate__, METH_VARARGS, "__getstate__() -- Get the record's data" }, {"__setstate__", (PyCFunction)Record___setstate__, METH_VARARGS, "__setstate__(v) -- Set the record's data" }, {"__init__", (PyCFunction)Record___setstate__, METH_VARARGS, "__init__([v]) -- Initialize a record, possibly with state" }, {NULL, NULL} /* sentinel */ }; /* ---------- */ static void Record_dealloc(Record *self) { Record_deal(self); Py_DECREF(self->ob_type); PyObject_DEL(self); } static PyObject * Record_getattr(Record *self, PyObject *name) { Py_ssize_t l, i; PyObject *io; if((l=Record_init(self)) < 0) return NULL; if ((io=Py_FindAttr((PyObject *)self, name))) return io; PyErr_Clear(); if((io=PyObject_GetItem(self->schema, name))) { UNLESS(PyInt_Check(io)) { PyErr_SetString(PyExc_TypeError, "invalid record schema"); return NULL; } i=PyInt_AsLong(io); if(i >= 0 && i < l) { ASSIGN(io, self->data[i]); UNLESS(io) io=Py_None; } else ASSIGN(io, Py_None); Py_INCREF(io); return io; } PyErr_SetObject(PyExc_AttributeError, name); return NULL; } static int Record_setattr(Record *self, PyObject *name, PyObject *v) { Py_ssize_t l, i; PyObject *io; if((l=Record_init(self)) < 0) return -1; if((io=PyObject_GetItem(self->schema, name))) { UNLESS(PyInt_Check(io)) { PyErr_SetString(PyExc_TypeError, "invalid record schema"); return -1; } i=PyInt_AsLong(io); Py_DECREF(io); if(i >= 0 && i < l) { Py_XINCREF(v); ASSIGN(self->data[i],v); return 0; } } PyErr_SetObject(PyExc_AttributeError, name); return -1; } static int Record_compare(Record *v, Record *w) { Py_ssize_t lv, lw, i; int c; PyObject **dv, **dw; if((lv=Record_init(v)) < 0) return -1; if((lw=Record_init(w)) < 0) return -1; if(lw < lv) lv=lw; for(i=0, dv=v->data, dw=w->data; i < lv; i++, dv++, dw++) { if(*dv) if(*dw) { if((c=PyObject_Compare(*dv,*dw))) return c; } else return 1; else if(*dw) return -1; } if(*dv) return 1; if(*dw) return -1; return 0; } /* Code to handle accessing Record objects as sequence objects */ static PyObject * Record_concat(Record *self, PyObject *bb) { PyErr_SetString(PyExc_TypeError, "Record objects do not support concatenation"); return NULL; } static PyObject * Record_repeat(Record *self, Py_ssize_t n) { PyErr_SetString(PyExc_TypeError, "Record objects do not support repetition"); return NULL; } static PyObject * IndexError(int i) { PyObject *v; if((v=PyInt_FromLong(i))) { PyErr_SetObject(PyExc_IndexError, v); Py_DECREF(v); } return NULL; } static PyObject * Record_item(Record *self, Py_ssize_t i) { PyObject *o; Py_ssize_t l; if((l=Record_init(self)) < 0) return NULL; if(i < 0 || i >= l) return IndexError(i); o=self->data[i]; UNLESS(o) o=Py_None; Py_INCREF(o); return o; } static PyObject * Record_slice(Record *self, Py_ssize_t ilow, Py_ssize_t ihigh) { PyErr_SetString(PyExc_TypeError, "Record objects do not support slicing"); return NULL; } static int Record_ass_item(Record *self, Py_ssize_t i, PyObject *v) { Py_ssize_t l; if((l=Record_init(self)) < 0) return -1; if(i < 0 || i >= l) { IndexError(i); return -1; } UNLESS(v) { PyErr_SetString(PyExc_TypeError,"cannot delete record items"); return -1; } Py_INCREF(v); ASSIGN(self->data[i], v); return 0; } static int Record_ass_slice(Record *self, int ilow, int ihigh, PyObject *v) { PyErr_SetString(PyExc_TypeError, "Record objects do not support slice assignment"); return -1; } static PySequenceMethods Record_as_sequence = { (lenfunc)Record_init, /*sq_length*/ (binaryfunc)Record_concat, /*sq_concat*/ (ssizeargfunc)Record_repeat, /*sq_repeat*/ (ssizeargfunc)Record_item, /*sq_item*/ (ssizessizeargfunc)Record_slice, /*sq_slice*/ (ssizeobjargproc)Record_ass_item, /*sq_ass_item*/ (ssizessizeobjargproc)Record_ass_slice, /*sq_ass_slice*/ }; /* -------------------------------------------------------------- */ static PyObject * Record_subscript(Record *self, PyObject *key) { Py_ssize_t i, l; PyObject *io; if((l=Record_init(self)) < 0) return NULL; if(PyInt_Check(key)) { i=PyInt_AsLong(key); if(i<0) i+=l; return Record_item(self, i); } if((io=PyObject_GetItem(self->schema, key))) { UNLESS(PyInt_Check(io)) { PyErr_SetString(PyExc_TypeError, "invalid record schema"); return NULL; } i=PyInt_AsLong(io); if(i >= 0 && i < l) { ASSIGN(io, self->data[i]); UNLESS(io) io=Py_None; } else ASSIGN(io, Py_None); Py_INCREF(io); return io; } PyErr_Clear(); if ((io=PyObject_GetAttr(OBJECT(self), key))) return io; PyErr_SetObject(PyExc_KeyError, key); return NULL; } static int Record_ass_sub(Record *self, PyObject *key, PyObject *v) { Py_ssize_t i, l; PyObject *io; if((l=Record_init(self)) < 0) return -1; if(PyInt_Check(key)) { i=PyInt_AsLong(key); if(i<0) i+=l; return Record_ass_item(self, i, v); } if((io=PyObject_GetItem(self->schema, key))) { UNLESS(PyInt_Check(io)) { PyErr_SetString(PyExc_TypeError, "invalid record schema"); return -1; } i=PyInt_AsLong(io); Py_DECREF(io); if(i >= 0 && i < l) { Py_XINCREF(v); ASSIGN(self->data[i],v); return 0; } } return -1; } static PyMappingMethods Record_as_mapping = { (lenfunc)Record_init, /*mp_length*/ (binaryfunc)Record_subscript, /*mp_subscript*/ (objobjargproc)Record_ass_sub, /*mp_ass_subscript*/ }; /* -------------------------------------------------------- */ static PyExtensionClass RecordType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "Record", /*tp_name*/ sizeof(Record), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Record_dealloc, /*tp_dealloc*/ (printfunc)0, /*tp_print*/ (getattrfunc)0, /*obsolete tp_getattr*/ (setattrfunc)0, /*obsolete tp_setattr*/ (cmpfunc)Record_compare, /*tp_compare*/ (reprfunc)0, /*tp_repr*/ 0, /*tp_as_number*/ &Record_as_sequence, /*tp_as_sequence*/ &Record_as_mapping, /*tp_as_mapping*/ (hashfunc)0, /*tp_hash*/ (ternaryfunc)0, /*tp_call*/ (reprfunc)0, /*tp_str*/ (getattrofunc)Record_getattr, /*tp_getattro*/ (setattrofunc)Record_setattr, /*tp_setattro*/ /* Space for future expansion */ 0L,0L, "Simple Record Types", /* Documentation string */ METHOD_CHAIN(Record_methods), (void*)(EXTENSIONCLASS_NOINSTDICT_FLAG), }; /* End of code for Record objects */ /* -------------------------------------------------------- */ /* List of methods defined in the module */ static struct PyMethodDef Module_Level__methods[] = { {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; /* Initialization function for the module (*must* be called initRecord) */ void init_Record(void) { PyObject *m, *d; UNLESS(py___record_schema__=PyString_FromString("__record_schema__")) return; UNLESS(ExtensionClassImported) return; /* Create the module and add the functions */ m = Py_InitModule4("_Record", Module_Level__methods, Record_module_documentation, (PyObject*)NULL,PYTHON_API_VERSION); /* Add some symbolic constants to the module */ d = PyModule_GetDict(m); PyExtensionClass_Export(d,"Record",RecordType); } zope2.13-2.13.21/source/Record/src/Record/__init__.py0000644000175000017500000000254212214017456021000 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Records Records are used to provide compact storage for database query result rows. They don't use instance dictionaries. Rather, they store they data in a compact array internally. They use a record schema to map names to positions within the array. >>> class R(Record): ... __record_schema__ = {'a': 0, 'b': 1, 'c': 2} >>> r = R() >>> r.__dict__ Traceback (most recent call last): ... AttributeError: __dict__ >>> r.a >>> r.a = 1 >>> r.a 1 >>> r.b >>> r.c Records can be used as mapping objects: >>> "%(a)s %(b)s %(c)s" % r '1 None None' >>> r['a'] 1 >>> r['b'] = 42 And like sequences: >>> r[0] 1 >>> r[1] 42 >>> list(r) [1, 42, None] $Id: __init__.py,v 1.2 2003/11/28 16:46:36 jim Exp $ """ from _Record import Record zope2.13-2.13.21/source/Record/src/Record/tests.py0000644000175000017500000000244112214017456020401 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Record tests $Id: tests.py,v 1.2 2003/11/28 16:46:36 jim Exp $ """ from Record import Record import pickle class P(Record): __record_schema__ = {'a': 0, 'b': 1, 'c': 2} def test_RecordPickling(): """ We can create records from sequences: >>> r = P(('x', 42, 1.23)) We can pickle them: >>> r2 = pickle.loads(pickle.dumps(r)) >>> list(r) == list(r2) 1 >>> r.__record_schema__ == r2.__record_schema__ 1 """ import unittest from doctest import DocTestSuite def test_suite(): return unittest.TestSuite(( DocTestSuite('Record'), DocTestSuite(), )) if __name__ == '__main__': unittest.main() zope2.13-2.13.21/source/Products.ExternalMethod/0000755000175000017500000000000012214017666020171 5ustar arnauarnauzope2.13-2.13.21/source/Products.ExternalMethod/setup.py0000644000175000017500000000277312214017666021714 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from setuptools import setup, find_packages setup(name='Products.ExternalMethod', version = '2.13.0', url='http://pypi.python.org/pypi/Products.ExternalMethod', license='ZPL 2.1', description="This package provides support for external Python methods " "within a Zope 2 environment.", author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=open('README.txt').read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), namespace_packages=['Products'], package_dir={'': 'src'}, install_requires=[ 'setuptools', 'AccessControl', 'Acquisition', 'ExtensionClass', 'Persistence', 'ZODB3', 'Zope2 >= 2.13.0a1' ], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/Products.ExternalMethod/PKG-INFO0000644000175000017500000000130012214017666021260 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.ExternalMethod Version: 2.13.0 Summary: This package provides support for external Python methods within a Zope 2 environment. Home-page: http://pypi.python.org/pypi/Products.ExternalMethod Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== The External Method package provides support for external Python methods, exposing them as callable objects within a Zope 2 environment. Changelog ========= 2.13.0 (2010-07-10) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/0000755000175000017500000000000012214017666022452 5ustar arnauarnauzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/0000755000175000017500000000000012214017666030671 5ustar arnauarnau././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/PKG-INFOzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/PKG-IN0000644000175000017500000000130012214017666031533 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.ExternalMethod Version: 2.13.0 Summary: This package provides support for external Python methods within a Zope 2 environment. Home-page: http://pypi.python.org/pypi/Products.ExternalMethod Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== The External Method package provides support for external Python methods, exposing them as callable objects within a Zope 2 environment. Changelog ========= 2.13.0 (2010-07-10) ------------------- - Released as separate package. Platform: UNKNOWN ././@LongLink0000000000000000000000000000016300000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/dependency_links.txtzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/depend0000644000175000017500000000000112214017666032042 0ustar arnauarnau ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/requires.txtzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/requir0000644000175000017500000000012712214017666032123 0ustar arnauarnausetuptools AccessControl Acquisition ExtensionClass Persistence ZODB3 Zope2 >= 2.13.0a1././@LongLink0000000000000000000000000000016500000000000011567 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/namespace_packages.txtzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/namesp0000644000175000017500000000001112214017666032067 0ustar arnauarnauProducts ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/top_level.txtzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/top_le0000644000175000017500000000001112214017666032066 0ustar arnauarnauProducts ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/SOURCES.txtzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/SOURCE0000644000175000017500000000122412214017666031613 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/Products.ExternalMethod.egg-info/PKG-INFO pip-egg-info/Products.ExternalMethod.egg-info/SOURCES.txt pip-egg-info/Products.ExternalMethod.egg-info/dependency_links.txt pip-egg-info/Products.ExternalMethod.egg-info/namespace_packages.txt pip-egg-info/Products.ExternalMethod.egg-info/not-zip-safe pip-egg-info/Products.ExternalMethod.egg-info/requires.txt pip-egg-info/Products.ExternalMethod.egg-info/top_level.txt src/Products/__init__.py src/Products/ExternalMethod/ExternalMethod.py src/Products/ExternalMethod/__init__.py src/Products/ExternalMethod/tests/__init__.py src/Products/ExternalMethod/tests/testExternalMethod.py././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/not-zip-safezope2.13-2.13.21/source/Products.ExternalMethod/pip-egg-info/Products.ExternalMethod.egg-info/not-zi0000644000175000017500000000000112214017666032023 0ustar arnauarnau zope2.13-2.13.21/source/Products.ExternalMethod/LICENSE.txt0000644000175000017500000000402612214017666022016 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/Products.ExternalMethod/README.txt0000644000175000017500000000023412214017666021666 0ustar arnauarnauOverview ======== The External Method package provides support for external Python methods, exposing them as callable objects within a Zope 2 environment. zope2.13-2.13.21/source/Products.ExternalMethod/setup.cfg0000644000175000017500000000007312214017666022012 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/Products.ExternalMethod/COPYRIGHT.txt0000644000175000017500000000004012214017666022274 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/Products.ExternalMethod/buildout.cfg0000644000175000017500000000031712214017666022502 0ustar arnauarnau[buildout] develop = . parts = interpreter test [interpreter] recipe = zc.recipe.egg interpreter = python eggs = Products.ExternalMethod [test] recipe = zc.recipe.testrunner eggs = Products.ExternalMethod zope2.13-2.13.21/source/Products.ExternalMethod/bootstrap.py0000644000175000017500000000742012214017666022563 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 108946 2010-02-12 02:40:18Z yusei $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/Products.ExternalMethod/CHANGES.txt0000644000175000017500000000013612214017666022002 0ustar arnauarnauChangelog ========= 2.13.0 (2010-07-10) ------------------- - Released as separate package. zope2.13-2.13.21/source/Products.ExternalMethod/src/0000755000175000017500000000000012214017666020760 5ustar arnauarnauzope2.13-2.13.21/source/Products.ExternalMethod/src/Products.ExternalMethod.egg-info/0000755000175000017500000000000012214017666027177 5ustar arnauarnauzope2.13-2.13.21/source/Products.ExternalMethod/src/Products.ExternalMethod.egg-info/PKG-INFO0000644000175000017500000000130012214017666030266 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.ExternalMethod Version: 2.13.0 Summary: This package provides support for external Python methods within a Zope 2 environment. Home-page: http://pypi.python.org/pypi/Products.ExternalMethod Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== The External Method package provides support for external Python methods, exposing them as callable objects within a Zope 2 environment. Changelog ========= 2.13.0 (2010-07-10) ------------------- - Released as separate package. Platform: UNKNOWN ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/src/Products.ExternalMethod.egg-info/dependency_links.txtzope2.13-2.13.21/source/Products.ExternalMethod/src/Products.ExternalMethod.egg-info/dependency_link0000644000175000017500000000000112214017666032244 0ustar arnauarnau zope2.13-2.13.21/source/Products.ExternalMethod/src/Products.ExternalMethod.egg-info/requires.txt0000644000175000017500000000012712214017666031577 0ustar arnauarnausetuptools AccessControl Acquisition ExtensionClass Persistence ZODB3 Zope2 >= 2.13.0a1././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/src/Products.ExternalMethod.egg-info/namespace_packages.txtzope2.13-2.13.21/source/Products.ExternalMethod/src/Products.ExternalMethod.egg-info/namespace_packa0000644000175000017500000000001112214017666032205 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.ExternalMethod/src/Products.ExternalMethod.egg-info/top_level.txt0000644000175000017500000000001112214017666031721 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.ExternalMethod/src/Products.ExternalMethod.egg-info/SOURCES.txt0000644000175000017500000000223612214017666031066 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/Products/__init__.py src/Products.ExternalMethod.egg-info/PKG-INFO src/Products.ExternalMethod.egg-info/SOURCES.txt src/Products.ExternalMethod.egg-info/dependency_links.txt src/Products.ExternalMethod.egg-info/namespace_packages.txt src/Products.ExternalMethod.egg-info/not-zip-safe src/Products.ExternalMethod.egg-info/requires.txt src/Products.ExternalMethod.egg-info/top_level.txt src/Products/ExternalMethod/ExternalMethod.py src/Products/ExternalMethod/__init__.py src/Products/ExternalMethod/extmethod.gif src/Products/ExternalMethod/dtml/methodAdd.dtml src/Products/ExternalMethod/dtml/methodEdit.dtml src/Products/ExternalMethod/help/External-Method.stx src/Products/ExternalMethod/help/External-Method_Add.stx src/Products/ExternalMethod/help/External-Method_Properties.stx src/Products/ExternalMethod/help/External-Method_Try-It.stx src/Products/ExternalMethod/help/ExternalMethod.py src/Products/ExternalMethod/tests/__init__.py src/Products/ExternalMethod/tests/testExternalMethod.py src/Products/ExternalMethod/tests/Extensions/Test.py src/Products/ExternalMethod/www/function.gifzope2.13-2.13.21/source/Products.ExternalMethod/src/Products.ExternalMethod.egg-info/not-zip-safe0000644000175000017500000000000112214017666031425 0ustar arnauarnau zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/0000755000175000017500000000000012214017666022563 5ustar arnauarnauzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/0000755000175000017500000000000012214017666025506 5ustar arnauarnauzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/www/0000755000175000017500000000000012214017666026332 5ustar arnauarnauzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/www/function.gif0000644000175000017500000000157712214017666030660 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,\ 4Aƒ(TxáA‚ &lHðaB‰("D±`F‰.d8dÁ(MJ,Ø0%Ë•%0s¥Í“7G.”i³#Fœ=ƒÖ º2ÀP¢?ozDšS§Ï›;zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/__init__.py0000644000175000017500000000171312214017666027621 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import ExternalMethod def initialize(context): context.registerClass( ExternalMethod.ExternalMethod, constructors=(ExternalMethod.manage_addExternalMethodForm, ExternalMethod.manage_addExternalMethod), icon='extmethod.gif', ) context.registerHelp() context.registerHelpTitle('Zope Help') zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/help/0000755000175000017500000000000012214017666026436 5ustar arnauarnau././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/help/External-Method_Try-It.stxzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/help/External-Method_Try0000644000175000017500000000034612214017666032222 0ustar arnauarnauExternal Method - Try It: Test the external method. Description This view executes the external method and returns the results if any. Only parameters from the web request are passed the the external method. zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/help/ExternalMethod.py0000644000175000017500000000703312214017666031736 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## def manage_addExternalMethod(id, title, module, function): """ Add an external method to an 'ObjectManager'. In addition to the standard object-creation arguments, 'id' and title, the following arguments are defined: function -- The name of the python function. This can be a an ordinary Python function, or a bound method. module -- The name of the file containing the function definition. The module normally resides in the 'Extensions' directory, however, the file name may have a prefix of 'product.', indicating that it should be found in a product directory. For example, if the module is: 'ACMEWidgets.foo', then an attempt will first be made to use the file 'lib/python/Products/ACMEWidgets/Extensions/foo.py'. If this failes, then the file 'Extensions/ACMEWidgets.foo.py' will be used. """ class ExternalMethod: """ Web-callable functions that encapsulate external Python functions. The function is defined in an external file. This file is treated like a module, but is not a module. It is not imported directly, but is rather read and evaluated. The file must reside in the 'Extensions' subdirectory of the Zope installation, or reside in the path specified by 'extensions' directive in zope.conf, or in an 'Extensions' subdirectory of a product directory. Due to the way ExternalMethods are loaded, it is not *currently* possible to import Python modules that reside in the 'Extensions' directory. It is possible to import modules found in the 'lib/python' directory of the Zope installation, or in packages that are in the 'lib/python' directory. """ __constructor__=manage_addExternalMethod def manage_edit(title, module, function, REQUEST=None): """ Change the External Method. See the description of manage_addExternalMethod for a description of the arguments 'module' and 'function'. Note that calling 'manage_edit' causes the "module" to be effectively reloaded. This is useful during debugging to see the effects of changes, but can lead to problems of functions rely on shared global data. """ def __call__(*args, **kw): """ Call the External Method. Calling an External Method is roughly equivalent to calling the original actual function from Python. Positional and keyword parameters can be passed as usual. Note however that unlike the case of a normal Python method, the "self" argument must be passed explicitly. An exception to this rule is made if: - The supplied number of arguments is one less than the required number of arguments, and - The name of the function's first argument is 'self'. In this case, the URL parent of the object is supplied as the first argument. """ ././@LongLink0000000000000000000000000000016000000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/help/External-Method_Properties.stxzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/help/External-Method_Pro0000644000175000017500000000164712214017666032211 0ustar arnauarnauExternal Method - Properties: Manage the properties of an external method. Description View and manage the properties of an External Method. Controls 'ID' -- Specifies the id of the external method. 'Title' -- Allows you to specify the title of the external method. 'Function name' -- Allows you to specify the name of the function (method) to use for the external method. 'Python module file' -- Allows you to specify the module in which the function is located. The Python module may be located in the Zope 'Extensions' directory, or in a 'Extensions' directory in a product directory. Product directories are located in 'lib/python/Products'. If the Python module is in a product directory this should be indicated in the name of the module which should be the name of the product followed by a dot followed by the name of the Python module file. ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/help/External-Method_Add.stxzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/help/External-Method_Add0000644000175000017500000000163612214017666032137 0ustar arnauarnauExternal Method - Add: Create a new External Method. Description Creates a new External Method and adds it to the current Folder. Controls 'ID' -- Specifies the id of the external method. 'Title' -- Allows you to specify the title of the external method. 'Function name' -- Allows you to specify the name of the function to use for the external method. 'Python module file' -- Allows you to specify the module in which the function is located. The Python module may be located in the Zope 'Extensions' directory, or in a 'Extensions' directory in a product directory. Product directories are located in 'lib/python/Products'. If the Python module is in a product directory this should be indicated in the name of the module which should be the name of the product followed by a dot followed by the name of the Python module file. zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/help/External-Method.stx0000644000175000017500000000033012214017666032172 0ustar arnauarnauExternal Method: Call Python methods from Zope External Methods associate a Python function on the filesystem with an object in Zope. This object can be called from DTML and Python like other Zope methods. zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/ExternalMethod.py0000644000175000017500000002165212214017666031011 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """External Method Product This product provides support for external methods, which allow domain-specific customization of web environments. """ import os import stat import sys from AccessControl.class_init import InitializeClass from AccessControl.Permissions import change_external_methods from AccessControl.Permissions import view_management_screens from AccessControl.Permissions import view as View from AccessControl.SecurityInfo import ClassSecurityInfo from Acquisition import Acquired from Acquisition import Explicit from App.Extensions import getObject from App.Extensions import getPath from App.Extensions import FuncCode from App.special_dtml import DTMLFile from OFS.role import RoleManager from OFS.SimpleItem import Item from Persistence import Persistent from App.Management import Navigation from ComputedAttribute import ComputedAttribute manage_addExternalMethodForm=DTMLFile('dtml/methodAdd', globals()) def manage_addExternalMethod(self, id, title, module, function, REQUEST=None): """Add an external method to a folder In addition to the standard object-creation arguments, 'id' and title, the following arguments are defined: function -- The name of the python function. This can be a an ordinary Python function, or a bound method. module -- The name of the file containing the function definition. The module normally resides in the 'Extensions' directory. If the zope.conf directive 'extensions' was overriden, then it will specify where modules should reside. However, the file name may have a prefix of 'product.', indicating that it should be found in a product directory. For example, if the module is: 'ACMEWidgets.foo', then an attempt will first be made to use the file 'lib/python/Products/ACMEWidgets/Extensions/foo.py'. If this failes, then the file 'Extensions/ACMEWidgets.foo.py' will be used. """ id = str(id) title = str(title) module = str(module) function = str(function) i = ExternalMethod(id, title, module, function) self._setObject(id, i) if REQUEST is not None: return self.manage_main(self, REQUEST) class ExternalMethod(Item, Persistent, Explicit, RoleManager, Navigation): """Web-callable functions that encapsulate external python functions. The function is defined in an external file. This file is treated like a module, but is not a module. It is not imported directly, but is rather read and evaluated. The file must reside in the 'Extensions' subdirectory of the Zope installation, or in the directory specified by the 'extensions' directive in zope.conf, or in an 'Extensions' subdirectory of a product directory. Due to the way ExternalMethods are loaded, it is not *currently* possible to use Python modules that reside in the 'Extensions' directory. It is possible to load modules found in the 'lib/python' directory of the Zope installation, or in packages that are in the 'lib/python' directory. """ meta_type = 'External Method' security = ClassSecurityInfo() security.declareObjectProtected(View) func_defaults = ComputedAttribute(lambda self: self.getFuncDefaults()) func_code = ComputedAttribute(lambda self: self.getFuncCode()) ZopeTime = Acquired HelpSys = Acquired manage_page_header = Acquired manage_options=( ( {'label': 'Properties', 'action': 'manage_main', 'help': ('ExternalMethod', 'External-Method_Properties.stx')}, {'label': 'Test', 'action': '', 'help': ('ExternalMethod', 'External-Method_Try-It.stx')}, ) + Item.manage_options + RoleManager.manage_options ) def __init__(self, id, title, module, function): self.id=id self.manage_edit(title, module, function) security.declareProtected(view_management_screens, 'manage_main') manage_main=DTMLFile('dtml/methodEdit', globals()) security.declareProtected(change_external_methods, 'manage_edit') def manage_edit(self, title, module, function, REQUEST=None): """Change the external method See the description of manage_addExternalMethod for a description of the arguments 'module' and 'function'. Note that calling 'manage_edit' causes the "module" to be effectively reloaded. This is useful during debugging to see the effects of changes, but can lead to problems of functions rely on shared global data. """ title=str(title) module=str(module) function=str(function) self.title=title if module[-3:]=='.py': module=module[:-3] elif module[-4:]=='.pyc': module=module[:-4] self._module=module self._function=function self.getFunction(1) if REQUEST: message="External Method Uploaded." return self.manage_main(self, REQUEST, manage_tabs_message=message) def getFunction(self, reload=0): f = getObject(self._module, self._function, reload) if hasattr(f, 'im_func'): ff = f.im_func else: ff = f self._v_func_defaults = ff.func_defaults self._v_func_code = FuncCode(ff, f is not ff) self._v_f = f return f def reloadIfChanged(self): # If the file has been modified since last loaded, force a reload. ts=os.stat(self.filepath())[stat.ST_MTIME] if (not hasattr(self, '_v_last_read') or (ts != self._v_last_read)): self._v_f=self.getFunction(1) self._v_last_read=ts def getFuncDefaults(self): import Globals # for data if Globals.DevelopmentMode: self.reloadIfChanged() if not hasattr(self, '_v_func_defaults'): self._v_f = self.getFunction() return self._v_func_defaults def getFuncCode(self): import Globals # for data if Globals.DevelopmentMode: self.reloadIfChanged() if not hasattr(self, '_v_func_code'): self._v_f = self.getFunction() return self._v_func_code security.declareProtected(View, '__call__') def __call__(self, *args, **kw): """Call an ExternalMethod Calling an External Method is roughly equivalent to calling the original actual function from Python. Positional and keyword parameters can be passed as usual. Note however that unlike the case of a normal Python method, the "self" argument must be passed explicitly. An exception to this rule is made if: - The supplied number of arguments is one less than the required number of arguments, and - The name of the function\'s first argument is 'self'. In this case, the URL parent of the object is supplied as the first argument. """ import Globals # for data filePath = self.filepath() if filePath==None: raise RuntimeError("external method could not be called " "because it is None") if not os.path.exists(filePath): raise RuntimeError("external method could not be called " "because the file does not exist") if Globals.DevelopmentMode: self.reloadIfChanged() if hasattr(self, '_v_f'): f = self._v_f else: f = self.getFunction() __traceback_info__=args, kw, self._v_func_defaults try: return f(*args, **kw) except TypeError, v: tb = sys.exc_info()[2] try: if ((self._v_func_code.co_argcount- len(self._v_func_defaults or ()) - 1 == len(args)) and self._v_func_code.co_varnames[0]=='self'): return f(self.aq_parent.this(), *args, **kw) raise TypeError, v, tb finally: tb = None def function(self): return self._function def module(self): return self._module def filepath(self): if not hasattr(self, '_v_filepath'): self._v_filepath=getPath('Extensions', self._module, suffixes=('', 'py', 'pyc', 'pyp')) return self._v_filepath InitializeClass(ExternalMethod) zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/extmethod.gif0000644000175000017500000000161312214017666030177 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,hH° À\XPáB‡ FTÑàD„èЀĄ à‘¡D# 4‰QaJ•+ Š|i ¤LFâ´igÎ98p0p(J H jTèQ —•*2¨À¦L Ú ;zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/tests/0000755000175000017500000000000012214017666026650 5ustar arnauarnau././@LongLink0000000000000000000000000000015000000000000011561 Lustar rootrootzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/tests/testExternalMethod.pyzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/tests/testExternalMethod0000644000175000017500000000443312214017666032422 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import math import os import sys import unittest import ZODB # dead goat import Products.ExternalMethod.tests from Products.ExternalMethod.ExternalMethod import ExternalMethod import App.config def package_home(globals_dict): __name__ = globals_dict['__name__'] m = sys.modules[__name__] if hasattr(m, '__path__'): r = m.__path__[0] elif "." in __name__: r = sys.modules[__name__.split('.', 1)[0]].__path__[0] else: r = __name__ return os.path.abspath(r) class TestExternalMethod(unittest.TestCase): def setUp(self): self._old = App.config.getConfiguration() cfg = App.config.DefaultConfiguration() cfg.instancehome = os.path.dirname( Products.ExternalMethod.tests.__file__) App.config.setConfiguration(cfg) def tearDown(self): App.config.setConfiguration(self._old) def testStorage(self): em1 = ExternalMethod('em', 'test method', 'Test', 'testf') self.assertEqual(em1(4), math.sqrt(4)) state = em1.__getstate__() em2 = ExternalMethod.__basicnew__() em2.__setstate__(state) self.assertEqual(em2(9), math.sqrt(9)) self.failIf('func_defaults' in state) def test_mapply(self): from ZPublisher.mapply import mapply em1 = ExternalMethod('em', 'test method', 'Test', 'testf') self.assertEqual(mapply(em1, (), {'arg1': 4}), math.sqrt(4)) state = em1.__getstate__() em2 = ExternalMethod.__basicnew__() em2.__setstate__(state) self.assertEqual(mapply(em1, (), {'arg1': 9}), math.sqrt(9)) def test_suite(): return unittest.makeSuite(TestExternalMethod) zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/tests/__init__.py0000644000175000017500000000120212214017666030754 0ustar arnauarnau############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/tests/Extensions/0000755000175000017500000000000012214017666031007 5ustar arnauarnauzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/tests/Extensions/Test.py0000644000175000017500000000011212214017666032272 0ustar arnauarnaufrom math import sqrt def testf(arg1, sqrt=sqrt): return sqrt(arg1) zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/dtml/0000755000175000017500000000000012214017666026446 5ustar arnauarnauzope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/dtml/methodAdd.dtml0000644000175000017500000000336312214017666031226 0ustar arnauarnau

    External Methods allow you to add functionality to Zope by writing Python functions which are exposed as callable Zope objects. The module name should give the name of the Python module without the ".py" file extension. The function name should name a callable object found in the module.

    Id
    Title
    Module Name
    Function Name
    zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/ExternalMethod/dtml/methodEdit.dtml0000644000175000017500000000244312214017666031421 0ustar arnauarnau
    Id
    &dtml-id;
    Title
    Module Name
    Function Name
    zope2.13-2.13.21/source/Products.ExternalMethod/src/Products/__init__.py0000644000175000017500000000007012214017666024671 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/Products.StandardCacheManagers/0000755000175000017500000000000012214017675021410 5ustar arnauarnauzope2.13-2.13.21/source/Products.StandardCacheManagers/setup.py0000644000175000017500000000262112214017675023123 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from setuptools import setup, find_packages setup(name='Products.StandardCacheManagers', version = '2.13.0', url='http://pypi.python.org/pypi/Products.StandardCacheManagers', license='ZPL 2.1', description="Cache managers for Zope 2.", author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=open('README.txt').read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), namespace_packages=['Products'], package_dir={'': 'src'}, install_requires=[ 'setuptools', 'AccessControl', 'transaction', 'Zope2 >= 2.13.0a1', 'zope.component', ], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/Products.StandardCacheManagers/PKG-INFO0000644000175000017500000000552412214017675022513 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.StandardCacheManagers Version: 2.13.0 Summary: Cache managers for Zope 2. Home-page: http://pypi.python.org/pypi/Products.StandardCacheManagers Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== This package provides two cache managers for Zope 2. A RAMCacheManager and an Accelerated HTTP cache manager, which adds HTTP cache headers to responses. The following is intended for people interested in the internals of RAMCacheManager, such as maintainers. Introduction ============ The caching framework does not interpret the data in any way, it acts just as a general storage for data passed to it. It tries to check if the data is pickleable though. IOW, only pickleable data is cacheable. The idea behind the RAMCacheManager is that it should be shared between threads, so that the same objects are not cached in each thread. This is achieved by storing the cache data structure itself as a module level variable (RAMCacheManager.caches). This, of course, requires locking on modifications of that data structure. Each RAMCacheManager instance has one cache in RAMCacheManager.caches dictionary. A unique __cacheid is generated when creating a cache manager and it's used as a key for caches. Object Hierarchy ================ RAMCacheManager RAMCache ObjectCacheEntries CacheEntry RAMCacheManager is a persistent placeful object. It is assigned a unique __cacheid on its creation. It is then used as a key to look up the corresponding RAMCache object in the global caches dictionary. So, each RAMCacheManager has a single RAMCache related to it. RAMCache is a volatile cache, unique for each RAMCacheManager. It is shared among threads and does all the locking. It has a writelock. No locking is done on reading though. RAMCache keeps a dictionary of ObjectCacheEntries indexed by the physical path of a cached object. ObjectCacheEntries is a container for cached values for a single object. The values in it are indexed by a tuple of a view_name, interesting request variables, and extra keywords passed to Cache.ZCache_set(). CacheEntry is a wrapper around a single cached value. It stores the data itself, creation time, view_name and keeps the access count. Changelog ========= 2.13.0 (2010-07-11) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/0000755000175000017500000000000012214017675023671 5ustar arnauarnau././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.egg-info/zope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.e0000755000175000017500000000000012214017675032001 5ustar arnauarnau././@LongLink0000000000000000000000000000016500000000000011567 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.egg-info/PKG-INFOzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.e0000644000175000017500000000552412214017675032011 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.StandardCacheManagers Version: 2.13.0 Summary: Cache managers for Zope 2. Home-page: http://pypi.python.org/pypi/Products.StandardCacheManagers Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== This package provides two cache managers for Zope 2. A RAMCacheManager and an Accelerated HTTP cache manager, which adds HTTP cache headers to responses. The following is intended for people interested in the internals of RAMCacheManager, such as maintainers. Introduction ============ The caching framework does not interpret the data in any way, it acts just as a general storage for data passed to it. It tries to check if the data is pickleable though. IOW, only pickleable data is cacheable. The idea behind the RAMCacheManager is that it should be shared between threads, so that the same objects are not cached in each thread. This is achieved by storing the cache data structure itself as a module level variable (RAMCacheManager.caches). This, of course, requires locking on modifications of that data structure. Each RAMCacheManager instance has one cache in RAMCacheManager.caches dictionary. A unique __cacheid is generated when creating a cache manager and it's used as a key for caches. Object Hierarchy ================ RAMCacheManager RAMCache ObjectCacheEntries CacheEntry RAMCacheManager is a persistent placeful object. It is assigned a unique __cacheid on its creation. It is then used as a key to look up the corresponding RAMCache object in the global caches dictionary. So, each RAMCacheManager has a single RAMCache related to it. RAMCache is a volatile cache, unique for each RAMCacheManager. It is shared among threads and does all the locking. It has a writelock. No locking is done on reading though. RAMCache keeps a dictionary of ObjectCacheEntries indexed by the physical path of a cached object. ObjectCacheEntries is a container for cached values for a single object. The values in it are indexed by a tuple of a view_name, interesting request variables, and extra keywords passed to Cache.ZCache_set(). CacheEntry is a wrapper around a single cached value. It stores the data itself, creation time, view_name and keeps the access count. Changelog ========= 2.13.0 (2010-07-11) ------------------- - Released as separate package. Platform: UNKNOWN ././@LongLink0000000000000000000000000000020100000000000011556 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.egg-info/dependency_links.txtzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.e0000644000175000017500000000000112214017675031772 0ustar arnauarnau ././@LongLink0000000000000000000000000000017100000000000011564 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.egg-info/requires.txtzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.e0000644000175000017500000000010512214017675031777 0ustar arnauarnausetuptools AccessControl transaction Zope2 >= 2.13.0a1 zope.component././@LongLink0000000000000000000000000000020300000000000011560 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.egg-info/namespace_packages.txtzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.e0000644000175000017500000000001112214017675031773 0ustar arnauarnauProducts ././@LongLink0000000000000000000000000000017200000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.egg-info/top_level.txtzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.e0000644000175000017500000000001112214017675031773 0ustar arnauarnauProducts ././@LongLink0000000000000000000000000000017000000000000011563 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.egg-info/SOURCES.txtzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.e0000644000175000017500000000165212214017675032007 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/Products.StandardCacheManagers.egg-info/PKG-INFO pip-egg-info/Products.StandardCacheManagers.egg-info/SOURCES.txt pip-egg-info/Products.StandardCacheManagers.egg-info/dependency_links.txt pip-egg-info/Products.StandardCacheManagers.egg-info/namespace_packages.txt pip-egg-info/Products.StandardCacheManagers.egg-info/not-zip-safe pip-egg-info/Products.StandardCacheManagers.egg-info/requires.txt pip-egg-info/Products.StandardCacheManagers.egg-info/top_level.txt src/Products/__init__.py src/Products/StandardCacheManagers/AcceleratedHTTPCacheManager.py src/Products/StandardCacheManagers/RAMCacheManager.py src/Products/StandardCacheManagers/__init__.py src/Products/StandardCacheManagers/subscribers.py src/Products/StandardCacheManagers/tests/__init__.py src/Products/StandardCacheManagers/tests/test_AcceleratedHTTPCacheManager.py src/Products/StandardCacheManagers/tests/test_CacheManagerLocation.py././@LongLink0000000000000000000000000000017100000000000011564 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.egg-info/not-zip-safezope2.13-2.13.21/source/Products.StandardCacheManagers/pip-egg-info/Products.StandardCacheManagers.e0000644000175000017500000000000112214017675031772 0ustar arnauarnau zope2.13-2.13.21/source/Products.StandardCacheManagers/LICENSE.txt0000644000175000017500000000402612214017675023235 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/Products.StandardCacheManagers/README.txt0000644000175000017500000000375612214017675023121 0ustar arnauarnauOverview ======== This package provides two cache managers for Zope 2. A RAMCacheManager and an Accelerated HTTP cache manager, which adds HTTP cache headers to responses. The following is intended for people interested in the internals of RAMCacheManager, such as maintainers. Introduction ============ The caching framework does not interpret the data in any way, it acts just as a general storage for data passed to it. It tries to check if the data is pickleable though. IOW, only pickleable data is cacheable. The idea behind the RAMCacheManager is that it should be shared between threads, so that the same objects are not cached in each thread. This is achieved by storing the cache data structure itself as a module level variable (RAMCacheManager.caches). This, of course, requires locking on modifications of that data structure. Each RAMCacheManager instance has one cache in RAMCacheManager.caches dictionary. A unique __cacheid is generated when creating a cache manager and it's used as a key for caches. Object Hierarchy ================ RAMCacheManager RAMCache ObjectCacheEntries CacheEntry RAMCacheManager is a persistent placeful object. It is assigned a unique __cacheid on its creation. It is then used as a key to look up the corresponding RAMCache object in the global caches dictionary. So, each RAMCacheManager has a single RAMCache related to it. RAMCache is a volatile cache, unique for each RAMCacheManager. It is shared among threads and does all the locking. It has a writelock. No locking is done on reading though. RAMCache keeps a dictionary of ObjectCacheEntries indexed by the physical path of a cached object. ObjectCacheEntries is a container for cached values for a single object. The values in it are indexed by a tuple of a view_name, interesting request variables, and extra keywords passed to Cache.ZCache_set(). CacheEntry is a wrapper around a single cached value. It stores the data itself, creation time, view_name and keeps the access count. zope2.13-2.13.21/source/Products.StandardCacheManagers/setup.cfg0000644000175000017500000000007312214017675023231 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/Products.StandardCacheManagers/COPYRIGHT.txt0000644000175000017500000000004012214017675023513 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/Products.StandardCacheManagers/buildout.cfg0000644000175000017500000000033512214017675023721 0ustar arnauarnau[buildout] develop = . parts = interpreter test [interpreter] recipe = zc.recipe.egg interpreter = python eggs = Products.StandardCacheManagers [test] recipe = zc.recipe.testrunner eggs = Products.StandardCacheManagers zope2.13-2.13.21/source/Products.StandardCacheManagers/bootstrap.py0000644000175000017500000000742012214017675024002 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 108946 2010-02-12 02:40:18Z yusei $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/Products.StandardCacheManagers/CHANGES.txt0000644000175000017500000000013612214017675023221 0ustar arnauarnauChangelog ========= 2.13.0 (2010-07-11) ------------------- - Released as separate package. zope2.13-2.13.21/source/Products.StandardCacheManagers/src/0000755000175000017500000000000012214017675022177 5ustar arnauarnauzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/0000755000175000017500000000000012214017675031635 5ustar arnauarnau././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/PKG-INFOzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/P0000644000175000017500000000552412214017675031765 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.StandardCacheManagers Version: 2.13.0 Summary: Cache managers for Zope 2. Home-page: http://pypi.python.org/pypi/Products.StandardCacheManagers Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== This package provides two cache managers for Zope 2. A RAMCacheManager and an Accelerated HTTP cache manager, which adds HTTP cache headers to responses. The following is intended for people interested in the internals of RAMCacheManager, such as maintainers. Introduction ============ The caching framework does not interpret the data in any way, it acts just as a general storage for data passed to it. It tries to check if the data is pickleable though. IOW, only pickleable data is cacheable. The idea behind the RAMCacheManager is that it should be shared between threads, so that the same objects are not cached in each thread. This is achieved by storing the cache data structure itself as a module level variable (RAMCacheManager.caches). This, of course, requires locking on modifications of that data structure. Each RAMCacheManager instance has one cache in RAMCacheManager.caches dictionary. A unique __cacheid is generated when creating a cache manager and it's used as a key for caches. Object Hierarchy ================ RAMCacheManager RAMCache ObjectCacheEntries CacheEntry RAMCacheManager is a persistent placeful object. It is assigned a unique __cacheid on its creation. It is then used as a key to look up the corresponding RAMCache object in the global caches dictionary. So, each RAMCacheManager has a single RAMCache related to it. RAMCache is a volatile cache, unique for each RAMCacheManager. It is shared among threads and does all the locking. It has a writelock. No locking is done on reading though. RAMCache keeps a dictionary of ObjectCacheEntries indexed by the physical path of a cached object. ObjectCacheEntries is a container for cached values for a single object. The values in it are indexed by a tuple of a view_name, interesting request variables, and extra keywords passed to Cache.ZCache_set(). CacheEntry is a wrapper around a single cached value. It stores the data itself, creation time, view_name and keeps the access count. Changelog ========= 2.13.0 (2010-07-11) ------------------- - Released as separate package. Platform: UNKNOWN ././@LongLink0000000000000000000000000000017000000000000011563 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/dependency_links.txtzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/d0000644000175000017500000000000112214017675031772 0ustar arnauarnau ././@LongLink0000000000000000000000000000016000000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/requires.txtzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/r0000644000175000017500000000010512214017675032015 0ustar arnauarnausetuptools AccessControl transaction Zope2 >= 2.13.0a1 zope.component././@LongLink0000000000000000000000000000017200000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/namespace_packages.txtzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/n0000644000175000017500000000001112214017675032005 0ustar arnauarnauProducts ././@LongLink0000000000000000000000000000016100000000000011563 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/top_level.txtzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/t0000644000175000017500000000001112214017675032013 0ustar arnauarnauProducts ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/SOURCES.txtzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/S0000644000175000017500000000266112214017675031767 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/Products/__init__.py src/Products.StandardCacheManagers.egg-info/PKG-INFO src/Products.StandardCacheManagers.egg-info/SOURCES.txt src/Products.StandardCacheManagers.egg-info/dependency_links.txt src/Products.StandardCacheManagers.egg-info/namespace_packages.txt src/Products.StandardCacheManagers.egg-info/not-zip-safe src/Products.StandardCacheManagers.egg-info/requires.txt src/Products.StandardCacheManagers.egg-info/top_level.txt src/Products/StandardCacheManagers/AcceleratedHTTPCacheManager.py src/Products/StandardCacheManagers/RAMCacheManager.py src/Products/StandardCacheManagers/__init__.py src/Products/StandardCacheManagers/cache.gif src/Products/StandardCacheManagers/configure.zcml src/Products/StandardCacheManagers/subscribers.py src/Products/StandardCacheManagers/dtml/addAccel.dtml src/Products/StandardCacheManagers/dtml/addRCM.dtml src/Products/StandardCacheManagers/dtml/propsAccel.dtml src/Products/StandardCacheManagers/dtml/propsRCM.dtml src/Products/StandardCacheManagers/dtml/statsAccel.dtml src/Products/StandardCacheManagers/dtml/statsRCM.dtml src/Products/StandardCacheManagers/help/Accel.stx src/Products/StandardCacheManagers/help/RAM.stx src/Products/StandardCacheManagers/tests/__init__.py src/Products/StandardCacheManagers/tests/test_AcceleratedHTTPCacheManager.py src/Products/StandardCacheManagers/tests/test_CacheManagerLocation.py././@LongLink0000000000000000000000000000016000000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/not-zip-safezope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products.StandardCacheManagers.egg-info/n0000644000175000017500000000000112214017675032004 0ustar arnauarnau zope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/0000755000175000017500000000000012214017675024002 5ustar arnauarnauzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/__init__.py0000644000175000017500000000007012214017675026110 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/0000755000175000017500000000000012214017675030144 5ustar arnauarnau././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/RAMCacheManager.pyzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/RAMCacheMa0000644000175000017500000004114212214017675031712 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## ''' RAM cache manager -- Caches the results of method calls in RAM. ''' from cgi import escape from thread import allocate_lock import time from AccessControl.class_init import InitializeClass from AccessControl.Permissions import view_management_screens from AccessControl.SecurityInfo import ClassSecurityInfo from App.special_dtml import DTMLFile from OFS.Cache import Cache from OFS.Cache import CacheManager from OFS.SimpleItem import SimpleItem try: from cPickle import Pickler from cPickle import HIGHEST_PROTOCOL except ImportError: from pickle import Pickler from pickle import HIGHEST_PROTOCOL _marker = [] # Create a new marker object. class CacheException (Exception): ''' A cache-related exception. ''' class CacheEntry: ''' Represents a cached value. ''' def __init__(self, index, data, view_name): try: # This is a protective barrier that hopefully prevents # us from caching something that might result in memory # leaks. It's also convenient for determining the # approximate memory usage of the cache entry. # DM 2004-11-29: this code causes excessive time. # Note also that it does not prevent us from # caching objects with references to persistent objects # When we do, nasty persistency errors are likely # to occur ("shouldn't load data while connection is closed"). #self.size = len(dumps(index)) + len(dumps(data)) sizer = _ByteCounter() pickler = Pickler(sizer, HIGHEST_PROTOCOL) pickler.dump(index) pickler.dump(data) self.size = sizer.getCount() except: raise CacheException('The data for the cache is not pickleable.') self.created = time.time() self.data = data self.view_name = view_name self.access_count = 0 class ObjectCacheEntries: ''' Represents the cache for one Zope object. ''' hits = 0 misses = 0 def __init__(self, path): self.physical_path = path self.lastmod = 0 # Mod time of the object, class, etc. self.entries = {} def aggregateIndex(self, view_name, req, req_names, local_keys): ''' Returns the index to be used when looking for or inserting a cache entry. view_name is a string. local_keys is a mapping or None. ''' req_index = [] # Note: req_names is already sorted. for key in req_names: if req is None: val = '' else: val = req.get(key, '') req_index.append((str(key), str(val))) if local_keys: local_index = [] for key, val in local_keys.items(): local_index.append((str(key), str(val))) local_index.sort() else: local_index = () return (str(view_name), tuple(req_index), tuple(local_index)) def getEntry(self, lastmod, index): if self.lastmod < lastmod: # Expired. self.entries = {} self.lastmod = lastmod return _marker return self.entries.get(index, _marker) def setEntry(self, lastmod, index, data, view_name): self.lastmod = lastmod self.entries[index] = CacheEntry(index, data, view_name) def delEntry(self, index): try: del self.entries[index] except KeyError: pass class RAMCache (Cache): # Note the need to take thread safety into account. # Also note that objects of this class are not persistent, # nor do they make use of acquisition. max_age = 0 def __init__(self): # cache maps physical paths to ObjectCacheEntries. self.cache = {} self.writelock = allocate_lock() self.next_cleanup = 0 def initSettings(self, kw): # Note that we lazily allow RAMCacheManager # to verify the correctness of the internal settings. self.__dict__.update(kw) def getObjectCacheEntries(self, ob, create=0): """ Finds or creates the associated ObjectCacheEntries object. Remember to lock writelock when calling with the 'create' flag. """ cache = self.cache path = ob.getPhysicalPath() oc = cache.get(path, None) if oc is None: if create: cache[path] = oc = ObjectCacheEntries(path) else: return None return oc def countAllEntries(self): ''' Returns the count of all cache entries. ''' count = 0 for oc in self.cache.values(): count = count + len(oc.entries) return count def countAccesses(self): ''' Returns a mapping of (n) -> number of entries accessed (n) times ''' counters = {} for oc in self.cache.values(): for entry in oc.entries.values(): access_count = entry.access_count counters[access_count] = counters.get( access_count, 0) + 1 return counters def clearAccessCounters(self): ''' Clears access_count for each cache entry. ''' for oc in self.cache.values(): for entry in oc.entries.values(): entry.access_count = 0 def deleteEntriesAtOrBelowThreshold(self, threshold_access_count): """ Deletes entries that haven't been accessed recently. """ self.writelock.acquire() try: for p, oc in self.cache.items(): for agindex, entry in oc.entries.items(): if entry.access_count <= threshold_access_count: del oc.entries[agindex] if len(oc.entries) < 1: del self.cache[p] finally: self.writelock.release() def deleteStaleEntries(self): """ Deletes entries that have expired. """ if self.max_age > 0: self.writelock.acquire() try: min_created = time.time() - self.max_age for p, oc in self.cache.items(): for agindex, entry in oc.entries.items(): if entry.created < min_created: del oc.entries[agindex] if len(oc.entries) < 1: del self.cache[p] finally: self.writelock.release() def cleanup(self): ''' Removes cache entries. ''' self.deleteStaleEntries() new_count = self.countAllEntries() if new_count > self.threshold: counters = self.countAccesses() priorities = counters.items() # Remove the least accessed entries until we've reached # our target count. if len(priorities) > 0: priorities.sort() access_count = 0 for access_count, effect in priorities: new_count = new_count - effect if new_count <= self.threshold: break self.deleteEntriesAtOrBelowThreshold(access_count) self.clearAccessCounters() def getCacheReport(self): """ Reports on the contents of the cache. """ rval = [] for oc in self.cache.values(): size = 0 ac = 0 views = [] for entry in oc.entries.values(): size = size + entry.size ac = ac + entry.access_count view = entry.view_name or '' if view not in views: views.append(view) views.sort() info = {'path': '/'.join(oc.physical_path), 'hits': oc.hits, 'misses': oc.misses, 'size': size, 'counter': ac, 'views': views, 'entries': len(oc.entries), } rval.append(info) return rval def ZCache_invalidate(self, ob): ''' Invalidates the cache entries that apply to ob. ''' path = ob.getPhysicalPath() # Invalidates all subobjects as well. self.writelock.acquire() try: for p, oc in self.cache.items(): pp = oc.physical_path if pp[:len(path)] == path: del self.cache[p] finally: self.writelock.release() def ZCache_get(self, ob, view_name='', keywords=None, mtime_func=None, default=None): ''' Gets a cache entry or returns default. ''' oc = self.getObjectCacheEntries(ob) if oc is None: return default lastmod = ob.ZCacheable_getModTime(mtime_func) index = oc.aggregateIndex(view_name, ob.REQUEST, self.request_vars, keywords) entry = oc.getEntry(lastmod, index) if entry is _marker: return default if self.max_age > 0 and entry.created < time.time() - self.max_age: # Expired. self.writelock.acquire() try: oc.delEntry(index) finally: self.writelock.release() return default oc.hits = oc.hits + 1 entry.access_count = entry.access_count + 1 return entry.data def ZCache_set(self, ob, data, view_name='', keywords=None, mtime_func=None): ''' Sets a cache entry. ''' now = time.time() if self.next_cleanup <= now: self.cleanup() self.next_cleanup = now + self.cleanup_interval lastmod = ob.ZCacheable_getModTime(mtime_func) self.writelock.acquire() try: oc = self.getObjectCacheEntries(ob, create=1) index = oc.aggregateIndex(view_name, ob.REQUEST, self.request_vars, keywords) oc.setEntry(lastmod, index, data, view_name) oc.misses = oc.misses + 1 finally: self.writelock.release() caches = {} PRODUCT_DIR = __name__.split('.')[-2] class RAMCacheManager (CacheManager, SimpleItem): """Manage a RAMCache, which stores rendered data in RAM. This is intended to be used as a low-level cache for expensive Python code, not for objects published under their own URLs such as web pages. RAMCacheManager *can* be used to cache complete publishable pages, such as DTMLMethods/Documents and Page Templates, but this is not advised: such objects typically do not attempt to cache important out-of-band data such as 3xx HTTP responses, and the client would get an erroneous 200 response. Such objects should instead be cached with an AcceleratedHTTPCacheManager and/or downstream caching. """ security = ClassSecurityInfo() security.setPermissionDefault('Change cache managers', ('Manager', )) manage_options = ( {'label': 'Properties', 'action': 'manage_main', 'help': (PRODUCT_DIR, 'RAM.stx')}, {'label': 'Statistics', 'action': 'manage_stats', 'help': (PRODUCT_DIR, 'RAM.stx')}, ) + CacheManager.manage_options + SimpleItem.manage_options meta_type = 'RAM Cache Manager' def __init__(self, ob_id): self.id = ob_id self.title = '' self._settings = { 'threshold': 1000, 'cleanup_interval': 300, 'request_vars': ('AUTHENTICATED_USER', ), 'max_age': 3600, } self._resetCacheId() def getId(self): ' ' return self.id security.declarePrivate('_remove_data') def _remove_data(self): caches.pop(self.__cacheid, None) security.declarePrivate('_resetCacheId') def _resetCacheId(self): self.__cacheid = '%s_%f' % (id(self), time.time()) ZCacheManager_getCache__roles__ = () def ZCacheManager_getCache(self): cacheid = self.__cacheid try: return caches[cacheid] except KeyError: cache = RAMCache() cache.initSettings(self._settings) caches[cacheid] = cache return cache security.declareProtected(view_management_screens, 'getSettings') def getSettings(self): 'Returns the current cache settings.' res = self._settings.copy() if 'max_age' not in res: res['max_age'] = 0 return res security.declareProtected(view_management_screens, 'manage_main') manage_main = DTMLFile('dtml/propsRCM', globals()) security.declareProtected('Change cache managers', 'manage_editProps') def manage_editProps(self, title, settings=None, REQUEST=None): 'Changes the cache settings.' if settings is None: settings = REQUEST self.title = str(title) request_vars = list(settings['request_vars']) request_vars.sort() self._settings = { 'threshold': int(settings['threshold']), 'cleanup_interval': int(settings['cleanup_interval']), 'request_vars': tuple(request_vars), 'max_age': int(settings['max_age']), } cache = self.ZCacheManager_getCache() cache.initSettings(self._settings) if REQUEST is not None: return self.manage_main( self, REQUEST, manage_tabs_message='Properties changed.') security.declareProtected(view_management_screens, 'manage_stats') manage_stats = DTMLFile('dtml/statsRCM', globals()) def _getSortInfo(self): """ Returns the value of sort_by and sort_reverse. If not found, returns default values. """ req = self.REQUEST sort_by = req.get('sort_by', 'hits') sort_reverse = int(req.get('sort_reverse', 1)) return sort_by, sort_reverse security.declareProtected(view_management_screens, 'getCacheReport') def getCacheReport(self): """ Returns the list of objects in the cache, sorted according to the user's preferences. """ sort_by, sort_reverse = self._getSortInfo() c = self.ZCacheManager_getCache() rval = c.getCacheReport() if sort_by: rval.sort(lambda e1, e2, sort_by=sort_by: cmp(e1[sort_by], e2[sort_by])) if sort_reverse: rval.reverse() return rval security.declareProtected(view_management_screens, 'sort_link') def sort_link(self, name, id): """ Utility for generating a sort link. """ sort_by, sort_reverse = self._getSortInfo() url = self.absolute_url() + '/manage_stats?sort_by=' + id newsr = 0 if sort_by == id: newsr = not sort_reverse url = url + '&sort_reverse=' + (newsr and '1' or '0') return '%s' % (escape(url, 1), escape(name)) security.declareProtected('Change cache managers', 'manage_invalidate') def manage_invalidate(self, paths, REQUEST=None): """ ZMI helper to invalidate an entry """ for path in paths: try: ob = self.unrestrictedTraverse(path) except (AttributeError, KeyError): pass ob.ZCacheable_invalidate() if REQUEST is not None: msg = 'Cache entries invalidated' return self.manage_stats(manage_tabs_message=msg) InitializeClass(RAMCacheManager) class _ByteCounter: '''auxiliary file like class which just counts the bytes written.''' _count = 0 def write(self, bytes): self._count += len(bytes) def getCount(self): return self._count manage_addRAMCacheManagerForm = DTMLFile('dtml/addRCM', globals()) def manage_addRAMCacheManager(self, id, REQUEST=None): 'Adds a RAM cache manager to the folder.' self._setObject(id, RAMCacheManager(id)) if REQUEST is not None: return self.manage_main(self, REQUEST) ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/configure.zcmlzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/configure.0000644000175000017500000000162612214017675032133 0ustar arnauarnau ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/subscribers.pyzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/subscriber0000644000175000017500000000163012214017675032232 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ subscribers to events affecting StandardCacheManagers """ def cloned(obj, event): """ Reset the Id of the module level cache so the clone gets a different cache than its source object """ obj._resetCacheId() def removed(obj, event): obj._remove_data() zope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/cache.gif0000644000175000017500000000023612214017675031677 0ustar arnauarnauGIF89aãÙÙÙtttbbb\\\HHH¦¦¦$$$ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!þ Cache manager!ù ,:ÉI«½8O*¿0t’ägg²µ²ç K6­³G„¡3+¸hƒ œ¼b2¨s¨©Z¯XM;././@LongLink0000000000000000000000000000017100000000000011564 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/AcceleratedHTTPCacheManager.pyzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/Accelerate0000644000175000017500000002423112214017675032121 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## ''' Accelerated HTTP cache manager -- Adds caching headers to the response so that downstream caches will cache according to a common policy. ''' from cgi import escape import httplib import logging import socket import time from urllib import quote import urlparse from AccessControl.class_init import InitializeClass from AccessControl.Permissions import view_management_screens from AccessControl.SecurityInfo import ClassSecurityInfo from App.Common import rfc1123_date from App.special_dtml import DTMLFile from OFS.Cache import Cache from OFS.Cache import CacheManager from OFS.SimpleItem import SimpleItem logger = logging.getLogger('Zope.AcceleratedHTTPCacheManager') class AcceleratedHTTPCache (Cache): # Note the need to take thread safety into account. # Also note that objects of this class are not persistent, # nor do they use acquisition. connection_factory = httplib.HTTPConnection def __init__(self): self.hit_counts = {} def initSettings(self, kw): # Note that we lazily allow AcceleratedHTTPCacheManager # to verify the correctness of the internal settings. self.__dict__.update(kw) def ZCache_invalidate(self, ob): # Note that this only works for default views of objects at # their canonical path. If an object is viewed and cached at # any other path via acquisition or virtual hosting, that # cache entry cannot be purged because there is an infinite # number of such possible paths, and Squid does not support # any kind of fuzzy purging; we have to specify exactly the # URL to purge. So we try to purge the known paths most # likely to turn up in practice: the physical path and the # current absolute_url_path. Any of those can be # wrong in some circumstances, but it may be the best we can # do :-( # It would be nice if Squid's purge feature was better # documented. (pot! kettle! black!) phys_path = ob.getPhysicalPath() if phys_path in self.hit_counts: del self.hit_counts[phys_path] purge_paths = (ob.absolute_url_path(), quote('/'.join(phys_path))) # Don't purge the same path twice. if purge_paths[0] == purge_paths[1]: purge_paths = purge_paths[:1] results = [] for url in self.notify_urls: if not url.strip(): continue # Send the PURGE request to each HTTP accelerator. if url[:7].lower() == 'http://': u = url else: u = 'http://' + url (scheme, host, path, params, query, fragment) = urlparse.urlparse(u) if path.lower().startswith('/http://'): path = path.lstrip('/') for ob_path in purge_paths: p = path.rstrip('/') + ob_path h = self.connection_factory(host) logger.debug('PURGING host %s, path %s' % (host, p)) # An exception on one purge should not prevent the others. try: h.request('PURGE', p) # This better not hang. I wish httplib gave us # control of timeouts. except socket.gaierror: msg = 'socket.gaierror: maybe the server ' + \ 'at %s is down, or the cache manager ' + \ 'is misconfigured?' logger.error(msg % url) continue r = h.getresponse() status = '%s %s' % (r.status, r.reason) results.append(status) logger.debug('purge response: %s' % status) return 'Server response(s): ' + ';'.join(results) def ZCache_get(self, ob, view_name, keywords, mtime_func, default): return default def ZCache_set(self, ob, data, view_name, keywords, mtime_func): # Note the blatant ignorance of view_name and keywords. # Standard HTTP accelerators are not able to make use of this # data. mtime_func is also ignored because using "now" for # Last-Modified is as good as using any time in the past. REQUEST = ob.REQUEST RESPONSE = REQUEST.RESPONSE anon = 1 u = REQUEST.get('AUTHENTICATED_USER', None) if u is not None: if u.getUserName() != 'Anonymous User': anon = 0 phys_path = ob.getPhysicalPath() if phys_path in self.hit_counts: hits = self.hit_counts[phys_path] else: self.hit_counts[phys_path] = hits = [0, 0] if anon: hits[0] = hits[0] + 1 else: hits[1] = hits[1] + 1 if not anon and self.anonymous_only: return # Set HTTP Expires and Cache-Control headers seconds=self.interval expires=rfc1123_date(time.time() + seconds) RESPONSE.setHeader('Last-Modified', rfc1123_date(time.time())) RESPONSE.setHeader('Cache-Control', 'max-age=%d' % seconds) RESPONSE.setHeader('Expires', expires) caches = {} PRODUCT_DIR = __name__.split('.')[-2] class AcceleratedHTTPCacheManager (CacheManager, SimpleItem): ' ' security = ClassSecurityInfo() security.setPermissionDefault('Change cache managers', ('Manager', )) manage_options = ( {'label': 'Properties', 'action': 'manage_main', 'help': (PRODUCT_DIR, 'Accel.stx')}, {'label': 'Statistics', 'action': 'manage_stats', 'help': (PRODUCT_DIR, 'Accel.stx')}, ) + CacheManager.manage_options + SimpleItem.manage_options meta_type = 'Accelerated HTTP Cache Manager' def __init__(self, ob_id): self.id = ob_id self.title = '' self._settings = {'anonymous_only': 1, 'interval': 3600, 'notify_urls': ()} self._resetCacheId() def getId(self): ' ' return self.id security.declarePrivate('_remove_data') def _remove_data(self): caches.pop(self.__cacheid, None) security.declarePrivate('_resetCacheId') def _resetCacheId(self): self.__cacheid = '%s_%f' % (id(self), time.time()) security.declarePrivate('ZCacheManager_getCache') def ZCacheManager_getCache(self): cacheid = self.__cacheid try: return caches[cacheid] except KeyError: cache = AcceleratedHTTPCache() cache.initSettings(self._settings) caches[cacheid] = cache return cache security.declareProtected(view_management_screens, 'getSettings') def getSettings(self): ' ' return self._settings.copy() # Don't let UI modify it. security.declareProtected(view_management_screens, 'manage_main') manage_main = DTMLFile('dtml/propsAccel', globals()) security.declareProtected('Change cache managers', 'manage_editProps') def manage_editProps(self, title, settings=None, REQUEST=None): ' ' if settings is None: settings = REQUEST self.title = str(title) self._settings = { 'anonymous_only': settings.get('anonymous_only') and 1 or 0, 'interval': int(settings['interval']), 'notify_urls': tuple(settings['notify_urls'])} cache = self.ZCacheManager_getCache() cache.initSettings(self._settings) if REQUEST is not None: return self.manage_main( self, REQUEST, manage_tabs_message='Properties changed.') security.declareProtected(view_management_screens, 'manage_stats') manage_stats = DTMLFile('dtml/statsAccel', globals()) def _getSortInfo(self): """ Returns the value of sort_by and sort_reverse. If not found, returns default values. """ req = self.REQUEST sort_by = req.get('sort_by', 'anon') sort_reverse = int(req.get('sort_reverse', 1)) return sort_by, sort_reverse security.declareProtected(view_management_screens, 'getCacheReport') def getCacheReport(self): """ Returns the list of objects in the cache, sorted according to the user's preferences. """ sort_by, sort_reverse = self._getSortInfo() c = self.ZCacheManager_getCache() rval = [] for path, (anon, auth) in c.hit_counts.items(): rval.append({'path': '/'.join(path), 'anon': anon, 'auth': auth}) if sort_by: rval.sort(lambda e1, e2, sort_by=sort_by: cmp(e1[sort_by], e2[sort_by])) if sort_reverse: rval.reverse() return rval security.declareProtected(view_management_screens, 'sort_link') def sort_link(self, name, id): """ Utility for generating a sort link. """ # XXX This ought to be in a library or something. sort_by, sort_reverse = self._getSortInfo() url = self.absolute_url() + '/manage_stats?sort_by=' + id newsr = 0 if sort_by == id: newsr = not sort_reverse url = url + '&sort_reverse=' + (newsr and '1' or '0') return '%s' % (escape(url, 1), escape(name)) InitializeClass(AcceleratedHTTPCacheManager) manage_addAcceleratedHTTPCacheManagerForm = DTMLFile('dtml/addAccel', globals()) def manage_addAcceleratedHTTPCacheManager(self, id, REQUEST=None): ' ' self._setObject(id, AcceleratedHTTPCacheManager(id)) if REQUEST is not None: return self.manage_main(self, REQUEST) ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/__init__.pyzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/__init__.p0000644000175000017500000000244612214017675032072 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## ''' Some standard Zope cache managers. ''' import RAMCacheManager import AcceleratedHTTPCacheManager def initialize(context): context.registerClass( RAMCacheManager.RAMCacheManager, constructors = (RAMCacheManager.manage_addRAMCacheManagerForm, RAMCacheManager.manage_addRAMCacheManager), icon="cache.gif" ) context.registerClass( AcceleratedHTTPCacheManager.AcceleratedHTTPCacheManager, constructors = ( AcceleratedHTTPCacheManager.manage_addAcceleratedHTTPCacheManagerForm, AcceleratedHTTPCacheManager.manage_addAcceleratedHTTPCacheManager), icon="cache.gif" ) context.registerHelp() zope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/help/0000755000175000017500000000000012214017675031074 5ustar arnauarnau././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/help/RAM.stxzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/help/RAM.s0000644000175000017500000000543112214017675031702 0ustar arnauarnau RAM Cache Managers The RAM cache manager allows you to cache the result of calling expensive objects, such as Python Scripts and External Methods, in memory. It provides access statistics and simple configuration options. Not all objects are appropriate for use with a RAM Cache Manager. See the **caveats** section below. Storing the result in memory results in the fastest possible cache retrieval, but carries some risks. Unconstrained, it can consume too much RAM. And it doesn't reduce network traffic, it only helps Zope return a result more quickly. Fortunately, RAM cache managers have tunable parameters. You can configure the threshold on the number of entries that should be in the cache, which defaults to 1000. Reduce it if the cache is taking up too much memory or increase it if entries are being cleared too often. You can also configure the cleanup interval. If the RAM cache is fluctuating too much in memory usage, reduce the cleanup interval. Finally, you can configure the list of REQUEST variables that will be used in the cache key. This can be a simple and effective way to distinguish requests from authenticated versus anonymous users or those with session cookies. If you find that some of your objects need certain cache parameters while others need somewhat different parameters, create multiple RAM cache managers. The 'Statistics' tab allows you to view a summary of the contents of the cache. Click the column headers to re-sort the list, twice to sort backwards. You can use the statistics to gauge the benefit of caching each of your objects. For a given object, if the number of hits is less than or not much greater than the number of misses, you probably need to re-evaluate how that object is cached. Caveats You should generally not cache the following with RAM Cache Manager: * Images * Files * Complete web pages Although Zope does not prevent you from doing so, it generally does not make sense to associate any of these objects with a RAM cache manager. The cache will simply not cache image or file data, since the data is already available in RAM. In addition, be careful with complete web pages. The problem is that most cacheable objects will cache only their return value; important out-of-band information such as the HTTP response code is typically not cached. For example, if you cache a page which calls RESPONSE.redirect(), a client that gets a cache hit will see an HTTP 200 response code instead of the redirect. For all of the above objects, another kind of cache manager, an *accelerated HTTP cache manager*, is available and more suitable. ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/help/Accel.stxzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/help/Accel0000644000175000017500000000613212214017675032030 0ustar arnauarnau Accelerated HTTP Cache Managers The HTTP protocol provides for headers that can indicate to downstream proxy caches, browser caches, and dedicated caches that certain documents and images are cacheable. Most images, for example, can safely be cached for a long time. Anonymous visits to most primary pages can be cached as well. An accelerated HTTP cache manager lets you control the headers that get sent with the responses to requests so that downstream caches will know what to cache and for how long. This allows you to reduce the traffic to your site and handle larger loads than otherwise possible. You can associate accelerated HTTP cache managers with any kind of cacheable object that can be viewed through the web. The main risk in using an accelerated HTTP cache manager involves a part of a page setting headers that apply to the whole response. If, for example, your home page contains three parts that are cacheable and one of those parts is associated with an accelerated HTTP cache manager, Zope will return the headers set by the part of the page, making downstream caches think that the whole page should be cached. The workaround is simple: don't use an accelerated HTTP cache manager with objects that make up parts of a page unless you really know what you're doing. There are some parameters available for accelerated HTTP cache managers. The interval is the number of seconds the downstream caches should cache the object. 3600 seconds, or one hour, is a good default. If you find that some objects need one interval and other objects should be set to another interval, use multiple cache managers. If you set the *cache anonymous connections only* checkbox, you will reduce the possibility of caching private data. The *notify URLs* parameter allows you to specify the URLs of specific downstream caches so they can receive invalidation messages as 'PURGE' directives. Dedicated HTTP cache software such as Squid will clear cached data for a given URL when receiving the 'PURGE' directive. (More details below.) Simple statistics are provided. Remember that the only time Zope receives a request that goes through an HTTP cache is when the HTTP cache had a *miss*. So the hits seen by Zope correspond to misses seen by the HTTP cache. To do traffic analysis, you should consult the downstream HTTP caches. When testing the accelerated HTTP cache manager, keep in mind that the *reload* button on most browsers causes the 'Pragma: no-cache' header to be sent, forcing HTTP caches to reload the page as well. Try using telnet, netcat, or tcpwatch to observe the headers. To allow Zope to execute the Squid PURGE directive, make sure the following lines or the equivalent are in squid.conf (changing 'localhost' to the correct host name if Squid is on a different machine):: acl PURGE method purge http_access allow localhost http_access allow purge localhost http_access deny purge http_access deny all zope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/tests/0000755000175000017500000000000012214017675031306 5ustar arnauarnau././@LongLink0000000000000000000000000000017500000000000011570 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/tests/test_CacheManagerLocation.pyzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/tests/test0000644000175000017500000001141712214017675032214 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ Unit tests for AcceleratedCacheManager module. """ import unittest import transaction import zope.component from zope.component import testing as componenttesting from zope.component import eventtesting from AccessControl import SecurityManager from AccessControl.SecurityManagement import newSecurityManager from AccessControl.SecurityManagement import noSecurityManager from OFS.tests.testCopySupport import CopySupportTestBase from OFS.tests.testCopySupport import UnitTestSecurityPolicy from OFS.tests.testCopySupport import UnitTestUser from Zope2.App import zcml from Products.StandardCacheManagers.RAMCacheManager import RAMCacheManager from Products.StandardCacheManagers.AcceleratedHTTPCacheManager \ import AcceleratedHTTPCacheManager import Products.StandardCacheManagers CACHE_META_TYPES = tuple(dict(name=instance_class.meta_type, action='unused_constructor_name', permission="Add %ss" % instance_class.meta_type) for instance_class in (RAMCacheManager, AcceleratedHTTPCacheManager)) class CacheManagerLocationTests(CopySupportTestBase): _targetClass = None def _makeOne(self, *args, **kw): return self._targetClass(*args, **kw) def setUp(self): componenttesting.setUp() eventtesting.setUp() zcml.load_config('meta.zcml', zope.component) zcml.load_config('configure.zcml', Products.StandardCacheManagers) folder1, folder2 = self._initFolders() folder1.all_meta_types = folder2.all_meta_types = CACHE_META_TYPES self.folder1 = folder1 self.folder2 = folder2 self.policy = UnitTestSecurityPolicy() self.oldPolicy = SecurityManager.setSecurityPolicy(self.policy) cm_id = 'cache' manager = self._makeOne(cm_id) self.folder1._setObject(cm_id, manager) self.cachemanager = self.folder1[cm_id] transaction.savepoint(optimistic=True) newSecurityManager(None, UnitTestUser().__of__(self.root)) CopySupportTestBase.setUp(self) def tearDown(self): noSecurityManager() SecurityManager.setSecurityPolicy(self.oldPolicy) del self.oldPolicy del self.policy del self.folder2 del self.folder1 self._cleanApp() componenttesting.tearDown() CopySupportTestBase.tearDown(self) def test_cache_differs_on_copy(self): # ensure copies don't hit the same cache cache = self.cachemanager.ZCacheManager_getCache() cachemanager_copy = self.folder2.manage_clone(self.cachemanager, 'cache_copy') cache_copy = cachemanager_copy.ZCacheManager_getCache() self.assertNotEqual(cache, cache_copy) def test_cache_remains_on_move(self): # test behaviour of cache on move. # NOTE: This test verifies current behaviour, but there is no actual # need for cache managers to maintain the same cache on move. # if physical path starts being used as a cache key, this test might # need to be fixed. cache = self.cachemanager.ZCacheManager_getCache() cut = self.folder1.manage_cutObjects(['cache']) self.folder2.manage_pasteObjects(cut) cachemanager_moved = self.folder2['cache'] cache_moved = cachemanager_moved.ZCacheManager_getCache() self.assertEqual(cache, cache_moved) def test_cache_deleted_on_remove(self): old_cache = self.cachemanager.ZCacheManager_getCache() self.folder1.manage_delObjects(['cache']) new_cache = self.cachemanager.ZCacheManager_getCache() self.assertNotEqual(old_cache, new_cache) class AcceleratedHTTPCacheManagerLocationTests(CacheManagerLocationTests): _targetClass = AcceleratedHTTPCacheManager class RamCacheManagerLocationTests(CacheManagerLocationTests): _targetClass = RAMCacheManager def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(AcceleratedHTTPCacheManagerLocationTests)) suite.addTest(unittest.makeSuite(RamCacheManagerLocationTests)) return suite ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/tests/__init__.pyzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/tests/__in0000644000175000017500000000126212214017675032136 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ Unit tests for StandardCacheManagers product. """ ././@LongLink0000000000000000000000000000020400000000000011561 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/tests/test_AcceleratedHTTPCacheManager.pyzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/tests/test0000644000175000017500000001217712214017675032220 0ustar arnauarnau############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """ Unit tests for AcceleratedCacheManager module. """ import unittest from Products.StandardCacheManagers.AcceleratedHTTPCacheManager \ import AcceleratedHTTPCache, AcceleratedHTTPCacheManager class DummyObject: def __init__(self, path='/path/to/object', urlpath=None): self.path = path if urlpath is None: self.urlpath = path else: self.urlpath = urlpath def getPhysicalPath(self): return tuple(self.path.split('/')) def absolute_url_path(self): return self.urlpath class MockResponse: status = '200' reason = "who knows, I'm just a mock" def MockConnectionClassFactory(): # Returns both a class that mocks an HTTPConnection, # and a reference to a data structure where it logs requests. request_log = [] class MockConnection: # Minimal replacement for httplib.HTTPConnection. def __init__(self, host): self.host = host self.request_log = request_log def request(self, method, path): self.request_log.append({'method': method, 'host': self.host, 'path': path}) def getresponse(self): return MockResponse() return MockConnection, request_log class AcceleratedHTTPCacheTests(unittest.TestCase): def _getTargetClass(self): return AcceleratedHTTPCache def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def test_PURGE_passes_Host_header(self): _TO_NOTIFY = 'localhost:1888' cache = self._makeOne() cache.notify_urls = ['http://%s' % _TO_NOTIFY] cache.connection_factory, requests = MockConnectionClassFactory() dummy = DummyObject() cache.ZCache_invalidate(dummy) self.assertEqual(len(requests), 1) result = requests[-1] self.assertEqual(result['method'], 'PURGE') self.assertEqual(result['host'], _TO_NOTIFY) self.assertEqual(result['path'], dummy.path) def test_multiple_notify(self): cache = self._makeOne() cache.notify_urls = ['http://foo', 'bar', 'http://baz/bat'] cache.connection_factory, requests = MockConnectionClassFactory() cache.ZCache_invalidate(DummyObject()) self.assertEqual(len(requests), 3) self.assertEqual(requests[0]['host'], 'foo') self.assertEqual(requests[1]['host'], 'bar') self.assertEqual(requests[2]['host'], 'baz') cache.ZCache_invalidate(DummyObject()) self.assertEqual(len(requests), 6) def test_vhost_purging_1447(self): # Test for http://www.zope.org/Collectors/Zope/1447 cache = self._makeOne() cache.notify_urls = ['http://foo.com'] cache.connection_factory, requests = MockConnectionClassFactory() dummy = DummyObject(urlpath='/published/elsewhere') cache.ZCache_invalidate(dummy) # That should fire off two invalidations, # one for the physical path and one for the abs. url path. self.assertEqual(len(requests), 2) self.assertEqual(requests[0]['path'], dummy.absolute_url_path()) self.assertEqual(requests[1]['path'], dummy.path) class CacheManagerTests(unittest.TestCase): def _getTargetClass(self): return AcceleratedHTTPCacheManager def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def _makeContext(self): from OFS.Folder import Folder root = Folder() root.getPhysicalPath = lambda: ('', 'some_path', ) cm_id = 'http_cache' manager = self._makeOne(cm_id) root._setObject(cm_id, manager) manager = root[cm_id] return root, manager def test_add(self): # ensure __init__ doesn't raise errors. root, cachemanager = self._makeContext() def test_ZCacheManager_getCache(self): root, cachemanager = self._makeContext() cache = cachemanager.ZCacheManager_getCache() self.assert_(isinstance(cache, AcceleratedHTTPCache)) def test_getSettings(self): root, cachemanager = self._makeContext() settings = cachemanager.getSettings() self.assert_('anonymous_only' in settings.keys()) self.assert_('interval' in settings.keys()) self.assert_('notify_urls' in settings.keys()) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(AcceleratedHTTPCacheTests)) suite.addTest(unittest.makeSuite(CacheManagerTests)) return suite zope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/0000755000175000017500000000000012214017675031104 5ustar arnauarnau././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/addAccel.dtmlzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/addAc0000644000175000017500000000133712214017675032027 0ustar arnauarnau
    Id
    ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/statsRCM.dtmlzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/stats0000644000175000017500000000603212214017675032166 0ustar arnauarnau

    Memory usage is approximate. It is based on the pickled value of the cached data. The cache is cleaned up by removing the least frequently accessed entries since the last cleanup operation. The determination is made using the recent hits counter.

    &dtml-hits;
    &dtml-counter;
    &dtml-misses;
    &dtml-size;
    &dtml-entries;

    Nothing is in the cache.

    ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/propsAccel.dtmlzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/props0000644000175000017500000000305012214017675032170 0ustar arnauarnau
    Title
    Interval (seconds)
    Cache anonymous
    connections only?
    checked="checked" />
    Notify URLs (via PURGE)
    ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/propsRCM.dtmlzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/props0000644000175000017500000000405012214017675032171 0ustar arnauarnau

    The RAM Cache Manager allows you to cache the result of calling expensive objects, such as Python Scripts and External Methods, in memory. Because it does not cache HTTP headers, caching full web pages is generally not advised.

    Title
    REQUEST variables
    Threshold entries
    Maximum age of a cache entry (seconds)
    Cleanup interval (seconds)
    ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/statsAccel.dtmlzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/stats0000644000175000017500000000251412214017675032167 0ustar arnauarnau

    Cache manager hits generally correspond to HTTP accelerator misses. A hit is counted in the "authenticated hits" column even if headers are only set for anonymous requests.

    &dtml-anon;
    &dtml-auth;

    Nothing is in the cache.

    ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/addRCM.dtmlzope2.13-2.13.21/source/Products.StandardCacheManagers/src/Products/StandardCacheManagers/dtml/addRC0000644000175000017500000000130612214017675032004 0ustar arnauarnau
    Id
    zope2.13-2.13.21/source/Products.OFSP/0000755000175000017500000000000012214017450016004 5ustar arnauarnauzope2.13-2.13.21/source/Products.OFSP/setup.py0000644000175000017500000000252712214017450017524 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from setuptools import setup, find_packages setup(name='Products.OFSP', version = '2.13.2', url='http://pypi.python.org/pypi/Products.OFSP', license='ZPL 2.1', description="General Zope 2 help screens.", author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=open('README.txt').read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), namespace_packages=['Products'], package_dir={'': 'src'}, install_requires=[ 'setuptools', 'AccessControl', 'Persistence', 'Zope2 >= 2.13.0a1', ], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/Products.OFSP/PKG-INFO0000644000175000017500000000162012214017450017100 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.OFSP Version: 2.13.2 Summary: General Zope 2 help screens. Home-page: http://pypi.python.org/pypi/Products.OFSP Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== OFSP provides the general Zope 2 help. Changelog ========= 2.13.2 (2010-07-25) ------------------- - Moved the registration of OFS types back into this packages `initialize` function to avoid test setup problems with installPackage in ZopeTestCase. 2.13.1 (2010-07-11) ------------------- - Added a ``configure.zcml`` to automatically load the OFS package. 2.13.0 (2010-07-10) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Products.OFSP/pip-egg-info/0000755000175000017500000000000012214017451020266 5ustar arnauarnauzope2.13-2.13.21/source/Products.OFSP/pip-egg-info/Products.OFSP.egg-info/0000755000175000017500000000000012214017451024331 5ustar arnauarnauzope2.13-2.13.21/source/Products.OFSP/pip-egg-info/Products.OFSP.egg-info/PKG-INFO0000644000175000017500000000162012214017451025425 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.OFSP Version: 2.13.2 Summary: General Zope 2 help screens. Home-page: http://pypi.python.org/pypi/Products.OFSP Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== OFSP provides the general Zope 2 help. Changelog ========= 2.13.2 (2010-07-25) ------------------- - Moved the registration of OFS types back into this packages `initialize` function to avoid test setup problems with installPackage in ZopeTestCase. 2.13.1 (2010-07-11) ------------------- - Added a ``configure.zcml`` to automatically load the OFS package. 2.13.0 (2010-07-10) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Products.OFSP/pip-egg-info/Products.OFSP.egg-info/dependency_links.txt0000644000175000017500000000000112214017451030377 0ustar arnauarnau zope2.13-2.13.21/source/Products.OFSP/pip-egg-info/Products.OFSP.egg-info/requires.txt0000644000175000017500000000006612214017451026733 0ustar arnauarnausetuptools AccessControl Persistence Zope2 >= 2.13.0a1zope2.13-2.13.21/source/Products.OFSP/pip-egg-info/Products.OFSP.egg-info/namespace_packages.txt0000644000175000017500000000001112214017451030654 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.OFSP/pip-egg-info/Products.OFSP.egg-info/top_level.txt0000644000175000017500000000001112214017451027053 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.OFSP/pip-egg-info/Products.OFSP.egg-info/SOURCES.txt0000644000175000017500000000075012214017451026217 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/Products.OFSP.egg-info/PKG-INFO pip-egg-info/Products.OFSP.egg-info/SOURCES.txt pip-egg-info/Products.OFSP.egg-info/dependency_links.txt pip-egg-info/Products.OFSP.egg-info/namespace_packages.txt pip-egg-info/Products.OFSP.egg-info/not-zip-safe pip-egg-info/Products.OFSP.egg-info/requires.txt pip-egg-info/Products.OFSP.egg-info/top_level.txt src/Products/__init__.py src/Products/OFSP/Draft.py src/Products/OFSP/Version.py src/Products/OFSP/__init__.pyzope2.13-2.13.21/source/Products.OFSP/pip-egg-info/Products.OFSP.egg-info/not-zip-safe0000644000175000017500000000000112214017451026557 0ustar arnauarnau zope2.13-2.13.21/source/Products.OFSP/LICENSE.txt0000644000175000017500000000402612214017450017631 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/Products.OFSP/README.txt0000644000175000017500000000007212214017450017501 0ustar arnauarnauOverview ======== OFSP provides the general Zope 2 help. zope2.13-2.13.21/source/Products.OFSP/setup.cfg0000644000175000017500000000007312214017450017625 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/Products.OFSP/COPYRIGHT.txt0000644000175000017500000000004012214017450020107 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/Products.OFSP/buildout.cfg0000644000175000017500000000027312214017450020316 0ustar arnauarnau[buildout] develop = . parts = interpreter test [interpreter] recipe = zc.recipe.egg interpreter = python eggs = Products.OFSP [test] recipe = zc.recipe.testrunner eggs = Products.OFSP zope2.13-2.13.21/source/Products.OFSP/bootstrap.py0000644000175000017500000000742012214017450020376 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 108946 2010-02-12 02:40:18Z yusei $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/Products.OFSP/CHANGES.txt0000644000175000017500000000061612214017450017620 0ustar arnauarnauChangelog ========= 2.13.2 (2010-07-25) ------------------- - Moved the registration of OFS types back into this packages `initialize` function to avoid test setup problems with installPackage in ZopeTestCase. 2.13.1 (2010-07-11) ------------------- - Added a ``configure.zcml`` to automatically load the OFS package. 2.13.0 (2010-07-10) ------------------- - Released as separate package. zope2.13-2.13.21/source/Products.OFSP/src/0000755000175000017500000000000012214017450016573 5ustar arnauarnauzope2.13-2.13.21/source/Products.OFSP/src/Products.OFSP.egg-info/0000755000175000017500000000000012214017450022636 5ustar arnauarnauzope2.13-2.13.21/source/Products.OFSP/src/Products.OFSP.egg-info/PKG-INFO0000644000175000017500000000162012214017450023732 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.OFSP Version: 2.13.2 Summary: General Zope 2 help screens. Home-page: http://pypi.python.org/pypi/Products.OFSP Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== OFSP provides the general Zope 2 help. Changelog ========= 2.13.2 (2010-07-25) ------------------- - Moved the registration of OFS types back into this packages `initialize` function to avoid test setup problems with installPackage in ZopeTestCase. 2.13.1 (2010-07-11) ------------------- - Added a ``configure.zcml`` to automatically load the OFS package. 2.13.0 (2010-07-10) ------------------- - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Products.OFSP/src/Products.OFSP.egg-info/dependency_links.txt0000644000175000017500000000000112214017450026704 0ustar arnauarnau zope2.13-2.13.21/source/Products.OFSP/src/Products.OFSP.egg-info/requires.txt0000644000175000017500000000006612214017450025240 0ustar arnauarnausetuptools AccessControl Persistence Zope2 >= 2.13.0a1zope2.13-2.13.21/source/Products.OFSP/src/Products.OFSP.egg-info/namespace_packages.txt0000644000175000017500000000001112214017450027161 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.OFSP/src/Products.OFSP.egg-info/top_level.txt0000644000175000017500000000001112214017450025360 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.OFSP/src/Products.OFSP.egg-info/SOURCES.txt0000644000175000017500000001006312214017450024522 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/Products/__init__.py src/Products.OFSP.egg-info/PKG-INFO src/Products.OFSP.egg-info/SOURCES.txt src/Products.OFSP.egg-info/dependency_links.txt src/Products.OFSP.egg-info/namespace_packages.txt src/Products.OFSP.egg-info/not-zip-safe src/Products.OFSP.egg-info/requires.txt src/Products.OFSP.egg-info/top_level.txt src/Products/OFSP/Draft.py src/Products/OFSP/Version.py src/Products/OFSP/__init__.py src/Products/OFSP/configure.zcml src/Products/OFSP/help/CacheManager-associate.stx src/Products/OFSP/help/Cacheable-properties.stx src/Products/OFSP/help/Caching.stx src/Products/OFSP/help/Control-Panel.stx src/Products/OFSP/help/Control-Panel_Contents.stx src/Products/OFSP/help/DTML-Document.stx src/Products/OFSP/help/DTML-DocumentOrMethod_Add.stx src/Products/OFSP/help/DTML-DocumentOrMethod_Edit.stx src/Products/OFSP/help/DTML-DocumentOrMethod_Proxy.stx src/Products/OFSP/help/DTML-DocumentOrMethod_Upload.stx src/Products/OFSP/help/DTML-DocumentOrMethod_View.stx src/Products/OFSP/help/DTML-Method.stx src/Products/OFSP/help/DTMLDocument.py src/Products/OFSP/help/DTMLMethod.py src/Products/OFSP/help/Database-Management.stx src/Products/OFSP/help/Database-Management_Activity.stx src/Products/OFSP/help/Database-Management_Cache-Parameters.stx src/Products/OFSP/help/Database-Management_Database.stx src/Products/OFSP/help/Database-Management_Flush-Cache.stx src/Products/OFSP/help/DateTime.py src/Products/OFSP/help/DavLocks-ManageLocks.stx src/Products/OFSP/help/Debug-Information_Debug.stx src/Products/OFSP/help/Debug-Information_Profile.stx src/Products/OFSP/help/File.py src/Products/OFSP/help/File.stx src/Products/OFSP/help/File_Add.stx src/Products/OFSP/help/File_Edit.stx src/Products/OFSP/help/File_Upload.stx src/Products/OFSP/help/File_View.stx src/Products/OFSP/help/Find.stx src/Products/OFSP/help/Find_Advanced.stx src/Products/OFSP/help/Folder.py src/Products/OFSP/help/Folder.stx src/Products/OFSP/help/Folder_Add.stx src/Products/OFSP/help/Folder_View.stx src/Products/OFSP/help/History.stx src/Products/OFSP/help/Image.py src/Products/OFSP/help/Image.stx src/Products/OFSP/help/Image_Edit.stx src/Products/OFSP/help/Image_View.stx src/Products/OFSP/help/ObjectManager.py src/Products/OFSP/help/ObjectManagerItem.py src/Products/OFSP/help/ObjectManager_Contents.stx src/Products/OFSP/help/ObjectManager_Import-Export.stx src/Products/OFSP/help/ObjectManager_Rename.stx src/Products/OFSP/help/OrderSupport.py src/Products/OFSP/help/OrderSupport_Contents.stx src/Products/OFSP/help/OrderedFolder.py src/Products/OFSP/help/Product-Management.stx src/Products/OFSP/help/Product.stx src/Products/OFSP/help/Product_Refresh.stx src/Products/OFSP/help/Properties.stx src/Products/OFSP/help/PropertyManager.py src/Products/OFSP/help/PropertySheet.py src/Products/OFSP/help/PropertySheets.py src/Products/OFSP/help/Request.py src/Products/OFSP/help/Response.py src/Products/OFSP/help/Undo.stx src/Products/OFSP/help/ZSearch-Interface.stx src/Products/OFSP/help/ZSearch-Interface_Add.stx src/Products/OFSP/help/dtml-call.stx src/Products/OFSP/help/dtml-comment.stx src/Products/OFSP/help/dtml-funcs.stx src/Products/OFSP/help/dtml-if.stx src/Products/OFSP/help/dtml-in.stx src/Products/OFSP/help/dtml-let.stx src/Products/OFSP/help/dtml-mime.stx src/Products/OFSP/help/dtml-raise.stx src/Products/OFSP/help/dtml-return.stx src/Products/OFSP/help/dtml-sendmail.stx src/Products/OFSP/help/dtml-sqlgroup.stx src/Products/OFSP/help/dtml-sqltest.stx src/Products/OFSP/help/dtml-sqlvar.stx src/Products/OFSP/help/dtml-tree.stx src/Products/OFSP/help/dtml-try.stx src/Products/OFSP/help/dtml-unless.stx src/Products/OFSP/help/dtml-var.stx src/Products/OFSP/help/dtml-with.stx src/Products/OFSP/help/math.py src/Products/OFSP/help/random.py src/Products/OFSP/help/sequence.py src/Products/OFSP/help/string.py src/Products/OFSP/images/File_icon.gif src/Products/OFSP/images/Folder_icon.gif src/Products/OFSP/images/Image_icon.gif src/Products/OFSP/images/UserFolder_icon.gif src/Products/OFSP/images/dtmldoc.gif src/Products/OFSP/images/dtmlmethod.gifzope2.13-2.13.21/source/Products.OFSP/src/Products.OFSP.egg-info/not-zip-safe0000644000175000017500000000000112214017450025064 0ustar arnauarnau zope2.13-2.13.21/source/Products.OFSP/src/Products/0000755000175000017500000000000012214017450020376 5ustar arnauarnauzope2.13-2.13.21/source/Products.OFSP/src/Products/__init__.py0000644000175000017500000000007012214017450022504 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/0000755000175000017500000000000012214017450021145 5ustar arnauarnauzope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/images/0000755000175000017500000000000012214017450022412 5ustar arnauarnauzope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/images/Image_icon.gif0000644000175000017500000000162312214017450025135 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,@p@° AxÀ°C‡€pa‹ ظ±bÆBNôø‘áÂZ¼ø!‘?> ‰¦Â—8s¾¤³$J” úüiÒãÅ]î$Ià¡C—<…6tÑæÉŒ/iZ*q)BŽ`Ã;zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/images/dtmldoc.gif0000644000175000017500000000161112214017450024526 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,f@° AxÀ°áƒ…*tØpáÄ)fŒ8"à A"ÌH‘€É#øÈðdÊ+U¶DÙ1¦Ã•h*´I1@Δ:ÑcC¦LJêaÈ«X;zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/images/File_icon.gif0000644000175000017500000000161512214017450024773 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,j@° AxÀ°áƒ…*tØpáÄ)fŒ8ðA€=‚ €pa€†< À’dÇ“ A²$àr"L”*i–ôH1åÊš&{æÊÓ¡O/G~ºsåÌ™?›RlˆTáÓ«-*Ý ;zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/images/UserFolder_icon.gif0000644000175000017500000000162512214017450026167 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,@rH° @,À°¡B°àÅ‹-JD¨0À‚‡F<øpƒ9~\r%Ä W2,ùr¢€8sêÄ)ñ €Ÿ(SÒ4ù³èɉ\2t¹`$B¦C&鱤T™[*¼:ÔjO Ê;zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/images/Folder_icon.gif0000644000175000017500000000160712214017450025330 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,@dH° @,PÈpáÂXð ¢Å‹#"lÈñaD‰ ¸ÑaG%&4É¥FbÊœó£D8G’숳gN•-Y¦D´áÐEMªpéI¥6HJU*€€;zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/images/dtmlmethod.gif0000644000175000017500000000156112214017450025245 0ustar arnauarnauGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,NH°`Á$¨0áB‡6Œhp"D 4@#E… t|¸Q¤I „ìˆÒ _¶,øæ˜$kÞÄ™sçÌ” zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/Draft.py0000644000175000017500000000203112214017450022553 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from OFS.SimpleItem import Item from Persistence import Persistent class Draft(Persistent, Item): "Draft objects" meta_type = 'Zope Draft' security = ClassSecurityInfo() def __init__(self, id, baseid, PATH_INFO): self.id = id def icon(self): return 'p_/broken' InitializeClass(Draft) zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/Version.py0000644000175000017500000000236612214017450023153 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Version object""" __version__='$Revision: 1.55 $'[11:-2] from AccessControl.class_init import InitializeClass from AccessControl.SecurityInfo import ClassSecurityInfo from OFS.SimpleItem import Item from Persistence import Persistent from OFS.ObjectManager import BeforeDeleteException class VersionException(BeforeDeleteException): pass class Version(Persistent, Item): """ """ meta_type='Version' security = ClassSecurityInfo() cookie='' index_html=None # Ugh. def __init__(self, id, title, REQUEST): self.id=id self.title=title def icon(self): return 'p_/broken' InitializeClass(Version) zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/__init__.py0000644000175000017500000000614212214017450023261 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import OFS.Image, OFS.Folder, OFS.userfolder import OFS.DTMLMethod, OFS.DTMLDocument, OFS.PropertySheets import OFS.OrderedFolder from AccessControl.Permissions import add_documents_images_and_files from AccessControl.Permissions import add_folders def initialize(context): context.registerClass( OFS.DTMLMethod.DTMLMethod, permission=add_documents_images_and_files, constructors=(OFS.DTMLMethod.addForm, OFS.DTMLMethod.addDTMLMethod,), icon='images/dtmlmethod.gif', legacy=( ('manage_addDocument', OFS.DTMLMethod.addDTMLMethod), ('manage_addDTMLMethod', OFS.DTMLMethod.addDTMLMethod), ) ) context.registerClass( OFS.DTMLDocument.DTMLDocument, permission=add_documents_images_and_files, constructors=(OFS.DTMLDocument.addForm, OFS.DTMLDocument.addDTMLDocument), icon='images/dtmldoc.gif', legacy=(('manage_addDTMLDocument', OFS.DTMLDocument.addDTMLDocument),), ) context.registerClass( OFS.Image.Image, permission=add_documents_images_and_files, constructors=(('imageAdd',OFS.Image.manage_addImageForm), OFS.Image.manage_addImage), icon='images/Image_icon.gif', legacy=(OFS.Image.manage_addImage,), ) context.registerClass( OFS.Image.File, permission=add_documents_images_and_files, constructors=(('fileAdd',OFS.Image.manage_addFileForm), OFS.Image.manage_addFile), icon='images/File_icon.gif', legacy=(OFS.Image.manage_addFile,), ) context.registerClass( OFS.Folder.Folder, constructors=(OFS.Folder.manage_addFolderForm, OFS.Folder.manage_addFolder), icon='images/Folder_icon.gif', legacy=(OFS.Folder.manage_addFolder,), ) context.registerClass( OFS.OrderedFolder.OrderedFolder, permission=add_folders, constructors=(OFS.OrderedFolder.manage_addOrderedFolderForm, OFS.OrderedFolder.manage_addOrderedFolder), icon='images/Folder_icon.gif', legacy=(OFS.OrderedFolder.manage_addOrderedFolder,), ) context.registerClass( OFS.userfolder.UserFolder, constructors=(OFS.userfolder.manage_addUserFolder,), icon='images/UserFolder_icon.gif', legacy=(OFS.userfolder.manage_addUserFolder,), ) context.registerHelp() context.registerHelpTitle('Zope Help') zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/0000755000175000017500000000000012214017450022075 5ustar arnauarnauzope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-unless.stx0000644000175000017500000000137312214017450025110 0ustar arnauarnauunless: Tests a condition The 'unless' tag provides a shortcut for testing negative conditions. For more complete condition testing use the 'if' tag. Syntax 'unless' tag syntax:: The 'unless' tag is a block tag. If the condition variable or expression evaluates to false, then the contained block is executed. Like the 'if' tag, variables that are not present are considered false. Examples Testing a variable:: The block will be executed if 'testMode' does not exist, or exists but is false. See Also "if tag":dtml-if.stx zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/File_Upload.stx0000644000175000017500000000045312214017450025022 0ustar arnauarnauFile/Image - Upload: File upload. Description Use this view to completely replace the File or Image with an uploaded file from your local computer. Controls 'Data' -- The file to upload. Use the 'Browse...' button to select a local file. 'Change' -- Uploads the file. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Find.stx0000644000175000017500000000215012214017450023513 0ustar arnauarnauObjectManager - Find: Search Zope. Description This view allows you to search for Zope objects. To find objects you specify search criteria in the top frame and then click the 'Find' button. The find results will appear in the button frame. For more search criteria click the 'Advanced...' link. Controls 'Find objects of type' -- The types of objects to find. 'with ids' -- The ids of objects to find. You may specify one or more ids separated by spaces. 'containing' -- The text that must be contained in the *body* of found items. Text in the title or other attribute fields will not be searched. 'modified' -- Allows you to restrict your search to a specific time period. You can choose whether objects 'before' or 'after' a specified date/time. **Note: The date should be a DateTime string such as 'YYYY/MM/DD hh:mm:ss', 'YYYY-MM-DD', or 'hh:mm'.** 'Search only in this folder' -- Find objects in this folder. 'Search all subfolders' -- Find objects in all subfolders. 'Find' -- Find objects matching the find criteria.zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Folder_View.stx0000644000175000017500000000033712214017450025045 0ustar arnauarnauFolder - View: Folder Preview. Description This view allows you to preview the public appearance of a Folder. This view will only be available if the Folder contains (or acquires) an 'index_html' object.zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/PropertySheets.py0000644000175000017500000000372112214017450025452 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class PropertySheets: """ A PropertySheet is an abstraction for organizing and working with a set of related properties. Conceptually it acts like a container for a set of related properties and meta-data describing those properties. PropertySheet objects are accessed through a PropertySheets object that acts as a collection of PropertySheet instances. Objects that support property sheets (objects that support the PropertyManager interface) have a 'propertysheets' attribute (a PropertySheets instance) that is the collection of PropertySheet objects. The PropertySheets object exposes an interface much like a Python mapping, so that individual PropertySheet objects may be accessed via dictionary-style key indexing. """ def values(): """ Return a sequence of all of the PropertySheet objects in the collection. Permission -- Python only """ def items(): """ Return a sequence containing an '(id, object)' tuple for each PropertySheet object in the collection. Permission -- Python only """ def get(name, default=None): """ Return the PropertySheet identified by 'name', or the value given in 'default' if the named PropertySheet is not found. Permission -- Python only """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Product-Management.stx0000644000175000017500000000027412214017450026332 0ustar arnauarnauProduct Management: Contains Zope Products. Description The Product Management Folder contains installed Zope Products. The notion of installed Zope products is deprecated. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/DavLocks-ManageLocks.stx0000644000175000017500000000277512214017450026540 0ustar arnauarnauWebDAV Lock Management - Manage Locks Description This view allows you to manage WebDAV WriteLocks. WebDAV, as an extension to the HTTP Protocol, allows the users to create *Write Locks* as a way of trying to avoid the "lost updates problem". However, sometimes WriteLocks may become abandoned. This may be due to users forgetting to unlock their resources, software failures such as crashes, etc. In many cases, locks might just time out before this becomes a problem. In cases where it's not, this control panel object may be used to locate locked resources inside of Zope and clear *ALL* of their WebDAV writelocks. Controls 'Path' -- This lets you enter a path (based off the root of the Zope site) to filter down the list of locked objects. Clicking 'Go' executes the filter. When locked objects are found, they are listed one per line with a checkbox that can be used to select the item. Also listed in each line is information about the lock(s) on the object - the user who created the lock (identified by the path to the user folder the user is defined in), and the locktoken that identifies the lock. In the majority of cases, there should only be one lock per object. '[Checkbox]' -- Selects locked items. 'Select All' -- This button marks all items displayed as selected. 'Deselect All' -- After 'Select All' has been clicked, it changes to say 'Deselect All'. Clicking this deselects all displayed items. 'Unlock objects' -- Unlocks the selected items.zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-comment.stx0000644000175000017500000000127612214017450025243 0ustar arnauarnaucomment: Comments DTML The comment tag lets you document your DTML with comments. You can also use it to temporarily disable DTML tags by commenting them out. Syntax 'comment' tag syntax:: The 'comment' tag is a block tag. The contents of the block are not executed, nor are they inserted into the DTML output. Examples Documenting DTML:: This content is not executed and does not appear in the output. Commenting out DTML:: This DTML is disabled and will not be executed. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/DTMLMethod.py0000644000175000017500000001104512214017450024351 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## def manage_addDTMLMethod(id, title): """ Add a DTML Method to the current ObjectManager """ class DTMLMethod: """ A DTML Method is a Zope object that contains and executes DTML code. It can act as a template to display other objects. It can also hold small pieces of content which are inserted into other DTML Documents or DTML Methods. The DTML Method's id is available via the 'document_id' variable and the title is available via the 'document_title' variable. """ def __call__(client=None, REQUEST={}, **kw): """ Calling a DTMLMethod causes the Method to interpret the DTML code that it contains. The method returns the result of the interpretation, which can be any kind of object. To accomplish its task, DTML Method often needs to resolve various names into objects. For example, when the code '<dtml-var spam>' is executed, the DTML engine tries to resolve the name 'spam'. In order to resolve names, the Method must be passed a namespace to look them up in. This can be done several ways: * By passing a 'client' object -- If the argument 'client' is passed, then names are looked up as attributes on the argument. * By passing a 'REQUEST' mapping -- If the argument 'REQUEST' is passed, then names are looked up as items on the argument. If the object is not a mapping, an TypeError will be raised when a name lookup is attempted. * By passing keyword arguments -- names and their values can be passed as keyword arguments to the Method. The namespace given to a DTML Method is the composite of these three methods. You can pass any number of them or none at all. Names will be looked up first in the keyword argument, next in the client and finally in the mapping. Unlike DTMLDocuments, DTMLMethods do not look up names in their own instance dictionary. Passing in a namespace to a DTML Method is often referred to as providing the Method with a *context*. DTML Methods can be called three ways: From DTML A DTML Method can be called from another DTML Method or Document:: In this example, the Method 'aDTMLMethod' is being called from another DTML object by name. The calling method passes the value 'this' as the client argument and the current DTML namespace as the REQUEST argument. The above is identical to this following usage in a DTML Python expression:: From Python Products, External Methods, and Scripts can call a DTML Method in the same way as calling a DTML Method from a Python expression in DTML; as shown in the previous example. By the Publisher When the URL of a DTML Method is fetched from Zope, the DTML Method is called by the publisher. The REQUEST object is passed as the second argument to the Method. Permission -- 'View' """ def manage_edit(data, title): """ Change the DTML Method, replacing its contents with 'data' and changing its title. The data argument may be a file object or a string. Permission -- 'Change DTML Methods' """ def document_src(): """ Returns the unrendered source text of the DTML Method. Permission -- 'View management screens' """ def get_size(): """ Returns the size of the unrendered source text of the DTML Method in bytes. Permission -- 'View' """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Control-Panel.stx0000644000175000017500000000052412214017450025313 0ustar arnauarnauControl Panel - Zope administration facilities. Description Control Panel provides centralized Zope administration facilities. In the Control Panel you can restart and shutdown Zope, access debugging information, manage the Zope database, and manage versions. Zope products are located inside the Control Panel. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/ObjectManager.py0000644000175000017500000001310412214017450025147 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class ObjectManager: """ An ObjectManager contains other Zope objects. The contained objects are Object Manager Items. To create an object inside an object manager use 'manage_addProduct':: self.manage_addProduct['OFS'].manage_addFolder(id, title) In DTML this would be:: These examples create a new Folder inside the current ObjectManager. 'manage_addProduct' is a mapping that provides access to product constructor methods. It is indexed by product id. Constructor methods are registered during product initialization and should be documented in the API docs for each addable object. """ def objectIds(type=None): """ This method returns a list of the ids of the contained objects. Optionally, you can pass an argument specifying what object meta_type(es) to restrict the results to. This argument can be a string specifying one meta_type, or it can be a list of strings to specify many. Example:: There are no sub-objects. This DTML code will display all the ids of the objects contained in the current Object Manager. Permission -- 'Access contents information' """ def objectValues(type=None): """ This method returns a sequence of contained objects. Like objectItems and objectIds, it accepts one argument, either a string or a list to restrict the results to objects of a given meta_type or set of meta_types. Example:: This is the icon for the: Folder
    . There are no Folders.
    The results were restricted to Folders by passing a meta_type to 'objectValues' method. Permission -- 'Access contents information' """ def objectItems(type=None): """ This method returns a sequence of (id, object) tuples. Like objectValues and objectIds, it accepts one argument, either a string or a list to restrict the results to objects of a given meta_type or set of meta_types. Each tuple's first element is the id of an object contained in the Object Manager, and the second element is the object itself. Example:: id: , type: There are no sub-objects. Permission -- 'Access contents information' """ def superValues(type): """ This method returns a list of objects of a given meta_type(es) contained in the Object Manager and all its parent Object Managers. The type argument specifies the meta_type(es). It can be a string specifying one meta_type, or it can be a list of strings to specify many. Permission -- Python only """ def manage_delObjects(ids): """ Removes one or more children from the Object Manager. The 'ids' argument is either a list of child ids, or a single child id. Permission -- 'Delete objects' """ def __getitem__(id): """ Returns a child object given a child id. If there is no child with the given id, a KeyError is raised. This method makes it easy to refer to children that have id with file extensions. For example:: page=folder['index.html'] Note: this function only finds children; it doesn't return properties or other non-child attributes. Note: this function doesn't use acquisition to find children. It only returns direct children of the Object Manager. By contrast, using dot notation or 'getattr' will locate children (and other attributes) via acquisition if necessary. Permission -- 'Access contents information' """ def setBrowserDefaultId(id='', acquire=0): """ Sets the id of the object or method used as the default method when the object manager is published. If acquire is set then the default method id will be acquired from the parent container. Permission -- 'Manage folderish settings' """ def getBrowserDefaultId(acquire=0): """ Returns the id of the object or method used as the default when the object manager is published. By default, this setting is acquired. If the acquire argument is true, then the return value will be acquired from the parent if it is not set locally. Otherwise, None is returned if the default id is not set on this object manager. Permission -- 'View' """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Properties.stx0000644000175000017500000000367312214017450025002 0ustar arnauarnauProperties - Define properties. Description This view allows you to edit and define properties on the current object. Property types 'boolean' -- 1 or 0. 'date' -- A 'DateTime' value, for example '12/31/1999 15:42:52 PST'. 'float' -- A decimal number, for example '12.4'. 'int' -- An integer number, for example, '12'. 'lines', 'ulines' -- A list of strings, one per line. 'long' -- A long integer, for example '12232322322323232323423'. 'string', 'ustring' -- A string of characters, for example 'This is a string'. 'text', 'utext' -- A multi-line string, for example a paragraph. 'tokens', 'utokens' -- A list of strings separated by white space, for example 'one two three'. 'selection' -- A string selected by a pop-up menu. 'multiple selection' -- A list of strings selected by a selection list. Some of these textual properties come in two forms. The 'u'-prefixed form stores Unicode text. The form without the prefix stores a python plain string object, which is commonly assumed to contain latin-1 text. Controls Editing Properties Existing properties can be edited by selecting them. '[Checkbox]' -- Select the properties to change. 'Property' -- The value of the property. 'Save Changes' -- Changes the value of the selected properties. 'Delete' -- Deletes the selected properties. Creating new properties 'Id' -- The id of the property. 'Type' -- The type of the property. 'Value' -- The value of the property. **Note: When creating 'selection' and 'multiple selection' properties, specify the name of another property (or method) as the 'Value'. This property (or method) should return a list of strings will be used to provide choices for the selection.** 'Add' -- Creates a new property. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/ObjectManager_Import-Export.stx0000644000175000017500000000173712214017450030157 0ustar arnauarnauFolder - Import/Export: Import/export Zope objects. Description This view allows you to import or export Zope objects. Imported objects will be inserted into the current object. Exported objects will be saved to a file on the Zope server or downloaded to the local client. Controls 'Export object id' -- The id of the object to be exported. **Note: The exported object must be contained by the current object.** 'Export to' -- Where you want to save the exported file. 'Download to local machine' downloads the export file to your client, 'Save to file server' saves the export file to the Zope 'var' directory. 'XML format?' -- Whether the exported object is in a binary format or in XML format. 'Export' -- Exports the object. 'Import file name' -- The filename of the Zope export file that you would like to import. The file must be located in the Zope 'import' directory. 'Import' -- Imports the object. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Cacheable-properties.stx0000644000175000017500000000165012214017450026660 0ustar arnauarnauCacheable objects For background information, see the description of cache management. The 'Cache' tab allows you to associate a cacheable object with a cache manager. It is only available when at least one cache manager exists somewhere in the current context. Use the drop-down box to select which cache manager should be associated with the object. Some types of objects provide additional caching options. DTML methods can be cached according to the DTML namespace. The entry box on the 'Cache' tab allows you to enter the names (one per line) that Zope should look up in the namespace to create the cache key. Note, however, that the namespace lookup operation can be expensive, so use this feature with care. Also note that it is not possible for accelerated HTTP cache managers to make use of this feature. (TODO) Python scripts may also provide further options. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-var.stx0000644000175000017500000001125012214017450024362 0ustar arnauarnauvar: Inserts a variable The 'var' tags allows you insert variables into DTML output. Syntax 'var' tag syntax:: The 'var' tag is a singleton tag. The 'var' tag finds a variable by searching the DTML namespace which usually consists of current object, the current object's containers, and finally the web request. If the variable is found, it is inserted into the DTML output. If not found, Zope raises an error. 'var' tag entity syntax:: &dtml-variableName; Entity syntax is a short cut which inserts and HTML quotes the variable. It is useful when inserting variables into HTML tags. 'var' tag entity syntax with attributes:: &dtml.attribute1[.attribute2]...-variableName; To a limited degree you may specify attributes with the entity syntax. You may include zero or more attributes delimited by periods. You cannot provide arguments for attributes using the entity syntax. If you provide zero or more attributes, then the variable is not automatically HTML quoted. Thus you can avoid HTML quoting with this syntax, '&dtml.-variableName;'. Attributes html_quote -- Convert characters that have special meaning in HTML to HTML character entities. missing=string -- Specify a default value in case Zope cannot find the variable. fmt=string -- Format a variable. Zope provides a few built-in formats including C-style format strings. For more information on C-style format strings see the "Python Library Reference":http://www.python.org/doc/current/lib/typesseq-strings.html If the format string is not a built-in format, then it is assumed to be a method of the object, and it called. whole-dollars -- Formats the variable as dollars. dollars-and-cents -- Formats the variable as dollars and cents. collection-length -- The length of the variable, assuming it is a sequence. structured-text -- Formats the variable as Structured Text. For more information on Structured Text see "Structured Text How-To":http://www.zope.org/Members/millejoh/structuredText on the Zope.org web site. null=string -- A default value to use if the variable is None. lower -- Converts upper-case letters to lower case. upper -- Converts lower-case letters to upper case. capitalize -- Capitalizes the first character of the inserted word. spacify -- Changes underscores in the inserted value to spaces. thousands_commas -- Inserts commas every three digits to the left of a decimal point in values containing numbers for example '12000' becomes '12,000'. url -- Inserts the URL of the object, by calling its 'absolute_url' method. url_quote -- Converts characters that have special meaning in URLs to HTML character entities. url_quote_plus -- URL quotes character, like 'url_quote' but also converts spaces to plus signs. url_unquote -- Converts HTML '%xx' escapes into their single character values (ie: undoes the effects of url_quote). url_unquote_plus -- Like url_unquote, but also replaces '+' characters with spaces (ie: undoes the effects of url_quote_plus). sql_quote -- Converts single quotes to pairs of single quotes. This is needed to safely include values in SQL strings. newline_to_br -- Convert newlines (including carriage returns) to HTML break tags. size=arg -- Truncates the variable at the given length (Note: if a space occurs in the second half of the truncated string, then the string is further truncated to the right-most space). etc=arg -- Specifies a string to add to the end of a string which has been truncated (by setting the 'size' attribute listed above). By default, this is '...' Examples Inserting a simple variable into a document:: Truncation:: will produce the following output if *colors* is the string 'red yellow green':: red yellow, etc. C-style string formatting:: renders to:: 23432.23 Inserting a variable, *link*, inside an HTML 'A' tag with the entity syntax:: Link Inserting a link to a document 'doc', using entity syntax with attributes:: This creates an HTML link to an object using its URL and title. This example calls the object's 'absolute_url' method for the URL (using the 'url' attribute) and its 'title_or_id' method for the title. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-return.stx0000644000175000017500000000112712214017450025113 0ustar arnauarnaureturn: Returns data The 'return' tag stops executing DTML and returns data. It mirrors the Python 'return' statement. Syntax 'return' tag syntax:: Stops execution of DTML and returns a variable or expression. The DTML output is not returned. Usually a return expression is more useful than a return variable. Scripts largely obsolete this tag. Examples Returning a variable:: Returning a Python dictionary:: zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/DTML-DocumentOrMethod_View.stx0000644000175000017500000000015112214017450027602 0ustar arnauarnauDTML Document/Method - View: Display Description This view displays a DTML Document or Method. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/DTMLDocument.py0000644000175000017500000001066212214017450024713 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## def manage_addDocument(id, title): """ Add a DTML Document to the current ObjectManager """ class DTMLDocument: """ A DTML Document is a Zope object that contains and executes DTML code. It is useful to represent web pages. """ def __call__(client=None, REQUEST={}, RESPONSE=None, **kw): """ Calling a DTMLDocument causes the Document to interpret the DTML code that it contains. The method returns the result of the interpretation, which can be any kind of object. To accomplish its task, DTML Document often needs to resolve various names into objects. For example, when the code '<dtml-var spam>' is executed, the DTML engine tries to resolve the name 'spam'. In order to resolve names, the Document must be passed a namespace to look them up in. This can be done several ways: * By passing a 'client' object -- If the argument 'client' is passed, then names are looked up as attributes on the argument. * By passing a 'REQUEST' mapping -- If the argument 'REQUEST' is passed, then names are looked up as items on the argument. If the object is not a mapping, an TypeError will be raised when a name lookup is attempted. * By passing keyword arguments -- names and their values can be passed as keyword arguments to the Document. The namespace given to a DTML Document is the composite of these three methods. You can pass any number of them or none at all. Names are looked up first in the keyword arguments, then in the client, and finally in the mapping. A DTMLDocument implicitly pass itself as a client argument in addition to the specified client, so names are looked up in the DTMLDocument itself. Passing in a namespace to a DTML Document is often referred to as providing the Document with a *context*. DTML Documents can be called three ways. From DTML A DTML Document can be called from another DTML Method or Document:: In this example, the Document 'aDTMLDocument' is being called from another DTML object by name. The calling method passes the value 'this' as the client argument and the current DTML namespace as the REQUEST argument. The above is identical to this following usage in a DTML Python expression:: From Python Products, External Methods, and Scripts can call a DTML Document in the same way as calling a DTML Document from a Python expression in DTML; as shown in the previous example. By the Publisher When the URL of a DTML Document is fetched from Zope, the DTML Document is called by the publisher. The REQUEST object is passed as the second argument to the Document. Permission -- 'View' """ def manage_edit(data, title): """ Change the DTML Document, replacing its contents with 'data' and changing its title. The data argument may be a file object or a string. Permission -- 'Change DTML Documents' """ def document_src(): """ Returns the unrendered source text of the DTML Document. Permission -- 'View management screens' """ def get_size(): """ Returns the size of the unrendered source text of the DTML Document in bytes. Permission -- 'View' """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Image_Edit.stx0000644000175000017500000000131112214017450024620 0ustar arnauarnauImage - Edit: Image Properties. Description This view allows you to edit Image properties. Form Elements 'Title' -- The optional title of the Image. 'Content type' -- The content type of the Image. Zope will try to guess an appropriate content type when you upload an Image. 'Preview' -- Displays a preview version of the actual image content. If an image is very large, the preview may be scaled to a smaller size. 'Last Modified' -- The time that the object was last changed. 'File Size' -- The size (in bytes) of the image data. 'File Data' -- The file to upload. Use the 'Browse...' button to select a local file. 'Upload' -- Uploads the file. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Image.stx0000644000175000017500000000043312214017450023657 0ustar arnauarnauImage - Image object. Description The Image object allows you to store image files in Zope. Images can be displayed in DTML by calling them. For example, <dtml-var myImageId>' The Image will generate an HTML 'IMG' tag with all the correct attributes. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Find_Advanced.stx0000644000175000017500000000322112214017450025300 0ustar arnauarnauObjectManager - Advanced Find: Search Zope. Description This view allows you to search for Zope objects. To find objects you specify search criteria in the top frame and then click the 'Find' button. The find results will appear in the button frame. For fewer search criteria click the 'Simple...' link. Controls 'Find objects of type' -- The types of objects to find. 'with ids' -- The ids of objects to find. You may specify one or more ids separated by spaces. 'containing' -- The text that must be contained in the *body* of found items. Text in the title or other attribute fields will not be searched. 'expr' -- A DTML expressions to restrict found items. If the expression evaluates to false in the context of the found object, the object is rejected. 'modified' -- Allows you to restrict your search to a specific time period. You can choose whether objects 'before' or 'after' a specified date/time. **Note: The date should be a DateTime string such as 'YYYY/MM/DD hh:mm:ss', 'YYYY-MM-DD', or 'hh:mm'.** 'where the roles' -- Use in combination with 'have permission' option. Restricts found objects to those which provide the the indicated permissions for the indicated roles. 'have permission' -- Use in combination with 'where the roles'.Restricts found objects to those which provide the the indicated permissions for the indicated roles. 'Search only in this folder' -- Find objects in this folder. 'Search all subfolders' -- Find objects in all subfolders. 'Find' -- Find objects matching the find criteria.zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/random.py0000644000175000017500000000071312214017450023730 0ustar arnauarnau""" random: Python 'random' module The 'random' module provides pseudo-random number functions. With it, you can generate random numbers and select random elements from sequences. This module is a standard Python module. Since Zope 2.4 requires Python 2.1, make sure to consult the Python 2.1 documentation. See Also "Python 'random' module":http://www.python.org/doc/current/lib/module-random.html documentation at Python.org """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/ObjectManager_Rename.stx0000644000175000017500000000046212214017450026627 0ustar arnauarnauObjectManager - Rename: Change object ids. Description This view allows you to change the id of one or more objects. Controls 'to' -- The new id of each object. The default value is the old id. 'OK' -- Changes the ids of the objects. 'Cancel' -- Cancels renaming operation. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-call.stx0000644000175000017500000000130412214017450024504 0ustar arnauarnaucall: Call a method The 'call' tag lets you call a method without inserting the results into the DTML output. Syntax 'call' tag syntax:: If the call tag uses a variable, the methods arguments are passed automatically by DTML just as with the 'var' tag. If the method is specified in a expression, then you must pass the arguments yourself. Examples Calling by variable name:: This calls the 'UpdateInfo' object automatically passing arguments. Calling by expression:: See Also "var tag":dtml-var.stx zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/ZSearch-Interface_Add.stx0000644000175000017500000000134012214017450026640 0ustar arnauarnauZ Search Interface - Add: Add a Zope Search Interface Description This view allows you to create new a search results document and optionally a search input form. Controls 'Select one or more seachable objects' -- The objects to be searched. 'Report Id' -- The id of the results document. 'Report Title' -- The optional title of the results document. 'Report Style' -- Search results format. 'Search Input Id' -- The id of the input form. **Note: If you do not enter a search input id then no search input form will be created.** 'Search Input Title' -- The optional title of the input form. 'Add' -- Creates a results document and optionally a search input form. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/File.py0000644000175000017500000000500312214017450023324 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## def manage_addFile(id, file='', title='', precondition='', content_type=''): """ Add a new File object. Creates a new File object 'id' with the contents of 'file' """ class File: """ A File is a Zope object that contains file content. A File object can be used to upload or download file information with Zope. Using a File object in Zope is easy. The most common usage is to display the contents of a file object in a web page. This is done by simply referencing the object from DTML:: A more complex example is presenting the File object for download by the user. The next example displays a link to every File object in a folder for the user to download::
    In this example, the 'absolute_url' method and 'id' are used to create a list of HTML hyperlinks to all of the File objects in the current Object Manager. Also see ObjectManager for details on the 'objectValues' method. """ def update_data(data, content_type=None, size=None): """ Updates the contents of the File with 'data'. The 'data' argument must be a string. If 'content_type' is not provided, then a content type will not be set. If size is not provided, the size of the file will be computed from 'data'. Permission -- Python only """ def getSize(): """ Returns the size of the file in bytes. Permission -- 'View' """ def getContentType(): """ Returns the content type of the file. Permission -- 'View' """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Caching.stx0000644000175000017500000000732612214017450024201 0ustar arnauarnauCache Management: Configurable Caching Performing some computations in Zope can take a long time or use a lot of resources. One way to deal with expensive tasks is to cache them. The first time the computation is requested, the results are stored in a table or *cache*. Subsequent requests get the results from the cache. This can result in a dramatic speed increase. There are so many possible strategies for caching that no one could possibly come up with them all. Caches can be stored in memory, on disk, on other computers, or by other means. Caches can be limited in size or unconstrained. They can be made to work with only specific types of objects. They can be tuned in different ways. So instead of trying to provide for every possible caching strategy, Zope defines an API called *cache management* that lets developers write their own caching strategies, or *cache managers*, and lets site administrators easily connect cacheable objects to those cache managers. You can use caching to speed up access to often-requested pages, reduce disk access and network traffic, and deal with heavy loads. All these benefits come with risks of excessive caching, however, so it's important to fine-tune the cache settings. More on this later. How to set up caching The first thing you need to do is create a cache manager instance. In the Zope management interface, go to a folder containing objects that would benefit from caching. From the add list, select a cache manager type such as 'RAM Cache Manager'. Use an ID that describes the purpose of the cache manager. Next, visit one of the objects that you want to cache. A new tab labeled 'Cache' should be visible. Select it. From the drop-down box, select the cache manager you just created and press the 'Change' button. The object is now ready to be cached. Visit the 'View' tab. If the object is a script that takes a long time to render, the first view will still take just as long as before. But if you're using a RAM cache manager or similar, the second view should be much faster. Press the *reload* button in your browser to try it out. You can associate many objects to a cache manager at once using the 'Associate' tab of all cache managers. Visit the cache manager object you created and select the 'Associate' tab. Press the 'Locate' button. Zope will locate all cacheable objects in the folder. Select the checkboxes next to the objects you want to cache and press the 'Save changes' button. Inherent risks Cache managers generally don't know the nature of what is being cached, so here are some issues that can surface: - Data that is intended for authorized viewers only can be inadvertently cached in public caches. - Data is cached for too long a time. - If more than one cache is involved, data is purged from one cache but not the other. - A method that makes up part of the page sets the caching headers for the entire response, fooling downstream caches into thinking the whole response should be cached. - Result data can depend on any number of objects. Early on it was decided that the standard cache managers will not try to deduce dependencies, but instead rely on the user for configuration of simple dependencies. Because of these risks, you should be careful when setting up caching. You'll need to fine-tune the cache settings. Sometimes you'll find that you can't cache one of your major pages, but that you can cache pieces of it. Also remember that caching can actually slow down Zope if it is applied unscrupulously. You should perform speed tests to verify that caching really does speed up your site. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/DTML-Method.stx0000644000175000017500000000061512214017450024615 0ustar arnauarnauDTML Method: Template object. Description DTML Methods provide general templating and scripting facilities. DTML Documents can contain scripting commands in Document Template Markup Language (DTML), which allows for dynamic behavior. Unlike DTML Documents, DTML Methods have no properties, and lookup variables in the namespace of the object they are bound to. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/History.stx0000644000175000017500000000155512214017450024304 0ustar arnauarnauHistory: Object history. Description DTML Documents and Methods keep a history of their contents. This view allows you to browse, compare, and revert to old versions. Controls Historical revisions are described by a date, a user and a URL. This describes at what time by whom and at what URL the object was changed. Click on a historical version to view the object as it existed at a given point in history. '[Checkbox]' -- Select a historical revision. 'Copy to present' -- Changes the object to the same state as the selected historical revision. 'Compare' -- Summarizes the difference between two historical revisions. If you select one historical revision it will be compared to the current state. The comparison shows the changes needed to change the older revision to match the newer revision. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-tree.stx0000644000175000017500000001016112214017450024531 0ustar arnauarnautree: Inserts a tree widget The 'tree' tag displays a dynamic tree widget by querying Zope objects. Syntax 'tree' tag syntax:: The 'tree' tag is a block tag. It renders a dynamic tree widget in HTML. The root of the tree is given by variable name or expression, if present, otherwise it defaults to the current object. The 'tree' block is rendered for each tree node, with the current node pushed onto the DTML namespace. Tree state is set in HTTP cookies. Thus for trees to work, cookies must be enabled. Also you can only have one tree per page. Attributes branches=string -- Finds tree branches by calling the named method. The default method is 'tpValues' which most Zope objects support. branches_expr=string -- Finds tree branches by evaluating the expression. id=string -- The name of a method or id to determine tree state. It defaults to 'tpId' which most Zope objects support. This attribute is for advanced usage only. url=string -- The name of a method or attribute to determine tree item URLs. It defaults to 'tpURL' which most Zope objects support. This attribute is for advanced usage only. leaves=string -- The name of a DTML Document or Method used to render nodes that don't have any children. Note: this document should begin with '' and end with '' in order to ensure proper display in the tree. header=string -- The name of a DTML Document or Method displayed before expanded nodes. If the header is not found, it is skipped. footer=string -- The name of a DTML Document or Method displayed after expanded nodes. If the footer is not found, it is skipped. nowrap=boolean -- If true then rather than wrap, nodes may be truncated to fit available space. sort=string -- Sorts the branches by the named attribute. reverse -- Reverses the order of the branches. assume_children=boolean -- Assumes that nodes have children. This is useful if fetching and querying child nodes is a costly process. This results in plus boxes being drawn next to all nodes. single=boolean -- Allows only one branch to be expanded at a time. When you expand a new branch, any other expanded branches close. skip_unauthorized -- Skips nodes that the user is unauthorized to see, rather than raising an error. urlparam=string -- A query string which is included in the expanding and contracting widget links. This attribute is for advanced usage only. prefix=string -- Provide versions of the tag variables that start with this prefix instead of "tree", and that use underscores (_) instead of hyphens (-). The prefix must start with a letter and contain only alphanumeric characters and underscores (_). Tag Variables tree-item-expanded -- True if the current node is expanded. tree-item-url -- The URL of the current node. tree-root-url -- The URL of the root node. tree-level -- The depth of the current node. Top-level nodes have a depth of zero. tree-colspan -- The number of levels deep the tree is being rendered. This variable along with the 'tree-level' variable can be used to calculate table rows and colspan settings when inserting table rows into the tree table. tree-state -- The tree state expressed as a list of ids and sub-lists of ids. This variable is for advanced usage only. Tag Control Variables You can control the tree tag by setting these variables. expand_all -- If this variable is true then the entire tree is expanded. collapse_all -- If this variable is true then the entire tree is collapsed. Examples Display a tree rooted in the current object:: Display a tree rooted in another object, using a custom branches method:: Node id : zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/File_Add.stx0000644000175000017500000000064112214017450024265 0ustar arnauarnauFile/Image - Add: Create a File or Image. Description Creates a new File or Image. Controls 'Id' -- The id of the File or Image. **Note: If you do not provide an Id then the file name will be used.** 'Title' -- The optional title of the File or Image. 'File' -- The file to upload. use the 'Browse...' button to select a local file. 'Add' -- Creates a new File or Image. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Image_View.stx0000644000175000017500000000012112214017450024643 0ustar arnauarnauImage - View: Preview Image. Description This view displays the Image. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/ZSearch-Interface.stx0000644000175000017500000000051312214017450026071 0ustar arnauarnauZ Search Interface: Search wizard. Description A Search Interface is a wizard that creates search and results forms. The Search Interface will create a search-input form and a report for displaying the search results. You can search ZCatalogs, or any other objects that implement the searchable interface. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-in.stx0000644000175000017500000002234112214017450024203 0ustar arnauarnauin: Loops over sequences The 'in' tag gives you powerful controls for looping over sequences and performing batch processing. Syntax 'in' tag syntax:: [] The 'in' block is repeated once for each item in the sequence variable or sequence expression. The current item is pushed on to the DTML namespace during each executing of the 'in' block. If there are no items in the sequence variable or expression, the optional 'else' block is executed. Attributes mapping -- Iterates over mapping objects rather than instances. This allows values of the mapping objects to be accessed as DTML variables. no_push_item -- Inhibit pushing sequence-item onto the namespace stack. reverse -- Reverses the sequence. sort=string -- Sorts the sequence by the given attribute name. start=int -- The number of the first item to be shown, where items are numbered from 1. end=int -- The number of the last item to be shown, where items are numbered from 1. size=int -- The size of the batch. skip_unauthorized -- Don't raise an exception if an unauthorized item is encountered. orphan=int -- The desired minimum batch size. This controls how sequences are split into batches. If a batch smaller than the orphan size would occur, then no split is performed, and a batch larger than the batch size results. For example, if the sequence size is 12, the batch size is 10 the orphan size is 3, then the result is one batch with all 12 items since splitting the items into two batches would result in a batch smaller than the orphan size. The default value is 0. overlap=int -- The number of items to overlap between batches. The default is no overlap. previous -- Iterates once if there is a previous batch. Sets batch variables for previous sequence. next -- Iterates once if there is a next batch. Sets batch variables for the next sequence. prefix=string -- Provide versions of the tag variables that start with this prefix instead of "sequence", and that use underscores (_) instead of hyphens (-). The prefix must start with a letter and contain only alphanumeric characters and underscores (_). sort_expr=expression -- Sorts the sequence by an attribute named by the value of the expression. This allows you to sort on different attributes. reverse_expr=expression -- Reverses the sequence if the expression evaluates to true. This allows you to selectively reverse the sequence. Tag Variables Current Item Variables These variables describe the current item. sequence-item -- The current item. sequence-key -- The current key. When looping over tuples of the form '(key,value)', the 'in' tag interprets them as '(sequence-key, sequence-item)'. sequence-index -- The index starting with 0 of the current item. sequence-number -- The index starting with 1 of the current item. sequence-roman -- The index in lowercase Roman numerals of the current item. sequence-Roman -- The index in uppercase Roman numerals of the current item. sequence-letter -- The index in lowercase letters of the current item. sequence-Letter -- The index in uppercase letters of the current item. sequence-start -- True if the current item is the first item. sequence-end -- True if the current item is the last item. sequence-even -- True if the index of the current item is even. sequence-odd -- True if the index of the current item is odd. sequence-length -- The length of the sequence. sequence-var-*variable* -- A variable in the current item. For example, 'sequence-var-title' is the 'title' variable of the current item. Normally you can access these variables directly since the current item is pushed on the DTML namespace. However these variables can be useful when displaying previous and next batch information. sequence-index-*variable* -- The index of a variable of the current item. Summary Variables These variable summarize information about numeric item variables. To use these variable you must loop over objects (like database query results) that have numeric variables. total-*variable* -- The total of all occurrences of an item variable. count-*variable* -- The number of occurrences of an item variable. min-*variable* -- The minimum value of an item variable. max-*variable* -- The maximum value of an item variable. mean-*variable* -- The mean value of an item variable. variance-*variable* -- The variance of an item variable with count-1 degrees of freedom. variance-n-*variable* -- The variance of an item variable with n degrees of freedom. standard-deviation-*variable* -- The standard-deviation of an item variable with count-1 degrees of freedom. standard-deviation-n-*variable* -- The standard-deviation of an item variable with n degrees of freedom. Grouping Variables These variables allow you to track changes in current item variables. first-*variable* -- True if the current item is the first with a particular value for a variable. last-*variable* -- True if the current item is the last with a particular value for a variable. Batch Variables sequence-query -- The query string with the 'start' variable removed. You can use this variable to construct links to next and previous batches. sequence-step-size -- The batch size. previous-sequence -- True if the current batch is not the first one. Note, this variable is only true for the first loop iteration. previous-sequence-start-index -- The starting index of the previous batch. previous-sequence-start-number -- The starting number of the previous batch. Note, this is the same as 'previous-sequence-start-index' + 1. previous-sequence-end-index -- The ending index of the previous batch. previous-sequence-end-number -- The ending number of the previous batch. Note, this is the same as 'previous-sequence-end-index' + 1. previous-sequence-size -- The size of the previous batch. previous-batches -- A sequence of mapping objects with information about all previous batches. Each mapping object has these keys 'batch-start-index', 'batch-end-index', and 'batch-size'. next-sequence -- True if the current batch is not the last batch. Note, this variable is only true for the last loop iteration. next-sequence-start-index -- The starting index of the next sequence. next-sequence-start-number -- The starting number of the next sequence. Note, this is the same as 'next-sequence-start-index' + 1. next-sequence-end-index -- The ending index of the next sequence. next-sequence-end-number -- The ending number of the next sequence. Note, this is the same as 'next-sequence-end-index' + 1. next-sequence-size -- The size of the next index. next-batches -- A sequence of mapping objects with information about all following batches. Each mapping object has these keys 'batch-start-index', 'batch-end-index', and 'batch-size'. Examples Looping over sub-objects:: title:
    Looping over two sets of objects, using prefixes::
    Looping over a list of '(key, value)' tuples:: id: , title:
    Creating alternate colored table cells:: bgcolor="#EEEEEE" bgcolor="#FFFFFF"
    Basic batch processing::

    Previous Next

    This example creates *Previous* and *Next* links to navigate between batches. Note, by using 'sequence-query', you do not lose any GET variables as you navigate between batches. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/ObjectManager_Contents.stx0000644000175000017500000000410512214017450027213 0ustar arnauarnauObjectManager - Contents: Edit contained objects. Description This view displays the contained objects and allows you to add, delete and change them. Each contained object is displayed on a line and is identified by an icon, an id and a title in parenthesis. Additionally, the size (if applicable) and the date during which the object was last modified are displayed. You can manage an object by clicking on its identifying link. Sorting You can sort contained objects by type, name (id), size, or modification date. To do so, click on the appropriate column heading. Clicking a second time on any column heading will reverse the sort on that field. Controls '[Checkbox]' -- Selects the object in order to perform operations on it. The operations you can perform are rename, cut, copy, delete, and export. Some operations may not be visible if they are not allowed. 'Rename' -- Changes the ids of the selected objects. 'Cut' -- Cuts selected objects and place them into the clipboard. This is similar to cut in most file managers. Cut objects can be pasted in a new location. When cut objects are pasted into another location the old objects are deleted. 'Copy' -- Copies selected objects and place them in the clipboard. This is similar to copy in most file managers. Copied objects can be pasted in a new location. 'Paste' -- Allows you to paste objects from the clipboard into this object. **Note: This option will only appear if objects have previously been copied or cut.** 'Delete' -- Deletes the selected objects. Deleted objects are *not* placed in the clipboard. 'Import/Export' -- Imports or exports a Zope object. 'Available Objects' -- Selects a type of object to add. 'Add' -- Adds an object specified in 'Available Objects'. 'Select All (Deselect All)' -- Toggles between selecting and deselecting each item currently displayed in the contents view. **Note: This control will only appear if your browser is javascript-enabled.** zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Database-Management_Database.stx0000644000175000017500000000154712214017450030206 0ustar arnauarnauDatabase Management - Database: Zope database information and pack. Description This view gives you information about the size and location of the Zope database. The Zope database stores all Zope objects. You can reduce the size of your Zope database by packing it. Packing removes old revisions of objects, thus freeing up space but also limiting your ability to undo object changes. Controls 'Pack' -- Pack will remove all versions of objects from the Zope database. This will reduce the size of your database. **Note Pack will prevent you from undoing some or all old transactions.** You can control which old revisions of objects are removed from the database by specifying how many days old the revisions must be to be removed. If you specify 0 days old, then all old object revisions will be removed. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Debug-Information_Profile.stx0000644000175000017500000000364612214017450027637 0ustar arnauarnauDebug Management - Profile: Performance testing. Description Zope can provide real-time profiling information. Profiling helps product authors to speed up sections of code that are taking too long to perform their tasks. In order to use profiling Zope has to be started with the 'PROFILE_PUBLISHER' environment variable set to a non-empty value. If the variable is set to a valid filesystem path, then the accumulated profile information will be dumped to the named file when Zope is shut down. If the variable is simply set to a non-empty value that is not a valid filesystem path then Zope will still run in profiling mode, but profile information will only be available through the web interface. **Note: Profiling will slow Zope performance significantly.** Once Zope has started in profiling mode visit your site with your Web browser - Zope will accumulate profiling information as you are working with your site. When you want to view the profiling information, visit the Control Panel, click on the 'Debugging information' link and select the 'Profiling' tab. The profiling screen will show a list of methods and the amount of time each method is taking. Multiple views are available by changing the sort order and pushing the "update" button. The online profiler is based on the standard Python profile module. For specific information on the meaning of the profile information, see the standard Python documentation for the 'profile' module. When you are done profiling turn profiling off by restarting Zope without the 'PROFILE_PUBLISHER' environment variable set. Controls 'Sort' -- How to sort function calls. 'Limit' -- How many function calls to display. 'Update' -- Updates the profiling information. See Also "Python profiler documentation":http://www.python.org/doc/current/lib/module-profile.htmlzope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/File_View.stx0000644000175000017500000000014412214017450024505 0ustar arnauarnauFile - View: File Preview. Description This view downloads a File to your local computer. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Image.py0000644000175000017500000000550212214017450023473 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## def manage_addImage(id, file, title='', precondition='', content_type=''): """ Add a new Image object. Creates a new Image object 'id' with the contents of 'file'. """ class Image: """ An Image is a Zope object that contains image content. An Image object can be used to upload or download image information with Zope. Image objects have two properties the define their dimension, 'height' and 'width'. These are calculated when the image is uploaded. For image types that Zope does not understand, these properties may be undefined. Using a Image object in Zope is easy. The most common usage is to display the contents of an image object in a web page. This is done by simply referencing the object from DTML:: This will generate an HTML IMG tag referencing the URL to the Image. This is equivalent to:: You can control the image display more precisely with the 'tag' method. For example:: """ def tag(height=None, width=None, alt=None, scale=0, xscale=0, yscale=0, **args): """ This method returns a string which contains an HTML IMG tag reference to the image. Optionally, the 'height', 'width', 'alt', 'scale', 'xscale' and 'yscale' arguments can be provided which are turned into HTML IMG tag attributes. Note, 'height' and 'width' are provided by default, and 'alt' comes from the 'title' property. Keyword arguments may be provided to support other or future IMG tag attributes. The one exception to this is the HTML Cascading Style Sheet tag 'class'. Because the word 'class' is a reserved keyword in Python, you must instead use the keyword argument 'css_class'. This will be turned into a 'class' HTML tag attribute on the rendered 'img' tag. Permission -- 'View' """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-sqlgroup.stx0000644000175000017500000000423512214017450025453 0ustar arnauarnausqlgroup: Formats complex SQL expressions The 'sqlgroup' tag formats complex boolean SQL expressions. You can use it along with the 'sqltest' tag to build dynamic SQL queries that tailor themselves to the environment. This tag is used in SQL Methods. Syntax 'sqlgroup' tag syntax:: [] [] ... The 'sqlgroup' tag is a block tag. It is divided into blocks with one or more optional 'or' and 'and' tags. 'sqlgroup' tags can be nested to produce complex logic. Attributes required=boolean -- Indicates whether the group is required. If it is not required and contains nothing, it is excluded from the DTML output. where=boolean -- If true, includes the string "where". This is useful for the outermost 'sqlgroup' tag in a SQL 'select' query. Examples Sample usage:: select * from employees If 'first' is 'Bob' and 'last' is 'Smith, McDonald' it renders:: select * from employees where (first='Bob' and last in ('Smith', 'McDonald') ) If 'salary' is 50000 and 'last' is 'Smith' it renders:: select * from employees where (salary > 50000.0 and last='Smith' ) Nested 'sqlgroup' tags:: select * from employees Given sample arguments, this template renders to SQL like so:: select * form employees where ( ( name like 'A*' and last like 'Smith' ) or salary > 20000.0 ) See Also "sqltest tag":dtml-sqltest.stx zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/ObjectManagerItem.py0000644000175000017500000001261212214017450025771 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class ObjectManagerItem: """ A Zope object that can be contained within an Object Manager. Almost all Zope objects that can be managed through the web are Object Manager Items. ObjectMangerItems have these instance attributes: 'title' -- The title of the object. This is an optional one-line string description of the object. 'meta_type' -- A short name for the type of the object. This is the name that shows up in product add list for the object and is used when filtering objects by type. This attribute is provided by the object's class and should not be changed directly. 'REQUEST' -- The current web request. This object is acquired and should not be set. """ def getId(): """ Returns the object's id. The 'id' is the unique name of the object within its parent object manager. This should be a string, and can contain letters, digits, underscores, dashes, commas, and spaces. This method replaces direct access to the 'id' attribute. Permission -- Always available """ def title_or_id(): """ If the title is not blank, return it, otherwise return the id. Permission -- Always available """ def title_and_id(): """ If the title is not blank, the return the title followed by the id in parentheses. Otherwise return the id. Permission -- Always available """ def manage_workspace(): """ This is the web method that is called when a user selects an item in a object manager contents view or in the Zope Management navigation view. Permission -- 'View management screens' """ def this(): """ Return the object. This turns out to be handy in two situations. First, it provides a way to refer to an object in DTML expressions. The second use for this is rather deep. It provides a way to acquire an object without getting the full context that it was acquired from. This is useful, for example, in cases where you are in a method of a non-item subobject of an item and you need to get the item outside of the context of the subobject. Permission -- Always available """ def absolute_url(relative=None): """ Return the absolute URL of the object. This a canonical URL based on the object's physical containment path. It is affected by the virtual host configuration, if any, and can be used by external agents, such as a browser, to address the object. If the relative argument is provided, with a true value, then the value of virtual_url_path() is returned. Some Products incorrectly use '/'+absolute_url(1) as an absolute-path reference. This breaks in certain virtual hosting situations, and should be changed to use absolute_url_path() instead. Permission -- Always available """ def absolute_url_path(): """ Return the path portion of the absolute URL of the object. This includes the leading slash, and can be used as an 'absolute-path reference' as defined in RFC 2396. Permission -- Always available """ def virtual_url_path(): """ Return a URL for the object, relative to the site root. If a virtual host is configured, the URL is a path relative to the virtual host's root object. Otherwise, it is the physical path. In either case, the URL does not begin with a slash. Permission -- Always available """ def getPhysicalRoot(): """ Returns the top-level Zope Application object. Permission -- Python only """ def getPhysicalPath(): """ Get the path of an object from the root, ignoring virtual hosts. Permission -- Always available """ def unrestrictedTraverse(path, default=None): """ Return the object obtained by traversing the given path from the object on which the method was called. This method begins with "unrestricted" because (almost) no security checks are performed. If an object is not found then the 'default' argument will be returned. Permission -- Python only """ def restrictedTraverse(path, default=None): """ Return the object obtained by traversing the given path from the object on which the method was called, performing security checks along the way. If an object is not found then the 'default' argument will be returned. Permission -- Always available """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/PropertySheet.py0000644000175000017500000001435612214017450025275 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class PropertySheet: """ A PropertySheet is an abstraction for organizing and working with a set of related properties. Conceptually it acts like a container for a set of related properties and meta-data describing those properties. A PropertySheet may or may not provide a web interface for managing its properties. """ def xml_namespace(): """ Return a namespace string usable as an xml namespace for this property set. This may be an empty string if there is no default namespace for a given property sheet. Permission -- Python only """ def getProperty(id, d=None): """ Get the property 'id', returning the optional second argument or None if no such property is found. Permission -- Python only """ def getPropertyType(id): """ Get the type of property 'id'. Returns None if no such property exists. Permission -- Python only """ def hasProperty(id): """ Returns true if 'self' has a property with the given 'id', false otherwise. Permission -- 'Access contents information' """ def propertyIds(): """ Returns a list of property ids. Permission -- 'Access contents information' """ def propertyValues(): """ Returns a list of actual property values. Permission -- 'Access contents information' """ def propertyItems(): """ Return a list of (id, property) tuples. Permission -- 'Access contents information' """ def propertyMap(): """ Returns a tuple of mappings, giving meta-data for properties. Permssion -- Python only """ def propertyInfo(): """ Returns a mapping containing property meta-data. Permission -- Python only """ def manage_addProperty(id, value, type, REQUEST=None): """ Add a new property with the given 'id', 'value' and 'type'. These are the property types: 'boolean' -- 1 or 0. 'date' -- A 'DateTime' value, for example '12/31/1999 15:42:52 PST'. 'float' -- A decimal number, for example '12.4'. 'int' -- An integer number, for example, '12'. 'lines' -- A list of strings, one per line. 'long' -- A long integer, for example '12232322322323232323423'. 'string' -- A string of characters, for example 'This is a string'. 'text' -- A multi-line string, for example a paragraph. 'tokens' -- A list of strings separated by white space, for example 'one two three'. 'selection' -- A string selected by a pop-up menu. 'multiple selection' -- A list of strings selected by a selection list. This method will use the passed in 'type' to try to convert the 'value' argument to the named type. If the given 'value' cannot be converted, a ValueError will be raised. The value given for 'selection' and 'multiple selection' properites may be an attribute or method name. The attribute or method must return a sequence values. If the given 'type' is not recognized, the 'value' and 'type' given are simply stored blindly by the object. If no value is passed in for 'REQUEST', the method will return 'None'. If a value is provided for 'REQUEST' (as it will when called via the web), the property management form for the object will be rendered and returned. This method may be called via the web, from DTML or from Python code. Permission -- 'Manage Properties' """ def manage_changeProperties(REQUEST=None, **kw): """ Change object properties by passing either a REQUEST object or name=value parameters Some objects have "special" properties defined by product authors that cannot be changed. If you try to change one of these properties through this method, an error will be raised. Note that no type checking or conversion happens when this method is called, so it is the caller's responsibility to ensure that the updated values are of the correct type. *This should probably change*. If a REQUEST object is passed (as it will be when called via the web), the method will return an HTML message dialog. If no REQUEST is passed, the method returns 'None' on success. This method may be called via the web, from DTML or from Python code. Permission -- 'Manage Properties' """ def manage_delProperties(ids=None, REQUEST=None): """ Delete one or more properties with the given 'ids'. The 'ids' argument should be a sequence (tuple or list) containing the ids of the properties to be deleted. If 'ids' is empty no action will be taken. If any of the properties named in 'ids' does not exist, an error will be raised. Some objects have "special" properties defined by product authors that cannot be deleted. If one of these properties is named in 'ids', an HTML error message is returned. If no value is passed in for 'REQUEST', the method will return None. If a value is provided for 'REQUEST' (as it will be when called via the web), the property management form for the object will be rendered and returned. This method may be called via the web, from DTML or from Python code. Permission -- 'Manage Properties' """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/CacheManager-associate.stx0000644000175000017500000000075212214017450027110 0ustar arnauarnauCache manager associations For background information, see the description of cache management. The 'Associate' form lets you search for cacheable objects and make or break multiple cache management associations at once. Simply select the search criteria then click the 'Locate' button. Zope will return a list of cacheable objects with a checkbox for each one. Select or unselect objects and click the 'Save changes' button when you're done. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/math.py0000644000175000017500000000055212214017450023402 0ustar arnauarnau""" math: Python 'math' module The 'math' module provides trigonometric and other math functions. It is a standard Python module. Since Zope 2.4 requires Python 2.1, make sure to consult the Python 2.1 documentation. See Also "Python 'math' module":http://www.python.org/doc/current/lib/module-math.html documentation at Python.org """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-if.stx0000644000175000017500000000301712214017450024172 0ustar arnauarnauif: Tests Conditions The 'if' tags allows you to test conditions and to take different actions depending on the conditions. The 'if' tag mirrors Python's 'if/elif/else' condition testing statements. Syntax If tag syntax:: [] ... [] The 'if' tag is a block tag. The 'if' tag and optional 'elif' tags take a condition variable name or a condition expression, but not both. If the condition name or expression evaluates to true then the 'if' block is executed. True means not zero, an empty string or an empty list. If the condition variable is not found then the condition is considered false. If the initial condition is false, each 'elif' condition is tested in turn. If any 'elif' condition is true, its block is executed. Finally the optional 'else' block is executed if none of the 'if' and 'elif' conditions were true. Only one block will be executed. Examples Testing for a variable:: The snake variable is true Testing for expression conditions:: num is greater than five num is less than five num must be five See Also "Python Tutorial: If Statements":http://www.python.org/doc/current/tut/node6.html#SECTION006100000000000000000 zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/DTML-Document.stx0000644000175000017500000000062012214017450025147 0ustar arnauarnauDTML Document: Content object. Description A DTML Document contains web-editable content. A DTML Document roughly corresponds to a web page. DTML Documents can contain scripting commands in Document Template Markup Language (DTML), which allows for dynamic behavior. Unlike DTML Methods, DTML Documents have properties and lookup variables in their own namespace.zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-try.stx0000644000175000017500000000476212214017450024422 0ustar arnauarnautry: Handles exceptions The 'try' tag allows exception handling in DTML, mirroring the Python 'try/except' and 'try/finally' constructs. Syntax The 'try' tag has two different syntaxes, 'try/except/else' and 'try/finally'. 'try/except/else' Syntax:: ... [] The 'try' tag encloses a block in which exceptions can be caught and handled. There can be one or more 'except' tags that handles zero or more exceptions. If an 'except' tag does not specify an exception, then it handles all exceptions. When an exception is raised, control jumps to the first 'except' tag that handles the exception. If there is no 'except' tag to handle the exception, then the exception is raised normally. If no exception is raised, and there is an 'else' tag, then the 'else' tag will be executed after the body of the 'try' tag. The 'except' and 'else' tags are optional. 'try/finally' Syntax:: The 'finally' tag cannot be used in the same 'try' block as the 'except' and 'else' tags. If there is a 'finally' tag, its block will be executed whether or not an exception is raised in the 'try' block. Attributes except -- Zero or more exception names. If no exceptions are listed then the except tag will handle all exceptions. Tag Variables Inside the 'except' block these variables are defined. error_type -- The exception type. error_value -- The exception value. error_tb -- The traceback. Examples Catching a math error:: You tried to divide by zero. Returning information about the handled exception:: An error occurred. Error type: Error value: Using finally to make sure to perform clean up regardless of whether an error is raised or not:: See Also "raise tag":dtml-raise.stx "Python Tutorial: Errors and Exceptions":http://www.python.org/doc/current/tut/node10.html "Python Built-in Exceptions":http://www.python.org/doc/current/lib/module-exceptions.html zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Database-Management_Flush-Cache.stx0000644000175000017500000000046012214017450030555 0ustar arnauarnauDatabase Management - Flush Cache: Zope Database cache flush. Description This view allows you to flush the Zope database cache. Controls 'Minimize' -- Allows you to remove all persistent objects from memory. They will be reloaded again on demand, when they are next accessed. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/OrderSupport.py0000644000175000017500000000675712214017450025136 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class OrderSupport: """ Ordered container mixin class. This is an extension to the regular ObjectManager. It saves the objects in order and lets you change the order of the contained objects. This is particular helpful, if the order does not depend on object attributes, but is totally user-specific. """ def moveObjectsByDelta(ids, delta, subset_ids=None): """ Move specified sub-objects by delta. If delta is higher than the possible maximum, objects will be moved to the bottom. If delta is lower than the possible minimum, objects will be moved to the top. If subset_ids is not None, delta will be interpreted relative to the subset specified by a sequence of ids. The position of objects that are not part of this subset will not be changed. The order of the objects specified by ids will always be preserved. So if you don't want to change their original order, make sure the order of ids corresponds to their original order. If an object with id doesn't exist an error will be raised. Permission -- Manage properties Returns -- Number of moved sub-objects """ def moveObjectsUp(ids, delta=1, subset_ids=None): """ Move specified sub-objects up by delta in container. If no delta is specified, delta is 1. See moveObjectsByDelta for more details. Permission -- Manage properties Returns -- Number of moved sub-objects """ def moveObjectsDown(ids, delta=1, subset_ids=None): """ Move specified sub-objects down by delta in container. If no delta is specified, delta is 1. See moveObjectsByDelta for more details. Permission -- Manage properties Returns -- Number of moved sub-objects """ def moveObjectsToTop(ids, subset_ids=None): """ Move specified sub-objects to top of container. See moveObjectsByDelta for more details. Permission -- Manage properties Returns -- Number of moved sub-objects """ def moveObjectsToBottom(ids, subset_ids=None): """ Move specified sub-objects to bottom of container. See moveObjectsByDelta for more details. Permission -- Manage properties Returns -- Number of moved sub-objects """ def orderObjects(key, reverse=None): """ Order sub-objects by key and direction. Permission -- Manage properties Returns -- Number of moved sub-objects """ def getObjectPosition(id): """ Get the position of an object by its id. Permission -- Access contents information Returns -- Position """ def moveObjectToPosition(id, position): """ Move specified object to absolute position. Permission -- Manage properties Returns -- Number of moved sub-objects """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Request.py0000644000175000017500000001465212214017450024107 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class Request: """ The request object encapsulates all of the information regarding the current request in Zope. This includes, the input headers, form data, server data, and cookies. The request object is a mapping object that represents a collection of variable to value mappings. In addition, variables are divided into five categories: - Environment variables These variables include input headers, server data, and other request-related data. The variable names are as specified in the CGI specification - Form data These are data extracted from either a URL-encoded query string or body, if present. - Cookies These are the cookie data, if present. - Lazy Data These are callables which are deferred until explicitly referenced, at which point they are resolved (called) and the result stored as "other" data, ie regular request data. Thus, they are "lazy" data items. An example is SESSION objects. Lazy data in the request may only be set by the Python method set_lazy(name,callable) on the REQUEST object. This method is not callable from DTML or through the web. - Other Data that may be set by an application object. The request object may be used as a mapping object, in which case values will be looked up in the order: environment variables, other variables, form data, and then cookies. These special variables are set in the Request: 'PARENTS' -- A list of the objects traversed to get to the published object. So, 'PARENTS[0]' would be the ancestor of the published object. 'REQUEST' -- The Request object. 'RESPONSE' -- The Response object. 'PUBLISHED' -- The actual object published as a result of url traversal. 'URL' -- The URL of the Request without query string. *URLn* -- 'URL0' is the same as 'URL'. 'URL1' is the same as 'URL0' with the last path element removed. 'URL2' is the same as 'URL1' with the last element removed. Etcetera. For example if URL='http://localhost/foo/bar', then URL1='http://localhost/foo' and URL2='http://localhost'. *URLPATHn* -- 'URLPATH0' is the path portion of 'URL', 'URLPATH1' is the path portion of 'URL1', and so on. For example if URL='http://localhost/foo/bar', then URLPATH1='/foo' and URLPATH2='/'. *BASEn* -- 'BASE0' is the URL up to but not including the Zope application object. 'BASE1' is the URL of the Zope application object. 'BASE2' is the URL of the Zope application object with an additional path element added in the path to the published object. Etcetera. For example if URL='http://localhost/Zope.cgi/foo/bar', then BASE0='http://localhost', BASE1='http://localhost/Zope.cgi', and BASE2='http://localhost/Zope.cgi/foo'. *BASEPATHn* -- 'BASEPATH0' is the path portion of 'BASE0', 'BASEPATH1' is the path portion of 'BASE1', and so on. 'BASEPATH1' is the externally visible path to the root Zope folder, equivalent to CGI's 'SCRIPT_NAME', but virtual-host aware. For example if URL='http://localhost/Zope.cgi/foo/bar', then BASEPATH0='/', BASEPATH1='/Zope.cgi', and BASEPATH2='/Zope.cgi/foo'. """ def set(name, value): """ Create a new name in the REQUEST object and assign it a value. This name and value is stored in the 'Other' category. Permission -- Always available """ def get_header(name, default=None): """ Return the named HTTP header, or an optional default argument or None if the header is not found. Note that both original and CGI header names without the leading 'HTTP_' are recognized, for example, 'Content-Type', 'CONTENT_TYPE' and 'HTTP_CONTENT_TYPE' should all return the Content-Type header, if available. Permission -- Always available """ def has_key(key): """ Returns a true value if the REQUEST object contains key, returns a false value otherwise. Permission -- Always available """ def keys(): """ Returns a sorted sequence of all keys in the REQUEST object. Permission -- Always available """ def items(): """ Returns a sequence of (key, value) tuples for all the keys in the REQUEST object. Permission -- Always available """ def values(): """ Returns a sequence of values for all the keys in the REQUEST object. Permission -- Always available """ def setServerURL(protocol=None, hostname=None, port=None): """ Sets the specified elements of 'SERVER_URL', also affecting 'URL', 'URLn', 'BASEn', and 'absolute_url()'. Provides virtual hosting support. Permission -- Always available """ def setVirtualRoot(path, hard=0): """ Alters 'URL', 'URLn', 'URLPATHn', 'BASEn', 'BASEPATHn', and 'absolute_url()' so that the current object has path 'path'. If 'hard' is true, 'PARENTS' is emptied. Provides virtual hosting support. Intended to be called from publishing traversal hooks. Permission -- Always available """ def text(): """ Returns information about the request as text. This is useful for debugging purposes. The returned text lists form contents, cookies, special variables, and evironment variables. Permissions -- Always available """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/DTML-DocumentOrMethod_Add.stx0000644000175000017500000000107412214017450027365 0ustar arnauarnauDTML Document/Method - Add: Create a DTML Document/Method Description Creates a new DTML Document or Method. Controls 'Id' -- The id of the DTML Document or Method. 'Title' -- The optional title of the DTML Document or Method. 'File' -- Allows you to upload a file to provide the contents for the DTML Document or Method. Use the 'Browse...' button to select a local file. 'Add' -- Create the DTML Document or Method. 'Add and Edit' -- Create the DTML Document or Method, and return the 'Edit' view of the created object. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/File_Edit.stx0000644000175000017500000000246212214017450024465 0ustar arnauarnauFile - Edit: File Properties. Description This view allows you to edit File properties. Form Elements 'Title' -- The optional title of the File. 'Content type' -- The content type of the file. Zope will try to guess an appropriate content type when you upload a File. 'Precondition' -- Allows you to specify a precondition for the File. A precondition is a method or document which is executed before the File is viewed or downloaded. If the precondition raises an exception then the File cannot be viewed. 'File Content' -- If the content of the file object is text-based and small enough to be edited with a Web form, a textarea containing the content of the file will be displayed. You can make changes to the content in this text area and click the 'Save Changes' button to update the file content. 'Last Modified' -- The time that the object was last changed. This is only displayed if the file data is non-text or too large to be edited with a Web form. 'File Size' -- The size (in bytes) of the image data. This is only displayed if the file data is not text or too large to be edited with a Web form. 'File Data' -- The file to upload. Use the 'Browse...' button to select a local file. 'Upload' -- Uploads the file. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Database-Management_Activity.stx0000644000175000017500000000535412214017450030276 0ustar arnauarnauDatabase Management - Activity Description This view displays activity in the ZODB over a period of time. It shows how many objects were loaded and stored. You can use this information to determine the optimal memory cache size for your Zope application. You can also use it to discover applications that write to the database too often. Information 'Keep History' -- Lets you define how many seconds of history to keep for analysis. 3600 is one hour. 86400 is one day. Note that in the current implementation, analysis data is kept only in memory and is never stored to disk, so each time you restart, you lose the historical information. 'Displayed Range' -- Tells you what period of time is displayed by the chart. 'Show current chart' -- Redisplays the chart for the current time. The chart contains a bar graph. The rightmost bar shows the most recent activity. The red portion indicates the number of objects stored and the blue portion indicates the number of objects loaded during that time period. To the right of the graph there is a total. If you click on a bar, the chart will zoom in on the time period for just that bar. You will see the details of the activity during that short time period. Click the "Show current chart" button to return to the chart for the current time. How to use this information Once Zope has loaded enough objects, the ZODB cache consistently keeps in the cache the number of objects you specify under the "Cache Parameters" tab. Because the cache size is so consistent and ZODB is so transparent to both the user and application developer, Zope applications can invisibly develop a performance problem by loading objects from ZODB on every request. Also, if the cache size is set too high, Zope will consume more RAM than it needs. You need to find a good balance that fits your site. If the bar chart shows a large number of objects being loaded all the time, increase the cache size, which will increase memory usage but should also increase performance. If the chart shows little activity even though the site is visited frequently, you can reduce the cache size so Zope will consume less RAM. As your site changes, its cache size requirements may change also, so remember to make adjustments over time. If the graph shows a lot of writes (a significant portion of red), some application or product may be writing to the database too frequently. Check the "undo" log for clues. Note that the activity graph does not show activity in mounted databases, so objects loaded and stored by the sessioning machinery are not counted in the graph. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-let.stx0000644000175000017500000000236712214017450024367 0ustar arnauarnaulet: Defines DTML variables The 'let' tag defines variables in the DTML namespace. Syntax 'let' tag syntax:: The 'let' tag is a block tag. Variables are defined by tag arguments. Defined variables are pushed onto the DTML namespace while the 'let' block is executed. Variables are defined by attributes. The 'let' tag can have one or more attributes with arbitrary names. If the attributes are defined with double quotes they are considered expressions, otherwise they are looked up by name. Attributes are processed in order, so later attributes can reference, and/or overwrite earlier ones. Examples Basic usage:: name: ids: Using the 'let' tag with the 'in' tag:: * = This yields:: 1 * 0 = 0 2 * 1 = 2 3 * 2 = 6 4 * 3 = 12 See Also "with tag":dtml-with.stx zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/PropertyManager.py0000644000175000017500000000427412214017450025575 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class PropertyManager: """ A Property Manager object has a collection of typed attributes called properties. Properties can be managed through the web or via DTML. In addition to having a type, properties can be writable or read-only and can have default values. """ def getProperty(id, d=None): """ Return the value of the property 'id'. If the property is not found the optional second argument or None is returned. Permission -- 'Access contents information' """ def getPropertyType(id): """ Get the type of property 'id'. Returns None if no such property exists. Permission -- 'Access contents information' """ def hasProperty(id): """ Returns a true value if the Property Manager has the property 'id'. Otherwise returns a false value. Permission -- 'Access contents information' """ def propertyIds(): """ Returns a list of property ids. Permission -- 'Access contents information' """ def propertyValues(): """ Returns a list of property values. Permission -- 'Access contents information' """ def propertyItems(): """ Return a list of (id, property) tuples. Permission -- 'Access contents information' """ def propertyMap(): """ Returns a tuple of mappings, giving meta-data for properties. The meta-data includes 'id', 'type', and 'mode'. Permission -- 'Access contents information' """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/File.stx0000644000175000017500000000045112214017450023514 0ustar arnauarnauFile: Generic File. Description File objects can hold any binary or textual data such as zip files, Java applets, video, text, etcetera. Files provide a very limited through the web interface and are appropriate for data that will not be manipulated very much by Zope. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-sqltest.stx0000644000175000017500000000424612214017450025300 0ustar arnauarnausqltest: Formats SQL condition tests The 'sqltest' tag inserts a condition test into SQL code. It tests a column against a variable. This tag is used in SQL Methods. Syntax 'sqltest' tag syntax:: The 'sqltest' tag is a singleton. It inserts a SQL condition test statement. It is used to build SQL queries. The 'sqltest' tag correctly escapes the inserted variable. The named variable or variable expression is tested against a SQL column using the specified comparison operation. Attributes type=string -- The type of the variable. Valid types include: 'string', 'int', 'float' and 'nb'. 'nb' means non-blank string, and should be used instead of 'string' unless you want to test for blank values. The type attribute is required and is used to properly escape inserted variable. column=string -- The name of the SQL column to test against. This attribute defaults to the variable name. multiple=boolean -- If true, then the variable may be a sequence of values to test the column against. optional=boolean -- If true, then the test is optional and will not be rendered if the variable is empty or non-existent. op=string -- The comparison operation. Valid comparisons include: eq -- equal to gt -- greater than lt -- less than ne -- not equal to ge -- greater than or equal to le -- less than or equal to The comparison defaults to equal to. If the comparison is not recognized it is used anyway. Thus you can use comparisons such as 'like'. Examples Basic usage:: select * from employees where If the 'name' variable is 'Bob' then this renders:: select * from employees where name = 'Bob' Multiple values:: select * from employees where If the 'empid' variable is '(12,14,17)' then this renders:: select * from employees where empid in (12, 14, 17) See Also "sqlgroup tag":dtml-sqlgroup.stx "sqlvar tag":dtml-sqlvar.stx zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Folder.stx0000644000175000017500000000040012214017450024042 0ustar arnauarnauFolder - Container object. Description Folders allow you to organize Zope objects by grouping them together inside Folders. Folder contain other Zope objects including other Folders. Folders behave like directories in a filesystem. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Undo.stx0000644000175000017500000000241512214017450023544 0ustar arnauarnauUndo - Undo management actions. Description This view allows you to undo changes to Zope's database. Zope allows you to undo changes to its database. Changes are defined in terms of transactions which group together related changes. Each transaction has a URL and a user associated with it. In general the URL of a transaction indicates the URL that was called to initiate the changes. If an object was changed several times and you want to recover a version several changes back, you have to undo all the intermediary transactions as well. Controls '[Checkbox]' -- Selects one or more transactions. Each line shows one transaction. The transactions are sorted by date and time. The checkbox in the front allows you to check the transactions you would like to undo. The next entry on the line is the URL that caused the transaction, followed by the user who committed the transaction and the time transaction was performed. 'Earlier Transactions' -- Allows you to see transactions that were performed earlier then the ones you currently see. 'Later Transactions' -- Allows you to see transactions that were performed later then the ones you currently see. 'Undo' -- Undo the selected transactions. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-raise.stx0000644000175000017500000000145412214017450024702 0ustar arnauarnauraise: Raises an exception The 'raise' tag raises an exception, mirroring the Python 'raise' statement. Syntax 'raise' tag syntax:: The 'raise' tag is a block tag. It raises an exception. Exceptions can be an exception class or a string. The contents of the tag are passed as the error value. Examples Raising a KeyError:: Raising an HTTP 404 error:: Web Page Not Found See Also "try tag":dtml-try.stx "Python Tutorial: Errors and Exceptions":http://www.python.org/doc/current/tut/node10.html "Python Built-in Exceptions":http://www.python.org/doc/current/lib/module-exceptions.html zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/sequence.py0000644000175000017500000000517312214017450024265 0ustar arnauarnau""" sequence: Sequence sorting module This module provides a 'sort' function for use with DTML, Page Templates, and Python-based Scripts. """ def sort(seq, sort): """ Sort the sequence *seq* of objects by the optional sort schema *sort*. *sort* is a sequence of tuples '(key, func, direction)' that describe the sort order. key -- Attribute of the object to be sorted. func -- Defines the compare function (optional). Allowed values: "cmp" -- Standard Python comparison function "nocase" -- Case-insensitive comparison "strcoll" or "locale" -- Locale-aware string comparison "strcoll_nocase" or "locale_nocase" -- Locale-aware case-insensitive string comparison other -- A specified, user-defined comparison function, should return 1, 0, -1. direction -- defines the sort direction for the key (optional). (allowed values: "asc", "desc") DTML Examples Sort child object (using the 'objectValues' method) by id (using the 'getId' method), ignoring case::
    Sort child objects by title (ignoring case) and date (from newest to oldest)::
    Page Template Examples You can use the 'sequence.sort' function in Python expressions to sort objects. Here's an example that mirrors the DTML example above::
    title modification date
    This example iterates over a sorted list of object, drawing a table row for each object. The objects are sorted by title and modification time. See Also "Python cmp function":http://www.python.org/doc/lib/built-in-funcs.html """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/DTML-DocumentOrMethod_Edit.stx0000644000175000017500000000201012214017450027551 0ustar arnauarnauDTML Document/Method - Edit: Edit contents. Description This view allows you to edit the contents of a DTML Document or Method. Information 'Id' -- The id of the DTML Document or Method. 'Size' -- The size of the contents. 'Last modified' -- The time the object was last changed. Controls 'Title' -- The optional title. '[Text area]' -- The contents of the DTML Document or Method. 'Taller' and 'Shorter' -- Allows to adjust the height of the contents text area. 'Wider' and 'Narrower' -- Allows to adjust the width of the contents text area. 'Save Changes' -- Changes the contents. **Note: When you change the contents it is parsed for correct DTML syntax. If there is a syntax error, the contents will not be changed.** 'File' -- Indicates a file to be uploaded to replace the contents of the current object. Use the 'Browse ...' button to select a local file. 'Upload File' -- Upload the file and change the contents. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/string.py0000644000175000017500000000060612214017450023757 0ustar arnauarnau""" string: Python 'string' module The 'string' module provides string manipulation, conversion, and searching functions. It is a standard Python module. Since Zope 2.4 requires Python 2.1, make sure to consult the Python 2.1 documentation. See Also "Python 'string' module":http://www.python.org/doc/current/lib/module-string.html documentation at Python.org """ ././@LongLink0000000000000000000000000000014600000000000011566 Lustar rootrootzope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Database-Management_Cache-Parameters.stxzope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Database-Management_Cache-Parameters.st0000644000175000017500000000427212214017450031414 0ustar arnauarnauDatabase Management - Cache Parameters: Zope database cache. Description This view allows you to view Zope database cache statistics and set cache parameters. The Zope database cache operates by keeping recently used objects in memory to improve performance. A large cache improves object access speed, but increases memory usage. A small cache reduces memory usage but may slow down object access speed. Information 'Total number of objects in the database' -- Indicates the number of persistent objects in the Zope database. All of these objects are stored on disk, some of them are in memory too. 'Total number of objects in memory from all caches' -- Indicates the number of objects which are currently cached in memory. This is a good indication of the amount of memory used by Zope. 'Target number of objects in memory per cache' -- Indicates the target number of objects to have in each cache at any given time. Zope will allow the number of objects to grow above this number while processing a request, but will always reduce the level to this number at the end of each request. This parameter is one factor affecting the amount of memory use by Zope. It controls the amount of memory used per cache, the other factor is the number of caches. In general, Zope uses one cache per worker thread (specified by the '-t' switch on the command line) 'Total number of objects in each cache' -- This table displays one row for each object cache. 'Number of objects in memory' -- This value should not stay larger than the configured target size for longer than one transaction. Note that the total number at the bottom of this column should be the same as the figure in the top half of the page. It may be slightly different because there is a small time interval between the calculation of the two totals. 'Number of ghost objects' -- Ghost objects are those which only have a tiny memory footprint because their full state has not been loaded. You generally do not need to worry about the number of ghost objects because they are so small. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-with.stx0000644000175000017500000000222112214017450024543 0ustar arnauarnauwith: Controls DTML variable look up The 'with' tag pushes an object onto the DTML namespace. Variables will be looked up in the pushed object first. Syntax 'with' tag syntax:: The 'with' tag is a block tag. It pushes the named variable or variable expression onto the DTML namespace for the duration of the 'with' block. Thus names are looked up in the pushed object first. Attributes only -- Limits the DTML namespace to only include the one defined in the 'with' tag. mapping -- Indicates that the variable or expression is a mapping object. This ensures that variables are looked up correctly in the mapping object. Examples Looking up a variable in the REQUEST:: 'id' was not in the request. Pushing the first child on the DTML namespace:: First child's id: See Also "let tag":dtml-let.stx zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Folder_Add.stx0000644000175000017500000000070612214017450024623 0ustar arnauarnauFolder - Add: Add a Folder. Description This view allows you to create a new Folder. Controls 'Id' -- The id of the Folder. 'Title' -- The optional title of the Folder. 'Create public interface' -- Creates an 'index_html' Page Template inside the new Folder. 'Create user folder' -- Creates a User Folder inside the new Folder to hold authorization information for the Folder. 'Add' -- Creates a new Folder. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/OrderSupport_Contents.stx0000644000175000017500000000607212214017450027167 0ustar arnauarnauObjectManager with OrderSupport - Contents: Edit contained objects. Description This view displays the contained objects and allows you to add, delete, change and order them. Each contained object is displayed on a line and is identified by an icon, an id and a title in parenthesis. Additionally, the size (if applicable) and the date during which the object was last modified are displayed. You can manage an object by clicking on its identifying link. Sorting vs. Ordering You can sort contained objects by type, name (id), size, modification date or by position (fixed order, see below). To do so, click on the appropriate column heading. Clicking a second time on any column heading (except position) will reverse the sort on that field. Sorting takes no actions on the contained objects, but will only change the users personal point of view. You can order contained objects with the controls on the bottom of the page. The order of objects ('sort by position') is stored in the database and remains fixed until it is changed by another user. Controls '[Checkbox]' -- Selects the object in order to perform operations on it. The operations you can perform are rename, cut, copy, delete, and export. Some operations may not be visible if they are not allowed. 'Rename' -- Changes the ids of the selected objects. 'Cut' -- Cuts selected objects and place them into the clipboard. This is similar to cut in most file managers. Cut objects can be pasted in a new location. When cut objects are pasted into another location the old objects are deleted. 'Copy' -- Copies selected objects and place them in the clipboard. This is similar to copy in most file managers. Copied objects can be pasted in a new location. 'Paste' -- Allows you to paste objects from the clipboard into this object. **Note: This option will only appear if objects have previously been copied or cut.** 'Delete' -- Deletes the selected objects. Deleted objects are *not* placed in the clipboard. 'Import/Export' -- Imports or exports a Zope object. 'Available Objects' -- Selects a type of object to add. 'Add' -- Adds an object specified in 'Available Objects'. 'Select All (Deselect All)' -- Toggles between selecting and deselecting each item currently displayed in the contents view. **Note: This control will only appear if your browser is javascript-enabled.** 'Set View as Default' -- Sets current sorted view as default contents view for this folder. **Note: This option will only appear if your current sorted view is not the default view.** **The following options will only appear in Position view:** 'Up' -- Moves selected objects up by the selected amount of steps (default is 1). 'Down' -- Moves selected objects down by the selected amount of steps (default is 1). 'Top' -- Moves selected objects to the top of the page. 'Bottom' -- Moves selected objects to the bottom of the page. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-funcs.stx0000644000175000017500000002434212214017450024716 0ustar arnauarnaufunctions: DTML Functions DTML utility functions provide some Python built-in functions and some DTML-specific functions. Functions abs(number) -- Return the absolute value of a number. The argument may be a plain or long integer or a floating point number. If the argument is a complex number, its magnitude is returned. chr(integer) -- Return a string of one character whose ASCII code is the integer, e.g., 'chr(97)' returns the string 'a'. This is the inverse of ord(). The argument must be in the range 0 to 255, inclusive; 'ValueError' will be raised if the integer is outside that range. DateTime() -- Returns a Zope 'DateTime' object given constructor arguments. See the "DateTime":DateTime.py API reference for more information on constructor arguments. divmod(number, number) -- Take two numbers as arguments and return a pair of numbers consisting of their quotient and remainder when using long division. With mixed operand types, the rules for binary arithmetic operators apply. For plain and long integers, the result is the same as '(a / b, a % b)'. For floating point numbers the result is '(q, a % b)', where *q* is usually 'math.floor(a / b)' but may be 1 less than that. In any case 'q * b + a % b' is very close to *a*, if 'a % b' is non-zero it has the same sign as *b*, and '0 <= abs(a % b) < abs(b)'. float(number) -- Convert a string or a number to floating point. If the argument is a string, it must contain a possibly signed decimal or floating point number, possibly embedded in whitespace; this behaves identical to 'string.atof(number)'. Otherwise, the argument may be a plain or long integer or a floating point number, and a floating point number with the same value (within Python's floating point precision) is returned. getattr(object, string) -- Return the value of the named attributed of object. name must be a string. If the string is the name of one of the object's attributes, the result is the value of that attribute. For example, 'getattr(x, "foobar")' is equivalent to 'x.foobar'. If the named attribute does not exist, default is returned if provided, otherwise 'AttributeError' is raised. getitem(variable, render=0) -- Returns the value of a DTML variable. If 'render' is true, the variable is rendered. See the 'render' function. hasattr(object, string) -- The arguments are an object and a string. The result is 1 if the string is the name of one of the object's attributes, 0 if not. (This is implemented by calling getattr(object, name) and seeing whether it raises an exception or not.) hash(object) -- Return the hash value of the object (if it has one). Hash values are integers. They are used to quickly compare dictionary keys during a dictionary lookup. Numeric values that compare equal have the same hash value (even if they are of different types, e.g. 1 and 1.0). has_key(variable) -- Returns true if the DTML namespace contains the named variable. hex(integer) -- Convert an integer number (of any size) to a hexadecimal string. The result is a valid Python expression. Note: this always yields an unsigned literal, e.g. on a 32-bit machine, 'hex(-1)' yields '0xffffffff'. When evaluated on a machine with the same word size, this literal is evaluated as -1; at a different word size, it may turn up as a large positive number or raise an 'OverflowError' exception. int(number) -- Convert a string or number to a plain integer. If the argument is a string, it must contain a possibly signed decimal number representable as a Python integer, possibly embedded in whitespace; this behaves identical to 'string.atoi(number[, radix]'). The 'radix' parameter gives the base for the conversion and may be any integer in the range 2 to 36. If 'radix' is specified and the number is not a string, 'TypeError' is raised. Otherwise, the argument may be a plain or long integer or a floating point number. Conversion of floating point numbers to integers is defined by the C semantics; normally the conversion truncates towards zero. len(sequence) -- Return the length (the number of items) of an object. The argument may be a sequence (string, tuple or list) or a mapping (dictionary). max(s) -- With a single argument s, return the largest item of a non-empty sequence (e.g., a string, tuple or list). With more than one argument, return the largest of the arguments. min(s) -- With a single argument s, return the smallest item of a non-empty sequence (e.g., a string, tuple or list). With more than one argument, return the smallest of the arguments. namespace([name=value]...) -- Returns a new DTML namespace object. Keyword argument 'name=value' pairs are pushed into the new namespace. oct(integer) -- Convert an integer number (of any size) to an octal string. The result is a valid Python expression. Note: this always yields an unsigned literal, e.g. on a 32-bit machine, 'oct(-1)' yields '037777777777'. When evaluated on a machine with the same word size, this literal is evaluated as -1; at a different word size, it may turn up as a large positive number or raise an OverflowError exception. ord(character) -- Return the ASCII value of a string of one character. E.g., 'ord("a")' returns the integer 97. This is the inverse of 'chr()'. pow(x, y [,z]) -- Return *x* to the power *y*; if *z* is present, return *x* to the power *y*, modulo *z* (computed more efficiently than 'pow(x, y) % z'). The arguments must have numeric types. With mixed operand types, the rules for binary arithmetic operators apply. The effective operand type is also the type of the result; if the result is not expressible in this type, the function raises an exception; e.g., 'pow(2, -1)' or 'pow(2, 35000)' is not allowed. range([start,] stop [,step]) -- This is a versatile function to create lists containing arithmetic progressions. The arguments must be plain integers. If the step argument is omitted, it defaults to 1. If the start argument is omitted, it defaults to 0. The full form returns a list of plain integers '[start, start + step, start + 2 * step, ...]'. If step is positive, the last element is the largest 'start + i * step' less than *stop*; if *step* is negative, the last element is the largest 'start + i * step' greater than *stop*. *step* must not be zero (or else 'ValueError' is raised). round(x [,n]) -- Return the floating point value *x* rounded to *n* digits after the decimal point. If n is omitted, it defaults to zero. The result is a floating point number. Values are rounded to the closest multiple of 10 to the power minus n; if two multiples are equally close, rounding is done away from 0 (so e.g. round(0.5) is 1.0 and round(-0.5) is -1.0). render(object) -- Render 'object'. For DTML objects this evaluates the DTML code with the current namespace. For other objects, this is equivalent to 'str(object)'. reorder(s [,with] [,without]) -- Reorder the items in s according to the order given in 'with' and without the items mentioned in 'without'. Items from s not mentioned in with are removed. s, with, and without are all either sequences of strings or sequences of key-value tuples, with ordering done on the keys. This function is useful for constructing ordered select lists. SecurityCalledByExecutable() -- Return a true if the current object (e.g. DTML document or method) is being called by an executable (e.g. another DTML document or method or script). SecurityCheckPermission(permission, object) -- Check whether the security context allows the given permission on the given object. For example, 'SecurityCheckPermission("Add Documents, Images, and Files", this())' would return true if the current user was authorized to create documents, images, and files in the current location. SecurityGetUser() -- Return the current user object. This is normally the same as the 'REQUEST.AUTHENTICATED_USER' object. However, the 'AUTHENTICATED_USER' object is insecure since it can be replaced. SecurityValidate([object] [,parent] [,name] [,value]) -- Return true if the value is accessible to the current user. 'object' is the object the value was accessed in, 'parent' is the container of the value, and 'name' is the named used to access the value (for example, if it was obtained via 'getattr'). You may omit some of the arguments, however it is best to provide all available arguments. SecurityValidateValue(object) -- Return true if the object is accessible to the current user. This function is the same as calling 'SecurityValidate(None, None, None, object)'. str(object) -- Return a string containing a nicely printable representation of an object. For strings, this returns the string itself. test(condition, result [,condition, result]... [,default]) -- Takes one or more condition, result pairs and returns the result of the first true condition. Only one result is returned, even if more than one condition is true. If no condition is true and a default is given, the default is returned. If no condition is true and there is no default, None is returned. unichr(number) -- Return a unicode string representing the value of number as a unicode character. This is the inverse of ord() for unicode characters. unicode(string[, encoding[, errors ] ]) -- Decodes string using the codec for encoding. Error handling is done according to errors. The default behavior is to decode UTF-8 in strict mode, meaning that encoding errors raise ValueError. Attributes None -- The 'None' object is equivalent to the Python built-in object 'None'. This is usually used to represent a Null or false value. See Also "'string' module":string.py "'random' module":random.py "'math' module":math.py "'sequence' module":sequence.py "Built-in Python Functions":http://www.python.org/doc/current/lib/built-in-funcs.html zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/OrderedFolder.py0000644000175000017500000000203612214017450025170 0ustar arnauarnau############################################################################## # # Copyright (c) 2003 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## def manage_addOrderedFolder(id, title='', createPublic=0, createUserF=0, REQUEST=None): """ Add a new ordered Folder object with id *id*. If the 'createPublic' and 'createUserF' parameters are set to any true value, an 'index_html' and a 'UserFolder' objects are created respectively in the new folder. """ class OrderedFolder: """ Extends the default Folder by order support. """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/DateTime.py0000644000175000017500000005356512214017450024161 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class DateTime: """ The DateTime object provides an interface for working with dates and times in various formats. DateTime also provides methods for calendar operations, date and time arithmetic and formatting. DateTime objects represent instants in time and provide interfaces for controlling its representation without affecting the absolute value of the object. DateTime objects may be created from a wide variety of string or numeric data, or may be computed from other DateTime objects. DateTimes support the ability to convert their representations to many major timezones, as well as the ability to create a DateTime object in the context of a given timezone. DateTime objects provide partial numerical behavior: - Two date-time objects can be subtracted to obtain a time, in days between the two. - A date-time object and a positive or negative number may be added to obtain a new date-time object that is the given number of days later than the input date-time object. - A positive or negative number and a date-time object may be added to obtain a new date-time object that is the given number of days later than the input date-time object. - A positive or negative number may be subtracted from a date-time object to obtain a new date-time object that is the given number of days earlier than the input date-time object. DateTime objects may be converted to integer, long, or float numbers of days since January 1, 1901, using the standard int, long, and float functions (Compatibility Note: int, long and float return the number of days since 1901 in GMT rather than local machine timezone). DateTime objects also provide access to their value in a float format usable with the python time module, provided that the value of the object falls in the range of the epoch-based time module. A DateTime object should be considered immutable; all conversion and numeric operations return a new DateTime object rather than modify the current object. A DateTime object always maintains its value as an absolute UTC time, and is represented in the context of some timezone based on the arguments used to create the object. A DateTime object's methods return values based on the timezone context. Note that in all cases the local machine timezone is used for representation if no timezone is specified. DateTimes may be created with from zero to seven arguments. - If the function is called with no arguments, then the current date/time is returned, represented in the timezone of the local machine. - If the function is invoked with a single string argument which is a recognized timezone name, an object representing the current time is returned, represented in the specified timezone. - If the function is invoked with a single string argument representing a valid date/time, an object representing that date/time will be returned. As a general rule, any date-time representation that is recognized and unambiguous to a resident of North America is acceptable.(The reason for this qualification is that in North America, a date like: 2/1/1994 is interpreted as February 1, 1994, while in some parts of the world, it is interpreted as January 2, 1994.) A date/time string consists of two components, a date component and an optional time component, separated by one or more spaces. If the time component is omitted, 12:00am is assumed. Any recognized timezone name specified as the final element of the date/time string will be used for computing the date/time value. (If you create a DateTime with the string 'Mar 9, 1997 1:45pm US/Pacific', the value will essentially be the same as if you had captured time.time() at the specified date and time on a machine in that timezone):: e=DateTime("US/Eastern") # returns current date/time, represented in US/Eastern. x=DateTime("1997/3/9 1:45pm") # returns specified time, represented in local machine zone. y=DateTime("Mar 9, 1997 13:45:00") # y is equal to x The date component consists of year, month, and day values. The year value must be a one-, two-, or four-digit integer. If a one- or two-digit year is used, the year is assumed to be in the twentieth century. The month may be an integer, from 1 to 12, a month name, or a month abbreviation, where a period may optionally follow the abbreviation. The day must be an integer from 1 to the number of days in the month. The year, month, and day values may be separated by periods, hyphens, forward slashes, or spaces. Extra spaces are permitted around the delimiters. Year, month, and day values may be given in any order as long as it is possible to distinguish the components. If all three components are numbers that are less than 13, then a month-day-year ordering is assumed. The time component consists of hour, minute, and second values separated by colons. The hour value must be an integer between 0 and 23 inclusively. The minute value must be an integer between 0 and 59 inclusively. The second value may be an integer value between 0 and 59.999 inclusively. The second value or both the minute and second values may be omitted. The time may be followed by am or pm in upper or lower case, in which case a 12-hour clock is assumed. - If the DateTime function is invoked with a single Numeric argument, the number is assumed to be a floating point value such as that returned by time.time(). A DateTime object is returned that represents the gmt value of the time.time() float represented in the local machine's timezone. - If the function is invoked with two numeric arguments, then the first is taken to be an integer year and the second argument is taken to be an offset in days from the beginning of the year, in the context of the local machine timezone. The date-time value returned is the given offset number of days from the beginning of the given year, represented in the timezone of the local machine. The offset may be positive or negative. Two-digit years are assumed to be in the twentieth century. - If the function is invoked with two arguments, the first a float representing a number of seconds past the epoch in gmt (such as those returned by time.time()) and the second a string naming a recognized timezone, a DateTime with a value of that gmt time will be returned, represented in the given timezone.:: import time t=time.time() now_east=DateTime(t,'US/Eastern') # Time t represented as US/Eastern now_west=DateTime(t,'US/Pacific') # Time t represented as US/Pacific # now_east == now_west # only their representations are different - If the function is invoked with three or more numeric arguments, then the first is taken to be an integer year, the second is taken to be an integer month, and the third is taken to be an integer day. If the combination of values is not valid, then a DateTimeError is raised. Two-digit years are assumed to be in the twentieth century. The fourth, fifth, and sixth arguments specify a time in hours, minutes, and seconds; hours and minutes should be positive integers and seconds is a positive floating point value, all of these default to zero if not given. An optional string may be given as the final argument to indicate timezone (the effect of this is as if you had taken the value of time.time() at that time on a machine in the specified timezone). New in Zope 2.7: A new keyword parameter "datefmt" can be passed to the constructor. If set to "international", the constructor is forced to treat ambigious dates as "days before month before year". This useful if you need to parse non-US dates in a reliable way If a string argument passed to the DateTime constructor cannot be parsed, it will raise DateTime.SyntaxError. Invalid date, time, or timezone components will raise a DateTime.DateTimeError. The module function Timezones() will return a list of the timezones recognized by the DateTime module. Recognition of timezone names is case-insensitive. """ def timeTime(): """ Return the date/time as a floating-point number in UTC, in the format used by the python time module. Note that it is possible to create date/time values with DateTime that have no meaningful value to the time module. Permission -- Always available """ def toZone(z): """ Return a DateTime with the value as the current object, represented in the indicated timezone. Permission -- Always available """ def isFuture(): """ Return true if this object represents a date/time later than the time of the call Permission -- Always available """ def isPast(): """ Return true if this object represents a date/time earlier than the time of the call Permission -- Always available """ def isCurrentYear(): """ Return true if this object represents a date/time that falls within the current year, in the context of this object\'s timezone representation Permission -- Always available """ def isCurrentMonth(): """ Return true if this object represents a date/time that falls within the current month, in the context of this object\'s timezone representation Permission -- Always available """ def isCurrentDay(): """ Return true if this object represents a date/time that falls within the current day, in the context of this object\'s timezone representation Permission -- Always available """ def isCurrentHour(): """ Return true if this object represents a date/time that falls within the current hour, in the context of this object\'s timezone representation Permission -- Always available """ def isCurrentMinute(): """ Return true if this object represents a date/time that falls within the current minute, in the context of this object\'s timezone representation Permission -- Always available """ def earliestTime(): """ Return a new DateTime object that represents the earliest possible time (in whole seconds) that still falls within the current object's day, in the object's timezone context Permission -- Always available """ def latestTime(): """ Return a new DateTime object that represents the latest possible time (in whole seconds) that still falls within the current object's day, in the object's timezone context Permission -- Always available """ def greaterThan(t): """ Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time greater than the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer milliseconds. Permission -- Always available """ def greaterThanEqualTo(t): """ Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time greater than or equal to the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer milliseconds. Permission -- Always available """ def equalTo(t): """ Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time equal to the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer milliseconds. Permission -- Always available """ def notEqualTo(t): """ Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time not equal to the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer milliseconds. Permission -- Always available """ def lessThan(t): """ Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time less than the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer milliseconds. Permission -- Always available """ def lessThanEqualTo(t): """ Compare this DateTime object to another DateTime object OR a floating point number such as that which is returned by the python time module. Returns true if the object represents a date/time less than or equal to the specified DateTime or time module style time. Revised to give more correct results through comparison of long integer milliseconds. Permission -- Always available """ def isLeapYear(): """ Return true if the current year (in the context of the object's timezone) is a leap year Permission -- Always available """ def dayOfYear(): """ Return the day of the year, in context of the timezone representation of the object Permission -- Always available """ # Component access def parts(): """ Return a tuple containing the calendar year, month, day, hour, minute second and timezone of the object Permission -- Always available """ def timezone(): """ Return the timezone in which the object is represented. Permission -- Always available """ def year(): """ Return the calendar year of the object Permission -- Always available """ def month(): """ Return the month of the object as an integer Permission -- Always available """ def Month(): """ Return the full month name Permission -- Always available """ def aMonth(): """ Return the abbreviated month name. Permission -- Always available """ def Mon(): """ Compatibility: see aMonth Permission -- Always available """ def pMonth(): """ Return the abbreviated (with period) month name. Permission -- Always available """ def Mon_(): """ Compatibility: see pMonth Permission -- Always available """ def day(): """ Return the integer day Permission -- Always available """ def Day(): """ Return the full name of the day of the week Permission -- Always available """ def DayOfWeek(): """ Compatibility: see Day Permission -- Always available """ def aDay(): """ Return the abbreviated name of the day of the week Permission -- Always available """ def pDay(): """ Return the abbreviated (with period) name of the day of the week Permission -- Always available """ def Day_(): """ Compatibility: see pDay Permission -- Always available """ def dow(): """ Return the integer day of the week, where Sunday is 0 Permission -- Always available """ def dow_1(): """ Return the integer day of the week, where Sunday is 1 Permission -- Always available """ def h_12(): """ Return the 12-hour clock representation of the hour Permission -- Always available """ def h_24(): """ Return the 24-hour clock representation of the hour Permission -- Always available """ def ampm(): """ Return the appropriate time modifier (am or pm) Permission -- Always available """ def hour(): """ Return the 24-hour clock representation of the hour Permission -- Always available """ def minute(): """ Return the minute Permission -- Always available """ def second(): """ Return the second Permission -- Always available """ def millis(): """ Return the millisecond since the epoch in GMT. Permission -- Always available """ def strftime(format): """ Return date time string formatted according to 'format' See Python's "time.strftime":http://www.python.org/doc/current/lib/module-time.html function. """ # General formats from previous DateTime def Date(): """ Return the date string for the object. Permission -- Always available """ def Time(): """ Return the time string for an object to the nearest second. Permission -- Always available """ def TimeMinutes(): """ Return the time string for an object not showing seconds. Permission -- Always available """ def AMPM(): """ Return the time string for an object to the nearest second. Permission -- Always available """ def AMPMMinutes(): """ Return the time string for an object not showing seconds. Permission -- Always available """ def PreciseTime(): """ Return the time string for the object. Permission -- Always available """ def PreciseAMPM(): """ Return the time string for the object. Permission -- Always available """ def yy(): """ Return calendar year as a 2 digit string Permission -- Always available """ def mm(): """ Return month as a 2 digit string Permission -- Always available """ def dd(): """ Return day as a 2 digit string Permission -- Always available """ def rfc822(): """ Return the date in RFC 822 format Permission -- Always available """ # New formats def fCommon(): """ Return a string representing the object's value in the format: March 1, 1997 1:45 pm Permission -- Always available """ def fCommonZ(): """ Return a string representing the object's value in the format: March 1, 1997 1:45 pm US/Eastern Permission -- Always available """ def aCommon(): """ Return a string representing the object's value in the format: Mar 1, 1997 1:45 pm Permission -- Always available """ def aCommonZ(): """ Return a string representing the object's value in the format: Mar 1, 1997 1:45 pm US/Eastern Permission -- Always available """ def pCommon(): """ Return a string representing the object's value in the format: Mar. 1, 1997 1:45 pm Permission -- Always available """ def pCommonZ(): """ Return a string representing the object's value in the format: Mar. 1, 1997 1:45 pm US/Eastern Permission -- Always available """ def ISO(): """ Return the object in ISO standard format Dates are output as: YYYY-MM-DD HH:MM:SS Permission -- Always available """ def HTML4(): """ Return the object in the format used in the HTML4.0 specification, one of the standard forms in ISO8601. See "HTML 4.0 Specification":http://www.w3.org/TR/NOTE-datetime Dates are output as: YYYY-MM-DDTHH:MM:SSZ T, Z are literal characters. The time is in UTC. Permission -- Always available """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Database-Management.stx0000644000175000017500000000047512214017450026421 0ustar arnauarnauDatabase Management: Zope Database Management. Description Database Management gives you access to Zope database controls. The Zope database stores Zope objects. You can manage the size of the Zope database by packing it. You can control memory usage with the Zope database cache parameters. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-sendmail.stx0000644000175000017500000000340012214017450025364 0ustar arnauarnausendmail: Sends email with SMTP The 'sendmail' tag sends an email message using SMTP. Syntax 'sendmail' tag syntax:: The 'sendmail' tag is a block tag. It either requires a 'mailhost' or a 'smtphost' argument, but not both. The tag block is sent as an email message. The beginning of the block describes the email headers. The headers are separated from the body by a blank line. Alternately the 'To', 'From' and 'Subject' headers can be set with tag arguments. Attributes mailhost -- The name of a Zope MailHost object to use to send email. You cannot specify both a mailhost and a smtphost. smtphost -- The name of a SMTP server used to send email. You cannot specify both a mailhost and a smtphost. port -- If the smtphost attribute is used, then the port attribute is used to specify a port number to connect to. If not specified, then port 25 will be used. mailto -- The recipient address or a list of recipient addresses separated by commas. This can also be specified with the 'To' header. mailfrom -- The sender address. This can also be specified with the 'From' header. subject -- The email subject. This can also be specified with the 'Subject' header. Examples Sending an email message using a Mail Host:: To: From: Subject: Dear , You order number is ready. Please pick it up at your soonest convenience. See Also "RFC 821 (SMTP Protocol)":http://www.ietf.org/rfc/rfc0821.txt "mime tag":dtml-mime.stx zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Folder.py0000644000175000017500000000157312214017450023670 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## def manage_addFolder(id, title): """ Add a Folder to the current ObjectManager Permission -- 'Add Folders' """ class Folder: """ A Folder is a generic container object in Zope. Folders are the most common ObjectManager subclass in Zope. """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Product_Refresh.stx0000644000175000017500000000524312214017450025737 0ustar arnauarnauProduct - Refresh: Reload a filesystem-based Python product. Description This view allows you to reload filesystem-based product code without restarting Zope. This function is useful during development of products. To enable your product to be refreshed, it is required that you put a file called 'refresh.txt' in your product directory. It can optionally contain a warning for others who might use the refresh function. (Producting refreshing is not perfect. Some products, especially "hotfix" style products which patch Zope, should not be refreshed. That's why 'refresh.txt' is required. Most products are safe to refresh, however.) There are two modes of operation. You can visit your product's 'Refresh' tab and manually push the refresh button. Or you can turn on "auto-refresh" mode, which causes Zope to periodically scan the modification time of the Python files that make up your product and execute a refresh operation in the background. **NOTE**: Don't enable auto-refresh for too many products at once. Scanning file modification times can take a lot of time per request. You can also select dependent refreshable products. If you have a product that subclasses from a product you're working on, you'll want to enable refresh for both products and add the product that subclasses as a dependent of the product you're modifying. This enables subclasses to be updated. Controls 'Refresh this product' -- The manual refresh button. 'Auto refresh mode' -- Check the checkbox to enable auto-refresh. 'Dependent auto-refreshable products' -- A list of other products which are auto-refreshable. How it works To execute a refresh, Zope looks in the sys.modules dictionary for modules with names that start with the prefix for your product. It tries to scan for dependencies between the modules that make up your product then uses Python's reload() function for each module in order. Then it sets a flag that will cause ZODB to dump its cache on the next connection so that changes to persistent classes will take effect. To implement auto-refresh, Zope stores a PersistentMapping called RefreshData on the database root object (below the Application object). The contents of the PersistentMapping are examined at the moment a database connection is opened by ZApplication. The PersistentMapping contains a list of which products have auto-refresh enabled. For each product with auto-refresh enabled, Zope compares the file mod times with the last recorded times and executes a refresh if there are any changes. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-mime.stx0000644000175000017500000000460412214017450024526 0ustar arnauarnaumime: Formats data with MIME The 'mime' tag allows you to create MIME encoded data. It is chiefly used to format email inside the 'sendmail' tag. Syntax 'mime' tag syntax:: [] ... The 'mime' tag is a block tag. The block is can be divided by one or more 'boundry' tags to create a multi-part MIME message. 'mime' tags may be nested. The 'mime' tag is most often used inside the 'sendmail' tag. Attributes Both the 'mime' and 'boundry' tags have the same attributes. encode=string -- MIME Content-Transfer-Encoding header, defaults to 'base64'. Valid encoding options include 'base64', 'quoted-printable', 'uuencode', 'x-uuencode', 'uue', 'x-uue', and '7bit'. If the 'encode' attribute is set to '7bit' no encoding is done on the block and the data is assumed to be in a valid MIME format. type=string -- MIME Content-Type header. type_expr=string -- MIME Content-Type header as a variable expression. You cannot use both 'type' and 'type_expr'. name=string -- MIME Content-Type header name. name_expr=string -- MIME Content-Type header name as a variable expression. You cannot use both 'name' and 'name_expr'. disposition=string -- MIME Content-Disposition header. disposition_expr=string -- MIME Content-Disposition header as a variable expression. You cannot use both 'disposition' and 'disposition_expr'. filename=string -- MIME Content-Disposition header filename. filename_expr=string -- MIME Content-Disposition header filename as a variable expression. You cannot use both 'filename' and 'filename_expr'. skip_expr=string -- A variable expression that if true, skips the block. You can use this attribute to selectively include MIME blocks. Examples Sending a file attachment:: To: Subject: Resume Hi, please take a look at my resume. See Also "Python Library: mimetools":http://www.python.org/doc/current/lib/module-mimetools.html zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/DTML-DocumentOrMethod_Proxy.stx0000644000175000017500000000134512214017450030017 0ustar arnauarnauDTML Document/Method - Proxy: Manage proxy roles. Description Proxy roles control security for DTML Documents and Methods. Normally a DTML Document or Method executes with an intersection of the owner's and viewer's roles. This means that the DTML can only perform actions that are available to both the owner and viewer. Proxy roles explicitly list the roles that a DTML Document or Method will execute with. This allows you to carefully control access. Proxy roles can either increase or decrease access. **Note: Proxy roles are limited to a subset of the owner's roles.** Controls 'Proxy Roles' -- The proxy roles for the DTML Document or Method. 'Change' -- Change the proxy roles. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/DTML-DocumentOrMethod_Upload.stx0000644000175000017500000000060012214017450030113 0ustar arnauarnauDTML Document/Method - Upload: Upload contents. Description Use this view to completely replace the contents of a DTML Document or Method with the contents of an uploaded file from your local computer. Controls 'File' -- The file to upload. Use the 'Browse ...' button to select a local file. 'Change' -- Upload the file and change the contents. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Control-Panel_Contents.stx0000644000175000017500000000340012214017450027164 0ustar arnauarnauControl Panel - Contents: Zope system controls Description This view displays information about the Zope process and allows you to restart and/or shutdown Zope. System Information 'Zope version' -- The version of Zope, the type of the release (binary/source), the Python version, and the platform the binaries were compiled on. 'Python version' -- The Python version that Zope is using. 'System Platform' -- The type of machine Zope is running on. 'INSTANCE_HOME' -- The filesystem path where Zope loads add-on software. 'CLIENT_HOME' -- The filesystem path where Zope stores data and log files. 'Process ID' -- The PID of the Zope process. 'Running for' -- How long the Zope process has been running. Management Options 'Database Management' -- Provides access to the database management functions such as packing and cache management. 'Product Management' -- Provides access to management functions for installed Zope Products. Controls 'Shutdown' -- Shutsdown the Zope process. **Important: You will not be able to access Zope through the web after shutting it down.** 'Restart' -- Restarts Zope. This control will only appear if Zope is running under daemon control or as a win32 service. **Note: It may take a few moments until the Zope comes back up after being restarted.** If your browser supports JavaScript, you should be able to add the following link to your bookmarks/favorites and use it to restart any Zope site: (Restart Zope). zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Response.py0000644000175000017500000001155412214017450024253 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## class Response: """ The Response object represents the response to a Zope request. """ def setStatus(status, reason=None): ''' Sets the HTTP status code of the response; the argument may either be an integer or one of the following strings: OK, Created, Accepted, NoContent, MovedPermanently, MovedTemporarily, NotModified, BadRequest, Unauthorized, Forbidden, NotFound, InternalError, NotImplemented, BadGateway, ServiceUnavailable that will be converted to the correct integer value. Permission -- Always available ''' def setHeader(name, value): ''' Sets an HTTP return header "name" with value "value", clearing the previous value set for the header, if one exists. If the literal flag is true, the case of the header name is preserved, otherwise word-capitalization will be performed on the header name on output. Permission -- Always available ''' def addHeader(name, value): ''' Set a new HTTP return header with the given value, while retaining any previously set headers with the same name. Permission -- Always available ''' def setBase(base): """ Set the base URL for the returned document. Permission -- Always available """ def appendCookie(name, value): ''' Returns an HTTP header that sets a cookie on cookie-enabled browsers with a key "name" and value "value". If a value for the cookie has previously been set in the response object, the new value is appended to the old one separated by a colon. Permission -- Always available ''' def expireCookie(name, **kw): ''' Cause an HTTP cookie to be removed from the browser The response will include an HTTP header that will remove the cookie corresponding to "name" on the client, if one exists. This is accomplished by sending a new cookie with an expiration date that has already passed. Note that some clients require a path to be specified - this path must exactly match the path given when creating the cookie. The path can be specified as a keyword argument. Permission -- Always available ''' def setCookie(name, value, quoted=True, **kw): ''' Set an HTTP cookie on the browser The response will include an HTTP header that sets a cookie on cookie-enabled browsers with a key "name" and value "value". This overwrites any previously set value for the cookie in the Response object. By default, the cookie value will be enclosed in double quotes. To suppress the double quotes you can pass the "quoted" argument with a False value such as False or 0. Permission -- Always available ''' def appendHeader(name, value, delimiter=","): ''' Append a value to a header. Sets an HTTP return header "name" with value "value", appending it following a comma if there was a previous value set for the header. Permission -- Always available ''' def redirect(location, lock=0): """ Cause a redirection without raising an error. If the "lock" keyword argument is passed with a true value, then the HTTP redirect response code will not be changed even if an error occurs later in request processing (after redirect() has been called). Permission -- Always available """ def write(data): """ Return data as a stream HTML data may be returned using a stream-oriented interface. This allows the browser to display partial results while computation of a response to proceed. The published object should first set any output headers or cookies on the response object. Note that published objects must not generate any errors after beginning stream-oriented output. Permission -- Always available """ zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Product.stx0000644000175000017500000000022712214017450024256 0ustar arnauarnauProduct - Zope extension. Description Products were an old mechanism by which you could extend Zope. The product concept is deprecated. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/Debug-Information_Debug.stx0000644000175000017500000000520112214017450027252 0ustar arnauarnauDebug Information - Debugging Information: Online Zope debugging Description This view provides simple debugging information to help product authors find memory leaks in their products as well as Zope itself. Debugging Information * The Zope version * The Python version * The system platform name * The filesystem path of the Zope core software, add-on software, and data files. * The ID number of the Zope process (if available on your platform) * The length of time Zope has been running * The Python module search paths (sys.path). * The top refcounts, including a table listing the changes since * the last snapshot * The open object database connections The Zope version, Python version, and system platform name are used to verify compatibility between Zope, Python, and your system. On platforms where the process ID number is available, the Zope process can be managed using a more forceful means than is available through the web. Refcounts The top refcounts list lets you take a look at what is being stored by Zope in memory. The scrollable list shows how many instances of each type of object are currently in memory. If there are items in the list with a very high refcount, there is a good chance there is a memory leak in a product or in Zope. Using the snapshot and refresh options allow you to determine which operations are causing memory leaks. Open Connections A database connection usually corresponds with an HTTP request. The left column shows the time at which the connection was opened. If there is a request that has been running for a long time you may need to restart Zope to kill the corresponding thread. The middle column usually shows the state of the REQUEST object with size of the cache for that connection in parentheses. The right column shows information about the version the user is working in. Controls 'Update Snapshot' -- Takes the current refcounts and store them in memory. Then each time the debugging page is reloaded, the table will show the difference in refcounts between the snapshot and the current state. 'Refresh' -- Reloads and updates the debugging information. 'Auto refresh interval' -- The number of seconds to wait before automatically refreshing the debugging information. 'Start auto refresh' -- Begins automatically refreshing the debugging information. 'Stop auto refresh' -- Ends automatic refreshing of the debugging information. zope2.13-2.13.21/source/Products.OFSP/src/Products/OFSP/help/dtml-sqlvar.stx0000644000175000017500000000202412214017450025101 0ustar arnauarnausqlvar: Inserts SQL variables The 'sqlvar' tag safely inserts variables into SQL code. This tag is used in SQL Methods. Syntax 'sqlvar' tag syntax:: The 'sqlvar' tag is a singleton. Like the 'var' tag, the 'sqlvar' tag looks up a variable and inserts it. Unlike the var tag, the formatting options are tailored for SQL code. Attributes type=string -- The type of the variable. Valid types include: 'string', 'int', 'float' and 'nb'. 'nb' means non-blank string and should be used in place of 'string' unless you want to use blank strings. The type attribute is required and is used to properly escape inserted variable. optional=boolean -- If true and the variable is null or non-existent, then nothing is inserted. Examples Basic usage:: select * from employees where name= This SQL quotes the 'name' string variable. See Also "sqltest tag":dtml-sqltest.stx zope2.13-2.13.21/source/Products.MIMETools/0000755000175000017500000000000012214017671017012 5ustar arnauarnauzope2.13-2.13.21/source/Products.MIMETools/setup.py0000644000175000017500000000255412214017671020532 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from setuptools import setup, find_packages setup(name='Products.MIMETools', version = '2.13.0', url='http://pypi.python.org/pypi/Products.MIMETools', license='ZPL 2.1', description="MIMETools provides the <!--#mime--> tag for " "DocumentTemplate.", author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', long_description=open('README.txt').read() + '\n' + open('CHANGES.txt').read(), packages=find_packages('src'), namespace_packages=['Products'], package_dir={'': 'src'}, install_requires=[ 'setuptools', 'DocumentTemplate', ], include_package_data=True, zip_safe=False, ) zope2.13-2.13.21/source/Products.MIMETools/PKG-INFO0000644000175000017500000001124612214017671020113 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.MIMETools Version: 2.13.0 Summary: MIMETools provides the <!--#mime--> tag for DocumentTemplate. Home-page: http://pypi.python.org/pypi/Products.MIMETools Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== Currently, the MIMETools product's only function is to provide the ```` DTML tag for the DocumentTemplate distribution. The ```` tag is used to construct MIME containers. The syntax of the ```` tag is:: Contents of first part Contents of second part Contents of nth part The area of data between tags, called a block, is encoded into whatever is specified with the 'encode' tag attribute for that block. If no encoding is specified, 'base64' is defaulted. Valid encoding options include 'base64', 'quoted-printable', 'uuencode', 'x-uuencode', 'uue' and 'x-uue'. If the 'encode' attribute is set to '7bit' no encoding is done on the block and the data is assumed to be in a valid MIME format. If the 'disposition' attribute is not specified for a certain block, then the 'Content-Disposition:' MIME header is not included in that block's MIME part. The entire MIME container, from the opening mime tag to the closing, has it's 'Content-Type:' MIME header set to 'multipart/mixed'. For example, the following DTML:: This is the first part. This is the second. Is rendered to the following text:: Content-Type: multipart/mixed; boundary="216.164.72.30.501.1550.923070182.795.22531" --216.164.72.30.501.1550.923070182.795.22531 Content-Type: text/plain Content-Transfer-Encoding: 7bit This is the first part. --216.164.72.30.501.1550.923070182.795.22531 Content-Type: text/plain Content-Transfer-Encoding: base64 VGhpcyBpcyB0aGUgc2Vjb25kLgo= --216.164.72.30.501.1550.923070182.795.22531-- The #mime tag is particularly handy in conjunction with the ``#sendmail`` tag. This allows Zope to send attachments along with email. Here is an example. Create a DTML method called 'input' with the following code::

    Send to:
    Create another DTML Method called 'send' with the following code:: From: michel@digicool.com To: Hi , someone sent you this attachment. Mail with attachment was sent. Notice that there is no blank line between the 'To:' header and the starting #mime tag. If a blank line is inserted between them then the message will not be interpreted as multipart by the receiving mail reader. Also notice that there is no newline between the #boundary tag and the #var tag, or the end of the #var tag and the closing #mime tag. This is important, if you break the tags up with newlines then they will be encoded and included in the MIME part, which is probably not what you're after. As per the MIME spec, #mime tags may be nested within #mime tags arbitrarily. Changelog ========= 2.13.0 (2010-07-10) ------------------- - PEP8 cleanup and added basic tests. - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Products.MIMETools/pip-egg-info/0000755000175000017500000000000012214017671021273 5ustar arnauarnauzope2.13-2.13.21/source/Products.MIMETools/pip-egg-info/Products.MIMETools.egg-info/0000755000175000017500000000000012214017671026337 5ustar arnauarnauzope2.13-2.13.21/source/Products.MIMETools/pip-egg-info/Products.MIMETools.egg-info/PKG-INFO0000644000175000017500000001124612214017671027440 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.MIMETools Version: 2.13.0 Summary: MIMETools provides the <!--#mime--> tag for DocumentTemplate. Home-page: http://pypi.python.org/pypi/Products.MIMETools Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== Currently, the MIMETools product's only function is to provide the ```` DTML tag for the DocumentTemplate distribution. The ```` tag is used to construct MIME containers. The syntax of the ```` tag is:: Contents of first part Contents of second part Contents of nth part The area of data between tags, called a block, is encoded into whatever is specified with the 'encode' tag attribute for that block. If no encoding is specified, 'base64' is defaulted. Valid encoding options include 'base64', 'quoted-printable', 'uuencode', 'x-uuencode', 'uue' and 'x-uue'. If the 'encode' attribute is set to '7bit' no encoding is done on the block and the data is assumed to be in a valid MIME format. If the 'disposition' attribute is not specified for a certain block, then the 'Content-Disposition:' MIME header is not included in that block's MIME part. The entire MIME container, from the opening mime tag to the closing, has it's 'Content-Type:' MIME header set to 'multipart/mixed'. For example, the following DTML:: This is the first part. This is the second. Is rendered to the following text:: Content-Type: multipart/mixed; boundary="216.164.72.30.501.1550.923070182.795.22531" --216.164.72.30.501.1550.923070182.795.22531 Content-Type: text/plain Content-Transfer-Encoding: 7bit This is the first part. --216.164.72.30.501.1550.923070182.795.22531 Content-Type: text/plain Content-Transfer-Encoding: base64 VGhpcyBpcyB0aGUgc2Vjb25kLgo= --216.164.72.30.501.1550.923070182.795.22531-- The #mime tag is particularly handy in conjunction with the ``#sendmail`` tag. This allows Zope to send attachments along with email. Here is an example. Create a DTML method called 'input' with the following code::

    Send to:
    Create another DTML Method called 'send' with the following code:: From: michel@digicool.com To: Hi , someone sent you this attachment. Mail with attachment was sent. Notice that there is no blank line between the 'To:' header and the starting #mime tag. If a blank line is inserted between them then the message will not be interpreted as multipart by the receiving mail reader. Also notice that there is no newline between the #boundary tag and the #var tag, or the end of the #var tag and the closing #mime tag. This is important, if you break the tags up with newlines then they will be encoded and included in the MIME part, which is probably not what you're after. As per the MIME spec, #mime tags may be nested within #mime tags arbitrarily. Changelog ========= 2.13.0 (2010-07-10) ------------------- - PEP8 cleanup and added basic tests. - Released as separate package. Platform: UNKNOWN ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootzope2.13-2.13.21/source/Products.MIMETools/pip-egg-info/Products.MIMETools.egg-info/dependency_links.txtzope2.13-2.13.21/source/Products.MIMETools/pip-egg-info/Products.MIMETools.egg-info/dependency_links0000644000175000017500000000000112214017671031567 0ustar arnauarnau zope2.13-2.13.21/source/Products.MIMETools/pip-egg-info/Products.MIMETools.egg-info/requires.txt0000644000175000017500000000003312214017671030733 0ustar arnauarnausetuptools DocumentTemplate././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootzope2.13-2.13.21/source/Products.MIMETools/pip-egg-info/Products.MIMETools.egg-info/namespace_packages.txtzope2.13-2.13.21/source/Products.MIMETools/pip-egg-info/Products.MIMETools.egg-info/namespace_packag0000644000175000017500000000001112214017671031514 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.MIMETools/pip-egg-info/Products.MIMETools.egg-info/top_level.txt0000644000175000017500000000001112214017671031061 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.MIMETools/pip-egg-info/Products.MIMETools.egg-info/SOURCES.txt0000644000175000017500000000103212214017671030217 0ustar arnauarnauREADME.txt setup.cfg pip-egg-info/Products.MIMETools.egg-info/PKG-INFO pip-egg-info/Products.MIMETools.egg-info/SOURCES.txt pip-egg-info/Products.MIMETools.egg-info/dependency_links.txt pip-egg-info/Products.MIMETools.egg-info/namespace_packages.txt pip-egg-info/Products.MIMETools.egg-info/not-zip-safe pip-egg-info/Products.MIMETools.egg-info/requires.txt pip-egg-info/Products.MIMETools.egg-info/top_level.txt src/Products/__init__.py src/Products/MIMETools/MIMETag.py src/Products/MIMETools/__init__.py src/Products/MIMETools/tests.pyzope2.13-2.13.21/source/Products.MIMETools/pip-egg-info/Products.MIMETools.egg-info/not-zip-safe0000644000175000017500000000000112214017671030565 0ustar arnauarnau zope2.13-2.13.21/source/Products.MIMETools/LICENSE.txt0000644000175000017500000000402612214017671020637 0ustar arnauarnauZope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 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 HOLDERS 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. zope2.13-2.13.21/source/Products.MIMETools/README.txt0000644000175000017500000000657012214017671020520 0ustar arnauarnauOverview ======== Currently, the MIMETools product's only function is to provide the ```` DTML tag for the DocumentTemplate distribution. The ```` tag is used to construct MIME containers. The syntax of the ```` tag is:: Contents of first part Contents of second part Contents of nth part The area of data between tags, called a block, is encoded into whatever is specified with the 'encode' tag attribute for that block. If no encoding is specified, 'base64' is defaulted. Valid encoding options include 'base64', 'quoted-printable', 'uuencode', 'x-uuencode', 'uue' and 'x-uue'. If the 'encode' attribute is set to '7bit' no encoding is done on the block and the data is assumed to be in a valid MIME format. If the 'disposition' attribute is not specified for a certain block, then the 'Content-Disposition:' MIME header is not included in that block's MIME part. The entire MIME container, from the opening mime tag to the closing, has it's 'Content-Type:' MIME header set to 'multipart/mixed'. For example, the following DTML:: This is the first part. This is the second. Is rendered to the following text:: Content-Type: multipart/mixed; boundary="216.164.72.30.501.1550.923070182.795.22531" --216.164.72.30.501.1550.923070182.795.22531 Content-Type: text/plain Content-Transfer-Encoding: 7bit This is the first part. --216.164.72.30.501.1550.923070182.795.22531 Content-Type: text/plain Content-Transfer-Encoding: base64 VGhpcyBpcyB0aGUgc2Vjb25kLgo= --216.164.72.30.501.1550.923070182.795.22531-- The #mime tag is particularly handy in conjunction with the ``#sendmail`` tag. This allows Zope to send attachments along with email. Here is an example. Create a DTML method called 'input' with the following code::

    Send to:
    Create another DTML Method called 'send' with the following code:: From: michel@digicool.com To: Hi , someone sent you this attachment. Mail with attachment was sent. Notice that there is no blank line between the 'To:' header and the starting #mime tag. If a blank line is inserted between them then the message will not be interpreted as multipart by the receiving mail reader. Also notice that there is no newline between the #boundary tag and the #var tag, or the end of the #var tag and the closing #mime tag. This is important, if you break the tags up with newlines then they will be encoded and included in the MIME part, which is probably not what you're after. As per the MIME spec, #mime tags may be nested within #mime tags arbitrarily. zope2.13-2.13.21/source/Products.MIMETools/setup.cfg0000644000175000017500000000007312214017671020633 0ustar arnauarnau[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope2.13-2.13.21/source/Products.MIMETools/COPYRIGHT.txt0000644000175000017500000000004012214017671021115 0ustar arnauarnauZope Foundation and Contributorszope2.13-2.13.21/source/Products.MIMETools/buildout.cfg0000644000175000017500000000030512214017671021320 0ustar arnauarnau[buildout] develop = . parts = interpreter test [interpreter] recipe = zc.recipe.egg interpreter = python eggs = Products.MIMETools [test] recipe = zc.recipe.testrunner eggs = Products.MIMETools zope2.13-2.13.21/source/Products.MIMETools/bootstrap.py0000644000175000017500000000742012214017671021404 0ustar arnauarnau############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 108946 2010-02-12 02:40:18Z yusei $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope2.13-2.13.21/source/Products.MIMETools/CHANGES.txt0000644000175000017500000000020512214017671020620 0ustar arnauarnauChangelog ========= 2.13.0 (2010-07-10) ------------------- - PEP8 cleanup and added basic tests. - Released as separate package. zope2.13-2.13.21/source/Products.MIMETools/src/0000755000175000017500000000000012214017671017601 5ustar arnauarnauzope2.13-2.13.21/source/Products.MIMETools/src/Products/0000755000175000017500000000000012214017671021404 5ustar arnauarnauzope2.13-2.13.21/source/Products.MIMETools/src/Products/__init__.py0000644000175000017500000000007012214017671023512 0ustar arnauarnau__import__('pkg_resources').declare_namespace(__name__) zope2.13-2.13.21/source/Products.MIMETools/src/Products/MIMETools/0000755000175000017500000000000012214017671023154 5ustar arnauarnauzope2.13-2.13.21/source/Products.MIMETools/src/Products/MIMETools/MIMETag.py0000644000175000017500000001677212214017671024726 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## from cStringIO import StringIO import mimetools from DocumentTemplate.DT_Util import Eval from DocumentTemplate.DT_Util import parse_params from DocumentTemplate.DT_Util import ParseError from DocumentTemplate.DT_Util import render_blocks from DocumentTemplate.DT_String import String class MIMEError(Exception): """MIME Tag Error""" ENCODINGS = ('base64', 'quoted-printable', 'uuencode', 'x-uuencode', 'uue', 'x-uue', '7bit') class MIMETag(object): '''''' name='mime' blockContinuations=('boundary', ) encode=None def __init__(self, blocks): self.sections = [] self.multipart = 'mixed' for tname, args, section in blocks: if tname == 'mime': args = parse_params(args, type=None, type_expr=None, disposition=None, disposition_expr=None, encode=None, encode_expr=None, name=None, name_expr=None, filename=None, filename_expr=None, cid=None, cid_expr=None, charset=None, charset_expr=None, skip_expr=None, multipart=None) self.multipart = args.get('multipart', 'mixed') else: args = parse_params(args, type=None, type_expr=None, disposition=None, disposition_expr=None, encode=None, encode_expr=None, name=None, name_expr=None, filename=None, filename_expr=None, cid=None, cid_expr=None, charset=None, charset_expr=None, skip_expr=None) if 'type_expr' in args: if 'type' in args: raise ParseError(_tm('type and type_expr given', 'mime')) args['type_expr'] = Eval(args['type_expr']) elif 'type' not in args: args['type']='application/octet-stream' if 'disposition_expr' in args: if 'disposition' in args: raise ParseError( _tm('disposition and disposition_expr given', 'mime')) args['disposition_expr'] = Eval(args['disposition_expr']) elif 'disposition' not in args: args['disposition'] = '' if 'encode_expr' in args: if 'encode' in args: raise ParseError( _tm('encode and encode_expr given', 'mime')) args['encode_expr'] = Eval(args['encode_expr']) elif 'encode' not in args: args['encode'] = 'base64' if 'name_expr' in args: if 'name' in args: raise ParseError(_tm('name and name_expr given', 'mime')) args['name_expr'] = Eval(args['name_expr']) elif 'name' not in args: args['name'] = '' if 'filename_expr' in args: if 'filename' in args: raise ParseError( _tm('filename and filename_expr given', 'mime')) args['filename_expr'] = Eval(args['filename_expr']) elif 'filename' not in args: args['filename'] = '' if 'cid_expr' in args: if 'cid' in args: raise ParseError(_tm('cid and cid_expr given', 'mime')) args['cid_expr'] = Eval(args['cid_expr']) elif 'cid' not in args: args['cid'] = '' if 'charset_expr' in args: if 'charset' in args: raise ParseError( _tm('charset and charset_expr given', 'mime')) args['charset_expr'] = Eval(args['charset_expr']) elif 'charset' not in args: args['charset'] = '' if 'skip_expr' in args: args['skip_expr'] = Eval(args['skip_expr']) if args['encode'] not in ENCODINGS: raise MIMEError('An unsupported encoding was specified in tag') self.sections.append((args, section.blocks)) def render(self, md): from MimeWriter import MimeWriter # deprecated since Python 2.3! IO = StringIO() IO.write("Mime-Version: 1.0\n") mw = MimeWriter(IO) outer = mw.startmultipartbody(self.multipart) last = None for x in self.sections: a, b = x if 'skip_expr' in a and a['skip_expr'].eval(md): continue inner = mw.nextpart() if 'type_expr' in a: t = a['type_expr'].eval(md) else: t = a['type'] if 'disposition_expr' in a: d = a['disposition_expr'].eval(md) else: d = a['disposition'] if 'encode_expr' in a: e = a['encode_expr'].eval(md) else: e = a['encode'] if 'name_expr' in a: n = a['name_expr'].eval(md) else: n = a['name'] if 'filename_expr' in a: f = a['filename_expr'].eval(md) else: f = a['filename'] if 'cid_expr' in a: cid = a['cid_expr'].eval(md) else: cid = a['cid'] if 'charset_expr' in a: charset = a['charset_expr'].eval(md) else: charset = a['charset'] if d: if f: inner.addheader('Content-Disposition', '%s;\n filename="%s"' % (d, f)) else: inner.addheader('Content-Disposition', d) inner.addheader('Content-Transfer-Encoding', e) if cid: inner.addheader('Content-ID', '<%s>' % cid) if n: plist = [('name', n)] else: plist = [] if t.startswith('text/'): plist.append(('charset', charset or 'us-ascii')) innerfile = inner.startbody(t, plist, 1) output = StringIO() if e == '7bit': innerfile.write(render_blocks(b, md)) else: mimetools.encode(StringIO(render_blocks(b, md)), output, e) output.seek(0) innerfile.write(output.read()) last = x # XXX what if self.sections is empty ??? does it matter that # mw.lastpart() is called right after mw.startmultipartbody() ? if last is not None and last is self.sections[-1]: mw.lastpart() outer.seek(0) return outer.read() __call__ = render String.commands['mime'] = MIMETag zope2.13-2.13.21/source/Products.MIMETools/src/Products/MIMETools/__init__.py0000644000175000017500000000116412214017671025267 0ustar arnauarnau############################################################################## # # Copyright (c) 2002 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import MIMETag zope2.13-2.13.21/source/Products.MIMETools/src/Products/MIMETools/tests.py0000644000175000017500000000331712214017671024674 0ustar arnauarnau############################################################################## # # Copyright (c) 2010 Zope Foundation and Contributors. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## import unittest class MimeTest(unittest.TestCase): def _getTargetClass(self): from Products.MIMETools import MIMETag return MIMETag.MIMETag def _makeOne(self, blocks=[]): klass = self._getTargetClass() return klass(blocks) def test_registered(self): klass = self._getTargetClass() from DocumentTemplate.DT_String import String self.failUnless('mime' in String.commands) self.failUnless(String.commands['mime'] is klass) def test_init(self): tag = self._makeOne() self.assertEquals(tag.sections, []) def test_render(self): tag = self._makeOne() result = tag.render(md={}) self.assert_("Mime-Version: 1.0" in result) self.assert_("Content-Type: multipart/mixed;" in result) def test_call(self): tag = self._makeOne() result = tag(md={}) self.assert_("Mime-Version: 1.0" in result) self.assert_("Content-Type: multipart/mixed;" in result) def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(MimeTest)) return suite zope2.13-2.13.21/source/Products.MIMETools/src/Products.MIMETools.egg-info/0000755000175000017500000000000012214017671024645 5ustar arnauarnauzope2.13-2.13.21/source/Products.MIMETools/src/Products.MIMETools.egg-info/PKG-INFO0000644000175000017500000001124612214017671025746 0ustar arnauarnauMetadata-Version: 1.0 Name: Products.MIMETools Version: 2.13.0 Summary: MIMETools provides the <!--#mime--> tag for DocumentTemplate. Home-page: http://pypi.python.org/pypi/Products.MIMETools Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: Overview ======== Currently, the MIMETools product's only function is to provide the ```` DTML tag for the DocumentTemplate distribution. The ```` tag is used to construct MIME containers. The syntax of the ```` tag is:: Contents of first part Contents of second part Contents of nth part The area of data between tags, called a block, is encoded into whatever is specified with the 'encode' tag attribute for that block. If no encoding is specified, 'base64' is defaulted. Valid encoding options include 'base64', 'quoted-printable', 'uuencode', 'x-uuencode', 'uue' and 'x-uue'. If the 'encode' attribute is set to '7bit' no encoding is done on the block and the data is assumed to be in a valid MIME format. If the 'disposition' attribute is not specified for a certain block, then the 'Content-Disposition:' MIME header is not included in that block's MIME part. The entire MIME container, from the opening mime tag to the closing, has it's 'Content-Type:' MIME header set to 'multipart/mixed'. For example, the following DTML:: This is the first part. This is the second. Is rendered to the following text:: Content-Type: multipart/mixed; boundary="216.164.72.30.501.1550.923070182.795.22531" --216.164.72.30.501.1550.923070182.795.22531 Content-Type: text/plain Content-Transfer-Encoding: 7bit This is the first part. --216.164.72.30.501.1550.923070182.795.22531 Content-Type: text/plain Content-Transfer-Encoding: base64 VGhpcyBpcyB0aGUgc2Vjb25kLgo= --216.164.72.30.501.1550.923070182.795.22531-- The #mime tag is particularly handy in conjunction with the ``#sendmail`` tag. This allows Zope to send attachments along with email. Here is an example. Create a DTML method called 'input' with the following code::

    Send to:
    Create another DTML Method called 'send' with the following code:: From: michel@digicool.com To: Hi , someone sent you this attachment. Mail with attachment was sent. Notice that there is no blank line between the 'To:' header and the starting #mime tag. If a blank line is inserted between them then the message will not be interpreted as multipart by the receiving mail reader. Also notice that there is no newline between the #boundary tag and the #var tag, or the end of the #var tag and the closing #mime tag. This is important, if you break the tags up with newlines then they will be encoded and included in the MIME part, which is probably not what you're after. As per the MIME spec, #mime tags may be nested within #mime tags arbitrarily. Changelog ========= 2.13.0 (2010-07-10) ------------------- - PEP8 cleanup and added basic tests. - Released as separate package. Platform: UNKNOWN zope2.13-2.13.21/source/Products.MIMETools/src/Products.MIMETools.egg-info/dependency_links.txt0000644000175000017500000000000112214017671030713 0ustar arnauarnau zope2.13-2.13.21/source/Products.MIMETools/src/Products.MIMETools.egg-info/requires.txt0000644000175000017500000000003312214017671027241 0ustar arnauarnausetuptools DocumentTemplatezope2.13-2.13.21/source/Products.MIMETools/src/Products.MIMETools.egg-info/namespace_packages.txt0000644000175000017500000000001112214017671031170 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.MIMETools/src/Products.MIMETools.egg-info/top_level.txt0000644000175000017500000000001112214017671027367 0ustar arnauarnauProducts zope2.13-2.13.21/source/Products.MIMETools/src/Products.MIMETools.egg-info/SOURCES.txt0000644000175000017500000000103212214017671026525 0ustar arnauarnauCHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/Products/__init__.py src/Products.MIMETools.egg-info/PKG-INFO src/Products.MIMETools.egg-info/SOURCES.txt src/Products.MIMETools.egg-info/dependency_links.txt src/Products.MIMETools.egg-info/namespace_packages.txt src/Products.MIMETools.egg-info/not-zip-safe src/Products.MIMETools.egg-info/requires.txt src/Products.MIMETools.egg-info/top_level.txt src/Products/MIMETools/MIMETag.py src/Products/MIMETools/__init__.py src/Products/MIMETools/tests.pyzope2.13-2.13.21/source/Products.MIMETools/src/Products.MIMETools.egg-info/not-zip-safe0000644000175000017500000000000112214017671027073 0ustar arnauarnau zope2.13-2.13.21/configuration/0000755000175000017500000000000012214017417015005 5ustar arnauarnauzope2.13-2.13.21/configuration/versions.cfg0000644000175000017500000000640312214017417017341 0ustar arnauarnau# Merging 2 version configuration files: # - http://download.zope.org/zopetoolkit/index/1.0.8/ztk-versions.cfg # - http://download.zope.org/Zope2/index/2.13.21/versions.cfg [versions] # Pinnings came from http://download.zope.org/zopetoolkit/index/1.0.8/ztk-versions.cfg distribute = 0.6.36 zope.dottedname = 3.4.6 zope.hookable = 3.4.1 tl.eggdeps = 0.4 zope.applicationcontrol = 3.5.5 zope.publisher = 3.12.6 zope.mkzeoinstance = 3.9.5 zc.lockfile = 1.0.2 restrictedpython = 3.6.0 zope.event = 3.5.2 zope.documenttemplate = 3.4.3 zope.pluggableauth = 1.0.3 zope.tal = 3.5.2 zope.dublincore = 3.7.0 jinja2 = 2.5.5 zope.viewlet = 3.7.2 zdaemon = 2.0.7 zope.location = 3.9.1 zope.deferredimport = 3.5.3 zope.configuration = 3.7.4 zope.componentvocabulary = 1.0.1 nose = 1.1.2 zope.i18n = 3.7.4 zope.traversing = 3.13.2 zope.contenttype = 3.5.5 zope.container = 3.11.2 zope.lifecycleevent = 3.6.2 zope.ramcache = 1.0 zope.i18nmessageid = 3.5.3 pastedeploy = 1.3.4 z3c.checkversions = 0.4.1 docutils = 0.7 zope.login = 1.0.0 zope.cachedescriptors = 3.5.1 zope.intid = 3.7.2 zope.exceptions = 3.6.2 zope.minmax = 1.1.2 zope.server = 3.6.3 paste = 1.7.5.1 zope.proxy = 3.6.1 zope.pagetemplate = 3.5.2 zope.ptresource = 3.9.0 zope.security = 3.7.4 zc.buildout = 1.7.1 zope.browserpage = 3.12.2 z3c.recipe.compattest = 0.12.2 zope.testing = 3.9.7 zope.authentication = 3.7.1 zope.deprecation = 3.4.1 zope.session = 3.9.5 zope.securitypolicy = 3.7.0 z3c.recipe.sphinxdoc = 0.0.8 pygments = 1.3.1 zope.sendmail = 3.7.5 z3c.recipe.depgraph = 0.5 zope.annotation = 3.5.0 zope.processlifetime = 1.0 py = 1.3.4 zope.password = 3.6.1 zope.principalregistry = 3.7.1 zope.browser = 1.3 zope.structuredtext = 3.5.1 zope.mimetype = 1.3.1 zope.browsermenu = 3.9.1 zope.size = 3.4.1 mr.developer = 1.25 pytz = 2013b clientform = 0.2.10 zc.recipe.testrunner = 1.2.1 zope.datetime = 3.4.1 zope.broken = 3.6.0 zope.component = 3.9.5 zope.copy = 3.5.0 zope.browserresource = 3.10.3 zope.formlib = 4.0.6 setuptools = 0.6c11 zope.filerepresentation = 3.6.1 zope.interface = 3.6.7 zc.resourcelibrary = 1.3.4 zope.kgs = 1.2.0 zope.principalannotation = 3.6.1 zope.sequencesort = 3.4.0 zope.catalog = 3.8.2 unittest2 = 0.5.1 zope.contentprovider = 3.7.2 zope.tales = 3.5.3 lxml = 2.2.8 coverage = 3.5.2 zope.schema = 3.7.1 zope.index = 3.6.4 zope.keyreference = 3.6.4 argparse = 1.1 transaction = 1.1.1 sphinx = 1.0.8 zope.site = 3.9.2 zope.copypastemove = 3.7.0 zope.error = 3.7.4 zc.recipe.egg = 1.3.2 pastescript = 1.7.5 # Pinnings came from http://download.zope.org/Zope2/index/2.13.21/versions.cfg zope2 = 2.13.21 repoze.tm2 = 1.0 zconfig = 2.9.1 products.zcatalog = 2.13.23 zlog = 2.11.1 mechanize = 0.2.5 repoze.who = 2.0 python-gettext = 1.2 products.externalmethod = 2.13.0 accesscontrol = 2.13.13 products.mimetools = 2.13.0 products.btreefolder2 = 2.13.4 products.mailhost = 2.13.1 zope.testbrowser = 3.11.1 zexceptions = 2.13.0 persistence = 2.13.2 repoze.retry = 1.2 products.pythonscripts = 2.13.2 missing = 2.13.1 products.ofsp = 2.13.2 multimapping = 2.13.0 zopeundo = 2.12.0 nt-svcutils = 2.13.0 datetime = 2.12.7 products.zctextindex = 2.13.4 extensionclass = 2.13.2 initgroups = 2.13.0 products.standardcachemanagers = 2.13.0 record = 2.13.0 zodb3 = 3.10.5 documenttemplate = 2.13.2 manuel = 1.1.1 tempstorage = 2.12.2 acquisition = 2.13.8